Closed14

Kotlin/WASM のお勉強

tanishikingtanishiking

Scala に一番似ていて WasmGC 対応している言語と言えば Kotlin なので、どういう感じでコンパイルしていくのか調べてみよう。
ちなみに LICENSE は Apache 2 https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt

https://github.com/JetBrains/kotlin/blob/8a863e00ba148f47d11c825faffd92c494e52ba6/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt#L594-L651

-Xwasm とかで kotlinc-js から wasm 生成できそう?

$ kotlinc-js -Xwasm helloworld.kt
error: IR: Specify output dir via -ir-output-dir
error: old Kotlin/JS compiler is no longer supported. Please migrate to the new JS IR backend

old Kotlin/JS compiler is no longer supported. Please migrate to the new JS IR backend いや、backendどうやって切り替えんねん。

isIrBackendEnabled かつ isPreIrBackendDisabled だと良さそうですね。

https://github.com/JetBrains/kotlin/blob/2932f49f65539dbc9a8c08c84808f76ab193b2b0/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java#L179-L192

https://github.com/JetBrains/kotlin/blob/2932f49f65539dbc9a8c08c84808f76ab193b2b0/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt#L732-L736

wasm だけだと isIrBackendEnabledtrue なだけ。

  • irOnly かつ wasm だったり
  • useK2irProduceJsirBuildCache だといけそう。
$ kotlinc-js -Xwasm -Xir-only -ir-output-dir=ir -ir-output-name=helloworld helloworld.kt
exception: java.lang.AssertionError: Built-in class kotlin.Unit is not found

う〜ん?なんかCLIだとどうやって動かすのかわからんな? これだ

https://youtrack.jetbrains.com/issue/KT-49894


https://github.com/JetBrains/kotlin/blob/6c3b740a057f9876eab36f003b6bb34c880eaf4b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt#L354-L388

https://github.com/JetBrains/kotlin/blob/6c3b740a057f9876eab36f003b6bb34c880eaf4b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt#L46-L95

https://github.com/JetBrains/kotlin/blob/master/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt#L97-L105

このへんがエントリーポイントっぽい

  • Kotlin IR はどんなデータ構造をしている?抽象度は?
  • Kotlin IR から wasm 向けに lower する、どんな変換をするんですか?
  • そして wasm に変換する。どんな変換?
tanishikingtanishiking

やることは

  • compileToLoweredIr
    • JS向けにLowerされたKotlin IRを更にWASM向けにLowerする
  • compileWasm
    • LowerしたKotlin IRをWasmに変換

概要

compileToLoweredIr

https://github.com/JetBrains/kotlin/blob/6c3b740a057f9876eab36f003b6bb34c880eaf4b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/compiler.kt#L87-L92

lowering list

https://github.com/JetBrains/kotlin/blob/6c3b740a057f9876eab36f003b6bb34c880eaf4b/compiler/ir/backend.wasm/src/org/jetbrains/kotlin/backend/wasm/WasmLoweringPhases.kt#L609

主な lowering phases

  • InlineFunctions
  • StaticMembersLowering
    • static members を top-level declaration に変換する。WASMに static なる概念はない
  • EnumWhenLowering
    • enum class Color { ... } when (c: Color) { ... } を単なる if-else に変換する。WASM に enum ない
  • StringConcatenationLowering
    • 文字列の結合を StringBuffer に変換、よく分からんけど良さそう
  • DefaultParameterValueLowering
    • WASMにdefault argumentないので、default args の埋め込み
  • TailrecLowering
    • tailrec function call を loop に変換
  • FunctionReferenceLowering:
    • function reference を integer に変換して table indices で call_indirect に変換? funcref は wasm gc で追加されたはずじゃ?
  • ClassReferenceLowering
    • class reference を integer に、これも reference type でなんとかなりませんか?
  • Interface Lowering
    • Interface to table dispatch (call_indirect)
  • BridgesConstruction
    • 何も分からん
  • CallableReferenceLowering
    • callable references を integer へ???? function reference と何が違う?

