Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GC言語のWasm化とComponent Modelサポートの実践と課題 - Scalaの場合

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on.

GC言語のWasm化とComponent Modelサポートの実践と課題 - Scalaの場合

Cloud Native Community Japan - Wasm Japan Meetup #3 at CNCF Cloud Native Community Japan https://community.cncf.io/events/details/cncf-cloud-native-community-japan-presents-cloud-native-community-japan-wasm-japan-meetup-3/

Avatar for Rikito Taniguchi

Rikito Taniguchi

March 06, 2026
Tweet

More Decks by Rikito Taniguchi

Other Decks in Programming

Transcript

  1. GC言語のWasm化と Component Modelサポートの 実践と課題 : Scalaの事例 CNCJ Wasm meetup Mar

    05, 2026 Rikito Taniguchi (@tanishiking) VirtusLab / Scala Center 1/15
  2. JVM baseline Node.js 24.10.0 Scala.js 1.20.1 OpenJDK Zulu24.28+83-CA Mac OS

    Sequoia 15.5 Apple M2 3/15 JS vs Wasm 低いほど早い
  3. GC言語のWasm化 4/15 VMのWasm化 (Ruby, Python など) • ❌ バイナリサイズ大 •

    ❌ 実行パフォーマンス GC を Wasm module組み込み on Linear Memory (Go など) • ❌ GC root 探索のためスタックをinspectする必要有 ◦ Wasmではスタックへのアクセス不可 ◦ Shadow stack など、GC root を覚えておく必要 → パフォーマンス劣化 • ❌ モジュール境界を越えてlive objectをトラッキング不可 ◦ Cycle collection problem
  4. Wasm GC 101: struct and array 5/15 • WasmランタイムによりGC管理されたstruct型とarray型(の値への参照型) •

    それらを操作する命令 ⭕ メモリ管理のコードをモジュールに組み込み不要 ⭕ モジュール境界を超えた参照のトラッキング (by ランタイム)
  5. オブジェクトモデル (type $c.Point2D (sub $c.Point (struct (field $vtable (ref $v.Point2D))

    (field $f.Point2D.x (mut f64)) (field $f.Point2D.y (mut f64))))) (type $v.Point2D (sub $v.Point (struct ;; class information ;; itables (field $m.Point2D.getClass (ref $1)) (field $m.Point2D.hashCode (ref $2)) (field $m.Point2D.toString (ref $3))))) abstract class Point class Point2D(val x: Double, val y: Double) extends Point (type $3 (func (param (ref any)) (result externref))) 7/15
  6. Virtual Method 呼び出し $vtable $f.Point2D.x $f.Point2D.y (local $p2d (ref null

    $c.Point2D)) local.get $p2d br_on_null $1 ;; jump to throw if null local.tee $tmp ;; for parameter of toString local.get $tmp struct.get $c.Point2D $vtable struct.get $v.Point2D $m.Point2D.toString call_ref $3 $getClass $hashCode $toString (type $3 (func (param (ref any)) (result externref))) vtable から toString メソッドの function reference を取得 8/15
  7. Wasm Component Model 101 ‘w’ ‘a’ ‘s’ ‘m’ Linear Memory

    ‘w’ ‘a’ ‘s’ ‘m’ Linear Memory Component A Component B 任意の言語間での相互関数呼び出しなどをサポートするための仕様 • Component - Wasm module を内包する linkable(?) format • WIT (Wasm Interface Type) - インタフェース記述言語 (IDL) • Canonical ABI - コンポーネント間のやりとりでのバイナリレベルの取り決め 10/15 // WIT greet: func(message: string) -> string ABIに従いデータ配置し外部関数呼出
  8. Wasm Component Model 101 ‘w’ ‘a’ ‘s’ ‘m’ Linear Memory

    ‘w’ ‘a’ ‘s’ ‘m’ Linear Memory Copy by VM Component A Component B Shared-nothing architecture: コンポーネント間で線形メモリなどの共有無 引数と返り値はそれぞれ、以下の方法で交換される • 各コンポーネントは線形メモリにデータを配置 • ランタイムがデータを別コンポーネントにコピー 11/15
  9. Wasm Component Model + WasmGC ‘w’ ‘a’ ‘s’ ‘m’ Linear

    Memory Copy (by producer) (array i16) “wasm” ‘w’ ‘a’ ‘s’ ‘m’ Linear Memory Copy (by producer) (array i16) “wasm” Copy by VM ❌ コンポーネント境界で WasmGC オブジェクトの参照を直接別コンポーネントに 渡すことはできない。 GCオブジェクトと線形メモリ間でのコピーオーバーヘッドが必要 Component A Component B 12/15
  10. ベンチマーク: Component Model Function Call Overhead Byte Size Scala Mean

    (ns) Rust Mean (ns) Scala SD (ns) Rust SD (ns) 20 945 184 320 50 200 1,994 487 187 47 2,000 22,269 3,573 748 212 20,000 125,425 29,035 4,631 3,183 identity 関数 (string -> string) のベンチマーク比較 • Scala ⇔ Scala (WasmGC) • Rust ⇔ Rust (non-WasmGC) 13/15
  11. Optimizing Component Model Calls in WasmGC? • WasmGC のオブジェクト参照をCanonical ABIで直接サポートする

    ◦ https://github.com/WebAssembly/component-model/issues/525 • Lazy Value Lowering (GCは関係ないけど...) ◦ https://github.com/WebAssembly/component-model/issues/383 14/15
  12. まとめ • Scala to Wasm compiler (WasmGC) ◦ https://github.com/scala-js/scala-js (JS

    embedding) ◦ https://github.com/scala-wasm/scala-wasm (WIP non-JS embedding) • Wasm へのコンパイルは最大 3x JS より実行速度早い • Component境界でのWasmGCのコピーオーバーヘッド 15/15 時間あったら話したい • JS interop • Box/UnboxとJS interop • JS primitive builtins proposal • i31ref •
  13. JS 関数呼び出し makeArray2(1, 2) def makeArray2(x: Int, y: Int): js.Array[Int]

    = js.Array(x, y) (func $f.makeArray2 (param $x i32) (param $y i32) (result anyref) ;; js Array local.get $x local.get $y call $customJSHelper.1) // JavaScript helper "1": ((x, y) => [x, y]), (import "__scalaJSCustomHelpers" "1" (func $customJSHelper.1 (param i32) (param i32) (result anyref))) [1]: except Long and Char. Scala.jsはScalaのプリミ ティブ値がJSプリミティブ値 に変換されることを保証[1] JS numbers 7/18
  14. Boxing とJS interop makeArray2(1, 2) def makeArray2[T](x: T, y: T):

    js.Array[T] = js.Array(x, y) Scala.jsはScalaのプリミ ティブ値がJSプリミティブ値 に変換されることを保証[ [1] (upcast される場合も ) // JavaScript helper "1": ((x, y) => [x, y]), (import "__scalaJSCustomHelpers" "1" (func $customJSHelper.1 (param anyref) (param anyref) (result anyref))) Should be JS numbers [1]: except Long and Char. (func $f.makeArray2 (param $x anyref) (param $y anyref) (result anyref) ;; js Array local.get $x local.get $y call $customJSHelper.1) 8/18
  15. i32値 を box して anyref のサブタイプとして扱う i32.const 1 call $boxInt

    i32.const 2 call $boxInt call $f.makeArray2 Boxed integer も JS 境界では number に変換されるべき (Scala.js semantics) (func $boxInt (param $x i32) (result (ref any)) どうやってプリミティブ値をボックスする? ;; class java.lang.Integer(value: Int) (type $c.j.l.Integer (sub $c.j.l.Object (struct ;; ... (field $f.value (mut i32))))) ❌ struct で box化? structはJSにopaque 9/18
  16. Boxing/Unboxing to JS primitives (import "__scalaJSHelpers" "boxInt" (func $boxInt (param

    i32) (result anyref))) (import "__scalaJSHelpers" "unboxInt" (func $unboxInt (param anyref) (result i32))) // JavaScript helper boxInt: (x) => x, unboxInt: (x) => x, Reference to JS number but calling JS from Wasm is expensive Convert between i32 ⇔ JS number 10/18
  17. Box integers using i31ref (func $bI (param $x i32) (result

    (ref any)) ;; ... ;; check if $x fits in ;; 31-bit signed integer if (result (ref any)) local.get $x call $boxInt else local.get $x ref.i31 end) If it doesn’t fit in 31bit int, use JS number If it’s small enough, use i31ref. No Wasm-to-JS call Boxing without crossing Wasm/JS boundary. i31 values are seen as JS numbers from JS host. https://www.w3.org/TR/wasm-js-api/#tojsvalue 11/18
  18. JS Primitive Builtins Proposal (Phase 2) Wasm builtin functions for

    manipulating JS primitives, efficiently callable without Wasm-to-JS overhead (similar to JS String Builtins proposal). "wasm:js-number" "fromI32" func fromI32(x: i32) -> (ref extern) "wasm:js-number" "toI32" func toI32(x: externref) -> i32 "wasm:js-number" "testI32" func testI32(x: externref) -> i32 https://github.com/WebAssembly /js-primitive-builtins 12/18
  19. Non-JS embedding JS embedding Non-JS embedding String Representation JS string

    + JS String Builtins Linked list around (array i16) Boxing/Unboxing JS primitive + i31ref I31ref + Box class class IntBox(value: Int) … JDK API impl Based on JS interop Regex, Math, Variable-Length Array, Encode/Decode decimal float, System APIs, etc WASI preview2 + reimplement in Scala