LLVM基本概念入门

2023-10-27

入职新公司以后,开始着手基于LLVM开发编译器,之前在前东家那里主要做gcc的开发,所以也算是有点基础,但拿到LLVM后,除了知道clang a.c -o a之外,好像其他的都有点差异。现在经过了小一个月的学习,也算有点收获。因为网上关于LLVM的中文资料一直也不多,即使能找到的一些,也都是停留在怎么使用的层面,对于编译器开发工程师入门来说,感觉完全不够,所以我写写基础概念,能帮到大家也挺好的。

本文的参考资料主要是官网的资料,经过自己的摸索和提炼,整理成文,本文需要一定的编译器基础知识。
有任何建议或疑问,欢迎留言

安装与使用

这部分我不讲,网上资料挺多,Clang驱动程序把整个LLVM和clang都集成起来,和gcc的调用接口统一起来了,所以会用gcc的,Clang的使用也没啥问题。

LLVM和Clang的背景

它最初的编写者,是一位叫做Chris Lattner(个人主页)的大神,硕博期间研究内容就是关于编译器优化的东西,发表了很多论文,博士论文是提出一套在编译时、链接时、运行时甚至是闲置时的优化策略,与此同时,LLVM的基本思想也就被确定了,这也让他在毕业前就在编译器圈子小有名气。

而在这之前,Apple公司一直使用GCC作为编译器,后来GCC对Objective-C的语言特性支持一直不够,Apple自己开发的GCC模块又很难得到GCC委员会的合并,所以老乔不开心。等到Chris Lattner毕业时,Apple就把他招入靡下,去开发自己的编译器,所以LLVM最初受到了Apple的大力支持。

LLVM最初设计时,因为只想着做优化方面的研究,所以只是想搭建一套虚拟机,所以当时这个的全称叫Low Level Virtual machine,后来因为要变成编译器了么,然后官方就放弃了这个称呼,不过LLVM的简称还是保留下来了。

因为LLVM只是一个编译器框架,所以还需要一个前端来支撑整个系统,所以Apple又拨款拨人一起研发了Clang,作为整个编译器的前端,Clang用来编译C、C++和Objective-C。所以当我接触Apple编译器时,当时的帖子里都说使用Clang/LLVM来和gcc做对比,当然是在代码优化、速度和敏捷性上比gcc强不少。这里我有两个文章,分别是gcc评价它在代码诊断方面和Clang的比较以及Clang评价它和gcc(以及其他几个开源编译器)的优缺点,还是很客观的。相比来说,Clang/LLVM的完整性还不够,毕竟还在发展中。

对了,Clang的发音是/ˈklæŋ/,这是官方确认过的,我现在还在纠正发音过程中(后续:半个月后已纠正完毕,无缝衔接)。

后来Apple推出了Swift,也是基于LLVM作为编译器框架的。

至于Chris Lattner,17年的时候跳到了特斯拉,再后来去了Google,在TensorFlow团队,还是挺厉害的。而LLVM开源之后由LLVM委员会负责维护,当然发展势头也很猛。

LLVM

LLVM是一个编译器框架,这就意味着它不是一个完整的编译器,其实从传统gcc编译器的架构上来讲,它只缺一个前端和一个链接器,就是一个完整的编译器了。 (这句话总结的并不好,LLVM作为编译器框架,是需要各种功能模块支撑起来的,它就不能被看做是系统的一部分,你仍可以将clang和lld都看做是LLVM的组成部分,框架的意思是,你可以基于LLVM提供的功能开发自己的模块,并集成在LLVM系统上,增加它的功能,或者就单纯自己开发软件工具,而利用LLVM来支撑底层实现)。LLVM由一些库和工具组成,正因为它的这种设计思想,使它可以很容易和IDE集成(因为IDE软件可以直接调用库来实现一些如静态检查这些功能),也很容易构建生成各种功能的工具(因为新的工具只需要调用需要的库就行)。

请看下边这个图:

在这里插入图片描述

这个图是Clang/LLVM的简单架构。最初时,LLVM的前端是GCC,后来Apple还是立志自己开发了一套Clang出来把GCC取代了,不过现在带有Dragon Egg的GCC还是可以生成LLVM IR,也同样可以取代Clang的功能,我们也可以开发自己的前端,和LLVM后端配合起来,实现我们自定义的编程语言的编译器。

