Clang - 将 C 标头编译为 LLVM IR/位码

2024-01-25

假设我有以下简单的 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)"}

我的第一直觉是求助于谷歌,我遇到了两个相关的问题:邮件列表中的一个 http://lists.cs.uiuc.edu/pipermail/llvmdev/2009-December/027979.html, and 来自 StackOverflow 的一份 https://stackoverflow.com/q/14032496/1311454。两者都建议使用-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 and -disable-llvm-optzns),但这对输出没有影响。使用以下变体did产生所需的 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.) 提前了解事物的类型。这让我...

Why?

基本上,我正在构建一种使用 LLVM 生成代码的语言的实现。实现应该通过仅指定 C 头文件和关联的库(无手动声明)来支持 C 互操作,然后编译器将在链接时使用它们来确保函数调用与其签名匹配。因此,我将问题缩小为两种可能的解决方案:

  1. 将头文件转为LLVM IR/bitcode,即可得到各个函数的类型签名
  2. Use libclang解析标头,然后从生成的 AST 中查询类型(我的“最后手段”,以防这个问题没有足够的答案)

TL;DR

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


也许是不太优雅的解决方案,但仍坚持doThings强制编译器发出 IR 的函数,因为使用了定义:

您发现这种方法的两个问题是,它需要修改标头,并且需要更深入地了解所涉及的类型,以便生成要放入函数中的“用途”。这两个问题都可以相对简单地克服:

  1. 而不是直接编译头文件,#include它(或更可能是它的预处理版本,或多个标头)来自包含所有“使用”代码的 .c 文件。足够简单:

    // foo.c
    #include "foo.h"
    void doThings(void) {
        ...
    }
    
  2. 您不需要详细的类型信息来生成名称的特定用法、将结构实例化与参数相匹配以及上面“使用”代码中的所有复杂性。您实际上不需要自己收集函数签名.

    您所需要的只是名称本身的列表,并跟踪它们是用于函数还是对象类型。然后,您可以重新定义“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 },
        };
    }
    

    这极大地简化了名称的必要“使用”,要么将其转换为统一的函数类型(并获取其指针而不是调用它),要么将其包装在&( and ){0}(实例化它不管它是什么)。这意味着您根本不需要存储实际类型信息,只需存储类型信息context您从中提取了标头中的名称。

    (显然,为虚拟函数和占位符类型提供扩展的唯一名称,这样它们就不会与您实际想要保留的代码发生冲突)

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


