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/位码 的相关文章

  • C# 从带引号的字符串中删除分隔符

    我正在编写一个程序 必须从文本文件中带引号的字符串中删除分隔符 例如 Hello my name is world 必须 Hello my name is world 起初这听起来很简单 我认为是这样 但是您需要检测引号何时开始 何时结束
  • TPL Dataflow如何删除块之间的链接

    我想知道 如何删除块之间的链接 换句话说 我想要与 LinkTo 相反 我想写一个基于 tlp 数据流的记录器 我编写了这个接口 并希望在需要时删除 ILogListener 的订阅 public interface ILogManager
  • 从变量使用 OLE DB 源命令的 EzAPI 等效项是什么?

    tl dr 使用 来自变量的 SQL 命令 数据访问模式的 OLE DB 源并分配变量的 EzAPI 代码是什么 Preamble 每月一次 我们需要使用生产数据的子集刷新我们的公共测试站点 我们已确定 根据我们的需求 SSIS 解决方案最
  • C# 3 新功能帖子(与 .Net 3.5 功能无关)[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions Net F
  • 检查文件是真实文件还是符号链接

    有没有办法使用 C 来判断文件是真实文件还是符号链接 我已经挖过了MSDN W32 文档 https learn microsoft com en us windows win32 fileio file management functi
  • 要实现 XML 可序列化,从 ICollection 继承的类型必须具有 Add 的实现

    我有来自现有项目的 CSLA 1 x 框架 对象 我试图在新的 Net 4 0 项目中使用它 这些对象正在生产中使用 如果没有 2 组对象 我确实无法将它们转换为 2 x 或 EF 在我的 c webservice 中 当我尝试运行它时 我
  • 将二维整数作为 Readonly/const 存储在单独的类中,同时保持非公开

    这是我在使用这个地方作为我的 去处 以获取关于什么有效 无效 为什么等的一般意见之后的第一个问题 那么让我们试试这个 由于我的经验有限 我一直在尝试寻找更好的方法来创建固定数据字段 我可以在整个程序中引用这些字段 例如我反复显示的最终用户可
  • 不屈不挠的野兽:一个二维字符数组,位于结构内部,位于非托管 dll 的内部

    我束紧腰 冒险进入了遗产之地 砍倒 召唤并集结了各种野兽 现在我站在了一个如此凶猛的生物面前 据我对我的弟兄们进行的详尽调查来看 我现在所面对的生物是如此凶猛 武器中 没有一个代码战士能够幸存 以下是详细信息 我试图将结构内部的二维字符数组
  • Err_Response_Headers_Multiple_Content_Disposition

    我需要导出 2csv单击一个按钮即可打开文件 下面是我生成2的代码csv files using System Data using System Data SqlClient using System Text using System
  • ASP.NET MVC - HybridViewResult (ViewResult /PartialViewResult)

    是否可以构建一个依赖于 Ajax 请求或 Http 请求返回的混合 ViewResultPartialViewResult or ViewResult IsAjaxRequest gt 返回 PartialViewResult IsAjax
  • 如何在调试时轻松查看事件订阅数量?

    在调试时 我可以查看一下textBox1 TextChanged查看事件订阅数量 如果是 那么我该如何钻取它 我需要知道在给定时间有多少订阅进行调试 因为看起来一个事件被多次触发 但我怀疑这个错误确实是因为textBox1 TextChan
  • 在 C++ 泛型编程中处理 void 赋值

    我有 C 代码 它包装任意 lambda 并返回 lambda 的结果 template
  • 在信号/槽处理期间删除 QObject

    我知道从槽处理中删除 QObject 可能会使应用程序崩溃 因为它可能有其他排队的事件 因此 我将使用 obj gt deleteLater 而不是使用 delete obj 据我所知 obj 等待处理所有排队的事件 然后 删除 obj Q
  • 如何使 RSACryptoServiceProvider 在没有填充(nopadding)的情况下工作?

    我需要使 C 应用程序与 Java 应用程序兼容 Java 应用程序使用Cipher getInstance RSA ECB nopadding 初始化器使密码 ECB 和无填充 但是 在 C 中 您有 2 个填充选项 OAEP 填充或 P
  • C# 多重继承

    目前我正在学习 C 和 ASP NET MVC 4代码优先方法 我是 Visual Basic 开发人员 现在我想开始 C 而且 现在我遇到了必须管理多重继承的情况 但是 对于Class i来说这是不可能的 那么 我应该如何管理我拥有的这些
  • NHibernate Criteria API 是否支持集合属性的投影?

    我需要使用条件 API 复制以下工作 HQL 查询 session CreateQuery select c from Parent p inner join p Children c where p Id 9 and c Id 33 Se
  • 将base64字符串转换为图像c#时出错

    我想在我的网页上显示图像 并单击应该下载的链接按钮 存储的图像文件以二进制格式存储在db中 将 base64 字符串转换为图像时显示错误 详细信息如下 帮助我找到合适的解决方案 谢谢 Error Code protected void Pa
  • 是一对一的关系不好的策略

    用户始终拥有一个钱包 一个钱包始终属于一位用户 由于我想分离与钱夹相关的属性 我创建了 Wallet 对象并能够跟踪钱交易 我创建了 public Wallet Entity
  • 实体框架中对象属性中的 NULL 值

    Tables Article Author Comment 1篇文章和1位作者可以有 评论 数据库中有 1 篇文章 1 位作者和 1 条评论 问题是 该代码 myBD my bd new myBD var articles by bd Ar
  • Fluent Validation 将 CustomAsync 更改为 MustAsync

    有人可以帮我解决这个问题吗 我正在努力改变CustomAsync 到 MustAsync 但我无法让事情发挥作用 下面是我的自定义方法 RuleFor o gt o MustAsync o gt return CheckIdNumberAl

随机推荐