前回引数を取ってダンプするechoモドキを書いた。
今度は引数で指定したパスのファイルを標準出力するcatモドキを書いてみる。
filesystem の型情報を埋め込むのが大変。 そろそろプリプロセッサ的なものを書く必要がありそう。
(component
;; このコンポーネントは簡易的な "cat" の実装です。
;; WASI の各種インスタンス(io/streams, cli/stdout, filesystem 等)を利用して
;; 順に引数を読み取り、指定されたファイルを開いて stdin/stdout に出力します。
;; ここでは core->component / component->core の canonical ABI バインディングが用いられています。
(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)))
(export "input-stream" (type $t_input_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)))))
(export "[method]input-stream.blocking-read"
(func
(param "self" (borrow $t_input_stream))
(param "size" u64)
(result (result (list u8) (error $t_stream_error)))))
))
(alias export $i_wasi_io_stream "output-stream" (type $t_output_stream))
(alias export $i_wasi_io_stream "input-stream" (type $t_input_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))))
))
(import "wasi:filesystem/types@0.2.7" (instance $i_wasi_filesystem_types
;; resourceの部分型$fdを定義しfile-descriptorとして払い出す
(export "descriptor" (type $t_file_descriptor (sub resource)))
(type $__t_path_flags
(flags "symlink-follow"))
(export "path-flags" (type $t_path_flags (eq $__t_path_flags)))
(type $__t_open_flags
(flags "create" "directory" "exclusive" "truncate"))
(export "open-flags" (type $t_open_flags (eq $__t_open_flags)))
(type $__t_descriptor_flags
(flags "read" "write" "file-integrity-sync" "data-integrity-sync" "requested-write-sync" "mutate-directory"))
(export "descriptor-flags" (type $t_descriptor_flags (eq $__t_descriptor_flags)))
(type $__t_error_code
(enum
"access"
"would-block"
"already"
"bad-descriptor"
"busy"
"deadlock"
"quota"
"exist"
"file-too-large"
"illegal-byte-sequence"
"in-progress"
"interrupted"
"invalid"
"io"
"is-directory"
"loop"
"too-many-links"
"message-size"
"name-too-long"
"no-device"
"no-entry"
"no-lock"
"insufficient-memory"
"insufficient-space"
"not-directory"
"not-empty"
"not-recoverable"
"unsupported"
"no-tty"
"no-such-device"
"overflow"
"not-permitted"
"pipe"
"read-only"
"invalid-seek"
"text-file-busy"
"cross-device"))
(export "error-code" (type $t_error_code (eq $__t_error_code)))
(export "[method]descriptor.open-at"
(func
(param "self" (borrow $t_file_descriptor))
(param "path-flags" $t_path_flags)
(param "path" string)
(param "open-flags" $t_open_flags)
(param "flags" $t_descriptor_flags)
(result (result (own $t_file_descriptor) (error $t_error_code)))))
(export "[method]descriptor.read-via-stream"
(func
(param "self" (borrow $t_file_descriptor))
(param "offset" u64)
(result (result (own $t_input_stream) (error $t_error_code)))))
))
(alias export $i_wasi_filesystem_types "descriptor" (type $t_descriptor))
(import "wasi:filesystem/preopens@0.2.7" (instance $i_wasi_filesystem_preopens
(export "get-directories" (func (result (list (tuple (own $t_descriptor) 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 func $_input_stream_read (canon lower (func $i_wasi_io_stream "[method]input-stream.blocking-read") (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))
(export "lower-read" (func $_input_stream_read))
)
(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 func $_get_directories (canon lower (func $i_wasi_filesystem_preopens "get-directories") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core instance $i_filesystem_preopens
(export "lower-get-directories" (func $_get_directories))
)
(core func $_descriptor_open_at (canon lower (func $i_wasi_filesystem_types "[method]descriptor.open-at") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core func $_descriptor_read_via_stream (canon lower (func $i_wasi_filesystem_types "[method]descriptor.read-via-stream") (memory $_memory) (realloc (func $i_memory "realloc"))))
(core instance $i_filesystem_types
(export "lower-open-at" (func $_descriptor_open_at))
(export "lower-read-via-stream" (func $_descriptor_read_via_stream))
)
(core module $m_app
(func $get_stdout (import "stream" "lower-get-stdout") (result i32))
(func $write (import "stream" "lower-write") (param i32 i32 i32 i32))
(func $read (import "stream" "lower-read") (param i32 i64 i32))
(func $get_arguments (import "environment" "lower-get-arguments") (param i32))
(func $get_directories (import "filesystem-preopens" "lower-get-directories") (param i32))
(func $open_at (import "filesystem-types" "lower-open-at") (param i32 i32 i32 i32 i32 i32 i32))
(func $read_via_stream (import "filesystem-types" "lower-read-via-stream") (param i32 i64 i32))
(memory $memory (import "memory" "memory") 1)
(func (export "main") (result i32)
(local $argv i32)
(local $argc i32)
(local $dirv i32)
(local $dirc i32)
(local $stream i32)
;; main の処理フロー(日本語解説):
;; 1. 標準出力のストリームハンドルを取得して $stream に格納
;; 2. get-arguments を呼び出し、アドレス 0x00 から argc/argv の情報を読み取る
;; 3. preopen のディレクトリリストを取得し、先頭の preopen ディレクトリを利用して open_at を呼び出す
;; 4. open_at の結果からファイル記述子を得て、read_via_stream -> read と呼び出してファイル内容を読み取る
;; 5. 読み取ったバッファを stdout に write する
(local.set $stream (call $get_stdout))
(call $get_arguments (i32.const 0x00)) ;; argvの格納先
(local.set $argv (i32.load (i32.const 0x00))) ;; argv
(local.set $argc (i32.load (i32.const 0x04))) ;; argc
(call $get_directories (i32.const 0x00)) ;; argvの格納先
(local.set $dirv (i32.load (i32.const 0x00))) ;; argv
(local.set $dirc (i32.load (i32.const 0x04))) ;; argc
(call $open_at
(i32.load (local.get $dirv)) ;; preopen dir fd
(i32.const 0) ;; path flags
(i32.load (i32.add (local.get $argv) (i32.const 8))) ;; path string ptr
(i32.load (i32.add (local.get $argv) (i32.const 12))) ;; path string len
(i32.const 0) ;; open flags
(i32.const 1) ;; descriptor flags
(i32.const 0x10) ;; return value
)
(call $read_via_stream
(i32.load (i32.add (i32.const 0x10) (i32.const 4))) ;; fd
(i64.const 0) ;; offset
(i32.const 0x18) ;; return value
)
(call $read
(i32.load (i32.add (i32.const 0x18) (i32.const 4))) ;; fd
(i64.const 63488) ;; max size
(i32.const 0x20) ;; return value
)
(call $write
(local.get $stream)
(i32.load (i32.add (i32.const 0x20) (i32.const 4))) ;; buffer ptr
(i32.load (i32.add (i32.const 0x20) (i32.const 8))) ;; buffer ptr
(i32.const 0x08) ;; return slot
)
(i32.const 0)
)
)
(core instance $i_app (instantiate $m_app
(with "stream" (instance $i_stream))
(with "environment" (instance $i_environment))
(with "filesystem-preopens" (instance $i_filesystem_preopens))
(with "filesystem-types" (instance $i_filesystem_types))
(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)))
)
)