diff options
Diffstat (limited to 'src/wasm')
-rw-r--r-- | src/wasm/wasm-binary.cpp | 98 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 96 | ||||
-rw-r--r-- | src/wasm/wasm-stack.cpp | 45 | ||||
-rw-r--r-- | src/wasm/wasm-validator.cpp | 105 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 56 |
5 files changed, 393 insertions, 7 deletions
diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 039cef5e5..7047b674e 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1754,7 +1754,8 @@ void WasmBinaryBuilder::processExpressions() { throwError("unexpected end of input"); } auto peek = input[pos]; - if (peek == BinaryConsts::End || peek == BinaryConsts::Else) { + if (peek == BinaryConsts::End || peek == BinaryConsts::Else || + peek == BinaryConsts::Catch) { if (debug) { std::cerr << "== processExpressions finished with unreachable" << std::endl; @@ -2260,8 +2261,21 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) { break; case BinaryConsts::End: case BinaryConsts::Else: + case BinaryConsts::Catch: curr = nullptr; break; + case BinaryConsts::Try: + visitTry((curr = allocator.alloc<Try>())->cast<Try>()); + break; + case BinaryConsts::Throw: + visitThrow((curr = allocator.alloc<Throw>())->cast<Throw>()); + break; + case BinaryConsts::Rethrow: + visitRethrow((curr = allocator.alloc<Rethrow>())->cast<Rethrow>()); + break; + case BinaryConsts::BrOnExn: + visitBrOnExn((curr = allocator.alloc<BrOnExn>())->cast<BrOnExn>()); + break; case BinaryConsts::AtomicPrefix: { code = static_cast<uint8_t>(getU32LEB()); if (maybeVisitLoad(curr, code, /*isAtomic=*/true)) { @@ -2420,6 +2434,7 @@ void WasmBinaryBuilder::visitBlock(Block* curr) { if (debug) { std::cerr << "zz node: Block" << std::endl; } + // special-case Block and de-recurse nested blocks in their first position, as // that is a common pattern that can be very highly nested. std::vector<Block*> stack; @@ -2467,10 +2482,22 @@ void WasmBinaryBuilder::visitBlock(Block* curr) { } } -Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type) { +// Gets a block of expressions. If it's just one, return that singleton. +// numPops is the number of pop instructions we add before starting to parse the +// block. Can be used when we need to assume certain number of values are on top +// of the stack in the beginning. +Expression* WasmBinaryBuilder::getBlockOrSingleton(Type type, + unsigned numPops) { Name label = getNextLabel(); breakStack.push_back({label, type != none && type != unreachable}); auto start = expressionStack.size(); + + Builder builder(wasm); + for (unsigned i = 0; i < numPops; i++) { + auto* pop = builder.makePop(exnref); + expressionStack.push_back(pop); + } + processExpressions(); size_t end = expressionStack.size(); if (end < start) { @@ -4347,6 +4374,73 @@ void WasmBinaryBuilder::visitDrop(Drop* curr) { curr->finalize(); } +void WasmBinaryBuilder::visitTry(Try* curr) { + if (debug) { + std::cerr << "zz node: Try" << std::endl; + } + // For simplicity of implementation, like if scopes, we create a hidden block + // within each try-body and catch-body, and let branches target those inner + // blocks instead. + curr->type = getType(); + curr->body = getBlockOrSingleton(curr->type); + if (lastSeparator != BinaryConsts::Catch) { + throwError("No catch instruction within a try scope"); + } + curr->catchBody = getBlockOrSingleton(curr->type, 1); + curr->finalize(curr->type); + if (lastSeparator != BinaryConsts::End) { + throwError("try should end with end"); + } +} + +void WasmBinaryBuilder::visitThrow(Throw* curr) { + if (debug) { + std::cerr << "zz node: Throw" << std::endl; + } + auto index = getU32LEB(); + if (index >= wasm.events.size()) { + throwError("bad event index"); + } + auto* event = wasm.events[index].get(); + curr->event = event->name; + size_t num = event->params.size(); + curr->operands.resize(num); + for (size_t i = 0; i < num; i++) { + curr->operands[num - i - 1] = popNonVoidExpression(); + } + curr->finalize(); +} + +void WasmBinaryBuilder::visitRethrow(Rethrow* curr) { + if (debug) { + std::cerr << "zz node: Rethrow" << std::endl; + } + curr->exnref = popNonVoidExpression(); + curr->finalize(); +} + +void WasmBinaryBuilder::visitBrOnExn(BrOnExn* curr) { + if (debug) { + std::cerr << "zz node: BrOnExn" << std::endl; + } + BreakTarget target = getBreakTarget(getU32LEB()); + curr->name = target.name; + auto index = getU32LEB(); + if (index >= wasm.events.size()) { + throwError("bad event index"); + } + curr->event = wasm.events[index]->name; + curr->exnref = popNonVoidExpression(); + + Event* event = wasm.getEventOrNull(curr->event); + assert(event && "br_on_exn's event must exist"); + + // Copy params info into BrOnExn, because it is necessary when BrOnExn is + // refinalized without the module. + curr->eventParams = event->params; + curr->finalize(); +} + void WasmBinaryBuilder::throwError(std::string text) { throw ParseException(text, 0, pos); } diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 485b3251f..e1413d5fd 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -1726,6 +1726,102 @@ Expression* SExpressionWasmBuilder::makeReturn(Element& s) { return ret; } +// try-catch-end is written in the folded wast format as +// (try +// ... +// (catch +// ... +// ) +// ) +// The parenthesis wrapping 'catch' is just a syntax and does not affect nested +// depths of instructions within. +Expression* SExpressionWasmBuilder::makeTry(Element& s) { + auto ret = allocator.alloc<Try>(); + Index i = 1; + Name sName; + if (s[i]->dollared()) { + // the try is labeled + sName = s[i++]->str(); + } else { + sName = "try"; + } + auto label = nameMapper.pushLabelName(sName); + Type type = parseOptionalResultType(s, i); // signature + if (elementStartsWith(*s[i], "catch")) { // empty try body + ret->body = makeNop(); + } else { + ret->body = parseExpression(*s[i++]); + } + if (!elementStartsWith(*s[i], "catch")) { + throw ParseException("catch clause does not exist"); + } + ret->catchBody = makeCatch(*s[i++]); + ret->finalize(type); + nameMapper.popLabelName(label); + // create a break target if we must + if (BranchUtils::BranchSeeker::hasNamed(ret, label)) { + auto* block = allocator.alloc<Block>(); + block->name = label; + block->list.push_back(ret); + block->finalize(ret->type); + return block; + } + return ret; +} + +Expression* SExpressionWasmBuilder::makeCatch(Element& s) { + if (!elementStartsWith(s, "catch")) { + throw ParseException("invalid catch clause", s.line, s.col); + } + auto ret = allocator.alloc<Block>(); + for (size_t i = 1; i < s.size(); i++) { + ret->list.push_back(parseExpression(s[i])); + } + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeThrow(Element& s) { + auto ret = allocator.alloc<Throw>(); + Index i = 1; + + ret->event = getEventName(*s[i++]); + if (!wasm.getEventOrNull(ret->event)) { + throw ParseException("bad event name", s[1]->line, s[1]->col); + } + for (; i < s.size(); i++) { + ret->operands.push_back(parseExpression(s[i])); + } + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeRethrow(Element& s) { + auto ret = allocator.alloc<Rethrow>(); + ret->exnref = parseExpression(*s[1]); + ret->finalize(); + return ret; +} + +Expression* SExpressionWasmBuilder::makeBrOnExn(Element& s) { + auto ret = allocator.alloc<BrOnExn>(); + size_t i = 1; + ret->name = getLabel(*s[i++]); + ret->event = getEventName(*s[i++]); + if (!wasm.getEventOrNull(ret->event)) { + throw ParseException("bad event name", s[1]->line, s[1]->col); + } + ret->exnref = parseExpression(s[i]); + + Event* event = wasm.getEventOrNull(ret->event); + assert(event && "br_on_exn's event must exist"); + // Copy params info into BrOnExn, because it is necessary when BrOnExn is + // refinalized without the module. + ret->eventParams = event->params; + ret->finalize(); + return ret; +} + // converts an s-expression string representing binary data into an output // sequence of raw bytes this appends to data, which may already contain // content. diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index b8ec4fa35..d3aba3b8c 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1396,6 +1396,32 @@ void BinaryInstWriter::visitHost(Host* curr) { o << U32LEB(0); // Reserved flags field } +void BinaryInstWriter::visitTry(Try* curr) { + breakStack.emplace_back(IMPOSSIBLE_CONTINUE); + o << int8_t(BinaryConsts::Try); + o << binaryType(curr->type != unreachable ? curr->type : none); +} + +void BinaryInstWriter::emitCatch() { + assert(!breakStack.empty()); + breakStack.pop_back(); + breakStack.emplace_back(IMPOSSIBLE_CONTINUE); + o << int8_t(BinaryConsts::Catch); +} + +void BinaryInstWriter::visitThrow(Throw* curr) { + o << int8_t(BinaryConsts::Throw) << U32LEB(parent.getEventIndex(curr->event)); +} + +void BinaryInstWriter::visitRethrow(Rethrow* curr) { + o << int8_t(BinaryConsts::Rethrow); +} + +void BinaryInstWriter::visitBrOnExn(BrOnExn* curr) { + o << int8_t(BinaryConsts::BrOnExn) << U32LEB(getBreakIndex(curr->name)) + << U32LEB(parent.getEventIndex(curr->event)); +} + void BinaryInstWriter::visitNop(Nop* curr) { o << int8_t(BinaryConsts::Nop); } void BinaryInstWriter::visitUnreachable(Unreachable* curr) { @@ -1466,12 +1492,18 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() { mappedLocals[i] = index + currLocalsByType[v128] - 1; continue; } + index += numLocalsByType[v128]; + if (type == exnref) { + mappedLocals[i] = index + currLocalsByType[exnref] - 1; + continue; + } WASM_UNREACHABLE(); } // Emit them. o << U32LEB((numLocalsByType[i32] ? 1 : 0) + (numLocalsByType[i64] ? 1 : 0) + (numLocalsByType[f32] ? 1 : 0) + (numLocalsByType[f64] ? 1 : 0) + - (numLocalsByType[v128] ? 1 : 0)); + (numLocalsByType[v128] ? 1 : 0) + + (numLocalsByType[exnref] ? 1 : 0)); if (numLocalsByType[i32]) { o << U32LEB(numLocalsByType[i32]) << binaryType(i32); } @@ -1487,6 +1519,9 @@ void BinaryInstWriter::mapLocalsAndEmitHeader() { if (numLocalsByType[v128]) { o << U32LEB(numLocalsByType[v128]) << binaryType(v128); } + if (numLocalsByType[exnref]) { + o << U32LEB(numLocalsByType[exnref]) << binaryType(exnref); + } } void BinaryInstWriter::emitMemoryAccess(size_t alignment, @@ -1513,6 +1548,8 @@ void StackIRGenerator::emit(Expression* curr) { stackInst = makeStackInst(StackInst::IfBegin, curr); } else if (curr->is<Loop>()) { stackInst = makeStackInst(StackInst::LoopBegin, curr); + } else if (curr->is<Try>()) { + stackInst = makeStackInst(StackInst::TryBegin, curr); } else { stackInst = makeStackInst(curr); } @@ -1527,6 +1564,8 @@ void StackIRGenerator::emitScopeEnd(Expression* curr) { stackInst = makeStackInst(StackInst::IfEnd, curr); } else if (curr->is<Loop>()) { stackInst = makeStackInst(StackInst::LoopEnd, curr); + } else if (curr->is<Try>()) { + stackInst = makeStackInst(StackInst::TryEnd, curr); } else { WASM_UNREACHABLE(); } @@ -1581,6 +1620,10 @@ void StackIRToBinaryWriter::write() { writer.emitIfElse(); break; } + case StackInst::Catch: { + writer.emitCatch(); + break; + } default: WASM_UNREACHABLE(); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 73731d3e5..8eb3341de 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -254,6 +254,7 @@ public: } void noteBreak(Name name, Expression* value, Expression* curr); + void noteBreak(Name name, Type valueType, Expression* curr); void visitBreak(Break* curr); void visitSwitch(Switch* curr); void visitCall(Call* curr); @@ -284,6 +285,10 @@ public: void visitDrop(Drop* curr); void visitReturn(Return* curr); void visitHost(Host* curr); + void visitTry(Try* curr); + void visitThrow(Throw* curr); + void visitRethrow(Rethrow* curr); + void visitBrOnExn(BrOnExn* curr); void visitFunction(Function* curr); // helpers @@ -519,11 +524,15 @@ void FunctionValidator::visitIf(If* curr) { void FunctionValidator::noteBreak(Name name, Expression* value, Expression* curr) { - Type valueType = none; - Index arity = 0; if (value) { - valueType = value->type; - shouldBeUnequal(valueType, none, curr, "breaks must have a valid value"); + shouldBeUnequal(value->type, none, curr, "breaks must have a valid value"); + } + noteBreak(name, value ? value->type : none, curr); +} + +void FunctionValidator::noteBreak(Name name, Type valueType, Expression* curr) { + Index arity = 0; + if (valueType != none) { arity = 1; } auto iter = breakInfos.find(name); @@ -1581,6 +1590,94 @@ void FunctionValidator::visitHost(Host* curr) { } } +void FunctionValidator::visitTry(Try* curr) { + if (curr->type != unreachable) { + shouldBeEqualOrFirstIsUnreachable( + curr->body->type, + curr->type, + curr->body, + "try's type does not match try body's type"); + shouldBeEqualOrFirstIsUnreachable( + curr->catchBody->type, + curr->type, + curr->catchBody, + "try's type does not match catch's body type"); + } + if (isConcreteType(curr->body->type)) { + shouldBeEqualOrFirstIsUnreachable( + curr->catchBody->type, + curr->body->type, + curr->catchBody, + "try's body type must match catch's body type"); + } + if (isConcreteType(curr->catchBody->type)) { + shouldBeEqualOrFirstIsUnreachable( + curr->body->type, + curr->catchBody->type, + curr->body, + "try's body type must match catch's body type"); + } +} + +void FunctionValidator::visitThrow(Throw* curr) { + if (!info.validateGlobally) { + return; + } + shouldBeEqual( + curr->type, unreachable, curr, "throw's type must be unreachable"); + auto* event = getModule()->getEventOrNull(curr->event); + if (!shouldBeTrue(!!event, curr, "throw's event must exist")) { + return; + } + if (!shouldBeTrue(curr->operands.size() == event->params.size(), + curr, + "event's param numbers must match")) { + return; + } + for (size_t i = 0; i < curr->operands.size(); i++) { + if (!shouldBeEqualOrFirstIsUnreachable(curr->operands[i]->type, + event->params[i], + curr->operands[i], + "event param types must match") && + !info.quiet) { + getStream() << "(on argument " << i << ")\n"; + } + } +} + +void FunctionValidator::visitRethrow(Rethrow* curr) { + shouldBeEqual( + curr->type, unreachable, curr, "rethrow's type must be unreachable"); + shouldBeEqual(curr->exnref->type, + exnref, + curr->exnref, + "rethrow's argument must be exnref type"); +} + +void FunctionValidator::visitBrOnExn(BrOnExn* curr) { + Event* event = getModule()->getEventOrNull(curr->event); + shouldBeTrue(event != nullptr, curr, "br_on_exn's event must exist"); + shouldBeTrue(event->params == curr->eventParams, + curr, + "br_on_exn's event params and event's params are different"); + noteBreak(curr->name, curr->getSingleSentType(), curr); + shouldBeTrue(curr->exnref->type == unreachable || + curr->exnref->type == exnref, + curr, + "br_on_exn's argument must be unreachable or exnref type"); + if (curr->exnref->type == unreachable) { + shouldBeTrue(curr->type == unreachable, + curr, + "If exnref argument's type is unreachable, br_on_exn should " + "be unreachable too"); + } else { + shouldBeTrue(curr->type == exnref, + curr, + "br_on_exn's type should be exnref unless its exnref argument " + "is unreachable"); + } +} + void FunctionValidator::visitFunction(Function* curr) { FeatureSet typeFeatures = getFeatures(curr->result); for (auto type : curr->params) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 04abbcd9f..97b60bfb5 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -168,6 +168,14 @@ const char* getExpressionName(Expression* curr) { return "push"; case Expression::Id::PopId: return "pop"; + case Expression::TryId: + return "try"; + case Expression::ThrowId: + return "throw"; + case Expression::RethrowId: + return "rethrow"; + case Expression::BrOnExnId: + return "br_on_exn"; case Expression::Id::NumExpressionIds: WASM_UNREACHABLE(); } @@ -204,6 +212,12 @@ struct TypeSeeker : public PostWalker<TypeSeeker> { } } + void visitBrOnExn(BrOnExn* curr) { + if (curr->name == targetName) { + types.push_back(curr->getSingleSentType()); + } + } + void visitBlock(Block* curr) { if (curr == target) { if (curr->list.size() > 0) { @@ -836,6 +850,48 @@ void Host::finalize() { } } +void Try::finalize() { + if (body->type == catchBody->type) { + type = body->type; + } else if (isConcreteType(body->type) && catchBody->type == unreachable) { + type = body->type; + } else if (isConcreteType(catchBody->type) && body->type == unreachable) { + type = catchBody->type; + } else { + type = none; + } +} + +void Try::finalize(Type type_) { + type = type_; + if (type == none && body->type == unreachable && + catchBody->type == unreachable) { + type = unreachable; + } +} + +void Throw::finalize() { type = unreachable; } + +void Rethrow::finalize() { type = unreachable; } + +void BrOnExn::finalize() { + if (exnref->type == unreachable) { + type = unreachable; + } else { + type = Type::exnref; + } +} + +// br_on_exn's type is exnref, which it pushes onto the stack when it is not +// taken, but the type of the value it pushes onto the stack when it is taken +// should be the event type. So this is the type we 'send' to the block end when +// it is taken. Currently we don't support multi value return from a block, we +// pick the type of the first param from the event. +// TODO Remove this function and generalize event type after multi-value support +Type BrOnExn::getSingleSentType() { + return eventParams.empty() ? none : eventParams.front(); +} + void Push::finalize() { if (value->type == unreachable) { type = unreachable; |