Closed8

rust の Wasm Component Model / WASIp2 の wat を眺める

tanishikingtanishiking

先人が既に眺めているけど、自分も Wasm Component Model の理解を深めるために、 cargo component で生成した WASIp2 を利用した component の WAT ファイルを wasm-tools を使って眺める

https://zenn.dev/ruccho/scraps/f534e0e44d6a38

wasm32-wasip1 で wasm component をビルドすると、WASIp2 を使っているような顔をしながら実は裏では WASIp1 を利用する adapter も一緒に生成されてしまう。複雑さが増してしまうので adapter を利用しないバイナリを生成したい。

wasm32-wasip2 ターゲットは (2024年10月17日時点では) nightly で使えるはずで、実際 rustup +nightly target add wasm32-wasip2 は成功して cargo component build --target wasm32-wasip2 は実行できるのだが、中身は wasm32-wasip1 と同じになってしまった。cargo component 側も対応が必要だったりするのかな?


https://qiita.com/fits/items/aaf477de275573658f8a

上のブログを見ていると、どうやら package.metadata.component.target.dependencies とかに wasip2 の依存を追加して wasm32-unknown-unknown でビルドしてやればちゃんと wasip2 を利用する component が作れるみたい。


interface を定義して wa.dev にアップロード (cargo component はローカルの wit を target 指定できないのかな?)

$ mkdir -p hello-wasip2-wit/wit
$ cd hello-wasip2-wit
$ cat wit/world.wit
package tanishiking:hello-wasip2@0.1.0;

world run {
    import wasi:cli/stdout@0.2.0;
    export wasi:cli/run@0.2.0;
}

$ wkg wit build
$ wkg publish tanishiking:hello-wasip2@0.1.0.wasm

実装

$ cd ..
$ ls
hello-wasip2-wit/
$ cargo component new --lib --namespace tanishiking --target tanishiking:hello-wasip2@0.1.0 hello-wasip2

$ cargo component build
// to check src/bindings.rs is generated
// implement
$ cat src/lib.rs
#[allow(warnings)]
mod bindings;

use bindings::exports::wasi::cli::run::Guest;
use bindings::wasi::cli::stdout::get_stdout;

struct Component;

impl Guest for Component {
    fn run() -> Result<(), ()> {
        let stdout = get_stdout();
        let s = "hello";
        stdout.write(s.as_bytes()).unwrap();
        stdout.flush().unwrap();
        Ok(())
    }
}

bindings::export!(Component with_types_in bindings);

$ cargo component build --target wasm32-unknown-unknown -r
// do not use wasm32-wasip1 to avoid using wasi preview1 adapter

$ wasmtime target/wasm32-unknown-unknown/release/hello_wasip2.wasm
hello%

https://github.com/tanishiking/ferrisify-wasm-component


wasm-tools print target/wasm32-unknown-unknown/release/hello_wasip2.wasm を explainer といっしょに読んでいく

https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#component-model-ast-explainer

tanishikingtanishiking

Wasm Component では 5つの component-level index (component) functions / (component) values / (component) types / component instances / components が追加される。また module instances / modules についても新たに index space が追加 (component には複数の core module 定義や、その instance が含まれるため)

対象的に Wasm 1.0 であった module 内の index space は (core) functions / (core) tables / (core) memories / (core) globals / (core) types というように "core" prefix をつけて呼ばれそう。

https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#index-spaces


それで .wasm の中身を見ていくとまず最初に以下のような type , import, alias がいくつか並んでいる。

