summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ir/possible-contents.cpp63
-rw-r--r--test/lit/passes/gufa-refs.wast168
2 files changed, 217 insertions, 14 deletions
diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp
index f1302b3dd..83d55e6f9 100644
--- a/src/ir/possible-contents.cpp
+++ b/src/ir/possible-contents.cpp
@@ -341,15 +341,18 @@ struct InfoCollector
addRoot(curr, PossibleContents::literal(curr->value));
}
void visitUnary(Unary* curr) {
- // TODO: Optimize cases like this using interpreter integration: if the
- // input is a Literal, we could interpret the Literal result.
+ // We could optimize cases like this using interpreter integration: if the
+ // input is a Literal, we could interpret the Literal result. However, if
+ // the input is a literal then the GUFA pass will emit a Const there, and
+ // the Precompute pass can use that later to interpret a result. That is,
+ // the input we need here, a constant, is already something GUFA can emit as
+ // an output. As a result, integrating the interpreter here would perhaps
+ // make compilation require fewer steps, but it wouldn't let us optimize
+ // more than we could before.
addRoot(curr);
}
void visitBinary(Binary* curr) { addRoot(curr); }
void visitSelect(Select* curr) {
- // TODO: We could use the fact that both sides are executed unconditionally
- // while optimizing (if one arm must trap, then the Select will trap,
- // which is not the same as with an If).
receiveChildValue(curr->ifTrue, curr);
receiveChildValue(curr->ifFalse, curr);
}
@@ -362,7 +365,11 @@ struct InfoCollector
PossibleContents::literal(Literal::makeNull(curr->type.getHeapType())));
}
void visitRefIs(RefIs* curr) {
- // TODO: optimize when possible
+ // TODO: Optimize when possible. For example, if we can infer an exact type
+ // here which allows us to know the result then we should do so. This
+ // is unlike the case in visitUnary, above: the information that lets
+ // us optimize *cannot* be written into Binaryen IR (unlike a Literal)
+ // so using it during this pass allows us to optimize new things.
addRoot(curr);
}
void visitRefFunc(RefFunc* curr) {
@@ -372,11 +379,11 @@ struct InfoCollector
}
void visitRefEq(RefEq* curr) {
// TODO: optimize when possible (e.g. when both sides must contain the same
- // global)
+ // global, or if we infer exact types that are different then the
+ // result must be 0)
addRoot(curr);
}
void visitTableGet(TableGet* curr) {
- // TODO: optimize when possible
addRoot(curr);
}
void visitTableSet(TableSet* curr) {}
@@ -408,15 +415,15 @@ struct InfoCollector
addRoot(curr);
}
- void visitRefTest(RefTest* curr) {
- // TODO: optimize when possible
- addRoot(curr);
- }
void visitRefCast(RefCast* curr) {
// We will handle this in a special way later during the flow, as ref.cast
// only allows valid values to flow through.
addChildParentLink(curr->ref, curr);
}
+ void visitRefTest(RefTest* curr) {
+ // We will handle this similarly to RefCast.
+ addChildParentLink(curr->ref, curr);
+ }
void visitBrOn(BrOn* curr) {
// TODO: optimize when possible
handleBreakValue(curr);
@@ -1107,6 +1114,9 @@ private:
// values to flow through it.
void flowRefCast(const PossibleContents& contents, RefCast* cast);
+ // The possible contents may allow us to infer an outcome, like with RefCast.
+ void flowRefTest(const PossibleContents& contents, RefTest* test);
+
// We will need subtypes during the flow, so compute them once ahead of time.
std::unique_ptr<SubTypes> subTypes;
@@ -1459,6 +1469,9 @@ void Flower::flowAfterUpdate(LocationIndex locationIndex) {
} else if (auto* cast = parent->dynCast<RefCast>()) {
assert(cast->ref == child);
flowRefCast(contents, cast);
+ } else if (auto* test = parent->dynCast<RefTest>()) {
+ assert(test->ref == child);
+ flowRefTest(contents, test);
} else {
// TODO: ref.test and all other casts can be optimized (see the cast
// helper code used in OptimizeInstructions and RemoveUnusedBrs)
@@ -1720,6 +1733,32 @@ void Flower::flowRefCast(const PossibleContents& contents, RefCast* cast) {
}
}
+void Flower::flowRefTest(const PossibleContents& contents, RefTest* test) {
+ PossibleContents filtered;
+ if (contents.isMany()) {
+ // Just pass the Many through.
+ filtered = contents;
+ } else {
+ // RefTest returns 1 iff the input is not null and is also a subtype.
+ bool isSubType =
+ HeapType::isSubType(contents.getType().getHeapType(), test->intendedType);
+ bool mayBeNull = contents.getType().isNullable();
+ if (!isSubType) {
+ filtered = PossibleContents::literal(Literal(int32_t(0)));
+ } else if (!mayBeNull) {
+ filtered = PossibleContents::literal(Literal(int32_t(1)));
+ } else {
+ filtered = PossibleContents::many();
+ }
+ }
+#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
+ std::cout << " ref.test passing through\n";
+ filtered.dump(std::cout);
+ std::cout << '\n';
+#endif
+ updateContents(ExpressionLocation{test, 0}, filtered);
+}
+
#if defined(POSSIBLE_CONTENTS_DEBUG) && POSSIBLE_CONTENTS_DEBUG >= 2
void Flower::dump(Location location) {
if (auto* loc = std::get_if<ExpressionLocation>(&location)) {
diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast
index 603dc514c..6e4a60f6d 100644
--- a/test/lit/passes/gufa-refs.wast
+++ b/test/lit/passes/gufa-refs.wast
@@ -2445,14 +2445,18 @@
(type $substruct (struct_subtype (field i32) (field i32) $struct))
;; CHECK: (type $none_=>_none (func_subtype func))
- ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
-
;; CHECK: (type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
(type $subsubstruct (struct_subtype (field i32) (field i32) (field i32) $substruct))
+ ;; CHECK: (type $none_=>_i32 (func_subtype (result i32) func))
+
+ ;; CHECK: (type $i32_=>_none (func_subtype (param i32) func))
+
;; CHECK: (import "a" "b" (func $import (result i32)))
(import "a" "b" (func $import (result i32)))
+ ;; CHECK: (export "ref.test-inexact" (func $ref.test-inexact))
+
;; CHECK: (func $test (type $none_=>_none)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
@@ -2583,6 +2587,166 @@
)
)
)
+
+ ;; CHECK: (func $ref.test-exact (type $none_=>_none)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $ref.test-exact
+ ;; This cast will fail: we know the exact type of the reference, and it is
+ ;; not a subtype.
+ (drop
+ (ref.test_static $substruct
+ (struct.new $struct
+ (i32.const 0)
+ )
+ )
+ )
+ ;; Casting a thing to itself must succeed.
+ (drop
+ (ref.test_static $substruct
+ (struct.new $substruct
+ (i32.const 1)
+ (i32.const 2)
+ )
+ )
+ )
+ ;; Casting a thing to a supertype must succeed.
+ (drop
+ (ref.test_static $substruct
+ (struct.new $subsubstruct
+ (i32.const 3)
+ (i32.const 4)
+ (i32.const 5)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $ref.test-inexact (type $i32_=>_none) (param $x i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $struct
+ ;; CHECK-NEXT: (select (result anyref)
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (ref.null any)
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $struct
+ ;; CHECK-NEXT: (select (result (ref $struct))
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: (i32.const 3)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $substruct
+ ;; CHECK-NEXT: (select (result (ref $struct))
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 4)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 5)
+ ;; CHECK-NEXT: (i32.const 6)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.test_static $subsubstruct
+ ;; CHECK-NEXT: (select (result (ref $struct))
+ ;; CHECK-NEXT: (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 7)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.new $substruct
+ ;; CHECK-NEXT: (i32.const 8)
+ ;; CHECK-NEXT: (i32.const 9)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $ref.test-inexact (export "ref.test-inexact") (param $x i32)
+ ;; The input to the ref.test is potentially null, so we cannot infer here.
+ (drop
+ (ref.test_static $struct
+ (select
+ (struct.new $struct
+ (i32.const 0)
+ )
+ (ref.null any)
+ (local.get $x)
+ )
+ )
+ )
+ ;; The input to the ref.test is either $struct or $substruct, both of which
+ ;; work, so here we can infer a 1 - but we need a cone type for that TODO
+ (drop
+ (ref.test_static $struct
+ (select
+ (struct.new $struct
+ (i32.const 1)
+ )
+ (struct.new $substruct
+ (i32.const 2)
+ (i32.const 3)
+ )
+ (local.get $x)
+ )
+ )
+ )
+ ;; As above, but now we test with $substruct, so one possibility fails and
+ ;; one succeeds. We cannot infer here.
+ (drop
+ (ref.test_static $substruct
+ (select
+ (struct.new $struct
+ (i32.const 4)
+ )
+ (struct.new $substruct
+ (i32.const 5)
+ (i32.const 6)
+ )
+ (local.get $x)
+ )
+ )
+ )
+ ;; Two possible types, both are supertypes, so neither is a subtype, and we
+ ;; can infer a 0 - but we need more precise type info than we have TODO
+ (drop
+ (ref.test_static $subsubstruct
+ (select
+ (struct.new $struct
+ (i32.const 7)
+ )
+ (struct.new $substruct
+ (i32.const 8)
+ (i32.const 9)
+ )
+ (local.get $x)
+ )
+ )
+ )
+ )
)
(module