diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index a948f258c65..fcb02f9c35f 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -1329,17 +1329,21 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, auto* copyBody = builder.blockify(ExpressionManipulator::copy(func->body, wasm)); - // Remove the items we've evalled. - for (Index i = 0; i < successes; i++) { - copyBody->list[i] = builder.makeNop(); - } - - // Put the local sets at the front of the function body. - auto* setsBlock = builder.makeBlock(); + // Replace the items we've evalled with the local sets at the front of the + // function body. Skip uninitialized non-nullable locals, whose default + // null value in the interpreter is not a valid subtype of the local's + // type. + std::vector newList; + newList.reserve(localExprs.size() + copyBody->list.size() - successes); for (Index i = 0; i < localExprs.size(); ++i) { - setsBlock->list.push_back(builder.makeLocalSet(i, localExprs[i])); + if (Type::isSubType(localExprs[i]->type, func->getLocalType(i))) { + newList.push_back(builder.makeLocalSet(i, localExprs[i])); + } } - copyBody = builder.makeSequence(setsBlock, copyBody, copyBody->type); + newList.insert(newList.end(), + copyBody->list.begin() + successes, + copyBody->list.end()); + copyBody->list.set(newList); // We may have return-called into a function with different parameter // types, but we ultimately need to export a function with the original diff --git a/test/lit/ctor-eval/materialize-null-local.wast b/test/lit/ctor-eval/materialize-null-local.wast index 1f880f6816b..fd330c398ee 100644 --- a/test/lit/ctor-eval/materialize-null-local.wast +++ b/test/lit/ctor-eval/materialize-null-local.wast @@ -1,12 +1,22 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. -;; RUN: wasm-ctor-eval %s -all --ctors=test --kept-exports=test --ignore-external-input -S -o - \ +;; RUN: wasm-ctor-eval %s -all --ctors=test,non_nullable --kept-exports=test,non_nullable --ignore-external-input -S -o - \ ;; RUN: | filecheck %s ;; Check that materializing a non-data null local, which at time of writing uses ;; Builder::makeConstantExpression, does not trigger an assertion failure. (module + ;; CHECK: (type $struct (struct)) + (type $struct (struct)) + + ;; CHECK: (type $1 (func (param externref))) + + ;; CHECK: (type $2 (func (result (ref $struct)))) + + ;; CHECK: (import "env" "g" (global $g i32)) + (import "env" "g" (global $g i32)) + (func $test (export "test") (param $0 externref) (local $3 anyref) (local.set $3 @@ -15,12 +25,74 @@ ) ) ) + + (func $non_nullable (export "non_nullable") (result (ref $struct)) + ;; Partially evaluate the first statement, then stop on the imported global. + ;; Do not emit an invalid (local.set $unset (ref.null none)) for the + ;; uninitialized non-nullable local, and ensure the initialized non-nullable + ;; local $init structurally dominates its use in the remaining body. + (local $init (ref $struct)) + (local $unset (ref $struct)) + (local.set $init + (struct.new $struct) + ) + (if + (global.get $g) + (then + (return + (local.get $init) + ) + ) + ) + (local.set $unset + (struct.new $struct) + ) + (if + (global.get $g) + (then + (local.set $unset + (local.get $init) + ) + ) + ) + (local.get $unset) + ) ) -;; CHECK: (type $0 (func (param externref))) +;; CHECK: (global $ctor-eval$global (ref (exact $struct)) (struct.new_default $struct)) -;; CHECK: (export "test" (func $test_1)) +;; CHECK: (export "test" (func $test_2)) -;; CHECK: (func $test_1 (type $0) (param $0 externref) +;; CHECK: (export "non_nullable" (func $non_nullable_3)) + +;; CHECK: (func $test_2 (type $1) (param $0 externref) ;; CHECK-NEXT: (local $3 anyref) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) + +;; CHECK: (func $non_nullable_3 (type $2) (result (ref $struct)) +;; CHECK-NEXT: (local $0 (ref (exact $struct))) +;; CHECK-NEXT: (local $1 (ref (exact $struct))) +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (global.get $ctor-eval$global) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (global.get $g) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (return +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (struct.new_default $struct) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (global.get $g) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: )