diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index fff4e4b8ed3..89a3584487c 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -568,14 +568,6 @@ struct ConstraintAnalysis return; } - // See above on binary action counting limits. - if (auto* binary = set->value->dynCast()) { - if (binaryActionCounts[binary]++ >= MaxBinaryActions) { - constraints.setProvesNothing(set->index); - return; - } - } - // Look at the fallthrough. It is valid to do so, because our constraints // only track two things, constants and locals. For a constant, it does // not change while falling through. For a local, the only way for the @@ -600,8 +592,37 @@ struct ConstraintAnalysis // opportunity to write any other value while falling through. (And, any // local.tee appearing here would have been reached earlier in the // traversal, and handled.) - auto* value = - Properties::getFallthrough(set->value, getPassOptions(), *getModule()); + auto* value = set->value; + while (1) { + if (value->is()) { + // We stop at the first tee: we don't need to look any further, and + // will just apply that local's values to ourselves, saving repeated + // work. + break; + } + auto* next = Properties::getImmediateFallthrough( + value, getPassOptions(), *getModule()); + if (value == next) { + break; + } else { + value = next; + } + } + + // Now that we know the value, check binary action counting limits (see + // above). + if (auto* binary = value->dynCast()) { + // The count may exceed the limit sometimes, but add a hard assert on + // never going up so high it is likely doing an unbounded computation. + auto& count = binaryActionCounts[binary]; + assert(count < MaxBinaryActions * 10); + count++; + if (count >= MaxBinaryActions) { + constraints.setProvesNothing(set->index); + return; + } + } + constraints.set(set->index, value); } } diff --git a/test/lit/passes/constraint-analysis-loops.wast b/test/lit/passes/constraint-analysis-loops.wast index 9c1e594f18e..7c66ee84429 100644 --- a/test/lit/passes/constraint-analysis-loops.wast +++ b/test/lit/passes/constraint-analysis-loops.wast @@ -1905,4 +1905,59 @@ ) ) ) + + ;; CHECK: (func $increment-tee (type $0) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $y i32) + ;; CHECK-NEXT: (loop $label1 + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_s + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $y + ;; CHECK-NEXT: (local.tee $x + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (local.get $y) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (br $label1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $increment-tee + (local $x i32) + (local $y i32) + ;; A loop, where $y is incremented but there is a tee in the middle. The loop + ;; is unbounded (the exit condition is never hit), so we must be careful to + ;; not keep calculating 1,2,3, without limit. The tee in the middle should not + ;; confuse us: we apply the +=1 operation to x directly, but y reads it + ;; through the tee. We should stop calculating anything about both rather than + ;; hang for a long time. + (loop $label1 + (if + (i32.gt_s + (local.get $y) + (local.get $x) + ) + (then + (unreachable) + ) + ) + (local.set $y + (local.tee $x + (i32.add + (local.get $y) + (i32.const 1) + ) + ) + ) + (br $label1) + ) + ) )