Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
8ba873f
feat(interpreter): legacy exception handling (throw-only) for fast-in…
matthargett May 17, 2026
91f51f3
fast-interp: loader-side exception-handler metadata table
matthargett May 17, 2026
5202bfa
fast-interp: runtime EH-frame stack + TRY push / END(try) pop
matthargett May 17, 2026
08625c5
fast-interp: WASM_OP_THROW catch-walk + return_func exception hook
matthargett May 18, 2026
683a9db
fast-interp: WASM_OP_RETHROW catch-walk re-raise
matthargett May 18, 2026
a1c647c
fast-interp: WASM_OP_DELEGATE forward-to-outer dispatch
matthargett May 18, 2026
169f9e7
fast-interp: tag-with-params payload routing (same-function dispatch)
matthargett May 18, 2026
72db3ca
fast-interp: tag-with-params loader bug-fix pass
matthargett May 18, 2026
d481d9b
fast-interp: result-typed try-region COPY-at-CATCH alignment
matthargett May 18, 2026
924217e
fast-interp: warn on br/br_if/br_table across try-region boundary
matthargett May 18, 2026
5b122fe
fast-interp: __builtin_expect cold-path hints on CALL_INDIRECT bounds…
matthargett May 18, 2026
04625e7
test_wamr.sh: enable wasm spec EH suite for fast-interp
matthargett May 18, 2026
57f4169
fast-interp: unwind skipped EH entries on outer-catch dispatch
matthargett May 19, 2026
54bac97
fast-interp: trap on cross-function exception payload propagation
matthargett May 19, 2026
231862a
fast-interp: reject br/br_if/br_table to loop entry from inside try-r…
matthargett May 19, 2026
0411662
fixup: MSVC `__builtin_expect` shim for the cold-path hints
matthargett May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion build-scripts/unsupported_combination.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,17 @@ endfunction()

if(WAMR_BUILD_EXCE_HANDLING EQUAL 1)
check_aot_mode_error("Unsupported build configuration: EXCE_HANDLING + AOT")
check_fast_interp_error("Unsupported build configuration: EXCE_HANDLING + FAST_INTERP")
# FAST_INTERP + EXCE_HANDLING is supported for *throw-only* shapes:
# WASM modules that declare tags and execute throw / rethrow without
# ever entering a same-function try / catch handler. The throw
# propagates to the caller via the existing got_exception bailout
# path, exactly like any other trap. This covers Porffor (its
# JS-to-wasm compiler emits 0 try/catch handlers; every JS throw
# escapes to the host). Modules that contain WASM_OP_TRY / CATCH /
# CATCH_ALL / DELEGATE still load, but those handlers report
# "unsupported opcode" at runtime — see the WASM_OP_TRY handler in
# core/iwasm/interpreter/wasm_interp_fast.c. Full same-function
# try / catch lowering is the natural follow-up.
check_fast_jit_error("Unsupported build configuration: EXCE_HANDLING + FAST_JIT")
check_llvm_jit_error("Unsupported build configuration: EXCE_HANDLING + JIT")
endif()
Expand Down
60 changes: 60 additions & 0 deletions core/iwasm/interpreter/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -681,6 +681,54 @@ typedef struct WASMImport {
} u;
} WASMImport;

