深入浅出LLVM

深入浅出LLVM

转自:https://www.jianshu.com/p/1367dad95445

什么是LLVM?

LLVM项目是模块化、可重用的编译器以及工具链技术的集合。

美国计算机协会 (ACM) 将其2012 年软件系统奖项颁给了LLVM,之前曾经获得此奖项的软件和技术包括:Java、Apache、 Mosaic、the World Wide Web、Smalltalk、UNIX、Eclipse等等。创始人:Chris Lattner亦是Swift之父。

趣闻:Chris Latter本来只是想写一个底层的虚拟机,这也是LLVM名字的由来,low level virtual machine,跟Java的JVM虚拟机一样,可是后来,llvm从来没有被用作过虚拟机,哪怕LLVM的名气已经传开了。所以人们决定仍然叫他LLVM,更多的时候只是当作“商标”一样的感觉在使用,其实它跟虚拟机没有半毛钱关系。官方描述如下
The name “LLVM” itself is not an acronym; it is the full name of the project. “LLVM”这个名称本身不是首字母缩略词; 它是项目的全名。

编译器架构

传统编译器架构

在这里插入图片描述

  • Frontend:前端
    词法分析、语法分析、语义分析、生成中间代码
  • Optimizer:优化器
    中间代码优化
  • Backend:后端
    生成机器码

LLVM架构

在这里插入图片描述

  • 不同的前端后端使用统一的中间代码LLVM Intermediate Representation (LLVM IR)

  • 如果需要支持一种新的编程语言,那么只需要实现一个新的前端

  • 如果需要支持一种新的硬件设备,那么只需要实现一个新的后端

  • 优化阶段是一个通用的阶段,它针对的是统一的LLVM IR,不论是支持新的编程语言,还是支持新的硬件设备,都不需要对优化阶段做修改

  • 相比之下,GCC的前端和后端没分得太开,前端后端耦合在了一起。所以GCC为了支持一门新的语言,或者为了支持一个新的目标平台,就 变得特别困难

  • LLVM现在被作为实现各种静态和运行时编译语言的通用基础结构(GCC家族、Java、.NET、Python、Ruby、Scheme、Haskell、D等)

什么是Clang

LLVM项目的一个子项目,基于LLVM架构的C/C++/Objective-C编译器前端。

*相比于GCC,Clang具有如下优点*

  • 编译速度快:在某些平台上,Clang的编译速度显著的快过GCC(Debug模式下编译OC速度比GGC快3倍)
  • 占用内存小:Clang生成的AST所占用的内存是GCC的五分之一左右
  • 模块化设计:Clang采用基于库的模块化设计,易于 IDE 集成及其他用途的重用
  • 诊断信息可读性强:在编译过程中,Clang 创建并保留了大量详细的元数据 (metadata),有利于调试和错误报告
  • 设计清晰简单,容易理解,易于扩展增强

在这里插入图片描述

LLVM整体架构,前端用的是clang,广义的LLVM是指整个LLVM架构,一般狭义的LLVM指的是LLVM后端(包含代码优化和目标代码生成)。

源代码(c/c++)经过clang–> 中间代码(经过一系列的优化,优化用的是Pass) --> 机器码

OC源文件的编译过程

这里用Xcode创建一个Test项目,然后cd到main.m的上一路径。
命令行查看编译的过程:$ clang -ccc-print-phases main.m

$ clang -ccc-print-phases main.m 0: input, "main.m", objective-c
1: preprocessor, {0}, objective-c-cpp-output
2: compiler, {1}, ir
3: backend, {2}, assembler
4: assembler, {3}, object
5: linker, {4}, image
6: bind-arch, "x86_64", {5}, image
  1. 找到main.m文件
  2. 预处理器,处理include、import、宏定义
  3. 编译器编译,编译成IR中间代码
  4. 后端,生成目标代码
  5. 汇编
  6. 链接其他动态库静态库
  7. 编译成适合某个架构的代码

