summaryrefslogtreecommitdiff
path: root/src/wasm
diff options
context:
space:
mode:
Diffstat (limited to 'src/wasm')
-rw-r--r--src/wasm/wasm-binary.cpp98
-rw-r--r--src/wasm/wasm-s-parser.cpp96
-rw-r--r--src/wasm/wasm-stack.cpp45
-rw-r--r--src/wasm/wasm-validator.cpp105
-rw-r--r--src/wasm/wasm.cpp56
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;