x86_64 Linux下LLVM 12调用C函数出现调用约定不匹配问题
问题根源
你遇到的是LLVM对匿名结构体和命名结构体的ABI处理规则不同,导致的参数传递不匹配问题。
你在IR中使用匿名结构体{ i32*, i32, i32, i32 }对应C语言的StringDictionary结构体时,LLVM会默认将该匿名结构体拆分为独立的标量值,尝试用寄存器传递,不会将其识别为需要遵循SysV ABI规则的整体MEMORY类参数。同时你定义的函数直接返回该结构体,也不符合x86_64下超过16字节结构体的返回规则:C ABI中这类大结构体返回时,由调用方分配返回值内存,将内存指针作为隐式第一个参数传递给被调用方,而非直接通过寄存器返回结构体。
解决步骤
- 首先在IR中显式定义命名结构体类型,不要使用匿名结构体:
%StringDictionary = type { i32*, i32, i32, i32 }
- 修正函数声明,添加
sret和byval属性标记返回值和传值参数:
declare void @growStringDictionary(%StringDictionary* sret(%StringDictionary), %StringDictionary* byval(%StringDictionary), i32)
参数说明:
- 第一个参数标记
sret,表示这是返回值的写入地址,对应ABI中隐式的返回值指针 - 第二个参数标记
byval,表示这是按值传递的结构体,LLVM会自动按照ABI规则将其放在栈上传递 - 第三个参数为原函数的
neededWordsCapacity入参
- 修正调用逻辑:
; 分配返回值和入参结构体的栈内存 %ret_dict = alloca %StringDictionary %arg_dict = alloca %StringDictionary ; 将你之前构造的结构体值%70写入入参内存 %arg_base = getelementptr inbounds %StringDictionary, %StringDictionary* %arg_dict, i32 0, i32 0 store i32* %70.0, i32** %arg_base %arg_elementSize = getelementptr inbounds %StringDictionary, %StringDictionary* %arg_dict, i32 0, i32 1 store i32 %70.1, i32* %arg_elementSize %arg_rowCount = getelementptr inbounds %StringDictionary, %StringDictionary* %arg_dict, i32 0, i32 2 store i32 %70.2, i32* %arg_rowCount %arg_wordsCapacity = getelementptr inbounds %StringDictionary, %StringDictionary* %arg_dict, i32 0, i32 3 store i32 %70.3, i32* %arg_wordsCapacity ; 调用函数 call void @growStringDictionary(%StringDictionary* sret(%StringDictionary) %ret_dict, %StringDictionary* byval(%StringDictionary) %arg_dict, i32 %73) ; 后续使用返回值直接从%ret_dict中读取对应字段即可
如果不确定正确的IR写法,可以将C代码用clang -S -emit-llvm -O0编译生成参考IR,对照clang的输出编写就能避免ABI不匹配问题。
内容的提问来源于stack exchange,提问作者Tudor Bosman
相关产品推荐
相关产品推荐

