Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions 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 visitRefEq(RefEq* curr) {
// The reference is compared for identity, but nothing more.
escapes = false;
fullyConsumes = true;
}

void visitRefAs(RefAs* curr) {
// TODO General OptimizeInstructions integration, that is, since we know
// that our allocation is what flows into this RefAs, we can
Expand Down Expand Up @@ -507,14 +513,18 @@ struct EscapeAnalyzer {
// efficient, but it would need to be more complex.
struct Struct2Local : PostWalker<Struct2Local> {
StructNew* allocation;
const EscapeAnalyzer& analyzer;

// The analyzer is not |const| because we update |analyzer.reached| as we go
// (see replaceCurrent, below).
EscapeAnalyzer& analyzer;

Function* func;
Module& wasm;
Builder builder;
const FieldList& fields;

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

Expression* replaceCurrent(Expression* expression) {
PostWalker<Struct2Local>::replaceCurrent(expression);
// Also update |reached|: we are replacing something that was reached, so
// logically the replacement is also reached. This update is necessary if
// the parent of an expression cares about whether a child was reached.
analyzer.reached.insert(expression);
return expression;
}

// Rewrite the code in visit* methods. The general approach taken is to
// replace the allocation with a null reference (which may require changing
// types in some places, like making a block return value nullable), and to
Expand Down Expand Up @@ -688,6 +707,27 @@ struct Struct2Local : PostWalker<Struct2Local> {
replaceCurrent(builder.makeBlock(contents));
}

void visitRefEq(RefEq* curr) {
if (!analyzer.reached.count(curr)) {
return;
}
Comment on lines +711 to +713
Copy link
Member

Choose a reason for hiding this comment

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

Just to make sure I understand: we've done a data flow analysis and determined that this expression is not dead? Is it possible for reached to be nonzero and still have type Type::unreachable below?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, yeah, "reach" is used in two ways here. reached means that the allocation, the struct.new / array.new, reaches this location. So this first check is just making sure that we only look in the places we traced the path of the allocation.

Copy link
Member Author

Choose a reason for hiding this comment

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

Consider

(ref.eq
  (unreachable)
  (struct.new ..)
)

That allocation reaches the ref.eq, but also the ref.eq is unreachable code, separately.


if (curr->type == Type::unreachable) {
// The result does not matter. Leave things as they are (and let DCE
// handle it).
return;
}

// If our reference is compared to itself, the result is 1. If it is
// compared to something else, the result must be 0, as our reference does
// not escape to any other place.
int32_t result = analyzer.reached.count(curr->left) > 0 &&
analyzer.reached.count(curr->right) > 0;
Comment on lines +724 to +725
Copy link
Member

Choose a reason for hiding this comment

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

How is this checking whether the expression is compared to itself?

Copy link
Member Author

Choose a reason for hiding this comment

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

reached is the places the allocation reaches. So if the allocation is on both sides, we must be equal. If the allocation reached only one, then since it did not escape to a place we can't analyze, the other arm must be something totally different.

Copy link
Member

Choose a reason for hiding this comment

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

What if one (or both) of the arms may or may not be the analyzed allocation?

Copy link
Member Author

Choose a reason for hiding this comment

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

We are never in an ambiguous situation. If one arm can contain a mixture of the analyzed allocation and something else then we consider that the Mixes situation, which we ignore:

if (interaction == ParentChildInteraction::Escapes ||
interaction == ParentChildInteraction::Mixes) {
// If the parent may let us escape, or the parent mixes other values
// up with us, give up.
return true;
}

In theory we could add runtime checks "is this the allocation", but it seems unpromising.

// For simplicity, simply drop the RefEq and put a constant result after.
replaceCurrent(builder.makeSequence(builder.makeDrop(curr),
builder.makeConst(Literal(result))));
}

void visitRefAs(RefAs* curr) {
if (!analyzer.reached.count(curr)) {
return;
Expand Down
Loading