LLVM IR是LLVM的中间表示,这是LLVM中很重要的一个东西,介绍它的文档就一个,LLVM Language Reference Manual(看名字就觉得大气,LLVM语言参考手册,但浩浩荡荡一大篇文章,读下来还是需要精力的),大多数的优化都依赖于LLVM IR展开。我把Opt单独画在一边,是为了简化图的内容,因为LLVM的一个设计思想是优化可以渗透在整个编译流程中各个阶段,比如编译时、链接时、运行时等。

在LLVM中,IR有三种表示,一种是可读的IR,类似于汇编代码,但其实它介于高等语言和汇编之间,这种表示就是给人看的,磁盘文件后缀为.ll;第二种是不可读的二进制IR,被称作位码(bitcode),磁盘文件后缀为.bc;第三种表示是一种内存格式,只保存在内存中,所以谈不上文件格式和文件后缀,这种格式是LLVM之所以编译快的一个原因,它不像gcc,每个阶段结束会生成一些中间过程文件,它编译的中间数据都是这第三种表示的IR。三种格式是完全等价的,我们可以在Clang/LLVM工具的参数中指定生成这些文件(默认不生成,对于非编译器开发人员来说,也没必要生成),可以通过llvm-asllvm-dis来在前两种文件之间做转换。

能注意到中间有个LLVM IR linker,这个是IR的链接器,而不是gcc中的那个链接器。为了实现链接时优化,LLVM在前端(Clang)生成单个代码单元的IR后,将整个工程的IR都链接起来,同时做链接时优化。

LLVM backend就是LLVM真正的后端,也被称为LLVM核心,包括编译、汇编、链接这一套,最后生成汇编文件或者目标码。这里的LLVM compiler和gcc中的compiler不一样,这里的LLVM compiler只是编译LLVM IR。

与gcc的关系

如果是像我这样从gcc过来的人,会不容易完全理解他们的结构对应关系。

gcc的编译器,输入是源代码,输出是汇编代码,相当于是LLVM中Clang一级加上IR linker再加上LLVM compiler中的生成汇编代码部分(Clang输出可执行文件的一条龙过程,不会生成汇编文件,内部全部走中间表示,生成汇编码和生成目标文件是并列的)。

gcc的汇编器,输入是汇编代码,输出是目标文件,相当于是LLVM中的llvm-mc(这是另一个工具,Clang一条龙默认不走这个工具,但会调用相同的库来做汇编指令的下降和发射)。

gcc的链接器,输入是目标文件,输出是最终可执行文件,相当于LLVM中的Linker,现在LLVM Linker还在开发中(已释出,叫lld,但仍然不成熟),所以Clang驱动程序调起来的链接器还是系统链接器,可以选择使用gcc的ld(这块会很快变,LLVM社区必然会在lld成熟后默认换上去,大家可以自行验证)。

概念上的一词多义

通常提到LLVM和Clang,其实会有多个概念,比如我上边会说到Clang前端和Clang驱动程序,其实是Clang的两个概念,不同的概念需要在上下文中去分辨。我从官网文档中翻译出来这一段:

LLVM

  • LLVM项目或基础架构:这是对整个LLVM编译器框架的程序,包括了前端、优化器、后端、汇编器、链接器,以及libc++、JIT等。上下文如:“LLVM项目由以下几个模块组成”。
  • 基于LLVM开发的编译器:这是指一部分或全部基于LLVM项目开发的编译器软件,软件可能基于LLVM的前端或后端来实现。上下文如:“我用LLVM将C语言编译到MIPS平台”。
  • LLVM库:LLVM项目由库代码和一些工具组成,有时会指代LLVM库内容。上下文如:“我的项目使用了LLVM的即时编译框架”(JIT是其中一个库)。
  • LLVM核心:在IR和后端算法上的内容,就是LLVM核心,也就是通常Clang/LLVM中的LLVM。上下文如:“LLVM和Clang是两个项目”。
  • LLVM IR:有些时候也会指代其中间表示。上下文如:“Clang是一个前端,能将源代码翻译成LLVM”。

Clang

