x86_64 调用函数时调用约定不正确
x86_64 incorrect calling convention when calling function
我对 LLVM 比较陌生,我正在尝试生成调用 C 函数的 LLVM IR (growDictionary
)。这是在 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个8bytes,而这8bytes都不在classSSE或SSEUP中,所以根据post合并清理部分变成了classMEMORY .) 粗略看了下反汇编,确实是这样:
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 [=13=]xffffffffffffffe0,%rsp
0x00007ffff7f98f79 <+9>: sub [=13=]x1c0,%rsp
0x00007ffff7f98f80 <+16>: mov %rsp,%rbx
0x00007ffff7f98f83 <+19>: mov %esi,0x15c(%rbx)
0x00007ffff7f98f89 <+25>: mov %rdi,0x160(%rbx)
[...]
%rdi
是写入 return 值的地址,%esi
是 uint32_t neededWordsCapacity 参数,没有使用其他参数传递寄存器。
到目前为止一切正常,但我现在正尝试从我生成的 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 都产生相同的结果)os 试图在寄存器中传递参数,这是 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 不匹配。是否有任何属性需要添加到函数声明或类型定义中,或者是否还有其他任何我遗漏的内容?
事实证明,这部分调用约定是由前端处理的(连同,我想,诸如“这是一个非平凡的 C++ 对象”之类的东西)。
以这个示例文件为例:
#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
说:
%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
}
请注意,addB
的参数已变为 %struct.B* byval(%struct.B)
,表明这是在堆栈上传递的。
我对 LLVM 比较陌生,我正在尝试生成调用 C 函数的 LLVM IR (growDictionary
)。这是在 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个8bytes,而这8bytes都不在classSSE或SSEUP中,所以根据post合并清理部分变成了classMEMORY .) 粗略看了下反汇编,确实是这样:
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 [=13=]xffffffffffffffe0,%rsp
0x00007ffff7f98f79 <+9>: sub [=13=]x1c0,%rsp
0x00007ffff7f98f80 <+16>: mov %rsp,%rbx
0x00007ffff7f98f83 <+19>: mov %esi,0x15c(%rbx)
0x00007ffff7f98f89 <+25>: mov %rdi,0x160(%rbx)
[...]
%rdi
是写入 return 值的地址,%esi
是 uint32_t neededWordsCapacity 参数,没有使用其他参数传递寄存器。
到目前为止一切正常,但我现在正尝试从我生成的 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 都产生相同的结果)os 试图在寄存器中传递参数,这是 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 不匹配。是否有任何属性需要添加到函数声明或类型定义中,或者是否还有其他任何我遗漏的内容?
事实证明,这部分调用约定是由前端处理的(连同,我想,诸如“这是一个非平凡的 C++ 对象”之类的东西)。
以这个示例文件为例:
#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
说:
%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
}
请注意,addB
的参数已变为 %struct.B* byval(%struct.B)
,表明这是在堆栈上传递的。