一个简单但黑客的起点(我可能会使用它,因为我的标准很低:D)可能是:

  • grep 通过标题#include采用尖括号参数的指令(即您不想为其生成声明的已安装标头)。
  • 使用此列表创建一个虚拟包含文件夹,其中包含所有必需的包含文件,但为空
  • 对其进行预处理,希望能够简化语法(clang -E -I local-dummy-includes/ -D"__attribute__(...)=" foo.h > temp/foo_pp.h或类似的东西)
  • grep 遍历 forstruct or union接下来是一个名字,}后面跟着一个名字,或者name (,并使用这个极其简化的非解析来构建虚拟函数中的使用列表,并发出 .c 文件的代码。

它不会捕捉到所有的可能性;但通过一些调整和扩展,它实际上可能会处理实际标头代码的很大一个子集。您可以在稍后阶段用专用的简化解析器(仅用于查看所需上下文模式的解析器)替换它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Clang - 将 C 标头编译为 LLVM IR/位码 的相关文章

  • 删除文件的最后 10 个字符

    我想删除文件的最后 10 个字符 说一个字符串 hello i am a c learner 是文件内的数据 我只是希望该文件是 hello i am a 文件的最后 10 个字符 即字符串 c learner 应在文件内消除 解决方案 将
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 如何在 Cassandra 中存储无符号整数?

    我通过 Datastax 驱动程序在 Cassandra 中存储一些数据 并且需要存储无符号 16 位和 32 位整数 对于无符号 16 位整数 我可以轻松地将它们存储为有符号 32 位整数 并根据需要进行转换 然而 对于无符号 64 位整
  • std::list 线程push_back、front、pop_front

    std list 线程安全吗 我假设不是这样 所以我添加了自己的同步机制 我认为我有正确的术语 但我仍然遇到问题 每个函数都由单独的线程调用 Thread1 不能等待 它必须尽可能快 std list
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 如何从本机 C(++) DLL 调用 .NET (C#) 代码?

    我有一个 C app exe 和一个 C my dll my dll NET 项目链接到本机 C DLL mynat dll 外部 C DLL 接口 并且从 C 调用 C DLL 可以正常工作 通过使用 DllImport mynat dl
  • 对类 static constexpr 结构的未定义引用,g++ 与 clang

    这是我的代码 a cp p struct int2 int x y struct Foo static constexpr int bar1 1 static constexpr int2 bar2 1 2 int foo1 return
  • 访问外部窗口句柄

    我当前正在处理的程序有问题 这是由于 vista Windows 7 中增强的安全性引起的 特别是 UIPI 它阻止完整性级别较低的窗口与较高完整性级别的窗口 对话 就我而言 我想告诉具有高完整性级别的窗口进入我们的应用程序 它在 XP 或
  • C# 列表通用扩展方法与非通用扩展方法

    这是一个简单的问题 我希望 集合类中有通用和非通用方法 例如List
  • 在 Unity 中实现 Fur with Shells 技术

    我正在尝试在 Unity 中实现皮毛贝壳技术 http developer download nvidia com SDK 10 5 direct3d Source Fur doc FurShellsAndFins pdf Fins 技术被
  • WcfSvcHost 的跨域异常

    对于另一个跨域问题 我深表歉意 我一整天都在与这个问题作斗争 现在已经到了沸腾的地步 我有一个 Silverlight 应用程序项目 SLApp1 一个用于托管 Silverlight SLApp1 Web 的 Web 项目和 WCF 项目
  • 为什么这个字符串用AesCryptoServiceProvider第二次解密时不相等?

    我在 C VS2012 NET 4 5 中的文本加密和解密方面遇到问题 具体来说 当我加密并随后解密字符串时 输出与输入不同 然而 奇怪的是 如果我复制加密的输出并将其硬编码为字符串文字 解密就会起作用 以下代码示例说明了该问题 我究竟做错
  • x:将 ViewModel 方法绑定到 DataTemplate 内的事件

    我基本上问同样的问题这个人 https stackoverflow com questions 10752448 binding to viewmodels property from a template 但在较新的背景下x Bind V
  • 实例化类时重写虚拟方法

    我有一个带有一些虚函数的类 让我们假设这是其中之一 public class AClassWhatever protected virtual string DoAThingToAString string inputString retu
  • 空指针与 int 等价

    Bjarne 在 C 编程语言 中写道 空指针与整数零不同 但 0 可以用作空指针的指针初始值设定项 这是否意味着 void voidPointer 0 int zero 0 int castPointer reinterpret cast
  • 相当于Linux中的导入库

    在 Windows C 中 当您想要链接 DLL 时 您必须提供导入库 但是在 GNU 构建系统中 当您想要链接 so 文件 相当于 dll 时 您就不需要链接 为什么是这样 是否有等效的 Windows 导入库 注意 我不会谈论在 Win
  • C++ 继承的内存布局

    如果我有两个类 一个类继承另一个类 并且子类仅包含函数 那么这两个类的内存布局是否相同 e g class Base int a b c class Derived public Base only functions 我读过编译器无法对数
  • 指针和内存范围

    我已经用 C 语言编程有一段时间了 但对 C 语言还是很陌生 有时我对 C 处理内存的方式感到困惑 考虑以下有效的 C 代码片段 const char string void where is this pointer variable l
  • 类型或命名空间“MyNamespace”不存在等

    我有通常的类型或命名空间名称不存在错误 除了我引用了程序集 using 语句没有显示为不正确 并且我引用的类是公共的 事实上 我在不同的解决方案中引用并使用相同的程序集来执行相同的操作 并且效果很好 顺便说一句 这是VS2010 有人有什么
  • 现代编译器是否优化乘以 1 和 -1

    如果我写 template

随机推荐