查看preprocessor(预处理)的结果:$ clang -E main.m
这个命令敲出,终端就会打印许多信息,大致如下:

# 1 "main.m"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 353 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "main.m" 2
.
.
.int main(int argc, const char * argv[]) {
@autoreleasepool {NSLog(@"Hello, World!");
}
return 0;
}

词法分析

词法分析,生成Token:$ clang -fmodules -E -Xclang -dump-tokens main.m
将代码分成一个个小单元(token)

举例如下:

void test(int a, int b){int c = a + b - 3;}
void 'void'  [StartOfLine]  Loc=<main.m:18:1>
identifier 'test'    [LeadingSpace] Loc=<main.m:18:6>
l_paren '('     Loc=<main.m:18:10>
int 'int'       Loc=<main.m:18:11>
identifier 'a'   [LeadingSpace] Loc=<main.m:18:15>
comma ','       Loc=<main.m:18:16>
int 'int'    [LeadingSpace] Loc=<main.m:18:18>
identifier 'b'   [LeadingSpace] Loc=<main.m:18:22>
r_paren ')'     Loc=<main.m:18:23>
l_brace '{'     Loc=<main.m:18:24>
int 'int'    [StartOfLine] [LeadingSpace]   Loc=<main.m:19:5>
identifier 'c'   [LeadingSpace] Loc=<main.m:19:9>
equal '='    [LeadingSpace] Loc=<main.m:19:11>
identifier 'a'   [LeadingSpace] Loc=<main.m:19:13>
plus '+'     [LeadingSpace] Loc=<main.m:19:15>
identifier 'b'   [LeadingSpace] Loc=<main.m:19:17>
minus '-'    [LeadingSpace] Loc=<main.m:19:19>
numeric_constant '3'     [LeadingSpace] Loc=<main.m:19:21>
semi ';'        Loc=<main.m:19:22>
r_brace '}'  [StartOfLine]  Loc=<main.m:20:1>
eof ''      Loc=<main.m:20:2>

可以看出,词法分析的时候,将上面的代码拆分一个个token,后面数字表示某一行的第几个字符,例如第一个void,表示第18行第一个字符。

AST-抽象语法树

语法分析,生成语法树(AST,Abstract Syntax Tree):$ clang -fmodules -fsyntax-only -Xclang -ast-dump main.m

通过语法树,我们能知道这个代码是做什么的。

还是刚刚的test函数,生成语法树如下:

