假设我有以下简单的C语言头文件:
// foo1.h
typedef int foo;
typedef struct {
foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
我的目标是获取这个文件,并生成类似于以下内容的LLVM模块:
%struct.bar = type { i32, i8* }
declare { i32, i8* } @baz(i32*, %struct.bar*, ...)
换句话说,将带有声明的C .h
文件转换成相应的LLVM IR,包括类型解析、宏展开等。
将其通过Clang传递以生成LLVM IR会产生一个空模块(因为实际上没有使用任何定义):
$ clang -cc1 -S -emit-llvm foo1.h -o -
; ModuleID = 'foo1.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
我的第一反应是去谷歌搜索,我找到了两个相关的问题:一个来自邮件列表,和一个来自StackOverflow。两者都建议使用-femit-all-decls
标志,因此我尝试了一下:
$ clang -cc1 -femit-all-decls -S -emit-llvm foo1.h -o -
; ModuleID = 'foo1.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
同样的结果。
我也尝试过禁用优化(使用-O0
和-disable-llvm-optzns
都尝试了),但对输出没有影响。使用以下变化确实产生了所需的IR:
// foo2.h
typedef int foo;
typedef struct {
foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
void doThings() {
foo a = 0;
bar myBar;
baz(&a, &myBar);
}
然后运行:
$ clang -cc1 -S -emit-llvm foo2.h -o -
; ModuleID = 'foo2.h'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-darwin13.3.0"
%struct.bar = type { i32, i8* }
; Function Attrs: nounwind
define void @doThings() #0 {
entry:
%a = alloca i32, align 4
%myBar = alloca %struct.bar, align 8
%coerce = alloca %struct.bar, align 8
store i32 0, i32* %a, align 4
%call = call { i32, i8* } (i32*, %struct.bar*, ...)* @baz(i32* %a, %struct.bar* %myBar)
%0 = bitcast %struct.bar* %coerce to { i32, i8* }*
%1 = getelementptr { i32, i8* }* %0, i32 0, i32 0
%2 = extractvalue { i32, i8* } %call, 0
store i32 %2, i32* %1, align 1
%3 = getelementptr { i32, i8* }* %0, i32 0, i32 1
%4 = extractvalue { i32, i8* } %call, 1
store i8* %4, i8** %3, align 1
ret void
}
declare { i32, i8* } @baz(i32*, %struct.bar*, ...) #1
attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
attributes #1 = { "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-realign-stack" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }
!llvm.ident = !{!0}
!0 = metadata !{metadata !"clang version 3.5 (trunk 200156) (llvm/trunk 200155)"}
除了占位符doThings
之外,这正是我想要的输出!问题在于这需要1)使用修改后的标头版本,并且2)事先知道事物的类型。这导致...
为什么?
基本上,我正在使用LLVM构建一种语言的实现来生成代码。该实现应通过指定C标头文件和相关库(无需手动声明),支持C交互,在链接时间之前由编译器使用以确保函数调用与其签名匹配。因此,我将问题缩小到了两种可能的解决方案:
- 将标头文件转换为LLVM IR / bitcode,然后可以获得每个函数的类型签名
- 使用
libclang
解析标头,然后从生成的AST中查询类型(如果对此问题没有足够的答案,则为“最后的手段”)
TL;DR
我需要接受C标头文件(例如上面的foo1.h
),并在不更改它的情况下使用Clang生成上述预期的LLVM IR,或者找到另一种从C标头文件获取函数签名的方法(最好使用libclang
或构建一个C解析器)。