compileWasm

  • WasmCompiledModuleFragment とかいうやつで コンパイル済み wasm module を保持していく
  • WasmModuleFragmentGenerator とかいうやつが IR module を WASM bytecode に変換していって、WasmCompiledModuleFragment に変換する
    val codeGenerator = WasmModuleFragmentGenerator(backendContext, compiledWasmModule, allowIncompleteImplementations = allowIncompleteImplementations)
    allModules.forEach { codeGenerator.collectInterfaceTables(it) }
    allModules.forEach { codeGenerator.generateModule(it) }
  • collectInterfaceTables
    • すべての入力IRモジュールをtraverseして、interface tableを収集。すべてのinterface class を見つけて、interface symbol から index への mappping を構築?
  • generateModule
    • IR中の各関数のシンボルに対して function を作る
    • 各 fields と class について global を作成 (fields は structures 使えば?)
    • IR type を WASM type に変換 ここ重要では
    • interface dispatch の対応
    • exception handling をなんとか
  • linkWasmCompiledFragments
    • 各IR moduleから作ったWASM fragmentsを一つの wasm module にlinkする
tanishikingtanishiking

う〜んやっぱり例がないとわかりません!各Lowering phaseのIR入出力とか見せてくれんか? というか Kotlin IR はどういう姿してんだよ

この IrTree っていうやつが Kotlin IR を定義する DSL になってるらしいが...

https://github.com/JetBrains/kotlin/blob/91465d7110e46a90ca9aa5c21182e55c2ff15329/compiler/ir/ir.tree/tree-generator/src/org/jetbrains/kotlin/ir/generator/IrTree.kt#L33

To debug WASM tests, run:
./gradlew :wasm:wasm.tests:test -Pfd.kotlin.wasm.debugMode=2
Values of the debug mode: 0 (or false), 1 (or true), 2.
Debug mode 2 will ensure that IR is dumped to a file after each lowering phase.
The IR dumps will appear next to the generated .js or .wat file.

https://github.com/JetBrains/kotlin/blob/91465d7110e46a90ca9aa5c21182e55c2ff15329/compiler/test-infrastructure/ReadMe.md#L340-L363

おっ、まずはこれの入出力眺めてみるか

tanishikingtanishiking
❯ ./gradlew :wasm:wasm.tests:test -Pfd.kotlin.wasm.debugMode=2

...
FAILURE: Build failed with an exception.

* What went wrong:
Could not determine the dependencies of task ':kotlin-stdlib:compileKotlinJvm'.
> Requesting vendor list failed: {"message":"Internal Server Error","_links":{"self":{"href":"/disco/v3.0/packages?jdk_version=6&distro=mandrel&operating_system=macos&latest=available&directly_downloadable=true","templated":false}},"_embedded":{"errors":[{"message":"Internal Server Error: Please provide a valid jdkVersion"}]}}

...

BUILD FAILED in 13s
55 actionable tasks: 6 executed, 49 up-to-date

https://github.com/JetBrains/kotlin/blob/5ce2a2cbee0e4751c14bbf8d4e0458e54e9454a9/ReadMe.md#L43-L74

なるほど

$ echo "kotlin.build.isObsoleteJdkOverrideEnabled=true" > local.properties

$ ./gradlew :wasm:wasm.tests:test
BUILD SUCCESSFUL in 2h 5m
547 actionable tasks: 497 executed, 50 up-to-date

ok

tanishikingtanishiking
$ ./gradlew :wasm:wasm.tests:test \
  --tests org.jetbrains.kotlin.wasm.test.K1WasmCodegenBoxTestGenerated \
  -Pfd.kotlin.wasm.debugMode=2 \
  -Pfd.org.jetbrains.kotlin.compiler.ir.dump.strategy="KotlinLike"

wasm/wasm.tests/build/out/codegen/k1Box/ の下に .ir.wat ファイルを生成する

tanishikingtanishiking

簡単そうなのでこれを見る

https://github.com/JetBrains/kotlin/blob/50f1ea79c508230d6aa0682a9206dce37248b99d/compiler/testData/codegen/box/traits/inheritedFun.kt#L2-L10

wasm/wasm.tests/build/out/codegen/k1Box/traits/inheritedFun/irdump/main に IR dump が入ってる

// 02_AFTER.ValidateIrBeforeLowering.kt.ir
// FILE: inheritedFun.kt
// path: /inheritedFun.kt