#if WASM_ENABLE_EXCE_HANDLING != 0 && WASM_ENABLE_FAST_INTERP != 0
/* One typed `catch N` clause inside a single try-region. The handler_pc
* points at the first opcode of the catch body in the rewritten fast-
* interp IR; the loader patches it in pass 2 of the preprocess pass. */
typedef struct WASMFastEHCatch {
uint32 tag_index;
uint8 *handler_pc;
/* Tag-with-params payload routing (same-function dispatch only).
* When this catch matches, the throw walker copies `param_cell_num`
* 32-bit cells from the throw site's *source* slots (encoded as
* `int16` immediates after the THROW opcode in the rewritten IR)
* into these *destination* slots in the catch body's `frame_lp`,
* then sets `frame_ip = handler_pc`. The destination slots are
* allocated by the CATCH loader at preprocess time, mirroring how
* block-with-params allocate fresh `dynamic_offset` slots via
* `PUSH_OFFSET_TYPE`. NULL iff `param_cell_num == 0` (the typical
* tag-without-params shape, e.g. Porffor's empty-payload tags).
*
* Cross-function dispatch (caller's catch fires for a callee's
* throw) does NOT copy the payload: the callee's source slots
* sit in a frame that's about to be torn down by return_func.
* That gap is documented as an ignored integration test —
* `cross_function_tag_with_params` in
* crates/benchmark-core/tests/eh_correctness.rs. */
uint32 param_cell_num;
int16 *param_dst_offsets;
} WASMFastEHCatch;

/* One entry per same-function try-region, indexed by the uint32 immediate
* emitted after the rewritten TRY opcode. Allocated once per function at
* load time, sized by `func->exception_handler_count`. At runtime the
* dispatch loop carries one stack-allocated handle per *active* try-
* region (see frame->eh_stack); hot ops (CALL / LOAD / STORE) never
* touch this table. */
typedef struct WASMFastEHEntry {
uint32 catch_count;
WASMFastEHCatch *catches; /* may be NULL when catch_count == 0 */
uint8 *catch_all_pc; /* NULL if no `catch_all` clause */
/* UINT32_MAX iff the try-region closes with `end`; otherwise the
* LEB depth from `delegate N`. */
uint32 delegate_target_depth;
/* Rewritten-IR pc of the op immediately after the try-region's `end`
* (or `delegate`). CATCH / CATCH_ALL handlers branch here when their
* body completes; the loader patches it when the `end` is seen. */
uint8 *end_of_region_pc;
} WASMFastEHEntry;
#endif /* WASM_ENABLE_EXCE_HANDLING && WASM_ENABLE_FAST_INTERP */

struct WASMFunction {
#if WASM_ENABLE_CUSTOM_NAME_SECTION != 0
char *field_name;
Expand Down Expand Up @@ -721,7 +769,19 @@ struct WASMFunction {
#endif

#if WASM_ENABLE_EXCE_HANDLING != 0
/* Number of `try` opcodes in this function. Populated by the loader
* during the preprocess pass (classic-interp uses this to size the
* runtime handler-pointer array stored on the value stack; fast-
* interp uses it to size `exception_handlers[]` below). */
uint32 exception_handler_count;
#if WASM_ENABLE_FAST_INTERP != 0
/* Per-function table of try-regions in source order, length
* `exception_handler_count`. Allocated and populated in pass 2 of
* the fast-interp preprocess pass; the uint32 immediate emitted
* after the rewritten TRY opcode is the index into this array.
* NULL iff `exception_handler_count == 0`. */
WASMFastEHEntry *exception_handlers;
#endif
#endif

#if WASM_ENABLE_FAST_JIT != 0 || WASM_ENABLE_JIT != 0 \
Expand Down
11 changes: 11 additions & 0 deletions core/iwasm/interpreter/wasm_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ typedef struct WASMInterpFrame {
*/
bool exception_raised;
uint32 tag_index;
#if WASM_ENABLE_FAST_INTERP != 0
/* Number of *currently-active* try-regions on this frame's eh-
* stack. The stack itself lives in the trailing cells of the
* frame's operand[] block — see call_func_from_entry in
* wasm_interp_fast.c where all_cell_num is grown by
* `exception_handler_count` cells per frame. Read+written only by
* the WASM_OP_TRY / CATCH / CATCH_ALL / END / THROW handlers; the
* hot ops (CALL / LOAD / STORE) never touch it, so this field
* stays cold and clusters with exception_raised/tag_index above. */
uint32 eh_count;
#endif
#endif

#if WASM_ENABLE_FAST_INTERP != 0
Expand Down
Loading
Loading