From f62d8f031e6190d7ebb9563f197cee548c7bd4db Mon Sep 17 00:00:00 2001 From: "shravanrn@gmail.com" Date: Wed, 16 Feb 2022 21:42:38 -0800 Subject: [PATCH 1/2] Library sandboxing changes - Windows/msvc support for runtime, codegen and builtins as well as support for a wider list of platforms/architectures/compilers including Android (arm architectures), Windows 32-bit etc. - Support for growable indirect function call tables - Support for library sandboxing apis --- wasm function type index lookup (so host can add new callbacks), make heaps aligned to 4gb for compatiblity with the RLBox sandboxing framework - Allow multiple sandbox instances of the same library by moving global vars into a context structure that is passed to every function - Migrated from emscripten to use of wasi-clang - Upstream libc-wasi does not support windows, so I have written a minimal cross platform wasi support which includes just basic wasi function (just enough to get simple IO libraries running. Nothing complicated like networking or filesystem is supported) - Removed use of per function call signal handler, setjmp/longjmp as this slows transitions - Removed name mangling to simplify function lookup - Remove stack depth counting as this adds unnecessary overhead - A debugging aid built directly into wasm2c generated code that is similar to valgrind like shadow memory (significantly eases debugging of wasm) - A wasm2c runner that can run full applications (code that has a main), when they are compiled via wasm2c to C and then to a shared library (.so/.dll) --- .gitignore | 3 +- CMakeLists.txt | 21 +- src/c-writer.cc | 620 ++++++++++++------- src/c-writer.h | 9 +- src/prebuilt/wasm2c.include.c | 352 +++++++++-- src/prebuilt/wasm2c.include.h | 18 +- src/tools/wasm2c.cc | 8 + src/wasm2c.c.tmpl | 350 +++++++++-- src/wasm2c.h.tmpl | 18 +- test/spec-wasm2c-prefix.c | 1 - wasm2c/README.md | 252 ++------ wasm2c/examples/fac/Makefile | 2 +- wasm2c/examples/fac/fac.c | 501 ++++++++++++--- wasm2c/examples/fac/fac.h | 23 +- wasm2c/examples/fac/main.c | 20 +- wasm2c/wasm-rt-impl.c | 435 +++++++++++--- wasm2c/wasm-rt-impl.h | 65 -- wasm2c/wasm-rt-os-unix.c | 294 +++++++++ wasm2c/wasm-rt-os-win.c | 325 ++++++++++ wasm2c/wasm-rt-os.h | 58 ++ wasm2c/wasm-rt-runner.c | 133 ++++ wasm2c/wasm-rt-shadow.cpp | 361 +++++++++++ wasm2c/wasm-rt-wasi.c | 1070 +++++++++++++++++++++++++++++++++ wasm2c/wasm-rt.h | 255 ++++++-- 24 files changed, 4368 insertions(+), 826 deletions(-) delete mode 100644 wasm2c/wasm-rt-impl.h create mode 100644 wasm2c/wasm-rt-os-unix.c create mode 100644 wasm2c/wasm-rt-os-win.c create mode 100644 wasm2c/wasm-rt-os.h create mode 100644 wasm2c/wasm-rt-runner.c create mode 100644 wasm2c/wasm-rt-shadow.cpp create mode 100644 wasm2c/wasm-rt-wasi.c diff --git a/.gitignore b/.gitignore index 645634fa9..94b43282b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ /bin -/build +/build* /out /fuzz-out /emscripten *.pyc +*.o .idea/ .vscode/ /cmake-build-debug/ diff --git a/CMakeLists.txt b/CMakeLists.txt index f5d2b96e8..b1f346c24 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -351,11 +351,9 @@ set(WABT_LIBRARY_SRC add_library(wabt STATIC ${WABT_LIBRARY_SRC}) -IF (NOT WIN32) - add_library(wasm-rt-impl STATIC wasm2c/wasm-rt-impl.c wasm2c/wasm-rt-impl.h) - install(TARGETS wasm-rt-impl DESTINATION ${CMAKE_INSTALL_LIBDIR}) - install(FILES wasm2c/wasm-rt.h wasm2c/wasm-rt-impl.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -endif () +add_library(wasm-rt-impl STATIC SHARED wasm2c/wasm-rt-impl.c wasm2c/wasm-rt-os-unix.c wasm2c/wasm-rt-os-win.c wasm2c/wasm-rt-wasi.c) +install(TARGETS wasm-rt-impl DESTINATION ${CMAKE_INSTALL_LIBDIR}) +install(FILES wasm2c/wasm-rt.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) if (BUILD_FUZZ_TOOLS) set(FUZZ_FLAGS "-fsanitize=fuzzer,address") @@ -472,6 +470,19 @@ if (BUILD_TOOLS) INSTALL ) +if (NOT WIN32) + set(LDL_LIB "-ldl") +else() + set(LDL_LIB "") +endif() + + wabt_executable( + NAME wasm2c-runner + SOURCES wasm2c/wasm-rt-runner.c + LIBS ${LDL_LIB} + INSTALL + ) + # wasm-opcodecnt wabt_executable( NAME wasm-opcodecnt diff --git a/src/c-writer.cc b/src/c-writer.cc index c1336671d..4c604d1aa 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -158,7 +158,6 @@ class CWriter { void PopLabel(); static std::string AddressOf(const std::string&); - static std::string Deref(const std::string&); static char MangleType(Type); static std::string MangleTypes(const TypeVector&); @@ -169,7 +168,6 @@ class CWriter { const TypeVector& result_types); static std::string MangleGlobalName(std::string_view, Type); static std::string LegalizeName(std::string_view); - static std::string ExportName(std::string_view mangled_name); std::string DefineName(SymbolSet*, std::string_view); std::string DefineImportName(const std::string& name, std::string_view module_name, @@ -193,12 +191,6 @@ class CWriter { std::string GetGlobalName(const std::string&) const; - enum class WriteExportsKind { - Declarations, - Definitions, - Initializers, - }; - void Write() {} void Write(Newline); void Write(OpenBrace); @@ -223,20 +215,31 @@ class CWriter { std::string GenerateHeaderGuard() const; void WriteSourceTop(); void WriteMultivalueTypes(); + void WriteSandboxStruct(); void WriteFuncTypes(); void WriteImports(); - void WriteFuncDeclarations(); - void WriteFuncDeclaration(const FuncDeclaration&, const std::string&); + bool IsFuncStatic(std::string name); + std::string GetFuncStaticOrExport(std::string); + void WriteFuncDeclarations(bool for_header); + void WriteFuncDeclaration(const FuncDeclaration&, const std::string&, bool add_storage_class); + void WriteEntryFuncs(); + void WriteEntryFunc(const FuncDeclaration&, const std::string&, bool add_storage_class); + void WriteImportFuncDeclaration(const FuncDeclaration&, const std::string&); + std::string GetMainMemoryName(); + void WriteGlobalInitializers(); void WriteGlobals(); + void WriteGlobalsExport(); void WriteGlobal(const Global&, const std::string&); void WriteMemories(); + void WriteMemoriesExport(); void WriteMemory(const std::string&); void WriteTables(); + void WriteTablesExport(); void WriteTable(const std::string&); void WriteDataInitializers(); void WriteElemInitializers(); - void WriteInitExports(); - void WriteExports(WriteExportsKind); + void WriteExportLookup(); + void WriteCallbackAddRemove(); void WriteInit(); void WriteFuncs(); void Write(const Func&); @@ -379,11 +382,6 @@ std::string CWriter::AddressOf(const std::string& s) { return "(&" + s + ")"; } -// static -std::string CWriter::Deref(const std::string& s) { - return "(*" + s + ")"; -} - // static char CWriter::MangleType(Type type) { switch (type) { @@ -450,11 +448,6 @@ std::string CWriter::MangleGlobalName(std::string_view name, Type type) { return MangleName(name) + MangleName(sig); } -// static -std::string CWriter::ExportName(std::string_view mangled_name) { - return "WASM_RT_ADD_PREFIX(" + std::string(mangled_name) + ")"; -} - // static std::string CWriter::LegalizeName(std::string_view name) { if (name.empty()) @@ -501,7 +494,7 @@ std::string CWriter::DefineImportName(const std::string& name, import_syms_.insert(name); global_syms_.insert(mangled); global_sym_map_.insert(SymbolMap::value_type(name, mangled)); - return "(*" + mangled + ")"; + return mangled; } std::string CWriter::DefineGlobalScopeName(const std::string& name) { @@ -612,12 +605,7 @@ void CWriter::Write(const ExternalPtr& name) { } void CWriter::Write(const ExternalRef& name) { - bool is_import = import_syms_.count(name.name) != 0; - if (is_import) { - Write(Deref(GetGlobalName(name.name))); - } else { - Write(GetGlobalName(name.name)); - } + Write(GetGlobalName(name.name)); } void CWriter::Write(const Var& var) { @@ -844,30 +832,75 @@ void CWriter::WriteMultivalueTypes() { } } +void CWriter::WriteSandboxStruct() { + Write("struct wasm2c_sandbox_t {", Newline()); + Indent(2); + + Write("wasm_sandbox_wasi_data wasi_data;", Newline()); + Write("u32 wasm_rt_call_stack_depth;", Newline()); + + WriteMemories(); + WriteTables(); + + { + Write("wasm_func_type_t* func_type_structs;"); + Write(Newline()); + Write("u32 func_type_count;"); + Write(Newline()); + Writef("u32 func_types[%" PRIzd "];", module_->types.size()); + Write(Newline()); + } + + WriteGlobals(); + + Dedent(2); + Write("};", Newline(), Newline()); +} + void CWriter::WriteFuncTypes() { Write(Newline()); - Writef("static u32 func_types[%" PRIzd "];", module_->types.size()); - Write(Newline(), Newline()); - Write("static void init_func_types(void) {", Newline()); + Write("static void init_func_types(wasm2c_sandbox_t* const sbx) ", OpenBrace()); Index func_type_index = 0; for (TypeEntry* type : module_->types) { FuncType* func_type = cast(type); Index num_params = func_type->GetNumParams(); Index num_results = func_type->GetNumResults(); - Write(" func_types[", func_type_index, "] = wasm_rt_register_func_type(", - num_params, ", ", num_results); - for (Index i = 0; i < num_params; ++i) { - Write(", ", TypeEnum(func_type->GetParamType(i))); - } - - for (Index i = 0; i < num_results; ++i) { - Write(", ", TypeEnum(func_type->GetResultType(i))); + Write(OpenBrace()); + Write("wasm_rt_type_t param_ret_types[] = { "); + bool first = true; + if (num_results == 0 && num_params == 0) { + // Make sure array has at least one element + Write("/* void(*)(void) */ WASM_RT_I32 "); + } else { + for (Index i = 0; i < num_params; ++i) { + if (!first) { + Write(", "); + } + Write(TypeEnum(func_type->GetParamType(i))); + first = false; + } + for (Index i = 0; i < num_results; ++i) { + if (!first) { + Write(", "); + } + Write(TypeEnum(func_type->GetResultType(i))); + first = false; + } } - + Write(" };", Newline()); + Write("sbx->func_types[", func_type_index, "] = wasm_rt_register_func_type(&sbx->func_type_structs, &sbx->func_type_count, ", + num_params, ", ", num_results, ", param_ret_types"); Write(");", Newline()); + Write(CloseBrace(), Newline()); ++func_type_index; } - Write("}", Newline()); + Write(CloseBrace(), Newline()); + + Write("static void cleanup_func_types(wasm2c_sandbox_t* const sbx) ", OpenBrace()); + { + Write("wasm_rt_cleanup_func_types(&sbx->func_type_structs, &sbx->func_type_count);", Newline()); + } + Write(CloseBrace(), Newline()); } void CWriter::WriteImports() { @@ -884,7 +917,7 @@ void CWriter::WriteImports() { switch (import->kind()) { case ExternalKind::Func: { const Func& func = cast(import)->func; - WriteFuncDeclaration( + WriteImportFuncDeclaration( func.decl, DefineImportName( func.name, import->module_name, @@ -926,7 +959,7 @@ void CWriter::WriteImports() { } } -void CWriter::WriteFuncDeclarations() { +void CWriter::WriteFuncDeclarations(bool for_header) { if (module_->funcs.size() == module_->num_func_imports) return; @@ -934,59 +967,189 @@ void CWriter::WriteFuncDeclarations() { Index func_index = 0; for (const Func* func : module_->funcs) { + std::string global_func_name; + if (for_header) { + global_func_name = DefineGlobalScopeName(func->name); + } else { + global_func_name = GetGlobalName(func->name); + } bool is_import = func_index < module_->num_func_imports; - if (!is_import) { - Write("static "); - WriteFuncDeclaration(func->decl, DefineGlobalScopeName(func->name)); + bool static_exclude = for_header && IsFuncStatic(global_func_name); + if (!is_import && !static_exclude) { + WriteFuncDeclaration(func->decl, global_func_name, true /* add_storage_class */); Write(";", Newline()); } ++func_index; } } + +void CWriter::WriteEntryFuncs() { + if (module_->funcs.size() == module_->num_func_imports) + return; + + Write(Newline()); + Write("#if defined(ENTRY_PROLOGUE) || defined(ENTRY_EPILOGUE)", Newline()); + + Index func_index = 0; + for (const Func* func : module_->funcs) { + bool is_import = func_index < module_->num_func_imports; + if (!is_import) { + WriteEntryFunc(func->decl, GetGlobalName(func->name), true /* add_storage_class */); + Write(Newline()); + } + ++func_index; + } + + Write("#endif", Newline()); +} + + +bool CWriter::IsFuncStatic(std::string name) { + // static functions starts with prefix __ + return name.rfind("w2c___", 0) == 0 || name == "w2c_main"; +} + +std::string CWriter::GetFuncStaticOrExport(std::string name) { + std::string static_export_string = IsFuncStatic(name)? "static " : "FUNC_EXPORT "; + return static_export_string; +} + void CWriter::WriteFuncDeclaration(const FuncDeclaration& decl, - const std::string& name) { - Write(ResultType(decl.sig.result_types), " ", name, "("); - if (decl.GetNumParams() == 0) { - Write("void"); - } else { + const std::string& name, + bool add_storage_class) { + // LLVM adds some extra function calls to all wasm objects prefixed with "__". + // Keep this static (private), else we cause symbol collisions when linking multiple wasm modules + // Additionally windows dlls have to export functions explicitly + if (add_storage_class) { + Write(GetFuncStaticOrExport(name)); + } + Write(ResultType(decl.sig.result_types), " ", name, "(wasm2c_sandbox_t* const"); + for (Index i = 0; i < decl.GetNumParams(); ++i) { + Write(", ", decl.GetParamType(i)); + } + Write(")"); +} + +void CWriter::WriteEntryFunc(const FuncDeclaration& decl, + const std::string& name, + bool add_storage_class) { + // LLVM adds some extra function calls to all wasm objects prefixed with "__". + // Keep this static (private), else we cause symbol collisions when linking multiple wasm modules + // Additionally windows dlls have to export functions explicitly + if (add_storage_class) { + Write(GetFuncStaticOrExport(name)); + } + Write(ResultType(decl.sig.result_types), " w2centry_", name, "(wasm2c_sandbox_t* const sbx"); + for (Index i = 0; i < decl.GetNumParams(); ++i) { + Write(", ", decl.GetParamType(i), " p", std::to_string(i)); + } + Write(") ", OpenBrace()); + { + Write("ENTRY_PROLOGUE;", Newline()); + if (!decl.sig.result_types.empty()) { + Write(ResultType(decl.sig.result_types), " ret = "); + } + Write(name, "(sbx"); for (Index i = 0; i < decl.GetNumParams(); ++i) { - if (i != 0) - Write(", "); - Write(decl.GetParamType(i)); + Write(", p", std::to_string(i)); + } + Write(");", Newline()); + Write("ENTRY_EPILOGUE;", Newline()); + if (!decl.sig.result_types.empty()) { + Write("return ret;", Newline()); } } + Write(CloseBrace(), Newline()); +} + +void CWriter::WriteImportFuncDeclaration(const FuncDeclaration& decl, + const std::string& name) { + Write(ResultType(decl.sig.result_types), " ", name, "(void*"); + for (Index i = 0; i < decl.GetNumParams(); ++i) { + Write(", ", decl.GetParamType(i)); + } Write(")"); } 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()); } ++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()); +void CWriter::WriteGlobalsExport() { + Index global_index = 0; + if (module_->globals.size() != module_->num_global_imports) { + + for (const Global* global : module_->globals) { + bool is_import = global_index < module_->num_global_imports; + if (!is_import) { + std::string curr_global_name = GetGlobalName(global->name); + Writef("if (strcmp(\"%s\", name) == 0)", curr_global_name.c_str()); + Write(OpenBrace()); + Write("return &(sbx->", curr_global_name, ");", Newline()); + Write(CloseBrace(), Newline()); + } + ++global_index; + } + } +} + +std::string CWriter::GetMainMemoryName() { + assert (!(module_->memories.size() == module_->num_memory_imports)); + assert(module_->memories.size() <= 1); + + std::string ret = GetGlobalName(module_->memories[0]->name); + return ret; +} + +void CWriter::WriteGlobalInitializers() { + + Write(Newline(), "static void init_globals(wasm2c_sandbox_t* const sbx) ", 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("sbx->", GlobalName(global->name), " = "); + WriteInitExpr(global->init_expr); + Write(";", Newline()); + } + ++global_index; + } + } + + if (global_index != 0) + { + Index global_index2 = 0; + std::string memory_name = GetMainMemoryName(); + for (const Global* global : module_->globals) { + bool is_import = global_index2 < module_->num_global_imports; + if (!is_import) { + std::string global_name = GetGlobalName(global->name); + std::string global_name_expr = "sbx->" + global_name; + Write("WASM2C_SHADOW_MEMORY_RESERVE(&(sbx->", memory_name ,"), ", global_name_expr, ", sizeof(", global_name_expr, "));", Newline()); + if (global_name == "w2c___heap_base") { + Write("WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(&(sbx->", memory_name, "), ", global_name_expr, ");", Newline()); + } + } + + ++global_index2; } - ++global_index; } + Write(CloseBrace(), Newline()); } @@ -1004,7 +1167,6 @@ void CWriter::WriteMemories() { 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()); } @@ -1012,6 +1174,25 @@ void CWriter::WriteMemories() { } } +void CWriter::WriteMemoriesExport() { + if (module_->memories.size() == module_->num_memory_imports) + return; + + assert(module_->memories.size() <= 1); + Index memory_index = 0; + for (const Memory* memory : module_->memories) { + bool is_import = memory_index < module_->num_memory_imports; + if (!is_import) { + std::string curr_memory_name = GetGlobalName(memory->name); + Writef("if (strcmp(\"%s\", name) == 0)", curr_memory_name.c_str()); + Write(OpenBrace()); + Write("return &(sbx->", curr_memory_name, ");", Newline()); + Write(CloseBrace(), Newline()); + } + ++memory_index; + } +} + void CWriter::WriteMemory(const std::string& name) { Write("wasm_rt_memory_t ", name, ";"); } @@ -1020,21 +1201,40 @@ void CWriter::WriteTables() { if (module_->tables.size() == module_->num_table_imports) 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)); + std::string curr_table_name = DefineGlobalScopeName(table->name); + Writef("u32 %s_current_index;", curr_table_name.c_str()); + Write(Newline()); + WriteTable(curr_table_name); Write(Newline()); } ++table_index; } } +void CWriter::WriteTablesExport() { + if (module_->tables.size() == module_->num_table_imports) + return; + + 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) { + std::string curr_table_name = GetGlobalName(table->name); + Writef("if (strcmp(\"%s\", name) == 0)", curr_table_name.c_str()); + Write(OpenBrace()); + Write("return &(sbx->", curr_table_name, ");", Newline()); + Write(CloseBrace(), Newline()); + } + ++table_index; + } +} + void CWriter::WriteTable(const std::string& name) { Write("wasm_rt_table_t ", name, ";"); } @@ -1066,41 +1266,55 @@ void CWriter::WriteDataInitializers() { memory = module_->memories[0]; } - Write(Newline(), "static void init_memory(void) ", OpenBrace()); + Write(Newline(), "static bool init_memory(wasm2c_sandbox_t* const sbx, uint32_t max_wasm_pages_from_rt) ", 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), ", ", - memory->page_limits.initial, ", ", max, ");", Newline()); + Write("const uint32_t max_pages_specified_in_module = ", memory->page_limits.has_max ? memory->page_limits.max : 0, ";", Newline()); + Write("const uint32_t max_pages = max_wasm_pages_from_rt == 0? max_pages_specified_in_module : max_wasm_pages_from_rt;", Newline()); + Write("const bool success = wasm_rt_allocate_memory(&(sbx->", ExternalRef(memory->name), "), ", + memory->page_limits.initial, ", max_pages);", Newline()); + Write("if (!success) { return false; }", Newline(), Newline()); } } data_segment_index = 0; for (const DataSegment* data_segment : module_->data_segments) { - Write("LOAD_DATA(", ExternalRef(memory->name), ", "); + Write("LOAD_DATA(sbx->", ExternalRef(memory->name), ", "); WriteInitExpr(data_segment->offset); Write(", data_segment_data_", data_segment_index, ", ", data_segment->data.size(), ");", Newline()); ++data_segment_index; } + if (memory) { + Write("sbx->wasi_data.heap_memory = &(sbx->", ExternalRef(memory->name), ");", Newline()); + } else { + Write("sbx->wasi_data.heap_memory = 0;", Newline()); + } + Write("return true;", Newline()); + Write(CloseBrace(), Newline()); + + Write(Newline(), "static void cleanup_memory(wasm2c_sandbox_t* const sbx) ", OpenBrace()); + if (memory) { + Write("wasm_rt_deallocate_memory(&(sbx->", ExternalRef(memory->name), "));", Newline()); + } Write(CloseBrace(), Newline()); } void CWriter::WriteElemInitializers() { const Table* table = module_->tables.empty() ? nullptr : module_->tables[0]; - Write(Newline(), "static void init_table(void) ", OpenBrace()); - Write("uint32_t offset;", Newline()); + Write(Newline(), "static void init_table(wasm2c_sandbox_t* const sbx) ", OpenBrace()); + Write("uint32_t offset = 0;", Newline()); 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(&(sbx->", ExternalRef(table->name), "), ", table->elem_limits.initial, ", ", max, ");", Newline()); } Index elem_segment_index = 0; + size_t first_unused_elem = 0; for (const ElemSegment* elem_segment : module_->elem_segments) { if (elem_segment->kind == SegmentKind::Passive) { continue; @@ -1119,109 +1333,61 @@ 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()); + Write("sbx->",ExternalRef(table->name), ".data[offset + ", i, + "] = (wasm_rt_elem_t){WASM_RT_INTERNAL_FUNCTION, sbx->func_types[", func_type_index, + "], (wasm_rt_funcref_t)", ExternalPtr(func->name), " };", Newline()); + if (i >= first_unused_elem) { + first_unused_elem = i+1; + } ++i; } ++elem_segment_index; } - + if (table){ + Write("sbx->", ExternalRef(table->name), "_current_index = offset + ", std::to_string(first_unused_elem), ";", Newline()); + } Write(CloseBrace(), Newline()); -} -void CWriter::WriteInitExports() { - Write(Newline(), "static void init_exports(void) ", OpenBrace()); - WriteExports(WriteExportsKind::Initializers); + Write(Newline(), "static void cleanup_table(wasm2c_sandbox_t* const sbx) ", OpenBrace()); + if (table && module_->num_table_imports == 0) { + Write("wasm_rt_deallocate_table(&(sbx->", ExternalRef(table->name), "));", Newline()); + } Write(CloseBrace(), Newline()); } -void CWriter::WriteExports(WriteExportsKind kind) { - if (module_->exports.empty()) - return; +void CWriter::WriteExportLookup() { + Write(Newline(), "static void* lookup_wasm2c_nonfunc_export(void* sbx_ptr, const char* name) ", OpenBrace()); + Write("wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr;", Newline()); - if (kind != WriteExportsKind::Initializers) { - Write(Newline()); - } - - for (const Export* export_ : module_->exports) { - Write("/* export: '", export_->name, "' */", Newline()); - if (kind == WriteExportsKind::Declarations) { - Write("extern "); - } - - std::string mangled_name; - std::string internal_name; - - switch (export_->kind) { - case ExternalKind::Func: { - const Func* func = module_->GetFunc(export_->var); - mangled_name = - ExportName(MangleFuncName(export_->name, func->decl.sig.param_types, - func->decl.sig.result_types)); - internal_name = func->name; - if (kind != WriteExportsKind::Initializers) { - WriteFuncDeclaration(func->decl, Deref(mangled_name)); - Write(";"); - } - break; - } - - case ExternalKind::Global: { - const Global* global = module_->GetGlobal(export_->var); - mangled_name = - ExportName(MangleGlobalName(export_->name, global->type)); - internal_name = global->name; - if (kind != WriteExportsKind::Initializers) { - WriteGlobal(*global, Deref(mangled_name)); - Write(";"); - } - break; - } + WriteMemoriesExport(); + WriteTablesExport(); + WriteGlobalsExport(); - case ExternalKind::Memory: { - const Memory* memory = module_->GetMemory(export_->var); - mangled_name = ExportName(MangleName(export_->name)); - internal_name = memory->name; - if (kind != WriteExportsKind::Initializers) { - WriteMemory(Deref(mangled_name)); - } - break; - } - - case ExternalKind::Table: { - const Table* table = module_->GetTable(export_->var); - mangled_name = ExportName(MangleName(export_->name)); - internal_name = table->name; - if (kind != WriteExportsKind::Initializers) { - WriteTable(Deref(mangled_name)); - } - break; - } - - default: - WABT_UNREACHABLE; - } + Write("return 0;", Newline()); + Write(CloseBrace(), Newline()); +} - if (kind == WriteExportsKind::Initializers) { - Write(mangled_name, " = ", ExternalPtr(internal_name), ";"); - } +void CWriter::WriteCallbackAddRemove() { + const Table* table = module_->tables.empty() ? nullptr : module_->tables[0]; - Write(Newline()); + Write(Newline(), "static wasm_rt_table_t* get_wasm2c_callback_table(void* sbx_ptr)", OpenBrace()); + if (table) { + Write("wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr;", Newline()); + Write("return &(sbx->", ExternalRef(table->name), ");", Newline()); + } else { + Write("return 0;", Newline()); } + Write(CloseBrace(), Newline()); } void CWriter::WriteInit() { - Write(Newline(), "void WASM_RT_ADD_PREFIX(init)(void) ", OpenBrace()); - Write("init_func_types();", Newline()); - Write("init_globals();", Newline()); - Write("init_memory();", Newline()); - Write("init_table();", Newline()); - Write("init_exports();", Newline()); + Write(Newline(), "static void init_module_starts(void) ", OpenBrace()); for (Var* var : module_->starts) { Write(ExternalRef(module_->GetFunc(*var)->name), "();", Newline()); } Write(CloseBrace(), Newline()); + + Write(s_source_sandboxapis); } void CWriter::WriteFuncs() { @@ -1241,10 +1407,18 @@ void CWriter::Write(const Func& func) { local_sym_map_.clear(); stack_var_sym_map_.clear(); - Write("static ", ResultType(func.decl.sig.result_types), " ", - GlobalName(func.name), "("); + std::string func_name_suffix; + auto out_func_name = GetGlobalName(func.name); + + if (out_func_name == "w2c_dlmalloc" || out_func_name == "w2c_dlfree") + { + func_name_suffix = "_wrapped"; + } + + Write(GetFuncStaticOrExport(out_func_name), ResultType(func.decl.sig.result_types), " ", + out_func_name + func_name_suffix, "("); WriteParamsAndLocals(); - Write("FUNC_PROLOGUE;", Newline()); + Write("FUNC_PROLOGUE(sbx);", Newline()); stream_ = &func_stream_; stream_->ClearOffset(); @@ -1257,7 +1431,7 @@ void CWriter::Write(const Func& func) { PopLabel(); ResetTypeStack(0); PushTypes(func.decl.sig.result_types); - Write("FUNC_EPILOGUE;", Newline()); + Write("FUNC_EPILOGUE(sbx);", Newline()); // Return the top of the stack implicitly. Index num_results = func.GetNumResults(); @@ -1284,6 +1458,24 @@ void CWriter::Write(const Func& func) { Write(CloseBrace()); + if (out_func_name == "w2c_dlmalloc") { + std::string memory_name = GetMainMemoryName(); + Write(Newline(), Newline()); + Write(GetFuncStaticOrExport(out_func_name), "u32 w2c_dlmalloc(wasm2c_sandbox_t* const sbx, u32 ptr_size) ", OpenBrace()); + Write("u32 ret = w2c_dlmalloc_wrapped(sbx, ptr_size);", Newline()); + Write("WASM2C_SHADOW_MEMORY_DLMALLOC(&(sbx->", memory_name, "), ret, ptr_size);", Newline()); + Write("WASM2C_MALLOC_FAIL_CHECK(ret, ptr_size);", Newline()); + Write("return ret;", Newline()); + Write(CloseBrace()); + } else if (out_func_name == "w2c_dlfree") { + std::string memory_name = GetMainMemoryName(); + Write(Newline(), Newline()); + Write(GetFuncStaticOrExport(out_func_name), "void w2c_dlfree(wasm2c_sandbox_t* const sbx, u32 ptr) ", OpenBrace()); + Write("WASM2C_SHADOW_MEMORY_DLFREE(&(sbx->", memory_name, "), ptr);", Newline()); + Write("w2c_dlfree_wrapped(sbx, ptr);", Newline()); + Write(CloseBrace()); + } + func_stream_.Clear(); func_ = nullptr; } @@ -1297,21 +1489,18 @@ void CWriter::WriteParamsAndLocals() { } void CWriter::WriteParams(const std::vector& index_to_name) { - if (func_->GetNumParams() == 0) { - Write("void"); - } else { - Indent(4); - for (Index i = 0; i < func_->GetNumParams(); ++i) { - if (i != 0) { - Write(", "); - if ((i % 8) == 0) - Write(Newline()); - } - Write(func_->GetParamType(i), " ", - DefineLocalScopeName(index_to_name[i])); + Indent(4); + Write("wasm2c_sandbox_t* const sbx"); + for (Index i = 0; i < func_->GetNumParams(); ++i) { + Write(", "); + if (i != 0) { + if ((i % 8) == 0) + Write(Newline()); } - Dedent(4); + Write(func_->GetParamType(i), " ", + DefineLocalScopeName(index_to_name[i])); } + Dedent(4); Write(") ", OpenBrace()); } @@ -1432,12 +1621,9 @@ void CWriter::Write(const ExprList& exprs) { Write(StackVar(num_params - 1, func.GetResultType(0)), " = "); } - Write(GlobalVar(var), "("); + Write(GlobalVar(var), "(sbx"); for (Index i = 0; i < num_params; ++i) { - if (i != 0) { - Write(", "); - } - Write(StackVar(num_params - i - 1)); + Write(", ", StackVar(num_params - i - 1)); } Write(");", Newline()); DropTypes(num_params); @@ -1463,10 +1649,7 @@ void CWriter::Write(const ExprList& exprs) { assert(type_stack_.size() > num_params); if (num_results > 1) { Write(OpenBrace()); - Write("struct ", MangleMultivalueTypes(decl.sig.result_types)); - Write(" tmp = "); - } else if (num_results == 1) { - Write(StackVar(num_params, decl.GetResultType(0)), " = "); + Write("struct ", MangleMultivalueTypes(decl.sig.result_types)," tmp;"); } assert(module_->tables.size() == 1); @@ -1475,9 +1658,22 @@ 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, "(*)"); + if (num_results > 0) { + assert(num_results == 1); + Write("CALL_INDIRECT_RESULT("); + if (num_results > 1) { + Write("tmp, "); + } else { + Write(StackVar(num_params, decl.GetResultType(0)), ", "); + } + } else { + Write("CALL_INDIRECT_VOID("); + } + + Write("sbx->", ExternalRef(table->name), ", "); + WriteFuncDeclaration(decl, "(*)", false /* add_storage_class*/); Write(", ", func_type_index, ", ", StackVar(0)); + Write(", sbx->func_types, sbx"); for (Index i = 0; i < num_params; ++i) { Write(", ", StackVar(num_params - i)); } @@ -1520,13 +1716,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), " = ", "sbx->", GlobalVar(var), ";", Newline()); break; } case ExprType::GlobalSet: { const Var& var = cast(&expr)->var; - Write(GlobalVar(var), " = ", StackVar(0), ";", Newline()); + Write("sbx->", GlobalVar(var), " = ", StackVar(0), ";", Newline()); DropTypes(1); break; } @@ -1617,8 +1813,8 @@ 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((&sbx->", ExternalRef(memory->name), + "), ", StackVar(0), ");", Newline()); break; } @@ -1627,7 +1823,7 @@ void CWriter::Write(const ExprList& exprs) { cast(&expr)->memidx)]; PushType(Type::I32); - Write(StackVar(0), " = ", ExternalRef(memory->name), ".pages;", + Write(StackVar(0), " = sbx->", ExternalRef(memory->name), ".pages;", Newline()); break; } @@ -2123,10 +2319,11 @@ 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, "(&(sbx->", ExternalRef(memory->name), + "), (u64)(", StackVar(0), ")"); if (expr.offset != 0) Write(" + ", expr.offset, "u"); + Write(", \"", GetGlobalName(func_->name), "\""); Write(");", Newline()); DropTypes(1); PushType(result_type); @@ -2151,10 +2348,12 @@ void CWriter::Write(const StoreExpr& expr) { Memory* memory = module_->memories[module_->GetMemoryIndex(expr.memidx)]; - Write(func, "(", ExternalPtr(memory->name), ", (u64)(", StackVar(1), ")"); + Write(func, "(&(sbx->", ExternalRef(memory->name), "), (u64)(", StackVar(1), ")"); if (expr.offset != 0) Write(" + ", expr.offset); - Write(", ", StackVar(0), ");", Newline()); + Write(", ", StackVar(0)); + Write(", \"", GetGlobalName(func_->name), "\""); + Write(");", Newline()); DropTypes(2); } @@ -2353,28 +2552,29 @@ void CWriter::WriteCHeader() { stream_ = h_stream_; std::string guard = GenerateHeaderGuard(); Write("#ifndef ", guard, Newline()); - Write("#define ", guard, Newline()); + Write("#define ", guard, Newline(), Newline()); + Write("#define WASM_CURR_MODULE_PREFIX ", options_.mod_name, Newline()); Write(s_header_top); WriteMultivalueTypes(); WriteImports(); - WriteExports(WriteExportsKind::Declarations); - Write(s_header_bottom); - Write(Newline(), "#endif /* ", guard, " */", Newline()); + WriteFuncDeclarations(true /* for_header */); + Write(s_header_bottom, Newline()); + Write("#endif /* ", guard, " */", Newline()); } void CWriter::WriteCSource() { stream_ = c_stream_; WriteSourceTop(); + WriteSandboxStruct(); WriteFuncTypes(); - WriteFuncDeclarations(); - WriteGlobals(); - WriteMemories(); - WriteTables(); + WriteFuncDeclarations(false /* for_header */); + WriteEntryFuncs(); + WriteGlobalInitializers(); WriteFuncs(); WriteDataInitializers(); WriteElemInitializers(); - WriteExports(WriteExportsKind::Definitions); - WriteInitExports(); + WriteExportLookup(); + WriteCallbackAddRemove(); WriteInit(); } diff --git a/src/c-writer.h b/src/c-writer.h index 8b3b44d2d..9a0083d55 100644 --- a/src/c-writer.h +++ b/src/c-writer.h @@ -19,12 +19,19 @@ #include "src/common.h" +#include + namespace wabt { struct Module; class Stream; -struct WriteCOptions {}; +struct WriteCOptions { + // Unique name for the module being generated. Each wasm sandboxed module in a single application should have a unique name. + // By default the module name is empty and need not be set if an application is only using one Wasm module or is using Wasm modules in shared libraries. + // However, if an application wants to statically link more than one Wasm module, it should assign each module a unique name. + std::string mod_name; +}; Result WriteC(Stream* c_stream, Stream* h_stream, diff --git a/src/prebuilt/wasm2c.include.c b/src/prebuilt/wasm2c.include.c index a004a10e1..eec5a4dd7 100644 --- a/src/prebuilt/wasm2c.include.c +++ b/src/prebuilt/wasm2c.include.c @@ -3,35 +3,128 @@ const char SECTION_NAME(includes)[] = "/* Automically generated by wasm2c */\n" "#include \n" "#include \n" +"#include \n" ; const char SECTION_NAME(declarations)[] = -"#define UNLIKELY(x) __builtin_expect(!!(x), 0)\n" -"#define LIKELY(x) __builtin_expect(!!(x), 1)\n" +"#if defined(_MSC_VER)\n" +"# define UNLIKELY(x) (x)\n" +"# define LIKELY(x) (x)\n" +"#else\n" +"# define UNLIKELY(x) __builtin_expect(!!(x), 0)\n" +"# define LIKELY(x) __builtin_expect(!!(x), 1)\n" +"#endif\n" "\n" "#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0)\n" "\n" -"#define FUNC_PROLOGUE \\\n" -" if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \\\n" +"#ifndef WASM2C_NOSTACK_DEPTH_CHECK\n" +"\n" +"/** Maximum stack depth before trapping. This can be configured by defining\n" +" * this symbol before including wasm-rt when building the generated c files,\n" +" * for example:\n" +" *\n" +" * ```\n" +" * cc -c -DWASM_RT_MAX_CALL_STACK_DEPTH=100 my_module.c -o my_module.o\n" +" * ```\n" +" * */\n" +"# ifndef WASM_RT_MAX_CALL_STACK_DEPTH\n" +"# define WASM_RT_MAX_CALL_STACK_DEPTH 500\n" +"# endif\n" +"\n" +"# define FUNC_PROLOGUE(sbx) \\\n" +" if (++(sbx->wasm_rt_call_stack_depth) > WASM_RT_MAX_CALL_STACK_DEPTH) \\\n" " TRAP(EXHAUSTION)\n" "\n" -"#define FUNC_EPILOGUE --wasm_rt_call_stack_depth\n" +"# define FUNC_EPILOGUE(sbx) --(sbx->wasm_rt_call_stack_depth)\n" +"\n" +"#else\n" +"# define FUNC_PROLOGUE(sbx)\n" +"# define FUNC_EPILOGUE(sbx)\n" +"#endif\n" +"\n" +"#ifdef EXTERNAL_CALLBACK_PROLOGUE\n" +"#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x) \\\n" +" if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \\\n" +" EXTERNAL_CALLBACK_PROLOGUE; \\\n" +" }\n" +"#else\n" +"#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x)\n" +"#endif\n" "\n" -"#define UNREACHABLE TRAP(UNREACHABLE)\n" +"#ifdef EXTERNAL_CALLBACK_EPILOGUE\n" +"#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x) \\\n" +" if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \\\n" +" EXTERNAL_CALLBACK_EPILOGUE; \\\n" +" }\n" +"#else\n" +"#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x)\n" +"#endif\n" +"\n" +"#define UNREACHABLE (void) TRAP(UNREACHABLE)\n" +"\n" +"#define CALL_INDIRECT_VOID(table, t, ft, x, func_types, ...) \\\n" +" if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \\\n" +" EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \\\n" +" ((t)table.data[x].func)(__VA_ARGS__); \\\n" +" EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \\\n" +" } else { \\\n" +" wasm_rt_callback_error_trap(&table, x, func_types[ft]); \\\n" +" }\n" +"\n" +"#define CALL_INDIRECT_RESULT(res, table, t, ft, x, func_types, ...) \\\n" +" if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \\\n" +" EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \\\n" +" res = ((t)table.data[x].func)(__VA_ARGS__); \\\n" +" EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \\\n" +" } else { \\\n" +" wasm_rt_callback_error_trap(&table, x, func_types[ft]); \\\n" +" }\n" +"\n" +"#if defined(WASM2C_MALLOC_FAIL_CALLBACK)\n" +"void WASM2C_MALLOC_FAIL_CALLBACK(u32 ptr_size);\n" +"# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size) \\\n" +" if (!ptr) { \\\n" +" WASM2C_MALLOC_FAIL_CALLBACK(ptr_size); \\\n" +" }\n" +"#else\n" +"# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size)\n" +"#endif\n" "\n" -"#define CALL_INDIRECT(table, t, ft, x, ...) \\\n" -" (LIKELY((x) < table.size && table.data[x].func && \\\n" -" table.data[x].func_type == func_types[ft]) \\\n" -" || TRAP(CALL_INDIRECT) \\\n" -" , ((t)table.data[x].func)(__VA_ARGS__))\n" +"#if defined(WASM_CHECK_SHADOW_MEMORY)\n" +"# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_load(mem, func_name, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_store(mem, func_name, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size) wasm2c_shadow_memory_reserve(mem, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size) wasm2c_shadow_memory_dlmalloc(mem, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr) wasm2c_shadow_memory_dlfree(mem, ptr)\n" +"# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr) wasm2c_shadow_memory_mark_globals_heap_boundary(mem, ptr)\n" +"#else\n" +"# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size)\n" +"# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr)\n" +"# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr)\n" +"#endif\n" "\n" "#define RANGE_CHECK(mem, a, t) \\\n" -" if (UNLIKELY((a) + sizeof(t) > mem->size)) TRAP(OOB)\n" +" if (UNLIKELY((a) + sizeof(t) > mem->size)) { (void) TRAP(OOB); }\n" +"\n" +"#ifdef WASM_USE_GUARD_PAGES\n" +"# define MEMCHECK(mem, a, t)\n" +"#else\n" +"# define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, t)\n" +"#endif\n" "\n" -"#if WASM_RT_MEMCHECK_SIGNAL_HANDLER\n" -"#define MEMCHECK(mem, a, t)\n" +"#if defined(WASM_USE_GUARD_PAGES) && UINTPTR_MAX == 0xffffffff\n" +"// on 32-bit platforms we have to mask memory access into range\n" +"# define MEM_ACCESS_REF(mem, addr) &mem->data[addr & mem->mem_mask]\n" "#else\n" -"#define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, t)\n" +"# define MEM_ACCESS_REF(mem, addr) &mem->data[addr]\n" +"#endif\n" +"\n" +"#if defined(WASM_USING_GLOBAL_HEAP)\n" +"# undef MEM_ACCESS_REF\n" +"# define MEM_ACCESS_REF(mem, addr) (char*) addr\n" "#endif\n" "\n" "#if WABT_BIG_ENDIAN\n" @@ -45,45 +138,55 @@ const char SECTION_NAME(declarations)[] = " dest_chars[n - i - 1] = cursor;\n" " }\n" "}\n" -"#define LOAD_DATA(m, o, i, s) do { \\\n" -" RANGE_CHECK((&m), m.size - o - s, char[s]); \\\n" -" load_data(&(m.data[m.size - o - s]), i, s); \\\n" -" } while (0)\n" -"#define DEFINE_LOAD(name, t1, t2, t3) \\\n" -" static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \\\n" -" MEMCHECK(mem, addr, t1); \\\n" -" t1 result; \\\n" -" __builtin_memcpy(&result, &mem->data[mem->size - addr - sizeof(t1)], sizeof(t1)); \\\n" -" return (t3)(t2)result; \\\n" -" }\n" -"\n" -"#define DEFINE_STORE(name, t1, t2) \\\n" -" static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \\\n" -" MEMCHECK(mem, addr, t1); \\\n" -" t1 wrapped = (t1)value; \\\n" -" __builtin_memcpy(&mem->data[mem->size - addr - sizeof(t1)], &wrapped, sizeof(t1)); \\\n" +"#define LOAD_DATA(m, o, i, s) do { \\\n" +" RANGE_CHECK((&m), m.size - o - s, char[s]); \\\n" +" load_data(&(m.data[m.size - o - s]), i, s); \\\n" +" WASM2C_SHADOW_MEMORY_RESERVE(&m, m.size - o - s, s); \\\n" +" WASM2C_SHADOW_MEMORY_STORE(&m, \"GlobalDataLoad\", m.size - o - s, s); \\\n" +"} while(0)\n" +"\n" +"#define DEFINE_LOAD(name, t1, t2, t3) \\\n" +" static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \\\n" +" MEMCHECK(mem, addr, t1); \\\n" +" t1 result; \\\n" +" memcpy(&result, MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1)), sizeof(t1)); \\\n" +" WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, mem->size - addr - sizeof(t1), sizeof(t1)); \\\n" +" return (t3)(t2)result; \\\n" +" }\n" +"\n" +"#define DEFINE_STORE(name, t1, t2) \\\n" +" static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \\\n" +" MEMCHECK(mem, addr, t1); \\\n" +" t1 wrapped = (t1)value; \\\n" +" memcpy(MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1), &wrapped, sizeof(t1)); \\\n" +" WASM2C_SHADOW_MEMORY_STORE(mem, func_name, mem->size - addr - sizeof(t1)), sizeof(t1)); \\\n" " }\n" "#else\n" "static inline void load_data(void *dest, const void *src, size_t n) {\n" " memcpy(dest, src, n);\n" "}\n" -"#define LOAD_DATA(m, o, i, s) do { \\\n" -" RANGE_CHECK((&m), o, char[s]); \\\n" -" load_data(&(m.data[o]), i, s); \\\n" -" } while (0)\n" -"#define DEFINE_LOAD(name, t1, t2, t3) \\\n" -" static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \\\n" -" MEMCHECK(mem, addr, t1); \\\n" -" t1 result; \\\n" -" __builtin_memcpy(&result, &mem->data[addr], sizeof(t1)); \\\n" -" return (t3)(t2)result; \\\n" -" }\n" -"\n" -"#define DEFINE_STORE(name, t1, t2) \\\n" -" static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \\\n" -" MEMCHECK(mem, addr, t1); \\\n" -" t1 wrapped = (t1)value; \\\n" -" __builtin_memcpy(&mem->data[addr], &wrapped, sizeof(t1)); \\\n" +"#define LOAD_DATA(m, o, i, s) { \\\n" +" RANGE_CHECK((&m), o, char[s]); \\\n" +" load_data(&(m.data[o]), i, s); \\\n" +" WASM2C_SHADOW_MEMORY_RESERVE(&m, o, s); \\\n" +" WASM2C_SHADOW_MEMORY_STORE(&m, \"GlobalDataLoad\", o, s); \\\n" +"}\n" +"\n" +"#define DEFINE_LOAD(name, t1, t2, t3) \\\n" +" static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \\\n" +" MEMCHECK(mem, addr, t1); \\\n" +" t1 result; \\\n" +" memcpy(&result, MEM_ACCESS_REF(mem, addr), sizeof(t1)); \\\n" +" WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, addr, sizeof(t1)); \\\n" +" return (t3)(t2)result; \\\n" +" }\n" +"\n" +"#define DEFINE_STORE(name, t1, t2) \\\n" +" static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \\\n" +" MEMCHECK(mem, addr, t1); \\\n" +" t1 wrapped = (t1)value; \\\n" +" memcpy(MEM_ACCESS_REF(mem, addr), &wrapped, sizeof(t1)); \\\n" +" WASM2C_SHADOW_MEMORY_STORE(mem, func_name, addr, sizeof(t1)); \\\n" " }\n" "#endif\n" "\n" @@ -111,12 +214,83 @@ const char SECTION_NAME(declarations)[] = "DEFINE_STORE(i64_store16, u16, u64);\n" "DEFINE_STORE(i64_store32, u32, u64);\n" "\n" -"#define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32)\n" -"#define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64)\n" -"#define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32)\n" -"#define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64)\n" -"#define I32_POPCNT(x) (__builtin_popcount(x))\n" -"#define I64_POPCNT(x) (__builtin_popcountll(x))\n" +"#if defined(_MSC_VER)\n" +"#include \n" +"\n" +"// Adapted from https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h\n" +"\n" +"static inline int I64_CLZ(unsigned long long v) {\n" +" unsigned long r = 0;\n" +"#if defined(_M_AMD64) || defined(_M_ARM)\n" +" if (_BitScanReverse64(&r, v)) {\n" +" return 63 - r;\n" +" }\n" +"#else\n" +" if (_BitScanReverse(&r, (unsigned long) (v >> 32))) {\n" +" return 31 - r;\n" +" } else if (_BitScanReverse(&r, (unsigned long) v)) {\n" +" return 63 - r;\n" +" }\n" +"#endif\n" +" return 64;\n" +"}\n" +"\n" +"static inline int I32_CLZ(unsigned long v) {\n" +" unsigned long r = 0;\n" +" if (_BitScanReverse(&r, v)) {\n" +" return 31 - r;\n" +" }\n" +" return 32;\n" +"}\n" +"\n" +"static inline int I64_CTZ(unsigned long long v) {\n" +" if (!v) {\n" +" return 64;\n" +" }\n" +" unsigned long r = 0;\n" +"#if defined(_M_AMD64) || defined(_M_ARM)\n" +" _BitScanForward64(&r, v);\n" +" return (int) r;\n" +"#else\n" +" if (_BitScanForward(&r, (unsigned int) (v))) {\n" +" return (int) (r);\n" +" }\n" +"\n" +" _BitScanForward(&r, (unsigned int) (v >> 32));\n" +" return (int) (r + 32);\n" +"#endif\n" +"}\n" +"\n" +"static inline int I32_CTZ(unsigned long v) {\n" +" if (!v) {\n" +" return 32;\n" +" }\n" +" unsigned long r = 0;\n" +" _BitScanForward(&r, v);\n" +" return (int) r;\n" +"}\n" +"\n" +"#define POPCOUNT_DEFINE_PORTABLE(f_n, T) \\\n" +" static inline u32 f_n(T x) { \\\n" +" x = x - ((x >> 1) & (T)~(T)0/3); \\\n" +" x = (x & (T)~(T)0/15*3) + ((x >> 2) & (T)~(T)0/15*3); \\\n" +" x = (x + (x >> 4)) & (T)~(T)0/255*15; \\\n" +" return (T)(x * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8; \\\n" +" }\n" +"\n" +"POPCOUNT_DEFINE_PORTABLE(I32_POPCNT, u32)\n" +"POPCOUNT_DEFINE_PORTABLE(I64_POPCNT, u64)\n" +"\n" +"#undef POPCOUNT_DEFINE_PORTABLE\n" +"\n" +"#else\n" +"# define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32)\n" +"# define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64)\n" +"# define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32)\n" +"# define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64)\n" +"# define I32_POPCNT(x) (__builtin_popcount(x))\n" +"# define I64_POPCNT(x) (__builtin_popcountll(x))\n" +"#endif\n" "\n" "#define DIV_S(ut, min, x, y) \\\n" " ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \\\n" @@ -214,5 +388,73 @@ const char SECTION_NAME(declarations)[] = "DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32)\n" "DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64)\n" "DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64)\n" +; + +const char SECTION_NAME(sandboxapis)[] = +"//test\n" +"\n" +"static u32 add_wasm2c_callback(void* sbx_ptr, u32 func_type_idx, void* func_ptr, wasm_rt_elem_target_class_t func_class) {\n" +" wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr);\n" +" if (!table) {\n" +" (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION);\n" +" }\n" +" for (u32 i = 1; i < table->max_size; i++) {\n" +" if (i >= table->size) {\n" +" wasm_rt_expand_table(table);\n" +" }\n" +" if (table->data[i].func == 0) {\n" +" table->data[i] = (wasm_rt_elem_t){ func_class, func_type_idx, (wasm_rt_funcref_t) func_ptr };\n" +" return i;\n" +" }\n" +" }\n" +" (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION);\n" +"}\n" +"\n" +"static void remove_wasm2c_callback(void* sbx_ptr, u32 callback_idx) {\n" +" wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr);\n" +" if (!table) {\n" +" abort();\n" +" }\n" +" table->data[callback_idx].func = 0;\n" +"}\n" +"\n" +"static u32 lookup_wasm2c_func_index(void* sbx_ptr, u32 param_count, u32 result_count, wasm_rt_type_t* types) {\n" +" wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr;\n" +" return wasm_rt_register_func_type(&sbx->func_type_structs, &sbx->func_type_count, param_count, result_count, types);\n" +"}\n" "\n" +"static void* create_wasm2c_sandbox(uint32_t max_wasm_pages) {\n" +" wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) calloc(sizeof(wasm2c_sandbox_t), 1);\n" +" if (!init_memory(sbx, max_wasm_pages)) {\n" +" free(sbx);\n" +" return 0;\n" +" }\n" +" init_func_types(sbx);\n" +" init_globals(sbx);\n" +" init_table(sbx);\n" +" wasm_rt_init_wasi(&(sbx->wasi_data));\n" +" init_module_starts();\n" +" return sbx;\n" +"}\n" +"\n" +"static void destroy_wasm2c_sandbox(void* aSbx) {\n" +" wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) aSbx;\n" +" cleanup_memory(sbx);\n" +" cleanup_func_types(sbx);\n" +" cleanup_table(sbx);\n" +" wasm_rt_cleanup_wasi(&(sbx->wasi_data));\n" +" free(sbx);\n" +"}\n" +"\n" +"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() {\n" +" wasm2c_sandbox_funcs_t ret;\n" +" ret.wasm_rt_sys_init = &wasm_rt_sys_init;\n" +" ret.create_wasm2c_sandbox = &create_wasm2c_sandbox;\n" +" ret.destroy_wasm2c_sandbox = &destroy_wasm2c_sandbox;\n" +" ret.lookup_wasm2c_nonfunc_export = &lookup_wasm2c_nonfunc_export;\n" +" ret.lookup_wasm2c_func_index = &lookup_wasm2c_func_index;\n" +" ret.add_wasm2c_callback = &add_wasm2c_callback;\n" +" ret.remove_wasm2c_callback = &remove_wasm2c_callback;\n" +" return ret;\n" +"}\n" ; diff --git a/src/prebuilt/wasm2c.include.h b/src/prebuilt/wasm2c.include.h index 4ba247516..fcd2ff4b5 100644 --- a/src/prebuilt/wasm2c.include.h +++ b/src/prebuilt/wasm2c.include.h @@ -17,6 +17,8 @@ const char SECTION_NAME(top)[] = "#define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y)\n" "#define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x)\n" "\n" +"#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x)\n" +"\n" "/* TODO(binji): only use stdint.h types in header */\n" "typedef uint8_t u8;\n" "typedef int8_t s8;\n" @@ -29,10 +31,24 @@ const char SECTION_NAME(top)[] = "typedef float f32;\n" "typedef double f64;\n" "\n" -"extern void WASM_RT_ADD_PREFIX(init)(void);\n" +"#ifndef WASM_DONT_EXPORT_FUNCS\n" +"# if defined(_WIN32)\n" +"# define FUNC_EXPORT __declspec(dllexport)\n" +"# else\n" +"# define FUNC_EXPORT\n" +"# endif\n" +"#else\n" +"# define FUNC_EXPORT\n" +"#endif\n" +"\n" +"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)();\n" +"\n" +"struct wasm2c_sandbox_t;\n" +"typedef struct wasm2c_sandbox_t wasm2c_sandbox_t;\n" ; const char SECTION_NAME(bottom)[] = +"\n" "#ifdef __cplusplus\n" "}\n" "#endif\n" diff --git a/src/tools/wasm2c.cc b/src/tools/wasm2c.cc index 656e53126..9e63b5ae5 100644 --- a/src/tools/wasm2c.cc +++ b/src/tools/wasm2c.cc @@ -70,6 +70,14 @@ static void ParseOptions(int argc, char** argv) { s_outfile = argument; ConvertBackslashToSlash(&s_outfile); }); + parser.AddOption( + 'n', "modname", "MODNAME", + "Unique name for the module being generated. Each wasm sandboxed module in a single application should have a unique name." + "By default the module name is empty and need not be set if an application is only using one Wasm module or is using Wasm modules in shared libraries." + "However, if an application wants to statically link more than one Wasm module, it should assign each module a unique name.", + [](const char* argument) { + s_write_c_options.mod_name = argument; + }); s_features.AddOptions(&parser); parser.AddOption("no-debug-names", "Ignore debug names in the binary file", []() { s_read_debug_names = false; }); diff --git a/src/wasm2c.c.tmpl b/src/wasm2c.c.tmpl index 2637ca13b..abe449a9d 100644 --- a/src/wasm2c.c.tmpl +++ b/src/wasm2c.c.tmpl @@ -2,33 +2,126 @@ /* Automically generated by wasm2c */ #include #include +#include %%declarations -#define UNLIKELY(x) __builtin_expect(!!(x), 0) -#define LIKELY(x) __builtin_expect(!!(x), 1) +#if defined(_MSC_VER) +# define UNLIKELY(x) (x) +# define LIKELY(x) (x) +#else +# define UNLIKELY(x) __builtin_expect(!!(x), 0) +# define LIKELY(x) __builtin_expect(!!(x), 1) +#endif #define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) -#define FUNC_PROLOGUE \ - if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ +#ifndef WASM2C_NOSTACK_DEPTH_CHECK + +/** Maximum stack depth before trapping. This can be configured by defining + * this symbol before including wasm-rt when building the generated c files, + * for example: + * + * ``` + * cc -c -DWASM_RT_MAX_CALL_STACK_DEPTH=100 my_module.c -o my_module.o + * ``` + * */ +# ifndef WASM_RT_MAX_CALL_STACK_DEPTH +# define WASM_RT_MAX_CALL_STACK_DEPTH 500 +# endif + +# define FUNC_PROLOGUE(sbx) \ + if (++(sbx->wasm_rt_call_stack_depth) > WASM_RT_MAX_CALL_STACK_DEPTH) \ TRAP(EXHAUSTION) -#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +# define FUNC_EPILOGUE(sbx) --(sbx->wasm_rt_call_stack_depth) -#define UNREACHABLE TRAP(UNREACHABLE) +#else +# define FUNC_PROLOGUE(sbx) +# define FUNC_EPILOGUE(sbx) +#endif -#define CALL_INDIRECT(table, t, ft, x, ...) \ - (LIKELY((x) < table.size && table.data[x].func && \ - table.data[x].func_type == func_types[ft]) \ - || TRAP(CALL_INDIRECT) \ - , ((t)table.data[x].func)(__VA_ARGS__)) +#ifdef EXTERNAL_CALLBACK_PROLOGUE +#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x) \ + if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \ + EXTERNAL_CALLBACK_PROLOGUE; \ + } +#else +#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x) +#endif + +#ifdef EXTERNAL_CALLBACK_EPILOGUE +#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x) \ + if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \ + EXTERNAL_CALLBACK_EPILOGUE; \ + } +#else +#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x) +#endif + +#define UNREACHABLE (void) TRAP(UNREACHABLE) + +#define CALL_INDIRECT_VOID(table, t, ft, x, func_types, ...) \ + if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \ + EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \ + ((t)table.data[x].func)(__VA_ARGS__); \ + EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \ + } else { \ + wasm_rt_callback_error_trap(&table, x, func_types[ft]); \ + } + +#define CALL_INDIRECT_RESULT(res, table, t, ft, x, func_types, ...) \ + if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \ + EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \ + res = ((t)table.data[x].func)(__VA_ARGS__); \ + EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \ + } else { \ + wasm_rt_callback_error_trap(&table, x, func_types[ft]); \ + } + +#if defined(WASM2C_MALLOC_FAIL_CALLBACK) +void WASM2C_MALLOC_FAIL_CALLBACK(u32 ptr_size); +# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size) \ + if (!ptr) { \ + WASM2C_MALLOC_FAIL_CALLBACK(ptr_size); \ + } +#else +# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size) +#endif + +#if defined(WASM_CHECK_SHADOW_MEMORY) +# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_load(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_store(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size) wasm2c_shadow_memory_reserve(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size) wasm2c_shadow_memory_dlmalloc(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr) wasm2c_shadow_memory_dlfree(mem, ptr) +# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr) wasm2c_shadow_memory_mark_globals_heap_boundary(mem, ptr) +#else +# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr) +# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr) +#endif #define RANGE_CHECK(mem, a, t) \ - if (UNLIKELY((a) + sizeof(t) > mem->size)) TRAP(OOB) + if (UNLIKELY((a) + sizeof(t) > mem->size)) { (void) TRAP(OOB); } -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER -#define MEMCHECK(mem, a, t) +#ifdef WASM_USE_GUARD_PAGES +# define MEMCHECK(mem, a, t) #else -#define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, t) +# define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, t) +#endif + +#if defined(WASM_USE_GUARD_PAGES) && UINTPTR_MAX == 0xffffffff +// on 32-bit platforms we have to mask memory access into range +# define MEM_ACCESS_REF(mem, addr) &mem->data[addr & mem->mem_mask] +#else +# define MEM_ACCESS_REF(mem, addr) &mem->data[addr] +#endif + +#if defined(WASM_USING_GLOBAL_HEAP) +# undef MEM_ACCESS_REF +# define MEM_ACCESS_REF(mem, addr) (char*) addr #endif #if WABT_BIG_ENDIAN @@ -42,45 +135,55 @@ static inline void load_data(void *dest, const void *src, size_t n) { dest_chars[n - i - 1] = cursor; } } -#define LOAD_DATA(m, o, i, s) do { \ - RANGE_CHECK((&m), m.size - o - s, char[s]); \ - load_data(&(m.data[m.size - o - s]), i, s); \ - } while (0) -#define DEFINE_LOAD(name, t1, t2, t3) \ - static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \ - MEMCHECK(mem, addr, t1); \ - t1 result; \ - __builtin_memcpy(&result, &mem->data[mem->size - addr - sizeof(t1)], sizeof(t1)); \ - return (t3)(t2)result; \ - } - -#define DEFINE_STORE(name, t1, t2) \ - static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \ - MEMCHECK(mem, addr, t1); \ - t1 wrapped = (t1)value; \ - __builtin_memcpy(&mem->data[mem->size - addr - sizeof(t1)], &wrapped, sizeof(t1)); \ +#define LOAD_DATA(m, o, i, s) do { \ + RANGE_CHECK((&m), m.size - o - s, char[s]); \ + load_data(&(m.data[m.size - o - s]), i, s); \ + WASM2C_SHADOW_MEMORY_RESERVE(&m, m.size - o - s, s); \ + WASM2C_SHADOW_MEMORY_STORE(&m, "GlobalDataLoad", m.size - o - s, s); \ +} while(0) + +#define DEFINE_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1)), sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, mem->size - addr - sizeof(t1), sizeof(t1)); \ + return (t3)(t2)result; \ + } + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1), &wrapped, sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_STORE(mem, func_name, mem->size - addr - sizeof(t1)), sizeof(t1)); \ } #else static inline void load_data(void *dest, const void *src, size_t n) { memcpy(dest, src, n); } -#define LOAD_DATA(m, o, i, s) do { \ - RANGE_CHECK((&m), o, char[s]); \ - load_data(&(m.data[o]), i, s); \ - } while (0) -#define DEFINE_LOAD(name, t1, t2, t3) \ - static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \ - MEMCHECK(mem, addr, t1); \ - t1 result; \ - __builtin_memcpy(&result, &mem->data[addr], sizeof(t1)); \ - return (t3)(t2)result; \ - } - -#define DEFINE_STORE(name, t1, t2) \ - static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \ - MEMCHECK(mem, addr, t1); \ - t1 wrapped = (t1)value; \ - __builtin_memcpy(&mem->data[addr], &wrapped, sizeof(t1)); \ +#define LOAD_DATA(m, o, i, s) { \ + RANGE_CHECK((&m), o, char[s]); \ + load_data(&(m.data[o]), i, s); \ + WASM2C_SHADOW_MEMORY_RESERVE(&m, o, s); \ + WASM2C_SHADOW_MEMORY_STORE(&m, "GlobalDataLoad", o, s); \ +} + +#define DEFINE_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, MEM_ACCESS_REF(mem, addr), sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, addr, sizeof(t1)); \ + return (t3)(t2)result; \ + } + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEM_ACCESS_REF(mem, addr), &wrapped, sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_STORE(mem, func_name, addr, sizeof(t1)); \ } #endif @@ -108,12 +211,83 @@ DEFINE_STORE(i64_store8, u8, u64); DEFINE_STORE(i64_store16, u16, u64); DEFINE_STORE(i64_store32, u32, u64); -#define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) -#define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) -#define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) -#define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) -#define I32_POPCNT(x) (__builtin_popcount(x)) -#define I64_POPCNT(x) (__builtin_popcountll(x)) +#if defined(_MSC_VER) +#include + +// Adapted from https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h + +static inline int I64_CLZ(unsigned long long v) { + unsigned long r = 0; +#if defined(_M_AMD64) || defined(_M_ARM) + if (_BitScanReverse64(&r, v)) { + return 63 - r; + } +#else + if (_BitScanReverse(&r, (unsigned long) (v >> 32))) { + return 31 - r; + } else if (_BitScanReverse(&r, (unsigned long) v)) { + return 63 - r; + } +#endif + return 64; +} + +static inline int I32_CLZ(unsigned long v) { + unsigned long r = 0; + if (_BitScanReverse(&r, v)) { + return 31 - r; + } + return 32; +} + +static inline int I64_CTZ(unsigned long long v) { + if (!v) { + return 64; + } + unsigned long r = 0; +#if defined(_M_AMD64) || defined(_M_ARM) + _BitScanForward64(&r, v); + return (int) r; +#else + if (_BitScanForward(&r, (unsigned int) (v))) { + return (int) (r); + } + + _BitScanForward(&r, (unsigned int) (v >> 32)); + return (int) (r + 32); +#endif +} + +static inline int I32_CTZ(unsigned long v) { + if (!v) { + return 32; + } + unsigned long r = 0; + _BitScanForward(&r, v); + return (int) r; +} + +#define POPCOUNT_DEFINE_PORTABLE(f_n, T) \ + static inline u32 f_n(T x) { \ + x = x - ((x >> 1) & (T)~(T)0/3); \ + x = (x & (T)~(T)0/15*3) + ((x >> 2) & (T)~(T)0/15*3); \ + x = (x + (x >> 4)) & (T)~(T)0/255*15; \ + return (T)(x * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8; \ + } + +POPCOUNT_DEFINE_PORTABLE(I32_POPCNT, u32) +POPCOUNT_DEFINE_PORTABLE(I64_POPCNT, u64) + +#undef POPCOUNT_DEFINE_PORTABLE + +#else +# define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) +# define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) +# define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) +# define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) +# define I32_POPCNT(x) (__builtin_popcount(x)) +# define I64_POPCNT(x) (__builtin_popcountll(x)) +#endif #define DIV_S(ut, min, x, y) \ ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ @@ -211,4 +385,70 @@ DEFINE_REINTERPRET(f32_reinterpret_i32, u32, f32) DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32) DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64) DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64) +%%sandboxapis +//test + +static u32 add_wasm2c_callback(void* sbx_ptr, u32 func_type_idx, void* func_ptr, wasm_rt_elem_target_class_t func_class) { + wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr); + if (!table) { + (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION); + } + for (u32 i = 1; i < table->max_size; i++) { + if (i >= table->size) { + wasm_rt_expand_table(table); + } + if (table->data[i].func == 0) { + table->data[i] = (wasm_rt_elem_t){ func_class, func_type_idx, (wasm_rt_funcref_t) func_ptr }; + return i; + } + } + (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION); +} + +static void remove_wasm2c_callback(void* sbx_ptr, u32 callback_idx) { + wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr); + if (!table) { + abort(); + } + table->data[callback_idx].func = 0; +} + +static u32 lookup_wasm2c_func_index(void* sbx_ptr, u32 param_count, u32 result_count, wasm_rt_type_t* types) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr; + return wasm_rt_register_func_type(&sbx->func_type_structs, &sbx->func_type_count, param_count, result_count, types); +} +static void* create_wasm2c_sandbox(uint32_t max_wasm_pages) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) calloc(sizeof(wasm2c_sandbox_t), 1); + if (!init_memory(sbx, max_wasm_pages)) { + free(sbx); + return 0; + } + init_func_types(sbx); + init_globals(sbx); + init_table(sbx); + wasm_rt_init_wasi(&(sbx->wasi_data)); + init_module_starts(); + return sbx; +} + +static void destroy_wasm2c_sandbox(void* aSbx) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) aSbx; + cleanup_memory(sbx); + cleanup_func_types(sbx); + cleanup_table(sbx); + wasm_rt_cleanup_wasi(&(sbx->wasi_data)); + free(sbx); +} + +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() { + wasm2c_sandbox_funcs_t ret; + ret.wasm_rt_sys_init = &wasm_rt_sys_init; + ret.create_wasm2c_sandbox = &create_wasm2c_sandbox; + ret.destroy_wasm2c_sandbox = &destroy_wasm2c_sandbox; + ret.lookup_wasm2c_nonfunc_export = &lookup_wasm2c_nonfunc_export; + ret.lookup_wasm2c_func_index = &lookup_wasm2c_func_index; + ret.add_wasm2c_callback = &add_wasm2c_callback; + ret.remove_wasm2c_callback = &remove_wasm2c_callback; + return ret; +} diff --git a/src/wasm2c.h.tmpl b/src/wasm2c.h.tmpl index 8478d81be..a79f924e0 100644 --- a/src/wasm2c.h.tmpl +++ b/src/wasm2c.h.tmpl @@ -16,6 +16,8 @@ extern "C" { #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) +#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) + /* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; @@ -28,8 +30,22 @@ typedef int64_t s64; typedef float f32; typedef double f64; -extern void WASM_RT_ADD_PREFIX(init)(void); +#ifndef WASM_DONT_EXPORT_FUNCS +# if defined(_WIN32) +# define FUNC_EXPORT __declspec(dllexport) +# else +# define FUNC_EXPORT +# endif +#else +# define FUNC_EXPORT +#endif + +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); + +struct wasm2c_sandbox_t; +typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; %%bottom + #ifdef __cplusplus } #endif diff --git a/test/spec-wasm2c-prefix.c b/test/spec-wasm2c-prefix.c index 50998f1bf..78b72e9e8 100644 --- a/test/spec-wasm2c-prefix.c +++ b/test/spec-wasm2c-prefix.c @@ -10,7 +10,6 @@ #include #include "wasm-rt.h" -#include "wasm-rt-impl.h" int g_tests_run; int g_tests_passed; diff --git a/wasm2c/README.md b/wasm2c/README.md index 43af5e40c..77e455ee7 100644 --- a/wasm2c/README.md +++ b/wasm2c/README.md @@ -48,15 +48,9 @@ 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 symbols for us, `init` and `Z_facZ_ii`. `init` -initializes the module, and `Z_facZ_ii` is our exported `fac` function, but -[name-mangled](https://en.wikipedia.org/wiki/Name_mangling) to include the -function signature. - -We can define `WASM_RT_MODULE_PREFIX` before including `fac.h` to generate -these symbols with a prefix, in case we already have a symbol called `init` (or -even `Z_facZ_ii`!) Note that you'll have to compile `fac.c` with this macro -too, for this to work. +`wasm2c` generates a few symbols for us, `get_wasm2c_sandbox_info` and `w2c_fac`. +`get_wasm2c_sandbox_info` gives us a way to create and destory wasm instances +initializes the module, and `w2c_fac` is our exported `fac` function. ```c #include @@ -71,30 +65,44 @@ int main(int argc, char** argv) { /* Make sure there is at least one command-line argument. */ if (argc < 2) return 1; - /* Convert the argument from a string to an int. We'll implicitly cast the int + /* Convert the argument from a string to an int. We'll implictly cast the int to a `u32`, which is what `fac` expects. */ u32 x = atoi(argv[1]); - /* Initialize the fac module. Since we didn't define WASM_RT_MODULE_PREFIX, - the initialization function is called `init`. */ - init(); + /* Retrieve sandbox details */ + wasm2c_sandbox_funcs_t sbx_details = get_wasm2c_sandbox_info(); + + /* One time initializations of minimum wasi runtime supported by wasm2c */ + sbx_details.wasm_rt_sys_init(); + + /* Optional upper limit for number of wasm pages for this module. + 0 means no limit */ + int max_wasm_page = 0; + + /* Create a sandbox instance */ + wasm2c_sandbox_t* sbx_instance = (wasm2c_sandbox_t*) sbx_details.create_wasm2c_sandbox(max_wasm_page); /* Call `fac`, using the mangled name. */ - u32 result = Z_facZ_ii(x); + u32 result = w2c_fac(sbx_instance, x); /* Print the result. */ printf("fac(%u) -> %u\n", x, result); + /* Destroy the sandbox instance */ + sbx_details.destroy_wasm2c_sandbox(sbx_instance); + return 0; } ``` To compile the executable, we need to use `main.c` and the generated `fac.c`. We'll also include `wasm-rt-impl.c` which has implementations of the various -`wasm_rt_*` functions used by `fac.c` and `fac.h`. +`wasm_rt_*` functions used by `fac.c` and `fac.h`, `wasm-rt-os-unix.c` and +`wasm-rt-os-win.c` which include some OS specific initialization and +`wasm-rt-wasi.c` which includes a minimum wasi implementation to get things running ```sh -$ cc -o fac main.c fac.c wasm-rt-impl.c +$ cc -o fac main.c fac.c wasm-rt-impl.c wasm-rt-os-unix.c wasm-rt-os-win.c wasm-rt-wasi.c ``` Now let's test it out! @@ -118,73 +126,17 @@ The generated header file looks something like this: ```c #ifndef FAC_H_GENERATED_ #define FAC_H_GENERATED_ -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef WASM_RT_INCLUDED_ -#define WASM_RT_INCLUDED_ - -... - -#endif /* WASM_RT_INCLUDED_ */ - -extern void WASM_RT_ADD_PREFIX(init)(void); - -/* export: 'fac' */ -extern u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32); -#ifdef __cplusplus -} -#endif - -#endif /* FAC_H_GENERATED_ */ -``` - -Let's look at each section. The outer `#ifndef` is standard C boilerplate for a -header. The `extern "C"` part makes sure to not mangle the symbols if using -this header in C++. -```c -#ifndef FAC_H_GENERATED_ -#define FAC_H_GENERATED_ +#define WASM_CURR_MODULE_PREFIX +/* Automically generated by wasm2c */ #ifdef __cplusplus extern "C" { #endif -... - -#ifdef __cplusplus -} -#endif -#endif /* FAC_H_GENERATED_ */ -``` - -This `WASM_RT_INCLUDED_` section contains a number of definitions required for -all WebAssembly modules. - -```c -#ifndef WASM_RT_INCLUDED_ -#define WASM_RT_INCLUDED_ - -... - -#endif /* WASM_RT_INCLUDED_ */ -``` - -First we can specify the maximum call depth before trapping. This defaults to -500: - -```c -#ifndef WASM_RT_MAX_CALL_STACK_DEPTH -#define WASM_RT_MAX_CALL_STACK_DEPTH 500 -#endif -``` +#include -Next we can specify a module prefix. This is useful if you are using multiple -modules that may use the same name as an export. Since we only have one module -here, it's fine to use the default which is an empty prefix: +#include "wasm-rt.h" -```c #ifndef WASM_RT_MODULE_PREFIX #define WASM_RT_MODULE_PREFIX #endif @@ -192,11 +144,10 @@ here, it's fine to use the default which is an empty prefix: #define WASM_RT_PASTE_(x, y) x ## y #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) -``` -Next are some convenient typedefs for integers and floats of fixed sizes: +#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) -```c +/* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; typedef uint16_t u16; @@ -207,132 +158,34 @@ typedef uint64_t u64; typedef int64_t s64; typedef float f32; typedef double f64; -``` - -Next is the `wasm_rt_trap_t` enum, which is used to give the reason a trap -occurred. - -```c -typedef enum { - WASM_RT_TRAP_NONE, - WASM_RT_TRAP_OOB, - WASM_RT_TRAP_INT_OVERFLOW, - WASM_RT_TRAP_DIV_BY_ZERO, - WASM_RT_TRAP_INVALID_CONVERSION, - WASM_RT_TRAP_UNREACHABLE, - WASM_RT_TRAP_CALL_INDIRECT, - WASM_RT_TRAP_EXHAUSTION, -} wasm_rt_trap_t; -``` - -Next is the `wasm_rt_type_t` enum, which is used for specifying function -signatures. The four WebAssembly value types are included: - -```c -typedef enum { - WASM_RT_I32, - WASM_RT_I64, - WASM_RT_F32, - WASM_RT_F64, -} wasm_rt_type_t; -``` - -Next is `wasm_rt_funcref_t`, the function signature for a generic function -callback. Since a WebAssembly table can contain functions of any given -signature, it is necessary to convert them to a canonical form: - -```c -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. - -```c -typedef struct { - uint32_t func_type; - wasm_rt_funcref_t func; -} wasm_rt_elem_t; -``` -Next is the definition of a memory instance. The `data` field is a pointer to -`size` bytes of linear memory. The `size` field of `wasm_rt_memory_t` is the -current size of the memory instance in bytes, whereas `pages` is the current -size in pages (65536 bytes.) `max_pages` is the maximum number of pages as -specified by the module, or `0xffffffff` if there is no limit. - -```c -typedef struct { - uint8_t* data; - uint32_t pages, max_pages; - uint32_t size; -} wasm_rt_memory_t; -``` +#ifndef WASM_DONT_EXPORT_FUNCS +# if defined(_WIN32) +# define FUNC_EXPORT __declspec(dllexport) +# else +# define FUNC_EXPORT +# endif +#else +# define FUNC_EXPORT +#endif -Next is the definition of a table instance. The `data` field is a pointer to -`size` elements. Like a memory instance, `size` is the current size of a table, -and `max_size` is the maximum size of the table, or `0xffffffff` if there is no -limit. +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); -```c -typedef struct { - wasm_rt_elem_t* data; - uint32_t max_size; - uint32_t size; -} wasm_rt_table_t; -``` +struct wasm2c_sandbox_t; +typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; -## Symbols that must be defined by the embedder +FUNC_EXPORT u32 w2c_fac(wasm2c_sandbox_t* const, u32); -Next in `fac.h` are a collection of extern symbols 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 -[`wasm-rt-impl.h`](wasm-rt-impl.h) and [`wasm-rt-impl.c`](wasm-rt-impl.c). +#ifdef __cplusplus +} +#endif -```c -extern void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); -extern uint32_t wasm_rt_register_func_type(uint32_t params, uint32_t results, ...); -extern void wasm_rt_allocate_memory(wasm_rt_memory_t*, uint32_t initial_pages, uint32_t max_pages); -extern uint32_t wasm_rt_grow_memory(wasm_rt_memory_t*, uint32_t pages); -extern void wasm_rt_allocate_table(wasm_rt_table_t*, uint32_t elements, uint32_t max_elements); -extern uint32_t wasm_rt_call_stack_depth; +#endif /* FAC_H_GENERATED_ */ ``` -`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. - -`wasm_rt_register_func_type` is a function that registers a function type. It -is a variadic function where the first two arguments give the number of -parameters and results, and the following arguments are the types. For example, -the function `func (param i32 f32) (result f64)` would register the function -type as -`wasm_rt_register_func_type(2, 1, WASM_RT_I32, WASM_RT_F32, WASM_RT_F64)`. - -`wasm_rt_allocate_memory` initializes a memory instance, and allocates at least -enough space for the given number of initial pages. The memory must be cleared -to zero. - -`wasm_rt_grow_memory` must grow the given memory instance by the given number -of pages. If there isn't enough memory to do so, or the new page count would be -greater than the maximum page count, the function must fail by returning -`0xffffffff`. If the function succeeds, it must return the previous size of the -memory instance, in pages. - -`wasm_rt_allocate_table` initializes a table instance, and allocates at least -enough space for the given number of initial elements. The elements must be -cleared to zero. - -`wasm_rt_call_stack_depth` is the current stack call depth. Since this is -shared between modules, it must be defined only once, by the embedder. - -## Exported symbols - -Finally, `fac.h` defines exported symbols provided by the module. In our +`fac.h` defines exported symbols provided by the module. In our example, the only function we exported was `fac`. An additional function is -provided called `init`, which initializes the module and must be called before -the module can be used: +provided called `get_wasm2c_sandbox_info`, which helps create wasm instances: ```c extern void WASM_RT_ADD_PREFIX(init)(void); @@ -341,13 +194,6 @@ extern void WASM_RT_ADD_PREFIX(init)(void); extern u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32); ``` -All exported names use `WASM_RT_ADD_PREFIX` (as described above) to allow the -symbols to placed in a namespace as decided by the embedder. All symbols are -also mangled so they include the types of the function signature. - -In our example, `Z_facZ_ii` is the mangling for a function named `fac` that -takes one `i32` parameter and returns one `i32` result. - ## A quick look at `fac.c` The contents of `fac.c` are internals, but it is useful to see a little about diff --git a/wasm2c/examples/fac/Makefile b/wasm2c/examples/fac/Makefile index f5b2a3384..fa88c1077 100644 --- a/wasm2c/examples/fac/Makefile +++ b/wasm2c/examples/fac/Makefile @@ -1,6 +1,6 @@ # Use implicit rules for compiling C files. CFLAGS=-I../.. -fac: main.o fac.o ../../wasm-rt-impl.o +fac: main.o fac.o ../../wasm-rt-impl.o ../../wasm-rt-os-unix.o ../../wasm-rt-os-win.o ../../wasm-rt-wasi.o fac.wasm: fac.wat ../../../bin/wat2wasm $< -o $@ diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c index acd8ed91f..aa13e82eb 100644 --- a/wasm2c/examples/fac/fac.c +++ b/wasm2c/examples/fac/fac.c @@ -1,44 +1,191 @@ /* Automically generated by wasm2c */ #include #include +#include #include "fac.h" -#define UNLIKELY(x) __builtin_expect(!!(x), 0) -#define LIKELY(x) __builtin_expect(!!(x), 1) +#if defined(_MSC_VER) +# define UNLIKELY(x) (x) +# define LIKELY(x) (x) +#else +# define UNLIKELY(x) __builtin_expect(!!(x), 0) +# define LIKELY(x) __builtin_expect(!!(x), 1) +#endif #define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) -#define FUNC_PROLOGUE \ - if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ +#ifndef WASM2C_NOSTACK_DEPTH_CHECK + +/** Maximum stack depth before trapping. This can be configured by defining + * this symbol before including wasm-rt when building the generated c files, + * for example: + * + * ``` + * cc -c -DWASM_RT_MAX_CALL_STACK_DEPTH=100 my_module.c -o my_module.o + * ``` + * */ +# ifndef WASM_RT_MAX_CALL_STACK_DEPTH +# define WASM_RT_MAX_CALL_STACK_DEPTH 500 +# endif + +# define FUNC_PROLOGUE(sbx) \ + if (++(sbx->wasm_rt_call_stack_depth) > WASM_RT_MAX_CALL_STACK_DEPTH) \ TRAP(EXHAUSTION) -#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +# define FUNC_EPILOGUE(sbx) --(sbx->wasm_rt_call_stack_depth) -#define UNREACHABLE TRAP(UNREACHABLE) +#else +# define FUNC_PROLOGUE(sbx) +# define FUNC_EPILOGUE(sbx) +#endif -#define CALL_INDIRECT(table, t, ft, x, ...) \ - (LIKELY((x) < table.size && table.data[x].func && \ - table.data[x].func_type == func_types[ft]) \ - ? ((t)table.data[x].func)(__VA_ARGS__) \ - : TRAP(CALL_INDIRECT)) +#ifdef EXTERNAL_CALLBACK_PROLOGUE +#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x) \ + if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \ + EXTERNAL_CALLBACK_PROLOGUE; \ + } +#else +#define EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x) +#endif + +#ifdef EXTERNAL_CALLBACK_EPILOGUE +#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x) \ + if (UNLIKELY(table.data[x].func_class == WASM_RT_EXTERNAL_FUNCTION)) { \ + EXTERNAL_CALLBACK_EPILOGUE; \ + } +#else +#define EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x) +#endif + +#define UNREACHABLE (void) TRAP(UNREACHABLE) + +#define CALL_INDIRECT_VOID(table, t, ft, x, func_types, ...) \ + if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \ + EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \ + ((t)table.data[x].func)(__VA_ARGS__); \ + EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \ + } else { \ + wasm_rt_callback_error_trap(&table, x, func_types[ft]); \ + } + +#define CALL_INDIRECT_RESULT(res, table, t, ft, x, func_types, ...) \ + if (LIKELY((x) < table.size && table.data[x].func && table.data[x].func_type == func_types[ft])) { \ + EXTERNAL_CALLBACK_PROLOGUE_EXEC(table, x); \ + res = ((t)table.data[x].func)(__VA_ARGS__); \ + EXTERNAL_CALLBACK_EPILOGUE_EXEC(table, x); \ + } else { \ + wasm_rt_callback_error_trap(&table, x, func_types[ft]); \ + } + +#if defined(WASM2C_MALLOC_FAIL_CALLBACK) +void WASM2C_MALLOC_FAIL_CALLBACK(u32 ptr_size); +# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size) \ + if (!ptr) { \ + WASM2C_MALLOC_FAIL_CALLBACK(ptr_size); \ + } +#else +# define WASM2C_MALLOC_FAIL_CHECK(ptr, ptr_size) +#endif + +#if defined(WASM_CHECK_SHADOW_MEMORY) +# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_load(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size) wasm2c_shadow_memory_store(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size) wasm2c_shadow_memory_reserve(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size) wasm2c_shadow_memory_dlmalloc(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr) wasm2c_shadow_memory_dlfree(mem, ptr) +# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr) wasm2c_shadow_memory_mark_globals_heap_boundary(mem, ptr) +#else +# define WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_STORE(mem, func_name, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_RESERVE(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLMALLOC(mem, ptr, ptr_size) +# define WASM2C_SHADOW_MEMORY_DLFREE(mem, ptr) +# define WASM2C_SHADOW_MEMORY_MARK_GLOBALS_HEAP_BOUNDARY(mem, ptr) +#endif + +#define RANGE_CHECK(mem, a, t) \ + if (UNLIKELY((a) + sizeof(t) > mem->size)) { (void) TRAP(OOB); } + +#ifdef WASM_USE_GUARD_PAGES +# define MEMCHECK(mem, a, t) +#else +# define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, t) +#endif + +#if defined(WASM_USE_GUARD_PAGES) && UINTPTR_MAX == 0xffffffff +// on 32-bit platforms we have to mask memory access into range +# define MEM_ACCESS_REF(mem, addr) &mem->data[addr & mem->mem_mask] +#else +# define MEM_ACCESS_REF(mem, addr) &mem->data[addr] +#endif + +#if defined(WASM_USING_GLOBAL_HEAP) +# undef MEM_ACCESS_REF +# define MEM_ACCESS_REF(mem, addr) (char*) addr +#endif + +#if WABT_BIG_ENDIAN +static inline void load_data(void *dest, const void *src, size_t n) { + size_t i = 0; + u8 *dest_chars = dest; + memcpy(dest, src, n); + for (i = 0; i < (n>>1); i++) { + u8 cursor = dest_chars[i]; + dest_chars[i] = dest_chars[n - i - 1]; + dest_chars[n - i - 1] = cursor; + } +} +#define LOAD_DATA(m, o, i, s) do { \ + RANGE_CHECK((&m), m.size - o - s, char[s]); \ + load_data(&(m.data[m.size - o - s]), i, s); \ + WASM2C_SHADOW_MEMORY_RESERVE(&m, m.size - o - s, s); \ + WASM2C_SHADOW_MEMORY_STORE(&m, "GlobalDataLoad", m.size - o - s, s); \ +} while(0) + +#define DEFINE_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1)), sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, mem->size - addr - sizeof(t1), sizeof(t1)); \ + return (t3)(t2)result; \ + } -#define MEMCHECK(mem, a, t) \ - if (UNLIKELY((a) + sizeof(t) > mem->size)) TRAP(OOB) +#define DEFINE_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEM_ACCESS_REF(mem, mem->size - addr - sizeof(t1), &wrapped, sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_STORE(mem, func_name, mem->size - addr - sizeof(t1)), sizeof(t1)); \ + } +#else +static inline void load_data(void *dest, const void *src, size_t n) { + memcpy(dest, src, n); +} +#define LOAD_DATA(m, o, i, s) { \ + RANGE_CHECK((&m), o, char[s]); \ + load_data(&(m.data[o]), i, s); \ + WASM2C_SHADOW_MEMORY_RESERVE(&m, o, s); \ + WASM2C_SHADOW_MEMORY_STORE(&m, "GlobalDataLoad", o, s); \ +} -#define DEFINE_LOAD(name, t1, t2, t3) \ - static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \ - MEMCHECK(mem, addr, t1); \ - t1 result; \ - memcpy(&result, &mem->data[addr], sizeof(t1)); \ - return (t3)(t2)result; \ +#define DEFINE_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, MEM_ACCESS_REF(mem, addr), sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_LOAD(mem, func_name, addr, sizeof(t1)); \ + return (t3)(t2)result; \ } -#define DEFINE_STORE(name, t1, t2) \ - static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \ - MEMCHECK(mem, addr, t1); \ - t1 wrapped = (t1)value; \ - memcpy(&mem->data[addr], &wrapped, sizeof(t1)); \ +#define DEFINE_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value, const char* func_name) { \ + MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEM_ACCESS_REF(mem, addr), &wrapped, sizeof(t1)); \ + WASM2C_SHADOW_MEMORY_STORE(mem, func_name, addr, sizeof(t1)); \ } +#endif DEFINE_LOAD(i32_load, u32, u32, u32); DEFINE_LOAD(i64_load, u64, u64, u64); @@ -64,12 +211,83 @@ DEFINE_STORE(i64_store8, u8, u64); DEFINE_STORE(i64_store16, u16, u64); DEFINE_STORE(i64_store32, u32, u64); -#define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) -#define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) -#define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) -#define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) -#define I32_POPCNT(x) (__builtin_popcount(x)) -#define I64_POPCNT(x) (__builtin_popcountll(x)) +#if defined(_MSC_VER) +#include + +// Adapted from https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h + +static inline int I64_CLZ(unsigned long long v) { + unsigned long r = 0; +#if defined(_M_AMD64) || defined(_M_ARM) + if (_BitScanReverse64(&r, v)) { + return 63 - r; + } +#else + if (_BitScanReverse(&r, (unsigned long) (v >> 32))) { + return 31 - r; + } else if (_BitScanReverse(&r, (unsigned long) v)) { + return 63 - r; + } +#endif + return 64; +} + +static inline int I32_CLZ(unsigned long v) { + unsigned long r = 0; + if (_BitScanReverse(&r, v)) { + return 31 - r; + } + return 32; +} + +static inline int I64_CTZ(unsigned long long v) { + if (!v) { + return 64; + } + unsigned long r = 0; +#if defined(_M_AMD64) || defined(_M_ARM) + _BitScanForward64(&r, v); + return (int) r; +#else + if (_BitScanForward(&r, (unsigned int) (v))) { + return (int) (r); + } + + _BitScanForward(&r, (unsigned int) (v >> 32)); + return (int) (r + 32); +#endif +} + +static inline int I32_CTZ(unsigned long v) { + if (!v) { + return 32; + } + unsigned long r = 0; + _BitScanForward(&r, v); + return (int) r; +} + +#define POPCOUNT_DEFINE_PORTABLE(f_n, T) \ + static inline u32 f_n(T x) { \ + x = x - ((x >> 1) & (T)~(T)0/3); \ + x = (x & (T)~(T)0/15*3) + ((x >> 2) & (T)~(T)0/15*3); \ + x = (x + (x >> 4)) & (T)~(T)0/255*15; \ + return (T)(x * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8; \ + } + +POPCOUNT_DEFINE_PORTABLE(I32_POPCNT, u32) +POPCOUNT_DEFINE_PORTABLE(I64_POPCNT, u64) + +#undef POPCOUNT_DEFINE_PORTABLE + +#else +# define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) +# define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) +# define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) +# define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) +# define I32_POPCNT(x) (__builtin_popcount(x)) +# define I64_POPCNT(x) (__builtin_popcountll(x)) +#endif #define DIV_S(ut, min, x, y) \ ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ @@ -114,25 +332,47 @@ DEFINE_STORE(i64_store32, u32, u64); : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ : (x > y) ? x : y) -#define TRUNC_S(ut, st, ft, min, max, maxop, x) \ - ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ - : (UNLIKELY((x) < (ft)(min) || (x) maxop (ft)(max))) ? TRAP(INT_OVERFLOW) \ - : (ut)(st)(x)) - -#define I32_TRUNC_S_F32(x) TRUNC_S(u32, s32, f32, INT32_MIN, INT32_MAX, >=, x) -#define I64_TRUNC_S_F32(x) TRUNC_S(u64, s64, f32, INT64_MIN, INT64_MAX, >=, x) -#define I32_TRUNC_S_F64(x) TRUNC_S(u32, s32, f64, INT32_MIN, INT32_MAX, >, x) -#define I64_TRUNC_S_F64(x) TRUNC_S(u64, s64, f64, INT64_MIN, INT64_MAX, >=, x) - -#define TRUNC_U(ut, ft, max, maxop, x) \ - ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ - : (UNLIKELY((x) <= (ft)-1 || (x) maxop (ft)(max))) ? TRAP(INT_OVERFLOW) \ - : (ut)(x)) - -#define I32_TRUNC_U_F32(x) TRUNC_U(u32, f32, UINT32_MAX, >=, x) -#define I64_TRUNC_U_F32(x) TRUNC_U(u64, f32, UINT64_MAX, >=, x) -#define I32_TRUNC_U_F64(x) TRUNC_U(u32, f64, UINT32_MAX, >, x) -#define I64_TRUNC_U_F64(x) TRUNC_U(u64, f64, UINT64_MAX, >=, x) +#define TRUNC_S(ut, st, ft, min, minop, max, x) \ + ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ + : (UNLIKELY(!((x)minop(min) && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ + : (ut)(st)(x)) + +#define I32_TRUNC_S_F32(x) TRUNC_S(u32, s32, f32, (f32)INT32_MIN, >=, 2147483648.f, x) +#define I64_TRUNC_S_F32(x) TRUNC_S(u64, s64, f32, (f32)INT64_MIN, >=, (f32)INT64_MAX, x) +#define I32_TRUNC_S_F64(x) TRUNC_S(u32, s32, f64, -2147483649., >, 2147483648., x) +#define I64_TRUNC_S_F64(x) TRUNC_S(u64, s64, f64, (f64)INT64_MIN, >=, (f64)INT64_MAX, x) + +#define TRUNC_U(ut, ft, max, x) \ + ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ + : (UNLIKELY(!((x) > (ft)-1 && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ + : (ut)(x)) + +#define I32_TRUNC_U_F32(x) TRUNC_U(u32, f32, 4294967296.f, x) +#define I64_TRUNC_U_F32(x) TRUNC_U(u64, f32, (f32)UINT64_MAX, x) +#define I32_TRUNC_U_F64(x) TRUNC_U(u32, f64, 4294967296., x) +#define I64_TRUNC_U_F64(x) TRUNC_U(u64, f64, (f64)UINT64_MAX, x) + +#define TRUNC_SAT_S(ut, st, ft, min, smin, minop, max, smax, x) \ + ((UNLIKELY((x) != (x))) ? 0 \ + : (UNLIKELY(!((x)minop(min)))) ? smin \ + : (UNLIKELY(!((x) < (max)))) ? smax \ + : (ut)(st)(x)) + +#define I32_TRUNC_SAT_S_F32(x) TRUNC_SAT_S(u32, s32, f32, (f32)INT32_MIN, INT32_MIN, >=, 2147483648.f, INT32_MAX, x) +#define I64_TRUNC_SAT_S_F32(x) TRUNC_SAT_S(u64, s64, f32, (f32)INT64_MIN, INT64_MIN, >=, (f32)INT64_MAX, INT64_MAX, x) +#define I32_TRUNC_SAT_S_F64(x) TRUNC_SAT_S(u32, s32, f64, -2147483649., INT32_MIN, >, 2147483648., INT32_MAX, x) +#define I64_TRUNC_SAT_S_F64(x) TRUNC_SAT_S(u64, s64, f64, (f64)INT64_MIN, INT64_MIN, >=, (f64)INT64_MAX, INT64_MAX, x) + +#define TRUNC_SAT_U(ut, ft, max, smax, x) \ + ((UNLIKELY((x) != (x))) ? 0 \ + : (UNLIKELY(!((x) > (ft)-1))) ? 0 \ + : (UNLIKELY(!((x) < (max)))) ? smax \ + : (ut)(x)) + +#define I32_TRUNC_SAT_U_F32(x) TRUNC_SAT_U(u32, f32, 4294967296.f, UINT32_MAX, x) +#define I64_TRUNC_SAT_U_F32(x) TRUNC_SAT_U(u64, f32, (f32)UINT64_MAX, UINT64_MAX, x) +#define I32_TRUNC_SAT_U_F64(x) TRUNC_SAT_U(u32, f64, 4294967296., UINT32_MAX, x) +#define I64_TRUNC_SAT_U_F64(x) TRUNC_SAT_U(u64, f64, (f64)UINT64_MAX, UINT64_MAX, x) #define DEFINE_REINTERPRET(name, t1, t2) \ static inline t2 name(t1 x) { \ @@ -145,58 +385,149 @@ DEFINE_REINTERPRET(f32_reinterpret_i32, u32, f32) DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32) DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64) DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64) +struct wasm2c_sandbox_t { + wasm_sandbox_wasi_data wasi_data; + u32 wasm_rt_call_stack_depth; + wasm_func_type_t* func_type_structs; + u32 func_type_count; + u32 func_types[1]; +}; + + +static void init_func_types(wasm2c_sandbox_t* const sbx) { + { + wasm_rt_type_t param_ret_types[] = { WASM_RT_I32, WASM_RT_I32 }; + sbx->func_types[0] = wasm_rt_register_func_type(&sbx->func_type_structs, &sbx->func_type_count, 1, 1, param_ret_types); + } +} +static void cleanup_func_types(wasm2c_sandbox_t* const sbx) { + wasm_rt_cleanup_func_types(&sbx->func_type_structs, &sbx->func_type_count); +} +FUNC_EXPORT u32 w2c_fac(wasm2c_sandbox_t* const, u32); -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); +#if defined(ENTRY_PROLOGUE) || defined(ENTRY_EPILOGUE) +FUNC_EXPORT u32 w2centry_w2c_fac(wasm2c_sandbox_t* const sbx, u32 p0) { + ENTRY_PROLOGUE; + u32 ret = w2c_fac(sbx, p0); + ENTRY_EPILOGUE; + return ret; } -static u32 fac(u32); +#endif -static void init_globals(void) { +static void init_globals(wasm2c_sandbox_t* const sbx) { } -static u32 fac(u32 p0) { - FUNC_PROLOGUE; - u32 i0, i1, i2; - i0 = p0; - i1 = 0u; - i0 = i0 == i1; - if (i0) { - i0 = 1u; +FUNC_EXPORT u32 w2c_fac(wasm2c_sandbox_t* const sbx, u32 w2c_p0) { + FUNC_PROLOGUE(sbx); + u32 w2c_i0, w2c_i1, w2c_i2; + w2c_i0 = w2c_p0; + w2c_i1 = 0u; + w2c_i0 = w2c_i0 == w2c_i1; + if (w2c_i0) { + w2c_i0 = 1u; } else { - i0 = p0; - i1 = p0; - i2 = 1u; - i1 -= i2; - i1 = fac(i1); - i0 *= i1; + w2c_i0 = w2c_p0; + w2c_i1 = w2c_p0; + w2c_i2 = 1u; + w2c_i1 -= w2c_i2; + w2c_i1 = w2c_fac(sbx, w2c_i1); + w2c_i0 *= w2c_i1; + } + FUNC_EPILOGUE(sbx); + return w2c_i0; +} + +static bool init_memory(wasm2c_sandbox_t* const sbx, uint32_t max_wasm_pages_from_rt) { + sbx->wasi_data.heap_memory = 0; + return true; +} + +static void cleanup_memory(wasm2c_sandbox_t* const sbx) { +} + +static void init_table(wasm2c_sandbox_t* const sbx) { + uint32_t offset = 0; +} + +static void cleanup_table(wasm2c_sandbox_t* const sbx) { +} + +static void* lookup_wasm2c_nonfunc_export(void* sbx_ptr, const char* name) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr; + return 0; +} + +static wasm_rt_table_t* get_wasm2c_callback_table(void* sbx_ptr){ + return 0; +} + +static void init_module_starts(void) { +} +//test + +static u32 add_wasm2c_callback(void* sbx_ptr, u32 func_type_idx, void* func_ptr, wasm_rt_elem_target_class_t func_class) { + wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr); + if (!table) { + (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION); + } + for (u32 i = 1; i < table->max_size; i++) { + if (i >= table->size) { + wasm_rt_expand_table(table); + } + if (table->data[i].func == 0) { + table->data[i] = (wasm_rt_elem_t){ func_class, func_type_idx, (wasm_rt_funcref_t) func_ptr }; + return i; + } } - FUNC_EPILOGUE; - return i0; + (void) TRAP(CALL_INDIRECT_TABLE_EXPANSION); } -static void init_memory(void) { +static void remove_wasm2c_callback(void* sbx_ptr, u32 callback_idx) { + wasm_rt_table_t* table = get_wasm2c_callback_table(sbx_ptr); + if (!table) { + abort(); + } + table->data[callback_idx].func = 0; } -static void init_table(void) { - uint32_t offset; +static u32 lookup_wasm2c_func_index(void* sbx_ptr, u32 param_count, u32 result_count, wasm_rt_type_t* types) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) sbx_ptr; + return wasm_rt_register_func_type(&sbx->func_type_structs, &sbx->func_type_count, param_count, result_count, types); } -/* export: 'fac' */ -u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32); +static void* create_wasm2c_sandbox(uint32_t max_wasm_pages) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) calloc(sizeof(wasm2c_sandbox_t), 1); + if (!init_memory(sbx, max_wasm_pages)) { + free(sbx); + return 0; + } + init_func_types(sbx); + init_globals(sbx); + init_table(sbx); + wasm_rt_init_wasi(&(sbx->wasi_data)); + init_module_starts(); + return sbx; +} -static void init_exports(void) { - /* export: 'fac' */ - WASM_RT_ADD_PREFIX(Z_facZ_ii) = (&fac); +static void destroy_wasm2c_sandbox(void* aSbx) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) aSbx; + cleanup_memory(sbx); + cleanup_func_types(sbx); + cleanup_table(sbx); + wasm_rt_cleanup_wasi(&(sbx->wasi_data)); + free(sbx); } -void WASM_RT_ADD_PREFIX(init)(void) { - init_func_types(); - init_globals(); - init_memory(); - init_table(); - init_exports(); +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() { + wasm2c_sandbox_funcs_t ret; + ret.wasm_rt_sys_init = &wasm_rt_sys_init; + ret.create_wasm2c_sandbox = &create_wasm2c_sandbox; + ret.destroy_wasm2c_sandbox = &destroy_wasm2c_sandbox; + ret.lookup_wasm2c_nonfunc_export = &lookup_wasm2c_nonfunc_export; + ret.lookup_wasm2c_func_index = &lookup_wasm2c_func_index; + ret.add_wasm2c_callback = &add_wasm2c_callback; + ret.remove_wasm2c_callback = &remove_wasm2c_callback; + return ret; } diff --git a/wasm2c/examples/fac/fac.h b/wasm2c/examples/fac/fac.h index 5e78f33bf..462c73b45 100644 --- a/wasm2c/examples/fac/fac.h +++ b/wasm2c/examples/fac/fac.h @@ -1,5 +1,7 @@ #ifndef FAC_H_GENERATED_ #define FAC_H_GENERATED_ + +#define WASM_CURR_MODULE_PREFIX /* Automically generated by wasm2c */ #ifdef __cplusplus extern "C" { @@ -17,6 +19,8 @@ extern "C" { #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) +#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) + /* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; @@ -29,10 +33,23 @@ typedef int64_t s64; typedef float f32; typedef double f64; -extern void WASM_RT_ADD_PREFIX(init)(void); +#ifndef WASM_DONT_EXPORT_FUNCS +# if defined(_WIN32) +# define FUNC_EXPORT __declspec(dllexport) +# else +# define FUNC_EXPORT +# endif +#else +# define FUNC_EXPORT +#endif + +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); + +struct wasm2c_sandbox_t; +typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; + +FUNC_EXPORT u32 w2c_fac(wasm2c_sandbox_t* const, u32); -/* export: 'fac' */ -extern u32 (*WASM_RT_ADD_PREFIX(Z_facZ_ii))(u32); #ifdef __cplusplus } #endif diff --git a/wasm2c/examples/fac/main.c b/wasm2c/examples/fac/main.c index f7628c52b..a0b4c72b1 100644 --- a/wasm2c/examples/fac/main.c +++ b/wasm2c/examples/fac/main.c @@ -14,15 +14,27 @@ int main(int argc, char** argv) { to a `u32`, which is what `fac` expects. */ u32 x = atoi(argv[1]); - /* Initialize the fac module. Since we didn't define WASM_RT_MODULE_PREFIX, - the initialization function is called `init`. */ - init(); + /* Retrieve sandbox details */ + wasm2c_sandbox_funcs_t sbx_details = get_wasm2c_sandbox_info(); + + /* One time initializations of minimum wasi runtime supported by wasm2c */ + sbx_details.wasm_rt_sys_init(); + + /* Optional upper limit for number of wasm pages for this module. + 0 means no limit */ + int max_wasm_page = 0; + + /* Create a sandbox instance */ + wasm2c_sandbox_t* sbx_instance = (wasm2c_sandbox_t*) sbx_details.create_wasm2c_sandbox(max_wasm_page); /* Call `fac`, using the mangled name. */ - u32 result = Z_facZ_ii(x); + u32 result = w2c_fac(sbx_instance, x); /* Print the result. */ printf("fac(%u) -> %u\n", x, result); + /* Destroy the sandbox instance */ + sbx_details.destroy_wasm2c_sandbox(sbx_instance); + return 0; } diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index 4a8b3baf4..881408131 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -14,9 +14,11 @@ * limitations under the License. */ -#include "wasm-rt-impl.h" +#include "wasm-rt-os.h" +#include "wasm-rt.h" #include +#include #include #include #include @@ -24,39 +26,94 @@ #include #include -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX -#include -#include -#include +#ifdef WASM_RT_CUSTOM_TRAP_HANDLER +// forward declare the signature of any custom trap handler +void WASM_RT_CUSTOM_TRAP_HANDLER(const char*); #endif -#define PAGE_SIZE 65536 - -typedef struct FuncType { - wasm_rt_type_t* params; - wasm_rt_type_t* results; - uint32_t param_count; - uint32_t result_count; -} FuncType; - -uint32_t wasm_rt_call_stack_depth; -uint32_t g_saved_call_stack_depth; - -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER -bool g_signal_handler_installed = false; +void wasm_rt_trap(wasm_rt_trap_t code) { + const char* error_message = "wasm2c: unknown trap"; + switch (code) { + case WASM_RT_TRAP_NONE: { + // this should never happen + error_message = "wasm2c: WASM_RT_TRAP_NONE"; + break; + } + case WASM_RT_TRAP_OOB: { + error_message = "wasm2c: WASM_RT_TRAP_OOB"; + break; + } + case WASM_RT_TRAP_INT_OVERFLOW: { + error_message = "wasm2c: WASM_RT_TRAP_INT_OVERFLOW"; + break; + } + case WASM_RT_TRAP_DIV_BY_ZERO: { + error_message = "wasm2c: WASM_RT_TRAP_DIV_BY_ZERO"; + break; + } + case WASM_RT_TRAP_INVALID_CONVERSION: { + error_message = "wasm2c: WASM_RT_TRAP_INVALID_CONVERSION"; + break; + } + case WASM_RT_TRAP_UNREACHABLE: { + error_message = "wasm2c: WASM_RT_TRAP_UNREACHABLE"; + break; + } + case WASM_RT_TRAP_CALL_INDIRECT_TABLE_EXPANSION: { + error_message = "wasm2c: WASM_RT_TRAP_CALL_INDIRECT_TABLE_EXPANSION"; + break; + } + case WASM_RT_TRAP_CALL_INDIRECT_OOB_INDEX: { + error_message = "wasm2c: WASM_RT_TRAP_CALL_INDIRECT_OOB_INDEX"; + break; + } + case WASM_RT_TRAP_CALL_INDIRECT_NULL_PTR: { + error_message = "wasm2c: WASM_RT_TRAP_CALL_INDIRECT_NULL_PTR"; + break; + } + case WASM_RT_TRAP_CALL_INDIRECT_TYPE_MISMATCH: { + error_message = "wasm2c: WASM_RT_TRAP_CALL_INDIRECT_TYPE_MISMATCH"; + break; + } + case WASM_RT_TRAP_CALL_INDIRECT_UNKNOWN_ERR: { + error_message = "wasm2c: WASM_RT_TRAP_CALL_INDIRECT_UNKNOWN_ERR"; + break; + } + case WASM_RT_TRAP_EXHAUSTION: { + error_message = "wasm2c: WASM_RT_TRAP_EXHAUSTION"; + break; + } + case WASM_RT_TRAP_SHADOW_MEM: { + error_message = "wasm2c: WASM_RT_TRAP_SHADOW_MEM"; + break; + } + case WASM_RT_TRAP_WASI: { + error_message = "wasm2c: WASM_RT_TRAP_WASI"; + break; + } + }; +#ifdef WASM_RT_CUSTOM_TRAP_HANDLER + WASM_RT_CUSTOM_TRAP_HANDLER(error_message); +#else + fprintf(stderr, "Error: %s\n", error_message); + abort(); #endif +} -jmp_buf g_jmp_buf; -FuncType* g_func_types; -uint32_t g_func_type_count; - -void wasm_rt_trap(wasm_rt_trap_t code) { - assert(code != WASM_RT_TRAP_NONE); - wasm_rt_call_stack_depth = g_saved_call_stack_depth; - WASM_RT_LONGJMP(g_jmp_buf, code); +void wasm_rt_callback_error_trap(wasm_rt_table_t* table, + uint32_t func_index, + uint32_t expected_func_type) { + if (func_index >= table->size) { + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_OOB_INDEX); + } else if (!table->data[func_index].func) { + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_NULL_PTR); + } else if (table->data[func_index].func_type != expected_func_type) { + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_TYPE_MISMATCH); + } + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_UNKNOWN_ERR); } -static bool func_types_are_equal(FuncType* a, FuncType* b) { +static bool func_types_are_equal(wasm_func_type_t* a, wasm_func_type_t* b) { if (a->param_count != b->param_count || a->result_count != b->result_count) return 0; uint32_t i; @@ -69,80 +126,213 @@ static bool func_types_are_equal(FuncType* a, FuncType* b) { return 1; } -uint32_t wasm_rt_register_func_type(uint32_t param_count, +uint32_t wasm_rt_register_func_type(wasm_func_type_t** p_func_type_structs, + uint32_t* p_func_type_count, + uint32_t param_count, uint32_t result_count, - ...) { - FuncType func_type; + wasm_rt_type_t* types) { + wasm_func_type_t func_type; + func_type.param_count = param_count; - func_type.params = malloc(param_count * sizeof(wasm_rt_type_t)); - func_type.result_count = result_count; - func_type.results = malloc(result_count * sizeof(wasm_rt_type_t)); + if (func_type.param_count != 0) { + func_type.params = malloc(param_count * sizeof(wasm_rt_type_t)); + assert(func_type.params != 0); + } else { + func_type.params = 0; + } - va_list args; - va_start(args, result_count); + func_type.result_count = result_count; + if (func_type.result_count != 0) { + func_type.results = malloc(result_count * sizeof(wasm_rt_type_t)); + assert(func_type.results != 0); + } else { + func_type.results = 0; + } uint32_t i; for (i = 0; i < param_count; ++i) - func_type.params[i] = va_arg(args, wasm_rt_type_t); + func_type.params[i] = types[i]; for (i = 0; i < result_count; ++i) - func_type.results[i] = va_arg(args, wasm_rt_type_t); - va_end(args); + func_type.results[i] = types[(uint64_t)(param_count) + i]; - for (i = 0; i < g_func_type_count; ++i) { - if (func_types_are_equal(&g_func_types[i], &func_type)) { - free(func_type.params); - free(func_type.results); + for (i = 0; i < *p_func_type_count; ++i) { + wasm_func_type_t* func_types = *p_func_type_structs; + if (func_types_are_equal(&func_types[i], &func_type)) { + if (func_type.params) { + free(func_type.params); + } + if (func_type.results) { + free(func_type.results); + } return i + 1; } } - uint32_t idx = g_func_type_count++; - g_func_types = realloc(g_func_types, g_func_type_count * sizeof(FuncType)); - g_func_types[idx] = func_type; + uint32_t idx = (*p_func_type_count)++; + // realloc works fine even if *p_func_type_structs is null + *p_func_type_structs = realloc(*p_func_type_structs, + *p_func_type_count * sizeof(wasm_func_type_t)); + (*p_func_type_structs)[idx] = func_type; return idx + 1; } -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX -static void signal_handler(int sig, siginfo_t* si, void* unused) { - wasm_rt_trap(WASM_RT_TRAP_OOB); +void wasm_rt_cleanup_func_types(wasm_func_type_t** p_func_type_structs, + uint32_t* p_func_type_count) { + // Use a u64 to iterate over u32 arrays to prevent infinite loops + const uint32_t func_count = *p_func_type_count; + for (uint64_t idx = 0; idx < func_count; idx++) { + wasm_func_type_t* func_type = &((*p_func_type_structs)[idx]); + if (func_type->params != 0) { + free(func_type->params); + func_type->params = 0; + } + if (func_type->results != 0) { + free(func_type->results); + func_type->results = 0; + } + } + free(*p_func_type_structs); +} + +#if UINTPTR_MAX == 0xffffffff +static int is_power_of_two(uint64_t x) { + return ((x != 0) && !(x & (x - 1))); } #endif -void wasm_rt_allocate_memory(wasm_rt_memory_t* memory, +#define WASM_PAGE_SIZE 65536 + +#if UINTPTR_MAX == 0xffffffffffffffff +// Guard page of 4GiB +#define WASM_HEAP_GUARD_PAGE_SIZE 0x100000000ull +// Heap aligned to 4GB +#define WASM_HEAP_ALIGNMENT 0x100000000ull +// By default max heap is 4GB +#define WASM_HEAP_DEFAULT_MAX_PAGES 65536 +// Runtime can override the max heap up to 4GB +#define WASM_HEAP_MAX_ALLOWED_PAGES 65536 +#elif UINTPTR_MAX == 0xffffffff +// No guard pages +#define WASM_HEAP_GUARD_PAGE_SIZE 0 +// Unaligned heap +#define WASM_HEAP_ALIGNMENT 0 +// Default max heap is 16MB (1GB if you enable incremental heaps) +#ifdef WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC +#define WASM_HEAP_DEFAULT_MAX_PAGES 16384 +#else +#define WASM_HEAP_DEFAULT_MAX_PAGES 256 +#endif +// Runtime can override the max heap up to 1GB +#define WASM_HEAP_MAX_ALLOWED_PAGES 16384 +#else +#error "Unknown pointer size" +#endif + +uint64_t wasm_rt_get_default_max_linear_memory_size() { + uint64_t ret = ((uint64_t)WASM_HEAP_DEFAULT_MAX_PAGES) * WASM_PAGE_SIZE; + return ret; +} + +static uint64_t compute_heap_reserve_space(uint32_t chosen_max_pages) { + const uint64_t heap_reserve_size = + ((uint64_t)chosen_max_pages) * WASM_PAGE_SIZE + WASM_HEAP_GUARD_PAGE_SIZE; + return heap_reserve_size; +} + +bool wasm_rt_allocate_memory(wasm_rt_memory_t* memory, uint32_t initial_pages, uint32_t max_pages) { - uint32_t byte_length = initial_pages * PAGE_SIZE; -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX - if (!g_signal_handler_installed) { - g_signal_handler_installed = true; - struct sigaction sa; - sa.sa_flags = SA_SIGINFO; - sigemptyset(&sa.sa_mask); - sa.sa_sigaction = signal_handler; - - /* Install SIGSEGV and SIGBUS handlers, since macOS seems to use SIGBUS. */ - if (sigaction(SIGSEGV, &sa, NULL) != 0 || - sigaction(SIGBUS, &sa, NULL) != 0) { - perror("sigaction failed"); - abort(); - } - } - - /* Reserve 8GiB. */ - void* addr = - mmap(NULL, 0x200000000ul, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (addr == (void*)-1) { - perror("mmap failed"); - abort(); - } - mprotect(addr, byte_length, PROT_READ | PROT_WRITE); - memory->data = addr; + const uint32_t byte_length = initial_pages * WASM_PAGE_SIZE; + + const uint32_t suggested_max_pages = + max_pages == 0 ? WASM_HEAP_DEFAULT_MAX_PAGES : max_pages; + const uint32_t chosen_max_pages = + (WASM_HEAP_MAX_ALLOWED_PAGES < suggested_max_pages) + ? WASM_HEAP_MAX_ALLOWED_PAGES + : suggested_max_pages; + + if (chosen_max_pages < initial_pages) { + return false; + } + +#ifdef WASM_USE_GUARD_PAGES + // mmap based heaps with guard pages + // Guard pages already allocates memory incrementally thus we don't need to + // look at WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC + void* addr = NULL; + const uint64_t retries = 10; + const uint64_t heap_reserve_size = + compute_heap_reserve_space(chosen_max_pages); + + // 32-bit platforms rely on masking for sandboxing + // thus we require the heap reserve size to always be a power of 2 +#if UINTPTR_MAX == 0xffffffff + if (!is_power_of_two(heap_reserve_size)) { + return false; + } +#endif + + for (uint64_t i = 0; i < retries; i++) { + addr = + os_mmap_aligned(NULL, heap_reserve_size, MMAP_PROT_NONE, MMAP_MAP_NONE, + WASM_HEAP_ALIGNMENT, 0 /* alignment_offset */); + if (addr) { + break; + } + } + + if (!addr) { + os_print_last_error("os_mmap failed."); + return false; + } + int ret = os_mmap_commit(addr, byte_length, MMAP_PROT_READ | MMAP_PROT_WRITE); + if (ret != 0) { + return false; + } + // This is a valid way to initialize a constant field that is not undefined + // behavior + // https://stackoverflow.com/questions/9691404/how-to-initialize-const-in-a-struct-in-c-with-malloc + // Summary: malloc of a struct, followed by a write to the constant fields is + // still defined behavior iff + // there is no prior read of the field + *(uint8_t**)&memory->data = addr; #else + // malloc based heaps +#ifdef WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC memory->data = calloc(byte_length, 1); +#else + const uint64_t heap_max_size = ((uint64_t)chosen_max_pages) * WASM_PAGE_SIZE; + *(uint8_t**)&memory->data = calloc(heap_max_size, 1); +#endif #endif + memory->size = byte_length; memory->pages = initial_pages; - memory->max_pages = max_pages; + memory->max_pages = chosen_max_pages; + + // 32-bit platforms use masking for sandboxing. Compute the mask +#if UINTPTR_MAX == 0xffffffff + *(uint32_t*)&memory->mem_mask = heap_reserve_size - 1; +#endif + +#if defined(WASM_CHECK_SHADOW_MEMORY) + wasm2c_shadow_memory_create(memory); +#endif + return true; +} + +void wasm_rt_deallocate_memory(wasm_rt_memory_t* memory) { +#ifdef WASM_USE_GUARD_PAGES + const uint64_t heap_reserve_size = + compute_heap_reserve_space(memory->max_pages); + os_munmap(memory->data, heap_reserve_size); +#else + free(memory->data); +#endif + +#if defined(WASM_CHECK_SHADOW_MEMORY) + wasm2c_shadow_memory_destroy(memory); +#endif } uint32_t wasm_rt_grow_memory(wasm_rt_memory_t* memory, uint32_t delta) { @@ -154,35 +344,108 @@ uint32_t wasm_rt_grow_memory(wasm_rt_memory_t* memory, uint32_t delta) { if (new_pages < old_pages || new_pages > memory->max_pages) { return (uint32_t)-1; } - uint32_t old_size = old_pages * PAGE_SIZE; - uint32_t new_size = new_pages * PAGE_SIZE; - uint32_t delta_size = delta * PAGE_SIZE; -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX - uint8_t* new_data = memory->data; - mprotect(new_data + old_size, delta_size, PROT_READ | PROT_WRITE); + uint32_t old_size = old_pages * WASM_PAGE_SIZE; + uint32_t new_size = new_pages * WASM_PAGE_SIZE; + uint32_t delta_size = delta * WASM_PAGE_SIZE; + +#ifdef WASM_USE_GUARD_PAGES + // mmap based heaps with guard pages + int ret = os_mmap_commit(memory->data + old_size, delta_size, + MMAP_PROT_READ | MMAP_PROT_WRITE); + if (ret != 0) { + return (uint32_t)-1; + } #else + // malloc based heaps --- if below macro is not defined, the max memory range + // is already allocated +#ifdef WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC uint8_t* new_data = realloc(memory->data, new_size); if (new_data == NULL) { return (uint32_t)-1; } #if !WABT_BIG_ENDIAN memset(new_data + old_size, 0, delta_size); +#endif + memory->data = new_data; #endif #endif + #if WABT_BIG_ENDIAN - memmove(new_data + new_size - old_size, new_data, old_size); - memset(new_data, 0, delta_size); + memmove(memory->data + new_size - old_size, memory->data, old_size); + memset(memory->data, 0, delta_size); #endif memory->pages = new_pages; memory->size = new_size; - memory->data = new_data; +#if defined(WASM_CHECK_SHADOW_MEMORY) + wasm2c_shadow_memory_expand(memory); +#endif return old_pages; } void wasm_rt_allocate_table(wasm_rt_table_t* table, uint32_t elements, uint32_t max_elements) { + assert(max_elements >= elements); table->size = elements; table->max_size = max_elements; table->data = calloc(table->size, sizeof(wasm_rt_elem_t)); + assert(table->data != 0); +} + +void wasm_rt_deallocate_table(wasm_rt_table_t* table) { + free(table->data); } + +#define WASM_SATURATING_U32_ADD(ret_ptr, a, b) \ + { \ + if ((a) > (UINT32_MAX - (b))) { \ + /* add will overflowed */ \ + *ret_ptr = UINT32_MAX; \ + } else { \ + *ret_ptr = (a) + (b); \ + } \ + } + +#define WASM_CHECKED_U32_RET_SIZE_T_MULTIPLY(ret_ptr, a, b) \ + { \ + if ((a) > (SIZE_MAX / (b))) { \ + /* multiple will overflowed */ \ + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_TABLE_EXPANSION); \ + } else { \ + /* convert to size by assigning */ \ + *ret_ptr = a; \ + *ret_ptr = *ret_ptr * b; \ + } \ + } + +void wasm_rt_expand_table(wasm_rt_table_t* table) { + uint32_t new_size = 0; + WASM_SATURATING_U32_ADD(&new_size, table->size, 32); + + if (new_size > table->max_size) { + new_size = table->max_size; + } + + if (table->size == new_size) { + // table is already as large as we allowed, can't expand further + wasm_rt_trap(WASM_RT_TRAP_CALL_INDIRECT_TABLE_EXPANSION); + } + + size_t allocation_size = 0; + WASM_CHECKED_U32_RET_SIZE_T_MULTIPLY(&allocation_size, sizeof(wasm_rt_elem_t), + new_size); + table->data = realloc(table->data, allocation_size); + assert(table->data != 0); + + memset(&(table->data[table->size]), 0, + allocation_size - (table->size * sizeof(wasm_rt_elem_t))); + table->size = new_size; +} + +#undef WASM_PAGE_SIZE +#undef WASM_HEAP_GUARD_PAGE_SIZE +#undef WASM_HEAP_ALIGNMENT +#undef WASM_HEAP_DEFAULT_MAX_PAGES +#undef WASM_HEAP_MAX_ALLOWED_PAGES +#undef WASM_SATURATING_U32_ADD +#undef WASM_CHECKED_U32_RET_SIZE_T_MULTIPLY diff --git a/wasm2c/wasm-rt-impl.h b/wasm2c/wasm-rt-impl.h deleted file mode 100644 index 39d5ed930..000000000 --- a/wasm2c/wasm-rt-impl.h +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2018 WebAssembly Community Group participants - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef WASM_RT_IMPL_H_ -#define WASM_RT_IMPL_H_ - -#include - -#include "wasm-rt.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** A setjmp buffer used for handling traps. */ -extern jmp_buf g_jmp_buf; - -/** Saved call stack depth that will be restored in case a trap occurs. */ -extern uint32_t g_saved_call_stack_depth; - -#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) -#endif - -/** Convenience macro to use before calling a wasm function. On first execution - * it will return `WASM_RT_TRAP_NONE` (i.e. 0). If the function traps, it will - * jump back and return the trap that occurred. - * - * ``` - * wasm_rt_trap_t code = wasm_rt_impl_try(); - * if (code != 0) { - * printf("A trap occurred with code: %d\n", code); - * ... - * } - * - * // Call the potentially-trapping function. - * my_wasm_func(); - * ``` - */ -#define wasm_rt_impl_try() \ - (g_saved_call_stack_depth = wasm_rt_call_stack_depth, \ - WASM_RT_SETJMP(g_jmp_buf)) - -#ifdef __cplusplus -} -#endif - -#endif // WASM_RT_IMPL_H_ diff --git a/wasm2c/wasm-rt-os-unix.c b/wasm2c/wasm-rt-os-unix.c new file mode 100644 index 000000000..c2035ec5b --- /dev/null +++ b/wasm2c/wasm-rt-os-unix.c @@ -0,0 +1,294 @@ +// Based on +// https://web.archive.org/web/20191012035921/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system#BSD +// Check for any posix or unix OS +#if !defined(_WIN32) && (defined(__unix__) || defined(__unix) || \ + (defined(__APPLE__) && defined(__MACH__))) + +#include "wasm-rt-os.h" +#include "wasm-rt.h" + +#include +#include +#include +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) +// Macs priors to OSX 10.12 don't have the clock functions. So we will use mac +// specific options +#include +#include +#endif +#include +#include + +#ifdef VERBOSE_LOGGING +#define VERBOSE_LOG(...) \ + { printf(__VA_ARGS__); } +#else +#define VERBOSE_LOG(...) +#endif + +size_t os_getpagesize() { + return getpagesize(); +} + +void* os_mmap(void* hint, size_t size, int prot, int flags) { + int map_prot = PROT_NONE; + int map_flags = MAP_ANONYMOUS | MAP_PRIVATE; + uint64_t request_size, page_size; + uint8_t* addr; + + page_size = (uint64_t)os_getpagesize(); + request_size = (size + page_size - 1) & ~(page_size - 1); + + if ((size_t)request_size < size) + /* integer overflow */ + return NULL; + + if (request_size > 16 * (uint64_t)UINT32_MAX) + /* At most 16 G is allowed */ + return NULL; + + if (prot & MMAP_PROT_READ) + map_prot |= PROT_READ; + + if (prot & MMAP_PROT_WRITE) + map_prot |= PROT_WRITE; + + if (prot & MMAP_PROT_EXEC) + map_prot |= PROT_EXEC; + +#if defined(BUILD_TARGET_X86_64) || defined(BUILD_TARGET_AMD_64) +#ifndef __APPLE__ + if (flags & MMAP_MAP_32BIT) + map_flags |= MAP_32BIT; +#endif +#endif + + if (flags & MMAP_MAP_FIXED) + map_flags |= MAP_FIXED; + + addr = mmap(hint, request_size, map_prot, map_flags, -1, 0); + + if (addr == MAP_FAILED) + return NULL; + + return addr; +} + +void os_munmap(void* addr, size_t size) { + uint64_t page_size = (uint64_t)os_getpagesize(); + uint64_t request_size = (size + page_size - 1) & ~(page_size - 1); + + if (addr) { + if (munmap(addr, request_size)) { + printf("os_munmap error addr:%p, size:0x%" PRIx64 ", errno:%d\n", addr, + request_size, errno); + } + } +} + +int os_mprotect(void* addr, size_t size, int prot) { + int map_prot = PROT_NONE; + uint64_t page_size = (uint64_t)os_getpagesize(); + uint64_t request_size = (size + page_size - 1) & ~(page_size - 1); + + if (!addr) + return 0; + + if (prot & MMAP_PROT_READ) + map_prot |= PROT_READ; + + if (prot & MMAP_PROT_WRITE) + map_prot |= PROT_WRITE; + + if (prot & MMAP_PROT_EXEC) + map_prot |= PROT_EXEC; + + return mprotect(addr, request_size, map_prot); +} + +void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset) { + size_t padded_length = requested_length + alignment + alignment_offset; + uintptr_t unaligned = (uintptr_t)os_mmap(addr, padded_length, prot, flags); + + VERBOSE_LOG( + "os_mmap_aligned: alignment:%llu, alignment_offset:%llu, " + "requested_length:%llu, padded_length: %llu, initial mapping: %p\n", + (unsigned long long)alignment, (unsigned long long)alignment_offset, + (unsigned long long)requested_length, (unsigned long long)padded_length, + (void*)unaligned); + + if (!unaligned) { + return (void*)unaligned; + } + + // Round up the next address that has addr % alignment = 0 + const size_t alignment_corrected = alignment == 0 ? 1 : alignment; + uintptr_t aligned_nonoffset = + (unaligned + (alignment_corrected - 1)) & ~(alignment_corrected - 1); + + // Currently offset 0 is aligned according to alignment + // Alignment needs to be enforced at the given offset + uintptr_t aligned = 0; + if ((aligned_nonoffset - alignment_offset) >= unaligned) { + aligned = aligned_nonoffset - alignment_offset; + } else { + aligned = aligned_nonoffset - alignment_offset + alignment; + } + + // Sanity check + if (aligned < unaligned || + (aligned + (requested_length - 1)) > (unaligned + (padded_length - 1)) || + (aligned + alignment_offset) % alignment_corrected != 0) { + VERBOSE_LOG("os_mmap_aligned: sanity check fail. aligned: %p\n", + (void*)aligned); + os_munmap((void*)unaligned, padded_length); + return NULL; + } + + { + size_t unused_front = aligned - unaligned; + if (unused_front != 0) { + os_munmap((void*)unaligned, unused_front); + } + } + + { + size_t unused_back = + (unaligned + (padded_length - 1)) - (aligned + (requested_length - 1)); + if (unused_back != 0) { + os_munmap((void*)(aligned + requested_length), unused_back); + } + } + + VERBOSE_LOG("os_mmap_aligned: final mapping: %p\n", (void*)aligned); + return (void*)aligned; +} + +int os_mmap_commit(void* curr_heap_end_pointer, + size_t expanded_size, + int prot) { + return os_mprotect(curr_heap_end_pointer, expanded_size, prot); +} + +#if defined(__APPLE__) && defined(__MACH__) +typedef struct { + mach_timebase_info_data_t timebase; /* numer = 0, denom = 0 */ + struct timespec inittime; /* nanoseconds since 1-Jan-1970 to init() */ + uint64_t initclock; /* ticks since boot to init() */ +} wasi_mac_clock_info_t; + +static wasi_mac_clock_info_t g_wasi_mac_clock_info; +static int g_os_data_initialized = 0; +#endif + +void os_init() { +#if defined(__APPLE__) && defined(__MACH__) + // From here: + // https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x/21352348#21352348 + if (mach_timebase_info(&g_wasi_mac_clock_info.timebase) != 0) { + wasm_rt_trap(WASM_RT_TRAP_WASI); + } + + // microseconds since 1 Jan 1970 + struct timeval micro; + if (gettimeofday(µ, NULL) != 0) { + wasm_rt_trap(WASM_RT_TRAP_WASI); + } + + g_wasi_mac_clock_info.initclock = mach_absolute_time(); + + g_wasi_mac_clock_info.inittime.tv_sec = micro.tv_sec; + g_wasi_mac_clock_info.inittime.tv_nsec = micro.tv_usec * 1000; + + g_os_data_initialized = 1; +#endif +} + +void os_clock_init(void** clock_data_pointer) { +#if defined(__APPLE__) && defined(__MACH__) + if (!g_os_data_initialized) { + os_init(); + } + + wasi_mac_clock_info_t* alloc = + (wasi_mac_clock_info_t*)malloc(sizeof(wasi_mac_clock_info_t)); + if (!alloc) { + wasm_rt_trap(WASM_RT_TRAP_WASI); + } + memcpy(alloc, &g_wasi_mac_clock_info, sizeof(wasi_mac_clock_info_t)); + *clock_data_pointer = alloc; +#endif +} + +void os_clock_cleanup(void** clock_data_pointer) { +#if defined(__APPLE__) && defined(__MACH__) + if (*clock_data_pointer == 0) { + free(*clock_data_pointer); + *clock_data_pointer = 0; + } +#endif +} + +int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct) { + int ret = 0; +#if defined(__APPLE__) && defined(__MACH__) + wasi_mac_clock_info_t* alloc = (wasi_mac_clock_info_t*)clock_data; + + // From here: + // https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x/21352348#21352348 + + (void)clock_id; + // ticks since init + uint64_t clock = mach_absolute_time() - alloc->initclock; + // nanoseconds since init + uint64_t nano = clock * (uint64_t)(alloc->timebase.numer) / + (uint64_t)(alloc->timebase.denom); + *out_struct = alloc->inittime; + +#define BILLION 1000000000L + out_struct->tv_sec += nano / BILLION; + out_struct->tv_nsec += nano % BILLION; + // normalize + out_struct->tv_sec += out_struct->tv_nsec / BILLION; + out_struct->tv_nsec = out_struct->tv_nsec % BILLION; +#undef BILLION +#else + ret = clock_gettime(clock_id, out_struct); +#endif + return ret; +} + +int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct) { + int ret = 0; +#if defined(__APPLE__) && defined(__MACH__) + (void)clock_id; + out_struct->tv_sec = 0; + out_struct->tv_nsec = 1; +#else + ret = clock_getres(clock_id, out_struct); +#endif + return ret; +} + +void os_print_last_error(const char* msg) { + perror(msg); +} + +#undef VERBOSE_LOG + +#else +// https://stackoverflow.com/questions/26541150/warning-iso-c-forbids-an-empty-translation-unit +typedef int make_iso_compilers_happy; +#endif \ No newline at end of file diff --git a/wasm2c/wasm-rt-os-win.c b/wasm2c/wasm-rt-os-win.c new file mode 100644 index 000000000..d4a8d83ea --- /dev/null +++ b/wasm2c/wasm-rt-os-win.c @@ -0,0 +1,325 @@ +// Based on +// https://web.archive.org/web/20191012035921/http://nadeausoftware.com/articles/2012/01/c_c_tip_how_use_compiler_predefined_macros_detect_operating_system#BSD +// Check for windows (non cygwin) environment +#if defined(_WIN32) + +#include "wasm-rt-os.h" +#include "wasm-rt.h" + +#include +#include +#include +#include +#include + +#include + +#ifdef VERBOSE_LOGGING +#define VERBOSE_LOG(...) \ + { printf(__VA_ARGS__); } +#else +#define VERBOSE_LOG(...) +#endif + +#define DONT_USE_VIRTUAL_ALLOC2 + +size_t os_getpagesize() { + SYSTEM_INFO S; + GetNativeSystemInfo(&S); + return S.dwPageSize; +} + +static void* win_mmap(void* hint, + size_t size, + int prot, + int flags, + DWORD alloc_flag) { + DWORD flProtect = PAGE_NOACCESS; + size_t request_size, page_size; + void* addr; + + page_size = os_getpagesize(); + request_size = (size + page_size - 1) & ~(page_size - 1); + + if (request_size < size) + /* integer overflow */ + return NULL; + + if (request_size == 0) + request_size = page_size; + + if (prot & MMAP_PROT_EXEC) { + if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_EXECUTE_READWRITE; + else + flProtect = PAGE_EXECUTE_READ; + } else if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_READWRITE; + else if (prot & MMAP_PROT_READ) + flProtect = PAGE_READONLY; + + addr = VirtualAlloc((LPVOID)hint, request_size, alloc_flag, flProtect); + return addr; +} + +void* os_mmap(void* hint, size_t size, int prot, int flags) { + DWORD alloc_flag = MEM_RESERVE | MEM_COMMIT; + return win_mmap(hint, size, prot, flags, alloc_flag); +} + +#ifndef DONT_USE_VIRTUAL_ALLOC2 +static void* win_mmap_aligned(void* hint, + size_t size, + int prot, + int flags, + size_t pow2alignment) { + DWORD alloc_flag = MEM_RESERVE | MEM_COMMIT; + DWORD flProtect = PAGE_NOACCESS; + size_t request_size, page_size; + void* addr; + + page_size = os_getpagesize(); + request_size = (size + page_size - 1) & ~(page_size - 1); + + if (request_size < size) + /* integer overflow */ + return NULL; + + if (request_size == 0) + request_size = page_size; + + if (prot & MMAP_PROT_EXEC) { + if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_EXECUTE_READWRITE; + else + flProtect = PAGE_EXECUTE_READ; + } else if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_READWRITE; + else if (prot & MMAP_PROT_READ) + flProtect = PAGE_READONLY; + + MEM_ADDRESS_REQUIREMENTS addressReqs = {0}; + MEM_EXTENDED_PARAMETER param = {0}; + + addressReqs.Alignment = pow2alignment; + addressReqs.HighestEndingAddress = 0; + addressReqs.LowestStartingAddress = 0; + + param.Type = MemExtendedParameterAddressRequirements; + param.Pointer = &addressReqs; + + addr = VirtualAlloc2(0, (LPVOID)addr, request_size, alloc_flag, flProtect, + ¶m, 1); + return addr; +} +#endif + +void os_munmap(void* addr, size_t size) { + DWORD alloc_flag = MEM_RELEASE; + if (addr) { + if (VirtualFree(addr, 0, alloc_flag) == 0) { + size_t page_size = os_getpagesize(); + size_t request_size = (size + page_size - 1) & ~(page_size - 1); + int64_t curr_err = errno; + printf("os_munmap error addr:%p, size:0x%zx, errno:%" PRId64 "\n", addr, + request_size, curr_err); + } + } +} + +int os_mprotect(void* addr, size_t size, int prot) { + DWORD flProtect = PAGE_NOACCESS; + + if (!addr) + return 0; + + if (prot & MMAP_PROT_EXEC) { + if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_EXECUTE_READWRITE; + else + flProtect = PAGE_EXECUTE_READ; + } else if (prot & MMAP_PROT_WRITE) + flProtect = PAGE_READWRITE; + else if (prot & MMAP_PROT_READ) + flProtect = PAGE_READONLY; + + DWORD old; + BOOL succeeded = VirtualProtect((LPVOID)addr, size, flProtect, &old); + return succeeded ? 0 : -1; +} + +#ifndef DONT_USE_VIRTUAL_ALLOC2 +static int IsPowerOfTwoOrZero(size_t x) { + return (x & (x - 1)) == 0; +} +#endif + +void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset) { +#ifndef DONT_USE_VIRTUAL_ALLOC2 + if (IsPowerOfTwoOrZero(alignment) && alignment_offset == 0) { + return win_mmap_aligned(addr, requested_length, prot, flags, alignment); + } else +#endif + { + size_t padded_length = requested_length + alignment + alignment_offset; + uintptr_t unaligned = + (uintptr_t)win_mmap(addr, padded_length, prot, flags, MEM_RESERVE); + + VERBOSE_LOG( + "os_mmap_aligned: alignment:%llu, alignment_offset:%llu, " + "requested_length:%llu, padded_length: %llu, initial mapping: %p\n", + (unsigned long long)alignment, (unsigned long long)alignment_offset, + (unsigned long long)requested_length, (unsigned long long)padded_length, + (void*)unaligned); + + if (!unaligned) { + return (void*)unaligned; + } + + // Round up the next address that has addr % alignment = 0 + const size_t alignment_corrected = alignment == 0 ? 1 : alignment; + uintptr_t aligned_nonoffset = + (unaligned + (alignment_corrected - 1)) & ~(alignment_corrected - 1); + + // Currently offset 0 is aligned according to alignment + // Alignment needs to be enforced at the given offset + uintptr_t aligned = 0; + if ((aligned_nonoffset - alignment_offset) >= unaligned) { + aligned = aligned_nonoffset - alignment_offset; + } else { + aligned = aligned_nonoffset - alignment_offset + alignment; + } + + if (aligned == unaligned && padded_length == requested_length) { + return (void*)aligned; + } + + // Sanity check + if (aligned < unaligned || + (aligned + (requested_length - 1)) > + (unaligned + (padded_length - 1)) || + (aligned + alignment_offset) % alignment_corrected != 0) { + VERBOSE_LOG("os_mmap_aligned: sanity check fail. aligned: %p\n", + (void*)aligned); + os_munmap((void*)unaligned, padded_length); + return NULL; + } + + // windows does not support partial unmapping, so unmap and remap + os_munmap((void*)unaligned, padded_length); + aligned = (uintptr_t)win_mmap((void*)aligned, requested_length, prot, flags, + MEM_RESERVE); + VERBOSE_LOG("os_mmap_aligned: final mapping: %p\n", (void*)aligned); + return (void*)aligned; + } +} + +int os_mmap_commit(void* curr_heap_end_pointer, + size_t expanded_size, + int prot) { + uintptr_t addr = (uintptr_t)win_mmap(curr_heap_end_pointer, expanded_size, + prot, MMAP_MAP_NONE, MEM_COMMIT); + int ret = addr ? 0 : -1; + return ret; +} + +typedef struct { + LARGE_INTEGER counts_per_sec; +} wasi_win_clock_info_t; + +static wasi_win_clock_info_t g_wasi_win_clock_info; +static int g_os_data_initialized = 0; + +void os_init() { + // From here: + // https://stackoverflow.com/questions/5404277/porting-clock-gettime-to-windows/38212960#38212960 + if (QueryPerformanceFrequency(&g_wasi_win_clock_info.counts_per_sec) == 0) { + wasm_rt_trap(WASM_RT_TRAP_WASI); + } + g_os_data_initialized = 1; +} + +void os_clock_init(void** clock_data_pointer) { + if (!g_os_data_initialized) { + os_init(); + } + + wasi_win_clock_info_t* alloc = + (wasi_win_clock_info_t*)malloc(sizeof(wasi_win_clock_info_t)); + if (!alloc) { + wasm_rt_trap(WASM_RT_TRAP_WASI); + } + memcpy(alloc, &g_wasi_win_clock_info, sizeof(wasi_win_clock_info_t)); + *clock_data_pointer = alloc; +} + +void os_clock_cleanup(void** clock_data_pointer) { + if (*clock_data_pointer == 0) { + free(*clock_data_pointer); + *clock_data_pointer = 0; + } +} + +int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct) { + wasi_win_clock_info_t* alloc = (wasi_win_clock_info_t*)clock_data; + + LARGE_INTEGER count; + (void)clock_id; + + if (alloc->counts_per_sec.QuadPart <= 0 || + QueryPerformanceCounter(&count) == 0) { + return -1; + } + +#define BILLION 1000000000LL + out_struct->tv_sec = count.QuadPart / alloc->counts_per_sec.QuadPart; + out_struct->tv_nsec = + ((count.QuadPart % alloc->counts_per_sec.QuadPart) * BILLION) / + alloc->counts_per_sec.QuadPart; +#undef BILLION + + return 0; +} + +int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct) { + (void)clock_id; + out_struct->tv_sec = 0; + out_struct->tv_nsec = 1000; + return 0; +} + +void os_print_last_error(const char* msg) { + DWORD errorMessageID = GetLastError(); + if (errorMessageID != 0) { + LPSTR messageBuffer = 0; + // The api creates the buffer that holds the message + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + (void)size; + // Copy the error message into a std::string. + printf("%s. %s\n", msg, messageBuffer); + LocalFree(messageBuffer); + } else { + printf("%s. No error code.\n", msg); + } +} + +#undef VERBOSE_LOG +#undef DONT_USE_VIRTUAL_ALLOC2 + +#else +// https://stackoverflow.com/questions/26541150/warning-iso-c-forbids-an-empty-translation-unit +typedef int make_iso_compilers_happy; +#endif diff --git a/wasm2c/wasm-rt-os.h b/wasm2c/wasm-rt-os.h new file mode 100644 index 000000000..6f77b01aa --- /dev/null +++ b/wasm2c/wasm-rt-os.h @@ -0,0 +1,58 @@ +#ifndef WASM_RT_OS_H_ +#define WASM_RT_OS_H_ + +#include +#include +#include + +enum { + MMAP_PROT_NONE = 0, + MMAP_PROT_READ = 1, + MMAP_PROT_WRITE = 2, + MMAP_PROT_EXEC = 4 +}; + +/* Memory map flags */ +enum { + MMAP_MAP_NONE = 0, + /* Put the mapping into 0 to 2 G, supported only on x86_64 */ + MMAP_MAP_32BIT = 1, + /* Don't interpret addr as a hint: place the mapping at exactly + that address. */ + MMAP_MAP_FIXED = 2 +}; + +void os_init(); + +size_t os_getpagesize(); +// Try allocating Memory space. +// Returns pointer to allocated region on success, 0 on failure. +void* os_mmap(void* hint, size_t size, int prot, int flags); +void os_munmap(void* addr, size_t size); +// Set the permissions of the memory region. +// Returns 0 on success, non zero on failure. +int os_mprotect(void* addr, size_t size, int prot); +// Like mmap but returns an aligned region +void* os_mmap_aligned(void* addr, + size_t requested_length, + int prot, + int flags, + size_t alignment, + size_t alignment_offset); +// Commits and sets the permissions on an already allocated memory region +// Returns 0 on success, non zero on failure. +int os_mmap_commit(void* curr_heap_end_pointer, size_t expanded_size, int prot); + +void os_clock_init(void** clock_data_pointer); +void os_clock_cleanup(void** clock_data_pointer); +int os_clock_gettime(void* clock_data, + int clock_id, + struct timespec* out_struct); +int os_clock_getres(void* clock_data, + int clock_id, + struct timespec* out_struct); + +// print the error message +void os_print_last_error(const char* msg); + +#endif diff --git a/wasm2c/wasm-rt-runner.c b/wasm2c/wasm-rt-runner.c new file mode 100644 index 000000000..bc143e4e3 --- /dev/null +++ b/wasm2c/wasm-rt-runner.c @@ -0,0 +1,133 @@ +#if defined(_WIN32) +// Remove warnings for strcat, strcpy as they are safely used here +#define _CRT_SECURE_NO_WARNINGS +#endif +#include +#include +#include + +#include "wasm-rt.h" + +#if defined(_WIN32) +// Ensure the min/max macro in the header doesn't collide with functions in +// std:: +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#define LINETERM "\r\n" +#else +#include +#define LINETERM "\n" +#endif + +void* open_lib(char const* wasm2c_module_path) { +#if defined(_WIN32) + void* library = LoadLibraryA(wasm2c_module_path); +#else + void* library = dlopen(wasm2c_module_path, RTLD_LAZY); +#endif + + if (!library) { +#if defined(_WIN32) + DWORD errorMessageID = GetLastError(); + if (errorMessageID != 0) { + LPSTR messageBuffer = 0; + // The api creates the buffer that holds the message + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, NULL); + + const size_t str_buff_size = size + 1; + char* error_msg = malloc(str_buff_size); + memcpy(error_msg, messageBuffer, str_buff_size - 1); + error_msg[str_buff_size - 1] = '\0'; + printf("Could not load wasm2c dynamic library: %s" LINETERM, error_msg); + free(error_msg); + + LocalFree(messageBuffer); + } +#else + const char* error_msg = dlerror(); + printf("Could not load wasm2c dynamic library: %s" LINETERM, error_msg); +#endif + + exit(1); + } + + return library; +} + +void close_lib(void* library) { +#if defined(_WIN32) + FreeLibrary((HMODULE)library); +#else + dlclose(library); +#endif +} + +void* symbol_lookup(void* library, const char* name) { +#if defined(_WIN32) + void* ret = GetProcAddress((HMODULE)library, name); +#else + void* ret = dlsym(library, name); +#endif + if (!ret) { + printf("Could not find symbol %s in wasm2c shared library" LINETERM, name); + } + return ret; +} + +char* get_info_func_name(char const* wasm_module_name) { + const char* info_func_suffix = "get_wasm2c_sandbox_info"; + char* info_func_str = + malloc(strlen(wasm_module_name) + strlen(info_func_suffix) + 1); + + strcpy(info_func_str, wasm_module_name); + strcat(info_func_str, info_func_suffix); + return info_func_str; +} + +typedef wasm2c_sandbox_funcs_t (*get_info_func_t)(); +typedef void (*wasm2c_start_func_t)(void* sbx); + +int main(int argc, char const* argv[]) { + if (argc < 2) { + printf( + "Expected argument: %s " + "[optional_module_prefix]" LINETERM, + argv[0]); + exit(1); + } + + char const* wasm2c_module_path = argv[1]; + char const* wasm_module_name = ""; + + if (argc >= 3) { + wasm_module_name = argv[2]; + } + + void* library = open_lib(wasm2c_module_path); + char* info_func_name = get_info_func_name(wasm_module_name); + + get_info_func_t get_info_func = + (get_info_func_t)symbol_lookup(library, info_func_name); + wasm2c_sandbox_funcs_t sandbox_info = get_info_func(); + + const uint32_t dont_override_heap_size = 0; + void* sandbox = sandbox_info.create_wasm2c_sandbox(dont_override_heap_size); + if (!sandbox) { + printf("Error: Could not create sandbox" LINETERM); + exit(1); + } + + wasm2c_start_func_t start_func = + (wasm2c_start_func_t)symbol_lookup(library, "w2c__start"); + start_func(sandbox); + + free(info_func_name); + close_lib(library); + return 0; +} diff --git a/wasm2c/wasm-rt-shadow.cpp b/wasm2c/wasm-rt-shadow.cpp new file mode 100644 index 000000000..551c74be4 --- /dev/null +++ b/wasm2c/wasm-rt-shadow.cpp @@ -0,0 +1,361 @@ +// Assume that this file is called only when shadow memory is used +#ifndef WASM_CHECK_SHADOW_MEMORY +#define WASM_CHECK_SHADOW_MEMORY +#endif + +#include "wasm-rt.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +typedef enum class ALLOC_STATE : uint8_t { + UNINIT = 0, ALLOCED, INITIALIZED, +} ALLOC_STATE; + +static const char * alloc_state_strings[] = { + "UNINIT", "ALLOCED", "INITIALIZED", +}; + +typedef enum class USED_STATE : uint8_t { + UNUSED = 0, FREED +} USED_STATE; + +static const char * used_state_strings[] = { + "UNUSED", "FREED" +}; + +typedef enum class OWN_STATE : uint8_t { + UNKNOWN = 0, PROGRAM, MALLOC +} OWN_STATE; + +static const char * own_state_strings[] = { + "UNKNOWN", "PROGRAM", "MALLOC" +}; + +typedef struct { + ALLOC_STATE alloc_state; + USED_STATE used_state; + OWN_STATE own_state; +} cell_data_t; + +static wasm2c_shadow_memory_cell_t pack(cell_data_t data) { + uint8_t alloc_bits = ((uint8_t)data.alloc_state) & 0b11; + uint8_t used_bits = (((uint8_t)data.used_state) & 0b11) << 2; + uint8_t own_bits = (((uint8_t)data.own_state) & 0b11) << 4; + uint8_t ret = alloc_bits | used_bits | own_bits; + return ret; +} + +static cell_data_t unpack(wasm2c_shadow_memory_cell_t byte) { + uint8_t alloc_bits = byte & 0b11; + uint8_t used_bits = (byte & 0b1100) >> 2; + uint8_t own_bits = (byte & 0b110000) >> 4; + cell_data_t ret { (ALLOC_STATE) alloc_bits, (USED_STATE) used_bits, (OWN_STATE) own_bits}; + return ret; +} + +void wasm2c_shadow_memory_create(wasm_rt_memory_t* mem) { + uint64_t new_size = ((uint64_t) mem->size) * sizeof(wasm2c_shadow_memory_cell_t); + mem->shadow_memory.data = (wasm2c_shadow_memory_cell_t*) calloc(new_size, 1); + assert(mem->shadow_memory.data != 0); + mem->shadow_memory.data_size = new_size; + mem->shadow_memory.allocation_sizes_map = new std::map; + mem->shadow_memory.heap_base = 0; +} + +void wasm2c_shadow_memory_expand(wasm_rt_memory_t* mem) { + uint64_t new_size = ((uint64_t) mem->size) * sizeof(wasm2c_shadow_memory_cell_t); + uint8_t* new_data = (uint8_t*) realloc(mem->shadow_memory.data, new_size); + assert(mem->shadow_memory.data != 0); + uint64_t old_size = mem->shadow_memory.data_size; + memset(new_data + old_size, 0, new_size - old_size); + mem->shadow_memory.data = new_data; + mem->shadow_memory.data_size = new_size; +} + +#define allocation_sizes_map(mem) ((std::map*) mem->shadow_memory.allocation_sizes_map) + +void wasm2c_shadow_memory_destroy(wasm_rt_memory_t* mem) { + free(mem->shadow_memory.data); + mem->shadow_memory.data = 0; + mem->shadow_memory.data_size = 0; + delete allocation_sizes_map(mem); + mem->shadow_memory.allocation_sizes_map = 0; + mem->shadow_memory.heap_base = 0; +} + +typedef void (iterate_t)(uint32_t index, cell_data_t* data); + +static inline void memory_state_iterate(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size, std::function callback) { + uint64_t max = ((uint64_t) ptr) + ptr_size; + assert(max <= UINT32_MAX); + + assert(max <= mem->shadow_memory.data_size); + + for (uint32_t i = ptr; i < ptr + ptr_size; i++) { + wasm2c_shadow_memory_cell_t curr_state = mem->shadow_memory.data[i]; + cell_data_t unpacked = unpack(curr_state); + callback(i, &unpacked); + wasm2c_shadow_memory_cell_t new_state = pack(unpacked); + mem->shadow_memory.data[i] = new_state; + } +} + +void wasm2c_shadow_memory_reserve(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size) { + memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){ + if (data->alloc_state == ALLOC_STATE::UNINIT) { + data->alloc_state = ALLOC_STATE::ALLOCED; + } + }); +} + +static void report_error(wasm_rt_memory_t* mem, const char* func_name, const char* error_message, uint32_t index, cell_data_t* data) { + const char* alloc_state_string = "<>"; + const char* used_state_string = "<>"; + const char* own_state_string = "<>"; + + if (data != 0) { + alloc_state_string = alloc_state_strings[(int) data->alloc_state]; + used_state_string = used_state_strings[(int) data->used_state]; + own_state_string = own_state_strings[(int) data->own_state]; + } + const uint64_t used_mem = wasm2c_shadow_memory_print_total_allocations(mem); + printf("WASM Shadow memory ASAN failed! %s (Func: %s, Index: %" PRIu32 ") (Cell state: %s, %s, %s) (Allocated mem: %" PRIu64 ")!\n", + error_message, func_name, index, + alloc_state_string, used_state_string, own_state_string, + used_mem + ); + fflush(stdout); + #ifndef WASM_CHECK_SHADOW_MEMORY_NO_ABORT_ON_FAIL + wasm_rt_trap(WASM_RT_TRAP_SHADOW_MEM); + #endif +} + +void wasm2c_shadow_memory_dlmalloc(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size) { + if (ptr == 0) { + // malloc failed + return; + } + + const char* func_name = ""; + if (ptr < mem->shadow_memory.heap_base) { + report_error(mem, func_name, "malloc returning a pointer outside the heap", ptr, 0); + } + + memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){ + if (data->alloc_state != ALLOC_STATE::UNINIT) { + report_error(mem, func_name, "Malloc returned a pointer in already occupied memory!", index, data); + } else { + data->alloc_state = ALLOC_STATE::ALLOCED; + } + }); + + auto alloc_map = allocation_sizes_map(mem); + (*alloc_map)[ptr] = ptr_size; +} + +void wasm2c_shadow_memory_dlfree(wasm_rt_memory_t* mem, uint32_t ptr) { + if (ptr == 0) { + // free noop + return; + } + + const char* func_name = ""; + + auto alloc_map = allocation_sizes_map(mem); + auto it = alloc_map->find(ptr); + if (it == alloc_map->end()) { + report_error(mem, func_name, "Freeing a pointer that was not allocated!", ptr, 0); + } else { + uint32_t ptr_size = it->second; + alloc_map->erase(it); + + memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){ + if (data->alloc_state == ALLOC_STATE::UNINIT) { + report_error(mem, func_name, "Freeing uninitialized memory", index, data); + } else { + data->alloc_state = ALLOC_STATE::UNINIT; + } + + // This memory is now "previously used" memory + data->used_state = USED_STATE::FREED; + }); + } +} + +void wasm2c_shadow_memory_mark_globals_heap_boundary(wasm_rt_memory_t* mem, uint32_t ptr) { + mem->shadow_memory.heap_base = ptr; + wasm2c_shadow_memory_reserve(mem, 0, ptr); +} + +static void check_heap_base_straddle(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) { + uint32_t heap_base = mem->shadow_memory.heap_base; + uint64_t max = ((uint64_t) ptr) + ptr_size; + assert(max <= UINT32_MAX); + + if (ptr < heap_base) { + if (ptr + ptr_size > heap_base) { + report_error(mem, func_name, "Memory operation straddling heap base!", ptr, 0 /* cell_data */); + } + } +} + +static bool is_malloc_family_function(const char* func_name, bool include_calloc, bool include_realloc) { + if(strcmp(func_name, "w2c_dlmalloc") == 0 || strcmp(func_name, "w2c_dlfree") == 0 || strcmp(func_name, "w2c_sbrk") == 0) { + return true; + } + if (include_calloc) { + if(strcmp(func_name, "w2c_calloc") == 0) { + return true; + } + } + if (include_realloc) { + if(strcmp(func_name, "w2c_realloc") == 0) { + return true; + } + } + return false; +} + +// Functions that intentionally read beyond the end of an allocation for performance +// The limit is upto 7 bytes past the end of the allocation +static bool is_overread_function(const char* func_name) { + return strcmp(func_name, "w2c_strlen") == 0; +} + +WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_load(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) { + check_heap_base_straddle(mem, func_name, ptr, ptr_size); + + bool malloc_read_family = is_malloc_family_function(func_name, true, true); + bool malloc_core_family = is_malloc_family_function(func_name, false, false); + bool malloc_any_family = malloc_read_family; + bool overread_func_family = is_overread_function(func_name); + + memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){ + // Is this function exempt from checking + bool exempt = false; + + // Malloc family of functions store chunk related information in the heap + // These look like unitialized operations + if (index >= mem->shadow_memory.heap_base && malloc_read_family) { + exempt = true; + } + + bool is_first_iteration = index == ptr; + // Functions that intentionally read beyond the end of an allocation for performance + // The limit is upto 7 bytes past the end of the allocation + if (overread_func_family && !is_first_iteration) { + exempt = true; + } + + if (!exempt) { +#ifdef WASM_CHECK_SHADOW_MEMORY_UNINIT_READ + if (data->alloc_state != ALLOC_STATE::INITIALIZED) { + report_error(mem, func_name, "Reading uninitialized or unallocated memory", index, data); + } +#else + if (data->alloc_state == ALLOC_STATE::UNINIT) { + report_error(mem, func_name, "Reading uninitialized memory", index, data); + } +#endif + } + + // Accessing C globals --- infer if this is malloc or program data + if (index < mem->shadow_memory.heap_base) { + if (malloc_core_family) { + // Accessing C globals from core malloc functions + if(data->own_state == OWN_STATE::PROGRAM) { + report_error(mem, func_name, "Core malloc functions reading non malloc globals", index, data); + } + data->own_state = OWN_STATE::MALLOC; + } else if (malloc_any_family) { + // Accessing C globals from malloc wrapper functions + // hard to infer as these access both regular and chunk memory, so leave things unchanged + } else { + if(data->own_state == OWN_STATE::MALLOC) { + report_error(mem, func_name, "Program reading malloc globals", index, data); + } + data->own_state = OWN_STATE::PROGRAM; + } + } + }); +} + +WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_store(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) { + check_heap_base_straddle(mem, func_name, ptr, ptr_size); + + bool malloc_write_family = is_malloc_family_function(func_name, false /* calloc shouldn't modify metadata */, true); + bool malloc_core_family = is_malloc_family_function(func_name, false, false); + bool malloc_any_family = is_malloc_family_function(func_name, true, true); + + memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){ + // Is this function exempt from checking + bool exempt = false; + + // Malloc family of functions store chunk related information in the heap + // These look like unitialized operations + if (index >= mem->shadow_memory.heap_base && malloc_write_family) { + exempt = true; + } + + if (!exempt) { + if (data->alloc_state == ALLOC_STATE::UNINIT) { + report_error(mem, func_name, "Writing uninitialized memory", index, data); + } else if (data->alloc_state == ALLOC_STATE::ALLOCED) { + data->alloc_state = ALLOC_STATE::INITIALIZED; + } + } + + // Accessing C globals --- infer if this is malloc or program data + if (index < mem->shadow_memory.heap_base) { + if (malloc_core_family) { + // Accessing C globals from core malloc functions + if(data->own_state == OWN_STATE::PROGRAM) { + report_error(mem, func_name, "Core malloc functions writing non malloc globals", index, data); + } + data->own_state = OWN_STATE::MALLOC; + } else if (malloc_any_family) { + // Accessing C globals from malloc wrapper functions + // hard to infer as these access both regular and chunk memory, so leave things unchanged + } else { + if(data->own_state == OWN_STATE::MALLOC) { + report_error(mem, func_name, "Program writing malloc globals", index, data); + } + data->own_state = OWN_STATE::PROGRAM; + } + } + }); +} + +WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_print_allocations(wasm_rt_memory_t* mem) { + auto alloc_map = allocation_sizes_map(mem); + puts("{ "); + int counter = 0; + for (auto i = alloc_map->begin(); i != alloc_map->end(); ++i) + { + printf("%" PRIu32 ": %" PRIu32 ", ", i->first, i->second); + counter++; + if (counter >= 40) { + counter = 0; + puts("\n"); + } + } + puts(" }"); +} + +WASM2C_FUNC_EXPORT uint64_t wasm2c_shadow_memory_print_total_allocations(wasm_rt_memory_t* mem) { + auto alloc_map = allocation_sizes_map(mem); + uint64_t used_memory = mem->shadow_memory.heap_base; + for (auto i = alloc_map->begin(); i != alloc_map->end(); ++i) + { + used_memory += i->second; + } + return used_memory; +} diff --git a/wasm2c/wasm-rt-wasi.c b/wasm2c/wasm-rt-wasi.c new file mode 100644 index 000000000..b99ce2df3 --- /dev/null +++ b/wasm2c/wasm-rt-wasi.c @@ -0,0 +1,1070 @@ +// This file contains modified versions of the emscripten runtime (as well as +// the wasi support it includes ) adapted for +// - library sandboxing --- these apis have to support multiple wasm2c +// sandboxed components and cannot use global variables. +// Instead they operate on the sbx context specified +// as the first parameter to the API +// - security --- APIs like args_get, sys_open, fd_write, sys_read seemed to +// have security bugs upstream. +// Additionally, we don't want to allow any file system +// access. Restrict opens to the null file (/dev/null on unix, +// nul on windows) + +///////////////////////////////////////////////////////////// +////////// File: Missing stuff from emscripten +///////////////////////////////////////////////////////////// +#include +#include "wasm-rt-os.h" + +typedef uint8_t u8; +typedef int8_t s8; +typedef uint16_t u16; +typedef int16_t s16; +typedef uint32_t u32; +typedef int32_t s32; +typedef uint64_t u64; +typedef int64_t s64; +typedef float f32; +typedef double f64; + +#ifndef WASM_RT_ADD_PREFIX +#define WASM_RT_ADD_PREFIX +#endif + +///////////////////////////////////////////////////////////// +////////// File: Modified emscripten/tools/wasm2c/base.c +///////////////////////////////////////////////////////////// + +/* + * Base of all support for wasm2c code. + */ + +#include +#include +#include +#include +#include +#include +#include + +// ssize_t detection: usually stdint provides it, but not on windows apparently +#ifdef _WIN32 +#ifdef _MSC_VER +#include +typedef SSIZE_T ssize_t; +#endif // _MSC_VER +#endif // _WIN32 + +#include +#include +#include + +#ifndef _WIN32 +#include +#else +#include +#endif + +#include "wasm-rt.h" + +#if defined(__GNUC__) +#define UNLIKELY(x) __builtin_expect(!!(x), 0) +#define LIKELY(x) __builtin_expect(!!(x), 1) +#else +#define UNLIKELY(x) (x) +#define LIKELY(x) (x) +#endif + +#if defined(_WIN32) +#define POSIX_PREFIX(func) _##func +#else +#define POSIX_PREFIX(func) func +#endif + +#define TRAP(x) wasm_rt_trap(WASM_RT_TRAP_##x) + +#define MEMACCESS(mem, addr) ((void*)&(mem->data[addr])) + +#define UNCOND_MEMCHECK_SIZE(mem, a, sz) \ + if (UNLIKELY((a) + sz > mem->size)) \ + TRAP(OOB) + +#define UNCOND_MEMCHECK(mem, a, t) UNCOND_MEMCHECK_SIZE(mem, a, sizeof(t)) + +#define DEFINE_WASI_LOAD(name, t1, t2, t3) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \ + UNCOND_MEMCHECK(mem, addr, t1); \ + t1 result; \ + memcpy(&result, MEMACCESS(mem, addr), sizeof(t1)); \ + return (t3)(t2)result; \ + } + +#define DEFINE_WASI_STORE(name, t1, t2) \ + static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \ + UNCOND_MEMCHECK(mem, addr, t1); \ + t1 wrapped = (t1)value; \ + memcpy(MEMACCESS(mem, addr), &wrapped, sizeof(t1)); \ + } + +DEFINE_WASI_LOAD(wasm_i32_load, u32, u32, u32); +DEFINE_WASI_LOAD(wasm_i64_load, u64, u64, u64); +DEFINE_WASI_LOAD(wasm_f32_load, f32, f32, f32); +DEFINE_WASI_LOAD(wasm_f64_load, f64, f64, f64); +DEFINE_WASI_LOAD(wasm_i32_load8_s, s8, s32, u32); +DEFINE_WASI_LOAD(wasm_i64_load8_s, s8, s64, u64); +DEFINE_WASI_LOAD(wasm_i32_load8_u, u8, u32, u32); +DEFINE_WASI_LOAD(wasm_i64_load8_u, u8, u64, u64); +DEFINE_WASI_LOAD(wasm_i32_load16_s, s16, s32, u32); +DEFINE_WASI_LOAD(wasm_i64_load16_s, s16, s64, u64); +DEFINE_WASI_LOAD(wasm_i32_load16_u, u16, u32, u32); +DEFINE_WASI_LOAD(wasm_i64_load16_u, u16, u64, u64); +DEFINE_WASI_LOAD(wasm_i64_load32_s, s32, s64, u64); +DEFINE_WASI_LOAD(wasm_i64_load32_u, u32, u64, u64); +DEFINE_WASI_STORE(wasm_i32_store, u32, u32); +DEFINE_WASI_STORE(wasm_i64_store, u64, u64); +DEFINE_WASI_STORE(wasm_f32_store, f32, f32); +DEFINE_WASI_STORE(wasm_f64_store, f64, f64); +DEFINE_WASI_STORE(wasm_i32_store8, u8, u32); +DEFINE_WASI_STORE(wasm_i32_store16, u16, u32); +DEFINE_WASI_STORE(wasm_i64_store8, u8, u64); +DEFINE_WASI_STORE(wasm_i64_store16, u16, u64); +DEFINE_WASI_STORE(wasm_i64_store32, u32, u64); + +// Imports + +#ifdef VERBOSE_LOGGING +#define VERBOSE_LOG(...) \ + { printf(__VA_ARGS__); } +#else +#define VERBOSE_LOG(...) +#endif + +#define STUB_IMPORT_IMPL(ret, name, params, returncode) \ + ret name params { \ + VERBOSE_LOG("[stub import: " #name "]\n"); \ + return returncode; \ + } + +// Generic abort method for a runtime error in the runtime. + +static void abort_with_message(const char* message) { + fprintf(stderr, "%s\n", message); + TRAP(UNREACHABLE); +} + +/////////////////////////////////////////// Emscripten runtime +////////////////////////////////////////////////// + +// Setjmp/longjmp are not currently supported safely. So lonjmp with abort, +// setjmp can be a noop. +void Z_envZ_emscripten_longjmpZ_vii(wasm_sandbox_wasi_data* wasi_data, + u32 buf, + u32 value) { + abort_with_message("longjmp not supported"); +} + +STUB_IMPORT_IMPL(u32, + Z_envZ_emscripten_setjmpZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 buf), + 0); + +void Z_envZ_emscripten_notify_memory_growthZ_vi( + wasm_sandbox_wasi_data* wasi_data, + u32 size) {} + +u32 Z_envZ_getTempRet0Z_iv(wasm_sandbox_wasi_data* wasi_data) { + return wasi_data->tempRet0; +} + +void Z_envZ_setTempRet0Z_vi(wasm_sandbox_wasi_data* wasi_data, u32 x) { + wasi_data->tempRet0 = x; +} + +static const char* get_null_file_path() { +#ifdef _WIN32 + const char* null_file = "nul"; +#else + const char* null_file = "/dev/null"; +#endif + return null_file; +} + +static int get_null_file_mode() { +#ifdef _WIN32 + const int null_file_mode = _S_IREAD | _S_IWRITE; +#else + const int null_file_mode = S_IRUSR | S_IWUSR; +#endif + return null_file_mode; +} + +static int get_null_file_flags() { +#ifdef _WIN32 + const int null_file_flags = _O_CREAT; +#else + const int null_file_flags = O_CREAT; +#endif + return null_file_flags; +} + +static int is_null_file(wasm_sandbox_wasi_data* wasi_data, u32 path) { + const char* tainted_string = + (const char*)MEMACCESS(wasi_data->heap_memory, path); + // deliberately truncate length to u32, else UNCOND_MEMCHECK_SIZE may have + // overflows + u32 path_length = strlen(tainted_string) + 1; + UNCOND_MEMCHECK_SIZE(wasi_data->heap_memory, path, path_length); + + const char* null_file = get_null_file_path(); + size_t null_file_len = strlen(null_file) + 1; + if (null_file_len != path_length) { + return 0; + } + + if (memcmp(null_file, tainted_string, null_file_len) != 0) { + return 0; + } + + return 1; +} + +// Syscalls return a negative error code +#define EM_EACCES -2 + +static u32 do_stat(wasm_sandbox_wasi_data* wasi_data, int nfd, u32 buf) { + struct stat nbuf; + if (fstat(nfd, &nbuf)) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + VERBOSE_LOG(" success, size=%ld\n", nbuf.st_size); + wasm_i32_store(wasi_data->heap_memory, buf + 0, nbuf.st_dev); + wasm_i32_store(wasi_data->heap_memory, buf + 4, 0); + wasm_i32_store(wasi_data->heap_memory, buf + 8, nbuf.st_ino); + wasm_i32_store(wasi_data->heap_memory, buf + 12, nbuf.st_mode); + wasm_i32_store(wasi_data->heap_memory, buf + 16, nbuf.st_nlink); + wasm_i32_store(wasi_data->heap_memory, buf + 20, nbuf.st_uid); + wasm_i32_store(wasi_data->heap_memory, buf + 24, nbuf.st_gid); + wasm_i32_store(wasi_data->heap_memory, buf + 28, nbuf.st_rdev); + wasm_i32_store(wasi_data->heap_memory, buf + 32, 0); + wasm_i64_store(wasi_data->heap_memory, buf + 40, nbuf.st_size); +#ifdef _WIN32 + wasm_i32_store(wasi_data->heap_memory, buf + 48, + 512); // fixed blocksize on windows + wasm_i32_store(wasi_data->heap_memory, buf + 52, + 0); // but no reported blocks... +#else + wasm_i32_store(wasi_data->heap_memory, buf + 48, nbuf.st_blksize); + wasm_i32_store(wasi_data->heap_memory, buf + 52, nbuf.st_blocks); +#endif +#if defined(__APPLE__) || defined(__NetBSD__) + wasm_i32_store(wasi_data->heap_memory, buf + 56, nbuf.st_atimespec.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 60, nbuf.st_atimespec.tv_nsec); + wasm_i32_store(wasi_data->heap_memory, buf + 64, nbuf.st_mtimespec.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 68, nbuf.st_mtimespec.tv_nsec); + wasm_i32_store(wasi_data->heap_memory, buf + 72, nbuf.st_ctimespec.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 76, nbuf.st_ctimespec.tv_nsec); +#elif defined(_WIN32) + wasm_i32_store(wasi_data->heap_memory, buf + 56, + gmtime(&nbuf.st_atime)->tm_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 60, 0); + wasm_i32_store(wasi_data->heap_memory, buf + 64, + gmtime(&nbuf.st_mtime)->tm_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 68, 0); + wasm_i32_store(wasi_data->heap_memory, buf + 72, + gmtime(&nbuf.st_ctime)->tm_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 76, 0); +#else + wasm_i32_store(wasi_data->heap_memory, buf + 56, nbuf.st_atim.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 60, nbuf.st_atim.tv_nsec); + wasm_i32_store(wasi_data->heap_memory, buf + 64, nbuf.st_mtim.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 68, nbuf.st_mtim.tv_nsec); + wasm_i32_store(wasi_data->heap_memory, buf + 72, nbuf.st_ctim.tv_sec); + wasm_i32_store(wasi_data->heap_memory, buf + 76, nbuf.st_ctim.tv_nsec); +#endif + wasm_i64_store(wasi_data->heap_memory, buf + 80, nbuf.st_ino); + return 0; +} + +#define WASM_STDIN 0 +#define WASM_STDOUT 1 +#define WASM_STDERR 2 + +static void init_fds(wasm_sandbox_wasi_data* wasi_data) { +#ifndef _WIN32 + wasi_data->wasm_fd_to_native[WASM_STDIN] = STDIN_FILENO; + wasi_data->wasm_fd_to_native[WASM_STDOUT] = STDOUT_FILENO; + wasi_data->wasm_fd_to_native[WASM_STDERR] = STDERR_FILENO; +#else + wasi_data->wasm_fd_to_native[WASM_STDIN] = _fileno(stdin); + wasi_data->wasm_fd_to_native[WASM_STDOUT] = _fileno(stdout); + wasi_data->wasm_fd_to_native[WASM_STDERR] = _fileno(stderr); +#endif + wasi_data->next_wasm_fd = 3; +} + +static u32 get_or_allocate_wasm_fd(wasm_sandbox_wasi_data* wasi_data, int nfd) { + // If the native fd is already mapped, return the same wasm fd for it. + for (uint32_t i = 0; i < wasi_data->next_wasm_fd; i++) { + if (wasi_data->wasm_fd_to_native[i] == nfd) { + return i; + } + } + u32 fd = wasi_data->next_wasm_fd; + if (fd >= WASM2C_WASI_MAX_FDS) { + abort_with_message("ran out of fds"); + } + wasi_data->wasm_fd_to_native[fd] = nfd; + wasi_data->next_wasm_fd++; + return fd; +} + +static int get_native_fd(wasm_sandbox_wasi_data* wasi_data, u32 fd) { + if (fd >= WASM2C_WASI_MAX_FDS || fd >= wasi_data->next_wasm_fd) { + return -1; + } + return wasi_data->wasm_fd_to_native[fd]; +} + +u32 Z_envZ___sys_accessZ_iii(wasm_sandbox_wasi_data* wasi_data, + u32 pathname, + u32 mode) { + VERBOSE_LOG(" access: %s 0x%x\n", + MEMACCESS(wasi_data->heap_memory, pathname), mode); + + // only permit access checks on the the null file + if (!is_null_file(wasi_data, pathname)) { + return -1; + } + + const char* null_file = get_null_file_path(); + const int null_file_mode = get_null_file_mode(); + + int result = POSIX_PREFIX(access)(null_file, null_file_mode); + if (result < 0) { + VERBOSE_LOG(" access error: %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return 0; +} + +u32 Z_envZ___sys_openZ_iiii(wasm_sandbox_wasi_data* wasi_data, + u32 path, + u32 flags, + u32 varargs) { + VERBOSE_LOG(" open: %s %d\n", MEMACCESS(wasi_data->heap_memory, path), + flags); + + // only permit opening the null file + if (!is_null_file(wasi_data, path)) { + return -1; + } + + const char* null_file = get_null_file_path(); + const int null_file_flags = get_null_file_flags(); + const int null_file_mode = get_null_file_mode(); + + // Specify the mode manually to make sure the sandboxed code is not using + // unusual values + int nfd = POSIX_PREFIX(open)(null_file, null_file_flags, null_file_mode); + VERBOSE_LOG(" => native %d\n", nfd); + if (nfd >= 0) { + u32 fd = get_or_allocate_wasm_fd(wasi_data, nfd); + VERBOSE_LOG(" => wasm %d\n", fd); + return fd; + } + return -1; +} + +u32 Z_envZ___sys_fstat64Z_iii(wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u32 buf) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" fstat64 %d (=> %d) %d\n", fd, nfd, buf); + if (nfd < 0) { + return EM_EACCES; + } + return do_stat(wasi_data, nfd, buf); +} + +u32 Z_envZ___sys_stat64Z_iii(wasm_sandbox_wasi_data* wasi_data, + u32 path, + u32 buf) { + VERBOSE_LOG(" stat64: %s\n", MEMACCESS(wasi_data->heap_memory, path)); + int fd = Z_envZ___sys_openZ_iiii(wasi_data, path, 0 /* read_only */, 0); + int nfd = get_native_fd(wasi_data, fd); + if (nfd < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return do_stat(wasi_data, nfd, buf); +} + +u32 Z_envZ___sys_readZ_iiii(wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u32 buf, + u32 count) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" read %d (=> %d) %d %d\n", fd, nfd, buf, count); + if (nfd < 0) { + VERBOSE_LOG(" bad fd\n"); + return EM_EACCES; + } + + void* target_buf = MEMACCESS(wasi_data->heap_memory, buf); + UNCOND_MEMCHECK_SIZE(wasi_data->heap_memory, buf, count); + + ssize_t ret = POSIX_PREFIX(read)(nfd, target_buf, count); + VERBOSE_LOG(" native read: %ld\n", ret); + if (ret < 0) { + VERBOSE_LOG(" read error %d %s\n", errno, strerror(errno)); + return EM_EACCES; + } + return ret; +} + +STUB_IMPORT_IMPL(u32, + Z_envZ_dlopenZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + 1); +STUB_IMPORT_IMPL(u32, + Z_envZ_dlcloseZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 a), + 1); +STUB_IMPORT_IMPL(u32, + Z_envZ_dlsymZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + 0); +STUB_IMPORT_IMPL(u32, + Z_envZ_dlerrorZ_iv, + (wasm_sandbox_wasi_data * wasi_data), + 0); +STUB_IMPORT_IMPL(u32, + Z_envZ_signalZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + -1); +STUB_IMPORT_IMPL(u32, + Z_envZ_systemZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 a), + -1); +STUB_IMPORT_IMPL(u32, + Z_envZ_utimesZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + -1); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_rmdirZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 a), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_renameZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_lstat64Z_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_dup3Z_iiii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b, u32 c), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_dup2Z_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_getcwdZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + EM_EACCES); +STUB_IMPORT_IMPL( + u32, + Z_envZ___sys_ftruncate64Z_iiiii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b, u32 c, u32 d), + EM_EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ___sys_unlinkZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 path), + EACCES); +STUB_IMPORT_IMPL(u32, + Z_envZ_pthread_mutexattr_initZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 a), + 0); +STUB_IMPORT_IMPL(u32, + Z_envZ_pthread_mutexattr_settypeZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + 0); +STUB_IMPORT_IMPL(u32, + Z_envZ_pthread_mutexattr_destroyZ_ii, + (wasm_sandbox_wasi_data * wasi_data, u32 a), + 0); +STUB_IMPORT_IMPL( + u32, + Z_envZ_pthread_createZ_iiiii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b, u32 c, u32 d), + -1); +STUB_IMPORT_IMPL(u32, + Z_envZ_pthread_joinZ_iii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b), + -1); +STUB_IMPORT_IMPL(u32, + Z_envZ___cxa_thread_atexitZ_iiii, + (wasm_sandbox_wasi_data * wasi_data, u32 a, u32 b, u32 c), + -1); + +/////////////////////////////////////////// WASI runtime +////////////////////////////////////////////////// + +////////////// Supported WASI APIs +// errno_t clock_res_get(void* ctx, clockid_t clock_id, timestamp_t* +// resolution); errno_t clock_time_get(void* ctx, clockid_t clock_id, +// timestamp_t precision, timestamp_t* time); + +// errno_t fd_close(void* ctx, fd_t fd); +// errno_t fd_read(void* ctx, fd_t fd, const iovec_t* iovs, size_t iovs_len, +// size_t* nread); errno_t fd_seek(void* ctx, fd_t fd, filedelta_t offset, +// whence_t whence, filesize_t* newoffset); errno_t fd_write(void* ctx, fd_t fd, +// const ciovec_t* iovs, size_t iovs_len, size_t* nwritten); +// --- Currently only the default descriptors of STDIN, STDOUT, STDERR and +// allowed by the runtime. +// seek and close additionally throw an error when operating on STDIN, +// STDOUT, STDERR. However, these functions are general if other descriptors +// are allowed by the runtime in the future. + +////////////// Partially supported WASI APIs + +// errno_t args_get(void* ctx, char** argv, char* argv_buf); +// errno_t args_sizes_get(void* ctx, size_t* argc, size_t* argv_buf_size); +// --- Reads from wasi_data->main_argv, but the runtime does not provide a way +// for the host app to set wasi_data->main_argv right now + +// errno_t proc_exit(void* ctx, exitcode_t rval); +// --- this is a no-op here in this runtime as the focus is on library +// sandboxing + +// errno_t environ_get(void* ctx, char** environment, char* environ_buf); +// errno_t environ_sizes_get(void* ctx, size_t* environ_count, size_t* +// environ_buf_size); +// --- the wasm2c module evironment variable is just an empty environment + +// errno_t fd_prestat_get(void* ctx, fd_t fd, prestat_t* buf); + +////////////// Unsupported WASI APIs +// errno_t fd_advise(void* ctx, fd_t fd, filesize_t offset, filesize_t len, +// advice_t advice); errno_t fd_allocate(void* ctx, fd_t fd, filesize_t offset, +// filesize_t len); errno_t fd_datasync(void* ctx, fd_t fd); errno_t +// fd_fdstat_get(void* ctx, fd_t fd, fdstat_t* buf); errno_t +// fd_fdstat_set_flags(void* ctx, fd_t fd, fdflags_t flags); errno_t +// fd_fdstat_set_rights(void* ctx, fd_t fd, rights_t fs_rights_base, rights_t +// fs_rights_inheriting); errno_t fd_filestat_get(void* ctx, fd_t fd, +// filestat_t* buf); errno_t fd_filestat_set_size(void* ctx, fd_t fd, filesize_t +// st_size); errno_t fd_filestat_set_times(void* ctx, fd_t fd, timestamp_t +// st_atim, timestamp_t st_mtim, fstflags_t fst_flags); errno_t fd_pread(void* +// ctx, fd_t fd, const iovec_t* iovs, size_t iovs_len, filesize_t offset, +// size_t* nread); errno_t fd_prestat_dir_name(void* ctx, fd_t fd, char* path, +// size_t path_len); errno_t fd_pwrite(void* ctx, fd_t fd, const ciovec_t* iovs, +// size_t iovs_len, filesize_t offset, size_t* nwritten); errno_t +// fd_readdir(void* ctx, fd_t fd, void* buf, size_t buf_len, dircookie_t cookie, +// size_t* bufused); errno_t fd_renumber(void* ctx, fd_t from, fd_t to); errno_t +// fd_sync(void* ctx, fd_t fd); errno_t fd_tell(void* ctx, fd_t fd, filesize_t* +// offset); errno_t path_create_directory(void* ctx, fd_t fd, const char* path, +// size_t path_len); errno_t path_filestat_get(void* ctx, fd_t fd, lookupflags_t +// flags, const char* path, size_t path_len, filestat_t* buf); errno_t +// path_filestat_set_times(void* ctx, fd_t fd, lookupflags_t flags, const char* +// path, size_t path_len, timestamp_t st_atim, timestamp_t st_mtim, fstflags_t +// fst_flags); errno_t path_link(void* ctx, fd_t old_fd, lookupflags_t +// old_flags, const char* old_path, size_t old_path_len, fd_t new_fd, const +// char* new_path, size_t new_path_len); errno_t path_open(void* ctx, fd_t +// dirfd, lookupflags_t dirflags, const char* path, size_t path_len, oflags_t +// o_flags, rights_t fs_rights_base, rights_t fs_rights_inheriting, fdflags_t +// fs_flags, fd_t* fd); errno_t path_readlink(void* ctx, fd_t fd, const char* +// path, size_t path_len, char* buf, size_t buf_len, size_t* bufused); errno_t +// path_remove_directory(void* ctx, fd_t fd, const char* path, size_t path_len); +// errno_t path_rename(void* ctx, fd_t old_fd, const char* old_path, size_t +// old_path_len, fd_t new_fd, const char* new_path, size_t new_path_len); +// errno_t path_symlink(void* ctx, const char* old_path, size_t old_path_len, +// fd_t fd, const char* new_path, size_t new_path_len); errno_t +// path_unlink_file(void* ctx, fd_t fd, const char* path, size_t path_len); +// errno_t poll_oneoff(void* ctx, const subscription_t* in, event_t* out, size_t +// nsubscriptions, size_t* nevents); errno_t proc_raise(void* ctx, signal_t +// sig); errno_t random_get(void* ctx, void* buf, size_t buf_len); errno_t +// sched_yield(t* uvwasi); errno_t sock_recv(void* ctx, fd_t sock, const +// iovec_t* ri_data, size_t ri_data_len, riflags_t ri_flags, size_t* ro_datalen, +// roflags_t* ro_flags); errno_t sock_send(void* ctx, fd_t sock, const ciovec_t* +// si_data, size_t si_data_len, siflags_t si_flags, size_t* so_datalen); errno_t +// sock_shutdown(void* ctx, fd_t sock, sdflags_t how); + +// Bad file descriptor. +#define WASI_BADF_ERROR 8 +// Invalid argument +#define WASI_INVAL_ERROR 28 +// Operation not permitted. +#define WASI_PERM_ERROR 63 +#define WASI_DEFAULT_ERROR WASI_PERM_ERROR + +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_adviseZ_iijji, + (u32 a, u64 b, u64 c, u32 d), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_allocateZ_iijj, + (u32 a, u64 b, u64 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_datasyncZ_ii, + (u32 a), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_fdstat_getZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_fdstat_set_flagsZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_fdstat_set_rightsZ_iijj, + (u32 a, u64 b, u64 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_filestat_getZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_filestat_set_sizeZ_iij, + (u32 a, u64 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_filestat_set_timesZ_iijji, + (u32 a, u64 b, u64 c, u32 d), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_preadZ_iiiiji, + (u32 a, u32 b, u32 c, u64 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_prestat_dir_nameZ_iiii, + (u32 a, u32 b, u32 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_pwriteZ_iiiiji, + (u32 a, u32 b, u32 c, u64 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_readdirZ_iiiiji, + (u32 a, u32 b, u32 c, u64 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_renumberZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_syncZ_ii, + (u32 a), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_fd_tellZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_create_directoryZ_iiii, + (u32 a, u32 b, u32 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_filestat_getZ_iiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_filestat_set_timesZ_iiiiijji, + (u32 a, u32 b, u32 c, u32 d, u64 e, u64 f, u32 g), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_linkZ_iiiiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e, u32 f, u32 g), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL( + u32, + Z_wasi_snapshot_preview1Z_path_openZ_iiiiiijjii, + (u32 a, u32 b, u32 c, u32 d, u32 e, u64 f, u64 g, u32 h, u32 i), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_readlinkZ_iiiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e, u32 f), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_remove_directoryZ_iiii, + (u32 a, u32 b, u32 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_renameZ_iiiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e, u32 f), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_symlinkZ_iiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_path_unlink_fileZ_iiii, + (u32 a, u32 b, u32 c), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_poll_oneoffZ_iiiii, + (u32 a, u32 b, u32 c, u32 d), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_proc_raiseZ_ii, + (u32 a), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_random_getZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_sched_yieldZ_i, + (), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_sock_recvZ_iiiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e, u32 f), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_sock_sendZ_iiiiii, + (u32 a, u32 b, u32 c, u32 d, u32 e), + WASI_DEFAULT_ERROR); +STUB_IMPORT_IMPL(u32, + Z_wasi_snapshot_preview1Z_sock_shutdownZ_iii, + (u32 a, u32 b), + WASI_DEFAULT_ERROR); + +///////////////////////////////////////////////////////////// +////////// App environment operations +///////////////////////////////////////////////////////////// + +// Original file: Modified emscripten/tools/wasm2c/main.c + +u32 Z_wasi_snapshot_preview1Z_args_getZ_iii(wasm_sandbox_wasi_data* wasi_data, + u32 argv, + u32 argv_buf) { + u32 buf_size = 0; + for (u32 i = 0; i < wasi_data->main_argc; i++) { + u32 ptr = argv_buf + buf_size; + wasm_i32_store(wasi_data->heap_memory, argv + i * 4, ptr); + + char* arg = wasi_data->main_argv[i]; + // need to check length of argv strings before copying + // deliberately truncate length to u32, else UNCOND_MEMCHECK_SIZE may have + // overflows + u32 len = strlen(arg) + 1; + UNCOND_MEMCHECK_SIZE(wasi_data->heap_memory, ptr, len); + + memcpy(MEMACCESS(wasi_data->heap_memory, ptr), arg, len); + // make sure string is null terminated + wasm_i32_store8(wasi_data->heap_memory, ptr + (len - 1), 0); + buf_size += len; + } + return 0; +} + +u32 Z_wasi_snapshot_preview1Z_args_sizes_getZ_iii( + wasm_sandbox_wasi_data* wasi_data, + u32 pargc, + u32 pargv_buf_size) { + wasm_i32_store(wasi_data->heap_memory, pargc, wasi_data->main_argc); + u32 buf_size = 0; + for (u32 i = 0; i < wasi_data->main_argc; i++) { + buf_size += strlen(wasi_data->main_argv[i]) + 1; + } + wasm_i32_store(wasi_data->heap_memory, pargv_buf_size, buf_size); + return 0; +} + +// Original file: Modified emscripten/tools/wasm2c/os.c + +#ifdef WASM2C_WASI_EXIT_HOST_ON_MODULE_EXIT +void Z_wasi_snapshot_preview1Z_proc_exitZ_vi(wasm_sandbox_wasi_data* wasi_data, + u32 x) { + exit(1); +} +#else +void Z_wasi_snapshot_preview1Z_proc_exitZ_vi(wasm_sandbox_wasi_data* wasi_data, + u32 x) { + // upstream emscripten implements this as exit(x) + // This seems like a bad idea as a misbehaving sandbox will cause the app to + // exit Since this is a library sandboxing runtime, it's fine to do nothing + // here. Worst case the library continues execution and returns malformed + // data, which is already a possibility in any sandbox library + VERBOSE_LOG( + "wasm2c module called proc_exit: this is a noop in this runtime\n"); +} +#endif + +u32 Z_wasi_snapshot_preview1Z_environ_sizes_getZ_iii( + wasm_sandbox_wasi_data* wasi_data, + u32 pcount, + u32 pbuf_size) { + // TODO: Allow the sandbox to have its own env + wasm_i32_store(wasi_data->heap_memory, pcount, 0); + wasm_i32_store(wasi_data->heap_memory, pbuf_size, 0); + return 0; +} + +u32 Z_wasi_snapshot_preview1Z_environ_getZ_iii( + wasm_sandbox_wasi_data* wasi_data, + u32 __environ, + u32 environ_buf) { + // TODO: Allow the sandbox to have its own env + return 0; +} + +///////////////////////////////////////////////////////////// +////////// File operations +///////////////////////////////////////////////////////////// + +u32 Z_wasi_snapshot_preview1Z_fd_prestat_getZ_iii( + wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u32 prestat) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" fd_prestat_get wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_BADF_ERROR; + } + + return WASI_DEFAULT_ERROR; +} + +u32 Z_wasi_snapshot_preview1Z_fd_writeZ_iiiii(wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u32 iov, + u32 iovcnt, + u32 pnum) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" fd_write wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(wasi_data->heap_memory, iov + i * 8); + u32 len = wasm_i32_load(wasi_data->heap_memory, iov + i * 8 + 4); + VERBOSE_LOG(" chunk %d %d\n", ptr, len); + + UNCOND_MEMCHECK_SIZE(wasi_data->heap_memory, ptr, len); + + ssize_t result; + // Use stdio for stdout/stderr to avoid mixing a low-level write() with + // other logging code, which can change the order from the expected. + if (fd == WASM_STDOUT) { + result = fwrite(MEMACCESS(wasi_data->heap_memory, ptr), 1, len, stdout); + } else if (fd == WASM_STDERR) { + result = fwrite(MEMACCESS(wasi_data->heap_memory, ptr), 1, len, stderr); + } else { + result = + POSIX_PREFIX(write)(nfd, MEMACCESS(wasi_data->heap_memory, ptr), len); + } + if (result < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + if ((size_t)result != len) { + VERBOSE_LOG(" amount error, %ld %d\n", result, len); + return WASI_DEFAULT_ERROR; + } + num += len; + } + VERBOSE_LOG(" success: %d\n", num); + wasm_i32_store(wasi_data->heap_memory, pnum, num); + return 0; +} + +u32 Z_wasi_snapshot_preview1Z_fd_readZ_iiiii(wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u32 iov, + u32 iovcnt, + u32 pnum) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" fd_read wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + + u32 num = 0; + for (u32 i = 0; i < iovcnt; i++) { + u32 ptr = wasm_i32_load(wasi_data->heap_memory, iov + i * 8); + u32 len = wasm_i32_load(wasi_data->heap_memory, iov + i * 8 + 4); + VERBOSE_LOG(" chunk %d %d\n", ptr, len); + + UNCOND_MEMCHECK_SIZE(wasi_data->heap_memory, ptr, len); + + ssize_t result = + POSIX_PREFIX(read)(nfd, MEMACCESS(wasi_data->heap_memory, ptr), len); + if (result < 0) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + num += result; + if ((size_t)result != len) { + break; // nothing more to read + } + } + VERBOSE_LOG(" success: %d\n", num); + wasm_i32_store(wasi_data->heap_memory, pnum, num); + return 0; +} + +u32 Z_wasi_snapshot_preview1Z_fd_closeZ_ii(wasm_sandbox_wasi_data* wasi_data, + u32 fd) { + int nfd = get_native_fd(wasi_data, fd); + VERBOSE_LOG(" close wasm %d => native %d\n", fd, nfd); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + // For additional safety don't allow seeking on the input, output and error + // streams + if (nfd == WASM_STDOUT || nfd == WASM_STDERR || nfd == WASM_STDIN) { + return WASI_DEFAULT_ERROR; + } + POSIX_PREFIX(close)(nfd); + return 0; +} + +static int whence_to_native(u32 whence) { + if (whence == 0) + return SEEK_SET; + if (whence == 1) + return SEEK_CUR; + if (whence == 2) + return SEEK_END; + return -1; +} + +u32 Z_wasi_snapshot_preview1Z_fd_seekZ_iijii(wasm_sandbox_wasi_data* wasi_data, + u32 fd, + u64 offset, + u32 whence, + u32 new_offset) { + int nfd = get_native_fd(wasi_data, fd); + int nwhence = whence_to_native(whence); + VERBOSE_LOG(" seek %d (=> native %d) %ld %d (=> %d) %d\n", fd, nfd, offset, + whence, nwhence, new_offset); + if (nfd < 0) { + return WASI_DEFAULT_ERROR; + } + + // For additional safety don't allow seeking on the input, output and error + // streams + if (nfd == WASM_STDOUT || nfd == WASM_STDERR || nfd == WASM_STDIN) { + return WASI_DEFAULT_ERROR; + } + + off_t off = POSIX_PREFIX(lseek)(nfd, offset, nwhence); + VERBOSE_LOG(" off: %ld\n", off); + if (off == (off_t)-1) { + VERBOSE_LOG(" error, %d %s\n", errno, strerror(errno)); + return WASI_DEFAULT_ERROR; + } + wasm_i64_store(wasi_data->heap_memory, new_offset, off); + return 0; +} + +// wasm2c includes a version of seek, the u64 offset in two u32 parts. Unclear +// if this is needed, as WASI does not require this, but no harm in including +// it. +u32 Z_wasi_snapshot_preview1Z_fd_seekZ_iiiiii(wasm_sandbox_wasi_data* wasi_data, + u32 a, + u32 b, + u32 c, + u32 d, + u32 e) { + return Z_wasi_snapshot_preview1Z_fd_seekZ_iijii(wasi_data, a, + b + (((u64)c) << 32), d, e); +} + +///////////////////////////////////////////////////////////// +////////// Clock operations +///////////////////////////////////////////////////////////// + +#define WASM_CLOCK_REALTIME 0 +#define WASM_CLOCK_MONOTONIC 1 +#define WASM_CLOCK_PROCESS_CPUTIME 2 +#define WASM_CLOCK_THREAD_CPUTIME_ID 3 + +static int check_clock(u32 clock_id) { + return clock_id == WASM_CLOCK_REALTIME || clock_id == WASM_CLOCK_MONOTONIC || + clock_id == WASM_CLOCK_PROCESS_CPUTIME || + clock_id == WASM_CLOCK_THREAD_CPUTIME_ID; +} + +// out is a pointer to a u64 timestamp in nanoseconds +// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-timestamp-u64 +u32 Z_wasi_snapshot_preview1Z_clock_time_getZ_iiji( + wasm_sandbox_wasi_data* wasi_data, + u32 clock_id, + u64 precision, + u32 out) { + if (!check_clock(clock_id)) { + return WASI_INVAL_ERROR; + } + + struct timespec out_struct; + int ret = os_clock_gettime(wasi_data->clock_data, clock_id, &out_struct); + u64 result = + ((u64)out_struct.tv_sec) * 1000 * 1000 * 1000 + ((u64)out_struct.tv_nsec); + wasm_i64_store(wasi_data->heap_memory, out, result); + return ret; +} + +u32 Z_wasi_snapshot_preview1Z_clock_res_getZ_iii( + wasm_sandbox_wasi_data* wasi_data, + u32 clock_id, + u32 out) { + if (!check_clock(clock_id)) { + return WASI_INVAL_ERROR; + } + + struct timespec out_struct; + int ret = os_clock_getres(wasi_data->clock_data, clock_id, &out_struct); + u64 result = + ((u64)out_struct.tv_sec) * 1000 * 1000 * 1000 + ((u64)out_struct.tv_nsec); + wasm_i64_store(wasi_data->heap_memory, out, result); + return ret; +} + +///////////////////////////////////////////////////////////// +////////// Misc +///////////////////////////////////////////////////////////// +void wasm_rt_sys_init() { + os_init(); +} + +void wasm_rt_init_wasi(wasm_sandbox_wasi_data* wasi_data) { + init_fds(wasi_data); + os_clock_init(&(wasi_data->clock_data)); + // Remove unused function warnings + (void)wasm_i32_load; + (void)wasm_i64_load; + (void)wasm_f32_load; + (void)wasm_f64_load; + (void)wasm_i32_load8_s; + (void)wasm_i64_load8_s; + (void)wasm_i32_load8_u; + (void)wasm_i64_load8_u; + (void)wasm_i32_load16_s; + (void)wasm_i64_load16_s; + (void)wasm_i32_load16_u; + (void)wasm_i64_load16_u; + (void)wasm_i64_load32_s; + (void)wasm_i64_load32_u; + (void)wasm_i32_store; + (void)wasm_i64_store; + (void)wasm_f32_store; + (void)wasm_f64_store; + (void)wasm_i32_store8; + (void)wasm_i32_store16; + (void)wasm_i64_store8; + (void)wasm_i64_store16; + (void)wasm_i64_store32; +} + +void wasm_rt_cleanup_wasi(wasm_sandbox_wasi_data* wasi_data) { + os_clock_cleanup(&(wasi_data->clock_data)); +} diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index b0577b363..d3ea1c020 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -17,58 +17,46 @@ #ifndef WASM_RT_H_ #define WASM_RT_H_ +#include +#include #include -#ifdef __cplusplus -extern "C" { +#if defined(_WIN32) +#define WASM2C_FUNC_EXPORT __declspec(dllexport) +#else +#define WASM2C_FUNC_EXPORT #endif -/** Maximum stack depth before trapping. This can be configured by defining - * this symbol before including wasm-rt when building the generated c files, - * for example: - * - * ``` - * cc -c -DWASM_RT_MAX_CALL_STACK_DEPTH=100 my_module.c -o my_module.o - * ``` - * */ -#ifndef WASM_RT_MAX_CALL_STACK_DEPTH -#define WASM_RT_MAX_CALL_STACK_DEPTH 500 +#ifdef __cplusplus +extern "C" { #endif -/** Enable memory checking via a signal handler via the following definition: - * - * #define WASM_RT_MEMCHECK_SIGNAL_HANDLER 1 - * - * This is usually 10%-25% faster, but requires OS-specific support. - * */ - -/** Check whether the signal handler is supported at all. */ -#if (defined(__linux__) || defined(__unix__) || defined(__APPLE__)) && \ - defined(__WORDSIZE) && __WORDSIZE == 64 - -/* If the signal handler is supported, then use it by default. */ -#ifndef WASM_RT_MEMCHECK_SIGNAL_HANDLER -#define WASM_RT_MEMCHECK_SIGNAL_HANDLER 1 +/** Check if we should use guard page model. + * This is enabled by default unless WASM_USE_EXPLICIT_BOUNDS_CHECKS is defined. + */ +#if defined(WASM_USE_GUARD_PAGES) && defined(WASM_USE_EXPLICIT_BOUNDS_CHECKS) +#error \ + "Cannot define both WASM_USE_GUARD_PAGES and WASM_USE_EXPLICIT_BOUNDS_CHECKS" +#elif !defined(WASM_USE_GUARD_PAGES) && \ + !defined(WASM_USE_EXPLICIT_BOUNDS_CHECKS) +// default to guard pages +#define WASM_USE_GUARD_PAGES #endif -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER -#define WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX 1 -#endif +/** Define WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC if you want the runtime to + * incrementally allocate heap/linear memory Note that this memory may be moved + * when it needs to expand + */ +#if defined(_MSC_VER) +#define WASM_RT_NO_RETURN __declspec(noreturn) #else - -/* The signal handler is not supported, error out if the user was trying to - * enable it. */ -#if WASM_RT_MEMCHECK_SIGNAL_HANDLER -#error "Signal handler is not supported for this OS/Architecture!" -#endif - -#define WASM_RT_MEMCHECK_SIGNAL_HANDLER 0 -#define WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX 0 - +#define WASM_RT_NO_RETURN __attribute__((noreturn)) #endif -/** Reason a trap occurred. Provide this to `wasm_rt_trap`. */ +/** Reason a trap occurred. Provide this to `wasm_rt_trap`. + * If you update this enum also update the error message in wasm_rt_trap. + */ typedef enum { WASM_RT_TRAP_NONE, /** No error. */ WASM_RT_TRAP_OOB, /** Out-of-bounds access in linear memory. */ @@ -76,8 +64,21 @@ typedef enum { WASM_RT_TRAP_DIV_BY_ZERO, /** Integer divide by zero. */ 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_EXHAUSTION, /** Call stack exhausted. */ + WASM_RT_TRAP_CALL_INDIRECT_TABLE_EXPANSION, /** Invalid call_indirect, as func + table cannot grow/grow further. + */ + WASM_RT_TRAP_CALL_INDIRECT_OOB_INDEX, /** Invalid call_indirect, due to index + larger than func table. */ + WASM_RT_TRAP_CALL_INDIRECT_NULL_PTR, /** Invalid call_indirect, as function + being invoked is null. */ + WASM_RT_TRAP_CALL_INDIRECT_TYPE_MISMATCH, /** Invalid call_indirect, as + function being invoked has an + unexpected type. */ + WASM_RT_TRAP_CALL_INDIRECT_UNKNOWN_ERR, /** Invalid call_indirect, for other + reason. */ + WASM_RT_TRAP_EXHAUSTION, /** Call stack exhausted. */ + WASM_RT_TRAP_SHADOW_MEM, /** Trap due to shadow memory mismatch */ + WASM_RT_TRAP_WASI, /** Trap due to WASI error */ } wasm_rt_trap_t; /** Value types. Used to define function signatures. */ @@ -93,8 +94,17 @@ typedef enum { * call. */ typedef void (*wasm_rt_funcref_t)(void); +/** + * The class of the indirect function being invoked + */ +typedef enum { + WASM_RT_INTERNAL_FUNCTION, + WASM_RT_EXTERNAL_FUNCTION +} wasm_rt_elem_target_class_t; + /** A single element of a Table. */ typedef struct { + wasm_rt_elem_target_class_t func_class; /** The index as returned from `wasm_rt_register_func_type`. */ uint32_t func_type; /** The function. The embedder must know the actual C signature of the @@ -102,15 +112,39 @@ typedef struct { wasm_rt_funcref_t func; } wasm_rt_elem_t; +typedef uint8_t wasm2c_shadow_memory_cell_t; + +typedef struct { + wasm2c_shadow_memory_cell_t* data; + size_t data_size; + void* allocation_sizes_map; + uint32_t heap_base; +} wasm2c_shadow_memory_t; + /** A Memory object. */ typedef struct { /** The linear memory data, with a byte length of `size`. */ +#if defined(WASM_USE_GUARD_PAGES) || \ + !defined(WASM_USE_INCREMENTAL_MOVEABLE_MEMORY_ALLOC) + uint8_t* const data; +#else uint8_t* data; +#endif /** The current and maximum page count for this Memory object. If there is no * maximum, `max_pages` is 0xffffffffu (i.e. UINT32_MAX). */ uint32_t pages, max_pages; /** The current size of the linear memory, in bytes. */ uint32_t size; + + /** 32-bit platforms use masking for sandboxing. This sets the mask, which is + * computed based on the heap size */ +#if UINTPTR_MAX == 0xffffffff + const uint32_t mem_mask; +#endif + +#if defined(WASM_CHECK_SHADOW_MEMORY) + wasm2c_shadow_memory_t shadow_memory; +#endif } wasm_rt_memory_t; /** A Table object. */ @@ -124,11 +158,69 @@ typedef struct { uint32_t size; } wasm_rt_table_t; +typedef struct wasm_func_type_t { + wasm_rt_type_t* params; + wasm_rt_type_t* results; + uint32_t param_count; + uint32_t result_count; +} wasm_func_type_t; + +#define WASM2C_WASI_MAX_FDS 32 +typedef struct wasm_sandbox_wasi_data { + wasm_rt_memory_t* heap_memory; + + uint32_t tempRet0; + + uint32_t main_argc; + char** main_argv; + + int wasm_fd_to_native[WASM2C_WASI_MAX_FDS]; + uint32_t next_wasm_fd; + + void* clock_data; + +} wasm_sandbox_wasi_data; + +typedef void (*wasm_rt_sys_init_t)(void); +typedef void* (*create_wasm2c_sandbox_t)(uint32_t max_wasm_pages); +typedef void (*destroy_wasm2c_sandbox_t)(void* sbx_ptr); +typedef void* (*lookup_wasm2c_nonfunc_export_t)(void* sbx_ptr, + const char* name); +typedef uint32_t (*lookup_wasm2c_func_index_t)(void* sbx_ptr, + uint32_t param_count, + uint32_t result_count, + wasm_rt_type_t* types); +typedef uint32_t (*add_wasm2c_callback_t)( + void* sbx_ptr, + uint32_t func_type_idx, + void* func_ptr, + wasm_rt_elem_target_class_t func_class); +typedef void (*remove_wasm2c_callback_t)(void* sbx_ptr, uint32_t callback_idx); + +typedef struct wasm2c_sandbox_funcs_t { + wasm_rt_sys_init_t wasm_rt_sys_init; + create_wasm2c_sandbox_t create_wasm2c_sandbox; + destroy_wasm2c_sandbox_t destroy_wasm2c_sandbox; + lookup_wasm2c_nonfunc_export_t lookup_wasm2c_nonfunc_export; + lookup_wasm2c_func_index_t lookup_wasm2c_func_index; + add_wasm2c_callback_t add_wasm2c_callback; + remove_wasm2c_callback_t remove_wasm2c_callback; +} wasm2c_sandbox_funcs_t; + /** 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. * * This is typically called by the generated code, and not the embedder. */ -extern void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); +WASM_RT_NO_RETURN extern void wasm_rt_trap(wasm_rt_trap_t); + +/** An indirect callback function failed. + * Deduce the reason for the failure and then call trap. + * + * This is typically called by the generated code, and not the embedder. */ +WASM_RT_NO_RETURN extern void wasm_rt_callback_error_trap( + wasm_rt_table_t* table, + uint32_t func_index, + uint32_t expected_func_type); /** Register a function type with the given signature. The returned function * index is guaranteed to be the same for all calls with the same signature. @@ -148,9 +240,20 @@ extern void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); * wasm_rt_register_func_type(2, 1, WASM_RT_I32, WASM_RT_F32, WASM_RT_I64); * => returns 1 * ``` */ -extern uint32_t wasm_rt_register_func_type(uint32_t params, - uint32_t results, - ...); +extern uint32_t wasm_rt_register_func_type( + wasm_func_type_t** p_func_type_structs, + uint32_t* p_func_type_count, + uint32_t params, + uint32_t results, + wasm_rt_type_t* types); + +extern void wasm_rt_cleanup_func_types(wasm_func_type_t** p_func_type_structs, + uint32_t* p_func_type_count); + +/** + * Return the default value of the maximum size allowed for wasm memory. + */ +extern uint64_t wasm_rt_get_default_max_linear_memory_size(); /** Initialize a Memory object with an initial page size of `initial_pages` and * a maximum page size of `max_pages`. @@ -160,10 +263,12 @@ extern uint32_t wasm_rt_register_func_type(uint32_t params, * // 1 initial page (65536 bytes), and a maximum of 2 pages. * wasm_rt_allocate_memory(&my_memory, 1, 2); * ``` */ -extern void wasm_rt_allocate_memory(wasm_rt_memory_t*, +extern bool wasm_rt_allocate_memory(wasm_rt_memory_t*, uint32_t initial_pages, uint32_t max_pages); +extern void wasm_rt_deallocate_memory(wasm_rt_memory_t*); + /** Grow a Memory object by `pages`, and return the previous page count. If * this new page count is greater than the maximum page count, the grow fails * and 0xffffffffu (UINT32_MAX) is returned instead. @@ -191,8 +296,60 @@ extern void wasm_rt_allocate_table(wasm_rt_table_t*, uint32_t elements, uint32_t max_elements); -/** Current call stack depth. */ -extern uint32_t wasm_rt_call_stack_depth; +extern void wasm_rt_deallocate_table(wasm_rt_table_t*); + +extern void wasm_rt_expand_table(wasm_rt_table_t*); + +// One time init function for wasm runtime. Should be called once for the +// current process +extern void wasm_rt_sys_init(); + +// Initialize wasi for the given sandbox. Called prior to sandbox execution. +extern void wasm_rt_init_wasi(wasm_sandbox_wasi_data*); + +extern void wasm_rt_cleanup_wasi(wasm_sandbox_wasi_data*); + +// Runtime functions for shadow memory + +// Create the shadow memory +extern void wasm2c_shadow_memory_create(wasm_rt_memory_t* mem); +// Expand the shadow memory to match wasm memory +extern void wasm2c_shadow_memory_expand(wasm_rt_memory_t* mem); +// Cleanup +extern void wasm2c_shadow_memory_destroy(wasm_rt_memory_t* mem); +// Perform checks for the load operation that completed +WASM2C_FUNC_EXPORT extern void wasm2c_shadow_memory_load(wasm_rt_memory_t* mem, + const char* func_name, + uint32_t ptr, + uint32_t ptr_size); +// Perform checks for the store operation that completed +WASM2C_FUNC_EXPORT extern void wasm2c_shadow_memory_store(wasm_rt_memory_t* mem, + const char* func_name, + uint32_t ptr, + uint32_t ptr_size); +// Mark an area as allocated, if it is currently unused. If already used, this +// is a noop. +extern void wasm2c_shadow_memory_reserve(wasm_rt_memory_t* mem, + uint32_t ptr, + uint32_t ptr_size); +// Perform checks for the malloc operation that completed +extern void wasm2c_shadow_memory_dlmalloc(wasm_rt_memory_t* mem, + uint32_t ptr, + uint32_t ptr_size); +// Perform checks for the free operation that will be run +extern void wasm2c_shadow_memory_dlfree(wasm_rt_memory_t* mem, uint32_t ptr); +// Pass on information about the boundary between wasm globals and heap +// Shadow asan will check that all malloc metadata structures below this +// boundary are only accessed by malloc related functions +extern void wasm2c_shadow_memory_mark_globals_heap_boundary( + wasm_rt_memory_t* mem, + uint32_t ptr); +// Print a list of all allocations currently active +WASM2C_FUNC_EXPORT extern void wasm2c_shadow_memory_print_allocations( + wasm_rt_memory_t* mem); +// Print the size of allocations currently active +WASM2C_FUNC_EXPORT uint64_t +wasm2c_shadow_memory_print_total_allocations(wasm_rt_memory_t* mem); #ifdef __cplusplus } From 4e151c11f61fac21db9671f38ae614d6eed9c7d2 Mon Sep 17 00:00:00 2001 From: "shravanrn@gmail.com" Date: Fri, 4 Mar 2022 22:15:54 -0800 Subject: [PATCH 2/2] Part 1 : FIx test suite --- src/c-writer.cc | 23 ++++++++------- src/prebuilt/wasm2c.include.c | 8 +++++- src/prebuilt/wasm2c.include.h | 4 +-- src/wasm2c.c.tmpl | 8 +++++- src/wasm2c.h.tmpl | 4 +-- test/run-spec-wasm2c.py | 53 ++++++++++++++++++++++------------- test/spec-wasm2c-prefix.c | 52 ++++++++++++++++++++++++++++++---- wasm2c/README.md | 5 +--- wasm2c/examples/fac/fac.c | 2 +- wasm2c/examples/fac/fac.h | 5 +--- wasm2c/wasm-rt-impl.c | 4 +-- wasm2c/wasm-rt-unified.c | 4 +++ wasm2c/wasm-rt.h | 3 ++ 13 files changed, 120 insertions(+), 55 deletions(-) create mode 100644 wasm2c/wasm-rt-unified.c diff --git a/src/c-writer.cc b/src/c-writer.cc index 4c604d1aa..d2b6f9024 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -172,7 +172,7 @@ class CWriter { std::string DefineImportName(const std::string& name, std::string_view module_name, std::string_view mangled_field_name); - std::string DefineGlobalScopeName(const std::string&); + std::string DefineGlobalScopeName(const std::string&, bool prefix = false); std::string DefineLocalScopeName(const std::string&); std::string DefineStackVarName(Index, Type, std::string_view); @@ -449,20 +449,21 @@ std::string CWriter::MangleGlobalName(std::string_view name, Type type) { } // static -std::string CWriter::LegalizeName(std::string_view name) { - if (name.empty()) +std::string CWriter::LegalizeName(std::string_view original) { + if (original.empty()) return "_"; std::string result; - result = isalpha(name[0]) ? name[0] : '_'; - for (size_t i = 1; i < name.size(); ++i) - result += isalnum(name[i]) ? name[i] : '_'; // In addition to containing valid characters for C, we must also avoid // colliding with things C cares about, such as reserved words (e.g. "void") // or a function name like main() (which a compiler will complain about if we // define it with another type). To avoid such problems, prefix. - result = "w2c_" + result; + std::string name = "w2c_" + original; + + result = isalpha(name[0]) ? name[0] : '_'; + for (size_t i = 1; i < name.size(); ++i) + result += isalnum(name[i]) ? name[i] : '_'; return result; } @@ -497,8 +498,11 @@ std::string CWriter::DefineImportName(const std::string& name, return mangled; } -std::string CWriter::DefineGlobalScopeName(const std::string& name) { +std::string CWriter::DefineGlobalScopeName(const std::string& name, bool prefix) { std::string unique = DefineName(&global_syms_, StripLeadingDollar(name)); + if(prefix) { + unique = "WASM_RT_ADD_PREFIX(" + unique + ")"; + } global_sym_map_.insert(SymbolMap::value_type(name, unique)); return unique; } @@ -969,7 +973,7 @@ void CWriter::WriteFuncDeclarations(bool for_header) { for (const Func* func : module_->funcs) { std::string global_func_name; if (for_header) { - global_func_name = DefineGlobalScopeName(func->name); + global_func_name = DefineGlobalScopeName(func->name, true /* prefix */); } else { global_func_name = GetGlobalName(func->name); } @@ -2553,7 +2557,6 @@ void CWriter::WriteCHeader() { std::string guard = GenerateHeaderGuard(); Write("#ifndef ", guard, Newline()); Write("#define ", guard, Newline(), Newline()); - Write("#define WASM_CURR_MODULE_PREFIX ", options_.mod_name, Newline()); Write(s_header_top); WriteMultivalueTypes(); WriteImports(); diff --git a/src/prebuilt/wasm2c.include.c b/src/prebuilt/wasm2c.include.c index eec5a4dd7..fdc5279f4 100644 --- a/src/prebuilt/wasm2c.include.c +++ b/src/prebuilt/wasm2c.include.c @@ -446,7 +446,12 @@ const char SECTION_NAME(sandboxapis)[] = " free(sbx);\n" "}\n" "\n" -"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() {\n" +"static void reset_wasm2c_stack_depth(void* aSbx) {\n" +" wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) aSbx;\n" +" sbx->wasm_rt_call_stack_depth = 0;\n" +"}\n" +"\n" +"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)() {\n" " wasm2c_sandbox_funcs_t ret;\n" " ret.wasm_rt_sys_init = &wasm_rt_sys_init;\n" " ret.create_wasm2c_sandbox = &create_wasm2c_sandbox;\n" @@ -455,6 +460,7 @@ const char SECTION_NAME(sandboxapis)[] = " ret.lookup_wasm2c_func_index = &lookup_wasm2c_func_index;\n" " ret.add_wasm2c_callback = &add_wasm2c_callback;\n" " ret.remove_wasm2c_callback = &remove_wasm2c_callback;\n" +" ret.reset_wasm2c_stack_depth = &reset_wasm2c_stack_depth;\n" " return ret;\n" "}\n" ; diff --git a/src/prebuilt/wasm2c.include.h b/src/prebuilt/wasm2c.include.h index fcd2ff4b5..2cb2dfb0b 100644 --- a/src/prebuilt/wasm2c.include.h +++ b/src/prebuilt/wasm2c.include.h @@ -17,8 +17,6 @@ const char SECTION_NAME(top)[] = "#define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y)\n" "#define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x)\n" "\n" -"#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x)\n" -"\n" "/* TODO(binji): only use stdint.h types in header */\n" "typedef uint8_t u8;\n" "typedef int8_t s8;\n" @@ -41,7 +39,7 @@ const char SECTION_NAME(top)[] = "# define FUNC_EXPORT\n" "#endif\n" "\n" -"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)();\n" +"FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)();\n" "\n" "struct wasm2c_sandbox_t;\n" "typedef struct wasm2c_sandbox_t wasm2c_sandbox_t;\n" diff --git a/src/wasm2c.c.tmpl b/src/wasm2c.c.tmpl index abe449a9d..3e6a3657a 100644 --- a/src/wasm2c.c.tmpl +++ b/src/wasm2c.c.tmpl @@ -441,7 +441,12 @@ static void destroy_wasm2c_sandbox(void* aSbx) { free(sbx); } -FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() { +static void reset_wasm2c_stack_depth(void* aSbx) { + wasm2c_sandbox_t* const sbx = (wasm2c_sandbox_t* const) aSbx; + sbx->wasm_rt_call_stack_depth = 0; +} + +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)() { wasm2c_sandbox_funcs_t ret; ret.wasm_rt_sys_init = &wasm_rt_sys_init; ret.create_wasm2c_sandbox = &create_wasm2c_sandbox; @@ -450,5 +455,6 @@ FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info) ret.lookup_wasm2c_func_index = &lookup_wasm2c_func_index; ret.add_wasm2c_callback = &add_wasm2c_callback; ret.remove_wasm2c_callback = &remove_wasm2c_callback; + ret.reset_wasm2c_stack_depth = &reset_wasm2c_stack_depth; return ret; } diff --git a/src/wasm2c.h.tmpl b/src/wasm2c.h.tmpl index a79f924e0..623fe0187 100644 --- a/src/wasm2c.h.tmpl +++ b/src/wasm2c.h.tmpl @@ -16,8 +16,6 @@ extern "C" { #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) -#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) - /* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; @@ -40,7 +38,7 @@ typedef double f64; # define FUNC_EXPORT #endif -FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)(); struct wasm2c_sandbox_t; typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; diff --git a/test/run-spec-wasm2c.py b/test/run-spec-wasm2c.py index c918ef3ad..6d0cf47a2 100755 --- a/test/run-spec-wasm2c.py +++ b/test/run-spec-wasm2c.py @@ -92,14 +92,18 @@ def MangleTypes(types): def MangleName(s): - def Mangle(match): - s = match.group(0) - return b'Z%02X' % s[0] + s = 'w2c_' + s + s = re.sub('[^a-zA-Z0-9]+', '_', s) + return s - # NOTE(binji): Z is not allowed. - pattern = b'([^_a-zA-Y0-9])' - result = 'Z_' + re.sub(pattern, Mangle, s.encode('utf-8')).decode('utf-8') - return result + # def Mangle(match): + # s = match.group(0) + # return b'Z%02X' % s[0] + + # # NOTE(binji): Z is not allowed. + # pattern = b'([^_a-zA-Y0-9])' + # result = 'Z_' + re.sub(pattern, Mangle, s.encode('utf-8')).decode('utf-8') + # return result def IsModuleCommand(command): @@ -144,7 +148,6 @@ def _CacheModulePrefixes(self): name = os.path.basename(command['filename']) name = os.path.splitext(name)[0] name = re.sub(r'[^a-zA-Z0-9_]', '_', name) - name = MangleName(name) self.module_prefix_map[idx] = name @@ -178,6 +181,7 @@ def _WriteFileAndLine(self, command): def _WriteIncludes(self): idx = 0 + self.out_file.write('#undef WASM_RT_MODULE_PREFIX\n\n') for filename in self.GetModuleFilenames(): header = os.path.splitext(filename)[0] + '.h' self.out_file.write( @@ -204,7 +208,10 @@ def _WriteCommand(self, command): def _WriteModuleCommand(self, command): self.module_idx += 1 - self.out_file.write('%sinit();\n' % self.GetModulePrefix()) + prefix = self.GetModulePrefix() + self.out_file.write('g_curr_sbx_details = %sget_wasm2c_sandbox_info();\n' % prefix) + self.out_file.write('g_curr_sbx_details.wasm_rt_sys_init();\n') + self.out_file.write('g_curr_sbx = (wasm2c_sandbox_t*) g_curr_sbx_details.create_wasm2c_sandbox(0 /* no max page limit */);\n') def _WriteAssertUninstantiableCommand(self, command): self.module_idx += 1 @@ -293,6 +300,9 @@ def _Constant(self, const): def _ConstantList(self, consts): return ', '.join(self._Constant(const) for const in consts) + def _ConstantPrefixList(self, prefix, consts): + return ', '.join(prefix + [ self._Constant(const) for const in consts ]) + def _Found(self, num, type_): return "actual.%s%s" % (MangleType(type_), num) @@ -323,10 +333,12 @@ def _Action(self, command): expected = command['expected'] type_ = action['type'] mangled_module_name = self.GetModulePrefix(action.get('module')) - field = (mangled_module_name + MangleName(action['field']) + - MangleName(self._ActionSig(action, expected))) + field = (mangled_module_name + MangleName(action['field']) + # + MangleName(self._ActionSig(action, expected)) + ) if type_ == 'invoke': - return '%s(%s)' % (field, self._ConstantList(action.get('args', []))) + inst_arg = ["g_curr_sbx"] + return '%s(%s)' % (field, self._ConstantPrefixList(inst_arg, action.get('args', []))) elif type_ == 'get': return '*%s' % field else: @@ -335,12 +347,12 @@ def _Action(self, command): def Compile(cc, c_filename, out_dir, *args): o_filename = utils.ChangeDir(utils.ChangeExt(c_filename, '.o'), out_dir) - cc.RunWithArgs('-c', c_filename, '-o', o_filename, *args) + cc.RunWithArgs('-c', c_filename, '-g', '-o', o_filename, *args) return o_filename def Link(cc, o_filenames, main_exe, *args): - args = ['-o', main_exe] + o_filenames + list(args) + args = ['-g', '-o', main_exe] + o_filenames + list(args) cc.RunWithArgs(*args) @@ -418,20 +430,21 @@ def main(args): o_filenames = [] includes = '-I%s' % options.wasmrt_dir - # Compile wasm-rt-impl. - wasm_rt_impl_c = os.path.join(options.wasmrt_dir, 'wasm-rt-impl.c') - o_filenames.append(Compile(cc, wasm_rt_impl_c, out_dir, includes)) + # Compile wasm-rt-unified. + define_trap = '-DWASM_RT_CUSTOM_TRAP_HANDLER=w2c_trap_handler' + wasm_rt_unified_c = os.path.join(options.wasmrt_dir, 'wasm-rt-unified.c') + o_filenames.append(Compile(cc, wasm_rt_unified_c, out_dir, includes, define_trap)) for i, wasm_filename in enumerate(cwriter.GetModuleFilenames()): wasm_filename = os.path.join(out_dir, wasm_filename) c_filename = utils.ChangeExt(wasm_filename, '.c') wasm2c.RunWithArgs(wasm_filename, '-o', c_filename) if options.compile: - defines = '-DWASM_RT_MODULE_PREFIX=%s' % cwriter.GetModulePrefix(i) - o_filenames.append(Compile(cc, c_filename, out_dir, includes, defines)) + define_mod_name = '-DWASM_RT_MODULE_PREFIX=%s' % cwriter.GetModulePrefix(i) + o_filenames.append(Compile(cc, c_filename, out_dir, includes, define_mod_name, define_trap)) if options.compile: - o_filenames.append(Compile(cc, main_filename, out_dir, includes, defines)) + o_filenames.append(Compile(cc, main_filename, out_dir, includes, define_trap)) main_exe = utils.ChangeExt(json_file_path, '') Link(cc, o_filenames, main_exe, '-lm') diff --git a/test/spec-wasm2c-prefix.c b/test/spec-wasm2c-prefix.c index 78b72e9e8..d07486d7d 100644 --- a/test/spec-wasm2c-prefix.c +++ b/test/spec-wasm2c-prefix.c @@ -2,6 +2,7 @@ #define __STDC_FORMAT_MACROS #include #include +#include #include #include #include @@ -9,10 +10,19 @@ #include #include +#define WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX 1 +#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX +#include +#include +#include +#endif + #include "wasm-rt.h" int g_tests_run; int g_tests_passed; +wasm2c_sandbox_funcs_t g_curr_sbx_details; +wasm2c_sandbox_t* g_curr_sbx = 0; static void run_spec_tests(void); @@ -24,10 +34,23 @@ static void error(const char* file, int line, const char* format, ...) { va_end(args); } +jmp_buf g_jmp_buf; + +void w2c_trap_handler(uint32_t code, const char* msg) { + g_curr_sbx_details.reset_wasm2c_stack_depth(g_curr_sbx); + siglongjmp(g_jmp_buf, code); +} + +#if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX +static void signal_handler(int sig, siginfo_t* si, void* unused) { + wasm_rt_trap(WASM_RT_TRAP_OOB); +} +#endif + #define ASSERT_TRAP(f) \ do { \ g_tests_run++; \ - if (wasm_rt_impl_try() != 0) { \ + if (sigsetjmp(g_jmp_buf, 1) != 0) { \ g_tests_passed++; \ } else { \ (void)(f); \ @@ -38,7 +61,7 @@ static void error(const char* file, int line, const char* format, ...) { #define ASSERT_EXHAUSTION(f) \ do { \ g_tests_run++; \ - wasm_rt_trap_t code = wasm_rt_impl_try(); \ + wasm_rt_trap_t code = sigsetjmp(g_jmp_buf, 1); \ switch (code) { \ case WASM_RT_TRAP_NONE: \ (void)(f); \ @@ -59,7 +82,7 @@ static void error(const char* file, int line, const char* format, ...) { #define ASSERT_RETURN(f) \ do { \ g_tests_run++; \ - if (wasm_rt_impl_try() != 0) { \ + if (sigsetjmp(g_jmp_buf, 1) != 0) { \ error(__FILE__, __LINE__, #f " trapped.\n"); \ } else { \ f; \ @@ -70,7 +93,7 @@ static void error(const char* file, int line, const char* format, ...) { #define ASSERT_RETURN_T(type, fmt, f, expected) \ do { \ g_tests_run++; \ - if (wasm_rt_impl_try() != 0) { \ + if (sigsetjmp(g_jmp_buf, 1) != 0) { \ error(__FILE__, __LINE__, #f " trapped.\n"); \ } else { \ type actual = f; \ @@ -87,7 +110,7 @@ static void error(const char* file, int line, const char* format, ...) { #define ASSERT_RETURN_NAN_T(type, itype, fmt, f, kind) \ do { \ g_tests_run++; \ - if (wasm_rt_impl_try() != 0) { \ + if (sigsetjmp(g_jmp_buf, 1) != 0) { \ error(__FILE__, __LINE__, #f " trapped.\n"); \ } else { \ type actual = f; \ @@ -113,7 +136,7 @@ static void error(const char* file, int line, const char* format, ...) { #define ASSERT_RETURN_MULTI_T(type, fmt, f, compare, expected, found) \ do { \ g_tests_run++; \ - if (wasm_rt_impl_try() != 0) { \ + if (sigsetjmp(g_jmp_buf, 1) != 0) { \ error(__FILE__, __LINE__, #f " trapped.\n"); \ } else { \ type actual = f; \ @@ -248,9 +271,26 @@ static void init_spectest_module(void) { wasm_rt_allocate_table(&spectest_table, 10, 20); } +static void install_signal_handler() { + #if WASM_RT_MEMCHECK_SIGNAL_HANDLER_POSIX + struct sigaction sa; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = signal_handler; + + /* Install SIGSEGV and SIGBUS handlers, since macOS seems to use SIGBUS. */ + if (sigaction(SIGSEGV, &sa, NULL) != 0 || + sigaction(SIGBUS, &sa, NULL) != 0) { + perror("sigaction failed"); + abort(); + } + #endif +} + int main(int argc, char** argv) { init_spectest_module(); + install_signal_handler(); run_spec_tests(); printf("%u/%u tests passed.\n", g_tests_passed, g_tests_run); return g_tests_passed != g_tests_run; diff --git a/wasm2c/README.md b/wasm2c/README.md index 77e455ee7..f2bd9b659 100644 --- a/wasm2c/README.md +++ b/wasm2c/README.md @@ -127,7 +127,6 @@ The generated header file looks something like this: #ifndef FAC_H_GENERATED_ #define FAC_H_GENERATED_ -#define WASM_CURR_MODULE_PREFIX /* Automically generated by wasm2c */ #ifdef __cplusplus extern "C" { @@ -145,8 +144,6 @@ extern "C" { #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) -#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) - /* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; @@ -169,7 +166,7 @@ typedef double f64; # define FUNC_EXPORT #endif -FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)(); struct wasm2c_sandbox_t; typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c index aa13e82eb..20c44c3f4 100644 --- a/wasm2c/examples/fac/fac.c +++ b/wasm2c/examples/fac/fac.c @@ -520,7 +520,7 @@ static void destroy_wasm2c_sandbox(void* aSbx) { free(sbx); } -FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)() { +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)() { wasm2c_sandbox_funcs_t ret; ret.wasm_rt_sys_init = &wasm_rt_sys_init; ret.create_wasm2c_sandbox = &create_wasm2c_sandbox; diff --git a/wasm2c/examples/fac/fac.h b/wasm2c/examples/fac/fac.h index 462c73b45..50469005d 100644 --- a/wasm2c/examples/fac/fac.h +++ b/wasm2c/examples/fac/fac.h @@ -1,7 +1,6 @@ #ifndef FAC_H_GENERATED_ #define FAC_H_GENERATED_ -#define WASM_CURR_MODULE_PREFIX /* Automically generated by wasm2c */ #ifdef __cplusplus extern "C" { @@ -19,8 +18,6 @@ extern "C" { #define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y) #define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x) -#define WASM_CURR_ADD_PREFIX(x) WASM_RT_PASTE(WASM_CURR_MODULE_PREFIX, x) - /* TODO(binji): only use stdint.h types in header */ typedef uint8_t u8; typedef int8_t s8; @@ -43,7 +40,7 @@ typedef double f64; # define FUNC_EXPORT #endif -FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_CURR_ADD_PREFIX(get_wasm2c_sandbox_info)(); +FUNC_EXPORT wasm2c_sandbox_funcs_t WASM_RT_ADD_PREFIX(get_wasm2c_sandbox_info)(); struct wasm2c_sandbox_t; typedef struct wasm2c_sandbox_t wasm2c_sandbox_t; diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index 881408131..8789a651c 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -28,7 +28,7 @@ #ifdef WASM_RT_CUSTOM_TRAP_HANDLER // forward declare the signature of any custom trap handler -void WASM_RT_CUSTOM_TRAP_HANDLER(const char*); +void WASM_RT_CUSTOM_TRAP_HANDLER(uint32_t, const char*); #endif void wasm_rt_trap(wasm_rt_trap_t code) { @@ -93,7 +93,7 @@ void wasm_rt_trap(wasm_rt_trap_t code) { } }; #ifdef WASM_RT_CUSTOM_TRAP_HANDLER - WASM_RT_CUSTOM_TRAP_HANDLER(error_message); + WASM_RT_CUSTOM_TRAP_HANDLER((uint32_t) code, error_message); #else fprintf(stderr, "Error: %s\n", error_message); abort(); diff --git a/wasm2c/wasm-rt-unified.c b/wasm2c/wasm-rt-unified.c new file mode 100644 index 000000000..d6142e9b8 --- /dev/null +++ b/wasm2c/wasm-rt-unified.c @@ -0,0 +1,4 @@ +#include "wasm-rt-impl.c" +#include "wasm-rt-os-unix.c" +#include "wasm-rt-os-win.c" +#include "wasm-rt-wasi.c" \ No newline at end of file diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index d3ea1c020..5cdef3961 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -197,6 +197,8 @@ typedef uint32_t (*add_wasm2c_callback_t)( wasm_rt_elem_target_class_t func_class); typedef void (*remove_wasm2c_callback_t)(void* sbx_ptr, uint32_t callback_idx); +typedef void (*reset_wasm2c_stack_depth_t)(void* sbx_ptr); + typedef struct wasm2c_sandbox_funcs_t { wasm_rt_sys_init_t wasm_rt_sys_init; create_wasm2c_sandbox_t create_wasm2c_sandbox; @@ -205,6 +207,7 @@ typedef struct wasm2c_sandbox_funcs_t { lookup_wasm2c_func_index_t lookup_wasm2c_func_index; add_wasm2c_callback_t add_wasm2c_callback; remove_wasm2c_callback_t remove_wasm2c_callback; + reset_wasm2c_stack_depth_t reset_wasm2c_stack_depth; } wasm2c_sandbox_funcs_t; /** Stop execution immediately and jump back to the call to `wasm_rt_try`.