diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 8d793045dfb..0c4e9f02b77 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4874,29 +4874,40 @@ class ModuleRunnerBase : public ExpressionRunner { return {}; } Flow visitTry(Try* curr) { - assert(!self()->isResuming()); // TODO - try { - return self()->visit(curr->body); - } catch (const WasmException& e) { - // If delegation is in progress and the current try is not the target of - // the delegation, don't handle it and just rethrow. - if (scope->currDelegateTarget.is()) { - if (scope->currDelegateTarget == curr->name) { - scope->currDelegateTarget = Name{}; - } else { - throw; - } + auto suspend = [&](Index resumeIndex, + std::optional exn = std::nullopt) { + if (exn) { + self()->pushResumeEntry({Literal(int32_t(resumeIndex)), *exn}, "try"); + } else { + self()->pushResumeEntry({Literal(int32_t(resumeIndex))}, "try"); + } + }; + + Index resumeIndex = -1; + std::optional resumedExn; + if (self()->isResuming()) { + auto entry = self()->popResumeEntry("try"); + assert(entry.size() == 1 || entry.size() == 2); + resumeIndex = entry[0].geti32(); + if (entry.size() == 2) { + resumedExn = entry[1]; } + } - auto processCatchBody = [&](Expression* catchBody) { + auto processCatchBody = + [&](Index i, Expression* catchBody, const WasmException& currentExn) { // Push the current exception onto the exceptionStack in case // 'rethrow's use it - exceptionStack.push_back(std::make_pair(e, curr->name)); + exceptionStack.push_back({currentExn, curr->name}); // We need to pop exceptionStack in either case: when the catch body // exits normally or when a new exception is thrown Flow ret; try { ret = self()->visit(catchBody); + if (ret.suspendTag) { + suspend(1 + i, currentExn.exn); + return ret; + } } catch (const WasmException&) { exceptionStack.pop_back(); throw; @@ -4905,16 +4916,46 @@ class ModuleRunnerBase : public ExpressionRunner { return ret; }; + if (self()->isResuming() && resumeIndex >= 1) { + Index i = resumeIndex - 1; + assert(i < curr->catchBodies.size()); + assert(resumedExn); + return processCatchBody( + i, curr->catchBodies[i], WasmException{*resumedExn}); + } + + Flow flow; + try { + if (!self()->isResuming() || resumeIndex == 0) { + flow = self()->visit(curr->body); + if (flow.suspendTag) { + suspend(0); + return flow; + } + return flow; + } + } catch (const WasmException& e) { + // If delegation is in progress and the current try is not the target of + // the delegation, don't handle it and just rethrow. + if (scope->currDelegateTarget.is()) { + if (scope->currDelegateTarget == curr->name) { + scope->currDelegateTarget = Name{}; + } else { + throw; + } + } + auto exnData = e.exn.getExnData(); for (size_t i = 0; i < curr->catchTags.size(); i++) { auto* tag = allTags[curr->catchTags[i]]; if (tag == exnData->tag) { multiValues.push_back(exnData->payload); - return processCatchBody(curr->catchBodies[i]); + return processCatchBody(i, curr->catchBodies[i], e); } } if (curr->hasCatchAll()) { - return processCatchBody(curr->catchBodies.back()); + return processCatchBody( + curr->catchBodies.size() - 1, curr->catchBodies.back(), e); } if (curr->isDelegate()) { scope->currDelegateTarget = curr->delegateTarget; @@ -4922,6 +4963,7 @@ class ModuleRunnerBase : public ExpressionRunner { // This exception is not caught by this try-catch. Rethrow it. throw; } + return flow; } Flow visitTryTable(TryTable* curr) { try { diff --git a/test/lit/exec/try-catch-resume.wast b/test/lit/exec/try-catch-resume.wast new file mode 100644 index 00000000000..25ac7375ca8 --- /dev/null +++ b/test/lit/exec/try-catch-resume.wast @@ -0,0 +1,160 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. +;; RUN: wasm-opt %s -all --fuzz-exec-before -o /dev/null 2>&1 | filecheck %s + +(module + (type $type_void (func)) + (type $i32 (func (result i32))) + (type $cont (cont $i32)) + (type $throw_func (func)) + (type $f_catch_sig (func (param (ref $throw_func)) (result i32))) + (type $cont_catch (cont $f_catch_sig)) + + (elem declare func $try_f1 $f_catch $f_rethrow $throw_tag1 $throw_tag2 $throw_tag3) + + (tag $tag_suspend (type $type_void)) + (tag $tag1 (type $type_void)) + (tag $tag2 (type $type_void)) + (tag $tag3 (type $type_void)) + + ;; CHECK: [fuzz-exec] export run_try_resume + ;; CHECK-NEXT: [fuzz-exec] note result: run_try_resume => 42 + (func $run_try_resume (export "run_try_resume") (result i32) + (local $cont (ref $cont)) + (local.set $cont (cont.new $cont (ref.func $try_f1))) + ;; Resume into the try blocks in $try_f1 and $try_f2. + (resume $cont + (block $block (result (ref $cont)) + ;; Run until the suspend in $try_f3. + (drop (resume $cont (on $tag_suspend $block) (local.get $cont))) + (unreachable) + ) + ) + ) + + (func $try_f1 (result i32) + (try (result i32) + (do (call $try_f2)) + (catch_all (unreachable)) + ) + ) + + (func $try_f2 (result i32) + (try (result i32) + (do (call $try_f3)) + (catch_all (unreachable)) + ) + ) + + (func $try_f3 (result i32) + (suspend $tag_suspend) + (i32.const 42) + ) + + (func $throw_tag1 (type $throw_func) + (throw $tag1) + ) + (func $throw_tag2 (type $throw_func) + (throw $tag2) + ) + (func $throw_tag3 (type $throw_func) + (throw $tag3) + ) + + (func $run_catch_test (param $throw (ref $throw_func)) (result i32) + (local $cont (ref $cont)) + (local.set $cont + (cont.bind $cont_catch $cont + (local.get $throw) + (cont.new $cont_catch (ref.func $f_catch)) + ) + ) + ;; Resume into the catch block. + (resume $cont + (block $block (result (ref $cont)) + ;; Run until the suspend inside a catch. + (drop (resume $cont (on $tag_suspend $block) (local.get $cont))) + (unreachable) + ) + ) + ) + + ;; CHECK: [fuzz-exec] export run_catch_1 + ;; CHECK-NEXT: [fuzz-exec] note result: run_catch_1 => 101 + (func $run_catch_1 (export "run_catch_1") (result i32) + (call $run_catch_test (ref.func $throw_tag1)) + ) + + ;; CHECK: [fuzz-exec] export run_catch_2 + ;; CHECK-NEXT: [fuzz-exec] note result: run_catch_2 => 102 + (func $run_catch_2 (export "run_catch_2") (result i32) + (call $run_catch_test (ref.func $throw_tag2)) + ) + + ;; CHECK: [fuzz-exec] export run_catch_all + ;; CHECK-NEXT: [fuzz-exec] note result: run_catch_all => 103 + (func $run_catch_all (export "run_catch_all") (result i32) + (call $run_catch_test (ref.func $throw_tag3)) + ) + + (func $f_catch (param $throw (ref $throw_func)) (result i32) + (try (result i32) + (do + (call_ref $throw_func (local.get $throw)) + (unreachable) + ) + (catch $tag1 + (suspend $tag_suspend) + (i32.const 101) + ) + (catch $tag2 + (suspend $tag_suspend) + (i32.const 102) + ) + (catch_all + (suspend $tag_suspend) + (i32.const 103) + ) + ) + ) + + (func $run_rethrow_test (result i32) + (local $cont (ref $cont)) + (local.set $cont (cont.new $cont (ref.func $f_rethrow))) + ;; Resume into the catch block. + (resume $cont + (block $block (result (ref $cont)) + ;; Run until the suspend inside a catch. + (drop (resume $cont (on $tag_suspend $block) (local.get $cont))) + (unreachable) + ) + ) + ) + + + ;; CHECK: [fuzz-exec] export run_rethrow + ;; CHECK-NEXT: [fuzz-exec] note result: run_rethrow => 201 + ;; CHECK-NEXT: warning: no passes specified, not doing any work + (func $run_rethrow (export "run_rethrow") (result i32) + (call $run_rethrow_test) + ) + + (func $f_rethrow (result i32) + (try $outer (result i32) + (do + (try $inner (result i32) + (do + (throw $tag1) + ) + (catch $tag1 + (suspend $tag_suspend) + ;; Check that the exception stack is properly restored. + (rethrow $inner) + ) + ) + ) + (catch $tag1 + (i32.const 201) + ) + ) + ) +)