WASIp2でcat

WASIp2でcat

前回引数を取ってダンプする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)))
  )
)