diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 5e8e3a0129c..fc273301712 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -274,15 +274,19 @@ void LocalConstraint::flip() { } void BasicBlockConstraintMap::set(Index index, const Constraint& c) { + // We should not set values in unreachable code. assert(!unreachable); - eraseStaleRefs(index); - map[index].set(c); - noteRefs(index, c); - // If the constraint refers to another local, add it there too. - if (std::holds_alternative(c.term)) { - approximateAndInternal(index, c, true); - } + // Clear the old state. + eraseStaleRefs(index); + map[index].setProvesNothing(); + // Note that this last line puts us in a temporarily invalid state: we do not + // normally store a constraint of proves-nothing in the map. Doing it this + // way, until approximateAnd adds the constraint, is more efficient than + // erasing and re-adding. + + // Apply the constraint. + approximateAnd(index, c); } void BasicBlockConstraintMap::setProvesNothing(Index index) { @@ -319,7 +323,8 @@ void BasicBlockConstraintMap::approximateOr( void BasicBlockConstraintMap::approximateAndInternal(Index index, const Constraint& c, - bool flip) { + bool flip, + bool isCopy) { Constraint actual = c; if (flip) { LocalConstraint flipped{index, c}; @@ -328,10 +333,26 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, actual = flipped.constraint; } - auto combined = get(index); - combined.approximateAnd(actual); + // Never add constraints to ourselves (x == x, etc., which can happen due to + // copying/flipping). + if (auto* other = std::get_if(&actual.term)) { + if (*other == index) { + return; + } + } + + // Refer to the constraints for this index. If this is the first access of + // the local, then we insert a new item into the map, which has a default of + // proxesEverything, which we need to flip (provesEverything cannot otherwise + // be found in the map, as we never store it). + auto [iter, _] = map.insert({index, AndedConstraintSet::makeProvesNothing()}); + auto& indexConstraints = iter->second; + // As in ::set(), this makes the map temporarily invalid until the + // approximateAnd, as we don't store proves-nothing in the map, normally. + + indexConstraints.approximateAnd(actual); - if (combined.provesEverything()) { + if (indexConstraints.provesEverything()) { // We just proved we are in unreachable code. unreachable = true; map.clear(); @@ -340,10 +361,7 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // We just added a constraint, so we can prove something (we may lose some // information as this is an approximate AND, but we cannot lose it all). - assert(!combined.provesNothing()); - - // Otherwise, this is an interesting state; set it. - map[index] = std::move(combined); + assert(!indexConstraints.provesNothing()); // Add a ref of what we are adding. Note that the approximation above may end // up not actually adding this, or adding only part of this, but it is safe to @@ -353,7 +371,19 @@ void BasicBlockConstraintMap::approximateAndInternal(Index index, // If this is not the flipped version, and it refers to a local, add the // flipped one too. if (!flip && std::holds_alternative(actual.term)) { - approximateAndInternal(index, actual, true); + approximateAndInternal(index, actual, true, isCopy); + } + + // If this constraint is simply "== x", then we are equal to that other local + // x, and can copy its constraints (if we are not already such a copy). + if (!isCopy) { + if (auto* other = std::get_if(&actual.term)) { + if (actual.op == Abstract::Eq) { + for (auto& otherC : get(*other)) { + approximateAndInternal(index, otherC, false, true); + } + } + } } } diff --git a/src/ir/constraint.h b/src/ir/constraint.h index 31a5dd04fed..1c91e0d1632 100644 --- a/src/ir/constraint.h +++ b/src/ir/constraint.h @@ -287,9 +287,12 @@ struct BasicBlockConstraintMap { // Internal version, with a flag to flip the constraint. Whenever we apply // e.g. x == y, we also apply y == x to y, to maintain the invariant described // above. When flip is true, we flip the constraint and apply it to the other - // index (y == x, in this example). - void - approximateAndInternal(Index index, const Constraint& c, bool flip = false); + // index (y == x, in this example). When isCopy is true, we are a copied + // constraint from another local, and we do not need to add new copies of it. + void approximateAndInternal(Index index, + const Constraint& c, + bool flip = false, + bool isCopy = false); }; std::ostream& operator<<(std::ostream& o, const Constraint& c); diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 5c2fe99b875..25173ee8d9f 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -54,6 +54,11 @@ struct Info { // For each local index, we track the constraints we know about it. We only do // so at the start of each block, which is enough for the analysis below. BasicBlockConstraintMap startConstraints; + + void dump(Function* func) { + std::cout << "Info{" << actions.size() << ", " << brancher << ", " + << startConstraints << "}\n"; + } }; struct ConstraintAnalysis diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index ce5d59afa7b..c5c7eb93245 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -1008,7 +1008,7 @@ ) ) - ;; CHECK: (func $conditional-binary-nesting (type $4) (param $x i32) (param $y i32) + ;; CHECK: (func $conditional-binary-nesting (type $2) (param $x i32) (param $y i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.eq ;; CHECK-NEXT: (local.get $x) @@ -1038,7 +1038,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $conditional-binary-nesting (type $4) (param $x i32) (param $y i32) + ;; OPTIN: (func $conditional-binary-nesting (type $2) (param $x i32) (param $y i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.eq ;; OPTIN-NEXT: (local.get $x) @@ -1510,7 +1510,7 @@ ) ) - ;; CHECK: (func $br_on_null (type $3) (param $param anyref) + ;; CHECK: (func $br_on_null (type $4) (param $param anyref) ;; CHECK-NEXT: (block $block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null @@ -1531,7 +1531,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_null (type $3) (param $param anyref) + ;; OPTIN: (func $br_on_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (block $block ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (ref.is_null @@ -1581,7 +1581,7 @@ ) ) - ;; CHECK: (func $br_on_non_null (type $3) (param $param anyref) + ;; CHECK: (func $br_on_non_null (type $4) (param $param anyref) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $block (result (ref any)) ;; CHECK-NEXT: (drop @@ -1602,7 +1602,7 @@ ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $br_on_non_null (type $3) (param $param anyref) + ;; OPTIN: (func $br_on_non_null (type $4) (param $param anyref) ;; OPTIN-NEXT: (drop ;; OPTIN-NEXT: (block $block (result (ref any)) ;; OPTIN-NEXT: (drop @@ -1652,7 +1652,7 @@ ) ) - ;; CHECK: (func $local-changes (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -1708,7 +1708,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -1838,7 +1838,7 @@ ) ) - ;; CHECK: (func $local-changes-2 (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $y) ;; CHECK-NEXT: ) @@ -1894,7 +1894,7 @@ ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-2 (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-2 (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (local.set $x ;; OPTIN-NEXT: (local.get $y) ;; OPTIN-NEXT: ) @@ -2228,7 +2228,7 @@ ) ) - ;; CHECK: (func $local-changes-ne (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; CHECK: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.ne ;; CHECK-NEXT: (local.get $x) @@ -2280,7 +2280,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; OPTIN: (func $local-changes-ne (type $2) (param $x i32) (param $y i32) (param $z i32) + ;; OPTIN: (func $local-changes-ne (type $3) (param $x i32) (param $y i32) (param $z i32) ;; OPTIN-NEXT: (if ;; OPTIN-NEXT: (i32.ne ;; OPTIN-NEXT: (local.get $x) @@ -2400,4 +2400,321 @@ ) ) ) + + ;; CHECK: (func $multi-local-copy (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (local $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 10) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy + (local $x i32) + (local $y i32) + ;; x is 10, y is copied. + (local.set $x + (i32.const 10) + ) + (local.set $y + (local.get $x) + ) + ;; Verify those values: both x and y are equal to 10. + (drop + (i32.eq + (local.get $x) + (i32.const 10) + ) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 10) + ) + ) + ) + + ;; CHECK: (func $multi-local-copy-if (type $2) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if (type $2) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (local.set $y + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if (param $x i32) (param $y i32) + (if + (i32.eq + (local.get $x) + (i32.const 42) + ) + (then + ;; We don't know anything yet. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; Copy x into y, and see that it is now equal to 42. + (local.set $y + (local.get $x) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed (equal to 42) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + (else + ;; We don't know anything yet. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; Copy x into y, and see that it is now *not* equal to 42. + (local.set $y + (local.get $x) + ) + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed (not equal to 42) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $multi-local-copy-if-2 (type $2) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if-2 (type $2) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if-2 (param $x i32) (param $y i32) + ;; As above, but the if's condition is on x == y, so that is where we + ;; apply equality between them. + (local.set $x + (i32.const 42) + ) + (if + (i32.eq + (local.get $x) + (local.get $y) + ) + (then + ;; This is true. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + ;; x was not changed, so this is still true. + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) + + ;; CHECK: (func $multi-local-copy-if-3 (type $2) (param $x i32) (param $y i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (i32.const 42) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $multi-local-copy-if-3 (type $2) (param $x i32) (param $y i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (i32.const 42) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.eq + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: (local.get $y) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (drop + ;; OPTIN-NEXT: (i32.const 1) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $multi-local-copy-if-3 (param $x i32) (param $y i32) + ;; As above, but the if's condition is flipped. + (local.set $x + (i32.const 42) + ) + (if + (i32.eq + (local.get $y) + (local.get $x) + ) + (then + ;; These are true. + (drop + (i32.eq + (local.get $y) + (i32.const 42) + ) + ) + (drop + (i32.eq + (local.get $x) + (i32.const 42) + ) + ) + ) + ) + ) )