假设我有以下简单的 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)"}
我的第一直觉是求助于谷歌,我遇到了两个相关的问题:邮件列表中的一个 http://lists.cs.uiuc.edu/pipermail/llvmdev/2009-December/027979.html, and 来自 StackOverflow 的一份 https://stackoverflow.com/q/14032496/1311454。两者都建议使用-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
and -disable-llvm-optzns
),但这对输出没有影响。使用以下变体did产生所需的 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.) 提前了解事物的类型。这让我...
Why?
基本上,我正在构建一种使用 LLVM 生成代码的语言的实现。实现应该通过仅指定 C 头文件和关联的库(无手动声明)来支持 C 互操作,然后编译器将在链接时使用它们来确保函数调用与其签名匹配。因此,我将问题缩小为两种可能的解决方案:
- 将头文件转为LLVM IR/bitcode,即可得到各个函数的类型签名
- Use
libclang
解析标头,然后从生成的 AST 中查询类型(我的“最后手段”,以防这个问题没有足够的答案)
TL;DR
我需要取一个C头文件(如上面的foo1.h
)并且在不更改它的情况下,使用 Clang 生成上述预期的 LLVM IR,或者找到另一种从 C 头文件获取函数签名的方法(最好使用libclang
或构建一个 C 解析器)