|-FunctionDecl 0x7fa1439f5630 <line:18:1, line:20:1> line:18:6 test 'void (int, int)'
| |-ParmVarDecl 0x7fa1439f54b0 <col:11, col:15> col:15 used a 'int'
| |-ParmVarDecl 0x7fa1439f5528 <col:18, col:22> col:22 used b 'int'
| `-CompoundStmt 0x7fa142167c88 <col:24, line:20:1>
|   `-DeclStmt 0x7fa142167c70 <line:19:5, col:22>
|     `-VarDecl 0x7fa1439f5708 <col:5, col:21> col:9 c 'int' cinit
|       `-BinaryOperator 0x7fa142167c48 <col:13, col:21> 'int' '-'
|         |-BinaryOperator 0x7fa142167c00 <col:13, col:17> 'int' '+'
|         | |-ImplicitCastExpr 0x7fa1439f57b8 <col:13> 'int' <LValueToRValue>
|         | | `-DeclRefExpr 0x7fa1439f5768 <col:13> 'int' lvalue ParmVar 0x7fa1439f54b0 'a' 'int'
|         | `-ImplicitCastExpr 0x7fa1439f57d0 <col:17> 'int' <LValueToRValue>
|         |   `-DeclRefExpr 0x7fa1439f5790 <col:17> 'int' lvalue ParmVar 0x7fa1439f5528 'b' 'int'
|         `-IntegerLiteral 0x7fa142167c28 <col:21> 'int' 3`-<undeserialized declarations>

在终端敲出的时候,终端很直观的帮我们用颜色区分。我们可以用图形显示如下:

在这里插入图片描述

LLVM IR

LLVM IR有3种表示形式(本质是等价的)

  • text:便于阅读的文本格式,类似于汇编语言,拓展名.ll, $ clang -S -emit-llvm main.m
  • memory:内存格式
  • bitcode:二进制格式,拓展名.bc, $ clang -c -emit-llvm main.m

我们以text形式编译查看:

; Function Attrs: noinline nounwind optnone ssp uwtable
define void @test(i32, i32) #2 {%3 = alloca i32, align 4%4 = alloca i32, align 4%5 = alloca i32, align 4store i32 %0, i32* %3, align 4store i32 %1, i32* %4, align 4%6 = load i32, i32* %3, align 4%7 = load i32, i32* %4, align 4%8 = add nsw i32 %6, %7%9 = sub nsw i32 %8, 3store i32 %9, i32* %5, align 4ret void
}

IR基本语法

注释以分号 ; 开头
全局标识符以@开头,局部标识符以%开头
alloca,在当前函数栈帧中分配内存
i32,32bit,4个字节的意思
align,内存对齐
store,写入数据
load,读取数据
官方语法参考 https://llvm.org/docs/LangRef.html

应用与实践

我们的开发都是基于源码开发,所以我们首先要进行源码下载和编译。

源码下载

# 下载LLVM
$ git clone https://git.llvm.org/git/llvm.git/# 下载clang
$ cd llvm/tools
$ git clone https://git.llvm.org/git/clang.git/# 备注:clang是llvm的子项目,但是它们的源码是分开的,我们需要将clang放在llvm/tools目录下。

源码编译

这里我们在终端敲出的clang是xcode默认内置clang编译器,我们自己要进行LLVM开发的话,需要编译属于我们自己的clang编译器。

# 首先安装cmake和ninja(先安装brew,https://brew.sh/)
$ brew install cmake
$ brew install ninja# ninja如果安装失败,可以直接从github获取release版放入【/usr/local/bin】中
# https://github.com/ninja-build/ninja/releases# 在LLVM源码同级目录下新建一个【llvm_build】目录(最终会在【llvm_build】目录下生成【build.ninja】$ cd llvm_build
$ cmake -G Ninja ../llvm -DCMAKE_INSTALL_PREFIX=LLVM的安装路径# 备注:生成build.ninja,就表示编译成功,-DCMAKE_INSTALL_PREFIX 表示编译好的东西放在指定的路径,-D表示参数。# 更多cmake相关选项,可以参考: https://llvm.org/docs/CMake.html

接下来依次执行编译、安装指令

$ ninja
# 编译完毕后, 【llvm_build】目录大概 21.05 G(这个真的是好大啊)
$ ninja install

然后到这里我们的编译就完成了。

另一种方式是通过Xcode编译,生成Xcode项目再进行编译,但是速度很慢(可能需要1个多小时)。

# 方法如下:
# 在llvm同级目录下新建一个【llvm_xcode】目录
$ cd llvm_xcode
$ cmake -G Xcode ../llvm

应用与实践的参考

  • libclang、libTooling
    官方参考:https://clang.llvm.org/docs/Tooling.html
    应用:语法树分析、语言转换等
  • Clang插件开发
    官方参考
    1、https://clang.llvm.org/docs/ClangPlugins.html
    2、https://clang.llvm.org/docs/ExternalClangExamples.html
    3、https://clang.llvm.org/docs/RAVFrontendAction.html
    应用:代码检查(命名规范、代码规范)等
  • Pass开发
    官方参考:https://llvm.org/docs/WritingAnLLVMPass.html
    应用:代码优化、代码混淆等
  • 开发新的编程语言
    1、 https://llvm-tutorial-cn.readthedocs.io/en/latest/index.html
    2、https://kaleidoscope-llvm-tutorial-zh-cn.readthedocs.io/zh_CN/latest/

参考:
https://juejin.im/post/5bfba01df265da614273939a

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/532694.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一分钟系列:什么是虚拟内存?

一分钟系列&#xff1a;什么是虚拟内存&#xff1f; 转自&#xff1a;https://mp.weixin.qq.com/s/opMgZrXV-lfgOWrNUMKweg 注&#xff1a;一分钟系列的篇幅都不长&#xff0c;适合吃饭蹲坑、地铁公交上食用&#xff5e; 内存对于用户来说就是一个字节数组&#xff0c;我们可…

11-Kafka

1 Kafka Kafka是一个分布式流式数据平台&#xff0c;它具有三个关键特性 Message System: Pub-Sub消息系统Availability & Reliability&#xff1a;以容错及持久化的方式存储数据记录流Scalable & Real time 1.1 Kafka架构体系 Kafka系统中存在5个关键组件 Producer…

虚拟内存精粹

虚拟内存精粹 标题&#xff1a;虚拟内存精粹 作者&#xff1a;潘建锋 原文&#xff1a;HTTPS://strikefreedom.top/memory-management–virtual-memory 导言 虚拟内存是当今计算机系统中最重要的抽象概念之一&#xff0c;它的提出是为了更加有效地管理内存并且降低内存出错的概…

深度学习自动编译和优化技术调研

深度学习自动编译和优化技术调研 转自&#xff1a;https://moqi.com.cn/blog/deeplearning/ 作者&#xff1a;墨奇科技全栈开发 在墨奇科技&#xff0c;我们需要将一些包含深度神经网络&#xff08;DNN&#xff09;的 AI 算法移植到边缘端的设备&#xff0c; 这些设备往往使用 …

Copy-On-Write COW机制

Copy-On-Write COW机制 转自&#xff1a;https://zhuanlan.zhihu.com/p/48147304 作者&#xff1a;Java3y 前言 只有光头才能变强 在读《Redis设计与实现》关于哈希表扩容的时候&#xff0c;发现这么一段话&#xff1a; 执行BGSAVE命令或者BGREWRITEAOF命令的过程中&#xff0c…

第2章线性表的基本使用及其cpp示例(第二章汇总,线性表都在这里)

2.1线性表的定义和特点 【类型定义&#xff1a; *是n个元素的有限序列 *除了第一个元素没有直接前驱和最后一个没有直接后驱之外&#xff0c;其余的每个元素只有一个直接前驱和直接后驱&#xff1b; &#xff08;a1,a2…an&#xff09; 【特征&#xff1a; *有穷性&#xff1…

2.3单链表的基本使用及其cpp示例

2.3线性表的链式表现与实现 2.3.1.1单链表 【特点&#xff1a; *用一组任意的存储单元存储线性表的数据元素 *利用指针实现用不同相邻的存储单元存放逻辑上相邻的元素 *每个元素ai&#xff0c;除存储本身信息外&#xff0c;还存储其直接后继的元素&#xff08;后一个元素的地址…

TVM:简介

TVM&#xff1a;简介概述 Apache TVM 是一个用于 CPU、GPU 和机器学习加速器的开源机器学习编译器框架。它旨在使机器学习工程师能够在任何硬件后端上高效地优化和运行计算。本教程的目的是通过定义和演示关键概念&#xff0c;引导您了解 TVM 的所有主要功能。新用户应该能够从…

2.3.3单链表的双向链表

2.3.3双向链表 插入、删除 指在前驱和后驱方向都能游历&#xff08;遍历&#xff09;的线性链表 双向链表的每个结点有两个指针域 【结构】&#xff1a;prior data next 双链表通常采用带头结点的循环链表形式 可理解为首位相接的数据“圈”&#xff0c;每个结点都可以向前…

nvidia-smi 命令详解

nvidia-smi 命令详解 简介 nvidia-smi - NVIDIA System Management Interface program nvidia smi&#xff08;也称为NVSMI&#xff09;为来自 Fermi 和更高体系结构系列的 nvidia Tesla、Quadro、GRID 和 GeForce 设备提供监控和管理功能。GeForce Titan系列设备支持大多数…

2.4一元多项式的表示及相加,含cpp算法

2.4一元多项式的表示及相加 n阶多项式的表示&#xff1a; n阶多项式有n1项 指数按升幂排序 【 优点&#xff1a; 多项式的项数可以动态增长&#xff0c;不存在存储溢出的问题插入&#xff0c;删除方便&#xff0c;不移动元素 【表示&#xff1a; 有两个数据域&#xff0c;一…

TVM:使用Tensor Expression (TE)来处理算子

TVM&#xff1a;使用Tensor Expression (TE)来处理算子 在本教程中&#xff0c;我们将聚焦于在 TVM 中使用张量表达式&#xff08;TE&#xff09;来定义张量计算和实现循环优化。TE用纯函数语言描述张量计算&#xff08;即每个表达式都没有副作用&#xff09;。当在 TVM 的整体…

4-数据结构-串的学习

4.1串类型的定义 1.串&#xff1a;&#xff08;或字符串&#xff09; 串是由多个字符组成的有限序列&#xff0c;记作&#xff1a;S‘c1c2c3…cn’ (n>0) 其中S是串的名字&#xff0c;‘c1c2c3…cn’ 是串值 ci是串中字符 n是串的长度&#xff0c;表示字符的数目 空串&a…

5-数据结构-数组的学习

5.1数组的定义 定义&#xff1a; 由一组类型相同的数据元素构成的有序集合&#xff0c;每个数据元素称为一个数据元素&#xff08;简称元素&#xff09;&#xff0c;每个元素受n&#xff08;n>1&#xff09;个线性关系的约束&#xff0c;每个元素在n个线性关系中的序号i1、…

timm 视觉库中的 create_model 函数详解

timm 视觉库中的 create_model 函数详解 最近一年 Vision Transformer 及其相关改进的工作层出不穷&#xff0c;在他们开源的代码中&#xff0c;大部分都用到了这样一个库&#xff1a;timm。各位炼丹师应该已经想必已经对其无比熟悉了&#xff0c;本文将介绍其中最关键的函数之…

C--数据结构--树的学习

6.2.1二叉树的性质 1.二叉树 性质&#xff1a; 1.若二叉树的层次从1开始&#xff0c;则在二叉树的第i层最多有2^(i-1)个结点 2.深度为k的二叉树最多有2^k -1个结点 &#xff08;k>1&#xff09; 3.对任何一颗二叉树&#xff0c;如果其叶结点个数为n0,度为2的非叶结点个数…

C语言—sort函数比较大小的快捷使用--algorithm头文件下

sort函数 一般情况下要将一组数从的大到小排序或从小到大排序&#xff0c;要定义一个新的函数排序。 而我们也可以直接使用在函数下的sort函数&#xff0c;只需加上头文件&#xff1a; #include<algorithm> using namespace std;sort格式&#xff1a;sort(首元素地址&…

AI编译器与传统编译器的联系与区别

AI编译器与传统编译器的区别与联系 总结整理自知乎问题 针对神经网络的编译器和传统编译器的区别和联系是什么&#xff1f;。 文中提到的答主的知乎主页&#xff1a;金雪锋、杨军、蓝色、SunnyCase、贝壳与知了、工藤福尔摩 笔者本人理解 为了不用直接手写机器码&#xff0…

python学习1:注释\变量类型\转换函数\转义字符\运算符

python基础学习 与大多数语言不同&#xff0c;python最具特色的就是使用缩进来表示代码块&#xff0c;不需要使用大括号 {} 。缩进的空格数是可变的&#xff0c;但是同一个代码块的语句必须包含相同的缩进空格数。 &#xff08;一个tab4个空格&#xff09; Python语言中常见的…

python 学习2 /输入/ 输出 /列表 /字典

python基础学习第二天 输入输出 xinput("输入内容") print(x)input输出&#xff1a; eval :去掉字符串外围的引号&#xff0c;按照python的语法执行内容 aeval(12) print(a)eval输出样式&#xff1a; 列表 建立&#xff0c;添加&#xff0c;插入&#xff0c;删去…