(component
  (type (;0;)
    (instance
      (export (;0;) "error" (type (sub resource)))
    )
  )
  (import "wasi:io/error@0.2.0" (instance (;0;) (type 0)))
  (alias export 0 "error" (type (;1;)))
  ;; ...

(type (;0;) (instance (export (;0;) "error" (type (sub resource)))))

top-level component は以下のようなBNFで構成され

component  ::= (component <id>? <definition>*)
definition ::= core-prefix(<core:module>)
             | core-prefix(<core:instance>)
             | core-prefix(<core:type>)
             | <component>
             | <instance>
             | <alias>
             | <type>
             | <canon>
             | <start> 🪺
             | <import>
             | <export>
             | <value> 🪙

where core-prefix(X) parses '(' 'core' Y ')' when X parses '(' Y ')'

この <type>Component-level type ってやつで、これは core module で定義される core typeよりも high level な構造を持つ

type          ::= (type <id>? <deftype>)
deftype       ::= <defvaltype>
                | <resourcetype>
                | <functype>
                | <componenttype>
                | <instancetype>
instancetype  ::= (instance <instancedecl>*)
instancedecl  ::= core-prefix(<core:type>)
                | <type>
                | <alias>
                | <exportdecl>
                | <value> 🪙
exportdecl    ::= (export <exportname> bind-id(<externdesc>))
externdesc    ::= (<sort> (type <u32>) )
                | core-prefix(<core:moduletype>)
                | <functype>
                | <componenttype>
                | <instancetype>
                | (value <valuebound>) 🪙
                | (type <typebound>)

https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#type-definitions

でこの (type (;0;) (instance ...)) はどんな型を ;0; に bind しているかというと instance type というやつ。なるほど interface みたいなものを定義するのね。

The instance type constructor describes a list of named, typed definitions that can be imported or exported by a component. Informally, instance types correspond to the usual concept of an "interface" and instance types thus serve as static interface descriptions.

つまり ``resource 型の subtype (subtype 関係って component model であるの?) である "error" を export する instance 型。ということになるのかな?

(import "wasi:io/error@0.2.0" (instance (;0;) (type 0)))

これは wasi:io/error@0.2.0 をインポート (これは多分 Runtime が提供する) し、type 0 (上で定義した instance 型) の component instance (;0;) に bind するということになる。

Both import and export definitions append a new element to the index space of the imported/exported sort which can be optionally bound to an identifier in the text format. In the case of imports, the identifier is bound just like Core WebAssembly, as part of the externdesc (e.g., (import "x" (func $x)) binds the identifier $x).
https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#import-and-export-definitions

確かに wasi:io/errorerror という resource を持つ

https://github.com/WebAssembly/WASI/blob/77435e2fa68f2b02a4e60d82e753b25e93dfeb47/wasip2/io/error.wit

(alias export 0 "error" (type (;1;)))

Alias definitions project definitions out of other components' index spaces and into the current component's index spaces

とのこので、alias は他のコンポーネントのインデックス空間からこのインデックス空間に定義をprojectすること。

alias            ::= (alias <aliastarget> (<sort> <id>?))
aliastarget      ::= export <instanceidx> <name>
                   | core export <core:instanceidx> <core:name>
                   | outer <u32> <u32>

(alias export 0 "error" (type (;1;))) の 0 は instanceidx、上で import した wasi:io/error@0.2.0 の instance を示す。

In the case of export aliases, validation ensures name is an export in the target instance and has a matching sort.

とあるように、instance 0 から export されている error という型定義を、このコンポーネントの ;1; という component-level type に割り当てている。
なので instance 0 からexportされているerror型を(type (;1;)) に alias しているってことかな。


こんな調子で

  • wasi:io/streams@0.2.0
  • wasi:cli/stdout@0.2.0

もimportされていっている。上2つの instance型は少し複雑なので見ても良いかも

tanishikingtanishiking

wasi:io/streams@0.2.0 の import, alias

  (type (;2;)
    (instance
      (export (;0;) "output-stream" (type (sub resource)))
      (alias outer 1 1 (type (;1;)))
      (export (;2;) "error" (type (eq 1)))
      (type (;3;) (own 2))
      (type (;4;) (variant (case "last-operation-failed" 3) (case "closed")))
      (export (;5;) "stream-error" (type (eq 4)))
      (type (;6;) (borrow 0))
      (type (;7;) (list u8))
      (type (;8;) (result (error 5)))
      (type (;9;) (func (param "self" 6) (param "contents" 7) (result 8)))
      (export (;0;) "[method]output-stream.write" (func (type 9)))
      (type (;10;) (func (param "self" 6) (result 8)))
      (export (;1;) "[method]output-stream.flush" (func (type 10)))
    )
  )
  (import "wasi:io/streams@0.2.0" (instance (;1;) (type 2)))
  (alias export 1 "output-stream" (type (;3;)))

はじめに interface 定義から、今回はちょっとややこしいな

(instance ...)

(export (;0;) "output-stream" (type (sub resource)))

これは instance が resource 型である output-stream を export していること

(alias outer 1 1 (type (;1;)))

  • In the case of outer aliases, the u32 pair serves as a de Bruijn index, with first u32 being the number of enclosing components/modules to skip and the second u32 being an index into the target's sort's index space. In particular, the first u32 can be 0, in which case the outer alias refers to the current component. To maintain the acyclicity of module instantiation, outer aliases are only allowed to refer to preceding outer definitions.

  • なので1つ外のcomponentの、target sort (index space の種類、func, value, type, component など) ここでは type の index 1 を(この componentの) type ;1; に割当ている。
  • で、外の type 1(alias export 0 "error" (type (;1;))) ですね。つまり type (;1;) は wasi:io/error の型

(export (;2;) "error" (type (eq 1)))

eq って何? どうやら externdesc には typebound を指定できるらしい (今まで読み飛ばしてた sub もそう)

externdesc ::= (<sort> (type <u32>) ) | ... | (type <typebound>), typebound ::= (eq <typeidx>) | (sub resource)

The eq bound adds a type equality rule (extending the built-in set of subtyping rules mentioned above) saying that the imported type is structurally equivalent to the type referenced in the bound

"subtyping rules mentioned above" って何?

the type equality relation on ASTs is relaxed to a more flexible subtyping relation. Currently, subtyping is only relaxed for instance and component types, but may be relaxed for more type constructors in the future to better support API Evolution (being careful to understand how subtyping manifests itself in the wide variety of source languages so that subtype-compatible updates don't inadvertantly break source-level clients).
Component and instance subtyping allows a subtype to export more and import less than is declared by the supertype, ignoring the exact order of imports and exports and considering only names

なるほど import と export をして 構造的部分型みたいなのになってるのね。
それで eq は構造が完全に一致していないといけない。なので (export (;2;) "error" (type (eq 1))) は先ほど import した error の型と完全に一致する error export が実装されていることを期待する

(type (;3;) (own 2))

own - a unique, opaque address of a resource that will be destroyed when this value is dropped
https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#fundamental-value-types

これを分かるには Handle Type というものを分かる必要がありそう。

The own and borrow value types are both handle types. Handles logically contain the opaque address of a resource and avoid copying the resource when passed across component boundaries. By way of metaphor to operating systems, handles are analogous to file descriptors, which are stored in a table and may only be used indirectly by untrusted user-mode processes via their integer index in the table.

handle っていうのは resource を指し示す file descriptor みたいなものらしい。core-module では i32 に lower され、component-level ではこのhandle typeにliftされる。

In the Component Model, handles are lifted-from and lowered-into i32 values that index an encapsulated per-component-instance handle table that is maintained by the canonical function definitions described below.

lift/lowerというのは component-level のfunctype/typeと、core functype/type との相互変換。

lift wraps a core function (of type core:functype) to produce a component function (of type functype) that can be passed to other components.
lower wraps a component function (of type functype) to produce a core function (of type core:functype) that can be imported and called from Core WebAssembly code inside the current component.

handle table なる新しい概念も出てきたけど一旦置いておく。 process に紐づく file descriptor 一覧みたいなの?

(type (;6;) (borrow 0))

an opaque address of a resource that must be dropped before the current export call returns
はあ

(type (;8;) (result (error 5)))

(result <valtype>? (error <valtype>)?)
result isn't just a variant, it's a variant that means success or failure, so source-code bindings can expose it via idiomatic source-language error reporting.

なるほどRustの Result<T, E> ? これは wit では result<_, stream-error> になる。


(import "wasi:io/streams@0.2.0" (instance (;1;) (type 2)))

ということで interface 定義したので、wasi:io/streams@0.2.0 を import して instance (;1;) に bind する

"[method]output-stream.write" こういうの、型は合ってるんだけど名前は当然違っていて、何か変換ルールがあるのか?

なんかそれっぽい https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md#import-and-export-definitions

export ::= (export <id>? "<exportname>" <sortidx> <externdesc>?)


exportname    ::= <plainname>
                | <interfacename>
importname    ::= ...
plainname     ::= <label>
                | '[constructor]' <label>
                | '[method]' <label> '.' <label>
                | '[static]' <label> '.' <label>

the first label is the name of the resource and the second label is the logical field name of the function.

なるほど [method]<resource name>.<function name> で書いて、code generator はネストしたメソッドを生成することを期待されるのか

型マッチしてそうですね https://wa.dev/wasi:io

(alias export 1 "output-stream" (type (;3;)))

これは先程と同様、instance 1 の output-stream の型を type (;3;) に bind

tanishikingtanishiking

ここから core module 3 つが定義される

(core module (;0;)

多分Rustのコード本体、ここは普通のWasmモジュールと同じ定義に従うのでそんなに見るところはないけど、wasi:cli/run がどう実装されているのか見ておきたい。まずは wasip2 の import

    (type (;2;) (func (param i32)))
    (type (;3;) (func (result i32)))
    (type (;4;) (func (param i32 i32 i32 i32)))
    (type (;5;) (func (param i32 i32)))

    (import "wasi:io/error@0.2.0" "[resource-drop]error" (func (;0;) (type 2)))
    (import "wasi:cli/stdout@0.2.0" "get-stdout" (func (;1;) (type 3)))
    (import "wasi:io/streams@0.2.0" "[method]output-stream.write" (func (;2;) (type 4)))
    (import "wasi:io/streams@0.2.0" "[method]output-stream.flush" (func (;3;) (type 5)))
    (import "wasi:io/streams@0.2.0" "[resource-drop]output-stream" (func (;4;) (type 2)))

get-stdout などの resource を扱う関数はすべて i32 (lower した resource handle)を取り扱う関数になっているな。

おもむろに export も見ておく

    (export "memory" (memory 0))
    (export "wasi:cli/run@0.2.0#run" (func 13))
    (export "cabi_realloc_wit_bindgen_0_34_0" (func 44))
    (export "cabi_realloc" (func 47))
    (export "__data_end" (global 1))
    (export "__heap_base" (global 2))
  • wasi:cli/run@0.2.0#run これが export されていると、ここが endpoint になる。#run?
    • 違う、top-level component が (export (;4;) "wasi:cli/run@0.2.0" (instance 3)) しているのが重要で、ここでの名前は何でも良いのだ
    • とにかく func 13 を endpoint として使ってほしそうに export しているので、それを見てみよう
  • cabi_realloc https://github.com/rust-lang/rust/blob/798fb83f7d24e31b16acca113496f39ff168c143/library/std/src/sys/pal/wasip2/cabi_realloc.rs#L1-L21 これ、canonicalABI調べるときにまた読み直そう
  • cabi_realloc_wit_bindgen_0_34_0 あとで調べる
  • func 13 の中身は canonical ABI を調べないとあんまり分からなそうだったのでまた後で調べる

(core module (;1;)

  (core module (;1;)
    (type (;0;) (func (param i32 i32 i32 i32)))
    (type (;1;) (func (param i32 i32)))
    (func $"indirect-wasi:io/streams@0.2.0-[method]output-stream.write" (;0;) (type 0) (param i32 i32 i32 i32)
      local.get 0
      local.get 1
      local.get 2
      local.get 3
      i32.const 0
      call_indirect (type 0)
    )
    (func $"indirect-wasi:io/streams@0.2.0-[method]output-stream.flush" (;1;) (type 1) (param i32 i32)
      local.get 0
      local.get 1
      i32.const 1
      call_indirect (type 1)
    )
    (table (;0;) 2 2 funcref)
    (export "0" (func $"indirect-wasi:io/streams@0.2.0-[method]output-stream.write"))
    (export "1" (func $"indirect-wasi:io/streams@0.2.0-[method]output-stream.flush"))
    (export "$imports" (table 0))
    (@producers
      (processed-by "wit-component" "0.216.0")
    )
  )
  • indirect-wasi:io/streams@0.2.0-[method]output-stream.write/flush は funcref を table から取得して呼び出してるだけに見える

(core module (;2;)

  (core module (;2;)
    (type (;0;) (func (param i32 i32 i32 i32)))
    (type (;1;) (func (param i32 i32)))
    (import "" "0" (func (;0;) (type 0)))
    (import "" "1" (func (;1;) (type 1)))
    (import "" "$imports" (table (;0;) 2 2 funcref))
    (elem (;0;) (i32.const 0) func 0 1)
    (@producers
      (processed-by "wit-component" "0.216.0")
    )
  )

関数とtableをimportしてるだけにみえる。なにこれ?

tanishikingtanishiking

ここまででコンポーネントの定義ができたので次は組み合わせてinstance化していく

下から見ていくと分かりやすい。最終的にこの component が export したい wasi:cli/run@0.2.0 から

export wasi:cli/run@0.2.0

  (instance (;3;) (instantiate 0
      (with "import-func-run" (func 3))
    )
  )
  (export (;4;) "wasi:cli/run@0.2.0" (instance 3)) ;; instance 3 を wasi:cli/run@0.2.0 として export

wasi:cli/run@0.2.0 は以下のような定義

@since(version = 0.2.0)
interface run {
  /// Run the program.
  @since(version = 0.2.0)
  run: func() -> result;
}
/// https://github.com/WebAssembly/WASI/blob/7e004629219a09e55ac4bd5897b05b2966a9f56d/wasip2/cli/run.wit

で、それを満たすような instance を用意して上げる必要がある。そしてそれが

(instance (;3;) (instantiate 0 (with "import-func-run" (func 3))))

どういう意味だろう

// component の instantiate (今回はこっち)
instance       ::= (instance <id>? <instanceexpr>)
instanceexpr   ::= (instantiate <componentidx> <instantiatearg>*)
                 | <inlineexport>*
instantiatearg ::= (with <name> <sortidx>)
                 | (with <name> (instance <inlineexport>*))
name           ::= <core:name>
sortidx        ::= (<sort> <u32>)
sort           ::= core <core:sort>
                 | func
                 | value 🪙
                 | type
                 | component
                 | instance

instantiate 0 (with "import-func-run" (func 3)component 0func 3 という (component-level)関数を imported-func-run という名前で instantiateする

  • component 0 はすぐ上にある定義(下で見る)
  • func 3 はこれまた上で定義されている関数、これも後で見ていこう。

component ;0; 定義

  (component (;0;)
    (type (;0;) (result))
    (type (;1;) (func (result 0)))
    (import "import-func-run" (func (;0;) (type 1)))
    (type (;2;) (result))
    (type (;3;) (func (result 2)))
    (export (;1;) "run" (func 0) (func (type 3)))
  )
  • import している import-func-run が先程 with で提供された名前と一致していることが分かる(型は知らんが)
  • (export (;1;) "run" (func 0) (func (type 3)))func 0 (import-func-run) を再度 export している。
  • 確かに wasip2:cli/run@0.2.0 のインターフェースを満たしている (subtype になっている)(というのか?)

(func (;3;) (type 8) (canon lift (core func 8)))

  (type (;7;) (result))
  (type (;8;) (func (result 7)))
  (func (;3;) (type 8) (canon lift (core func 8)))

canon lift は core function を component-level function に lift する関数。canon lower もあってこれは逆に component-level function を core function に lower する

この境界部分の変換はcanonicalABIでいくつかオプションが指定可能なようで string-encoding やどの linear memory を使うかなどの option も指定できるぽい。これは canonical ABI を眺めていく過程で調べる。

canon    ::= (canon lift core-prefix(<core:funcidx>) <canonopt>* bind-id(<externdesc>))
           | (canon lower <funcidx> <canonopt>* (core func <id>?))
canonopt ::= string-encoding=utf8
           | string-encoding=utf16
           | string-encoding=latin1+utf16
           | (memory <core:memidx>)
           | (realloc <core:funcidx>)
           | (post-return <core:funcidx>)
           | async 🔀
           | (callback <core:funcidx>) 🔀
  • lift wraps a core function (of type core:functype) to produce a component function (of type functype) that can be passed to other components.
  • lower wraps a component function (of type functype) to produce a core function (of type core:functype) that can be imported and called from Core WebAssembly code inside the current component.

ともかく、 (func (;3;) (type 8) (canon lift (core func 8))) は core function 8 を component-level function 3 (type 8) に lift している。core func 8 を見てみよう

(alias core export 4 "wasi:cli/run@0.2.0#run" (core func (;8;)))

これは core module instance 4 から export されている "wasi:cli/run@0.2.0#run" を core function 8 として bind するという意味。その名前前に見たな。

この core instance 4 は実は core module 0 を instantiate するもの、次はそれを見ていこう

tanishikingtanishiking

(core instance (;4;) (instantiate 0

  (core instance (;4;) (instantiate 0
      (with "wasi:io/error@0.2.0" (instance 1)) ;; core module instance 1 を `wasi:io/error@0.2.0` として与える
      (with "wasi:cli/stdout@0.2.0" (instance 2))
      (with "wasi:io/streams@0.2.0" (instance 3))
    )
  )

core module 0 は例のメインの instance で、以下のようなimportsがあった

    (type (;2;) (func (param i32)))
    (type (;3;) (func (result i32)))
    (type (;4;) (func (param i32 i32 i32 i32)))
    (type (;5;) (func (param i32 i32)))

    (import "wasi:io/error@0.2.0" "[resource-drop]error" (func (;0;) (type 2)))
    (import "wasi:cli/stdout@0.2.0" "get-stdout" (func (;1;) (type 3)))
    (import "wasi:io/streams@0.2.0" "[method]output-stream.write" (func (;2;) (type 4)))
    (import "wasi:io/streams@0.2.0" "[method]output-stream.flush" (func (;3;) (type 5)))
    (import "wasi:io/streams@0.2.0" "[resource-drop]output-stream" (func (;4;) (type 2)))

core:instantiatearg が component instance を渡している一方。core module の instance は当然ながら core function を期待している。ここらへんの変換はどのように行われるんだろうか

  • インポートの最初の core:name は、core:instantiatearg の名前付きリストとマッチする必要がある。wasi:cli/error@0.2.0 など。
  • インポートの2番目の core:name は、見つけたcore module instanceのexportから名前を照合して、どのexport関数がimportされるかが決定される。

それぞれの core module instance は以下のように定義されており、確かに名前がマッチする

  (core instance (;1;)
    (export "[resource-drop]error" (func 0))
  )
  (core instance (;2;)
    (export "get-stdout" (func 1))
  )
  (core instance (;3;)
    (export "[resource-drop]output-stream" (func 2))
    (export "[method]output-stream.write" (func 3))
    (export "[method]output-stream.flush" (func 4))
  )

core instance を instantiate じゃない方法で作っているの初めて見た。どういう意味だろう? これらは inlineexport というやつで、まあ想像したら分かるように core function などをそのまま export するだけの便利記法。

core:instanceexpr   ::= (instantiate <core:moduleidx> <core:instantiatearg>*)
                      | <core:inlineexport>*
core:inlineexport   ::= (export <core:name> <core:sortidx>)

The core:inlineexport* form of core:instanceexpr allows module instances to be created by directly tupling together preceding definitions, without the need to instantiate a helper module. The core:inlineexport* form of core:instantiatearg is syntactic sugar that is expanded during text format parsing into an out-of-line instance definition referenced by with.

次はそれらの core function 0-4 を見ていく

tanishikingtanishiking

core function 0-4

  (core func (;0;) (canon resource.drop 5))
  (core func (;1;) (canon lower (func 0)))
  (core func (;2;) (canon resource.drop 6))
  (alias core export 0 "0" (core func (;3;)))
  (alias core export 0 "1" (core func (;4;)))

ひとつひとつ見ていく

(core func (;0;) (canon resource.drop 5))

(export "[resource-drop]error" (func 0)) に対応するやつ。

canon resource.dropcanonical built-ins というやつで

stack-switching や thread proposal を除けば今知っておくべきものは以下の3つ

canon ::= ...
        | (canon resource.new <typeidx> (core func <id>?))
        | (canon resource.drop <typeidx> async? (core func <id>?))
        | (canon resource.rep <typeidx> (core func <id>?))
  • The resource.new built-in has type [i32] -> [i32] and creates a new resource (with resource type typeidx) with the given i32 value as its representation and returning the i32 index of a new handle pointing to this resource.
  • The resource.drop drops a resource handle (with resource type typeidx) at a given i32 index. If the dropped handle owns the resource, the resource's dtor is called, if present. If async is not specified, the core function type of resource.drop is [i32] -> []. Otherwise, the core function type of async drop is [i32] -> [i32], where the returned i32 is either 0 (if the drop completed eagerly) or the index of the in-progress drop.
  • The resource.rep built-in has type [i32] -> [i32] and returns the i32 representation of the resource (with resource type typeidx) pointed to by the handle at the given i32 index.

resource をnew・dropするという行為が具体的にどういうことを表しているのかは canonical ABI を見たほうが良いかもしれない。とりあえず canon resource.drop 5 引数として受け取ったi32の resource handle (type 5) を drop するということらしい?

type 5 はというと以下のように定義されていて

  (alias export 0 "error" (type (;5;)))

instance 0 (core module instance ではない!!すぐ直前に (core instance (;0;) (instantiate 1)) があるがこれを示しているわけではない) から export されている "error" という型定義を、component level type ;5; に割り当てている。そしてそれは (sub resource) だった。

(core func (;1;) (canon lower (func 0)))

(export "get-stdout" (func 1)) に対応するやつ。

canon lower (func 0) は component-level function 0 を core function 1 に lower してくれる。

  • component-level function 0 は (alias export 2 "get-stdout" (func (;0;))) で instance 2 の "get-stdout" を func 0 に bind
  • instance 2 は (import "wasi:cli/stdout@0.2.0" (instance (;2;) (type 4)))
  • これを canon lower して core function 1 になり、core module の import を見る限りそのシグネチャは (func (result i32)) になっていてほしい。うーんどういう変換が行われるんですかね? これも canonical ABI みないとわからん?

(core func (;2;) (canon resource.drop 6))

引数として受け取ったi32の resource handle (type 6) を drop する

  (alias export 1 "output-stream" (type (;6;)))

instance 1 は (import "wasi:io/streams@0.2.0" (instance (;1;) (type 2)))output-stream は resource 型

(alias core export 0 "0" (core func (;3;)))

  • core module instance 0 の export "0" を core func (;3;) に bind
  • core module instance 0 は (core instance (;0;) (instantiate 1)) で、 core module 1 を instantiate したもの
  • その "0"(export "0" (func $"indirect-wasi:io/streams@0.2.0-[method]output-stream.write"))

(alias core export 0 "1" (core func (;4;)))

これは同様

tanishikingtanishiking

だいたい読めるようになったけど、まだ CanonicalABI 知らないとわからないところ多く

  • canon lift/lower が component-level と core module とをどう変換しているのかよく分からなかった。
  • core module 内では resource handle などがどのように利用されるのかよく分かってない。(読もうとしたけど意味分からなかった)
  • cabi_realloc など

次は CanonicalABI と照らし合わせながら WASIp2 を使って core module の関数を読んでいくぞ

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