Stream: git-wasmtime

Topic: wasmtime / issue #13614 Uncaught wasm exceptions can be c...


view this post on Zulip Wasmtime GitHub notifications bot (Jun 11 2026 at 20:36):

alexcrichton added the wasm-proposal:exceptions label to Issue #13614.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 11 2026 at 20:36):

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

view this post on Zulip Wasmtime GitHub notifications bot (Jun 11 2026 at 20:36):

alexcrichton opened issue #13614:

This test:

(component
  (component $A
    (core module $a
      (type $t (func))
      (tag $t (type $t))
      (func (export "throw") throw $t)
    )
    (core instance $a (instantiate $a))
    (func (export "throw") (canon lift (core func $a "throw")))
  )
  (component $B
    (import "a" (instance $a (export "throw" (func))))

    (core func $throw (canon lower (func $a "throw")))
    (core module $b
      (import "" "throw" (func $throw))

      (func (export "run")
        block $a
          try_table (catch_all $a)
            call $throw
          end
        end
      )
    )
    (core instance $b (instantiate $b
      (with "" (instance
        (export "throw" (func $throw))
      ))
    ))
    (func (export "run") (canon lift (core func $b "run")))
  )

  (instance $a (instantiate $A))
  (instance $b (instantiate $B (with "a" (instance $a))))
  (export "run" (func $b "run"))
)

(assert_trap (invoke "run") "something about an uncaught wasm exception")

currently fails:

$ cargo run wast foo.wast
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/wasmtime wast foo.wast`
Error: failed to run script file 'foo.wast'

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

While this isn't strictly defined by the component model spec per-se, I'm almost certain that the desired behavior here is that uncaught exceptions within a component don't get propagated anywhere else and turn into unconditional traps.


I talked a bit about this with @fitzgen today and one solution is to insert catch_all instructions in component-to-component adapters. The downside of this is that it might pessimize the sync-to-sync adapter even further insfoar as I'm not sure what the inlining story would be in that case. Another possible solution might be to adjust the tag tables of modules to change the encoding of catch-all exceptions in such a manner that only exceptions/tags from the current component instance are caught. As I write this down this is difficult to articulate which means it might not be viable...

cc @cfallin as this is exception-related
cc @lukewagner as this is component-model-spec related and I'd want to confirm with you that it's expected that catch_all is NOT expected to work across component boundaries. In other words, uncaught exceptions should indeed be unrecoverable traps

view this post on Zulip Wasmtime GitHub notifications bot (Jun 11 2026 at 20:37):

alexcrichton commented on issue #13614:

oop looks like Chris/Nick talked as well and I raced #13613 being created

view this post on Zulip Wasmtime GitHub notifications bot (Jun 11 2026 at 20:40):

cfallin commented on issue #13614:

Ah, yes, sorry about the race -- my PR fixes an issue from the future...

I don't think that try_call should pessimize inlining: the inliner explicitly handles it as well, fortunately.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 12 2026 at 19:15):

alexcrichton closed issue #13614:

This test:

(component
  (component $A
    (core module $a
      (type $t (func))
      (tag $t (type $t))
      (func (export "throw") throw $t)
    )
    (core instance $a (instantiate $a))
    (func (export "throw") (canon lift (core func $a "throw")))
  )
  (component $B
    (import "a" (instance $a (export "throw" (func))))

    (core func $throw (canon lower (func $a "throw")))
    (core module $b
      (import "" "throw" (func $throw))

      (func (export "run")
        block $a
          try_table (catch_all $a)
            call $throw
          end
        end
      )
    )
    (core instance $b (instantiate $b
      (with "" (instance
        (export "throw" (func $throw))
      ))
    ))
    (func (export "run") (canon lift (core func $b "run")))
  )

  (instance $a (instantiate $A))
  (instance $b (instantiate $B (with "a" (instance $a))))
  (export "run" (func $b "run"))
)

(assert_trap (invoke "run") "something about an uncaught wasm exception")

currently fails:

$ cargo run wast foo.wast
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
     Running `target/debug/wasmtime wast foo.wast`
Error: failed to run script file 'foo.wast'

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

While this isn't strictly defined by the component model spec per-se, I'm almost certain that the desired behavior here is that uncaught exceptions within a component don't get propagated anywhere else and turn into unconditional traps.


I talked a bit about this with @fitzgen today and one solution is to insert catch_all instructions in component-to-component adapters. The downside of this is that it might pessimize the sync-to-sync adapter even further insfoar as I'm not sure what the inlining story would be in that case. Another possible solution might be to adjust the tag tables of modules to change the encoding of catch-all exceptions in such a manner that only exceptions/tags from the current component instance are caught. As I write this down this is difficult to articulate which means it might not be viable...

cc @cfallin as this is exception-related
cc @lukewagner as this is component-model-spec related and I'd want to confirm with you that it's expected that catch_all is NOT expected to work across component boundaries. In other words, uncaught exceptions should indeed be unrecoverable traps


Last updated: Jul 29 2026 at 05:03 UTC