From dd44ca91986d8de2425cfb4d8e31a23c5d9e873f Mon Sep 17 00:00:00 2001 From: Yuhan Deng <31569419+yhdengh@users.noreply.github.com> Date: Fri, 16 Sep 2022 09:47:25 -0700 Subject: [PATCH] wasm2c: support for module instancing (#1814) Co-authored-by: Angela Montemayor --- src/c-writer.cc | 688 ++++++++++++++++++++++------- src/template/wasm2c.declarations.c | 2 + src/template/wasm2c.includes.c | 1 + test/run-spec-wasm2c.py | 59 ++- test/spec-wasm2c-prefix.c | 75 ++-- wasm2c/README.md | 236 ++++++++-- wasm2c/examples/fac/fac.c | 43 +- wasm2c/examples/fac/fac.h | 11 +- wasm2c/examples/fac/main.c | 15 +- wasm2c/examples/rot13/main.c | 72 +-- wasm2c/wasm-rt-impl.c | 8 + wasm2c/wasm-rt.h | 7 + 12 files changed, 949 insertions(+), 268 deletions(-) diff --git a/src/c-writer.cc b/src/c-writer.cc index 32aee51d4..0c201b12f 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -77,6 +77,8 @@ typedef Name<0> LocalName; typedef Name<1> GlobalName; typedef Name<2> ExternalPtr; typedef Name<3> ExternalRef; +typedef Name<4> ExternalInstancePtr; +typedef Name<5> ExternalInstanceRef; struct GotoLabel { explicit GotoLabel(const Var& var) : var(var) {} @@ -93,6 +95,11 @@ struct GlobalVar { const Var& var; }; +struct GlobalInstanceVar { + explicit GlobalInstanceVar(const Var& var) : var(var) {} + const Var& var; +}; + struct StackVar { explicit StackVar(Index index, Type type = Type::Any) : index(index), type(type) {} @@ -185,10 +192,18 @@ class CWriter { static std::string MangleName(std::string_view); static std::string LegalizeName(std::string_view); std::string ExportName(std::string_view mangled_name); + std::string ModuleInstanceTypeName() const; + static std::string MangleModuleInstanceName( + const std::string_view module_name); + static std::string MangleModuleInstanceTypeName( + const std::string_view module_name); std::string DefineName(SymbolSet*, std::string_view); std::string DefineImportName(const std::string& name, std::string_view module_name, std::string_view mangled_field_name); + std::string DefineImportInstanceName(const std::string& name, + std::string_view module_name, + std::string_view mangled_field_name); std::string DefineGlobalScopeName(const std::string&); std::string DefineLocalScopeName(const std::string&); std::string DefineStackVarName(Index, Type, std::string_view); @@ -211,7 +226,6 @@ class CWriter { enum class WriteExportsKind { Declarations, Definitions, - Initializers, }; void Write() {} @@ -224,6 +238,8 @@ class CWriter { void Write(const GlobalName&); void Write(const ExternalPtr&); void Write(const ExternalRef&); + void Write(const ExternalInstancePtr&); + void Write(const ExternalInstanceRef&); void Write(Type); void Write(SignedType); void Write(TypeEnum); @@ -231,6 +247,7 @@ class CWriter { void Write(const GotoLabel&); void Write(const LabelDecl&); void Write(const GlobalVar&); + void Write(const GlobalInstanceVar&); void Write(const StackVar&); void Write(const ResultType&); void Write(const Const&); @@ -241,9 +258,17 @@ class CWriter { void WriteTagTypes(); void WriteFuncTypes(); void WriteTags(); + void ComputeUniqueImports(); + void BeginInstance(); void WriteImports(); void WriteFuncDeclarations(); void WriteFuncDeclaration(const FuncDeclaration&, const std::string&); + void WriteImportFuncDeclaration(const FuncDeclaration&, + const std::string& module_name, + const std::string&); + void WriteCallIndirectFuncDeclaration(const FuncDeclaration&, + const std::string&); + void WriteModuleInstance(); void WriteGlobals(); void WriteGlobal(const Global&, const std::string&); void WriteGlobalPtr(const Global&, const std::string&); @@ -253,16 +278,21 @@ class CWriter { void WriteTables(); void WriteTable(const std::string&); void WriteTablePtr(const std::string&); + void WriteGlobalInitializers(); void WriteDataInitializers(); void WriteElemInitializers(); - void WriteInitExports(); void WriteExports(WriteExportsKind); + void WriteInitDecl(); + void WriteFreeDecl(); void WriteInit(); void WriteFree(); + void WriteInitInstanceImport(); void WriteFuncs(); void Write(const Func&); void WriteParamsAndLocals(); void WriteParams(const std::vector& index_to_name); + void WriteParamSymbols(const std::vector& index_to_name); + void WriteParamTypes(const FuncDeclaration& decl); void WriteLocals(const std::vector& index_to_name); void WriteStackVarDeclarations(); void Write(const ExprList&); @@ -317,6 +347,7 @@ class CWriter { SymbolMap global_sym_map_; SymbolMap local_sym_map_; + SymbolMap import_module_sym_map_; StackVarSymbolMap stack_var_sym_map_; SymbolSet global_syms_; SymbolSet local_syms_; @@ -326,6 +357,10 @@ class CWriter { std::vector try_catch_stack_; std::string module_prefix_; + std::vector unique_imports_; + SymbolSet import_module_set_; // modules that are imported from + SymbolSet import_func_module_set_; // modules that funcs are imported from + std::vector> func_sections_; SymbolSet func_includes_; }; @@ -467,11 +502,26 @@ std::string CWriter::MangleName(std::string_view name) { return result; } -// static std::string CWriter::ExportName(std::string_view mangled_name) { return module_prefix_ + std::string(mangled_name); } +std::string CWriter::ModuleInstanceTypeName() const { + return module_prefix_ + "_instance_t"; +} + +// static +std::string CWriter::MangleModuleInstanceName( + const std::string_view module_name) { + return MangleName(module_name) + "_instance"; +} + +// static +std::string CWriter::MangleModuleInstanceTypeName( + const std::string_view module_name) { + return MangleName(module_name) + "_instance_t"; +} + // static std::string CWriter::LegalizeName(std::string_view name) { if (name.empty()) @@ -513,8 +563,18 @@ std::string_view StripLeadingDollar(std::string_view name) { std::string CWriter::DefineImportName(const std::string& name, std::string_view module, - std::string_view mangled_field_name) { - std::string mangled = MangleName(module) + mangled_field_name; + std::string_view field_name) { + std::string mangled = MangleName(module) + MangleName(field_name); + import_syms_.insert(name); + global_syms_.insert(mangled); + global_sym_map_.insert(SymbolMap::value_type(name, mangled)); + return mangled; +} + +std::string CWriter::DefineImportInstanceName(const std::string& name, + std::string_view module, + std::string_view field_name) { + std::string mangled = MangleName(module) + MangleName(field_name); import_syms_.insert(name); global_syms_.insert(mangled); global_sym_map_.insert(SymbolMap::value_type(name, mangled)); @@ -628,6 +688,15 @@ void CWriter::Write(const ExternalPtr& name) { } } +void CWriter::Write(const ExternalInstancePtr& name) { + bool is_import = import_syms_.count(name.name) != 0; + if (is_import) { + Write("instance->", GetGlobalName(name.name)); + } else { + Write("&instance->", GetGlobalName(name.name)); + } +} + void CWriter::Write(const ExternalRef& name) { bool is_import = import_syms_.count(name.name) != 0; if (is_import) { @@ -637,6 +706,15 @@ void CWriter::Write(const ExternalRef& name) { } } +void CWriter::Write(const ExternalInstanceRef& name) { + bool is_import = import_syms_.count(name.name) != 0; + if (is_import) { + Write(Deref("instance->" + GetGlobalName(name.name))); + } else { + Write("instance->", GetGlobalName(name.name)); + } +} + void CWriter::Write(const Var& var) { assert(var.is_name()); Write(LocalName(var.name())); @@ -687,6 +765,11 @@ void CWriter::Write(const GlobalVar& var) { Write(ExternalRef(var.var.name())); } +void CWriter::Write(const GlobalInstanceVar& var) { + assert(var.var.is_name()); + Write(ExternalInstanceRef(var.var.name())); +} + void CWriter::Write(const StackVar& sv) { Index index = type_stack_.size() - 1 - sv.index; Type type = sv.type; @@ -808,6 +891,21 @@ void CWriter::Write(const Const& const_) { } } +void CWriter::WriteInitDecl() { + Write("void " + module_prefix_ + "_init_module(void);", Newline()); + Write("void " + module_prefix_ + "_instantiate(", ModuleInstanceTypeName(), + "*"); + for (auto import_module_name : import_module_set_) { + Write(", struct ", MangleModuleInstanceTypeName(import_module_name), "*"); + } + Write(");", Newline()); +} + +void CWriter::WriteFreeDecl() { + Write("void " + module_prefix_ + "_free(", ModuleInstanceTypeName(), "*);", + Newline()); +} + void CWriter::WriteInitExpr(const ExprList& expr_list) { if (expr_list.empty()) return; @@ -820,7 +918,7 @@ void CWriter::WriteInitExpr(const ExprList& expr_list) { break; case ExprType::GlobalGet: - Write(GlobalVar(cast(expr)->var)); + Write(GlobalInstanceVar(cast(expr)->var)); break; default: @@ -960,57 +1058,151 @@ void CWriter::WriteTags() { Write(CloseBrace(), Newline()); } -void CWriter::WriteImports() { - if (module_->imports.empty()) +void CWriter::ComputeUniqueImports() { + using modname_name_pair = std::pair; + std::map import_map; + for (const Import* import : module_->imports) { + // After emplacing, the returned bool says whether the insert happened; + // i.e., was there already an import with the same modname and name? + // If there was, make sure it was at least the same kind of import. + const auto iterator_and_insertion_bool = import_map.emplace( + modname_name_pair(import->module_name, import->field_name), import); + if (!iterator_and_insertion_bool.second) { + if (iterator_and_insertion_bool.first->second->kind() != import->kind()) { + UNIMPLEMENTED("contradictory import declaration"); + } else { + fprintf(stderr, "warning: duplicate import declaration \"%s\" \"%s\"\n", + import->module_name.c_str(), import->field_name.c_str()); + } + } + import_module_set_.insert(import->module_name); + if (import->kind() == ExternalKind::Func) { + import_func_module_set_.insert(import->module_name); + } + } + + for (const auto& node : import_map) { + unique_imports_.push_back(node.second); + } +} + +void CWriter::BeginInstance() { + if (module_->imports.empty()) { + Write("typedef struct ", ModuleInstanceTypeName(), " ", OpenBrace()); return; + } - Write(Newline()); + ComputeUniqueImports(); - // TODO(binji): Write imports ordered by type. + // define names of per-instance imports for (const Import* import : module_->imports) { - Write("/* import: '", import->module_name, "' '", import->field_name, - "' */", Newline()); - Write("extern "); switch (import->kind()) { case ExternalKind::Func: { const Func& func = cast(import)->func; - WriteFuncDeclaration(func.decl, - DefineImportName(func.name, import->module_name, - MangleName(import->field_name))); - Write(";"); + DefineImportName(func.name, import->module_name, import->field_name); + import_module_sym_map_.emplace(func.name, import->module_name); + } break; + + case ExternalKind::Tag: { + const Tag& tag = cast(import)->tag; + DefineImportName(tag.name, import->module_name, import->field_name); + import_module_sym_map_.emplace(tag.name, import->module_name); + } break; + + case ExternalKind::Global: + DefineImportInstanceName(cast(import)->global.name, + import->module_name, import->field_name); break; - } + case ExternalKind::Memory: + DefineImportInstanceName(cast(import)->memory.name, + import->module_name, import->field_name); + break; + + case ExternalKind::Table: + DefineImportInstanceName(cast(import)->table.name, + import->module_name, import->field_name); + break; + + default: + WABT_UNREACHABLE; + } + } + + // Forward declaring module instance types + for (auto import_module : import_module_set_) { + Write("struct ", MangleModuleInstanceTypeName(import_module), ";", + Newline()); + } + + // Forward declaring module imports + for (const Import* import : unique_imports_) { + if ((import->kind() == ExternalKind::Func) || + (import->kind() == ExternalKind::Tag)) { + continue; + } + + Write("extern "); + switch (import->kind()) { case ExternalKind::Global: { const Global& global = cast(import)->global; - WriteGlobal(global, DefineImportName(global.name, import->module_name, - MangleName(import->field_name))); - Write(";"); + Write(global.type); break; } case ExternalKind::Memory: { - const Memory& memory = cast(import)->memory; - WriteMemory(DefineImportName(memory.name, import->module_name, - MangleName(import->field_name))); + Write("wasm_rt_memory_t"); break; } case ExternalKind::Table: { - const Table& table = cast(import)->table; - WriteTable(DefineImportName(table.name, import->module_name, - MangleName(import->field_name))); + Write("wasm_rt_table_t"); break; } - case ExternalKind::Tag: { - const Tag& tag = cast(import)->tag; - Write("const u32 ", - DefineImportName(tag.name, import->module_name, - MangleName(import->field_name)), - ";"); + default: + WABT_UNREACHABLE; + } + Write("* ", MangleName(import->module_name), MangleName(import->field_name), + "(struct ", MangleModuleInstanceTypeName(import->module_name), "*);", + Newline()); + } + Write(Newline()); + + // Add pointers to module instances that any func is imported from, + // so that imported functions can be given their own module instances + // when invoked + Write("typedef struct ", ModuleInstanceTypeName(), " ", OpenBrace()); + for (auto import_module : import_func_module_set_) { + Write("struct ", MangleModuleInstanceTypeName(import_module), "* ", + MangleModuleInstanceName(import_module) + ";", Newline()); + } + + for (const Import* import : unique_imports_) { + if ((import->kind() == ExternalKind::Func) || + (import->kind() == ExternalKind::Tag)) { + continue; + } + + Write("/* import: '", import->module_name, "' '", import->field_name, + "' */", Newline()); + + switch (import->kind()) { + case ExternalKind::Global: + WriteGlobal(cast(import)->global, + "*" + MangleName(import->module_name) + + MangleName(import->field_name)); + break; + + case ExternalKind::Memory: + WriteMemory("*" + MangleName(import->module_name) + + MangleName(import->field_name)); + break; + + case ExternalKind::Table: + WriteTable("*" + MangleName(import->module_name) + + MangleName(import->field_name)); break; - } default: WABT_UNREACHABLE; @@ -1020,6 +1212,32 @@ void CWriter::WriteImports() { } } +// Write module-wide imports (funcs & tags), which aren't tied to an instance. +void CWriter::WriteImports() { + if (module_->imports.empty()) + return; + + Write(Newline()); + + for (const Import* import : unique_imports_) { + if (import->kind() == ExternalKind::Func) { + Write("/* import: '", import->module_name, "' '", import->field_name, + "' */", Newline()); + const Func& func = cast(import)->func; + WriteImportFuncDeclaration( + func.decl, import->module_name, + MangleName(import->module_name) + MangleName(import->field_name)); + Write(";"); + Write(Newline()); + } else if (import->kind() == ExternalKind::Tag) { + Write("/* import: '", import->module_name, "' '", import->field_name, + "' */", Newline()); + Write("extern const u32 *", MangleName(import->module_name), + MangleName(import->field_name), ";", Newline()); + } + } +} + void CWriter::WriteFuncDeclarations() { if (module_->funcs.size() == module_->num_func_imports) return; @@ -1041,68 +1259,73 @@ void CWriter::WriteFuncDeclarations() { void CWriter::WriteFuncDeclaration(const FuncDeclaration& decl, const std::string& name) { Write(ResultType(decl.sig.result_types), " ", name, "("); - if (decl.GetNumParams() == 0) { - Write("void"); - } else { - for (Index i = 0; i < decl.GetNumParams(); ++i) { - if (i != 0) - Write(", "); - Write(decl.GetParamType(i)); - } - } + Write(ModuleInstanceTypeName(), "*"); + WriteParamTypes(decl); + Write(")"); +} + +void CWriter::WriteImportFuncDeclaration(const FuncDeclaration& decl, + const std::string& module_name, + const std::string& name) { + Write(ResultType(decl.sig.result_types), " ", name, "("); + Write("struct ", MangleModuleInstanceTypeName(module_name), "*"); + WriteParamTypes(decl); + Write(")"); +} + +void CWriter::WriteCallIndirectFuncDeclaration(const FuncDeclaration& decl, + const std::string& name) { + Write(ResultType(decl.sig.result_types), " ", name, "(void*"); + WriteParamTypes(decl); Write(")"); } +void CWriter::WriteModuleInstance() { + BeginInstance(); + WriteGlobals(); + WriteMemories(); + WriteTables(); + + // C forbids an empty struct + if (module_->globals.empty() && module_->memories.empty() && + module_->tables.empty()) { + Write("char dummy_member;", Newline()); + } + + Write(CloseBrace(), " ", ModuleInstanceTypeName(), ";", Newline()); + Write(Newline()); +} + void CWriter::WriteGlobals() { Index global_index = 0; if (module_->globals.size() != module_->num_global_imports) { - Write(Newline()); - for (const Global* global : module_->globals) { bool is_import = global_index < module_->num_global_imports; if (!is_import) { - Write("static "); WriteGlobal(*global, DefineGlobalScopeName(global->name)); - Write(";", Newline()); + Write(Newline()); } ++global_index; } } - - Write(Newline(), "static void init_globals(void) ", OpenBrace()); - global_index = 0; - for (const Global* global : module_->globals) { - bool is_import = global_index < module_->num_global_imports; - if (!is_import) { - assert(!global->init_expr.empty()); - Write(GlobalName(global->name), " = "); - WriteInitExpr(global->init_expr); - Write(";", Newline()); - } - ++global_index; - } - Write(CloseBrace(), Newline()); } void CWriter::WriteGlobal(const Global& global, const std::string& name) { - Write(global.type, " ", name); + Write(global.type, " ", name, ";"); } void CWriter::WriteGlobalPtr(const Global& global, const std::string& name) { - Write(global.type, "* ", name); + Write(global.type, "* ", name, "(", ModuleInstanceTypeName(), "* instance)"); } void CWriter::WriteMemories() { if (module_->memories.size() == module_->num_memory_imports) return; - Write(Newline()); - Index memory_index = 0; for (const Memory* memory : module_->memories) { bool is_import = memory_index < module_->num_memory_imports; if (!is_import) { - Write("static "); WriteMemory(DefineGlobalScopeName(memory->name)); Write(Newline()); } @@ -1115,7 +1338,8 @@ void CWriter::WriteMemory(const std::string& name) { } void CWriter::WriteMemoryPtr(const std::string& name) { - Write("wasm_rt_memory_t* ", name, ";"); + Write("wasm_rt_memory_t* ", name, "(", ModuleInstanceTypeName(), + "* instance)"); } void CWriter::WriteTables() { @@ -1123,14 +1347,11 @@ void CWriter::WriteTables() { return; } - Write(Newline()); - assert(module_->tables.size() <= 1); Index table_index = 0; for (const Table* table : module_->tables) { bool is_import = table_index < module_->num_table_imports; if (!is_import) { - Write("static "); WriteTable(DefineGlobalScopeName(table->name)); Write(Newline()); } @@ -1143,7 +1364,25 @@ void CWriter::WriteTable(const std::string& name) { } void CWriter::WriteTablePtr(const std::string& name) { - Write("wasm_rt_table_t* ", name, ";"); + Write("wasm_rt_table_t* ", name, "(", ModuleInstanceTypeName(), + "* instance)"); +} + +void CWriter::WriteGlobalInitializers() { + Write(Newline(), "static void init_globals(", ModuleInstanceTypeName(), + "* instance) ", OpenBrace()); + Index global_index = 0; + for (const Global* global : module_->globals) { + bool is_import = global_index < module_->num_global_imports; + if (!is_import) { + assert(!global->init_expr.empty()); + Write(ExternalInstanceRef(global->name), " = "); + WriteInitExpr(global->init_expr); + Write(";", Newline()); + } + ++global_index; + } + Write(CloseBrace(), Newline()); } void CWriter::WriteDataInitializers() { @@ -1178,20 +1417,21 @@ void CWriter::WriteDataInitializers() { memory = module_->memories[0]; } - Write(Newline(), "static void init_memory(void) ", OpenBrace()); + Write("static void init_memory(", ModuleInstanceTypeName(), "* instance) ", + OpenBrace()); if (module_->memories.size() > module_->num_memory_imports) { Index memory_idx = module_->num_memory_imports; for (Index i = memory_idx; i < module_->memories.size(); i++) { memory = module_->memories[i]; uint32_t max = memory->page_limits.has_max ? memory->page_limits.max : 65536; - Write("wasm_rt_allocate_memory(", ExternalPtr(memory->name), ", ", + Write("wasm_rt_allocate_memory(", ExternalInstancePtr(memory->name), ", ", memory->page_limits.initial, ", ", max, ");", Newline()); } } data_segment_index = 0; for (const DataSegment* data_segment : module_->data_segments) { - Write("LOAD_DATA(", ExternalRef(memory->name), ", "); + Write("LOAD_DATA(", ExternalInstanceRef(memory->name), ", "); WriteInitExpr(data_segment->offset); Write(", data_segment_data_", data_segment_index, ", ", data_segment->data.size()); @@ -1203,7 +1443,8 @@ void CWriter::WriteDataInitializers() { } void CWriter::WriteElemInitializers() { - Write(Newline(), "static void init_table(void) ", OpenBrace()); + Write(Newline(), "static void init_table(", ModuleInstanceTypeName(), + "* instance) ", OpenBrace()); if (!module_->types.size()) { // If there are no types there cannot be any table entries either. @@ -1219,7 +1460,7 @@ void CWriter::WriteElemInitializers() { if (table && module_->num_table_imports == 0) { uint32_t max = table->elem_limits.has_max ? table->elem_limits.max : UINT32_MAX; - Write("wasm_rt_allocate_table(", ExternalPtr(table->name), ", ", + Write("wasm_rt_allocate_table(", ExternalInstancePtr(table->name), ", ", table->elem_limits.initial, ", ", max, ");", Newline()); } Index elem_segment_index = 0; @@ -1241,9 +1482,20 @@ void CWriter::WriteElemInitializers() { const Func* func = module_->GetFunc(cast(expr)->var); Index func_type_index = module_->GetFuncTypeIndex(func->decl.type_var); - Write(ExternalRef(table->name), ".data[offset + ", i, - "] = (wasm_rt_elem_t){func_types[", func_type_index, - "], (wasm_rt_funcref_t)", ExternalPtr(func->name), "};", Newline()); + bool is_import = import_module_sym_map_.count(func->name) != 0; + if (is_import) { + Write(ExternalInstanceRef(table->name), ".data[offset + ", i, + "] = (wasm_rt_elem_t){func_types[", func_type_index, + "], (wasm_rt_funcref_t)", ExternalPtr(func->name), + ", (void*)(instance->", + MangleModuleInstanceName(import_module_sym_map_[func->name]), + ")};", Newline()); + } else { + Write(ExternalInstanceRef(table->name), ".data[offset + ", i, + "] = (wasm_rt_elem_t){func_types[", func_type_index, + "], (wasm_rt_funcref_t)", ExternalPtr(func->name), + ", (void*)instance};", Newline()); + } ++i; } ++elem_segment_index; @@ -1252,37 +1504,34 @@ void CWriter::WriteElemInitializers() { Write(CloseBrace(), Newline()); } -void CWriter::WriteInitExports() { - Write(Newline(), "static void init_exports(void) ", OpenBrace()); - WriteExports(WriteExportsKind::Initializers); - Write(CloseBrace(), Newline()); -} - void CWriter::WriteExports(WriteExportsKind kind) { if (module_->exports.empty()) return; - if (kind != WriteExportsKind::Initializers) { - Write(Newline()); - } - for (const Export* export_ : module_->exports) { - Write("/* export: '", export_->name, "' */", Newline()); - if (kind == WriteExportsKind::Declarations) { - Write("extern "); - } + Write(Newline(), "/* export: '", export_->name, "' */", Newline()); std::string mangled_name; std::string internal_name; + std::vector index_to_name; switch (export_->kind) { case ExternalKind::Func: { const Func* func = module_->GetFunc(export_->var); mangled_name = ExportName(MangleName(export_->name)); internal_name = func->name; - if (kind != WriteExportsKind::Initializers) { - WriteFuncDeclaration(func->decl, Deref(mangled_name)); - Write(";"); + if (kind == WriteExportsKind::Declarations) { + WriteFuncDeclaration(func->decl, mangled_name); + } else { + func_ = func; + local_syms_ = global_syms_; + local_sym_map_.clear(); + stack_var_sym_map_.clear(); + Write(ResultType(func_->decl.sig.result_types), " ", mangled_name, + "("); + MakeTypeBindingReverseMapping(func_->GetNumParamsAndLocals(), + func_->bindings, &index_to_name); + WriteParams(index_to_name); } break; } @@ -1291,10 +1540,7 @@ void CWriter::WriteExports(WriteExportsKind kind) { const Global* global = module_->GetGlobal(export_->var); mangled_name = ExportName(MangleName(export_->name)); internal_name = global->name; - if (kind != WriteExportsKind::Initializers) { - WriteGlobalPtr(*global, mangled_name); - Write(";"); - } + WriteGlobalPtr(*global, mangled_name); break; } @@ -1302,9 +1548,7 @@ void CWriter::WriteExports(WriteExportsKind kind) { const Memory* memory = module_->GetMemory(export_->var); mangled_name = ExportName(MangleName(export_->name)); internal_name = memory->name; - if (kind != WriteExportsKind::Initializers) { - WriteMemoryPtr(mangled_name); - } + WriteMemoryPtr(mangled_name); break; } @@ -1312,9 +1556,7 @@ void CWriter::WriteExports(WriteExportsKind kind) { const Table* table = module_->GetTable(export_->var); mangled_name = ExportName(MangleName(export_->name)); internal_name = table->name; - if (kind != WriteExportsKind::Initializers) { - WriteTablePtr(mangled_name); - } + WriteTablePtr(mangled_name); break; } @@ -1322,9 +1564,10 @@ void CWriter::WriteExports(WriteExportsKind kind) { const Tag* tag = module_->GetTag(export_->var); mangled_name = ExportName(MangleName(export_->name)); internal_name = tag->name; - if (kind != WriteExportsKind::Initializers) { - Write("const u32 *", mangled_name, ";"); + if (kind == WriteExportsKind::Declarations) { + Write("extern "); } + Write("const u32 *", mangled_name); break; } @@ -1332,30 +1575,145 @@ void CWriter::WriteExports(WriteExportsKind kind) { WABT_UNREACHABLE; } - if (kind == WriteExportsKind::Initializers) { - Write(mangled_name, " = ", ExternalPtr(internal_name), ";"); + if (kind == WriteExportsKind::Declarations) { + Write(";"); + continue; } - Write(Newline()); + Write(" "); + switch (export_->kind) { + case ExternalKind::Func: { + Write(OpenBrace()); + Write("return ", ExternalRef(internal_name), "("); + + bool is_import = import_module_sym_map_.count(internal_name) != 0; + if (is_import) { + Write("instance->", MangleModuleInstanceName( + import_module_sym_map_[internal_name])); + } else { + Write("instance"); + } + WriteParamSymbols(index_to_name); + Write(CloseBrace(), Newline()); + + local_sym_map_.clear(); + stack_var_sym_map_.clear(); + func_ = nullptr; + break; + } + + case ExternalKind::Global: + case ExternalKind::Memory: + case ExternalKind::Table: { + Write(OpenBrace()); + Write("return ", ExternalInstancePtr(internal_name), ";", Newline()); + Write(CloseBrace(), Newline()); + break; + } + + case ExternalKind::Tag: + Write("= ", ExternalPtr(internal_name), ";", Newline()); + break; + + default: + WABT_UNREACHABLE; + } } } void CWriter::WriteInit() { - Write(Newline(), "void ", module_prefix_, "_init(void) ", OpenBrace()); + Write(Newline(), "void " + module_prefix_ + "_init_module(void) ", + OpenBrace()); + Write("assert(wasm_rt_is_initialized());", Newline()); + Write("s_module_initialized = true;", Newline()); Write("init_func_types();", Newline()); Write("init_tags();", Newline()); - Write("init_globals();", Newline()); - Write("init_memory();", Newline()); - Write("init_table();", Newline()); - Write("init_exports();", Newline()); + Write(CloseBrace(), Newline()); + + Write(Newline(), "void " + module_prefix_ + "_instantiate(", + ModuleInstanceTypeName(), "* instance"); + for (auto import_module_name : import_module_set_) { + Write(", struct ", MangleModuleInstanceTypeName(import_module_name), "* ", + MangleModuleInstanceName(import_module_name)); + } + Write(") ", OpenBrace()); + + Write("assert(wasm_rt_is_initialized());", Newline()); + Write("assert(s_module_initialized);", Newline()); + + if (!module_->imports.empty()) { + Write("init_instance_import(instance"); + for (auto import_module_name : import_module_set_) { + Write(", ", MangleModuleInstanceName(import_module_name)); + } + Write(");", Newline()); + } + + Write("init_globals(instance);", Newline()); + Write("init_memory(instance);", Newline()); + Write("init_table(instance);", Newline()); for (Var* var : module_->starts) { - Write(ExternalRef(module_->GetFunc(*var)->name), "();", Newline()); + Write(ExternalRef(module_->GetFunc(*var)->name)); + bool is_import = + import_module_sym_map_.count(module_->GetFunc(*var)->name) != 0; + if (is_import) { + Write("(instance->", + MangleModuleInstanceName( + import_module_sym_map_[module_->GetFunc(*var)->name]) + + ");"); + } else { + Write("(instance);"); + } + Write(Newline()); + } + Write(CloseBrace(), Newline()); +} + +void CWriter::WriteInitInstanceImport() { + if (module_->imports.empty()) + return; + + Write(Newline(), "static void init_instance_import(", + ModuleInstanceTypeName(), "* instance"); + for (auto import_module_name : import_module_set_) { + Write(", struct ", MangleModuleInstanceTypeName(import_module_name), "* ", + MangleModuleInstanceName(import_module_name)); + } + Write(")", OpenBrace()); + + for (auto import_module : import_func_module_set_) { + Write("instance->", MangleModuleInstanceName(import_module), " = ", + MangleModuleInstanceName(import_module), ";", Newline()); + } + + for (const Import* import : unique_imports_) { + switch (import->kind()) { + case ExternalKind::Func: + case ExternalKind::Tag: + break; + + case ExternalKind::Global: + case ExternalKind::Memory: + case ExternalKind::Table: { + Write("instance->", + MangleName(import->module_name) + MangleName(import->field_name), + " = ", + MangleName(import->module_name) + MangleName(import->field_name), + "(", MangleModuleInstanceName(import->module_name), ");", + Newline()); + break; + } + + default: + WABT_UNREACHABLE; + } } Write(CloseBrace(), Newline()); } void CWriter::WriteFree() { - Write(Newline(), "void " + module_prefix_ + "_free(void) ", OpenBrace()); + Write(Newline(), "void " + module_prefix_ + "_free(", + ModuleInstanceTypeName(), "* instance) ", OpenBrace()); if (module_->types.size()) { // If there are no types there cannot be any table entries either. @@ -1364,7 +1722,8 @@ void CWriter::WriteFree() { for (const Table* table : module_->tables) { bool is_import = table_index < module_->num_table_imports; if (!is_import) { - Write("wasm_rt_free_table(", ExternalPtr(table->name), ");", Newline()); + Write("wasm_rt_free_table(", ExternalInstancePtr(table->name), ");", + Newline()); } ++table_index; } @@ -1375,7 +1734,7 @@ void CWriter::WriteFree() { for (const Memory* memory : module_->memories) { bool is_import = memory_index < module_->num_memory_imports; if (!is_import) { - Write("wasm_rt_free_memory(", ExternalPtr(memory->name), ");", + Write("wasm_rt_free_memory(", ExternalInstancePtr(memory->name), ");", Newline()); } ++memory_index; @@ -1463,26 +1822,49 @@ void CWriter::WriteParamsAndLocals() { MakeTypeBindingReverseMapping(func_->GetNumParamsAndLocals(), func_->bindings, &index_to_name); WriteParams(index_to_name); + Write(" ", OpenBrace()); WriteLocals(index_to_name); } void CWriter::WriteParams(const std::vector& index_to_name) { - if (func_->GetNumParams() == 0) { - Write("void"); - } else { + Write(ModuleInstanceTypeName(), "* instance"); + if (func_->GetNumParams() != 0) { Indent(4); for (Index i = 0; i < func_->GetNumParams(); ++i) { - if (i != 0) { - Write(", "); - if ((i % 8) == 0) - Write(Newline()); + Write(", "); + if (i != 0 && (i % 8) == 0) { + Write(Newline()); } Write(func_->GetParamType(i), " ", DefineLocalScopeName(index_to_name[i])); } Dedent(4); } - Write(") ", OpenBrace()); + Write(")"); +} + +void CWriter::WriteParamSymbols(const std::vector& index_to_name) { + if (func_->GetNumParams() != 0) { + Indent(4); + for (Index i = 0; i < func_->GetNumParams(); ++i) { + Write(", "); + if (i != 0 && (i % 8) == 0) { + Write(Newline()); + } + Write(local_sym_map_[index_to_name[i]]); + } + Dedent(4); + } + Write(");", Newline()); +} + +void CWriter::WriteParamTypes(const FuncDeclaration& decl) { + if (decl.GetNumParams() != 0) { + for (Index i = 0; i < decl.GetNumParams(); ++i) { + Write(", "); + Write(decl.GetParamType(i)); + } + } } void CWriter::WriteLocals(const std::vector& index_to_name) { @@ -1786,10 +2168,15 @@ void CWriter::Write(const ExprList& exprs) { } Write(GlobalVar(var), "("); + bool is_import = import_module_sym_map_.count(func.name) != 0; + if (is_import) { + Write("instance->", + MangleModuleInstanceName(import_module_sym_map_[func.name])); + } else { + Write("instance"); + } for (Index i = 0; i < num_params; ++i) { - if (i != 0) { - Write(", "); - } + Write(", "); Write(StackVar(num_params - i - 1)); } Write(");", Newline()); @@ -1828,9 +2215,11 @@ void CWriter::Write(const ExprList& exprs) { assert(decl.has_func_type); Index func_type_index = module_->GetFuncTypeIndex(decl.type_var); - Write("CALL_INDIRECT(", ExternalRef(table->name), ", "); - WriteFuncDeclaration(decl, "(*)"); + Write("CALL_INDIRECT(", ExternalInstanceRef(table->name), ", "); + WriteCallIndirectFuncDeclaration(decl, "(*)"); Write(", ", func_type_index, ", ", StackVar(0)); + Write(", ", ExternalInstanceRef(table->name), ".data[", StackVar(0), + "].module_instance"); for (Index i = 0; i < num_params; ++i) { Write(", ", StackVar(num_params - i)); } @@ -1876,13 +2265,13 @@ void CWriter::Write(const ExprList& exprs) { case ExprType::GlobalGet: { const Var& var = cast(&expr)->var; PushType(module_->GetGlobal(var)->type); - Write(StackVar(0), " = ", GlobalVar(var), ";", Newline()); + Write(StackVar(0), " = ", GlobalInstanceVar(var), ";", Newline()); break; } case ExprType::GlobalSet: { const Var& var = cast(&expr)->var; - Write(GlobalVar(var), " = ", StackVar(0), ";", Newline()); + Write(GlobalInstanceVar(var), " = ", StackVar(0), ";", Newline()); DropTypes(1); break; } @@ -1973,8 +2362,9 @@ void CWriter::Write(const ExprList& exprs) { Memory* memory = module_->memories[module_->GetMemoryIndex( cast(&expr)->memidx)]; - Write(StackVar(0), " = wasm_rt_grow_memory(", ExternalPtr(memory->name), - ", ", StackVar(0), ");", Newline()); + Write(StackVar(0), " = wasm_rt_grow_memory(", + ExternalInstancePtr(memory->name), ", ", StackVar(0), ");", + Newline()); break; } @@ -1983,7 +2373,7 @@ void CWriter::Write(const ExprList& exprs) { cast(&expr)->memidx)]; PushType(Type::I32); - Write(StackVar(0), " = ", ExternalRef(memory->name), ".pages;", + Write(StackVar(0), " = ", ExternalInstanceRef(memory->name), ".pages;", Newline()); break; } @@ -2536,8 +2926,8 @@ void CWriter::Write(const LoadExpr& expr) { Memory* memory = module_->memories[module_->GetMemoryIndex(expr.memidx)]; Type result_type = expr.opcode.GetResultType(); - Write(StackVar(0, result_type), " = ", func, "(", ExternalPtr(memory->name), - ", (u64)(", StackVar(0), ")"); + Write(StackVar(0, result_type), " = ", func, "(", + ExternalInstancePtr(memory->name), ", (u64)(", StackVar(0), ")"); if (expr.offset != 0) Write(" + ", expr.offset, "u"); Write(");", Newline()); @@ -2564,7 +2954,8 @@ void CWriter::Write(const StoreExpr& expr) { Memory* memory = module_->memories[module_->GetMemoryIndex(expr.memidx)]; - Write(func, "(", ExternalPtr(memory->name), ", (u64)(", StackVar(1), ")"); + Write(func, "(", ExternalInstancePtr(memory->name), ", (u64)(", StackVar(1), + ")"); if (expr.offset != 0) Write(" + ", expr.offset); Write(", ", StackVar(0), ");", Newline()); @@ -2750,7 +3141,7 @@ void CWriter::Write(const LoadSplatExpr& expr) { Type result_type = expr.opcode.GetResultType(); Write(StackVar(0, result_type), " = ", expr.opcode.GetName(), "(", - ExternalPtr(memory->name), ", (u64)(", StackVar(0)); + ExternalInstancePtr(memory->name), ", (u64)(", StackVar(0)); if (expr.offset != 0) Write(" + ", expr.offset); Write("));", Newline()); @@ -2771,12 +3162,13 @@ void CWriter::WriteCHeader() { Write(Newline()); Write(s_header_top); Write(Newline()); + WriteModuleInstance(); + WriteInitDecl(); + WriteFreeDecl(); WriteMultivalueTypes(); WriteImports(); - Write("void ", module_prefix_, "_init(void);", Newline()); - Write("void ", module_prefix_, "_free(void);", Newline()); WriteExports(WriteExportsKind::Declarations); - Write(s_header_bottom); + Write(Newline(), s_header_bottom); Write(Newline(), "#endif /* ", guard, " */", Newline()); } @@ -2788,14 +3180,12 @@ void CWriter::WriteCSource() { WriteTagTypes(); WriteTags(); WriteFuncDeclarations(); - WriteGlobals(); - WriteMemories(); - WriteTables(); WriteFuncs(); + WriteGlobalInitializers(); WriteDataInitializers(); WriteElemInitializers(); WriteExports(WriteExportsKind::Definitions); - WriteInitExports(); + WriteInitInstanceImport(); WriteInit(); WriteFree(); } diff --git a/src/template/wasm2c.declarations.c b/src/template/wasm2c.declarations.c index f036ba191..4459790f7 100644 --- a/src/template/wasm2c.declarations.c +++ b/src/template/wasm2c.declarations.c @@ -443,3 +443,5 @@ static float wasm_sqrtf(float x) { } return sqrtf(x); } + +static bool s_module_initialized = false; diff --git a/src/template/wasm2c.includes.c b/src/template/wasm2c.includes.c index 8b61e6436..59def29c8 100644 --- a/src/template/wasm2c.includes.c +++ b/src/template/wasm2c.includes.c @@ -1,3 +1,4 @@ +#include #include #include #if defined(_MSC_VER) diff --git a/test/run-spec-wasm2c.py b/test/run-spec-wasm2c.py index 3660f804a..e32c90781 100755 --- a/test/run-spec-wasm2c.py +++ b/test/run-spec-wasm2c.py @@ -122,10 +122,10 @@ def __init__(self, spec_json, prefix, out_file, out_dir): self.module_name_to_idx = {} self.module_prefix_map = {} self.unmangled_names = {} - - def Write(self): self._MaybeWriteDummyModule() self._CacheModulePrefixes() + + def Write(self): self._WriteIncludes() self.out_file.write(self.prefix) self.out_file.write("\nvoid run_spec_tests(void) {\n\n") @@ -145,6 +145,9 @@ def GetModulePrefix(self, idx_or_name=None): def GetModulePrefixUnmangled(self, idx): return self.unmangled_names[idx] + def GetModuleInstanceName(self, idx_or_name=None): + return self.GetModulePrefix() + '_instance' + def _CacheModulePrefixes(self): idx = 0 for command in self.commands: @@ -173,6 +176,29 @@ def _CacheModulePrefixes(self): self.module_prefix_map[name_idx] = name self.unmangled_names[name_idx] = command['as'] + def _WriteModuleInitCall(self, command, uninstantiable): + header_filename = utils.ChangeExt(command['filename'], '.h') + with open(os.path.join(self.out_dir, header_filename), encoding='utf-8') as f: + imported_modules = set() + for line in f: + if 'import: ' in line: + line_split = line.split() + import_module_name = MangleName(line_split[2][1:-1]) + imported_modules.add(import_module_name) + + if uninstantiable: + self.out_file.write('ASSERT_TRAP(') + + self.out_file.write('%s_instantiate(&%s_instance' % (self.GetModulePrefix(), self.GetModulePrefix())) + for imported_module in sorted(imported_modules): + self.out_file.write(', &%s_instance' % imported_module) + self.out_file.write(')') + + if uninstantiable: + self.out_file.write(')') + + self.out_file.write(';\n') + def _MaybeWriteDummyModule(self): if len(self.GetModuleFilenames()) == 0: # This test doesn't have any valid modules, so just use a dummy instead. @@ -212,15 +238,19 @@ def _WriteCommand(self, command): def _WriteModuleCommand(self, command): self.module_idx += 1 - self.out_file.write('%s_init();\n' % self.GetModulePrefix()) + self.out_file.write('%s_init_module();\n' % self.GetModulePrefix()) + self.out_file.write('%s_instance_t %s;\n' % (self.GetModulePrefix(), self.GetModuleInstanceName())) + self._WriteModuleInitCall(command, False) def _WriteModuleCleanUps(self): for idx in range(self.module_idx): - self.out_file.write("%s_free();\n" % self.GetModulePrefix(idx)) + self.out_file.write("%s_free(&%s_instance);\n" % (self.GetModulePrefix(idx), self.GetModulePrefix(idx))) def _WriteAssertUninstantiableCommand(self, command): self.module_idx += 1 - self.out_file.write('ASSERT_TRAP(%s_init());\n' % self.GetModulePrefix()) + self.out_file.write('%s_init_module();\n' % self.GetModulePrefix()) + self.out_file.write('%s_instance_t %s;\n' % (self.GetModulePrefix(), self.GetModuleInstanceName())) + self._WriteModuleInitCall(command, True) def _WriteActionCommand(self, command): self.out_file.write('%s;\n' % self._Action(command)) @@ -326,9 +356,14 @@ def _Action(self, command): mangled_module_name = self.GetModulePrefix(action.get('module')) field = mangled_module_name + MangleName(action['field']) if type_ == 'invoke': - return '%s(%s)' % (field, self._ConstantList(action.get('args', []))) + args = self._ConstantList(action.get('args', [])) + if len(args) == 0: + args = f'&{mangled_module_name}_instance' + else: + args = f'&{mangled_module_name}_instance, {args}' + return '%s(%s)' % (field, args) elif type_ == 'get': - return '*%s' % field + return '*%s(%s)' % (field, '&' + mangled_module_name + '_instance') else: raise Error('Unexpected action type: %s' % type_) @@ -460,11 +495,6 @@ def main(args): output = io.StringIO() cwriter = CWriter(spec_json, prefix, output, out_dir) - cwriter.Write() - - main_filename = utils.ChangeExt(json_file_path, '-main.c') - with open(main_filename, 'w') as out_main_file: - out_main_file.write(output.getvalue()) o_filenames = [] includes = '-I%s' % options.wasmrt_dir @@ -477,6 +507,11 @@ def main(args): if options.compile: o_filenames.append(Compile(cc, c_filename, out_dir, includes)) + cwriter.Write() + main_filename = utils.ChangeExt(json_file_path, '-main.c') + with open(main_filename, 'w') as out_main_file: + out_main_file.write(output.getvalue()) + if options.compile: # Compile wasm-rt-impl. wasm_rt_impl_c = os.path.join(options.wasmrt_dir, 'wasm-rt-impl.c') diff --git a/test/spec-wasm2c-prefix.c b/test/spec-wasm2c-prefix.c index 8e5705885..9ef7be16c 100644 --- a/test/spec-wasm2c-prefix.c +++ b/test/spec-wasm2c-prefix.c @@ -213,59 +213,82 @@ static bool is_arithmetic_nan_f64(u64 x) { return (x & 0x7ff8000000000000) == 0x7ff8000000000000; } +typedef struct Z_spectest_instance_t { + wasm_rt_table_t spectest_table; + wasm_rt_memory_t spectest_memory; + uint32_t spectest_global_i32; + uint64_t spectest_global_i64; + float spectest_global_f32; + double spectest_global_f64; +} Z_spectest_instance_t; + +static Z_spectest_instance_t Z_spectest_instance; /* * spectest implementations */ -static void spectest_print(void) { +void Z_spectestZ_print(Z_spectest_instance_t* instance) { printf("spectest.print()\n"); } -static void spectest_print_i32(uint32_t i) { +void Z_spectestZ_print_i32(Z_spectest_instance_t* instance, uint32_t i) { printf("spectest.print_i32(%d)\n", i); } -static void spectest_print_f32(float f) { +void Z_spectestZ_print_f32(Z_spectest_instance_t* instance, float f) { printf("spectest.print_f32(%g)\n", f); } -static void spectest_print_i32_f32(uint32_t i, float f) { +void Z_spectestZ_print_i32_f32(Z_spectest_instance_t* instance, + uint32_t i, + float f) { printf("spectest.print_i32_f32(%d %g)\n", i, f); } -static void spectest_print_f64(double d) { +void Z_spectestZ_print_f64(Z_spectest_instance_t* instance, double d) { printf("spectest.print_f64(%g)\n", d); } -static void spectest_print_f64_f64(double d1, double d2) { +void Z_spectestZ_print_f64_f64(Z_spectest_instance_t* instance, + double d1, + double d2) { printf("spectest.print_f64_f64(%g %g)\n", d1, d2); } -static wasm_rt_table_t spectest_table; -static wasm_rt_memory_t spectest_memory; -static uint32_t spectest_global_i32 = 666; -static uint64_t spectest_global_i64 = 666l; - -void (*Z_spectestZ_print)(void) = &spectest_print; -void (*Z_spectestZ_print_i32)(uint32_t) = &spectest_print_i32; -void (*Z_spectestZ_print_f32)(float) = &spectest_print_f32; -void (*Z_spectestZ_print_i32_f32)(uint32_t, float) = &spectest_print_i32_f32; -void (*Z_spectestZ_print_f64)(double) = &spectest_print_f64; -void (*Z_spectestZ_print_f64_f64)(double, double) = &spectest_print_f64_f64; -wasm_rt_table_t* Z_spectestZ_table = &spectest_table; -wasm_rt_memory_t* Z_spectestZ_memory = &spectest_memory; -uint32_t* Z_spectestZ_global_i32 = &spectest_global_i32; -uint64_t* Z_spectestZ_global_i64 = &spectest_global_i64; - -static void init_spectest_module(void) { - wasm_rt_allocate_memory(&spectest_memory, 1, 2); - wasm_rt_allocate_table(&spectest_table, 10, 20); +wasm_rt_table_t* Z_spectestZ_table(Z_spectest_instance_t* instance) { + return &instance->spectest_table; +} + +wasm_rt_memory_t* Z_spectestZ_memory(Z_spectest_instance_t* instance) { + return &instance->spectest_memory; +} + +uint32_t* Z_spectestZ_global_i32(Z_spectest_instance_t* instance) { + return &instance->spectest_global_i32; +} + +uint64_t* Z_spectestZ_global_i64(Z_spectest_instance_t* instance) { + return &instance->spectest_global_i64; +} + +float* Z_spectestZ_global_f32(Z_spectest_instance_t* instance) { + return &instance->spectest_global_f32; } +double* Z_spectestZ_global_f64(Z_spectest_instance_t* instance) { + return &instance->spectest_global_f64; +} + +static void init_spectest_module(Z_spectest_instance_t* instance) { + instance->spectest_global_i32 = 666; + instance->spectest_global_i64 = 666l; + wasm_rt_allocate_memory(&instance->spectest_memory, 1, 2); + wasm_rt_allocate_table(&instance->spectest_table, 10, 20); +} int main(int argc, char** argv) { wasm_rt_init(); - init_spectest_module(); + init_spectest_module(&Z_spectest_instance); run_spec_tests(); printf("%u/%u tests passed.\n", g_tests_passed, g_tests_run); wasm_rt_free(); diff --git a/wasm2c/README.md b/wasm2c/README.md index c95a0f44f..a8a816f7a 100644 --- a/wasm2c/README.md +++ b/wasm2c/README.md @@ -16,6 +16,7 @@ $ wasm2c test.wasm --no-debug-names -o test.c Let's look at a simple example of a factorial function. ```wasm +(memory $mem 1) (func (export "fac") (param $x i32) (result i32) (if (result i32) (i32.eq (local.get $x) (i32.const 0)) (then (i32.const 1)) @@ -48,14 +49,19 @@ files. To actually use our `fac` module, we'll use create a new file, `main.c`, that include `fac.h`, initializes the module, and calls `fac`. -`wasm2c` generates a few C symbols based on the `fac.wasm` module. `Z_fac_init` -and `Z_fac_Z_fac`. The former initializes the module, and the later is our +`wasm2c` generates a few C symbols based on the `fac.wasm` module: `Z_fac_init_module`, `Z_fac_instantiate` +and `Z_facZ_fac`. The first initializes the module, the second constructs an instance of the module, and the third is the exported `fac` function. All the exported symbols shared a common prefix (`Z_fac`) which, by default, is based on the name section in the module or the name of input file. This prefix can be overridden using the `-n/--module-name` command line flag. +In addition to parameters defined in `fac.wat`, `Z_fac_instantiate` and `Z_facZ_fac` +take in a pointer to a `Z_fac_instance_t`. The structure is used to +store the context information of the module instance, and `main.c` is responsible +for providing it. + ```c #include #include @@ -64,7 +70,8 @@ can be overridden using the `-n/--module-name` command line flag. int main(int argc, char** argv) { /* Make sure there is at least one command-line argument. */ - if (argc < 2) return 1; + if (argc < 2) + return 1; /* Convert the argument from a string to an int. We'll implicitly cast the int to a `u32`, which is what `fac` expects. */ @@ -73,23 +80,31 @@ int main(int argc, char** argv) { /* Initialize the Wasm runtime. */ wasm_rt_init(); - /* Initialize the fac module. */ - Z_fac_init(); + /* Initialize the `fac` module (this registers the module's function types in + * a global data structure) */ + Z_fac_init_module(); + + /* Declare an instance of the `fac` module. */ + Z_fac_instance_t instance; + + /* Construct the module instance. */ + Z_fac_instantiate(&instance); /* Call `fac`, using the mangled name. */ - u32 result = Z_facZ_fac(x); + u32 result = Z_facZ_fac(&instance, x); /* Print the result. */ printf("fac(%u) -> %u\n", x, result); /* Free the fac module. */ - Z_fac_free(); + Z_fac_free(&instance); /* Free the Wasm runtime state. */ wasm_rt_free(); return 0; } + ``` ## Compiling the wasm2c output @@ -140,27 +155,34 @@ The generated header file looks something like this: #include "wasm-rt.h" ... - #ifndef WASM_RT_CORE_TYPES_DEFINED #define WASM_RT_CORE_TYPES_DEFINED + ... + #endif #ifdef __cplusplus extern "C" { #endif -void Z_fac_init(void); -void Z_fac_free(void); +typedef struct Z_fac_instance_t { + char dummy_member; +} Z_fac_instance_t; + +void Z_fac_init_module(void); +void Z_fac_instantiate(Z_fac_instance_t*); +void Z_fac_free(Z_fac_instance_t*); /* export: 'fac' */ -extern u32 (*Z_facZ_fac)(u32); +u32 Z_facZ_fac(Z_fac_instance_t*, u32); #ifdef __cplusplus } #endif #endif /* FAC_H_GENERATED_ */ + ``` Let's look at each section. The outer `#ifndef` is standard C @@ -208,12 +230,15 @@ typedef void (*wasm_rt_funcref_t)(void); ``` Next are the definitions for a table element. `func_type` is a function index -as returned by `wasm_rt_register_func_type` described below. +as returned by `wasm_rt_register_func_type` described below. `module_instance` +is the pointer to the module instance that should be passed in when the func is +called. ```c typedef struct { uint32_t func_type; wasm_rt_funcref_t func; + void* module_instance; } wasm_rt_elem_t; ``` @@ -246,7 +271,7 @@ typedef struct { ## Symbols that must be defined by the embedder -Next in `wasm-rt.h` are a collection of extern symbols that must be implemented by +Next in `wasm-rt.h` are a collection of function declarations that must be implemented by the embedder (i.e. you) before this C source can be used. A C implementation of these functions is defined in @@ -254,6 +279,7 @@ A C implementation of these functions is defined in ```c void wasm_rt_init(void); +bool wasm_rt_is_initialized(void); void wasm_rt_free(void); void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); const char* wasm_rt_strerror(wasm_rt_trap_t trap); @@ -266,6 +292,11 @@ void wasm_rt_free_table(wasm_rt_table_t*); uint32_t wasm_rt_call_stack_depth; /* on platforms that don't use the signal handler to detect exhaustion */ ``` +`wasm_rt_init` must be called by the embedder before anything else, to +initialize the runtime. `wasm_rt_free` frees any global +state. `wasm_rt_is_initialized` can be used to confirm that the +runtime has been initialized. + `wasm_rt_trap` is a function that is called when the module traps. Some possible implementations are to throw a C++ exception, or to just abort the program execution. @@ -344,24 +375,61 @@ must be of type `WASM_RT_UNWIND_TARGET`. ## Exported symbols -Finally, `fac.h` defines exported symbols provided by the module. In our -example, the only function we exported was `fac`. Two additional -functions are provided called `init`, which initializes the module and -must be called before the module can be used, and `free`, which frees -the module's state (its memory and table instances). +Finally, `fac.h` defines the module instance type (which in the case +of `fac` is essentially empty), and the exported symbols provided by +the module. In our example, the only function we exported was +`fac`. `Z_fac_init_module()` initializes the whole module and must be +called before any instance of the module is used. + +`Z_fac_instantiate(Z_fac_instance_t*)` creates an instance of +the module and must be called before the module instance can be +used. `Z_fac_free(Z_fac_instance_t*)` frees the instance. ```c -void Z_fac_init(void); -void Z_fac_free(void); +typedef struct Z_fac_instance_t { + char dummy_member; +} Z_fac_instance_t; + +void Z_fac_init_module(void); +void Z_fac_instantiate(Z_fac_instance_t*); +void Z_fac_free(Z_fac_instance_t*); /* export: 'fac' */ -extern u32 (*Z_facZ_fac)(u32); +u32 Z_facZ_fac(Z_fac_instance_t*, u32); ``` -All exported names use the mangled module name as a prefix (as described above) -to avoid collisions with other C symbols or symbols from other modules. +## Handling other kinds of imports and exports of modules -In our example, `Z_facZ_fac` is the mangling of the function named `fac`. +Exported functions are handled by declaring a prefixed equivalent +function in the header. If a module is imports a function, `wasm2c` +declares the function in the output header file, and the host function +is responsible for defining the function. + +Exports of other kinds (globals, memories, tables) are handled +differently, since they are part of the module instance, and each +instance can have its own exports. For these cases, `wasm2c` provides +a function that takes in a module instance as argument, and returns +the corresponding export. For example, if `fac` exported a memory as +such: + +```wasm +(export "mem" (memory $mem)) +``` + +then `wasm2c` would declare the following function in the header: + +```c +/* export: 'mem' */ +extern wasm_rt_memory_t* Z_facZ_mem(Z_fac_instance_t*); +``` + +which would be defined as: +```c +/* export: 'mem' */ +wasm_rt_memory_t* Z_fac_Z_mem(Z_fac_instance_t* instance) { + return &instance->w2c_M0; +} +``` ## A quick look at `fac.c` @@ -380,7 +448,7 @@ module doesn't use any globals, memory or tables. The most interesting part is the definition of the function `fac`: ```c -static u32 w2c_fac(u32 w2c_p0) { +static u32 w2c_fac(Z_fac_instance_t* instance, u32 w2c_p0) { FUNC_PROLOGUE; u32 w2c_i0, w2c_i1, w2c_i2; w2c_i0 = w2c_p0; @@ -393,7 +461,7 @@ static u32 w2c_fac(u32 w2c_p0) { w2c_i1 = w2c_p0; w2c_i2 = 1u; w2c_i1 -= w2c_i2; - w2c_i1 = w2c_fac(w2c_i1); + w2c_i1 = w2c_fac(instance, w2c_i1); w2c_i0 *= w2c_i1; } FUNC_EPILOGUE; @@ -451,3 +519,119 @@ $ wat-desugar fac-flat.wat --fold -o fac-folded.wat The formatting is different and the variable and function names are gone, but the structure is the same. + +## Create multiple instances of a module + +Since information about the execution context, such as memories, is encapsulated +in the module instance structure, and a pointer to the structure is being passed through +function calls, multiple instances of the same module can be instantiated alongside +one another. + +We can take a look at another version of the `main` function for a `rot13` example. By +declaring two sets of context information, two instances of `rot13` can be instantiated +in the same address space. + +```c +#include +#include +#include + +#include "rot13.h" + +/* Define structure to hold the imports */ +struct Z_host_instance_t { + wasm_rt_memory_t memory; + char* input; +}; + +/* Accessor to access the memory member of the host */ +wasm_rt_memory_t* Z_hostZ_mem(struct Z_host_instance_t* instance) { + return &instance->memory; +} + +/* Declare the implementations of the imports. */ +static u32 fill_buf(struct Z_host_instance_t* instance, u32 ptr, u32 size); +static void buf_done(struct Z_host_instance_t* instance, u32 ptr, u32 size); + +/* Define host-provided functions under the names imported by the `rot13` instance */ +u32 Z_hostZ_fill_buf(struct Z_host_instance_t* instance, + u32 ptr, + u32 size) { + return fill_buf(instance, ptr, size); +} + +void Z_hostZ_buf_done(struct Z_host_instance_t* instance, + u32 ptr, + u32 size) { + return buf_done(instance, ptr, size); +} + +int main(int argc, char** argv) { + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Initialize the rot13 module. */ + Z_rot13_init_module(); + + /* Declare two instances of the `rot13` module. */ + Z_rot13_instance_t rot13_instance_1; + Z_rot13_instance_t rot13_instance_2; + + /* Create two `host` module instances to store the memory and current string */ + struct Z_host_instance_t host_instance_1; + struct Z_host_instance_t host_instance_2; + /* Allocate 1 page of wasm memory (64KiB). */ + wasm_rt_allocate_memory(&host_instance_1.memory, 1, 1); + wasm_rt_allocate_memory(&host_instance_2.memory, 1, 1); + + /* Construct the module instances */ + Z_rot13_instantiate(&rot13_instance_1, &host_instance_1); + Z_rot13_instantiate(&rot13_instance_2, &host_instance_2); + + /* Call `rot13` on first two argument, using the mangled name. */ + assert(argc > 2); + host_instance_1.input = argv[1]; + Z_rot13Z_rot13(&rot13_instance_1); + host_instance_2.input = argv[2]; + Z_rot13Z_rot13(&rot13_instance_2); + + /* Free the rot13 modules. */ + Z_rot13_free(&rot13_instance_1); + Z_rot13_free(&rot13_instance_2); + + /* Free the Wasm runtime state. */ + wasm_rt_free(); + + return 0; +} + +/* Fill the wasm buffer with the input to be rot13'd. + * + * params: + * ptr: The wasm memory address of the buffer to fill data. + * size: The size of the buffer in wasm memory. + * result: + * The number of bytes filled into the buffer. (Must be <= size). + */ +u32 fill_buf(struct Z_host_instance_t* instance, u32 ptr, u32 size) { + for (size_t i = 0; i < size; ++i) { + if (instance->input[i] == 0) { + return i; + } + instance->memory.data[ptr + i] = instance->input[i]; + } + return size; +} + +/* Called when the wasm buffer has been rot13'd. + * + * params: + * ptr: The wasm memory address of the buffer. + * size: The size of the buffer in wasm memory. + */ +void buf_done(struct Z_host_instance_t* instance, u32 ptr, u32 size) { + /* The output buffer is not necessarily null-terminated, so use the %*.s + * printf format to limit the number of characters printed. */ + printf("%s -> %.*s\n", instance->input, (int)size, &instance->memory.data[ptr]); +} +``` diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c index 1fc18ea24..303790f24 100644 --- a/wasm2c/examples/fac/fac.c +++ b/wasm2c/examples/fac/fac.c @@ -1,4 +1,5 @@ /* Automatically generated by wasm2c */ +#include #include #include #if defined(_MSC_VER) @@ -455,6 +456,8 @@ static float wasm_sqrtf(float x) { return sqrtf(x); } +static bool s_module_initialized = false; + 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); @@ -463,12 +466,9 @@ static void init_func_types(void) { static void init_tags(void) { } -static u32 w2c_fac(u32); - -static void init_globals(void) { -} +static u32 w2c_fac(Z_fac_instance_t*, u32); -static u32 w2c_fac(u32 w2c_p0) { +static u32 w2c_fac(Z_fac_instance_t* instance, u32 w2c_p0) { FUNC_PROLOGUE; u32 w2c_i0, w2c_i1, w2c_i2; w2c_i0 = w2c_p0; @@ -481,36 +481,41 @@ static u32 w2c_fac(u32 w2c_p0) { w2c_i1 = w2c_p0; w2c_i2 = 1u; w2c_i1 -= w2c_i2; - w2c_i1 = w2c_fac(w2c_i1); + w2c_i1 = w2c_fac(instance, w2c_i1); w2c_i0 *= w2c_i1; } FUNC_EPILOGUE; return w2c_i0; } -static void init_memory(void) { +static void init_globals(Z_fac_instance_t* instance) { +} +static void init_memory(Z_fac_instance_t* instance) { } -static void init_table(void) { +static void init_table(Z_fac_instance_t* instance) { uint32_t offset; } /* export: 'fac' */ -u32 (*Z_facZ_fac)(u32); - -static void init_exports(void) { - /* export: 'fac' */ - Z_facZ_fac = &w2c_fac; +u32 Z_facZ_fac(Z_fac_instance_t* instance, u32 w2c_p0) { + return w2c_fac(instance, w2c_p0); } -void Z_fac_init(void) { +void Z_fac_init_module(void) { + assert(wasm_rt_is_initialized()); + s_module_initialized = true; init_func_types(); init_tags(); - init_globals(); - init_memory(); - init_table(); - init_exports(); } -void Z_fac_free(void) { +void Z_fac_instantiate(Z_fac_instance_t* instance) { + assert(wasm_rt_is_initialized()); + assert(s_module_initialized); + init_globals(instance); + init_memory(instance); + init_table(instance); +} + +void Z_fac_free(Z_fac_instance_t* instance) { } diff --git a/wasm2c/examples/fac/fac.h b/wasm2c/examples/fac/fac.h index f1832a579..6e36ada23 100644 --- a/wasm2c/examples/fac/fac.h +++ b/wasm2c/examples/fac/fac.h @@ -25,11 +25,16 @@ typedef double f64; extern "C" { #endif -void Z_fac_init(void); -void Z_fac_free(void); +typedef struct Z_fac_instance_t { + char dummy_member; +} Z_fac_instance_t; + +void Z_fac_init_module(void); +void Z_fac_instantiate(Z_fac_instance_t*); +void Z_fac_free(Z_fac_instance_t*); /* export: 'fac' */ -extern u32 (*Z_facZ_fac)(u32); +u32 Z_facZ_fac(Z_fac_instance_t*, u32); #ifdef __cplusplus } diff --git a/wasm2c/examples/fac/main.c b/wasm2c/examples/fac/main.c index 489516ffe..5a91b1611 100644 --- a/wasm2c/examples/fac/main.c +++ b/wasm2c/examples/fac/main.c @@ -14,17 +14,24 @@ int main(int argc, char** argv) { /* Initialize the Wasm runtime. */ wasm_rt_init(); - /* Initialize the fac module. */ - Z_fac_init(); + /* Initialize the `fac` module (this registers the module's function types in + * a global data structure) */ + Z_fac_init_module(); + + /* Declare an instance of the `fac` module. */ + Z_fac_instance_t instance; + + /* Construct the module instance. */ + Z_fac_instantiate(&instance); /* Call `fac`, using the mangled name. */ - u32 result = Z_facZ_fac(x); + u32 result = Z_facZ_fac(&instance, x); /* Print the result. */ printf("fac(%u) -> %u\n", x, result); /* Free the fac module. */ - Z_fac_free(); + Z_fac_free(&instance); /* Free the Wasm runtime state. */ wasm_rt_free(); diff --git a/wasm2c/examples/rot13/main.c b/wasm2c/examples/rot13/main.c index 321f02294..2980324e8 100644 --- a/wasm2c/examples/rot13/main.c +++ b/wasm2c/examples/rot13/main.c @@ -19,48 +19,61 @@ #include "rot13.h" -/* Define the imports as declared in rot13.h. */ -wasm_rt_memory_t (*Z_hostZ_mem); -u32 (*Z_hostZ_fill_buf)(u32, u32); -void (*Z_hostZ_buf_done)(u32, u32); +/* Define structure to hold the imports */ +struct Z_host_instance_t { + wasm_rt_memory_t memory; + char* input; +}; -/* Define the implementations of the imports. */ -static wasm_rt_memory_t s_memory; -static u32 fill_buf(u32 ptr, u32 size); -static void buf_done(u32 ptr, u32 size); +/* Accessor to access the memory member of the host */ +wasm_rt_memory_t* Z_hostZ_mem(struct Z_host_instance_t* instance) { + return &instance->memory; +} + +/* Declare the implementations of the imports. */ +static u32 fill_buf(struct Z_host_instance_t* instance, u32 ptr, u32 size); +static void buf_done(struct Z_host_instance_t* instance, u32 ptr, u32 size); + +/* Define host-provided functions under the names imported by the `rot13` + * instance */ +u32 Z_hostZ_fill_buf(struct Z_host_instance_t* instance, u32 ptr, u32 size) { + return fill_buf(instance, ptr, size); +} -/* The string that is currently being processed. This needs to be static - * because the buffer is filled in the callback. */ -static const char* s_input; +void Z_hostZ_buf_done(struct Z_host_instance_t* instance, u32 ptr, u32 size) { + return buf_done(instance, ptr, size); +} int main(int argc, char** argv) { /* Initialize the Wasm runtime. */ wasm_rt_init(); /* Initialize the rot13 module. */ - Z_rot13_init(); + Z_rot13_init_module(); + + /* Declare an instance of the `rot13` module. */ + Z_rot13_instance_t rot13_instance; + /* Create a `host` module instance to store the memory and current string */ + struct Z_host_instance_t host_instance; /* Allocate 1 page of wasm memory (64KiB). */ - wasm_rt_allocate_memory(&s_memory, 1, 1); + wasm_rt_allocate_memory(&host_instance.memory, 1, 1); - /* Provide the imports expected by the module: "host.mem", "host.fill_buf" - * and "host.buf_done". Their mangled names are `Z_hostZ_mem`, - * `Z_hostZ_fill_buf` and `Z_hostZ_buf_done`. */ - Z_hostZ_mem = &s_memory; - Z_hostZ_fill_buf = &fill_buf; - Z_hostZ_buf_done = &buf_done; + /* Construct the module instance */ + Z_rot13_instantiate(&rot13_instance, &host_instance); - /* Call `rot13` on each argument, using the mangled name. */ + /* Call `rot13` on each argument. */ while (argc > 1) { /* Move to next arg. Do this first, so the program name is skipped. */ - argc--; argv++; + argc--; + argv++; - s_input = argv[0]; - Z_rot13Z_rot13(); + host_instance.input = argv[0]; + Z_rot13Z_rot13(&rot13_instance); } /* Free the rot13 module. */ - Z_rot13_free(); + Z_rot13_free(&rot13_instance); /* Free the Wasm runtime state. */ wasm_rt_free(); @@ -76,12 +89,12 @@ int main(int argc, char** argv) { * result: * The number of bytes filled into the buffer. (Must be <= size). */ -u32 fill_buf(u32 ptr, u32 size) { +u32 fill_buf(struct Z_host_instance_t* instance, u32 ptr, u32 size) { for (size_t i = 0; i < size; ++i) { - if (s_input[i] == 0) { + if (instance->input[i] == 0) { return i; } - s_memory.data[ptr + i] = s_input[i]; + instance->memory.data[ptr + i] = instance->input[i]; } return size; } @@ -92,8 +105,9 @@ u32 fill_buf(u32 ptr, u32 size) { * ptr: The wasm memory address of the buffer. * size: The size of the buffer in wasm memory. */ -void buf_done(u32 ptr, u32 size) { +void buf_done(struct Z_host_instance_t* instance, u32 ptr, u32 size) { /* The output buffer is not necessarily null-terminated, so use the %*.s * printf format to limit the number of characters printed. */ - printf("%s -> %.*s\n", s_input, (int)size, &s_memory.data[ptr]); + printf("%s -> %.*s\n", instance->input, (int)size, + &instance->memory.data[ptr]); } diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index e9f29e00b..efc6e80aa 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -265,6 +265,14 @@ void wasm_rt_init(void) { #endif } +bool wasm_rt_is_initialized(void) { +#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX + return g_signal_handler_installed; +#else + return true; +#endif +} + void wasm_rt_free(void) { #if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX free(g_alt_stack); diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index 51a5dcd51..7b8ec1f4a 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -146,6 +146,10 @@ typedef struct { /** The function. The embedder must know the actual C signature of the * function and cast to it before calling. */ wasm_rt_funcref_t func; + /** A function instance is a closure of the function over an instance + * of the originating module. The module_instance element will be passed into + * the function at runtime. */ + void* module_instance; } wasm_rt_elem_t; /** A Memory object. */ @@ -173,6 +177,9 @@ typedef struct { /** Initialize the runtime. */ void wasm_rt_init(void); +/** Is the runtime initialized? */ +bool wasm_rt_is_initialized(void); + /** Free the runtime's state. */ void wasm_rt_free(void);