按值传递结构,同时符合 LLVM IR 中的 C 调用约定

Passing structs by-value while conforming to the C calling convention in LLVM IR

我想在 C++ 和 JIT 的 LLVM 程序之间按值传递结构。我已经看到很多关于这个的讨论,甚至还有一些关于 SO 的问题。我读过,如果我想让我的程序真正按值传递,我需要做一些叫做 "argument coercion" 的事情。使用 byvalsret 看起来像是简单的跨平台解决方案。这仍然有点痛苦,C++ 代码必须记住传递指针而不是值(尽管调用代码是 C++,所以我可以做一些模板魔术)。

我对这个问题了解得越多,我就越不明白。调用约定是一个特定于平台的问题,应该由代码生成器处理,对吧?我不明白为什么特定于平台的代码生成器不只是处理特定于平台的结构处理方式(同时符合平台的 C ABI)。前端应该与平台无关!

有没有为我做参数强制的通行证?访问每个函数声明和每个函数调用并转换所有结构以便它们与平台的 C ABI 兼容的传递?我觉得如果它存在并且 Clang 不使用它,那么所有前端都会使用它,所以也许这是不可能的。为什么这不是一个可行的解决方案?如果 pass 可以解决这个问题,那么我希望它成为 LLVM 的一部分。

我不明白为什么每个前端都必须进行参数强制转换。我什至不明白如何进行参数强制。我见过一些人使用 Clang 代码生成代码并分解出执行参数强制的部分的例子。不幸的是,如果我想要真正的 C ABI 兼容性,这似乎是最好的解决方案。甚至可以为完全不同的语言重用另一个前端的一部分这一事实让我继续想知道为什么这必须在前端完成?

必须为此做点什么!我们不能一直在每个前端编写相同的 C ABI 兼容性代码。太荒谬了!可能我根本就不懂。

有人可以帮我解决这个问题吗?我正在考虑使用 byvalsret 只是因为它比修改 clang 代码生成器更容易。有没有更简单的方法?

在 LLVM IR 中按值传递结构时,您必须制定自己的规则。我选择了最简单的规则集。

假设我有这样一个程序:

struct MyStruct {
  int a;
  char b, c, d, e;
};

MyStruct identityImpl(MyStruct s) {
  return s;
}

MyStruct identity(MyStruct s) {
  return identityImpl(s);
}

此程序的 LLVM IR 等同于:

void identityImpl(MyStruct *ret, const MyStruct *s) {
  MyStruct localS = *s;
  *ret = localS;
}

void identity(MyStruct *ret, const MyStruct *s) {
  MyStruct localS = *s;
  MyStruct localRet;
  identityImpl(&localRet, &localS);
  *ret = localRet;
}

这不是传递结构的最有效方式,因为 MyStruct 可以放入 64 位寄存器中。但是,优化器可以删除 localS 并直接使用 s 如果它可以证明 localS 从未被写入。这两个函数都优化为对 memcpy.

的一次调用

这只花了半天时间。走 Clang 路线可能至少需要一周时间。我仍然认为我不得不这样做是相当不幸的,但我现在明白了这个问题。平台的 C ABI 未指定结构的传递。