Handle sharedness in Heap2Local's Array2Struct#8515
Conversation
Heap2Local uses an internal utility called Array2Struct to turn non-escaping array allocations into struct allocations so they can subsequently be optimized by the Heap2Local struct optimizations. It previously used a non-shared struct type, even for shared array types, but this could cause problems when the non-shared struct type became a non-shared null in places that required a shared type to validate. This could specifically occur when the allocation flowed into a StructCmpxchg `expected` field that needed to be a subtype of shared eqref. As a drive-by to make the regression test parse correctly, fix child-typer for StructCmpxchg to correctly handle sharedness in the expected field as well.
aheejin
left a comment
There was a problem hiding this comment.
I'm not familiar with this pass (#3866 said I reviewed it, which I don't have any memory of... 🫠)
This could specifically occur when the allocation flowed into a StructCmpxchg expected field that needed to be a subtype of shared eqref.
Why is the expected operand in StructCmpxchg special?
As a drive-by to make the regression test parse correctly, fix child-typer for StructCmpxchg to correctly handle sharedness in the expected field as well.
How is this fix related to the Heap2Local fix? What happens if this child-typer is not fixed here?
| auto expectedType = type; | ||
| if (expectedType.isRef()) { | ||
| expectedType = | ||
| Type(HeapTypes::eq.getBasic(type.getHeapType().getShared()), Nullable); |
There was a problem hiding this comment.
This is preexisting, but why does this have to be Nullable?
There was a problem hiding this comment.
When the type of the accessed struct field is a reference, the expected operand is allowed to be any subtype of (ref null eq) (i.e. eqref), or (ref null (shared eq)) if the field type is a shared reference. In either case, the expected operand can be nullable.
| ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 | ||
| ;; CHECK-NEXT: (local.get $struct) | ||
| ;; CHECK-NEXT: (block (result (ref null (shared none))) | ||
| ;; CHECK-NEXT: (ref.null (shared none)) |
There was a problem hiding this comment.
I get that Heap2Local can extract fields like i32s out of a struct if that struct doesn't escape, but that would just extract the fields into locals and wouldn't create a random i32 values (like 0) to set them, right? Why can we replace (array.new_fixed $array 0) with (ref.null (shared none)) here? Where did this value none come from?
There was a problem hiding this comment.
The way Heap2Local works in general is that it places struct fields in locals and also replaces the struct itself with a null value. This generally makes it easier to update the IR than it would be if we removed the value entirely. For example, in this test it would not be valid to leave the expected operand with type none, so we would have had to fix up the struct.atomic.rmw.cmpxchg to maintain validity even though it is unreachable. But making the expected operand null is still valid, so we can less work to fix up the IR.
The original fuzzer test case depending on running --gufa before --heap2local to set up the IR that triggers the bug. But for checked-in regression test, we want to be able to parse the Wasm text directly into the IR that triggers the bug. Without the fix to child-typer, IRBuilder sees the unreachable (drop
(local.get $shared-struct)
)
(drop
(struct.new_default $shared-struct)
)
(struct.atomic.rmw.cmpxchg <unprintable type> 0
(unreachable)
(unreachable)
(unreachable)
)But this IR does not trigger the bug. |
Heap2Local uses an internal utility called Array2Struct to turn non-escaping array allocations into struct allocations so they can subsequently be optimized by the Heap2Local struct optimizations. It
previously used a non-shared struct type, even for shared array types, but this could cause problems when the non-shared struct type became a non-shared null in places that required a shared type to
validate. This could specifically occur when the allocation flowed into a StructCmpxchg
expectedfield that needed to be a subtype of shared eqref.As a drive-by to make the regression test parse correctly, fix child-typer for StructCmpxchg to correctly handle sharedness in the expected field as well.