Kyl*_*acy 22 c c++ llvm clang llvm-ir
假设我有以下简单的C头文件:
// foo1.h
typedef int foo;
typedef struct {
foo a;
char const* b;
} bar;
bar baz(foo*, bar*, ...);
Run Code Online (Sandbox Code Playgroud)
我的目标是获取此文件,并生成一个如下所示的LLVM模块:
%struct.bar = type { i32, i8* }
declare { i32, i8* } @baz(i32*, %struct.bar*, ...)
Run Code Online (Sandbox Code Playgroud)
换句话说,将.h带有声明的C 文件转换为等效的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)"}
Run Code Online (Sandbox Code Playgroud)
我的第一直觉是转向谷歌,我遇到了两个相关的问题:一个来自邮件列表,另一个来自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)"}
Run Code Online (Sandbox Code Playgroud)
结果相同.
我也尝试过禁用优化(使用-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);
}
Run Code Online (Sandbox Code Playgroud)
然后运行:
$ 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)"}
Run Code Online (Sandbox Code Playgroud)
除了占位符doThings,这正是我想要输出的样子!问题是这需要1.)使用标题的修改版本,以及2.)事先知道事物的类型.这导致我......
基本上,我正在使用LLVM构建一种语言实现来生成代码.该实现应该通过仅指定C头文件和关联的lib(无手动声明)来支持C interop,然后编译器将在链接时使用它以确保函数调用与其签名匹配.因此,我将问题缩小到2种可能的解决方案:
libclang解析头,然后查询从得到的AST类型(我的"最后一招"的情况下,没有足够的答案这个问题)我需要获取一个C头文件(如上所述foo1.h),并且在不更改它的情况下,使用Clang,OR生成上述预期的LLVM IR,找到从C头文件获取函数签名的另一种方法(最好使用libclang或构建C解析器) )
也许是不太优雅的解决方案,但保留了doThings强制编译器发出 IR 的函数的想法,因为使用了定义:
您发现这种方法的两个问题是,它需要修改标头,并且需要更深入地了解所涉及的类型,以便生成要放入函数中的“用途”。这两个问题都可以相对简单地克服:
它不是直接编译标头,而是#include从包含所有“使用”代码的 .c 文件(或更可能是其预处理版本或多个标头)编译标头。足够简单:
// foo.c
#include "foo.h"
void doThings(void) {
...
}
Run Code Online (Sandbox Code Playgroud)您不需要详细的类型信息来生成名称的特定用法、将结构实例化与参数相匹配以及上面“使用”代码中的所有复杂性。您实际上不需要自己收集函数签名。
您所需要的只是名称本身的列表,并跟踪它们是用于函数还是对象类型。然后,您可以重新定义“uses”函数,如下所示:
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 },
};
}
Run Code Online (Sandbox Code Playgroud)
这极大地简化了名称的必要“使用”,要么将其转换为统一的函数类型(并获取其指针而不是调用它),要么将其包装在&(and中){0}(无论它是什么,都将其实例化)。这意味着您根本不需要存储实际的类型信息,只需存储您在标头中提取名称的上下文类型。
(显然,为虚拟函数和占位符类型提供扩展的唯一名称,这样它们就不会与您实际想要保留的代码发生冲突)
这极大地简化了解析步骤,因为您只需要识别结构/联合或函数声明的上下文,而实际上不需要对周围的信息进行太多处理。
一个简单但黑客的起点(我可能会使用它,因为我的标准很低:D)可能是:
#include采用尖括号参数的指令(即您不想为其生成声明的已安装标头)。clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h或类似的东西)struct或union后跟一个名称,}后跟一个名称或name (,并使用这个极其简化的非解析来构建虚拟函数中的使用列表,并发出 .c 文件的代码。它不会捕捉到所有的可能性;但通过一些调整和扩展,它实际上可能会处理实际标头代码的很大一个子集。您可以在稍后阶段用专用的简化解析器(仅用于查看所需上下文模式的解析器)替换它。