diff options
author | Soni L. <EnderMoneyMod@gmail.com> | 2024-09-23 17:01:18 -0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-23 13:01:18 -0700 |
commit | 0a2a59fae573c2d793501720a2faf986118ee4f4 (patch) | |
tree | a95fd371a30945944121f1ae816491b1daf1031e | |
parent | b287470b53ef074332c4b0056cfc05486a85c4a7 (diff) | |
download | wabt-0a2a59fae573c2d793501720a2faf986118ee4f4.tar.gz wabt-0a2a59fae573c2d793501720a2faf986118ee4f4.tar.bz2 wabt-0a2a59fae573c2d793501720a2faf986118ee4f4.zip |
wasm2c: Use wrappers for function references (#2465)
Clang 17(?) tightened UBSAN checks, so that you now get this:
```
- test/wasm2c/spec/call_indirect.txt
expected error code 0, got 1.
STDERR MISMATCH:
--- expected
+++ actual
@@ -0,0 +1,3 @@
+out/test/wasm2c/spec/call_indirect/call_indirect.0.c:2144:12: runtime error: call to function w2c_call__indirect__0__wasm_f0 through pointer to incorrect function type 'unsigned int (*)(void *)'
+/home/runner/work/wabt/wabt/out/test/wasm2c/spec/call_indirect/call_indirect.0.c:1925: note: w2c_call__indirect__0__wasm_f0 defined here
+SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior out/test/wasm2c/spec/call_indirect/call_indirect.0.c:2144:12
STDOUT MISMATCH:
--- expected
+++ actual
@@ -1 +0,0 @@
-134/134 tests passed.
```
This happens because emitted functions use a typed module instance,
while function references use a `void*` instance. It is UB in C to call
the former with the latter, so clang is correct here.
We had to pick one of two ways to fix this: either emit `void*` wrapper
functions that do the appropriate downcasting for any module functions
that go into a table (potentially including imported functions), or the
approach that takes significantly less effort of changing everything to
`void*` and downcasting internally. ~~We obviously chose the latter.~~
We eventually started emitting wrapper functions.
-rw-r--r-- | .github/workflows/build.yml | 2 | ||||
-rw-r--r-- | include/wabt/ir.h | 7 | ||||
-rw-r--r-- | src/binary-reader-ir.cc | 1 | ||||
-rw-r--r-- | src/c-writer.cc | 70 | ||||
-rw-r--r-- | test/wasm2c/check-imports.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/hello.txt | 6 | ||||
-rw-r--r-- | test/wasm2c/tail-calls.txt | 6 |
7 files changed, 88 insertions, 10 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92b5a088..99ffb6eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,7 +99,7 @@ jobs: sanitize: name: sanitize - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 env: USE_NINJA: "1" CC: "clang" diff --git a/include/wabt/ir.h b/include/wabt/ir.h index 49b7e2b0..80e1c2a8 100644 --- a/include/wabt/ir.h +++ b/include/wabt/ir.h @@ -25,6 +25,7 @@ #include <string_view> #include <type_traits> #include <vector> +#include <set> #include "wabt/binding-hash.h" #include "wabt/common.h" @@ -1280,6 +1281,12 @@ struct Module { bool exceptions = false; bool threads = false; } features_used; + + // The BinaryReaderIR tracks function references used by the module, whether + // in element segment initializers, global initializers, or functions. wasm2c + // needs to emit wrappers for any functions that might get used as function + // references, and uses this information to limit its output. + std::set<Index> used_func_refs; }; enum class ScriptModuleType { diff --git a/src/binary-reader-ir.cc b/src/binary-reader-ir.cc index 8c79cd52..0b3e6f4d 100644 --- a/src/binary-reader-ir.cc +++ b/src/binary-reader-ir.cc @@ -1119,6 +1119,7 @@ Result BinaryReaderIR::OnTableFillExpr(Index table_index) { } Result BinaryReaderIR::OnRefFuncExpr(Index func_index) { + module_->used_func_refs.insert(func_index); return AppendExpr( std::make_unique<RefFuncExpr>(Var(func_index, GetLocation()))); } diff --git a/src/c-writer.cc b/src/c-writer.cc index cbffa1d4..9704f0e2 100644 --- a/src/c-writer.cc +++ b/src/c-writer.cc @@ -103,6 +103,11 @@ struct ExternalRef : GlobalName { using GlobalName::GlobalName; }; +struct WrapperRef : GlobalName { + explicit WrapperRef(const std::string& name) + : GlobalName(ModuleFieldType::Func, name) {} +}; + struct TailCallRef : GlobalName { explicit TailCallRef(const std::string& name) : GlobalName(ModuleFieldType::Func, name) {} @@ -342,6 +347,7 @@ class CWriter { void Write(const GlobalName&); void Write(const TagSymbol&); void Write(const ExternalRef&); + void Write(const WrapperRef&); void Write(const TailCallRef&); void Write(const ExternalInstancePtr&); void Write(const ExternalInstanceRef&); @@ -379,6 +385,7 @@ class CWriter { void WriteImportFuncDeclaration(const FuncDeclaration&, const std::string& module_name, const std::string&); + void WriteFuncRefWrapper(const Func* func); void WriteCallIndirectFuncDeclaration(const FuncDeclaration&, const std::string&); void ComputeSimdScope(); @@ -402,6 +409,7 @@ class CWriter { void WriteDataInitializers(); void WriteElemInitializerDecls(); void WriteElemInitializers(); + void WriteFuncRefWrappers(); void WriteElemTableInit(bool, const ElemSegment*, const Table*); bool IsSingleUnsharedMemory(); void InstallSegueBase(Memory* memory, bool save_old_value); @@ -544,6 +552,7 @@ static constexpr char kParamSuffix = static constexpr char kLabelSuffix = kParamSuffix + 1; static constexpr char kGlobalSymbolPrefix[] = "w2c_"; +static constexpr char kWrapperSymbolPrefix[] = "wrap_"; static constexpr char kLocalSymbolPrefix[] = "var_"; static constexpr char kAdminSymbolPrefix[] = "wasm2c_"; static constexpr char kTailCallSymbolPrefix[] = "wasm_tailcall_"; @@ -1125,6 +1134,10 @@ void CWriter::Write(const ExternalRef& name) { } } +void CWriter::Write(const WrapperRef& name) { + Write(kWrapperSymbolPrefix, GlobalName(name)); +} + void CWriter::Write(const TailCallRef& name) { Write(GetTailCallRef(name.name)); } @@ -1446,8 +1459,7 @@ void CWriter::WriteInitExprTerminal(const Expr* expr) { const FuncType* func_type = module_->GetFuncType(decl.type_var); Write("(wasm_rt_funcref_t){", FuncTypeExpr(func_type), ", ", - "(wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", {"); + "(wasm_rt_function_ptr_t)", WrapperRef(func->name), ", {"); if (options_.features.tail_call_enabled() && (IsImport(func->name) || func->features_used.tailcall)) { Write(TailCallRef(func->name)); @@ -2286,6 +2298,53 @@ void CWriter::WriteElemInitializerDecls() { } } +void CWriter::WriteFuncRefWrapper(const Func* func) { + Write("static ", func->decl.sig.result_types, " ", WrapperRef(func->name)); + Write("(void *instance"); + if (func->GetNumParams() != 0) { + Indent(4); + for (Index i = 0; i < func->GetNumParams(); ++i) { + Write(", "); + if (i != 0 && (i % 8) == 0) { + Write(Newline()); + } + Write(func->GetParamType(i), " ", kLocalSymbolPrefix); + Writef("%d", i); + } + Dedent(4); + } + Write(") ", OpenBrace()); + Write("return "); + Write(ExternalRef(ModuleFieldType::Func, func->name)); + Write("(instance"); + if (func->GetNumParams() != 0) { + Indent(4); + for (Index i = 0; i < func->GetNumParams(); ++i) { + Write(", "); + if (i != 0 && (i % 8) == 0) { + Write(Newline()); + } + Write(kLocalSymbolPrefix); + Writef("%d", i); + } + Dedent(4); + } + Write(");", Newline(), CloseBrace(), Newline()); +} + +void CWriter::WriteFuncRefWrappers() { + std::set<std::string> unique_func_wrappers; + + for (Index index : module_->used_func_refs) { + assert(index < module_->funcs.size()); + const Func* func = module_->funcs[index]; + if (unique_func_wrappers.count(func->name) == 0) { + WriteFuncRefWrapper(func); + unique_func_wrappers.insert(func->name); + } + } +} + void CWriter::WriteElemInitializers() { if (module_->tables.empty()) { return; @@ -2315,8 +2374,7 @@ void CWriter::WriteElemInitializers() { const Func* func = module_->GetFunc(cast<RefFuncExpr>(&expr)->var); const FuncType* func_type = module_->GetFuncType(func->decl.type_var); Write("{RefFunc, ", FuncTypeExpr(func_type), - ", (wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", {"); + ", (wasm_rt_function_ptr_t)", WrapperRef(func->name), ", {"); if (options_.features.tail_call_enabled() && (IsImport(func->name) || func->features_used.tailcall)) { Write(TailCallRef(func->name)); @@ -3740,8 +3798,7 @@ void CWriter::Write(const ExprList& exprs) { const FuncType* func_type = module_->GetFuncType(decl.type_var); Write(StackVar(0), " = (wasm_rt_funcref_t){", FuncTypeExpr(func_type), - ", (wasm_rt_function_ptr_t)", - ExternalRef(ModuleFieldType::Func, func->name), ", {"); + ", (wasm_rt_function_ptr_t)", WrapperRef(func->name), ", {"); if (options_.features.tail_call_enabled() && (IsImport(func->name) || func->features_used.tailcall)) { Write(TailCallRef(func->name)); @@ -5858,6 +5915,7 @@ void CWriter::WriteCSource() { WriteMultiCTop(); WriteFuncTypes(); WriteTags(); + WriteFuncRefWrappers(); WriteGlobalInitializers(); WriteDataInitializers(); WriteElemInitializers(); diff --git a/test/wasm2c/check-imports.txt b/test/wasm2c/check-imports.txt index f2fe508e..0947a201 100644 --- a/test/wasm2c/check-imports.txt +++ b/test/wasm2c/check-imports.txt @@ -802,11 +802,15 @@ FUNC_TYPE_T(w2c_test_t0) = "\x07\x80\x96\x7a\x42\xf7\x3e\xe6\x70\x5c\x2f\xac\x83 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"; FUNC_TYPE_T(w2c_test_t2) = "\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"; +static u32 wrap_w2c_test_f1(void *instance) { + return w2c_test_f1(instance); +} + static void init_memories(w2c_test* instance) { } static const wasm_elem_segment_expr_t elem_segment_exprs_w2c_test_e0[] = { - {RefFunc, w2c_test_t1, (wasm_rt_function_ptr_t)w2c_test_f1, {NULL}, 0}, + {RefFunc, w2c_test_t1, (wasm_rt_function_ptr_t)wrap_w2c_test_f1, {NULL}, 0}, }; static void init_tables(w2c_test* instance) { diff --git a/test/wasm2c/hello.txt b/test/wasm2c/hello.txt index 2a0d79f0..918efe8c 100644 --- a/test/wasm2c/hello.txt +++ b/test/wasm2c/hello.txt @@ -808,6 +808,10 @@ FUNC_TYPE_T(w2c_test_t0) = "\xf6\x98\x1b\xc6\x10\xda\xb7\xb2\x63\x37\xcd\xdc\x72 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"; FUNC_TYPE_T(w2c_test_t2) = "\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"; +static u32 wrap_w2c_wasi__snapshot__preview1_fd_write(void *instance, u32 var_0, u32 var_1, u32 var_2, u32 var_3) { + return w2c_wasi__snapshot__preview1_fd_write(instance, var_0, var_1, var_2, var_3); +} + static const u8 data_segment_data_w2c_test_d0[] = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x0a, @@ -822,7 +826,7 @@ static void init_data_instances(w2c_test *instance) { } static const wasm_elem_segment_expr_t elem_segment_exprs_w2c_test_e0[] = { - {RefFunc, w2c_test_t0, (wasm_rt_function_ptr_t)w2c_wasi__snapshot__preview1_fd_write, {NULL}, offsetof(w2c_test, w2c_wasi__snapshot__preview1_instance)}, + {RefFunc, w2c_test_t0, (wasm_rt_function_ptr_t)wrap_w2c_wasi__snapshot__preview1_fd_write, {NULL}, offsetof(w2c_test, w2c_wasi__snapshot__preview1_instance)}, }; static void init_tables(w2c_test* instance) { diff --git a/test/wasm2c/tail-calls.txt b/test/wasm2c/tail-calls.txt index b27d7417..bbc02c07 100644 --- a/test/wasm2c/tail-calls.txt +++ b/test/wasm2c/tail-calls.txt @@ -811,8 +811,12 @@ FUNC_TYPE_T(w2c_test_i32_f32) = "\x98\x89\x5c\xbd\x28\xfd\x0e\x4d\xc5\xdc\x68\x2 FUNC_TYPE_T(w2c_test_t1) = "\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"; FUNC_TYPE_T(w2c_test_t2) = "\xe5\x11\x86\xc7\x24\xdb\x44\x80\xbe\xd1\xe0\x89\xbc\xc0\x20\xea\xfb\x1c\x9a\x27\xa5\xc3\xdb\xca\x5d\xb0\x05\x0f\x7c\x03\x74\x0a"; +static void wrap_w2c_spectest_print_i32_f32(void *instance, u32 var_0, f32 var_1) { + return w2c_spectest_print_i32_f32(instance, var_0, var_1); +} + static const wasm_elem_segment_expr_t elem_segment_exprs_w2c_test_e0[] = { - {RefFunc, w2c_test_i32_f32, (wasm_rt_function_ptr_t)w2c_spectest_print_i32_f32, {wasm_tailcall_w2c_spectest_print_i32_f32}, offsetof(w2c_test, w2c_spectest_instance)}, + {RefFunc, w2c_test_i32_f32, (wasm_rt_function_ptr_t)wrap_w2c_spectest_print_i32_f32, {wasm_tailcall_w2c_spectest_print_i32_f32}, offsetof(w2c_test, w2c_spectest_instance)}, }; static void init_tables(w2c_test* instance) { |