HelloWorldに引き続き、WAT形式でWASIp2を書いていく。今回は、コマンドライン引数をとって、引数を標準出力に出す。
引数を扱うミニマムな実装
get-arguments
を使うが特に使うことなく捨てて、そのまま終了するミニマムなプログラム。
get-arguments
はコンポーネントインターフェースとしては引数0個、返り値として、list<string>
を返す。
これをコア関数で表すと、最新ではresult
が複数値に対応しているので (result i32 i32)
になるはずなんだけど、wasmtimeはどうやら、result
が1つの値しか返せなかった時の仕様のままらしく、引数に返り値を格納するポインタを渡す形式らしい。 つまり、 (func (result i32 i32))
でなく、(func (param i32))
。
(component
(import "wasi:cli/environment@0.2.7" (instance $i_wasi_cli_environment
(export "get-arguments" (func (result (list string))))
))
(core module $m_memory
(memory (export "memory") 1)
;; realloc function with canonical ABI
;; Parameters: (original_ptr: i32, original_size: i32, alignment: i32, new_size: i32) -> i32
(func (export "realloc")
(param $original_ptr i32)
(param $original_size i32)
(param $alignment i32)
(param $new_size i32)
(result i32)
(i32.const 1024) ;; Dummy implementation, always returns 1024
)
)
(core instance $i_memory (instantiate $m_memory))
(alias core export $i_memory "memory" (core memory $_memory))
(core func $_get_arguments (canon lower (func $i_wasi_cli_environment "get-arguments") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core instance $i_environment
(export "lower-get-arguments" (func $_get_arguments))
)
(core module $m_app
(func $get_arguments (import "environment" "lower-get-arguments") (param i32))
;; (memory $memory (import "memory" "memory") 1)
(func (export "main") (result i32)
(call $get_arguments (i32.const 2048)) ;; argvの格納先
(i32.const 0)
)
)
(core instance $i_app (instantiate $m_app
(with "environment" (instance $i_environment))
;; (with "memory" (instance $i_memory))
))
(func $main (result (result)) (canon lift (core func $i_app "main")))
(component $app
(import "main" (func $t_main (result (result))))
(export "run" (func $t_main))
)
(instance
(export "wasi:cli/run@0.2.7")
(instantiate $app
(with "main" (func $main)))
)
)
目新しいものは realloc
。メモリ操作が必要なコンポーネント関数には渡さないといけない。
(core func $_get_arguments
(canon lower
(func $i_wasi_cli_environment "get-arguments")
(memory $_memory)
(realloc (func $i_memory "realloc"))))
とりあえず、ダミーとして、常に 1024
番地を返す実装を書いた。
;; realloc function with canonical ABI
;; Parameters: (original_ptr: i32, original_size: i32, alignment: i32, new_size: i32) -> i32
(func (export "realloc")
(param $original_ptr i32)
(param $original_size i32)
(param $alignment i32)
(param $new_size i32)
(result i32)
(i32.const 1024) ;; Dummy implementation, always returns 1024
)
args
の1要素ごとに実行されるようで、1024
番地を先頭に渡した引数がセグメントごとに何回も書き込まれる
引数配列を標準出力する。
get-args
を正しく呼び出すには、realloc
の実装が必要なので、wasmtimeの実装から拝借する。
標準出力まわりは前回のHello,Worldで使用したのでそれを流用。
(component
(import "wasi:io/error@0.2.7" (instance $i_wasi_io_error
(export "error" (type (sub resource)))
))
(alias export $i_wasi_io_error "error" (type $t_io_error))
(import "wasi:io/streams@0.2.7" (instance $i_wasi_io_stream
;; resourceの部分型$osを定義しoutput-streamとして払い出す
(export "output-stream" (type $t_output_stream (sub resource)))
;; エラーの内部型を定義
(type $t_error
(variant
(case "last-operation-failed" (own $t_io_error))
(case "closed")))
;; エラー型エクスポート
(export "stream-error" (type $t_stream_error (eq $t_error)))
;; output-streamのメソッドをエクスポート
(export "[method]output-stream.write"
(func
(param "self" (borrow $t_output_stream))
(param "contents" (list u8))
(result (result (error $t_stream_error)))))
))
(alias export $i_wasi_io_stream "output-stream" (type $t_output_stream))
(import "wasi:cli/stdout@0.2.7" (instance $i_wasi_cli_stdout
(export "output-stream" (type (eq $t_output_stream)))
(export "get-stdout" (func (result (own $t_output_stream))))
))
(import "wasi:cli/environment@0.2.7" (instance $i_wasi_cli_environment
(export "get-arguments" (func (result (list string))))
))
(core module $m_memory
(memory (export "memory") 1)
(data (i32.const 512) "\n")
(global $last (mut i32) (i32.const 1024))
(func $realloc (export "realloc")
(param $old_ptr i32)
(param $old_size i32)
(param $align i32)
(param $new_size i32)
(result i32)
(local $ret i32)
;; Test if the old pointer is non-null
local.get $old_ptr
if
;; If the old size is bigger than the new size then
;; this is a shrink and transparently allow it
local.get $old_size
local.get $new_size
i32.gt_u
if
local.get $old_ptr
return
end
;; otherwise fall through to allocate a new chunk which will later
;; copy data over
end
;; align up `$last`
(global.set $last
(i32.and
(i32.add
(global.get $last)
(i32.add
(local.get $align)
(i32.const -1)))
(i32.xor
(i32.add
(local.get $align)
(i32.const -1))
(i32.const -1))))
;; save the current value of `$last` as the return value
global.get $last
local.set $ret
;; bump our pointer
(global.set $last
(i32.add
(global.get $last)
(local.get $new_size)))
;; while `memory.size` is less than `$last`, grow memory
;; by one page
(loop $loop
(if
(i32.lt_u
(i32.mul (memory.size) (i32.const 65536))
(global.get $last))
(then
i32.const 1
memory.grow
;; test to make sure growth succeeded
i32.const -1
i32.eq
if unreachable end
br $loop)))
;; ensure anything necessary is set to valid data by spraying a bit
;; pattern that is invalid
local.get $ret
i32.const 0xde
local.get $new_size
memory.fill
;; If the old pointer is present then that means this was a reallocation
;; of an existing chunk which means the existing data must be copied.
local.get $old_ptr
if
local.get $ret ;; destination
local.get $old_ptr ;; source
local.get $old_size ;; size
memory.copy
end
local.get $ret
)
)
(core instance $i_memory (instantiate $m_memory))
(alias core export $i_memory "memory" (core memory $_memory))
(core func $_get_stdout (canon lower (func $i_wasi_cli_stdout "get-stdout")))
(core func $_output_stream_write (canon lower (func $i_wasi_io_stream "[method]output-stream.write") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core instance $i_stream
(export "lower-get-stdout" (func $_get_stdout))
(export "lower-write" (func $_output_stream_write))
)
(core func $_get_arguments (canon lower (func $i_wasi_cli_environment "get-arguments") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core instance $i_environment
(export "lower-get-arguments" (func $_get_arguments))
)
(core module $m_app
(func $get_stdout (import "output-stream" "lower-get-stdout") (result i32))
(func $write (import "output-stream" "lower-write") (param i32 i32 i32 i32))
(func $get_arguments (import "environment" "lower-get-arguments") (param i32))
(memory $memory (import "memory" "memory") 1)
(func (export "main") (result i32)
(local $argv i32)
(local $argc i32)
(local $i i32)
(local $ptr i32)
(local $stream i32)
(local.set $stream (call $get_stdout))
(call $get_arguments (i32.const 0)) ;; argvの格納先
(local.set $argv (i32.load (i32.const 0))) ;; argv
(local.set $argc (i32.load (i32.const 4))) ;; argc
(local.set $i (i32.const 0))
(local.set $ptr (local.get $argv))
(loop $loop
(i32.lt_s (local.get $i) (local.get $argc))
(if (then
(call $write
(local.get $stream)
(i32.load (local.get $ptr))
(i32.load (i32.add (local.get $ptr) (i32.const 4)))
(i32.const 8) ;; return value
)
(call $write
(local.get $stream)
(i32.const 512) ;; "\n"
(i32.const 1) ;; length
(i32.const 8) ;; return value
)
(local.set $i (i32.add (local.get $i) (i32.const 1)))
(local.set $ptr (i32.add (local.get $ptr) (i32.const 8)))
br $loop
))
)
(i32.const 0)
)
)
(core instance $i_app (instantiate $m_app
(with "output-stream" (instance $i_stream))
(with "environment" (instance $i_environment))
(with "memory" (instance $i_memory))
))
(func $main (result (result)) (canon lift (core func $i_app "main")))
(component $app
(import "main" (func $t_main (result (result))))
(export "run" (func $t_main))
)
(instance
(export "wasi:cli/run@0.2.7")
(instantiate $app
(with "main" (func $main)))
)
)
長い。引数返すだけのほぼechoプログラムなのに。
$ wasmtime run .\03_args\main.wat arg1 arg2 arg3
main.wat
arg1
arg2
arg3
$