Skip to content

Commit 94d77ef

Browse files
authored
OptimizeInstructions: Refinalize after a cast removal (#4611)
Casts can replace a type with a subtype, which normally has no downsides, but in a corner case of struct types it can lead to us needing to refinalize higher up too, see details in the comment. We have avoided any Refinalize calls in OptimizeInstructions, but the case handled here requires it sadly. I considered moving it to another pass, but this is a peephole optimization so there isn't really a better place.
1 parent be25a9c commit 94d77ef

File tree

2 files changed

+81
-3
lines changed

2 files changed

+81
-3
lines changed

src/passes/OptimizeInstructions.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ struct OptimizeInstructions
208208

209209
bool fastMath;
210210

211+
// In rare cases we make a change to a type, and will do a refinalize.
212+
bool refinalize = false;
213+
211214
void doWalkFunction(Function* func) {
212215
fastMath = getPassOptions().fastMath;
213216

@@ -221,6 +224,10 @@ struct OptimizeInstructions
221224
// Main walk.
222225
super::doWalkFunction(func);
223226

227+
if (refinalize) {
228+
ReFinalize().walkFunctionInModule(func, getModule());
229+
}
230+
224231
// Final optimizations.
225232
{
226233
FinalOptimizer optimizer(getPassOptions());
@@ -1622,6 +1629,25 @@ struct OptimizeInstructions
16221629
passOptions));
16231630
} else {
16241631
replaceCurrent(curr->ref);
1632+
1633+
// We must refinalize here, as we may be returning a more specific
1634+
// type, which can alter the parent. For example:
1635+
//
1636+
// (struct.get $parent 0
1637+
// (ref.cast_static $parent
1638+
// (local.get $child)
1639+
// )
1640+
// )
1641+
//
1642+
// Try to cast a $child to its parent, $parent. That always works,
1643+
// so the cast can be removed.
1644+
// Then once the cast is removed, the outer struct.get
1645+
// will have a reference with a different type, making it a
1646+
// (struct.get $child ..) instead of $parent.
1647+
// But if $parent and $child have different types on field 0 (the
1648+
// child may have a more refined one) then the struct.get must be
1649+
// refinalized so the IR node has the expected type.
1650+
refinalize = true;
16251651
}
16261652
return;
16271653
}

test/lit/passes/optimize-instructions-gc-iit.wast

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2-
;; RUN: wasm-opt %s --optimize-instructions --ignore-implicit-traps --enable-reference-types --enable-gc -S -o - \
2+
;; RUN: foreach %s %t wasm-opt --optimize-instructions --ignore-implicit-traps --enable-reference-types --enable-gc -S -o - \
33
;; RUN: | filecheck %s
4-
;; RUN: wasm-opt %s --optimize-instructions --ignore-implicit-traps --enable-reference-types --enable-gc --nominal -S -o - \
4+
;; RUN: foreach %s %t wasm-opt --optimize-instructions --ignore-implicit-traps --enable-reference-types --enable-gc --nominal -S -o - \
55
;; RUN: | filecheck %s --check-prefix NOMNL
66
;; Also test trapsNeverHappen (with nominal; no need for both type system modes).
7-
;; RUN: wasm-opt %s --optimize-instructions --traps-never-happen --enable-reference-types --enable-gc --nominal -S -o - \
7+
;; RUN: foreach %s %t wasm-opt --optimize-instructions --traps-never-happen --enable-reference-types --enable-gc --nominal -S -o - \
88
;; RUN: | filecheck %s --check-prefix NOMNL-TNH
99

1010
(module
@@ -367,3 +367,55 @@
367367
)
368368
)
369369
)
370+
371+
(module
372+
;; CHECK: (type $B (struct (field (ref null $A))))
373+
374+
;; CHECK: (type $A (struct ))
375+
;; NOMNL: (type $C (struct_subtype (field (ref null $D)) $B))
376+
377+
;; NOMNL: (type $D (struct_subtype $A))
378+
379+
;; NOMNL: (type $A (struct_subtype data))
380+
;; NOMNL-TNH: (type $C (struct_subtype (field (ref null $D)) $B))
381+
382+
;; NOMNL-TNH: (type $D (struct_subtype $A))
383+
384+
;; NOMNL-TNH: (type $A (struct_subtype data))
385+
(type $A (struct_subtype data))
386+
;; NOMNL: (type $B (struct_subtype (field (ref null $A)) $A))
387+
;; NOMNL-TNH: (type $B (struct_subtype (field (ref null $A)) $A))
388+
(type $B (struct_subtype (field (ref null $A)) $A))
389+
(type $C (struct_subtype (field (ref null $D)) $B))
390+
(type $D (struct_subtype $A))
391+
392+
;; CHECK: (func $test (param $C (ref $B)) (result anyref)
393+
;; CHECK-NEXT: (struct.get $B 0
394+
;; CHECK-NEXT: (local.get $C)
395+
;; CHECK-NEXT: )
396+
;; CHECK-NEXT: )
397+
;; NOMNL: (func $test (type $ref|$C|_=>_anyref) (param $C (ref $C)) (result anyref)
398+
;; NOMNL-NEXT: (struct.get $C 0
399+
;; NOMNL-NEXT: (local.get $C)
400+
;; NOMNL-NEXT: )
401+
;; NOMNL-NEXT: )
402+
;; NOMNL-TNH: (func $test (type $ref|$C|_=>_anyref) (param $C (ref $C)) (result anyref)
403+
;; NOMNL-TNH-NEXT: (struct.get $C 0
404+
;; NOMNL-TNH-NEXT: (local.get $C)
405+
;; NOMNL-TNH-NEXT: )
406+
;; NOMNL-TNH-NEXT: )
407+
(func $test (param $C (ref $C)) (result anyref)
408+
(struct.get $B 0
409+
(ref.cast_static $B ;; Try to cast a $C to its parent, $B. That always
410+
;; works, so the cast can be removed.
411+
;; Then once the cast is removed, the outer struct.get
412+
;; will have a reference with a different type,
413+
;; making it a (struct.get $C ..) instead of $B.
414+
;; But $B and $C have different types on field 0, and
415+
;; so the struct.get must be refinalized so the node
416+
;; has the expected type.
417+
(local.get $C)
418+
)
419+
)
420+
)
421+
)

0 commit comments

Comments
 (0)