首页 > 解决方案 > 如何使用 clang -emit-llvm 编译和保留“未使用”的 C 声明

问题描述

语境

我正在为需要大量运行时函数的语言编写编译器。我使用 LLVM 作为我的后端,因此 codegen 需要所有这些运行时类型(函数、结构等)的类型,而不是使用 LLVM API 手动定义所有这些类型或手写 LLVM IR 我想写C 中的头文件并编译为编译器可以使用LLVMParseBitcodeInContext2.

问题

我遇到的问题是 clang 似乎没有保留任何函数定义未使用的任何类型声明。Clang听起来好像应该解决它,但不幸的是它不是,而且谷歌搜索表明它的命名错误,因为它只影响未使用的定义,而不是声明。-femit-all-decls

然后我想也许如果我只将头文件编译成.gch文件,我可以用LLVMParseBitcodeInContext2同样的方式把它们拉进来(因为文档说他们使用“相同的”位码格式”,但是这样做的错误error: Invalid bitcode signature一定是不同的。也许区别小到可以解决吗?

有什么建议或相对简单的解决方法可以为复杂的运行时自动化?如果有人对处理这个一般用例有一个完全替代的建议,我也会感兴趣,记住我不想为我生成的每个目标文件静态链接运行时函数体,只是类型。我想这也是其他编译器也需要的东西,所以如果我接近这个错误我不会感到惊讶。


例如给定这个输入:

运行时.h

struct Foo {
  int a;
  int b;
};

struct Foo * something_with_foo(struct Foo *foo);

我需要一个具有等效 IR 的位码文件

运行时.ll

; ...etc...

%struct.Foo = type { i32, i32 }

declare %struct.Foo* @something_with_foo(%struct.Foo*)

; ...etc...

我可以手动编写所有内容,但这将是重复的,因为我还需要为其他互操作创建 C 标头,并且最好不必手动保持它们同步。运行时间相当大。我想我也可以反过来做:在 LLVM IR 中编写声明并生成 C 头文件。


几年前有人问过这个问题,但是对于这种大小和类型复杂的运行时,所提出的解决方案相当笨拙且相当不切实际:Clang - Compiling a C header to LLVM IR/bitcode

标签: ccompiler-constructionclangllvmbitcode

解决方案


Clang 的预编译头实现似乎没有输出 LLVM IR,而只输出 AST(抽象语法树),因此不需要再次解析头:

AST 文件本身包含 Clang 的抽象语法树和支持数据结构的序列化表示,使用与 LLVM 的位码文件格式相同的压缩位流存储。

底层的二进制格式可能相同,但听起来内容不同,LLVM 的位码格式在这种情况下只是一个容器。这从网站上的帮助页面上不是很清楚,所以我只是推测。LLVM/Clang 专家可以帮助澄清这一点。

不幸的是,似乎没有一种优雅的方式来解决这个问题。为了最大限度地减少实现您想要的工作所需的工作,我建议构建一个最小的 C/C++ 源文件,该文件以某种方式使用您想要编译为 LLVM IR 的所有声明。例如,您只需要声明一个指向结构的指针以确保它不会被优化掉,并且您可能只需为函数提供一个空定义以保留其签名。

一旦你有一个最小的源文件,编译它clang -O0 -c -emit-llvm -o precompiled.ll以获得一个具有 LLVM IR 格式的所有定义的模块。

您发布的片段中的一个示例:

struct Foo {
  int a;
  int b;
};

// Fake function definition.
struct Foo *  something_with_foo(struct Foo *foo)
{
    return NULL;
}

// A global variable.
struct Foo* x;

显示保留定义的输出:https ://godbolt.org/g/2F89BH


推荐阅读