Skip to content

Commit 7e782f6

Browse files
authored
Heap2Local: Handle repeated optimization that drops allocations (#8011)
1 parent c73011b commit 7e782f6

File tree

2 files changed

+92
-8
lines changed

2 files changed

+92
-8
lines changed

src/passes/Heap2Local.cpp

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,12 @@ struct Struct2Local : PostWalker<Struct2Local> {
857857
}
858858

859859
if (curr->desc) {
860+
auto descTrap = [&]() {
861+
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
862+
builder.makeDrop(curr->desc),
863+
builder.makeUnreachable()));
864+
};
865+
860866
// If we are doing a ref.cast_desc of the optimized allocation, but the
861867
// allocation does not have a descriptor, then we know the cast must fail.
862868
// We also know the cast must fail (except for nulls it might let through)
@@ -888,21 +894,16 @@ struct Struct2Local : PostWalker<Struct2Local> {
888894
} else {
889895
// Either the cast does not allow nulls or we know the value isn't
890896
// null anyway, so the cast certainly fails.
891-
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
892-
builder.makeDrop(curr->desc),
893-
builder.makeUnreachable()));
897+
descTrap();
894898
}
895-
} else {
896-
assert(allocIsCastRef);
899+
} else if (allocIsCastRef) {
897900
if (!Type::isSubType(allocation->type, curr->type)) {
898901
// The cast fails, so it must trap. We mark such failing casts as
899902
// fully consuming their inputs, so we cannot just emit the explicit
900903
// descriptor equality check below because it would appear to be able
901904
// to propagate the optimized allocation on to the parent (as a null
902905
// value, which might not validate).
903-
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
904-
builder.makeDrop(curr->desc),
905-
builder.makeUnreachable()));
906+
descTrap();
906907
} else {
907908
// The cast succeeds iff the optimized allocation's descriptor is the
908909
// same as the given descriptor and traps otherwise.
@@ -915,6 +916,15 @@ struct Struct2Local : PostWalker<Struct2Local> {
915916
builder.makeRefNull(allocation->type.getHeapType()),
916917
builder.makeUnreachable())));
917918
}
919+
} else {
920+
// The allocation is neither the ref nor the descriptor inputs to this
921+
// cast. This can happen if a previous operation led to the StructNew
922+
// being dropped, as a result if it being used in unreachable code (it
923+
// ends up happening because some of the initial analysis, like Parents,
924+
// is stale; we could also recompute Parents after each Struct2Local,
925+
// but it is simple enough to handle this with a trap).
926+
assert(curr->type == Type::unreachable);
927+
descTrap();
918928
}
919929
} else {
920930
// We know this RefCast receives our allocation, so we can see whether it

test/lit/passes/heap2local-desc.wast

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1144,3 +1144,77 @@
11441144
)
11451145
)
11461146

1147+
;; A chain of descriptors, where initial optimizations influence later ones.
1148+
(module
1149+
(rec
1150+
;; CHECK: (rec
1151+
;; CHECK-NEXT: (type $A (shared (descriptor $B (struct))))
1152+
(type $A (shared (descriptor $B (struct))))
1153+
;; CHECK: (type $B (sub (shared (describes $A (descriptor $C (struct))))))
1154+
(type $B (sub (shared (describes $A (descriptor $C (struct))))))
1155+
;; CHECK: (type $C (sub (shared (describes $B (struct)))))
1156+
(type $C (sub (shared (describes $B (struct)))))
1157+
)
1158+
1159+
;; CHECK: (type $3 (func (result (ref (shared any)))))
1160+
1161+
;; CHECK: (func $test (type $3) (result (ref (shared any)))
1162+
;; CHECK-NEXT: (local $temp (ref $C))
1163+
;; CHECK-NEXT: (local $1 (ref (shared none)))
1164+
;; CHECK-NEXT: (local $2 (ref (shared none)))
1165+
;; CHECK-NEXT: (drop
1166+
;; CHECK-NEXT: (block (result (ref null (shared none)))
1167+
;; CHECK-NEXT: (ref.null (shared none))
1168+
;; CHECK-NEXT: )
1169+
;; CHECK-NEXT: )
1170+
;; CHECK-NEXT: (block
1171+
;; CHECK-NEXT: (drop
1172+
;; CHECK-NEXT: (block
1173+
;; CHECK-NEXT: (drop
1174+
;; CHECK-NEXT: (block (result (ref null (shared none)))
1175+
;; CHECK-NEXT: (local.set $2
1176+
;; CHECK-NEXT: (ref.as_non_null
1177+
;; CHECK-NEXT: (ref.null (shared none))
1178+
;; CHECK-NEXT: )
1179+
;; CHECK-NEXT: )
1180+
;; CHECK-NEXT: (local.set $1
1181+
;; CHECK-NEXT: (local.get $2)
1182+
;; CHECK-NEXT: )
1183+
;; CHECK-NEXT: (ref.null (shared none))
1184+
;; CHECK-NEXT: )
1185+
;; CHECK-NEXT: )
1186+
;; CHECK-NEXT: (drop
1187+
;; CHECK-NEXT: (ref.null (shared none))
1188+
;; CHECK-NEXT: )
1189+
;; CHECK-NEXT: (unreachable)
1190+
;; CHECK-NEXT: )
1191+
;; CHECK-NEXT: )
1192+
;; CHECK-NEXT: (drop
1193+
;; CHECK-NEXT: (block (result (ref null (shared none)))
1194+
;; CHECK-NEXT: (ref.null (shared none))
1195+
;; CHECK-NEXT: )
1196+
;; CHECK-NEXT: )
1197+
;; CHECK-NEXT: (unreachable)
1198+
;; CHECK-NEXT: )
1199+
;; CHECK-NEXT: )
1200+
(func $test (result (ref (shared any)))
1201+
(local $temp (ref $C))
1202+
(local.set $temp
1203+
;; We optimize this first, making the |local.get| below unreachable, and
1204+
;; making that inner ref.cast_desc unreachable, which leads to the
1205+
;; |struct.new_default $B| being dropped, and in particular having a new
1206+
;; parent (the drop). We should not get confused and error internally.
1207+
(struct.new_default $C)
1208+
)
1209+
(ref.cast_desc (ref $B)
1210+
(ref.cast_desc (ref $B)
1211+
(struct.new_default $B
1212+
(ref.null (shared none))
1213+
)
1214+
(local.get $temp)
1215+
)
1216+
(struct.new_default $C)
1217+
)
1218+
)
1219+
)
1220+

0 commit comments

Comments
 (0)