From 76e039e9f912e20cc5e55476ef9aa19972c7ed6d Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Fri, 11 Sep 2026 14:37:10 -0700 Subject: [PATCH] [ctor-eval] Fix partial evaluation of return calls wasm-ctor-eval had a bug where return calls with non-serializable arguments would incorrectly overwrite global state before failing to serialize, leading to later crashes. Fix the problem by waiting to update global state until we are sure that the operation will be committed. --- src/tools/wasm-ctor-eval.cpp | 15 ++++---- test/lit/ctor-eval/return_call.wast | 56 +++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 6 deletions(-) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 9025832bc00..a948f258c65 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1217,26 +1217,29 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, if (flow.breakTo == RETURN_CALL_FLOW) { // The return-called function is stored in the last value. - func = wasm.getFunction(flow.values.back().getFunc()); + auto* nextFunc = wasm.getFunction(flow.values.back().getFunc()); flow.values.pop_back(); - params = std::move(flow.values); + auto nextParams = std::move(flow.values); // Serialize the arguments for the new function and save the module // state in case we fail to eval the new function. - localExprs.clear(); - for (auto& param : params) { + std::vector nextLocalExprs; + for (auto& param : nextParams) { auto* serialized = interface.getSerialization(param); if (!serialized) { break; } - localExprs.push_back(serialized); + nextLocalExprs.push_back(serialized); } - if (localExprs.size() < params.size()) { + if (nextLocalExprs.size() < nextParams.size()) { if (!quiet) { std::cout << " ...stopping due to non-serializable param\n"; } break; } + func = nextFunc; + params = std::move(nextParams); + localExprs = std::move(nextLocalExprs); interface.applyToModule(); goto start_eval; } diff --git a/test/lit/ctor-eval/return_call.wast b/test/lit/ctor-eval/return_call.wast index 20dace4dbc9..06cd3619d5f 100644 --- a/test/lit/ctor-eval/return_call.wast +++ b/test/lit/ctor-eval/return_call.wast @@ -484,3 +484,59 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) +(module + ;; Return call with a non-serializable parameter (continuation) stops + ;; evaluating without crashing when partially evaluating the caller. + ;; CHECK: (type $func (func)) + (type $func (func)) + ;; CHECK: (type $cont (cont $func)) + (type $cont (cont $func)) + + ;; CHECK: (type $2 (func (param (ref $cont)))) + + ;; CHECK: (global $g (mut i32) (i32.const 2)) + (global $g (export "g") (mut i32) (i32.const 0)) + + ;; CHECK: (elem declare func $callee) + (elem declare func $callee) + + ;; CHECK: (export "g" (global $g)) + + ;; CHECK: (export "test" (func $test_3)) + + ;; CHECK: (func $callee (type $func) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $callee (type $func) + (nop) + ) + + ;; CHECK: (func $target (type $2) (param $0 (ref $cont)) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + (func $target (param (ref $cont)) + (nop) + ) + + (func $test (export "test") + (global.set $g + (i32.const 1) + ) + (global.set $g + (i32.const 2) + ) + (return_call $target + (cont.new $cont + (ref.func $callee) + ) + ) + ) +) + +;; CHECK: (func $test_3 (type $func) +;; CHECK-NEXT: (return_call $target +;; CHECK-NEXT: (cont.new $cont +;; CHECK-NEXT: (ref.func $callee) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: )