本文会介绍 Kotlin Compiler 中 IR 是什么,以及操作 IR 的 api 是什么 在了解 IR 相关 api 后,可以继续学习 IR Transform Plugin 教程,自己动手制作一个 Transform IR 的插件
Kotlin IR 是什么
IR 全称是 intermediate representation,表示编译过程中的中间信息,由编译器前端
对源码分析后得到,随后会输入到后端
进一步编译为机器码
IR 可以有一系列的表现方式,由高层表示逐渐下降(lowering)到低层
我们所讨论的 Kotlin IR 是抽象语法树结构(AST),是比较高层的 IR 表示类型。
有了完备的 IR,就可以利用不同的 后端
,编出不同的目标代码,比如 JVM
的字节码,或者运行在 iOS
的机器码,这样就达到了跨端的目的,想了解更多 kotlin 跨端技术可以参考 kotlinlang.org/docs/multip…
如何查看 Kotlin IR 结构
IR 树示例
我们先看一个例子,直观感受一下 IR 树是什么样子的
fun main() {
println("Hello, World!")
}
上述代码在转成 IR 信息后,是这样的
MODULE_FRAGMENT name:<main>
FILE fqName:<root> fileName:/var/folders/lt/k622ndqs14l7_tcxst93z3cm0000gp/T/Kotlin-Compilation7335327567848552666/sources/main.kt
FUN name:main visibility:public modality:FINAL <> () returnType:kotlin.Unit
BLOCK_BODY
CALL 'public final fun println (message: kotlin.Any?): kotlin.Unit [inline] declared in kotlin.io.ConsoleKt' type=kotlin.Unit origin=null
message: CONST String type=kotlin.String value="Hello, World!"
IrElement 和 IrElementVisitor
查看 IR 的 Api 中,最重要的是 IrElement
和 IrElementVisitor
IrElement
是所有 IR 元素的父类,包含两个抽象方法
fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R
fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D): Unit
accept 一般会调用 visitor 对应的 visitXXX 方法 acceptChildren 会依次调用 IR 子元素的 accept 方法
下面看看 IrClass
的具体实现,便于我们理解
override fun <R, D> accept(visitor: IrElementVisitor<R, D>, data: D): R =
visitor.visitClass(this, data) // 调用 visitor 对应的 visitClass
override fun <D> acceptChildren(visitor: IrElementVisitor<Unit, D>, data: D) { // 遍历 IrClass 子元素,并调用对应的 accept 方法
thisReceiver?.accept(visitor, data)
typeParameters.forEach { it.accept(visitor, data) }
declarations.forEach { it.accept(visitor, data) }
}
<D,R> 输入和输出
我们注意到 Visitor 中还包含了 <R, D>
两个泛型参数,D 作为参数传入,R 作为返回值,这分别代表什么意思呢?
D data
表示在 visit ir 时的输入数据,我们可以把想要传递的上下文信息通过这个参数传入,在具体 visit 的过程中使用。
比如,我们可以修改打印 IR 元素前的缩进,或是随便插入什么其他信息都可以
class StringIndentVisitor : IrElementVisitor<Unit, String> {
override fun visitElement(element: IrElement, data: String) {
println("$data${render(element)} {")
element.acceptChildren(this, " $data")
println("$data}")
}
}
R
表示在 accept visitor 时拿到的返回结果,在较少的 Transform ir 场景我们会用到这个能力,其他情况使用 Unit 即可。下面有个真实场景不会用到的例子,但能说明返回值的用法。
// Not as efficient as a while loop, but exemplifies how the output type could be used
class RootParentVisitor : IrElementVisitor<IrDeclarationParent?, Nothing?> {
override fun visitElement(element: IrElement, data: Nothing?): IrDeclarationParent? = null
override fun visitDeclaration(declaration: IrDeclarationBase, data: Nothing?): IrDeclarationParent {
val parent = declaration.parent
return parent.accept(this, null) ?: parent
}
}
另外。我们可以注意到 acceptChildren
接收的 visitor 泛型是 <D, Unit> ,也就是说我们不能在通过 acceptChildren 遍历 IR 树的过程中获取返回值,如果你需要在过程中保存一些信息,可以从外部传入一个变量保存。比如
class CollectingVisitor(
private val elements: MutableList<IrElement>
) : IrElementVisitor<Unit, Nothing?> {
override fun visitElement(element: IrElement, data: Nothing?) {
elements.add(element)
element.acceptChildren(this, data)
}
}
fun collect(element: IrElement) = buildList<IrElement> {
element.accept(CollectingVisitor(this), null)
}
自顶向下遍历 IR 树
了解了 IrElement 和 IrElementVisitor 后,我们可以自己实现一个类似 IrElement.dump()
的功能。通过重写 visitElement,在其中进行 acceptChildren,就能自顶向下递归遍历 IR 树,通过 element.render()
打印每个 IR 元素,代码如下
class RecursiveVisitor: IrElementVisitor<Unit, Nothing?> {
override fun visitElement(element: IrElement, data: Nothing?) {
println("visitElement:: ${element.render()}")
element.acceptChildren(this, data)
}
}
其他 IR 操作 API
创建方法调用 IR
// 通过传入方法全限定名找到具体方法 ir,除了方法还有找 class 等 api
val printlnFunc = irPluginContext.referenceFunctions(FqName("kotlin.io.println")).single {
val parameters = it.owner.valueParameters
parameters.size == 1 && parameters[0].type == irPluginContext.irBuiltIns.anyNType
}
//通过传入 IrFunction 到 irCall 完成方法的调用,其中 putValueArgument 可以向目标函数传入参数
irCall(printlnFunc).also {
it.putValueArgument(0, irString("Hello, World"))
}
拼接字符串
val concat = irConcat()
concat.addArgument(irString("a"))
concat.addArgument(irString("b"))
创建局部变量 IR,获取变量值
val i = irTemporary(irConcat().also { // i = "Hello, World"
it.addArgument(irString("Hello, World"))
})
irGet(i) // 得到 i 的值 "Hello, World"
修改方法体
DeclarationIrBuilder(irPluginContext, irFunction.symbol).irBlockBody {
+irCall(...) // 可通过加号 插入自己的方法调用
for(statement in irBody.statements) { //原有方法体中的表达式
+statement
}
}
持续补充...
参考
Writing Your Second Kotlin Compiler Plugin, Part 2 — Inspecting Kotlin IR
Writing Your Second Kotlin Compiler Plugin, Part 3 — Navigating Kotlin IR