Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WasmGC] Heap2Local: Optimize RefIs and RefTest #6705

Merged
merged 23 commits into from
Jul 11, 2024
63 changes: 62 additions & 1 deletion src/passes/Heap2Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ struct EscapeAnalyzer {
void visitLocalSet(LocalSet* curr) { escapes = false; }

// Reference operations. TODO add more
void visitRefIsNull(RefIsNull* curr) {
// The reference is compared to null, but nothing more.
escapes = false;
fullyConsumes = true;
}

void visitRefEq(RefEq* curr) {
// The reference is compared for identity, but nothing more.
escapes = false;
Expand All @@ -367,6 +373,11 @@ struct EscapeAnalyzer {
}
}

void visitRefTest(RefTest* curr) {
escapes = false;
fullyConsumes = true;
}

void visitRefCast(RefCast* curr) {
// As it is our allocation that flows through here, we need to
// check that the cast will not trap, so that we can continue
Expand Down Expand Up @@ -707,6 +718,17 @@ struct Struct2Local : PostWalker<Struct2Local> {
replaceCurrent(builder.makeBlock(contents));
}

void visitRefIsNull(RefIsNull* curr) {
if (!analyzer.reached.count(curr)) {
return;
}

// The result must be 0, since the allocation is not null. Drop the RefIs
// and append that.
replaceCurrent(builder.makeSequence(
builder.makeDrop(curr), builder.makeConst(Literal(int32_t(0)))));
}

void visitRefEq(RefEq* curr) {
if (!analyzer.reached.count(curr)) {
return;
Expand Down Expand Up @@ -739,6 +761,23 @@ struct Struct2Local : PostWalker<Struct2Local> {
replaceCurrent(curr->value);
}

void visitRefTest(RefTest* curr) {
if (!analyzer.reached.count(curr)) {
return;
}

// This test operates on the allocation, which means we can compute whether
// it will succeed statically. We do not even need
// GCTypeUtils::evaluateCastCheck because we know the allocation's type
// precisely (it cannot be a strict subtype of the type - it is the type).
int32_t result = Type::isSubType(allocation->type, curr->castType);
// Remove the RefTest and leave only its reference child. If we kept it,
// we'd need to refinalize (as the input to the test changes, since the
// reference becomes a null, which has a different type).
Comment on lines +774 to +776
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also keep only the operands of other operations we currently drop, which would add the very least make the code more consistent and reduce the number of IR nodes in the output.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, looks like that would affect just RefEq. I can do that separately from this PR.

replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeConst(Literal(result))));
}

void visitRefCast(RefCast* curr) {
if (!analyzer.reached.count(curr)) {
return;
Expand Down Expand Up @@ -820,12 +859,15 @@ struct Array2Struct : PostWalker<Array2Struct> {
EscapeAnalyzer& analyzer;
Function* func;
Builder builder;
// The original type of the allocation, before we turn it into a struct.
Type originalType;

Array2Struct(Expression* allocation,
EscapeAnalyzer& analyzer,
Function* func,
Module& wasm)
: allocation(allocation), analyzer(analyzer), func(func), builder(wasm) {
: allocation(allocation), analyzer(analyzer), func(func), builder(wasm),
originalType(allocation->type) {

// Build the struct type we need: as many fields as the size of the array,
// all of the same type as the array's element.
Expand Down Expand Up @@ -989,6 +1031,25 @@ struct Array2Struct : PostWalker<Array2Struct> {
noteCurrentIsReached();
}

// Some additional operations need special handling
void visitRefTest(RefTest* curr) {
if (!analyzer.reached.count(curr)) {
return;
}

// When we ref.test an array allocation, we cannot simply turn the array
// into a struct, as then the test will behave different. (Note that this is
// not a problem for ref.*cast*, as the cast simply goes away when the value
// flows through, and we verify it will do so in the escape analysis.) To
// handle this, check if the test succeeds or not, and write out the outcome
// here (similar to Struct2Local::visitRefTest). Note that we test on
// |originalType| here and not |allocation->type|, as the allocation has
// been turned into a struct.
int32_t result = Type::isSubType(originalType, curr->castType);
replaceCurrent(builder.makeSequence(builder.makeDrop(curr),
builder.makeConst(Literal(result))));
}

// Get the value in an expression we know must contain a constant index.
Index getIndex(Expression* curr) {
return curr->cast<Const>()->value.getUnsigned();
Expand Down
Loading
Loading