Skip to content

Commit 97e0ecf

Browse files
authored
Precompute: Avoid caching heap values of partial precomputations (#7766)
Fixes a regression from #7763 This is pretty subtle, unfortunately. We cache the results of a struct.new etc as we go, so that identity is preserved for precomputing RefEq etc. The old code always computed structs in full before checking the cache. #7763 removed that code as an optimization. But, it turns out, that was load-bearing in that it, by pure luck, happened to avoid a bug. The bug that was avoided, and which this PR fixes, is that when we partially precompute, we modify a struct.new and see if we can precompute that version. We were still using the same heap value cache, so we could cache the value, then later end up using it for the original (before, we'd still compute the struct.new again and use those values). To fix that, do such speculative precomputations on a side (throwaway) cache. Fixes #7765
1 parent b75c885 commit 97e0ecf

File tree

2 files changed

+72
-5
lines changed

2 files changed

+72
-5
lines changed

src/passes/Precompute.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -672,10 +672,15 @@ struct Precompute
672672
auto** pointerToSelect =
673673
getChildPointerInImmediateParent(stack, selectIndex, func);
674674
*pointerToSelect = select->ifTrue;
675-
auto ifTrue = precomputeExpression(parent);
675+
// When we perform these speculative precomputations, we must not use
676+
// the normal heapValues, as we are testing modified versions of
677+
// |parent|. Results here must not be cached for later.
678+
HeapValues temp;
679+
auto ifTrue = precomputeExpression(parent, true, &temp);
680+
temp.clear();
676681
if (isValidPrecomputation(ifTrue)) {
677682
*pointerToSelect = select->ifFalse;
678-
auto ifFalse = precomputeExpression(parent);
683+
auto ifFalse = precomputeExpression(parent, true, &temp);
679684
if (isValidPrecomputation(ifFalse)) {
680685
// Wonderful, we can precompute here! The select can now contain the
681686
// computed values in its arms.
@@ -714,12 +719,19 @@ struct Precompute
714719

715720
private:
716721
// Precompute an expression, returning a flow, which may be a constant
717-
// (that we can replace the expression with if replaceExpression is set).
718-
Flow precomputeExpression(Expression* curr, bool replaceExpression = true) {
722+
// (that we can replace the expression with if replaceExpression is set). When
723+
// |usedHeapValues| is provided, we use those values instead of the normal
724+
// |heapValues| (that is, we do not use the normal heap value cache).
725+
Flow precomputeExpression(Expression* curr,
726+
bool replaceExpression = true,
727+
HeapValues* usedHeapValues = nullptr) {
728+
if (!usedHeapValues) {
729+
usedHeapValues = &heapValues;
730+
}
719731
Flow flow;
720732
try {
721733
flow = PrecomputingExpressionRunner(
722-
getModule(), getValues, heapValues, replaceExpression)
734+
getModule(), getValues, *usedHeapValues, replaceExpression)
723735
.visit(curr);
724736
} catch (NonconstantException&) {
725737
return Flow(NONCONSTANT_FLOW);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2+
3+
;; RUN: foreach %s %t wasm-opt --precompute-propagate --optimize-level=2 -all -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (type $empty (struct))
7+
(type $empty (struct))
8+
9+
;; CHECK: (type $i32_any (sub (struct (field i32) (field (ref any)))))
10+
(type $i32_any (sub (struct (field i32) (field (ref any)))))
11+
12+
;; CHECK: (func $partial-gc-cache (type $1) (result i32)
13+
;; CHECK-NEXT: (local $x i32)
14+
;; CHECK-NEXT: (drop
15+
;; CHECK-NEXT: (struct.new $i32_any
16+
;; CHECK-NEXT: (i32.const 0)
17+
;; CHECK-NEXT: (select (result (ref (exact $empty)))
18+
;; CHECK-NEXT: (struct.new_default $empty)
19+
;; CHECK-NEXT: (struct.new_default $empty)
20+
;; CHECK-NEXT: (i32.trunc_f64_s
21+
;; CHECK-NEXT: (f64.const 562949953421311)
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: )
24+
;; CHECK-NEXT: )
25+
;; CHECK-NEXT: )
26+
;; CHECK-NEXT: (i32.const 0)
27+
;; CHECK-NEXT: )
28+
(func $partial-gc-cache (result i32)
29+
(local $x i32)
30+
;; The select here looks promising for partial computation. We will therefore
31+
;; try to precompute variations of the outer struct.new. While doing so we
32+
;; should not confuse later computations, and in particular, not store
33+
;; anything in the heap value cache - the risk is that these partial
34+
;; precomputations will not have the i32.trunc which traps, so they will
35+
;; execute without trapping, and if we cached that result for the outer
36+
;; struct.new, we'd later think that results applies here (if we did, we'd
37+
;; think this does not trap, and precompute it into a nop, removing the
38+
;; trap erroneously).
39+
(drop
40+
(struct.new $i32_any
41+
(i32.const 0)
42+
(select (result (ref $empty))
43+
(struct.new $empty)
44+
(struct.new $empty)
45+
(i32.trunc_f64_s
46+
(f64.const 562949953421311)
47+
)
48+
)
49+
)
50+
)
51+
;; This local causes propagation to work, which causes the extra
52+
;; optimizations that lead to the bug above.
53+
(local.get $x)
54+
)
55+
)

0 commit comments

Comments
 (0)