Stream: git-wasmtime

Topic: wasmtime / issue #12108 Intra-component stream reads/writ...


view this post on Zulip Wasmtime GitHub notifications bot (Dec 02 2025 at 19:15):

alexcrichton assigned dicej to issue #12108.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 02 2025 at 19:15):

alexcrichton opened issue #12108:

For this test case:

(component
  (core module $libc (memory (export "m") 1))
  (core instance $libc (instantiate $libc))

  (type $s (stream u32))
  (core func $stream.new (canon stream.new $s))
  (core func $stream.read (canon stream.read $s async (memory $libc "m")))
  (core func $stream.write (canon stream.write $s async (memory $libc "m")))

  (core module $m
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run")
      (local $tmp i64)
      (local $r i32)
      (local $w i32)
      (local.set $tmp (call $stream.new))

      (local.set $r (i32.wrap_i64 (local.get $tmp)))
      (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32))))

      (call $stream.read (local.get $r) (i32.const 0) (i32.const 4))
      i32.const -1 ;; BLOCKED
      i32.ne
      if unreachable end

      (call $stream.write (local.get $w) (i32.const 0) (i32.const 4))
      drop
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "stream.new" (func $stream.new))
      (export "stream.read" (func $stream.read))
      (export "stream.write" (func $stream.write))
    ))
  ))

  (func (export "run") (canon lift (core func $i "run")))
)

(assert_trap (invoke "run") "some message here")

this yields:

$ cargo run wast foo.wast -W component-model-async
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/x86_64-unknown-linux-gnu/debug/wasmtime wast foo.wast -W component-model-async`
Error: failed to run script file 'foo.wast'

Caused by:
    0: failed directive on foo.wast:45
    1: expected trap, got Component([])

view this post on Zulip Wasmtime GitHub notifications bot (Dec 02 2025 at 19:15):

alexcrichton added the wasm-proposal:component-model-async label to Issue #12108.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 02 2025 at 19:16):

alexcrichton commented on issue #12108:

Same for futures:

(component
  (core module $libc (memory (export "m") 1))
  (core instance $libc (instantiate $libc))

  (type $s (future u32))
  (core func $future.new (canon future.new $s))
  (core func $future.read (canon future.read $s async (memory $libc "m")))
  (core func $future.write (canon future.write $s async (memory $libc "m")))

  (core module $m
    (import "" "future.new" (func $future.new (result i64)))
    (import "" "future.read" (func $future.read (param i32 i32) (result i32)))
    (import "" "future.write" (func $future.write (param i32 i32) (result i32)))

    (func (export "run")
      (local $tmp i64)
      (local $r i32)
      (local $w i32)
      (local.set $tmp (call $future.new))

      (local.set $r (i32.wrap_i64 (local.get $tmp)))
      (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32))))

      (call $future.read (local.get $r) (i32.const 0))
      i32.const -1 ;; BLOCKED
      i32.ne
      if unreachable end

      (call $future.write (local.get $w) (i32.const 0))
      drop
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "future.new" (func $future.new))
      (export "future.read" (func $future.read))
      (export "future.write" (func $future.write))
    ))
  ))

  (func (export "run") (canon lift (core func $i "run")))
)

(assert_trap (invoke "run") "some message here")

view this post on Zulip Wasmtime GitHub notifications bot (Dec 03 2025 at 15:55):

dicej commented on issue #12108:

FYI, I've fixed this locally, but it turns out the p3_http_middleware_host_to_host is doing intra-component stream<u8> reads and writes. Specifically, the p3_http_middleware component is creating a stream, wrapping it in a request, and passing that request to the p3_http_echo component. Then the p3_http_echo component consumes the request, wraps the stream in a response, and returns the response. Finally, the p3_http_middleware component consumes the response and reads from the stream, not realizing it's the same stream it created.

I think we anticipated this kind of scenario, but it's not clear to me which component ought to change its behavior here. It seems unfair to make the p3_http_echo component conservatively create an intermediate stream to avoid passing the original one back to the caller, and it's not clear to me how p3_http_middleware could be defensive about it either.

@lukewagner do you have thoughts on this?

view this post on Zulip Wasmtime GitHub notifications bot (Dec 03 2025 at 16:37):

lukewagner commented on issue #12108:

The same-component trap is very much a "this will bite folks in unexpected and unfair ways" TODO to fix, so I think, unfortunately, the "right" fix is to have the echo server do an intermediate copy. FWIW, IIUC, once we get lazy-lowering, I think the same-component-instance case should mostly fall out for free (or with only modest effort) and so we could simply delete the trap.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 03 2025 at 17:18):

dicej commented on issue #12108:

I'm remembering now when we last discussed this, and I guess the ideal solution would be for wit_bindgen's Stream{Writer::write,Reader::read} to check for the intra-component case and switch to an in-memory channel. The tricky part is switching back and forth between the "intra" and "inter" states as the read handle is passed in and out of the component.

view this post on Zulip Wasmtime GitHub notifications bot (Dec 03 2025 at 18:47):

alexcrichton closed issue #12108:

For this test case:

(component
  (core module $libc (memory (export "m") 1))
  (core instance $libc (instantiate $libc))

  (type $s (stream u32))
  (core func $stream.new (canon stream.new $s))
  (core func $stream.read (canon stream.read $s async (memory $libc "m")))
  (core func $stream.write (canon stream.write $s async (memory $libc "m")))

  (core module $m
    (import "" "stream.new" (func $stream.new (result i64)))
    (import "" "stream.read" (func $stream.read (param i32 i32 i32) (result i32)))
    (import "" "stream.write" (func $stream.write (param i32 i32 i32) (result i32)))

    (func (export "run")
      (local $tmp i64)
      (local $r i32)
      (local $w i32)
      (local.set $tmp (call $stream.new))

      (local.set $r (i32.wrap_i64 (local.get $tmp)))
      (local.set $w (i32.wrap_i64 (i64.shr_u (local.get $tmp) (i64.const 32))))

      (call $stream.read (local.get $r) (i32.const 0) (i32.const 4))
      i32.const -1 ;; BLOCKED
      i32.ne
      if unreachable end

      (call $stream.write (local.get $w) (i32.const 0) (i32.const 4))
      drop
    )
  )

  (core instance $i (instantiate $m
    (with "" (instance
      (export "stream.new" (func $stream.new))
      (export "stream.read" (func $stream.read))
      (export "stream.write" (func $stream.write))
    ))
  ))

  (func (export "run") (canon lift (core func $i "run")))
)

(assert_trap (invoke "run") "some message here")

this yields:

$ cargo run wast foo.wast -W component-model-async
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
     Running `target/x86_64-unknown-linux-gnu/debug/wasmtime wast foo.wast -W component-model-async`
Error: failed to run script file 'foo.wast'

Caused by:
    0: failed directive on foo.wast:45
    1: expected trap, got Component([])


Last updated: Dec 06 2025 at 07:03 UTC