summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/lubs.cpp10
-rw-r--r--src/ir/lubs.h82
-rw-r--r--src/passes/DeadArgumentElimination.cpp16
-rw-r--r--src/passes/GlobalRefining.cpp7
-rw-r--r--src/passes/LocalSubtyping.cpp7
-rw-r--r--src/passes/SignatureRefining.cpp13
-rw-r--r--src/passes/TypeRefining.cpp16
-rw-r--r--test/lit/passes/dae-gc-refine-params.wast66
-rw-r--r--test/lit/passes/dae-gc.wast4
-rw-r--r--test/lit/passes/global-refining.wast11
-rw-r--r--test/lit/passes/signature-refining.wast22
-rw-r--r--test/lit/passes/type-refining-isorecursive.wast40
-rw-r--r--test/lit/passes/type-refining.wast16
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)
)