interface A {
  fun f(): Int {
    return 239
  }

  /* fake */ override operator fun equals(other: Any?): Boolean

  /* fake */ override fun hashCode(): Int

  /* fake */ override fun toString(): String

}

class B : A {
  constructor() /* primary */ {
    super/*Any*/()
    /* <init>() */

  }

  /* fake */ override fun f(): Int

  /* fake */ override operator fun equals(other: Any?): Boolean

  /* fake */ override fun hashCode(): Int

  /* fake */ override fun toString(): String

}

@JsExport
fun box(): String {
  return when {
    EQEQ(arg0 = B().f(), arg1 = 239) -> "OK"
    true -> "fail"
  }
}

最終的にこうなる

// 257_AFTER.ValidateIrAfterLowering.kt.ir
// FILE: inheritedFun.kt
// path: /inheritedFun.kt

interface A {
  fun f(): Int {
    return 239
  }

  /* fake */ override operator fun equals(other: Any?): Boolean

  /* fake */ override fun hashCode(): Int

  /* fake */ override fun toString(): String

}

class B : A {
  constructor() /* primary */ {
    super/*Any*/()
    { // BLOCK
    }
  }

  /* fake */ override fun f(): Int

  /* fake */ override operator fun equals(other: Any?): Boolean

  /* fake */ override fun hashCode(): Int

  /* fake */ override fun toString(): String

}

fun box(): String {
  return when {
    wasm_i32_eq(a = { // BLOCK
      val tmp: B = B()
      tmp.f()
    }, b = 239) -> "OK"
    true -> "fail"
  }
}

@JsExport
@JsName(name = "box")
fun box__JsExportAdapter(): JsString? {
  val currentIsNotFirstWasmExportCall: Boolean = #isNotFirstWasmExportCall
  { // BLOCK
    { // RETURNABLE BLOCK
      return { // BLOCK
        val tmp: JsString? = { // RETURNABLE BLOCK
          kotlinToJsAnyAdapter(x = return kotlinToJsAnyAdapter(x = try try return // COMPOSITE {
          #isNotFirstWasmExportCall = true
          return kotlinToJsStringAdapter(x = box())
          // }
          catch (e: Throwable)return when {
            currentIsNotFirstWasmExportCall -> throw e
            else -> throwAsJsException(t = e)
          }

          catch (t: Throwable)// COMPOSITE {
          #isNotFirstWasmExportCall = currentIsNotFirstWasmExportCall
          throw t
          // }
))
        }
        // COMPOSITE {
        #isNotFirstWasmExportCall = currentIsNotFirstWasmExportCall
        // }
        tmp
      }
    }
    // COMPOSITE {
    #isNotFirstWasmExportCall = currentIsNotFirstWasmExportCall
    // }
  }
}
tanishikingtanishiking
interface A {
    fun f():Int = 239
}

class B() : A

これは以下のようになってる

(type $B___type_585 (sub $kotlin.Any___type_131
  (struct
    (field (ref $B.vtable___type_368))
    (field (ref null struct))
    (field (mut i32))
    (field (mut i32)))
))

(type $kotlin.Any___type_131
  (struct
    (field (ref $kotlin.Any.vtable___type_112))
    (field (ref null struct))
    (field (mut i32))
    (field (mut i32))))

各関数のvtableがあるのはOK、それ以外の3つのフィールドはなんだろう? 2つめは itable

Introducing Kotlin/Wasm · seb.deleuze.fr

typeInfo と hashCode

Any の vtable

(type $kotlin.Any.vtable___type_112
  (struct
    (field (ref null $____type_731))
    (field (ref null $____type_728))
    (field (ref null $____type_762))))

(type $____type_731
  (func (param
    (ref null $kotlin.Any___type_131)
    (ref null $kotlin.Any___type_131)) (result i32)))

(type $____type_728
  (func (param
    (ref null $kotlin.Any___type_131))
    (result i32)))

(type $____type_762
  (func (param
    (ref null $kotlin.Any___type_131))
    (result (ref null $kotlin.String___type_514))))

う〜んこのシグネチャは...!? このメソッドたちでは?

  • /* fake */ override operator fun equals(other: Any?): Boolean
  • /* fake */ override fun hashCode(): Int
  • /* fake */ override fun toString(): String

