美文网首页
Swift高级进阶-Swift编译过程,”SIL代码“,“IR语

Swift高级进阶-Swift编译过程,”SIL代码“,“IR语

作者: 一眼万年的星空 | 来源:发表于2021-12-13 17:17 被阅读0次

    swift编译过程

    如果不懂LLVM,Clang的同学可以去了解下它的知识点 一些文章中有详细介绍 OC 的编译过程 ,本文来探索一下 Swift 的编译过程。Swift 的编译过程中使用 Swiftc ,与 Clang 一样,SwiftcLLVM编译架构的一个前端。

    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 的编译流程:

    image.png

    与 Clang 相比, LLVM前端的流程中,在AST 和 IR之间,多了一层中间语言SIL ( Swift Intermediate Language ) 这么做的目的是希望弥补一些 Clang 编译器的缺陷,如无法执行一些高级分析,可靠的诊断和优化,而 AST 和LLVM IR 都不是合适的选择。因此,SIL应运而生,用来解决现有的缺陷。

    SIL代码

    SIL 官方文档传送门

    由于 SILSwift 在编译过程中的中间产物, 通过 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>}*
    
    

    [图片上传失败...(image-3be4d6-1639387053332)]

    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语

          本文链接:https://www.haomeiwen.com/subject/hxxsfrtx.html