diff options
-rw-r--r-- | src/ir/lubs.cpp | 10 | ||||
-rw-r--r-- | src/ir/lubs.h | 82 | ||||
-rw-r--r-- | src/passes/DeadArgumentElimination.cpp | 16 | ||||
-rw-r--r-- | src/passes/GlobalRefining.cpp | 7 | ||||
-rw-r--r-- | src/passes/LocalSubtyping.cpp | 7 | ||||
-rw-r--r-- | src/passes/SignatureRefining.cpp | 13 | ||||
-rw-r--r-- | src/passes/TypeRefining.cpp | 16 | ||||
-rw-r--r-- | test/lit/passes/dae-gc-refine-params.wast | 66 | ||||
-rw-r--r-- | test/lit/passes/dae-gc.wast | 4 | ||||
-rw-r--r-- | test/lit/passes/global-refining.wast | 11 | ||||
-rw-r--r-- | test/lit/passes/signature-refining.wast | 22 | ||||
-rw-r--r-- | test/lit/passes/type-refining-isorecursive.wast | 40 | ||||
-rw-r--r-- | test/lit/passes/type-refining.wast | 16 |
13 files changed, 132 insertions, 178 deletions
diff --git a/src/ir/lubs.cpp b/src/ir/lubs.cpp index 852c27677..ead1299b5 100644 --- a/src/ir/lubs.cpp +++ b/src/ir/lubs.cpp @@ -45,15 +45,15 @@ LUBFinder getResultsLUB(Function* func, Module& wasm) { // ) ReFinalize().walkFunctionInModule(func, &wasm); - lub.noteUpdatableExpression(func->body); - if (lub.getBestPossible() == originalType) { + lub.note(func->body->type); + if (lub.getLUB() == originalType) { return lub; } // Scan the body and look at the returns. First, return expressions. for (auto* ret : FindAll<Return>(func->body).list) { - lub.noteUpdatableExpression(ret->value); - if (lub.getBestPossible() == originalType) { + lub.note(ret->value->type); + if (lub.getLUB() == originalType) { return lub; } } @@ -64,7 +64,7 @@ LUBFinder getResultsLUB(Function* func, Module& wasm) { // Return whether we still look ok to do the optimization. If this is // false then we can stop here. lub.note(type); - return lub.getBestPossible() != originalType; + return lub.getLUB() != originalType; }; for (auto* call : FindAll<Call>(func->body).list) { diff --git a/src/ir/lubs.h b/src/ir/lubs.h index 05c6b07fd..afc5c0676 100644 --- a/src/ir/lubs.h +++ b/src/ir/lubs.h @@ -25,16 +25,7 @@ namespace wasm { // // Helper to find a LUB of a series of expressions. This works incrementally so // that if we see we are not improving on an existing type then we can stop -// early. It also notes null expressions that can be updated later, and if -// updating them would allow a better LUB it can do so. That is, given this: -// -// (ref.null any) ;; an expression that we can update -// (.. something of type data ..) -// -// We can update that null to type (ref null data) which would allow setting -// that as the LUB. This is important in cases where there is a null initial -// value in a field, for example: we should not let the type there prevent us -// from optimizing - after all, all nulls compare equally anyhow. +// early. // struct LUBFinder { LUBFinder() {} @@ -44,61 +35,11 @@ struct LUBFinder { // Note another type to take into account in the lub. void note(Type type) { lub = Type::getLeastUpperBound(lub, type); } - // Note an expression that can be updated, that is, that we can modify in - // safe ways if doing so would allow us to get a better lub. The specific - // optimization possible here involves nulls, see the top comment. - void noteUpdatableExpression(Expression* curr) { - if (auto* null = curr->dynCast<RefNull>()) { - nulls.insert(null); - } else { - note(curr->type); - } - } - - void noteNullDefault() { - // A default value is indicated by a null pointer. - nulls.insert(nullptr); - } - - // Returns whether we noted any (reachable) value. This ignores nulls, as they - // do not contribute type information - we do not try to find a lub based on - // them (rather we update them to the LUB). + // Returns whether we noted any (reachable) value. bool noted() { return lub != Type::unreachable; } - // Returns the best possible lub. This ignores updatable nulls for the reasons - // mentioned above, since they will not limit us, aside from making the type - // nullable if nulls exist. This does not update the nulls. - Type getBestPossible() { - if (lub == Type::unreachable) { - // Perhaps if we have seen nulls we could compute a lub on them, but it's - // not clear that is helpful. - return lub; - } - - // We have a lub. Make it nullable if we need to. - if (!lub.isNullable() && !nulls.empty()) { - return Type(lub.getHeapType(), Nullable); - } else { - return lub; - } - } - - // Update the nulls for the best possible LUB, if we found one. - void updateNulls() { - auto newType = getBestPossible(); - if (newType != Type::unreachable) { - for (auto* null : nulls) { - // Default null values (represented as nullptr here) do not need to be - // updated. Aside from that, if this null is already of a more specific - // type, also do not update it - it's never worth making a type less - // specific. What we care about here is making sure the nulls are all - // specific enough given the LUB that is being applied. - if (null && !Type::isSubType(null->type, newType)) { - null->finalize(newType); - } - } - } - } + // Returns the lub. + Type getLUB() { return lub; } // Combines the information in another LUBFinder into this one, and returns // whether we changed anything. @@ -106,26 +47,13 @@ struct LUBFinder { // Check if the lub was changed. auto old = lub; note(other.lub); - bool changed = old != lub; - - // Check if we added new updatable nulls. - for (auto* null : other.nulls) { - if (nulls.insert(null).second) { - changed = true; - } - } - - return changed; + return old != lub; } private: // The least upper bound. As we go this always contains the latest value based // on everything we've seen so far, except for nulls. Type lub = Type::unreachable; - - // Nulls that we can update. A nullptr here indicates an "implicit" null, that - // is, a null default value. - std::unordered_set<RefNull*> nulls; }; namespace LUB { diff --git a/src/passes/DeadArgumentElimination.cpp b/src/passes/DeadArgumentElimination.cpp index d319340b8..f739e9263 100644 --- a/src/passes/DeadArgumentElimination.cpp +++ b/src/passes/DeadArgumentElimination.cpp @@ -381,8 +381,8 @@ private: auto& lub = lubs[i]; for (auto* call : calls) { auto* operand = call->operands[i]; - lub.noteUpdatableExpression(operand); - if (lub.getBestPossible() == originalType) { + lub.note(operand->type); + if (lub.getLUB() == originalType) { // We failed to refine this parameter to anything more specific. break; } @@ -392,7 +392,7 @@ private: if (!lub.noted()) { return; } - newParamTypes.push_back(lub.getBestPossible()); + newParamTypes.push_back(lub.getLUB()); } // Check if we are able to optimize here before we do the work to scan the @@ -405,12 +405,7 @@ private: // We can do this! TypeUpdating::updateParamTypes(func, newParamTypes, *module); - // Update anything the lubs need to update. - for (auto& lub : lubs) { - lub.updateNulls(); - } - - // Also update the function's type. + // Update the function's type. func->setParams(newParams); } @@ -436,9 +431,8 @@ private: if (!lub.noted()) { return false; } - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != func->getResults()) { - lub.updateNulls(); func->setResults(newType); for (auto* call : calls) { if (call->type != Type::unreachable) { diff --git a/src/passes/GlobalRefining.cpp b/src/passes/GlobalRefining.cpp index 0576e3285..67a186f92 100644 --- a/src/passes/GlobalRefining.cpp +++ b/src/passes/GlobalRefining.cpp @@ -59,7 +59,7 @@ struct GlobalRefining : public Pass { // Combine all the information we gathered and compute lubs. for (auto& [func, info] : analysis.map) { for (auto* set : info.sets) { - lubs[set->name].noteUpdatableExpression(set->value); + lubs[set->name].note(set->value->type); } } @@ -73,7 +73,7 @@ struct GlobalRefining : public Pass { auto& lub = lubs[global->name]; // Note the initial value. - lub.noteUpdatableExpression(global->init); + lub.note(global->init->type); // The initial value cannot be unreachable, but it might be null, and all // other values might be too. In that case, we've noted nothing useful @@ -83,12 +83,11 @@ struct GlobalRefining : public Pass { } auto oldType = global->type; - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != oldType) { // We found an improvement! assert(Type::isSubType(newType, oldType)); global->type = newType; - lub.updateNulls(); optimized = true; } } diff --git a/src/passes/LocalSubtyping.cpp b/src/passes/LocalSubtyping.cpp index cf8570986..0d41434fc 100644 --- a/src/passes/LocalSubtyping.cpp +++ b/src/passes/LocalSubtyping.cpp @@ -134,8 +134,8 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { // Find all the types assigned to the var, and compute the optimal LUB. LUBFinder lub; for (auto* set : setsForLocal[i]) { - lub.noteUpdatableExpression(set->value); - if (lub.getBestPossible() == oldType) { + lub.note(set->value->type); + if (lub.getLUB() == oldType) { break; } } @@ -144,7 +144,7 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { continue; } - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); assert(newType != Type::none); // in valid wasm there must be a LUB // Remove non-nullability if we disallow that in locals. @@ -165,7 +165,6 @@ struct LocalSubtyping : public WalkerPass<PostWalker<LocalSubtyping>> { func->vars[i - varBase] = newType; more = true; optimized = true; - lub.updateNulls(); // Update gets and tees. for (auto* get : getsForLocal[i]) { diff --git a/src/passes/SignatureRefining.cpp b/src/passes/SignatureRefining.cpp index 132079b76..15b89b2a7 100644 --- a/src/passes/SignatureRefining.cpp +++ b/src/passes/SignatureRefining.cpp @@ -161,7 +161,7 @@ struct SignatureRefining : public Pass { auto updateLUBs = [&](const ExpressionList& operands) { for (Index i = 0; i < numParams; i++) { - paramLUBs[i].noteUpdatableExpression(operands[i]); + paramLUBs[i].note(operands[i]->type); } }; @@ -178,7 +178,7 @@ struct SignatureRefining : public Pass { if (!lub.noted()) { break; } - newParamsTypes.push_back(lub.getBestPossible()); + newParamsTypes.push_back(lub.getLUB()); } Type newParams; if (newParamsTypes.size() < numParams) { @@ -197,7 +197,7 @@ struct SignatureRefining : public Pass { // value, or it can return a value but traps instead etc.). newResults = func->getResults(); } else { - newResults = resultsLUB.getBestPossible(); + newResults = resultsLUB.getLUB(); } if (newParams == func->getParams() && newResults == func->getResults()) { @@ -207,14 +207,7 @@ struct SignatureRefining : public Pass { // We found an improvement! newSignatures[type] = Signature(newParams, newResults); - // Update nulls as necessary, now that we are changing things. - if (newParams != func->getParams()) { - for (auto& lub : paramLUBs) { - lub.updateNulls(); - } - } if (newResults != func->getResults()) { - resultsLUB.updateNulls(); refinedResults = true; // Update the types of calls using the signature. diff --git a/src/passes/TypeRefining.cpp b/src/passes/TypeRefining.cpp index e9aa07ca6..6e2c96fce 100644 --- a/src/passes/TypeRefining.cpp +++ b/src/passes/TypeRefining.cpp @@ -53,7 +53,7 @@ struct FieldInfoScanner HeapType type, Index index, FieldInfo& info) { - info.noteUpdatableExpression(expr); + info.note(expr->type); } void @@ -61,7 +61,7 @@ struct FieldInfoScanner // Default values do not affect what the heap type of a field can be turned // into. Note them, however, as they force us to keep the type nullable. if (fieldType.isRef()) { - info.noteNullDefault(); + info.note(Type(fieldType.getHeapType().getBottom(), Nullable)); } } @@ -173,9 +173,9 @@ struct TypeRefining : public Pass { if (auto super = type.getSuperType()) { auto& superFields = super->getStruct().fields; for (Index i = 0; i < superFields.size(); i++) { - auto newSuperType = finalInfos[*super][i].getBestPossible(); + auto newSuperType = finalInfos[*super][i].getLUB(); auto& info = finalInfos[type][i]; - auto newType = info.getBestPossible(); + auto newType = info.getLUB(); if (!Type::isSubType(newType, newSuperType)) { // To ensure we are a subtype of the super's field, simply copy that // value, which is more specific than us. @@ -210,10 +210,9 @@ struct TypeRefining : public Pass { for (Index i = 0; i < fields.size(); i++) { auto oldType = fields[i].type; auto& lub = finalInfos[type][i]; - auto newType = lub.getBestPossible(); + auto newType = lub.getLUB(); if (newType != oldType) { canOptimize = true; - lub.updateNulls(); } } @@ -256,8 +255,7 @@ struct TypeRefining : public Pass { } auto oldType = curr->ref->type.getHeapType(); - auto newFieldType = - parent.finalInfos[oldType][curr->index].getBestPossible(); + auto newFieldType = parent.finalInfos[oldType][curr->index].getLUB(); if (!Type::isSubType(newFieldType, curr->type)) { // This instruction is invalid, so it must be the result of the // situation described above: we ignored the read during our @@ -298,7 +296,7 @@ struct TypeRefining : public Pass { if (!oldType.isRef()) { continue; } - auto newType = parent.finalInfos[oldStructType][i].getBestPossible(); + auto newType = parent.finalInfos[oldStructType][i].getLUB(); newFields[i].type = getTempType(newType); } } diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 53110efca..dc3c6879f 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -12,16 +12,20 @@ ;; NOMNL: (type ${i32} (struct_subtype (field i32) ${})) (type ${i32} (struct_subtype (field i32) ${})) + ;; CHECK: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32})) + + ;; CHECK: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32})) + ;; CHECK: (type ${f64} (struct_subtype (field f64) ${})) + ;; NOMNL: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32})) + + ;; NOMNL: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32})) + ;; NOMNL: (type ${f64} (struct_subtype (field f64) ${})) (type ${f64} (struct_subtype (field f64) ${})) -;; CHECK: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32})) -;; NOMNL: (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32})) (type ${i32_i64} (struct_subtype (field i32) (field i64) ${i32})) - ;; CHECK: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32})) - ;; NOMNL: (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32})) (type ${i32_f32} (struct_subtype (field i32) (field f32) ${i32})) ;; CHECK: (func $call-various-params-no (type $none_=>_none) @@ -583,34 +587,70 @@ ) ;; CHECK: (func $get_null_{i32} (type $none_=>_ref?|${i32}|) (result (ref null ${i32})) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (select (result (ref null ${i32})) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (struct.new_default ${i32}) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $get_null_{i32} (type $none_=>_ref?|${i32}|) (result (ref null ${i32})) - ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (select (result (ref null ${i32})) + ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (struct.new_default ${i32}) + ;; NOMNL-NEXT: (i32.const 0) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $get_null_{i32} (result (ref null ${i32})) ;; Helper function that returns a null value of ${i32}. We use this instead of ;; a direct ref.null because those can be rewritten by LUBFinder. - (ref.null ${i32}) + (select + (ref.null none) + (struct.new_default ${i32}) + (i32.const 0) + ) ) ;; CHECK: (func $get_null_{i32_i64} (type $none_=>_ref?|${i32_i64}|) (result (ref null ${i32_i64})) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (select (result (ref null ${i32_i64})) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (struct.new_default ${i32_i64}) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $get_null_{i32_i64} (type $none_=>_ref?|${i32_i64}|) (result (ref null ${i32_i64})) - ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (select (result (ref null ${i32_i64})) + ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (struct.new_default ${i32_i64}) + ;; NOMNL-NEXT: (i32.const 0) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $get_null_{i32_i64} (result (ref null ${i32_i64})) - (ref.null ${i32_i64}) + (select + (ref.null none) + (struct.new_default ${i32_i64}) + (i32.const 0) + ) ) ;; CHECK: (func $get_null_{i32_f32} (type $none_=>_ref?|${i32_f32}|) (result (ref null ${i32_f32})) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (select (result (ref null ${i32_f32})) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (struct.new_default ${i32_f32}) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $get_null_{i32_f32} (type $none_=>_ref?|${i32_f32}|) (result (ref null ${i32_f32})) - ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (select (result (ref null ${i32_f32})) + ;; NOMNL-NEXT: (ref.null none) + ;; NOMNL-NEXT: (struct.new_default ${i32_f32}) + ;; NOMNL-NEXT: (i32.const 0) + ;; NOMNL-NEXT: ) ;; NOMNL-NEXT: ) (func $get_null_{i32_f32} (result (ref null ${i32_f32})) - (ref.null ${i32_f32}) + (select + (ref.null none) + (struct.new_default ${i32_f32}) + (i32.const 0) + ) ) ) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 8e55e6ec8..2f023b075 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -157,7 +157,7 @@ ) ;; CHECK: (func $bar (type $i31ref_=>_none) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 anyref) + ;; CHECK-NEXT: (local $1 nullref) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -171,7 +171,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; NOMNL: (func $bar (type $i31ref_=>_none) (param $0 i31ref) - ;; NOMNL-NEXT: (local $1 anyref) + ;; NOMNL-NEXT: (local $1 nullref) ;; NOMNL-NEXT: (local.set $1 ;; NOMNL-NEXT: (ref.null none) ;; NOMNL-NEXT: ) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 2aa803285..06af50bf1 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -2,14 +2,13 @@ ;; RUN: foreach %s %t wasm-opt --nominal --global-refining -all -S -o - | filecheck %s (module - ;; Globals with no assignments aside from their initial values. The first is - ;; a null, so we have nothing concrete to improve with (though we could use - ;; the type of the null perhaps, TODO). The second is a ref.func which lets - ;; us refine. + ;; Globals with no assignments aside from their initial values. The first is a + ;; null, so we can optimize to a nullfuncref. The second is a ref.func which + ;; lets us refine to the specific function type. ;; CHECK: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut funcref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) @@ -26,7 +25,7 @@ ;; CHECK: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut funcref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) (global $func-func-init (mut funcref) (ref.func $foo)) diff --git a/test/lit/passes/signature-refining.wast b/test/lit/passes/signature-refining.wast index f254a2f63..a17032789 100644 --- a/test/lit/passes/signature-refining.wast +++ b/test/lit/passes/signature-refining.wast @@ -508,8 +508,8 @@ (type $sig-can-refine (func_subtype (result anyref) func)) ;; Also a single function, but no refinement is possible. - ;; CHECK: (type $sig-cannot-refine (func (result anyref))) - (type $sig-cannot-refine (func_subtype (result anyref) func)) + ;; CHECK: (type $sig-cannot-refine (func (result (ref func)))) + (type $sig-cannot-refine (func_subtype (result (ref func)) func)) ;; The single function never returns, so no refinement is possible. ;; CHECK: (type $sig-unreachable (func (result anyref))) @@ -517,7 +517,7 @@ ;; CHECK: (type $none_=>_none (func)) - ;; CHECK: (elem declare func $func-can-refine) + ;; CHECK: (elem declare func $func-can-refine $func-cannot-refine) ;; CHECK: (func $func-can-refine (type $sig-can-refine) (result (ref $struct)) ;; CHECK-NEXT: (struct.new_default $struct) @@ -526,11 +526,19 @@ (struct.new $struct) ) - ;; CHECK: (func $func-cannot-refine (type $sig-cannot-refine) (result anyref) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK: (func $func-cannot-refine (type $sig-cannot-refine) (result (ref func)) + ;; CHECK-NEXT: (select (result (ref func)) + ;; CHECK-NEXT: (ref.func $func-can-refine) + ;; CHECK-NEXT: (ref.func $func-cannot-refine) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $func-cannot-refine (type $sig-cannot-refine) (result anyref) - (ref.null any) + (func $func-cannot-refine (type $sig-cannot-refine) (result (ref func)) + (select + (ref.func $func-can-refine) + (ref.func $func-cannot-refine) + (i32.const 0) + ) ) ;; CHECK: (func $func-unreachable (type $sig-unreachable) (result anyref) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 3ede5146c..216462b07 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,12 +5,12 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $0 (struct (field anyref) (field (ref $1)))) - (type $0 (struct_subtype (ref null any) anyref data)) - ;; CHECK: (type $1 (struct (field eqref) (field (ref $2)))) - (type $1 (struct_subtype (ref null eq) anyref data)) - ;; CHECK: (type $2 (struct (field i31ref) (field (ref $0)))) - (type $2 (struct_subtype (ref null i31) anyref data)) + ;; CHECK-NEXT: (type $0 (struct (field nullref) (field (ref $1)))) + (type $0 (struct_subtype nullref anyref data)) + ;; CHECK: (type $1 (struct (field nullfuncref) (field (ref $2)))) + (type $1 (struct_subtype nullfuncref anyref data)) + ;; CHECK: (type $2 (struct (field nullexternref) (field (ref $0)))) + (type $2 (struct_subtype nullexternref anyref data)) ;; CHECK: (type $ref|$0|_ref|$1|_ref|$2|_=>_none (func (param (ref $0) (ref $1) (ref $2)))) @@ -23,13 +23,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $1 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $2 - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (ref.null noextern) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -37,19 +37,19 @@ (func $foo (param $x (ref $0)) (param $y (ref $1)) (param $z (ref $2)) (drop (struct.new $0 - (ref.null any) + (ref.null none) (local.get $y) ) ) (drop (struct.new $1 - (ref.null eq) + (ref.null nofunc) (local.get $z) ) ) (drop (struct.new $2 - (ref.null i31) + (ref.null noextern) (local.get $x) ) ) @@ -65,12 +65,12 @@ ;; CHECK-NEXT: (type $all (struct (field i32) (field (ref $0)) (field (ref $1)) (field (ref $2)))) (type $all (struct_subtype i32 anyref anyref anyref data)) - ;; CHECK: (type $0 (struct (field anyref) (field (ref null $all)) (field (ref $0)))) - (type $0 (struct_subtype (ref null any) anyref anyref data)) - ;; CHECK: (type $1 (struct_subtype (field eqref) (field (ref null $all)) (field (ref $0)) $0)) - (type $1 (struct_subtype (ref null eq) anyref anyref $0)) - ;; CHECK: (type $2 (struct_subtype (field i31ref) (field (ref null $all)) (field (ref $0)) $1)) - (type $2 (struct_subtype (ref null i31) anyref anyref $1)) + ;; CHECK: (type $0 (struct (field (ref null $all)) (field (ref $0)))) + (type $0 (struct_subtype anyref anyref data)) + ;; CHECK: (type $1 (struct_subtype (field (ref null $all)) (field (ref $0)) $0)) + (type $1 (struct_subtype anyref anyref $0)) + ;; CHECK: (type $2 (struct_subtype (field (ref null $all)) (field (ref $0)) $1)) + (type $2 (struct_subtype anyref anyref $1)) ;; CHECK: (type $ref|$0|_ref|$1|_ref|$2|_=>_none (func (param (ref $0) (ref $1) (ref $2)))) @@ -86,21 +86,18 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $0 - ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $all) ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $1 - ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $all) ;; CHECK-NEXT: (local.get $z) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $2 - ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $all) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) @@ -118,21 +115,18 @@ ) (drop (struct.new $0 - (ref.null any) (local.get $all) (local.get $y) ) ) (drop (struct.new $1 - (ref.null eq) (local.get $all) (local.get $z) ) ) (drop (struct.new $2 - (ref.null i31) (local.get $all) (local.get $x) ) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 082f16bfb..f6d7ec7b2 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -5,15 +5,17 @@ ;; A struct with three fields. The first will have no writes, the second one ;; write of the same type, and the last a write of a subtype, which will allow ;; us to specialize that one. - ;; CHECK: (type $struct (struct (field (mut anyref)) (field (mut anyref)) (field (mut (ref i31))))) - (type $struct (struct_subtype (field (mut anyref)) (field (mut anyref)) (field (mut anyref)) data)) + ;; CHECK: (type $struct (struct (field (mut anyref)) (field (mut (ref i31))) (field (mut (ref i31))))) + (type $struct (struct_subtype (field (mut anyref)) (field (mut (ref i31))) (field (mut anyref)) data)) ;; CHECK: (type $ref|$struct|_=>_none (func (param (ref $struct)))) ;; CHECK: (func $work (type $ref|$struct|_=>_none) (param $struct (ref $struct)) ;; CHECK-NEXT: (struct.set $struct 1 ;; CHECK-NEXT: (local.get $struct) - ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i31.new + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 2 ;; CHECK-NEXT: (local.get $struct) @@ -30,7 +32,7 @@ (func $work (param $struct (ref $struct)) (struct.set $struct 1 (local.get $struct) - (ref.null any) + (i31.new (i32.const 0)) ) (struct.set $struct 2 (local.get $struct) @@ -655,7 +657,7 @@ ) (module - ;; CHECK: (type $struct (struct (field (mut dataref)))) + ;; CHECK: (type $struct (struct (field (mut nullref)))) (type $struct (struct_subtype (field (mut (ref null data))) data)) ;; CHECK: (type $ref|$struct|_=>_none (func (param (ref $struct)))) @@ -666,8 +668,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $work (param $struct (ref $struct)) - ;; The only write to this struct is of a null default value. There is - ;; nothing to optimize here. + ;; The only write to this struct is of a null default value, so we can + ;; optimize to nullref. (drop (struct.new_default $struct) ) |