diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index 762c0a94e66..f99ed5093e1 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -474,6 +474,8 @@ struct TypeRefining : public Pass { TypeRewriter(wasm, *this).update(); + // Refinalization fixes up types and makes them fit in the places we write + // them to. ReFinalize().run(getPassRunner(), &wasm); // After refinalizing, we may still have situations that do not validate. @@ -514,9 +516,7 @@ struct TypeRefining : public Pass { for (Index i = 0; i < fields.size(); i++) { auto*& operand = curr->operands[i]; auto fieldType = fields[i].type; - if (!Type::isSubType(operand->type, fieldType)) { - operand = Builder(*getModule()).makeRefCast(operand, fieldType); - } + operand = fixType(operand, fieldType); } } @@ -532,17 +532,45 @@ struct TypeRefining : public Pass { } auto fieldType = type.getStruct().fields[curr->index].type; + curr->value = fixType(curr->value, fieldType); + } + + bool refinalize = false; + + // Fix up a given value so it fits into the type the location it is + // written to. + Expression* fixType(Expression* value, Type type) { + if (Type::isSubType(value->type, type)) { + return value; + } + // We cast to fix this up. An exception is a bottom type, which we can + // handle by emitting a null (which works with types that cannot be + // cast, like continuations; it also explicitly provides the value being + // written, which other passes would do anyhow). + Builder builder(*getModule()); + auto heapType = type.getHeapType(); + if (heapType.isBottom()) { + auto* drop = builder.makeDrop(value); + if (type.isNonNullable()) { + // This will just trap. Make it trap, and update parents' types. + refinalize = true; + return builder.makeSequence(drop, builder.makeUnreachable()); + } else { + return builder.makeSequence(drop, builder.makeRefNull(heapType)); + } + } + return builder.makeRefCast(value, type); + } - if (!Type::isSubType(curr->value->type, fieldType)) { - curr->value = - Builder(*getModule()).makeRefCast(curr->value, fieldType); + void visitFunction(Function* func) { + if (refinalize) { + ReFinalize().walkFunctionInModule(func, getModule()); } } }; WriteUpdater updater; updater.run(getPassRunner(), &wasm); - updater.runOnModuleCode(getPassRunner(), &wasm); } }; diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index d6330890d8f..f572385d2a7 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -611,3 +611,54 @@ ) ) +;; When refining to nullcontref we cannot use a cast. Just emit a null. +(module + (rec + ;; NRML: (rec + ;; NRML-NEXT: (type $func (sub (func))) + ;; GUFA: (rec + ;; GUFA-NEXT: (type $func (sub (func))) + (type $func (sub (func))) + ;; NRML: (type $cont (sub (cont $func))) + ;; GUFA: (type $cont (sub (cont $func))) + (type $cont (sub (cont $func))) + ;; NRML: (type $struct (struct (field (ref null $cont)))) + ;; GUFA: (type $struct (struct (field nullcontref))) + (type $struct (struct (field (ref null $cont)))) + ) + + ;; NRML: (type $3 (func)) + + ;; NRML: (func $test (type $3) + ;; NRML-NEXT: (local $null (ref null $cont)) + ;; NRML-NEXT: (drop + ;; NRML-NEXT: (struct.new $struct + ;; NRML-NEXT: (local.get $null) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; NRML-NEXT: ) + ;; GUFA: (type $3 (func)) + + ;; GUFA: (func $test (type $3) + ;; GUFA-NEXT: (local $null (ref null $cont)) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (struct.new $struct + ;; GUFA-NEXT: (block (result nullcontref) + ;; GUFA-NEXT: (drop + ;; GUFA-NEXT: (local.get $null) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: (ref.null nocont) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + ;; GUFA-NEXT: ) + (func $test + (local $null (ref null $cont)) + (drop + (struct.new $struct + (local.get $null) + ) + ) + ) +) + diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index ec64c58bebc..4c0261620d0 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1252,23 +1252,29 @@ (tag $tag) ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref noextern) - ;; CHECK-NEXT: (try (result externref) - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try (result externref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.get $extern) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (local.get $extern) - ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $struct.new (param $extern externref) (result anyref) @@ -1282,8 +1288,8 @@ ;; type than the body. ;; ;; In such situations we rely on other optimizations to improve things, like - ;; getting rid of the catch in this case. In this pass we add a cast to get - ;; things to validate, which should be removable by other passes later on. + ;; getting rid of the catch in this case. In this pass we add an unreachable + ;; for the uninhabitable type, which fixes validation. (struct.new $A (try (result externref) (do @@ -1305,21 +1311,24 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref noextern) - ;; CHECK-NEXT: (try (result externref) - ;; CHECK-NEXT: (do - ;; CHECK-NEXT: (struct.get $A 0 - ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.as_non_null - ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try (result externref) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (struct.get $A 0 + ;; CHECK-NEXT: (struct.new $A + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (local.get $extern) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.get $extern) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: )