通过 LLVM IR 看语言特性(1)
时间:2022-07-28
本文章向大家介绍通过 LLVM IR 看语言特性(1),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
前言
本系列文章会展示一些系列源码到 LLVM IR 语言的转换。目标是让我们更好的理解编译器是怎么运作的。
基本类型转换是如何发生的?
首先,我们先从一个最简单的问题开始:我们都知道下面 i 值会因为类型转换变为 1。那么,这种类型转换是如何发生的?
int i = 1.23456;// i=1;
通常来说,它可能是通过下面的一种或者几种方式进行的。下面,我们会通过转换 LLVM IR 的方式进行验证。
- 程序运行时,通过特殊的指令处理将
1.23456
转为1
吗 - 程序运行时,调用某些基础库方法(就像
[obj aMethod]
都会被翻译成objc_msgSend(obj, sel/*@selector(aMethod)*/)
一样) - 程序编译阶段,编译器就已经将
1.23456
转化为1
编译阶段的组成
解答上面的疑问前,为了对新人友好一些,我们还是先回顾一下编译阶段的组成:
-
预编译 对源码执行预处理操作,比如展开
#includes
#defines
-
编译
- 解析预处理后的文件,构建 AST(源码中间语言)
- 根据 AST 产出 LLVM IR(编译中间语言)
- 编译后端 根据目标机器特性,产出汇编码(可读性高于机器码)
- 汇编 将汇编码转化为机器码
- 链接 将多个对象文件组装为单个可执行文件
LLVM IR 是什么?
很明显,所有的源码都会在编译阶段转为 LLVM IR。
LLVM IR 是 LLVM intermediate representation (llvm 中间表示)的简称。
LLVM 除了是一个开源的编译器外,还代表一种基于静态单赋值(SSA)的语言,可以提供类型安全、低级操作、灵活性和代表所有“高级语言”的能力。
这门语言的语法很简单,我们会在后续的文章中逐渐介绍它的一些语法。
基本类型转换实现
首先,我们先通过 clang -S -emit-llvm main.c
命令将文章开头的代码转为 LLVM IR
语言:
// clang -S -emit-llvm main.c
int main() {
int i = 1.23456;
}
我们重点看一下第7行至10行。
我们重点看一下第7行至10行。
- 第 7 行
define dso_local i32 @main() #0
-
define
代表这里定义了一个函数 -
dso_local
是运行时抢占说明符(Runtime Preemption Specifiers
),可以先忽略。 -
i32
代表32位整型,与 C 语言类似,它的返回类型在函数名之前。 -
@main
代表函数名。 LLVM 标识符有两种基本类型:全局和本地。全局标识符(函数、全局变量)以@
字符开头。本地标识符(寄存器名、类型)以%
字符开头。 -
#0
代表属性组。 虽然我们只是简单的定义了一个main
函数。但是,对于编译器,这个函数具有大量的属性。本例中,它的属性是{ noinline nounwind optnone uwtable "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }
。 相信读者很快就能发现,它实际上就是第 13 行的内容。 因为函数的属性很长,又加上很多函数的属性都一样。为了保持可读性,LLVM IR
使用属性组来替代重复出现的属性。
-
- 第 8 行
%1 = alloca i32, align 4
-
%1
代表一个本地变量。我们前面已经提到过%
代表本地标识符。 -
alloca
代表一个内存指令。alloca
指令表示在当前执行的函数的栈帧上分配内存,当此函数返回其调用方时自动释放内存。 -
i32
代表alloca
申请了一个32位整型大小空间 -
align 4
代表alloca
申请的地址会落在4
的边界上
-
- 第 9 行
store i32 1, i32* %1, align 4
-
store
同样是一个内存指令。它标志将值
存到某个地址
。 -
i32 1
代表被存储的值 是32位整形 1。 -
i32* %1
代表地址是前面在栈中申请的位置。 - align 4` 同样代表这个操作必须是按照4对齐的
-
- 第 10 行
ret i32 0
-
ret
是为了将控制权返回调用方。这里是将 整数0 返回给调用方。
-
简单总结一下上面的流程:
- 申请一块空间
- 将
1
存到这块空间
由此可见,本例中,在编译阶段,编译器就已经将 1.23456
转化为 1
扩展阅读
http://llvm.org/docs/LangRef.html
http://llvm.org/docs/LangRef.html#abstract
- Java案例-数组随机数
- Go语言图片处理和生成缩略图的方法
- Python3 怎么将Unicode转中文,以及GBK乱码ÖйúÉÙÊýÃñ×åÌØÉ«´åÕ¯
- 数据结构和算法——旋转打印链表
- C/C++——set的基本操作总结
- PHP基础——字符串的常用操作
- NLP之tfidf与textrank算法细节对比基于结巴分词
- 【Go 语言社区】算法课程 第一季 第4节-汉诺塔
- C/C++——map的基本操作总结
- Python生成词云图,TIIDF方法文本挖掘: 词频统计,词云图
- C/C++——vector的基本操作总结
- 数据库负载急剧提高的应急处理(二) (r9笔记第55天)
- 社团划分——有向图的Label Propagation算法
- Python基础——网络编程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS7如何重置root密码的方法
- Linux下RPM打包制作过程
- linux ssh端口转发的三种方式
- 学习Centos7软raid5的挂载
- linux中crw brw lrw等等文件属性是什么
- centos中文件与权限的基本操作教程
- 在Linux中使用tcpdump命令捕获与分析数据包详解
- easyswoole一键安装脚本及宝塔安装错误问题
- CentOS7系统增加swap的操作方法实例
- iOS逆向之OpenSSH登录iPhone
- linux中SUID,SGID与SBIT的奇妙用途详解
- 详解Linux文件操作知识点
- Linux中nohup与&的用法和区别详解
- Linux中有效地管理进程的8个命令
- Centos7 下安装python3及卸载的教程