summaryrefslogtreecommitdiff
path: root/src/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'src/wasm')
-rw-r--r--src/wasm/wasm-binary.cpp173
-rw-r--r--src/wasm/wasm-s-parser.cpp56
-rw-r--r--src/wasm/wasm-stack.cpp16
-rw-r--r--src/wasm/wasm-validator.cpp68
-rw-r--r--src/wasm/wasm.cpp1
5 files changed, 245 insertions, 69 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp
index f3ec376d1..878293caa 100644
--- a/src/wasm/wasm-binary.cpp
+++ b/src/wasm/wasm-binary.cpp
@@ -1918,7 +1918,9 @@ void WasmBinaryBuilder::readFunctions() {
debugLocation.clear();
willBeIgnored = false;
// process body
- assert(breakTargetNames.size() == 0);
+ assert(breakStack.empty());
+ assert(breakTargetNames.empty());
+ assert(delegateTargetNames.empty());
assert(breakStack.empty());
assert(expressionStack.empty());
assert(controlFlowStack.empty());
@@ -1926,8 +1928,9 @@ void WasmBinaryBuilder::readFunctions() {
assert(depth == 0);
func->body = getBlockOrSingleton(func->sig.results);
assert(depth == 0);
- assert(breakStack.size() == 0);
- assert(breakTargetNames.size() == 0);
+ assert(breakStack.empty());
+ assert(breakTargetNames.empty());
+ assert(delegateTargetNames.empty());
if (!expressionStack.empty()) {
throwError("stack not empty on function exit");
}
@@ -2210,7 +2213,8 @@ void WasmBinaryBuilder::processExpressions() {
}
auto peek = input[pos];
if (peek == BinaryConsts::End || peek == BinaryConsts::Else ||
- peek == BinaryConsts::Catch || peek == BinaryConsts::CatchAll) {
+ peek == BinaryConsts::Catch || peek == BinaryConsts::CatchAll ||
+ peek == BinaryConsts::Delegate) {
BYN_TRACE("== processExpressions finished with unreachable"
<< std::endl);
lastSeparator = BinaryConsts::ASTNodes(peek);
@@ -2987,6 +2991,14 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
}
break;
}
+ case BinaryConsts::Delegate: {
+ curr = nullptr;
+ if (DWARF && currFunction) {
+ assert(!controlFlowStack.empty());
+ controlFlowStack.pop_back();
+ }
+ break;
+ }
case BinaryConsts::RefNull:
visitRefNull((curr = allocator.alloc<RefNull>())->cast<RefNull>());
break;
@@ -3376,7 +3388,8 @@ Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type) {
block->name = label;
block->finalize(type);
// maybe we don't need a block here?
- if (breakTargetNames.find(block->name) == breakTargetNames.end()) {
+ if (breakTargetNames.find(block->name) == breakTargetNames.end() &&
+ delegateTargetNames.find(block->name) == delegateTargetNames.end()) {
block->name = Name();
if (block->list.size() == 1) {
return block->list[0];
@@ -3452,6 +3465,28 @@ WasmBinaryBuilder::getBreakTarget(int32_t offset) {
return ret;
}
+Name WasmBinaryBuilder::getDelegateTargetName(int32_t offset) {
+ BYN_TRACE("getDelegateTarget " << offset << std::endl);
+ // We always start parsing a function by creating a block label and pushing it
+ // in breakStack in getBlockOrSingleton, so if a 'delegate''s target is that
+ // block, it does not mean it targets that block; it throws to the caller.
+ if (breakStack.size() - 1 == size_t(offset)) {
+ return DELEGATE_CALLER_TARGET;
+ }
+ size_t index = breakStack.size() - 1 - offset;
+ if (index > breakStack.size()) {
+ throwError("bad delegate index (high)");
+ }
+ BYN_TRACE("delegate target " << breakStack[index].name << std::endl);
+ auto& ret = breakStack[index];
+ // if the delegate is in literally unreachable code, then we will not emit it
+ // anyhow, so do not note that the target has delegate to it
+ if (!willBeIgnored) {
+ delegateTargetNames.insert(ret.name);
+ }
+ return ret.name;
+}
+
void WasmBinaryBuilder::visitBreak(Break* curr, uint8_t code) {
BYN_TRACE("zz node: Break, code " << int32_t(code) << std::endl);
BreakTarget target = getBreakTarget(getU32LEB());
@@ -5746,58 +5781,13 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
curr->type = getType();
curr->body = getBlockOrSingleton(curr->type);
if (lastSeparator != BinaryConsts::Catch &&
- lastSeparator != BinaryConsts::CatchAll) {
+ lastSeparator != BinaryConsts::CatchAll &&
+ lastSeparator != BinaryConsts::Delegate) {
throwError("No catch instruction within a try scope");
}
- // For simplicity, we create an inner block within the catch body too, but the
- // one within the 'catch' *must* be omitted when we write out the binary back
- // later, because the 'catch' instruction pushes a value onto the stack and
- // the inner block does not support block input parameters without multivalue
- // support.
- // try
- // ...
- // catch ;; Pushes a value onto the stack
- // block ;; Inner block. Should be deleted when writing binary!
- // use the pushed value
- // end
- // end
- //
- // But when input binary code is like
- // try
- // ...
- // catch
- // br 0
- // end
- //
- // 'br 0' accidentally happens to target the inner block, creating code like
- // this in Binaryen IR, making the inner block not deletable, resulting in a
- // validation error:
- // (try
- // ...
- // (catch
- // (block $label0 ;; Cannot be deleted, because there's a branch to this
- // ...
- // (br $label0)
- // )
- // )
- // )
- //
- // When this happens, we fix this by creating a block that wraps the whole
- // try-catch, and making the branches target that block instead, like this:
- // (block $label ;; New enclosing block, new target for the branch
- // (try
- // ...
- // (catch
- // (block ;; Now this can be deleted when writing binary
- // ...
- // (br $label0)
- // )
- // )
- // )
- // )
-
Builder builder(wasm);
+ // A nameless label shared by all catch body blocks
Name catchLabel = getNextLabel();
breakStack.push_back({catchLabel, curr->type});
@@ -5839,8 +5829,84 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
readCatchBody(Type::none);
}
}
+ breakStack.pop_back();
+
+ if (lastSeparator == BinaryConsts::Delegate) {
+ curr->delegateTarget = getDelegateTargetName(getU32LEB());
+ }
+
+ // For simplicity, we make try's labels only can be targeted by delegates, and
+ // delegates can only target try's labels. (If they target blocks or loops, it
+ // is a validation failure.) Because we create an inner block within each try
+ // and catch body, if any delegate targets those inner blocks, we should make
+ // them target the try's label instead.
+ curr->name = getNextLabel();
+ if (auto* block = curr->body->dynCast<Block>()) {
+ if (block->name.is()) {
+ if (delegateTargetNames.find(block->name) != delegateTargetNames.end()) {
+ BranchUtils::replaceDelegateTargets(block, block->name, curr->name);
+ delegateTargetNames.erase(block->name);
+ }
+ // maybe we don't need a block here?
+ if (block->list.size() == 1) {
+ curr->body = block->list[0];
+ }
+ }
+ }
+ if (delegateTargetNames.find(catchLabel) != delegateTargetNames.end()) {
+ for (auto* catchBody : curr->catchBodies) {
+ BranchUtils::replaceDelegateTargets(catchBody, catchLabel, curr->name);
+ }
+ delegateTargetNames.erase(catchLabel);
+ }
curr->finalize(curr->type);
+ // For simplicity, we create an inner block within the catch body too, but the
+ // one within the 'catch' *must* be omitted when we write out the binary back
+ // later, because the 'catch' instruction pushes a value onto the stack and
+ // the inner block does not support block input parameters without multivalue
+ // support.
+ // try
+ // ...
+ // catch $e ;; Pushes value(s) onto the stack
+ // block ;; Inner block. Should be deleted when writing binary!
+ // use the pushed value
+ // end
+ // end
+ //
+ // But when input binary code is like
+ // try
+ // ...
+ // catch $e
+ // br 0
+ // end
+ //
+ // 'br 0' accidentally happens to target the inner block, creating code like
+ // this in Binaryen IR, making the inner block not deletable, resulting in a
+ // validation error:
+ // (try
+ // ...
+ // (catch $e
+ // (block $label0 ;; Cannot be deleted, because there's a branch to this
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ //
+ // When this happens, we fix this by creating a block that wraps the whole
+ // try-catch, and making the branches target that block instead, like this:
+ // (block $label ;; New enclosing block, new target for the branch
+ // (try
+ // ...
+ // (catch $e
+ // (block ;; Now this can be deleted when writing binary
+ // ...
+ // (br $label0)
+ // )
+ // )
+ // )
+ // )
if (breakTargetNames.find(catchLabel) == breakTargetNames.end()) {
out = curr;
} else {
@@ -5848,7 +5914,6 @@ void WasmBinaryBuilder::visitTryOrTryInBlock(Expression*& out) {
auto* block = builder.makeBlock(catchLabel, curr);
out = block;
}
- breakStack.pop_back();
breakTargetNames.erase(catchLabel);
}
diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp
index bcce3993e..d2b24fbed 100644
--- a/src/wasm/wasm-s-parser.cpp
+++ b/src/wasm/wasm-s-parser.cpp
@@ -1859,7 +1859,7 @@ Expression* SExpressionWasmBuilder::makeCallIndirect(Element& s,
return ret;
}
-Name SExpressionWasmBuilder::getLabel(Element& s) {
+Name SExpressionWasmBuilder::getLabel(Element& s, LabelType labelType) {
if (s.dollared()) {
return nameMapper.sourceToUnique(s.str());
} else {
@@ -1876,10 +1876,14 @@ Name SExpressionWasmBuilder::getLabel(Element& s) {
throw ParseException("invalid label", s.line, s.col);
}
if (offset == nameMapper.labelStack.size()) {
- // a break to the function's scope. this means we need an automatic block,
- // with a name
- brokeToAutoBlock = true;
- return FAKE_RETURN;
+ if (labelType == LabelType::Break) {
+ // a break to the function's scope. this means we need an automatic
+ // block, with a name
+ brokeToAutoBlock = true;
+ return FAKE_RETURN;
+ }
+ // This is a delegate that delegates to the caller
+ return DELEGATE_CALLER_TARGET;
}
return nameMapper.labelStack[nameMapper.labelStack.size() - 1 - offset];
}
@@ -1975,12 +1979,13 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
return ret;
}
-// try-catch-end is written in the folded wast format as
+// try can be either in the form of try-catch or try-delegate.
+// try-catch is written in the folded wast format as
// (try
// (do
// ...
// )
-// (catch
+// (catch $e
// ...
// )
// ...
@@ -1991,6 +1996,14 @@ Expression* SExpressionWasmBuilder::makeRefEq(Element& s) {
// Any number of catch blocks can exist, including none. Zero or one catch_all
// block can exist, and if it does, it should be at the end. There should be at
// least one catch or catch_all body per try.
+//
+// try-delegate is written in the folded format as
+// (try
+// (do
+// ...
+// )
+// (delegate $label)
+// )
Expression* SExpressionWasmBuilder::makeTry(Element& s) {
auto ret = allocator.alloc<Try>();
Index i = 1;
@@ -2001,7 +2014,7 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) {
} else {
sName = "try";
}
- auto label = nameMapper.pushLabelName(sName);
+ ret->name = nameMapper.pushLabelName(sName);
Type type = parseOptionalResultType(s, i); // signature
if (!elementStartsWith(*s[i], "do")) {
@@ -2027,21 +2040,38 @@ Expression* SExpressionWasmBuilder::makeTry(Element& s) {
ret->catchBodies.push_back(makeMaybeBlock(*s[i++], 1, type));
}
+ // 'delegate' cannot target its own try. So we pop the name here.
+ nameMapper.popLabelName(ret->name);
+
+ if (i < s.size() && elementStartsWith(*s[i], "delegate")) {
+ Element& inner = *s[i++];
+ if (inner.size() != 2) {
+ throw ParseException("invalid delegate", inner.line, inner.col);
+ }
+ ret->delegateTarget = getLabel(*inner[1], LabelType::Delegate);
+ }
+
if (i != s.size()) {
throw ParseException(
"there should be at most one catch_all block at the end", s.line, s.col);
}
- if (ret->catchBodies.empty()) {
- throw ParseException("no catch bodies", s.line, s.col);
+ if (ret->catchBodies.empty() && !ret->isDelegate()) {
+ throw ParseException("no catch bodies or delegate", s.line, s.col);
}
ret->finalize(type);
- nameMapper.popLabelName(label);
+
// create a break target if we must
- if (BranchUtils::BranchSeeker::has(ret, label)) {
+ if (BranchUtils::BranchSeeker::has(ret, ret->name)) {
auto* block = allocator.alloc<Block>();
- block->name = label;
+ // We create a different name for the wrapping block, because try's name can
+ // be used by internal delegates
+ block->name = nameMapper.pushLabelName(sName);
+ // For simplicity, try's name canonly be targeted by delegates. Make the
+ // branches target the new wrapping block instead.
+ BranchUtils::replaceBranchTargets(ret, ret->name, block->name);
block->list.push_back(ret);
+ nameMapper.popLabelName(block->name);
block->finalize(type);
return block;
}
diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp
index 00b1e5368..cb9d94093 100644
--- a/src/wasm/wasm-stack.cpp
+++ b/src/wasm/wasm-stack.cpp
@@ -1904,7 +1904,7 @@ void BinaryInstWriter::visitRefEq(RefEq* curr) {
}
void BinaryInstWriter::visitTry(Try* curr) {
- breakStack.emplace_back(IMPOSSIBLE_CONTINUE);
+ breakStack.push_back(curr->name);
o << int8_t(BinaryConsts::Try);
emitResultType(curr->type);
}
@@ -1924,6 +1924,13 @@ void BinaryInstWriter::emitCatchAll(Try* curr) {
o << int8_t(BinaryConsts::CatchAll);
}
+void BinaryInstWriter::emitDelegate(Try* curr) {
+ assert(!breakStack.empty());
+ breakStack.pop_back();
+ o << int8_t(BinaryConsts::Delegate)
+ << U32LEB(getBreakIndex(curr->delegateTarget));
+}
+
void BinaryInstWriter::visitThrow(Throw* curr) {
o << int8_t(BinaryConsts::Throw) << U32LEB(parent.getEventIndex(curr->event));
}
@@ -2216,6 +2223,9 @@ void BinaryInstWriter::emitMemoryAccess(size_t alignment,
}
int32_t BinaryInstWriter::getBreakIndex(Name name) { // -1 if not found
+ if (name == DELEGATE_CALLER_TARGET) {
+ return breakStack.size();
+ }
for (int i = breakStack.size() - 1; i >= 0; i--) {
if (breakStack[i] == name) {
return breakStack.size() - 1 - i;
@@ -2321,6 +2331,10 @@ void StackIRToBinaryWriter::write() {
writer.emitCatchAll(inst->origin->cast<Try>());
break;
}
+ case StackInst::Delegate: {
+ writer.emitDelegate(inst->origin->cast<Try>());
+ break;
+ }
default:
WASM_UNREACHABLE("unexpected op");
}
diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp
index 276b9602f..beb994390 100644
--- a/src/wasm/wasm-validator.cpp
+++ b/src/wasm/wasm-validator.cpp
@@ -236,6 +236,7 @@ struct FunctionValidator : public WalkerPass<PostWalker<FunctionValidator>> {
};
std::unordered_map<Name, BreakInfo> breakInfos;
+ std::unordered_set<Name> delegateTargetNames;
std::set<Type> returnTypes; // types used in returns
@@ -276,11 +277,52 @@ public:
void visitLoop(Loop* curr);
void visitIf(If* curr);
+ static void visitPreTry(FunctionValidator* self, Expression** currp) {
+ auto* curr = (*currp)->cast<Try>();
+ if (curr->name.is()) {
+ self->delegateTargetNames.insert(curr->name);
+ }
+ }
+
+ // We remove try's label before proceeding to verify catch bodies because the
+ // following is a validation failure:
+ // (try $l0
+ // (do ... )
+ // (catch $e
+ // (try
+ // (do ...)
+ // (delegate $l0) ;; validation failure
+ // )
+ // )
+ // )
+ // Unlike branches, if delegate's target 'catch' is located above the
+ // delegate, it is a validation failure.
+ static void visitPreCatch(FunctionValidator* self, Expression** currp) {
+ auto* curr = (*currp)->cast<Try>();
+ if (curr->name.is()) {
+ self->delegateTargetNames.erase(curr->name);
+ }
+ }
+
// override scan to add a pre and a post check task to all nodes
static void scan(FunctionValidator* self, Expression** currp) {
+ auto* curr = *currp;
+ // Treat 'Try' specially because we need to run visitPreCatch between the
+ // try body and catch bodies
+ if (curr->is<Try>()) {
+ self->pushTask(doVisitTry, currp);
+ auto& list = curr->cast<Try>()->catchBodies;
+ for (int i = int(list.size()) - 1; i >= 0; i--) {
+ self->pushTask(scan, &list[i]);
+ }
+ self->pushTask(visitPreCatch, currp);
+ self->pushTask(scan, &curr->cast<Try>()->body);
+ self->pushTask(visitPreTry, currp);
+ return;
+ }
+
PostWalker<FunctionValidator>::scan(self, currp);
- auto* curr = *currp;
if (curr->is<Block>()) {
self->pushTask(visitPreBlock, currp);
}
@@ -334,6 +376,7 @@ public:
void visitRefIs(RefIs* curr);
void visitRefFunc(RefFunc* curr);
void visitRefEq(RefEq* curr);
+ void noteDelegate(Name name, Expression* curr);
void visitTry(Try* curr);
void visitThrow(Throw* curr);
void visitRethrow(Rethrow* curr);
@@ -767,6 +810,7 @@ void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) {
}
}
}
+
void FunctionValidator::visitBreak(Break* curr) {
noteBreak(curr->name, curr->value, curr);
if (curr->value) {
@@ -2029,6 +2073,14 @@ void FunctionValidator::visitRefEq(RefEq* curr) {
"ref.eq's right argument should be a subtype of eqref");
}
+void FunctionValidator::noteDelegate(Name name, Expression* curr) {
+ if (name != DELEGATE_CALLER_TARGET) {
+ shouldBeTrue(delegateTargetNames.find(name) != delegateTargetNames.end(),
+ curr,
+ "all delegate targets must be valid");
+ }
+}
+
void FunctionValidator::visitTry(Try* curr) {
shouldBeTrue(getModule()->features.hasExceptionHandling(),
curr,
@@ -2061,6 +2113,17 @@ void FunctionValidator::visitTry(Try* curr) {
shouldBeTrue(curr->catchBodies.size() - curr->catchEvents.size() <= 1,
curr,
"the number of catch blocks and events do not match");
+
+ shouldBeFalse(curr->isCatch() && curr->isDelegate(),
+ curr,
+ "try cannot have both catch and delegate at the same time");
+ shouldBeTrue(curr->isCatch() || curr->isDelegate(),
+ curr,
+ "try should have either catches or a delegate");
+
+ if (curr->isDelegate()) {
+ noteDelegate(curr->delegateTarget, curr);
+ }
}
void FunctionValidator::visitThrow(Throw* curr) {
@@ -2470,6 +2533,9 @@ void FunctionValidator::visitFunction(Function* curr) {
shouldBeTrue(
breakInfos.empty(), curr->body, "all named break targets must exist");
+ shouldBeTrue(delegateTargetNames.empty(),
+ curr->body,
+ "all named delegate targets must exist");
returnTypes.clear();
labelNames.clear();
// validate optional local names
diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp
index 7bf9b8604..92e03f579 100644
--- a/src/wasm/wasm.cpp
+++ b/src/wasm/wasm.cpp
@@ -88,6 +88,7 @@ Name CASE("case");
Name BR("br");
Name FUNCREF("funcref");
Name FAKE_RETURN("fake_return_waka123");
+Name DELEGATE_CALLER_TARGET("delegate_caller_target_waka123");
Name MUT("mut");
Name SPECTEST("spectest");
Name PRINT("print");