Closed9

Kotlin/Wasm Internal その2

tanishikingtanishiking

collectInterfaceTables

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt#L53-L66

(generateModule は関係ない)

acceptVisitor(irModuleFragment, interfaceCollector)

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/ir.tree/gen/org/jetbrains/kotlin/ir/IrElement.kt#L40-L47

acceptVoiddata として null を渡すという話。collectInterface の visitor method で何をするかが分かるはず

interfaceCollector

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleFragmentGenerator.kt#L37-L50

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()

tanishikingtanishiking

DeclarationGenerator

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L30-L34

Array の特別対応

kotlin array を wasm の array にするっぽい。 getWasmArrayAnnotation ってなんですか? (TODO)

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt#L161-L164

(array (field $field (mut <type>))) こういう感じになるんかな

context.defineGcType(symbol, wasmArrayDeclaration) に登録。これが後でどうなるのやら...?
ところで array に生えてるメソッドなんかはどうなるんだろう?

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L342-L356

interface declaration の場合

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L360-L368

createVirtualTableStruct (名前は $nameStr.itable) で is final

createVirtualTableStruct

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L227-L247

method 一覧と super type と name を受け取って、vtable の型を構築。割りと素直な実装

結果を context.defineVTableGcType(symbol, vtableStruct)

tanishikingtanishiking
type = WasmRefNullType(WasmHeapType.Type(context.referenceFunctionType(it.function.symbol))),

これを詳しく見ていく

context.referenceFunctionType(it.function.symbol) から

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt#L177-L178

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt#L30-L31

data class WasmFunctionType(
    val parameterTypes: List<WasmType>,
    val resultTypes: List<WasmType>
) : WasmTypeDeclaration("")

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt#L74-L85

WasmSymbol で ir に対する symbol を作っていく。この段階では "unbound" な symbol、このあと linkWasmModule で symbol に対する関数を bind していく。

WasmHeapType.Type(...)

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt#L40-L45

これは何?

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Types.kt#L31

ref null でラップするだけ

なんか、いまいちこの段階では...何? あとのlinkingの段階でいろいろ埋まっていくのだろうか?

tanishikingtanishiking

いったん compiledWasmModule.linkWasmCompiledFragments() で symbol 解決されていくさまを見たい

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt#L114

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt#L104-L249

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt#L251-L260

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Symbol.kt#L23-L25

ここだね、defined にもいろいろ値が入っているはずなのね、例えば

bind(functions.unbound, functions.defined)
val functions =
  ReferencableAndDefinable<IrFunctionSymbol, WasmFunction>()

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmCompiledModuleFragment.kt#L88-L98

functions.define(...) みたいなのがあるはずなのよね。

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmModuleCodegenContext.kt#L82-L84

defineFunction はどこで呼ばれてるのかな? 二箇所で両方とも DeclarationGenerator.ktvisitFunction

imported function

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L125-L133

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L79-L94

@WasmImport annotation もしくは kotlin.JsFun

fun IrAnnotationContainer.getJsFunAnnotation(): String? =
    getAnnotation(FqName("kotlin.JsFun"))?.getSingleConstStringArgument()

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/wasm/wasm.ir/src/org/jetbrains/kotlin/wasm/ir/Declarations.kt#L40-L56

data class WasmImportDescriptor(
    val moduleName: String,
    val declarationName: String
)
  (import "$moduleName" "$declarationName" 
    (func $name (param $a i32) (param $b i32) (result i32)))

こんな感じになるのかね


普通のパス

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L175

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L135

    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.unboundfunctions.definitions は揃ってるはず

tanishikingtanishiking

visitClass

interface じゃない場合

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L369-L397

createVTable

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L249-L285

これで最終的に

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)
tanishikingtanishiking

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))

こうなって欲しいはず。

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L287-L336

val supportedInterface = metadata.interfaces.firstOrNull()?.symbol ?: return これ1つ目の interface 取得してるだけだよね? なにこれ? 複数の interface を実装していた場合はどうなる? IR の段階ではそれらを implements している interface がいる感じになるんですか? TODO

createDeclarationByInterface

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L200-L225

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() 

こういう場合 SubB を直接実装しているとは言わない。

interface A
interface B
interface C: A, B
class Derived: C

この場合は DerivedA, 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)

こうなる

tanishikingtanishiking

visitClass 続き

val vtableRefGcType = WasmRefType(WasmHeapType.Type(context.referenceVTableGcType(symbol)))
val classITableRefGcType = WasmRefNullType(WasmHeapType.Simple.Struct)

ここで vtableitable の型を取得するんだけど

何で 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

tanishikingtanishiking

visitFunction 編

https://github.com/JetBrains/kotlin/blob/c4fc0b919dfe9b746f9ecfcc08668fa05b839064/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/DeclarationGenerator.kt#L67-L198

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) }

https://github.com/JetBrains/kotlin/blob/5728b347333f7a2cf6a081c30c0b0b82c7f89f5c/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/WasmFunctionCodegenContext.kt#L40-L53

各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) }

https://github.com/JetBrains/kotlin/blob/5728b347333f7a2cf6a081c30c0b0b82c7f89f5c/compiler/ir/ir.psi2ir/src/org/jetbrains/kotlin/psi2ir/generators/BodyGenerator.kt#L61-L91

こういうのを生成してくれる

...
    ;; 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

https://github.com/JetBrains/kotlin/blob/5728b347333f7a2cf6a081c30c0b0b82c7f89f5c/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt#L656-L659

何で IrExpressionIrVariable しか来ない想定なの? もしかして前の 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 されてるわけでもなく

https://github.com/JetBrains/kotlin/blob/af5f76048b551576a554ae6765074b4b2d14b5b8/compiler/ir/ir.tree/gen/org/jetbrains/kotlin/ir/visitors/IrElementVisitorVoid.kt#L187-L193

IrExpression の implementation 調べてみたら 100個くらいあってvisitSetValue やら visitSetField やらなんやらを見ていく感じになるのかな。これはまた別々で見ていくと良いだろう

visitVariable

https://github.com/JetBrains/kotlin/blob/5728b347333f7a2cf6a081c30c0b0b82c7f89f5c/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/ir2wasm/BodyGenerator.kt#L937-L946

このスクラップは3ヶ月前にクローズされました