通常我们在命令行上调用的clang工具,是Clang驱动程序,因为LLVM本质上只是一个编译器框架,所以需要一个驱动程序把整个编译器的功能串起来,clang能够监控整个编译器的流程,即能够调用到Clang和LLVM的各种库,最终实现编译的功能。
BTW,其实gcc也是驱动程序,由它将ccasld等程序驱动起来。
如果由clang来监控运行,则整个IR的阶段,IR的表示形式都是内存形式,这样就不会在编译过程中产生中间文件,提高了编译的效率。另一种方法是调用LLVM的工具,类似于gcc中的ccasld一样,LLVM也有自己的工具,这样工具之间运行需要用户来控制输入输出,这时的IR表示形式就是硬盘格式,可以是LLVM汇编(.ll),也可以是位码(.bc)。

LLVM工具

前边总是提到LLVM工具,这一节介绍一下常用工具。LLVM工具用来调用LLVM的一部分库,实现库的功能,通常使用编译器的人会调用到这些工具。

有关于库和工具,我有个很形象的比喻,库就相当于厨房的各种厨艺和厨师,而工具就相当于前台和菜单,源程序就是我们带的食材,可执行文件就是最终的菜。我们总是需要通过前台来告诉我们需要吃什么菜,怎么吃,而前台负责调度后厨的哪些厨师和厨艺来加工这道菜,不同的前台输出的菜不完全相同,而它们可以调度相同的厨师去实现对应厨艺。

opt

这是一个在IR级别做程序优化的工具,输入和输出都是同一类型的LLVM IR,很好理解,做优化不必要修改文件格式。设计编译器的同学会经常性的调用这个工具来验证自己的优化pass是否正确。
BTW,反过来,优化pass不一定作用于LLVM IR,比如作用于后端的MI,这时opt是不能使用的。

llc

这是微观意义上的LLVM编译器,不同于gcc的编译器,它的输入是LLVM IR,输出是汇编文件或者是目标文件。通过-filetype=asm或者-filetype=obj来指定输出是汇编文件还是目标文件,若生成是目标文件,llc会调用LLVM中的汇编输出的代码库来工作(注意这个汇编器和gcc的汇编器也不同,它输入的是MI,是一种后端的中间表示)。除此之外,还可以用-On来指定优化级别(llc默认优化级别是-O2),或者其他一些参数。

llc -filetype=asm main.bc -O0 -o main.s
llc -filetype=obj main.bc -O0 -o main.o

.bc文件换成.ll文件也可以)

llvm-mc

这是微观意义上的LLVM汇编器,它输入汇编文件,输出目标文件。同时,它也可以反汇编,指定特殊参数(–disassemble)就行。可以发现,llc和llvm-mc都会调用到输出目标文件的库,也就是MCObjectStreamer
有关于这个工具,可以查看这个比较老旧的文档学习,有些东西是变了的,要批判的看 \逃。

lli

这个工具是LLVM IR的解释器,也是一个JIT编译器。我其实从这里才知道,原来谈C语言是一门编译型语言,并不客观,因为LLVM可以把C语言翻译成LLVM IR,然后解释执行,与Java的那一套类似,这也是最初LLVM编写时的实现(一个虚拟机运行IR)。

llvm-link

最早看到这个工具,以为是链接器,其实它是IR级别的链接器,链接的是IR文件。谈到这里,可以说一下LLVM针对多个源文件编译时的两种目标码输出方式。

  1. 第一种是LLVM先通过前端把每个源文件单独翻译成IR级别,然后用llvm-link链接成一个IR,然后再经过优化、后端等步骤生成目标文件,使用llvm-link的同时,可以使用链接时优化。不过需要注意,这种方式同样需要最终调用链接器,将这个目标文件链接成可执行文件。
  2. 第二种是LLVM通过前端把每个源文件单独翻译后,再单独经过优化、后端等工作,将每个源文件生成目标文件,之后再调用链接器,将所有目标文件链接成可执行文件。
llvm-link main.bc sum.bc -o sum.linked.bc

llvm-as

这是针对LLVM IR的汇编器,其实名字里带as,实际上不是gcc那个as,它的功能是将.ll文件翻译为.bc文件,LLVM项目里,.ll称为LLVM汇编码,所以llvm-as也就是IR的汇编器了。

llvm-dis

与llvm-as刚好相反,IR的反汇编器,用来将.bc文件翻译为.ll文件。

clang

