diff options
-rw-r--r-- | src/ir/possible-contents.cpp | 63 | ||||
-rw-r--r-- | test/lit/passes/gufa-refs.wast | 168 |
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 |