diff options
Diffstat (limited to 'src/wasm')
-rw-r--r-- | src/wasm/wasm-binary.cpp | 173 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 56 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 16 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 68 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 1 |
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"); |