最后也提一下clang,毕竟它也是现在LLVM项目中一个很重要的前端工具。clang能够调用起来整个编译器的流程,也就是上边其他工具调用的库,它很多都同样会调用。clang通过指定-emit-llvm参数,可以配合-S-c生成.ll.bc文件,这样我们就能把Clang的部分和LLVM的后端分离开来独立运行,对于观察编译器流程来说,很实用。

clang -emit-llvm -c main.c -o main.bc
clang -emit-llvm -S main.c -o main.ll

还有一些其他工具,就不举例了,可以查看LLVM项目路径下/src/tools/中查看。

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

LLVM基本概念入门 的相关文章

  • 是否可以使用 gold 链接器编译和链接 Clang/LLVM?

    我正在为 LLVM Clang 编写自定义通道 重新编译往往需要一段时间并使用大量内存 我听说 gold 链接器 1 比标准 ld 链接器花费更少的时间并且 2 使用更少的内存 有没有办法将标志传递到 LLVM Clang 构建过程并更改为
  • clang 是否提供类似于 GCC 6.x 的函数多版本控制 (target_clones) 的功能?

    我读了这篇 LWN 文章 https lwn net Articles 691932 饶有兴趣 执行摘要 GCC 6 x 支持所谓的函数多版本控制 它可以构建同一函数的多个版本 并针对不同的指令集进行优化 假设您有一台支持 AVX2 的机器
  • clang 实例化后静态成员初始化

    这样的代码可以用 GCC 编译 但 clang 3 5 失败 include
  • 要求编译器发出无分支/恒定时间代码

    在密码学中 任何依赖于秘密数据 例如私钥 的代码都必须在恒定时间内执行 以避免侧信道定时攻击 https en wikipedia org wiki Timing attack 目前最流行的架构 x86 64 和 ARM AArch64 都
  • _mm_max_ss 在 clang 和 gcc 之间有不同的行为

    我正在尝试使用 clang 和 gcc 交叉编译一个项目 但在使用时发现一些奇怪的差异 mm max ss e g m128 a mm set ss std numeric limits
  • Clang 代码覆盖无效输出

    所以我按照这些说明检查并构建了 clang trunkhttp clang llvm org get started html http clang llvm org get started html 我可以使用 coverage 构建二进
  • Qt 编译器标志顺序

    我的目标是消除某些类型的编译器警告 我发现可以通过在 pro 文件中添加编译器标志来做到这一点 QMAKE CXXFLAGS Wno unused variable Wno reorder 问题是它们被添加在 Qt 构建系统生成的标志之前
  • -fno-omit-frame-pointer 与 clang 等效的编译器选项

    我想用DS 5 Streamline profiler来分析我的代码 在文档中提到 为了能够查看调用堆栈 我们需要使用编译器选项来编译代码 fno omit frame pointer gcc 中有这个选项 clang 也有等效的选项吗 f
  • 由于 abi::cxx11 符号导致的链接问题?

    我们最近收到一份报告 因为GCC 5 1 libstdc 和双 ABI http gcc gnu org onlinedocs libstdc manual using dual abi html 它似乎Clang 不知道 GCC 内联名称
  • 如何在 Clang 中检测 libstdc++ 版本?

    我想用 Clang 编写一个 可移植 C 库 可移植 意味着我 在 C 预处理器中 检测编译环境中可用的 C 功能 并使用这些功能或提供我的解决方法 这与 Boost 库所做的类似 然而 某些功能的存在并不取决于语言 而是取决于标准库的实现
  • 如何为大量标头生成 .pch?

    我的代码一直使用 libcxx sdl 和其他一些库 考虑到每个标头可以包含一些其他标头 即使具有像 ifdef include endif 这样的复杂条件 我如何生成 pch 这就是为什么很难理解所需的头文件列表 我应该只使用在中找到的所
  • 编译器是否允许优化堆内存分配?

    考虑以下使用以下简单代码new 我知道没有delete 但这与这个问题无关 int main int mem new int 100 return 0 编译器是否允许优化new call 在我的研究中 g 5 2 0 https gcc g
  • 如何确定特定的 LLVM 指令是否依赖于另一个指令?

    我正在尝试编写 LLVM 优化过程 我需要一种方法来确定一个 LLVM 指令是否影响另一个指令 或依赖于另一个指令 这些依赖关系可以具有不同的性质 第一条指令创建一个值 另一条指令将其用作操作数 第一条指令写入内存位置 另一条指令从该位置读
  • 由于没有匹配的函数,LLVM 构建错误

    我克隆了 LLVM git 存储库并遵循https llvm org docs GettingStarted html https llvm org docs GettingStarted html 配置后 cmake SOURCEDIR
  • std::istringstream >> 使奇怪的行为加倍

    下面的代码打印0在 mac osx 上使用 clang 其他地方都会打印5 clang https ideone com mVgpzS gcc https ideone com oZ0hy6 include
  • 使用预编译头减少 clang 编译时间

    我正在开发一个数据库项目 该项目将查询 以某种高级语言表示 编译为 C 代码 这段代码由数据库编译并执行 那部分工作得很好 现在 我正在尝试减少 C 查询代码的编译时间 我想知道是否可以使用预编译头来提高性能 该查询被转换为一个名为 Que
  • Clang 3.1 + libc++ 编译错误

    我已经构建并安装了 在前缀下 alt LLVM Clang trunk 2012 年 4 月 23 日 在 Ubuntu 12 04 上成功使用 GCC 4 6 然后使用此 Clang 构建的 libc 当我想使用它时我必须同时提供 lc
  • 如何使用 Clang 编译器和 CMake 进行分析

    Question 1 What output我应该期待当我想使用进行分析时clang编译器 2 我该怎么办profiling for a C project它使用clang作为编译器andCMake 作为构建工具 重新分析我所使用的内容 1
  • 编译器如何如此好地优化这个阶乘函数?

    所以我一直在研究一些神奇的东西O3在 GCC 中 实际上我正在使用 Clang 进行编译 但这与 GCC 相同 我猜优化器的很大一部分是从 GCC 转移到 Clang 的 考虑这个 C 程序 int foo int n if n 0 ret
  • clang内存消毒剂;如何让它打印源代码行号

    我正在编译我的程序clang fsanitize memory fsanitize memory track origins fno omit frame pointer g O0当我运行它时 输出是 matiu matiu laptop

