diff options
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | src/apply-names.cc | 1 | ||||
-rw-r--r-- | src/c-writer.cc | 442 | ||||
-rw-r--r-- | src/generate-names.cc | 6 | ||||
-rw-r--r-- | src/stream.h | 1 | ||||
-rw-r--r-- | src/template/wasm2c.declarations.c | 2 | ||||
-rw-r--r-- | src/template/wasm2c.includes.c | 6 | ||||
-rw-r--r-- | src/tools/wasm2c.cc | 5 | ||||
-rwxr-xr-x | test/run-spec-wasm2c.py | 5 | ||||
-rw-r--r-- | test/spec-wasm2c-prefix.c | 11 | ||||
-rw-r--r-- | test/wasm2c/bad-enable-feature.txt | 4 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/binary.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/exports.txt | 13 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/imports.txt | 24 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/rethrow.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/tag.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/throw.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/try_catch.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/spec/exception-handling/try_delegate.txt | 6 | ||||
-rw-r--r-- | wasm2c/examples/fac/fac.c | 11 | ||||
-rw-r--r-- | wasm2c/wasm-rt-impl.c | 53 | ||||
-rw-r--r-- | wasm2c/wasm-rt-impl.h | 8 | ||||
-rw-r--r-- | wasm2c/wasm-rt.h | 54 |
23 files changed, 643 insertions, 41 deletions
@@ -47,7 +47,7 @@ Wabt has been compiled to JavaScript via emscripten. Some of the functionality i | Proposal | flag | default | binary | text | validate | interpret | wasm2c | | --------------------- | --------------------------- | - | - | - | - | - | - | -| [exception handling][]| `--enable-exceptions` | | ✓ | ✓ | ✓ | ✓ | | +| [exception handling][]| `--enable-exceptions` | | ✓ | ✓ | ✓ | ✓ | ✓ | | [mutable globals][] | `--disable-mutable-globals` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | [nontrapping float-to-int conversions][] | `--disable-saturating-float-to-int` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | [sign extension][] | `--disable-sign-extension` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | diff --git a/src/apply-names.cc b/src/apply-names.cc index b0cc41fa..9f5b2002 100644 --- a/src/apply-names.cc +++ b/src/apply-names.cc @@ -375,6 +375,7 @@ Result NameApplier::OnCatchExpr(TryExpr*, Catch* expr) { } Result NameApplier::OnDelegateExpr(TryExpr* expr) { + PopLabel(); std::string_view label = FindLabelByVar(&expr->delegate_target); UseNameForVar(label, &expr->delegate_target); return Result::Ok; diff --git a/src/c-writer.cc b/src/c-writer.cc index 92d55ac7..bd7ac766 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -48,11 +48,13 @@ struct Label { const std::string& name, const TypeVector& sig, size_t type_stack_size, + size_t try_catch_stack_size, bool used = false) : label_type(label_type), name(name), sig(sig), type_stack_size(type_stack_size), + try_catch_stack_size(try_catch_stack_size), used(used) {} bool HasValue() const { return !sig.empty(); } @@ -61,6 +63,7 @@ struct Label { const std::string& name; const TypeVector& sig; size_t type_stack_size; + size_t try_catch_stack_size; bool used = false; }; @@ -112,6 +115,14 @@ struct ResultType { const TypeVector& types; }; +struct TryCatchLabel { + TryCatchLabel(const std::string& name, size_t try_catch_stack_size) + : name(name), try_catch_stack_size(try_catch_stack_size), used(false) {} + std::string name; + size_t try_catch_stack_size; + bool used; +}; + struct Newline {}; struct OpenBrace {}; struct CloseBrace {}; @@ -161,7 +172,7 @@ class CWriter { const std::string& name, const FuncSignature&, bool used = false); - const Label* FindLabel(const Var& var); + const Label* FindLabel(const Var& var, bool mark_used = true); bool IsTopLabelUsed() const; void PopLabel(); @@ -170,6 +181,7 @@ class CWriter { static char MangleType(Type); static std::string MangleMultivalueTypes(const TypeVector&); + static std::string MangleTagTypes(const TypeVector&); static std::string MangleName(std::string_view); static std::string LegalizeName(std::string_view); std::string ExportName(std::string_view mangled_name); @@ -226,7 +238,9 @@ class CWriter { std::string GenerateHeaderGuard() const; void WriteSourceTop(); void WriteMultivalueTypes(); + void WriteTagTypes(); void WriteFuncTypes(); + void WriteTags(); void WriteImports(); void WriteFuncDeclarations(); void WriteFuncDeclaration(const FuncDeclaration&, const std::string&); @@ -274,12 +288,23 @@ class CWriter { void Write(const SimdShuffleOpExpr&); void Write(const LoadSplatExpr&); void Write(const LoadZeroExpr&); + void Write(const Block&); + + size_t BeginTry(const TryExpr& tryexpr); + void WriteTryCatch(const TryExpr& tryexpr); + void WriteTryDelegate(const TryExpr& tryexpr); + void Write(const Catch& c); + void WriteThrow(); + + void PushTryCatch(const std::string& name); + void PopTryCatch(); + + void PushFuncSection(const std::string_view include_condition = ""); const WriteCOptions& options_; const Module* module_ = nullptr; const Func* func_ = nullptr; Stream* stream_ = nullptr; - MemoryStream func_stream_; Stream* c_stream_ = nullptr; Stream* h_stream_ = nullptr; std::string header_name_; @@ -295,7 +320,11 @@ class CWriter { SymbolSet import_syms_; TypeVector type_stack_; std::vector<Label> label_stack_; + std::vector<TryCatchLabel> try_catch_stack_; std::string module_prefix_; + + std::vector<std::pair<std::string, MemoryStream>> func_sections_; + SymbolSet func_includes_; }; static const char kImplicitFuncLabel[] = "$Bfunc"; @@ -333,13 +362,15 @@ void CWriter::PushLabel(LabelType label_type, bool used) { if (label_type == LabelType::Loop) label_stack_.emplace_back(label_type, name, sig.param_types, - type_stack_.size(), used); + type_stack_.size(), try_catch_stack_.size(), + used); else label_stack_.emplace_back(label_type, name, sig.result_types, - type_stack_.size(), used); + type_stack_.size(), try_catch_stack_.size(), + used); } -const Label* CWriter::FindLabel(const Var& var) { +const Label* CWriter::FindLabel(const Var& var, bool mark_used) { Label* label = nullptr; if (var.is_index()) { @@ -358,7 +389,9 @@ const Label* CWriter::FindLabel(const Var& var) { } assert(label); - label->used = true; + if (mark_used) { + label->used = true; + } return label; } @@ -403,6 +436,16 @@ std::string CWriter::MangleMultivalueTypes(const TypeVector& types) { } // static +std::string CWriter::MangleTagTypes(const TypeVector& types) { + assert(types.size() >= 2); + std::string result = "wasm_tag_"; + for (auto type : types) { + result += MangleType(type); + } + return result; +} + +// static std::string CWriter::MangleName(std::string_view name) { const char kPrefix = 'Z'; std::string result = "Z_"; @@ -610,6 +653,15 @@ void CWriter::Write(const GotoLabel& goto_label) { StackVar(amount - i - 1), "; "); } } + + assert(try_catch_stack_.size() >= label->try_catch_stack_size); + + if (try_catch_stack_.size() != label->try_catch_stack_size) { + const std::string& name = + try_catch_stack_.at(label->try_catch_stack_size).name; + + Write("wasm_rt_set_unwind_target(", name, "_outer_target);", Newline()); + } } if (goto_label.var.is_name()) { @@ -816,6 +868,28 @@ void CWriter::WriteMultivalueTypes() { } } +void CWriter::WriteTagTypes() { + for (const Tag* tag : module_->tags) { + const FuncDeclaration& tag_type = tag->decl; + Index num_params = tag_type.GetNumParams(); + if (num_params <= 1) { + continue; + } + const std::string name = MangleTagTypes(tag_type.sig.param_types); + // use same method as WriteMultivalueTypes + Write("#ifndef ", name, Newline()); + Write("#define ", name, " ", name, Newline()); + Write("struct ", name, " ", OpenBrace()); + for (Index i = 0; i < num_params; ++i) { + Type type = tag_type.GetParamType(i); + Write(type); + Writef(" %c%d;", MangleType(type), i); + Write(Newline()); + } + Write(CloseBrace(), ";", Newline(), "#endif /* ", name, " */", Newline()); + } +} + void CWriter::WriteFuncTypes() { if (module_->types.size()) { Writef("static u32 func_types[%" PRIzd "];", module_->types.size()); @@ -847,6 +921,49 @@ void CWriter::WriteFuncTypes() { Write(CloseBrace(), Newline()); } +void CWriter::WriteTags() { + if (module_->tags.empty()) { + Write("static void init_tags(void) ", OpenBrace(), CloseBrace(), Newline()); + return; + } + + Writef("static u32 tag[%" PRIzd "];", module_->tags.size()); + Write(Newline(), Newline()); + + Write("static void init_tags(void) ", OpenBrace()); + + Index tag_index = 0; + for (const Import* import : module_->imports) { + if (import->kind() != ExternalKind::Tag) { + continue; + } + + Write("tag[", tag_index, "] = *", MangleName(import->module_name), + MangleName(import->field_name), ";", Newline()); + ++tag_index; + } + + for (auto it = module_->tags.cbegin() + tag_index; it != module_->tags.cend(); + ++it) { + const Tag* tag = *it; + const FuncDeclaration& tag_type = tag->decl; + Index num_params = tag_type.GetNumParams(); + Write("tag[", tag_index, "] = wasm_rt_register_tag("); + + if (num_params == 0) { + Write("0"); + } else if (num_params == 1) { + Write("sizeof(", tag_type.GetParamType(0), ")"); + } else { + Write("sizeof(struct ", MangleTagTypes(tag_type.sig.param_types), ")"); + } + + Write(");", Newline()); + ++tag_index; + } + Write(CloseBrace(), Newline()); +} + void CWriter::WriteImports() { if (module_->imports.empty()) return; @@ -890,6 +1007,15 @@ void CWriter::WriteImports() { break; } + case ExternalKind::Tag: { + const Tag& tag = cast<TagImport>(import)->tag; + Write("u32 ", + DefineImportName(tag.name, import->module_name, + MangleName(import->field_name)), + ";"); + break; + } + default: WABT_UNREACHABLE; } @@ -1184,12 +1310,27 @@ void CWriter::WriteExports(WriteExportsKind kind) { break; } + case ExternalKind::Tag: { + const Tag* tag = module_->GetTag(export_->var); + mangled_name = ExportName(MangleName(export_->name)); + internal_name = tag->name; + if (kind != WriteExportsKind::Initializers) { + Write("u32 *", mangled_name, ";"); + } + break; + } + default: WABT_UNREACHABLE; } if (kind == WriteExportsKind::Initializers) { - Write(mangled_name, " = ", ExternalPtr(internal_name), ";"); + if (export_->kind == ExternalKind::Tag) { + Write(mangled_name, " = &tag[", module_->GetTagIndex(export_->var), + "];"); + } else { + Write(mangled_name, " = ", ExternalPtr(internal_name), ";"); + } } Write(Newline()); @@ -1199,6 +1340,7 @@ void CWriter::WriteExports(WriteExportsKind kind) { void CWriter::WriteInit() { Write(Newline(), "void ", module_prefix_, "_init(void) ", OpenBrace()); Write("init_func_types();", Newline()); + Write("init_tags();", Newline()); Write("init_globals();", Newline()); Write("init_memory();", Newline()); Write("init_table();", Newline()); @@ -1250,20 +1392,26 @@ void CWriter::WriteFuncs() { } } +void CWriter::PushFuncSection(const std::string_view include_condition) { + func_sections_.emplace_back(include_condition, MemoryStream{}); + stream_ = &func_sections_.back().second; +} + void CWriter::Write(const Func& func) { func_ = &func; // Copy symbols from global symbol table so we don't shadow them. local_syms_ = global_syms_; local_sym_map_.clear(); stack_var_sym_map_.clear(); + func_sections_.clear(); + func_includes_.clear(); Write("static ", ResultType(func.decl.sig.result_types), " ", GlobalName(func.name), "("); WriteParamsAndLocals(); Write("FUNC_PROLOGUE;", Newline()); - stream_ = &func_stream_; - stream_->ClearOffset(); + PushFuncSection(); std::string label = DefineLocalScopeName(kImplicitFuncLabel); ResetTypeStack(0); @@ -1295,12 +1443,15 @@ void CWriter::Write(const Func& func) { WriteStackVarDeclarations(); - std::unique_ptr<OutputBuffer> buf = func_stream_.ReleaseOutputBuffer(); - stream_->WriteData(buf->data.data(), buf->data.size()); + for (auto& [condition, stream] : func_sections_) { + std::unique_ptr<OutputBuffer> buf = stream.ReleaseOutputBuffer(); + if (condition.empty() || func_includes_.count(condition)) { + stream_->WriteData(buf->data.data(), buf->data.size()); + } + } Write(CloseBrace()); - func_stream_.Clear(); func_ = nullptr; } @@ -1387,6 +1538,199 @@ void CWriter::WriteStackVarDeclarations() { } } +void CWriter::Write(const Block& block) { + std::string label = DefineLocalScopeName(block.label); + DropTypes(block.decl.GetNumParams()); + size_t mark = MarkTypeStack(); + PushLabel(LabelType::Block, block.label, block.decl.sig); + PushTypes(block.decl.sig.param_types); + Write(block.exprs, LabelDecl(label)); + ResetTypeStack(mark); + PopLabel(); + PushTypes(block.decl.sig.result_types); +} + +size_t CWriter::BeginTry(const TryExpr& tryexpr) { + Write(OpenBrace()); /* beginning of try-catch */ + const std::string tlabel = DefineLocalScopeName(tryexpr.block.label); + Write("jmp_buf *", tlabel, "_outer_target = wasm_rt_get_unwind_target();", + Newline()); + Write("jmp_buf ", tlabel, "_unwind_target;", Newline()); + Write("if (!wasm_rt_try(", tlabel, "_unwind_target)) "); + Write(OpenBrace()); /* beginning of try block */ + DropTypes(tryexpr.block.decl.GetNumParams()); + const size_t mark = MarkTypeStack(); + PushLabel(LabelType::Try, tryexpr.block.label, tryexpr.block.decl.sig); + PushTypes(tryexpr.block.decl.sig.param_types); + Write("wasm_rt_set_unwind_target(&", tlabel, "_unwind_target);", Newline()); + PushTryCatch(tlabel); + Write(tryexpr.block.exprs); + ResetTypeStack(mark); + Write("wasm_rt_set_unwind_target(", tlabel, "_outer_target);", Newline()); + Write(CloseBrace()); /* end of try block */ + Write(" else ", OpenBrace()); /* beginning of catch blocks or delegate */ + assert(label_stack_.back().name == tryexpr.block.label); + assert(label_stack_.back().label_type == LabelType::Try); + label_stack_.back().label_type = LabelType::Catch; + if (try_catch_stack_.back().used) { + Write(tlabel, "_catch:;", Newline()); + } + + return mark; +} + +void CWriter::WriteTryCatch(const TryExpr& tryexpr) { + const size_t mark = BeginTry(tryexpr); + + /* exception has been thrown -- do we catch it? */ + + assert(local_sym_map_.count(tryexpr.block.label) == 1); + const std::string& tlabel = local_sym_map_[tryexpr.block.label]; + + Write("wasm_rt_set_unwind_target(", tlabel, "_outer_target);", Newline()); + PopTryCatch(); + + /* save the thrown exception to the stack if it might be rethrown later */ + PushFuncSection("rethrow_" + tlabel); + Write("/* save exception ", tlabel, " for rethrow */", Newline()); + Write("uint32_t ", tlabel, "_tag = wasm_rt_exception_tag();", Newline()); + Write("uint32_t ", tlabel, "_size = wasm_rt_exception_size();", Newline()); + Write("void *", tlabel, " = alloca(", tlabel, "_size);", Newline()); + Write("wasm_rt_memcpy(", tlabel, ", wasm_rt_exception(), ", tlabel, "_size);", + Newline()); + PushFuncSection(); + + assert(!tryexpr.catches.empty()); + bool has_catch_all{}; + for (auto it = tryexpr.catches.cbegin(); it != tryexpr.catches.cend(); ++it) { + if (it == tryexpr.catches.cbegin()) { + Write(Newline()); + } else { + Write(" else "); + } + ResetTypeStack(mark); + Write(*it); + if (it->IsCatchAll()) { + has_catch_all = true; + break; + } + } + if (!has_catch_all) { + /* if not caught, rethrow */ + Write(" else ", OpenBrace()); + WriteThrow(); + Write(CloseBrace(), Newline()); + } + Write(CloseBrace(), Newline()); /* end of catch blocks */ + Write(CloseBrace(), Newline()); /* end of try-catch */ + + ResetTypeStack(mark); + Write(LabelDecl(label_stack_.back().name)); + PopLabel(); + PushTypes(tryexpr.block.decl.sig.result_types); +} + +void CWriter::Write(const Catch& c) { + if (c.IsCatchAll()) { + Write(c.exprs); + return; + } + + Write("if (wasm_rt_exception_tag() == tag[", module_->GetTagIndex(c.var), + "]) ", OpenBrace()); + + const Tag* tag = module_->GetTag(c.var); + const FuncDeclaration& tag_type = tag->decl; + const Index num_params = tag_type.GetNumParams(); + if (num_params == 1) { + PushType(tag_type.GetParamType(0)); + Write("wasm_rt_memcpy(&", StackVar(0), ", wasm_rt_exception(), sizeof(", + tag_type.GetParamType(0), "));", Newline()); + } else if (num_params > 1) { + for (const auto& type : tag_type.sig.param_types) { + PushType(type); + } + Write(OpenBrace()); + Write("struct ", MangleTagTypes(tag_type.sig.param_types), " tmp;", + Newline()); + Write("wasm_rt_memcpy(&tmp, wasm_rt_exception(), sizeof(tmp));", Newline()); + for (unsigned int i = 0; i < tag_type.sig.param_types.size(); ++i) { + Write(StackVar(i)); + Writef(" = tmp.%c%d;", MangleType(tag_type.sig.param_types.at(i)), i); + Write(Newline()); + } + + Write(CloseBrace(), Newline()); + } + + Write(c.exprs); + Write(CloseBrace()); +} + +void CWriter::WriteThrow() { + if (try_catch_stack_.empty()) { + Write("wasm_rt_throw();", Newline()); + } else { + Write("goto ", try_catch_stack_.back().name, "_catch;", Newline()); + try_catch_stack_.back().used = true; + } +} + +void CWriter::PushTryCatch(const std::string& name) { + try_catch_stack_.emplace_back(name, try_catch_stack_.size()); +} + +void CWriter::PopTryCatch() { + assert(!try_catch_stack_.empty()); + try_catch_stack_.pop_back(); +} + +void CWriter::WriteTryDelegate(const TryExpr& tryexpr) { + const size_t mark = BeginTry(tryexpr); + + /* exception has been thrown -- where do we delegate it? */ + + if (tryexpr.delegate_target.is_index()) { + /* must be the implicit function label */ + assert(!try_catch_stack_.empty()); + const std::string& unwind_name = try_catch_stack_.at(0).name; + Write("wasm_rt_set_unwind_target(", unwind_name, "_outer_target);", + Newline()); + + Write("wasm_rt_throw();", Newline()); + } else { + const Label* label = FindLabel(tryexpr.delegate_target, false); + + assert(try_catch_stack_.size() >= label->try_catch_stack_size); + + if (label->label_type == LabelType::Try) { + Write("goto ", LocalName(label->name), "_catch;", Newline()); + try_catch_stack_.at(label->try_catch_stack_size).used = true; + } else if (label->try_catch_stack_size == 0) { + assert(!try_catch_stack_.empty()); + const std::string& unwind_name = try_catch_stack_.at(0).name; + Write("wasm_rt_set_unwind_target(", unwind_name, "_outer_target);", + Newline()); + + Write("wasm_rt_throw();", Newline()); + } else { + const std::string label_target = + try_catch_stack_.at(label->try_catch_stack_size - 1).name + "_catch"; + Write("goto ", label_target, ";", Newline()); + try_catch_stack_.at(label->try_catch_stack_size - 1).used = true; + } + } + + Write(CloseBrace(), Newline()); + Write(CloseBrace(), Newline()); + + PopTryCatch(); + ResetTypeStack(mark); + Write(LabelDecl(label_stack_.back().name)); + PopLabel(); + PushTypes(tryexpr.block.decl.sig.result_types); +} + void CWriter::Write(const ExprList& exprs) { for (const Expr& expr : exprs) { switch (expr.type()) { @@ -1394,19 +1738,9 @@ void CWriter::Write(const ExprList& exprs) { Write(*cast<BinaryExpr>(&expr)); break; - case ExprType::Block: { - const Block& block = cast<BlockExpr>(&expr)->block; - std::string label = DefineLocalScopeName(block.label); - DropTypes(block.decl.GetNumParams()); - size_t mark = MarkTypeStack(); - PushLabel(LabelType::Block, block.label, block.decl.sig); - PushTypes(block.decl.sig.param_types); - Write(block.exprs, LabelDecl(label)); - ResetTypeStack(mark); - PopLabel(); - PushTypes(block.decl.sig.result_types); + case ExprType::Block: + Write(cast<BlockExpr>(&expr)->block); break; - } case ExprType::Br: Write(GotoLabel(cast<BrExpr>(&expr)->var), Newline()); @@ -1714,6 +2048,61 @@ void CWriter::Write(const ExprList& exprs) { Write("UNREACHABLE;", Newline()); return; + case ExprType::Throw: { + const Var& var = cast<ThrowExpr>(&expr)->var; + const Tag* tag = module_->GetTag(var); + const Index tag_index = module_->GetTagIndex(var); + + Index num_params = tag->decl.GetNumParams(); + if (num_params == 0) { + Write("wasm_rt_load_exception(tag[", tag_index, "], 0, NULL);", + Newline()); + } else if (num_params == 1) { + Write("wasm_rt_load_exception(tag[", tag_index, "], sizeof(", + tag->decl.GetParamType(0), "), &", StackVar(0), ");", + Newline()); + } else { + Write(OpenBrace()); + Write("struct ", MangleTagTypes(tag->decl.sig.param_types)); + Write(" tmp = {"); + for (Index i = 0; i < num_params; ++i) { + Write(StackVar(i), ", "); + } + Write("};", Newline()); + Write("wasm_rt_load_exception(tag[", tag_index, + "], sizeof(tmp), &tmp);", Newline()); + Write(CloseBrace(), Newline()); + } + + WriteThrow(); + } break; + + case ExprType::Rethrow: { + const RethrowExpr* rethrow = cast<RethrowExpr>(&expr); + assert(rethrow->var.is_name()); + const LocalName ex{rethrow->var.name()}; + assert(local_sym_map_.count(ex.name) == 1); + func_includes_.insert("rethrow_" + local_sym_map_[ex.name]); + Write("wasm_rt_load_exception(", ex, "_tag, ", ex, "_size, ", ex, ");", + Newline()); + WriteThrow(); + } break; + + case ExprType::Try: { + const TryExpr& tryexpr = *cast<TryExpr>(&expr); + switch (tryexpr.kind) { + case TryKind::Plain: + Write(tryexpr.block); + break; + case TryKind::Catch: + WriteTryCatch(tryexpr); + break; + case TryKind::Delegate: + WriteTryDelegate(tryexpr); + break; + } + } break; + case ExprType::AtomicLoad: case ExprType::AtomicRmw: case ExprType::AtomicRmwCmpxchg: @@ -1721,11 +2110,8 @@ void CWriter::Write(const ExprList& exprs) { case ExprType::AtomicWait: case ExprType::AtomicFence: case ExprType::AtomicNotify: - case ExprType::Rethrow: case ExprType::ReturnCall: case ExprType::ReturnCallIndirect: - case ExprType::Throw: - case ExprType::Try: case ExprType::CallRef: UNIMPLEMENTED("..."); break; @@ -2397,6 +2783,8 @@ void CWriter::WriteCSource() { Write("/* Automatically generated by wasm2c */", Newline()); WriteSourceTop(); WriteFuncTypes(); + WriteTagTypes(); + WriteTags(); WriteFuncDeclarations(); WriteGlobals(); WriteMemories(); diff --git a/src/generate-names.cc b/src/generate-names.cc index 35f136c3..a7f5d360 100644 --- a/src/generate-names.cc +++ b/src/generate-names.cc @@ -37,6 +37,7 @@ class NameGenerator : public ExprVisitor::DelegateNop { // Implementation of ExprVisitor::DelegateNop. Result BeginBlockExpr(BlockExpr* expr) override; + Result BeginTryExpr(TryExpr* expr) override; Result BeginLoopExpr(LoopExpr* expr) override; Result BeginIfExpr(IfExpr* expr) override; @@ -212,6 +213,11 @@ Result NameGenerator::BeginBlockExpr(BlockExpr* expr) { return Result::Ok; } +Result NameGenerator::BeginTryExpr(TryExpr* expr) { + MaybeGenerateName("T", label_count_++, &expr->block.label); + return Result::Ok; +} + Result NameGenerator::BeginLoopExpr(LoopExpr* expr) { MaybeGenerateName("L", label_count_++, &expr->block.label); return Result::Ok; diff --git a/src/stream.h b/src/stream.h index 391b2f51..07c507bd 100644 --- a/src/stream.h +++ b/src/stream.h @@ -170,6 +170,7 @@ struct OutputBuffer { class MemoryStream : public Stream { public: WABT_DISALLOW_COPY_AND_ASSIGN(MemoryStream); + MemoryStream(MemoryStream&&) = default; explicit MemoryStream(Stream* log_stream = nullptr); explicit MemoryStream(std::unique_ptr<OutputBuffer>&&, Stream* log_stream = nullptr); diff --git a/src/template/wasm2c.declarations.c b/src/template/wasm2c.declarations.c index 3861c52d..f036ba19 100644 --- a/src/template/wasm2c.declarations.c +++ b/src/template/wasm2c.declarations.c @@ -122,7 +122,7 @@ DEFINE_STORE(i64_store32, u32, u64) #if defined(_MSC_VER) -#include <intrin.h> +#define alloca _alloca // Adapted from // https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h diff --git a/src/template/wasm2c.includes.c b/src/template/wasm2c.includes.c index 91e7c21d..8b61e643 100644 --- a/src/template/wasm2c.includes.c +++ b/src/template/wasm2c.includes.c @@ -1,2 +1,8 @@ #include <math.h> #include <string.h> +#if defined(_MSC_VER) +#include <intrin.h> +#include <malloc.h> +#else +#include <alloca.h> +#endif diff --git a/src/tools/wasm2c.cc b/src/tools/wasm2c.cc index f37c5c5b..7ed745f4 100644 --- a/src/tools/wasm2c.cc +++ b/src/tools/wasm2c.cc @@ -57,7 +57,8 @@ examples: )"; static const std::string supported_features[] = { - "multi-memory", "multi-value", "sign-extend", "saturating-float-to-int"}; + "multi-memory", "multi-value", "sign-extend", "saturating-float-to-int", + "exceptions"}; static bool IsFeatureSupported(const std::string& feature) { return std::find(std::begin(supported_features), std::end(supported_features), @@ -105,7 +106,7 @@ static void ParseOptions(int argc, char** argv) { if (any_non_supported_feature) { fprintf(stderr, - "wasm2c currently only supports a fixed set of features.\n"); + "wasm2c currently only supports a limited set of features.\n"); exit(1); } s_features.disable_bulk_memory(); diff --git a/test/run-spec-wasm2c.py b/test/run-spec-wasm2c.py index 5a246650..3660f804 100755 --- a/test/run-spec-wasm2c.py +++ b/test/run-spec-wasm2c.py @@ -200,6 +200,7 @@ class CWriter(object): 'action': self._WriteActionCommand, 'assert_return': self._WriteAssertReturnCommand, 'assert_trap': self._WriteAssertActionCommand, + 'assert_exception': self._WriteAssertActionCommand, 'assert_exhaustion': self._WriteAssertActionCommand, } @@ -276,6 +277,7 @@ class CWriter(object): 'assert_exhaustion': 'ASSERT_EXHAUSTION', 'assert_return': 'ASSERT_RETURN', 'assert_trap': 'ASSERT_TRAP', + 'assert_exception': 'ASSERT_EXCEPTION', } assert_macro = assert_map[command['type']] @@ -412,6 +414,7 @@ def main(args): help='print the commands that are run.', action='store_true') parser.add_argument('file', help='wast file.') + parser.add_argument('--enable-exceptions', action='store_true') parser.add_argument('--enable-multi-memory', action='store_true') parser.add_argument('--disable-bulk-memory', action='store_true') parser.add_argument('--disable-reference-types', action='store_true') @@ -425,6 +428,7 @@ def main(args): wast2json.verbose = options.print_cmd wast2json.AppendOptionalArgs({ '-v': options.verbose, + '--enable-exceptions': options.enable_exceptions, '--enable-multi-memory': options.enable_multi_memory, '--disable-bulk-memory': options.disable_bulk_memory, '--disable-reference-types': options.disable_reference_types}) @@ -438,6 +442,7 @@ def main(args): error_cmdline=options.error_cmdline) wasm2c.verbose = options.print_cmd wasm2c.AppendOptionalArgs({ + '--enable-exceptions': options.enable_exceptions, '--enable-multi-memory': options.enable_multi_memory}) options.cflags += shlex.split(os.environ.get('WASM2C_CFLAGS', '')) diff --git a/test/spec-wasm2c-prefix.c b/test/spec-wasm2c-prefix.c index 4415be6f..8e570588 100644 --- a/test/spec-wasm2c-prefix.c +++ b/test/spec-wasm2c-prefix.c @@ -25,6 +25,17 @@ static void error(const char* file, int line, const char* format, ...) { va_end(args); } +#define ASSERT_EXCEPTION(f) \ + do { \ + g_tests_run++; \ + if (wasm_rt_impl_try() == WASM_RT_TRAP_UNCAUGHT_EXCEPTION) { \ + g_tests_passed++; \ + } else { \ + (void)(f); \ + error(__FILE__, __LINE__, "expected " #f " to throw exception.\n"); \ + } \ + } while (0) + #define ASSERT_TRAP(f) \ do { \ g_tests_run++; \ diff --git a/test/wasm2c/bad-enable-feature.txt b/test/wasm2c/bad-enable-feature.txt index 609cb53b..9c748bb7 100644 --- a/test/wasm2c/bad-enable-feature.txt +++ b/test/wasm2c/bad-enable-feature.txt @@ -1,6 +1,6 @@ ;;; RUN: %(wasm2c)s -;;; ARGS: --enable-exceptions %(in_file)s +;;; ARGS: --enable-threads %(in_file)s ;;; ERROR: 1 (;; STDERR ;;; -wasm2c currently only supports a fixed set of features. +wasm2c currently only supports a limited set of features. ;;; STDERR ;;) diff --git a/test/wasm2c/spec/exception-handling/binary.txt b/test/wasm2c/spec/exception-handling/binary.txt new file mode 100644 index 00000000..59615955 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/binary.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/binary.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +0/0 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/exports.txt b/test/wasm2c/spec/exception-handling/exports.txt new file mode 100644 index 00000000..4b1fc5a4 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/exports.txt @@ -0,0 +1,13 @@ +;;; TOOL: run-spec-wasm2c +(module) +(;; STDOUT ;;; +0/0 tests passed. +;;; STDOUT ;;) + +;; To be replaced with below after reference types land +;; ;;; TOOL: run-spec-wasm2c +;; ;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/exports.wast +;; ;;; ARGS*: --enable-exceptions +;; (;; STDOUT ;;; +;; 9/9 tests passed. +;; ;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/imports.txt b/test/wasm2c/spec/exception-handling/imports.txt new file mode 100644 index 00000000..797faf50 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/imports.txt @@ -0,0 +1,24 @@ +;;; TOOL: run-spec-wasm2c +(module) +(;; STDOUT ;;; +0/0 tests passed. +;;; STDOUT ;;) + +;; To be replaced with below after reference types land +;; ;;; TOOL: run-spec-wasm2c +;; ;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/imports.wast +;; ;;; ARGS*: --enable-exceptions +;; (;; STDOUT ;;; +;; spectest.print_i32(13) +;; spectest.print_i32_f32(14 42) +;; spectest.print_i32(13) +;; spectest.print_i32(13) +;; spectest.print_f32(13) +;; spectest.print_i32(13) +;; spectest.print_f64_f64(25 53) +;; spectest.print_f64(24) +;; spectest.print_f64(24) +;; spectest.print_f64(24) +;; spectest.print_i32(13) +;; 34/34 tests passed. +;; ;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/rethrow.txt b/test/wasm2c/spec/exception-handling/rethrow.txt new file mode 100644 index 00000000..7f1903cf --- /dev/null +++ b/test/wasm2c/spec/exception-handling/rethrow.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/rethrow.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +12/12 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/tag.txt b/test/wasm2c/spec/exception-handling/tag.txt new file mode 100644 index 00000000..8824d43c --- /dev/null +++ b/test/wasm2c/spec/exception-handling/tag.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/tag.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +0/0 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/throw.txt b/test/wasm2c/spec/exception-handling/throw.txt new file mode 100644 index 00000000..fd751380 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/throw.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/throw.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +7/7 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/try_catch.txt b/test/wasm2c/spec/exception-handling/try_catch.txt new file mode 100644 index 00000000..049cf665 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/try_catch.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/try_catch.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +27/27 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/exception-handling/try_delegate.txt b/test/wasm2c/spec/exception-handling/try_delegate.txt new file mode 100644 index 00000000..9b1d7999 --- /dev/null +++ b/test/wasm2c/spec/exception-handling/try_delegate.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/exception-handling/try_delegate.wast +;;; ARGS*: --enable-exceptions +(;; STDOUT ;;; +15/15 tests passed. +;;; STDOUT ;;) diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c index 106d7768..05b93cd8 100644 --- a/wasm2c/examples/fac/fac.c +++ b/wasm2c/examples/fac/fac.c @@ -1,6 +1,12 @@ /* Automatically generated by wasm2c */ #include <math.h> #include <string.h> +#if defined(_MSC_VER) +#include <intrin.h> +#include <malloc.h> +#else +#include <alloca.h> +#endif #include "fac.h" @@ -127,7 +133,7 @@ DEFINE_STORE(i64_store32, u32, u64) #if defined(_MSC_VER) -#include <intrin.h> +#define alloca _alloca // Adapted from // https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h @@ -453,6 +459,8 @@ static u32 func_types[1]; static void init_func_types(void) { func_types[0] = wasm_rt_register_func_type(1, 1, WASM_RT_I32, WASM_RT_I32); } +static void init_tags(void) { +} static u32 w2c_fac(u32); @@ -496,6 +504,7 @@ static void init_exports(void) { void Z_fac_init(void) { init_func_types(); + init_tags(); init_globals(); init_memory(); init_table(); diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index 885a58a2..96b3b395 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -37,6 +37,7 @@ #endif #define PAGE_SIZE 65536 +#define MAX_EXCEPTION_SIZE PAGE_SIZE typedef struct FuncType { wasm_rt_type_t* params; @@ -58,6 +59,12 @@ static uint32_t g_func_type_count; jmp_buf wasm_rt_jmp_buf; +static uint32_t g_active_exception_tag; +static uint8_t g_active_exception[MAX_EXCEPTION_SIZE]; +static uint32_t g_active_exception_size; + +static jmp_buf* g_unwind_target; + void wasm_rt_trap(wasm_rt_trap_t code) { assert(code != WASM_RT_TRAP_NONE); #if !WASM_RT_MEMCHECK_SIGNAL_HANDLER @@ -112,6 +119,50 @@ uint32_t wasm_rt_register_func_type(uint32_t param_count, return idx + 1; } +uint32_t wasm_rt_register_tag(uint32_t size) { + static uint32_t s_tag_count = 0; + + if (size > MAX_EXCEPTION_SIZE) { + wasm_rt_trap(WASM_RT_TRAP_EXHAUSTION); + } + return s_tag_count++; +} + +void wasm_rt_load_exception(uint32_t tag, uint32_t size, const void* values) { + assert(size <= MAX_EXCEPTION_SIZE); + + g_active_exception_tag = tag; + g_active_exception_size = size; + + if (size) { + memcpy(g_active_exception, values, size); + } +} + +WASM_RT_NO_RETURN void wasm_rt_throw(void) { + WASM_RT_LONGJMP(*g_unwind_target, WASM_RT_TRAP_UNCAUGHT_EXCEPTION); +} + +jmp_buf* wasm_rt_get_unwind_target(void) { + return g_unwind_target; +} + +void wasm_rt_set_unwind_target(jmp_buf* target) { + g_unwind_target = target; +} + +uint32_t wasm_rt_exception_tag(void) { + return g_active_exception_tag; +} + +uint32_t wasm_rt_exception_size(void) { + return g_active_exception_size; +} + +void* wasm_rt_exception(void) { + return g_active_exception; +} + #if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX static void signal_handler(int sig, siginfo_t* si, void* unused) { if (si->si_code == SEGV_ACCERR) { @@ -325,6 +376,8 @@ const char* wasm_rt_strerror(wasm_rt_trap_t trap) { return "Unreachable instruction executed"; case WASM_RT_TRAP_CALL_INDIRECT: return "Invalid call_indirect"; + case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: + return "Uncaught exception"; } return "invalid trap code"; } diff --git a/wasm2c/wasm-rt-impl.h b/wasm2c/wasm-rt-impl.h index c275bbf6..c11c80b9 100644 --- a/wasm2c/wasm-rt-impl.h +++ b/wasm2c/wasm-rt-impl.h @@ -17,8 +17,6 @@ #ifndef WASM_RT_IMPL_H_ #define WASM_RT_IMPL_H_ -#include <setjmp.h> - #include "wasm-rt.h" #ifdef __cplusplus @@ -29,10 +27,8 @@ extern "C" { extern jmp_buf wasm_rt_jmp_buf; #if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX -#define WASM_RT_SETJMP(buf) sigsetjmp(buf, 1) #define WASM_RT_LONGJMP(buf, val) siglongjmp(buf, val) #else -#define WASM_RT_SETJMP(buf) setjmp(buf) #define WASM_RT_LONGJMP(buf, val) longjmp(buf, val) /** Saved call stack depth that will be restored in case a trap occurs. */ extern uint32_t wasm_rt_saved_call_stack_depth; @@ -55,10 +51,12 @@ extern uint32_t wasm_rt_saved_call_stack_depth; * ``` */ #if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX -#define wasm_rt_impl_try() WASM_RT_SETJMP(wasm_rt_jmp_buf) +#define wasm_rt_impl_try() \ + (wasm_rt_set_unwind_target(&wasm_rt_jmp_buf), WASM_RT_SETJMP(wasm_rt_jmp_buf)) #else #define wasm_rt_impl_try() \ (wasm_rt_saved_call_stack_depth = wasm_rt_call_stack_depth, \ + wasm_rt_set_unwind_target(&wasm_rt_jmp_buf), \ WASM_RT_SETJMP(wasm_rt_jmp_buf)) #endif diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index fed82a27..fd895453 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -17,6 +17,7 @@ #ifndef WASM_RT_H_ #define WASM_RT_H_ +#include <setjmp.h> #include <stdbool.h> #include <stdint.h> #include <string.h> @@ -115,6 +116,7 @@ typedef enum { WASM_RT_TRAP_INVALID_CONVERSION, /** Conversion from NaN to integer. */ WASM_RT_TRAP_UNREACHABLE, /** Unreachable instruction executed. */ WASM_RT_TRAP_CALL_INDIRECT, /** Invalid call_indirect, for any reason. */ + WASM_RT_TRAP_UNCAUGHT_EXCEPTION, /* Exception thrown and not caught */ #if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS WASM_RT_TRAP_EXHAUSTION = WASM_RT_TRAP_OOB, #else @@ -175,8 +177,8 @@ void wasm_rt_init(void); void wasm_rt_free(void); /** - * Stop execution immediately and jump back to the call to `wasm_rt_try`. - * The result of `wasm_rt_try` will be the provided trap reason. + * Stop execution immediately and jump back to the call to `wasm_rt_impl_try`. + * The result of `wasm_rt_impl_try` will be the provided trap reason. * * This is typically called by the generated code, and not the embedder. */ @@ -210,6 +212,54 @@ const char* wasm_rt_strerror(wasm_rt_trap_t trap); uint32_t wasm_rt_register_func_type(uint32_t params, uint32_t results, ...); /** + * Register a tag with the given size. Returns the tag. + */ +uint32_t wasm_rt_register_tag(uint32_t size); + +/** + * Set the active exception to given tag, size, and contents. + */ +void wasm_rt_load_exception(uint32_t tag, uint32_t size, const void* values); + +/** + * Throw the active exception. + */ +WASM_RT_NO_RETURN void wasm_rt_throw(void); + +/** + * Get the current unwind target if an exception is thrown. + */ +jmp_buf* wasm_rt_get_unwind_target(void); + +/** + * Set the unwind target if an exception is thrown. + */ +void wasm_rt_set_unwind_target(jmp_buf* target); + +/** + * Tag of the active exception. + */ +uint32_t wasm_rt_exception_tag(void); + +/** + * Size of the active exception. + */ +uint32_t wasm_rt_exception_size(void); + +/** + * Contents of the active exception. + */ +void* wasm_rt_exception(void); + +#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX +#define WASM_RT_SETJMP(buf) sigsetjmp(buf, 1) +#else +#define WASM_RT_SETJMP(buf) setjmp(buf) +#endif + +#define wasm_rt_try(target) WASM_RT_SETJMP(target) + +/** * Initialize a Memory object with an initial page size of `initial_pages` and * a maximum page size of `max_pages`. * |