B の vtable

(type $B.vtable___type_368 (sub $kotlin.Any.vtable___type_112
  (struct
    (field (ref null $____type_731))
    (field (ref null $____type_728))
    (field (ref null $____type_762))
    (field (ref null $____type_728)))))

上の3つは Any と同じやつだとして、最後の $____type_728Any -> Int なので多分これが f() なんだろうな

vtable の型定義しか見ていない、実体はどこに? いました

こういう書き方できるんだ??? ref $B.vtable___type_368 型の global $B.vtable___g_500 を定義。それぞれのフィールドに funcref を初期値として入れる

(global $B.vtable___g_500 (ref $B.vtable___type_368)
    ref.func $kotlin.Any.equals___fun_1738
    ref.func $kotlin.Any.hashCode___fun_1741
    ref.func $kotlin.Any.toString___fun_1742
    ref.func $A.f___fun_3362
    struct.new $B.vtable___type_368)

equals くらいは一応みておくか。

(func $kotlin.Any.equals___fun_1738 (type $____type_731)
    (param $0_<this> (ref null $kotlin.Any___type_131))
    (param $1_other (ref null $kotlin.Any___type_131)) (result i32)
    local.get $0_<this>  ;; type: kotlin.Any
    local.get $1_other  ;; type: kotlin.Any?
    ref.eq
    return)

$A.f___fun_3362 はどうでしょう

(func $A.f___fun_3362 (type $____type_728)
    (param $0_<this> (ref null $kotlin.Any___type_131)) (result i32)
    i32.const 239
    return)

うむ。これoverrideしてるケースとかもみたいな。