随机推荐

  • springboot 一个项目中配置多个redis实例

    在实际的项目中 可能一个项目需要操作多个不同redis的数据 那么我们就需要做相应的配置 以下是基于springboot 首先在我们项目的 application proterties中添加如下配置 有几个就写几个 注意这里的命名 spri
  • 使用ffmpeg 命令分割视频方法

    用法说明 ss 起始时间 i 要分割的是频文件 t 分割时长 格式如下 可以是 t xx 单位 秒 或者 t 01 00 00 时 分 秒 注意 ss 要放在 i 之前 实例 ffmpeg ss 00 00 00 i Video 20210
  • JS实现翻译的多种方案

    JS实现翻译的多种方案 1 language js 库 https languages js org docs 适应于 React Angular 和 Vue2 需要时再学 import Languages from languages j
  • 分库分表实战(7):抽丝剥茧 — 千万级数据之sql优化下篇

    V X ruyuanhadeng获得600 页原创精品文章汇总PDF 前言 上一期 我们讲解了sql优化的一般流程 不管是优化join语句 where语句 聚合函数还是排序操作 核心在于利用索引来优化sql语句 但是 大家以为我们为字段创建
  • sonar报java.io.StreamCorruptedException: invalid internal transport message format, got (48,54,54,50)

    昨天运行sonar还好好的 今天运行就自动退出了 sonar log日志文件如下 gt Wrapper Started as Console Launching a JVM Wrapper Version 3 2 3 http wrappe
  • 蓝桥杯题库 历届试题部分(C++、Java)代码实现(31-45)

    文章目录 五 历届试题 PREV 31 小朋友排队 PREV 32 分糖果 PREV 33 兰顿蚂蚁 PREV 34 矩阵翻硬币 PREV 35 正则问题 PREV 36 包子凑数 PREV 37 分巧克力 PREV 38 油漆面积 PRE
  • LDO的六大重要参数,你务必一一牢记

    在电子设计中 我们经常需要用到不同的直流电压给不同器件供电 其中用的最广泛的就是通过LDO稳压芯片来实现得到不同的直流电压输出 因为成本低 性能好 且使用起来也很简单 让LDO稳压芯片用的也越来越多 几乎每款电子产品里都有其身影 说它好用
  • 【STM32】认识库函数引脚GPIO开启时钟,需要初始化的结构体GPIOMode_TypeDef

    GPIO Mode AIN 0x0 GPIO Mode IN FLOATING 0x04 GPIO Mode IPD 0x28 GPIO Mode IPU 0x48 GPIO Mode Out OD 0x14 GPIO Mode Out P
  • html安卓关闭输入面板,tabletpc输入面板关闭不了怎么办(tablet pc输入面板关闭方法)...

    平时在我们用到一些电脑中的小工具的时候可以快速的打开 大多数的情况下只要想关闭打开的面板 只需要关闭右上角的红色小叉就可以快速的关闭 tablet pc 输入面板这样关闭不了怎么解决 我们在使用这个小工具的时候通常会在任务栏中点击右键 在菜
  • Android 获取项目证书指纹MD5、SHA1、SHA256步骤详解

    前些天发现了一个蛮有意思的人工智能学习网站 8个字形容一下 通俗易懂 风趣幽默 感觉非常有意思 忍不住分享一下给大家 点击跳转到教程 第一步 首先找到Android studio依赖的本地JDK路径 第二步 找到路径输入cmd 第三步 输入
  • Restful风格详解

    Restful风格的描述与解释 转载 https www cnblogs com letsdaydayup p 14441123 html 文章目录 Restful风格的描述与解释 一 什么是Restful风格 二 Restful的特点 实
  • OpenHarmony 3D显示支持

    前言 OpenHarmony系统是一个非常先进 现代化设计理念的新系统其系统架构图如下 一 图形子系统架构图 图形子系统是最复杂的一个 标准版这里2D的部分 foundation graphic graphic 2d rosen modul
  • 流计算处理系统入门

    时间可以划分成两种 处理时间 数据抵达流计算系统开始进行处理的时间 数据被处理的时间 事件时间 被检测系统获得数据的时间 一般用时间戳的方式携带在数据中 处理时间 晚于 数据事件时间 流计算框架 Hadoop 批处理框架 采集的数据全存入H
  • xssgame第十一关至第十四关

    第十一关 首先查看源码 我们获得到新的t ref参数 这个参数有点像http的请求头的refer值 我们用bp抓包尝试在请求头refer输入值查看结果 打开bp 添加一条 Referer type text nm use ver alert
  • 目标检测中将已有的.xml数据集转换成.txt数据集(附代码,归一化后供YOLO格式使用)

    目标检测中将已有的 xml数据集转换成 txt数据集 附代码 归一化后供YOLO格式使用 1 新建项目 我新建了data目录并新建Annotations images ImageSets JPEGImages labels 五个文件夹 其中
  • 有限状态自动机

    1 什么是有限状态自动机 1 1什么是计算 维基百科定义 计算 英语 Calculation 是一种将 单一或多个的输入值 转换为 单一或多个的结果 的一种思考过程 可以简单理解为给出一个问题得到一个答案的过程 如下图所示日常生活比较常见计
  • 【问题解决】glob.glob 如何匹配所有子文件夹下的文件 —— recursive=True

    一 仅匹配一级目录下的文件 import glob label dir data part1 dir1 txt datas glob glob label dir print datas gt gt gt data part1 dir1 0
  • Java中的类、对象以及方法简单定义与参数传递

    目录 一 类和对象的概念 二 方法 一 类和对象的概念 对象 现实世界中客观存在的物体都是对象 具有属性和方法 其中属性描述对象的特征 方法描述对象的行为 类 具有相同属性和方法的多个对象的集合 类是对对象的抽象 对象是类的具体 类是创建对
  • 关于安装pytorch、cuda对应GPU显卡算力问题(记录贴)

    贴几个链接 ngc pytorch容器版本对应关系 CUDA Toolkit版本及可用PyTorch对应关系 NVIDIA英伟达GPU显卡算力表 总结 安装torch时会安装对应的cuda版本 目前来看cuda10对应的是7 5的算力 适用
  • LLVM基本概念入门

    入职新公司以后 开始着手基于LLVM开发编译器 之前在前东家那里主要做gcc的开发 所以也算是有点基础 但拿到LLVM后 除了知道clang a c o a之外 好像其他的都有点差异 现在经过了小一个月的学习 也算有点收获 因为网上关于LL