
我们继续研究Swift编译器。 本部分专门介绍Swift中级语言。
如果您没有看过以前的文章,建议您点击链接阅读:
西尔根
下一步是将类型化的AST转换为原始SIL。 Swift中间语言(SIL)是专门为Swift创建的中间表示形式。 在文档中可以找到所有说明的说明 。
SIL具有SSA表格。 静态单一分配(SSA)-一种代码表示,其中每个变量仅分配一次值。 它是通过添加其他变量从常规代码创建的。 例如,使用数字后缀表示每次分配后变量的版本。
由于这种形式,编译器更容易优化代码。 下面是有关伪代码的示例。 显然,第一行是不必要的:
a = 1 a = 2 b = a
但这仅适用于我们。 为了教导编译器确定这一点,必须编写非平凡的算法。 但是使用SSA,这要容易得多。 现在,即使对于简单的编译器,很明显, 也不会使用变量a1的值,并且可以删除此行:
a1 = 1 a2 = 2 b1 = a2
SIL允许您对AST阶段难以完成或无法完成的Swift代码进行特定的优化和检查。
使用SIL Generator
要生成SIL,请使用-emit-silgen标志 :
swiftc -emit-silgen main.swift
命令的结果:
sil_stage raw import Builtin import Swift import SwiftShims let x: Int // x sil_global hidden [let] @$S4main1xSivp : $Int // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$S4main1xSivp // id: %2 %3 = global_addr @$S4main1xSivp : $*Int // user: %8 %4 = metatype $@thin Int.Type // user: %7 %5 = integer_literal $Builtin.Int2048, 16 // user: %7 // function_ref Int.init(_builtinIntegerLiteral:) %6 = function_ref @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %7 %7 = apply %6(%5, %4) : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int // user: %8 store %7 to [trivial] %3 : $*Int // id: %8 %9 = integer_literal $Builtin.Int32, 0 // user: %10 %10 = struct $Int32 (%9 : $Builtin.Int32) // user: %11 return %10 : $Int32 // id: %11 } // end sil function 'main' // Int.init(_builtinIntegerLiteral:) sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
像LLVM IR一样,SIL可以作为源代码输出。 您可以在其中找到,在此阶段添加了Swift模块Builtin,Swift和SwiftShims的导入。
尽管您可以在Swift的全局范围内直接编写代码,但SILGen仍会生成主要函数,即程序的入口点。 所有代码都位于其中,除了声明常量外,因为它是全局的,应该可以在任何地方访问。
大多数线具有相似的结构。 左侧是伪寄存器,用于保存指令结果。 接下来-指令本身及其参数,最后-注释,指示将使用该寄存器的寄存器。
例如,在此行上创建了一个Int2048类型的整数文字,值16。此文字存储在第五个寄存器中,将用于计算第七个的值:
%5 = integer_literal $Builtin.Int2048, 16 // user: %7
函数声明以关键字sil开头。 以下是带有@前缀的名称,调用约定,参数,返回类型和功能代码。 对于Int.init初始值设定项(_builtinIntegerLiteral :) ,当然没有指定,因为此函数来自另一个模块,只需要声明即可,但无需定义。 美元符号表示类型指示的开头:
// Int.init(_builtinIntegerLiteral:) sil [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int
调用约定表示如何正确调用函数。 这是生成机器代码所必需的。 这些原理的详细描述超出了本文的范围。
初始化程序的名称以及结构,类,方法,协议的名称都会失真(名称重整)。 这样可以立即解决几个问题。
首先,它允许在不同的模块和嵌套实体中使用相同的名称。 例如,对于第一种方法fff ,使用名称S4main3AAAV3fffSiyF ,对于第二种方法,使用S4main3BBBVVffffSiyF :
struct AAA { func fff() -> Int { return 8 } } struct BBB { func fff() -> Int { return 8 } }
S表示Swift,4是模块名称中的字符数,3是类名称。 在文字初始值设定项中, Si表示标准类型Swift.Int。
其次,将名称和函数参数的类型添加到名称中。 这允许使用重载。 例如,对于第一种方法, 将生成S4main3AAAV3fff3iiiS2i_tF ,对于第二种方法-S4main3AAAV3fff3dddSiSd_tF :
struct AAA { func fff(iii internalName: Int) -> Int { return 8 } func fff(ddd internalName: Double) -> Int { return 8 } }
在参数名称之后,将指示返回值的类型,然后是参数类型。 但是,未显示其内部名称。 不幸的是,在Swift中没有有关名称改写的文档,其实现可能随时更改。
函数名称后跟其定义。 它由一个或多个基本块组成。 基本块是具有一个入口点和一个出口点的指令序列,其中不包含分支指令或提早退出的条件。
main函数具有一个基本单元,该基本单元将传递给该函数的所有参数作为输入并包含其所有代码,因为其中没有分支:
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
我们可以假定,用括号括起来的每个合并范围都是一个单独的基本单位。 假设代码包含一个分支:
// before if 2 > 5 { // true } else { // false } // after
在这种情况下,将至少为以下生成4个基本块:
- 分支之前的代码,
- 表达式为真的情况
- 表达式为假的情况
- 分支后的代码。
cond_br-条件跳转的指令。 如果伪寄存器%14的值为真,则执行到块bb1的转换。 如果不是,则在bb2中 。 br-无条件跳转,开始执行指定的基本块:
// before cond_br %14, bb1, bb2 // id: %15 bb1: // true br bb3 // id: %21 bb2: // Preds: bb0 // false br bb3 // id: %27 bb3: // Preds: bb2 bb1 // after
源代码:
分析在最后阶段获得的原始中间表示形式的正确性,并将其转换为规范的:标记为透明的函数是内联的 (函数调用由其主体替换),计算常量表达式的值,检查该函数以确保返回值的函数在所有代码分支中执行此操作,依此类推。
这些转换是强制性的,即使禁用代码优化也可以执行。
佳能SIL一代
要生成规范的SIL,请使用-emit-sil标志:
swiftc -emit-sil main.swift
命令的结果:
sil_stage canonical import Builtin import Swift import SwiftShims let x: Int // x sil_global hidden [let] @$S4main1xSivp : $Int // main sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 { bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>): alloc_global @$S4main1xSivp // id: %2 %3 = global_addr @$S4main1xSivp : $*Int // user: %6 %4 = integer_literal $Builtin.Int64, 16 // user: %5 %5 = struct $Int (%4 : $Builtin.Int64) // user: %6 store %5 to %3 : $*Int // id: %6 %7 = integer_literal $Builtin.Int32, 0 // user: %8 %8 = struct $Int32 (%7 : $Builtin.Int32) // user: %9 return %8 : $Int32 // id: %9 } // end sil function 'main' // Int.init(_builtinIntegerLiteral:) sil public_external [transparent] [serialized] @$SSi22_builtinIntegerLiteralSiBi2048__tcfC : $@convention(method) (Builtin.Int2048, @thin Int.Type) -> Int { // %0 // user: %2 bb0(%0 : $Builtin.Int2048, %1 : $@thin Int.Type): %2 = builtin "s_to_s_checked_trunc_Int2048_Int64"(%0 : $Builtin.Int2048) : $(Builtin.Int64, Builtin.Int1) // user: %3 %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4 %4 = struct $Int (%3 : $Builtin.Int64) // user: %5 return %4 : $Int // id: %5 } // end sil function '$SSi22_builtinIntegerLiteralSiBi2048__tcfC'
在这个简单的示例中几乎没有变化。 要查看优化器的实际工作,您需要使代码复杂一点。 例如,添加以下内容:
let x = 16 + 8
在他的原始SIL中,您可以找到这些文字的附加内容:
%13 = function_ref @$SSi1poiyS2i_SitFZ : $@convention(method) (Int, Int, @thin Int.Type) -> Int // user: %14 %14 = apply %13(%8, %12, %4) : $@convention(method) (Int, Int, @thin Int.Type) -> Int // user: %15
但是在规范中不再存在。 而是使用24的常数:
%4 = integer_literal $Builtin.Int64, 24 // user: %5
源代码:
Sil优化
如果启用了优化,则会应用其他特定于Swift的转换。 其中包括泛型的专业化 (针对特定类型的参数优化泛型代码),非虚拟化 (将动态调用替换为静态调用),内联, ARC优化等等。 对这些技术的说明不适合已经过多的文章。
源代码:
由于SIL是一种Swift功能,因此我这次没有显示实现示例。 在进行LLVM IR生成时,我们将在下一部分返回到括号编译器。