Kotlin/Wasm Internal その2
compiler/ir/backend.wasm/.../kotlin/backend/wasm/compiler.kt
の compileWasm
collectInterfaceTables
(generateModule は関係ない)
acceptVisitor(irModuleFragment, interfaceCollector)
acceptVoid
は data
として null を渡すという話。collectInterface の visitor method で何をするかが分かるはず
interfaceCollector
override fun visitClass(declrataion: IrClass) { ... }
isExternal
, getWasmArrayAnnotation != null
, isInterface
, isAbstractOrSealed
の場合は何もしない。ここの処理必要? どうせ これらに interfaces はないのでは?
val classMetadata = declarationGenerator.context.getClassMetadata(declaration.symbol)
if (classMetadata.interfaces.isNotEmpty()) {
hierarchyDisjointUnions.addUnion(classMetadata.interfaces.map { it.symbol })
}
class が実装している interface を集める。それぞれの interface を
hierarchyDisjointUnions.addUnion
hierarchyDisjointUnions
hierarchyDisjointUnions.compress()
DeclarationGenerator
Array の特別対応
kotlin array を wasm の array にするっぽい。 getWasmArrayAnnotation
ってなんですか? (TODO)
(array (field $field (mut <type>)))
こういう感じになるんかな
context.defineGcType(symbol, wasmArrayDeclaration)
に登録。これが後でどうなるのやら...?
ところで array に生えてるメソッドなんかはどうなるんだろう?
interface declaration の場合
createVirtualTableStruct
(名前は $nameStr.itable
) で is final
createVirtualTableStruct
method 一覧と super type と name を受け取って、vtable の型を構築。割りと素直な実装
結果を context.defineVTableGcType(symbol, vtableStruct)
type = WasmRefNullType(WasmHeapType.Type(context.referenceFunctionType(it.function.symbol))),
これを詳しく見ていく
context.referenceFunctionType(it.function.symbol)
から
data class WasmFunctionType(
val parameterTypes: List<WasmType>,
val resultTypes: List<WasmType>
) : WasmTypeDeclaration("")
WasmSymbol
で ir に対する symbol を作っていく。この段階では "unbound" な symbol、このあと linkWasmModule
で symbol に対する関数を bind していく。
WasmHeapType.Type(...)
これは何?
ref null
でラップするだけ
なんか、いまいちこの段階では...何? あとのlinkingの段階でいろいろ埋まっていくのだろうか?
いったん compiledWasmModule.linkWasmCompiledFragments()
で symbol 解決されていくさまを見たい
ここだね、defined
にもいろいろ値が入っているはずなのね、例えば
bind(functions.unbound, functions.defined)
val functions =
ReferencableAndDefinable<IrFunctionSymbol, WasmFunction>()
functions.define(...)
みたいなのがあるはずなのよね。
defineFunction
はどこで呼ばれてるのかな? 二箇所で両方とも DeclarationGenerator.kt
の visitFunction
内
imported function
@WasmImport
annotation もしくは kotlin.JsFun
fun IrAnnotationContainer.getJsFunAnnotation(): String? =
getAnnotation(FqName("kotlin.JsFun"))?.getSingleConstStringArgument()
data class WasmImportDescriptor(
val moduleName: String,
val declarationName: String
)
(import "$moduleName" "$declarationName"
(func $name (param $a i32) (param $b i32) (result i32)))
こんな感じになるのかね
普通のパス
class Defined(
name: String,
type: WasmSymbolReadOnly<WasmFunctionType>,
val locals: MutableList<WasmLocal> = mutableListOf(),
val instructions: MutableList<WasmInstr> = mutableListOf()
) : WasmFunction(name, type)
locals と instructions は後で設定されそう
こんな感じ、visitFunction
は interface の function declaration (実装なし)も実行されそうなので、それなら functions.unbound
と functions.definitions
は揃ってるはず
visitClass
interface じゃない場合
createVTable
-
createVirtualTableStruct
からのcontext.defineVTableGcType
で vtable の型定義や unbound type symbol の生成 -
context.referenceVTableGcType(symbol)
などで、vtable の型への参照のシンボルを生成 -
metadata.virtualMethods.forEachIndexed
-
.filterVirtualFunctions()
で.filter { it.dispatchReceiverParameter != null }
とかで filter してんのかな、interface から来たメソッドは itable から呼ばれるわけなので、vtable に登録しなくても良い気がするんだけど... - https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ClassInfo.kt#L99-L119
- https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/ClassInfo.kt#L158-L163
-
-
buildInstr(WasmOp.REF_FUNC, location, WasmImmediate.FuncIdx(context.referenceFunction(method.function.symbol)))
- buildInstr
-
context.referenceFunction(method.function.symbol)
でそのメソッドへの参照のsymbolを作る(unbound) -
WasmImmediate.FuncIdx
で 関数に id をつける - wasmIrToText の appendModuleFieldReference でいい感じに名前がつけられていく?
-
ref.func
で 関数参照
buildStructNew(vTableTypeReference, location)
これで最終的に
ref.func $Base.foo___fun_62
ref.func ...
struct.new $Base.vtable___type_26
みたいなのができる。
最後にglobalに定義
context.defineGlobalVTable(
irClass = symbol,
wasmGlobal = WasmGlobal(vtableName, vTableRefGcType, false, initVTableGlobal)
)
これでこんな感じになる
(global $Base.vtable___g_24 (ref $Base.vtable___type_26)
ref.func $Base.foo___fun_62
struct.new $Base.vtable___type_26)
createClassITable
https://zenn.dev/tanishiking/articles/2023-12-kotlin-wasm-mapping#interface-dispatch こういう感じで
interface Base1 {
fun foo(): Int
}
interface Base2 {
fun bar(): Int
}
interface Base: Base1, Base2
class Derived: Base {
override fun foo(): Int = 1
override fun bar(): Int = 1
}
(type $classITable___type_20 (struct
(field (ref null $Base1.itable___type_13))
(field (ref null $Base2.itable___type_14))
(field (ref null $Base.itable___type_15))))
(type $Base1.itable___type_13 (struct (field (ref null $____type_55))))
(type $Base2.itable___type_14 (struct (field (ref null $____type_55))))
(type $Base.itable___type_15 (struct))
こうなって欲しいはず。
val supportedInterface = metadata.interfaces.firstOrNull()?.symbol ?: return
これ1つ目の interface 取得してるだけだよね? なにこれ? 複数の interface を実装していた場合はどうなる? IR の段階ではそれらを implements
している interface がいる感じになるんですか? TODO
createDeclarationByInterface
val iFacesUnion = hierarchyDisjointUnions[iFace]
これで iFace
の super class? interface? を全部取得する?
hierarchyDisjointUnions は class や interface の親子関係を保持ているのかな?
val struct = WasmStructDeclaration(
name = "classITable",
fields = fields,
superType = null
)
このへんでこういう感じの itable の型を作る
(type $classITable___type_20 (struct
(field (ref null $Base1.itable___type_13))
(field (ref null $Base2.itable___type_14))
(field (ref null $Base.itable___type_15))))
initITableGlobal
class が その interface を直接実装しているかどうか
if (!metadata.interfaces.contains(iFace.owner)) {
buildRefNull(iFaceVTableGcNullHeapType, location)
continue
}
何?
metadata.interfaces.contains(iFace.owner)
その class が iFace
を直接実装しているかどうか
interface B
open class Super: B
class Sub: Super()
こういう場合 Sub
は B
を直接実装しているとは言わない。
interface A
interface B
interface C: A, B
class Derived: C
この場合は Derived
は A
, B
, C
を directry implement している。(ただの super-interface だしね)
直接実装していない場合は ref.null
interface をiterate していく
interface の 各メソッドについて、concrete virtual method で signature が一致するものをその class から探していく。
val classMethod: VirtualMethodMetadata? = metadata.virtualMethods
find { it.signature == method.signature && it.function.modality != Modality.ABSTRACT }
// 見つからなかったらerror
if (classMethod == null && !allowIncompleteImplementations) {
error("Cannot find interface implementation of method ${method.signature} in class ${klass.fqNameWhenAvailable}") }
見つけたら virtual function への reference のシンボルを作りつつ、ref.func
を itable に登録
見つけられなかったら ref.null
(いる?これ)
if (classMethod != null) {
val functionTypeReference = context.referenceFunction(classMethod.function.symbol)
buildInstr(WasmOp.REF_FUNC, location, WasmImmediate.FuncIdx(functionTypeReference))
} else {
//This erased by DCE so abstract version appeared in non-abstract class
buildRefNull(WasmHeapType.Type(context.referenceFunctionType(method.function.symbol)), location)
}
最後に buildStructNew(iFaceVTableGcType, location)
。
ref.func $Derived.foo___fun_62
struct.new $Base1.itable___type_13
これを各 interface に対してやっていくと
(global $Derived.classITable___g_27 (ref $classITable___type_20)
ref.func $Derived.foo___fun_62
struct.new $Base1.itable___type_13
ref.func $Derived.bar___fun_63
struct.new $Base2.itable___type_14
struct.new $Base.itable___type_15
struct.new $classITable___type_20)
こうなる
visitClass
続き
val vtableRefGcType = WasmRefType(WasmHeapType.Type(context.referenceVTableGcType(symbol)))
val classITableRefGcType = WasmRefNullType(WasmHeapType.Simple.Struct)
ここで vtable
と itable
の型を取得するんだけど
何で classITable
の型は struct
なんですか??? WasmHeapType.Type(context.referenceClassITableGcType(symbol))
にして、call site で cast してるの?
fields.add(WasmStructFieldDeclaration("vtable", vtableRefGcType, false))
fields.add(WasmStructFieldDeclaration("itable", classITableRefGcType, false))
declaration.allFields(irBuiltIns).mapTo(fields) {
WasmStructFieldDeclaration(
name = it.name.toString(),
type = context.transformFieldType(it.type),
isMutable = true
)
}
これで
(type $Foo___type_36 (sub $kotlin.Any___type_13 (struct
(field (ref $Foo.vtable___type_26))
(field (ref null struct))
(field (mut i32))
(field (mut i32))
(field (mut i32)) ;; bar
(field (mut i32))))) ;; baz
こうなっていく? それにしては name
はどこにも出現しないが...
そこから
val superClass = metadata.superClass
val structType = WasmStructDeclaration(
name = nameStr,
fields = fields,
superType = superClass?.let { context.referenceGcType(superClass.klass.symbol) }
)
context.defineGcType(symbol, structType)
こういう感じでtype symbolを登録して
context.generateTypeInfo(symbol, binaryDataStruct(metadata))
この typeinfo はどう使われるんでしょうね〜 TODO
visitFunction 編
val function = WasmFunction.Defined(...)
あたりまでは https://zenn.dev/tanishiking/scraps/f98bd756e29c1a#comment-fa1e3902b4807e で見たわね
val functionCodegenContext = WasmFunctionCodegenContext(declaration, function, backendContext, context)
WasmFunctionCodeGenContext
に
-
irFunction(declaration)
- The Kotlin IR function node being compiled to Wasm -
wasmFunction
- The Wasm function that is being generated -
backendContext
- General backend context for Wasm codegen -
context
- The parent WasmModuleCodegenContext
for (irParameter in irParameters) { functionCodegenContext.defineLocal(irParameter.symbol) }
各parameterについて
- Validate that this value declaration isn't already defined to avoid redefinition.
- Get the name and type information from the IR value declaration to initialize the WasmLocal.
- Check if the declaration is a function parameter or not to set the isParameter flag on the WasmLocal.
if (owner is IrValueParameter) context.transformValueParameterType(owner) else context.transformType(owner.type),
- Add the new WasmLocal to the wasmLocals map to track the mapping from IR value to WebAssembly local.
- Also add the WasmLocal to the wasmFunction's locals list to define it in the generated WebAssembly function.
visit している function が constructor だった場合
if (declaration is IrConstructor) { bodyBuilder.generateObjectCreationPrefixIfNeeded(declaration) }
こういうのを生成してくれる
...
;; Object creation prefix
local.get $0_<this>
ref.is_null
if
;; Any parameters
global.get $Foo.vtable___g_24
ref.null struct
i32.const 452
i32.const 0
i32.const 0
i32.const 0
struct.new $Foo___type_36
local.set $0_<this>
end
...
body generation
require(declaration.body is IrBlockBody) { "Only IrBlockBody is supported" }
declaration.body?.acceptVoid(bodyBuilder)
Since declaration.body
is an instance of IrBlockBody
, visitBlockBody
何で IrExpression
と IrVariable
しか来ない想定なの? もしかして前の Lowering の段階で statement は消えている?
private fun generateStatement(statement: IrStatement) {
when(statement) {
is IrExpression -> generateAsStatement(statement)
is IrVariable -> statement.acceptVoid(this)
else -> error("Unsupported node type: ${statement::class.simpleName}")
}
}
generateExpression
// Generates code for the given IR element but *never* leaves anything on the stack.
private fun generateAsStatement(statement: IrExpression) {
generateExpression(statement)
// ...
internal fun generateExpression(expression: IrExpression) {
expression.acceptVoid(this) // ...
visitExpression
はこんな感じで BodyGenerator
で override されてるわけでもなく
で IrExpression
の implementation 調べてみたら 100個くらいあってvisitSetValue
やら visitSetField
やらなんやらを見ていく感じになるのかな。これはまた別々で見ていくと良いだろう
visitVariable