我对 LLVM 比较陌生,我正在尝试生成调用 C 函数 ( growDictionary
) 的 LLVM IR。这是在 x86_64 Linux 上,使用 llvm 12:
$ llc-12 --version
Ubuntu LLVM version 12.0.1
Optimized build.
Default target: x86_64-pc-linux-gnu
Host CPU: broadwell
该函数(在 C++ 中定义为extern "C"
,用 clang 12 编译):
struct StringDictionary {
uint32_t* base;
uint32_t elementSize;
uint32_t rowCount;
uint32_t wordsCapacity;
};
extern "C" {
StringDictionary growStringDictionary(StringDictionary dict,
uint32_t neededWordsCapacity);
}
该函数按值获取 StringDictionary 对象,但是根据 x86_64 ABI(https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf,第 3.2.3 节, “参数传递”)应该让它在堆栈上传递。(对象的大小大于 2 个八字节,并且八字节都不属于 SSE 或 SSEUP 类,因此根据“合并后清理”部分它变成了 MEMORY 类。)粗略查看反汇编确认这确实是案子:
Dump of assembler code for function growStringDictionary(rockset::jit::StringDictionary, uint32_t):
0x00007ffff7f98f70 <+0>: push %rbp
0x00007ffff7f98f71 <+1>: mov %rsp,%rbp
0x00007ffff7f98f74 <+4>: push %rbx
0x00007ffff7f98f75 <+5>: and $0xffffffffffffffe0,%rsp
0x00007ffff7f98f79 <+9>: sub $0x1c0,%rsp
0x00007ffff7f98f80 <+16>: mov %rsp,%rbx
0x00007ffff7f98f83 <+19>: mov %esi,0x15c(%rbx)
0x00007ffff7f98f89 <+25>: mov %rdi,0x160(%rbx)
[...]
%rdi
是将写入返回值的地址,%esi
是 uint32_t 需要的WordsCapacity 参数,不使用其他参数传递寄存器。
到目前为止一切都很好,但我现在正试图从我生成的 IR 中调用这个函数,它试图在寄存器中传递所有参数。以下是相关的代码部分:
%83 = call { i32*, i32, i32, i32 } @growStringDictionary({ i32*, i32, i32, i32 } %70, i32 %73)
[...]
declare { i32*, i32, i32, i32 } @growStringDictionary({ i32*, i32, i32, i32 }, i32)
请注意,调用约定是默认的(未更改为 fastcc 之类的)。
生成的代码(我尝试使用的 JIT 和 llc 产生相同的结果)操作系统试图在寄存器中传递参数,这是来自的输出llc -O0
;-O3
很相似:
movl 148(%rsp), %r9d # 4-byte Reload
movl 140(%rsp), %r8d # 4-byte Reload
movl 136(%rsp), %ecx # 4-byte Reload
movl 132(%rsp), %edx # 4-byte Reload
movq 120(%rsp), %rsi # 8-byte Reload
leaq 376(%rsp), %rdi
callq growStringDictionary@PLT
不出所料,我的代码段错误。
我很惊讶 llc 生成的代码与 ABI 不匹配。我需要在函数声明或类型定义上添加任何属性,还是我还缺少其他任何属性?