diff --git a/src/c-writer.cc b/src/c-writer.cc index 3ab7540726..798dc520a2 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -103,6 +103,11 @@ struct ExternalRef : GlobalName { using GlobalName::GlobalName; }; +struct TailCallRef : GlobalName { + explicit TailCallRef(const std::string& name) + : GlobalName(ModuleFieldType::Func, name) {} +}; + struct ExternalInstancePtr : GlobalName { using GlobalName::GlobalName; }; @@ -259,13 +264,15 @@ class CWriter { static constexpr char MangleType(Type); static constexpr char MangleField(ModuleFieldType); static std::string MangleMultivalueTypes(const TypeVector&); - static std::string MangleTagTypes(const TypeVector&); static std::string Mangle(std::string_view name, bool double_underscores); static std::string MangleName(std::string_view); static std::string MangleModuleName(std::string_view); static std::string ExportName(std::string_view module_name, std::string_view export_name); std::string ExportName(std::string_view export_name) const; + static std::string TailCallExportName(std::string_view module_name, + std::string_view export_name); + std::string TailCallExportName(std::string_view export_name) const; std::string ModuleInstanceTypeName() const; static std::string ModuleInstanceTypeName(std::string_view module_name); void ClaimName(SymbolSet& set, @@ -297,6 +304,7 @@ class CWriter { std::string GetGlobalName(ModuleFieldType, const std::string&) const; std::string GetLocalName(const std::string&, bool is_label) const; + std::string GetTailCallRef(const std::string&) const; void Indent(int size = INDENT_SIZE); void Dedent(int size = INDENT_SIZE); @@ -333,6 +341,7 @@ class CWriter { void Write(const GlobalName&); void Write(const TagPtr&); void Write(const ExternalRef&); + void Write(const TailCallRef&); void Write(const ExternalInstancePtr&); void Write(const ExternalInstanceRef&); void Write(Type); @@ -350,7 +359,9 @@ class CWriter { void WriteSourceTop(); void WriteMultiCTop(); void WriteMultiCTopEmpty(); - void WriteMultivalueTypes(); + void WriteMultivalueType(const TypeVector&); + void WriteMultivalueParamTypes(); + void WriteMultivalueResultTypes(); void WriteTagTypes(); void WriteFuncTypeDecls(); void WriteFuncTypes(); @@ -360,8 +371,10 @@ class CWriter { void ComputeUniqueImports(); void BeginInstance(); void WriteImports(); + void WriteTailCallWeakImports(); void WriteFuncDeclarations(); void WriteFuncDeclaration(const FuncDeclaration&, const std::string&); + void WriteTailCallFuncDeclaration(const std::string&); void WriteImportFuncDeclaration(const FuncDeclaration&, const std::string& module_name, const std::string&); @@ -390,6 +403,7 @@ class CWriter { void WriteElemInitializers(); void WriteElemTableInit(bool, const ElemSegment*, const Table*); void WriteExports(CWriterPhase); + void WriteTailCallExports(CWriterPhase); void WriteInitDecl(); void WriteFreeDecl(); void WriteGetFuncTypeDecl(); @@ -400,6 +414,7 @@ class CWriter { void WriteImportProperties(CWriterPhase); void WriteFuncs(); void Write(const Func&); + void WriteTailCallee(const Func&); void WriteParamsAndLocals(); void WriteParams(const std::vector& index_to_name); void WriteParamSymbols(const std::vector& index_to_name); @@ -493,6 +508,8 @@ class CWriter { name_to_output_file_index_; bool simd_used_in_header_; + + bool in_tail_callee_; }; // TODO: if WABT begins supporting debug names for labels, @@ -508,6 +525,8 @@ static constexpr char kLabelSuffix = kParamSuffix + 1; static constexpr char kGlobalSymbolPrefix[] = "w2c_"; static constexpr char kLocalSymbolPrefix[] = "var_"; static constexpr char kAdminSymbolPrefix[] = "wasm2c_"; +static constexpr char kTailCallSymbolPrefix[] = "wasm2c_tailcall_"; +static constexpr unsigned int kTailCallStackSize = 1024; size_t CWriter::MarkTypeStack() const { return type_stack_.size(); @@ -634,16 +653,6 @@ std::string CWriter::MangleMultivalueTypes(const TypeVector& types) { return result; } -// static -std::string CWriter::MangleTagTypes(const TypeVector& types) { - assert(types.size() >= 2); - std::string result = "wasm_tag_"; - for (auto type : types) { - result += MangleType(type); - } - return result; -} - /* The C symbol for an export from this module. */ std::string CWriter::ExportName(std::string_view export_name) const { return kGlobalSymbolPrefix + module_prefix_ + '_' + MangleName(export_name); @@ -657,6 +666,20 @@ std::string CWriter::ExportName(std::string_view module_name, MangleName(export_name); } +/* The C symbol for a tail-callee export from this module. */ +std::string CWriter::TailCallExportName(std::string_view export_name) const { + return kTailCallSymbolPrefix + std::to_string(kTailCallStackSize) + '_' + + ExportName(export_name); +} + +/* The C symbol for a tail-callee export from an arbitrary module. */ +// static +std::string CWriter::TailCallExportName(std::string_view module_name, + std::string_view export_name) { + return kTailCallSymbolPrefix + std::to_string(kTailCallStackSize) + '_' + + ExportName(module_name, export_name); +} + /* The type name of an instance of this module. */ std::string CWriter::ModuleInstanceTypeName() const { return kGlobalSymbolPrefix + module_prefix_; @@ -927,6 +950,11 @@ std::string CWriter::GetLocalName(const std::string& name, return local_sym_map_.at(mangled); } +std::string CWriter::GetTailCallRef(const std::string& name) const { + return kTailCallSymbolPrefix + std::to_string(kTailCallStackSize) + '_' + + GetGlobalName(ModuleFieldType::Func, name); +} + std::string CWriter::DefineParamName(std::string_view name) { return DefineLocalScopeName(name, false); } @@ -1071,6 +1099,10 @@ void CWriter::Write(const ExternalRef& name) { } } +void CWriter::Write(const TailCallRef& name) { + Write(GetTailCallRef(name.name)); +} + void CWriter::Write(const ExternalInstanceRef& name) { if (IsImport(name.name)) { Write("(*instance->", GlobalName(name), ")"); @@ -1377,7 +1409,8 @@ void CWriter::WriteInitExprTerminal(const Expr* expr) { Write("(wasm_rt_funcref_t){", FuncTypeExpr(func_type), ", ", "(wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", "); + ExternalRef(ModuleFieldType::Func, func->name), ", {", + TailCallRef(func->name), "}, "); if (IsImport(func->name)) { Write("instance->", GlobalName(ModuleFieldType::Import, @@ -1445,26 +1478,37 @@ void CWriter::WriteMultiCTopEmpty() { } } -void CWriter::WriteMultivalueTypes() { +void CWriter::WriteMultivalueType(const TypeVector& types) { + const std::string name = MangleMultivalueTypes(types); + // these ifndefs are actually to support importing multiple modules + // incidentally they also mean we don't have to bother with deduplication + Write(Newline(), "#ifndef ", name, Newline()); + Write("#define ", name, " ", name, Newline()); + Write("struct ", name, " ", OpenBrace()); + for (Index i = 0; i < types.size(); ++i) { + const Type type = types[i]; + Write(type); + Writef(" %c%d;", MangleType(type), i); + Write(Newline()); + } + Write(CloseBrace(), ";", Newline(), "#endif /* ", name, " */", Newline()); +} + +void CWriter::WriteMultivalueResultTypes() { for (TypeEntry* type : module_->types) { FuncType* func_type = cast(type); - Index num_results = func_type->GetNumResults(); - if (num_results <= 1) { - continue; + if (func_type->GetNumResults() > 1) { + WriteMultivalueType(func_type->sig.result_types); } - std::string name = MangleMultivalueTypes(func_type->sig.result_types); - // these ifndefs are actually to support importing multiple modules - // incidentally they also mean we don't have to bother with deduplication - Write("#ifndef ", name, Newline()); - Write("#define ", name, " ", name, Newline()); - Write("struct ", name, " ", OpenBrace()); - for (Index i = 0; i < num_results; ++i) { - Type type = func_type->GetResultType(i); - Write(type); - Writef(" %c%d;", MangleType(type), i); - Write(Newline()); + } +} + +void CWriter::WriteMultivalueParamTypes() { + for (TypeEntry* type : module_->types) { + FuncType* func_type = cast(type); + if (func_type->GetNumParams() > 1) { + WriteMultivalueType(func_type->sig.param_types); } - Write(CloseBrace(), ";", Newline(), "#endif /* ", name, " */", Newline()); } } @@ -1475,18 +1519,7 @@ void CWriter::WriteTagTypes() { if (num_params <= 1) { continue; } - const std::string name = MangleTagTypes(tag_type.sig.param_types); - // use same method as WriteMultivalueTypes - Write("#ifndef ", name, Newline()); - Write("#define ", name, " ", name, Newline()); - Write("struct ", name, " ", OpenBrace()); - for (Index i = 0; i < num_params; ++i) { - Type type = tag_type.GetParamType(i); - Write(type); - Writef(" %c%d;", MangleType(type), i); - Write(Newline()); - } - Write(CloseBrace(), ";", Newline(), "#endif /* ", name, " */", Newline()); + WriteMultivalueType(tag_type.sig.param_types); } } @@ -1750,6 +1783,8 @@ void CWriter::WriteImports() { ExportName(import->module_name, import->field_name)); Write(";"); Write(Newline()); + WriteTailCallFuncDeclaration(GetTailCallRef(func.name)); + Write(";", Newline()); } else if (import->kind() == ExternalKind::Tag) { Write(Newline(), "/* import: '", SanitizeForComment(import->module_name), "' '", SanitizeForComment(import->field_name), "' */", Newline()); @@ -1760,6 +1795,23 @@ void CWriter::WriteImports() { } } +void CWriter::WriteTailCallWeakImports() { + for (const Import* import : unique_imports_) { + if (import->kind() != ExternalKind::Func) { + continue; + } + + Write(Newline(), "/* handler for missing tail-call on import: '", + SanitizeForComment(import->module_name), "' '", + SanitizeForComment(import->field_name), "' */", Newline()); + Write("__attribute__((weak)) "); + WriteTailCallFuncDeclaration( + TailCallExportName(import->module_name, import->field_name)); + Write(" ", OpenBrace(), "TRAP(UNHANDLED_TAIL_CALL);", Newline(), + CloseBrace(), Newline()); + } +} + void CWriter::WriteFuncDeclarations() { if (module_->funcs.size() == module_->num_func_imports) return; @@ -1774,6 +1826,10 @@ void CWriter::WriteFuncDeclarations() { WriteFuncDeclaration( func->decl, DefineGlobalScopeName(ModuleFieldType::Func, func->name)); Write(";", Newline()); + + Write(InternalSymbolScope()); + WriteTailCallFuncDeclaration(GetTailCallRef(func->name)); + Write(";", Newline()); } ++func_index; } @@ -1787,6 +1843,12 @@ void CWriter::WriteFuncDeclaration(const FuncDeclaration& decl, Write(")"); } +void CWriter::WriteTailCallFuncDeclaration(const std::string& mangled_name) { + Write("void ", mangled_name, + "(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t " + "*next)"); +} + void CWriter::WriteImportFuncDeclaration(const FuncDeclaration& decl, const std::string& module_name, const std::string& name) { @@ -2149,7 +2211,8 @@ void CWriter::WriteElemInitializers() { const Func* func = module_->GetFunc(cast(&expr)->var); const FuncType* func_type = module_->GetFuncType(func->decl.type_var); Write("{", FuncTypeExpr(func_type), ", (wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", "); + ExternalRef(ModuleFieldType::Func, func->name), ", {", + TailCallRef(func->name), "}, "); if (IsImport(func->name)) { Write("offsetof(", ModuleInstanceTypeName(), ", ", GlobalName(ModuleFieldType::Import, @@ -2161,7 +2224,7 @@ void CWriter::WriteElemInitializers() { Write("},", Newline()); } break; case ExprType::RefNull: - Write("{NULL, NULL, 0},", Newline()); + Write("{NULL, NULL, {}, 0},", Newline()); break; default: WABT_UNREACHABLE; @@ -2387,6 +2450,29 @@ void CWriter::WriteExports(CWriterPhase kind) { } } +void CWriter::WriteTailCallExports(CWriterPhase kind) { + for (const Export* export_ : module_->exports) { + if (export_->kind != ExternalKind::Func) { + continue; + } + + const std::string mangled_name = TailCallExportName(export_->name); + + Write(Newline(), "/* export for tail-call of '", + SanitizeForComment(export_->name), "' */", Newline()); + if (kind == CWriterPhase::Declarations) { + WriteTailCallFuncDeclaration(mangled_name); + Write(";", Newline()); + } else { + WriteTailCallFuncDeclaration(mangled_name); + Write(" ", OpenBrace()); + const Func* func = module_->GetFunc(export_->var); + Write(TailCallRef(func->name), "(instance_ptr, tail_call_stack, next);", + Newline(), CloseBrace(), Newline()); + } + } +} + void CWriter::WriteInit() { Write(Newline(), "void ", kAdminSymbolPrefix, module_prefix_, "_instantiate(", ModuleInstanceTypeName(), "* instance"); @@ -2600,6 +2686,7 @@ void CWriter::WriteFuncs() { if (!is_import) { stream_ = c_streams_.at(c_stream_assignment.at(func_index)); Write(*func); + WriteTailCallee(*func); } ++func_index; } @@ -2616,6 +2703,7 @@ bool CWriter::IsImport(const std::string& name) const { void CWriter::Write(const Func& func) { func_ = &func; + in_tail_callee_ = false; local_syms_.clear(); local_sym_map_.clear(); stack_var_sym_map_.clear(); @@ -2687,6 +2775,126 @@ void CWriter::Write(const Func& func) { func_ = nullptr; } +void CWriter::WriteTailCallee(const Func& func) { + func_ = &func; + in_tail_callee_ = true; + local_syms_.clear(); + local_sym_map_.clear(); + stack_var_sym_map_.clear(); + func_sections_.clear(); + func_includes_.clear(); + + Stream* prev_stream = stream_; + + Write(Newline()); + + PushFuncSection(); + WriteTailCallFuncDeclaration(GetTailCallRef(func.name)); + Write(" ", OpenBrace()); + if (func.GetNumParams()) { + Write("wasm_static_assert(sizeof(", func.decl.sig.param_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + if (func.GetNumResults() && + func.decl.sig.result_types != func.decl.sig.param_types) { + Write("wasm_static_assert(sizeof(", func.decl.sig.result_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + Write(ModuleInstanceTypeName(), "* instance = *instance_ptr;", Newline()); + + std::vector index_to_name; + MakeTypeBindingReverseMapping(func_->GetNumParamsAndLocals(), func_->bindings, + &index_to_name); + if (func_->GetNumParams() == 1) { + Write(func_->GetParamType(0), " ", DefineParamName(index_to_name[0]), + " = *(", func_->GetParamType(0), "*)tail_call_stack;", Newline()); + } else if (func_->GetNumParams() > 1) { + for (Type type : {Type::I32, Type::I64, Type::F32, Type::F64, Type::V128, + Type::FuncRef, Type::ExternRef}) { + Index param_index = 0; + size_t count = 0; + for (Type param_type : func_->decl.sig.param_types) { + if (param_type == type) { + if (count == 0) { + Write(type, " "); + Indent(4); + } else { + Write(", "); + if ((count % 8) == 0) + Write(Newline()); + } + + Write(DefineParamName(index_to_name[param_index])); + ++count; + } + ++param_index; + } + if (count != 0) { + Dedent(4); + Write(";", Newline()); + } + } + Write(OpenBrace()); + Write("struct ", MangleMultivalueTypes(func_->decl.sig.param_types), + " *tmp = tail_call_stack;", Newline()); + for (Index i = 0; i < func_->GetNumParams(); ++i) { + Write(ParamName(index_to_name[i])); + Writef(" = tmp->%c%d;", MangleType(func_->GetParamType(i)), i); + Write(Newline()); + } + Write(CloseBrace(), Newline()); + } + + WriteLocals(index_to_name); + + PushFuncSection(); + + std::string label = DefineLabelName(kImplicitFuncLabel); + ResetTypeStack(0); + std::string empty; // Must not be temporary, since address is taken by Label. + PushLabel(LabelType::Func, empty, func.decl.sig); + Write(func.exprs, LabelDecl(label)); + PopLabel(); + ResetTypeStack(0); + PushTypes(func.decl.sig.result_types); + + // Return the top of the stack implicitly. + Index num_results = func.GetNumResults(); + if (num_results == 1) { + Write("wasm_rt_memcpy(tail_call_stack, &", StackVar(0), ", sizeof(", + StackVar(0), "));", Newline()); + } else if (num_results >= 2) { + Write(OpenBrace()); + Write(func.decl.sig.result_types, " *tmp = tail_call_stack;", Newline()); + for (Index i = 0; i < num_results; ++i) { + Type type = func.GetResultType(i); + Writef("tmp->%c%d = ", MangleType(type), i); + Write(StackVar(num_results - i - 1), ";", Newline()); + } + Write(CloseBrace(), Newline()); + } + Write("next->fn = NULL;", Newline()); + + stream_ = prev_stream; + + for (size_t i = 0; i < func_sections_.size(); ++i) { + auto& [condition, stream] = func_sections_.at(i); + std::unique_ptr buf = stream.ReleaseOutputBuffer(); + if (condition.empty() || func_includes_.count(condition)) { + stream_->WriteData(buf->data.data(), buf->data.size()); + } + + if (i == 0) { + WriteStackVarDeclarations(); // these come immediately after section #0 + // (return type/name/params/locals) + } + } + + Write(CloseBrace(), Newline()); + + func_ = nullptr; +} + void CWriter::WriteParamsAndLocals() { std::vector index_to_name; MakeTypeBindingReverseMapping(func_->GetNumParamsAndLocals(), func_->bindings, @@ -2915,7 +3123,7 @@ void CWriter::Write(const Catch& c) { PushType(type); } Write(OpenBrace()); - Write("struct ", MangleTagTypes(tag_type.sig.param_types), " tmp;", + Write("struct ", MangleMultivalueTypes(tag_type.sig.param_types), " tmp;", Newline()); Write("wasm_rt_memcpy(&tmp, wasm_rt_exception(), sizeof(tmp));", Newline()); for (unsigned int i = 0; i < tag_type.sig.param_types.size(); ++i) { @@ -3378,7 +3586,8 @@ void CWriter::Write(const ExprList& exprs) { Write(StackVar(0), " = (wasm_rt_funcref_t){", FuncTypeExpr(func_type), ", (wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", "); + ExternalRef(ModuleFieldType::Func, func->name), ", {", + TailCallRef(func->name), "}, "); if (IsImport(func->name)) { Write("instance->", GlobalName(ModuleFieldType::Import, @@ -3514,7 +3723,7 @@ void CWriter::Write(const ExprList& exprs) { Newline()); } else { Write(OpenBrace()); - Write("struct ", MangleTagTypes(tag->decl.sig.param_types)); + Write("struct ", MangleMultivalueTypes(tag->decl.sig.param_types)); Write(" tmp = {"); for (Index i = 0; i < num_params; ++i) { Write(StackVar(i), ", "); @@ -3578,10 +3787,196 @@ void CWriter::Write(const ExprList& exprs) { break; } + case ExprType::ReturnCall: { + const Label* label = FindLabel(Var(label_stack_.size() - 1, {})); + assert(try_catch_stack_.size() >= label->try_catch_stack_size); + + if (try_catch_stack_.size() != label->try_catch_stack_size) { + const std::string& name = + try_catch_stack_.at(label->try_catch_stack_size).name; + + Write("wasm_rt_set_unwind_target(", name, "_outer_target);", + Newline()); + } + + const auto inst = cast(&expr); + Write(OpenBrace()); + if (!in_tail_callee_) { + Write("void *instance_ptr_storage;", Newline()); + Write("void **instance_ptr = &instance_ptr_storage;", Newline()); + Write("char tail_call_stack[", std::to_string(kTailCallStackSize), + "];", Newline()); + Write("wasm_rt_tailcallee_t next_storage;", Newline(), Newline()); + Write("wasm_rt_tailcallee_t *next = &next_storage;", Newline()); + } + const Func& func = *module_->GetFunc(inst->var); + const Index num_params = func.GetNumParams(); + const Index num_results = func.GetNumResults(); + + if (func.GetNumParams()) { + Write("wasm_static_assert(sizeof(", func.decl.sig.param_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + if (func.GetNumResults() && + func.decl.sig.result_types != func.decl.sig.param_types) { + Write("wasm_static_assert(sizeof(", func.decl.sig.result_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + + if (num_params == 1) { + Write("wasm_rt_memcpy(tail_call_stack, &", StackVar(0), ", sizeof(", + StackVar(0), "));", Newline()); + } else if (num_params >= 2) { + Write(OpenBrace()); + Write("struct ", MangleMultivalueTypes(func.decl.sig.param_types), + " *tmp = (struct ", + MangleMultivalueTypes(func.decl.sig.param_types), + " *)tail_call_stack;", Newline()); + for (Index i = 0; i < num_params; ++i) { + Type type = func.GetParamType(i); + Writef("tmp->%c%d = ", MangleType(type), i); + Write(StackVar(num_params - i - 1), ";", Newline()); + } + Write(CloseBrace(), Newline()); + } + + Write("next->fn = ", TailCallRef(func.name), ";", Newline()); + if (IsImport(func.name)) { + Write("*instance_ptr = ", + GlobalName(ModuleFieldType::Import, + import_module_sym_map_.at(func.name)), + ";", Newline()); + } + + if (in_tail_callee_) { + Write("return;", Newline()); + } else { + Write( + "while (next->fn) { next->fn( instance_ptr, tail_call_stack, " + "next ); }", + Newline()); + DropTypes(num_params); + if (num_results == 1) { + PushTypes(func.decl.sig.result_types); + Write(StackVar(0), " = *(", StackType(0), "*)tail_call_stack;", + Newline()); + } else if (num_results >= 2) { + Write(OpenBrace()); + Write("struct ", MangleMultivalueTypes(func.decl.sig.result_types), + " *tmp = tail_call_stack;", Newline()); + for (Index i = 0; i < num_results; ++i) { + Type type = func.GetResultType(i); + PushType(type); + Write(StackVar(0)); + Writef(" = tmp->%c%d;", MangleType(type), i); + Write(Newline()); + } + Write(CloseBrace(), Newline()); + } + + Write("goto ", LabelName(kImplicitFuncLabel), ";", Newline()); + } + + Write(CloseBrace(), Newline()); + return; + } + + case ExprType::ReturnCallIndirect: { + const Label* label = FindLabel(Var(label_stack_.size() - 1, {})); + assert(try_catch_stack_.size() >= label->try_catch_stack_size); + + if (try_catch_stack_.size() != label->try_catch_stack_size) { + const std::string& name = + try_catch_stack_.at(label->try_catch_stack_size).name; + + Write("wasm_rt_set_unwind_target(", name, "_outer_target);", + Newline()); + } + + const auto inst = cast(&expr); + Write(OpenBrace()); + if (!in_tail_callee_) { + Write("void *instance_ptr_storage;", Newline()); + Write("void **instance_ptr = &instance_ptr_storage;", Newline()); + Write("char tail_call_stack[", std::to_string(kTailCallStackSize), + "];", Newline()); + Write("wasm_rt_tailcallee_t next_storage;", Newline(), Newline()); + Write("wasm_rt_tailcallee_t *next = &next_storage;", Newline()); + } + const FuncDeclaration& decl = inst->decl; + const Index num_params = decl.GetNumParams(); + const Index num_results = decl.GetNumResults(); + + if (decl.GetNumParams()) { + Write("wasm_static_assert(sizeof(", decl.sig.param_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + if (decl.GetNumResults() && + decl.sig.result_types != decl.sig.param_types) { + Write("wasm_static_assert(sizeof(", decl.sig.result_types, + ") <= ", kTailCallStackSize, ");", Newline()); + } + + if (num_params == 1) { + Write("wasm_rt_memcpy(tail_call_stack, &", + StackVar(num_params, decl.GetResultType(0)), ", sizeof(", + decl.GetResultType(0), "));", Newline()); + } else if (num_params >= 2) { + Write(OpenBrace()); + Write("struct ", MangleMultivalueTypes(decl.sig.param_types), + " *tmp = (struct ", MangleMultivalueTypes(decl.sig.param_types), + " *)tail_call_stack;", Newline()); + for (Index i = 0; i < num_params; ++i) { + Type type = decl.GetParamType(i); + Writef("tmp->%c%d = ", MangleType(type), i); + Write(StackVar(num_params - i), ";", Newline()); + } + Write(CloseBrace(), Newline()); + } + + const Table* table = module_->GetTable(inst->table); + + assert(decl.has_func_type); + const FuncType* func_type = module_->GetFuncType(decl.type_var); + + Write("RETURN_CALL_INDIRECT(", + ExternalInstanceRef(ModuleFieldType::Table, table->name), ", ", + FuncTypeExpr(func_type), ", ", StackVar(0), ");", Newline()); + + if (in_tail_callee_) { + Write("return;", Newline()); + } else { + Write( + "while (next->fn) { next->fn( instance_ptr, tail_call_stack, " + "next ); }", + Newline()); + DropTypes(num_params + 1); + if (num_results == 1) { + PushTypes(decl.sig.result_types); + Write(StackVar(0), " = *(", StackType(0), "*)tail_call_stack;", + Newline()); + } else if (num_results >= 2) { + Write(OpenBrace()); + Write("struct ", MangleMultivalueTypes(decl.sig.result_types), + " *tmp = tail_call_stack;", Newline()); + for (Index i = 0; i < num_results; ++i) { + Type type = decl.GetResultType(i); + PushType(type); + Write(StackVar(0)); + Writef(" = tmp->%c%d;", MangleType(type), i); + Write(Newline()); + } + Write(CloseBrace(), Newline()); + } + Write("goto ", LabelName(kImplicitFuncLabel), ";", Newline()); + } + + Write(CloseBrace(), Newline()); + return; + } + case ExprType::AtomicWait: case ExprType::AtomicNotify: - case ExprType::ReturnCall: - case ExprType::ReturnCallIndirect: case ExprType::CallRef: UNIMPLEMENTED("..."); break; @@ -5358,10 +5753,11 @@ void CWriter::WriteCHeader() { WriteInitDecl(); WriteFreeDecl(); WriteGetFuncTypeDecl(); - WriteMultivalueTypes(); + WriteMultivalueResultTypes(); WriteImports(); WriteImportProperties(CWriterPhase::Declarations); WriteExports(CWriterPhase::Declarations); + WriteTailCallExports(CWriterPhase::Declarations); Write(Newline()); Write(s_header_bottom); Write(Newline(), "#endif /* ", guard, " */", Newline()); @@ -5380,6 +5776,7 @@ void CWriter::WriteCSource() { WriteFuncDeclarations(); WriteDataInitializerDecls(); WriteElemInitializerDecls(); + WriteMultivalueParamTypes(); /* Write the module-wide material to the first output stream */ stream_ = c_streams_.front(); @@ -5390,6 +5787,8 @@ void CWriter::WriteCSource() { WriteDataInitializers(); WriteElemInitializers(); WriteExports(CWriterPhase::Definitions); + WriteTailCallExports(CWriterPhase::Definitions); + WriteTailCallWeakImports(); WriteInitInstanceImport(); WriteImportProperties(CWriterPhase::Definitions); WriteInit(); diff --git a/src/prebuilt/wasm2c_source_declarations.cc b/src/prebuilt/wasm2c_source_declarations.cc index ca541778e4..014129b631 100644 --- a/src/prebuilt/wasm2c_source_declarations.cc +++ b/src/prebuilt/wasm2c_source_declarations.cc @@ -46,6 +46,19 @@ R"w2c_template( TRAP(CALL_INDIRECT), \ R"w2c_template( ((t)table.data[x].func)(__VA_ARGS__)) )w2c_template" R"w2c_template( +#define RETURN_CALL_INDIRECT(table, ft, x) \ +)w2c_template" +R"w2c_template( (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ +)w2c_template" +R"w2c_template( func_types_eq(ft, table.data[x].func_type)) || \ +)w2c_template" +R"w2c_template( TRAP(CALL_INDIRECT), \ +)w2c_template" +R"w2c_template( (next->fn = table.data[x].func_tailcallee.fn, \ +)w2c_template" +R"w2c_template( *instance_ptr = table.data[x].module_instance)) +)w2c_template" +R"w2c_template( #ifdef SUPPORT_MEMORY64 )w2c_template" R"w2c_template(#define RANGE_CHECK(mem, offset, len) \ @@ -922,6 +935,8 @@ R"w2c_template( wasm_rt_func_type_t type; )w2c_template" R"w2c_template( wasm_rt_function_ptr_t func; )w2c_template" +R"w2c_template( wasm_rt_tailcallee_t func_tailcallee; +)w2c_template" R"w2c_template( size_t module_offset; )w2c_template" R"w2c_template(} wasm_elem_segment_expr_t; @@ -953,11 +968,11 @@ R"w2c_template( for (u32 i = 0; i < n; i++) { )w2c_template" R"w2c_template( const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; )w2c_template" -R"w2c_template( dest->data[dest_addr + i] = +R"w2c_template( dest->data[dest_addr + i] = (wasm_rt_funcref_t){ )w2c_template" -R"w2c_template( (wasm_rt_funcref_t){src_expr->type, src_expr->func, +R"w2c_template( src_expr->type, src_expr->func, src_expr->func_tailcallee, )w2c_template" -R"w2c_template( (char*)module_instance + src_expr->module_offset}; +R"w2c_template( (char*)module_instance + src_expr->module_offset}; )w2c_template" R"w2c_template( } )w2c_template" @@ -1107,4 +1122,15 @@ R"w2c_template(#define FUNC_TYPE_T(x) static const char x[] )w2c_template" R"w2c_template(#endif )w2c_template" +R"w2c_template( +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +)w2c_template" +R"w2c_template(#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define wasm_static_assert assert +)w2c_template" +R"w2c_template(#endif +)w2c_template" ; diff --git a/src/template/wasm2c.declarations.c b/src/template/wasm2c.declarations.c index 2e4fefb991..ee243dc662 100644 --- a/src/template/wasm2c.declarations.c +++ b/src/template/wasm2c.declarations.c @@ -26,6 +26,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -493,6 +500,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -509,9 +517,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -591,3 +599,9 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_EXTERN_T(x) const char x[] #define FUNC_TYPE_T(x) static const char x[] #endif + +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif diff --git a/src/tools/wasm2c.cc b/src/tools/wasm2c.cc index 10d9210ede..c1718d60c3 100644 --- a/src/tools/wasm2c.cc +++ b/src/tools/wasm2c.cc @@ -61,7 +61,7 @@ static const char s_description[] = static const std::string supported_features[] = { "multi-memory", "multi-value", "sign-extension", "saturating-float-to-int", "exceptions", "memory64", "extended-const", "simd", - "threads"}; + "threads", "tail-call"}; static bool IsFeatureSupported(const std::string& feature) { return std::find(std::begin(supported_features), std::end(supported_features), diff --git a/test/run-spec-wasm2c.py b/test/run-spec-wasm2c.py index 9f45499914..6a308e554b 100755 --- a/test/run-spec-wasm2c.py +++ b/test/run-spec-wasm2c.py @@ -533,6 +533,7 @@ def main(args): parser.add_argument('--enable-memory64', action='store_true') parser.add_argument('--enable-extended-const', action='store_true') parser.add_argument('--enable-threads', action='store_true') + parser.add_argument('--enable-tail-call', action='store_true') parser.add_argument('--disable-bulk-memory', action='store_true') parser.add_argument('--disable-reference-types', action='store_true') parser.add_argument('--debug-names', action='store_true') @@ -553,6 +554,7 @@ def main(args): '--enable-memory64': options.enable_memory64, '--enable-extended-const': options.enable_extended_const, '--enable-threads': options.enable_threads, + '--enable-tail-call': options.enable_tail_call, '--enable-multi-memory': options.enable_multi_memory, '--disable-bulk-memory': options.disable_bulk_memory, '--disable-reference-types': options.disable_reference_types, @@ -571,6 +573,7 @@ def main(args): '--enable-memory64': options.enable_memory64, '--enable-extended-const': options.enable_extended_const, '--enable-threads': options.enable_threads, + '--enable-tail-call': options.enable_tail_call, '--enable-multi-memory': options.enable_multi_memory}) options.cflags += shlex.split(os.environ.get('WASM2C_CFLAGS', '')) diff --git a/test/wasm2c/add.txt b/test/wasm2c/add.txt index 59e2d3f385..3a2921ae6c 100644 --- a/test/wasm2c/add.txt +++ b/test/wasm2c/add.txt @@ -41,6 +41,9 @@ wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t res /* export: 'add' */ u32 w2c_test_add(w2c_test*, u32, u32); +/* export for tail-call of 'add' */ +void wasm2c_tailcall_1024_w2c_test_add(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + #ifdef __cplusplus } #endif @@ -93,6 +96,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -560,6 +570,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -576,9 +587,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -659,7 +670,22 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + static u32 w2c_test_add_0(w2c_test*, u32, u32); +static void wasm2c_tailcall_1024_w2c_test_add_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +#ifndef wasm_multi_ii +#define wasm_multi_ii wasm_multi_ii +struct wasm_multi_ii { + u32 i0; + u32 i1; +}; +#endif /* wasm_multi_ii */ FUNC_TYPE_T(w2c_test_t0) = "\x92\xfb\x6a\xdf\x49\x07\x0a\x83\xbe\x08\x02\x68\xcd\xf6\x95\x27\x4a\xc2\xf3\xe5\xe4\x7d\x29\x49\xe8\xed\x42\x92\x6a\x9d\xda\xf0"; @@ -668,6 +694,11 @@ u32 w2c_test_add(w2c_test* instance, u32 var_p0, u32 var_p1) { return w2c_test_add_0(instance, var_p0, var_p1); } +/* export for tail-call of 'add' */ +void wasm2c_tailcall_1024_w2c_test_add(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test_add_0(instance_ptr, tail_call_stack, next); +} + void wasm2c_test_instantiate(w2c_test* instance) { assert(wasm_rt_is_initialized()); } @@ -699,4 +730,22 @@ u32 w2c_test_add_0(w2c_test* instance, u32 var_p0, u32 var_p1) { FUNC_EPILOGUE; return var_i0; } + +void wasm2c_tailcall_1024_w2c_test_add_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_static_assert(sizeof(struct wasm_multi_ii) <= 1024); + wasm_static_assert(sizeof(u32) <= 1024); + w2c_test* instance = *instance_ptr; + u32 var_p0, var_p1; + { + struct wasm_multi_ii *tmp = tail_call_stack; + var_p0 = tmp->i0; + var_p1 = tmp->i1; + } + u32 var_i0, var_i1; + var_i0 = var_p0; + var_i1 = var_p1; + var_i0 += var_i1; + wasm_rt_memcpy(tail_call_stack, &var_i0, sizeof(var_i0)); + next->fn = NULL; +} ;;; STDOUT ;;) diff --git a/test/wasm2c/check-imports.txt b/test/wasm2c/check-imports.txt index 119fba987f..a4fa3223ec 100644 --- a/test/wasm2c/check-imports.txt +++ b/test/wasm2c/check-imports.txt @@ -116,6 +116,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -583,6 +590,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -599,9 +607,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -682,9 +690,26 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + static u32 w2c_test_f0(w2c_test*, u32); +static void wasm2c_tailcall_1024_w2c_test_f0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); static u32 w2c_test_f1(w2c_test*); +static void wasm2c_tailcall_1024_w2c_test_f1(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); static u32 w2c_test_f2(w2c_test*, u32, u32); +static void wasm2c_tailcall_1024_w2c_test_f2(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +#ifndef wasm_multi_ii +#define wasm_multi_ii wasm_multi_ii +struct wasm_multi_ii { + u32 i0; + u32 i1; +}; +#endif /* wasm_multi_ii */ FUNC_TYPE_T(w2c_test_t0) = "\x07\x80\x96\x7a\x42\xf7\x3e\xe6\x70\x5c\x2f\xac\x83\xf5\x67\xd2\xa2\xa0\x69\x41\x5f\xf8\xe7\x96\x7f\x23\xab\x00\x03\x5f\x4a\x3c"; FUNC_TYPE_T(w2c_test_t1) = "\x72\xab\x00\xdf\x20\x3d\xce\xa1\xf2\x29\xc7\x9d\x13\x40\x7e\x98\xac\x7d\x41\x4a\x53\x2e\x42\x42\x61\x55\x2e\xaa\xeb\xbe\xc6\x35"; @@ -694,7 +719,7 @@ static void init_memories(w2c_test* instance) { } static const wasm_elem_segment_expr_t elem_segment_exprs_w2c_test_e0[] = { - {w2c_test_t1, (wasm_rt_function_ptr_t)w2c_test_f1, 0}, + {w2c_test_t1, (wasm_rt_function_ptr_t)w2c_test_f1, {wasm2c_tailcall_1024_w2c_test_f1}, 0}, }; static void init_tables(w2c_test* instance) { @@ -768,6 +793,17 @@ u32 w2c_test_f0(w2c_test* instance, u32 var_p0) { return var_i0; } +void wasm2c_tailcall_1024_w2c_test_f0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_static_assert(sizeof(u32) <= 1024); + w2c_test* instance = *instance_ptr; + u32 var_p0 = *(u32*)tail_call_stack; + u32 var_i0; + var_i0 = var_p0; + var_i0 = CALL_INDIRECT((*instance->w2c_env_0x5F_indirect_function_table), u32 (*)(void*), w2c_test_t1, var_i0, (*instance->w2c_env_0x5F_indirect_function_table).data[var_i0].module_instance); + wasm_rt_memcpy(tail_call_stack, &var_i0, sizeof(var_i0)); + next->fn = NULL; +} + u32 w2c_test_f1(w2c_test* instance) { FUNC_PROLOGUE; u32 var_i0; @@ -777,6 +813,16 @@ u32 w2c_test_f1(w2c_test* instance) { return var_i0; } +void wasm2c_tailcall_1024_w2c_test_f1(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_static_assert(sizeof(u32) <= 1024); + w2c_test* instance = *instance_ptr; + u32 var_i0; + var_i0 = 16u; + var_i0 = i32_load(instance->w2c_env_0x5F_linear_memory, (u64)(var_i0)); + wasm_rt_memcpy(tail_call_stack, &var_i0, sizeof(var_i0)); + next->fn = NULL; +} + u32 w2c_test_f2(w2c_test* instance, u32 var_p0, u32 var_p1) { FUNC_PROLOGUE; u32 var_i0; @@ -785,4 +831,21 @@ u32 w2c_test_f2(w2c_test* instance, u32 var_p0, u32 var_p1) { FUNC_EPILOGUE; return var_i0; } + +void wasm2c_tailcall_1024_w2c_test_f2(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_static_assert(sizeof(struct wasm_multi_ii) <= 1024); + wasm_static_assert(sizeof(u32) <= 1024); + w2c_test* instance = *instance_ptr; + u32 var_p0, var_p1; + { + struct wasm_multi_ii *tmp = tail_call_stack; + var_p0 = tmp->i0; + var_p1 = tmp->i1; + } + u32 var_i0; + var_i0 = 1u; + var_i0 = w2c_test_f0(instance, var_i0); + wasm_rt_memcpy(tail_call_stack, &var_i0, sizeof(var_i0)); + next->fn = NULL; +} ;;; STDOUT ;;) diff --git a/test/wasm2c/export-names.txt b/test/wasm2c/export-names.txt index a2ff7554d8..1fc90d09df 100644 --- a/test/wasm2c/export-names.txt +++ b/test/wasm2c/export-names.txt @@ -64,6 +64,21 @@ void w2c_test_0x0A(w2c_test*); /* export: '\E2\9D\A4\EF\B8\8F' */ void w2c_test_0xE20x9D0xA40xEF0xB80x8F(w2c_test*); +/* export for tail-call of '' */ +void wasm2c_tailcall_1024_w2c_test_(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +/* export for tail-call of '*\2F' */ +void wasm2c_tailcall_1024_w2c_test_0x2A0x2F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +/* export for tail-call of '\3F\3F\2F' */ +void wasm2c_tailcall_1024_w2c_test_0x3F0x3F0x2F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +/* export for tail-call of '\0A' */ +void wasm2c_tailcall_1024_w2c_test_0x0A(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +/* export for tail-call of '\E2\9D\A4\EF\B8\8F' */ +void wasm2c_tailcall_1024_w2c_test_0xE20x9D0xA40xEF0xB80x8F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + #ifdef __cplusplus } #endif @@ -116,6 +131,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -583,6 +605,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -599,9 +622,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -682,7 +705,14 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + static void w2c_test__0(w2c_test*); +static void wasm2c_tailcall_1024_w2c_test__0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); FUNC_TYPE_T(w2c_test_t0) = "\x36\xa9\xe7\xf1\xc9\x5b\x82\xff\xb9\x97\x43\xe0\xc5\xc4\xce\x95\xd8\x3c\x9a\x43\x0a\xac\x59\xf8\x4e\xf3\xcb\xfa\xb6\x14\x50\x68"; @@ -714,6 +744,31 @@ void w2c_test_0xE20x9D0xA40xEF0xB80x8F(w2c_test* instance) { w2c_test__0(instance); } +/* export for tail-call of '' */ +void wasm2c_tailcall_1024_w2c_test_(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test__0(instance_ptr, tail_call_stack, next); +} + +/* export for tail-call of '*\2F' */ +void wasm2c_tailcall_1024_w2c_test_0x2A0x2F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test__0(instance_ptr, tail_call_stack, next); +} + +/* export for tail-call of '\3F\3F\2F' */ +void wasm2c_tailcall_1024_w2c_test_0x3F0x3F0x2F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test__0(instance_ptr, tail_call_stack, next); +} + +/* export for tail-call of '\0A' */ +void wasm2c_tailcall_1024_w2c_test_0x0A(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test__0(instance_ptr, tail_call_stack, next); +} + +/* export for tail-call of '\E2\9D\A4\EF\B8\8F' */ +void wasm2c_tailcall_1024_w2c_test_0xE20x9D0xA40xEF0xB80x8F(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test__0(instance_ptr, tail_call_stack, next); +} + static void init_instance_import(w2c_test* instance, struct w2c_0x5Cmodule* w2c_0x5Cmodule_instance) { instance->w2c_0x5Cmodule_import0x200x2A0x2F = w2c_0x5Cmodule_import0x200x2A0x2F(w2c_0x5Cmodule_instance); } @@ -750,4 +805,9 @@ void w2c_test__0(w2c_test* instance) { FUNC_PROLOGUE; FUNC_EPILOGUE; } + +void wasm2c_tailcall_1024_w2c_test__0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + w2c_test* instance = *instance_ptr; + next->fn = NULL; +} ;;; STDOUT ;;) diff --git a/test/wasm2c/hello.txt b/test/wasm2c/hello.txt index 65216214e8..ac1c67118e 100644 --- a/test/wasm2c/hello.txt +++ b/test/wasm2c/hello.txt @@ -62,9 +62,11 @@ wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t res /* import: 'wasi_snapshot_preview1' 'fd_write' */ u32 w2c_wasi__snapshot__preview1_fd_write(struct w2c_wasi__snapshot__preview1*, u32, u32, u32, u32); +void wasm2c_tailcall_1024_w2c_wasi__snapshot__preview1_fd_write(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); /* import: 'wasi_snapshot_preview1' 'proc_exit' */ void w2c_wasi__snapshot__preview1_proc_exit(struct w2c_wasi__snapshot__preview1*, u32); +void wasm2c_tailcall_1024_w2c_wasi__snapshot__preview1_proc_exit(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); /* export: 'memory' */ wasm_rt_memory_t* w2c_test_memory(w2c_test* instance); @@ -72,6 +74,9 @@ wasm_rt_memory_t* w2c_test_memory(w2c_test* instance); /* export: '_start' */ void w2c_test_0x5Fstart(w2c_test*); +/* export for tail-call of '_start' */ +void wasm2c_tailcall_1024_w2c_test_0x5Fstart(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + #ifdef __cplusplus } #endif @@ -124,6 +129,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -591,6 +603,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -607,9 +620,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -690,7 +703,24 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + static void w2c_test_0x5Fstart_0(w2c_test*); +static void wasm2c_tailcall_1024_w2c_test_0x5Fstart_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +#ifndef wasm_multi_iiii +#define wasm_multi_iiii wasm_multi_iiii +struct wasm_multi_iiii { + u32 i0; + u32 i1; + u32 i2; + u32 i3; +}; +#endif /* wasm_multi_iiii */ FUNC_TYPE_T(w2c_test_t0) = "\xf6\x98\x1b\xc6\x10\xda\xb7\xb2\x63\x37\xcd\xdc\x72\xca\xe9\x50\x00\x13\xba\x10\x6c\xde\x87\x27\x10\xf8\x86\x2f\xe3\xdb\x94\xe4"; FUNC_TYPE_T(w2c_test_t1) = "\x89\x3a\x3d\x2c\x8f\x4d\x7f\x6d\x6c\x9d\x62\x67\x29\xaf\x3d\x44\x39\x8e\xc3\xf3\xe8\x51\xc1\x99\xb9\xdd\x9f\xd5\x3d\x1f\xd3\xe4"; @@ -710,7 +740,7 @@ static void init_data_instances(w2c_test *instance) { } static const wasm_elem_segment_expr_t elem_segment_exprs_w2c_test_e0[] = { - {w2c_test_t0, (wasm_rt_function_ptr_t)w2c_wasi__snapshot__preview1_fd_write, offsetof(w2c_test, w2c_wasi__snapshot__preview1_instance)}, + {w2c_test_t0, (wasm_rt_function_ptr_t)w2c_wasi__snapshot__preview1_fd_write, {wasm2c_tailcall_1024_w2c_wasi__snapshot__preview1_fd_write}, offsetof(w2c_test, w2c_wasi__snapshot__preview1_instance)}, }; static void init_tables(w2c_test* instance) { @@ -731,6 +761,21 @@ void w2c_test_0x5Fstart(w2c_test* instance) { w2c_test_0x5Fstart_0(instance); } +/* export for tail-call of '_start' */ +void wasm2c_tailcall_1024_w2c_test_0x5Fstart(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_test_0x5Fstart_0(instance_ptr, tail_call_stack, next); +} + +/* handler for missing tail-call on import: 'wasi_snapshot_preview1' 'fd_write' */ +__attribute__((weak)) void wasm2c_tailcall_1024_w2c_wasi__snapshot__preview1_fd_write(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + TRAP(UNHANDLED_TAIL_CALL); +} + +/* handler for missing tail-call on import: 'wasi_snapshot_preview1' 'proc_exit' */ +__attribute__((weak)) void wasm2c_tailcall_1024_w2c_wasi__snapshot__preview1_proc_exit(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + TRAP(UNHANDLED_TAIL_CALL); +} + static void init_instance_import(w2c_test* instance, struct w2c_wasi__snapshot__preview1* w2c_wasi__snapshot__preview1_instance) { instance->w2c_wasi__snapshot__preview1_instance = w2c_wasi__snapshot__preview1_instance; } @@ -800,4 +845,23 @@ void w2c_test_0x5Fstart_0(w2c_test* instance) { w2c_wasi__snapshot__preview1_proc_exit(instance->w2c_wasi__snapshot__preview1_instance, var_i0); FUNC_EPILOGUE; } + +void wasm2c_tailcall_1024_w2c_test_0x5Fstart_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + w2c_test* instance = *instance_ptr; + u32 var_i0, var_i1, var_i2, var_i3, var_i4; + var_i0 = 0u; + var_i1 = 8u; + i32_store(&instance->w2c_memory, (u64)(var_i0), var_i1); + var_i0 = 4u; + var_i1 = 14u; + i32_store(&instance->w2c_memory, (u64)(var_i0), var_i1); + var_i0 = 1u; + var_i1 = 0u; + var_i2 = 1u; + var_i3 = 0u; + var_i4 = 0u; + var_i0 = CALL_INDIRECT(instance->w2c_T0, u32 (*)(void*, u32, u32, u32, u32), w2c_test_t0, var_i4, instance->w2c_T0.data[var_i4].module_instance, var_i0, var_i1, var_i2, var_i3); + w2c_wasi__snapshot__preview1_proc_exit(instance->w2c_wasi__snapshot__preview1_instance, var_i0); + next->fn = NULL; +} ;;; STDOUT ;;) diff --git a/test/wasm2c/minimal.txt b/test/wasm2c/minimal.txt index 498d9472c0..315789bb3b 100644 --- a/test/wasm2c/minimal.txt +++ b/test/wasm2c/minimal.txt @@ -87,6 +87,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -554,6 +561,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -570,9 +578,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -653,6 +661,12 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + void wasm2c_test_instantiate(w2c_test* instance) { assert(wasm_rt_is_initialized()); } diff --git a/test/wasm2c/spec/tail-call/return_call.txt b/test/wasm2c/spec/tail-call/return_call.txt new file mode 100644 index 0000000000..6218eeac59 --- /dev/null +++ b/test/wasm2c/spec/tail-call/return_call.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/tail-call/return_call.wast +;;; ARGS*: --enable-tail-call +(;; STDOUT ;;; +31/31 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/spec/tail-call/return_call_indirect.txt b/test/wasm2c/spec/tail-call/return_call_indirect.txt new file mode 100644 index 0000000000..8cf46bd900 --- /dev/null +++ b/test/wasm2c/spec/tail-call/return_call_indirect.txt @@ -0,0 +1,6 @@ +;;; TOOL: run-spec-wasm2c +;;; STDIN_FILE: third_party/testsuite/proposals/tail-call/return_call_indirect.wast +;;; ARGS*: --enable-tail-call +(;; STDOUT ;;; +47/47 tests passed. +;;; STDOUT ;;) diff --git a/wasm2c/examples/callback/main.c b/wasm2c/examples/callback/main.c index 8c6f9db09a..4da7651232 100644 --- a/wasm2c/examples/callback/main.c +++ b/wasm2c/examples/callback/main.c @@ -26,7 +26,8 @@ int main(int argc, char** argv) { */ wasm_rt_func_type_t fn_type = wasm2c_callback_get_func_type(1, 0, WASM_RT_I32); - wasm_rt_funcref_t fn_ref = {fn_type, (wasm_rt_function_ptr_t)print, &inst}; + wasm_rt_funcref_t fn_ref = {fn_type, (wasm_rt_function_ptr_t)print, NULL, + &inst}; w2c_callback_set_print_function(&inst, fn_ref); /* "say_hello" uses the previously installed callback. */ diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c index 819a4ebf1e..c8366396f4 100644 --- a/wasm2c/examples/fac/fac.c +++ b/wasm2c/examples/fac/fac.c @@ -45,6 +45,13 @@ static inline bool func_types_eq(const wasm_rt_func_type_t a, TRAP(CALL_INDIRECT), \ ((t)table.data[x].func)(__VA_ARGS__)) +#define RETURN_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func_tailcallee.fn && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT), \ + (next->fn = table.data[x].func_tailcallee.fn, \ + *instance_ptr = table.data[x].module_instance)) + #ifdef SUPPORT_MEMORY64 #define RANGE_CHECK(mem, offset, len) \ do { \ @@ -512,6 +519,7 @@ static inline void memory_init(wasm_rt_memory_t* dest, typedef struct { wasm_rt_func_type_t type; wasm_rt_function_ptr_t func; + wasm_rt_tailcallee_t func_tailcallee; size_t module_offset; } wasm_elem_segment_expr_t; @@ -528,9 +536,9 @@ static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, TRAP(OOB); for (u32 i = 0; i < n; i++) { const wasm_elem_segment_expr_t* src_expr = &src[src_addr + i]; - dest->data[dest_addr + i] = - (wasm_rt_funcref_t){src_expr->type, src_expr->func, - (char*)module_instance + src_expr->module_offset}; + dest->data[dest_addr + i] = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; } } @@ -611,7 +619,14 @@ DEFINE_TABLE_FILL(externref) #define FUNC_TYPE_T(x) static const char x[] #endif +#if (__STDC_VERSION__ >= 201112L) || defined(_Static_assert) +#define wasm_static_assert(X) _Static_assert(X, "assertion failure") +#else +#define wasm_static_assert assert +#endif + static u32 w2c_fac_fac_0(w2c_fac*, u32); +static void wasm2c_tailcall_1024_w2c_fac_fac_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); FUNC_TYPE_T(w2c_fac_t0) = "\x07\x80\x96\x7a\x42\xf7\x3e\xe6\x70\x5c\x2f\xac\x83\xf5\x67\xd2\xa2\xa0\x69\x41\x5f\xf8\xe7\x96\x7f\x23\xab\x00\x03\x5f\x4a\x3c"; @@ -620,6 +635,11 @@ u32 w2c_fac_fac(w2c_fac* instance, u32 var_p0) { return w2c_fac_fac_0(instance, var_p0); } +/* export for tail-call of 'fac' */ +void wasm2c_tailcall_1024_w2c_fac_fac(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm2c_tailcall_1024_w2c_fac_fac_0(instance_ptr, tail_call_stack, next); +} + void wasm2c_fac_instantiate(w2c_fac* instance) { assert(wasm_rt_is_initialized()); } @@ -661,3 +681,25 @@ u32 w2c_fac_fac_0(w2c_fac* instance, u32 var_p0) { FUNC_EPILOGUE; return var_i0; } + +void wasm2c_tailcall_1024_w2c_fac_fac_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_static_assert(sizeof(u32) <= 1024); + w2c_fac* instance = *instance_ptr; + u32 var_p0 = *(u32*)tail_call_stack; + u32 var_i0, var_i1, var_i2; + var_i0 = var_p0; + var_i1 = 0u; + var_i0 = var_i0 == var_i1; + if (var_i0) { + var_i0 = 1u; + } else { + var_i0 = var_p0; + var_i1 = var_p0; + var_i2 = 1u; + var_i1 -= var_i2; + var_i1 = w2c_fac_fac_0(instance, var_i1); + var_i0 *= var_i1; + } + wasm_rt_memcpy(tail_call_stack, &var_i0, sizeof(var_i0)); + next->fn = NULL; +} diff --git a/wasm2c/examples/fac/fac.h b/wasm2c/examples/fac/fac.h index ac0cd5a976..229e5d3a92 100644 --- a/wasm2c/examples/fac/fac.h +++ b/wasm2c/examples/fac/fac.h @@ -35,6 +35,9 @@ wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t resu /* export: 'fac' */ u32 w2c_fac_fac(w2c_fac*, u32); +/* export for tail-call of 'fac' */ +void wasm2c_tailcall_1024_w2c_fac_fac(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + #ifdef __cplusplus } #endif diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c index 542230871a..f729bc5293 100644 --- a/wasm2c/wasm-rt-impl.c +++ b/wasm2c/wasm-rt-impl.c @@ -414,11 +414,13 @@ const char* wasm_rt_strerror(wasm_rt_trap_t trap) { case WASM_RT_TRAP_UNREACHABLE: return "Unreachable instruction executed"; case WASM_RT_TRAP_CALL_INDIRECT: - return "Invalid call_indirect"; + return "Invalid call_indirect or return_call_indirect"; case WASM_RT_TRAP_UNCAUGHT_EXCEPTION: return "Uncaught exception"; case WASM_RT_TRAP_UNALIGNED: return "Unaligned atomic memory access"; + case WASM_RT_TRAP_UNHANDLED_TAIL_CALL: + return "Host function or imported module lacks support for tail calls"; } return "invalid trap code"; } diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index bdeecd569b..79b4c16563 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -218,7 +218,10 @@ typedef enum { WASM_RT_TRAP_UNREACHABLE, /** Unreachable instruction executed. */ WASM_RT_TRAP_CALL_INDIRECT, /** Invalid call_indirect, for any reason. */ WASM_RT_TRAP_UNCAUGHT_EXCEPTION, /* Exception thrown and not caught. */ - WASM_RT_TRAP_UNALIGNED, /** Unaligned atomic instruction executed. */ + WASM_RT_TRAP_UNALIGNED, /** Unaligned atomic instruction executed. */ + WASM_RT_TRAP_UNHANDLED_TAIL_CALL, /** Tail call made to host function or + wasm2c-generated module that doesn't + provide tail-call handler */ #if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS WASM_RT_TRAP_EXHAUSTION = WASM_RT_TRAP_OOB, #else @@ -245,6 +248,15 @@ typedef enum { */ typedef void (*wasm_rt_function_ptr_t)(void); +/** + * A pointer to a "tail-callee" function, called by a tail-call + * trampoline or by another tail-callee function. (The definition uses a + * single-member struct to allow a recursive definition.) + */ +typedef struct wasm_rt_tailcallee_t { + void (*fn)(void**, void*, struct wasm_rt_tailcallee_t*); +} wasm_rt_tailcallee_t; + /** * The type of a function (an arbitrary number of param and result types). * This is represented as an opaque 256-bit ID. @@ -259,6 +271,8 @@ typedef struct { /** The function. The embedder must know the actual C signature of the * function and cast to it before calling. */ wasm_rt_function_ptr_t func; + /** An alternate version of the function to be used when tail-called. */ + wasm_rt_tailcallee_t func_tailcallee; /** A function instance is a closure of the function over an instance * of the originating module. The module_instance element will be passed into * the function at runtime. */ @@ -266,7 +280,10 @@ typedef struct { } wasm_rt_funcref_t; /** Default (null) value of a funcref */ -static const wasm_rt_funcref_t wasm_rt_funcref_null_value = {NULL, NULL, NULL}; +static const wasm_rt_funcref_t wasm_rt_funcref_null_value = {NULL, + NULL, + {}, + NULL}; /** The type of an external reference (opaque to WebAssembly). */ typedef void* wasm_rt_externref_t;