summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--src/apply-names.cc1
-rw-r--r--src/c-writer.cc442
-rw-r--r--src/generate-names.cc6
-rw-r--r--src/stream.h1
-rw-r--r--src/template/wasm2c.declarations.c2
-rw-r--r--src/template/wasm2c.includes.c6
-rw-r--r--src/tools/wasm2c.cc5
-rwxr-xr-xtest/run-spec-wasm2c.py5
-rw-r--r--test/spec-wasm2c-prefix.c11
-rw-r--r--test/wasm2c/bad-enable-feature.txt4
-rw-r--r--test/wasm2c/spec/exception-handling/binary.txt6
-rw-r--r--test/wasm2c/spec/exception-handling/exports.txt13
-rw-r--r--test/wasm2c/spec/exception-handling/imports.txt24
-rw-r--r--test/wasm2c/spec/exception-handling/rethrow.txt6
-rw-r--r--test/wasm2c/spec/exception-handling/tag.txt6
-rw-r--r--test/wasm2c/spec/exception-handling/throw.txt6
-rw-r--r--test/wasm2c/spec/exception-handling/try_catch.txt6
-rw-r--r--test/wasm2c/spec/exception-handling/try_delegate.txt6
-rw-r--r--wasm2c/examples/fac/fac.c11
-rw-r--r--wasm2c/wasm-rt-impl.c53
-rw-r--r--wasm2c/wasm-rt-impl.h8
-rw-r--r--wasm2c/wasm-rt.h54
23 files changed, 643 insertions, 41 deletions
diff --git a/README.md b/README.md
index 706646e2..5daca95d 100644
--- a/README.md
+++ b/README.md
@@ -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`.
*