Skip to content

Commit a8066e6

Browse files
authored
Heap2Local: Properly handle failing array casts (#6772)
Followup to #6727 which added support for failing casts in Struct2Local, but it turns out that it required Array2Struct changes as well. Specifically, when we turn an array into a struct then casts can look like they behave differently (what used to be an array input, becomes a struct), so like with RefTest that we already handled, check if the cast succeeds in the original form and handle that.
1 parent 84daeca commit a8066e6

File tree

2 files changed

+164
-5
lines changed

2 files changed

+164
-5
lines changed

src/passes/Heap2Local.cpp

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,11 @@ struct Array2Struct : PostWalker<Array2Struct> {
862862
// The original type of the allocation, before we turn it into a struct.
863863
Type originalType;
864864

865+
// The type of the struct we are changing to (nullable and non-nullable
866+
// variations).
867+
Type nullStruct;
868+
Type nonNullStruct;
869+
865870
Array2Struct(Expression* allocation,
866871
EscapeAnalyzer& analyzer,
867872
Function* func,
@@ -928,9 +933,15 @@ struct Array2Struct : PostWalker<Array2Struct> {
928933
// lowered away to locals anyhow.
929934
auto nullArray = Type(arrayType, Nullable);
930935
auto nonNullArray = Type(arrayType, NonNullable);
931-
auto nullStruct = Type(structType, Nullable);
932-
auto nonNullStruct = Type(structType, NonNullable);
936+
nullStruct = Type(structType, Nullable);
937+
nonNullStruct = Type(structType, NonNullable);
933938
for (auto* reached : analyzer.reached) {
939+
if (reached->is<RefCast>()) {
940+
// Casts must be handled later: We need to see the old type, and to
941+
// potentially replace the cast based on that, see below.
942+
continue;
943+
}
944+
934945
// We must check subtyping here because the allocation may be upcast as it
935946
// flows around. If we do see such upcasting then we are refining here and
936947
// must refinalize.
@@ -1032,15 +1043,14 @@ struct Array2Struct : PostWalker<Array2Struct> {
10321043
}
10331044

10341045
// Some additional operations need special handling
1046+
10351047
void visitRefTest(RefTest* curr) {
10361048
if (!analyzer.reached.count(curr)) {
10371049
return;
10381050
}
10391051

10401052
// When we ref.test an array allocation, we cannot simply turn the array
1041-
// into a struct, as then the test will behave different. (Note that this is
1042-
// not a problem for ref.*cast*, as the cast simply goes away when the value
1043-
// flows through, and we verify it will do so in the escape analysis.) To
1053+
// into a struct, as then the test will behave differently. To properly
10441054
// handle this, check if the test succeeds or not, and write out the outcome
10451055
// here (similar to Struct2Local::visitRefTest). Note that we test on
10461056
// |originalType| here and not |allocation->type|, as the allocation has
@@ -1050,6 +1060,30 @@ struct Array2Struct : PostWalker<Array2Struct> {
10501060
builder.makeConst(Literal(result))));
10511061
}
10521062

1063+
void visitRefCast(RefCast* curr) {
1064+
if (!analyzer.reached.count(curr)) {
1065+
return;
1066+
}
1067+
1068+
// As with RefTest, we need to check if the cast succeeds with the array
1069+
// type before we turn it into a struct type (as after that change, the
1070+
// outcome of the cast will look different).
1071+
if (!Type::isSubType(originalType, curr->type)) {
1072+
// The cast fails, ensure we trap with an unreachable.
1073+
replaceCurrent(builder.makeSequence(builder.makeDrop(curr),
1074+
builder.makeUnreachable()));
1075+
} else {
1076+
// The cast succeeds. Update the type. (It is ok to use the non-nullable
1077+
// type here unconditionally, since we know the allocation flows through
1078+
// here, and anyhow we will be removing the reference during Struct2Local,
1079+
// later.)
1080+
curr->type = nonNullStruct;
1081+
}
1082+
1083+
// Regardless of how we altered the type here, refinalize.
1084+
refinalize = true;
1085+
}
1086+
10531087
// Get the value in an expression we know must contain a constant index.
10541088
Index getIndex(Expression* curr) {
10551089
return curr->cast<Const>()->value.getUnsigned();

test/lit/passes/heap2local.wast

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4303,3 +4303,128 @@
43034303
)
43044304
)
43054305
)
4306+
4307+
;; Array casts to structs and arrays.
4308+
(module
4309+
;; CHECK: (type $1 (func))
4310+
4311+
;; CHECK: (type $0 (func (result (ref struct))))
4312+
(type $0 (func (result (ref struct))))
4313+
;; CHECK: (type $3 (func (result structref)))
4314+
4315+
;; CHECK: (type $4 (func (result (ref array))))
4316+
4317+
;; CHECK: (type $array (array i8))
4318+
(type $array (array i8))
4319+
4320+
;; CHECK: (func $array.cast.struct (type $0) (result (ref struct))
4321+
;; CHECK-NEXT: (local $eq (ref eq))
4322+
;; CHECK-NEXT: (drop
4323+
;; CHECK-NEXT: (block (result nullref)
4324+
;; CHECK-NEXT: (ref.null none)
4325+
;; CHECK-NEXT: )
4326+
;; CHECK-NEXT: )
4327+
;; CHECK-NEXT: (unreachable)
4328+
;; CHECK-NEXT: )
4329+
(func $array.cast.struct (result (ref struct))
4330+
(local $eq (ref eq))
4331+
;; This cast will fail: we cast an array to struct. That we go through
4332+
;; (ref eq) in the middle, which seems like it could cast to struct, should
4333+
;; not confuse us. And, as the cast fails, the reference does not escape.
4334+
;; We can optimize here and will emit an unreachable for the failing cast.
4335+
(ref.cast (ref struct)
4336+
(local.tee $eq
4337+
(array.new_fixed $array 0)
4338+
)
4339+
)
4340+
)
4341+
4342+
;; CHECK: (func $array.cast.struct.null (type $3) (result structref)
4343+
;; CHECK-NEXT: (local $eq (ref eq))
4344+
;; CHECK-NEXT: (drop
4345+
;; CHECK-NEXT: (block (result nullref)
4346+
;; CHECK-NEXT: (ref.null none)
4347+
;; CHECK-NEXT: )
4348+
;; CHECK-NEXT: )
4349+
;; CHECK-NEXT: (unreachable)
4350+
;; CHECK-NEXT: )
4351+
(func $array.cast.struct.null (result (ref null struct))
4352+
(local $eq (ref eq))
4353+
;; As above but the cast is to a nullable type, which changes nothing.
4354+
(ref.cast (ref null struct)
4355+
(local.tee $eq
4356+
(array.new_fixed $array 0)
4357+
)
4358+
)
4359+
)
4360+
4361+
;; CHECK: (func $array.cast.array (type $4) (result (ref array))
4362+
;; CHECK-NEXT: (local $eq (ref eq))
4363+
;; CHECK-NEXT: (ref.cast (ref array)
4364+
;; CHECK-NEXT: (local.tee $eq
4365+
;; CHECK-NEXT: (array.new_fixed $array 0)
4366+
;; CHECK-NEXT: )
4367+
;; CHECK-NEXT: )
4368+
;; CHECK-NEXT: )
4369+
(func $array.cast.array (result (ref array))
4370+
(local $eq (ref eq))
4371+
;; Now we cast to array, and the cast succeeds, so we escape, and do
4372+
;; nothing to optimize.
4373+
(ref.cast (ref array)
4374+
(local.tee $eq
4375+
(array.new_fixed $array 0)
4376+
)
4377+
)
4378+
)
4379+
4380+
;; CHECK: (func $array.cast.array.set (type $1)
4381+
;; CHECK-NEXT: (local $eq (ref eq))
4382+
;; CHECK-NEXT: (local $array (ref array))
4383+
;; CHECK-NEXT: (drop
4384+
;; CHECK-NEXT: (block (result nullref)
4385+
;; CHECK-NEXT: (ref.null none)
4386+
;; CHECK-NEXT: )
4387+
;; CHECK-NEXT: )
4388+
;; CHECK-NEXT: )
4389+
(func $array.cast.array.set
4390+
(local $eq (ref eq))
4391+
(local $array (ref array))
4392+
;; As above, but now we store the result in a local rather than return it
4393+
;; out, so it does not escape.
4394+
(local.set $array
4395+
(ref.cast (ref array)
4396+
(local.tee $eq
4397+
(array.new_fixed $array 0)
4398+
)
4399+
)
4400+
)
4401+
)
4402+
4403+
;; CHECK: (func $array.cast.struct.set (type $1)
4404+
;; CHECK-NEXT: (local $eq (ref eq))
4405+
;; CHECK-NEXT: (local $struct (ref struct))
4406+
;; CHECK-NEXT: (local.tee $struct
4407+
;; CHECK-NEXT: (block
4408+
;; CHECK-NEXT: (drop
4409+
;; CHECK-NEXT: (block (result nullref)
4410+
;; CHECK-NEXT: (ref.null none)
4411+
;; CHECK-NEXT: )
4412+
;; CHECK-NEXT: )
4413+
;; CHECK-NEXT: (unreachable)
4414+
;; CHECK-NEXT: )
4415+
;; CHECK-NEXT: )
4416+
;; CHECK-NEXT: )
4417+
(func $array.cast.struct.set
4418+
(local $eq (ref eq))
4419+
(local $struct (ref struct))
4420+
;; As above, but now the cast fails and is stored to a struct local. We do not
4421+
;; escape and we emit an unreachable for the cast.
4422+
(local.set $struct
4423+
(ref.cast (ref struct)
4424+
(local.tee $eq
4425+
(array.new_fixed $array 0)
4426+
)
4427+
)
4428+
)
4429+
)
4430+
)

0 commit comments

Comments
 (0)