Skip to content

[EH] Support reading/writing of delegate #3561

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

Merged
merged 5 commits into from
Feb 12, 2021
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
20 changes: 13 additions & 7 deletions src/ir/ExpressionAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "ir/iteration.h"
#include "ir/load-utils.h"
#include "ir/utils.h"
#include "shared-constants.h"
#include "support/hash.h"
#include "support/small_vector.h"
#include "wasm-traversal.h"
Expand Down Expand Up @@ -261,6 +262,9 @@ size_t ExpressionAnalyzer::hash(Expression* curr) {

Hasher(Expression* curr) {
stack.push_back(curr);
// DELEGATE_CALLER_TARGET is a fake target used to denote delegating to
// the caller. Add it here to prevent the unknown name error.
noteScopeName(DELEGATE_CALLER_TARGET);

while (stack.size() > 0) {
curr = stack.back();
Expand Down Expand Up @@ -327,13 +331,15 @@ size_t ExpressionAnalyzer::hash(Expression* curr) {
}
}
void visitScopeName(Name curr) {
// Names are relative, we give the same hash for
// (block $x (br $x))
// (block $y (br $y))
static_assert(sizeof(Index) == sizeof(int32_t),
"wasm64 will need changes here");
assert(internalNames.find(curr) != internalNames.end());
rehash(digest, internalNames[curr]);
if (curr.is()) { // try's delegate target can be null
// Names are relative, we give the same hash for
// (block $x (br $x))
// (block $y (br $y))
static_assert(sizeof(Index) == sizeof(int32_t),
"wasm64 will need changes here");
assert(internalNames.find(curr) != internalNames.end());
rehash(digest, internalNames[curr]);
}
}
void visitNonScopeName(Name curr) { rehash(digest, uint64_t(curr.str)); }
void visitType(Type curr) { rehash(digest, curr.getID()); }
Expand Down
48 changes: 44 additions & 4 deletions src/ir/branch-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ inline bool isBranchReachable(Expression* expr) {
return true;
}

// Perform a generic operation on uses of scope names (branch targets) in an
// expression. The provided function receives a Name& which it can modify if it
// needs to.
// Perform a generic operation on uses of scope names (branch + delegate
// targets) in an expression. The provided function receives a Name& which it
// can modify if it needs to.
template<typename T> void operateOnScopeNameUses(Expression* expr, T func) {
#define DELEGATE_ID expr->_id

Expand Down Expand Up @@ -83,7 +83,7 @@ void operateOnScopeNameUsesAndSentTypes(Expression* expr, T func) {
} else if (auto* br = expr->dynCast<BrOn>()) {
func(name, br->getCastType());
} else {
WASM_UNREACHABLE("bad br type");
assert(expr->is<Try>()); // delegate
}
});
}
Expand Down Expand Up @@ -135,6 +135,46 @@ inline bool replacePossibleTarget(Expression* branch, Name from, Name to) {
return worked;
}

// Replace all delegate targets within the given AST.
inline void replaceDelegateTargets(Expression* ast, Name from, Name to) {
struct Replacer
: public PostWalker<Replacer, UnifiedExpressionVisitor<Replacer>> {
Name from, to;
Replacer(Name from, Name to) : from(from), to(to) {}
void visitExpression(Expression* curr) {
if (curr->is<Try>()) {
operateOnScopeNameUses(curr, [&](Name& name) {
if (name == from) {
name = to;
}
});
}
}
};
Replacer replacer(from, to);
replacer.walk(ast);
}

// Replace all branch targets within the given AST.
inline void replaceBranchTargets(Expression* ast, Name from, Name to) {
struct Replacer
: public PostWalker<Replacer, UnifiedExpressionVisitor<Replacer>> {
Name from, to;
Replacer(Name from, Name to) : from(from), to(to) {}
void visitExpression(Expression* curr) {
if (Properties::isBranch(curr)) {
operateOnScopeNameUses(curr, [&](Name& name) {
if (name == from) {
name = to;
}
});
}
}
};
Replacer replacer(from, to);
replacer.walk(ast);
}

