Skip to content

Commit d5e8918

Browse files
authored
[Custom Descriptors] Handle nullable ref.cast_desc in Heap2Local (#7744)
Heap2Local previously assumed that if an optimized allocation flowed into a ref.cast_desc as the descriptor operand, the cast must fail because the same allocation is known not to be the the descriptor of the cast value. This did not account for the case where the cast value is null and the cast allows nulls, though. Fix the output in that case to cast the value to null and avoid trapping where the original descriptor cast would not have trapped.
1 parent e3798b4 commit d5e8918

File tree

2 files changed

+325
-19
lines changed

2 files changed

+325
-19
lines changed

src/passes/Heap2Local.cpp

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@
154154
#include "ir/bits.h"
155155
#include "ir/branch-utils.h"
156156
#include "ir/eh-utils.h"
157-
#include "ir/find_all.h"
158157
#include "ir/local-graph.h"
159158
#include "ir/parents.h"
160159
#include "ir/properties.h"
@@ -402,7 +401,11 @@ struct EscapeAnalyzer {
402401
fullyConsumes = true;
403402
}
404403
} else {
405-
assert(curr->desc == child);
404+
// Either the child is the descriptor, in which case we consume it, or
405+
// we have already optimized this ref.cast_desc for an allocation that
406+
// flowed through as its `ref`. In the latter case the current child
407+
// must have originally been the descriptor, so we can still say it's
408+
// fully consumed, but we cannot assert that curr->desc == child.
406409
fullyConsumes = true;
407410
}
408411
}
@@ -850,19 +853,42 @@ struct Struct2Local : PostWalker<Struct2Local> {
850853
}
851854

852855
if (curr->desc) {
853-
// If we are doing a ref.cast_desc of the optimized allocation, but we
854-
// know it does not have a descriptor, then we know the cast must fail. We
855-
// also know the cast must fail if the optimized allocation flows in as
856-
// the descriptor, since it cannot possibly have been used in the
857-
// allocation of the cast value without having been considered to escape.
858-
if (!allocation->desc || analyzer.getInteraction(curr->desc) ==
859-
ParentChildInteraction::Flows) {
860-
// The allocation does not have a descriptor, so there is no way for the
861-
// cast to succeed.
862-
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
863-
builder.makeDrop(curr->desc),
864-
builder.makeUnreachable()));
856+
// If we are doing a ref.cast_desc of the optimized allocation, but the
857+
// allocation does not have a descriptor, then we know the cast must fail.
858+
// We also know the cast must fail (except for nulls it might let through)
859+
// if the optimized allocation flows in as the descriptor, since it cannot
860+
// possibly have been used in the allocation of the cast value without
861+
// having been considered to escape.
862+
bool allocIsCastDesc =
863+
analyzer.getInteraction(curr->desc) == ParentChildInteraction::Flows;
864+
if (!allocation->desc || allocIsCastDesc) {
865+
// It would seem convenient to use ChildLocalizer here, but we cannot.
866+
// ChildLocalizer would create a local.set for a desc operand with
867+
// side effects, but that local.set would not be reflected in the parent
868+
// map, so it would not be updated if the allocation flowing through
869+
// that desc operand were later optimized.
870+
if (allocIsCastDesc && curr->type.isNullable()) {
871+
// There might be a null value to let through. Reuse curr as a cast to
872+
// null. Use a scratch local to move the reference value past the desc
873+
// value.
874+
Index scratch = builder.addVar(func, curr->ref->type);
875+
replaceCurrent(
876+
builder.blockify(builder.makeLocalSet(scratch, curr->ref),
877+
builder.makeDrop(curr->desc),
878+
curr));
879+
curr->desc = nullptr;
880+
curr->type = curr->type.with(curr->type.getHeapType().getBottom());
881+
curr->ref = builder.makeLocalGet(scratch, curr->ref->type);
882+
} else {
883+
// Either the cast does not allow nulls or we know the value isn't
884+
// null anyway, so the cast certainly fails.
885+
replaceCurrent(builder.blockify(builder.makeDrop(curr->ref),
886+
builder.makeDrop(curr->desc),
887+
builder.makeUnreachable()));
888+
}
865889
} else {
890+
assert(analyzer.getInteraction(curr->ref) ==
891+
ParentChildInteraction::Flows);
866892
// The cast succeeds iff the optimized allocation's descriptor is the
867893
// same as the given descriptor and traps otherwise.
868894
auto type = allocation->desc->type;

0 commit comments

Comments
 (0)