使用Clang将C头文件编译为LLVM IR/bitcode

24

假设我有以下简单的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交互,在链接时间之前由编译器使用以确保函数调用与其签名匹配。因此,我将问题缩小到了两种可能的解决方案:

  1. 将标头文件转换为LLVM IR / bitcode,然后可以获得每个函数的类型签名
  2. 使用libclang解析标头,然后从生成的AST中查询类型(如果对此问题没有足够的答案,则为“最后的手段”)

TL;DR

我需要接受C标头文件(例如上面的foo1.h),并在不更改它的情况下使用Clang生成上述预期的LLVM IR,或者找到另一种从C标头文件获取函数签名的方法(最好使用libclang或构建一个C解析器)。


2
如果我理解正确的话,它不会发出 IR ,除非你真正使用了声明的内容。所以,你可以改变 Clang 的行为来发出 IR ,即使没有使用声明。这可能比自己处理 AST 更容易。最有可能的是,你需要在代码中翻转一个布尔值,至于在哪里,我不知道。 - shrm
2
@mishr 是的,我确实相信你是对的,修改Clang的源代码并简单地切换开关会更容易,而不是使用libclang的AST。然而,长期来看,依赖于这样一个大型项目的自定义分支肯定不太实际(因为每个后续版本的Clang都需要打补丁)。更不用说,这个问题是针对一个开源项目的一部分,所以要求其他人安装特定的Clang分支版本似乎是不公平的。 - Kyle Lacy
似乎你并不孤单:http://stackoverflow.com/questions/14032496/how-can-i-code-generate-unused-declarations-with-clang - Matthias
3
发现这个网址:http://clang-developers.42468.n3.nabble.com/On-preserving-unused-file-local-definitions-at-O0-td4038825.html。要点:在clang开发社区中,讨论是否应该在-O0级别中丢弃未使用的项。 -femit_all_decls命名不当,控制定义的输出而非声明。我建议提交一个修复该行为的补丁,以符合您的需求。 - Matthias
1
你最终解决了吗?我也在编写自己的语言,旨在与现有代码互操作,因此我需要一些方法来引用现有文件中的结构体/函数。我考虑过让我的构建系统编译所有内容(我的语言+其他语言)到LLVM IR,并使用“llvm-link”将它们全部链接在一起,但我还不知道那是否可行。我真的不想进行任何额外的解析,下面的答案就是非常恶心:( - Thomas
显示剩余3条评论
1个回答

5
也许这不是最优雅的解决方案,但是我们可以继续使用doThings函数的想法,因为定义被使用所以强制编译器生成IR代码:
你提出了这种方法存在两个问题:需要修改头文件和需要更深入地理解涉及的类型才能生成放入函数中的“用法”。但这两个问题都可以相对简单地解决:
  1. Instead of compiling the header directly, #include it (or more likely, a preprocessed version of it, or multiple headers) from a .c file that contains all the "uses" code. Straightforward enough:

    // foo.c
    #include "foo.h"
    void doThings(void) {
        ...
    }
    
  2. You don't need detailed type information to generate specific usages of the names, matching up struct instantiations to parameters and all that complexity as you have in the "uses" code above. You don't actually need to gather the function signatures yourself.

    All you need is the list of the names themselves and to keep track of whether they're for a function or for an object type. You can then redefine your "uses" function to look like this:

    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 },
        };
    }
    

    This greatly simplifies the necessary "uses" of a name to either casting it to a uniform function type (and taking its pointer rather than calling it), or wrapping it in &( and ){0} (instantiating it regardless of what it is). This means you don't need to store actual type information at all, only the kind of context from which you extracted the name in the header.

    (obviously give the dummy function and the placeholder types extended unique names so they don't clash with the code you actually want to keep)

这极大地简化了解析步骤,因为您只需要识别结构体/联合体或函数声明的上下文,而无需实际进行太多周围信息的处理。


一个简单但是有些hackish的起点(我可能会使用它,因为我的标准很低:D)可能包括:

  • 通过头文件中的#include指令搜索带有尖括号参数(即您不想生成声明的已安装头文件)。
  • 使用此列表创建一个虚拟的include文件夹,其中包含所有必要的include文件,但内容为空
  • 预处理它以期简化语法(clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h或类似的命令)
  • 搜索structunion后跟名称、}后跟名称或名称(,并使用这个非常简化的非解析方法来构建虚拟函数使用列表,并发出.c文件的代码。

它不能覆盖所有可能性;但通过一些调整和扩展,它可能会实际处理大量逼真的头文件代码的子集。您可以在以后用专用的简化解析器(建立在只查看您需要的上下文模式的基础上)替换它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接