Skip to content

Commit d6b4f01

Browse files
authored
[WasmGC] Heap2Local: Optimize RefEq (#6703)
If an allocation does not escape, then we can compute ref.eq for it: when compared to itself the result is 1, and when compared to anything else it is 0 (since it did not escape, anything else must be different).
1 parent 654ee6e commit d6b4f01

File tree

2 files changed

+352
-10
lines changed

2 files changed

+352
-10
lines changed

src/passes/Heap2Local.cpp

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,12 @@ struct EscapeAnalyzer {
349349
void visitLocalSet(LocalSet* curr) { escapes = false; }
350350

351351
// Reference operations. TODO add more
352+
void visitRefEq(RefEq* curr) {
353+
// The reference is compared for identity, but nothing more.
354+
escapes = false;
355+
fullyConsumes = true;
356+
}
357+
352358
void visitRefAs(RefAs* curr) {
353359
// TODO General OptimizeInstructions integration, that is, since we know
354360
// that our allocation is what flows into this RefAs, we can
@@ -507,14 +513,18 @@ struct EscapeAnalyzer {
507513
// efficient, but it would need to be more complex.
508514
struct Struct2Local : PostWalker<Struct2Local> {
509515
StructNew* allocation;
510-
const EscapeAnalyzer& analyzer;
516+
517+
// The analyzer is not |const| because we update |analyzer.reached| as we go
518+
// (see replaceCurrent, below).
519+
EscapeAnalyzer& analyzer;
520+
511521
Function* func;
512522
Module& wasm;
513523
Builder builder;
514524
const FieldList& fields;
515525

516526
Struct2Local(StructNew* allocation,
517-
const EscapeAnalyzer& analyzer,
527+
EscapeAnalyzer& analyzer,
518528
Function* func,
519529
Module& wasm)
520530
: allocation(allocation), analyzer(analyzer), func(func), wasm(wasm),
@@ -539,6 +549,15 @@ struct Struct2Local : PostWalker<Struct2Local> {
539549
// In rare cases we may need to refinalize, see below.
540550
bool refinalize = false;
541551

552+
Expression* replaceCurrent(Expression* expression) {
553+
PostWalker<Struct2Local>::replaceCurrent(expression);
554+
// Also update |reached|: we are replacing something that was reached, so
555+
// logically the replacement is also reached. This update is necessary if
556+
// the parent of an expression cares about whether a child was reached.
557+
analyzer.reached.insert(expression);
558+
return expression;
559+
}
560+
542561
// Rewrite the code in visit* methods. The general approach taken is to
543562
// replace the allocation with a null reference (which may require changing
544563
// types in some places, like making a block return value nullable), and to
@@ -688,6 +707,27 @@ struct Struct2Local : PostWalker<Struct2Local> {
688707
replaceCurrent(builder.makeBlock(contents));
689708
}
690709

710+
void visitRefEq(RefEq* curr) {
711+
if (!analyzer.reached.count(curr)) {
712+
return;
713+
}
714+
715+
if (curr->type == Type::unreachable) {
716+
// The result does not matter. Leave things as they are (and let DCE
717+
// handle it).
718+
return;
719+
}
720+
721+
// If our reference is compared to itself, the result is 1. If it is
722+
// compared to something else, the result must be 0, as our reference does
723+
// not escape to any other place.
724+
int32_t result = analyzer.reached.count(curr->left) > 0 &&
725+
analyzer.reached.count(curr->right) > 0;
726+
// For simplicity, simply drop the RefEq and put a constant result after.
727+
replaceCurrent(builder.makeSequence(builder.makeDrop(curr),
728+
builder.makeConst(Literal(result))));
729+
}
730+
691731
void visitRefAs(RefAs* curr) {
692732
if (!analyzer.reached.count(curr)) {
693733
return;

0 commit comments

Comments
 (0)