wasm gc proposal 勉強
記事になりました
Heap Types
wasm gc proposal による wasm に stack, linear memory に加えて heap が追加される(追加されるというか "The introduction of managed data adds new forms of types that describe the layout of memory blocks on the heap" で wasm実行エンジンにheapが必要になってくるという言い方のほうが正しいのかな)
そのような heap に配置されるデータレイアウトを表現する構造(Heap Types)は以下の三種類に分類される
- Internal (values in Wasm representation)
- External (values in a host-specific representation)
- Functions
Internal Types
Reference Types
Typed Function References によって導入された型
(reference type proposal で導入された extern
と func
は後述)
(ref null? <heap-type>)
(type $A (struct)) ;; index = 0
(func $f1 (param (ref null $A))) ;; func f1(a *A) { ... }
(func $f2 (result (ref 0)))) ;; func f2() *A { ... }
(ref null <heap-type>)
だと nullable
Aggregate Types
dynamically allocated managed data (strcutures, arrays, or unboxed scalars)
Structures
(type $time (struct (field i32) (field f64)))
(type $point (struct (field $x f64) (field $y f64) (field $z f64)))
structtype ::= struct <fieldtype>*
fieldtype ::= <mutability> <storagetype>
- ...
現行のwasm spec見ても mutability
や field に名前つけられる?ようなWAT文法の仕様見つけられなかったんだけど、 (field mut? <field-name>? <storagetype>)
って感じかな?
Array
(type $vector (array (mut f64)))
(type $matrix (array (mut (ref $vector))))
structures とあんまり変わらない arraytype ::= array <fieldtype>
Recursive types
rec
によって相互再帰している型を定義できるし、単一の型定義でも再帰的な定義ができるようになる。
(rec
(type $A (struct (field $b (ref null $B))))
(type B$ (struct (field $a (ref null $A))))
)
(type $C (struct field $f i32) (field $c (ref null $C)))
Unboxed Scalars
i31
https://github.com/WebAssembly/gc/blob/main/proposals/gc/Overview.md#unboxed-scalars
i32
のような数値型も他のheap型と同様に扱うためにそれらの型はboxingされることになる。アクセス頻度の高いpremitive typesのboxing costを避けるために、導入されるのが unboxed scalars.
32bitの数値をboxingする代わりに、31bitに実際の値を格納し、残りの1bitに"僕ポインタじゃないよ!残りの31bitに値が入ってるからそのまま使っていいよ"ということを表すフラグを設けるというもの。これにより31bit数値型をboxingコストなしで他のreference typeと同様に利用することができる
(という理解)
External Types
ホストにおける参照/関数をWASM内で externref/funcref
という型として扱うことができる。以下の hello
関数は externref
型のパラメータを受け取ることができて、例えばホスト側のJSコードは hello
に対してJS内の参照を引数として与えることができる。
(func (export "hello") (param externref) ...)
以下の記事がとてもわかりやすかった
関連して type imports proposal がある
Type Hierarchy
-
eq
はref.eq
による比較が可能なすべての reference types の共通する super type -
any
は intern types にの top型 -
none
は internal types の bottom型 -
noextern
は external types の bottom型 -
nofunc
は function types の bottom型 -
struct
はすべての struct types の super type -
array
はすべての array types の super type -
i31
は unboxed scalars
つまり type hierarchy はこうなる
Subtypes
(type $A (struct)) ;; これは `(type $A (sub (struct)))` の省略形
(type $A (sub (struct))) ;; class A {}
;; i32 の field を持ち A の subtype である B を定義
(type $B (sub $A (struct (field i32))))
;; i32 と i64 の fields を持ち B の subtype である C を定義
(type $C (sub final $B (struct (field i32 i64))))
the preexisting syntax with no sub clause is redefined to be a shorthand for a sub clause with empty typeidx list
Abbreviations
funcref == (ref null func)
externref == (ref null extern)
anyref == (ref null any)
nullref == (ref null none)
nullexternref == (ref null noextern)
nullfuncref == (ref null nofunc)
eqref == (ref null eq)
structref == (ref null struct)
arrayref == (ref null array)
i31ref == (ref null i31)
New instructions
i31.*
i31.get_u
や i31.get_s
(get_u/s が 符号拡張あり/なし)
array.*
と struct.*
-
array.new
,struct.new
-
array.get/set
,struct.get/set
array.len/fill/copy
(type $vector (array (mut f64)))
(type $tup (struct i64 i64 i32))
;; array.new <type> <values>
;; struct.new <type> <values>
(local.set $v (array.new $vector (f64.const 1) (i32.const 3)))
(local.set $t (struct.new $tup (i64.const 1) (i64.const 2) (i64.const 1)))
;; array.get <type> <arrayref> <index>
;; struct.get <type> <index> <structref>
(array.get $vector (local.get $v) (i32.const 1)) ;; $v の 1番目の要素を取得
(struct.get $tup 1 (local.get $t)) ;; $t の1番目の要素を取得
;; array.set <type> <arrayref> <index> <value>
;; struct.set <type> <index> <sturctref> <value>
(array.set $vector (local.get $v) (i32.const 2) (i32.const 5)) ;; $v の2番目の要素に5をset
(struct.set $tup 1 (local.get $t) (i64.const 100)) ;; $t の1番目のフィールドに100をセット
その他の array.*
は MVPに書いてあるとおり (new_defaultとかあまり分かってない)
br_on_*
reference type の値に応じた branching
-
br_on_cast
-
(br_on_cast $label <ref ht> <rtt>)
- operand が rtt の subtype であれば、rtt に cast された operand を引数として
$label
に分岐する (stackにoperandをpushしてジャンプってこと?)
-
-
br_on_cast_fail
(br>on_cast_fail $label <operant> <rtt>)
- operand が rtt に cast できなかった場合、operand を引数として
$label
に分岐する(?)
-
br_on_null $l
-
(br_on_null $l (local.get $r))
$r
が null reference なら$l
に分岐。$r
を stack に push するのか?
-
br_on_non_null $l
ref.*
casting and testing values of reference types
-
ref.null ht
-ht
型の null reference を作る(ref.null eq)
-
ref.i31 $x
-$x: i32
からi31ref
をつくる(ref.i31 1)
ref.test <ref ht> <runtime-type>
- 第一引数のreferenceを第二引数のruntime-typeにcastできるか調べる。できるなら1、できないなら0
-
ref.cast <ref ht> <runtime-type>
- 第一引数のreferenceを第二引数のruntime-typeにcast
- castできない場合はtrap
-
ref.is_null <ref ht>
- 与えた reference が null なら 1、そうでないなら0
-
ref.as_non_null <ref null ht>
- nullable な reference を non-nullable にする? よく分かってない -
ref.eq <ref ht> <ref ht>
2つのeqref
な operand を受け取って、ふたつの参照の値の同一性チェックを行う。(具体的な同一性チェックの話は見つけられてない) -
ref.func
- function を引数にして function reference を作る
extern.*
"converts an external value into the internal representation" とかその逆とか書いてるけどよく分かってない
extern.convert_any
any.convert_extern