type $B___type_585 (sub $kotlin.Any___type_131 の初期化

;; (type $____type_1842 (func (param (ref null $B___type_585)) (result (ref null $B___type_585))))
(func $B.<init>___fun_3363 (type $____type_1842)
    (param $0_<this> (ref null $B___type_585)) (result (ref null $B___type_585))
    
    ;; Object creation prefix
    local.get $0_<this>
    ref.is_null
    if
        
        ;; Any parameters
        global.get $B.vtable___g_500
        global.get $B.classITable___g_696
        i32.const 10720 ;; typeinfo?
        i32.const 0 ;; hashcode? 何で0?
        
        struct.new $B___type_585
        local.set $0_<this>
    end
    
    local.get $0_<this>
    return)

B.classITable____g_696

(global $B.classITable___g_696 (ref $classITable___type_152)
    ref.func $A.f___fun_3362 ;; fun 関数の実装
    struct.new $A.itable___type_130
    struct.new $classITable___type_152)

(func $A.f___fun_3362 (type $____type_728) ;; これは fun 関数の実装だ
    (param $0_<this> (ref null $kotlin.Any___type_131)) (result i32)
    i32.const 239
    return)

(type $A.itable___type_130 (struct (field (ref null $____type_728)))) ;; ___type_728 は fun 関数の型
(type $classITable___type_152 (struct (field (ref null $A.itable___type_130))))

Bのitableには Aのitableがはいっているだけ。Aのitableには fun 関数の reference が入っている。

tanishikingtanishiking

次は interface dispatch 側を見ていく

(type $____type_1209 (func (param) (result (ref null $kotlin.String___type_514))))

(func $box___fun_3364 (type $____type_1209) (result (ref null $kotlin.String___type_514))
    ;; val tmp: B = B()
    (local $0_tmp (ref null $B___type_585))
    ref.null none
    call $B.<init>___fun_3363
    local.tee $0_tmp  ;; type: <root>.B
    local.get $0_tmp  ;; type: <root>.B
    
    ;; tmp.f()
    ;; interface call: A.f
    struct.get $kotlin.Any___type_131 1
    ref.cast $classITable___type_152
    struct.get $classITable___type_152 0
    struct.get $A.itable___type_130 0
    call_ref (type $____type_728)
    
    i32.const 239
    i32.eq
    if (result (ref null $kotlin.String___type_514))
        
        ;; const string: "OK"
        i32.const 538
        i32.const 15934
        i32.const 2
        call $kotlin.stringLiteral___fun_2417
        
    else
        
        ;; const string: "fail"
        i32.const 539
        i32.const 15938
        i32.const 4
        call $kotlin.stringLiteral___fun_2417
        
    end
    return)

大事なのはこのへん、B 型のインスタンスをstackに2つ積んでいる。

1つめは struct.get ... で funcref を取得するため、もうひとつが call_ref の引数

    local.tee $0_tmp  ;; type: <root>.B
    local.get $0_tmp  ;; type: <root>.B
    
    ;; tmp.f()
    ;; interface call: A.f
    struct.get $kotlin.Any___type_131 1
    ref.cast $classITable___type_152
    struct.get $classITable___type_152 0
    struct.get $A.itable___type_130 0
    call_ref (type $____type_728)

これ

Hidden comment
tanishikingtanishiking

もうちょっと難しい diamond 継承の例

https://github.com/JetBrains/kotlin/blob/b2041e092737fa349f60002820ab5d21222e3062/compiler/testData/codegen/box/traits/diamondPropertyAccessors.kt#L1-L23

ここでは C の実装が呼ばれてほしいはず

(func $box___fun_3365 (type $____type_1212) (result (ref null $kotlin.String___type_517))
    (local $0_tmp (ref null $Impl___type_588))
    (local $1_tmp (ref null $Impl___type_588))
    ref.null none
    call $Impl.<init>___fun_3364
    local.tee $0_tmp  ;; type: <root>.Impl
    i32.const 0
    local.get $0_tmp  ;; type: <root>.Impl
    
    ;; interface call: C.<set-bar>
    struct.get $kotlin.Any___type_134 1
    ref.cast $classITable___type_155
    struct.get $classITable___type_155 2
    struct.get $C.itable___type_132 0
    call_ref (type $____type_1024)
    
    ref.null none
    call $Impl.<init>___fun_3364
    local.tee $1_tmp  ;; type: <root>.Impl
    local.get $1_tmp  ;; type: <root>.Impl
    
    ;; interface call: C.<get-bar>
    struct.get $kotlin.Any___type_134 1
    ref.cast $classITable___type_155
    struct.get $classITable___type_155 2
    struct.get $C.itable___type_132 1
    call_ref (type $____type_731)
    
    i32.eqz
    if
        
        ;; const string: "Fail get"
        i32.const 539
        i32.const 15956
        i32.const 8
        call $kotlin.stringLiteral___fun_2417
        
        return
    else
    end
    
    ;; const string: "OK"
    i32.const 540
    i32.const 15972
    i32.const 2
    call $kotlin.stringLiteral___fun_2417
    
    return)

これなんでBの探索とか実行時にやらないで、最初からC見に行けてるの???

tanishikingtanishiking

interface call

こういう場合で、静的には A か B かわからないときどうすんのさ。型推論で v: {I & J} まで分かってる。

interface K

interface I : K {
    fun ff(): String
}

interface J : K {}

class A: I, J {
    override fun ff() = "OK"
}

class B: I, J {
    override fun ff() = "Irrelevant"
}

fun box(): String {
    val v = if (true) A() else B()
    return v.ff()
}
    (func $box___fun_64 (type $____type_93) (result (ref null $kotlin.String___type_34))
        (local $0_v (ref null $kotlin.Any___type_16))
        (local $1_tmp (ref null $kotlin.Any___type_16))
        ref.null none
        call $A.<init>___fun_60
        local.tee $0_v  ;; type: <root>.K 
        local.tee $1_tmp  ;; type: <root>.I <-- RTTI: A
        local.get $1_tmp  ;; type: <root>.I <-- RTTI: A
        
        ;; interface call: I.ff
        struct.get $kotlin.Any___type_16 1
        ref.cast $classITable___type_20
        struct.get $classITable___type_20 1
        struct.get $I.itable___type_14 0
        call_ref (type $____type_68)
        
        return)

I.itable___type_14$____type_68 なる型の function reference を呼び出す。

(type $classITable___type_20 (struct
  (field (ref null $K.itable___type_13))
  (field (ref null $I.itable___type_14))
  (field (ref null $J.itable___type_15))))

(type $K.itable___type_13 (struct))
(type $I.itable___type_14 (struct
  (field (ref null $____type_68))))
(type $J.itable___type_15 (struct))

;; ff の型? receiver は Any になるのね
(type $____type_68 (func
  (param (ref null $kotlin.Any___type_16))
  (result (ref null $kotlin.String___type_34))))

で、class A の itable はこんな感じで、I.itable に A で override した実装が入っていますね。

(global $A.classITable___g_26 (ref $classITable___type_20)
    struct.new $K.itable___type_13
    ref.func $A.ff___fun_61
    struct.new $I.itable___type_14
    struct.new $J.itable___type_15
    struct.new $classITable___type_20)

(func $A.ff___fun_61 (type $____type_68)
    (param $0_<this> (ref null $kotlin.Any___type_16)) (result (ref null $kotlin.String___type_34))
    
    ;; const string: "OK"
    i32.const 25
    i32.const 998
    i32.const 2
    call $kotlin.stringLiteral___fun_17
    
    return)
tanishikingtanishiking

WasmGC のなしだと virtual dispatch や interface dispatch はどうしてる?

  • Rust: How does dynamic dispatch work in WebAssembly?
    • aggregated type がないので、linear memory 上に allocation して〜メモリ上のオブジェクトへのメモリを〜みたいなのをコンパイラが実装してて大変そう。WasmGC だと実行エンジンがヒープ管理をしてくれるはずなので楽
    • typed function references がないので、table に関数を登録して call_indirect で呼び出す
    • virtual dispatch は global vtable あればいい
    • interface dispatch も別に call_indirect があればいい?allocate したオブジェクトが、関数の実装のインデックスを知っていれば問題ないはず。
  • 問題になってくるのは WasmGC によって subtyping がある場合
    • call_indirect の実行時型チェックは subtyping チェックをしてくれますか?
    • 上の例で、ff の receiver の型は、A とか B みたいな derived type になる。
    • でも呼び出し側は実行時の型がわからないはず? なので、I とかを receiver の型とする
      • あるいは rtti を call_indirect になんとか食わせる...
      • もしくは型チェック無視 -> unsound やんけ
    • A <: I ではあるんですが、これ callee の型が caller の(指定した)型の subtype であることは許容しますか? しない場合は call_indirect による dynamic dispatch で困るケースでそう。

https://github.com/WebAssembly/gc/issues/329

tanishikingtanishiking

j2cl は?

(なんか Bazel 5.4.0 だと動かなった ()

// google/j2cl/samples/wasm/src/main/java/com/google/j2cl/samples/wasm/HelloWorld.java
package com.google.j2cl.samples.wasm;

class Animal {
    String name;
    Animal(String name) { this.name = name; }
    void eat() { System.out.println(name + " is eating."); }
}

class Dog extends Animal {
    Dog(String name) { super(name); }
    String bark() { return "Woof! Woof!"; }
}

public class HelloWorld {
    public static String getHelloWorld() {
        Animal genericAnimal = new Animal("Generic Animal");
        genericAnimal.eat();

        System.out.println("---------------");

        Dog myDog = new Dog("Buddy");
        myDog.eat();
        return myDog.bark();
    }
}
$ cd samples/wasm
$ bazel build src/main/java/com/google/j2cl/samples/wasm:jsapp 
 ;;; Code for com.google.j2cl.samples.wasm.Dog [type definition]
 (type $com.google.j2cl.samples.wasm.Dog (sub $com.google.j2cl.samples.wasm.Animal (struct
  (field $vtable (ref $com.google.j2cl.samples.wasm.Dog.vtable))
  (field $itable (ref $itable))
  (field $$systemIdentityHashCode@java.lang.Object (mut i32))
  (field $name@com.google.j2cl.samples.wasm.Animal (mut (ref null $java.lang.String)))
  ))
 )
 (type $com.google.j2cl.samples.wasm.Dog.vtable (sub $com.google.j2cl.samples.wasm.Animal.vtable (struct
  (field $$getClassImpl__java_lang_Class (ref $function.$getClassImpl__java_lang_Class))
  (field $m_equals__java_lang_Object__boolean (ref $function.m_equals__java_lang_Object__boolean))
  (field $m_getClass__java_lang_Class (ref $function.m_getClass__java_lang_Class))
  (field $m_hashCode__int (ref $function.m_hashCode__int))
  (field $m_toString__java_lang_String (ref $function.m_toString__java_lang_String))
  (field $m_eat__void_$pp_com_google_j2cl_samples_wasm (ref $function.m_eat__void_$pp_com_google_j2cl_samples_wasm))
  (field $m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm (ref $function.m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm))
  ))
 )
 ;;; End of code for com.google.j2cl.samples.wasm.Dog [type definition]



;;; String HelloWorld.getHelloWorld()
(func $m_getHelloWorld__java_lang_String@com.google.j2cl.samples.wasm.HelloWorld
 (result (ref null $java.lang.String))
 ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:37:25
 (local $genericAnimal (ref null $com.google.j2cl.samples.wasm.Animal))
 (local $$qualifier (ref null $java.io.PrintStream))
 (local $myDog (ref null $com.google.j2cl.samples.wasm.Dog))
 (block
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:37:41
  (call $$clinit__void_<once>_@com.google.j2cl.samples.wasm.HelloWorld )
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:38:8
  (local.set $genericAnimal (call $$create__java_lang_String@com.google.j2cl.samples.wasm.Animal (call $$getString_|Generic_Animal|__java_lang_String_<once>_@com.google.j2cl.samples.wasm.HelloWorld )))
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:39:8
  (call_ref $function.m_eat__void_$pp_com_google_j2cl_samples_wasm (ref.as_non_null (local.get $genericAnimal))(struct.get $com.google.j2cl.samples.wasm.Animal.vtable $m_eat__void_$pp_com_google_j2cl_samples_wasm (struct.get $com.google.j2cl.samples.wasm.Animal $vtable(local.get $genericAnimal))))
  (block
   ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:41:8
   (local.set $$qualifier (block (result (ref null $java.io.PrintStream))
    (call $$clinit__void_<once>_@java.lang.System )
    (global.get $out@java.lang.System)
   ))
   ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:41:8
   (call_ref $function.m_println__java_lang_String__void (ref.as_non_null (local.get $$qualifier))(call $$getString_|_______________|__java_lang_String_<once>_@com.google.j2cl.samples.wasm.HelloWorld )(struct.get $java.io.PrintStream.vtable $m_println__java_lang_String__void (struct.get $java.io.PrintStream $vtable(local.get $$qualifier))))
  )
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:43:8
  (local.set $myDog (call $$create__java_lang_String@com.google.j2cl.samples.wasm.Dog (call $$getString_|Buddy|__java_lang_String_<once>_@com.google.j2cl.samples.wasm.HelloWorld )))
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:44:8
  (call_ref $function.m_eat__void_$pp_com_google_j2cl_samples_wasm (ref.as_non_null (local.get $myDog))(struct.get $com.google.j2cl.samples.wasm.Animal.vtable $m_eat__void_$pp_com_google_j2cl_samples_wasm (struct.get $com.google.j2cl.samples.wasm.Animal $vtable(local.get $myDog))))
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:45:8
  (return (call_ref $function.m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm (ref.as_non_null (local.get $myDog))(struct.get $com.google.j2cl.samples.wasm.Dog.vtable $m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm (struct.get $com.google.j2cl.samples.wasm.Dog $vtable(local.get $myDog)))))
 )
)

このへん見ると分かるけど、Kotlin/Wasm と同様に各オブジェクトは vtable や itable へのインスタンスを持っている形。

  (call_ref $function.m_eat__void_$pp_com_google_j2cl_samples_wasm (ref.as_non_null (local.get $myDog))(struct.get $com.google.j2cl.samples.wasm.Animal.vtable $m_eat__void_$pp_com_google_j2cl_samples_wasm (struct.get $com.google.j2cl.samples.wasm.Animal $vtable(local.get $myDog))))
  ;;@ com/google/j2cl/samples/wasm/HelloWorld.java:45:8
  (return (call_ref $function.m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm (ref.as_non_null (local.get $myDog))(struct.get $com.google.j2cl.samples.wasm.Dog.vtable $m_bark__java_lang_String_$pp_com_google_j2cl_samples_wasm (struct.get $com.google.j2cl.samples.wasm.Dog $vtable(local.get $myDog)))))
このスクラップは2023/12/18にクローズされました