如何使用 clang -emit-llvm 编译和保留 "unused" C 声明
How to compile and keep "unused" C declarations with clang -emit-llvm
上下文
我正在为一种需要大量运行时函数的语言编写编译器。我使用 LLVM 作为我的后端,所以 codegen 需要所有这些运行时类型(函数、结构等)的类型,而不是使用 LLVM API 手动定义它们或手写 LLVM IR 我想写headers 在 C 中编译为编译器可以使用 LLVMParseBitcodeInContext2
.
引入的位码
问题
我遇到的问题是 clang 似乎没有保留任何函数 定义未使用的任何类型 声明 。 Clang 有 -femit-all-decls
听起来 应该可以解决它,但不幸的是它不是,谷歌搜索表明它被错误命名,因为它只影响未使用的定义,而不影响声明。
然后我想也许如果我只将 headers 编译成 .gch
文件,我可以用 LLVMParseBitcodeInContext2
以相同的方式将它们拉入(因为文档说他们使用 "the same" 位码格式”,但是 error: Invalid bitcode signature
这样做会出错,所以一定有所不同。也许差异小到足以解决问题?
有什么建议或相对简单的解决方法可以自动处理复杂的运行时吗?如果有人对处理这个一般用例有完全替代的建议,我也会感兴趣,记住我不想在运行时函数体中为每个 object 文件静态 link 我生成,只是类型。我想这是其他编译器也需要的东西,所以如果我处理这个错误我不会感到惊讶。
例如鉴于此输入:
runtime.h
struct Foo {
int a;
int b;
};
struct Foo * something_with_foo(struct Foo *foo);
我需要一个具有这个等效 IR 的位码文件
runtime.ll
; ...etc...
%struct.Foo = type { i32, i32 }
declare %struct.Foo* @something_with_foo(%struct.Foo*)
; ...etc...
我可以全部手写,但这会是重复的,因为我还需要为其他互操作创建 C headers,最好不要手动保持它们同步。运行时间相当大。我想我也可以反过来做:在 LLVM IR 中编写声明并生成 C headers.
几年前有人问过这个问题,但所提出的解决方案对于这种规模和类型复杂性的运行时而言相当老套且相当不切实际:Clang - Compiling a C header to LLVM IR/bitcode
Clang 的 precompiled headers 实现似乎没有输出 LLVM IR,而只输出 AST(抽象语法树),因此 header 不需要再次解析:
The AST file itself contains a serialized representation of Clang’s
abstract syntax trees and supporting data structures, stored using the
same compressed bitstream as LLVM’s bitcode file format.
底层的二进制格式可能相同,但听起来内容不同,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
因此,clang
实际上并没有过滤掉未使用的声明。它推迟发出前向声明,直到它们第一次使用。每当使用函数时,它都会检查它是否已经发出,如果没有发出函数声明。
你可以看看these lines in the clang repo。
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition())
return;
此处的简单解决方法是注释最后两行或仅将 && false
添加到第二个条件。
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition() && false)
return;
这将导致 clang
在看到它时立即发出声明,这也可能会改变定义在 .ll
(或 .bc
)文件中出现的顺序.假设这不是问题。
为了使其更清晰,您还可以添加命令行标志 --emit-all-declarations
并在继续之前检查此处。
上下文
我正在为一种需要大量运行时函数的语言编写编译器。我使用 LLVM 作为我的后端,所以 codegen 需要所有这些运行时类型(函数、结构等)的类型,而不是使用 LLVM API 手动定义它们或手写 LLVM IR 我想写headers 在 C 中编译为编译器可以使用 LLVMParseBitcodeInContext2
.
问题
我遇到的问题是 clang 似乎没有保留任何函数 定义未使用的任何类型 声明 。 Clang 有 -femit-all-decls
听起来 应该可以解决它,但不幸的是它不是,谷歌搜索表明它被错误命名,因为它只影响未使用的定义,而不影响声明。
然后我想也许如果我只将 headers 编译成 .gch
文件,我可以用 LLVMParseBitcodeInContext2
以相同的方式将它们拉入(因为文档说他们使用 "the same" 位码格式”,但是 error: Invalid bitcode signature
这样做会出错,所以一定有所不同。也许差异小到足以解决问题?
有什么建议或相对简单的解决方法可以自动处理复杂的运行时吗?如果有人对处理这个一般用例有完全替代的建议,我也会感兴趣,记住我不想在运行时函数体中为每个 object 文件静态 link 我生成,只是类型。我想这是其他编译器也需要的东西,所以如果我处理这个错误我不会感到惊讶。
例如鉴于此输入:
runtime.h
struct Foo {
int a;
int b;
};
struct Foo * something_with_foo(struct Foo *foo);
我需要一个具有这个等效 IR 的位码文件
runtime.ll
; ...etc...
%struct.Foo = type { i32, i32 }
declare %struct.Foo* @something_with_foo(%struct.Foo*)
; ...etc...
我可以全部手写,但这会是重复的,因为我还需要为其他互操作创建 C headers,最好不要手动保持它们同步。运行时间相当大。我想我也可以反过来做:在 LLVM IR 中编写声明并生成 C headers.
几年前有人问过这个问题,但所提出的解决方案对于这种规模和类型复杂性的运行时而言相当老套且相当不切实际:Clang - Compiling a C header to LLVM IR/bitcode
Clang 的 precompiled headers 实现似乎没有输出 LLVM IR,而只输出 AST(抽象语法树),因此 header 不需要再次解析:
The AST file itself contains a serialized representation of Clang’s abstract syntax trees and supporting data structures, stored using the same compressed bitstream as LLVM’s bitcode file format.
底层的二进制格式可能相同,但听起来内容不同,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
因此,clang
实际上并没有过滤掉未使用的声明。它推迟发出前向声明,直到它们第一次使用。每当使用函数时,它都会检查它是否已经发出,如果没有发出函数声明。
你可以看看these lines in the clang repo。
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition())
return;
此处的简单解决方法是注释最后两行或仅将 && false
添加到第二个条件。
// Forward declarations are emitted lazily on first use.
if (!FD->doesThisDeclarationHaveABody()) {
if (!FD->doesDeclarationForceExternallyVisibleDefinition() && false)
return;
这将导致 clang
在看到它时立即发出声明,这也可能会改变定义在 .ll
(或 .bc
)文件中出现的顺序.假设这不是问题。
为了使其更清晰,您还可以添加命令行标志 --emit-all-declarations
并在继续之前检查此处。