From cd82e884511b23421c498b5d5e33d7299971863c Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Thu, 14 May 2026 20:46:44 +0000 Subject: [PATCH 1/7] Import various wasm2c test and support files from wabt. These files are unmodified. Any necessary modifications will be included in follow-up commits. --- .../wasm2c_atomicops_source_declarations.cc | 454 ++++++ src/prebuilt/wasm2c_header_bottom.cc | 7 + src/prebuilt/wasm2c_header_top.cc | 37 + .../wasm2c_simd_source_declarations.cc | 672 ++++++++ src/prebuilt/wasm2c_source_declarations.cc | 1357 +++++++++++++++++ src/prebuilt/wasm2c_source_includes.cc | 33 + src/templates/wasm2c.bottom.h | 3 + src/templates/wasm2c.declarations.c | 725 +++++++++ src/templates/wasm2c.includes.c | 16 + src/templates/wasm2c.top.h | 19 + src/templates/wasm2c_atomicops.declarations.c | 237 +++ src/templates/wasm2c_simd.declarations.c | 342 +++++ test/wasm2c/add.txt | 835 ++++++++++ test/wasm2c/address-overflow.txt | 12 + test/wasm2c/bad-enable-feature.txt | 6 + test/wasm2c/check-imports.txt | 939 ++++++++++++ test/wasm2c/duplicate-names.txt | 11 + test/wasm2c/export-names.txt | 942 ++++++++++++ test/wasm2c/hello.txt | 961 ++++++++++++ test/wasm2c/minimal.txt | 800 ++++++++++ test/wasm2c/tail-calls.txt | 1051 +++++++++++++ wasm2c/.gitignore | 17 + wasm2c/README.md | 722 +++++++++ wasm2c/benchmarks/dhrystone/.gitignore | 5 + wasm2c/benchmarks/dhrystone/dhrystone.wasm | Bin 0 -> 33999 bytes wasm2c/benchmarks/dhrystone/main.c | 265 ++++ wasm2c/benchmarks/dhrystone/src/README.md | 23 + wasm2c/benchmarks/dhrystone/src/dhry.h | 306 ++++ wasm2c/benchmarks/dhrystone/src/dhry_1.c | 485 ++++++ wasm2c/benchmarks/dhrystone/src/dhry_2.c | 187 +++ wasm2c/examples/callback/callback.wat | 19 + wasm2c/examples/callback/main.c | 41 + wasm2c/examples/fac/fac.c | 796 ++++++++++ wasm2c/examples/fac/fac.h | 42 + wasm2c/examples/fac/fac.wat | 8 + wasm2c/examples/fac/main.c | 39 + wasm2c/examples/rot13/main.c | 101 ++ wasm2c/examples/rot13/rot13.wat | 56 + wasm2c/examples/threads/sample.wat | 6 + wasm2c/examples/threads/threads.c | 105 ++ wasm2c/wasm-rt-exceptions-impl.c | 73 + wasm2c/wasm-rt-exceptions.h | 140 ++ wasm2c/wasm-rt-impl-tableops.inc | 87 ++ wasm2c/wasm-rt-impl.c | 401 +++++ wasm2c/wasm-rt-impl.h | 68 + wasm2c/wasm-rt-mem-impl-helper.inc | 197 +++ wasm2c/wasm-rt-mem-impl.c | 161 ++ wasm2c/wasm-rt.h | 724 +++++++++ 48 files changed, 14533 insertions(+) create mode 100644 src/prebuilt/wasm2c_atomicops_source_declarations.cc create mode 100644 src/prebuilt/wasm2c_header_bottom.cc create mode 100644 src/prebuilt/wasm2c_header_top.cc create mode 100644 src/prebuilt/wasm2c_simd_source_declarations.cc create mode 100644 src/prebuilt/wasm2c_source_declarations.cc create mode 100644 src/prebuilt/wasm2c_source_includes.cc create mode 100644 src/templates/wasm2c.bottom.h create mode 100644 src/templates/wasm2c.declarations.c create mode 100644 src/templates/wasm2c.includes.c create mode 100644 src/templates/wasm2c.top.h create mode 100644 src/templates/wasm2c_atomicops.declarations.c create mode 100644 src/templates/wasm2c_simd.declarations.c create mode 100644 test/wasm2c/add.txt create mode 100644 test/wasm2c/address-overflow.txt create mode 100644 test/wasm2c/bad-enable-feature.txt create mode 100644 test/wasm2c/check-imports.txt create mode 100644 test/wasm2c/duplicate-names.txt create mode 100644 test/wasm2c/export-names.txt create mode 100644 test/wasm2c/hello.txt create mode 100644 test/wasm2c/minimal.txt create mode 100644 test/wasm2c/tail-calls.txt create mode 100644 wasm2c/.gitignore create mode 100644 wasm2c/README.md create mode 100644 wasm2c/benchmarks/dhrystone/.gitignore create mode 100755 wasm2c/benchmarks/dhrystone/dhrystone.wasm create mode 100644 wasm2c/benchmarks/dhrystone/main.c create mode 100644 wasm2c/benchmarks/dhrystone/src/README.md create mode 100644 wasm2c/benchmarks/dhrystone/src/dhry.h create mode 100644 wasm2c/benchmarks/dhrystone/src/dhry_1.c create mode 100644 wasm2c/benchmarks/dhrystone/src/dhry_2.c create mode 100644 wasm2c/examples/callback/callback.wat create mode 100644 wasm2c/examples/callback/main.c create mode 100644 wasm2c/examples/fac/fac.c create mode 100644 wasm2c/examples/fac/fac.h create mode 100644 wasm2c/examples/fac/fac.wat create mode 100644 wasm2c/examples/fac/main.c create mode 100644 wasm2c/examples/rot13/main.c create mode 100644 wasm2c/examples/rot13/rot13.wat create mode 100644 wasm2c/examples/threads/sample.wat create mode 100644 wasm2c/examples/threads/threads.c create mode 100644 wasm2c/wasm-rt-exceptions-impl.c create mode 100644 wasm2c/wasm-rt-exceptions.h create mode 100644 wasm2c/wasm-rt-impl-tableops.inc create mode 100644 wasm2c/wasm-rt-impl.c create mode 100644 wasm2c/wasm-rt-impl.h create mode 100644 wasm2c/wasm-rt-mem-impl-helper.inc create mode 100644 wasm2c/wasm-rt-mem-impl.c create mode 100644 wasm2c/wasm-rt.h diff --git a/src/prebuilt/wasm2c_atomicops_source_declarations.cc b/src/prebuilt/wasm2c_atomicops_source_declarations.cc new file mode 100644 index 00000000000..dc02591cb2a --- /dev/null +++ b/src/prebuilt/wasm2c_atomicops_source_declarations.cc @@ -0,0 +1,454 @@ +const char* s_atomicops_source_declarations = R"w2c_template(#include +)w2c_template" +R"w2c_template( +#ifndef WASM_RT_C11_AVAILABLE +)w2c_template" +R"w2c_template(#error "C11 is required for Wasm threads and shared memory support" +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#define ATOMIC_ALIGNMENT_CHECK(addr, t1) \ +)w2c_template" +R"w2c_template( if (UNLIKELY(addr % sizeof(t1))) { \ +)w2c_template" +R"w2c_template( TRAP(UNALIGNED); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +#define DEFINE_SHARED_LOAD(name, t1, t2, t3, force_read) \ +)w2c_template" +R"w2c_template( static inline t3 name##_unchecked(wasm_rt_shared_memory_t* mem, u64 addr) { \ +)w2c_template" +R"w2c_template( t1 result; \ +)w2c_template" +R"w2c_template( result = atomic_load_explicit( \ +)w2c_template" +R"w2c_template( (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), \ +)w2c_template" +R"w2c_template( memory_order_relaxed); \ +)w2c_template" +R"w2c_template( force_read(result); \ +)w2c_template" +R"w2c_template( return (t3)(t2)result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS0(name, _shared_, t1, return, t3) +)w2c_template" +R"w2c_template( +DEFINE_SHARED_LOAD(i32_load_shared, u32, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load_shared, u64, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(f32_load_shared, f32, f32, f32, FORCE_READ_FLOAT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(f64_load_shared, f64, f64, f64, FORCE_READ_FLOAT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i32_load8_s_shared, s8, s32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load8_s_shared, s8, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i32_load8_u_shared, u8, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load8_u_shared, u8, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i32_load16_s_shared, s16, s32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load16_s_shared, s16, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i32_load16_u_shared, u16, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load16_u_shared, u16, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load32_s_shared, s32, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_SHARED_LOAD(i64_load32_u_shared, u32, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template( +#define DEFINE_SHARED_STORE(name, t1, t2) \ +)w2c_template" +R"w2c_template( static inline void name##_unchecked(wasm_rt_shared_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t2 value) { \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( atomic_store_explicit( \ +)w2c_template" +R"w2c_template( (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped, \ +)w2c_template" +R"w2c_template( memory_order_relaxed); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _shared_, t1, , void, t2) +)w2c_template" +R"w2c_template( +DEFINE_SHARED_STORE(i32_store_shared, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i64_store_shared, u64, u64) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(f32_store_shared, f32, f32) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(f64_store_shared, f64, f64) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i32_store8_shared, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i32_store16_shared, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i64_store8_shared, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i64_store16_shared, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_SHARED_STORE(i64_store32_shared, u32, u64) +)w2c_template" +R"w2c_template( +#define DEFINE_ATOMIC_LOAD(name, t1, t2, t3, force_read) \ +)w2c_template" +R"w2c_template( static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 result; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&result, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ +)w2c_template" +R"w2c_template( force_read(result); \ +)w2c_template" +R"w2c_template( return (t3)(t2)result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS0(name, _, t1, return, t3) \ +)w2c_template" +R"w2c_template( static inline t3 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 result; \ +)w2c_template" +R"w2c_template( result = \ +)w2c_template" +R"w2c_template( atomic_load((_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1))); \ +)w2c_template" +R"w2c_template( force_read(result); \ +)w2c_template" +R"w2c_template( return (t3)(t2)result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS0(name##_shared, _shared_, t1, return, t3) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_LOAD(i32_atomic_load, u32, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i64_atomic_load, u64, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i32_atomic_load8_u, u8, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i64_atomic_load8_u, u8, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i32_atomic_load16_u, u16, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i64_atomic_load16_u, u16, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_LOAD(i64_atomic_load32_u, u32, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template( +#define DEFINE_ATOMIC_STORE(name, t1, t2) \ +)w2c_template" +R"w2c_template( static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &wrapped, sizeof(t1)); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t1, , void, t2) \ +)w2c_template" +R"w2c_template( static inline void name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr, t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( atomic_store((_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), \ +)w2c_template" +R"w2c_template( wrapped); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name##_shared, _shared_, t1, , void, t2) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_STORE(i32_atomic_store, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i64_atomic_store, u64, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i32_atomic_store8, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i32_atomic_store16, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i64_atomic_store8, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i64_atomic_store16, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_STORE(i64_atomic_store32, u32, u64) +)w2c_template" +R"w2c_template( +#define DEFINE_ATOMIC_RMW(name, opname, op, t1, t2) \ +)w2c_template" +R"w2c_template( static inline t2 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( t1 ret; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ +)w2c_template" +R"w2c_template( ret = ret op wrapped; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &ret, sizeof(t1)); \ +)w2c_template" +R"w2c_template( return (t2)ret; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t1, return, t2, t2) \ +)w2c_template" +R"w2c_template( static inline t2 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr, t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( t1 ret = atomic_##opname( \ +)w2c_template" +R"w2c_template( (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped); \ +)w2c_template" +R"w2c_template( return (t2)ret; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name##_shared, _shared_, t1, return, t2, t2) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_add_u, fetch_add, +, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw16_add_u, fetch_add, +, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw_add, fetch_add, +, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw8_add_u, fetch_add, +, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw16_add_u, fetch_add, +, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw32_add_u, fetch_add, +, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw_add, fetch_add, +, u64, u64) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_sub_u, fetch_sub, -, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw16_sub_u, fetch_sub, -, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw_sub, fetch_sub, -, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw8_sub_u, fetch_sub, -, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw16_sub_u, fetch_sub, -, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw32_sub_u, fetch_sub, -, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw_sub, fetch_sub, -, u64, u64) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_and_u, fetch_and, &, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw16_and_u, fetch_and, &, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw_and, fetch_and, &, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw8_and_u, fetch_and, &, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw16_and_u, fetch_and, &, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw32_and_u, fetch_and, &, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw_and, fetch_and, &, u64, u64) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_or_u, fetch_or, |, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw16_or_u, fetch_or, |, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw_or, fetch_or, |, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw8_or_u, fetch_or, |, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw16_or_u, fetch_or, |, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw32_or_u, fetch_or, |, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw_or, fetch_or, |, u64, u64) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_xor_u, fetch_xor, ^, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw16_xor_u, fetch_xor, ^, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i32_atomic_rmw_xor, fetch_xor, ^, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw8_xor_u, fetch_xor, ^, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw16_xor_u, fetch_xor, ^, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw32_xor_u, fetch_xor, ^, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_RMW(i64_atomic_rmw_xor, fetch_xor, ^, u64, u64) +)w2c_template" +R"w2c_template( +#define DEFINE_ATOMIC_XCHG(name, opname, t1, t2) \ +)w2c_template" +R"w2c_template( static inline t2 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( t1 ret; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &wrapped, sizeof(t1)); \ +)w2c_template" +R"w2c_template( return (t2)ret; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t1, return, t2, t2) \ +)w2c_template" +R"w2c_template( static inline t2 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr, t2 value) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t1); \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( t1 ret = atomic_##opname( \ +)w2c_template" +R"w2c_template( (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped); \ +)w2c_template" +R"w2c_template( return (t2)ret; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name##_shared, _shared_, t1, return, t2, t2) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_XCHG(i32_atomic_rmw8_xchg_u, exchange, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i32_atomic_rmw16_xchg_u, exchange, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i32_atomic_rmw_xchg, exchange, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i64_atomic_rmw8_xchg_u, exchange, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i64_atomic_rmw16_xchg_u, exchange, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i64_atomic_rmw32_xchg_u, exchange, u32, u64) +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_XCHG(i64_atomic_rmw_xchg, exchange, u64, u64) +)w2c_template" +R"w2c_template( +#define DEFINE_ATOMIC_CMP_XCHG(name, t1, t2) \ +)w2c_template" +R"w2c_template( static inline t1 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t1 expected, t1 replacement) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t2); \ +)w2c_template" +R"w2c_template( t2 expected_wrapped = (t2)expected; \ +)w2c_template" +R"w2c_template( t2 replacement_wrapped = (t2)replacement; \ +)w2c_template" +R"w2c_template( t2 ret; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t2)), sizeof(t2)); \ +)w2c_template" +R"w2c_template( if (ret == expected_wrapped) { \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t2)), &replacement_wrapped, \ +)w2c_template" +R"w2c_template( sizeof(t2)); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( return (t1)expected_wrapped; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS2(name, _, t2, return, t1, t1, t1) \ +)w2c_template" +R"w2c_template( static inline t1 name##_shared_unchecked( \ +)w2c_template" +R"w2c_template( wasm_rt_shared_memory_t* mem, u64 addr, t1 expected, t1 replacement) { \ +)w2c_template" +R"w2c_template( ATOMIC_ALIGNMENT_CHECK(addr, t2); \ +)w2c_template" +R"w2c_template( t2 expected_wrapped = (t2)expected; \ +)w2c_template" +R"w2c_template( t2 replacement_wrapped = (t2)replacement; \ +)w2c_template" +R"w2c_template( atomic_compare_exchange_strong( \ +)w2c_template" +R"w2c_template( (_Atomic volatile t2*)MEM_ADDR(mem, addr, sizeof(t2)), \ +)w2c_template" +R"w2c_template( &expected_wrapped, replacement_wrapped); \ +)w2c_template" +R"w2c_template( return (t1)expected_wrapped; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS2(name##_shared, _shared_, t2, return, t1, t1, t1) +)w2c_template" +R"w2c_template( +DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw8_cmpxchg_u, u32, u8); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw16_cmpxchg_u, u32, u16); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw_cmpxchg, u32, u32); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw8_cmpxchg_u, u64, u8); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw16_cmpxchg_u, u64, u16); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw32_cmpxchg_u, u64, u32); +)w2c_template" +R"w2c_template(DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw_cmpxchg, u64, u64); +)w2c_template" +R"w2c_template( +#define atomic_fence() atomic_thread_fence(memory_order_seq_cst) +)w2c_template" +; diff --git a/src/prebuilt/wasm2c_header_bottom.cc b/src/prebuilt/wasm2c_header_bottom.cc new file mode 100644 index 00000000000..7db8468ff78 --- /dev/null +++ b/src/prebuilt/wasm2c_header_bottom.cc @@ -0,0 +1,7 @@ +const char* s_header_bottom = R"w2c_template(#ifdef __cplusplus +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(#endif +)w2c_template" +; diff --git a/src/prebuilt/wasm2c_header_top.cc b/src/prebuilt/wasm2c_header_top.cc new file mode 100644 index 00000000000..a72864c8db2 --- /dev/null +++ b/src/prebuilt/wasm2c_header_top.cc @@ -0,0 +1,37 @@ +const char* s_header_top = R"w2c_template(#include +)w2c_template" +R"w2c_template( +#ifndef WASM_RT_CORE_TYPES_DEFINED +)w2c_template" +R"w2c_template(#define WASM_RT_CORE_TYPES_DEFINED +)w2c_template" +R"w2c_template(typedef uint8_t u8; +)w2c_template" +R"w2c_template(typedef int8_t s8; +)w2c_template" +R"w2c_template(typedef uint16_t u16; +)w2c_template" +R"w2c_template(typedef int16_t s16; +)w2c_template" +R"w2c_template(typedef uint32_t u32; +)w2c_template" +R"w2c_template(typedef int32_t s32; +)w2c_template" +R"w2c_template(typedef uint64_t u64; +)w2c_template" +R"w2c_template(typedef int64_t s64; +)w2c_template" +R"w2c_template(typedef float f32; +)w2c_template" +R"w2c_template(typedef double f64; +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#ifdef __cplusplus +)w2c_template" +R"w2c_template(extern "C" { +)w2c_template" +R"w2c_template(#endif +)w2c_template" +; diff --git a/src/prebuilt/wasm2c_simd_source_declarations.cc b/src/prebuilt/wasm2c_simd_source_declarations.cc new file mode 100644 index 00000000000..2e70ecce052 --- /dev/null +++ b/src/prebuilt/wasm2c_simd_source_declarations.cc @@ -0,0 +1,672 @@ +const char* s_simd_source_declarations = R"w2c_template(#if defined(__GNUC__) && defined(__x86_64__) +)w2c_template" +R"w2c_template(#define SIMD_FORCE_READ(var) __asm__("" ::"x"(var)); +)w2c_template" +R"w2c_template(#elif defined(__GNUC__) && defined(__aarch64__) +)w2c_template" +R"w2c_template(#define SIMD_FORCE_READ(var) __asm__("" ::"w"(var)); +)w2c_template" +R"w2c_template(#elif defined(__s390x__) +)w2c_template" +R"w2c_template(#define SIMD_FORCE_READ(var) __asm__("" ::"d"(var)); +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define SIMD_FORCE_READ(var) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(// TODO: equivalent constraint for ARM and other architectures +)w2c_template" +R"w2c_template( +#define DEFINE_SIMD_LOAD_FUNC(name, func, t) \ +)w2c_template" +R"w2c_template( static inline v128 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ +)w2c_template" +R"w2c_template( v128 result = func(MEM_ADDR(mem, addr, sizeof(t))); \ +)w2c_template" +R"w2c_template( SIMD_FORCE_READ(result); \ +)w2c_template" +R"w2c_template( return result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS0(name, _, t, return, v128); +)w2c_template" +R"w2c_template( +#define DEFINE_SIMD_LOAD_LANE(name, func, t, lane) \ +)w2c_template" +R"w2c_template( static inline v128 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( v128 vec) { \ +)w2c_template" +R"w2c_template( v128 result = func(MEM_ADDR(mem, addr, sizeof(t)), vec, lane); \ +)w2c_template" +R"w2c_template( SIMD_FORCE_READ(result); \ +)w2c_template" +R"w2c_template( return result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t, return, v128, v128); +)w2c_template" +R"w2c_template( +#define DEFINE_SIMD_STORE(name, t) \ +)w2c_template" +R"w2c_template( static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( v128 value) { \ +)w2c_template" +R"w2c_template( simde_wasm_v128_store(MEM_ADDR(mem, addr, sizeof(t)), value); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t, , void, v128); +)w2c_template" +R"w2c_template( +#define DEFINE_SIMD_STORE_LANE(name, func, t, lane) \ +)w2c_template" +R"w2c_template( static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( v128 value) { \ +)w2c_template" +R"w2c_template( func(MEM_ADDR(mem, addr, sizeof(t)), value, lane); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t, , void, v128); +)w2c_template" +R"w2c_template( +// clang-format off +)w2c_template" +R"w2c_template(#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(static inline v128 v128_impl_load32_zero(const void* a) { +)w2c_template" +R"w2c_template( return simde_wasm_i8x16_swizzle( +)w2c_template" +R"w2c_template( simde_wasm_v128_load32_zero(a), +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3)); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(static inline v128 v128_impl_load64_zero(const void* a) { +)w2c_template" +R"w2c_template( return simde_wasm_i8x16_swizzle( +)w2c_template" +R"w2c_template( simde_wasm_v128_load64_zero(a), +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define v128_impl_load32_zero simde_wasm_v128_load32_zero +)w2c_template" +R"w2c_template(#define v128_impl_load64_zero simde_wasm_v128_load64_zero +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +DEFINE_SIMD_LOAD_FUNC(v128_load, simde_wasm_v128_load, v128) +)w2c_template" +R"w2c_template( +DEFINE_SIMD_LOAD_FUNC(v128_load8_splat, simde_wasm_v128_load8_splat, u8) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(v128_load16_splat, simde_wasm_v128_load16_splat, u16) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(v128_load32_splat, simde_wasm_v128_load32_splat, u32) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(v128_load64_splat, simde_wasm_v128_load64_splat, u64) +)w2c_template" +R"w2c_template( +DEFINE_SIMD_LOAD_FUNC(i16x8_load8x8, simde_wasm_i16x8_load8x8, u64) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(u16x8_load8x8, simde_wasm_u16x8_load8x8, u64) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(i32x4_load16x4, simde_wasm_i32x4_load16x4, u64) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(u32x4_load16x4, simde_wasm_u32x4_load16x4, u64) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(i64x2_load32x2, simde_wasm_i64x2_load32x2, u64) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(u64x2_load32x2, simde_wasm_u64x2_load32x2, u64) +)w2c_template" +R"w2c_template( +DEFINE_SIMD_LOAD_FUNC(v128_load32_zero, v128_impl_load32_zero, u32) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_FUNC(v128_load64_zero, v128_impl_load64_zero, u64) +)w2c_template" +R"w2c_template( +#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane0, simde_wasm_v128_load8_lane, u8, 15) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane1, simde_wasm_v128_load8_lane, u8, 14) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane2, simde_wasm_v128_load8_lane, u8, 13) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane3, simde_wasm_v128_load8_lane, u8, 12) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane4, simde_wasm_v128_load8_lane, u8, 11) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane5, simde_wasm_v128_load8_lane, u8, 10) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane6, simde_wasm_v128_load8_lane, u8, 9) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane7, simde_wasm_v128_load8_lane, u8, 8) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane8, simde_wasm_v128_load8_lane, u8, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane9, simde_wasm_v128_load8_lane, u8, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane10, simde_wasm_v128_load8_lane, u8, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane11, simde_wasm_v128_load8_lane, u8, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane12, simde_wasm_v128_load8_lane, u8, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane13, simde_wasm_v128_load8_lane, u8, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane14, simde_wasm_v128_load8_lane, u8, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane15, simde_wasm_v128_load8_lane, u8, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane0, simde_wasm_v128_load16_lane, u16, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane1, simde_wasm_v128_load16_lane, u16, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane2, simde_wasm_v128_load16_lane, u16, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane3, simde_wasm_v128_load16_lane, u16, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane4, simde_wasm_v128_load16_lane, u16, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane5, simde_wasm_v128_load16_lane, u16, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane6, simde_wasm_v128_load16_lane, u16, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane7, simde_wasm_v128_load16_lane, u16, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane0, simde_wasm_v128_load32_lane, u32, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane1, simde_wasm_v128_load32_lane, u32, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane2, simde_wasm_v128_load32_lane, u32, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane3, simde_wasm_v128_load32_lane, u32, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load64_lane0, simde_wasm_v128_load64_lane, u64, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load64_lane1, simde_wasm_v128_load64_lane, u64, 0) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane0, simde_wasm_v128_load8_lane, u8, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane1, simde_wasm_v128_load8_lane, u8, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane2, simde_wasm_v128_load8_lane, u8, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane3, simde_wasm_v128_load8_lane, u8, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane4, simde_wasm_v128_load8_lane, u8, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane5, simde_wasm_v128_load8_lane, u8, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane6, simde_wasm_v128_load8_lane, u8, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane7, simde_wasm_v128_load8_lane, u8, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane8, simde_wasm_v128_load8_lane, u8, 8) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane9, simde_wasm_v128_load8_lane, u8, 9) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane10, simde_wasm_v128_load8_lane, u8, 10) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane11, simde_wasm_v128_load8_lane, u8, 11) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane12, simde_wasm_v128_load8_lane, u8, 12) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane13, simde_wasm_v128_load8_lane, u8, 13) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane14, simde_wasm_v128_load8_lane, u8, 14) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load8_lane15, simde_wasm_v128_load8_lane, u8, 15) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane0, simde_wasm_v128_load16_lane, u16, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane1, simde_wasm_v128_load16_lane, u16, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane2, simde_wasm_v128_load16_lane, u16, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane3, simde_wasm_v128_load16_lane, u16, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane4, simde_wasm_v128_load16_lane, u16, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane5, simde_wasm_v128_load16_lane, u16, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane6, simde_wasm_v128_load16_lane, u16, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load16_lane7, simde_wasm_v128_load16_lane, u16, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane0, simde_wasm_v128_load32_lane, u32, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane1, simde_wasm_v128_load32_lane, u32, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane2, simde_wasm_v128_load32_lane, u32, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load32_lane3, simde_wasm_v128_load32_lane, u32, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load64_lane0, simde_wasm_v128_load64_lane, u64, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_LOAD_LANE(v128_load64_lane1, simde_wasm_v128_load64_lane, u64, 1) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +DEFINE_SIMD_STORE(v128_store, v128) +)w2c_template" +R"w2c_template( +#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane0, simde_wasm_v128_store8_lane, u8, 15) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane1, simde_wasm_v128_store8_lane, u8, 14) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane2, simde_wasm_v128_store8_lane, u8, 13) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane3, simde_wasm_v128_store8_lane, u8, 12) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane4, simde_wasm_v128_store8_lane, u8, 11) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane5, simde_wasm_v128_store8_lane, u8, 10) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane6, simde_wasm_v128_store8_lane, u8, 9) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane7, simde_wasm_v128_store8_lane, u8, 8) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane8, simde_wasm_v128_store8_lane, u8, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane9, simde_wasm_v128_store8_lane, u8, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane10, simde_wasm_v128_store8_lane, u8, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane11, simde_wasm_v128_store8_lane, u8, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane12, simde_wasm_v128_store8_lane, u8, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane13, simde_wasm_v128_store8_lane, u8, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane14, simde_wasm_v128_store8_lane, u8, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane15, simde_wasm_v128_store8_lane, u8, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane0, simde_wasm_v128_store16_lane, u16, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane1, simde_wasm_v128_store16_lane, u16, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane2, simde_wasm_v128_store16_lane, u16, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane3, simde_wasm_v128_store16_lane, u16, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane4, simde_wasm_v128_store16_lane, u16, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane5, simde_wasm_v128_store16_lane, u16, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane6, simde_wasm_v128_store16_lane, u16, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane7, simde_wasm_v128_store16_lane, u16, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane0, simde_wasm_v128_store32_lane, u32, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane1, simde_wasm_v128_store32_lane, u32, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane2, simde_wasm_v128_store32_lane, u32, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane3, simde_wasm_v128_store32_lane, u32, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store64_lane0, simde_wasm_v128_store64_lane, u64, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store64_lane1, simde_wasm_v128_store64_lane, u64, 0) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane0, simde_wasm_v128_store8_lane, u8, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane1, simde_wasm_v128_store8_lane, u8, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane2, simde_wasm_v128_store8_lane, u8, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane3, simde_wasm_v128_store8_lane, u8, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane4, simde_wasm_v128_store8_lane, u8, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane5, simde_wasm_v128_store8_lane, u8, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane6, simde_wasm_v128_store8_lane, u8, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane7, simde_wasm_v128_store8_lane, u8, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane8, simde_wasm_v128_store8_lane, u8, 8) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane9, simde_wasm_v128_store8_lane, u8, 9) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane10, simde_wasm_v128_store8_lane, u8, 10) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane11, simde_wasm_v128_store8_lane, u8, 11) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane12, simde_wasm_v128_store8_lane, u8, 12) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane13, simde_wasm_v128_store8_lane, u8, 13) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane14, simde_wasm_v128_store8_lane, u8, 14) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store8_lane15, simde_wasm_v128_store8_lane, u8, 15) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane0, simde_wasm_v128_store16_lane, u16, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane1, simde_wasm_v128_store16_lane, u16, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane2, simde_wasm_v128_store16_lane, u16, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane3, simde_wasm_v128_store16_lane, u16, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane4, simde_wasm_v128_store16_lane, u16, 4) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane5, simde_wasm_v128_store16_lane, u16, 5) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane6, simde_wasm_v128_store16_lane, u16, 6) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store16_lane7, simde_wasm_v128_store16_lane, u16, 7) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane0, simde_wasm_v128_store32_lane, u32, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane1, simde_wasm_v128_store32_lane, u32, 1) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane2, simde_wasm_v128_store32_lane, u32, 2) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store32_lane3, simde_wasm_v128_store32_lane, u32, 3) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store64_lane0, simde_wasm_v128_store64_lane, u64, 0) +)w2c_template" +R"w2c_template(DEFINE_SIMD_STORE_LANE(v128_store64_lane1, simde_wasm_v128_store64_lane, u64, 1) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(#define v128_const(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) simde_wasm_i8x16_const(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_i8x16_extract_lane(v, l) simde_wasm_i8x16_extract_lane(v, 15-(l)) +)w2c_template" +R"w2c_template(#define v128_u8x16_extract_lane(v, l) simde_wasm_u8x16_extract_lane(v, 15-(l)) +)w2c_template" +R"w2c_template(#define v128_i16x8_extract_lane(v, l) simde_wasm_i16x8_extract_lane(v, 7-(l)) +)w2c_template" +R"w2c_template(#define v128_u16x8_extract_lane(v, l) simde_wasm_u16x8_extract_lane(v, 7-(l)) +)w2c_template" +R"w2c_template(#define v128_i32x4_extract_lane(v, l) simde_wasm_i32x4_extract_lane(v, 3-(l)) +)w2c_template" +R"w2c_template(#define v128_i64x2_extract_lane(v, l) simde_wasm_i64x2_extract_lane(v, 1-(l)) +)w2c_template" +R"w2c_template(#define v128_f32x4_extract_lane(v, l) simde_wasm_f32x4_extract_lane(v, 3-(l)) +)w2c_template" +R"w2c_template(#define v128_f64x2_extract_lane(v, l) simde_wasm_f64x2_extract_lane(v, 1-(l)) +)w2c_template" +R"w2c_template(#define v128_i8x16_replace_lane(v, l, x) simde_wasm_i8x16_replace_lane(v, 15-(l), x) +)w2c_template" +R"w2c_template(#define v128_u8x16_replace_lane(v, l, x) simde_wasm_u8x16_replace_lane(v, 15-(l), x) +)w2c_template" +R"w2c_template(#define v128_i16x8_replace_lane(v, l, x) simde_wasm_i16x8_replace_lane(v, 7-(l), x) +)w2c_template" +R"w2c_template(#define v128_u16x8_replace_lane(v, l, x) simde_wasm_u16x8_replace_lane(v, 7-(l), x) +)w2c_template" +R"w2c_template(#define v128_i32x4_replace_lane(v, l, x) simde_wasm_i32x4_replace_lane(v, 3-(l), x) +)w2c_template" +R"w2c_template(#define v128_i64x2_replace_lane(v, l, x) simde_wasm_i64x2_replace_lane(v, 1-(l), x) +)w2c_template" +R"w2c_template(#define v128_f32x4_replace_lane(v, l, x) simde_wasm_f32x4_replace_lane(v, 3-(l), x) +)w2c_template" +R"w2c_template(#define v128_f64x2_replace_lane(v, l, x) simde_wasm_f64x2_replace_lane(v, 1-(l), x) +)w2c_template" +R"w2c_template(#define v128_i8x16_bitmask(v) simde_wasm_i8x16_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))) +)w2c_template" +R"w2c_template(#define v128_i16x8_bitmask(v) simde_wasm_i16x8_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1))) +)w2c_template" +R"w2c_template(#define v128_i32x4_bitmask(v) simde_wasm_i32x4_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3))) +)w2c_template" +R"w2c_template(#define v128_i64x2_bitmask(v) simde_wasm_i64x2_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +)w2c_template" +R"w2c_template(#define v128_i8x16_swizzle(v1, v2) simde_wasm_i8x16_swizzle(v1, simde_wasm_v128_xor(v2, simde_wasm_i8x16_splat(15))) +)w2c_template" +R"w2c_template(#define v128_i8x16_shuffle(v1,v2,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) simde_wasm_i8x16_shuffle(v2,v1,31-(p),31-(o),31-(n),31-(m),31-(l),31-(k),31-(j),31-(i),31-(h),31-(g),31-(f),31-(e),31-(d),31-(c),31-(b),31-(a)) +)w2c_template" +R"w2c_template(#define v128_i16x8_extmul_high_i8x16 simde_wasm_i16x8_extmul_low_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extmul_high_u8x16 simde_wasm_u16x8_extmul_low_u8x16 +)w2c_template" +R"w2c_template(#define v128_i16x8_extmul_low_i8x16 simde_wasm_i16x8_extmul_high_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extmul_low_u8x16 simde_wasm_u16x8_extmul_high_u8x16 +)w2c_template" +R"w2c_template(#define v128_i32x4_extmul_high_i16x8 simde_wasm_i32x4_extmul_low_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extmul_high_u16x8 simde_wasm_u32x4_extmul_low_u16x8 +)w2c_template" +R"w2c_template(#define v128_i32x4_extmul_low_i16x8 simde_wasm_i32x4_extmul_high_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extmul_low_u16x8 simde_wasm_u32x4_extmul_high_u16x8 +)w2c_template" +R"w2c_template(#define v128_i64x2_extmul_high_i32x4 simde_wasm_i64x2_extmul_low_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extmul_high_u32x4 simde_wasm_u64x2_extmul_low_u32x4 +)w2c_template" +R"w2c_template(#define v128_i64x2_extmul_low_i32x4 simde_wasm_i64x2_extmul_high_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extmul_low_u32x4 simde_wasm_u64x2_extmul_high_u32x4 +)w2c_template" +R"w2c_template(#define v128_i16x8_extend_high_i8x16 simde_wasm_i16x8_extend_low_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extend_high_u8x16 simde_wasm_u16x8_extend_low_u8x16 +)w2c_template" +R"w2c_template(#define v128_i16x8_extend_low_i8x16 simde_wasm_i16x8_extend_high_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extend_low_u8x16 simde_wasm_u16x8_extend_high_u8x16 +)w2c_template" +R"w2c_template(#define v128_i32x4_extend_high_i16x8 simde_wasm_i32x4_extend_low_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extend_high_u16x8 simde_wasm_u32x4_extend_low_u16x8 +)w2c_template" +R"w2c_template(#define v128_i32x4_extend_low_i16x8 simde_wasm_i32x4_extend_high_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extend_low_u16x8 simde_wasm_u32x4_extend_high_u16x8 +)w2c_template" +R"w2c_template(#define v128_i64x2_extend_high_i32x4 simde_wasm_i64x2_extend_low_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extend_high_u32x4 simde_wasm_u64x2_extend_low_u32x4 +)w2c_template" +R"w2c_template(#define v128_i64x2_extend_low_i32x4 simde_wasm_i64x2_extend_high_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extend_low_u32x4 simde_wasm_u64x2_extend_high_u32x4 +)w2c_template" +R"w2c_template(#define v128_i32x4_trunc_sat_f64x2_zero(a) \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( simde_wasm_i32x4_trunc_sat_f64x2_zero(a), \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +)w2c_template" +R"w2c_template(#define v128_u32x4_trunc_sat_f64x2_zero(a) \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( simde_wasm_u32x4_trunc_sat_f64x2_zero(a), \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +)w2c_template" +R"w2c_template(#define v128_i16x8_narrow_i32x4(a,b) simde_wasm_i16x8_narrow_i32x4(b,a) +)w2c_template" +R"w2c_template(#define v128_u16x8_narrow_i32x4(a,b) simde_wasm_u16x8_narrow_i32x4(b,a) +)w2c_template" +R"w2c_template(#define v128_i8x16_narrow_i16x8(a,b) simde_wasm_i8x16_narrow_i16x8(b,a) +)w2c_template" +R"w2c_template(#define v128_u8x16_narrow_i16x8(a,b) simde_wasm_u8x16_narrow_i16x8(b,a) +)w2c_template" +R"w2c_template(#define v128_f64x2_promote_low_f32x4(a) \ +)w2c_template" +R"w2c_template( simde_wasm_f64x2_promote_low_f32x4(simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( a, \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +)w2c_template" +R"w2c_template(#define v128_f32x4_demote_f64x2_zero(a) \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( simde_wasm_f32x4_demote_f64x2_zero(a), \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +)w2c_template" +R"w2c_template(#define v128_f64x2_convert_low_i32x4(a) \ +)w2c_template" +R"w2c_template( simde_wasm_f64x2_convert_low_i32x4(simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( a, \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +)w2c_template" +R"w2c_template(#define v128_f64x2_convert_low_u32x4(a) \ +)w2c_template" +R"w2c_template( simde_wasm_f64x2_convert_low_u32x4(simde_wasm_i8x16_swizzle( \ +)w2c_template" +R"w2c_template( a, \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define v128_const simde_wasm_i8x16_const +)w2c_template" +R"w2c_template(#define v128_i8x16_extract_lane simde_wasm_i8x16_extract_lane +)w2c_template" +R"w2c_template(#define v128_u8x16_extract_lane simde_wasm_u8x16_extract_lane +)w2c_template" +R"w2c_template(#define v128_i16x8_extract_lane simde_wasm_i16x8_extract_lane +)w2c_template" +R"w2c_template(#define v128_u16x8_extract_lane simde_wasm_u16x8_extract_lane +)w2c_template" +R"w2c_template(#define v128_i32x4_extract_lane simde_wasm_i32x4_extract_lane +)w2c_template" +R"w2c_template(#define v128_i64x2_extract_lane simde_wasm_i64x2_extract_lane +)w2c_template" +R"w2c_template(#define v128_f32x4_extract_lane simde_wasm_f32x4_extract_lane +)w2c_template" +R"w2c_template(#define v128_f64x2_extract_lane simde_wasm_f64x2_extract_lane +)w2c_template" +R"w2c_template(#define v128_i8x16_replace_lane simde_wasm_i8x16_replace_lane +)w2c_template" +R"w2c_template(#define v128_u8x16_replace_lane simde_wasm_u8x16_replace_lane +)w2c_template" +R"w2c_template(#define v128_i16x8_replace_lane simde_wasm_i16x8_replace_lane +)w2c_template" +R"w2c_template(#define v128_u16x8_replace_lane simde_wasm_u16x8_replace_lane +)w2c_template" +R"w2c_template(#define v128_i32x4_replace_lane simde_wasm_i32x4_replace_lane +)w2c_template" +R"w2c_template(#define v128_i64x2_replace_lane simde_wasm_i64x2_replace_lane +)w2c_template" +R"w2c_template(#define v128_f32x4_replace_lane simde_wasm_f32x4_replace_lane +)w2c_template" +R"w2c_template(#define v128_f64x2_replace_lane simde_wasm_f64x2_replace_lane +)w2c_template" +R"w2c_template(#define v128_i8x16_bitmask simde_wasm_i8x16_bitmask +)w2c_template" +R"w2c_template(#define v128_i16x8_bitmask simde_wasm_i16x8_bitmask +)w2c_template" +R"w2c_template(#define v128_i32x4_bitmask simde_wasm_i32x4_bitmask +)w2c_template" +R"w2c_template(#define v128_i64x2_bitmask simde_wasm_i64x2_bitmask +)w2c_template" +R"w2c_template(#define v128_i8x16_swizzle simde_wasm_i8x16_swizzle +)w2c_template" +R"w2c_template(#define v128_i8x16_shuffle simde_wasm_i8x16_shuffle +)w2c_template" +R"w2c_template(#define v128_i16x8_extmul_high_i8x16 simde_wasm_i16x8_extmul_high_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extmul_high_u8x16 simde_wasm_u16x8_extmul_high_u8x16 +)w2c_template" +R"w2c_template(#define v128_i16x8_extmul_low_i8x16 simde_wasm_i16x8_extmul_low_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extmul_low_u8x16 simde_wasm_u16x8_extmul_low_u8x16 +)w2c_template" +R"w2c_template(#define v128_i32x4_extmul_high_i16x8 simde_wasm_i32x4_extmul_high_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extmul_high_u16x8 simde_wasm_u32x4_extmul_high_u16x8 +)w2c_template" +R"w2c_template(#define v128_i32x4_extmul_low_i16x8 simde_wasm_i32x4_extmul_low_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extmul_low_u16x8 simde_wasm_u32x4_extmul_low_u16x8 +)w2c_template" +R"w2c_template(#define v128_i64x2_extmul_high_i32x4 simde_wasm_i64x2_extmul_high_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extmul_high_u32x4 simde_wasm_u64x2_extmul_high_u32x4 +)w2c_template" +R"w2c_template(#define v128_i64x2_extmul_low_i32x4 simde_wasm_i64x2_extmul_low_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extmul_low_u32x4 simde_wasm_u64x2_extmul_low_u32x4 +)w2c_template" +R"w2c_template(#define v128_i16x8_extend_high_i8x16 simde_wasm_i16x8_extend_high_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extend_high_u8x16 simde_wasm_u16x8_extend_high_u8x16 +)w2c_template" +R"w2c_template(#define v128_i16x8_extend_low_i8x16 simde_wasm_i16x8_extend_low_i8x16 +)w2c_template" +R"w2c_template(#define v128_u16x8_extend_low_u8x16 simde_wasm_u16x8_extend_low_u8x16 +)w2c_template" +R"w2c_template(#define v128_i32x4_extend_high_i16x8 simde_wasm_i32x4_extend_high_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extend_high_u16x8 simde_wasm_u32x4_extend_high_u16x8 +)w2c_template" +R"w2c_template(#define v128_i32x4_extend_low_i16x8 simde_wasm_i32x4_extend_low_i16x8 +)w2c_template" +R"w2c_template(#define v128_u32x4_extend_low_u16x8 simde_wasm_u32x4_extend_low_u16x8 +)w2c_template" +R"w2c_template(#define v128_i64x2_extend_high_i32x4 simde_wasm_i64x2_extend_high_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extend_high_u32x4 simde_wasm_u64x2_extend_high_u32x4 +)w2c_template" +R"w2c_template(#define v128_i64x2_extend_low_i32x4 simde_wasm_i64x2_extend_low_i32x4 +)w2c_template" +R"w2c_template(#define v128_u64x2_extend_low_u32x4 simde_wasm_u64x2_extend_low_u32x4 +)w2c_template" +R"w2c_template(#define v128_i32x4_trunc_sat_f64x2_zero simde_wasm_i32x4_trunc_sat_f64x2_zero +)w2c_template" +R"w2c_template(#define v128_u32x4_trunc_sat_f64x2_zero simde_wasm_u32x4_trunc_sat_f64x2_zero +)w2c_template" +R"w2c_template(#define v128_i16x8_narrow_i32x4 simde_wasm_i16x8_narrow_i32x4 +)w2c_template" +R"w2c_template(#define v128_u16x8_narrow_i32x4 simde_wasm_u16x8_narrow_i32x4 +)w2c_template" +R"w2c_template(#define v128_i8x16_narrow_i16x8 simde_wasm_i8x16_narrow_i16x8 +)w2c_template" +R"w2c_template(#define v128_u8x16_narrow_i16x8 simde_wasm_u8x16_narrow_i16x8 +)w2c_template" +R"w2c_template(#define v128_f64x2_promote_low_f32x4 simde_wasm_f64x2_promote_low_f32x4 +)w2c_template" +R"w2c_template(#define v128_f32x4_demote_f64x2_zero simde_wasm_f32x4_demote_f64x2_zero +)w2c_template" +R"w2c_template(#define v128_f64x2_convert_low_i32x4 simde_wasm_f64x2_convert_low_i32x4 +)w2c_template" +R"w2c_template(#define v128_f64x2_convert_low_u32x4 simde_wasm_f64x2_convert_low_u32x4 +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(// clang-format on +)w2c_template" +; diff --git a/src/prebuilt/wasm2c_source_declarations.cc b/src/prebuilt/wasm2c_source_declarations.cc new file mode 100644 index 00000000000..c70ba70940b --- /dev/null +++ b/src/prebuilt/wasm2c_source_declarations.cc @@ -0,0 +1,1357 @@ +const char* s_source_declarations = R"w2c_template( +// Computes a pointer to an object of the given size in a little-endian memory. +)w2c_template" +R"w2c_template(// +)w2c_template" +R"w2c_template(// On a little-endian host, this is just &mem->data[addr] - the object's size is +)w2c_template" +R"w2c_template(// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +)w2c_template" +R"w2c_template(// is the object's size. +)w2c_template" +R"w2c_template(// +)w2c_template" +R"w2c_template(// Note that mem may be evaluated multiple times. +)w2c_template" +R"w2c_template(// +)w2c_template" +R"w2c_template(// Parameters: +)w2c_template" +R"w2c_template(// mem - The memory. +)w2c_template" +R"w2c_template(// addr - The address. +)w2c_template" +R"w2c_template(// n - The size of the object. +)w2c_template" +R"w2c_template(// +)w2c_template" +R"w2c_template(// Result: +)w2c_template" +R"w2c_template(// A pointer for an object of size n. +)w2c_template" +R"w2c_template(#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +// We can only use Segue for this module if it uses a single unshared imported +)w2c_template" +R"w2c_template(// or exported memory +)w2c_template" +R"w2c_template(#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +)w2c_template" +R"w2c_template(#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +)w2c_template" +R"w2c_template(// POSIX uses FS for TLS, GS is free +)w2c_template" +R"w2c_template(static inline void* wasm_rt_segue_read_base() { +)w2c_template" +R"w2c_template( if (wasm_rt_fsgsbase_inst_supported) { +)w2c_template" +R"w2c_template( return (void*)__builtin_ia32_rdgsbase64(); +)w2c_template" +R"w2c_template( } else { +)w2c_template" +R"w2c_template( return wasm_rt_syscall_get_segue_base(); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(static inline void wasm_rt_segue_write_base(void* base) { +)w2c_template" +R"w2c_template(#if WASM_RT_SEGUE_FREE_SEGMENT +)w2c_template" +R"w2c_template( if (wasm_rt_last_segment_val == base) { +)w2c_template" +R"w2c_template( return; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( + wasm_rt_last_segment_val = base; +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( + if (wasm_rt_fsgsbase_inst_supported) { +)w2c_template" +R"w2c_template( __builtin_ia32_wrgsbase64((uintptr_t)base); +)w2c_template" +R"w2c_template( } else { +)w2c_template" +R"w2c_template( wasm_rt_syscall_set_segue_base(base); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) +)w2c_template" +R"w2c_template( +#if WASM_RT_STACK_DEPTH_COUNT +)w2c_template" +R"w2c_template(#define FUNC_PROLOGUE \ +)w2c_template" +R"w2c_template( if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ +)w2c_template" +R"w2c_template( TRAP(EXHAUSTION); +)w2c_template" +R"w2c_template( +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define FUNC_PROLOGUE +)w2c_template" +R"w2c_template( +#define FUNC_EPILOGUE +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#define UNREACHABLE TRAP(UNREACHABLE) +)w2c_template" +R"w2c_template( +static inline bool func_types_eq(const wasm_rt_func_type_t a, +)w2c_template" +R"w2c_template( const wasm_rt_func_type_t b) { +)w2c_template" +R"w2c_template( return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define CHECK_CALL_INDIRECT(table, ft, x) \ +)w2c_template" +R"w2c_template( (LIKELY((x) < table.size && table.data[x].func && \ +)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( +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) +)w2c_template" +R"w2c_template( +#define CALL_INDIRECT(table, t, ft, x, ...) \ +)w2c_template" +R"w2c_template( (CHECK_CALL_INDIRECT(table, ft, x), \ +)w2c_template" +R"w2c_template( DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) +)w2c_template" +R"w2c_template( +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +)w2c_template" +R"w2c_template(#if __has_builtin(__builtin_add_overflow) +)w2c_template" +R"w2c_template( return __builtin_add_overflow(a, b, resptr); +)w2c_template" +R"w2c_template(#elif defined(_MSC_VER) +)w2c_template" +R"w2c_template( return _addcarry_u64(0, a, b, resptr); +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define RANGE_CHECK(mem, offset, len) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( uint64_t res; \ +)w2c_template" +R"w2c_template( if (UNLIKELY(add_overflow(offset, len, &res))) \ +)w2c_template" +R"w2c_template( TRAP(OOB); \ +)w2c_template" +R"w2c_template( if (UNLIKELY(res > (mem)->size)) \ +)w2c_template" +R"w2c_template( TRAP(OOB); \ +)w2c_template" +R"w2c_template( } while (0); +)w2c_template" +R"w2c_template( +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#define WASM_RT_CHECK_BASE(mem) \ +)w2c_template" +R"w2c_template( if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ +)w2c_template" +R"w2c_template( puts("Segment register mismatch\n"); \ +)w2c_template" +R"w2c_template( abort(); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define WASM_RT_CHECK_BASE(mem) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +)w2c_template" +R"w2c_template(// default-page-size, 32-bit memories. It may do nothing at all +)w2c_template" +R"w2c_template(// (if hardware bounds-checking is enabled via guard pages) +)w2c_template" +R"w2c_template(// or it may do a slightly faster RANGE_CHECK. +)w2c_template" +R"w2c_template(#if WASM_RT_MEMCHECK_GUARD_PAGES +)w2c_template" +R"w2c_template(#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define MEMCHECK_DEFAULT32(mem, a, t) \ +)w2c_template" +R"w2c_template( WASM_RT_CHECK_BASE(mem); \ +)w2c_template" +R"w2c_template( if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ +)w2c_template" +R"w2c_template( TRAP(OOB); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +// MEMCHECK_GENERAL can be used for any memory +)w2c_template" +R"w2c_template(#define MEMCHECK_GENERAL(mem, a, t) \ +)w2c_template" +R"w2c_template( WASM_RT_CHECK_BASE(mem); \ +)w2c_template" +R"w2c_template( RANGE_CHECK(mem, a, sizeof(t)); +)w2c_template" +R"w2c_template( +#ifdef __GNUC__ +)w2c_template" +R"w2c_template(#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +)w2c_template" +R"w2c_template(// Clang on Mips requires "f" constraints on floats +)w2c_template" +R"w2c_template(// See https://github.com/llvm/llvm-project/issues/64241 +)w2c_template" +R"w2c_template(#if defined(__clang__) && \ +)w2c_template" +R"w2c_template( (defined(mips) || defined(__mips__) || defined(__mips)) +)w2c_template" +R"w2c_template(#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define FORCE_READ_INT(var) +)w2c_template" +R"w2c_template(#define FORCE_READ_FLOAT(var) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +static inline void load_data(u8* dest, const u8* src, size_t n) { +)w2c_template" +R"w2c_template( if (!n) { +)w2c_template" +R"w2c_template( return; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template( for (size_t i = 0; i < n; i++) { +)w2c_template" +R"w2c_template( dest[i] = src[n - i - 1]; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template( wasm_rt_memcpy(dest, src, n); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define LOAD_DATA(m, o, i, s) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( RANGE_CHECK((&m), o, s); \ +)w2c_template" +R"w2c_template( load_data(MEM_ADDR(&m, o, s), i, s); \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ +)w2c_template" +R"w2c_template( static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr) { \ +)w2c_template" +R"w2c_template( MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ +)w2c_template" +R"w2c_template( MEMCHECK_GENERAL(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ +)w2c_template" +R"w2c_template( val_type1) \ +)w2c_template" +R"w2c_template( static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr, val_type1 val1) { \ +)w2c_template" +R"w2c_template( MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr, val1); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( val_type1 val1) { \ +)w2c_template" +R"w2c_template( MEMCHECK_GENERAL(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr, val1); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ +)w2c_template" +R"w2c_template( val_type1, val_type2) \ +)w2c_template" +R"w2c_template( static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +)w2c_template" +R"w2c_template( u64 addr, val_type1 val1, \ +)w2c_template" +R"w2c_template( val_type2 val2) { \ +)w2c_template" +R"w2c_template( MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr, val1, val2); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( val_type1 val1, val_type2 val2) { \ +)w2c_template" +R"w2c_template( MEMCHECK_GENERAL(mem, addr, mem_type); \ +)w2c_template" +R"w2c_template( ret_kw name##_unchecked(mem, addr, val1, val2); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ +)w2c_template" +R"w2c_template( static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ +)w2c_template" +R"w2c_template( t1 result; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ +)w2c_template" +R"w2c_template( sizeof(t1)); \ +)w2c_template" +R"w2c_template( force_read(result); \ +)w2c_template" +R"w2c_template( return (t3)(t2)result; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS0(name, _, t1, return, t3) +)w2c_template" +R"w2c_template( +#define DEFINE_STORE(name, t1, t2) \ +)w2c_template" +R"w2c_template( static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +)w2c_template" +R"w2c_template( t2 value) { \ +)w2c_template" +R"w2c_template( t1 wrapped = (t1)value; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ +)w2c_template" +R"w2c_template( sizeof(t1)); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( DEF_MEM_CHECKS1(name, _, t1, , void, t2) +)w2c_template" +R"w2c_template( +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +)w2c_template" +R"w2c_template(DEFINE_STORE(i32_store, u32, u32) +)w2c_template" +R"w2c_template(DEFINE_STORE(i64_store, u64, u64) +)w2c_template" +R"w2c_template(DEFINE_STORE(f32_store, f32, f32) +)w2c_template" +R"w2c_template(DEFINE_STORE(f64_store, f64, f64) +)w2c_template" +R"w2c_template(DEFINE_STORE(i32_store8, u8, u32) +)w2c_template" +R"w2c_template(DEFINE_STORE(i32_store16, u16, u32) +)w2c_template" +R"w2c_template(DEFINE_STORE(i64_store8, u8, u64) +)w2c_template" +R"w2c_template(DEFINE_STORE(i64_store16, u16, u64) +)w2c_template" +R"w2c_template(DEFINE_STORE(i64_store32, u32, u64) +)w2c_template" +R"w2c_template( +#if defined(_MSC_VER) +)w2c_template" +R"w2c_template( +// Adapted from +)w2c_template" +R"w2c_template(// https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h +)w2c_template" +R"w2c_template( +static inline int I64_CLZ(unsigned long long v) { +)w2c_template" +R"w2c_template( unsigned long r = 0; +)w2c_template" +R"w2c_template(#if defined(_M_AMD64) || defined(_M_ARM) +)w2c_template" +R"w2c_template( if (_BitScanReverse64(&r, v)) { +)w2c_template" +R"w2c_template( return 63 - r; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template( if (_BitScanReverse(&r, (unsigned long)(v >> 32))) { +)w2c_template" +R"w2c_template( return 31 - r; +)w2c_template" +R"w2c_template( } else if (_BitScanReverse(&r, (unsigned long)v)) { +)w2c_template" +R"w2c_template( return 63 - r; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( return 64; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline int I32_CLZ(unsigned long v) { +)w2c_template" +R"w2c_template( unsigned long r = 0; +)w2c_template" +R"w2c_template( if (_BitScanReverse(&r, v)) { +)w2c_template" +R"w2c_template( return 31 - r; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return 32; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline int I64_CTZ(unsigned long long v) { +)w2c_template" +R"w2c_template( if (!v) { +)w2c_template" +R"w2c_template( return 64; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( unsigned long r = 0; +)w2c_template" +R"w2c_template(#if defined(_M_AMD64) || defined(_M_ARM) +)w2c_template" +R"w2c_template( _BitScanForward64(&r, v); +)w2c_template" +R"w2c_template( return (int)r; +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template( if (_BitScanForward(&r, (unsigned int)(v))) { +)w2c_template" +R"w2c_template( return (int)(r); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( + _BitScanForward(&r, (unsigned int)(v >> 32)); +)w2c_template" +R"w2c_template( return (int)(r + 32); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline int I32_CTZ(unsigned long v) { +)w2c_template" +R"w2c_template( if (!v) { +)w2c_template" +R"w2c_template( return 32; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( unsigned long r = 0; +)w2c_template" +R"w2c_template( _BitScanForward(&r, v); +)w2c_template" +R"w2c_template( return (int)r; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define POPCOUNT_DEFINE_PORTABLE(f_n, T) \ +)w2c_template" +R"w2c_template( static inline u32 f_n(T x) { \ +)w2c_template" +R"w2c_template( x = x - ((x >> 1) & (T) ~(T)0 / 3); \ +)w2c_template" +R"w2c_template( x = (x & (T) ~(T)0 / 15 * 3) + ((x >> 2) & (T) ~(T)0 / 15 * 3); \ +)w2c_template" +R"w2c_template( x = (x + (x >> 4)) & (T) ~(T)0 / 255 * 15; \ +)w2c_template" +R"w2c_template( return (T)(x * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8; \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +POPCOUNT_DEFINE_PORTABLE(I32_POPCNT, u32) +)w2c_template" +R"w2c_template(POPCOUNT_DEFINE_PORTABLE(I64_POPCNT, u64) +)w2c_template" +R"w2c_template( +#undef POPCOUNT_DEFINE_PORTABLE +)w2c_template" +R"w2c_template( +#else +)w2c_template" +R"w2c_template( +#define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) +)w2c_template" +R"w2c_template(#define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) +)w2c_template" +R"w2c_template(#define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) +)w2c_template" +R"w2c_template(#define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) +)w2c_template" +R"w2c_template(#define I32_POPCNT(x) (__builtin_popcount(x)) +)w2c_template" +R"w2c_template(#define I64_POPCNT(x) (__builtin_popcountll(x)) +)w2c_template" +R"w2c_template( +#endif +)w2c_template" +R"w2c_template( +#define DIV_S(ut, min, x, y) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ +)w2c_template" +R"w2c_template( : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ +)w2c_template" +R"w2c_template( : (ut)((x) / (y))) +)w2c_template" +R"w2c_template( +#define REM_S(ut, min, x, y) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ +)w2c_template" +R"w2c_template( : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ +)w2c_template" +R"w2c_template( : (ut)((x) % (y))) +)w2c_template" +R"w2c_template( +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +)w2c_template" +R"w2c_template(#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +)w2c_template" +R"w2c_template(#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +)w2c_template" +R"w2c_template(#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) +)w2c_template" +R"w2c_template( +#define DIVREM_U(op, x, y) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) +)w2c_template" +R"w2c_template( +#define DIV_U(x, y) DIVREM_U(/, x, y) +)w2c_template" +R"w2c_template(#define REM_U(x, y) DIVREM_U(%, x, y) +)w2c_template" +R"w2c_template( +#define ROTL(x, y, mask) \ +)w2c_template" +R"w2c_template( (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +)w2c_template" +R"w2c_template(#define ROTR(x, y, mask) \ +)w2c_template" +R"w2c_template( (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) +)w2c_template" +R"w2c_template( +#define I32_ROTL(x, y) ROTL(x, y, 31) +)w2c_template" +R"w2c_template(#define I64_ROTL(x, y) ROTL(x, y, 63) +)w2c_template" +R"w2c_template(#define I32_ROTR(x, y) ROTR(x, y, 31) +)w2c_template" +R"w2c_template(#define I64_ROTR(x, y) ROTR(x, y, 63) +)w2c_template" +R"w2c_template( +#define FMIN(x, y) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? NAN \ +)w2c_template" +R"w2c_template( : (UNLIKELY((y) != (y))) ? NAN \ +)w2c_template" +R"w2c_template( : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ +)w2c_template" +R"w2c_template( : (x < y) ? x \ +)w2c_template" +R"w2c_template( : y) +)w2c_template" +R"w2c_template( +#define FMAX(x, y) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? NAN \ +)w2c_template" +R"w2c_template( : (UNLIKELY((y) != (y))) ? NAN \ +)w2c_template" +R"w2c_template( : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ +)w2c_template" +R"w2c_template( : (x > y) ? x \ +)w2c_template" +R"w2c_template( : y) +)w2c_template" +R"w2c_template( +#define TRUNC_S(ut, st, ft, min, minop, max, x) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x)minop(min) && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ +)w2c_template" +R"w2c_template( : (ut)(st)(x)) +)w2c_template" +R"w2c_template( +#define I32_TRUNC_S_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_S(u32, s32, f32, (f32)INT32_MIN, >=, 2147483648.f, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_S_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_S(u64, s64, f32, (f32)INT64_MIN, >=, (f32)INT64_MAX, x) +)w2c_template" +R"w2c_template(#define I32_TRUNC_S_F64(x) \ +)w2c_template" +R"w2c_template( TRUNC_S(u32, s32, f64, -2147483649., >, 2147483648., x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_S_F64(x) \ +)w2c_template" +R"w2c_template( TRUNC_S(u64, s64, f64, (f64)INT64_MIN, >=, (f64)INT64_MAX, x) +)w2c_template" +R"w2c_template( +#define TRUNC_U(ut, ft, max, x) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x) > (ft) - 1 && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ +)w2c_template" +R"w2c_template( : (ut)(x)) +)w2c_template" +R"w2c_template( +#define I32_TRUNC_U_F32(x) TRUNC_U(u32, f32, 4294967296.f, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_U_F32(x) TRUNC_U(u64, f32, (f32)UINT64_MAX, x) +)w2c_template" +R"w2c_template(#define I32_TRUNC_U_F64(x) TRUNC_U(u32, f64, 4294967296., x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_U_F64(x) TRUNC_U(u64, f64, (f64)UINT64_MAX, x) +)w2c_template" +R"w2c_template( +#define TRUNC_SAT_S(ut, st, ft, min, smin, minop, max, smax, x) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? 0 \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x)minop(min)))) ? smin \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x) < (max)))) ? smax \ +)w2c_template" +R"w2c_template( : (ut)(st)(x)) +)w2c_template" +R"w2c_template( +#define I32_TRUNC_SAT_S_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_S(u32, s32, f32, (f32)INT32_MIN, INT32_MIN, >=, 2147483648.f, \ +)w2c_template" +R"w2c_template( INT32_MAX, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_SAT_S_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_S(u64, s64, f32, (f32)INT64_MIN, INT64_MIN, >=, (f32)INT64_MAX, \ +)w2c_template" +R"w2c_template( INT64_MAX, x) +)w2c_template" +R"w2c_template(#define I32_TRUNC_SAT_S_F64(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_S(u32, s32, f64, -2147483649., INT32_MIN, >, 2147483648., \ +)w2c_template" +R"w2c_template( INT32_MAX, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_SAT_S_F64(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_S(u64, s64, f64, (f64)INT64_MIN, INT64_MIN, >=, (f64)INT64_MAX, \ +)w2c_template" +R"w2c_template( INT64_MAX, x) +)w2c_template" +R"w2c_template( +#define TRUNC_SAT_U(ut, ft, max, smax, x) \ +)w2c_template" +R"w2c_template( ((UNLIKELY((x) != (x))) ? 0 \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x) > (ft) - 1))) ? 0 \ +)w2c_template" +R"w2c_template( : (UNLIKELY(!((x) < (max)))) ? smax \ +)w2c_template" +R"w2c_template( : (ut)(x)) +)w2c_template" +R"w2c_template( +#define I32_TRUNC_SAT_U_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_U(u32, f32, 4294967296.f, UINT32_MAX, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_SAT_U_F32(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_U(u64, f32, (f32)UINT64_MAX, UINT64_MAX, x) +)w2c_template" +R"w2c_template(#define I32_TRUNC_SAT_U_F64(x) TRUNC_SAT_U(u32, f64, 4294967296., UINT32_MAX, x) +)w2c_template" +R"w2c_template(#define I64_TRUNC_SAT_U_F64(x) \ +)w2c_template" +R"w2c_template( TRUNC_SAT_U(u64, f64, (f64)UINT64_MAX, UINT64_MAX, x) +)w2c_template" +R"w2c_template( +#define DEFINE_REINTERPRET(name, t1, t2) \ +)w2c_template" +R"w2c_template( static inline t2 name(t1 x) { \ +)w2c_template" +R"w2c_template( t2 result; \ +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&result, &x, sizeof(result)); \ +)w2c_template" +R"w2c_template( return result; \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +DEFINE_REINTERPRET(f32_reinterpret_i32, u32, f32) +)w2c_template" +R"w2c_template(DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32) +)w2c_template" +R"w2c_template(DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64) +)w2c_template" +R"w2c_template(DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64) +)w2c_template" +R"w2c_template( +static float quiet_nanf(float x) { +)w2c_template" +R"w2c_template( uint32_t tmp; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&tmp, &x, 4); +)w2c_template" +R"w2c_template( tmp |= 0x7fc00000lu; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&x, &tmp, 4); +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double quiet_nan(double x) { +)w2c_template" +R"w2c_template( uint64_t tmp; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&tmp, &x, 8); +)w2c_template" +R"w2c_template( tmp |= 0x7ff8000000000000llu; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&x, &tmp, 8); +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_quiet(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_quietf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_floor(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return floor(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_floorf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return floorf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_ceil(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return ceil(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_ceilf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return ceilf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_trunc(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return trunc(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_truncf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return truncf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_nearbyintf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return nearbyintf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_nearbyint(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return nearbyint(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_fabsf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( uint32_t tmp; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&tmp, &x, 4); +)w2c_template" +R"w2c_template( tmp = tmp & ~(1UL << 31); +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&x, &tmp, 4); +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return fabsf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_fabs(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( uint64_t tmp; +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&tmp, &x, 8); +)w2c_template" +R"w2c_template( tmp = tmp & ~(1ULL << 63); +)w2c_template" +R"w2c_template( wasm_rt_memcpy(&x, &tmp, 8); +)w2c_template" +R"w2c_template( return x; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return fabs(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static double wasm_sqrt(double x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nan(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return sqrt(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static float wasm_sqrtf(float x) { +)w2c_template" +R"w2c_template( if (UNLIKELY(isnan(x))) { +)w2c_template" +R"w2c_template( return quiet_nanf(x); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( return sqrtf(x); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { +)w2c_template" +R"w2c_template( RANGE_CHECK(mem, d, n); +)w2c_template" +R"w2c_template( memset(MEM_ADDR(mem, d, n), val, n); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline void memory_copy(wasm_rt_memory_t* dest, +)w2c_template" +R"w2c_template( const wasm_rt_memory_t* src, +)w2c_template" +R"w2c_template( u64 dest_addr, +)w2c_template" +R"w2c_template( u64 src_addr, +)w2c_template" +R"w2c_template( u64 n) { +)w2c_template" +R"w2c_template( RANGE_CHECK(dest, dest_addr, n); +)w2c_template" +R"w2c_template( RANGE_CHECK(src, src_addr, n); +)w2c_template" +R"w2c_template( memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline void memory_init(wasm_rt_memory_t* dest, +)w2c_template" +R"w2c_template( const u8* src, +)w2c_template" +R"w2c_template( u32 src_size, +)w2c_template" +R"w2c_template( u64 dest_addr, +)w2c_template" +R"w2c_template( u32 src_addr, +)w2c_template" +R"w2c_template( u32 n) { +)w2c_template" +R"w2c_template( if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +)w2c_template" +R"w2c_template( TRAP(OOB); +)w2c_template" +R"w2c_template( LOAD_DATA((*dest), dest_addr, src + src_addr, n); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; +)w2c_template" +R"w2c_template( +typedef struct { +)w2c_template" +R"w2c_template( wasm_elem_segment_expr_type_t expr_type; +)w2c_template" +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; +)w2c_template" +R"w2c_template( +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, +)w2c_template" +R"w2c_template( const wasm_elem_segment_expr_t* src, +)w2c_template" +R"w2c_template( u32 src_size, +)w2c_template" +R"w2c_template( u64 dest_addr, +)w2c_template" +R"w2c_template( u32 src_addr, +)w2c_template" +R"w2c_template( u32 n, +)w2c_template" +R"w2c_template( void* module_instance) { +)w2c_template" +R"w2c_template( if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +)w2c_template" +R"w2c_template( TRAP(OOB); +)w2c_template" +R"w2c_template( RANGE_CHECK(dest, dest_addr, n); +)w2c_template" +R"w2c_template( for (u32 i = 0; i < n; i++) { +)w2c_template" +R"w2c_template( const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; +)w2c_template" +R"w2c_template( wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); +)w2c_template" +R"w2c_template( switch (src_expr->expr_type) { +)w2c_template" +R"w2c_template( case RefFunc: +)w2c_template" +R"w2c_template( *dest_val = (wasm_rt_funcref_t){ +)w2c_template" +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}; +)w2c_template" +R"w2c_template( break; +)w2c_template" +R"w2c_template( case RefNull: +)w2c_template" +R"w2c_template( *dest_val = wasm_rt_funcref_null_value; +)w2c_template" +R"w2c_template( break; +)w2c_template" +R"w2c_template( case GlobalGet: +)w2c_template" +R"w2c_template( *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + +)w2c_template" +R"w2c_template( src_expr->module_offset); +)w2c_template" +R"w2c_template( break; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +// Currently wasm2c only supports initializing externref tables with ref.null. +)w2c_template" +R"w2c_template(static inline void externref_table_init(wasm_rt_externref_table_t* dest, +)w2c_template" +R"w2c_template( u32 src_size, +)w2c_template" +R"w2c_template( u64 dest_addr, +)w2c_template" +R"w2c_template( u32 src_addr, +)w2c_template" +R"w2c_template( u32 n) { +)w2c_template" +R"w2c_template( if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +)w2c_template" +R"w2c_template( TRAP(OOB); +)w2c_template" +R"w2c_template( RANGE_CHECK(dest, dest_addr, n); +)w2c_template" +R"w2c_template( for (u32 i = 0; i < n; i++) { +)w2c_template" +R"w2c_template( dest->data[dest_addr + i] = wasm_rt_externref_null_value; +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define DEFINE_TABLE_COPY(type) \ +)w2c_template" +R"w2c_template( static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ +)w2c_template" +R"w2c_template( const wasm_rt_##type##_table_t* src, \ +)w2c_template" +R"w2c_template( u64 dest_addr, u64 src_addr, u64 n) { \ +)w2c_template" +R"w2c_template( RANGE_CHECK(dest, dest_addr, n); \ +)w2c_template" +R"w2c_template( RANGE_CHECK(src, src_addr, n); \ +)w2c_template" +R"w2c_template( memmove(dest->data + dest_addr, src->data + src_addr, \ +)w2c_template" +R"w2c_template( n * sizeof(wasm_rt_##type##_t)); \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +DEFINE_TABLE_COPY(funcref) +)w2c_template" +R"w2c_template(DEFINE_TABLE_COPY(externref) +)w2c_template" +R"w2c_template( +#define DEFINE_TABLE_GET(type) \ +)w2c_template" +R"w2c_template( static inline wasm_rt_##type##_t type##_table_get( \ +)w2c_template" +R"w2c_template( const wasm_rt_##type##_table_t* table, u64 i) { \ +)w2c_template" +R"w2c_template( if (UNLIKELY(i >= table->size)) \ +)w2c_template" +R"w2c_template( TRAP(OOB); \ +)w2c_template" +R"w2c_template( return table->data[i]; \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +DEFINE_TABLE_GET(funcref) +)w2c_template" +R"w2c_template(DEFINE_TABLE_GET(externref) +)w2c_template" +R"w2c_template( +#define DEFINE_TABLE_SET(type) \ +)w2c_template" +R"w2c_template( static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ +)w2c_template" +R"w2c_template( u64 i, const wasm_rt_##type##_t val) { \ +)w2c_template" +R"w2c_template( if (UNLIKELY(i >= table->size)) \ +)w2c_template" +R"w2c_template( TRAP(OOB); \ +)w2c_template" +R"w2c_template( table->data[i] = val; \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +DEFINE_TABLE_SET(funcref) +)w2c_template" +R"w2c_template(DEFINE_TABLE_SET(externref) +)w2c_template" +R"w2c_template( +#define DEFINE_TABLE_FILL(type) \ +)w2c_template" +R"w2c_template( static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ +)w2c_template" +R"w2c_template( u64 d, const wasm_rt_##type##_t val, \ +)w2c_template" +R"w2c_template( u64 n) { \ +)w2c_template" +R"w2c_template( RANGE_CHECK(table, d, n); \ +)w2c_template" +R"w2c_template( for (uint32_t i = d; i < d + n; i++) { \ +)w2c_template" +R"w2c_template( table->data[i] = val; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( +DEFINE_TABLE_FILL(funcref) +)w2c_template" +R"w2c_template(DEFINE_TABLE_FILL(externref) +)w2c_template" +R"w2c_template( +#if defined(__GNUC__) || defined(__clang__) +)w2c_template" +R"w2c_template(#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +)w2c_template" +R"w2c_template(#define FUNC_TYPE_EXTERN_T(x) const char* const x +)w2c_template" +R"w2c_template(#define FUNC_TYPE_T(x) static const char* const x +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +)w2c_template" +R"w2c_template(#define FUNC_TYPE_EXTERN_T(x) const char x[] +)w2c_template" +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 static_assert(X) \ +)w2c_template" +R"w2c_template( extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +#ifdef _MSC_VER +)w2c_template" +R"w2c_template(#define WEAK_FUNC_DECL(func, fallback) \ +)w2c_template" +R"w2c_template( __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ +)w2c_template" +R"w2c_template( \ +)w2c_template" +R"w2c_template( void \ +)w2c_template" +R"w2c_template( fallback(void** instance_ptr, void* tail_call_stack, \ +)w2c_template" +R"w2c_template( wasm_rt_tailcallee_t* next) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define WEAK_FUNC_DECL(func, fallback) \ +)w2c_template" +R"w2c_template( __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ +)w2c_template" +R"w2c_template( wasm_rt_tailcallee_t* next) +)w2c_template" +R"w2c_template(#endif +)w2c_template" +; diff --git a/src/prebuilt/wasm2c_source_includes.cc b/src/prebuilt/wasm2c_source_includes.cc new file mode 100644 index 00000000000..a17c5511b7c --- /dev/null +++ b/src/prebuilt/wasm2c_source_includes.cc @@ -0,0 +1,33 @@ +const char* s_source_includes = R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#if defined(__MINGW32__) +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#elif defined(_MSC_VER) +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#define alloca _alloca +)w2c_template" +R"w2c_template(#elif defined(__FreeBSD__) || defined(__OpenBSD__) +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#endif +)w2c_template" +; diff --git a/src/templates/wasm2c.bottom.h b/src/templates/wasm2c.bottom.h new file mode 100644 index 00000000000..d4aa34d6885 --- /dev/null +++ b/src/templates/wasm2c.bottom.h @@ -0,0 +1,3 @@ +#ifdef __cplusplus +} +#endif diff --git a/src/templates/wasm2c.declarations.c b/src/templates/wasm2c.declarations.c new file mode 100644 index 00000000000..a0cd5a7c827 --- /dev/null +++ b/src/templates/wasm2c.declarations.c @@ -0,0 +1,725 @@ + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif diff --git a/src/templates/wasm2c.includes.c b/src/templates/wasm2c.includes.c new file mode 100644 index 00000000000..bc5a1c96269 --- /dev/null +++ b/src/templates/wasm2c.includes.c @@ -0,0 +1,16 @@ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif diff --git a/src/templates/wasm2c.top.h b/src/templates/wasm2c.top.h new file mode 100644 index 00000000000..e14197ab669 --- /dev/null +++ b/src/templates/wasm2c.top.h @@ -0,0 +1,19 @@ +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif diff --git a/src/templates/wasm2c_atomicops.declarations.c b/src/templates/wasm2c_atomicops.declarations.c new file mode 100644 index 00000000000..cb25da5135b --- /dev/null +++ b/src/templates/wasm2c_atomicops.declarations.c @@ -0,0 +1,237 @@ +#include + +#ifndef WASM_RT_C11_AVAILABLE +#error "C11 is required for Wasm threads and shared memory support" +#endif + +#define ATOMIC_ALIGNMENT_CHECK(addr, t1) \ + if (UNLIKELY(addr % sizeof(t1))) { \ + TRAP(UNALIGNED); \ + } + +#define DEFINE_SHARED_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_shared_memory_t* mem, u64 addr) { \ + t1 result; \ + result = atomic_load_explicit( \ + (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), \ + memory_order_relaxed); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _shared_, t1, return, t3) + +DEFINE_SHARED_LOAD(i32_load_shared, u32, u32, u32, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load_shared, u64, u64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(f32_load_shared, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_SHARED_LOAD(f64_load_shared, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_SHARED_LOAD(i32_load8_s_shared, s8, s32, u32, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load8_s_shared, s8, s64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i32_load8_u_shared, u8, u32, u32, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load8_u_shared, u8, u64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i32_load16_s_shared, s16, s32, u32, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load16_s_shared, s16, s64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i32_load16_u_shared, u16, u32, u32, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load16_u_shared, u16, u64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load32_s_shared, s32, s64, u64, FORCE_READ_INT) +DEFINE_SHARED_LOAD(i64_load32_u_shared, u32, u64, u64, FORCE_READ_INT) + +#define DEFINE_SHARED_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_shared_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + atomic_store_explicit( \ + (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped, \ + memory_order_relaxed); \ + } \ + DEF_MEM_CHECKS1(name, _shared_, t1, , void, t2) + +DEFINE_SHARED_STORE(i32_store_shared, u32, u32) +DEFINE_SHARED_STORE(i64_store_shared, u64, u64) +DEFINE_SHARED_STORE(f32_store_shared, f32, f32) +DEFINE_SHARED_STORE(f64_store_shared, f64, f64) +DEFINE_SHARED_STORE(i32_store8_shared, u8, u32) +DEFINE_SHARED_STORE(i32_store16_shared, u16, u32) +DEFINE_SHARED_STORE(i64_store8_shared, u8, u64) +DEFINE_SHARED_STORE(i64_store16_shared, u16, u64) +DEFINE_SHARED_STORE(i64_store32_shared, u32, u64) + +#define DEFINE_ATOMIC_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) \ + static inline t3 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ + u64 addr) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 result; \ + result = \ + atomic_load((_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1))); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name##_shared, _shared_, t1, return, t3) + +DEFINE_ATOMIC_LOAD(i32_atomic_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i64_atomic_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i32_atomic_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i64_atomic_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i32_atomic_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i64_atomic_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_ATOMIC_LOAD(i64_atomic_load32_u, u32, u64, u64, FORCE_READ_INT) + +#define DEFINE_ATOMIC_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &wrapped, sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) \ + static inline void name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ + u64 addr, t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + atomic_store((_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), \ + wrapped); \ + } \ + DEF_MEM_CHECKS1(name##_shared, _shared_, t1, , void, t2) + +DEFINE_ATOMIC_STORE(i32_atomic_store, u32, u32) +DEFINE_ATOMIC_STORE(i64_atomic_store, u64, u64) +DEFINE_ATOMIC_STORE(i32_atomic_store8, u8, u32) +DEFINE_ATOMIC_STORE(i32_atomic_store16, u16, u32) +DEFINE_ATOMIC_STORE(i64_atomic_store8, u8, u64) +DEFINE_ATOMIC_STORE(i64_atomic_store16, u16, u64) +DEFINE_ATOMIC_STORE(i64_atomic_store32, u32, u64) + +#define DEFINE_ATOMIC_RMW(name, opname, op, t1, t2) \ + static inline t2 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + t1 ret; \ + wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ + ret = ret op wrapped; \ + wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &ret, sizeof(t1)); \ + return (t2)ret; \ + } \ + DEF_MEM_CHECKS1(name, _, t1, return, t2, t2) \ + static inline t2 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ + u64 addr, t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + t1 ret = atomic_##opname( \ + (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped); \ + return (t2)ret; \ + } \ + DEF_MEM_CHECKS1(name##_shared, _shared_, t1, return, t2, t2) + +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_add_u, fetch_add, +, u8, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw16_add_u, fetch_add, +, u16, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw_add, fetch_add, +, u32, u32) +DEFINE_ATOMIC_RMW(i64_atomic_rmw8_add_u, fetch_add, +, u8, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw16_add_u, fetch_add, +, u16, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw32_add_u, fetch_add, +, u32, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw_add, fetch_add, +, u64, u64) + +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_sub_u, fetch_sub, -, u8, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw16_sub_u, fetch_sub, -, u16, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw_sub, fetch_sub, -, u32, u32) +DEFINE_ATOMIC_RMW(i64_atomic_rmw8_sub_u, fetch_sub, -, u8, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw16_sub_u, fetch_sub, -, u16, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw32_sub_u, fetch_sub, -, u32, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw_sub, fetch_sub, -, u64, u64) + +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_and_u, fetch_and, &, u8, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw16_and_u, fetch_and, &, u16, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw_and, fetch_and, &, u32, u32) +DEFINE_ATOMIC_RMW(i64_atomic_rmw8_and_u, fetch_and, &, u8, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw16_and_u, fetch_and, &, u16, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw32_and_u, fetch_and, &, u32, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw_and, fetch_and, &, u64, u64) + +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_or_u, fetch_or, |, u8, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw16_or_u, fetch_or, |, u16, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw_or, fetch_or, |, u32, u32) +DEFINE_ATOMIC_RMW(i64_atomic_rmw8_or_u, fetch_or, |, u8, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw16_or_u, fetch_or, |, u16, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw32_or_u, fetch_or, |, u32, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw_or, fetch_or, |, u64, u64) + +DEFINE_ATOMIC_RMW(i32_atomic_rmw8_xor_u, fetch_xor, ^, u8, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw16_xor_u, fetch_xor, ^, u16, u32) +DEFINE_ATOMIC_RMW(i32_atomic_rmw_xor, fetch_xor, ^, u32, u32) +DEFINE_ATOMIC_RMW(i64_atomic_rmw8_xor_u, fetch_xor, ^, u8, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw16_xor_u, fetch_xor, ^, u16, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw32_xor_u, fetch_xor, ^, u32, u64) +DEFINE_ATOMIC_RMW(i64_atomic_rmw_xor, fetch_xor, ^, u64, u64) + +#define DEFINE_ATOMIC_XCHG(name, opname, t1, t2) \ + static inline t2 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + t1 ret; \ + wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t1)), sizeof(t1)); \ + wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t1)), &wrapped, sizeof(t1)); \ + return (t2)ret; \ + } \ + DEF_MEM_CHECKS1(name, _, t1, return, t2, t2) \ + static inline t2 name##_shared_unchecked(wasm_rt_shared_memory_t* mem, \ + u64 addr, t2 value) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t1); \ + t1 wrapped = (t1)value; \ + t1 ret = atomic_##opname( \ + (_Atomic volatile t1*)MEM_ADDR(mem, addr, sizeof(t1)), wrapped); \ + return (t2)ret; \ + } \ + DEF_MEM_CHECKS1(name##_shared, _shared_, t1, return, t2, t2) + +DEFINE_ATOMIC_XCHG(i32_atomic_rmw8_xchg_u, exchange, u8, u32) +DEFINE_ATOMIC_XCHG(i32_atomic_rmw16_xchg_u, exchange, u16, u32) +DEFINE_ATOMIC_XCHG(i32_atomic_rmw_xchg, exchange, u32, u32) +DEFINE_ATOMIC_XCHG(i64_atomic_rmw8_xchg_u, exchange, u8, u64) +DEFINE_ATOMIC_XCHG(i64_atomic_rmw16_xchg_u, exchange, u16, u64) +DEFINE_ATOMIC_XCHG(i64_atomic_rmw32_xchg_u, exchange, u32, u64) +DEFINE_ATOMIC_XCHG(i64_atomic_rmw_xchg, exchange, u64, u64) + +#define DEFINE_ATOMIC_CMP_XCHG(name, t1, t2) \ + static inline t1 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t1 expected, t1 replacement) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t2); \ + t2 expected_wrapped = (t2)expected; \ + t2 replacement_wrapped = (t2)replacement; \ + t2 ret; \ + wasm_rt_memcpy(&ret, MEM_ADDR(mem, addr, sizeof(t2)), sizeof(t2)); \ + if (ret == expected_wrapped) { \ + wasm_rt_memcpy(MEM_ADDR(mem, addr, sizeof(t2)), &replacement_wrapped, \ + sizeof(t2)); \ + } \ + return (t1)expected_wrapped; \ + } \ + DEF_MEM_CHECKS2(name, _, t2, return, t1, t1, t1) \ + static inline t1 name##_shared_unchecked( \ + wasm_rt_shared_memory_t* mem, u64 addr, t1 expected, t1 replacement) { \ + ATOMIC_ALIGNMENT_CHECK(addr, t2); \ + t2 expected_wrapped = (t2)expected; \ + t2 replacement_wrapped = (t2)replacement; \ + atomic_compare_exchange_strong( \ + (_Atomic volatile t2*)MEM_ADDR(mem, addr, sizeof(t2)), \ + &expected_wrapped, replacement_wrapped); \ + return (t1)expected_wrapped; \ + } \ + DEF_MEM_CHECKS2(name##_shared, _shared_, t2, return, t1, t1, t1) + +DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw8_cmpxchg_u, u32, u8); +DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw16_cmpxchg_u, u32, u16); +DEFINE_ATOMIC_CMP_XCHG(i32_atomic_rmw_cmpxchg, u32, u32); +DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw8_cmpxchg_u, u64, u8); +DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw16_cmpxchg_u, u64, u16); +DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw32_cmpxchg_u, u64, u32); +DEFINE_ATOMIC_CMP_XCHG(i64_atomic_rmw_cmpxchg, u64, u64); + +#define atomic_fence() atomic_thread_fence(memory_order_seq_cst) diff --git a/src/templates/wasm2c_simd.declarations.c b/src/templates/wasm2c_simd.declarations.c new file mode 100644 index 00000000000..abf3ce0f6e1 --- /dev/null +++ b/src/templates/wasm2c_simd.declarations.c @@ -0,0 +1,342 @@ +#if defined(__GNUC__) && defined(__x86_64__) +#define SIMD_FORCE_READ(var) __asm__("" ::"x"(var)); +#elif defined(__GNUC__) && defined(__aarch64__) +#define SIMD_FORCE_READ(var) __asm__("" ::"w"(var)); +#elif defined(__s390x__) +#define SIMD_FORCE_READ(var) __asm__("" ::"d"(var)); +#else +#define SIMD_FORCE_READ(var) +#endif +// TODO: equivalent constraint for ARM and other architectures + +#define DEFINE_SIMD_LOAD_FUNC(name, func, t) \ + static inline v128 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + v128 result = func(MEM_ADDR(mem, addr, sizeof(t))); \ + SIMD_FORCE_READ(result); \ + return result; \ + } \ + DEF_MEM_CHECKS0(name, _, t, return, v128); + +#define DEFINE_SIMD_LOAD_LANE(name, func, t, lane) \ + static inline v128 name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + v128 vec) { \ + v128 result = func(MEM_ADDR(mem, addr, sizeof(t)), vec, lane); \ + SIMD_FORCE_READ(result); \ + return result; \ + } \ + DEF_MEM_CHECKS1(name, _, t, return, v128, v128); + +#define DEFINE_SIMD_STORE(name, t) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + v128 value) { \ + simde_wasm_v128_store(MEM_ADDR(mem, addr, sizeof(t)), value); \ + } \ + DEF_MEM_CHECKS1(name, _, t, , void, v128); + +#define DEFINE_SIMD_STORE_LANE(name, func, t, lane) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + v128 value) { \ + func(MEM_ADDR(mem, addr, sizeof(t)), value, lane); \ + } \ + DEF_MEM_CHECKS1(name, _, t, , void, v128); + +// clang-format off +#if WABT_BIG_ENDIAN +static inline v128 v128_impl_load32_zero(const void* a) { + return simde_wasm_i8x16_swizzle( + simde_wasm_v128_load32_zero(a), + simde_wasm_i8x16_const(12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3)); +} +static inline v128 v128_impl_load64_zero(const void* a) { + return simde_wasm_i8x16_swizzle( + simde_wasm_v128_load64_zero(a), + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)); +} +#else +#define v128_impl_load32_zero simde_wasm_v128_load32_zero +#define v128_impl_load64_zero simde_wasm_v128_load64_zero +#endif + +DEFINE_SIMD_LOAD_FUNC(v128_load, simde_wasm_v128_load, v128) + +DEFINE_SIMD_LOAD_FUNC(v128_load8_splat, simde_wasm_v128_load8_splat, u8) +DEFINE_SIMD_LOAD_FUNC(v128_load16_splat, simde_wasm_v128_load16_splat, u16) +DEFINE_SIMD_LOAD_FUNC(v128_load32_splat, simde_wasm_v128_load32_splat, u32) +DEFINE_SIMD_LOAD_FUNC(v128_load64_splat, simde_wasm_v128_load64_splat, u64) + +DEFINE_SIMD_LOAD_FUNC(i16x8_load8x8, simde_wasm_i16x8_load8x8, u64) +DEFINE_SIMD_LOAD_FUNC(u16x8_load8x8, simde_wasm_u16x8_load8x8, u64) +DEFINE_SIMD_LOAD_FUNC(i32x4_load16x4, simde_wasm_i32x4_load16x4, u64) +DEFINE_SIMD_LOAD_FUNC(u32x4_load16x4, simde_wasm_u32x4_load16x4, u64) +DEFINE_SIMD_LOAD_FUNC(i64x2_load32x2, simde_wasm_i64x2_load32x2, u64) +DEFINE_SIMD_LOAD_FUNC(u64x2_load32x2, simde_wasm_u64x2_load32x2, u64) + +DEFINE_SIMD_LOAD_FUNC(v128_load32_zero, v128_impl_load32_zero, u32) +DEFINE_SIMD_LOAD_FUNC(v128_load64_zero, v128_impl_load64_zero, u64) + +#if WABT_BIG_ENDIAN +DEFINE_SIMD_LOAD_LANE(v128_load8_lane0, simde_wasm_v128_load8_lane, u8, 15) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane1, simde_wasm_v128_load8_lane, u8, 14) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane2, simde_wasm_v128_load8_lane, u8, 13) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane3, simde_wasm_v128_load8_lane, u8, 12) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane4, simde_wasm_v128_load8_lane, u8, 11) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane5, simde_wasm_v128_load8_lane, u8, 10) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane6, simde_wasm_v128_load8_lane, u8, 9) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane7, simde_wasm_v128_load8_lane, u8, 8) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane8, simde_wasm_v128_load8_lane, u8, 7) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane9, simde_wasm_v128_load8_lane, u8, 6) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane10, simde_wasm_v128_load8_lane, u8, 5) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane11, simde_wasm_v128_load8_lane, u8, 4) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane12, simde_wasm_v128_load8_lane, u8, 3) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane13, simde_wasm_v128_load8_lane, u8, 2) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane14, simde_wasm_v128_load8_lane, u8, 1) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane15, simde_wasm_v128_load8_lane, u8, 0) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane0, simde_wasm_v128_load16_lane, u16, 7) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane1, simde_wasm_v128_load16_lane, u16, 6) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane2, simde_wasm_v128_load16_lane, u16, 5) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane3, simde_wasm_v128_load16_lane, u16, 4) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane4, simde_wasm_v128_load16_lane, u16, 3) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane5, simde_wasm_v128_load16_lane, u16, 2) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane6, simde_wasm_v128_load16_lane, u16, 1) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane7, simde_wasm_v128_load16_lane, u16, 0) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane0, simde_wasm_v128_load32_lane, u32, 3) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane1, simde_wasm_v128_load32_lane, u32, 2) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane2, simde_wasm_v128_load32_lane, u32, 1) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane3, simde_wasm_v128_load32_lane, u32, 0) +DEFINE_SIMD_LOAD_LANE(v128_load64_lane0, simde_wasm_v128_load64_lane, u64, 1) +DEFINE_SIMD_LOAD_LANE(v128_load64_lane1, simde_wasm_v128_load64_lane, u64, 0) +#else +DEFINE_SIMD_LOAD_LANE(v128_load8_lane0, simde_wasm_v128_load8_lane, u8, 0) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane1, simde_wasm_v128_load8_lane, u8, 1) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane2, simde_wasm_v128_load8_lane, u8, 2) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane3, simde_wasm_v128_load8_lane, u8, 3) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane4, simde_wasm_v128_load8_lane, u8, 4) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane5, simde_wasm_v128_load8_lane, u8, 5) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane6, simde_wasm_v128_load8_lane, u8, 6) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane7, simde_wasm_v128_load8_lane, u8, 7) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane8, simde_wasm_v128_load8_lane, u8, 8) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane9, simde_wasm_v128_load8_lane, u8, 9) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane10, simde_wasm_v128_load8_lane, u8, 10) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane11, simde_wasm_v128_load8_lane, u8, 11) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane12, simde_wasm_v128_load8_lane, u8, 12) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane13, simde_wasm_v128_load8_lane, u8, 13) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane14, simde_wasm_v128_load8_lane, u8, 14) +DEFINE_SIMD_LOAD_LANE(v128_load8_lane15, simde_wasm_v128_load8_lane, u8, 15) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane0, simde_wasm_v128_load16_lane, u16, 0) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane1, simde_wasm_v128_load16_lane, u16, 1) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane2, simde_wasm_v128_load16_lane, u16, 2) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane3, simde_wasm_v128_load16_lane, u16, 3) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane4, simde_wasm_v128_load16_lane, u16, 4) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane5, simde_wasm_v128_load16_lane, u16, 5) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane6, simde_wasm_v128_load16_lane, u16, 6) +DEFINE_SIMD_LOAD_LANE(v128_load16_lane7, simde_wasm_v128_load16_lane, u16, 7) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane0, simde_wasm_v128_load32_lane, u32, 0) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane1, simde_wasm_v128_load32_lane, u32, 1) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane2, simde_wasm_v128_load32_lane, u32, 2) +DEFINE_SIMD_LOAD_LANE(v128_load32_lane3, simde_wasm_v128_load32_lane, u32, 3) +DEFINE_SIMD_LOAD_LANE(v128_load64_lane0, simde_wasm_v128_load64_lane, u64, 0) +DEFINE_SIMD_LOAD_LANE(v128_load64_lane1, simde_wasm_v128_load64_lane, u64, 1) +#endif + +DEFINE_SIMD_STORE(v128_store, v128) + +#if WABT_BIG_ENDIAN +DEFINE_SIMD_STORE_LANE(v128_store8_lane0, simde_wasm_v128_store8_lane, u8, 15) +DEFINE_SIMD_STORE_LANE(v128_store8_lane1, simde_wasm_v128_store8_lane, u8, 14) +DEFINE_SIMD_STORE_LANE(v128_store8_lane2, simde_wasm_v128_store8_lane, u8, 13) +DEFINE_SIMD_STORE_LANE(v128_store8_lane3, simde_wasm_v128_store8_lane, u8, 12) +DEFINE_SIMD_STORE_LANE(v128_store8_lane4, simde_wasm_v128_store8_lane, u8, 11) +DEFINE_SIMD_STORE_LANE(v128_store8_lane5, simde_wasm_v128_store8_lane, u8, 10) +DEFINE_SIMD_STORE_LANE(v128_store8_lane6, simde_wasm_v128_store8_lane, u8, 9) +DEFINE_SIMD_STORE_LANE(v128_store8_lane7, simde_wasm_v128_store8_lane, u8, 8) +DEFINE_SIMD_STORE_LANE(v128_store8_lane8, simde_wasm_v128_store8_lane, u8, 7) +DEFINE_SIMD_STORE_LANE(v128_store8_lane9, simde_wasm_v128_store8_lane, u8, 6) +DEFINE_SIMD_STORE_LANE(v128_store8_lane10, simde_wasm_v128_store8_lane, u8, 5) +DEFINE_SIMD_STORE_LANE(v128_store8_lane11, simde_wasm_v128_store8_lane, u8, 4) +DEFINE_SIMD_STORE_LANE(v128_store8_lane12, simde_wasm_v128_store8_lane, u8, 3) +DEFINE_SIMD_STORE_LANE(v128_store8_lane13, simde_wasm_v128_store8_lane, u8, 2) +DEFINE_SIMD_STORE_LANE(v128_store8_lane14, simde_wasm_v128_store8_lane, u8, 1) +DEFINE_SIMD_STORE_LANE(v128_store8_lane15, simde_wasm_v128_store8_lane, u8, 0) +DEFINE_SIMD_STORE_LANE(v128_store16_lane0, simde_wasm_v128_store16_lane, u16, 7) +DEFINE_SIMD_STORE_LANE(v128_store16_lane1, simde_wasm_v128_store16_lane, u16, 6) +DEFINE_SIMD_STORE_LANE(v128_store16_lane2, simde_wasm_v128_store16_lane, u16, 5) +DEFINE_SIMD_STORE_LANE(v128_store16_lane3, simde_wasm_v128_store16_lane, u16, 4) +DEFINE_SIMD_STORE_LANE(v128_store16_lane4, simde_wasm_v128_store16_lane, u16, 3) +DEFINE_SIMD_STORE_LANE(v128_store16_lane5, simde_wasm_v128_store16_lane, u16, 2) +DEFINE_SIMD_STORE_LANE(v128_store16_lane6, simde_wasm_v128_store16_lane, u16, 1) +DEFINE_SIMD_STORE_LANE(v128_store16_lane7, simde_wasm_v128_store16_lane, u16, 0) +DEFINE_SIMD_STORE_LANE(v128_store32_lane0, simde_wasm_v128_store32_lane, u32, 3) +DEFINE_SIMD_STORE_LANE(v128_store32_lane1, simde_wasm_v128_store32_lane, u32, 2) +DEFINE_SIMD_STORE_LANE(v128_store32_lane2, simde_wasm_v128_store32_lane, u32, 1) +DEFINE_SIMD_STORE_LANE(v128_store32_lane3, simde_wasm_v128_store32_lane, u32, 0) +DEFINE_SIMD_STORE_LANE(v128_store64_lane0, simde_wasm_v128_store64_lane, u64, 1) +DEFINE_SIMD_STORE_LANE(v128_store64_lane1, simde_wasm_v128_store64_lane, u64, 0) +#else +DEFINE_SIMD_STORE_LANE(v128_store8_lane0, simde_wasm_v128_store8_lane, u8, 0) +DEFINE_SIMD_STORE_LANE(v128_store8_lane1, simde_wasm_v128_store8_lane, u8, 1) +DEFINE_SIMD_STORE_LANE(v128_store8_lane2, simde_wasm_v128_store8_lane, u8, 2) +DEFINE_SIMD_STORE_LANE(v128_store8_lane3, simde_wasm_v128_store8_lane, u8, 3) +DEFINE_SIMD_STORE_LANE(v128_store8_lane4, simde_wasm_v128_store8_lane, u8, 4) +DEFINE_SIMD_STORE_LANE(v128_store8_lane5, simde_wasm_v128_store8_lane, u8, 5) +DEFINE_SIMD_STORE_LANE(v128_store8_lane6, simde_wasm_v128_store8_lane, u8, 6) +DEFINE_SIMD_STORE_LANE(v128_store8_lane7, simde_wasm_v128_store8_lane, u8, 7) +DEFINE_SIMD_STORE_LANE(v128_store8_lane8, simde_wasm_v128_store8_lane, u8, 8) +DEFINE_SIMD_STORE_LANE(v128_store8_lane9, simde_wasm_v128_store8_lane, u8, 9) +DEFINE_SIMD_STORE_LANE(v128_store8_lane10, simde_wasm_v128_store8_lane, u8, 10) +DEFINE_SIMD_STORE_LANE(v128_store8_lane11, simde_wasm_v128_store8_lane, u8, 11) +DEFINE_SIMD_STORE_LANE(v128_store8_lane12, simde_wasm_v128_store8_lane, u8, 12) +DEFINE_SIMD_STORE_LANE(v128_store8_lane13, simde_wasm_v128_store8_lane, u8, 13) +DEFINE_SIMD_STORE_LANE(v128_store8_lane14, simde_wasm_v128_store8_lane, u8, 14) +DEFINE_SIMD_STORE_LANE(v128_store8_lane15, simde_wasm_v128_store8_lane, u8, 15) +DEFINE_SIMD_STORE_LANE(v128_store16_lane0, simde_wasm_v128_store16_lane, u16, 0) +DEFINE_SIMD_STORE_LANE(v128_store16_lane1, simde_wasm_v128_store16_lane, u16, 1) +DEFINE_SIMD_STORE_LANE(v128_store16_lane2, simde_wasm_v128_store16_lane, u16, 2) +DEFINE_SIMD_STORE_LANE(v128_store16_lane3, simde_wasm_v128_store16_lane, u16, 3) +DEFINE_SIMD_STORE_LANE(v128_store16_lane4, simde_wasm_v128_store16_lane, u16, 4) +DEFINE_SIMD_STORE_LANE(v128_store16_lane5, simde_wasm_v128_store16_lane, u16, 5) +DEFINE_SIMD_STORE_LANE(v128_store16_lane6, simde_wasm_v128_store16_lane, u16, 6) +DEFINE_SIMD_STORE_LANE(v128_store16_lane7, simde_wasm_v128_store16_lane, u16, 7) +DEFINE_SIMD_STORE_LANE(v128_store32_lane0, simde_wasm_v128_store32_lane, u32, 0) +DEFINE_SIMD_STORE_LANE(v128_store32_lane1, simde_wasm_v128_store32_lane, u32, 1) +DEFINE_SIMD_STORE_LANE(v128_store32_lane2, simde_wasm_v128_store32_lane, u32, 2) +DEFINE_SIMD_STORE_LANE(v128_store32_lane3, simde_wasm_v128_store32_lane, u32, 3) +DEFINE_SIMD_STORE_LANE(v128_store64_lane0, simde_wasm_v128_store64_lane, u64, 0) +DEFINE_SIMD_STORE_LANE(v128_store64_lane1, simde_wasm_v128_store64_lane, u64, 1) +#endif + +#if WABT_BIG_ENDIAN +#define v128_const(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) simde_wasm_i8x16_const(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +#define v128_i8x16_extract_lane(v, l) simde_wasm_i8x16_extract_lane(v, 15-(l)) +#define v128_u8x16_extract_lane(v, l) simde_wasm_u8x16_extract_lane(v, 15-(l)) +#define v128_i16x8_extract_lane(v, l) simde_wasm_i16x8_extract_lane(v, 7-(l)) +#define v128_u16x8_extract_lane(v, l) simde_wasm_u16x8_extract_lane(v, 7-(l)) +#define v128_i32x4_extract_lane(v, l) simde_wasm_i32x4_extract_lane(v, 3-(l)) +#define v128_i64x2_extract_lane(v, l) simde_wasm_i64x2_extract_lane(v, 1-(l)) +#define v128_f32x4_extract_lane(v, l) simde_wasm_f32x4_extract_lane(v, 3-(l)) +#define v128_f64x2_extract_lane(v, l) simde_wasm_f64x2_extract_lane(v, 1-(l)) +#define v128_i8x16_replace_lane(v, l, x) simde_wasm_i8x16_replace_lane(v, 15-(l), x) +#define v128_u8x16_replace_lane(v, l, x) simde_wasm_u8x16_replace_lane(v, 15-(l), x) +#define v128_i16x8_replace_lane(v, l, x) simde_wasm_i16x8_replace_lane(v, 7-(l), x) +#define v128_u16x8_replace_lane(v, l, x) simde_wasm_u16x8_replace_lane(v, 7-(l), x) +#define v128_i32x4_replace_lane(v, l, x) simde_wasm_i32x4_replace_lane(v, 3-(l), x) +#define v128_i64x2_replace_lane(v, l, x) simde_wasm_i64x2_replace_lane(v, 1-(l), x) +#define v128_f32x4_replace_lane(v, l, x) simde_wasm_f32x4_replace_lane(v, 3-(l), x) +#define v128_f64x2_replace_lane(v, l, x) simde_wasm_f64x2_replace_lane(v, 1-(l), x) +#define v128_i8x16_bitmask(v) simde_wasm_i8x16_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))) +#define v128_i16x8_bitmask(v) simde_wasm_i16x8_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(14,15,12,13,10,11,8,9,6,7,4,5,2,3,0,1))) +#define v128_i32x4_bitmask(v) simde_wasm_i32x4_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(12,13,14,15,8,9,10,11,4,5,6,7,0,1,2,3))) +#define v128_i64x2_bitmask(v) simde_wasm_i64x2_bitmask(simde_wasm_i8x16_swizzle(v, simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +#define v128_i8x16_swizzle(v1, v2) simde_wasm_i8x16_swizzle(v1, simde_wasm_v128_xor(v2, simde_wasm_i8x16_splat(15))) +#define v128_i8x16_shuffle(v1,v2,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) simde_wasm_i8x16_shuffle(v2,v1,31-(p),31-(o),31-(n),31-(m),31-(l),31-(k),31-(j),31-(i),31-(h),31-(g),31-(f),31-(e),31-(d),31-(c),31-(b),31-(a)) +#define v128_i16x8_extmul_high_i8x16 simde_wasm_i16x8_extmul_low_i8x16 +#define v128_u16x8_extmul_high_u8x16 simde_wasm_u16x8_extmul_low_u8x16 +#define v128_i16x8_extmul_low_i8x16 simde_wasm_i16x8_extmul_high_i8x16 +#define v128_u16x8_extmul_low_u8x16 simde_wasm_u16x8_extmul_high_u8x16 +#define v128_i32x4_extmul_high_i16x8 simde_wasm_i32x4_extmul_low_i16x8 +#define v128_u32x4_extmul_high_u16x8 simde_wasm_u32x4_extmul_low_u16x8 +#define v128_i32x4_extmul_low_i16x8 simde_wasm_i32x4_extmul_high_i16x8 +#define v128_u32x4_extmul_low_u16x8 simde_wasm_u32x4_extmul_high_u16x8 +#define v128_i64x2_extmul_high_i32x4 simde_wasm_i64x2_extmul_low_i32x4 +#define v128_u64x2_extmul_high_u32x4 simde_wasm_u64x2_extmul_low_u32x4 +#define v128_i64x2_extmul_low_i32x4 simde_wasm_i64x2_extmul_high_i32x4 +#define v128_u64x2_extmul_low_u32x4 simde_wasm_u64x2_extmul_high_u32x4 +#define v128_i16x8_extend_high_i8x16 simde_wasm_i16x8_extend_low_i8x16 +#define v128_u16x8_extend_high_u8x16 simde_wasm_u16x8_extend_low_u8x16 +#define v128_i16x8_extend_low_i8x16 simde_wasm_i16x8_extend_high_i8x16 +#define v128_u16x8_extend_low_u8x16 simde_wasm_u16x8_extend_high_u8x16 +#define v128_i32x4_extend_high_i16x8 simde_wasm_i32x4_extend_low_i16x8 +#define v128_u32x4_extend_high_u16x8 simde_wasm_u32x4_extend_low_u16x8 +#define v128_i32x4_extend_low_i16x8 simde_wasm_i32x4_extend_high_i16x8 +#define v128_u32x4_extend_low_u16x8 simde_wasm_u32x4_extend_high_u16x8 +#define v128_i64x2_extend_high_i32x4 simde_wasm_i64x2_extend_low_i32x4 +#define v128_u64x2_extend_high_u32x4 simde_wasm_u64x2_extend_low_u32x4 +#define v128_i64x2_extend_low_i32x4 simde_wasm_i64x2_extend_high_i32x4 +#define v128_u64x2_extend_low_u32x4 simde_wasm_u64x2_extend_high_u32x4 +#define v128_i32x4_trunc_sat_f64x2_zero(a) \ + simde_wasm_i8x16_swizzle( \ + simde_wasm_i32x4_trunc_sat_f64x2_zero(a), \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +#define v128_u32x4_trunc_sat_f64x2_zero(a) \ + simde_wasm_i8x16_swizzle( \ + simde_wasm_u32x4_trunc_sat_f64x2_zero(a), \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +#define v128_i16x8_narrow_i32x4(a,b) simde_wasm_i16x8_narrow_i32x4(b,a) +#define v128_u16x8_narrow_i32x4(a,b) simde_wasm_u16x8_narrow_i32x4(b,a) +#define v128_i8x16_narrow_i16x8(a,b) simde_wasm_i8x16_narrow_i16x8(b,a) +#define v128_u8x16_narrow_i16x8(a,b) simde_wasm_u8x16_narrow_i16x8(b,a) +#define v128_f64x2_promote_low_f32x4(a) \ + simde_wasm_f64x2_promote_low_f32x4(simde_wasm_i8x16_swizzle( \ + a, \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +#define v128_f32x4_demote_f64x2_zero(a) \ + simde_wasm_i8x16_swizzle( \ + simde_wasm_f32x4_demote_f64x2_zero(a), \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7)) +#define v128_f64x2_convert_low_i32x4(a) \ + simde_wasm_f64x2_convert_low_i32x4(simde_wasm_i8x16_swizzle( \ + a, \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +#define v128_f64x2_convert_low_u32x4(a) \ + simde_wasm_f64x2_convert_low_u32x4(simde_wasm_i8x16_swizzle( \ + a, \ + simde_wasm_i8x16_const(8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7))) +#else +#define v128_const simde_wasm_i8x16_const +#define v128_i8x16_extract_lane simde_wasm_i8x16_extract_lane +#define v128_u8x16_extract_lane simde_wasm_u8x16_extract_lane +#define v128_i16x8_extract_lane simde_wasm_i16x8_extract_lane +#define v128_u16x8_extract_lane simde_wasm_u16x8_extract_lane +#define v128_i32x4_extract_lane simde_wasm_i32x4_extract_lane +#define v128_i64x2_extract_lane simde_wasm_i64x2_extract_lane +#define v128_f32x4_extract_lane simde_wasm_f32x4_extract_lane +#define v128_f64x2_extract_lane simde_wasm_f64x2_extract_lane +#define v128_i8x16_replace_lane simde_wasm_i8x16_replace_lane +#define v128_u8x16_replace_lane simde_wasm_u8x16_replace_lane +#define v128_i16x8_replace_lane simde_wasm_i16x8_replace_lane +#define v128_u16x8_replace_lane simde_wasm_u16x8_replace_lane +#define v128_i32x4_replace_lane simde_wasm_i32x4_replace_lane +#define v128_i64x2_replace_lane simde_wasm_i64x2_replace_lane +#define v128_f32x4_replace_lane simde_wasm_f32x4_replace_lane +#define v128_f64x2_replace_lane simde_wasm_f64x2_replace_lane +#define v128_i8x16_bitmask simde_wasm_i8x16_bitmask +#define v128_i16x8_bitmask simde_wasm_i16x8_bitmask +#define v128_i32x4_bitmask simde_wasm_i32x4_bitmask +#define v128_i64x2_bitmask simde_wasm_i64x2_bitmask +#define v128_i8x16_swizzle simde_wasm_i8x16_swizzle +#define v128_i8x16_shuffle simde_wasm_i8x16_shuffle +#define v128_i16x8_extmul_high_i8x16 simde_wasm_i16x8_extmul_high_i8x16 +#define v128_u16x8_extmul_high_u8x16 simde_wasm_u16x8_extmul_high_u8x16 +#define v128_i16x8_extmul_low_i8x16 simde_wasm_i16x8_extmul_low_i8x16 +#define v128_u16x8_extmul_low_u8x16 simde_wasm_u16x8_extmul_low_u8x16 +#define v128_i32x4_extmul_high_i16x8 simde_wasm_i32x4_extmul_high_i16x8 +#define v128_u32x4_extmul_high_u16x8 simde_wasm_u32x4_extmul_high_u16x8 +#define v128_i32x4_extmul_low_i16x8 simde_wasm_i32x4_extmul_low_i16x8 +#define v128_u32x4_extmul_low_u16x8 simde_wasm_u32x4_extmul_low_u16x8 +#define v128_i64x2_extmul_high_i32x4 simde_wasm_i64x2_extmul_high_i32x4 +#define v128_u64x2_extmul_high_u32x4 simde_wasm_u64x2_extmul_high_u32x4 +#define v128_i64x2_extmul_low_i32x4 simde_wasm_i64x2_extmul_low_i32x4 +#define v128_u64x2_extmul_low_u32x4 simde_wasm_u64x2_extmul_low_u32x4 +#define v128_i16x8_extend_high_i8x16 simde_wasm_i16x8_extend_high_i8x16 +#define v128_u16x8_extend_high_u8x16 simde_wasm_u16x8_extend_high_u8x16 +#define v128_i16x8_extend_low_i8x16 simde_wasm_i16x8_extend_low_i8x16 +#define v128_u16x8_extend_low_u8x16 simde_wasm_u16x8_extend_low_u8x16 +#define v128_i32x4_extend_high_i16x8 simde_wasm_i32x4_extend_high_i16x8 +#define v128_u32x4_extend_high_u16x8 simde_wasm_u32x4_extend_high_u16x8 +#define v128_i32x4_extend_low_i16x8 simde_wasm_i32x4_extend_low_i16x8 +#define v128_u32x4_extend_low_u16x8 simde_wasm_u32x4_extend_low_u16x8 +#define v128_i64x2_extend_high_i32x4 simde_wasm_i64x2_extend_high_i32x4 +#define v128_u64x2_extend_high_u32x4 simde_wasm_u64x2_extend_high_u32x4 +#define v128_i64x2_extend_low_i32x4 simde_wasm_i64x2_extend_low_i32x4 +#define v128_u64x2_extend_low_u32x4 simde_wasm_u64x2_extend_low_u32x4 +#define v128_i32x4_trunc_sat_f64x2_zero simde_wasm_i32x4_trunc_sat_f64x2_zero +#define v128_u32x4_trunc_sat_f64x2_zero simde_wasm_u32x4_trunc_sat_f64x2_zero +#define v128_i16x8_narrow_i32x4 simde_wasm_i16x8_narrow_i32x4 +#define v128_u16x8_narrow_i32x4 simde_wasm_u16x8_narrow_i32x4 +#define v128_i8x16_narrow_i16x8 simde_wasm_i8x16_narrow_i16x8 +#define v128_u8x16_narrow_i16x8 simde_wasm_u8x16_narrow_i16x8 +#define v128_f64x2_promote_low_f32x4 simde_wasm_f64x2_promote_low_f32x4 +#define v128_f32x4_demote_f64x2_zero simde_wasm_f32x4_demote_f64x2_zero +#define v128_f64x2_convert_low_i32x4 simde_wasm_f64x2_convert_low_i32x4 +#define v128_f64x2_convert_low_u32x4 simde_wasm_f64x2_convert_low_u32x4 +#endif +// clang-format on diff --git a/test/wasm2c/add.txt b/test/wasm2c/add.txt new file mode 100644 index 00000000000..de04bf123f5 --- /dev/null +++ b/test/wasm2c/add.txt @@ -0,0 +1,835 @@ +;;; TOOL: run-wasm2c +(module + (func (export "add") (param i32 i32) (result i32) + (i32.add (local.get 0) (local.get 1)))) + +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct w2c_test { + char dummy_member; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +/* export: 'add' */ +u32 w2c_test_add(w2c_test*, u32, u32); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static u32 w2c_test_add_0(w2c_test*, u32, u32); + +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"; + +/* export: 'add' */ +u32 w2c_test_add(w2c_test* instance, u32 var_p0, u32 var_p1) { + u32 ret = w2c_test_add_0(instance, var_p0, var_p1); + return ret; +} + +void wasm2c_test_instantiate(w2c_test* instance) { + assert(wasm_rt_is_initialized()); +} + +void wasm2c_test_free(w2c_test* instance) { +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 2 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t0; + } + va_end(args); + } + + return NULL; +} + +u32 w2c_test_add_0(w2c_test* instance, u32 var_p0, u32 var_p1) { + FUNC_PROLOGUE; + u32 var_i0, var_i1; + var_i0 = var_p0; + var_i1 = var_p1; + var_i0 += var_i1; + FUNC_EPILOGUE; + return var_i0; +} +;;; STDOUT ;;) diff --git a/test/wasm2c/address-overflow.txt b/test/wasm2c/address-overflow.txt new file mode 100644 index 00000000000..c32a52a85d1 --- /dev/null +++ b/test/wasm2c/address-overflow.txt @@ -0,0 +1,12 @@ +;;; TOOL: run-spec-wasm2c +(module + (memory 1) + (func (export "test") (param i32) + local.get 0 + i32.load8_u offset=1 + drop) +) +(assert_trap (invoke "test" (i32.const -1)) "out of bounds memory access") +(;; STDOUT ;;; +1/1 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/bad-enable-feature.txt b/test/wasm2c/bad-enable-feature.txt new file mode 100644 index 00000000000..c41d0991687 --- /dev/null +++ b/test/wasm2c/bad-enable-feature.txt @@ -0,0 +1,6 @@ +;;; RUN: %(wasm2c)s +;;; ARGS: --enable-gc %(in_file)s +;;; ERROR: 1 +(;; STDERR ;;; +wasm2c currently only supports a limited set of features. +;;; STDERR ;;) diff --git a/test/wasm2c/check-imports.txt b/test/wasm2c/check-imports.txt new file mode 100644 index 00000000000..b5db913726c --- /dev/null +++ b/test/wasm2c/check-imports.txt @@ -0,0 +1,939 @@ +;;; TOOL: run-wasm2c +(module + (type (;0;) (func (param i32) (result i32))) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (import "env" "__linear_memory" (memory (;0;) 0)) + (import "env" "__indirect_function_table" (table (;0;) 1 funcref)) + (func $invoke (type 0) (param i32) (result i32) + local.get 0 + call_indirect (type 1)) + (func $callee (type 1) (result i32) + i32.const 16 + i32.load) + (func $main (type 2) (param i32 i32) (result i32) + i32.const 1 + call $invoke) + (elem (;0;) (i32.const 1) func $callee)) + +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct w2c_env; +extern wasm_rt_funcref_table_t* w2c_env_0x5F_indirect_function_table(struct w2c_env*); +extern wasm_rt_memory_t* w2c_env_0x5F_linear_memory(struct w2c_env*); + +typedef struct w2c_test { + /* import: 'env' '__indirect_function_table' */ + wasm_rt_funcref_table_t *w2c_env_0x5F_indirect_function_table; + /* import: 'env' '__linear_memory' */ + wasm_rt_memory_t *w2c_env_0x5F_linear_memory; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*, struct w2c_env*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +extern const u32 wasm2c_test_min_env_0x5F_indirect_function_table; +extern const u32 wasm2c_test_max_env_0x5F_indirect_function_table; +extern const u64 wasm2c_test_min_env_0x5F_linear_memory; +extern const u64 wasm2c_test_max_env_0x5F_linear_memory; +extern const u8 wasm2c_test_is64_env_0x5F_linear_memory; +extern const u32 wasm2c_test_pagesize_env_0x5F_linear_memory; + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" +#define IS_SINGLE_UNSHARED_MEMORY 1 + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static u32 w2c_test_f0(w2c_test*, u32); +static u32 w2c_test_f1(w2c_test*); +static u32 w2c_test_f2(w2c_test*, u32, u32); + +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"; +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((struct w2c_test*) 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)wrap_w2c_test_f1, {NULL}, 0}, +}; + +static void init_tables(w2c_test* instance) { + funcref_table_init(instance->w2c_env_0x5F_indirect_function_table, elem_segment_exprs_w2c_test_e0, 1, 1u, 0, 1, instance); +} + +static void init_elem_instances(w2c_test *instance) { +} + +static void init_instance_import(w2c_test* instance, struct w2c_env* w2c_env_instance) { + instance->w2c_env_0x5F_indirect_function_table = w2c_env_0x5F_indirect_function_table(w2c_env_instance); + instance->w2c_env_0x5F_linear_memory = w2c_env_0x5F_linear_memory(w2c_env_instance); +} + +const u32 wasm2c_test_min_env_0x5F_indirect_function_table = 1; +const u32 wasm2c_test_max_env_0x5F_indirect_function_table = 4294967295; +const u64 wasm2c_test_min_env_0x5F_linear_memory = 0; +const u64 wasm2c_test_max_env_0x5F_linear_memory = 65536; +const u8 wasm2c_test_is64_env_0x5F_linear_memory = 0; +const u32 wasm2c_test_pagesize_env_0x5F_linear_memory = 65536; + +void wasm2c_test_instantiate(w2c_test* instance, struct w2c_env* w2c_env_instance) { + assert(wasm_rt_is_initialized()); + init_instance_import(instance, w2c_env_instance); + init_tables(instance); + init_memories(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_env_0x5F_linear_memory).data); +#endif + init_elem_instances(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +void wasm2c_test_free(w2c_test* instance) { +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 1 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t0; + } + va_end(args); + } + + if (param_count == 0 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t1; + } + va_end(args); + } + + if (param_count == 2 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t2; + } + va_end(args); + } + + return NULL; +} + +u32 w2c_test_f0(w2c_test* instance, u32 var_p0) { + FUNC_PROLOGUE; + 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); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE + wasm_rt_segue_write_base((*instance->w2c_env_0x5F_linear_memory).data); +#endif + FUNC_EPILOGUE; + return var_i0; +} + +u32 w2c_test_f1(w2c_test* instance) { + FUNC_PROLOGUE; + u32 var_i0; + var_i0 = 16u; + var_i0 = i32_load_default32(instance->w2c_env_0x5F_linear_memory, (u64)(var_i0)); + FUNC_EPILOGUE; + return var_i0; +} + +u32 w2c_test_f2(w2c_test* instance, u32 var_p0, u32 var_p1) { + FUNC_PROLOGUE; + u32 var_i0; + var_i0 = 1u; + var_i0 = w2c_test_f0(instance, var_i0); + FUNC_EPILOGUE; + return var_i0; +} +;;; STDOUT ;;) diff --git a/test/wasm2c/duplicate-names.txt b/test/wasm2c/duplicate-names.txt new file mode 100644 index 00000000000..3bd9b95fc52 --- /dev/null +++ b/test/wasm2c/duplicate-names.txt @@ -0,0 +1,11 @@ +;;; TOOL: run-spec-wasm2c +;;; ARGS: --debug-names +(module + (func $B0 (export "$B0") (param $B0 i32) + (block + (br 0)))) + +(assert_return (invoke "$B0" (i32.const 5))) +(;; STDOUT ;;; +1/1 tests passed. +;;; STDOUT ;;) diff --git a/test/wasm2c/export-names.txt b/test/wasm2c/export-names.txt new file mode 100644 index 00000000000..96bff103258 --- /dev/null +++ b/test/wasm2c/export-names.txt @@ -0,0 +1,942 @@ +;;; TOOL: run-wasm2c +(memory $m (import "\\module" "import */") 0) +(func $f) +(export "" (func $f)) +(export "*/" (func $f)) +(export "??/" (func $f)) +(export "\0a" (func $f)) +(export "❤️" (func $f)) +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct w2c_0x5Cmodule; +extern wasm_rt_memory_t* w2c_0x5Cmodule_import0x200x2A0x2F(struct w2c_0x5Cmodule*); + +typedef struct w2c_test { + /* import: '\module' 'import *\2F' */ + wasm_rt_memory_t *w2c_0x5Cmodule_import0x200x2A0x2F; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*, struct w2c_0x5Cmodule*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +extern const u64 wasm2c_test_min_0x5Cmodule_import0x200x2A0x2F; +extern const u64 wasm2c_test_max_0x5Cmodule_import0x200x2A0x2F; +extern const u8 wasm2c_test_is64_0x5Cmodule_import0x200x2A0x2F; +extern const u32 wasm2c_test_pagesize_0x5Cmodule_import0x200x2A0x2F; + +/* export: '' */ +void w2c_test_(w2c_test*); + +/* export: '*\2F' */ +void w2c_test_0x2A0x2F(w2c_test*); + +/* export: '\3F\3F\2F' */ +void w2c_test_0x3F0x3F0x2F(w2c_test*); + +/* export: '\0A' */ +void w2c_test_0x0A(w2c_test*); + +/* export: '\E2\9D\A4\EF\B8\8F' */ +void w2c_test_0xE20x9D0xA40xEF0xB80x8F(w2c_test*); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" +#define IS_SINGLE_UNSHARED_MEMORY 1 + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static void w2c_test__0(w2c_test*); + +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"; + +static void init_memories(w2c_test* instance) { +} + +/* export: '' */ +void w2c_test_(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif + w2c_test__0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +/* export: '*\2F' */ +void w2c_test_0x2A0x2F(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif + w2c_test__0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +/* export: '\3F\3F\2F' */ +void w2c_test_0x3F0x3F0x2F(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif + w2c_test__0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +/* export: '\0A' */ +void w2c_test_0x0A(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif + w2c_test__0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +/* export: '\E2\9D\A4\EF\B8\8F' */ +void w2c_test_0xE20x9D0xA40xEF0xB80x8F(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif + w2c_test__0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +static void init_instance_import(w2c_test* instance, struct w2c_0x5Cmodule* w2c_0x5Cmodule_instance) { + instance->w2c_0x5Cmodule_import0x200x2A0x2F = w2c_0x5Cmodule_import0x200x2A0x2F(w2c_0x5Cmodule_instance); +} + +const u64 wasm2c_test_min_0x5Cmodule_import0x200x2A0x2F = 0; +const u64 wasm2c_test_max_0x5Cmodule_import0x200x2A0x2F = 65536; +const u8 wasm2c_test_is64_0x5Cmodule_import0x200x2A0x2F = 0; +const u32 wasm2c_test_pagesize_0x5Cmodule_import0x200x2A0x2F = 65536; + +void wasm2c_test_instantiate(w2c_test* instance, struct w2c_0x5Cmodule* w2c_0x5Cmodule_instance) { + assert(wasm_rt_is_initialized()); + init_instance_import(instance, w2c_0x5Cmodule_instance); + init_memories(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base((*instance->w2c_0x5Cmodule_import0x200x2A0x2F).data); +#endif +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +void wasm2c_test_free(w2c_test* instance) { +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 0 && result_count == 0) { + va_start(args, result_count); + if (true) { + va_end(args); + return w2c_test_t0; + } + va_end(args); + } + + return NULL; +} + +void w2c_test__0(w2c_test* instance) { + FUNC_PROLOGUE; + FUNC_EPILOGUE; +} +;;; STDOUT ;;) diff --git a/test/wasm2c/hello.txt b/test/wasm2c/hello.txt new file mode 100644 index 00000000000..b79bcd377f6 --- /dev/null +++ b/test/wasm2c/hello.txt @@ -0,0 +1,961 @@ +;;; TOOL: run-wasm2c +(module + (type $fd_write_t (func (param i32 i32 i32 i32) (result i32))) + (import "wasi_snapshot_preview1" "fd_write" (func $fd_write (type $fd_write_t))) + (import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32))) + (memory (export "memory") 1) + (data (i32.const 8) "Hello, world.\0a") + (table funcref (elem $fd_write)) + (func (export "_start") + ;; the string is already in memory (from the active data segment) + ;; step 1: store the string's address and length in memory + (i32.store (i32.const 0) ;; iov_base location in memory + (i32.const 8)) ;; iov_base value + (i32.store (i32.const 4) ;; iov_size location in memory + (i32.const 14)) ;; iov_size value + ;; step 2: write the string from memory + (call $proc_exit (call_indirect (type $fd_write_t) + (i32.const 1) ;; fd + (i32.const 0) ;; iovs location in memory + (i32.const 1) ;; iovs_len + (i32.const 0) ;; retptr0 location in memory + (i32.const 0))))) ;; fd_write function + +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct w2c_wasi__snapshot__preview1; + +typedef struct w2c_test { + struct w2c_wasi__snapshot__preview1* w2c_wasi__snapshot__preview1_instance; + wasm_rt_memory_t w2c_memory; + wasm_rt_funcref_table_t w2c_T0; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*, struct w2c_wasi__snapshot__preview1*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +/* import: 'wasi_snapshot_preview1' 'fd_write' */ +u32 w2c_wasi__snapshot__preview1_fd_write(struct w2c_wasi__snapshot__preview1*, u32, u32, u32, u32); + +/* import: 'wasi_snapshot_preview1' 'proc_exit' */ +void w2c_wasi__snapshot__preview1_proc_exit(struct w2c_wasi__snapshot__preview1*, u32); + +/* export: 'memory' */ +wasm_rt_memory_t* w2c_test_memory(w2c_test* instance); + +/* export: '_start' */ +void w2c_test_0x5Fstart(w2c_test*); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" +#define IS_SINGLE_UNSHARED_MEMORY 1 + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static void w2c_test_0x5Fstart_0(w2c_test*); + +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"; +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((struct w2c_wasi__snapshot__preview1*) 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, +}; + +static void init_memories(w2c_test* instance) { + wasm_rt_allocate_memory(&instance->w2c_memory, 1, 65536, 0, 65536); + LOAD_DATA(instance->w2c_memory, 8u, data_segment_data_w2c_test_d0, 14); +} + +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)wrap_w2c_wasi__snapshot__preview1_fd_write, {NULL}, offsetof(w2c_test, w2c_wasi__snapshot__preview1_instance)}, +}; + +static void init_tables(w2c_test* instance) { + wasm_rt_allocate_funcref_table(&instance->w2c_T0, 1, 1); + funcref_table_init(&instance->w2c_T0, elem_segment_exprs_w2c_test_e0, 1, 0u, 0, 1, instance); +} + +static void init_elem_instances(w2c_test *instance) { +} + +/* export: 'memory' */ +wasm_rt_memory_t* w2c_test_memory(w2c_test* instance) { + return &instance->w2c_memory; +} + +/* export: '_start' */ +void w2c_test_0x5Fstart(w2c_test* instance) { +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base(instance->w2c_memory.data); +#endif + w2c_test_0x5Fstart_0(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +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; +} + +void wasm2c_test_instantiate(w2c_test* instance, struct w2c_wasi__snapshot__preview1* w2c_wasi__snapshot__preview1_instance) { + assert(wasm_rt_is_initialized()); + init_instance_import(instance, w2c_wasi__snapshot__preview1_instance); + init_tables(instance); + init_memories(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +#if !WASM_RT_SEGUE_FREE_SEGMENT + void* segue_saved_base = wasm_rt_segue_read_base(); +#endif + wasm_rt_segue_write_base(instance->w2c_memory.data); +#endif + init_elem_instances(instance); + init_data_instances(instance); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && !WASM_RT_SEGUE_FREE_SEGMENT + wasm_rt_segue_write_base(segue_saved_base); +#endif +} + +void wasm2c_test_free(w2c_test* instance) { + wasm_rt_free_funcref_table(&instance->w2c_T0); + wasm_rt_free_memory(&instance->w2c_memory); +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 4 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t0; + } + va_end(args); + } + + if (param_count == 1 && result_count == 0) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_test_t1; + } + va_end(args); + } + + if (param_count == 0 && result_count == 0) { + va_start(args, result_count); + if (true) { + va_end(args); + return w2c_test_t2; + } + va_end(args); + } + + return NULL; +} + +void w2c_test_0x5Fstart_0(w2c_test* instance) { + FUNC_PROLOGUE; + u32 var_i0, var_i1, var_i2, var_i3, var_i4; + var_i0 = 0u; + var_i1 = 8u; + i32_store_default32(&instance->w2c_memory, (u64)(var_i0), var_i1); + var_i0 = 4u; + var_i1 = 14u; + i32_store_default32(&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); +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE + wasm_rt_segue_write_base(instance->w2c_memory.data); +#endif + w2c_wasi__snapshot__preview1_proc_exit(instance->w2c_wasi__snapshot__preview1_instance, var_i0); + FUNC_EPILOGUE; +} +;;; STDOUT ;;) diff --git a/test/wasm2c/minimal.txt b/test/wasm2c/minimal.txt new file mode 100644 index 00000000000..4fdff6d3927 --- /dev/null +++ b/test/wasm2c/minimal.txt @@ -0,0 +1,800 @@ +;;; TOOL: run-wasm2c +(module) +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct w2c_test { + char dummy_member; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +void wasm2c_test_instantiate(w2c_test* instance) { + assert(wasm_rt_is_initialized()); +} + +void wasm2c_test_free(w2c_test* instance) { +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + return NULL; +} +;;; STDOUT ;;) diff --git a/test/wasm2c/tail-calls.txt b/test/wasm2c/tail-calls.txt new file mode 100644 index 00000000000..a5f40b28bc1 --- /dev/null +++ b/test/wasm2c/tail-calls.txt @@ -0,0 +1,1051 @@ +;;; TOOL: run-wasm2c +;;; ARGS0: --debug-names +;;; ARGS*: --enable-tail-call +(type $i32_f32 (func (param i32 f32))) +(import "spectest" "print_i32_f32" (func $print_i32_f32 (type $i32_f32))) +(table $tab funcref (elem $print_i32_f32)) +(func (export "tailcaller") + (return_call_indirect (type $i32_f32) + (i32.const 1) (f32.const 2.0) (i32.const 0))) +(func $infiniteloop (param i32 f64) (result i32 f64) + (return_call $infiniteloop (local.get 0) (local.get 1))) +(;; STDOUT ;;; +/* Automatically generated by wasm2c */ +#ifndef WASM_H_GENERATED_ +#define WASM_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct w2c_spectest; + +typedef struct w2c_test { + struct w2c_spectest* w2c_spectest_instance; + wasm_rt_funcref_table_t w2c_tab; +} w2c_test; + +void wasm2c_test_instantiate(w2c_test*, struct w2c_spectest*); +void wasm2c_test_free(w2c_test*); +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +#ifndef wasm_multi_id +#define wasm_multi_id wasm_multi_id +struct wasm_multi_id { + u32 i0; + f64 d1; +}; +#endif /* wasm_multi_id */ + +/* import: 'spectest' 'print_i32_f32' */ +void w2c_spectest_print_i32_f32(struct w2c_spectest*, u32, f32); +void wasm_tailcall_w2c_spectest_print_i32_f32(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +/* export: 'tailcaller' */ +void w2c_test_tailcaller(w2c_test*); + +/* export for tail-call of 'tailcaller' */ +void wasm_tailcall_w2c_test_tailcaller(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_H_GENERATED_ */ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "wasm.h" + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static void w2c_test_tailcaller_0(w2c_test*); +static void wasm_tailcall_w2c_test_tailcaller_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); +static struct wasm_multi_id w2c_test_infiniteloop(w2c_test*, u32, f64); +static void wasm_tailcall_w2c_test_infiniteloop(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next); + +#ifndef wasm_multi_if +#define wasm_multi_if wasm_multi_if +struct wasm_multi_if { + u32 i0; + f32 f1; +}; +#endif /* wasm_multi_if */ + +FUNC_TYPE_T(w2c_test_i32_f32) = "\x98\x89\x5c\xbd\x28\xfd\x0e\x4d\xc5\xdc\x68\x2c\x7c\xee\x61\x09\x14\x19\x30\x62\xc2\x2f\x49\xc5\xb5\x81\x57\x55\x6b\xe7\xa5\xb9"; +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((struct w2c_spectest*) 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)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) { + wasm_rt_allocate_funcref_table(&instance->w2c_tab, 1, 1); + funcref_table_init(&instance->w2c_tab, elem_segment_exprs_w2c_test_e0, 1, 0u, 0, 1, instance); +} + +static void init_elem_instances(w2c_test *instance) { +} + +/* export: 'tailcaller' */ +void w2c_test_tailcaller(w2c_test* instance) { + w2c_test_tailcaller_0(instance); +} + +/* export for tail-call of 'tailcaller' */ +void wasm_tailcall_w2c_test_tailcaller(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + wasm_tailcall_w2c_test_tailcaller_0(instance_ptr, tail_call_stack, next); +} + +/* handler for missing tail-call on import: 'spectest' 'print_i32_f32' */ +WEAK_FUNC_DECL(wasm_tailcall_w2c_spectest_print_i32_f32, wasm_fallback_test_w2c_spectest_print_i32_f32) +{ + next->fn = NULL; + struct wasm_multi_if params; + wasm_rt_memcpy(¶ms, tail_call_stack, sizeof(params)); + w2c_spectest_print_i32_f32(*instance_ptr, params.i0, params.f1); +} + +static void init_instance_import(w2c_test* instance, struct w2c_spectest* w2c_spectest_instance) { + instance->w2c_spectest_instance = w2c_spectest_instance; +} + +void wasm2c_test_instantiate(w2c_test* instance, struct w2c_spectest* w2c_spectest_instance) { + assert(wasm_rt_is_initialized()); + init_instance_import(instance, w2c_spectest_instance); + init_tables(instance); + init_elem_instances(instance); +} + +void wasm2c_test_free(w2c_test* instance) { + wasm_rt_free_funcref_table(&instance->w2c_tab); +} + +wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 2 && result_count == 0) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_F32) { + va_end(args); + return w2c_test_i32_f32; + } + va_end(args); + } + + if (param_count == 0 && result_count == 0) { + va_start(args, result_count); + if (true) { + va_end(args); + return w2c_test_t1; + } + va_end(args); + } + + if (param_count == 2 && result_count == 2) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_F64 && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_F64) { + va_end(args); + return w2c_test_t2; + } + va_end(args); + } + + return NULL; +} + +void w2c_test_tailcaller_0(w2c_test* instance) { + FUNC_PROLOGUE; + u32 var_i0, var_i2; + f32 var_f1; + var_i0 = 1u; + var_f1 = 2; + var_i2 = 0u; + static_assert(sizeof(struct wasm_multi_if) <= 1024); + CHECK_CALL_INDIRECT(instance->w2c_tab, w2c_test_i32_f32, var_i2); + if (!instance->w2c_tab.data[var_i2].func_tailcallee.fn) { + CALL_INDIRECT(instance->w2c_tab, void (*)(void*, u32, f32), w2c_test_i32_f32, var_i2, instance->w2c_tab.data[var_i2].module_instance, var_i0, var_f1); + } else { + void *instance_ptr_storage; + void **instance_ptr = &instance_ptr_storage; + char tail_call_stack[1024]; + wasm_rt_tailcallee_t next_storage; + wasm_rt_tailcallee_t *next = &next_storage; + { + struct wasm_multi_if tmp; + tmp.i0 = var_i0; + tmp.f1 = var_f1; + wasm_rt_memcpy(tail_call_stack, &tmp, sizeof(tmp)); + } + next->fn = instance->w2c_tab.data[var_i2].func_tailcallee.fn; + *instance_ptr = instance->w2c_tab.data[var_i2].module_instance; + while (next->fn) { next->fn(instance_ptr, tail_call_stack, next); } + } + goto var_Bfunc; + var_Bfunc:; + FUNC_EPILOGUE; +} + +void wasm_tailcall_w2c_test_tailcaller_0(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + w2c_test* instance = *instance_ptr; + u32 var_i0, var_i2; + f32 var_f1; + var_i0 = 1u; + var_f1 = 2; + var_i2 = 0u; + static_assert(sizeof(struct wasm_multi_if) <= 1024); + CHECK_CALL_INDIRECT(instance->w2c_tab, w2c_test_i32_f32, var_i2); + if (!instance->w2c_tab.data[var_i2].func_tailcallee.fn) { + CALL_INDIRECT(instance->w2c_tab, void (*)(void*, u32, f32), w2c_test_i32_f32, var_i2, instance->w2c_tab.data[var_i2].module_instance, var_i0, var_f1); + next->fn = NULL; + } else { + { + struct wasm_multi_if tmp; + tmp.i0 = var_i0; + tmp.f1 = var_f1; + wasm_rt_memcpy(tail_call_stack, &tmp, sizeof(tmp)); + } + next->fn = instance->w2c_tab.data[var_i2].func_tailcallee.fn; + *instance_ptr = instance->w2c_tab.data[var_i2].module_instance; + } + return; + var_Bfunc:; + next->fn = NULL; +} + +struct wasm_multi_id w2c_test_infiniteloop(w2c_test* instance, u32 var_p0, f64 var_p1) { + FUNC_PROLOGUE; + u32 var_i0; + f64 var_d1; + var_i0 = var_p0; + var_d1 = var_p1; + static_assert(sizeof(struct wasm_multi_id) <= 1024); + { + void *instance_ptr_storage; + void **instance_ptr = &instance_ptr_storage; + char tail_call_stack[1024]; + wasm_rt_tailcallee_t next_storage; + wasm_rt_tailcallee_t *next = &next_storage; + { + struct wasm_multi_id tmp; + tmp.i0 = var_i0; + tmp.d1 = var_d1; + wasm_rt_memcpy(tail_call_stack, &tmp, sizeof(tmp)); + } + next->fn = wasm_tailcall_w2c_test_infiniteloop; + while (next->fn) { next->fn(instance_ptr, tail_call_stack, next); } + { + struct wasm_multi_id tmp; + wasm_rt_memcpy(&tmp, tail_call_stack, sizeof(tmp)); + var_i0 = tmp.i0; + var_d1 = tmp.d1; + } + } + goto var_Bfunc; + var_Bfunc:; + FUNC_EPILOGUE; + { + struct wasm_multi_id tmp; + tmp.i0 = var_i0; + tmp.d1 = var_d1; + return tmp; + } +} + +void wasm_tailcall_w2c_test_infiniteloop(void **instance_ptr, void *tail_call_stack, wasm_rt_tailcallee_t *next) { + static_assert(sizeof(struct wasm_multi_id) <= 1024); + w2c_test* instance = *instance_ptr; + u32 var_p0; + f64 var_p1; + { + struct wasm_multi_id tmp; + wasm_rt_memcpy(&tmp, tail_call_stack, sizeof(tmp)); + var_p0 = tmp.i0; + var_p1 = tmp.d1; + } + u32 var_i0; + f64 var_d1; + var_i0 = var_p0; + var_d1 = var_p1; + static_assert(sizeof(struct wasm_multi_id) <= 1024); + { + { + struct wasm_multi_id tmp; + tmp.i0 = var_i0; + tmp.d1 = var_d1; + wasm_rt_memcpy(tail_call_stack, &tmp, sizeof(tmp)); + } + next->fn = wasm_tailcall_w2c_test_infiniteloop; + } + return; + var_Bfunc:; + { + struct wasm_multi_id tmp; + tmp.i0 = var_i0; + tmp.d1 = var_d1; + wasm_rt_memcpy(tail_call_stack, &tmp, sizeof(tmp)); + } + next->fn = NULL; +} +;;; STDOUT ;;) diff --git a/wasm2c/.gitignore b/wasm2c/.gitignore new file mode 100644 index 00000000000..70f38091775 --- /dev/null +++ b/wasm2c/.gitignore @@ -0,0 +1,17 @@ +wasm-rt-impl.o +wasm-rt-mem-impl.o +wasm-rt-exceptions-impl.o +examples/**/*.o +examples/fac/fac +examples/rot13/rot13 +examples/rot13/rot13.c +examples/rot13/rot13.h +examples/rot13/rot13.wasm +examples/callback/callback +examples/callback/callback.c +examples/callback/callback.h +examples/callback/callback.wasm +examples/threads/threads +examples/threads/sample.c +examples/threads/sample.h +examples/threads/sample.wasm diff --git a/wasm2c/README.md b/wasm2c/README.md new file mode 100644 index 00000000000..b7ab3bca2e4 --- /dev/null +++ b/wasm2c/README.md @@ -0,0 +1,722 @@ +# wasm2c: Convert wasm files to C source and header + +`wasm2c` takes a WebAssembly module and produces an equivalent C source and +header. Some examples: + +```sh +# parse binary file test.wasm and write test.c and test.h +$ wasm2c test.wasm -o test.c + +# parse test.wasm, write test.c and test.h, but ignore the debug names, if any +$ wasm2c test.wasm --no-debug-names -o test.c +``` + +The C code produced targets the C99 standard. If, however, the Wasm module uses +Wasm threads/atomics, the code produced targets the C11 standard. + +## Tutorial: .wat -> .wasm -> .c + +Let's look at a simple example of a factorial function. + +```wasm +(memory $mem 1) +(func (export "fac") (param $x i32) (result i32) + (if (result i32) (i32.eq (local.get $x) (i32.const 0)) + (then (i32.const 1)) + (else + (i32.mul (local.get $x) (call 0 (i32.sub (local.get $x) (i32.const 1)))) + ) + ) +) +``` + +Save this to `fac.wat`. We can convert this to a `.wasm` file by using the +`wat2wasm` tool: + +```sh +$ wat2wasm fac.wat -o fac.wasm +``` + +We can then convert it to a C source and header by using the `wasm2c` tool: + +```sh +$ wasm2c fac.wasm -o fac.c +``` + +This generates two files, `fac.c` and `fac.h`. We'll take a closer look at +these files below, but first let's show a simple example of how to use these +files. + +## Using the generated module + +To actually use our `fac` module, we'll use create a new file, `main.c`, that +include `fac.h`, initializes the module, and calls `fac`. + +`wasm2c` generates a few C symbols based on the `fac.wasm` module. +The first is `w2c_fac`, a type that represents an instance of the +`fac` module. `wasm2c` generates functions that construct and free a +`w2c_fac` instance: `wasm2c_fac_instantiate` and +`wasm2c_fac_free`. Finally, `wasm2c` generates the exported `fac` +function itself (`w2c_fac_fac`), which acts on a `w2c_fac` instance. + +All the exported symbols shared a common module ID (`fac`) which, by default, is +based on the name section in the module or the name of input file. This prefix +can be overridden using the `-n/--module-name` command line flag. + +```c +#include +#include + +#include "fac.h" + +int main(int argc, char** argv) { + /* Make sure there is at least one command-line argument. */ + if (argc < 2) { + printf("Invalid argument. Expected '%s NUMBER'\n", argv[0]); + return 1; + } + + /* Convert the argument from a string to an int. We'll implicitly cast the int + to a `u32`, which is what `fac` expects. */ + u32 x = atoi(argv[1]); + + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Declare an instance of the `fac` module. */ + w2c_fac fac; + + /* Construct the module instance. */ + wasm2c_fac_instantiate(&fac); + + /* Call `fac`, using the mangled name. */ + u32 result = w2c_fac_fac(&fac, x); + + /* Print the result. */ + printf("fac(%u) -> %u\n", x, result); + + /* Free the fac module. */ + wasm2c_fac_free(&fac); + + /* Free the Wasm runtime state. */ + wasm_rt_free(); + + return 0; +} + +``` + +## Compiling the wasm2c output + +To compile the executable, we need to use `main.c` and the generated `fac.c`. +We'll also include `wasm-rt-impl.c` and `wasm-rt-mem-impl.c`, which have implementations of the various +`wasm_rt_*` functions used by `fac.c` and `fac.h`. + +```sh +$ cc -o fac main.c fac.c wasm2c/wasm-rt-impl.c wasm2c/wasm-rt-mem-impl.c -Iwasm2c -lm +``` + +A note on compiling with optimization: wasm2c relies on certain +behavior from the C compiler to maintain conformance with the +WebAssembly specification, especially with regards to requirements to +convert "signaling" to "quiet" floating-point NaN values and for +infinite recursion to produce a trap. When compiling with optimization +(e.g. `-O2` or `-O3`), it's necessary to disable some optimizations to +preserve conformance. With GCC 11, adding the command-line arguments +`-fno-optimize-sibling-calls -frounding-math -fsignaling-nans` appears +to be sufficient. With clang 14, just `-fno-optimize-sibling-calls +-frounding-math` appears to be sufficient. + +Now let's test it out! + +```sh +$ ./fac 1 +fac(1) -> 1 +$ ./fac 5 +fac(5) -> 120 +$ ./fac 10 +fac(10) -> 3628800 +``` + +You can take a look at the all of these files in +[wasm2c/examples/fac](/wasm2c/examples/fac). + +### Enabling extra sanity checks + +Wasm2c provides a macro `WASM_RT_SANITY_CHECKS` that if defined enables +additional sanity checks in the produced Wasm2c code. Note that this may have a +high performance overhead, and is thus only recommended for debug builds. + +### Enabling Segue (a Linux x86_64 target specific optimization) + +Wasm2c can use the "Segue" optimization if allowed. The segue optimization uses +an x86 segment register to store the location of Wasm's linear memory, when +compiling a Wasm module with clang, running on x86_64 Linux, the macro +`WASM_RT_ALLOW_SEGUE` is defined, and the flag `-mfsgsbase` is passed to clang. +Segue is not used if + +1. The Wasm module uses a more than a single unshared imported or exported + memory +2. The wasm2c code is compiled with GCC. Segue requires intrinsics for + (rd|wr)gsbase, "address namespaces" for accessing pointers, and support for + memcpy on pointers with custom "address namespaces". GCC does not support the + memcpy requirement. +3. The code is compiled for Windows as Windows doesn't restore the segment + register on context switch. + +The wasm2c generated code automatically sets the unused segment register (the +`%gs` register on x86_64 Linux) during the function calls into wasm2c generated +module, restores it after calls to external modules etc. Any host function +written in C would continue to work without changes as C code does not modify +the unused segment register `%gs` (See +[here](https://www.kernel.org/doc/html/next/x86/x86_64/fsgs.html) for details). +However, any host functions written in assembly that clobber the free segment +register must restore the value of this register prior to executing or returning +control to wasm2c generated code. + +As an additional optimization, if the host program does not use the `%gs` +segment register for any other purpose (which is typically the case in most +programs), you can additionally allow wasm2c to unconditionally overwrite the +value of the `%gs` register without restoring the old value. This can be done +defining the macro `WASM_RT_SEGUE_FREE_SEGMENT`. + +You can test the performance of the Segue optimization by running Dhrystone with +and without Segue: + +```bash +cd wasm2c/benchmarks/segue && make +``` + +## Looking at the generated header, `fac.h` + +The generated header file looks something like this: + +```c +/* Automatically generated by wasm2c */ +#ifndef FAC_H_GENERATED_ +#define FAC_H_GENERATED_ + +... + +#include "wasm-rt.h" + +... +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED + +... + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct w2c_fac { + char dummy_member; +} w2c_fac; + +void wasm2c_fac_instantiate(w2c_fac*); +void wasm2c_fac_free(w2c_fac*); +wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +/* export: 'fac' */ +u32 w2c_fac_fac(w2c_fac*, u32); + +#ifdef __cplusplus +} +#endif + +#endif /* FAC_H_GENERATED_ */ + +``` + +Let's look at each section. The outer `#ifndef` is standard C +boilerplate for a header. This `WASM_RT_CORE_TYPES_DEFINED` section +contains a number of definitions required for all WebAssembly +modules. The `extern "C"` part makes sure to not mangle the symbols if +using this header in C++. + +The included `wasm-rt.h` file also includes a number of relevant definitions. +First 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_UNCAUGHT_EXCEPTION, + WASM_RT_TRAP_EXHAUSTION, +} wasm_rt_trap_t; +``` + +Next is the `wasm_rt_type_t` enum, which is used for specifying function +signatures. Six WebAssembly value types are included: + +```c +typedef enum { + WASM_RT_I32, + WASM_RT_I64, + WASM_RT_F32, + WASM_RT_F64, + WASM_RT_FUNCREF, + WASM_RT_EXTERNREF, +} wasm_rt_type_t; + +Next is `wasm_rt_function_ptr_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_function_ptr_t)(void); +``` + +Next is the definition for a function reference (in WebAssembly 1.0, +this was the type of all table elements, but funcrefs can now also be +used as ordinary values, and tables can alternately be declared as +type externref). In this structure, `wasm_rt_func_type_t` is an opaque +256-bit ID that can be looked up via the `Z_[modname]_get_func_type` +function. (A demonstration of this can be found in the `callback` +example.) `module_instance` is the pointer to the function's +originating module instance, which will be passed in when the func is +called. + +```c +typedef struct { + wasm_rt_func_type_t func_type; + wasm_rt_function_ptr_t func; + void* module_instance; +} wasm_rt_funcref_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, `pages` is the current +size in pages, and `page_size` contains the page size in bytes (65,536 by default). +`max_pages` is the maximum number of pages specified by the module or allowed +by the memory index type (`is64` is true for memories that can grow to 2^64 bytes; +`false` for memories limited to 2^32 bytes). + +```c +typedef struct { + uint8_t* data; + uint32_t page_size; + uint64_t pages, max_pages; + uint64_t size; + bool is64; +} wasm_rt_memory_t; +``` + +This is followed by the definition of a shared memory instance. This is similar +to a regular memory instance, but represents memory that can be used by multiple +Wasm instances, and thus enforces a minimum amount of memory order on +operations. The Shared memory definition has one additional member, `mem_lock`, +which is a lock that is used during memory grow operations for thread safety. + +```c +typedef struct { + _Atomic volatile uint8_t* data; + uint64_t pages, max_pages; + uint64_t size; + bool is64; + mtx_t mem_lock; +} wasm_rt_shared_memory_t; +``` + +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. + +```c +typedef struct { + wasm_rt_funcref_t* data; + uint32_t max_size; + uint32_t size; +} wasm_rt_funcref_table_t; +``` + +## Symbols that must be defined by the embedder + +Next in `wasm-rt.h` are a collection of function declarations that must be implemented by +the embedder (i.e. you) before this C source can be used. + +A C implementation of these functions is defined in +[`wasm-rt-impl.h`](wasm-rt-impl.h) and [`wasm-rt-impl.c`](wasm-rt-impl.c). + +```c +void wasm_rt_init(void); +bool wasm_rt_is_initialized(void); +void wasm_rt_free(void); +void wasm_rt_trap(wasm_rt_trap_t) __attribute__((noreturn)); +const char* wasm_rt_strerror(wasm_rt_trap_t trap); +void wasm_rt_allocate_memory(wasm_rt_memory_t*, uint32_t initial_pages, uint32_t max_pages, bool is64, uint32_t page_size); +uint32_t wasm_rt_grow_memory(wasm_rt_memory_t*, uint32_t pages); +void wasm_rt_free_memory(wasm_rt_memory_t*); +void wasm_rt_allocate_memory_shared(wasm_rt_shared_memory_t*, uint32_t initial_pages, uint32_t max_pages, bool is64, uint32_t page_size); +uint32_t wasm_rt_grow_memory_shared(wasm_rt_shared_memory_t*, uint32_t pages); +void wasm_rt_free_memory_shared(wasm_rt_shared_memory_t*); +void wasm_rt_allocate_funcref_table(wasm_rt_table_t*, uint32_t elements, uint32_t max_elements); +void wasm_rt_allocate_externref_table(wasm_rt_externref_table_t*, uint32_t elements, uint32_t max_elements); +void wasm_rt_free_funcref_table(wasm_rt_table_t*); +void wasm_rt_free_externref_table(wasm_rt_table_t*); +uint32_t wasm_rt_call_stack_depth; /* on platforms that don't use the signal handler to detect exhaustion */ +void wasm_rt_init_thread(void); +void wasm_rt_free_thread(void); +``` + +`wasm_rt_init` must be called by the embedder before anything else, to +initialize the runtime. `wasm_rt_free` frees any global +state. `wasm_rt_is_initialized` can be used to confirm that the +runtime has been initialized. + +`wasm_rt_trap` is a function that is called when the module traps. Some possible +implementations are to throw a C++ exception, or to just abort the program +execution. The default runtime included in wasm2c unwinds the stack using +`longjmp`. The host can overide this call to `longjmp` by compiling the runtime +with `WASM_RT_TRAP_HANDLER` defined to the name of a trap handler function. The +handler function should be a function taking a `wasm_rt_trap_t` as a parameter +and returning `void`. e.g. `-DWASM_RT_TRAP_HANDLER=my_trap_handler` + +`wasm_rt_allocate_memory` initializes a memory instance, and allocates +at least enough space for the given number of initial pages, each of +size `page_size` (which must be `WASM_DEFAULT_PAGE_SIZE`, equal to 64 +KiB, unless using the custom-page-sizes feature). The memory must be +cleared to zero. The `is64` parameter indicates if the memory is +indexed with an i32 or i64 address. + +`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. The host can optionally be notified of failures by +compiling the runtime with `WASM_RT_GROW_FAILED_HANDLER` defined to the name of +a handler function. The handler function should be a function taking no +arguments and returning `void` . e.g. +`-DWASM_RT_GROW_FAILED_HANDLER=my_growfail_handler` + +`wasm_rt_free_memory` frees the memory instance. + +`wasm_rt_allocate_memory_shared` initializes a memory instance that can be +shared by different Wasm threads. Its operation is otherwise similar to +`wasm_rt_allocate_memory`. + +`wasm_rt_grow_memory_shared` must grow the given shared memory instance by the +given number of pages. It's operation is otherwise similar to +`wasm_rt_grow_memory`. + +`wasm_rt_free_memory_shared` frees the shared memory instance. + +`wasm_rt_allocate_funcref_table` and the similar `..._externref_table` +initialize a table instance of the given type, and allocate at least +enough space for the given number of initial elements. The elements +must be cleared to zero. + +`wasm_rt_free_funcref_table` and `..._externref_table` free the table instance. + +`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. +It is only used on platforms that don't use the signal handler to detect +exhaustion. + +`wasm_rt_init_thread` and `wasm_rt_free_thread` are used to initialize +and free the runtime state for a given thread (other than the one that +called `wasm_rt_init`). An example can be found in +`wasm2c/examples/threads`. + +### Runtime support for exception handling + +Several additional symbols must be defined if wasm2c is being run with support +for exceptions (`--enable-exceptions`). These are defined in +`wasm-rt-exceptions.h`. These symbols are: + +```c +void wasm_rt_load_exception(const char* tag, uint32_t size, const void* values); +WASM_RT_NO_RETURN void wasm_rt_throw(void); +WASM_RT_UNWIND_TARGET +WASM_RT_UNWIND_TARGET* wasm_rt_get_unwind_target(void); +void wasm_rt_set_unwind_target(WASM_RT_UNWIND_TARGET* target); +uint32_t wasm_rt_exception_tag(void); +uint32_t wasm_rt_exception_size(void); +void* wasm_rt_exception(void); +wasm_rt_try(target) +``` + +A C implementation of these functions is also available in +[`wasm-rt-exceptions-impl.c`](wasm-rt-exceptions-impl.c). + +`wasm_rt_load_exception` sets the active exception to a given tag, size, and contents. + +`wasm_rt_throw` throws the active exception. + +`WASM_RT_UNWIND_TARGET` is the type of an unwind target if an +exception is thrown and caught. + +`wasm_rt_get_unwind_target` gets the current unwind target if an exception is thrown. + +`wasm_rt_set_unwind_target` sets the unwind target if an exception is thrown. + +Three functions provide access to the active exception: +`wasm_rt_exception_tag`, `wasm_rt_exception_size`, and +`wasm_rt_exception` return its tag, size, and contents, respectively. + +`wasm_rt_try(target)` is a macro that captures the current calling +environment as an unwind target and stores it into `target`, which +must be of type `WASM_RT_UNWIND_TARGET`. + +## Exported symbols + +Finally, `fac.h` defines the module instance type (which in the case +of `fac` is essentially empty), and the exported symbols provided by +the module. In our example, the only function we exported was +`fac`. + +`wasm2c_fac_instantiate(w2c_fac*)` creates an instance of +the module and must be called before the module instance can be +used. `wasm2c_fac_free(w2c_fac*)` frees the instance. +`wasm2c_fac_get_func_type` can be used to look up a function type ID +at runtime. It is a variadic function where the first two arguments +give the number of parameters and results, and the following arguments +are the types from the wasm_rt_type_t enum described above. The +`callback` example demonstrates using this to pass a host function to +a WebAssembly module dynamically at runtime. + +```c +typedef struct w2c_fac { + char dummy_member; +} w2c_fac; + +void wasm2c_fac_instantiate(w2c_fac*); +void wasm2c_fac_free(w2c_fac*); +wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +/* export: 'fac' */ +u32 w2c_fac_fac(w2c_fac*, u32); +``` + +## Handling other kinds of imports and exports of modules + +Exported functions are handled by declaring a prefixed equivalent +function in the header. If a module imports a function, `wasm2c` +declares the function in the output header file, and the host function +is responsible for defining the function. + +Exports of other kinds (globals, memories, tables) are handled +differently, since they are part of the module instance, and each +instance can have its own exports. For these cases, `wasm2c` provides +a function that takes in a module instance as argument, and returns +the corresponding export. For example, if `fac` exported a memory as +such: + +```wasm +(export "mem" (memory $mem)) +``` + +then `wasm2c` would declare the following function in the header: + +```c +/* export: 'mem' */ +wasm_rt_memory_t* w2c_fac_mem(w2c_fac* instance); + +``` + +which would be defined as: +```c +/* export: 'mem' */ +wasm_rt_memory_t* w2c_fac_mem(w2c_fac* instance) { + return &instance->w2c_mem; +} +``` + +## A quick look at `fac.c` + +The contents of `fac.c` are internals, but it is useful to see a little about +how it works. + +The first few hundred lines define macros that are used to implement the +various WebAssembly instructions. Their implementations may be interesting to +the curious reader, but are out of scope for this document. + +Following those definitions are various initialization functions (`init`, `free`, +`init_func_types`, `init_globals`, `init_memory`, `init_table`, and +`init_exports`.) In our example, most of these functions are empty, since the +module doesn't use any globals, memory or tables. + +The most interesting part is the definition of the function `fac`: + +```c +static u32 w2c_fac_fac_0(w2c_fac* instance, u32 var_p0) { + FUNC_PROLOGUE; + 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; + } + FUNC_EPILOGUE; + return var_i0; +} +``` + +If you look at the original WebAssembly text in the flat format, you can see +that there is a 1-1 mapping in the output: + +```wasm +(func $fac (param $x i32) (result i32) + local.get $x + i32.const 0 + i32.eq + if (result i32) + i32.const 1 + else + local.get $x + local.get $x + i32.const 1 + i32.sub + call 0 + i32.mul + end) +``` + +This looks different than the factorial function above because it is using the +"flat format" instead of the "folded format". You can use `wat-desugar` to +convert between the two to be sure: + +```sh +$ wat-desugar fac-flat.wat --fold -o fac-folded.wat +``` + +```wasm +(module + (func (;0;) (param i32) (result i32) + (if (result i32) ;; label = @1 + (i32.eq + (local.get 0) + (i32.const 0)) + (then + (i32.const 1)) + (else + (i32.mul + (local.get 0) + (call 0 + (i32.sub + (local.get 0) + (i32.const 1))))))) + (export "fac" (func 0)) + (type (;0;) (func (param i32) (result i32)))) +``` + +The formatting is different and the variable and function names are gone, but +the structure is the same. + +## Create multiple instances of a module + +Since information about the execution context, such as memories, is encapsulated +in the module instance structure, and a pointer to the structure is being passed through +function calls, multiple instances of the same module can be instantiated alongside +one another. + +We can take a look at another version of the `main` function for a `rot13` example. By +declaring two sets of context information, two instances of `rot13` can be instantiated +in the same address space. + +```c +#include +#include +#include + +#include "rot13.h" + +/* Define structure to hold the imports */ +typedef struct w2c_host { + wasm_rt_memory_t memory; + char* input; +} w2c_host; + +/* Accessor to access the memory member of the host */ +wasm_rt_memory_t* w2c_host_mem(w2c_host* instance) { + return &instance->memory; +} + +int main(int argc, char** argv) { + /* Make sure there is at least one command-line argument. */ + if (argc < 2) { + printf("Invalid argument. Expected '%s WORD...'\n", argv[0]); + return 1; + } + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Create two `host` instances to store the memory and current string */ + w2c_host host_1, host_2; + wasm_rt_allocate_memory(&host_1.memory, 1, 1, false, WASM_DEFAULT_PAGE_SIZE); + wasm_rt_allocate_memory(&host_2.memory, 1, 1, false, WASM_DEFAULT_PAGE_SIZE); + + /* Construct the `rot13` module instances */ + w2c_rot13 rot13_1, rot13_2; + wasm2c_rot13_instantiate(&rot13_1, &host_1); + wasm2c_rot13_instantiate(&rot13_2, &host_2); + + /* Call `rot13` on the first two arguments. */ + assert(argc > 2); + host_1.input = argv[1]; + w2c_rot13_rot13(&rot13_1); + host_2.input = argv[2]; + w2c_rot13_rot13(&rot13_2); + + /* Free the rot13 instances. */ + wasm2c_rot13_free(&rot13_1); + wasm2c_rot13_free(&rot13_2); + + /* Free the Wasm runtime state. */ + wasm_rt_free(); + + return 0; +} + +/* Fill the wasm buffer with the input to be rot13'd. + * + * params: + * instance: An instance of the w2c_host structure + * ptr: The wasm memory address of the buffer to fill data. + * size: The size of the buffer in wasm memory. + * result: + * The number of bytes filled into the buffer. (Must be <= size). + */ +u32 w2c_host_fill_buf(w2c_host* instance, u32 ptr, u32 size) { + for (size_t i = 0; i < size; ++i) { + if (instance->input[i] == 0) { + return i; + } + instance->memory.data[ptr + i] = instance->input[i]; + } + return size; +} + +/* Called when the wasm buffer has been rot13'd. + * + * params: + * w2c_host: An instance of the w2c_host structure + * ptr: The wasm memory address of the buffer. + * size: The size of the buffer in wasm memory. + */ +void w2c_host_buf_done(w2c_host* instance, u32 ptr, u32 size) { + /* The output buffer is not necessarily null-terminated, so use the %*.s + * printf format to limit the number of characters printed. */ + printf("%s -> %.*s\n", instance->input, (int)size, &instance->memory.data[ptr]); +} +``` diff --git a/wasm2c/benchmarks/dhrystone/.gitignore b/wasm2c/benchmarks/dhrystone/.gitignore new file mode 100644 index 00000000000..7cc065143d1 --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/.gitignore @@ -0,0 +1,5 @@ +dhrystone_native +dhrystone +dhrystone_segue +dhrystone.h +dhrystone.c diff --git a/wasm2c/benchmarks/dhrystone/dhrystone.wasm b/wasm2c/benchmarks/dhrystone/dhrystone.wasm new file mode 100755 index 0000000000000000000000000000000000000000..b652757f00059cc1a318559f6e5ba6137ad4e8ca GIT binary patch literal 33999 zcmd753v^ufecyQ>GlLlnKn@841PD_6&xo=}QZ%jVxzl$B|Q|NfW!RH?)(s?m5n>y4$pFk55WIgK@c`;ACA}8*TZ$b zURV!b_;A#_S^sdTjshM2+Yb!agAWIR1_m}aU<7|?WN=ZJ8tpF__S|~-T=Ys*9=kBp znV;?~&8&3JE_bI_+RgLx%?r1eXWGwnrk`ncgXkI~w%Lfz{GV;U&A8dc<=KVl?)vU(j*F|iAVV!1kAt+u0j#j$Rp6@n;(ltg@R@%$6 z)6I+X-C!V2rpo20GFXkGLSbMaiUx+FC@w_B(m=UD&rrD#6@n6-g&-&dVOR);2e*}x zBF#}01=03GFjsA_m1;gR? zY7kUiQ2S{$1l8Z)6OF9L&)vw~Mv%M~FEpY9{yIpG)?QL;BR&>=Tk!LS!U+v=$D@KP zIC@#v6^=!3YLF`=L#^Z>ja=1QNpL9mdp|<^@jwmZ*6^FX24@Yw)oZxf8ore^M9D_n zO5yy9HO7JSjk|5+S9&8^>Sz6sjhHdq{(`@KF@`aN$D(h%CMpJNKPR&GB>&>i^Huwz zzsXx)yt!UX2J595#`Y$C3swZl#_wvzU$z;pn1tQ>Ks2R`hv9u#kKI5L>hB|y^|I#I zE5JY^FKHf^Uk&aJUi>|O{WY%N{Eu9N>Kl=0YzzwLpAc5ZjY>7|ZZ)cE{LP+pB&wNI!&dD*bMY@(*; zAY*EO!Ftdz6K-SZU=%b8Zs<@H95)G7JZ>zOvft>Am%eY#`Ta8-Y;?&|= zjU9Q*LA4xgHFl;gL(-$7*Q23UVhvO2?uhK`p!C+K-A{+A`1{qQ(v-gKor69}26^X26OR*=@`zx6+&K z+|;t08aLzF%?@r{cC(Y4gXv8*iR(im=@zdOXo^(ot3kb>o>&MYkk7}ia4Grqp!OGq z=R-1hB=~tGXoz!%HF|1IX?{O54%4jP^VXm#DAafS-J7ZEeB0lmNvu}|wXk)m0V7g` z-ai~vt1foshw7L-ohUo(gAqx**hsadF?3E-Qid-il@@S!U5b$T? z*qYr%gQ~LAe`(`KWMOR9~N8v*$S#Q-#@FA?CX_;I3SsCF~Zsk*05{Rcu zC%=opg5;C-nK3LWo}A1_NJuFok42ZFHbOO5YM$kdF!@XAoHu%NE^V5VDv4K^-t59=e&tY!!>@oU$}<9VSs*|9Zj--}9y$xEsFyw(SPGv3~0HgiMyfh8HW zUz1_?IFoam5t&8)+$QdNiCB7L5_vgHeuvCSX7O?lho6vh%m7?pJSnKF#FMq7(LM3J z)LY{E;z?(hldJ7H>q4)4;@Ri{zP@;Db}F8oSBa;u_gJ)&OXqTs>-T@Tu5|qT>q#dY zkumqno8k4jNjG?fe{^JYAI>%XjUG-vcWoGPGQ5dM#mgsirGC8!<5Sm$VTn<)*}M2` z=~y(C<8!G8>qj2VqVZ*G{HaZzN2Yw(55FgN$v;Wsy~~;RZ#T)o z54TnCiEH_A9EHygl22~pk{@pC-V+De@N{fn^v6S+PS<{16388pzXix^aq9p*>Z<-g z;6ls71G!yGYQ;?b=Ynm;I0&gBVN(+4xj^#mfZ`$;imKEbftVQ8$-+XXoVZ%uj^lDb zZ6#4l4KIaZah--JiMpg_q~OJtRczK)Y&A;i7-%)hzO~?jR`Q9k+9hnz#0L0 zR;v*?Ci@OIkX{v>FI~v920FRk84)kWhp9MG4jfNTV6 z9FqJeE33&jzwldMD0WmZ8ziN&$D&XAX}bLOe-?y|jY3^wtz)tqFVgVF#t&^!b+kw) z0ajR8_3e|sW1&$@{&{dpI8hB?GqBR&q!TH;H{X6KwHVO&hQCbKk40bCWjsYj{%u_; zHUFkv2we&X?qB!!CgMW!Ek7*1`%X6LjlMypRBMvo%X;7Ry(!3a?q%O=jVgf9#El4{ zRy~2q#gx!w`zkCmZiu1VS0@_<)GjHs$h;nIQDzhtm>af1SiC(Ly(Hcqi+)23h;_%J zUrsM0(iOY(%*8d{eGFsD?CZ*J7bM>aAC)Eu?B_MJF_6?~9+71EuAZ7i5b^m2|5KE$ zq9kUh;Ir!Db`o|lrnuXPGd8MD@*Ktr8ZiZCqv-X0AbG)+F04|I2We9fVGM$=?-E>aod4+cITj*KJf*t!N%_6Mp%J`2owC<}Wi@^tnLoyb)&Byni+j#BdoBs&{l~*@dwE+2Wh^joO zm!he73ci#MMzx0aI7-Pb8;54#9<}k|M!6vX`gY^-;p8_He4uc$tc)e7OL9QOTLZh;2 zT*Z1_CB>UiAg37&4OWwNs_r29wRKF?Y!VDsR=uEehT{S1Ww0&cUF0tJ*RiPNt+(D9 z&@4oUR`a#--|l0#&D6bsYN25pZ|t`gsXOKdzcx0uTMG(b>L%OI%Ldx6)He6im7OM| zt@BuZJK5W+ml3=%Kl3Um+a>{(GUsioCO-~hK{CO(@caUw`%g}G>%za1NiGx7fQz31 z`vK%rs#n~ALGsrnVpA+@$>cl0P6eiv?33kcM^OSOot$LIz$9xDpIOC$jR^^XwGCVa zFHz3Dgd4;qNLbf$7Nyi#z*BhQf}XP|$yu25@Xlh>J z=rXH#T}JVShAPtKD|FP9Q|$5D>UDVlT@E(sve$H3i`gJ3dtELAGRF;lELvdS?5=3V z*CjQ&3Tbn+kE36K)w1t{Tlv+YoFPJSO38KG?HC;@9m0yc8@l} z$|xg~$LBvbjGuFbM~7ui>FRihhlax;0HP`j4!-3O@gNzB!keW<$Vs_$>{nJQ%<+QB z*GVl0;2UjaH?WXPogi-Fz{h$t#oGck%z&oH9=Zp@a}>x*JDD`F;CuHB$yL;umaIQC zT*Uby^9lg?3WkcHt#(QyBuyD+S#bfSwJRcf#-7ksJV04SdL{1&1)(P-gad^qbO*N^ zus6B8<&_}kBsezk6~9RYB^Z#k0FF35e$W=runV&Y6HYzZc$e5}I zBpLusws32jnj#0CGGl8O3uK#QRcUdcuBtS5U7XlQ}u5cva$rTA9=2Y^&Hc+UDSvXD? zjK%d?EZ*n#Fr z%9eKL)6*ne9X{Io>1pIgp}l-a*rnB!P#F8k97J`F@JgPDV}XM?$BuBzO6pc|S{^VkJU85y^*IA_Al|i;^3ZRI82> z1t%@gT6!;YKvtO{aW`C7oHASz= z$ZCpSl~L+8;6s0v5m=RhszNml~!JfcW^NBuR3kA@} zqN;Tv4a7wwnFBQ10N|jK<9ww=@ihcB(4nG)#VJZ+UzC6nN>>kXGN>LhQ0Z7ujEbo< z>c~n(1*4$H7aa|23s|i$c@wybn_iU-@r8yAQ_7!jZ3L+@ z!>L@E@t|JV=5$P%*&#-mgG`x&T$xqLV}(j5R5p1ZB*pUX1x{#hL;i`>3T2jeX3bQY ziMm^r%vK<8QfAQaSLRAuGNa6utYj7w`buUJIg;UH(OwsOWoExAs|hzNa|LClP9N~f zEcYO3xPe@m4gYb#r9|;Hq(qT%C^HcNWiEJSMg&sk3X-Yh%FM2Y$|sZ=g_AO?;HhNF zjN1ts70C_9F4ICpe#dCA~}C^9!rwkt?u>{g(rsR!bdl_{O^1|-$T1*v|=7lQ)~0Ex*?VTbdctGwUjP>yrOLe+FGKw=N=xbQnp2lR{xhh@ekV;FNoD8<)rsAqzmo2$6eYL$% z=`zDeV5GjeE=#d9UAFFiT}D0nbs1u=)@358<>{#|tJ?OPKKS)qm#?=Q?sYof3#Y=j z^v8SCYwU)b^7@1avdq01b++k=ZloEwwA-?75iJ4mP?AV|fNhNph^g8mxu4KY^|V z7Aw+W2$4`51aH?&ly3&oc|F6)v-y%`^Yji;&K0i8=8L^-9;>F{Bb#TO3;|#$qLLV9 zk88U|%L`D+h05k_Nd}Y6mo&Dl`I=(oUWh6*^u*Nm$Hl1pE@7{ZL6}e^gCV~64`o-d z#m+g#kZvnlj8SxgMIH$)y_aZ^$5R6>zik*`Emvmq1<~o9H*`{4kj*P!hY}PXteInv z4oXUVIiL9T7;*|j?0Gi=!)lg)Lkk7(fG*cKNubc?TfY$ zVd@SRq7~+~x9b77ZG)!(!u;u5#4{-qbu7_as53D-4#{3sTCyt$U|(kD!26Wup<$rN zrx+&w(zI#2BBQNfxA_XG?rUj}yk8+FBsEml@+3is6g*YeJ_S{^acu>q_iYsvP25UB zJ<~GeMvmYP@Y{xcF;oy zEXq3Tfng{Hax_9-B{WB*rMcU#!pIpfKY4yD>^vMOrnCI|$`s7PemE0_=+zHJlQ#e)={(?8~9yTkH58I|&kNO?RCS9u2GkZxL(B5>O}k-W4#EA^jT_nox~dUyyIQ_C`rQ0MSJ zC0x)2SjaO_*r$-e%~1Nz2_Xa)C57524QaX&8&vIAQYlx4Hz-kE_40H){f@@9M$W0h%;3ff;YT6|Yv)mXGvWMr;f~KH6*JFji3e zSnoca_3>WIXgZ6>A3wBZ>ks!@jnRKj_d&G~@bP%ymFqr;?^=ZN*omvjItTWR;kENq z$Xw4wP@BBgOV)8*eqQZm50fpyZ9(l7-PC?opVjwr4C^Yb@_uJ1x%?T17+-!>gX;*V zPX^U;ko;9WXRCfJMkk`F+W(%;?_eRpe@DxB*l)n2KPuSY{OlhE?CPIE zlFr!sJ~^tEQ(E;t&zgXiviB<(oTCG6k#@+ZV`$>e!3OS7MIc|u3?a~X3;SWUB&hz( zW~)!VkM!i;V7oVaMd4U}t{!6&pOb*Sx!2gy^=RxOBw1bp3{6Rk)~q!ibDm1Q$~*@KtO+v5N##wtZr!;ZD~R)Zg9ae!GJN-H)N#Q|aHpP^GvXB=Q#PaG&32iVBdp(W^H zrwt~f7cgrQ8-X`V)dOZ+wM*h%JxVy;Dj}elaH{t6YJF(3x)?A$@TyaLRgFmirLwdL zQK#CN3~tg##%$6@fady$ywS%b$n-H8^!k|WU#r$_vD!~*8F@t;6)0^co5TR%#10|hC7Xkme<_L^a01$eAi zIAJ>e&d{%i@E;c7#_()0yE&Q`c#nrI5notdl{Igsg|?#$g}QAFJlf#&VAq~6FokBG zM7r`MCj*d&N{FGPz`ZPv2|ONHP;&*_EehonLH34RnSI&}OW7-aR%l$?!Mdof8m`B@ zuzIE^F4uRWSqcWVBzk`}7yNtcG#v>zjwZe3s+Jtko=<4`C{`6gNTwI0^NGu36jk3# zPht`F7!Cp8GO{kmJXCZ-5Aiy48NepngIrOscG}BhHi@udUyZM}3(C z_pT+iLQ|73h504#F{Q2g`aEOx{W9Ou^sk3v3781R*Ex01*Hyomo#EV;#`@;Y+81rj&uJgSE41IuI4K^QB)su?w%5Ll-w z$)mHE=QYsMDSHlbuht>J6}1h-GU8~@+42xuD}#Xc3=igb$lJ1{*ubt4l}}Lx52HjK zmZHoAjY1KMEWl}0DL=2VrLH5We&=Lyl5!_}uFOvrq*A%eQH)|bqL};!XQPHJZ~%jM zl@>TlR7}3}VGe5YeZ0kUo}w6?ywKr&R6oAjswc#zF0AizBW|Zk@V5p!j}URVYt?sVBIf(~$Wi`|m_r*m5`@W3N{vRmpBhyl8+Eit^)V*n5*h2pY!F+N z*mZrh>PFqJg+>pr#^@u%ymFlG!h#3v79JoQ)V?6)qqrOymQw)y2->lm(cvvY*ceZq zY&FJ`ik`vXQ=;U6d6y(+oJPd;@g%J8aAWn|(y$5UjVB*gjl7>JN$A2Z*FW-)3zl|{ z2>zJHiFaA%hq9=7VLUb{MYS(WJs59$C33O^DL;UB|F0M@WDw$P06ztA#ekX( zC}acNRRea}fMPa)9}2hv0yzRWl(GRwuNtt|1`K2a?!Ib(vjKz{PsJq#fX#Fsuo0DP zgibl;BTyUh%ydjoqE#&8biw7pVWd#ccyEQKDELbH=UPL`bPKW`W@ zPnrT^X8L=Rik?D;b>6hC3kF~NTv#8Ar#xY9U%xu*ct`4_^xfQ&TaTutG$;^K2sKe*c1=O(=66T5DZh_C-2WURM5Wz(xHF{1O1Y-fwE$;N)(cxY|A zyS8(9XRTJ-+50o3%Q(BQ?JO77U!bqU>P<(gY;UR?e zX0%`;DH%Uc6Y?82`--J~(5%#IC)^p@+kZ2e-MmV&Gen5+msg1iE6mf4zz8lo{Z^9Q52;+u(Qt`1?-=#wx`;~Q!=?=BnrSfl=bDtB_ z-q41W6u^zORjIoZ+dqhK+=;@Abw?m0kzgevP7IL3n&(QZjuzDSD%%HSZ$-c0rX>be zlcOo%0S`FN0B=G&T_tkku4WMzrRI8s*EYw!VtJ}gtp6!i;V74L&o>-ehc)D~@?PME zWTU1KX+0nDQk1oQRF|FrR%4@N?KLa0^1X5xgb6&xlduW^~#PVro3 z;cjq~ll6T$gHHrcK^7t3jeAyu;)q)v?;&Hv~wA9&W%*HzpP=kQBI4x%N?R$y;E}qa4#$VmC1i&p`I!VZFZr zvo|jB5|gG9``H2pE(vhrbi2?P<;5r<@pe>WY%+a-z^t)sLl*jc66j|!xR(hs?S&Q) z?14Hf2b%|zUFKr}soWJ>N2NL$l~>T{MgiwU3FG^ri4i?u7&(;h0{)0a0|r}SrHu1W zkVdVozgYFiklyL((|V3;7Z2lV8&Hf=ESh09zTyqO@#%U4oxKt697z7W+4N0US-bE3 z3wdwyl3DzZ>eE~Njg$48+>H;`4`}JerydCG&*k-5*m9%tTU5-{!RqxrZZuT}UNV+4 zj8X|m8T9cElz~~Bl!0j|gUS?i^GanvetjhbXjo-8;x!ATbJ z`ZQ197@16d%(tbs>l&_cdNPS~AU(BLy6v^%PmEZ!6yCZ$xmI}5qX*yN=OF%p88-yH z+-Rf%3xXLLsr<>JB`&CoC~r63mHfZ%URd}bz50@{-XZa`tTNSy*O;S(l=0uw<7Ta*++IN{uCR#~lNm=nBl3q}i zcddYZ1v=8y-aMAir$@y$!_|$SukWMA8}DitdM|EZmm4!jIqCxfA@RK1WvTdt+q>%a zA!6Wi94vK#DMV+#dPX!+0y`oniBP;IIezECkl&3b9eq%FE~LLJ`E54VfO1F;;wWw) ziPqd9sjG|KRVEWA4fnp|j^P9z%?RDiCWqvl5?-<_&znEb=Qc`d!Ug%e^A zhibx{o8ot6ae?sz+TSIgTxc9h23Co*egILsl?+Ia4sqs&EeVe@f$VGl?6yS((}bgE z@(^`>r&%gZd6MPwj1G*3`9fryX?SPYCu!>698CD z3d&n`j`Bv@whuj2=+qIWkl%1Lzpb&=blw~HWfquSeD^+EWioB&b{xRr`NkpYgmHK1 z)MWiIs~-THn*{oZyx`$a*AF`dn<^{AD^uqH_j&kb*O1dut3oj9!{iiEajWcZcJFSr zyiP*CVweqaW6}t3NY*GPAj3fkq;COGo?{-W^wv*6wPFKJFpzTMzeADWmDhS{Dy0JY zO%m2}2N$FO5*L;+Ld7-q7@`PS)>apX8zY%Ln_%{Wx03(*A-SCiRkrJ>Wk^zr7npd2 zrXPG{wJ}QB)uO0f=SVZx`&;$>Hmz|81-qb{$69#uhA*36H*fEeh=&wH2aM@(S2TMnv1 z&ws@+rf+OA_fWfWVDgdSGM5LiKs^tuzsuN>t=za7zQ|534`!!w5Ju3V<;~OEXl0Wn zI9;mCi&=TQf2}dnA)|!Gv`5lVH74V>8Udc@nje1dj^eKQ;v2 z&6LTC9%F9zqmzw^`gpcE4T9rRe#`L7<|J&d9AA=&zP~(_#PA3vdIdV-jQ0w-j4_-% z8}3+&fVv57(u}e}l|&B@+bqp!$UAF6NHF$g_AVHxG8>YNV7-%^T~jtG z2|1cEV{4K}!LOpf4^Q^!;2x(WM`q;j(|$Wc9+Bk8Gf{sf`G5uu@*ZBA{@coBwSn!Q zDD!AIpbWy1^X|ZC*Bj%vub~$Z*|n?d%b#(JtP`E_L%&LZv`(s&Sa#U5mz$jd$t*o zE`dd4GD1aBBr;HPTcuzqxW33H_{_XopGWh17MVw5<0f-z{WjU})JT4Fo0PZ6r&cg1 z#mk<%0uyjy3&(b&{Ya&RHbuteuQI#!cWE5%c_D1D&jabsJ5X{I=;W8ESu+>N)h^3C zp5-6;AQo2g!Qah3f${+3-|Q1q{iMENGTGTmc=G9eL2pfW(4ulK0__1>Y0fh$gN_tP112Rd4pUu$I%<-g3k}9zzLo%5W>W2p2F3q%$PSp=f zgyTsU9``lkvC}7Ohx67~$hR%}$Xk*{DLazxjVC|o%Md2@jqc6+!(6fEh^iQEZ)>sL z3T;0BC;4}WKl3sbfJm^s|L|uh2TTcVzhOf9s;3pbbPfluaAeQKiRei+*Z+4C)V==m z_+3+Z0^igBCtOwO`;AG|My1%$&?f$fGP`};ZEq=+v*$LOk~>S;IfbPx!G}rz+@iPP z^+uv!nRJaWSZ<0h-q^UPXWbMGS3MA*8v9khMG*u?r`QjHUy>p@tF1sHN-j<#846Of z^A)`vkQv$4NOJ4Ffp!cc@PVnR=z>6-96tR<_xCL{CKS)eEXE(^86Xuc97z`;$_*-4 z8pqbiX(oAoLCw*Znxa15PAuAOX(!`-?wlI*YIpv()gMS)hXn5Q98X&JY zG7-4&u88NXoL$_ai*3GZb-nXh2|qx}i-bB2r+o=lv?se;uXsmz%@yywcGuD1nzL@- z-1P@S<_hxPcJA8_$WN;Lf!KaE*u!za58rOD*8YxEgA(LwsZtk8kE(w?>>dBx;C;=a zS0`+f1EZ?BdH0CtRI*POs!cz5v7y7HdKytky<0tE0zV&kg?o!MvS(M!A&R6w+oH@Q zw?E+Zjnj_;rfOZ?8zg^QKf9j1sk7exIPRY8J&02r_9G1HsDO?o+D4Ora(jj!c7j{#{lehmKnSD)b3J z9c%?oN0kUlyr#+yAvEa2MurMKyfEoU5`pYc(^ey+djo`%Jnu*HFi0cgtwzT81~DVq zH`D6u)M+GNMLp7FRTuq#gWzX&qao_Iqumjr0mJOaa}ixln}e~PE2T3|R$vLgE-GpB z+!mw84(+PU4{9)(_gIy#^Hp?FdR$+1G$6=8wwxn4HU095^^PCsEN_8xmj;I~;OLYY zuLWr5QSmCWb0B@d>``oyS@wpqjqr!ifatXslte#6#0?K5vZo&mxh)9Z8$gEQKezhg zyjA2kbG1w=Px#gInDD|um>L+$d5$;^LVjb-FKO@$)WHzz(K~C!q|`cum!Bn=2bu?i?oSHHUt@K2w&+CFKXISQAd)ohVW(N*sI5a z;rW7fID$wxl=@iPB5ivl2~!H?-A&k+)3Ak4OA&Pi%^ z`dhd5C4pQ_yCdqJY~VybP(NtGZ1Qq_)%@2G(^m3ni}}^r{Aw-(h4c!m$#~np$uHAz z@fS~ggs4(`L9*pYzqtU;;vbL4L7Xpp!Tfk^FVX9ebtJQwSn^Y)a{bk3I2mt}vDjf= z+kSFNJ`RrPxk&1=y#vS5NRB|t#)g{*5JGGw6*~*0Hw)EY47E^D56x`y_cH6u2AuhJ z`Ec*;ky+jtZPkcpzIvpoBn18-dM7k7=3G_vdQN~C*E_=QzyAJ(7QYydv@c4(bzaUQ zh*mYU!D^xUyAg4X;lV=+ZT>%K^>38=KxfD^H{%p8j-Mcq?OK52FavXqKm59Yk7Jzu z#&TK6Pt(srDMAsw(ylPU$57C z+`Ku|1K7i{RTQDKB?qCvyM89yO7<9wv>5n0;R$o3o!L_MUPXaKRxhbpOdu5~itW4^ zw}&(cet|&kt4f;qk%?fzg)W-p4NlZ`h!6UHU47MGi5Q)PmU!q6ndmr2lJrS1#dI>FgaHAHVZyf@Qy%1FYmeCWM@23k5kTolh?5E=VH3 z0BswunP1{!im{jU0Mqh;e5yGs{eJixHZ=p3Lr!AB5~PjiT5R>+m(q~~K#}O(c|N(k zC4d}$4(@u$ z+77R1j_ots?6D#ql$63+!jpAezW)`3WQRBE;A|(plPOmRd&cp!55zK{Q!-eN!{{{1A+N;k^Y%M(WqJLism=;+1Z$k7iSKKdc9j~u;t_~=DP z(__t<&RV-U=k$*wy6*C_>ztceT;$3vpYG1gFS&EgnWg!qXU?oGx^}a(w%F|iPcAa6 z>E@Sa+jg;6z=wJ$?W3xs`iaS%2QGkDFG4rJ1E*e(6l$Iy0;` z({VGWm(MqY$CJl{2OfUve|q`@Pdxshd-AC#9(pQy>=t+X%};;8J^g{FPCfq6R^3lO z@z{gc>Av&kryjUY_Z>Gso_C*m{A6%)eraaWou64;Yj)hhcf0v>D~ru@%_aV+$NA-@ zBXiA_=F%Luhwcq-4Gu1?EiN7k9yr^6w$oi+YPu86rP;IRX4(t4xDPhl9cH*YZoSo;mCC74F5h+wJMwryp8eKK;Mk{lBL7ci8>iw;ksmxO~j=@x-l<-pZGArZtL^_e*E^6FB$4d}p?uj&i|OBmA(ORUNm2GCplw%_iB9-be7k zJ>$Nw*LmetAUgW^a%F#ZZ{zy-m9<~_9bD5s27{Y!KRU;!{>}~t zPp_@4EVsL+8ED73nXciwxD3K`%WJ0>o0-DSciik+yN!M>KI`V@J2Ujn-8%SzW!GHN zwVUtW;%4UN+>yoEBP;FsrEX_-X6c9^9Z4Z!@!hjcw>ZDF;AWT4ots$#KycIIoEf#d zY4ME9G*!)K++Yv{A%CAD*M#pW~E(CKHpO@^+nVH3^Zf#q}ZBF%Ptxg9)Z z4#X`jckw0sJFgwt7w83#(=y|_-DiU*o9%P+9kU?JZN51dJb^gP2yGrW=jOXz+8@VP zt<9cwXXc>~+nJkhH)p&2Tc&xB{s~oKc2Xnwr&K%mt`Ew>&CxCLug5lxw{tUH7Jqg{ zc(~5W%q(nZp6OzZZjOJ87Al@tD|K(pP)I=S~bx4hQfJowC72Tne`-03=0Xl)69K6}=X zOXlW))xpU-#snwY%L~mVw=xftPTlrID$h&=DcT4D9n-X21S$W#e0vf)h3oGV*axtD zPo;76!HdmV2?m8v7s^iw)+#;4n|MrO)jXfl4!HE}xgD<~AW<}y1mGk0aTXq`PfkD+Axra>7aO!sVQ zsaue*Eju$d>7OR+%LPOYCf$u%6UEnY_H#amyE$ z@O^We%?3-KZQ>7tlWR*}OEfeJ&GExFr#3=>6`7`r?ZKM6?8U22_ zh_giR(@pr-X@Ui#C6ebLkb$6hJf6V!TW(P0k4LW5KkXP5VB{)P0bzeMvcxcKEs?UcbSTxnK>uZ6okCfOem!&AGmpq z$D7>?%k2dj&K!IveEIu+E85C3m;AVXLnIAqr!MXE+L<%(+IyiMK~Ed=v=+&Nj2-;b zHeuCk?0L(XMXY)5Sy}K(d-)kN%U*APD@uifG_602+s7aOz}hk{v3YT}*_@MDNc-l` zF0VKukX#N;_s^`%oSt8t@6MAi%`bI8dwzCarWpil(c{5C4gXc)%Z2gqFBbnpG*(WAxD=wk3x^uG9a;%^7P9zS3BM(~CB+2Vm{DLfZ93O`?XAo%_8 z7lMz)9}fOaI8gXx;eRU}4n7p#6aIMcwc<~O_eUqg|5OAu{!9de!NkObF4LBZuB-ac zpNU|{ApP;+KoCw=$oYdxiOy@w5eQy0{ zQ@=59>v4JOuKX$pcI(1lksIh5%OGoAVA&eF_E=j?KKnvCoGeDlKX z(}bdq{sH^&=sO(Sng6p*n-RT(8MBI7)5_<-WeVj+VkD!z~Q$Ci%*@MCaLU}cT9`V=cbkQO)CTKR7%re*zOJ%l>Amp zgrnK%V?+M?_^|)Jds|5XdHVM4{`-zvNu~Gl?2hu>qQBZ%IMZ%6M@n;x`rcL23d^&j z+o#1Ycnk-`=CR`RgCMfIIz6rRrq3_W&y6$4q@T%pVzj>@7wDd>d9&0ev+0>=_U=lj zZ{q*HZM{}8`G&q}#>xG)bU@B7SF~tXFLX}xL&C+mwR7j5y>Sx@=fuJTB`Z_T+%z~n zy?kbITJ*kaXd1rG%`Y3@Z>~&Fcb9FIgBnB{a%OS)!l8kcwJu3)``z@PvF?k9Oa9`P z;u$(SM+*A9wP-qb+i(WdZjY91%K6*#X1l+mj7D?_f6v|t$sj>0J#9UA4NOljiuU*9 z*y-+AxzlZ1)pNXj{*0&P?yCQuo?UJ??-^KGUXdl<8?VgFy>~+S>$I=MrU~iOc`EY0 z5@pBixs~s;@0Dl2U*BZ;_mxp_j~3(03Q}-yX~2!&VvIN zX4~E6bEi+1E}*FAPCrycu`aF*>M+>z7IhVYWDjt|~`?AD{V9(4!LcDpN`dvCk#nfdP7wbQp!Pu{k; zc;0_de=fJkVBB4}?c?v6J$lEn*}HGQ^Y(jA-+9lSXO7)<^seJa?|9GY_uSPyc2Dz; zyJqh=6x_2NYeLzl&opPcY*%&S!`0Jkiwj45L3ViixizaJk37T9!OUXkaJe)8%+e96 GivJ%2hzY|0 literal 0 HcmV?d00001 diff --git a/wasm2c/benchmarks/dhrystone/main.c b/wasm2c/benchmarks/dhrystone/main.c new file mode 100644 index 00000000000..80a6c561a46 --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/main.c @@ -0,0 +1,265 @@ +#include +#include +#include + +#include "uvwasi.h" + +#include "dhrystone.h" + +struct w2c_wasi__snapshot__preview1 { + wasm_rt_memory_t* w2c_memory; + uvwasi_t* uvwasi; +}; + +#define WASI_SUCCESS 0 +#define WASI_BADF_ERROR 8 + +typedef uint32_t u32; +typedef uint64_t u64; + +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#define RANGE_CHECK(mem, offset, len) \ + if (offset + (uint64_t)len > mem->size) \ + TRAP(OOB); + +static inline void memory_fill(wasm_rt_memory_t* mem, u32 d, u32 val, u32 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +#define MEMCHECK(mem, a, t) RANGE_CHECK(mem, a, sizeof(t)) + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +#else +#define FORCE_READ_INT(var) +#endif + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \ + MEMCHECK(mem, addr, t1); \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + 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; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } + +DEFINE_LOAD(i8_load, u8, u8, u8, FORCE_READ_INT) +DEFINE_LOAD(i16_load, u16, u16, u16, FORCE_READ_INT) +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i8_store, u8, u8) +DEFINE_STORE(i16_store, u16, u16) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) + +u32 w2c_wasi__snapshot__preview1_args_get( + struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c) { + return WASI_SUCCESS; +} +u32 w2c_wasi__snapshot__preview1_args_sizes_get( + struct w2c_wasi__snapshot__preview1* a, + u32 str_count, + u32 buff_size) { + i32_store(a->w2c_memory, str_count, 0); + i32_store(a->w2c_memory, buff_size, 0); + return WASI_SUCCESS; +} +u32 w2c_wasi__snapshot__preview1_fd_prestat_get( + struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c) { + return WASI_BADF_ERROR; +} + +u32 w2c_wasi__snapshot__preview1_fd_write( + struct w2c_wasi__snapshot__preview1* a, + u32 fd, + u32 iovs_offset, + u32 iovs_len, + u32 nwritten) { + if (iovs_len > 32) + return UVWASI_EINVAL; + uvwasi_ciovec_t iovs[iovs_len]; + + for (uvwasi_size_t i = 0; i < iovs_len; ++i) { + u32 wasi_iovs_i = iovs_offset + i * sizeof(uvwasi_size_t[2]); + u32 buf_loc = i32_load(a->w2c_memory, wasi_iovs_i); + u32 buf_len = i32_load(a->w2c_memory, wasi_iovs_i + sizeof(uvwasi_size_t)); + iovs[i].buf = MEM_ADDR(a->w2c_memory, buf_loc, buf_len); + iovs[i].buf_len = buf_len; + } + + uvwasi_size_t num_written; + uvwasi_errno_t ret = + uvwasi_fd_write(a->uvwasi, fd, iovs, iovs_len, &num_written); + i32_store(a->w2c_memory, nwritten, num_written); + return ret; +} + +uint32_t w2c_wasi__snapshot__preview1_fd_fdstat_get( + struct w2c_wasi__snapshot__preview1* a, + u32 fd, + u32 stat) { + uvwasi_fdstat_t uvstat; + uvwasi_errno_t ret = uvwasi_fd_fdstat_get(a->uvwasi, fd, &uvstat); + if (ret == UVWASI_ESUCCESS) { + memory_fill(a->w2c_memory, stat, 0, 24); + i8_store(a->w2c_memory, stat, uvstat.fs_filetype); + i16_store(a->w2c_memory, stat + 2, uvstat.fs_flags); + i64_store(a->w2c_memory, stat + 8, uvstat.fs_rights_base); + i64_store(a->w2c_memory, stat + 16, uvstat.fs_rights_inheriting); + } + return ret; +} + +u32 w2c_wasi__snapshot__preview1_clock_time_get( + struct w2c_wasi__snapshot__preview1* a, + u32 clk_id, + u64 precision, + u32 result) { + uvwasi_timestamp_t t; + uvwasi_errno_t ret = uvwasi_clock_time_get(a->uvwasi, clk_id, precision, &t); + i64_store(a->w2c_memory, result, t); + return ret; +} + +u32 w2c_wasi__snapshot__preview1_clock_res_get( + struct w2c_wasi__snapshot__preview1* a, + u32 clk_id, + u32 result) { + uvwasi_timestamp_t t; + uvwasi_errno_t ret = uvwasi_clock_res_get(a->uvwasi, clk_id, &t); + i64_store(a->w2c_memory, result, t); + return ret; +} + +u32 w2c_wasi__snapshot__preview1_fd_seek(struct w2c_wasi__snapshot__preview1* a, + u32 b, + u64 c, + u32 d, + u32 e) { + printf("fd_seek not implemented\n"); + abort(); +} +u32 w2c_wasi__snapshot__preview1_fd_read(struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c, + u32 d, + u32 e) { + printf("fd_read not implemented\n"); + abort(); +} +u32 w2c_wasi__snapshot__preview1_fd_close( + struct w2c_wasi__snapshot__preview1* a, + u32 b) { + printf("fd_close not implemented\n"); + abort(); +} +u32 w2c_wasi__snapshot__preview1_fd_fdstat_set_flags( + struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c) { + printf("fd_fdstat_set_flags not implemented\n"); + abort(); +} +u32 w2c_wasi__snapshot__preview1_fd_prestat_dir_name( + struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c, + u32 d) { + printf("fd_prestat_dir_name not implemented\n"); + abort(); +} +u32 w2c_wasi__snapshot__preview1_path_open( + struct w2c_wasi__snapshot__preview1* a, + u32 b, + u32 c, + u32 d, + u32 e, + u32 f, + u64 g, + u64 h, + u32 i, + u32 end) { + printf("path_open not implemented\n"); + abort(); +} +void w2c_wasi__snapshot__preview1_proc_exit( + struct w2c_wasi__snapshot__preview1* a, + u32 b) { + printf("proc_exit not implemented\n"); + abort(); +} + +int main(int argc, char const* argv[]) { + w2c_dhrystone dhrystone; + struct w2c_wasi__snapshot__preview1 wasi; + uvwasi_t local_uvwasi_state; + uvwasi_options_t init_options; + + // pass in standard descriptors + init_options.in = 0; + init_options.out = 1; + init_options.err = 2; + init_options.fd_table_size = 10; + + // pass in args and environement + extern const char** environ; + init_options.argc = argc; + init_options.argv = argv; + init_options.envp = (const char**)environ; + + // no sandboxing enforced, binary has access to everything user does + init_options.preopenc = 2; + init_options.preopens = calloc(2, sizeof(uvwasi_preopen_t)); + + init_options.preopens[0].mapped_path = "/"; + init_options.preopens[0].real_path = "/"; + init_options.preopens[1].mapped_path = "./"; + init_options.preopens[1].real_path = "."; + + init_options.allocator = NULL; + + wasm_rt_init(); + uvwasi_errno_t ret = uvwasi_init(&local_uvwasi_state, &init_options); + + if (ret != UVWASI_ESUCCESS) { + printf("uvwasi_init failed with error %d\n", ret); + exit(1); + } + + wasi.w2c_memory = &dhrystone.w2c_memory; + wasi.uvwasi = &local_uvwasi_state, + + wasm2c_dhrystone_instantiate(&dhrystone, &wasi); + + w2c_dhrystone_0x5Fstart(&dhrystone); + + wasm2c_dhrystone_free(&dhrystone); + + uvwasi_destroy(&local_uvwasi_state); + wasm_rt_free(); + + return 0; +} diff --git a/wasm2c/benchmarks/dhrystone/src/README.md b/wasm2c/benchmarks/dhrystone/src/README.md new file mode 100644 index 00000000000..30d270e9bcc --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/src/README.md @@ -0,0 +1,23 @@ +The Dhrystone benchmark: a popular benchmark for CPU/compiler performance +measurement. Description and sources available +[here](https://www.netlib.org/benchmark/dhry-c). + +# Running the benchmark +Use the command `make benchmark` to run the benchmark. + +This compares the performance of three builds of Dhrystone (1) Native (2) Wasm2c +(3) Wasm2C + Segue optimization. The Segue optimization is enabled only on +specific CPU+OS+Compiler combinations. If unsupported on your platform, builds +(2) and (3) above will be identical + +# Sample output + +``` +Starting Dhrystone benchmark. (Smaller number is better) +Native +Microseconds for one run through Dhrystone: 0.011133 +Wasm +Microseconds for one run through Dhrystone: 0.013670 +Wasm+Segue +Microseconds for one run through Dhrystone: 0.008666 +``` \ No newline at end of file diff --git a/wasm2c/benchmarks/dhrystone/src/dhry.h b/wasm2c/benchmarks/dhrystone/src/dhry.h new file mode 100644 index 00000000000..be0f701e14f --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/src/dhry.h @@ -0,0 +1,306 @@ +/* + ************************************************************************** + * DHRYSTONE 2.1 BENCHMARK PC VERSION + ************************************************************************** + * + * "DHRYSTONE" Benchmark Program + * ----------------------------- + * + * Version: C, Version 2.1 + * + * File: dhry.h (part 1 of 3) + * + * Date: May 25, 1988 + * + * Author: Reinhold P. Weicker + * Siemens AG, AUT E 51 + * Postfach 3220 + * 8520 Erlangen + * Germany (West) + * Phone: [+49]-9131-7-20330 + * (8-17 Central European Time) + * Usenet: ..!mcsun!unido!estevax!weicker + * + * Original Version (in Ada) published in + * "Communications of the ACM" vol. 27., no. 10 (Oct. 1984), + * pp. 1013 - 1030, together with the statistics + * on which the distribution of statements etc. is based. + * + * In this C version, the following C library functions are used: + * - strcpy, strcmp (inside the measurement loop) + * - printf, scanf (outside the measurement loop) + * In addition, Berkeley UNIX system calls "times ()" or "time ()" + * are used for execution time measurement. For measurements + * on other systems, these calls have to be changed. + * + * Collection of Results: + * Reinhold Weicker (address see above) and + * + * Rick Richardson + * PC Research. Inc. + * 94 Apple Orchard Drive + * Tinton Falls, NJ 07724 + * Phone: (201) 389-8963 (9-17 EST) + * Usenet: ...!uunet!pcrat!rick + * + * Please send results to Rick Richardson and/or Reinhold Weicker. + * Complete information should be given on hardware and software used. + * Hardware information includes: Machine type, CPU, type and size + * of caches; for microprocessors: clock frequency, memory speed + * (number of wait states). + * Software information includes: Compiler (and runtime library) + * manufacturer and version, compilation switches, OS version. + * The Operating System version may give an indication about the + * compiler; Dhrystone itself performs no OS calls in the measurement + * loop. + * + * The complete output generated by the program should be mailed + * such that at least some checks for correctness can be made. + * + ************************************************************************** + * + * This version has changes made by Roy Longbottom to conform to a common + * format for a series of standard benchmarks for PCs: + * + * Running time greater than 5 seconds due to inaccuracy of the PC clock. + * + * Automatic adjustment of run time, no manually inserted parameters. + * + * Initial display of calibration times to confirm linearity. + * + * Display of results within one screen (or at a slow speed as the test + * progresses) so that it can be seen to have run successfully. + * + * Facilities to type in details of system used etc. + * + * All results and details appended to a results file. + * + * + * Roy Longbottom + * 101323.2241@compuserve.com + * + ************************************************************************** + * + * For details of history, changes, other defines, benchmark construction + * statistics see official versions from ftp.nosc.mil/pub/aburto where + * the latest table of results (dhry.tbl) are available. See also + * netlib@ornl.gov + * + ************************************************************************** + * + * Defines: The following "Defines" are possible: + * -DREG=register (default: Not defined) + * As an approximation to what an average C programmer + * might do, the "register" storage class is applied + * (if enabled by -DREG=register) + * - for local variables, if they are used (dynamically) + * five or more times + * - for parameters if they are used (dynamically) + * six or more times + * Note that an optimal "register" strategy is + * compiler-dependent, and that "register" declarations + * do not necessarily lead to faster execution. + * -DNOSTRUCTASSIGN (default: Not defined) + * Define if the C compiler does not support + * assignment of structures. + * -DNOENUMS (default: Not defined) + * Define if the C compiler does not support + * enumeration types. + *************************************************************************** + * + * Compilation model and measurement (IMPORTANT): + * + * This C version of Dhrystone consists of three files: + * - dhry.h (this file, containing global definitions and comments) + * - dhry_1.c (containing the code corresponding to Ada package Pack_1) + * - dhry_2.c (containing the code corresponding to Ada package Pack_2) + * + * The following "ground rules" apply for measurements: + * - Separate compilation + * - No procedure merging + * - Otherwise, compiler optimizations are allowed but should be indicated + * - Default results are those without register declarations + * See the companion paper "Rationale for Dhrystone Version 2" for a more + * detailed discussion of these ground rules. + * + * For 16-Bit processors (e.g. 80186, 80286), times for all compilation + * models ("small", "medium", "large" etc.) should be given if possible, + * together with a definition of these models for the compiler system used. + * + ************************************************************************** + * Examples of Pentium Results + * + * Dhrystone Benchmark Version 2.1 (Language: C) + * + * Month run 4/1996 + * PC model Escom + * CPU Pentium + * Clock MHz 100 + * Cache 256K + * Options Neptune chipset + * OS/DOS Windows 95 + * Compiler Watcom C/ C++ 10.5 Win386 + * OptLevel -otexan -zp8 -fp5 -5r + * Run by Roy Longbottom + * From UK + * Mail 101323.2241@compuserve.com + * + * Final values (* implementation-dependent): + * + * Int_Glob: O.K. 5 + * Bool_Glob: O.K. 1 + * Ch_1_Glob: O.K. A + * Ch_2_Glob: O.K. B + * Arr_1_Glob[8]: O.K. 7 + * Arr_2_Glob8/7: O.K. 1600010 + * Ptr_Glob-> + * Ptr_Comp: * 98008 + * Discr: O.K. 0 + * Enum_Comp: O.K. 2 + * Int_Comp: O.K. 17 + * Str_Comp: O.K. DHRYSTONE PROGRAM, SOME STRING + * Next_Ptr_Glob-> + * Ptr_Comp: * 98008 same as above + * Discr: O.K. 0 + * Enum_Comp: O.K. 1 + * Int_Comp: O.K. 18 + * Str_Comp: O.K. DHRYSTONE PROGRAM, SOME STRING + * Int_1_Loc: O.K. 5 + * Int_2_Loc: O.K. 13 + * Int_3_Loc: O.K. 7 + * Enum_Loc: O.K. 1 + * Str_1_Loc: O.K. DHRYSTONE PROGRAM, 1'ST STRING + * Str_2_Loc: O.K. DHRYSTONE PROGRAM, 2'ND STRING + * + * Register option Selected. + * + * Microseconds 1 loop: 4.53 + * Dhrystones / second: 220690 + * VAX MIPS rating: 125.61 + * + * + * Dhrystone Benchmark Version 2.1 (Language: C) + * + * Month run 4/1996 + * PC model Escom + * CPU Pentium + * Clock MHz 100 + * Cache 256K + * Options Neptune chipset + * OS/DOS Windows 95 + * Compiler Watcom C/ C++ 10.5 Win386 + * OptLevel No optimisation + * Run by Roy Longbottom + * From UK + * Mail 101323.2241@compuserve.com + * + * Final values (* implementation-dependent): + * + * Int_Glob: O.K. 5 + * Bool_Glob: O.K. 1 + * Ch_1_Glob: O.K. A + * Ch_2_Glob: O.K. B + * Arr_1_Glob[8]: O.K. 7 + * Arr_2_Glob8/7: O.K. 320010 + * Ptr_Glob-> + * Ptr_Comp: * 98004 + * Discr: O.K. 0 + * Enum_Comp: O.K. 2 + * Int_Comp: O.K. 17 + * Str_Comp: O.K. DHRYSTONE PROGRAM, SOME STRING + * Next_Ptr_Glob-> + * Ptr_Comp: * 98004 same as above + * Discr: O.K. 0 + * Enum_Comp: O.K. 1 + * Int_Comp: O.K. 18 + * Str_Comp: O.K. DHRYSTONE PROGRAM, SOME STRING + * Int_1_Loc: O.K. 5 + * Int_2_Loc: O.K. 13 + * Int_3_Loc: O.K. 7 + * Enum_Loc: O.K. 1 + * Str_1_Loc: O.K. DHRYSTONE PROGRAM, 1'ST STRING + * Str_2_Loc: O.K. DHRYSTONE PROGRAM, 2'ND STRING + * + * Register option Not selected. + * + * Microseconds 1 loop: 20.06 + * Dhrystones / second: 49844 + * VAX MIPS rating: 28.37 + * + ************************************************************************** + */ + +/* Compiler and system dependent definitions: */ + +#ifndef TIME +#define TIMES +#endif +/* Use times(2) time function unless */ +/* explicitly defined otherwise */ + +#ifdef TIMES +/* #include + #include */ +/* for "times" */ +#endif + +#define Mic_secs_Per_Second 1000000.0 +/* Berkeley UNIX C returns process times in seconds/HZ */ + +#ifdef NOSTRUCTASSIGN +#define structassign(d, s) memcpy(&(d), &(s), sizeof(d)) +#else +#define structassign(d, s) d = s +#endif + +#ifdef NOENUM +#define Ident_1 0 +#define Ident_2 1 +#define Ident_3 2 +#define Ident_4 3 +#define Ident_5 4 +typedef int Enumeration; +#else +typedef enum { Ident_1, Ident_2, Ident_3, Ident_4, Ident_5 } Enumeration; +#endif +/* for boolean and enumeration types in Ada, Pascal */ + +/* General definitions: */ + +#include +#include + +/* for strcpy, strcmp */ + +#define Null 0 +/* Value of a Null pointer */ +#define true 1 +#define false 0 + +typedef int One_Thirty; +typedef int One_Fifty; +typedef char Capital_Letter; +typedef int Boolean; +typedef char Str_30[31]; +typedef int Arr_1_Dim[50]; +typedef int Arr_2_Dim[50][50]; + +typedef struct record { + struct record *Ptr_Comp; + Enumeration Discr; + union { + struct { + Enumeration Enum_Comp; + int Int_Comp; + char Str_Comp[31]; + } var_1; + struct { + Enumeration E_Comp_2; + char Str_2_Comp[31]; + } var_2; + struct { + char Ch_1_Comp; + char Ch_2_Comp; + } var_3; + } variant; +} Rec_Type, *Rec_Pointer; diff --git a/wasm2c/benchmarks/dhrystone/src/dhry_1.c b/wasm2c/benchmarks/dhrystone/src/dhry_1.c new file mode 100644 index 00000000000..fb23dd3a374 --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/src/dhry_1.c @@ -0,0 +1,485 @@ +/* + ************************************************************************* + * + * "DHRYSTONE" Benchmark Program + * ----------------------------- + * + * Version: C, Version 2.1 + * + * File: dhry_1.c (part 2 of 3) + * + * Date: May 25, 1988 + * + * Author: Reinhold P. Weicker + * + ************************************************************************* + */ + +#include +#include +#include +#include "dhry.h" + +/* Global Variables: */ + +Rec_Pointer Ptr_Glob, Next_Ptr_Glob; +int Int_Glob; +Boolean Bool_Glob; +char Ch_1_Glob, Ch_2_Glob; +int Arr_1_Glob[50]; +int Arr_2_Glob[50][50]; + +Enumeration +Func_1(Capital_Letter Ch_1_Par_Val, Capital_Letter Ch_2_Par_Val); +/* +forward declaration necessary since Enumeration may not simply be int +*/ + +#ifndef ROPT +#define REG +/* REG becomes defined as empty */ +/* i.e. no register variables */ +#else +#define REG register +#endif + +void +Proc_1(REG Rec_Pointer Ptr_Val_Par); +void +Proc_2(One_Fifty *Int_Par_Ref); +void +Proc_3(Rec_Pointer *Ptr_Ref_Par); +void +Proc_4(); +void +Proc_5(); +void +Proc_6(Enumeration Enum_Val_Par, Enumeration *Enum_Ref_Par); +void +Proc_7(One_Fifty Int_1_Par_Val, One_Fifty Int_2_Par_Val, + One_Fifty *Int_Par_Ref); +void +Proc_8(Arr_1_Dim Arr_1_Par_Ref, Arr_2_Dim Arr_2_Par_Ref, int Int_1_Par_Val, + int Int_2_Par_Val); + +Boolean +Func_2(Str_30 Str_1_Par_Ref, Str_30 Str_2_Par_Ref); + +/* variables for time measurement: */ + +#define Too_Small_Time 2 +/* Measurements should last at least 2 seconds */ + +#define BILLION 1000000000L +#define MILLION 1000000 +struct timespec Begin_Time, End_Time; +double User_Time; + +double Microseconds, Dhrystones_Per_Second, Vax_Mips; + +/* end of variables for time measurement */ + +int +main(int argc, char *argv[]) +/*****/ + +/* main program, corresponds to procedures */ +/* Main and Proc_0 in the Ada version */ +{ + One_Fifty Int_1_Loc; + REG One_Fifty Int_2_Loc; + One_Fifty Int_3_Loc; + REG char Ch_Index; + Enumeration Enum_Loc; + Str_30 Str_1_Loc; + Str_30 Str_2_Loc; + REG int Run_Index; + REG int Number_Of_Runs; + int endit, count = 10; + char general[9][80] = { " " }; + + /*********************************************************************** + * Change for compiler and optimisation used * + ***********************************************************************/ + + Next_Ptr_Glob = (Rec_Pointer)malloc(sizeof(Rec_Type)); + Ptr_Glob = (Rec_Pointer)malloc(sizeof(Rec_Type)); + + Ptr_Glob->Ptr_Comp = Next_Ptr_Glob; + Ptr_Glob->Discr = Ident_1; + Ptr_Glob->variant.var_1.Enum_Comp = Ident_3; + Ptr_Glob->variant.var_1.Int_Comp = 40; + strcpy(Ptr_Glob->variant.var_1.Str_Comp, "DHRYSTONE PROGRAM, SOME STRING"); + strcpy(Str_1_Loc, "DHRYSTONE PROGRAM, 1'ST STRING"); + + Arr_2_Glob[8][7] = 10; + /* Was missing in published program. Without this statement, */ + /* Arr_2_Glob [8][7] would have an undefined value. */ + /* Warning: With 16-Bit processors and Number_Of_Runs > 32000, */ + /* overflow may occur for this array element. */ + + printf("\n"); + printf("Dhrystone Benchmark, Version 2.1 (Language: C or C++)\n"); + printf("\n"); + + Number_Of_Runs = 5000; + + do { + + Number_Of_Runs = Number_Of_Runs * 2; + count = count - 1; + Arr_2_Glob[8][7] = 10; + + /***************/ + /* Start timer */ + /***************/ + + clock_gettime(CLOCK_MONOTONIC, &Begin_Time); + + for (Run_Index = 1; Run_Index <= Number_Of_Runs; ++Run_Index) { + + Proc_5(); + Proc_4(); + /* Ch_1_Glob == 'A', Ch_2_Glob == 'B', Bool_Glob == true */ + Int_1_Loc = 2; + Int_2_Loc = 3; + strcpy(Str_2_Loc, "DHRYSTONE PROGRAM, 2'ND STRING"); + Enum_Loc = Ident_2; + Bool_Glob = !Func_2(Str_1_Loc, Str_2_Loc); + /* Bool_Glob == 1 */ + while (Int_1_Loc < Int_2_Loc) /* loop body executed once */ + { + Int_3_Loc = 5 * Int_1_Loc - Int_2_Loc; + /* Int_3_Loc == 7 */ + Proc_7(Int_1_Loc, Int_2_Loc, &Int_3_Loc); + /* Int_3_Loc == 7 */ + Int_1_Loc += 1; + } /* while */ + /* Int_1_Loc == 3, Int_2_Loc == 3, Int_3_Loc == 7 */ + Proc_8(Arr_1_Glob, Arr_2_Glob, Int_1_Loc, Int_3_Loc); + /* Int_Glob == 5 */ + Proc_1(Ptr_Glob); + for (Ch_Index = 'A'; Ch_Index <= Ch_2_Glob; ++Ch_Index) + /* loop body executed twice */ + { + if (Enum_Loc == Func_1(Ch_Index, 'C')) + /* then, not executed */ + { + Proc_6(Ident_1, &Enum_Loc); + strcpy(Str_2_Loc, "DHRYSTONE PROGRAM, 3'RD STRING"); + Int_2_Loc = Run_Index; + Int_Glob = Run_Index; + } + } + /* Int_1_Loc == 3, Int_2_Loc == 3, Int_3_Loc == 7 */ + Int_2_Loc = Int_2_Loc * Int_1_Loc; + Int_1_Loc = Int_2_Loc / Int_3_Loc; + Int_2_Loc = 7 * (Int_2_Loc - Int_3_Loc) - Int_1_Loc; + /* Int_1_Loc == 1, Int_2_Loc == 13, Int_3_Loc == 7 */ + Proc_2(&Int_1_Loc); + /* Int_1_Loc == 5 */ + + } /* loop "for Run_Index" */ + + /**************/ + /* Stop timer */ + /**************/ + + clock_gettime(CLOCK_MONOTONIC, &End_Time); + + User_Time = (End_Time.tv_sec - Begin_Time.tv_sec) * MILLION + + (End_Time.tv_nsec - Begin_Time.tv_nsec) / 1000; + User_Time = User_Time / MILLION; /* convert to seconds */ + + printf("%ld runs %lf seconds \n", (long)Number_Of_Runs, User_Time); + if (User_Time > 5.0) { + count = 0; + } + else { + if (User_Time < 0.1) { + Number_Of_Runs = Number_Of_Runs * 5; + } + } + } /* calibrate/run do while */ + while (count > 0); + + printf("\n"); + printf("Final values (* implementation-dependent):\n"); + printf("\n"); + printf("Int_Glob: "); + if (Int_Glob == 5) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Int_Glob); + + printf("Bool_Glob: "); + if (Bool_Glob == 1) + printf("O.K. "); + else + printf("WRONG "); + printf("%d\n", Bool_Glob); + + printf("Ch_1_Glob: "); + if (Ch_1_Glob == 'A') + printf("O.K. "); + else + printf("WRONG "); + printf("%c ", Ch_1_Glob); + + printf("Ch_2_Glob: "); + if (Ch_2_Glob == 'B') + printf("O.K. "); + else + printf("WRONG "); + printf("%c\n", Ch_2_Glob); + + printf("Arr_1_Glob[8]: "); + if (Arr_1_Glob[8] == 7) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Arr_1_Glob[8]); + + printf("Arr_2_Glob8/7: "); + if (Arr_2_Glob[8][7] == Number_Of_Runs + 10) + printf("O.K. "); + else + printf("WRONG "); + printf("%10d\n", Arr_2_Glob[8][7]); + + printf("Ptr_Glob-> "); + printf(" Ptr_Comp: * %p\n", Ptr_Glob->Ptr_Comp); + + printf(" Discr: "); + if (Ptr_Glob->Discr == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Ptr_Glob->Discr); + + printf("Enum_Comp: "); + if (Ptr_Glob->variant.var_1.Enum_Comp == 2) + printf("O.K. "); + else + printf("WRONG "); + printf("%d\n", Ptr_Glob->variant.var_1.Enum_Comp); + + printf(" Int_Comp: "); + if (Ptr_Glob->variant.var_1.Int_Comp == 17) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Ptr_Glob->variant.var_1.Int_Comp); + + printf("Str_Comp: "); + if (strcmp(Ptr_Glob->variant.var_1.Str_Comp, + "DHRYSTONE PROGRAM, SOME STRING") + == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%s\n", Ptr_Glob->variant.var_1.Str_Comp); + + printf("Next_Ptr_Glob-> "); + printf(" Ptr_Comp: * %p", Next_Ptr_Glob->Ptr_Comp); + printf(" same as above\n"); + + printf(" Discr: "); + if (Next_Ptr_Glob->Discr == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Next_Ptr_Glob->Discr); + + printf("Enum_Comp: "); + if (Next_Ptr_Glob->variant.var_1.Enum_Comp == 1) + printf("O.K. "); + else + printf("WRONG "); + printf("%d\n", Next_Ptr_Glob->variant.var_1.Enum_Comp); + + printf(" Int_Comp: "); + if (Next_Ptr_Glob->variant.var_1.Int_Comp == 18) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Next_Ptr_Glob->variant.var_1.Int_Comp); + + printf("Str_Comp: "); + if (strcmp(Next_Ptr_Glob->variant.var_1.Str_Comp, + "DHRYSTONE PROGRAM, SOME STRING") + == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%s\n", Next_Ptr_Glob->variant.var_1.Str_Comp); + + printf("Int_1_Loc: "); + if (Int_1_Loc == 5) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Int_1_Loc); + + printf("Int_2_Loc: "); + if (Int_2_Loc == 13) + printf("O.K. "); + else + printf("WRONG "); + printf("%d\n", Int_2_Loc); + + printf("Int_3_Loc: "); + if (Int_3_Loc == 7) + printf("O.K. "); + else + printf("WRONG "); + printf("%d ", Int_3_Loc); + + printf("Enum_Loc: "); + if (Enum_Loc == 1) + printf("O.K. "); + else + printf("WRONG "); + printf("%d\n", Enum_Loc); + + printf("Str_1_Loc: "); + if (strcmp(Str_1_Loc, "DHRYSTONE PROGRAM, 1'ST STRING") == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%s\n", Str_1_Loc); + + printf("Str_2_Loc: "); + if (strcmp(Str_2_Loc, "DHRYSTONE PROGRAM, 2'ND STRING") == 0) + printf("O.K. "); + else + printf("WRONG "); + printf("%s\n", Str_2_Loc); + + printf("\n"); + + if (User_Time < Too_Small_Time) { + printf("Measured time too small to obtain meaningful results\n"); + printf("Please increase number of runs\n"); + printf("\n"); + } + else { + Microseconds = User_Time * Mic_secs_Per_Second / (double)Number_Of_Runs; + Dhrystones_Per_Second = (double)Number_Of_Runs / User_Time; + Vax_Mips = Dhrystones_Per_Second / 1757.0; + + printf("Microseconds for one run through Dhrystone: "); + printf("%lf \n", Microseconds); + printf("Dhrystones per Second: "); + printf("%lf \n", Dhrystones_Per_Second); + printf("VAX MIPS rating = "); + printf("%lf \n", Vax_Mips); + printf("\n"); + } + + free(Next_Ptr_Glob); + free(Ptr_Glob); + return 0; +} + +void +Proc_1(REG Rec_Pointer Ptr_Val_Par) +/******************/ + +/* executed once */ +{ + REG Rec_Pointer Next_Record = Ptr_Val_Par->Ptr_Comp; + /* == Ptr_Glob_Next */ + /* Local variable, initialized with Ptr_Val_Par->Ptr_Comp, */ + /* corresponds to "rename" in Ada, "with" in Pascal */ + + structassign(*Ptr_Val_Par->Ptr_Comp, *Ptr_Glob); + Ptr_Val_Par->variant.var_1.Int_Comp = 5; + Next_Record->variant.var_1.Int_Comp = Ptr_Val_Par->variant.var_1.Int_Comp; + Next_Record->Ptr_Comp = Ptr_Val_Par->Ptr_Comp; + Proc_3(&Next_Record->Ptr_Comp); + /* Ptr_Val_Par->Ptr_Comp->Ptr_Comp + == Ptr_Glob->Ptr_Comp */ + if (Next_Record->Discr == Ident_1) + /* then, executed */ + { + Next_Record->variant.var_1.Int_Comp = 6; + Proc_6(Ptr_Val_Par->variant.var_1.Enum_Comp, + &Next_Record->variant.var_1.Enum_Comp); + Next_Record->Ptr_Comp = Ptr_Glob->Ptr_Comp; + Proc_7(Next_Record->variant.var_1.Int_Comp, 10, + &Next_Record->variant.var_1.Int_Comp); + } + else { /* not executed */ + structassign(*Ptr_Val_Par, *Ptr_Val_Par->Ptr_Comp); + } +} /* Proc_1 */ + +void +Proc_2(One_Fifty *Int_Par_Ref) +/******************/ +/* executed once */ +/* *Int_Par_Ref == 1, becomes 4 */ + +{ + One_Fifty Int_Loc; + Enumeration Enum_Loc; + + Int_Loc = *Int_Par_Ref + 10; + do /* executed once */ + if (Ch_1_Glob == 'A') + /* then, executed */ + { + Int_Loc -= 1; + *Int_Par_Ref = Int_Loc - Int_Glob; + Enum_Loc = Ident_1; + } /* if */ + while (Enum_Loc != Ident_1); /* true */ +} /* Proc_2 */ + +void +Proc_3(Rec_Pointer *Ptr_Ref_Par) +/******************/ +/* executed once */ +/* Ptr_Ref_Par becomes Ptr_Glob */ + +{ + if (Ptr_Glob != Null) + /* then, executed */ + *Ptr_Ref_Par = Ptr_Glob->Ptr_Comp; + Proc_7(10, Int_Glob, &Ptr_Glob->variant.var_1.Int_Comp); +} /* Proc_3 */ + +void +Proc_4() /* without parameters */ +/*******/ +/* executed once */ +{ + Boolean Bool_Loc; + + Bool_Loc = Ch_1_Glob == 'A'; + Bool_Glob = Bool_Loc | Bool_Glob; + Ch_2_Glob = 'B'; +} /* Proc_4 */ + +void +Proc_5() /* without parameters */ +/*******/ +/* executed once */ +{ + Ch_1_Glob = 'A'; + Bool_Glob = false; +} /* Proc_5 */ + +/* Procedure for the assignment of structures, */ +/* if the C compiler doesn't support this feature */ +#ifdef NOSTRUCTASSIGN +memcpy(d, s, l) register char *d; +register char *s; +register int l; +{ + while (l--) + *d++ = *s++; +} +#endif diff --git a/wasm2c/benchmarks/dhrystone/src/dhry_2.c b/wasm2c/benchmarks/dhrystone/src/dhry_2.c new file mode 100644 index 00000000000..276785cbf4e --- /dev/null +++ b/wasm2c/benchmarks/dhrystone/src/dhry_2.c @@ -0,0 +1,187 @@ +/* + ************************************************************************* + * + * "DHRYSTONE" Benchmark Program + * ----------------------------- + * + * Version: C, Version 2.1 + * + * File: dhry_2.c (part 3 of 3) + * + * Date: May 25, 1988 + * + * Author: Reinhold P. Weicker + * + ************************************************************************* + */ + +#include "dhry.h" + +#ifndef REG +#define REG +/* REG becomes defined as empty */ +/* i.e. no register variables */ +#else +#define REG register +#endif + +extern int Int_Glob; +extern char Ch_1_Glob; + +Boolean +Func_3(Enumeration Enum_Par_Val); + +void +Proc_6(Enumeration Enum_Val_Par, Enumeration *Enum_Ref_Par) +/*********************************/ +/* executed once */ +/* Enum_Val_Par == Ident_3, Enum_Ref_Par becomes Ident_2 */ + +{ + *Enum_Ref_Par = Enum_Val_Par; + if (!Func_3(Enum_Val_Par)) + /* then, not executed */ + *Enum_Ref_Par = Ident_4; + switch (Enum_Val_Par) { + case Ident_1: + *Enum_Ref_Par = Ident_1; + break; + case Ident_2: + if (Int_Glob > 100) + /* then */ + *Enum_Ref_Par = Ident_1; + else + *Enum_Ref_Par = Ident_4; + break; + case Ident_3: /* executed */ + *Enum_Ref_Par = Ident_2; + break; + case Ident_4: + break; + case Ident_5: + *Enum_Ref_Par = Ident_3; + break; + } /* switch */ +} /* Proc_6 */ + +void +Proc_7(One_Fifty Int_1_Par_Val, One_Fifty Int_2_Par_Val, One_Fifty *Int_Par_Ref) +/**********************************************/ +/* executed three times */ +/* first call: Int_1_Par_Val == 2, Int_2_Par_Val == 3, */ +/* Int_Par_Ref becomes 7 */ +/* second call: Int_1_Par_Val == 10, Int_2_Par_Val == 5, */ +/* Int_Par_Ref becomes 17 */ +/* third call: Int_1_Par_Val == 6, Int_2_Par_Val == 10, */ +/* Int_Par_Ref becomes 18 */ + +{ + One_Fifty Int_Loc; + + Int_Loc = Int_1_Par_Val + 2; + *Int_Par_Ref = Int_2_Par_Val + Int_Loc; +} /* Proc_7 */ + +void +Proc_8(Arr_1_Dim Arr_1_Par_Ref, Arr_2_Dim Arr_2_Par_Ref, int Int_1_Par_Val, + int Int_2_Par_Val) +/*********************************************************************/ +/* executed once */ +/* Int_Par_Val_1 == 3 */ +/* Int_Par_Val_2 == 7 */ + +{ + REG One_Fifty Int_Index; + REG One_Fifty Int_Loc; + + Int_Loc = Int_1_Par_Val + 5; + Arr_1_Par_Ref[Int_Loc] = Int_2_Par_Val; + Arr_1_Par_Ref[Int_Loc + 1] = Arr_1_Par_Ref[Int_Loc]; + Arr_1_Par_Ref[Int_Loc + 30] = Int_Loc; + for (Int_Index = Int_Loc; Int_Index <= Int_Loc + 1; ++Int_Index) + Arr_2_Par_Ref[Int_Loc][Int_Index] = Int_Loc; + Arr_2_Par_Ref[Int_Loc][Int_Loc - 1] += 1; + Arr_2_Par_Ref[Int_Loc + 20][Int_Loc] = Arr_1_Par_Ref[Int_Loc]; + Int_Glob = 5; +} /* Proc_8 */ + +Enumeration +Func_1(Capital_Letter Ch_1_Par_Val, Capital_Letter Ch_2_Par_Val) +/*************************************************/ +/* executed three times */ +/* first call: Ch_1_Par_Val == 'H', Ch_2_Par_Val == 'R' */ +/* second call: Ch_1_Par_Val == 'A', Ch_2_Par_Val == 'C' */ +/* third call: Ch_1_Par_Val == 'B', Ch_2_Par_Val == 'C' */ + +{ + Capital_Letter Ch_1_Loc; + Capital_Letter Ch_2_Loc; + + Ch_1_Loc = Ch_1_Par_Val; + Ch_2_Loc = Ch_1_Loc; + if (Ch_2_Loc != Ch_2_Par_Val) + /* then, executed */ + return (Ident_1); + else /* not executed */ + { + Ch_1_Glob = Ch_1_Loc; + return (Ident_2); + } +} /* Func_1 */ + +Boolean +Func_2(Str_30 Str_1_Par_Ref, Str_30 Str_2_Par_Ref) +/*************************************************/ +/* executed once */ +/* Str_1_Par_Ref == "DHRYSTONE PROGRAM, 1'ST STRING" */ +/* Str_2_Par_Ref == "DHRYSTONE PROGRAM, 2'ND STRING" */ + +{ + REG One_Thirty Int_Loc; + Capital_Letter Ch_Loc; + + Int_Loc = 2; + while (Int_Loc <= 2) /* loop body executed once */ + if (Func_1(Str_1_Par_Ref[Int_Loc], Str_2_Par_Ref[Int_Loc + 1]) + == Ident_1) + /* then, executed */ + { + Ch_Loc = 'A'; + Int_Loc += 1; + } /* if, while */ + if (Ch_Loc >= 'W' && Ch_Loc < 'Z') + /* then, not executed */ + Int_Loc = 7; + if (Ch_Loc == 'R') + /* then, not executed */ + return (true); + else /* executed */ + { + if (strcmp(Str_1_Par_Ref, Str_2_Par_Ref) > 0) + /* then, not executed */ + { + Int_Loc += 7; + Int_Glob = Int_Loc; + return (true); + } + else /* executed */ + return (false); + } /* if Ch_Loc */ +} /* Func_2 */ + +Boolean +Func_3(Enumeration Enum_Par_Val) +/***************************/ +/* executed once */ +/* Enum_Par_Val == Ident_3 */ + +{ + Enumeration Enum_Loc; + + Enum_Loc = Enum_Par_Val; + if (Enum_Loc == Ident_3) + /* then, executed */ + return (true); + else /* not executed */ + return (false); +} /* Func_3 */ diff --git a/wasm2c/examples/callback/callback.wat b/wasm2c/examples/callback/callback.wat new file mode 100644 index 00000000000..9786209a990 --- /dev/null +++ b/wasm2c/examples/callback/callback.wat @@ -0,0 +1,19 @@ +;; Module demonstrating use of a host-installed callback function. + +;; The type of the callback function. The type ID can be looked up outside the module by calling +;; wasm2c_[modname]_get_func_type(1, 0, WASM_RT_I32) (indicating 1 param, 0 results, param type is i32). +(type $print_type (func (param i32))) + +;; An indirect function table to hold the callback function +(table $table 1 funcref) + +;; A memory holding the string to be printed +(memory (export "memory") (data "Hello, world.\00")) + +;; Allow the host to set the callback function +(func (export "set_print_function") (param funcref) + (table.set $table (i32.const 0) (local.get 0))) + +;; Call the callback function with the location of "Hello, world." +(func (export "say_hello") + (call_indirect (type $print_type) (i32.const 0) (i32.const 0))) diff --git a/wasm2c/examples/callback/main.c b/wasm2c/examples/callback/main.c new file mode 100644 index 00000000000..8d9ab59e1d3 --- /dev/null +++ b/wasm2c/examples/callback/main.c @@ -0,0 +1,41 @@ +#include + +#include "callback.h" + +/* + * The callback function. Prints the null-terminated string at the given + * location in the instance's exported memory. + */ +void print(w2c_callback* instance, uint32_t ptr) { + puts(w2c_callback_memory(instance)->data + ptr); +} + +int main(int argc, char** argv) { + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Instantiate the callback module. */ + w2c_callback inst; + wasm2c_callback_instantiate(&inst); + + /* + * Call the module's "set_print_function" function, which takes a funcref to + * the callback. A funcref has three members: the function type (which can be + * looked up with "Z_callback_get_func_type"), a pointer to the function, and + * a module instance pointer that will be passed to the function when called. + */ + 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, {NULL}, &inst}; + w2c_callback_set_print_function(&inst, fn_ref); + + /* "say_hello" uses the previously installed callback. */ + w2c_callback_say_hello(&inst); + + /* Free the module instance and the Wasm runtime state. */ + wasm2c_callback_free(&inst); + wasm_rt_free(); + + return 0; +} diff --git a/wasm2c/examples/fac/fac.c b/wasm2c/examples/fac/fac.c new file mode 100644 index 00000000000..5c3a80a50a7 --- /dev/null +++ b/wasm2c/examples/fac/fac.c @@ -0,0 +1,796 @@ +/* Automatically generated by wasm2c */ +#include +#include +#include +#include +#include +#if defined(__MINGW32__) +#include +#elif defined(_MSC_VER) +#include +#include +#define alloca _alloca +#elif defined(__FreeBSD__) || defined(__OpenBSD__) +#include +#else +#include +#endif + +#include "fac.h" + +// Computes a pointer to an object of the given size in a little-endian memory. +// +// On a little-endian host, this is just &mem->data[addr] - the object's size is +// unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +// is the object's size. +// +// Note that mem may be evaluated multiple times. +// +// Parameters: +// mem - The memory. +// addr - The address. +// n - The size of the object. +// +// Result: +// A pointer for an object of size n. +#if WABT_BIG_ENDIAN +#define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +#else +#define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +#endif + +// We can only use Segue for this module if it uses a single unshared imported +// or exported memory +#if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +#else +#define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +#endif + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +// POSIX uses FS for TLS, GS is free +static inline void* wasm_rt_segue_read_base() { + if (wasm_rt_fsgsbase_inst_supported) { + return (void*)__builtin_ia32_rdgsbase64(); + } else { + return wasm_rt_syscall_get_segue_base(); + } +} +static inline void wasm_rt_segue_write_base(void* base) { +#if WASM_RT_SEGUE_FREE_SEGMENT + if (wasm_rt_last_segment_val == base) { + return; + } + + wasm_rt_last_segment_val = base; +#endif + + if (wasm_rt_fsgsbase_inst_supported) { + __builtin_ia32_wrgsbase64((uintptr_t)base); + } else { + wasm_rt_syscall_set_segue_base(base); + } +} +#define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +#else +#define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +#endif + +#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) + +#if WASM_RT_STACK_DEPTH_COUNT +#define FUNC_PROLOGUE \ + if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ + TRAP(EXHAUSTION); + +#define FUNC_EPILOGUE --wasm_rt_call_stack_depth +#else +#define FUNC_PROLOGUE + +#define FUNC_EPILOGUE +#endif + +#define UNREACHABLE TRAP(UNREACHABLE) + +static inline bool func_types_eq(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +} + +#define CHECK_CALL_INDIRECT(table, ft, x) \ + (LIKELY((x) < table.size && table.data[x].func && \ + func_types_eq(ft, table.data[x].func_type)) || \ + TRAP(CALL_INDIRECT)) + +#define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) + +#define CALL_INDIRECT(table, t, ft, x, ...) \ + (CHECK_CALL_INDIRECT(table, ft, x), \ + DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) + +static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +#if __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(a, b, resptr); +#elif defined(_MSC_VER) + return _addcarry_u64(0, a, b, resptr); +#else +#error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +#endif +} + +#define RANGE_CHECK(mem, offset, len) \ + do { \ + uint64_t res; \ + if (UNLIKELY(add_overflow(offset, len, &res))) \ + TRAP(OOB); \ + if (UNLIKELY(res > (mem)->size)) \ + TRAP(OOB); \ + } while (0); + +#if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +#include +#define WASM_RT_CHECK_BASE(mem) \ + if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ + puts("Segment register mismatch\n"); \ + abort(); \ + } +#else +#define WASM_RT_CHECK_BASE(mem) +#endif + +// MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +// default-page-size, 32-bit memories. It may do nothing at all +// (if hardware bounds-checking is enabled via guard pages) +// or it may do a slightly faster RANGE_CHECK. +#if WASM_RT_MEMCHECK_GUARD_PAGES +#define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +#else +#define MEMCHECK_DEFAULT32(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ + TRAP(OOB); +#endif + +// MEMCHECK_GENERAL can be used for any memory +#define MEMCHECK_GENERAL(mem, a, t) \ + WASM_RT_CHECK_BASE(mem); \ + RANGE_CHECK(mem, a, sizeof(t)); + +#ifdef __GNUC__ +#define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +// Clang on Mips requires "f" constraints on floats +// See https://github.com/llvm/llvm-project/issues/64241 +#if defined(__clang__) && \ + (defined(mips) || defined(__mips__) || defined(__mips)) +#define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +#else +#define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +#endif +#else +#define FORCE_READ_INT(var) +#define FORCE_READ_FLOAT(var) +#endif + +static inline void load_data(u8* dest, const u8* src, size_t n) { + if (!n) { + return; + } +#if WABT_BIG_ENDIAN + for (size_t i = 0; i < n; i++) { + dest[i] = src[n - i - 1]; + } +#else + wasm_rt_memcpy(dest, src, n); +#endif +} + +#define LOAD_DATA(m, o, i, s) \ + do { \ + RANGE_CHECK((&m), o, s); \ + load_data(MEM_ADDR(&m, o, s), i, s); \ + } while (0) + +#define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr); \ + } + +#define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ + val_type1) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1); \ + } + +#define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ + val_type1, val_type2) \ + static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ + u64 addr, val_type1 val1, \ + val_type2 val2) { \ + MEMCHECK_DEFAULT32(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } \ + static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ + val_type1 val1, val_type2 val2) { \ + MEMCHECK_GENERAL(mem, addr, mem_type); \ + ret_kw name##_unchecked(mem, addr, val1, val2); \ + } + +#define DEFINE_LOAD(name, t1, t2, t3, force_read) \ + static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ + t1 result; \ + wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ + sizeof(t1)); \ + force_read(result); \ + return (t3)(t2)result; \ + } \ + DEF_MEM_CHECKS0(name, _, t1, return, t3) + +#define DEFINE_STORE(name, t1, t2) \ + static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ + t2 value) { \ + t1 wrapped = (t1)value; \ + wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ + sizeof(t1)); \ + } \ + DEF_MEM_CHECKS1(name, _, t1, , void, t2) + +DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +DEFINE_STORE(i32_store, u32, u32) +DEFINE_STORE(i64_store, u64, u64) +DEFINE_STORE(f32_store, f32, f32) +DEFINE_STORE(f64_store, f64, f64) +DEFINE_STORE(i32_store8, u8, u32) +DEFINE_STORE(i32_store16, u16, u32) +DEFINE_STORE(i64_store8, u8, u64) +DEFINE_STORE(i64_store16, u16, u64) +DEFINE_STORE(i64_store32, u32, u64) + +#if defined(_MSC_VER) + +// 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) \ + : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ + : (ut)((x) / (y))) + +#define REM_S(ut, min, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ + : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ + : (ut)((x) % (y))) + +#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) + +#define DIVREM_U(op, x, y) \ + ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) + +#define DIV_U(x, y) DIVREM_U(/, x, y) +#define REM_U(x, y) DIVREM_U(%, x, y) + +#define ROTL(x, y, mask) \ + (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +#define ROTR(x, y, mask) \ + (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) + +#define I32_ROTL(x, y) ROTL(x, y, 31) +#define I64_ROTL(x, y) ROTL(x, y, 63) +#define I32_ROTR(x, y) ROTR(x, y, 31) +#define I64_ROTR(x, y) ROTR(x, y, 63) + +#define FMIN(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ + : (x < y) ? x \ + : y) + +#define FMAX(x, y) \ + ((UNLIKELY((x) != (x))) ? NAN \ + : (UNLIKELY((y) != (y))) ? NAN \ + : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ + : (x > y) ? x \ + : y) + +#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) { \ + t2 result; \ + wasm_rt_memcpy(&result, &x, sizeof(result)); \ + return result; \ + } + +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) + +static float quiet_nanf(float x) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp |= 0x7fc00000lu; + wasm_rt_memcpy(&x, &tmp, 4); + return x; +} + +static double quiet_nan(double x) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp |= 0x7ff8000000000000llu; + wasm_rt_memcpy(&x, &tmp, 8); + return x; +} + +static double wasm_quiet(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return x; +} + +static float wasm_quietf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return x; +} + +static double wasm_floor(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return floor(x); +} + +static float wasm_floorf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return floorf(x); +} + +static double wasm_ceil(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return ceil(x); +} + +static float wasm_ceilf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return ceilf(x); +} + +static double wasm_trunc(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return trunc(x); +} + +static float wasm_truncf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return truncf(x); +} + +static float wasm_nearbyintf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return nearbyintf(x); +} + +static double wasm_nearbyint(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return nearbyint(x); +} + +static float wasm_fabsf(float x) { + if (UNLIKELY(isnan(x))) { + uint32_t tmp; + wasm_rt_memcpy(&tmp, &x, 4); + tmp = tmp & ~(1UL << 31); + wasm_rt_memcpy(&x, &tmp, 4); + return x; + } + return fabsf(x); +} + +static double wasm_fabs(double x) { + if (UNLIKELY(isnan(x))) { + uint64_t tmp; + wasm_rt_memcpy(&tmp, &x, 8); + tmp = tmp & ~(1ULL << 63); + wasm_rt_memcpy(&x, &tmp, 8); + return x; + } + return fabs(x); +} + +static double wasm_sqrt(double x) { + if (UNLIKELY(isnan(x))) { + return quiet_nan(x); + } + return sqrt(x); +} + +static float wasm_sqrtf(float x) { + if (UNLIKELY(isnan(x))) { + return quiet_nanf(x); + } + return sqrtf(x); +} + +static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { + RANGE_CHECK(mem, d, n); + memset(MEM_ADDR(mem, d, n), val, n); +} + +static inline void memory_copy(wasm_rt_memory_t* dest, + const wasm_rt_memory_t* src, + u64 dest_addr, + u64 src_addr, + u64 n) { + RANGE_CHECK(dest, dest_addr, n); + RANGE_CHECK(src, src_addr, n); + memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +} + +static inline void memory_init(wasm_rt_memory_t* dest, + const u8* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + LOAD_DATA((*dest), dest_addr, src + src_addr, n); +} + +typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; + +typedef struct { + wasm_elem_segment_expr_type_t expr_type; + 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; + +static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, + const wasm_elem_segment_expr_t* src, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n, + void* module_instance) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; + wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); + switch (src_expr->expr_type) { + case RefFunc: + *dest_val = (wasm_rt_funcref_t){ + src_expr->type, src_expr->func, src_expr->func_tailcallee, + (char*)module_instance + src_expr->module_offset}; + break; + case RefNull: + *dest_val = wasm_rt_funcref_null_value; + break; + case GlobalGet: + *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + + src_expr->module_offset); + break; + } + } +} + +// Currently wasm2c only supports initializing externref tables with ref.null. +static inline void externref_table_init(wasm_rt_externref_table_t* dest, + u32 src_size, + u64 dest_addr, + u32 src_addr, + u32 n) { + if (UNLIKELY(src_addr + (uint64_t)n > src_size)) + TRAP(OOB); + RANGE_CHECK(dest, dest_addr, n); + for (u32 i = 0; i < n; i++) { + dest->data[dest_addr + i] = wasm_rt_externref_null_value; + } +} + +#define DEFINE_TABLE_COPY(type) \ + static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ + const wasm_rt_##type##_table_t* src, \ + u64 dest_addr, u64 src_addr, u64 n) { \ + RANGE_CHECK(dest, dest_addr, n); \ + RANGE_CHECK(src, src_addr, n); \ + memmove(dest->data + dest_addr, src->data + src_addr, \ + n * sizeof(wasm_rt_##type##_t)); \ + } + +DEFINE_TABLE_COPY(funcref) +DEFINE_TABLE_COPY(externref) + +#define DEFINE_TABLE_GET(type) \ + static inline wasm_rt_##type##_t type##_table_get( \ + const wasm_rt_##type##_table_t* table, u64 i) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + return table->data[i]; \ + } + +DEFINE_TABLE_GET(funcref) +DEFINE_TABLE_GET(externref) + +#define DEFINE_TABLE_SET(type) \ + static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ + u64 i, const wasm_rt_##type##_t val) { \ + if (UNLIKELY(i >= table->size)) \ + TRAP(OOB); \ + table->data[i] = val; \ + } + +DEFINE_TABLE_SET(funcref) +DEFINE_TABLE_SET(externref) + +#define DEFINE_TABLE_FILL(type) \ + static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ + u64 d, const wasm_rt_##type##_t val, \ + u64 n) { \ + RANGE_CHECK(table, d, n); \ + for (uint32_t i = d; i < d + n; i++) { \ + table->data[i] = val; \ + } \ + } + +DEFINE_TABLE_FILL(funcref) +DEFINE_TABLE_FILL(externref) + +#if defined(__GNUC__) || defined(__clang__) +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +#define FUNC_TYPE_EXTERN_T(x) const char* const x +#define FUNC_TYPE_T(x) static const char* const x +#else +#define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +#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 static_assert(X) \ + extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +#endif + +#ifdef _MSC_VER +#define WEAK_FUNC_DECL(func, fallback) \ + __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ + \ + void \ + fallback(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#else +#define WEAK_FUNC_DECL(func, fallback) \ + __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ + wasm_rt_tailcallee_t* next) +#endif + +static u32 w2c_fac_fac_0(w2c_fac*, u32); + +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"; + +/* export: 'fac' */ +u32 w2c_fac_fac(w2c_fac* instance, u32 var_p0) { + u32 ret = w2c_fac_fac_0(instance, var_p0); + return ret; +} + +void wasm2c_fac_instantiate(w2c_fac* instance) { + assert(wasm_rt_is_initialized()); +} + +void wasm2c_fac_free(w2c_fac* instance) { +} + +wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...) { + va_list args; + + if (param_count == 1 && result_count == 1) { + va_start(args, result_count); + if (true && va_arg(args, int) == WASM_RT_I32 && va_arg(args, int) == WASM_RT_I32) { + va_end(args); + return w2c_fac_t0; + } + va_end(args); + } + + return NULL; +} + +u32 w2c_fac_fac_0(w2c_fac* instance, u32 var_p0) { + FUNC_PROLOGUE; + 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; + } + FUNC_EPILOGUE; + return var_i0; +} diff --git a/wasm2c/examples/fac/fac.h b/wasm2c/examples/fac/fac.h new file mode 100644 index 00000000000..ac0cd5a9762 --- /dev/null +++ b/wasm2c/examples/fac/fac.h @@ -0,0 +1,42 @@ +/* Automatically generated by wasm2c */ +#ifndef FAC_H_GENERATED_ +#define FAC_H_GENERATED_ + +#include "wasm-rt.h" + +#include + +#ifndef WASM_RT_CORE_TYPES_DEFINED +#define WASM_RT_CORE_TYPES_DEFINED +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; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct w2c_fac { + char dummy_member; +} w2c_fac; + +void wasm2c_fac_instantiate(w2c_fac*); +void wasm2c_fac_free(w2c_fac*); +wasm_rt_func_type_t wasm2c_fac_get_func_type(uint32_t param_count, uint32_t result_count, ...); + +/* export: 'fac' */ +u32 w2c_fac_fac(w2c_fac*, u32); + +#ifdef __cplusplus +} +#endif + +#endif /* FAC_H_GENERATED_ */ diff --git a/wasm2c/examples/fac/fac.wat b/wasm2c/examples/fac/fac.wat new file mode 100644 index 00000000000..017a97aaf0c --- /dev/null +++ b/wasm2c/examples/fac/fac.wat @@ -0,0 +1,8 @@ +(func (export "fac") (param i32) (result i32) + (if (result i32) (i32.eq (local.get 0) (i32.const 0)) + (then (i32.const 1)) + (else + (i32.mul (local.get 0) (call 0 (i32.sub (local.get 0) (i32.const 1)))) + ) + ) +) diff --git a/wasm2c/examples/fac/main.c b/wasm2c/examples/fac/main.c new file mode 100644 index 00000000000..eb85376e132 --- /dev/null +++ b/wasm2c/examples/fac/main.c @@ -0,0 +1,39 @@ +#include +#include + +#include "fac.h" + +int main(int argc, char** argv) { + /* Make sure there is at least one command-line argument. */ + if (argc < 2) { + printf("Invalid argument. Expected '%s NUMBER'\n", argv[0]); + return 1; + } + + /* Convert the argument from a string to an int. We'll implicitly cast the int + to a `u32`, which is what `fac` expects. */ + u32 x = atoi(argv[1]); + + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Declare an instance of the `fac` module. */ + w2c_fac fac; + + /* Construct the module instance. */ + wasm2c_fac_instantiate(&fac); + + /* Call `fac`, using the mangled name. */ + u32 result = w2c_fac_fac(&fac, x); + + /* Print the result. */ + printf("fac(%u) -> %u\n", x, result); + + /* Free the fac module. */ + wasm2c_fac_free(&fac); + + /* Free the Wasm runtime state. */ + wasm_rt_free(); + + return 0; +} diff --git a/wasm2c/examples/rot13/main.c b/wasm2c/examples/rot13/main.c new file mode 100644 index 00000000000..bff10482ee0 --- /dev/null +++ b/wasm2c/examples/rot13/main.c @@ -0,0 +1,101 @@ +/* Entry point for the rot13 example. + * + * This example shows how you can fulfill wasm module imports in your C + * program, and access linear memory. + * + * The program reads arguments from the command line, and [rot13] encodes them, + * e.g.: + * + * ``` + * $ rot13 foo bar + * foo -> sbb + * bar -> one + * ``` + * + * [rot13]: https://en.wikipedia.org/wiki/ROT13 + */ +#include +#include + +#include "rot13.h" + +/* Define structure to hold the imports */ +struct w2c_host { + wasm_rt_memory_t memory; + char* input; +}; + +/* Accessor to access the memory member of the host */ +wasm_rt_memory_t* w2c_host_mem(struct w2c_host* instance) { + return &instance->memory; +} + +int main(int argc, char** argv) { + /* Make sure there is at least one command-line argument. */ + if (argc < 2) { + printf("Invalid argument. Expected '%s WORD...'\n", argv[0]); + return 1; + } + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Create a structure to store the memory and current string, allocating 1 + page of Wasm memory (64 KiB) that the rot13 module instance will import. */ + struct w2c_host host; + wasm_rt_allocate_memory(&host.memory, 1, 1, false, WASM_DEFAULT_PAGE_SIZE); + + // Construct an instance of the `rot13` module, which imports from the host. + w2c_rot13 rot13; + wasm2c_rot13_instantiate(&rot13, &host); + + /* Call `rot13` on each argument. */ + while (argc > 1) { + /* Move to next arg. Do this first, so the program name is skipped. */ + argc--; + argv++; + + host.input = argv[0]; + w2c_rot13_rot13(&rot13); + } + + /* Free the rot13 module. */ + wasm2c_rot13_free(&rot13); + + /* Free the Wasm runtime state. */ + wasm_rt_free(); + + return 0; +} + +/* Fill the wasm buffer with the input to be rot13'd. + * + * params: + * instance: An instance of the w2c_host structure + * ptr: The wasm memory address of the buffer to fill data. + * size: The size of the buffer in wasm memory. + * result: + * The number of bytes filled into the buffer. (Must be <= size). + */ +u32 w2c_host_fill_buf(struct w2c_host* instance, u32 ptr, u32 size) { + for (size_t i = 0; i < size; ++i) { + if (instance->input[i] == 0) { + return i; + } + instance->memory.data[ptr + i] = instance->input[i]; + } + return size; +} + +/* Called when the wasm buffer has been rot13'd. + * + * params: + * w2c_host: An instance of the w2c_host structure + * ptr: The wasm memory address of the buffer. + * size: The size of the buffer in wasm memory. + */ +void w2c_host_buf_done(struct w2c_host* instance, u32 ptr, u32 size) { + /* The output buffer is not necessarily null-terminated, so use the %*.s + * printf format to limit the number of characters printed. */ + printf("%s -> %.*s\n", instance->input, (int)size, + &instance->memory.data[ptr]); +} diff --git a/wasm2c/examples/rot13/rot13.wat b/wasm2c/examples/rot13/rot13.wat new file mode 100644 index 00000000000..605c89f9a59 --- /dev/null +++ b/wasm2c/examples/rot13/rot13.wat @@ -0,0 +1,56 @@ +(import "host" "mem" (memory $mem 1)) +(import "host" "fill_buf" (func $fill_buf (param i32 i32) (result i32))) +(import "host" "buf_done" (func $buf_done (param i32 i32))) + +(func $rot13c (param $c i32) (result i32) + (local $uc i32) + + ;; No change if < 'A'. + (if (i32.lt_u (local.get $c) (i32.const 65)) + (then (return (local.get $c)))) + + ;; Clear 5th bit of c, to force uppercase. 0xdf = 0b11011111 + (local.set $uc (i32.and (local.get $c) (i32.const 0xdf))) + + ;; In range ['A', 'M'] return |c| + 13. + (if (i32.le_u (local.get $uc) (i32.const 77)) + (then (return (i32.add (local.get $c) (i32.const 13))))) + + ;; In range ['N', 'Z'] return |c| - 13. + (if (i32.le_u (local.get $uc) (i32.const 90)) + (then (return (i32.sub (local.get $c) (i32.const 13))))) + + ;; No change for everything else. + (return (local.get $c)) +) + +(func (export "rot13") + (local $size i32) + (local $i i32) + + ;; Ask host to fill memory [0, 1024) with data. + (call $fill_buf (i32.const 0) (i32.const 1024)) + + ;; The host returns the size filled. + (local.set $size) + + ;; Loop over all bytes and rot13 them. + (block $exit + (loop $top + ;; if (i >= size) break + (if (i32.ge_u (local.get $i) (local.get $size)) (then (br $exit))) + + ;; mem[i] = rot13c(mem[i]) + (i32.store8 + (local.get $i) + (call $rot13c + (i32.load8_u (local.get $i)))) + + ;; i++ + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $top) + ) + ) + + (call $buf_done (i32.const 0) (local.get $size)) +) diff --git a/wasm2c/examples/threads/sample.wat b/wasm2c/examples/threads/sample.wat new file mode 100644 index 00000000000..f9680dfffab --- /dev/null +++ b/wasm2c/examples/threads/sample.wat @@ -0,0 +1,6 @@ +;; Module for demonstrating multi-threaded runtime + +(func (export "multiplyby3") (param i32) (result i32) (i32.mul (local.get 0) (i32.const 3))) + +(func $stackoverflow (export "stackoverflow") + (call $stackoverflow)) diff --git a/wasm2c/examples/threads/threads.c b/wasm2c/examples/threads/threads.c new file mode 100644 index 00000000000..14587ed6c96 --- /dev/null +++ b/wasm2c/examples/threads/threads.c @@ -0,0 +1,105 @@ +#include +#include + +#include "sample.h" +#include "wasm-rt-exceptions.h" +#include "wasm-rt-impl.h" + +#define NUM_THREADS 1024 + +void* do_thread(void* arg); + +/** + * Example demonstrating use of the wasm2c runtime in multithreaded code. + * + * The program calls wasm_rt_init() on startup, and each new thread calls + * wasm_rt_init_thread() before instantiating a Wasm module. The sample + * module is designed to trap with stack exhaustion; this example tests + * that each thread can successfully catch the trap (in its own altstack) + * independently. + */ + +int main(int argc, char** argv) { + pthread_t threads[NUM_THREADS]; + int arguments[NUM_THREADS]; + + /* Initialize the Wasm runtime. */ + wasm_rt_init(); + + /* Create and launch threads running the `do_thread` function. */ + for (int i = 0; i < NUM_THREADS; ++i) { + arguments[i] = i; + if (pthread_create(&threads[i], NULL, do_thread, &arguments[i])) { + perror("pthread_create"); + exit(EXIT_FAILURE); + } + } + + /* Join each thread. */ + for (int i = 0; i < NUM_THREADS; ++i) { + void* retval; + if (pthread_join(threads[i], &retval)) { + perror("pthread_join"); + exit(EXIT_FAILURE); + } + + /* Verify returned value is as expected */ + if ((retval != &arguments[i]) || (arguments[i] != 3 * i)) { + fprintf(stderr, "Unexpected return value from thread.\n"); + exit(EXIT_FAILURE); + } + } + + /* Free the Wasm runtime's state. */ + wasm_rt_free(); + + printf("%d/%d threads trapped successfully.\n", NUM_THREADS, NUM_THREADS); + + return EXIT_SUCCESS; +} + +void* do_thread(void* arg) { + int param; + memcpy(¶m, arg, sizeof(int)); + + /* Initialize the per-thread context for the Wasm runtime (in + practice, this allocates and installs an alternate stack for + catching segfaults caused by stack exhaustion or out-of-bounds + memory access). */ + wasm_rt_init_thread(); + + /* Instantiate the Wasm module. */ + w2c_sample inst; + wasm2c_sample_instantiate(&inst); + + /* Expect a stack-exhaustion trap. (N.B. in a pthreads-created stack, Linux's + segfault for stack overflow appears the same as one for memory OOB. This is + similar to the behavior of macOS when exhausting a non-pthreads stack. */ + wasm_rt_trap_t code = wasm_rt_impl_try(); + if (code != 0) { + if (code == WASM_RT_TRAP_OOB || code == WASM_RT_TRAP_EXHAUSTION) { + /* Trap arrived as expected. Now call the "real" function. */ + int returnval = w2c_sample_multiplyby3(&inst, param); + memcpy(arg, &returnval, sizeof(int)); + + /* Free the module instance. */ + wasm2c_sample_free(&inst); + + /* Free the per-thread runtime context. */ + wasm_rt_free_thread(); + + return arg; + } else { + fprintf(stderr, "Expected OOB or exhaustion trap but got %s\n", + wasm_rt_strerror(code)); + return NULL; + } + } + + /* Call the stack-overflow function. Expect a trap back to above. */ + w2c_sample_stackoverflow(&inst); + + /* If no trap... */ + fprintf(stderr, "Expected trap but did not get one\n"); + return NULL; +} diff --git a/wasm2c/wasm-rt-exceptions-impl.c b/wasm2c/wasm-rt-exceptions-impl.c new file mode 100644 index 00000000000..73e97201c50 --- /dev/null +++ b/wasm2c/wasm-rt-exceptions-impl.c @@ -0,0 +1,73 @@ +/* + * 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. + */ + +#include "wasm-rt.h" + +#include "wasm-rt-exceptions.h" + +#include + +#define MAX_EXCEPTION_SIZE 256 + +static WASM_RT_THREAD_LOCAL wasm_rt_tag_t g_active_exception_tag; +static WASM_RT_THREAD_LOCAL uint8_t g_active_exception[MAX_EXCEPTION_SIZE]; +static WASM_RT_THREAD_LOCAL uint32_t g_active_exception_size; + +static WASM_RT_THREAD_LOCAL wasm_rt_jmp_buf* g_unwind_target; + +void wasm_rt_load_exception(const wasm_rt_tag_t tag, + uint32_t size, + const void* values) { + if (size > MAX_EXCEPTION_SIZE) { + wasm_rt_trap(WASM_RT_TRAP_EXHAUSTION); + } + + g_active_exception_tag = tag; + g_active_exception_size = size; + + if (size) { + memcpy(g_active_exception, values, size); + } +} + +WASM_RT_NO_RETURN void wasm_rt_throw(void) { + WASM_RT_LONGJMP(*g_unwind_target, WASM_RT_TRAP_UNCAUGHT_EXCEPTION); +} + +WASM_RT_UNWIND_TARGET* wasm_rt_get_unwind_target(void) { + return g_unwind_target; +} + +void wasm_rt_set_unwind_target(WASM_RT_UNWIND_TARGET* target) { + g_unwind_target = target; +} + +wasm_rt_tag_t wasm_rt_exception_tag(void) { + return g_active_exception_tag; +} + +uint32_t wasm_rt_exception_size(void) { + return g_active_exception_size; +} + +void* wasm_rt_exception(void) { + return g_active_exception; +} + +// Include table operations for exnref +#define WASM_RT_TABLE_OPS_EXNREF +#include "wasm-rt-impl-tableops.inc" +#undef WASM_RT_TABLE_OPS_EXNREF diff --git a/wasm2c/wasm-rt-exceptions.h b/wasm2c/wasm-rt-exceptions.h new file mode 100644 index 00000000000..9e95c196e74 --- /dev/null +++ b/wasm2c/wasm-rt-exceptions.h @@ -0,0 +1,140 @@ +/* + * 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_EXCEPTIONS_H_ +#define WASM_RT_EXCEPTIONS_H_ + +#include "wasm-rt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * A tag is represented as an arbitrary pointer. + */ +typedef const void* wasm_rt_tag_t; + +/** + * Set the active exception to given tag, size, and contents. + */ +void wasm_rt_load_exception(const wasm_rt_tag_t tag, + uint32_t size, + const void* values); + +/** + * Throw the active exception. + */ +WASM_RT_NO_RETURN void wasm_rt_throw(void); + +/** + * The type of an unwind target if an exception is thrown and caught. + */ +#define WASM_RT_UNWIND_TARGET wasm_rt_jmp_buf + +/** + * Get the current unwind target if an exception is thrown. + */ +WASM_RT_UNWIND_TARGET* wasm_rt_get_unwind_target(void); + +/** + * Set the unwind target if an exception is thrown. + */ +void wasm_rt_set_unwind_target(WASM_RT_UNWIND_TARGET* target); + +/** + * Tag of the active exception. + */ +wasm_rt_tag_t wasm_rt_exception_tag(void); + +/** + * Size of the active exception. + */ +uint32_t wasm_rt_exception_size(void); + +/** + * Contents of the active exception. + */ +void* wasm_rt_exception(void); + +/** + * The maximum size of an exception. + */ +#define WASM_EXN_MAX_SIZE 256 + +/** + * An exception instance (the runtime representation of a function). + * These can be stored in tables of type exnref, or used as values. + */ +typedef struct { + /** The exceptions's tag. */ + wasm_rt_tag_t tag; + /** The size of the exception. */ + uint32_t size; + /** + * The actual contents of the exception are stored inline. + */ + char data[WASM_EXN_MAX_SIZE]; +} wasm_rt_exnref_t; + +/** Default (null) value of an exnref */ +#define wasm_rt_exnref_null_value ((wasm_rt_exnref_t){NULL, 0, {0}}) + +/** A Table of type exnref. */ +typedef struct { + /** The table element data, with an element count of `size`. */ + wasm_rt_exnref_t* data; + /** + * The maximum element count of this Table object. If there is no maximum, + * `max_size` is 0xffffffffu (i.e. UINT32_MAX). + */ + uint32_t max_size; + /** The current element count of the table. */ + uint32_t size; +} wasm_rt_exnref_table_t; + +/** + * Initialize an exnref Table object with an element count of `elements` and a + * maximum size of `max_elements`. + * + * ``` + * wasm_rt_exnref_table_t my_table; + * // 5 elements and a maximum of 10 elements. + * wasm_rt_allocate_exnref_table(&my_table, 5, 10); + * ``` + */ +void wasm_rt_allocate_exnref_table(wasm_rt_exnref_table_t*, + uint32_t elements, + uint32_t max_elements); + +/** Free an exnref Table object. */ +void wasm_rt_free_exnref_table(wasm_rt_exnref_table_t*); + +/** + * Grow a Table object by `delta` elements (giving the new elements the value + * `init`), and return the previous element count. If this new element count is + * greater than the maximum element count, the grow fails and 0xffffffffu + * (UINT32_MAX) is returned instead. + */ +uint32_t wasm_rt_grow_exnref_table(wasm_rt_exnref_table_t*, + uint32_t delta, + wasm_rt_exnref_t init); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/wasm2c/wasm-rt-impl-tableops.inc b/wasm2c/wasm-rt-impl-tableops.inc new file mode 100644 index 00000000000..76193028725 --- /dev/null +++ b/wasm2c/wasm-rt-impl-tableops.inc @@ -0,0 +1,87 @@ +/* + * 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. + */ + +// This file is used as a template to generate code for table operations for +// funcref or externref. For this, the file must be included after defining +// either WASM_RT_TABLE_OPS_FUNCREF or WASM_RT_TABLE_OPS_EXTERNREF + +#if defined(WASM_RT_TABLE_OPS_FUNCREF) + \ + defined(WASM_RT_TABLE_OPS_EXTERNREF) + \ + defined(WASM_RT_TABLE_OPS_EXNREF) > \ + 1 +#error \ + "Expected only one of { WASM_RT_TABLE_OPS_FUNCREF, WASM_RT_TABLE_OPS_EXTERNREF, WASM_RT_TABLE_OPS_EXNREF } to be defined" +#elif defined(WASM_RT_TABLE_OPS_FUNCREF) + \ + defined(WASM_RT_TABLE_OPS_EXTERNREF) + \ + defined(WASM_RT_TABLE_OPS_EXNREF) < \ + 1 +#error \ + "Expected one of { WASM_RT_TABLE_OPS_FUNCREF, WASM_RT_TABLE_OPS_EXTERNREF, WASM_RT_TABLE_OPS_EXNREF } to be defined" +#endif + +#if defined(WASM_RT_TABLE_OPS_FUNCREF) +#define WASM_RT_TABLE_TYPE wasm_rt_funcref_table_t +#define WASM_RT_TABLE_ELEMENT_TYPE wasm_rt_funcref_t +#define WASM_RT_TABLE_APINAME(name) name##_funcref_table +#elif defined(WASM_RT_TABLE_OPS_EXTERNREF) +#define WASM_RT_TABLE_TYPE wasm_rt_externref_table_t +#define WASM_RT_TABLE_ELEMENT_TYPE wasm_rt_externref_t +#define WASM_RT_TABLE_APINAME(name) name##_externref_table +#elif defined(WASM_RT_TABLE_OPS_EXNREF) +#define WASM_RT_TABLE_TYPE wasm_rt_exnref_table_t +#define WASM_RT_TABLE_ELEMENT_TYPE wasm_rt_exnref_t +#define WASM_RT_TABLE_APINAME(name) name##_exnref_table +#endif + +void WASM_RT_TABLE_APINAME(wasm_rt_allocate)(WASM_RT_TABLE_TYPE* table, + uint32_t elements, + uint32_t max_elements) { + table->size = elements; + table->max_size = max_elements; + table->data = calloc(table->size, sizeof(WASM_RT_TABLE_ELEMENT_TYPE)); +} + +void WASM_RT_TABLE_APINAME(wasm_rt_free)(WASM_RT_TABLE_TYPE* table) { + free(table->data); +} + +uint32_t WASM_RT_TABLE_APINAME(wasm_rt_grow)(WASM_RT_TABLE_TYPE* table, + uint32_t delta, + WASM_RT_TABLE_ELEMENT_TYPE init) { + uint32_t old_elems = table->size; + uint64_t new_elems = (uint64_t)table->size + delta; + if (new_elems == 0) { + return 0; + } + if ((new_elems < old_elems) || (new_elems > table->max_size)) { + return (uint32_t)-1; + } + void* new_data = + realloc(table->data, new_elems * sizeof(WASM_RT_TABLE_ELEMENT_TYPE)); + if (!new_data) { + return (uint32_t)-1; + } + table->data = new_data; + table->size = new_elems; + for (uint32_t i = old_elems; i < new_elems; i++) { + table->data[i] = init; + } + return old_elems; +} + +#undef WASM_RT_TABLE_APINAME +#undef WASM_RT_TABLE_ELEMENT_TYPE +#undef WASM_RT_TABLE_TYPE diff --git a/wasm2c/wasm-rt-impl.c b/wasm2c/wasm-rt-impl.c new file mode 100644 index 00000000000..0cf0723a95d --- /dev/null +++ b/wasm2c/wasm-rt-impl.c @@ -0,0 +1,401 @@ +/* + * 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. + */ + +#include "wasm-rt-impl.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#if WASM_RT_INSTALL_SIGNAL_HANDLER && !defined(_WIN32) +#include +#include +#endif + +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifndef NDEBUG +#define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__); +#else +#define DEBUG_PRINTF(...) +#endif + +#if WASM_RT_INSTALL_SIGNAL_HANDLER +static bool g_signal_handler_installed = false; +#ifdef _WIN32 +static void* g_sig_handler_handle = 0; +#endif +#endif + +#if WASM_RT_USE_SEGUE +bool wasm_rt_fsgsbase_inst_supported = false; +#ifdef __linux__ +#include +#ifdef __GLIBC__ +#include +#endif +#include // For ARCH_SET_GS +#include // For SYS_arch_prctl +#include // For syscall +#ifndef HWCAP2_FSGSBASE +#define HWCAP2_FSGSBASE (1 << 1) +#endif +#elif defined(__FreeBSD__) +#include // For amd64_set_gsbase etc. +#endif +#endif + +#if WASM_RT_SEGUE_FREE_SEGMENT +WASM_RT_THREAD_LOCAL void* wasm_rt_last_segment_val = NULL; +#endif + +#if WASM_RT_STACK_DEPTH_COUNT +WASM_RT_THREAD_LOCAL uint32_t wasm_rt_call_stack_depth; +WASM_RT_THREAD_LOCAL uint32_t wasm_rt_saved_call_stack_depth; +#elif WASM_RT_STACK_EXHAUSTION_HANDLER +static WASM_RT_THREAD_LOCAL void* g_alt_stack = NULL; +#endif + +#ifndef WASM_RT_TRAP_HANDLER +WASM_RT_THREAD_LOCAL wasm_rt_jmp_buf g_wasm_rt_jmp_buf; +#endif + +#ifdef WASM_RT_TRAP_HANDLER +extern void WASM_RT_TRAP_HANDLER(wasm_rt_trap_t code); +#endif + +void wasm_rt_trap(wasm_rt_trap_t code) { + assert(code != WASM_RT_TRAP_NONE); +#if WASM_RT_STACK_DEPTH_COUNT + wasm_rt_call_stack_depth = wasm_rt_saved_call_stack_depth; +#endif + +#ifdef WASM_RT_TRAP_HANDLER + WASM_RT_TRAP_HANDLER(code); + wasm_rt_unreachable(); +#else + WASM_RT_LONGJMP(g_wasm_rt_jmp_buf, code); +#endif +} + +#ifdef _WIN32 + +#if WASM_RT_INSTALL_SIGNAL_HANDLER + +static LONG os_signal_handler(PEXCEPTION_POINTERS info) { + if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { + wasm_rt_trap(WASM_RT_TRAP_OOB); + } else if (info->ExceptionRecord->ExceptionCode == EXCEPTION_STACK_OVERFLOW) { + wasm_rt_trap(WASM_RT_TRAP_EXHAUSTION); + } + return EXCEPTION_CONTINUE_SEARCH; +} + +static void os_install_signal_handler(void) { + g_sig_handler_handle = + AddVectoredExceptionHandler(1 /* CALL_FIRST */, os_signal_handler); +} + +static void os_cleanup_signal_handler(void) { + RemoveVectoredExceptionHandler(g_sig_handler_handle); +} + +#endif + +#else + +#if WASM_RT_INSTALL_SIGNAL_HANDLER +static void os_signal_handler(int sig, siginfo_t* si, void* unused) { + if (si->si_code == SEGV_ACCERR) { + wasm_rt_trap(WASM_RT_TRAP_OOB); + } else { + wasm_rt_trap(WASM_RT_TRAP_EXHAUSTION); + } +} + +static void os_install_signal_handler(void) { + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_flags = SA_SIGINFO; +#if WASM_RT_STACK_EXHAUSTION_HANDLER + sa.sa_flags |= SA_ONSTACK; +#endif + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = os_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(); + } +} + +static void os_cleanup_signal_handler(void) { + /* Undo what was done in os_install_signal_handler */ + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_handler = SIG_DFL; + if (sigaction(SIGSEGV, &sa, NULL) != 0 || sigaction(SIGBUS, &sa, NULL)) { + perror("sigaction failed"); + abort(); + } +} +#endif + +#if WASM_RT_STACK_EXHAUSTION_HANDLER +static bool os_has_altstack_installed() { + /* check for altstack already in place */ + stack_t ss; + if (sigaltstack(NULL, &ss) != 0) { + perror("os_has_altstack_installed: sigaltstack failed"); + abort(); + } + + return !(ss.ss_flags & SS_DISABLE); +} + +/* These routines set up an altstack to handle SIGSEGV from stack overflow. */ +static void os_allocate_and_install_altstack(void) { + /* verify altstack not already allocated */ + assert(!g_alt_stack && + "wasm-rt error: tried to re-allocate thread-local alternate stack"); + + /* We could check and warn if an altstack is already installed, but some + * sanitizers install their own altstack, so this warning would fire + * spuriously and break the test outputs. */ + + /* allocate altstack */ + g_alt_stack = malloc(SIGSTKSZ); + if (g_alt_stack == NULL) { + perror("malloc failed"); + abort(); + } + + /* install altstack */ + stack_t ss; + ss.ss_sp = g_alt_stack; + ss.ss_flags = 0; + ss.ss_size = SIGSTKSZ; + if (sigaltstack(&ss, NULL) != 0) { + perror("os_allocate_and_install_altstack: sigaltstack failed"); + abort(); + } +} + +static void os_disable_and_deallocate_altstack(void) { + /* in debug build, verify altstack allocated */ + assert(g_alt_stack && + "wasm-rt error: thread-local alternate stack not allocated"); + + /* verify altstack was still in place */ + stack_t ss; + if (sigaltstack(NULL, &ss) != 0) { + perror("os_disable_and_deallocate_altstack: sigaltstack failed"); + abort(); + } + + if ((!g_alt_stack) || (ss.ss_flags & SS_DISABLE) || + (ss.ss_sp != g_alt_stack) || (ss.ss_size != SIGSTKSZ)) { + DEBUG_PRINTF( + "wasm-rt warning: alternate stack was modified unexpectedly\n"); + return; + } + + assert(!(ss.ss_flags & SS_ONSTACK) && + "attempt to deallocate altstack while in use"); + + /* disable and free */ + ss.ss_flags = SS_DISABLE; + if (sigaltstack(&ss, NULL) != 0) { + perror("sigaltstack disable failed"); + abort(); + } + assert(!os_has_altstack_installed()); + free(g_alt_stack); + g_alt_stack = NULL; +} +#endif + +#endif + +#if WASM_RT_USE_SEGUE && defined(__FreeBSD__) +static void call_cpuid(uint64_t* rax, + uint64_t* rbx, + uint64_t* rcx, + uint64_t* rdx) { + __asm__ volatile( + "cpuid" + : "=a"(*rax), "=b"(*rbx), "=c"(*rcx), "=d"(*rdx) // output operands + : "a"(*rax), "c"(*rcx) // input operands + ); +} +#endif + +void wasm_rt_init(void) { + wasm_rt_init_thread(); +#if WASM_RT_INSTALL_SIGNAL_HANDLER + if (!g_signal_handler_installed) { + g_signal_handler_installed = true; + os_install_signal_handler(); + } +#endif + +#if WASM_RT_USE_SEGUE +#if defined(__linux__) && defined(__GLIBC__) && __GLIBC__ >= 2 && \ + __GLIBC_MINOR__ >= 18 + // Check for support for userspace wrgsbase instructions + unsigned long val = getauxval(AT_HWCAP2); + wasm_rt_fsgsbase_inst_supported = val & HWCAP2_FSGSBASE; +#elif defined(__FreeBSD__) + // FreeBSD enables fsgsbase on the newer kernels if the hardware supports it. + // We just need to check if the hardware supports it by querying the correct + // cpuid leaf. + uint64_t rax, rbx, rcx, rdx; + rax = 0; + call_cpuid(&rax, &rbx, &rcx, &rdx); + + if (rax > 7) { + rax = 7; + rcx = 0; + call_cpuid(&rax, &rbx, &rcx, &rdx); + if (rbx & 0x1) { + wasm_rt_fsgsbase_inst_supported = true; + } + } +#endif +#endif + + assert(wasm_rt_is_initialized()); +} + +bool wasm_rt_is_initialized(void) { +#if WASM_RT_STACK_EXHAUSTION_HANDLER + if (!os_has_altstack_installed()) { + return false; + } +#endif +#if WASM_RT_INSTALL_SIGNAL_HANDLER + return g_signal_handler_installed; +#else + return true; +#endif +} + +void wasm_rt_free(void) { + assert(wasm_rt_is_initialized()); +#if WASM_RT_INSTALL_SIGNAL_HANDLER + os_cleanup_signal_handler(); + g_signal_handler_installed = false; +#endif + wasm_rt_free_thread(); +} + +void wasm_rt_init_thread(void) { +#if WASM_RT_STACK_EXHAUSTION_HANDLER + os_allocate_and_install_altstack(); +#endif +} + +void wasm_rt_free_thread(void) { +#if WASM_RT_STACK_EXHAUSTION_HANDLER + os_disable_and_deallocate_altstack(); +#endif +} + +#if WASM_RT_USE_SEGUE +void wasm_rt_syscall_set_segue_base(void* base) { + int error_code = 0; +#ifdef __linux__ + error_code = syscall(SYS_arch_prctl, ARCH_SET_GS, base); +#elif defined(__FreeBSD__) + error_code = amd64_set_gsbase(base); +#else +#error "Unknown platform" +#endif + if (error_code != 0) { + perror("wasm_rt_syscall_set_segue_base error"); + abort(); + } +} +void* wasm_rt_syscall_get_segue_base() { + void* base; + int error_code = 0; +#ifdef __linux__ + error_code = syscall(SYS_arch_prctl, ARCH_GET_GS, &base); +#elif defined(__FreeBSD__) + error_code = amd64_get_gsbase(&base); +#else +#error "Unknown platform" +#endif + if (error_code != 0) { + perror("wasm_rt_syscall_get_segue_base error"); + abort(); + } + return base; +} +#endif + +// Include table operations for funcref +#define WASM_RT_TABLE_OPS_FUNCREF +#include "wasm-rt-impl-tableops.inc" +#undef WASM_RT_TABLE_OPS_FUNCREF + +// Include table operations for externref +#define WASM_RT_TABLE_OPS_EXTERNREF +#include "wasm-rt-impl-tableops.inc" +#undef WASM_RT_TABLE_OPS_EXTERNREF + +const char* wasm_rt_strerror(wasm_rt_trap_t trap) { + switch (trap) { + case WASM_RT_TRAP_NONE: + return "No error"; + case WASM_RT_TRAP_OOB: +#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS + return "Out-of-bounds access in linear memory or a table, or call stack " + "exhausted"; +#else + return "Out-of-bounds access in linear memory or a table"; + case WASM_RT_TRAP_EXHAUSTION: + return "Call stack exhausted"; +#endif + case WASM_RT_TRAP_INT_OVERFLOW: + return "Integer overflow on divide or truncation"; + case WASM_RT_TRAP_DIV_BY_ZERO: + return "Integer divide by zero"; + case WASM_RT_TRAP_INVALID_CONVERSION: + return "Conversion from NaN to integer"; + case WASM_RT_TRAP_UNREACHABLE: + return "Unreachable instruction executed"; + case WASM_RT_TRAP_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_NULL_REF: + return "Null reference"; + } + return "invalid trap code"; +} diff --git a/wasm2c/wasm-rt-impl.h b/wasm2c/wasm-rt-impl.h new file mode 100644 index 00000000000..ed46b097211 --- /dev/null +++ b/wasm2c/wasm-rt-impl.h @@ -0,0 +1,68 @@ +/* + * 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 "wasm-rt.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef WASM_RT_TRAP_HANDLER +/** A setjmp buffer used for handling traps. */ +extern WASM_RT_THREAD_LOCAL wasm_rt_jmp_buf g_wasm_rt_jmp_buf; +#endif + +#if WASM_RT_STACK_DEPTH_COUNT +/** Saved call stack depth that will be restored in case a trap occurs. */ +extern WASM_RT_THREAD_LOCAL uint32_t wasm_rt_saved_call_stack_depth; +#define WASM_RT_SAVE_STACK_DEPTH() \ + wasm_rt_saved_call_stack_depth = wasm_rt_call_stack_depth +#else +#define WASM_RT_SAVE_STACK_DEPTH() (void)0 +#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() \ + (WASM_RT_SAVE_STACK_DEPTH(), wasm_rt_set_unwind_target(&g_wasm_rt_jmp_buf), \ + WASM_RT_SETJMP(g_wasm_rt_jmp_buf)) + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_RT_IMPL_H_ */ diff --git a/wasm2c/wasm-rt-mem-impl-helper.inc b/wasm2c/wasm-rt-mem-impl-helper.inc new file mode 100644 index 00000000000..63d10bc57e1 --- /dev/null +++ b/wasm2c/wasm-rt-mem-impl-helper.inc @@ -0,0 +1,197 @@ +/* + * 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. + */ + +// This file is used as a template to generate code for regular memories or for +// shared memories. For this, the file must be included after defining either +// WASM_RT_MEM_OPS or WASM_RT_MEM_OPS_SHARED. + +#if defined(WASM_RT_MEM_OPS) && defined(WASM_RT_MEM_OPS_SHARED) +#error \ + "Expected only one of { WASM_RT_MEM_OPS, WASM_RT_MEM_OPS_SHARED } to be defined" +#elif !defined(WASM_RT_MEM_OPS) && !defined(WASM_RT_MEM_OPS_SHARED) +#error \ + "Expected one of { WASM_RT_MEM_OPS, WASM_RT_MEM_OPS_SHARED } to be defined" +#endif + +// Shared memory operations are defined only if we have C11 +#if defined(WASM_RT_MEM_OPS) || \ + (defined(WASM_RT_MEM_OPS_SHARED) && defined(WASM_RT_C11_AVAILABLE)) + +#ifdef WASM_RT_MEM_OPS + +// Memory operations on wasm_rt_memory_t +#define MEMORY_TYPE wasm_rt_memory_t +#define MEMORY_API_NAME(name) name +#define MEMORY_CELL_TYPE uint8_t* +#define MEMORY_LOCK_VAR_INIT(name) +#define MEMORY_LOCK_AQUIRE(name) +#define MEMORY_LOCK_RELEASE(name) + +#else + +// Memory operations on wasm_rt_shared_memory_t +#define MEMORY_TYPE wasm_rt_shared_memory_t +#define MEMORY_API_NAME(name) name##_shared +#define MEMORY_CELL_TYPE _Atomic volatile uint8_t* + +#if WASM_RT_USE_CRITICALSECTION +#define MEMORY_LOCK_VAR_INIT(name) WIN_MEMORY_LOCK_VAR_INIT(name) +#define MEMORY_LOCK_AQUIRE(name) WIN_MEMORY_LOCK_AQUIRE(name) +#define MEMORY_LOCK_RELEASE(name) WIN_MEMORY_LOCK_RELEASE(name) +#elif WASM_RT_USE_PTHREADS +#define MEMORY_LOCK_VAR_INIT(name) PTHREAD_MEMORY_LOCK_VAR_INIT(name) +#define MEMORY_LOCK_AQUIRE(name) PTHREAD_MEMORY_LOCK_AQUIRE(name) +#define MEMORY_LOCK_RELEASE(name) PTHREAD_MEMORY_LOCK_RELEASE(name) +#endif + +#endif + +bool MEMORY_API_NAME(wasm_rt_memory_is_default32)(const MEMORY_TYPE* memory) { + return memory->page_size == WASM_DEFAULT_PAGE_SIZE && !memory->is64; +} + +void MEMORY_API_NAME(wasm_rt_allocate_memory)(MEMORY_TYPE* memory, + uint64_t initial_pages, + uint64_t max_pages, + bool is64, + uint32_t page_size) { + uint64_t byte_length = initial_pages * page_size; + memory->size = byte_length; + memory->pages = initial_pages; + memory->max_pages = max_pages; + memory->is64 = is64; + memory->page_size = page_size; + MEMORY_LOCK_VAR_INIT(memory->mem_lock); + +#if WASM_RT_USE_MMAP + if (MEMORY_API_NAME(wasm_rt_memory_is_default32)(memory)) { + const uint64_t mmap_size = + get_alloc_size_for_mmap_default32(memory->max_pages); + uint8_t* addr = os_mmap(mmap_size); + if (!addr) { + os_print_last_error("os_mmap failed."); + abort(); + } + uint8_t* data_end = addr + mmap_size; +#if !WABT_BIG_ENDIAN + int ret = os_mprotect(addr, byte_length); +#else + int ret = os_mprotect(data_end - byte_length, byte_length); +#endif + if (ret != 0) { + os_print_last_error("os_mprotect failed."); + abort(); + } + memory->data = (MEMORY_CELL_TYPE)addr; + memory->data_end = (MEMORY_CELL_TYPE)data_end; + return; + } +#endif + + memory->data = (MEMORY_CELL_TYPE)calloc(byte_length, 1); + memory->data_end = memory->data + byte_length; +} + +// Returns 0 on success +static int MEMORY_API_NAME(expand_data_allocation)(MEMORY_TYPE* memory, + uint64_t old_size, + uint64_t new_size, + uint64_t delta_size) { +#if WASM_RT_USE_MMAP + if (MEMORY_API_NAME(wasm_rt_memory_is_default32)(memory)) { +#if !WABT_BIG_ENDIAN + return os_mprotect((void*)(memory->data + old_size), delta_size); +#else + return os_mprotect((void*)(memory->data_end - old_size - delta_size), + delta_size); +#endif + } +#endif + + uint8_t* new_data = realloc((void*)memory->data, new_size); + if (new_data == NULL) { + return -1; + } +#if !WABT_BIG_ENDIAN + memset((void*)(new_data + old_size), 0, delta_size); +#else + memmove((void*)(new_data + new_size - old_size), (void*)new_data, old_size); + memset((void*)new_data, 0, delta_size); +#endif + + memory->data = (MEMORY_CELL_TYPE)new_data; + memory->data_end = memory->data + new_size; + return 0; +} + +static uint64_t MEMORY_API_NAME(grow_memory_impl)(MEMORY_TYPE* memory, + uint64_t delta) { + uint64_t old_pages = memory->pages; + uint64_t new_pages = memory->pages + delta; + if (new_pages == 0) { + return 0; + } + if (new_pages < old_pages || new_pages > memory->max_pages) { + return (uint64_t)-1; + } + uint64_t old_size = old_pages * memory->page_size; + uint64_t new_size = new_pages * memory->page_size; + uint64_t delta_size = delta * memory->page_size; + + int err = MEMORY_API_NAME(expand_data_allocation)(memory, old_size, new_size, + delta_size); + if (err != 0) { + return (uint64_t)-1; + } + + memory->pages = new_pages; + memory->size = new_size; + return old_pages; +} + +uint64_t MEMORY_API_NAME(wasm_rt_grow_memory)(MEMORY_TYPE* memory, + uint64_t delta) { + MEMORY_LOCK_AQUIRE(memory->mem_lock); + uint64_t ret = MEMORY_API_NAME(grow_memory_impl)(memory, delta); + MEMORY_LOCK_RELEASE(memory->mem_lock); +#ifdef WASM_RT_GROW_FAILED_HANDLER + if (ret == (uint64_t)-1) { + WASM_RT_GROW_FAILED_HANDLER(); + } +#endif + return ret; +} + +void MEMORY_API_NAME(wasm_rt_free_memory)(MEMORY_TYPE* memory) { +#if WASM_RT_USE_MMAP + if (MEMORY_API_NAME(wasm_rt_memory_is_default32)(memory)) { + const uint64_t mmap_size = + get_alloc_size_for_mmap_default32(memory->max_pages); + os_munmap((void*)memory->data, mmap_size); // ignore error + return; + } +#endif + free((void*)memory->data); +} + +#undef MEMORY_LOCK_RELEASE +#undef MEMORY_LOCK_AQUIRE +#undef MEMORY_LOCK_VAR_INIT +#undef MEMORY_CELL_TYPE +#undef MEMORY_API_NAME +#undef MEMORY_TYPE + +#endif diff --git a/wasm2c/wasm-rt-mem-impl.c b/wasm2c/wasm-rt-mem-impl.c new file mode 100644 index 00000000000..953c92dbe7a --- /dev/null +++ b/wasm2c/wasm-rt-mem-impl.c @@ -0,0 +1,161 @@ +/* + * 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. + */ + +#include "wasm-rt-impl.h" + +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +#ifdef WASM_RT_GROW_FAILED_HANDLER +extern void WASM_RT_GROW_FAILED_HANDLER(); +#endif + +#define PTHREAD_MEMORY_LOCK_VAR_INIT(name) \ + if (pthread_mutex_init(&(name), NULL) != 0) { \ + fprintf(stderr, "Lock init failed\n"); \ + abort(); \ + } +#define PTHREAD_MEMORY_LOCK_AQUIRE(name) \ + if (pthread_mutex_lock(&(name)) != 0) { \ + fprintf(stderr, "Lock acquire failed\n"); \ + abort(); \ + } +#define PTHREAD_MEMORY_LOCK_RELEASE(name) \ + if (pthread_mutex_unlock(&(name)) != 0) { \ + fprintf(stderr, "Lock release failed\n"); \ + abort(); \ + } + +#define WIN_MEMORY_LOCK_VAR_INIT(name) InitializeCriticalSection(&(name)) +#define WIN_MEMORY_LOCK_AQUIRE(name) EnterCriticalSection(&(name)) +#define WIN_MEMORY_LOCK_RELEASE(name) LeaveCriticalSection(&(name)) + +#if WASM_RT_USE_MMAP + +#ifdef _WIN32 +static void* os_mmap(size_t size) { + void* ret = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS); + return ret; +} + +static int os_munmap(void* addr, size_t size) { + // Windows can only unmap the whole mapping + (void)size; /* unused */ + BOOL succeeded = VirtualFree(addr, 0, MEM_RELEASE); + return succeeded ? 0 : -1; +} + +static int os_mprotect(void* addr, size_t size) { + if (size == 0) { + return 0; + } + void* ret = VirtualAlloc(addr, size, MEM_COMMIT, PAGE_READWRITE); + if (ret == addr) { + return 0; + } + VirtualFree(addr, 0, MEM_RELEASE); + return -1; +} + +static 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; + printf("%s. %s\n", msg, messageBuffer); + LocalFree(messageBuffer); + } else { + printf("%s. No error code.\n", msg); + } +} + +#else /* !_WIN32 */ +static void* os_mmap(size_t size) { + int map_prot = PROT_NONE; + int map_flags = MAP_ANONYMOUS | MAP_PRIVATE; + uint8_t* addr = mmap(NULL, size, map_prot, map_flags, -1, 0); + if (addr == MAP_FAILED) + return NULL; + return addr; +} + +static int os_munmap(void* addr, size_t size) { + return munmap(addr, size); +} + +static int os_mprotect(void* addr, size_t size) { + if (size == 0) { + return 0; + } + return mprotect(addr, size, PROT_READ | PROT_WRITE); +} + +static void os_print_last_error(const char* msg) { + perror(msg); +} + +#endif /* _WIN32 */ + +static uint64_t get_alloc_size_for_mmap_default32(uint64_t max_pages) { +#if WASM_RT_MEMCHECK_GUARD_PAGES + /* Reserve 8GiB. */ + const uint64_t max_size = 0x200000000ul; + return max_size; +#else + if (max_pages != 0) { + const uint64_t max_size = max_pages * WASM_DEFAULT_PAGE_SIZE; + return max_size; + } + + /* Reserve 4GiB. */ + const uint64_t max_size = 0x100000000ul; + return max_size; +#endif +} + +#endif /* WASM_RT_USE_MMAP */ + +// Include operations for memory +#define WASM_RT_MEM_OPS +#include "wasm-rt-mem-impl-helper.inc" +#undef WASM_RT_MEM_OPS + +// Include operations for shared memory +#define WASM_RT_MEM_OPS_SHARED +#include "wasm-rt-mem-impl-helper.inc" +#undef WASM_RT_MEM_OPS_SHARED + +#undef C11_MEMORY_LOCK_VAR_INIT +#undef C11_MEMORY_LOCK_AQUIRE +#undef C11_MEMORY_LOCK_RELEASE +#undef PTHREAD_MEMORY_LOCK_VAR_INIT +#undef PTHREAD_MEMORY_LOCK_AQUIRE +#undef PTHREAD_MEMORY_LOCK_RELEASE +#undef WIN_MEMORY_LOCK_VAR_INIT +#undef WIN_MEMORY_LOCK_AQUIRE +#undef WIN_MEMORY_LOCK_RELEASE diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h new file mode 100644 index 00000000000..c6859f738a5 --- /dev/null +++ b/wasm2c/wasm-rt.h @@ -0,0 +1,724 @@ +/* + * 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_H_ +#define WASM_RT_H_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __has_builtin +#define __has_builtin(x) 0 /** Compatibility with non-clang compilers. */ +#endif + +#if __has_builtin(__builtin_expect) +#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 __has_builtin(__builtin_memcpy) +#define wasm_rt_memcpy __builtin_memcpy +#else +#define wasm_rt_memcpy memcpy +#endif + +#if __has_builtin(__builtin_unreachable) +#define wasm_rt_unreachable __builtin_unreachable +#else +#define wasm_rt_unreachable abort +#endif + +#ifdef __STDC_VERSION__ +#if __STDC_VERSION__ >= 201112L +#define WASM_RT_C11_AVAILABLE +#endif +#endif + +/** + * Many devices don't implement the C11 threads.h. We use CriticalSection APIs + * for Windows and pthreads on other platforms where threads are not available. + */ +#ifdef WASM_RT_C11_AVAILABLE + +#if defined(_WIN32) +#include +#define WASM_RT_MUTEX CRITICAL_SECTION +#define WASM_RT_USE_CRITICALSECTION 1 +#else +#include +#define WASM_RT_MUTEX pthread_mutex_t +#define WASM_RT_USE_PTHREADS 1 +#endif + +#endif + +#ifdef WASM_RT_C11_AVAILABLE +#define WASM_RT_THREAD_LOCAL _Thread_local +#elif defined(_MSC_VER) +#define WASM_RT_THREAD_LOCAL __declspec(thread) +#elif (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__) +// Disabled on Apple systems due to sporadic test failures. +#define WASM_RT_THREAD_LOCAL __thread +#else +#define WASM_RT_THREAD_LOCAL +#endif + +/** + * If enabled, perform additional sanity checks in the generated wasm2c code and + * wasm2c runtime. This is useful to enable on debug builds. + */ +#ifndef WASM_RT_SANITY_CHECKS +#define WASM_RT_SANITY_CHECKS 0 +#endif + +/** + * Backward compatibility: Convert the previously exposed + * WASM_RT_MEMCHECK_SIGNAL_HANDLER macro to the ALLOCATION and CHECK macros that + * are now used. + */ +#if defined(WASM_RT_MEMCHECK_SIGNAL_HANDLER) + +#if WASM_RT_MEMCHECK_SIGNAL_HANDLER +#define WASM_RT_USE_MMAP 1 +#define WASM_RT_MEMCHECK_GUARD_PAGES 1 +#else +#define WASM_RT_USE_MMAP 0 +#define WASM_RT_MEMCHECK_BOUNDS_CHECK 1 +#endif + +#warning \ + "WASM_RT_MEMCHECK_SIGNAL_HANDLER has been deprecated in favor of WASM_RT_USE_MMAP and WASM_RT_MEMORY_CHECK_* macros" +#endif + +/** + * Specify if we use OR mmap/mprotect (+ Windows equivalents) OR malloc/realloc + * for the Wasm memory allocation and growth. mmap/mprotect guarantees memory + * will grow without being moved, while malloc ensures the virtual memory is + * consumed only as needed, but may relocate the memory to handle memory + * fragmentation. + * + * This defaults to malloc on 32-bit platforms or if memory64 support is needed. + * It defaults to mmap on 64-bit platforms assuming memory64 support is not + * needed (so we can use the guard based range checks below). + */ +#ifndef WASM_RT_USE_MMAP +#if UINTPTR_MAX > 0xffffffff +#define WASM_RT_USE_MMAP 1 +#else +#define WASM_RT_USE_MMAP 0 +#endif +#endif + +/** + * Set the range checking strategy for Wasm memories. + * + * GUARD_PAGES: memory accesses rely on unmapped pages/guard pages to trap + * out-of-bound accesses. + * + * BOUNDS_CHECK: memory accesses are checked with explicit bounds checks. + * + * This defaults to GUARD_PAGES as this is the fastest option, iff the + * requirements of GUARD_PAGES --- 64-bit platforms, MMAP allocation strategy, + * no 64-bit memories --- are met. This falls back to BOUNDS otherwise. + */ + +/** Check if Guard checks are supported */ +#if UINTPTR_MAX > 0xffffffff && WASM_RT_USE_MMAP +#define WASM_RT_GUARD_PAGES_SUPPORTED 1 +#else +#define WASM_RT_GUARD_PAGES_SUPPORTED 0 +#endif + +/** Specify defaults for memory checks if unspecified */ +#if !defined(WASM_RT_MEMCHECK_GUARD_PAGES) && \ + !defined(WASM_RT_MEMCHECK_BOUNDS_CHECK) +#if WASM_RT_GUARD_PAGES_SUPPORTED +#define WASM_RT_MEMCHECK_GUARD_PAGES 1 +#else +#define WASM_RT_MEMCHECK_BOUNDS_CHECK 1 +#endif +#endif + +/** Ensure the macros are defined */ +#ifndef WASM_RT_MEMCHECK_GUARD_PAGES +#define WASM_RT_MEMCHECK_GUARD_PAGES 0 +#endif +#ifndef WASM_RT_MEMCHECK_BOUNDS_CHECK +#define WASM_RT_MEMCHECK_BOUNDS_CHECK 0 +#endif + +/** Sanity check the use of guard pages */ +#if WASM_RT_MEMCHECK_GUARD_PAGES && !WASM_RT_GUARD_PAGES_SUPPORTED +#error \ + "WASM_RT_MEMCHECK_GUARD_PAGES not supported on this platform/configuration" +#endif + +#if WASM_RT_MEMCHECK_GUARD_PAGES && WASM_RT_MEMCHECK_BOUNDS_CHECK +#error \ + "Cannot use both WASM_RT_MEMCHECK_GUARD_PAGES and WASM_RT_MEMCHECK_BOUNDS_CHECK" + +#elif !WASM_RT_MEMCHECK_GUARD_PAGES && !WASM_RT_MEMCHECK_BOUNDS_CHECK +#error \ + "Must choose at least one from WASM_RT_MEMCHECK_GUARD_PAGES and WASM_RT_MEMCHECK_BOUNDS_CHECK" +#endif + +/** + * Some configurations above require the Wasm runtime to install a signal + * handler. However, this can be explicitly disallowed by the host using + * WASM_RT_SKIP_SIGNAL_RECOVERY. In this case, when the wasm code encounters an + * OOB access, it may either trap or abort. + */ +#ifndef WASM_RT_SKIP_SIGNAL_RECOVERY +#define WASM_RT_SKIP_SIGNAL_RECOVERY 0 +#endif + +#if WASM_RT_MEMCHECK_GUARD_PAGES && !WASM_RT_SKIP_SIGNAL_RECOVERY +#define WASM_RT_INSTALL_SIGNAL_HANDLER 1 +#else +#define WASM_RT_INSTALL_SIGNAL_HANDLER 0 +#endif + +/** + * This macro, if defined to 1 (i.e., allows the "segue" optimization), allows + * Wasm2c to use segment registers to speedup access to the linear heap. Note + * that even if allowed in this way, the segment registers would only be used if + * Wasm2c output is compiled for a suitable architecture and OS and the produces + * C file is compiled by supported compilers. The extact restrictions are listed + * in detail in src/template/wasm2c.declarations.c + */ +#ifndef WASM_RT_ALLOW_SEGUE +#define WASM_RT_ALLOW_SEGUE 0 +#endif + +/** + * The segue optimization restores x86 segments to their old values when exiting + * wasm2c code. If WASM_RT_SEGUE_FREE_SEGMENT is defined, wasm2c assumes it has + * exclusive use of the segment and optimizes performance in two ways. First, it + * does not restore the "old" value of the segment during exits. Second, wasm2c + * only sets the segment register if the value has changed since the last time + * it was set. + */ +#ifndef WASM_RT_SEGUE_FREE_SEGMENT +#define WASM_RT_SEGUE_FREE_SEGMENT 0 +#endif + +#ifndef WASM_RT_USE_SEGUE +// Memory functions can use the segue optimization if allowed. The segue +// optimization uses x86 segments to point to a linear memory. We use this +// optimization when: +// +// (1) Segue is allowed using WASM_RT_ALLOW_SEGUE +// (2) on x86_64 without WABT_BIG_ENDIAN enabled +// (3) the compiler supports: intrinsics for (rd|wr)gsbase, "address namespaces" +// for accessing pointers, and supports memcpy on pointers with custom +// "address namespaces". GCC does not support the memcpy requirement, so +// this leaves only clang (version 9 or later) for now. +// (4) The OS provides a way to query if (rd|wr)gsbase is allowed by the kernel +// or the implementation has to use a syscall for this. +// (5) The OS doesn't replace the segment register on context switch which +// eliminates windows for now +// +// While more OS can be supported in the future, we only support linux for now +#if WASM_RT_ALLOW_SEGUE && !WABT_BIG_ENDIAN && \ + (defined(__x86_64__) || defined(_M_X64)) && __clang__ && \ + (__clang_major__ >= 9) && __has_builtin(__builtin_ia32_wrgsbase64) && \ + !defined(_WIN32) && !defined(__ANDROID__) && \ + (defined(__linux__) || defined(__FreeBSD__)) +#define WASM_RT_USE_SEGUE 1 +#else +#define WASM_RT_USE_SEGUE 0 +#endif +#endif + +/** + * This macro, if defined, allows the embedder to disable all stack exhaustion + * checks. This a non conformant configuration, i.e., this does not respect + * Wasm's specification, and may compromise security. Use with caution. + */ +#ifndef WASM_RT_NONCONFORMING_UNCHECKED_STACK_EXHAUSTION +#define WASM_RT_NONCONFORMING_UNCHECKED_STACK_EXHAUSTION 0 +#endif + +/** + * We need to detect and trap stack overflows. If we use a signal handler on + * POSIX systems, this can detect call stack overflows. On windows, or platforms + * without a signal handler, we use stack depth counting. The s390x big endian + * platform additionally seems to have issues with stack guard pages, so we play + * it safe and use stack counting on big endian platforms. + */ +#if !defined(WASM_RT_STACK_DEPTH_COUNT) && \ + !defined(WASM_RT_STACK_EXHAUSTION_HANDLER) && \ + !WASM_RT_NONCONFORMING_UNCHECKED_STACK_EXHAUSTION + +#if WASM_RT_INSTALL_SIGNAL_HANDLER && !defined(_WIN32) && !WABT_BIG_ENDIAN +#define WASM_RT_STACK_EXHAUSTION_HANDLER 1 +#else +#define WASM_RT_STACK_DEPTH_COUNT 1 +#endif + +#endif + +/** Ensure the stack macros are defined */ +#ifndef WASM_RT_STACK_DEPTH_COUNT +#define WASM_RT_STACK_DEPTH_COUNT 0 +#endif +#ifndef WASM_RT_STACK_EXHAUSTION_HANDLER +#define WASM_RT_STACK_EXHAUSTION_HANDLER 0 +#endif + +#if WASM_RT_NONCONFORMING_UNCHECKED_STACK_EXHAUSTION + +#if (WASM_RT_STACK_EXHAUSTION_HANDLER + WASM_RT_STACK_DEPTH_COUNT) != 0 +#error \ + "Cannot specify WASM_RT_NONCONFORMING_UNCHECKED_STACK_EXHAUSTION along with WASM_RT_STACK_EXHAUSTION_HANDLER or WASM_RT_STACK_DEPTH_COUNT" +#endif + +#else + +#if (WASM_RT_STACK_EXHAUSTION_HANDLER + WASM_RT_STACK_DEPTH_COUNT) > 1 +#error \ + "Cannot specify multiple options from WASM_RT_STACK_EXHAUSTION_HANDLER , WASM_RT_STACK_DEPTH_COUNT" +#elif (WASM_RT_STACK_EXHAUSTION_HANDLER + WASM_RT_STACK_DEPTH_COUNT) == 0 +#error \ + "Must specify one of WASM_RT_STACK_EXHAUSTION_HANDLER , WASM_RT_STACK_DEPTH_COUNT" +#endif + +#endif + +#if WASM_RT_STACK_EXHAUSTION_HANDLER && !WASM_RT_INSTALL_SIGNAL_HANDLER +#error \ + "WASM_RT_STACK_EXHAUSTION_HANDLER can only be used if WASM_RT_INSTALL_SIGNAL_HANDLER is enabled" +#endif + +#if WASM_RT_STACK_DEPTH_COUNT +/** + * When the signal handler cannot be used to detect stack overflows, stack depth + * is limited explicitly. The maximum stack depth before trapping 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 + +/** Current call stack depth. */ +extern WASM_RT_THREAD_LOCAL uint32_t wasm_rt_call_stack_depth; + +#endif + +#if WASM_RT_USE_SEGUE +/** + * The segue optimization uses x86 segments to point to a linear memory. If + * used, the runtime must query whether it can use the fast userspace wrgsbase + * instructions or whether it must invoke syscalls to set the segment base, + * depending on the supported CPU features. The result of this query is saved in + * this variable. + */ +extern bool wasm_rt_fsgsbase_inst_supported; +/** + * If fast userspace wrgsbase instructions don't exist, the runtime most provide + * a function that invokes the OS' underlying syscall to set the segment base. + */ +void wasm_rt_syscall_set_segue_base(void* base); +/** + * If fast userspace rdgsbase instructions don't exist, the runtime most provide + * a function that invokes the OS' underlying syscall to get the segment base. + */ +void* wasm_rt_syscall_get_segue_base(); +/** + * If WASM_RT_SEGUE_FREE_SEGMENT is defined, we must only set the segment + * register if it was changed since the last time it was set. The last value set + * on the segment register is stored in this variable. + */ +#if WASM_RT_SEGUE_FREE_SEGMENT +extern WASM_RT_THREAD_LOCAL void* wasm_rt_last_segment_val; +#endif +#endif + +#if defined(_MSC_VER) +#define WASM_RT_NO_RETURN __declspec(noreturn) +#else +#define WASM_RT_NO_RETURN __attribute__((noreturn)) +#endif + +#if defined(__APPLE__) && WASM_RT_STACK_EXHAUSTION_HANDLER +#define WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS 1 +#else +#define WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS 0 +#endif + +/** Reason a trap occurred. Provide this to `wasm_rt_trap`. */ +typedef enum { + WASM_RT_TRAP_NONE, /** No error. */ + WASM_RT_TRAP_OOB, /** Out-of-bounds access in linear memory or a table. */ + WASM_RT_TRAP_INT_OVERFLOW, /** Integer overflow on divide or truncation. */ + 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_NULL_REF, /** Null reference. */ + WASM_RT_TRAP_UNCAUGHT_EXCEPTION, /** Exception thrown and not caught. */ + WASM_RT_TRAP_UNALIGNED, /** Unaligned atomic instruction executed. */ +#if WASM_RT_MERGED_OOB_AND_EXHAUSTION_TRAPS + WASM_RT_TRAP_EXHAUSTION = WASM_RT_TRAP_OOB, +#else + WASM_RT_TRAP_EXHAUSTION, /** Call stack exhausted. */ +#endif +} wasm_rt_trap_t; + +/** Value types. Used to define function signatures. */ +typedef enum { + WASM_RT_I32, + WASM_RT_I64, + WASM_RT_F32, + WASM_RT_F64, + WASM_RT_V128, + WASM_RT_FUNCREF, + WASM_RT_EXTERNREF, + WASM_RT_EXNREF, +} wasm_rt_type_t; + +/** + * A generic function pointer type, both for Wasm functions (`code`) + * and host functions (`hostcode`). All function pointers are stored + * in this canonical form, but must be cast to their proper signature + * to call. + */ +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** instance_ptr, + void* tail_call_stack, + struct wasm_rt_tailcallee_t* next); +} 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. + */ +typedef const char* wasm_rt_func_type_t; + +/** + * A function instance (the runtime representation of a function). + * These can be stored in tables of type funcref, or used as values. + */ +typedef struct { + /** The function's type. */ + wasm_rt_func_type_t func_type; + /** + * 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. + */ + void* module_instance; +} wasm_rt_funcref_t; + +/** Default (null) value of a funcref */ +#define wasm_rt_funcref_null_value \ + ((wasm_rt_funcref_t){NULL, NULL, {NULL}, NULL}) + +/** The type of an external reference (opaque to WebAssembly). */ +typedef void* wasm_rt_externref_t; + +/** Default (null) value of an externref */ +#define wasm_rt_externref_null_value ((wasm_rt_externref_t){NULL}) + +/** A Memory object. */ +typedef struct { + /** The linear memory data, with a byte length of `size`. */ + uint8_t* data; + /** The location after the the reserved space for the linear memory data. */ + uint8_t* data_end; + /** The page size for this Memory object + (always 64 KiB without the custom-page-sizes feature) */ + uint32_t page_size; + /** The current page count for this Memory object. */ + uint64_t pages; + /** The maximum page count for this Memory object. */ + uint64_t max_pages; + /** The current size of the linear memory, in bytes. */ + uint64_t size; + /** Is this memory indexed by u64 (as opposed to default u32) */ + bool is64; +} wasm_rt_memory_t; + +#ifdef WASM_RT_C11_AVAILABLE +/** A shared Memory object. */ +typedef struct { + /** + * The linear memory data, with a byte length of `size`. The memory is marked + * atomic as it is shared and may have to be accessed with different memory + * orders --- sequential when being accessed atomically, relaxed otherwise. + * Unfortunately, the C standard does not state what happens if there are + * overlaps in two memory accesses which have a memory order, e.g., an + * atomic32 being read from the same location an atomic64 is read. One way to + * prevent optimizations from assuming non-overlapping behavior as typically + * done in C is to mark the memory as volatile. Thus the memory is atomic and + * volatile. + */ + _Atomic volatile uint8_t* data; + /** The location one byte after the reserved space for the linear memory data. + * This includes any reserved pages that are not yet allocated. */ + _Atomic volatile uint8_t* data_end; + /** The page size for this Memory object + (always 64 KiB without the custom-page-sizes feature) */ + uint32_t page_size; + /** The current page count for this Memory object. */ + uint64_t pages; + /* The maximum page count for this Memory object. */ + uint64_t max_pages; + /** The current size of the linear memory, in bytes. */ + uint64_t size; + /** Is this memory indexed by u64 (as opposed to default u32) */ + bool is64; + /** Lock used to ensure operations such as memory grow are threadsafe */ + WASM_RT_MUTEX mem_lock; +} wasm_rt_shared_memory_t; +#endif + +/** A Table of type funcref. */ +typedef struct { + /** The table element data, with an element count of `size`. */ + wasm_rt_funcref_t* data; + /** + * The maximum element count of this Table object. If there is no maximum, + * `max_size` is 0xffffffffu (i.e. UINT32_MAX). + */ + uint32_t max_size; + /** The current element count of the table. */ + uint32_t size; +} wasm_rt_funcref_table_t; + +/** A Table of type externref. */ +typedef struct { + /** The table element data, with an element count of `size`. */ + wasm_rt_externref_t* data; + /** + * The maximum element count of this Table object. If there is no maximum, + * `max_size` is 0xffffffffu (i.e. UINT32_MAX). + */ + uint32_t max_size; + /** The current element count of the table. */ + uint32_t size; +} wasm_rt_externref_table_t; + +/** Initialize the runtime. */ +void wasm_rt_init(void); + +/** Is the runtime initialized? */ +bool wasm_rt_is_initialized(void); + +/** Free the runtime's state. */ +void wasm_rt_free(void); + +/* + * Initialize the multithreaded runtime for a given thread. Must be + * called by each thread (other than the one that called wasm_rt_init) + * before initializing a Wasm module or calling an exported + * function. + */ +void wasm_rt_init_thread(void); + +/* + * Free the individual thread's state. + */ +void wasm_rt_free_thread(void); + +/** A hardened jmp_buf that allows checking for initialization before use */ +typedef struct { + /** Is the jmp buf intialized? */ + bool initialized; + /** jmp_buf contents */ + jmp_buf buffer; +} wasm_rt_jmp_buf; + +#ifndef _WIN32 +#define WASM_RT_SETJMP_TRAP_SETBUF(buf) sigsetjmp(buf, 1) + +/** + * On macOS XNU, there is a bug where nested `sigsetjmp` and `siglongjmp` + * across threads that have an allocated alternate signal stack (`SS_ONSTACK`) + * will erroneously cause the kernel to preserve the `SS_ONSTACK` flag in the + * thread state + * + * See: https://github.com/WebAssembly/wabt/issues/2654 + * See: https://github.com/golang/go/issues/44501 + */ +#define WASM_RT_SETJMP_EXN_SETBUF(buf) sigsetjmp(buf, 0) +#else +#define WASM_RT_SETJMP_TRAP_SETBUF(buf) setjmp(buf) +#define WASM_RT_SETJMP_EXN_SETBUF(buf) setjmp(buf) +#endif + +#define WASM_RT_SETJMP(buf) \ + ((buf).initialized = true, WASM_RT_SETJMP_TRAP_SETBUF((buf).buffer)) +#define WASM_RT_SETJMP_EXN(buf) \ + ((buf).initialized = true, WASM_RT_SETJMP_EXN_SETBUF((buf).buffer)) + +#ifndef _WIN32 +#define WASM_RT_LONGJMP_UNCHECKED(buf, val) siglongjmp(buf, val) +#else +#define WASM_RT_LONGJMP_UNCHECKED(buf, val) longjmp(buf, val) +#endif + +#define WASM_RT_LONGJMP(buf, val) \ + /** Abort on failure as this may be called in the trap handler */ \ + if (!((buf).initialized)) \ + abort(); \ + (buf).initialized = false; \ + WASM_RT_LONGJMP_UNCHECKED((buf).buffer, val) + +/** + * Stop execution immediately and jump back to the call to `wasm_rt_impl_try`. + * The result of `wasm_rt_impl_try` will be the provided trap reason. + * + * This is typically called by the generated code, and not the embedder. + */ +WASM_RT_NO_RETURN void wasm_rt_trap(wasm_rt_trap_t); + +/** Return a human readable error string based on a trap type. */ +const char* wasm_rt_strerror(wasm_rt_trap_t trap); + +#define wasm_rt_try(target) WASM_RT_SETJMP_EXN(target) + +/** WebAssembly's default page size (64 KiB) */ +#define WASM_DEFAULT_PAGE_SIZE 65536 + +/** + * Initialize a Memory object with an initial page size of `initial_pages` and + * a maximum page size of `max_pages`, indexed with an i32 or i64. + * + * ``` + * wasm_rt_memory_t my_memory; + * // 1 initial page (65536 bytes), and a maximum of 2 pages, + * // indexed with an i32 + * wasm_rt_allocate_memory(&my_memory, 1, 2, false, WASM_DEFAULT_PAGE_SIZE); + * ``` + */ +void wasm_rt_allocate_memory(wasm_rt_memory_t*, + uint64_t initial_pages, + uint64_t max_pages, + bool is64, + uint32_t page_size); + +/** + * 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. + * + * ``` + * wasm_rt_memory_t my_memory; + * ... + * // Grow memory by 10 pages. + * uint32_t old_page_size = wasm_rt_grow_memory(&my_memory, 10); + * if (old_page_size == UINT32_MAX) { + * // Failed to grow memory. + * } + * ``` + */ +uint64_t wasm_rt_grow_memory(wasm_rt_memory_t*, uint64_t pages); + +/** Free a Memory object. */ +void wasm_rt_free_memory(wasm_rt_memory_t*); + +#ifdef WASM_RT_C11_AVAILABLE +/** Shared memory version of wasm_rt_allocate_memory */ +void wasm_rt_allocate_memory_shared(wasm_rt_shared_memory_t*, + uint64_t initial_pages, + uint64_t max_pages, + bool is64, + uint32_t page_size); + +/** Shared memory version of wasm_rt_grow_memory */ +uint64_t wasm_rt_grow_memory_shared(wasm_rt_shared_memory_t*, uint64_t pages); + +/** Shared memory version of wasm_rt_free_memory */ +void wasm_rt_free_memory_shared(wasm_rt_shared_memory_t*); +#endif + +/** + * Initialize a funcref Table object with an element count of `elements` and a + * maximum size of `max_elements`. + * + * ``` + * wasm_rt_funcref_table_t my_table; + * // 5 elements and a maximum of 10 elements. + * wasm_rt_allocate_funcref_table(&my_table, 5, 10); + * ``` + */ +void wasm_rt_allocate_funcref_table(wasm_rt_funcref_table_t*, + uint32_t elements, + uint32_t max_elements); + +/** Free a funcref Table object. */ +void wasm_rt_free_funcref_table(wasm_rt_funcref_table_t*); + +/** + * Initialize an externref Table object with an element count + * of `elements` and a maximum size of `max_elements`. + * Usage as per wasm_rt_allocate_funcref_table. + */ +void wasm_rt_allocate_externref_table(wasm_rt_externref_table_t*, + uint32_t elements, + uint32_t max_elements); + +/** Free an externref Table object. */ +void wasm_rt_free_externref_table(wasm_rt_externref_table_t*); + +/** + * Grow a Table object by `delta` elements (giving the new elements the value + * `init`), and return the previous element count. If this new element count is + * greater than the maximum element count, the grow fails and 0xffffffffu + * (UINT32_MAX) is returned instead. + */ +uint32_t wasm_rt_grow_funcref_table(wasm_rt_funcref_table_t*, + uint32_t delta, + wasm_rt_funcref_t init); +uint32_t wasm_rt_grow_externref_table(wasm_rt_externref_table_t*, + uint32_t delta, + wasm_rt_externref_t init); + +#ifdef __cplusplus +} +#endif + +#endif /* WASM_RT_H_ */ From 80912eaa60049d339456acb955fdae4f4934c153 Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Fri, 15 May 2026 17:05:34 +0000 Subject: [PATCH 2/7] import simde from https://github.com/simd-everywhere/simde --- .gitmodules | 3 +++ third_party/simde | 1 + 2 files changed, 4 insertions(+) create mode 160000 third_party/simde diff --git a/.gitmodules b/.gitmodules index a674125e389..aa7869d7cf0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "third_party/mimalloc"] path = third_party/mimalloc url = https://github.com/microsoft/mimalloc.git +[submodule "third_party/simde"] + path = third_party/simde + url = https://github.com/simd-everywhere/simde diff --git a/third_party/simde b/third_party/simde new file mode 160000 index 00000000000..f3e8262173b --- /dev/null +++ b/third_party/simde @@ -0,0 +1 @@ +Subproject commit f3e8262173b7089db9a9d57a9ecef8dd07ad9c97 From bb0a426c385f830b72447894e0f0b745aeadae67 Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Tue, 19 May 2026 19:17:11 +0000 Subject: [PATCH 3/7] Import picosha2 --- .gitmodules | 3 +++ third_party/picosha2 | 1 + 2 files changed, 4 insertions(+) create mode 160000 third_party/picosha2 diff --git a/.gitmodules b/.gitmodules index aa7869d7cf0..6251faea65b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "third_party/simde"] path = third_party/simde url = https://github.com/simd-everywhere/simde +[submodule "third_party/picosha2"] + path = third_party/picosha2 + url = https://github.com/okdshin/PicoSHA2 diff --git a/third_party/picosha2 b/third_party/picosha2 new file mode 160000 index 00000000000..161cb3fc417 --- /dev/null +++ b/third_party/picosha2 @@ -0,0 +1 @@ +Subproject commit 161cb3fc4170fa7a3eca9e582cebd27cc4d1fe29 From 159e2e851f2da81a40a89c450dc588c215e7f441 Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Thu, 21 May 2026 17:56:49 +0000 Subject: [PATCH 4/7] [wasm2c] Add the rules to build the prebuilt files from the associated templates. --- .gitignore | 1 + scripts/gen-wasm2c-templates.cmake | 5 ++++ src/tools/CMakeLists.txt | 38 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 scripts/gen-wasm2c-templates.cmake diff --git a/.gitignore b/.gitignore index b88109b38e0..670fa20ded3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ options-pinned.h # files related to building in-tree CMakeFiles *.cmake +!scripts/gen-wasm2c-templates.cmake /CMakeCache.txt /Makefile /*.ninja diff --git a/scripts/gen-wasm2c-templates.cmake b/scripts/gen-wasm2c-templates.cmake new file mode 100644 index 00000000000..db554543d93 --- /dev/null +++ b/scripts/gen-wasm2c-templates.cmake @@ -0,0 +1,5 @@ +# https://stackoverflow.com/a/47801116 +file(READ ${in} content) +string(REGEX REPLACE "(.[^\n]*\n)" "R\"w2c_template(\\1)w2c_template\"\n" content "${content}") +set(content "const char* ${symbol} = ${content};\n") +file(WRITE ${out} "${content}") diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 91fea46b1df..ab9781189cf 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -11,6 +11,44 @@ set(fuzzing_SOURCES binaryen_add_executable(wasm-opt "${fuzzing_SOURCES};wasm-opt.cpp") binaryen_add_executable(wasm-metadce wasm-metadce.cpp) binaryen_add_executable(wasm2js wasm2js.cpp) +set(TEMPLATE_CMAKE ${PROJECT_SOURCE_DIR}/scripts/gen-wasm2c-templates.cmake) +set(TEMPLATE_DIR ${PROJECT_SOURCE_DIR}/src/templates) +set(PREBUILT_DIR ${PROJECT_SOURCE_DIR}/src/prebuilt) + +add_custom_command( + OUTPUT ${PREBUILT_DIR}/wasm2c_header_top.cc + ${PREBUILT_DIR}/wasm2c_header_bottom.cc + ${PREBUILT_DIR}/wasm2c_source_includes.cc + ${PREBUILT_DIR}/wasm2c_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc + + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_header_top.cc" -D in="${TEMPLATE_DIR}/wasm2c.top.h" -D symbol="s_header_top" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_header_bottom.cc" -D in="${TEMPLATE_DIR}/wasm2c.bottom.h" -D symbol="s_header_bottom" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_source_includes.cc" -D in="${TEMPLATE_DIR}/wasm2c.includes.c" -D symbol="s_source_includes" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c.declarations.c" -D symbol="s_source_declarations" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c_simd.declarations.c" -D symbol="s_simd_source_declarations" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c_atomicops.declarations.c" -D symbol="s_atomicops_source_declarations" -P ${TEMPLATE_CMAKE} + + DEPENDS ${TEMPLATE_DIR}/wasm2c.top.h + ${TEMPLATE_DIR}/wasm2c.bottom.h + ${TEMPLATE_DIR}/wasm2c.includes.c + ${TEMPLATE_DIR}/wasm2c.declarations.c + ${TEMPLATE_DIR}/wasm2c_simd.declarations.c + ${TEMPLATE_DIR}/wasm2c_atomicops.declarations.c +) + +set(WASM2C_GENERATED_SOURCES + ${PREBUILT_DIR}/wasm2c_header_top.cc + ${PREBUILT_DIR}/wasm2c_header_bottom.cc + ${PREBUILT_DIR}/wasm2c_source_includes.cc + ${PREBUILT_DIR}/wasm2c_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc +) + +binaryen_add_executable(wasm2c "wasm2c.cpp;${WASM2C_GENERATED_SOURCES}") +target_include_directories(wasm2c PRIVATE ${PROJECT_SOURCE_DIR}/third_party/picosha2) binaryen_add_executable(wasm-emscripten-finalize wasm-emscripten-finalize.cpp) binaryen_add_executable(wasm-as wasm-as.cpp) binaryen_add_executable(wasm-dis wasm-dis.cpp) From 45ea87405edf4766ed8779712c55d9c24384cdef Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Thu, 21 May 2026 22:53:23 +0000 Subject: [PATCH 5/7] checkpoint --- check.py | 3 +- scripts/test/shared.py | 1 + scripts/test/wasm2c.py | 123 +++++ src/prebuilt/wasm2c_spec_top.cc | 945 ++++++++++++++++++++++++++++++++ src/templates/wasm2c_spec.top.c | 502 +++++++++++++++++ src/tools/CMakeLists.txt | 4 + src/tools/wasm2c.cpp | 203 +++++++ src/wasm2c.h | 614 +++++++++++++++++++++ test/lit/wasm2c/minimal.wast | 802 +++++++++++++++++++++++++++ 9 files changed, 3196 insertions(+), 1 deletion(-) create mode 100644 scripts/test/wasm2c.py create mode 100644 src/prebuilt/wasm2c_spec_top.cc create mode 100644 src/templates/wasm2c_spec.top.c create mode 100644 src/tools/wasm2c.cpp create mode 100644 src/wasm2c.h create mode 100644 test/lit/wasm2c/minimal.wast diff --git a/check.py b/check.py index d1e1f49103a..3f9897a87c8 100755 --- a/check.py +++ b/check.py @@ -25,7 +25,7 @@ from multiprocessing.pool import ThreadPool from pathlib import Path -from scripts.test import binaryenjs, finalize, shared, support, wasm2js, wasm_opt +from scripts.test import binaryenjs, finalize, shared, support, wasm2js, wasm2c, wasm_opt from scripts.test.shared import print_heading assert sys.version_info >= (3, 10), 'requires Python 3.10' @@ -441,6 +441,7 @@ def wrapper(*args, **kwargs): 'spec': run_spec_tests, 'finalize': finalize.test_wasm_emscripten_finalize, 'wasm2js': wasm2js.test_wasm2js, + 'wasm2c-spec': wasm2c.test_wasm2c_spec, 'validator': run_validator_tests, 'example': run_example_tests, 'unit': run_unittest, diff --git a/scripts/test/shared.py b/scripts/test/shared.py index d8211d8143a..7eec347ca36 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -213,6 +213,7 @@ def is_exe(fpath): WASM_AS = [os.path.join(options.binaryen_bin, 'wasm-as')] WASM_DIS = [os.path.join(options.binaryen_bin, 'wasm-dis')] WASM2JS = [os.path.join(options.binaryen_bin, 'wasm2js')] +WASM2C = [os.path.join(options.binaryen_bin, 'wasm2c')] WASM_CTOR_EVAL = [os.path.join(options.binaryen_bin, 'wasm-ctor-eval')] WASM_SHELL = [os.path.join(options.binaryen_bin, 'wasm-shell')] WASM_REDUCE = [os.path.join(options.binaryen_bin, 'wasm-reduce')] diff --git a/scripts/test/wasm2c.py b/scripts/test/wasm2c.py new file mode 100644 index 00000000000..5d29cd4519b --- /dev/null +++ b/scripts/test/wasm2c.py @@ -0,0 +1,123 @@ +# Copyright 2026 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. + +import os +import re +import tempfile +import subprocess +from . import shared, support +from .shared import print_heading + +SPEC_LIST = [ + 'unreachable.wast', +] + +def run_in_sandbox(cmd, temp_dir, expected_status=0): + print("executing: ", " ".join(cmd)) + proc = subprocess.run(cmd, cwd=temp_dir, capture_output=True, text=True) + if expected_status is not None and proc.returncode != expected_status: + raise Exception(f"Command `{' '.join(cmd)}` failed with exit code {proc.returncode}. Stderr:\n{proc.stderr}") + return proc.stdout, proc.stderr + +def test_wasm2c_spec_execute(spec_wast_path): + basename = os.path.basename(spec_wast_path) + if basename not in SPEC_LIST: + print('..', basename, '(skipped)') + return + + print('..', basename) + + is_fail_test = '.fail.' in basename or basename.endswith('.fail.wast') + + try: + splits = support.split_wast(spec_wast_path) + except Exception as e: + shared.fail_with_error(f"Failed to split wast {basename}: {e}") + return + + with tempfile.TemporaryDirectory(delete=False, prefix=f"wasm2c_spec_{basename.replace('.wast', '').replace('.', '_')}_") as temp_dir: + # Combine module definitions and assertions into a single combined wast file + split_wast_path = os.path.join(temp_dir, 'split.wast') + with open(split_wast_path, 'w') as sf: + for module_wat, assertions in splits: + if not module_wat: + continue + if isinstance(module_wat, bytes): + sf.write(module_wat.decode('utf-8', errors='ignore')) + else: + sf.write(module_wat) + sf.write("\n\n") + for a in assertions: + sf.write(a) + sf.write("\n") + sf.write("\n\n") + + split_c = 'split.c' + split_h = 'split.h' + wasm2c_cmd = [shared.WASM2C[0], 'split.wast', '-o', split_c, '--allow-asserts'] + + if is_fail_test: + # Compile-failure test: expect validation to fail + try: + run_in_sandbox(wasm2c_cmd, temp_dir, expected_status=0) + shared.fail_with_error(f"Expected wasm2c validation to fail for {basename}, but it succeeded!") + except Exception as e: + if "failed with exit code 1" in str(e): + print(f"Spec fail-test {basename} passed (failed validation as expected).") + return + else: + raise e + return + else: + run_in_sandbox(wasm2c_cmd, temp_dir) + + # Compile C source portably using NATIVECC + c_sources = [os.path.join(temp_dir, 'split.c')] + wasm2c_dir = os.path.join(shared.options.binaryen_root, 'wasm2c') + c_sources.append(os.path.join(wasm2c_dir, 'wasm-rt-impl.c')) + c_sources.append(os.path.join(wasm2c_dir, 'wasm-rt-mem-impl.c')) + + cmd = [shared.NATIVECC, '-O2', '-std=c11', '-D_GNU_SOURCE', '-D_DEFAULT_SOURCE', '-I.', f"-I{wasm2c_dir}", f"-I{temp_dir}"] + c_sources + ['-o', 'spec_test_runner'] + + cmd += ['-fno-optimize-sibling-calls', '-frounding-math'] + if 'gcc' in shared.NATIVECC.lower(): + cmd.append('-fsignaling-nans') + + cmd.append('-lm') + cmd.append('-lpthread') + + run_in_sandbox(cmd, temp_dir) + + # Run spec test runner binary and assert success + actual_stdout, _ = run_in_sandbox(['./spec_test_runner'], temp_dir) + + if "assertions passed" not in actual_stdout: + shared.fail_with_error(f"Spec runner failed to run correctly. Stdout:\n{actual_stdout}") + else: + match = re.search(r'(\d+)/(\d+) assertions passed', actual_stdout) + if match: + passed = int(match.group(1)) + total = int(match.group(2)) + if passed != total: + shared.fail_with_error(f"Spec test {basename} FAILED: {passed}/{total} passed.") + else: + print(f"Spec test {basename} passed: {passed}/{total} assertions.") + +def test_wasm2c_spec(): + print_heading('checking wasm2c compiled spec testcases...') + if shared.skip_if_on_windows('wasm2c-spec'): + return + spec_tests = shared.options.spec_tests + for t in spec_tests: + test_wasm2c_spec_execute(t) diff --git a/src/prebuilt/wasm2c_spec_top.cc b/src/prebuilt/wasm2c_spec_top.cc new file mode 100644 index 00000000000..25bb552349a --- /dev/null +++ b/src/prebuilt/wasm2c_spec_top.cc @@ -0,0 +1,945 @@ +const char* s_spec_top = R"w2c_template(#include +)w2c_template" +R"w2c_template(#define __STDC_FORMAT_MACROS +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template( +#include "wasm-rt.h" +)w2c_template" +R"w2c_template(#include "wasm-rt-impl.h" +)w2c_template" +R"w2c_template(#include "wasm-rt-exceptions.h" +)w2c_template" +R"w2c_template( +/* NOTE: function argument evaluation order is implementation-defined in C, +)w2c_template" +R"w2c_template( so it SHOULD NOT be relied on by tests. */ +)w2c_template" +R"w2c_template(#if WABT_BIG_ENDIAN +)w2c_template" +R"w2c_template(#define v128_i8x16_make(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ +)w2c_template" +R"w2c_template( simde_wasm_i8x16_make(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_u8x16_make(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ +)w2c_template" +R"w2c_template( simde_wasm_u8x16_make(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_i16x8_make(a,b,c,d,e,f,g,h) simde_wasm_i16x8_make(h,g,f,e,d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_u16x8_make(a,b,c,d,e,f,g,h) simde_wasm_u16x8_make(h,g,f,e,d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_i32x4_make(a,b,c,d) simde_wasm_i32x4_make(d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_u32x4_make(a,b,c,d) simde_wasm_u32x4_make(d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_i64x2_make(a,b) simde_wasm_i64x2_make(b,a) +)w2c_template" +R"w2c_template(#define v128_u64x2_make(a,b) simde_wasm_u64x2_make(b,a) +)w2c_template" +R"w2c_template(#define v128_f32x4_make(a,b,c,d) simde_wasm_f32x4_make(d,c,b,a) +)w2c_template" +R"w2c_template(#define v128_f64x2_make(a,b) simde_wasm_f64x2_make(b,a) +)w2c_template" +R"w2c_template(#define v128_i8x16_extract_lane(a,n) simde_wasm_u8x16_extract_lane(a,15-(n)) +)w2c_template" +R"w2c_template(#define v128_u8x16_extract_lane(a,n) simde_wasm_u8x16_extract_lane(a,15-(n)) +)w2c_template" +R"w2c_template(#define v128_i16x8_extract_lane(a,n) simde_wasm_u16x8_extract_lane(a,7-(n)) +)w2c_template" +R"w2c_template(#define v128_u16x8_extract_lane(a,n) simde_wasm_u16x8_extract_lane(a,7-(n)) +)w2c_template" +R"w2c_template(#define v128_i32x4_extract_lane(a,n) simde_wasm_u32x4_extract_lane(a,3-(n)) +)w2c_template" +R"w2c_template(#define v128_u32x4_extract_lane(a,n) simde_wasm_u32x4_extract_lane(a,3-(n)) +)w2c_template" +R"w2c_template(#define v128_i64x2_extract_lane(a,n) simde_wasm_u64x2_extract_lane(a,1-(n)) +)w2c_template" +R"w2c_template(#define v128_u64x2_extract_lane(a,n) simde_wasm_u64x2_extract_lane(a,1-(n)) +)w2c_template" +R"w2c_template(#define v128_f32x4_extract_lane(a,n) simde_wasm_f32x4_extract_lane(a,3-(n)) +)w2c_template" +R"w2c_template(#define v128_f64x2_extract_lane(a,n) simde_wasm_f64x2_extract_lane(a,1-(n)) +)w2c_template" +R"w2c_template(#else +)w2c_template" +R"w2c_template(#define v128_i8x16_make simde_wasm_i8x16_make +)w2c_template" +R"w2c_template(#define v128_u8x16_make simde_wasm_u8x16_make +)w2c_template" +R"w2c_template(#define v128_i16x8_make simde_wasm_i16x8_make +)w2c_template" +R"w2c_template(#define v128_u16x8_make simde_wasm_u16x8_make +)w2c_template" +R"w2c_template(#define v128_i32x4_make simde_wasm_i32x4_make +)w2c_template" +R"w2c_template(#define v128_u32x4_make simde_wasm_u32x4_make +)w2c_template" +R"w2c_template(#define v128_i64x2_make simde_wasm_i64x2_make +)w2c_template" +R"w2c_template(#define v128_u64x2_make simde_wasm_u64x2_make +)w2c_template" +R"w2c_template(#define v128_f32x4_make simde_wasm_f32x4_make +)w2c_template" +R"w2c_template(#define v128_f64x2_make simde_wasm_f64x2_make +)w2c_template" +R"w2c_template(// like is_equal_TYPE below, always use unsigned for these +)w2c_template" +R"w2c_template(#define v128_i8x16_extract_lane simde_wasm_u8x16_extract_lane +)w2c_template" +R"w2c_template(#define v128_u8x16_extract_lane simde_wasm_u8x16_extract_lane +)w2c_template" +R"w2c_template(#define v128_i16x8_extract_lane simde_wasm_u16x8_extract_lane +)w2c_template" +R"w2c_template(#define v128_u16x8_extract_lane simde_wasm_u16x8_extract_lane +)w2c_template" +R"w2c_template(#define v128_i32x4_extract_lane simde_wasm_u32x4_extract_lane +)w2c_template" +R"w2c_template(#define v128_u32x4_extract_lane simde_wasm_u32x4_extract_lane +)w2c_template" +R"w2c_template(#define v128_i64x2_extract_lane simde_wasm_u64x2_extract_lane +)w2c_template" +R"w2c_template(#define v128_u64x2_extract_lane simde_wasm_u64x2_extract_lane +)w2c_template" +R"w2c_template(#define v128_f32x4_extract_lane simde_wasm_f32x4_extract_lane +)w2c_template" +R"w2c_template(#define v128_f64x2_extract_lane simde_wasm_f64x2_extract_lane +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +static int g_tests_run; +)w2c_template" +R"w2c_template(static int g_tests_passed; +)w2c_template" +R"w2c_template( +static void run_spec_tests(void); +)w2c_template" +R"w2c_template( +static void error(const char* file, int line, const char* format, ...) { +)w2c_template" +R"w2c_template( va_list args; +)w2c_template" +R"w2c_template( va_start(args, format); +)w2c_template" +R"w2c_template( fprintf(stderr, "%s:%d: assertion failed: ", file, line); +)w2c_template" +R"w2c_template( vfprintf(stderr, format, args); +)w2c_template" +R"w2c_template( va_end(args); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define ASSERT_EXCEPTION(f) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( if (wasm_rt_impl_try() == WASM_RT_TRAP_UNCAUGHT_EXCEPTION) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( (void)(f); \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, "expected " #f " to throw exception.\n"); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_TRAP(f) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( if (wasm_rt_impl_try() != 0) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( (void)(f); \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, "expected " #f " to trap.\n"); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_EXHAUSTION(f) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( wasm_rt_trap_t code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( switch (code) { \ +)w2c_template" +R"w2c_template( case WASM_RT_TRAP_NONE: \ +)w2c_template" +R"w2c_template( (void)(f); \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, "expected " #f " to trap.\n"); \ +)w2c_template" +R"w2c_template( break; \ +)w2c_template" +R"w2c_template( case WASM_RT_TRAP_EXHAUSTION: \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( break; \ +)w2c_template" +R"w2c_template( default: \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "expected " #f \ +)w2c_template" +R"w2c_template( " to trap due to exhaustion, got trap code %d.\n", \ +)w2c_template" +R"w2c_template( code); \ +)w2c_template" +R"w2c_template( break; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN(f) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( f; \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN_T(type, fmt, f, expected) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( type actual = f; \ +)w2c_template" +R"w2c_template( if (is_equal_##type(actual, expected)) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "in " #f ": expected %" fmt ", got %" fmt ".\n", expected, \ +)w2c_template" +R"w2c_template( actual); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN_FUNCREF(f, expected) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( wasm_rt_funcref_t actual = f; \ +)w2c_template" +R"w2c_template( if (is_equal_wasm_rt_funcref_t(actual, expected)) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "in " #f ": mismatch between expected and actual funcref"); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN_EXNREF(f, expected) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( wasm_rt_exnref_t actual = f; \ +)w2c_template" +R"w2c_template( if (is_equal_wasm_rt_exnref_t(actual, expected)) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "in " #f ": mismatch between expected and actual exnref"); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN_NAN_T(type, itype, fmt, f, kind) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( type actual = f; \ +)w2c_template" +R"w2c_template( itype iactual; \ +)w2c_template" +R"w2c_template( memcpy(&iactual, &actual, sizeof(iactual)); \ +)w2c_template" +R"w2c_template( if (is_##kind##_nan_##type(iactual)) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "in " #f ": expected result to be a " #kind " nan, got 0x%" fmt \ +)w2c_template" +R"w2c_template( ".\n", \ +)w2c_template" +R"w2c_template( iactual); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( +#define MULTI_T_UNPACK_(...) __VA_ARGS__ +)w2c_template" +R"w2c_template(#define MULTI_T_UNPACK(arg) MULTI_T_UNPACK_ arg +)w2c_template" +R"w2c_template(#define MULTI_i8 "%" PRIu8 " " +)w2c_template" +R"w2c_template(#define MULTI_i16 "%" PRIu16 " " +)w2c_template" +R"w2c_template(#define MULTI_i32 "%u " +)w2c_template" +R"w2c_template(#define MULTI_i64 "%" PRIu64 " " +)w2c_template" +R"w2c_template(#define MULTI_f32 "%.9g " +)w2c_template" +R"w2c_template(#define MULTI_f64 "%.17g " +)w2c_template" +R"w2c_template(#define MULTI_str "%s " +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_MULTI_T(type, fmt_expected, fmt_got, f, compare, \ +)w2c_template" +R"w2c_template( expected, found) \ +)w2c_template" +R"w2c_template( do { \ +)w2c_template" +R"w2c_template( g_tests_run++; \ +)w2c_template" +R"w2c_template( int trap_code = wasm_rt_impl_try(); \ +)w2c_template" +R"w2c_template( if (trap_code) { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, #f " trapped (%s).\n", \ +)w2c_template" +R"w2c_template( wasm_rt_strerror(trap_code)); \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( type actual = f; \ +)w2c_template" +R"w2c_template( if (compare) { \ +)w2c_template" +R"w2c_template( g_tests_passed++; \ +)w2c_template" +R"w2c_template( } else { \ +)w2c_template" +R"w2c_template( error(__FILE__, __LINE__, \ +)w2c_template" +R"w2c_template( "in " #f ": expected <" fmt_expected ">, got <" fmt_got ">.\n", \ +)w2c_template" +R"w2c_template( MULTI_T_UNPACK(expected), MULTI_T_UNPACK(found)); \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } \ +)w2c_template" +R"w2c_template( } while (0) +)w2c_template" +R"w2c_template( + +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_I32(f, expected) ASSERT_RETURN_T(u32, "u", f, expected) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_I64(f, expected) ASSERT_RETURN_T(u64, PRIu64, f, expected) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_F32(f, expected) ASSERT_RETURN_T(f32, ".9g", f, expected) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_F64(f, expected) ASSERT_RETURN_T(f64, ".17g", f, expected) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_EXTERNREF(f, expected) \ +)w2c_template" +R"w2c_template( ASSERT_RETURN_T(wasm_rt_externref_t, "p", f, expected); +)w2c_template" +R"w2c_template( +#define ASSERT_RETURN_CANONICAL_NAN_F32(f) \ +)w2c_template" +R"w2c_template( ASSERT_RETURN_NAN_T(f32, u32, "08x", f, canonical) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_CANONICAL_NAN_F64(f) \ +)w2c_template" +R"w2c_template( ASSERT_RETURN_NAN_T(f64, u64, "016x", f, canonical) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_ARITHMETIC_NAN_F32(f) \ +)w2c_template" +R"w2c_template( ASSERT_RETURN_NAN_T(f32, u32, "08x", f, arithmetic) +)w2c_template" +R"w2c_template(#define ASSERT_RETURN_ARITHMETIC_NAN_F64(f) \ +)w2c_template" +R"w2c_template( ASSERT_RETURN_NAN_T(f64, u64, "016x", f, arithmetic) +)w2c_template" +R"w2c_template( +static bool is_equal_u8(u8 x, u8 y) { +)w2c_template" +R"w2c_template( return x == y; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_u16(u16 x, u16 y) { +)w2c_template" +R"w2c_template( return x == y; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_u32(u32 x, u32 y) { +)w2c_template" +R"w2c_template( return x == y; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_u64(u64 x, u64 y) { +)w2c_template" +R"w2c_template( return x == y; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#define is_equal_i8 is_equal_u8 +)w2c_template" +R"w2c_template(#define is_equal_i16 is_equal_u16 +)w2c_template" +R"w2c_template(#define is_equal_i32 is_equal_u32 +)w2c_template" +R"w2c_template(#define is_equal_i64 is_equal_u64 +)w2c_template" +R"w2c_template( +static bool is_equal_wasm_rt_externref_t(wasm_rt_externref_t x, +)w2c_template" +R"w2c_template( wasm_rt_externref_t y) { +)w2c_template" +R"w2c_template( return x == y; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static inline bool is_equal_wasm_rt_func_type_t(const wasm_rt_func_type_t a, +)w2c_template" +R"w2c_template( const wasm_rt_func_type_t b) { +)w2c_template" +R"w2c_template( return (a == b) || (a && b && !memcmp(a, b, 32)); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_wasm_rt_funcref_t(wasm_rt_funcref_t x, +)w2c_template" +R"w2c_template( wasm_rt_funcref_t y) { +)w2c_template" +R"w2c_template( return is_equal_wasm_rt_func_type_t(x.func_type, y.func_type) && +)w2c_template" +R"w2c_template( (x.func == y.func) && (x.module_instance == y.module_instance); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +#ifdef WASM_EXN_MAX_SIZE +)w2c_template" +R"w2c_template(static bool is_equal_wasm_rt_exnref_t(wasm_rt_exnref_t x, wasm_rt_exnref_t y) { +)w2c_template" +R"w2c_template( return x.tag == y.tag && x.size == y.size && !memcmp(x.data, y.data, x.size); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +wasm_rt_externref_t spectest_make_externref(uintptr_t x) { +)w2c_template" +R"w2c_template( return (wasm_rt_externref_t)(x + 1); // externref(0) is not null +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static u32 f32_bits(f32 x) { +)w2c_template" +R"w2c_template( u32 ux; +)w2c_template" +R"w2c_template( memcpy(&ux, &x, sizeof(ux)); +)w2c_template" +R"w2c_template( return ux; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static u64 f64_bits(f64 x) { +)w2c_template" +R"w2c_template( u64 ux; +)w2c_template" +R"w2c_template( memcpy(&ux, &x, sizeof(ux)); +)w2c_template" +R"w2c_template( return ux; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_f32(f32 x, f32 y) { +)w2c_template" +R"w2c_template( return f32_bits(x) == f32_bits(y); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_equal_f64(f64 x, f64 y) { +)w2c_template" +R"w2c_template( return f64_bits(x) == f64_bits(y); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static f32 make_nan_f32(u32 x) { +)w2c_template" +R"w2c_template( x |= 0x7f800000; +)w2c_template" +R"w2c_template( f32 res; +)w2c_template" +R"w2c_template( memcpy(&res, &x, sizeof(res)); +)w2c_template" +R"w2c_template( return res; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static f64 make_nan_f64(u64 x) { +)w2c_template" +R"w2c_template( x |= 0x7ff0000000000000; +)w2c_template" +R"w2c_template( f64 res; +)w2c_template" +R"w2c_template( memcpy(&res, &x, sizeof(res)); +)w2c_template" +R"w2c_template( return res; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_canonical_nan_f32(u32 x) { +)w2c_template" +R"w2c_template( return (x & 0x7fffffff) == 0x7fc00000; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_canonical_nan_f64(u64 x) { +)w2c_template" +R"w2c_template( return (x & 0x7fffffffffffffff) == 0x7ff8000000000000; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_arithmetic_nan_f32(u32 x) { +)w2c_template" +R"w2c_template( return (x & 0x7fc00000) == 0x7fc00000; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static bool is_arithmetic_nan_f64(u64 x) { +)w2c_template" +R"w2c_template( return (x & 0x7ff8000000000000) == 0x7ff8000000000000; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +typedef struct w2c_spectest { +)w2c_template" +R"w2c_template( wasm_rt_funcref_table_t spectest_table; +)w2c_template" +R"w2c_template( wasm_rt_funcref_table_t spectest_table64; +)w2c_template" +R"w2c_template( wasm_rt_memory_t spectest_memory; +)w2c_template" +R"w2c_template( uint32_t spectest_global_i32; +)w2c_template" +R"w2c_template( uint64_t spectest_global_i64; +)w2c_template" +R"w2c_template( float spectest_global_f32; +)w2c_template" +R"w2c_template( double spectest_global_f64; +)w2c_template" +R"w2c_template(} w2c_spectest; +)w2c_template" +R"w2c_template( +static w2c_spectest spectest_instance; +)w2c_template" +R"w2c_template( +/* +)w2c_template" +R"w2c_template( * spectest implementations +)w2c_template" +R"w2c_template( */ +)w2c_template" +R"w2c_template(void w2c_spectest_print(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( printf("spectest.print()\n"); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_i32(w2c_spectest* instance, uint32_t i) { +)w2c_template" +R"w2c_template( printf("spectest.print_i32(%d)\n", i); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_i64(w2c_spectest* instance, uint64_t i) { +)w2c_template" +R"w2c_template( printf("spectest.print_i64(%" PRIu64 ")\n", i); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_f32(w2c_spectest* instance, float f) { +)w2c_template" +R"w2c_template( printf("spectest.print_f32(%g)\n", f); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_i32_f32(w2c_spectest* instance, uint32_t i, float f) { +)w2c_template" +R"w2c_template( printf("spectest.print_i32_f32(%d %g)\n", i, f); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_f64(w2c_spectest* instance, double d) { +)w2c_template" +R"w2c_template( printf("spectest.print_f64(%g)\n", d); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +void w2c_spectest_print_f64_f64(w2c_spectest* instance, double d1, double d2) { +)w2c_template" +R"w2c_template( printf("spectest.print_f64_f64(%g %g)\n", d1, d2); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +wasm_rt_funcref_table_t* w2c_spectest_table(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_table; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +wasm_rt_funcref_table_t* w2c_spectest_table64(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_table64; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +wasm_rt_memory_t* w2c_spectest_memory(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_memory; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +uint32_t* w2c_spectest_global_i32(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_global_i32; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +uint64_t* w2c_spectest_global_i64(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_global_i64; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +float* w2c_spectest_global_f32(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_global_f32; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +double* w2c_spectest_global_f64(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( return &instance->spectest_global_f64; +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static void init_spectest_module(w2c_spectest* instance) { +)w2c_template" +R"w2c_template( instance->spectest_global_i32 = 666; +)w2c_template" +R"w2c_template( instance->spectest_global_i64 = 666l; +)w2c_template" +R"w2c_template( instance->spectest_global_f32 = 666.6; +)w2c_template" +R"w2c_template( instance->spectest_global_f64 = 666.6; +)w2c_template" +R"w2c_template( wasm_rt_allocate_memory(&instance->spectest_memory, 1, 2, false, +)w2c_template" +R"w2c_template( WASM_DEFAULT_PAGE_SIZE); +)w2c_template" +R"w2c_template( wasm_rt_allocate_funcref_table(&instance->spectest_table, 10, 20); +)w2c_template" +R"w2c_template( wasm_rt_allocate_funcref_table(&instance->spectest_table64, 10, 20); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +// POSIX-only test config where embedder handles signals instead of w2c runtime +)w2c_template" +R"w2c_template(#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING +)w2c_template" +R"w2c_template(#include +)w2c_template" +R"w2c_template( +static void posix_signal_handler(int sig, siginfo_t* si, void* unused) { +)w2c_template" +R"w2c_template( wasm_rt_trap((si->si_code == SEGV_ACCERR) ? WASM_RT_TRAP_OOB +)w2c_template" +R"w2c_template( : WASM_RT_TRAP_EXHAUSTION); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static void posix_install_signal_handler(void) { +)w2c_template" +R"w2c_template( /* install altstack */ +)w2c_template" +R"w2c_template( stack_t ss; +)w2c_template" +R"w2c_template( ss.ss_sp = malloc(SIGSTKSZ); +)w2c_template" +R"w2c_template( ss.ss_flags = 0; +)w2c_template" +R"w2c_template( ss.ss_size = SIGSTKSZ; +)w2c_template" +R"w2c_template( if (sigaltstack(&ss, NULL) != 0) { +)w2c_template" +R"w2c_template( perror("sigaltstack failed"); +)w2c_template" +R"w2c_template( abort(); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( + /* install signal handler */ +)w2c_template" +R"w2c_template( struct sigaction sa; +)w2c_template" +R"w2c_template( memset(&sa, '\0', sizeof(sa)); +)w2c_template" +R"w2c_template( sa.sa_flags = SA_SIGINFO | SA_ONSTACK; +)w2c_template" +R"w2c_template( sigemptyset(&sa.sa_mask); +)w2c_template" +R"w2c_template( sa.sa_sigaction = posix_signal_handler; +)w2c_template" +R"w2c_template( if (sigaction(SIGSEGV, &sa, NULL) != 0 || sigaction(SIGBUS, &sa, NULL) != 0) { +)w2c_template" +R"w2c_template( perror("sigaction failed"); +)w2c_template" +R"w2c_template( abort(); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template( +static void posix_cleanup_signal_handler(void) { +)w2c_template" +R"w2c_template( /* remove signal handler */ +)w2c_template" +R"w2c_template( struct sigaction sa; +)w2c_template" +R"w2c_template( memset(&sa, '\0', sizeof(sa)); +)w2c_template" +R"w2c_template( sa.sa_handler = SIG_DFL; +)w2c_template" +R"w2c_template( if (sigaction(SIGSEGV, &sa, NULL) != 0 || sigaction(SIGBUS, &sa, NULL)) { +)w2c_template" +R"w2c_template( perror("sigaction failed"); +)w2c_template" +R"w2c_template( abort(); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( + /* disable and free altstack */ +)w2c_template" +R"w2c_template( stack_t ss; +)w2c_template" +R"w2c_template( ss.ss_flags = SS_DISABLE; +)w2c_template" +R"w2c_template( if (sigaltstack(&ss, NULL) != 0) { +)w2c_template" +R"w2c_template( perror("sigaltstack failed"); +)w2c_template" +R"w2c_template( abort(); +)w2c_template" +R"w2c_template( } +)w2c_template" +R"w2c_template( free(ss.ss_sp); +)w2c_template" +R"w2c_template(} +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( +int main(int argc, char** argv) { +)w2c_template" +R"w2c_template(#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING +)w2c_template" +R"w2c_template( posix_install_signal_handler(); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( wasm_rt_init(); +)w2c_template" +R"w2c_template( init_spectest_module(&spectest_instance); +)w2c_template" +R"w2c_template( run_spec_tests(); +)w2c_template" +R"w2c_template( printf("%u/%u tests passed.\n", g_tests_passed, g_tests_run); +)w2c_template" +R"w2c_template( wasm_rt_free(); +)w2c_template" +R"w2c_template(#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING +)w2c_template" +R"w2c_template( posix_cleanup_signal_handler(); +)w2c_template" +R"w2c_template(#endif +)w2c_template" +R"w2c_template( return g_tests_passed != g_tests_run; +)w2c_template" +R"w2c_template(} +)w2c_template" +; diff --git a/src/templates/wasm2c_spec.top.c b/src/templates/wasm2c_spec.top.c new file mode 100644 index 00000000000..bc86f8fd4e8 --- /dev/null +++ b/src/templates/wasm2c_spec.top.c @@ -0,0 +1,502 @@ +#include +#define __STDC_FORMAT_MACROS +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wasm-rt.h" +#include "wasm-rt-impl.h" +#include "wasm-rt-exceptions.h" + +/* NOTE: function argument evaluation order is implementation-defined in C, + so it SHOULD NOT be relied on by tests. */ +#if WABT_BIG_ENDIAN +#define v128_i8x16_make(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ + simde_wasm_i8x16_make(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +#define v128_u8x16_make(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p) \ + simde_wasm_u8x16_make(p,o,n,m,l,k,j,i,h,g,f,e,d,c,b,a) +#define v128_i16x8_make(a,b,c,d,e,f,g,h) simde_wasm_i16x8_make(h,g,f,e,d,c,b,a) +#define v128_u16x8_make(a,b,c,d,e,f,g,h) simde_wasm_u16x8_make(h,g,f,e,d,c,b,a) +#define v128_i32x4_make(a,b,c,d) simde_wasm_i32x4_make(d,c,b,a) +#define v128_u32x4_make(a,b,c,d) simde_wasm_u32x4_make(d,c,b,a) +#define v128_i64x2_make(a,b) simde_wasm_i64x2_make(b,a) +#define v128_u64x2_make(a,b) simde_wasm_u64x2_make(b,a) +#define v128_f32x4_make(a,b,c,d) simde_wasm_f32x4_make(d,c,b,a) +#define v128_f64x2_make(a,b) simde_wasm_f64x2_make(b,a) +#define v128_i8x16_extract_lane(a,n) simde_wasm_u8x16_extract_lane(a,15-(n)) +#define v128_u8x16_extract_lane(a,n) simde_wasm_u8x16_extract_lane(a,15-(n)) +#define v128_i16x8_extract_lane(a,n) simde_wasm_u16x8_extract_lane(a,7-(n)) +#define v128_u16x8_extract_lane(a,n) simde_wasm_u16x8_extract_lane(a,7-(n)) +#define v128_i32x4_extract_lane(a,n) simde_wasm_u32x4_extract_lane(a,3-(n)) +#define v128_u32x4_extract_lane(a,n) simde_wasm_u32x4_extract_lane(a,3-(n)) +#define v128_i64x2_extract_lane(a,n) simde_wasm_u64x2_extract_lane(a,1-(n)) +#define v128_u64x2_extract_lane(a,n) simde_wasm_u64x2_extract_lane(a,1-(n)) +#define v128_f32x4_extract_lane(a,n) simde_wasm_f32x4_extract_lane(a,3-(n)) +#define v128_f64x2_extract_lane(a,n) simde_wasm_f64x2_extract_lane(a,1-(n)) +#else +#define v128_i8x16_make simde_wasm_i8x16_make +#define v128_u8x16_make simde_wasm_u8x16_make +#define v128_i16x8_make simde_wasm_i16x8_make +#define v128_u16x8_make simde_wasm_u16x8_make +#define v128_i32x4_make simde_wasm_i32x4_make +#define v128_u32x4_make simde_wasm_u32x4_make +#define v128_i64x2_make simde_wasm_i64x2_make +#define v128_u64x2_make simde_wasm_u64x2_make +#define v128_f32x4_make simde_wasm_f32x4_make +#define v128_f64x2_make simde_wasm_f64x2_make +// like is_equal_TYPE below, always use unsigned for these +#define v128_i8x16_extract_lane simde_wasm_u8x16_extract_lane +#define v128_u8x16_extract_lane simde_wasm_u8x16_extract_lane +#define v128_i16x8_extract_lane simde_wasm_u16x8_extract_lane +#define v128_u16x8_extract_lane simde_wasm_u16x8_extract_lane +#define v128_i32x4_extract_lane simde_wasm_u32x4_extract_lane +#define v128_u32x4_extract_lane simde_wasm_u32x4_extract_lane +#define v128_i64x2_extract_lane simde_wasm_u64x2_extract_lane +#define v128_u64x2_extract_lane simde_wasm_u64x2_extract_lane +#define v128_f32x4_extract_lane simde_wasm_f32x4_extract_lane +#define v128_f64x2_extract_lane simde_wasm_f64x2_extract_lane +#endif + +static int g_tests_run; +static int g_tests_passed; + +static void run_spec_tests(void); + +static void error(const char* file, int line, const char* format, ...) { + va_list args; + va_start(args, format); + fprintf(stderr, "%s:%d: assertion failed: ", file, line); + vfprintf(stderr, format, args); + va_end(args); +} + +#define ASSERT_EXCEPTION(f) \ + do { \ + g_tests_run++; \ + if (wasm_rt_impl_try() == WASM_RT_TRAP_UNCAUGHT_EXCEPTION) { \ + g_tests_passed++; \ + } else { \ + (void)(f); \ + error(__FILE__, __LINE__, "expected " #f " to throw exception.\n"); \ + } \ + } while (0) + +#define ASSERT_TRAP(f) \ + do { \ + g_tests_run++; \ + if (wasm_rt_impl_try() != 0) { \ + g_tests_passed++; \ + } else { \ + (void)(f); \ + error(__FILE__, __LINE__, "expected " #f " to trap.\n"); \ + } \ + } while (0) + +#define ASSERT_EXHAUSTION(f) \ + do { \ + g_tests_run++; \ + wasm_rt_trap_t code = wasm_rt_impl_try(); \ + switch (code) { \ + case WASM_RT_TRAP_NONE: \ + (void)(f); \ + error(__FILE__, __LINE__, "expected " #f " to trap.\n"); \ + break; \ + case WASM_RT_TRAP_EXHAUSTION: \ + g_tests_passed++; \ + break; \ + default: \ + error(__FILE__, __LINE__, \ + "expected " #f \ + " to trap due to exhaustion, got trap code %d.\n", \ + code); \ + break; \ + } \ + } while (0) + +#define ASSERT_RETURN(f) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + f; \ + g_tests_passed++; \ + } \ + } while (0) + +#define ASSERT_RETURN_T(type, fmt, f, expected) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + type actual = f; \ + if (is_equal_##type(actual, expected)) { \ + g_tests_passed++; \ + } else { \ + error(__FILE__, __LINE__, \ + "in " #f ": expected %" fmt ", got %" fmt ".\n", expected, \ + actual); \ + } \ + } \ + } while (0) + +#define ASSERT_RETURN_FUNCREF(f, expected) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + wasm_rt_funcref_t actual = f; \ + if (is_equal_wasm_rt_funcref_t(actual, expected)) { \ + g_tests_passed++; \ + } else { \ + error(__FILE__, __LINE__, \ + "in " #f ": mismatch between expected and actual funcref"); \ + } \ + } \ + } while (0) + +#define ASSERT_RETURN_EXNREF(f, expected) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + wasm_rt_exnref_t actual = f; \ + if (is_equal_wasm_rt_exnref_t(actual, expected)) { \ + g_tests_passed++; \ + } else { \ + error(__FILE__, __LINE__, \ + "in " #f ": mismatch between expected and actual exnref"); \ + } \ + } \ + } while (0) + +#define ASSERT_RETURN_NAN_T(type, itype, fmt, f, kind) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + type actual = f; \ + itype iactual; \ + memcpy(&iactual, &actual, sizeof(iactual)); \ + if (is_##kind##_nan_##type(iactual)) { \ + g_tests_passed++; \ + } else { \ + error(__FILE__, __LINE__, \ + "in " #f ": expected result to be a " #kind " nan, got 0x%" fmt \ + ".\n", \ + iactual); \ + } \ + } \ + } while (0) + +#define MULTI_T_UNPACK_(...) __VA_ARGS__ +#define MULTI_T_UNPACK(arg) MULTI_T_UNPACK_ arg +#define MULTI_i8 "%" PRIu8 " " +#define MULTI_i16 "%" PRIu16 " " +#define MULTI_i32 "%u " +#define MULTI_i64 "%" PRIu64 " " +#define MULTI_f32 "%.9g " +#define MULTI_f64 "%.17g " +#define MULTI_str "%s " +#define ASSERT_RETURN_MULTI_T(type, fmt_expected, fmt_got, f, compare, \ + expected, found) \ + do { \ + g_tests_run++; \ + int trap_code = wasm_rt_impl_try(); \ + if (trap_code) { \ + error(__FILE__, __LINE__, #f " trapped (%s).\n", \ + wasm_rt_strerror(trap_code)); \ + } else { \ + type actual = f; \ + if (compare) { \ + g_tests_passed++; \ + } else { \ + error(__FILE__, __LINE__, \ + "in " #f ": expected <" fmt_expected ">, got <" fmt_got ">.\n", \ + MULTI_T_UNPACK(expected), MULTI_T_UNPACK(found)); \ + } \ + } \ + } while (0) + + +#define ASSERT_RETURN_I32(f, expected) ASSERT_RETURN_T(u32, "u", f, expected) +#define ASSERT_RETURN_I64(f, expected) ASSERT_RETURN_T(u64, PRIu64, f, expected) +#define ASSERT_RETURN_F32(f, expected) ASSERT_RETURN_T(f32, ".9g", f, expected) +#define ASSERT_RETURN_F64(f, expected) ASSERT_RETURN_T(f64, ".17g", f, expected) +#define ASSERT_RETURN_EXTERNREF(f, expected) \ + ASSERT_RETURN_T(wasm_rt_externref_t, "p", f, expected); + +#define ASSERT_RETURN_CANONICAL_NAN_F32(f) \ + ASSERT_RETURN_NAN_T(f32, u32, "08x", f, canonical) +#define ASSERT_RETURN_CANONICAL_NAN_F64(f) \ + ASSERT_RETURN_NAN_T(f64, u64, "016x", f, canonical) +#define ASSERT_RETURN_ARITHMETIC_NAN_F32(f) \ + ASSERT_RETURN_NAN_T(f32, u32, "08x", f, arithmetic) +#define ASSERT_RETURN_ARITHMETIC_NAN_F64(f) \ + ASSERT_RETURN_NAN_T(f64, u64, "016x", f, arithmetic) + +static bool is_equal_u8(u8 x, u8 y) { + return x == y; +} + +static bool is_equal_u16(u16 x, u16 y) { + return x == y; +} + +static bool is_equal_u32(u32 x, u32 y) { + return x == y; +} + +static bool is_equal_u64(u64 x, u64 y) { + return x == y; +} + +#define is_equal_i8 is_equal_u8 +#define is_equal_i16 is_equal_u16 +#define is_equal_i32 is_equal_u32 +#define is_equal_i64 is_equal_u64 + +static bool is_equal_wasm_rt_externref_t(wasm_rt_externref_t x, + wasm_rt_externref_t y) { + return x == y; +} + +static inline bool is_equal_wasm_rt_func_type_t(const wasm_rt_func_type_t a, + const wasm_rt_func_type_t b) { + return (a == b) || (a && b && !memcmp(a, b, 32)); +} + +static bool is_equal_wasm_rt_funcref_t(wasm_rt_funcref_t x, + wasm_rt_funcref_t y) { + return is_equal_wasm_rt_func_type_t(x.func_type, y.func_type) && + (x.func == y.func) && (x.module_instance == y.module_instance); +} + +#ifdef WASM_EXN_MAX_SIZE +static bool is_equal_wasm_rt_exnref_t(wasm_rt_exnref_t x, wasm_rt_exnref_t y) { + return x.tag == y.tag && x.size == y.size && !memcmp(x.data, y.data, x.size); +} +#endif + +wasm_rt_externref_t spectest_make_externref(uintptr_t x) { + return (wasm_rt_externref_t)(x + 1); // externref(0) is not null +} + +static u32 f32_bits(f32 x) { + u32 ux; + memcpy(&ux, &x, sizeof(ux)); + return ux; +} + +static u64 f64_bits(f64 x) { + u64 ux; + memcpy(&ux, &x, sizeof(ux)); + return ux; +} + +static bool is_equal_f32(f32 x, f32 y) { + return f32_bits(x) == f32_bits(y); +} + +static bool is_equal_f64(f64 x, f64 y) { + return f64_bits(x) == f64_bits(y); +} + +static f32 make_nan_f32(u32 x) { + x |= 0x7f800000; + f32 res; + memcpy(&res, &x, sizeof(res)); + return res; +} + +static f64 make_nan_f64(u64 x) { + x |= 0x7ff0000000000000; + f64 res; + memcpy(&res, &x, sizeof(res)); + return res; +} + +static bool is_canonical_nan_f32(u32 x) { + return (x & 0x7fffffff) == 0x7fc00000; +} + +static bool is_canonical_nan_f64(u64 x) { + return (x & 0x7fffffffffffffff) == 0x7ff8000000000000; +} + +static bool is_arithmetic_nan_f32(u32 x) { + return (x & 0x7fc00000) == 0x7fc00000; +} + +static bool is_arithmetic_nan_f64(u64 x) { + return (x & 0x7ff8000000000000) == 0x7ff8000000000000; +} + +typedef struct w2c_spectest { + wasm_rt_funcref_table_t spectest_table; + wasm_rt_funcref_table_t spectest_table64; + wasm_rt_memory_t spectest_memory; + uint32_t spectest_global_i32; + uint64_t spectest_global_i64; + float spectest_global_f32; + double spectest_global_f64; +} w2c_spectest; + +static w2c_spectest spectest_instance; + +/* + * spectest implementations + */ +void w2c_spectest_print(w2c_spectest* instance) { + printf("spectest.print()\n"); +} + +void w2c_spectest_print_i32(w2c_spectest* instance, uint32_t i) { + printf("spectest.print_i32(%d)\n", i); +} + +void w2c_spectest_print_i64(w2c_spectest* instance, uint64_t i) { + printf("spectest.print_i64(%" PRIu64 ")\n", i); +} + +void w2c_spectest_print_f32(w2c_spectest* instance, float f) { + printf("spectest.print_f32(%g)\n", f); +} + +void w2c_spectest_print_i32_f32(w2c_spectest* instance, uint32_t i, float f) { + printf("spectest.print_i32_f32(%d %g)\n", i, f); +} + +void w2c_spectest_print_f64(w2c_spectest* instance, double d) { + printf("spectest.print_f64(%g)\n", d); +} + +void w2c_spectest_print_f64_f64(w2c_spectest* instance, double d1, double d2) { + printf("spectest.print_f64_f64(%g %g)\n", d1, d2); +} + +wasm_rt_funcref_table_t* w2c_spectest_table(w2c_spectest* instance) { + return &instance->spectest_table; +} + +wasm_rt_funcref_table_t* w2c_spectest_table64(w2c_spectest* instance) { + return &instance->spectest_table64; +} + +wasm_rt_memory_t* w2c_spectest_memory(w2c_spectest* instance) { + return &instance->spectest_memory; +} + +uint32_t* w2c_spectest_global_i32(w2c_spectest* instance) { + return &instance->spectest_global_i32; +} + +uint64_t* w2c_spectest_global_i64(w2c_spectest* instance) { + return &instance->spectest_global_i64; +} + +float* w2c_spectest_global_f32(w2c_spectest* instance) { + return &instance->spectest_global_f32; +} + +double* w2c_spectest_global_f64(w2c_spectest* instance) { + return &instance->spectest_global_f64; +} + +static void init_spectest_module(w2c_spectest* instance) { + instance->spectest_global_i32 = 666; + instance->spectest_global_i64 = 666l; + instance->spectest_global_f32 = 666.6; + instance->spectest_global_f64 = 666.6; + wasm_rt_allocate_memory(&instance->spectest_memory, 1, 2, false, + WASM_DEFAULT_PAGE_SIZE); + wasm_rt_allocate_funcref_table(&instance->spectest_table, 10, 20); + wasm_rt_allocate_funcref_table(&instance->spectest_table64, 10, 20); +} + +// POSIX-only test config where embedder handles signals instead of w2c runtime +#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING +#include + +static void posix_signal_handler(int sig, siginfo_t* si, void* unused) { + wasm_rt_trap((si->si_code == SEGV_ACCERR) ? WASM_RT_TRAP_OOB + : WASM_RT_TRAP_EXHAUSTION); +} + +static void posix_install_signal_handler(void) { + /* install altstack */ + stack_t ss; + ss.ss_sp = malloc(SIGSTKSZ); + ss.ss_flags = 0; + ss.ss_size = SIGSTKSZ; + if (sigaltstack(&ss, NULL) != 0) { + perror("sigaltstack failed"); + abort(); + } + + /* install signal handler */ + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&sa.sa_mask); + sa.sa_sigaction = posix_signal_handler; + if (sigaction(SIGSEGV, &sa, NULL) != 0 || sigaction(SIGBUS, &sa, NULL) != 0) { + perror("sigaction failed"); + abort(); + } +} + +static void posix_cleanup_signal_handler(void) { + /* remove signal handler */ + struct sigaction sa; + memset(&sa, '\0', sizeof(sa)); + sa.sa_handler = SIG_DFL; + if (sigaction(SIGSEGV, &sa, NULL) != 0 || sigaction(SIGBUS, &sa, NULL)) { + perror("sigaction failed"); + abort(); + } + + /* disable and free altstack */ + stack_t ss; + ss.ss_flags = SS_DISABLE; + if (sigaltstack(&ss, NULL) != 0) { + perror("sigaltstack failed"); + abort(); + } + free(ss.ss_sp); +} +#endif + +int main(int argc, char** argv) { +#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING + posix_install_signal_handler(); +#endif + wasm_rt_init(); + init_spectest_module(&spectest_instance); + run_spec_tests(); + printf("%u/%u tests passed.\n", g_tests_passed, g_tests_run); + wasm_rt_free(); +#ifdef WASM2C_TEST_EMBEDDER_SIGNAL_HANDLING + posix_cleanup_signal_handler(); +#endif + return g_tests_passed != g_tests_run; +} diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index ab9781189cf..61a6d2d7002 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -22,6 +22,7 @@ add_custom_command( ${PREBUILT_DIR}/wasm2c_source_declarations.cc ${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc ${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_spec_top.cc COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_header_top.cc" -D in="${TEMPLATE_DIR}/wasm2c.top.h" -D symbol="s_header_top" -P ${TEMPLATE_CMAKE} COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_header_bottom.cc" -D in="${TEMPLATE_DIR}/wasm2c.bottom.h" -D symbol="s_header_bottom" -P ${TEMPLATE_CMAKE} @@ -29,6 +30,7 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c.declarations.c" -D symbol="s_source_declarations" -P ${TEMPLATE_CMAKE} COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c_simd.declarations.c" -D symbol="s_simd_source_declarations" -P ${TEMPLATE_CMAKE} COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc" -D in="${TEMPLATE_DIR}/wasm2c_atomicops.declarations.c" -D symbol="s_atomicops_source_declarations" -P ${TEMPLATE_CMAKE} + COMMAND ${CMAKE_COMMAND} -D out="${PREBUILT_DIR}/wasm2c_spec_top.cc" -D in="${TEMPLATE_DIR}/wasm2c_spec.top.c" -D symbol="s_spec_top" -P ${TEMPLATE_CMAKE} DEPENDS ${TEMPLATE_DIR}/wasm2c.top.h ${TEMPLATE_DIR}/wasm2c.bottom.h @@ -36,6 +38,7 @@ add_custom_command( ${TEMPLATE_DIR}/wasm2c.declarations.c ${TEMPLATE_DIR}/wasm2c_simd.declarations.c ${TEMPLATE_DIR}/wasm2c_atomicops.declarations.c + ${TEMPLATE_DIR}/wasm2c_spec.top.c ) set(WASM2C_GENERATED_SOURCES @@ -45,6 +48,7 @@ set(WASM2C_GENERATED_SOURCES ${PREBUILT_DIR}/wasm2c_source_declarations.cc ${PREBUILT_DIR}/wasm2c_simd_source_declarations.cc ${PREBUILT_DIR}/wasm2c_atomicops_source_declarations.cc + ${PREBUILT_DIR}/wasm2c_spec_top.cc ) binaryen_add_executable(wasm2c "wasm2c.cpp;${WASM2C_GENERATED_SOURCES}") diff --git a/src/tools/wasm2c.cpp b/src/tools/wasm2c.cpp new file mode 100644 index 00000000000..aee05ae79f7 --- /dev/null +++ b/src/tools/wasm2c.cpp @@ -0,0 +1,203 @@ +/* + * Copyright 2026 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. + */ + +// +// wasm2c console tool +// + +#include "wasm2c.h" +#include "optimization-options.h" +#include "parser/wat-parser.h" +#include "pass.h" +#include "support/colors.h" +#include "support/command-line.h" +#include "support/file.h" + +using namespace wasm; +using namespace wasm::WATParser; + +int main(int argc, const char* argv[]) { + Wasm2CBuilder::Flags flags; + + const std::string Wasm2COption = "wasm2c options"; + + OptimizationOptions options( + "wasm2c", "Transform .wasm/.wat files to standard C source and headers"); + options + .add("--output", + "-o", + "Output file path (derives .h by changing extension, writes both to " + "stdout if not specified)", + Wasm2COption, + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["output"] = argument; + Colors::setEnabled(false); + }) + .add("--allow-asserts", + "", + "Allow compilation of .wast testing asserts", + Wasm2COption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + o->extra["asserts"] = "1"; + }) + .add("--prefix", + "-n", + "Set the name prefix for the generated C symbols", + Wasm2COption, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + flags.moduleName = argument; + }) + .add_positional("INFILE", + Options::Arguments::One, + [](Options* o, const std::string& argument) { + o->extra["infile"] = argument; + }); + + options.parse(argc, argv); + if (options.debug) { + flags.debug = true; + } + + std::optional script; + std::shared_ptr wasm; + + + auto& input = options.extra["infile"]; + std::string suffix(".wasm"); + bool binaryInput = + input.size() >= suffix.size() && + input.compare(input.size() - suffix.size(), suffix.size(), suffix) == 0; + + try { + // If the input filename ends in `.wasm`, then parse it in binary form, + // otherwise assume it's a `*.wat` file and go from there. + // + // Note that we're not using the built-in `ModuleReader` which will also do + // similar logic here because when testing C files we use the + // `--allow-asserts` flag which means we need to parse the extra + // s-expressions that come at the end of the `*.wast` file after the module + // is defined. + if (binaryInput) { + wasm = std::make_shared(); + options.applyOptionsBeforeParse(*wasm); + ModuleReader reader; + reader.read(input, *wasm, ""); + } else { + auto input(read_file(options.extra["infile"], Flags::Text)); + + auto parsed = parseScript(input); + if (auto* err = parsed.getErr()) { + Fatal() << err->msg; + } + script = std::move(*parsed); + + // Find the first module in the script. + if (script->empty()) { + Fatal() << "expected module"; + } + if (auto* mod = std::get_if(&(*script)[0].cmd)) { + if (mod->isDefinition) { + Fatal() << "module definition is not supported"; + } + if (auto* w = std::get_if>(&mod->module)) { + wasm = *w; + // This isn't actually before the parse, but we can't apply the + // feature options any earlier. FIXME. + options.applyOptionsBeforeParse(*wasm); + } + } + if (!wasm) { + Fatal() << "expected module as first command in script"; + } + } + } catch (ParseException& p) { + p.dump(std::cerr); + Fatal() << "error in parsing input"; + } catch (std::bad_alloc&) { + Fatal() << "error in building module, std::bad_alloc (possibly invalid " + "request for silly amounts of memory)"; + } + + options.applyOptionsAfterParse(*wasm); + if (options.passOptions.validate) { + if (!WasmValidator().validate(*wasm)) { + std::cout << *wasm << '\n'; + Fatal() << "error in validating input"; + } + } + + if (options.debug) { + std::cerr << "j-printing..." << std::endl; + } + + std::string output_c_path = options.extra["output"]; + if (!output_c_path.empty()) { + // Derive output header file path (.h instead of .c) + std::string output_h_path = output_c_path; + size_t last_dot = output_h_path.find_last_of('.'); + if (last_dot != std::string::npos) { + output_h_path.replace(last_dot, std::string::npos, ".h"); + } else { + output_h_path += ".h"; + } + + // Extract header basename for custom `#include "[basename]"` inside .c file + size_t last_slash = output_h_path.find_last_of("/\\"); + std::string header_basename = (last_slash == std::string::npos) + ? output_h_path + : output_h_path.substr(last_slash + 1); + flags.headerName = header_basename; + + Output c_out(output_c_path, Flags::Text); + Output h_out(output_h_path, Flags::Text); + + if (script && options.extra["asserts"] == "1") { + AssertionEmitter emitter(*script, flags, options.passOptions); + emitter.emit(c_out.getStream(), h_out.getStream()); + } else { + optimizeWasm(*wasm, options.passOptions); + Wasm2CBuilder builder(flags); + builder.processWasm(wasm.get(), c_out.getStream(), h_out.getStream()); + } + } else { + // Write both to stdout sequentially + std::cout << "/* === HEADER FILE === */\n"; + std::stringstream h_stream; + std::stringstream c_stream; + + flags.headerName = "wasm.h"; // Default fallback + if (script && options.extra["asserts"] == "1") { + AssertionEmitter emitter(*script, flags, options.passOptions); + emitter.emit(c_stream, h_stream); + } else { + optimizeWasm(*wasm, options.passOptions); + Wasm2CBuilder builder(flags); + builder.processWasm(wasm.get(), c_stream, h_stream); + } + std::cout << h_stream.str(); + std::cout << "\n/* === SOURCE FILE === */\n"; + std::cout << c_stream.str(); + } + + if (options.debug) { + std::cerr << "done." << std::endl; + } + + flush_and_quick_exit(0); +} diff --git a/src/wasm2c.h b/src/wasm2c.h new file mode 100644 index 00000000000..52cb6323eb6 --- /dev/null +++ b/src/wasm2c.h @@ -0,0 +1,614 @@ +/* + * Copyright 2026 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. + */ + +// +// WebAssembly-to-C code translator. Converts wasm functions into +// valid standards-compliant C. +// + +#ifndef wasm_wasm2c_h +#define wasm_wasm2c_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ir/module-utils.h" +#include "parser/wat-parser.h" +#include "parsing.h" +#include "pass.h" +#include "picosha2.h" +#include "wasm-io.h" +#include "wasm-validator.h" +#include "wasm.h" + +// code to be inserted into the generated output +extern const char* s_header_top; +extern const char* s_header_bottom; +extern const char* s_source_includes; +extern const char* s_source_declarations; +extern const char* s_spec_top; + +namespace wasm { + +class CPrinter; +typedef CPrinter& (*CPrinterManipulator)(CPrinter&); + +class CPrinter { +public: + std::ostream& out; + int indent_level = 0; + bool start_of_line = true; + + CPrinter(std::ostream& out) : out(out) {} + + void indent() { indent_level++; } + void outdent() { indent_level--; } + + CPrinter& operator<<(CPrinter& (*pf)(CPrinter&)) { return pf(*this); } + + template CPrinter& operator<<(const T& val) { + if (start_of_line) { + for (int i = 0; i < indent_level * 2; i++) { + out << ' '; + } + start_of_line = false; + } + out << val; + return *this; + } +}; + +inline CPrinter& endl(CPrinter& p) { + p.out << '\n'; + p.start_of_line = true; + return p; +} + +class Wasm2CBuilder { +public: + struct Flags { + bool debug = false; + std::string moduleName = ""; + std::string headerName = ""; + }; + + Wasm2CBuilder(Flags f) : flags(f) {} + + static std::string mangleName(const std::string& name) { + if (name.empty()) + return ""; + std::string result; + bool is_first = true; + for (char c : name) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9')) { + result += c; + is_first = false; + } else if (c == '_') { + if (is_first) { + result += "0x5F"; + is_first = false; + } else { + result += '_'; + } + } else { + char buf[8]; + snprintf(buf, sizeof(buf), "0x%02X", (unsigned char)c); + result += buf; + is_first = false; + } + } + return result; + } + + std::string getCType(Type type) { + if (type == Type::i32) + return "u32"; + if (type == Type::i64) + return "u64"; + if (type == Type::f32) + return "f32"; + if (type == Type::f64) + return "f64"; + if (type == Type::none) + return "void"; + Fatal() << "Unsupported type: " << type; + return ""; + } + + std::vector getBasicTypes(Type type) { + std::vector result; + if (type.isTuple()) { + for (auto t : type) { + result.push_back(t); + } + } else if (type != Type::none) { + result.push_back(type); + } + return result; + } + + void processWasm(Module* wasm, std::ostream& c_out, std::ostream& h_out) { + this->module = wasm; + std::string module_name = + flags.moduleName.empty() ? "test" : mangleName(flags.moduleName); + std::string guard_name = "WASM_H_GENERATED_" + module_name; + std::transform( + guard_name.begin(), guard_name.end(), guard_name.begin(), ::toupper); + + CPrinter h(h_out); + CPrinter c(c_out); + + // Header File + h << "/* Automatically generated by wasm2c */" << endl; + h << "#ifndef " << guard_name << endl; + h << "#define " << guard_name << endl << endl; + h << "#include \"wasm-rt.h\"" << endl << endl; + h << s_header_top << endl; + + // Structure context definition + h << "typedef struct w2c_" << module_name << " {" << endl; + h.indent(); + h << "char dummy_member;" << endl; + h.outdent(); + h << "} w2c_" << module_name << ";" << endl << endl; + // + // Lifecycle signatures in header + h << "void wasm2c_" << module_name << "_instantiate(w2c_" << module_name + << "*"; + h << ");" << endl; + + h << "void wasm2c_" << module_name << "_free(w2c_" << module_name << "*);" + << endl; + h << "wasm_rt_func_type_t wasm2c_" << module_name + << "_get_func_type(uint32_t param_count, uint32_t result_count, ...);" + << endl + << endl; + + h << s_header_bottom << endl; + h << "#endif /* " << guard_name << " */" << endl; + + // Source File + c << "/* Automatically generated by wasm2c */" << endl; + c << s_source_includes << endl; + + if (!flags.headerName.empty()) { + c << "#include \"" << flags.headerName << "\"" << endl; + } else { + c << "#include \"wasm.h\"" << endl; + } + + c << s_source_declarations << endl; + + // Instantiate hooks + c << "void wasm2c_" << module_name << "_instantiate(w2c_" << module_name + << "* instance"; + c << ") {" << endl; + c.indent(); + c << "assert(wasm_rt_is_initialized());" << endl; + c.outdent(); + c << "}" << endl << endl; + + // Free hooks + c << "void wasm2c_" << module_name << "_free(w2c_" << module_name + << "* instance) {" << endl; + c << "}" << endl << endl; + + // Signature match ladders + c << "wasm_rt_func_type_t wasm2c_" << module_name + << "_get_func_type(uint32_t param_count, uint32_t result_count, ...) {" + << endl; + c.indent(); + c << "va_list args;" << endl << endl; + + c << "return NULL;" << endl; + c.outdent(); + c << "}" << endl << endl; + } + +private: + Flags flags; + Module* module = nullptr; + + void processFunction(Function* func, CPrinter& c) { + processStatement(func->body, func, c); + } + + void processStatement(Expression* expr, Function* func, CPrinter& c) { + if (!expr) + return; + + if (expr->is()) { + return; + } + + Fatal() << "Unsupported statement type"; + std::string val = processExpression(expr, func, c); + c << val << ";" << endl; + } + + std::string processExpression(Expression* curr, Function* func, CPrinter& c) { + if (!curr) + return ""; + + if (curr->is()) { + return "TRAP(UNREACHABLE)"; + } + + Fatal() << "Unsupported expression node type."; + return ""; + } +}; + +static void optimizeWasm(Module& wasm, PassOptions options) { + // Run flattening and simplification passes before codegen + PassRunner runner(&wasm, options); + runner.add("flatten"); + runner.add("simplify-locals-notee-nostructure"); + runner.add("reorder-locals"); + runner.add("vacuum"); + runner.run(); +} + +class AssertionEmitter { +public: + AssertionEmitter(WATParser::WASTScript& script, + Wasm2CBuilder::Flags flags, + const PassOptions& options) + : script(script), flags(flags), options(options) {} + + void emit(std::ostream& c_out, std::ostream& h_out) { + CPrinter h(h_out); + CPrinter c(c_out); + + // Print static prefix templates to C source stream + if (!flags.headerName.empty()) { + c << "#include \"" << flags.headerName << "\"" << endl; + } else { + c << "#include \"wasm.h\"" << endl; + } + c << s_spec_top; + + std::stringstream h_stream; + std::stringstream c_stream; + std::vector checks; + std::vector prefixes; + + // Loop sequentially through WASTScript AST commands + for (size_t i = 0; i < script.size(); i++) { + auto& entry = script[i]; + auto& cmd = entry.cmd; + + if (auto* mod = std::get_if(&cmd)) { + if (mod->isDefinition) { + Fatal() << "Module definition is not supported"; + } + auto* w = std::get_if>(&mod->module); + if (!w) { + Fatal() << "Expected parsed Module pointer inside WASTModule"; + } + auto wasm = *w; + std::string prefix = "spec_" + std::to_string(module_counter++); + + name_to_prefix[prefix] = prefix; + if (wasm->name.is()) { + name_to_prefix[Wasm2CBuilder::mangleName(wasm->name.toString())] = + prefix; + } + + Wasm2CBuilder::Flags mod_flags = flags; + mod_flags.moduleName = prefix; + + // Run standard validation and flattening simplification passes + optimizeWasm(*wasm.get(), options); + Wasm2CBuilder builder(mod_flags); + builder.processWasm(wasm.get(), c_stream, h_stream); + + // Cache exported function return types and parameter types + for (auto& exp : wasm->exports) { + if (exp->kind == ExternalKind::Function) { + Function* func = wasm->getFunction(*exp->getInternalName()); + std::string c_func_name = + "w2c_" + prefix + "_" + + Wasm2CBuilder::mangleName(exp->name.toString()); + func_returns[c_func_name] = builder.getCType(func->getResults()); + func_params[c_func_name] = builder.getBasicTypes(func->getParams()); + } + } + + last_module_prefix = prefix; + prefixes.push_back(prefix); + wasm_modules_cache[prefix] = wasm; + + } else if (auto* reg = std::get_if(&cmd)) { + std::string mangled_instance_name = + reg->instanceName.has_value() + ? Wasm2CBuilder::mangleName(reg->instanceName->toString()) + : ""; + + std::string target_prefix; + if (reg->instanceName.has_value()) { + if (!name_to_prefix.count(mangled_instance_name)) { + Fatal() << "Unregistered instance name mapping for alias register: " + << mangled_instance_name; + } + target_prefix = name_to_prefix[mangled_instance_name]; + } else { + target_prefix = last_module_prefix; + } + + std::string alias = Wasm2CBuilder::mangleName(reg->name.toString()); + name_to_prefix[alias] = target_prefix; + + // Output preprocessor define redirects + h_stream << "#define w2c_" << alias << " w2c_" << target_prefix << "\n"; + h_stream << "#define wasm2c_" << alias << "_instantiate wasm2c_" + << target_prefix << "_instantiate\n"; + h_stream << "#define wasm2c_" << alias << "_free wasm2c_" + << target_prefix << "_free\n"; + + // Redirect all exported symbols from the alias to the target provider + // prefix + if (wasm_modules_cache.count(target_prefix)) { + auto wasm = wasm_modules_cache[target_prefix]; + for (auto& exp : wasm->exports) { + std::string exp_name = + Wasm2CBuilder::mangleName(exp->name.toString()); + h_stream << "#define w2c_" << alias << "_" << exp_name << " w2c_" + << target_prefix << "_" << exp_name << "\n"; + } + } + h_stream << "\n"; + + } else if (auto* assn = std::get_if(&cmd)) { + if (auto* ret = std::get_if(assn)) { + if (auto* inv = std::get_if(&ret->action)) { + std::string expr_call = translateInvoke(*inv); + std::string field_name = + "w2c_" + getInvokePrefix(*inv) + "_" + + Wasm2CBuilder::mangleName(inv->name.toString()); + + std::string assert_macro; + auto& exp = ret->expected[0][0]; // Get first alternative + if (auto* lit = std::get_if(&exp)) { + std::string ret_type = func_returns[field_name]; + switch (ret_type) { + case "i32": + assert_macro = "ASSERT_RETURN_I32"; + break; + case "i64": + assert_macro = "ASSERT_RETURN_I64"; + break; + case "f32": + assert_macro = "ASSERT_RETURN_F32"; + break; + case "f64": + assert_macro = "ASSERT_RETURN_F64"; + break; + default: + Fatal() << "Unsupported return type: " << ret_type; + break; + } + checks.push_back(assert_macro + "(" + expr_call + ", " + + translateLiteral(*lit) + ");"); + } + } else if (auto* nan_val = std::get_if(&exp)) { + if (nan_val->type == Type::f32) { + switch (nan_val->kind) { + case WATParser::NaNKind::Canonical: + assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F32"; + break; + case WATParser::NaNKind::Arithmetic: + assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F32"; + break; + default: + Fatal() << "Unsupported NaN kind: " << nan_val->kind; + } + } else if (nan_val->type == Type::f64) { + switch (nan_val->kind) { + case WATParser::NaNKind::Canonical: + assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F64"; + break; + case WATParser::NaNKind::Arithmetic: + assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F64"; + break; + default: + Fatal() << "Unsupported NaN kind: " << nan_val->kind; + } + } + checks.push_back(assert_macro + "(" + expr_call + ");"); + } + } else if (auto* act = std::get_if(assn)) { + if (act->type == WATParser::ActionAssertionType::Trap) { + if (auto* inv = + std::get_if(&act->action)) { + std::string expr_call = translateInvoke(*inv); + checks.push_back("ASSERT_TRAP(" + expr_call + ");"); + } + } + } + } + } + + // Write C headers and definitions + h << endl + << "/* === MODULE HEADERS === */" << endl + << h_stream.str() << endl; + c << endl + << "/* === MODULE SOURCES === */" << endl + << c_stream.str() << endl; + + // Write main execution entry point + c << "void run_spec_tests() {" << endl; + c.indent(); + + // Declare all module instances + for (auto& prefix : prefixes) { + c << "w2c_" << prefix << " inst_" << prefix << ";" << endl; + } + c << endl; + + // Instantiate all module instances matching their imports metadata + for (auto& prefix : prefixes) { + c << "wasm2c_" << prefix << "_instantiate(&inst_" << prefix; + + // Gather all unique imported modules namespaces + std::set imported_namespaces; + for (auto& mem : wasm_modules_cache[prefix]->memories) { + if (mem->imported()) + imported_namespaces.insert( + Wasm2CBuilder::mangleName(mem->module.toString())); + } + for (auto& table : wasm_modules_cache[prefix]->tables) { + if (table->imported()) + imported_namespaces.insert( + Wasm2CBuilder::mangleName(table->module.toString())); + } + for (auto& global : wasm_modules_cache[prefix]->globals) { + if (global->imported()) + imported_namespaces.insert( + Wasm2CBuilder::mangleName(global->module.toString())); + } + ModuleUtils::iterImportedFunctions( + *wasm_modules_cache[prefix], [&](Function* func) { + imported_namespaces.insert( + Wasm2CBuilder::mangleName(func->module.toString())); + }); + + for (auto& ns : imported_namespaces) { + if (ns == "spectest") { + c << ", w2c_spectest_instance"; + } else { + std::string prov_prefix = name_to_prefix[ns]; + c << ", (struct w2c_" << ns << "*)&inst_" << prov_prefix; + } + } + c << ");" << endl; + } + c << endl; + + // Run all checks + for (auto& chk : checks) { + c << chk << endl; + } + c << endl; + + // Free contexts in REVERSE order of instantiation + for (auto it = prefixes.rbegin(); it != prefixes.rend(); ++it) { + c << "wasm2c_" << *it << "_free(&inst_" << *it << ");" << endl; + } + + c.outdent(); + c << "}" << endl; + } + +private: + WATParser::WASTScript& script; + Wasm2CBuilder::Flags flags; + PassOptions options; + + std::map name_to_prefix; + std::map> func_params; + std::map func_returns; + std::map> wasm_modules_cache; + std::string last_module_prefix = ""; + size_t module_counter = 0; + + std::string getInvokePrefix(const WATParser::InvokeAction& invoke) { + if (invoke.base.has_value()) { + std::string base_mangled = + Wasm2CBuilder::mangleName(invoke.base->toString()); + if (name_to_prefix.count(base_mangled)) { + return name_to_prefix[base_mangled]; + } + } + return last_module_prefix; + } + + std::string translateInvoke(const WATParser::InvokeAction& invoke) { + std::string prefix = getInvokePrefix(invoke); + std::string mangled_name = + Wasm2CBuilder::mangleName(invoke.name.toString()); + std::string field_name = "w2c_" + prefix + "_" + mangled_name; + + std::string expr = field_name + "(&inst_" + prefix; + for (auto& arg : invoke.args) { + expr += ", " + translateLiteral(arg); + } + expr += ")"; + return expr; + } + + std::string translateLiteral(const Literal& lit) { + std::stringstream os; + switch (lit.type.getBasic()) { + case Type::i32: + os << lit.geti32() << "u"; + break; + case Type::i64: + os << lit.geti64() << "ULL"; + break; + case Type::f32: + os << "float_from_u32(0x" << std::hex << lit.reinterpreti32() << "u)" + << std::dec; + break; + case Type::f64: + os << "double_from_u64(0x" << std::hex << lit.reinterpreti64() << "ULL)" + << std::dec; + break; + default: + Fatal() << "Unsupported literal type: " << lit.type; + } + return os.str(); + } + + std::string getTrapEnum(const std::string& msg) { + if (msg.find("out of bounds") != std::string::npos || + msg.find("bounds") != std::string::npos) + return "WASM_RT_TRAP_OOB"; + if (msg.find("overflow") != std::string::npos) + return "WASM_RT_TRAP_INT_OVERFLOW"; + if (msg.find("divide") != std::string::npos || + msg.find("zero") != std::string::npos) + return "WASM_RT_TRAP_DIV_BY_ZERO"; + if (msg.find("invalid conversion") != std::string::npos) + return "WASM_RT_TRAP_INVALID_CONVERSION"; + if (msg.find("unreachable") != std::string::npos) + return "WASM_RT_TRAP_UNREACHABLE"; + if (msg.find("indirect call") != std::string::npos || + msg.find("undefined") != std::string::npos || + msg.find("uninitialized") != std::string::npos || + msg.find("signature mismatch") != std::string::npos) { + return "WASM_RT_TRAP_CALL_INDIRECT"; + } + if (msg.find("exhaustion") != std::string::npos || + msg.find("exhausted") != std::string::npos) + return "WASM_RT_TRAP_EXHAUSTION"; + return "WASM_RT_TRAP_NONE"; + } +}; + +} // namespace wasm + +#endif // wasm_wasm2c_h diff --git a/test/lit/wasm2c/minimal.wast b/test/lit/wasm2c/minimal.wast new file mode 100644 index 00000000000..862cfe714af --- /dev/null +++ b/test/lit/wasm2c/minimal.wast @@ -0,0 +1,802 @@ +;; RUN: wasm2c %s -o %t.c +;; RUN: cat %t.h | filecheck %s --match-full-lines --strict-whitespace --check-prefixes CHECK-HEADER +;; RUN: cat %t.c | filecheck %s --match-full-lines --strict-whitespace -DHEADER_FILE=%basename_t.tmp.h --check-prefixes CHECK-SOURCE +(module) + +;; CHECK-HEADER-NEXT: /* Automatically generated by wasm2c */ +;; CHECK-HEADER-NEXT: #ifndef WASM_H_GENERATED_TEST +;; CHECK-HEADER-NEXT: #define WASM_H_GENERATED_TEST +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #include "wasm-rt.h" +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #include +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #ifndef WASM_RT_CORE_TYPES_DEFINED +;; CHECK-HEADER-NEXT: #define WASM_RT_CORE_TYPES_DEFINED +;; CHECK-HEADER-NEXT: typedef uint8_t u8; +;; CHECK-HEADER-NEXT: typedef int8_t s8; +;; CHECK-HEADER-NEXT: typedef uint16_t u16; +;; CHECK-HEADER-NEXT: typedef int16_t s16; +;; CHECK-HEADER-NEXT: typedef uint32_t u32; +;; CHECK-HEADER-NEXT: typedef int32_t s32; +;; CHECK-HEADER-NEXT: typedef uint64_t u64; +;; CHECK-HEADER-NEXT: typedef int64_t s64; +;; CHECK-HEADER-NEXT: typedef float f32; +;; CHECK-HEADER-NEXT: typedef double f64; +;; CHECK-HEADER-NEXT: #endif +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #ifdef __cplusplus +;; CHECK-HEADER-NEXT: extern "C" { +;; CHECK-HEADER-NEXT: #endif +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: typedef struct w2c_test { +;; CHECK-HEADER-NEXT: char dummy_member; +;; CHECK-HEADER-NEXT: } w2c_test; +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: void wasm2c_test_instantiate(w2c_test*); +;; CHECK-HEADER-NEXT: void wasm2c_test_free(w2c_test*); +;; CHECK-HEADER-NEXT: wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...); +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #ifdef __cplusplus +;; CHECK-HEADER-NEXT: } +;; CHECK-HEADER-NEXT: #endif +;; CHECK-HEADER-EMPTY: +;; CHECK-HEADER-NEXT: #endif /* WASM_H_GENERATED_TEST */ + +;; CHECK-SOURCE-NEXT: /* Automatically generated by wasm2c */ +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #if defined(__MINGW32__) +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #elif defined(_MSC_VER) +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #define alloca _alloca +;; CHECK-SOURCE-NEXT: #elif defined(__FreeBSD__) || defined(__OpenBSD__) +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #include "[[HEADER_FILE]]" +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // Computes a pointer to an object of the given size in a little-endian memory. +;; CHECK-SOURCE-NEXT: // +;; CHECK-SOURCE-NEXT: // On a little-endian host, this is just &mem->data[addr] - the object's size is +;; CHECK-SOURCE-NEXT: // unused. On a big-endian host, it's &mem->data[mem->size - addr - n], where n +;; CHECK-SOURCE-NEXT: // is the object's size. +;; CHECK-SOURCE-NEXT: // +;; CHECK-SOURCE-NEXT: // Note that mem may be evaluated multiple times. +;; CHECK-SOURCE-NEXT: // +;; CHECK-SOURCE-NEXT: // Parameters: +;; CHECK-SOURCE-NEXT: // mem - The memory. +;; CHECK-SOURCE-NEXT: // addr - The address. +;; CHECK-SOURCE-NEXT: // n - The size of the object. +;; CHECK-SOURCE-NEXT: // +;; CHECK-SOURCE-NEXT: // Result: +;; CHECK-SOURCE-NEXT: // A pointer for an object of size n. +;; CHECK-SOURCE-NEXT: #if WABT_BIG_ENDIAN +;; CHECK-SOURCE-NEXT: #define MEM_ADDR(mem, addr, n) ((mem)->data_end - (addr) - (n)) +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define MEM_ADDR(mem, addr, n) &((mem)->data[addr]) +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // We can only use Segue for this module if it uses a single unshared imported +;; CHECK-SOURCE-NEXT: // or exported memory +;; CHECK-SOURCE-NEXT: #if WASM_RT_USE_SEGUE && IS_SINGLE_UNSHARED_MEMORY +;; CHECK-SOURCE-NEXT: #define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 1 +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define WASM_RT_USE_SEGUE_FOR_THIS_MODULE 0 +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if WASM_RT_USE_SEGUE_FOR_THIS_MODULE +;; CHECK-SOURCE-NEXT: // POSIX uses FS for TLS, GS is free +;; CHECK-SOURCE-NEXT: static inline void* wasm_rt_segue_read_base() { +;; CHECK-SOURCE-NEXT: if (wasm_rt_fsgsbase_inst_supported) { +;; CHECK-SOURCE-NEXT: return (void*)__builtin_ia32_rdgsbase64(); +;; CHECK-SOURCE-NEXT: } else { +;; CHECK-SOURCE-NEXT: return wasm_rt_syscall_get_segue_base(); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: static inline void wasm_rt_segue_write_base(void* base) { +;; CHECK-SOURCE-NEXT: #if WASM_RT_SEGUE_FREE_SEGMENT +;; CHECK-SOURCE-NEXT: if (wasm_rt_last_segment_val == base) { +;; CHECK-SOURCE-NEXT: return; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: wasm_rt_last_segment_val = base; +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: if (wasm_rt_fsgsbase_inst_supported) { +;; CHECK-SOURCE-NEXT: __builtin_ia32_wrgsbase64((uintptr_t)base); +;; CHECK-SOURCE-NEXT: } else { +;; CHECK-SOURCE-NEXT: wasm_rt_syscall_set_segue_base(base); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #define MEM_ADDR_MEMOP(mem, addr, n) ((uint8_t __seg_gs*)(uintptr_t)addr) +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define MEM_ADDR_MEMOP(mem, addr, n) MEM_ADDR(mem, addr, n) +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if WASM_RT_STACK_DEPTH_COUNT +;; CHECK-SOURCE-NEXT: #define FUNC_PROLOGUE \ +;; CHECK-SOURCE-NEXT: if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \ +;; CHECK-SOURCE-NEXT: TRAP(EXHAUSTION); +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define FUNC_EPILOGUE --wasm_rt_call_stack_depth +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define FUNC_PROLOGUE +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define FUNC_EPILOGUE +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define UNREACHABLE TRAP(UNREACHABLE) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline bool func_types_eq(const wasm_rt_func_type_t a, +;; CHECK-SOURCE-NEXT: const wasm_rt_func_type_t b) { +;; CHECK-SOURCE-NEXT: return (a == b) || LIKELY(a && b && !memcmp(a, b, 32)); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define CHECK_CALL_INDIRECT(table, ft, x) \ +;; CHECK-SOURCE-NEXT: (LIKELY((x) < table.size && table.data[x].func && \ +;; CHECK-SOURCE-NEXT: func_types_eq(ft, table.data[x].func_type)) || \ +;; CHECK-SOURCE-NEXT: TRAP(CALL_INDIRECT)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DO_CALL_INDIRECT(table, t, x, ...) ((t)table.data[x].func)(__VA_ARGS__) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define CALL_INDIRECT(table, t, ft, x, ...) \ +;; CHECK-SOURCE-NEXT: (CHECK_CALL_INDIRECT(table, ft, x), \ +;; CHECK-SOURCE-NEXT: DO_CALL_INDIRECT(table, t, x, __VA_ARGS__)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline bool add_overflow(uint64_t a, uint64_t b, uint64_t* resptr) { +;; CHECK-SOURCE-NEXT: #if __has_builtin(__builtin_add_overflow) +;; CHECK-SOURCE-NEXT: return __builtin_add_overflow(a, b, resptr); +;; CHECK-SOURCE-NEXT: #elif defined(_MSC_VER) +;; CHECK-SOURCE-NEXT: return _addcarry_u64(0, a, b, resptr); +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #error "Missing implementation of __builtin_add_overflow or _addcarry_u64" +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define RANGE_CHECK(mem, offset, len) \ +;; CHECK-SOURCE-NEXT: do { \ +;; CHECK-SOURCE-NEXT: uint64_t res; \ +;; CHECK-SOURCE-NEXT: if (UNLIKELY(add_overflow(offset, len, &res))) \ +;; CHECK-SOURCE-NEXT: TRAP(OOB); \ +;; CHECK-SOURCE-NEXT: if (UNLIKELY(res > (mem)->size)) \ +;; CHECK-SOURCE-NEXT: TRAP(OOB); \ +;; CHECK-SOURCE-NEXT: } while (0); +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if WASM_RT_USE_SEGUE_FOR_THIS_MODULE && WASM_RT_SANITY_CHECKS +;; CHECK-SOURCE-NEXT: #include +;; CHECK-SOURCE-NEXT: #define WASM_RT_CHECK_BASE(mem) \ +;; CHECK-SOURCE-NEXT: if (((uintptr_t)((mem)->data)) != ((uintptr_t)wasm_rt_segue_read_base())) { \ +;; CHECK-SOURCE-NEXT: puts("Segment register mismatch\n"); \ +;; CHECK-SOURCE-NEXT: abort(); \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define WASM_RT_CHECK_BASE(mem) +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // MEMCHECK_DEFAULT32 is an "accelerated" MEMCHECK used only for +;; CHECK-SOURCE-NEXT: // default-page-size, 32-bit memories. It may do nothing at all +;; CHECK-SOURCE-NEXT: // (if hardware bounds-checking is enabled via guard pages) +;; CHECK-SOURCE-NEXT: // or it may do a slightly faster RANGE_CHECK. +;; CHECK-SOURCE-NEXT: #if WASM_RT_MEMCHECK_GUARD_PAGES +;; CHECK-SOURCE-NEXT: #define MEMCHECK_DEFAULT32(mem, a, t) WASM_RT_CHECK_BASE(mem); +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define MEMCHECK_DEFAULT32(mem, a, t) \ +;; CHECK-SOURCE-NEXT: WASM_RT_CHECK_BASE(mem); \ +;; CHECK-SOURCE-NEXT: if (UNLIKELY(a + (uint64_t)sizeof(t) > mem->size)) \ +;; CHECK-SOURCE-NEXT: TRAP(OOB); +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // MEMCHECK_GENERAL can be used for any memory +;; CHECK-SOURCE-NEXT: #define MEMCHECK_GENERAL(mem, a, t) \ +;; CHECK-SOURCE-NEXT: WASM_RT_CHECK_BASE(mem); \ +;; CHECK-SOURCE-NEXT: RANGE_CHECK(mem, a, sizeof(t)); +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #ifdef __GNUC__ +;; CHECK-SOURCE-NEXT: #define FORCE_READ_INT(var) __asm__("" ::"r"(var)); +;; CHECK-SOURCE-NEXT: // Clang on Mips requires "f" constraints on floats +;; CHECK-SOURCE-NEXT: // See https://github.com/llvm/llvm-project/issues/64241 +;; CHECK-SOURCE-NEXT: #if defined(__clang__) && \ +;; CHECK-SOURCE-NEXT: (defined(mips) || defined(__mips__) || defined(__mips)) +;; CHECK-SOURCE-NEXT: #define FORCE_READ_FLOAT(var) __asm__("" ::"f"(var)); +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define FORCE_READ_FLOAT(var) __asm__("" ::"r"(var)); +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define FORCE_READ_INT(var) +;; CHECK-SOURCE-NEXT: #define FORCE_READ_FLOAT(var) +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline void load_data(u8* dest, const u8* src, size_t n) { +;; CHECK-SOURCE-NEXT: if (!n) { +;; CHECK-SOURCE-NEXT: return; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #if WABT_BIG_ENDIAN +;; CHECK-SOURCE-NEXT: for (size_t i = 0; i < n; i++) { +;; CHECK-SOURCE-NEXT: dest[i] = src[n - i - 1]; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(dest, src, n); +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define LOAD_DATA(m, o, i, s) \ +;; CHECK-SOURCE-NEXT: do { \ +;; CHECK-SOURCE-NEXT: RANGE_CHECK((&m), o, s); \ +;; CHECK-SOURCE-NEXT: load_data(MEM_ADDR(&m, o, s), i, s); \ +;; CHECK-SOURCE-NEXT: } while (0) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEF_MEM_CHECKS0(name, shared, mem_type, ret_kw, return_type) \ +;; CHECK-SOURCE-NEXT: static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +;; CHECK-SOURCE-NEXT: u64 addr) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr); \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_GENERAL(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr); \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEF_MEM_CHECKS1(name, shared, mem_type, ret_kw, return_type, \ +;; CHECK-SOURCE-NEXT: val_type1) \ +;; CHECK-SOURCE-NEXT: static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +;; CHECK-SOURCE-NEXT: u64 addr, val_type1 val1) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr, val1); \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ +;; CHECK-SOURCE-NEXT: val_type1 val1) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_GENERAL(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr, val1); \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEF_MEM_CHECKS2(name, shared, mem_type, ret_kw, return_type, \ +;; CHECK-SOURCE-NEXT: val_type1, val_type2) \ +;; CHECK-SOURCE-NEXT: static inline return_type name##_default32(wasm_rt##shared##memory_t* mem, \ +;; CHECK-SOURCE-NEXT: u64 addr, val_type1 val1, \ +;; CHECK-SOURCE-NEXT: val_type2 val2) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_DEFAULT32(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr, val1, val2); \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: static inline return_type name(wasm_rt##shared##memory_t* mem, u64 addr, \ +;; CHECK-SOURCE-NEXT: val_type1 val1, val_type2 val2) { \ +;; CHECK-SOURCE-NEXT: MEMCHECK_GENERAL(mem, addr, mem_type); \ +;; CHECK-SOURCE-NEXT: ret_kw name##_unchecked(mem, addr, val1, val2); \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_LOAD(name, t1, t2, t3, force_read) \ +;; CHECK-SOURCE-NEXT: static inline t3 name##_unchecked(wasm_rt_memory_t* mem, u64 addr) { \ +;; CHECK-SOURCE-NEXT: t1 result; \ +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&result, MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), \ +;; CHECK-SOURCE-NEXT: sizeof(t1)); \ +;; CHECK-SOURCE-NEXT: force_read(result); \ +;; CHECK-SOURCE-NEXT: return (t3)(t2)result; \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: DEF_MEM_CHECKS0(name, _, t1, return, t3) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_STORE(name, t1, t2) \ +;; CHECK-SOURCE-NEXT: static inline void name##_unchecked(wasm_rt_memory_t* mem, u64 addr, \ +;; CHECK-SOURCE-NEXT: t2 value) { \ +;; CHECK-SOURCE-NEXT: t1 wrapped = (t1)value; \ +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(MEM_ADDR_MEMOP(mem, addr, sizeof(t1)), &wrapped, \ +;; CHECK-SOURCE-NEXT: sizeof(t1)); \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: DEF_MEM_CHECKS1(name, _, t1, , void, t2) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i32_load, u32, u32, u32, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load, u64, u64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(f32_load, f32, f32, f32, FORCE_READ_FLOAT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(f64_load, f64, f64, f64, FORCE_READ_FLOAT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i32_load8_s, s8, s32, u32, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load8_s, s8, s64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i32_load8_u, u8, u32, u32, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load8_u, u8, u64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i32_load16_s, s16, s32, u32, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load16_s, s16, s64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i32_load16_u, u16, u32, u32, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load16_u, u16, u64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load32_s, s32, s64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_LOAD(i64_load32_u, u32, u64, u64, FORCE_READ_INT) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i32_store, u32, u32) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i64_store, u64, u64) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(f32_store, f32, f32) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(f64_store, f64, f64) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i32_store8, u8, u32) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i32_store16, u16, u32) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i64_store8, u8, u64) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i64_store16, u16, u64) +;; CHECK-SOURCE-NEXT: DEFINE_STORE(i64_store32, u32, u64) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if defined(_MSC_VER) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // Adapted from +;; CHECK-SOURCE-NEXT: // https://github.com/nemequ/portable-snippets/blob/master/builtin/builtin.h +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline int I64_CLZ(unsigned long long v) { +;; CHECK-SOURCE-NEXT: unsigned long r = 0; +;; CHECK-SOURCE-NEXT: #if defined(_M_AMD64) || defined(_M_ARM) +;; CHECK-SOURCE-NEXT: if (_BitScanReverse64(&r, v)) { +;; CHECK-SOURCE-NEXT: return 63 - r; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: if (_BitScanReverse(&r, (unsigned long)(v >> 32))) { +;; CHECK-SOURCE-NEXT: return 31 - r; +;; CHECK-SOURCE-NEXT: } else if (_BitScanReverse(&r, (unsigned long)v)) { +;; CHECK-SOURCE-NEXT: return 63 - r; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-NEXT: return 64; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline int I32_CLZ(unsigned long v) { +;; CHECK-SOURCE-NEXT: unsigned long r = 0; +;; CHECK-SOURCE-NEXT: if (_BitScanReverse(&r, v)) { +;; CHECK-SOURCE-NEXT: return 31 - r; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return 32; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline int I64_CTZ(unsigned long long v) { +;; CHECK-SOURCE-NEXT: if (!v) { +;; CHECK-SOURCE-NEXT: return 64; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: unsigned long r = 0; +;; CHECK-SOURCE-NEXT: #if defined(_M_AMD64) || defined(_M_ARM) +;; CHECK-SOURCE-NEXT: _BitScanForward64(&r, v); +;; CHECK-SOURCE-NEXT: return (int)r; +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: if (_BitScanForward(&r, (unsigned int)(v))) { +;; CHECK-SOURCE-NEXT: return (int)(r); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: _BitScanForward(&r, (unsigned int)(v >> 32)); +;; CHECK-SOURCE-NEXT: return (int)(r + 32); +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline int I32_CTZ(unsigned long v) { +;; CHECK-SOURCE-NEXT: if (!v) { +;; CHECK-SOURCE-NEXT: return 32; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: unsigned long r = 0; +;; CHECK-SOURCE-NEXT: _BitScanForward(&r, v); +;; CHECK-SOURCE-NEXT: return (int)r; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define POPCOUNT_DEFINE_PORTABLE(f_n, T) \ +;; CHECK-SOURCE-NEXT: static inline u32 f_n(T x) { \ +;; CHECK-SOURCE-NEXT: x = x - ((x >> 1) & (T) ~(T)0 / 3); \ +;; CHECK-SOURCE-NEXT: x = (x & (T) ~(T)0 / 15 * 3) + ((x >> 2) & (T) ~(T)0 / 15 * 3); \ +;; CHECK-SOURCE-NEXT: x = (x + (x >> 4)) & (T) ~(T)0 / 255 * 15; \ +;; CHECK-SOURCE-NEXT: return (T)(x * ((T) ~(T)0 / 255)) >> (sizeof(T) - 1) * 8; \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: POPCOUNT_DEFINE_PORTABLE(I32_POPCNT, u32) +;; CHECK-SOURCE-NEXT: POPCOUNT_DEFINE_PORTABLE(I64_POPCNT, u64) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #undef POPCOUNT_DEFINE_PORTABLE +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32) +;; CHECK-SOURCE-NEXT: #define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64) +;; CHECK-SOURCE-NEXT: #define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32) +;; CHECK-SOURCE-NEXT: #define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64) +;; CHECK-SOURCE-NEXT: #define I32_POPCNT(x) (__builtin_popcount(x)) +;; CHECK-SOURCE-NEXT: #define I64_POPCNT(x) (__builtin_popcountll(x)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DIV_S(ut, min, x, y) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \ +;; CHECK-SOURCE-NEXT: : (ut)((x) / (y))) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define REM_S(ut, min, x, y) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((x) == min && (y) == -1)) ? 0 \ +;; CHECK-SOURCE-NEXT: : (ut)((x) % (y))) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y) +;; CHECK-SOURCE-NEXT: #define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y) +;; CHECK-SOURCE-NEXT: #define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y) +;; CHECK-SOURCE-NEXT: #define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DIVREM_U(op, x, y) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x)op(y))) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DIV_U(x, y) DIVREM_U(/, x, y) +;; CHECK-SOURCE-NEXT: #define REM_U(x, y) DIVREM_U(%, x, y) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define ROTL(x, y, mask) \ +;; CHECK-SOURCE-NEXT: (((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask)))) +;; CHECK-SOURCE-NEXT: #define ROTR(x, y, mask) \ +;; CHECK-SOURCE-NEXT: (((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask)))) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_ROTL(x, y) ROTL(x, y, 31) +;; CHECK-SOURCE-NEXT: #define I64_ROTL(x, y) ROTL(x, y, 63) +;; CHECK-SOURCE-NEXT: #define I32_ROTR(x, y) ROTR(x, y, 31) +;; CHECK-SOURCE-NEXT: #define I64_ROTR(x, y) ROTR(x, y, 63) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define FMIN(x, y) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? NAN \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((y) != (y))) ? NAN \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \ +;; CHECK-SOURCE-NEXT: : (x < y) ? x \ +;; CHECK-SOURCE-NEXT: : y) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define FMAX(x, y) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? NAN \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((y) != (y))) ? NAN \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \ +;; CHECK-SOURCE-NEXT: : (x > y) ? x \ +;; CHECK-SOURCE-NEXT: : y) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define TRUNC_S(ut, st, ft, min, minop, max, x) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x)minop(min) && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ +;; CHECK-SOURCE-NEXT: : (ut)(st)(x)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_S_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_S(u32, s32, f32, (f32)INT32_MIN, >=, 2147483648.f, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_S_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_S(u64, s64, f32, (f32)INT64_MIN, >=, (f32)INT64_MAX, x) +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_S_F64(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_S(u32, s32, f64, -2147483649., >, 2147483648., x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_S_F64(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_S(u64, s64, f64, (f64)INT64_MIN, >=, (f64)INT64_MAX, x) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define TRUNC_U(ut, ft, max, x) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x) > (ft) - 1 && (x) < (max)))) ? TRAP(INT_OVERFLOW) \ +;; CHECK-SOURCE-NEXT: : (ut)(x)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_U_F32(x) TRUNC_U(u32, f32, 4294967296.f, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_U_F32(x) TRUNC_U(u64, f32, (f32)UINT64_MAX, x) +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_U_F64(x) TRUNC_U(u32, f64, 4294967296., x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_U_F64(x) TRUNC_U(u64, f64, (f64)UINT64_MAX, x) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define TRUNC_SAT_S(ut, st, ft, min, smin, minop, max, smax, x) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? 0 \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x)minop(min)))) ? smin \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x) < (max)))) ? smax \ +;; CHECK-SOURCE-NEXT: : (ut)(st)(x)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_SAT_S_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_S(u32, s32, f32, (f32)INT32_MIN, INT32_MIN, >=, 2147483648.f, \ +;; CHECK-SOURCE-NEXT: INT32_MAX, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_SAT_S_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_S(u64, s64, f32, (f32)INT64_MIN, INT64_MIN, >=, (f32)INT64_MAX, \ +;; CHECK-SOURCE-NEXT: INT64_MAX, x) +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_SAT_S_F64(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_S(u32, s32, f64, -2147483649., INT32_MIN, >, 2147483648., \ +;; CHECK-SOURCE-NEXT: INT32_MAX, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_SAT_S_F64(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_S(u64, s64, f64, (f64)INT64_MIN, INT64_MIN, >=, (f64)INT64_MAX, \ +;; CHECK-SOURCE-NEXT: INT64_MAX, x) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define TRUNC_SAT_U(ut, ft, max, smax, x) \ +;; CHECK-SOURCE-NEXT: ((UNLIKELY((x) != (x))) ? 0 \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x) > (ft) - 1))) ? 0 \ +;; CHECK-SOURCE-NEXT: : (UNLIKELY(!((x) < (max)))) ? smax \ +;; CHECK-SOURCE-NEXT: : (ut)(x)) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_SAT_U_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_U(u32, f32, 4294967296.f, UINT32_MAX, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_SAT_U_F32(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_U(u64, f32, (f32)UINT64_MAX, UINT64_MAX, x) +;; CHECK-SOURCE-NEXT: #define I32_TRUNC_SAT_U_F64(x) TRUNC_SAT_U(u32, f64, 4294967296., UINT32_MAX, x) +;; CHECK-SOURCE-NEXT: #define I64_TRUNC_SAT_U_F64(x) \ +;; CHECK-SOURCE-NEXT: TRUNC_SAT_U(u64, f64, (f64)UINT64_MAX, UINT64_MAX, x) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_REINTERPRET(name, t1, t2) \ +;; CHECK-SOURCE-NEXT: static inline t2 name(t1 x) { \ +;; CHECK-SOURCE-NEXT: t2 result; \ +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&result, &x, sizeof(result)); \ +;; CHECK-SOURCE-NEXT: return result; \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_REINTERPRET(f32_reinterpret_i32, u32, f32) +;; CHECK-SOURCE-NEXT: DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32) +;; CHECK-SOURCE-NEXT: DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64) +;; CHECK-SOURCE-NEXT: DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float quiet_nanf(float x) { +;; CHECK-SOURCE-NEXT: uint32_t tmp; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&tmp, &x, 4); +;; CHECK-SOURCE-NEXT: tmp |= 0x7fc00000lu; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&x, &tmp, 4); +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double quiet_nan(double x) { +;; CHECK-SOURCE-NEXT: uint64_t tmp; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&tmp, &x, 8); +;; CHECK-SOURCE-NEXT: tmp |= 0x7ff8000000000000llu; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&x, &tmp, 8); +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_quiet(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_quietf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_floor(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return floor(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_floorf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return floorf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_ceil(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return ceil(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_ceilf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return ceilf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_trunc(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return trunc(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_truncf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return truncf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_nearbyintf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return nearbyintf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_nearbyint(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return nearbyint(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_fabsf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: uint32_t tmp; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&tmp, &x, 4); +;; CHECK-SOURCE-NEXT: tmp = tmp & ~(1UL << 31); +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&x, &tmp, 4); +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return fabsf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_fabs(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: uint64_t tmp; +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&tmp, &x, 8); +;; CHECK-SOURCE-NEXT: tmp = tmp & ~(1ULL << 63); +;; CHECK-SOURCE-NEXT: wasm_rt_memcpy(&x, &tmp, 8); +;; CHECK-SOURCE-NEXT: return x; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return fabs(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static double wasm_sqrt(double x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nan(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return sqrt(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static float wasm_sqrtf(float x) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(isnan(x))) { +;; CHECK-SOURCE-NEXT: return quiet_nanf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: return sqrtf(x); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline void memory_fill(wasm_rt_memory_t* mem, u64 d, u32 val, u64 n) { +;; CHECK-SOURCE-NEXT: RANGE_CHECK(mem, d, n); +;; CHECK-SOURCE-NEXT: memset(MEM_ADDR(mem, d, n), val, n); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline void memory_copy(wasm_rt_memory_t* dest, +;; CHECK-SOURCE-NEXT: const wasm_rt_memory_t* src, +;; CHECK-SOURCE-NEXT: u64 dest_addr, +;; CHECK-SOURCE-NEXT: u64 src_addr, +;; CHECK-SOURCE-NEXT: u64 n) { +;; CHECK-SOURCE-NEXT: RANGE_CHECK(dest, dest_addr, n); +;; CHECK-SOURCE-NEXT: RANGE_CHECK(src, src_addr, n); +;; CHECK-SOURCE-NEXT: memmove(MEM_ADDR(dest, dest_addr, n), MEM_ADDR(src, src_addr, n), n); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline void memory_init(wasm_rt_memory_t* dest, +;; CHECK-SOURCE-NEXT: const u8* src, +;; CHECK-SOURCE-NEXT: u32 src_size, +;; CHECK-SOURCE-NEXT: u64 dest_addr, +;; CHECK-SOURCE-NEXT: u32 src_addr, +;; CHECK-SOURCE-NEXT: u32 n) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +;; CHECK-SOURCE-NEXT: TRAP(OOB); +;; CHECK-SOURCE-NEXT: LOAD_DATA((*dest), dest_addr, src + src_addr, n); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: typedef enum { RefFunc, RefNull, GlobalGet } wasm_elem_segment_expr_type_t; +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: typedef struct { +;; CHECK-SOURCE-NEXT: wasm_elem_segment_expr_type_t expr_type; +;; CHECK-SOURCE-NEXT: wasm_rt_func_type_t type; +;; CHECK-SOURCE-NEXT: wasm_rt_function_ptr_t func; +;; CHECK-SOURCE-NEXT: wasm_rt_tailcallee_t func_tailcallee; +;; CHECK-SOURCE-NEXT: size_t module_offset; +;; CHECK-SOURCE-NEXT: } wasm_elem_segment_expr_t; +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: static inline void funcref_table_init(wasm_rt_funcref_table_t* dest, +;; CHECK-SOURCE-NEXT: const wasm_elem_segment_expr_t* src, +;; CHECK-SOURCE-NEXT: u32 src_size, +;; CHECK-SOURCE-NEXT: u64 dest_addr, +;; CHECK-SOURCE-NEXT: u32 src_addr, +;; CHECK-SOURCE-NEXT: u32 n, +;; CHECK-SOURCE-NEXT: void* module_instance) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +;; CHECK-SOURCE-NEXT: TRAP(OOB); +;; CHECK-SOURCE-NEXT: RANGE_CHECK(dest, dest_addr, n); +;; CHECK-SOURCE-NEXT: for (u32 i = 0; i < n; i++) { +;; CHECK-SOURCE-NEXT: const wasm_elem_segment_expr_t* const src_expr = &src[src_addr + i]; +;; CHECK-SOURCE-NEXT: wasm_rt_funcref_t* const dest_val = &(dest->data[dest_addr + i]); +;; CHECK-SOURCE-NEXT: switch (src_expr->expr_type) { +;; CHECK-SOURCE-NEXT: case RefFunc: +;; CHECK-SOURCE-NEXT: *dest_val = (wasm_rt_funcref_t){ +;; CHECK-SOURCE-NEXT: src_expr->type, src_expr->func, src_expr->func_tailcallee, +;; CHECK-SOURCE-NEXT: (char*)module_instance + src_expr->module_offset}; +;; CHECK-SOURCE-NEXT: break; +;; CHECK-SOURCE-NEXT: case RefNull: +;; CHECK-SOURCE-NEXT: *dest_val = wasm_rt_funcref_null_value; +;; CHECK-SOURCE-NEXT: break; +;; CHECK-SOURCE-NEXT: case GlobalGet: +;; CHECK-SOURCE-NEXT: *dest_val = **(wasm_rt_funcref_t**)((char*)module_instance + +;; CHECK-SOURCE-NEXT: src_expr->module_offset); +;; CHECK-SOURCE-NEXT: break; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: // Currently wasm2c only supports initializing externref tables with ref.null. +;; CHECK-SOURCE-NEXT: static inline void externref_table_init(wasm_rt_externref_table_t* dest, +;; CHECK-SOURCE-NEXT: u32 src_size, +;; CHECK-SOURCE-NEXT: u64 dest_addr, +;; CHECK-SOURCE-NEXT: u32 src_addr, +;; CHECK-SOURCE-NEXT: u32 n) { +;; CHECK-SOURCE-NEXT: if (UNLIKELY(src_addr + (uint64_t)n > src_size)) +;; CHECK-SOURCE-NEXT: TRAP(OOB); +;; CHECK-SOURCE-NEXT: RANGE_CHECK(dest, dest_addr, n); +;; CHECK-SOURCE-NEXT: for (u32 i = 0; i < n; i++) { +;; CHECK-SOURCE-NEXT: dest->data[dest_addr + i] = wasm_rt_externref_null_value; +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_TABLE_COPY(type) \ +;; CHECK-SOURCE-NEXT: static inline void type##_table_copy(wasm_rt_##type##_table_t* dest, \ +;; CHECK-SOURCE-NEXT: const wasm_rt_##type##_table_t* src, \ +;; CHECK-SOURCE-NEXT: u64 dest_addr, u64 src_addr, u64 n) { \ +;; CHECK-SOURCE-NEXT: RANGE_CHECK(dest, dest_addr, n); \ +;; CHECK-SOURCE-NEXT: RANGE_CHECK(src, src_addr, n); \ +;; CHECK-SOURCE-NEXT: memmove(dest->data + dest_addr, src->data + src_addr, \ +;; CHECK-SOURCE-NEXT: n * sizeof(wasm_rt_##type##_t)); \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_COPY(funcref) +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_COPY(externref) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_TABLE_GET(type) \ +;; CHECK-SOURCE-NEXT: static inline wasm_rt_##type##_t type##_table_get( \ +;; CHECK-SOURCE-NEXT: const wasm_rt_##type##_table_t* table, u64 i) { \ +;; CHECK-SOURCE-NEXT: if (UNLIKELY(i >= table->size)) \ +;; CHECK-SOURCE-NEXT: TRAP(OOB); \ +;; CHECK-SOURCE-NEXT: return table->data[i]; \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_GET(funcref) +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_GET(externref) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_TABLE_SET(type) \ +;; CHECK-SOURCE-NEXT: static inline void type##_table_set(const wasm_rt_##type##_table_t* table, \ +;; CHECK-SOURCE-NEXT: u64 i, const wasm_rt_##type##_t val) { \ +;; CHECK-SOURCE-NEXT: if (UNLIKELY(i >= table->size)) \ +;; CHECK-SOURCE-NEXT: TRAP(OOB); \ +;; CHECK-SOURCE-NEXT: table->data[i] = val; \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_SET(funcref) +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_SET(externref) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #define DEFINE_TABLE_FILL(type) \ +;; CHECK-SOURCE-NEXT: static inline void type##_table_fill(const wasm_rt_##type##_table_t* table, \ +;; CHECK-SOURCE-NEXT: u64 d, const wasm_rt_##type##_t val, \ +;; CHECK-SOURCE-NEXT: u64 n) { \ +;; CHECK-SOURCE-NEXT: RANGE_CHECK(table, d, n); \ +;; CHECK-SOURCE-NEXT: for (uint32_t i = d; i < d + n; i++) { \ +;; CHECK-SOURCE-NEXT: table->data[i] = val; \ +;; CHECK-SOURCE-NEXT: } \ +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_FILL(funcref) +;; CHECK-SOURCE-NEXT: DEFINE_TABLE_FILL(externref) +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if defined(__GNUC__) || defined(__clang__) +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_DECL_EXTERN_T(x) extern const char* const x +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_EXTERN_T(x) const char* const x +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_T(x) static const char* const x +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_DECL_EXTERN_T(x) extern const char x[] +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_EXTERN_T(x) const char x[] +;; CHECK-SOURCE-NEXT: #define FUNC_TYPE_T(x) static const char x[] +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #if (__STDC_VERSION__ < 201112L) && !defined(static_assert) +;; CHECK-SOURCE-NEXT: #define static_assert(X) \ +;; CHECK-SOURCE-NEXT: extern int(*assertion(void))[!!sizeof(struct { int x : (X) ? 2 : -1; })]; +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: #ifdef _MSC_VER +;; CHECK-SOURCE-NEXT: #define WEAK_FUNC_DECL(func, fallback) \ +;; CHECK-SOURCE-NEXT: __pragma(comment(linker, "/alternatename:" #func "=" #fallback)) \ +;; CHECK-SOURCE-NEXT: \ +;; CHECK-SOURCE-NEXT: void \ +;; CHECK-SOURCE-NEXT: fallback(void** instance_ptr, void* tail_call_stack, \ +;; CHECK-SOURCE-NEXT: wasm_rt_tailcallee_t* next) +;; CHECK-SOURCE-NEXT: #else +;; CHECK-SOURCE-NEXT: #define WEAK_FUNC_DECL(func, fallback) \ +;; CHECK-SOURCE-NEXT: __attribute__((weak)) void func(void** instance_ptr, void* tail_call_stack, \ +;; CHECK-SOURCE-NEXT: wasm_rt_tailcallee_t* next) +;; CHECK-SOURCE-NEXT: #endif +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: void wasm2c_test_instantiate(w2c_test* instance) { +;; CHECK-SOURCE-NEXT: assert(wasm_rt_is_initialized()); +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: void wasm2c_test_free(w2c_test* instance) { +;; CHECK-SOURCE-NEXT: } +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: wasm_rt_func_type_t wasm2c_test_get_func_type(uint32_t param_count, uint32_t result_count, ...) { +;; CHECK-SOURCE-NEXT: va_list args; +;; CHECK-SOURCE-EMPTY: +;; CHECK-SOURCE-NEXT: return NULL; +;; CHECK-SOURCE-NEXT: } From f0c48b8438b270fc57a8789641e199cdfc664164 Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Fri, 22 May 2026 17:13:36 +0000 Subject: [PATCH 6/7] checkpoint 2 --- scripts/test/wasm2c.py | 123 +++++----------- src/tools/wasm2c.cpp | 17 +-- src/wasm2c.h | 325 +++++++++++++++++++++++++++++++---------- test/spec/minimal.wast | 1 + 4 files changed, 286 insertions(+), 180 deletions(-) create mode 100644 test/spec/minimal.wast diff --git a/scripts/test/wasm2c.py b/scripts/test/wasm2c.py index 5d29cd4519b..b6313eac2d7 100644 --- a/scripts/test/wasm2c.py +++ b/scripts/test/wasm2c.py @@ -12,112 +12,57 @@ # See the License for the specific language governing permissions and # limitations under the License. -import os -import re -import tempfile +import pathlib import subprocess +import tempfile + from . import shared, support from .shared import print_heading -SPEC_LIST = [ +spec_tests = [ 'unreachable.wast', ] -def run_in_sandbox(cmd, temp_dir, expected_status=0): - print("executing: ", " ".join(cmd)) - proc = subprocess.run(cmd, cwd=temp_dir, capture_output=True, text=True) - if expected_status is not None and proc.returncode != expected_status: - raise Exception(f"Command `{' '.join(cmd)}` failed with exit code {proc.returncode}. Stderr:\n{proc.stderr}") - return proc.stdout, proc.stderr - -def test_wasm2c_spec_execute(spec_wast_path): - basename = os.path.basename(spec_wast_path) - if basename not in SPEC_LIST: - print('..', basename, '(skipped)') - return +def test_wasm2c_spec_output(): + for t in shared.options.spec_tests: + test_path = pathlib.Path(t) + if test_path.name not in spec_tests: + continue - print('..', basename) + print('..', test_path.name) - is_fail_test = '.fail.' in basename or basename.endswith('.fail.wast') + is_fail_test = '.fail' in test_path.name - try: - splits = support.split_wast(spec_wast_path) - except Exception as e: - shared.fail_with_error(f"Failed to split wast {basename}: {e}") - return + test_subdir = f'wasm2c_spec_{test_path.stem}' + test_subdir_path = pathlib.Path(test_subdir) + test_subdir_path.mkdir(exist_ok=True) + + wasm2c_cmd = [shared.WASM2C[0], t, '-o', f'{test_subdir}/{test_path.stem}.c', '--allow-asserts'] + support.run_command(wasm2c_cmd, expected_status = (1 if is_fail_test else 0)) + + c_sources = sorted(test_subdir_path.glob('*.c')) + + wasm2c_dir = pathlib.Path(shared.options.binaryen_root) / 'wasm2c' + c_sources.append(wasm2c_dir / 'wasm-rt-impl.c') + c_sources.append(wasm2c_dir / 'wasm-rt-mem-impl.c') + c_sources.append(wasm2c_dir / 'wasm-rt-exceptions-impl.c') + + compile_cmd = [shared.NATIVECC, '-O2', '-std=c11', '-D_GNU_SOURCE', '-D_DEFAULT_SOURCE', '-I.', f"-I{wasm2c_dir}"] + [str(s) for s in c_sources] + ['-o', f'{test_subdir}/spec_test_runner'] - with tempfile.TemporaryDirectory(delete=False, prefix=f"wasm2c_spec_{basename.replace('.wast', '').replace('.', '_')}_") as temp_dir: - # Combine module definitions and assertions into a single combined wast file - split_wast_path = os.path.join(temp_dir, 'split.wast') - with open(split_wast_path, 'w') as sf: - for module_wat, assertions in splits: - if not module_wat: - continue - if isinstance(module_wat, bytes): - sf.write(module_wat.decode('utf-8', errors='ignore')) - else: - sf.write(module_wat) - sf.write("\n\n") - for a in assertions: - sf.write(a) - sf.write("\n") - sf.write("\n\n") - - split_c = 'split.c' - split_h = 'split.h' - wasm2c_cmd = [shared.WASM2C[0], 'split.wast', '-o', split_c, '--allow-asserts'] - - if is_fail_test: - # Compile-failure test: expect validation to fail - try: - run_in_sandbox(wasm2c_cmd, temp_dir, expected_status=0) - shared.fail_with_error(f"Expected wasm2c validation to fail for {basename}, but it succeeded!") - except Exception as e: - if "failed with exit code 1" in str(e): - print(f"Spec fail-test {basename} passed (failed validation as expected).") - return - else: - raise e - return - else: - run_in_sandbox(wasm2c_cmd, temp_dir) - - # Compile C source portably using NATIVECC - c_sources = [os.path.join(temp_dir, 'split.c')] - wasm2c_dir = os.path.join(shared.options.binaryen_root, 'wasm2c') - c_sources.append(os.path.join(wasm2c_dir, 'wasm-rt-impl.c')) - c_sources.append(os.path.join(wasm2c_dir, 'wasm-rt-mem-impl.c')) - - cmd = [shared.NATIVECC, '-O2', '-std=c11', '-D_GNU_SOURCE', '-D_DEFAULT_SOURCE', '-I.', f"-I{wasm2c_dir}", f"-I{temp_dir}"] + c_sources + ['-o', 'spec_test_runner'] - - cmd += ['-fno-optimize-sibling-calls', '-frounding-math'] + compile_cmd += ['-fno-optimize-sibling-calls', '-frounding-math'] if 'gcc' in shared.NATIVECC.lower(): - cmd.append('-fsignaling-nans') + compile_cmd.append('-fsignaling-nans') - cmd.append('-lm') - cmd.append('-lpthread') + compile_cmd.append('-lm') + compile_cmd.append('-lpthread') - run_in_sandbox(cmd, temp_dir) + support.run_command(compile_cmd) # Run spec test runner binary and assert success - actual_stdout, _ = run_in_sandbox(['./spec_test_runner'], temp_dir) - - if "assertions passed" not in actual_stdout: - shared.fail_with_error(f"Spec runner failed to run correctly. Stdout:\n{actual_stdout}") - else: - match = re.search(r'(\d+)/(\d+) assertions passed', actual_stdout) - if match: - passed = int(match.group(1)) - total = int(match.group(2)) - if passed != total: - shared.fail_with_error(f"Spec test {basename} FAILED: {passed}/{total} passed.") - else: - print(f"Spec test {basename} passed: {passed}/{total} assertions.") + support.run_command([f'{test_subdir}/spec_test_runner']) def test_wasm2c_spec(): - print_heading('checking wasm2c compiled spec testcases...') + print_heading('checking wasm2c spec testcases...') if shared.skip_if_on_windows('wasm2c-spec'): return - spec_tests = shared.options.spec_tests - for t in spec_tests: - test_wasm2c_spec_execute(t) + test_wasm2c_spec_output() diff --git a/src/tools/wasm2c.cpp b/src/tools/wasm2c.cpp index aee05ae79f7..07c43969341 100644 --- a/src/tools/wasm2c.cpp +++ b/src/tools/wasm2c.cpp @@ -70,9 +70,6 @@ int main(int argc, const char* argv[]) { }); options.parse(argc, argv); - if (options.debug) { - flags.debug = true; - } std::optional script; std::shared_ptr wasm; @@ -142,10 +139,6 @@ int main(int argc, const char* argv[]) { } } - if (options.debug) { - std::cerr << "j-printing..." << std::endl; - } - std::string output_c_path = options.extra["output"]; if (!output_c_path.empty()) { // Derive output header file path (.h instead of .c) @@ -165,12 +158,12 @@ int main(int argc, const char* argv[]) { flags.headerName = header_basename; Output c_out(output_c_path, Flags::Text); - Output h_out(output_h_path, Flags::Text); if (script && options.extra["asserts"] == "1") { AssertionEmitter emitter(*script, flags, options.passOptions); - emitter.emit(c_out.getStream(), h_out.getStream()); + emitter.emit(c_out.getStream(), output_c_path); } else { + Output h_out(output_h_path, Flags::Text); optimizeWasm(*wasm, options.passOptions); Wasm2CBuilder builder(flags); builder.processWasm(wasm.get(), c_out.getStream(), h_out.getStream()); @@ -184,7 +177,7 @@ int main(int argc, const char* argv[]) { flags.headerName = "wasm.h"; // Default fallback if (script && options.extra["asserts"] == "1") { AssertionEmitter emitter(*script, flags, options.passOptions); - emitter.emit(c_stream, h_stream); + emitter.emit(c_stream, ""); } else { optimizeWasm(*wasm, options.passOptions); Wasm2CBuilder builder(flags); @@ -195,9 +188,5 @@ int main(int argc, const char* argv[]) { std::cout << c_stream.str(); } - if (options.debug) { - std::cerr << "done." << std::endl; - } - flush_and_quick_exit(0); } diff --git a/src/wasm2c.h b/src/wasm2c.h index 52cb6323eb6..8d1fe245af3 100644 --- a/src/wasm2c.h +++ b/src/wasm2c.h @@ -38,6 +38,7 @@ #include "parsing.h" #include "pass.h" #include "picosha2.h" +#include "support/file.h" #include "wasm-io.h" #include "wasm-validator.h" #include "wasm.h" @@ -185,6 +186,33 @@ class Wasm2CBuilder { << "_get_func_type(uint32_t param_count, uint32_t result_count, ...);" << endl << endl; + // Function declarations in header + h << "/* Exported functions */" << endl; + for (auto& func : wasm->functions) { + bool is_exported = false; + std::string exported_name; + for (auto& exp : wasm->exports) { + if (exp->kind == ExternalKind::Function) { + if (Name* name_ptr = exp->getInternalName()) { + if (*name_ptr == func->name) { + is_exported = true; + exported_name = mangleName(exp->name.toString()); + break; + } + } + } + } + if (!is_exported) + continue; + + h << getCType(func->getResults()) << " w2c_" << module_name << "_" + << exported_name << "(w2c_" << module_name << "*"; + for (auto type : getBasicTypes(func->getParams())) { + h << ", " << getCType(type); + } + h << ");" << endl; + } + h << endl; h << s_header_bottom << endl; h << "#endif /* " << guard_name << " */" << endl; @@ -201,6 +229,40 @@ class Wasm2CBuilder { c << s_source_declarations << endl; + // Forward declarations for all functions in source + c << "/* Forward declarations */" << endl; + for (auto& func : wasm->functions) { + bool is_exported = false; + std::string exported_name; + for (auto& exp : wasm->exports) { + if (exp->kind == ExternalKind::Function) { + if (Name* name_ptr = exp->getInternalName()) { + if (*name_ptr == func->name) { + is_exported = true; + exported_name = mangleName(exp->name.toString()); + break; + } + } + } + } + + std::string c_func_name = + is_exported + ? "w2c_" + module_name + "_" + exported_name + : "w2c_" + module_name + "_" + mangleName(func->name.toString()); + + if (!is_exported) { + c << "static "; + } + c << getCType(func->getResults()) << " " << c_func_name << "(w2c_" + << module_name << "*"; + for (auto type : getBasicTypes(func->getParams())) { + c << ", " << getCType(type); + } + c << ");" << endl; + } + c << endl; + // Instantiate hooks c << "void wasm2c_" << module_name << "_instantiate(w2c_" << module_name << "* instance"; @@ -225,12 +287,72 @@ class Wasm2CBuilder { c << "return NULL;" << endl; c.outdent(); c << "}" << endl << endl; + + // Function implementations in source + for (auto& func : wasm->functions) { + bool is_exported = false; + std::string exported_name; + for (auto& exp : wasm->exports) { + if (exp->kind == ExternalKind::Function) { + if (Name* name_ptr = exp->getInternalName()) { + if (*name_ptr == func->name) { + is_exported = true; + exported_name = mangleName(exp->name.toString()); + break; + } + } + } + } + + std::string c_func_name = + is_exported + ? "w2c_" + module_name + "_" + exported_name + : "w2c_" + module_name + "_" + mangleName(func->name.toString()); + + if (!is_exported) { + c << "static "; + } + c << getCType(func->getResults()) << " " << c_func_name << "(w2c_" + << module_name << "* instance"; + size_t param_idx = 0; + for (auto type : getBasicTypes(func->getParams())) { + c << ", " << getCType(type) << " var" << param_idx++; + } + c << ") {" << endl; + c.indent(); + processFunction(func.get(), c); + c.outdent(); + c << "}" << endl << endl; + } } private: Flags flags; Module* module = nullptr; + std::string translateLiteral(const Literal& lit) { + std::stringstream os; + switch (lit.type.getBasic()) { + case Type::i32: + os << lit.geti32() << "u"; + break; + case Type::i64: + os << lit.geti64() << "ULL"; + break; + case Type::f32: + os << "float_from_u32(0x" << std::hex << lit.reinterpreti32() << "u)" + << std::dec; + break; + case Type::f64: + os << "double_from_u64(0x" << std::hex << lit.reinterpreti64() << "ULL)" + << std::dec; + break; + default: + Fatal() << "Unsupported literal type: " << lit.type; + } + return os.str(); + } + void processFunction(Function* func, CPrinter& c) { processStatement(func->body, func, c); } @@ -243,9 +365,17 @@ class Wasm2CBuilder { return; } - Fatal() << "Unsupported statement type"; + if (auto* block = expr->dynCast()) { + for (auto* child : block->list) { + processStatement(child, func, c); + } + return; + } + std::string val = processExpression(expr, func, c); - c << val << ";" << endl; + if (!val.empty()) { + c << val << ";" << endl; + } } std::string processExpression(Expression* curr, Function* func, CPrinter& c) { @@ -256,8 +386,30 @@ class Wasm2CBuilder { return "TRAP(UNREACHABLE)"; } - Fatal() << "Unsupported expression node type."; - return ""; + if (auto* const_node = curr->dynCast()) { + return translateLiteral(const_node->value); + } + + if (auto* call = curr->dynCast()) { + std::string module_name = + flags.moduleName.empty() ? "test" : mangleName(flags.moduleName); + std::string expr = "w2c_" + module_name + "_" + + mangleName(call->target.toString()) + "(instance"; + for (auto* operand : call->operands) { + expr += ", " + processExpression(operand, func, c); + } + expr += ")"; + return expr; + } + + // Dynamic warning & dummy fallback to compile spec tests containing + // unsupported nodes + std::stringstream ss; + ss << *curr; + std::cerr + << "Warning: Unsupported expression node type (generating dummy C): " + << ss.str() << std::endl; + return "0"; } }; @@ -271,6 +423,26 @@ static void optimizeWasm(Module& wasm, PassOptions options) { runner.run(); } +inline std::string strip_extension(const std::string& path) { + size_t last_dot = path.find_last_of('.'); + if (last_dot == std::string::npos) { + return path; + } + size_t last_slash = path.find_last_of("/\\"); + if (last_slash != std::string::npos && last_dot < last_slash) { + return path; + } + return path.substr(0, last_dot); +} + +inline std::string get_basename(const std::string& path) { + size_t last_slash = path.find_last_of("/\\"); + if (last_slash == std::string::npos) { + return path; + } + return path.substr(last_slash + 1); +} + class AssertionEmitter { public: AssertionEmitter(WATParser::WASTScript& script, @@ -278,20 +450,13 @@ class AssertionEmitter { const PassOptions& options) : script(script), flags(flags), options(options) {} - void emit(std::ostream& c_out, std::ostream& h_out) { - CPrinter h(h_out); + void emit(std::ostream& c_out, const std::string& output_c_path) { CPrinter c(c_out); - // Print static prefix templates to C source stream - if (!flags.headerName.empty()) { - c << "#include \"" << flags.headerName << "\"" << endl; - } else { - c << "#include \"wasm.h\"" << endl; - } - c << s_spec_top; + std::string base_path = + output_c_path.empty() ? "spec" : strip_extension(output_c_path); + std::string base_basename = get_basename(base_path); - std::stringstream h_stream; - std::stringstream c_stream; std::vector checks; std::vector prefixes; @@ -309,7 +474,8 @@ class AssertionEmitter { Fatal() << "Expected parsed Module pointer inside WASTModule"; } auto wasm = *w; - std::string prefix = "spec_" + std::to_string(module_counter++); + size_t current_idx = module_counter++; + std::string prefix = "spec_" + std::to_string(current_idx); name_to_prefix[prefix] = prefix; if (wasm->name.is()) { @@ -317,13 +483,30 @@ class AssertionEmitter { prefix; } + // Generate separate files for this module + std::string mod_h_filename = + base_basename + "." + std::to_string(current_idx) + ".h"; + + std::string mod_c_path = + base_path + "." + std::to_string(current_idx) + ".c"; + std::string mod_h_path = + base_path + "." + std::to_string(current_idx) + ".h"; + + Output mod_c_out(mod_c_path, Flags::Text); + Output mod_h_out(mod_h_path, Flags::Text); + Wasm2CBuilder::Flags mod_flags = flags; mod_flags.moduleName = prefix; + mod_flags.headerName = mod_h_filename; // Run standard validation and flattening simplification passes optimizeWasm(*wasm.get(), options); Wasm2CBuilder builder(mod_flags); - builder.processWasm(wasm.get(), c_stream, h_stream); + builder.processWasm( + wasm.get(), mod_c_out.getStream(), mod_h_out.getStream()); + + c << "#include \"" << mod_h_filename << "\"" << endl; + c << s_spec_top << endl << endl; // Cache exported function return types and parameter types for (auto& exp : wasm->exports) { @@ -332,7 +515,7 @@ class AssertionEmitter { std::string c_func_name = "w2c_" + prefix + "_" + Wasm2CBuilder::mangleName(exp->name.toString()); - func_returns[c_func_name] = builder.getCType(func->getResults()); + func_returns[c_func_name] = func->getResults(); func_params[c_func_name] = builder.getBasicTypes(func->getParams()); } } @@ -361,12 +544,11 @@ class AssertionEmitter { std::string alias = Wasm2CBuilder::mangleName(reg->name.toString()); name_to_prefix[alias] = target_prefix; - // Output preprocessor define redirects - h_stream << "#define w2c_" << alias << " w2c_" << target_prefix << "\n"; - h_stream << "#define wasm2c_" << alias << "_instantiate wasm2c_" - << target_prefix << "_instantiate\n"; - h_stream << "#define wasm2c_" << alias << "_free wasm2c_" - << target_prefix << "_free\n"; + c << "#define w2c_" << alias << " w2c_" << target_prefix << endl; + c << "#define wasm2c_" << alias << "_instantiate wasm2c_" + << target_prefix << "_instantiate" << endl; + c << "#define wasm2c_" << alias << "_free wasm2c_" << target_prefix + << "_free" << endl; // Redirect all exported symbols from the alias to the target provider // prefix @@ -375,11 +557,11 @@ class AssertionEmitter { for (auto& exp : wasm->exports) { std::string exp_name = Wasm2CBuilder::mangleName(exp->name.toString()); - h_stream << "#define w2c_" << alias << "_" << exp_name << " w2c_" - << target_prefix << "_" << exp_name << "\n"; + c << "#define w2c_" << alias << "_" << exp_name << " w2c_" + << target_prefix << "_" << exp_name << endl; } } - h_stream << "\n"; + c << endl; } else if (auto* assn = std::get_if(&cmd)) { if (auto* ret = std::get_if(assn)) { @@ -392,52 +574,49 @@ class AssertionEmitter { std::string assert_macro; auto& exp = ret->expected[0][0]; // Get first alternative if (auto* lit = std::get_if(&exp)) { - std::string ret_type = func_returns[field_name]; - switch (ret_type) { - case "i32": - assert_macro = "ASSERT_RETURN_I32"; - break; - case "i64": - assert_macro = "ASSERT_RETURN_I64"; - break; - case "f32": - assert_macro = "ASSERT_RETURN_F32"; - break; - case "f64": - assert_macro = "ASSERT_RETURN_F64"; - break; - default: - Fatal() << "Unsupported return type: " << ret_type; - break; + Type ret_type = func_returns[field_name]; + if (ret_type == Type::i32) { + assert_macro = "ASSERT_RETURN_I32"; + } else if (ret_type == Type::i64) { + assert_macro = "ASSERT_RETURN_I64"; + } else if (ret_type == Type::f32) { + assert_macro = "ASSERT_RETURN_F32"; + } else if (ret_type == Type::f64) { + assert_macro = "ASSERT_RETURN_F64"; + } else { + Fatal() << "Unsupported return type: " << ret_type.toString(); } checks.push_back(assert_macro + "(" + expr_call + ", " + translateLiteral(*lit) + ");"); - } - } else if (auto* nan_val = std::get_if(&exp)) { - if (nan_val->type == Type::f32) { - switch (nan_val->kind) { - case WATParser::NaNKind::Canonical: - assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F32"; - break; - case WATParser::NaNKind::Arithmetic: - assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F32"; - break; - default: - Fatal() << "Unsupported NaN kind: " << nan_val->kind; - } - } else if (nan_val->type == Type::f64) { - switch (nan_val->kind) { - case WATParser::NaNKind::Canonical: - assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F64"; - break; - case WATParser::NaNKind::Arithmetic: - assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F64"; - break; - default: - Fatal() << "Unsupported NaN kind: " << nan_val->kind; + } else if (auto* nan_val = + std::get_if(&exp)) { + if (nan_val->type == Type::f32) { + switch (nan_val->kind) { + case WATParser::NaNKind::Canonical: + assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F32"; + break; + case WATParser::NaNKind::Arithmetic: + assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F32"; + break; + default: + Fatal() << "Unsupported NaN kind: " + << static_cast(nan_val->kind); + } + } else if (nan_val->type == Type::f64) { + switch (nan_val->kind) { + case WATParser::NaNKind::Canonical: + assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F64"; + break; + case WATParser::NaNKind::Arithmetic: + assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F64"; + break; + default: + Fatal() << "Unsupported NaN kind: " + << static_cast(nan_val->kind); + } } + checks.push_back(assert_macro + "(" + expr_call + ");"); } - checks.push_back(assert_macro + "(" + expr_call + ");"); } } else if (auto* act = std::get_if(assn)) { if (act->type == WATParser::ActionAssertionType::Trap) { @@ -451,14 +630,6 @@ class AssertionEmitter { } } - // Write C headers and definitions - h << endl - << "/* === MODULE HEADERS === */" << endl - << h_stream.str() << endl; - c << endl - << "/* === MODULE SOURCES === */" << endl - << c_stream.str() << endl; - // Write main execution entry point c << "void run_spec_tests() {" << endl; c.indent(); @@ -530,7 +701,7 @@ class AssertionEmitter { std::map name_to_prefix; std::map> func_params; - std::map func_returns; + std::map func_returns; std::map> wasm_modules_cache; std::string last_module_prefix = ""; size_t module_counter = 0; diff --git a/test/spec/minimal.wast b/test/spec/minimal.wast new file mode 100644 index 00000000000..3af8f254547 --- /dev/null +++ b/test/spec/minimal.wast @@ -0,0 +1 @@ +(module) From 4c211bea7968a285d796fceb2cc0a6164de6120c Mon Sep 17 00:00:00 2001 From: Lexi Bromfield Date: Fri, 22 May 2026 17:30:54 +0000 Subject: [PATCH 7/7] checkpoint --- src/wasm2c.h | 368 +-------------------------------------------------- 1 file changed, 4 insertions(+), 364 deletions(-) diff --git a/src/wasm2c.h b/src/wasm2c.h index 8d1fe245af3..c54cbd6c546 100644 --- a/src/wasm2c.h +++ b/src/wasm2c.h @@ -22,23 +22,13 @@ #ifndef wasm_wasm2c_h #define wasm_wasm2c_h -#include -#include -#include #include -#include -#include -#include #include -#include #include -#include "ir/module-utils.h" #include "parser/wat-parser.h" #include "parsing.h" #include "pass.h" -#include "picosha2.h" -#include "support/file.h" #include "wasm-io.h" #include "wasm-validator.h" #include "wasm.h" @@ -184,10 +174,8 @@ class Wasm2CBuilder { << endl; h << "wasm_rt_func_type_t wasm2c_" << module_name << "_get_func_type(uint32_t param_count, uint32_t result_count, ...);" - << endl << endl; // Function declarations in header - h << "/* Exported functions */" << endl; for (auto& func : wasm->functions) { bool is_exported = false; std::string exported_name; @@ -230,7 +218,6 @@ class Wasm2CBuilder { c << s_source_declarations << endl; // Forward declarations for all functions in source - c << "/* Forward declarations */" << endl; for (auto& func : wasm->functions) { bool is_exported = false; std::string exported_name; @@ -261,7 +248,6 @@ class Wasm2CBuilder { } c << ");" << endl; } - c << endl; // Instantiate hooks c << "void wasm2c_" << module_name << "_instantiate(w2c_" << module_name @@ -319,9 +305,6 @@ class Wasm2CBuilder { c << ", " << getCType(type) << " var" << param_idx++; } c << ") {" << endl; - c.indent(); - processFunction(func.get(), c); - c.outdent(); c << "}" << endl << endl; } } @@ -329,88 +312,6 @@ class Wasm2CBuilder { private: Flags flags; Module* module = nullptr; - - std::string translateLiteral(const Literal& lit) { - std::stringstream os; - switch (lit.type.getBasic()) { - case Type::i32: - os << lit.geti32() << "u"; - break; - case Type::i64: - os << lit.geti64() << "ULL"; - break; - case Type::f32: - os << "float_from_u32(0x" << std::hex << lit.reinterpreti32() << "u)" - << std::dec; - break; - case Type::f64: - os << "double_from_u64(0x" << std::hex << lit.reinterpreti64() << "ULL)" - << std::dec; - break; - default: - Fatal() << "Unsupported literal type: " << lit.type; - } - return os.str(); - } - - void processFunction(Function* func, CPrinter& c) { - processStatement(func->body, func, c); - } - - void processStatement(Expression* expr, Function* func, CPrinter& c) { - if (!expr) - return; - - if (expr->is()) { - return; - } - - if (auto* block = expr->dynCast()) { - for (auto* child : block->list) { - processStatement(child, func, c); - } - return; - } - - std::string val = processExpression(expr, func, c); - if (!val.empty()) { - c << val << ";" << endl; - } - } - - std::string processExpression(Expression* curr, Function* func, CPrinter& c) { - if (!curr) - return ""; - - if (curr->is()) { - return "TRAP(UNREACHABLE)"; - } - - if (auto* const_node = curr->dynCast()) { - return translateLiteral(const_node->value); - } - - if (auto* call = curr->dynCast()) { - std::string module_name = - flags.moduleName.empty() ? "test" : mangleName(flags.moduleName); - std::string expr = "w2c_" + module_name + "_" + - mangleName(call->target.toString()) + "(instance"; - for (auto* operand : call->operands) { - expr += ", " + processExpression(operand, func, c); - } - expr += ")"; - return expr; - } - - // Dynamic warning & dummy fallback to compile spec tests containing - // unsupported nodes - std::stringstream ss; - ss << *curr; - std::cerr - << "Warning: Unsupported expression node type (generating dummy C): " - << ss.str() << std::endl; - return "0"; - } }; static void optimizeWasm(Module& wasm, PassOptions options) { @@ -457,9 +358,6 @@ class AssertionEmitter { output_c_path.empty() ? "spec" : strip_extension(output_c_path); std::string base_basename = get_basename(base_path); - std::vector checks; - std::vector prefixes; - // Loop sequentially through WASTScript AST commands for (size_t i = 0; i < script.size(); i++) { auto& entry = script[i]; @@ -477,12 +375,6 @@ class AssertionEmitter { size_t current_idx = module_counter++; std::string prefix = "spec_" + std::to_string(current_idx); - name_to_prefix[prefix] = prefix; - if (wasm->name.is()) { - name_to_prefix[Wasm2CBuilder::mangleName(wasm->name.toString())] = - prefix; - } - // Generate separate files for this module std::string mod_h_filename = base_basename + "." + std::to_string(current_idx) + ".h"; @@ -508,189 +400,15 @@ class AssertionEmitter { c << "#include \"" << mod_h_filename << "\"" << endl; c << s_spec_top << endl << endl; - // Cache exported function return types and parameter types - for (auto& exp : wasm->exports) { - if (exp->kind == ExternalKind::Function) { - Function* func = wasm->getFunction(*exp->getInternalName()); - std::string c_func_name = - "w2c_" + prefix + "_" + - Wasm2CBuilder::mangleName(exp->name.toString()); - func_returns[c_func_name] = func->getResults(); - func_params[c_func_name] = builder.getBasicTypes(func->getParams()); - } - } - - last_module_prefix = prefix; - prefixes.push_back(prefix); - wasm_modules_cache[prefix] = wasm; - - } else if (auto* reg = std::get_if(&cmd)) { - std::string mangled_instance_name = - reg->instanceName.has_value() - ? Wasm2CBuilder::mangleName(reg->instanceName->toString()) - : ""; - - std::string target_prefix; - if (reg->instanceName.has_value()) { - if (!name_to_prefix.count(mangled_instance_name)) { - Fatal() << "Unregistered instance name mapping for alias register: " - << mangled_instance_name; - } - target_prefix = name_to_prefix[mangled_instance_name]; - } else { - target_prefix = last_module_prefix; - } - - std::string alias = Wasm2CBuilder::mangleName(reg->name.toString()); - name_to_prefix[alias] = target_prefix; - - c << "#define w2c_" << alias << " w2c_" << target_prefix << endl; - c << "#define wasm2c_" << alias << "_instantiate wasm2c_" - << target_prefix << "_instantiate" << endl; - c << "#define wasm2c_" << alias << "_free wasm2c_" << target_prefix - << "_free" << endl; - - // Redirect all exported symbols from the alias to the target provider - // prefix - if (wasm_modules_cache.count(target_prefix)) { - auto wasm = wasm_modules_cache[target_prefix]; - for (auto& exp : wasm->exports) { - std::string exp_name = - Wasm2CBuilder::mangleName(exp->name.toString()); - c << "#define w2c_" << alias << "_" << exp_name << " w2c_" - << target_prefix << "_" << exp_name << endl; - } - } - c << endl; - - } else if (auto* assn = std::get_if(&cmd)) { - if (auto* ret = std::get_if(assn)) { - if (auto* inv = std::get_if(&ret->action)) { - std::string expr_call = translateInvoke(*inv); - std::string field_name = - "w2c_" + getInvokePrefix(*inv) + "_" + - Wasm2CBuilder::mangleName(inv->name.toString()); - - std::string assert_macro; - auto& exp = ret->expected[0][0]; // Get first alternative - if (auto* lit = std::get_if(&exp)) { - Type ret_type = func_returns[field_name]; - if (ret_type == Type::i32) { - assert_macro = "ASSERT_RETURN_I32"; - } else if (ret_type == Type::i64) { - assert_macro = "ASSERT_RETURN_I64"; - } else if (ret_type == Type::f32) { - assert_macro = "ASSERT_RETURN_F32"; - } else if (ret_type == Type::f64) { - assert_macro = "ASSERT_RETURN_F64"; - } else { - Fatal() << "Unsupported return type: " << ret_type.toString(); - } - checks.push_back(assert_macro + "(" + expr_call + ", " + - translateLiteral(*lit) + ");"); - } else if (auto* nan_val = - std::get_if(&exp)) { - if (nan_val->type == Type::f32) { - switch (nan_val->kind) { - case WATParser::NaNKind::Canonical: - assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F32"; - break; - case WATParser::NaNKind::Arithmetic: - assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F32"; - break; - default: - Fatal() << "Unsupported NaN kind: " - << static_cast(nan_val->kind); - } - } else if (nan_val->type == Type::f64) { - switch (nan_val->kind) { - case WATParser::NaNKind::Canonical: - assert_macro = "ASSERT_RETURN_CANONICAL_NAN_F64"; - break; - case WATParser::NaNKind::Arithmetic: - assert_macro = "ASSERT_RETURN_ARITHMETIC_NAN_F64"; - break; - default: - Fatal() << "Unsupported NaN kind: " - << static_cast(nan_val->kind); - } - } - checks.push_back(assert_macro + "(" + expr_call + ");"); - } - } - } else if (auto* act = std::get_if(assn)) { - if (act->type == WATParser::ActionAssertionType::Trap) { - if (auto* inv = - std::get_if(&act->action)) { - std::string expr_call = translateInvoke(*inv); - checks.push_back("ASSERT_TRAP(" + expr_call + ");"); - } - } - } + } else if (std::get_if(&cmd)) { + Fatal() << "register is not yet supported"; + } else if (std::get_if(&cmd)) { + Fatal() << "assertions are not yet supported"; } } // Write main execution entry point c << "void run_spec_tests() {" << endl; - c.indent(); - - // Declare all module instances - for (auto& prefix : prefixes) { - c << "w2c_" << prefix << " inst_" << prefix << ";" << endl; - } - c << endl; - - // Instantiate all module instances matching their imports metadata - for (auto& prefix : prefixes) { - c << "wasm2c_" << prefix << "_instantiate(&inst_" << prefix; - - // Gather all unique imported modules namespaces - std::set imported_namespaces; - for (auto& mem : wasm_modules_cache[prefix]->memories) { - if (mem->imported()) - imported_namespaces.insert( - Wasm2CBuilder::mangleName(mem->module.toString())); - } - for (auto& table : wasm_modules_cache[prefix]->tables) { - if (table->imported()) - imported_namespaces.insert( - Wasm2CBuilder::mangleName(table->module.toString())); - } - for (auto& global : wasm_modules_cache[prefix]->globals) { - if (global->imported()) - imported_namespaces.insert( - Wasm2CBuilder::mangleName(global->module.toString())); - } - ModuleUtils::iterImportedFunctions( - *wasm_modules_cache[prefix], [&](Function* func) { - imported_namespaces.insert( - Wasm2CBuilder::mangleName(func->module.toString())); - }); - - for (auto& ns : imported_namespaces) { - if (ns == "spectest") { - c << ", w2c_spectest_instance"; - } else { - std::string prov_prefix = name_to_prefix[ns]; - c << ", (struct w2c_" << ns << "*)&inst_" << prov_prefix; - } - } - c << ");" << endl; - } - c << endl; - - // Run all checks - for (auto& chk : checks) { - c << chk << endl; - } - c << endl; - - // Free contexts in REVERSE order of instantiation - for (auto it = prefixes.rbegin(); it != prefixes.rend(); ++it) { - c << "wasm2c_" << *it << "_free(&inst_" << *it << ");" << endl; - } - - c.outdent(); c << "}" << endl; } @@ -699,85 +417,7 @@ class AssertionEmitter { Wasm2CBuilder::Flags flags; PassOptions options; - std::map name_to_prefix; - std::map> func_params; - std::map func_returns; - std::map> wasm_modules_cache; - std::string last_module_prefix = ""; size_t module_counter = 0; - - std::string getInvokePrefix(const WATParser::InvokeAction& invoke) { - if (invoke.base.has_value()) { - std::string base_mangled = - Wasm2CBuilder::mangleName(invoke.base->toString()); - if (name_to_prefix.count(base_mangled)) { - return name_to_prefix[base_mangled]; - } - } - return last_module_prefix; - } - - std::string translateInvoke(const WATParser::InvokeAction& invoke) { - std::string prefix = getInvokePrefix(invoke); - std::string mangled_name = - Wasm2CBuilder::mangleName(invoke.name.toString()); - std::string field_name = "w2c_" + prefix + "_" + mangled_name; - - std::string expr = field_name + "(&inst_" + prefix; - for (auto& arg : invoke.args) { - expr += ", " + translateLiteral(arg); - } - expr += ")"; - return expr; - } - - std::string translateLiteral(const Literal& lit) { - std::stringstream os; - switch (lit.type.getBasic()) { - case Type::i32: - os << lit.geti32() << "u"; - break; - case Type::i64: - os << lit.geti64() << "ULL"; - break; - case Type::f32: - os << "float_from_u32(0x" << std::hex << lit.reinterpreti32() << "u)" - << std::dec; - break; - case Type::f64: - os << "double_from_u64(0x" << std::hex << lit.reinterpreti64() << "ULL)" - << std::dec; - break; - default: - Fatal() << "Unsupported literal type: " << lit.type; - } - return os.str(); - } - - std::string getTrapEnum(const std::string& msg) { - if (msg.find("out of bounds") != std::string::npos || - msg.find("bounds") != std::string::npos) - return "WASM_RT_TRAP_OOB"; - if (msg.find("overflow") != std::string::npos) - return "WASM_RT_TRAP_INT_OVERFLOW"; - if (msg.find("divide") != std::string::npos || - msg.find("zero") != std::string::npos) - return "WASM_RT_TRAP_DIV_BY_ZERO"; - if (msg.find("invalid conversion") != std::string::npos) - return "WASM_RT_TRAP_INVALID_CONVERSION"; - if (msg.find("unreachable") != std::string::npos) - return "WASM_RT_TRAP_UNREACHABLE"; - if (msg.find("indirect call") != std::string::npos || - msg.find("undefined") != std::string::npos || - msg.find("uninitialized") != std::string::npos || - msg.find("signature mismatch") != std::string::npos) { - return "WASM_RT_TRAP_CALL_INDIRECT"; - } - if (msg.find("exhaustion") != std::string::npos || - msg.find("exhausted") != std::string::npos) - return "WASM_RT_TRAP_EXHAUSTION"; - return "WASM_RT_TRAP_NONE"; - } }; } // namespace wasm