[英]x86_64 incorrect calling convention when calling function
I'm relatively new to LLVM, and I'm attempting to generate LLVM IR that calls a C function ( growDictionary
).我对 LLVM 比较陌生,我正在尝试生成调用 C 函数(
growDictionary
)的 LLVM IR。 This is on x86_64 Linux, using llvm 12:这是在 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
The function (defined in C++ as extern "C"
, compiled with clang 12):函数(在 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);
}
The function takes the StringDictionary object by value, but, according to the x86_64 ABI ( https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf , section 3.2.3, "Parameter Passing") should have it passed on the stack.该函数按值获取 StringDictionary 对象,但是,根据 x86_64 ABI ( https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf ,第 3.2.3 节, “参数传递”)应该让它在堆栈上传递。 (The object's size is greater than 2 eightbytes and neither of the eightbytes is in class SSE or SSEUP, so it turns into class MEMORY according to the "post merger cleanup" section.) A cursory look at the disassembly confirms that this is indeed the case:
(对象的大小大于 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
is the address where the return value will be written, %esi
is the uint32_t neededWordsCapacity argument, no other argument passing registers are used. %rdi
是返回值将被写入的地址, %esi
是 uint32_t neededWordsCapacity 参数,没有使用其他参数传递寄存器。
This is all fine so far, but I'm now trying to call this function from my generated IR, and it tries to pass all arguments in registers.到目前为止一切都很好,但我现在试图从我生成的 IR 中调用这个函数,它试图在寄存器中传递所有参数。 Here are the relevant sections of code:
以下是代码的相关部分:
%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)
Note that the calling convention is default (not changed to something like fastcc).请注意,调用约定是默认的(未更改为类似 fastcc 的约定)。
The generated code (both the JIT I'm trying to use and llc produce the same result) os trying to pass the argument in registers, here's the output from llc -O0
;生成的代码(我尝试使用的 JIT 和 llc 产生相同的结果)尝试在寄存器中传递参数,这是
llc -O0
的输出; -O3
is similar: -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
Unsurprisingly, my code segfaults.不出所料,我的代码有段错误。
I'm surprised that llc generated code that doesn't match the ABI.我很惊讶 llc 生成的代码与 ABI 不匹配。 Are there any attributes I need to put on the function declaration, or on the type definition, or is there anything else that I'm missing?
我是否需要在函数声明或类型定义上添加任何属性,或者还有什么我遗漏的吗?
It turns out that this part of the calling convention is handled by the frontend (together with, I presume, things like "this is a non-trivial C++ object").事实证明,调用约定的这一部分由前端处理(连同,我认为,诸如“这是一个非平凡的 C++ 对象”之类的事情)。
Take this example file:以这个示例文件为例:
#include <stdint.h>
struct A {
uint32_t* p;
uint32_t a;
uint32_t b;
};
struct B {
uint32_t* p;
uint32_t a;
uint32_t b;
uint32_t c;
};
uint32_t addA(struct A x) {
return x.a + x.b;
}
uint32_t addB(struct B x) {
return x.a + x.b + x.c;
}
clang -S -emit-llvm
says: clang -S -emit-llvm
说:
%struct.A = type { i32*, i32, i32 }
%struct.B = type { i32*, i32, i32, i32 }
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @addA(i32* %0, i64 %1) #0 {
%3 = alloca %struct.A, align 8
%4 = bitcast %struct.A* %3 to { i32*, i64 }*
%5 = getelementptr inbounds { i32*, i64 }, { i32*, i64 }* %4, i32 0, i32 0
store i32* %0, i32** %5, align 8
%6 = getelementptr inbounds { i32*, i64 }, { i32*, i64 }* %4, i32 0, i32 1
store i64 %1, i64* %6, align 8
%7 = getelementptr inbounds %struct.A, %struct.A* %3, i32 0, i32 1
%8 = load i32, i32* %7, align 8
%9 = getelementptr inbounds %struct.A, %struct.A* %3, i32 0, i32 2
%10 = load i32, i32* %9, align 4
%11 = add i32 %8, %10
ret i32 %11
}
; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @addB(%struct.B* byval(%struct.B) align 8 %0) #0 {
%2 = getelementptr inbounds %struct.B, %struct.B* %0, i32 0, i32 1
%3 = load i32, i32* %2, align 8
%4 = getelementptr inbounds %struct.B, %struct.B* %0, i32 0, i32 2
%5 = load i32, i32* %4, align 4
%6 = add i32 %3, %5
%7 = getelementptr inbounds %struct.B, %struct.B* %0, i32 0, i32 3
%8 = load i32, i32* %7, align 8
%9 = add i32 %6, %8
ret i32 %9
}
Note that the argument to addB
has become %struct.B* byval(%struct.B)
indicating that this is passed on the stack.请注意,参数
addB
已成%struct.B* byval(%struct.B)
表示,这是在栈上传递。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.