// Returns the set of targets to which we branch that are
// outside of an expression.
inline NameSet getExitingBranches(Expression* ast) {
Expand Down
10 changes: 8 additions & 2 deletions src/ir/properties.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ inline bool isControlFlowStructure(Expression* curr) {
curr->is<Try>();
}

// Check if an expression is a control flow construct with a name,
// which implies it may have breaks to it.
// Check if an expression is a control flow construct with a name, which implies
// it may have breaks or delegates to it.
inline bool isNamedControlFlow(Expression* curr) {
if (auto* block = curr->dynCast<Block>()) {
return block->name.is();
} else if (auto* loop = curr->dynCast<Loop>()) {
return loop->name.is();
} else if (auto* try_ = curr->dynCast<Try>()) {
return try_->name.is();
}
return false;
}
Expand Down Expand Up @@ -104,6 +106,10 @@ inline bool isConstantExpression(const Expression* curr) {
return false;
}

inline bool isBranch(const Expression* curr) {
return curr->is<Break>() || curr->is<Switch>() || curr->is<BrOn>();
}

inline Literal getLiteral(const Expression* curr) {
if (auto* c = curr->dynCast<Const>()) {
return c->value;
Expand Down
8 changes: 8 additions & 0 deletions src/passes/Poppify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ struct Poppifier : BinaryenIRWriter<Poppifier> {
void emitIfElse(If* curr);
void emitCatch(Try* curr, Index i);
void emitCatchAll(Try* curr);
void emitDelegate(Try* curr);
void emitScopeEnd(Expression* curr);
void emitFunctionEnd();
void emitUnreachable();
Expand Down Expand Up @@ -272,6 +273,13 @@ void Poppifier::emitCatchAll(Try* curr) {
scopeStack.emplace_back(Scope::Catch);
}

void Poppifier::emitDelegate(Try* curr) {
auto& scope = scopeStack.back();
assert(scope.kind == Scope::Try);
patchScope(curr->body);
scopeStack.back().instrs.push_back(curr);
}

void Poppifier::emitScopeEnd(Expression* curr) {
switch (scopeStack.back().kind) {
case Scope::Block:
Expand Down
77 changes: 72 additions & 5 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1764,6 +1764,10 @@ struct PrintExpressionContents
void visitRefEq(RefEq* curr) { printMedium(o, "ref.eq"); }
void visitTry(Try* curr) {
printMedium(o, "try");
if (curr->name.is()) {
o << ' ';
printName(curr->name, o);
}
if (curr->type.isConcrete()) {
o << ' ' << ResultType(curr->type);
}
Expand Down Expand Up @@ -1955,6 +1959,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
Function::DebugLocation lastPrintedLocation;
bool debugInfo;

// Used to print delegate's depth argument when it throws to the caller
int controlFlowDepth = 0;

PrintSExpression(std::ostream& o) : o(o) {
setMinify(false);
if (!full) {
Expand Down Expand Up @@ -2100,6 +2107,9 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
break; // that's all we can recurse, start to unwind
}
}

int startControlFlowDepth = controlFlowDepth;
controlFlowDepth += stack.size();
auto* top = stack.back();
while (stack.size() > 0) {
curr = stack.back();
Expand Down Expand Up @@ -2129,8 +2139,10 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
o << ' ' << curr->name;
}
}
controlFlowDepth = startControlFlowDepth;
}
void visitIf(If* curr) {
controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
Expand All @@ -2147,8 +2159,10 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
if (full) {
o << " ;; end if";
}
controlFlowDepth--;
}
void visitLoop(Loop* curr) {
controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
Expand All @@ -2160,6 +2174,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
o << ' ' << curr->name;
}
}
controlFlowDepth--;
}
void visitBreak(Break* curr) {
o << '(';
Expand Down Expand Up @@ -2490,13 +2505,28 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
// (do
// ...
// )
// (catch
// ...
// (catch $e
// ...
// )
// ...
// (catch_all
// ...
// )
// )
// The parenthesis wrapping 'catch' is just a syntax and does not affect
// nested depths of instructions within.
// The parenthesis wrapping do/catch/catch_all is just a syntax and does not
// affect nested depths of instructions within.
//
// try-delegate is written in the forded format as
// (try
// (do
// ...
// )
// (delegate $label)
// )
// When the 'delegate' delegates to the caller, we write the argument as an
// immediate.
void visitTry(Try* curr) {
controlFlowDepth++;
o << '(';
printExpressionContents(curr);
incIndent();
Expand All @@ -2521,12 +2551,26 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
if (curr->hasCatchAll()) {
doIndent(o, indent);
printDebugDelimiterLocation(curr, curr->catchEvents.size());
o << "(catch_all";
o << '(';
printMedium(o, "catch_all");
incIndent();
maybePrintImplicitBlock(curr->catchBodies.back(), true);
decIndent();
o << "\n";
}
controlFlowDepth--;

if (curr->isDelegate()) {
doIndent(o, indent);
o << '(';
printMedium(o, "delegate ");
if (curr->delegateTarget == DELEGATE_CALLER_TARGET) {
o << controlFlowDepth;
} else {
printName(curr->delegateTarget, o);
}
o << ")\n";
}
decIndent();
if (full) {
o << " ;; end try";
Expand Down Expand Up @@ -2913,6 +2957,7 @@ struct PrintSExpression : public OverriddenVisitor<PrintSExpression> {
} else {
printFullLine(curr->body);
}
assert(controlFlowDepth == 0);
} else {
// Print the stack IR.
printStackIR(curr->stackIR.get(), o, curr);
Expand Down Expand Up @@ -3324,6 +3369,11 @@ printStackInst(StackInst* inst, std::ostream& o, Function* func) {
printMedium(o, "catch_all");
break;
}
case StackInst::Delegate: {
printMedium(o, "delegate ");
printName(inst->origin->cast<Try>()->delegateTarget, o);
break;
}
default:
WASM_UNREACHABLE("unexpeted op");
}
Expand All @@ -3339,6 +3389,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
}
};

int controlFlowDepth = 0;
// Stack to track indices of catches within a try
SmallVector<Index, 4> catchIndexStack;
for (Index i = 0; i < (*ir).size(); i++) {
Expand All @@ -3364,6 +3415,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
case StackInst::BlockBegin:
case StackInst::IfBegin:
case StackInst::LoopBegin: {
controlFlowDepth++;
doIndent();
PrintExpressionContents(func, o).visit(inst->origin);
indent++;
Expand All @@ -3375,6 +3427,7 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd: {
controlFlowDepth--;
indent--;
doIndent();
printMedium(o, "end");
Expand Down Expand Up @@ -3403,11 +3456,25 @@ printStackIR(StackIR* ir, std::ostream& o, Function* func) {
indent++;
break;
}
case StackInst::Delegate: {
controlFlowDepth--;
indent--;
doIndent();
printMedium(o, "delegate ");
Try* curr = inst->origin->cast<Try>();
if (curr->delegateTarget == DELEGATE_CALLER_TARGET) {
o << controlFlowDepth;
} else {
printName(curr->delegateTarget, o);
}
break;
}
default:
WASM_UNREACHABLE("unexpeted op");
}
std::cout << '\n';
}
assert(controlFlowDepth == 0);
return o;
}

Expand Down
2 changes: 2 additions & 0 deletions src/passes/RemoveUnusedNames.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ struct RemoveUnusedNames
}
}

void visitTry(Try* curr) { handleBreakTarget(curr->name); }

void visitFunction(Function* curr) { assert(branchesSeen.empty()); }
};

Expand Down
4 changes: 3 additions & 1 deletion src/passes/StackIR.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class StackIROptimizer {
case StackInst::LoopEnd:
case StackInst::Catch:
case StackInst::CatchAll:
case StackInst::Delegate:
case StackInst::TryEnd: {
return true;
}
Expand All @@ -284,7 +285,8 @@ class StackIROptimizer {
case StackInst::BlockEnd:
case StackInst::IfEnd:
case StackInst::LoopEnd:
case StackInst::TryEnd: {
case StackInst::TryEnd:
case StackInst::Delegate: {
return true;
}
default: { return false; }
Expand Down
1 change: 1 addition & 0 deletions src/shared-constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ extern Name CASE;
extern Name BR;
extern Name FUNCREF;
extern Name FAKE_RETURN;
extern Name DELEGATE_CALLER_TARGET;
extern Name MUT;
extern Name SPECTEST;
extern Name PRINT;
Expand Down
Loading