Swift高级进阶-Swift编译过程,”SIL代码“,“IR语法”
swift编译过程
如果不懂LLVM,Clang的同学可以去了解下它的知识点 一些文章中有详细介绍 OC 的编译过程 ,本文来探索一下 Swift 的编译过程。Swift 的编译过程中使用 Swiftc ,与 Clang 一样,Swiftc 是LLVM编译架构的一个前端。
swiftc常用命令:
-dump-ast 解析和类型检查源文件 & 转换成 AST
-dump-parse 解析源文件 & 转换成 AST
-emit-assembly 生成汇编文件
-emit-bc 生成 LLVM Bitcode 文件
-emit-executable 生成已链接的可执行文件
-emit-imported-modules 生成已导入的库
-emit-ir 生成 LLVM IR 文件
-emit-library 生成已连接的库
-emit-object 生成目标文件
-emit-silgen 生成 raw SIL 文件(第一个阶段)
-emit-sil 生成 canonical SIL 文件(第2个阶段)
-index-file 为源文件生成索引数据
-print-ast 解析和类型检查源文件 & 转换成更简约的格式更好的 AST
-typecheck 解析和类型检查源文件
swift 的编译流程:
与 Clang 相比, LLVM前端的流程中,在AST 和 IR之间,多了一层中间语言SIL (Swift Intermediate Language ) , 这么做的目的是希望弥补一些 Clang 编译器的缺陷,如无法执行一些高级分析,可靠的诊断和优化,而 AST 和LLVM IR 都不是合适的选择。因此,SIL应运而生,用来解决现有的缺陷。
SIL代码
由于 SIL 是 Swift 在编译过程中的中间产物, 通过 SIL 可以了解swift 底层的实现细节,帮助我们理解一些问题)
源码生成 SIL 的命令如下:
// 将m ain.swift 编译成 SIL 代码
swiftc -emit-sil main.swift
// 将 main.swift 编译成 SIL,并保存到 main.sil 文件中
swiftc -emit-sil main.swift >> main.sil
// 将 main.swift 编译成 SIL的同时, 将命名重整后的符号恢复原样,并保存到 main.sil 文件中
swiftc -emit-sil main.swift | xcrun swift-demangle >> main.sil
SIL 语法和 IR 语法有点相似,常见语法含义:
- load: 读取数据
- sil_global:标记变量为全局变量
- hidden: 标记只针对同一个Swift模块中的对象可见
- alloc_global: 开辟全局变量的内存
- global_addr: 获取全局变量的地址
- ref_element_addr: 获取元素地址
- init_existential_addr: 指令会生成 Existential Container 结构, 包裹着实例变量和协议对应的 PWT
- destroy_addr
- bb0 / bb1 ... : basic block 数字,表示一个代码块,SIL中没有分支语句,只有入口和出口
- alloc_ref / dealloc_ref: 开辟/释放内存
- function_ref: 获取直接派发函数地址.
- class_method: 通过函数表获取方法.
- witness_method: 通过 PWT 获取对应的函数地址
- objc_method : 获取OC 方法地址
- apply:调用函数
- store A to B : 把A 的值存储到B中。
- begin_access / end_access: 开始、结束访问
- [modify] / [read] / [deinit] :修改型访问、读取型访问、删除型访问
- [dynamic]:动态访问
- [static]:静态访问
- retain_value: 引用计数 + 1
- release_value: 引用计数 - 1
- metatype 获得元类型
- @thick 描述元类型代表的形式,是引用 对象类型或是其子类,
- @thin 代表一个确切的值 类型,不需要存储,
- $ : 类型标识
- %: 表示寄存器,类似局部常量,赋值后不可修改。如果再需要新的寄存器,就增加寄存器编号,这样操作有利于编译器的优化;后续进行降级操作 时,才会把这些带编号的虚拟寄存器 转换成对应体系结构的真实寄存器。
- @ : SIL中所有标识符均以@符号开头
- @main 方法名字是 main
- @_hasStorage 标识属性是存储属性
- @_hasInitialValue 标识属性有初始值
- @owned 代表函数接收者负责销毁返回值
- @convention 这个标识用于明确指定当函数调用时参数和返回值应该如何被处理
- @convention(c) 表示使用C函数的方式进行调用
- @convention(swift) 纯Swift函数的默认调用方式
- @convention(method) 柯里化的函数调用方式
- @convention(witness_method) 协议方法调用,它等同于convention(method),除了在处理范型类型参数时
- @convention(objc_method) Objective-C方式调用
常见ARM64汇编指令
bl : 地址跳转
blr : 带返回的地址跳转, 跳转回指令后面跟随寄存器中保存的地址
mov: 把一个寄存器里的值,复制到另一个寄存器
mov x0, x8 把 x0 的值,复制到 x8 中
ldr: 把内存中的值,读取到寄存器中
ldr x0, [x0, x8] 把 x0 + x8 的地址里面的值,写到 x0 寄存器 中
str: 把寄存器里面的值写入到内存中
str x0, [x0, x8] 把寄存器 x0 的值,写入到 x0 + x8 的地址中
4. 常见的 IR 语法:
官方文档传送门☞官方文档
- @ 全局标识
- % 局部标识
- alloca 开辟空间
- align 内存对齐
- i32 32位, 4字节
- store 写入内存
- load 读取数据
- call 调用函数
- ret 返回
bitcast
以新的数据类型去读取原类的值(也就是类型转换)
//以ty2 的数据类型 去读取 ty 类型的 value,读取步长是ty2
<result> = bitcast <ty> <value> to <ty2>
// eg: bitcast (%struct.str* @global to i8*)
// 以i8* 的数据类型 去读取 struct.str* 类型的 global,读取步长是i8*
getelementptr
- 指令(GER):进行地址计算,来获取复合数据结构子元素的地址,并不访问内存。
- 至少要有2个参数
- 第一个参数是类型 & 地址,也就是开始读取内存的变量首地址
- 第二个及以后的参数,表示要进行计算的参数,比如结构体或者数据的第几个元素
// 获取复合数据类型的指定元素的地址
getelementptr <ty> <value>, <index>, <index>, ...
如果 p 是一个数组, 第二个参数表示获取数组里索引是1的值,也就是p[1]
getelementptr %struct.munger_struct* %P, i32 1
如果 p 是一个数组,里面放了几个结构体, 第二个参数依旧表示p[1],第3个参数表示获取数组索引为1的结构体的索引为0的元素,即p[1][0]
getelementptr %struct.munger_struct* %P, i32 1, i32 0
如果结构体内还有数据类型的嵌套,再获取其内部嵌套的值,可以继续追加参数, 所以说参数至少有2个,第一个是数据的首地址,第二个参数开始都表示索引值。
官网:
struct munger_struct {
int f1;
int f2;
};
void munge(struct munger_struct *P) {
P[0].f1 = P[1].f1 + P[2].f2;
}
...
munger_struct Array[3];
...
munge(Array);
转成IR代码:
void %munge(%struct.munger_struct* %P) {
entry:
%tmp = getelementptr %struct.munger_struct* %P, i32 1, i32 0
%tmp = load i32* %tmp
%tmp6 = getelementptr %struct.munger_struct* %P, i32 2, i32 1
%tmp7 = load i32* %tmp6
%tmp8 = add i32 %tmp7, %tmp
%tmp9 = getelementptr %struct.munger_struct* %P, i32 0, i32 0
store i32 %tmp8, i32* %tmp9
ret void
}
getelementptr inbounds:
- 与 getelementptr 类似, 不过它将类型与值分开了,分别作为2个参数。所以这个指令,至少有3个参数,前两个是类型和值,第三个开始是索引参数
<result> = getelementptr inbounds <ty>, <ty>* <ptrval>{, [inrange] <ty> <idx>}*
extractvalue
// 获取复合数据类型的指定元素的值
<result> = extractvalue <aggregate type> <val>, <idx>{, <idx>}*
insertvalue
// 像复合类型中的指定索引位置插入值
<result> = insertvalue <aggregate type> <val>, <ty> <elt>, <idx>{, <idx>}* ;
// insertvalue %struct.tm %ret_val.fca.0.insert, i32 %b1, 1
// 向类型为%struct.tm的%ret_val.fca.0.insert变量的索引为 1 的位置,插入%b1
结构体
%T = type {<type list>}
// eg: %swift.refcounted = type { %swift.type*, i64 }
数组
[<elementnumber> x <elementtype>]
// eg: alloca[24 x i8], align8 表示数组里面放了24个 i8 的整数
指针
<type> *
//eg: i64* 表示64 位的整型
青山不改,绿水长流,后会有期,感谢每一位佳人的支持!
Swift高级进阶-Swift编译过程,”SIL代码“,“IR语法”的更多相关文章
- Swift高级语法学习总结(转)
Swift高级语法学习总结 1.函数 1.1 func funcNmae()->(){} 这样就定义了一个函数,它的参数为空,返回值为空,如果有参数和返回值直接写在两个括号里就可以了 1.2 参 ...
- Swift高级语法学习总结
Swift基础语法学习总结Swift高级语法学习总结Swift语法总结补充(一) 1.函数 1.1 func funcNmae()->(){} 这样就定义了一个函数,它的参数为空,返回值为空,如 ...
- .NET 代码编译过程
作为一种代码指令平台,Microsoft .NET比微软公司先前推出的其他技术平台要来得更为复杂.由于.NET提供了对多种编程语言以及(在理论上说)多重平台的支持,这就需要在传统的两个代 码层添加一个 ...
- .Net高级进阶,在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码?
本文将通过场景例子演示,来通俗易懂的讲解在复杂的业务逻辑下,如何以最简练的代码,最直观的编写事务代码. 通过一系列优化最终达到两个效果,1.通过代码块来控制事务(分布式事务),2.通过委托优化Tran ...
- python代码是解释型语言,为什么还有编译过程?
Python 代码在运行前,会先编译(翻译)成中间代码,每个 .py 文件将被换转成 .pyc 文件,.pyc 就是一种字节码文件,它是与平台无关的中间代码,不管你放在 Windows 还是 Linu ...
- C:编译过程、目标代码文件、 可执行文件和库
C编程的基本策略是, 用程序把源代码文件转换为可执行文件(其中包含可直接运行的机器语言代码). 典型的C实现通过编译和链接两个步骤来完成这一过程. 编译器把源代码转换成中间代码, 链接器把中间代码和其 ...
- iOS Swift 开发语言之初接触,纯代码创建UIView,UITableView,UICollectionView
1. 初始化Label设置AttributeString override func viewDidLoad() { let label = UILabel(frame:CGRect(x:,y:,wi ...
- C语言代码区错误以及编译过程
C语言代码区错误 欲想了解C语言代码段会有如何错误,我们必须首先了解编译器是如何把C语言文本信息编译成为可以执行的机器码的. 背景介绍 测试使用的C语言代码 导入标准库,定义宏变量,定义结构体,重命名 ...
- 我也来谈javascript高级编程之:javascript函数编译过程
前言 题目有点大,其实也就是手痒...跟大家来扯一下javascript编译过程. 那么到底什么是“编译”呢 这个...本人文笔太差,我还是直接举例子吧. 相信玩过js童鞋应该都看过下面这样一个面试题 ...
- iOS 编译过程原理(1)
一.前言 一般可以将编程语言分为两种,编译语言和直译式语言. 像 C++.Objective-C 都是编译语言.编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在 CPU 上执行,所以 ...
随机推荐
- [转帖]shell删除文件前几行或后几行
https://www.cnblogs.com/1394htw/p/14852207.html shell删除文件前几行或后几行 #!/bin/bash #删除前两行 sed -i '1,2d' fi ...
- 部分操作系统不支持 zip unzip 大于4G的文件.
https://www.ibm.com/mysupport/s/question/0D50z00006PD9XXCA1/bad-zipfile-offset-local-header-sig-erro ...
- ELK运维文档
Logstash 目录 Logstash Monitoring API Node Info API Plugins Info API Node Stats API Hot Threads API lo ...
- Java单元测试浅析(JUnit+Mockito)
作者:京东物流 秦彪 1. 什么是单元测试 (1)单元测试环节: 测试过程按照阶段划分分为:单元测试.集成测试.系统测试.验收测试等.相关含义如下: 1) 单元测试: 针对计算机程序模块进 ...
- 重新学习一下new Date()
new Date()你知道多少 很多小伙伴可能都知道, Date是js中的一个内置对象,用于处理日期和时间. 当你调用 new Date() 时,它会创建一个新的日期(Date) 对象. 表示当前本地 ...
- vue3中mixins的使用
vue3-mixins 在开发的过程中我们会遇见相同或者相似的逻辑代码. 可以通过vue的 mixin 功能抽离公共的业务逻辑, 然后通过impor再组件中引入.通过mixins注册进来. 这样我们就 ...
- 独立安装VS的C++编译器build tools
Microsoft C++ 生成工具 Microsoft C++ 生成工具 - Visual Studio Microsoft C++ 生成工具通过可编写脚本的独立安装程序提供 MSVC 工具集,无需 ...
- JVM底层原理
目录 1.类加载器与ClassFileFormate 2.JVM内存模型 3.对象在JVM中的创建与内存分配 4.对象引用与垃圾回收算法 5.JVM垃圾回收 6.G1垃圾回收器 7.ZGC垃圾回收器
- C/C++ 获取主机网卡MAC地址
MAC地址(Media Access Control address),又称为物理地址或硬件地址,是网络适配器(网卡)在制造时被分配的全球唯一的48位地址.这个地址是数据链路层(OSI模型的第二层)的 ...
- 15.1 套接字通过域名取IP地址
首先我们来实现一个DNS查询功能,该功能的目的是传入一个网站域名自动将该域名解析为对应的IP地址,该功能的实现依赖于gethostbyname函数,该函数将主机名作为参数,并返回一个指向hostent ...