假设我有以下琐碎的 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)"}
我的第一 react 是求助于谷歌,我遇到了两个相关的问题:one from a mailing list , 和 one from 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.) 使用修改后的 header 版本,以及 2.) 提前了解事物的类型。这导致我...
为什么?
基本上,我正在为使用 LLVM 生成代码的语言构建一个实现。实现应该通过仅指定 C 头文件和关联的库(无手动声明)来支持 C 互操作,然后编译器将在链接时间之前使用它们来确保函数调用与其签名匹配。因此,我将问题缩小到 2 种可能的解决方案:
- 将头文件转成LLVM IR/bitcode,然后可以得到每个函数的类型签名
- 使用
libclang
解析 header ,然后从生成的 AST 中查询类型(我的“最后手段”,以防这个问题没有足够的答案)
TL;DR
我需要获取一个 C 头文件(例如上面的 foo1.h
),并且在不更改它的情况下,使用 Clang 生成上述预期的 LLVM IR,或者,找到另一种方法从 C 头文件中获取函数签名(最好使用 libclang
或构建 C 解析器)
最佳答案
也许是不太优雅的解决方案,但坚持使用 doThings
函数的想法,该函数会强制编译器发出 IR,因为使用了定义:
您使用这种方法发现的两个问题是它需要修改 header ,以及它需要更深入地了解所涉及的类型才能生成“使用”以放入函数中。这两个都可以相对简单地克服:
不是直接编译头文件,而是从包含所有“用途”的 .c 文件中
#include
它(或者更可能是它的预处理版本或多个头文件)代码。很简单:// foo.c #include "foo.h" void doThings(void) { ... }
您不需要详细的类型信息来生成名称的特定用法,将结构实例化与参数匹配,以及上面“使用”代码中的所有复杂性。 您实际上不需要自己收集函数签名。
您所需要的只是名称本身的列表,并跟踪它们是用于函数还是用于对象类型。然后,您可以重新定义您的“使用”函数,如下所示:
void * doThings(void) { typedef void * (*vfun)(void); typedef union v { void * o; vfun f; } v; return (v[]) { (v){ .o = &(bar){0} }, (v){ .f = (vfun)baz }, }; }
这大大简化了名称的必要“使用”,将其转换为统一的函数类型(并获取其指针而不是调用它),或将其包装在
&(
和){0}
(实例化它不管它是什么)。这意味着您根本不需要存储实际的类型信息,只需要存储您在标题中提取名称的那种 context。(显然给虚拟函数和占位符类型扩展唯一名称,这样它们就不会与您实际想要保留的代码冲突)
这极大地简化了解析步骤,因为您只需要识别结构/union 或函数声明的上下文,而实际上不需要对周围信息做很多事情。
一个简单但粗俗的起点(我可能会使用它,因为我的标准低 :D )可能是:
- grep 查找带有尖括号参数的
#include
指令的 header (即您不想同时为其生成声明的已安装 header )。 - 使用此列表创建一个虚拟包含文件夹,其中包含所有必要的包含文件,但为空
- 对其进行预处理,希望能简化语法(
clang -E -I local-dummy-includes/-D"__attribute__(...)="foo.h > temp/foo_pp.h
或类似的东西) - grep through for
struct
或union
后跟名称,}
后跟名称,或name (
,并使用这种极其简化的非解析来构建虚拟函数中的使用列表,并发出 .c 文件的代码。
它不会捕获所有的可能性;但是通过一些调整和扩展,它实际上可能会处理大量实际的 header 代码。您可以在稍后阶段将其替换为专用的简化解析器(仅用于查看所需上下文模式的解析器)。
关于c++ - Clang - 将 C header 编译为 LLVM IR/位码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24728901/