Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions src/passes/OptimizeInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1796,6 +1796,8 @@ struct OptimizeInstructions
}

void visitRefEq(RefEq* curr) {
Builder builder(*getModule());

// Check for unreachability. Note we must check both the children and the
// ref.eq itself, as in e.g. optimizeTernary, as we only refinalize at the
// end, so unreachable children may not update the parent yet.
Expand Down Expand Up @@ -1832,12 +1834,16 @@ struct OptimizeInstructions
skipCast(curr->left, nullableEq);
skipCast(curr->right, nullableEq);

// Identical references compare equal.
// (Technically we do not need to check if the inputs are also foldable into
// a single one, but we do not have utility code to handle non-foldable
// cases yet; the foldable case we do handle is the common one of the first
// child being a tee and the second a get of that tee. TODO)
// Identical references compare equal. If the inputs are foldable, we need
// only keep one of them.
if (areConsecutiveInputsEqualAndFoldable(curr->left, curr->right)) {
replaceCurrent(
builder.makeSequence(builder.makeDrop(curr->left),
builder.makeConst(Literal::makeOne(Type::i32))));
return;
}

if (areConsecutiveInputsEqual(curr->left, curr->right)) {
replaceCurrent(
getDroppedChildrenAndAppend(curr, Literal::makeOne(Type::i32)));
return;
Expand Down Expand Up @@ -2867,9 +2873,24 @@ struct OptimizeInstructions
return false;
}

// They do look the same! Make sure nothing executed in between them can
// affect the value of `right` and make it different from `left`.
if (interferingEffects.orderedBefore(effects(right))) {
// They do look the same! Determine whether the left hand expression can
// change the value of the operands flowing into the right-hand expression.
// Do not consider the right-hand expression itself here because the
// expressions might be idempotent function calls and we might otherwise
// mistakenly conclude that the first call interferes with the second.
EffectAnalyzer rightEffects(getPassOptions(), *getModule());
for (auto* child : ChildIterator(right)) {
rightEffects.walk(child);
}
ShallowEffectAnalyzer leftEffects(getPassOptions(), *getModule(), left);
if (leftEffects.orderedBefore(rightEffects)) {
return false;
}

// Make sure nothing executed in between the expressions can affect the
// value of `right` (or its operands) and make it different from `left`.
rightEffects.visit(right);
if (interferingEffects.orderedBefore(rightEffects)) {
return false;
}

Expand All @@ -2896,7 +2917,8 @@ struct OptimizeInstructions
return true;
}

// To fold the right side into the left, it must have no effects.
// To fold the right side into the left, it must have no unremovable
// effects.
auto rightMightHaveEffects = true;
if (auto* call = right->dynCast<Call>()) {
// If these are a pair of idempotent calls, then the second has no
Expand Down Expand Up @@ -2929,7 +2951,9 @@ struct OptimizeInstructions
}
ShallowEffectAnalyzer parentEffects(
getPassOptions(), *getModule(), call);
if (parentEffects.invalidates(childEffects)) {
// Check if the first parent is ordered before the second child (in the
// example above, the first call vs the second global.get).
if (parentEffects.orderedBefore(childEffects)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (parentEffects.orderedBefore(childEffects)) {
// Check if the first parent is ordered before the second child (in the
// example above, the first call vs the second global.get).
if (parentEffects.orderedBefore(childEffects)) {

return false;
}
// No effects are possible.
Expand Down
14 changes: 12 additions & 2 deletions test/lit/passes/optimize-instructions-gc-iit.wast
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,22 @@

;; CHECK: (func $ref-eq-ref-cast (type $6) (param $x eqref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; TNH: (func $ref-eq-ref-cast (type $6) (param $x eqref)
;; TNH-NEXT: (drop
;; TNH-NEXT: (i32.const 1)
;; TNH-NEXT: (block (result i32)
;; TNH-NEXT: (drop
;; TNH-NEXT: (local.get $x)
;; TNH-NEXT: )
;; TNH-NEXT: (i32.const 1)
;; TNH-NEXT: )
;; TNH-NEXT: )
;; TNH-NEXT: )
(func $ref-eq-ref-cast (param $x eqref)
Expand Down
38 changes: 26 additions & 12 deletions test/lit/passes/optimize-instructions-gc-tnh.wast
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,48 @@
)
)

;; TNH: (func $ref.eq-no (type $8) (param $a eqref) (param $b eqref) (param $any anyref)
;; TNH: (func $ref.eq-different-traps (type $8) (param $a eqref) (param $b eqref) (param $any anyref)
;; TNH-NEXT: (drop
;; TNH-NEXT: (i32.const 1)
;; TNH-NEXT: (block (result i32)
;; TNH-NEXT: (drop
;; TNH-NEXT: (ref.cast (ref null $struct)
;; TNH-NEXT: (local.get $any)
;; TNH-NEXT: )
;; TNH-NEXT: )
;; TNH-NEXT: (i32.const 1)
;; TNH-NEXT: )
;; TNH-NEXT: )
;; TNH-NEXT: )
;; NO_TNH: (func $ref.eq-no (type $8) (param $a eqref) (param $b eqref) (param $any anyref)
;; NO_TNH: (func $ref.eq-different-traps (type $8) (param $a eqref) (param $b eqref) (param $any anyref)
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (ref.eq
;; NO_TNH-NEXT: (ref.cast (ref null $struct)
;; NO_TNH-NEXT: (local.get $any)
;; NO_TNH-NEXT: (block (result i32)
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (ref.cast (ref null $struct)
;; NO_TNH-NEXT: (local.get $any)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: (ref.cast (ref struct)
;; NO_TNH-NEXT: (local.get $any)
;; NO_TNH-NEXT: (drop
;; NO_TNH-NEXT: (ref.cast (ref struct)
;; NO_TNH-NEXT: (local.get $any)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: (i32.const 1)
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
;; NO_TNH-NEXT: )
(func $ref.eq-no (param $a (ref null eq)) (param $b (ref null eq)) (param $any anyref)
;; We must leave the inputs to ref.eq of type eqref or a subtype.
(func $ref.eq-different-traps (param $a (ref null eq)) (param $b (ref null eq)) (param $any anyref)
;; The right hand side traps on nulls but the left hand side does not. We
;; can always see that they produce the same value, but we can only fold
;; the right side away if we assume traps never happen.
(drop
(ref.eq
(ref.cast (ref null $struct)
(local.get $any) ;; *Not* an eqref!
(local.get $any)
)
(ref.as_non_null
(ref.cast (ref struct)
(ref.as_non_null
(local.get $any) ;; *Not* an eqref!
(local.get $any)
)
)
)
Expand Down
112 changes: 92 additions & 20 deletions test/lit/passes/optimize-instructions-gc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
;; CHECK: (import "env" "get-i32" (func $get-i32 (type $8) (result i32)))
(import "env" "get-i32" (func $get-i32 (result i32)))

;; CHECK: (global $g (mut eqref) (ref.i31
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: ))
(global $g (mut eqref) (ref.i31 (i32.const 0)))

;; These functions test if an `if` with subtyped arms is correctly folded
;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold)
;; CHECK: (func $if-arms-subtype-fold (type $29) (result anyref)
Expand Down Expand Up @@ -415,7 +420,12 @@
;; CHECK-NEXT: (local $lx eqref)
;; CHECK-NEXT: (local $ly eqref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
Expand All @@ -427,10 +437,23 @@
;; CHECK-NEXT: (call $get-eqref)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (local.get $lx)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref-eq (param $x eqref) (param $y eqref)
Expand Down Expand Up @@ -489,22 +512,49 @@
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (global.get $g)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (global.get $g)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (call $nothing)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result eqref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
Expand All @@ -518,7 +568,22 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref-eq-corner-cases (param $x eqref)
;; side effects prevent optimization
;; Side effects generally prevent optimization. Here the call might change
;; the value of the global.
(drop
(ref.eq
(block (result eqref)
(call $nothing)
(global.get $g)
)
(block (result eqref)
(call $nothing)
(global.get $g)
)
)
)
;; But here the call cannot change the value of the local, so we can still
;; optimize.
(drop
(ref.eq
(block (result eqref)
Expand All @@ -531,18 +596,18 @@
)
)
)
;; allocation prevents optimization
;; Allocation prevents optimization.
(drop
(ref.eq
(struct.new_default $struct)
(struct.new_default $struct)
)
)
;; but irrelevant allocations do not prevent optimization
;; But irrelevant allocations do not prevent optimization.
(drop
(ref.eq
(block (result eqref)
;; an allocation that does not trouble us
;; An allocation that does not trouble us.
(drop
(struct.new_default $struct)
)
Expand All @@ -552,15 +617,15 @@
(drop
(struct.new_default $struct)
)
;; add a nop to make the two inputs to ref.eq not structurally equal,
;; Add a nop to make the two inputs to ref.eq not structurally equal,
;; but in a way that does not matter (since only the value falling
;; out does)
;; out does).
(nop)
(local.get $x)
)
)
)
;; a tee does not prevent optimization, as we can fold the tee and the get.
;; A tee does not prevent optimization, as we can fold the tee and the get.
(drop
(ref.eq
(local.tee $x
Expand All @@ -573,17 +638,19 @@

;; CHECK: (func $ref-eq-ref-cast (type $5) (param $x eqref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.eq
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.cast (ref null $struct)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref null $struct)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref-eq-ref-cast (param $x eqref)
;; it is almost valid to look through a cast, except that it might trap so
;; there is a side effect
;; We can see that both sides produce the same value if they finish
;; executing, but we have to preserve the trap.
(drop
(ref.eq
(local.get $x)
Expand Down Expand Up @@ -899,7 +966,12 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $ref-eq-null (param $x eqref)
Expand Down
Loading
Loading