Skip to content

Instantly share code, notes, and snippets.

@lifthrasiir
Last active September 26, 2025 06:42
Show Gist options
  • Save lifthrasiir/e0d8587cc6e4f8bee9d7afb28105cc0c to your computer and use it in GitHub Desktop.
Save lifthrasiir/e0d8587cc6e4f8bee9d7afb28105cc0c to your computer and use it in GitHub Desktop.
WebAssembly interpreter in a Header (WAH) -- See https://github.com/lifthrasiir/wah/ for the recent version.
*.exe
wah_test_basic
wah_test_control_flow
wah_test_determinism
wah_test_export
wah_test_functions
wah_test_globals
wah_test_memory
wah_test_ops
wah_test_overflow
wah_test_parse
wah_test_simd
wah_test_table
wah_test_traps
wah_test_types
wah_fuzz_afl
fuzz
*.gcda
*.gcno
cov
coverage.info
coverage_report
.claude

WAH (WebAssembly in a Header) Project Overview

This project implements a WebAssembly (WASM) interpreter entirely within a single C header file (wah.h). It provides the necessary data structures, parsing logic, and execution engine to run WebAssembly modules.

Implementation Strategy

The WAH interpreter employs a two-pass approach for efficient WebAssembly module execution:

  1. Pre-parsing: After initial parsing of the WASM binary, the bytecode for each function undergoes a two-pass "pre-parsing" phase (wah_preparse_code) to optimize it for interpretation.

    • Pass 1: Analysis and Layout. This pass scans the raw WASM bytecode to calculate the final size of the optimized code and resolve all jump targets. It identifies control flow blocks (block, loop, if, else). For backward-branching loop blocks, the jump target is their own starting instruction, which is recorded immediately. For forward-branching block and if blocks, the jump target is the location of their corresponding end instruction (or else for an if). These forward-jump targets are resolved and recorded only when the else or end instruction is encountered during the scan. This pass effectively lays out the structure of the final code and resolves all jump destinations before the second pass begins.

    • Pass 2: Code Generation. This pass generates the final, optimized bytecode. It iterates through the raw WASM code again, emitting a new stream of instructions. Variable-length LEB128 encoded arguments are decoded and replaced with fixed-size native types (e.g., uint32_t). Branch instructions (br, br_if, br_table) are written with the pre-calculated absolute jump offsets from Pass 1. Structural opcodes like block, loop, and end (except for the final one) are removed entirely, as their control flow logic is now embedded directly into the jump offsets of the branch instructions. This process eliminates the need for runtime LEB128 decoding and complex control flow resolution, significantly speeding up interpretation.

  2. Custom Call Stack: Unlike traditional compiled code that relies on the host system's call stack, WAH implements its own explicit call stack (wah_call_frame_t within wah_exec_context_t). This custom stack manages function call frames, instruction pointers, and local variable offsets. This design choice ensures portability across different environments and provides fine-grained control over the execution state, preventing host stack overflow issues and enabling features like stack inspection if needed.

  3. Interpreter Dispatch Strategy: The WAH interpreter supports two main dispatch strategies for executing pre-parsed bytecode, controlled by the WAH_USE_MUSTTAIL macro:

    • Switch-based Interpreter: This traditional approach uses a central while loop that fetches each opcode and dispatches to a large switch statement. Each case within the switch handles a specific opcode's logic. While straightforward to implement, it can incur overhead from branch prediction misses and the switch statement itself.

    • Computed Goto Interpreter: When WAH_USE_COMPUTED_GOTO is defined (automatically enabled for GCC/Clang compilers), the interpreter utilizes a "computed goto" or "direct threading" dispatch mechanism. This approach creates a jump table where each entry is the address of the code that implements a specific opcode. After an opcode's logic is executed, the interpreter directly jumps to the next opcode's handler by looking up its address in the jump table. This method significantly reduces dispatch overhead compared to a switch-based interpreter by eliminating the need for repeated switch statement evaluations and improving branch prediction. It offers performance benefits similar to direct threaded code but uses goto *address; instead of tail calls.

    • Direct Threaded Code Interpreter: When WAH_USE_MUSTTAIL is defined (automatically enabled for recent enough GCC/Clang), the interpreter leverages C compiler extensions (like GCC/Clang's __attribute__((musttail))) for tail call optimization. Instead of a central switch loop, each opcode's logic is encapsulated in a dedicated function (e.g., wah_run_I32_CONST for WAH_OP_I32_CONST). After an opcode is executed, the WAH_NEXT() macro performs a tail-recursive call to a dispatcher function (wah_run_single), which then tail-calls the next opcode's handler. This effectively transforms function calls into direct jumps between opcode handlers, significantly reducing dispatch overhead and improving performance by:

      • Minimizing stack usage, as new stack frames are not pushed for each instruction dispatch.
      • Improving branch prediction, as the flow of control is more direct. This strategy is generally faster for interpreters due to its reduced overhead compared to switch-based dispatch.

Error Handling

All public API functions return a wah_error_t enum value. WAH_OK indicates success, while other values signify specific errors such as invalid WASM format, out-of-memory conditions, or runtime traps. Use wah_strerror(err) to get a human-readable description of an error.

Development Notes

  • The interpreter is designed to be self-contained within wah.h by defining WAH_IMPLEMENTATION in one C/C++ file.
  • It uses standard C libraries (stdint.h, stddef.h, stdbool.h, string.h, stdlib.h, assert.h, math.h, stdio.h).
  • Platform-specific intrinsics (__builtin_popcount, _BitScanReverse, etc.) are used for performance where available, with generic fallbacks.
  • Manual WASM Binary Creation: When manually crafting WASM binaries (without tools like wat2wasm), extreme care must be taken with section size calculations. Always double-check that the initial size numbers are correct. During testing, explicitly verify that the result code is not WAH_ERROR_UNEXPECTED_EOF to catch early errors related to incorrect section sizing.
  • Numeric Overflow Checks: During arithmetic operations inside parsing (e.g., addition, multiplication), it is crucial to explicitly check for potential numeric overflows. If an overflow occurs, it must be reported using the WAH_ERROR_TOO_LARGE error code to indicate that the result exceeds the representable range. Note that this doesn't apply to runtime numeric operations which assume 2's complements.

Testing

  • To run all tests in Windows, execute test.bat (or simply test) from the command line. To run tests that start with a specific prefix (e.g., wah_test_control_flow.c), execute test <prefix> (e.g., test control_flow, NOT test wah_test_control_flow nor test test_control_flow).
  • To run all tests in POSIX-like environments, execute make test from the command line. To run tests with a specific name, execute make test_control_flow for wah_test_control_flow.c and so on.
  • Debugging with WAH_DEBUG: For debugging purposes, you can declare the WAH_DEBUG macro. When running test scripts, you can enable this by executing test -g ... instead of test ....
    • When WAH_DEBUG is enabled, WAH_LOG allows you to print logs in a (line number) content format using printf-like syntax.
    • Similarly, WAH_CHECK and similar macros will automatically output the failure location and error codes using WAH_LOG when WAH_DEBUG is active.
    • For debugging logs, it is best to use WAH_LOG exclusively due to the aforementioned reasons. In particular do not expand WAH_CHECK for logging.
  • Strive to use an existing test file for new test cases. Introduce a new file only when a new major category is warranted.
  • IMPORTANT: Any new bug should introduce a failing regression test that still does compile, so that the fix is demonstrated to reliably make it pass. Do not code the fix first!
# Compiler and flags
CC ?= gcc
CFLAGS ?= -W -Wall -Wextra
LDFLAGS ?= -lm # Link math library, potentially needed for wah.h
# Debugging options
# If DEBUG environment variable is set to 1, add debugging flags and disable optimizations.
ifdef DEBUG
CFLAGS += -DWAH_DEBUG -g -O0
else
CFLAGS += -O2
endif
# List of test source files
TEST_SRCS := $(wildcard wah_test_*.c)
# List of compiled test executables (without .exe extension for WSL environment)
TEST_BINS := $(patsubst %.c, %, $(TEST_SRCS))
# Rule for fuzzing with afl-fuzz
FUZZ_HARNESS_SRC := wah_fuzz_afl.c
FUZZ_HARNESS_BIN := wah_fuzz_afl
# Default fuzzing directories
IN_DIR ?= fuzz/in_dir
OUT_DIR ?= fuzz/out_dir
# Compiler for fuzzing harness (afl-clang for instrumentation)
AFL_CC ?= afl-clang # Or afl-clang-fast if available and configured
# CFLAGS for coverage generation
# These flags enable gcov instrumentation.
GCOV_CFLAGS := -fprofile-arcs -ftest-coverage
# Default target: if 'make' is run without arguments, 'test' target is executed.
.PHONY: all
all: test
# Target to compile and run all tests
# First, build all test executables, then run each one sequentially.
.PHONY: test
test: $(TEST_BINS)
@echo "## Running all tests..."
@for test_bin in $(TEST_BINS); do \
echo "## Running $$test_bin..."; \
./$$test_bin; \
if [ $$? -ne 0 ]; then \
echo "## $$test_bin failed."; \
exit 1; \
fi; \
echo ""; \
done
@echo "## All tests passed."
# Rule to compile a single test source file into an executable
# This rule is used by the 'test' target to build all test binaries.
% : %.c
@echo "## Compiling $<..."
$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS)
# Template for individual test rules
# $(1) is the test source file (e.g., wah_test_simd.c)
define RUN_SINGLE_TEST_TEMPLATE
TEST_NAME := $(patsubst wah_test_%.c, %, $(1))
TEST_TARGET := $(patsubst wah_test_%.c, test_%, $(1))
.PHONY: $(TEST_TARGET)
$(TEST_TARGET):
@echo "## Running $(TEST_NAME)..."
$(CC) $(CFLAGS) $(1) -o $(TEST_NAME) $(LDFLAGS)
./$(TEST_NAME)
if [ $$? -ne 0 ]; then \
echo "## $(TEST_NAME) failed."; \
exit 1; \
fi
echo ""
rm -f $(TEST_NAME)
endef
# Generate individual test targets using the template
$(foreach test_src, $(TEST_SRCS), $(eval $(call RUN_SINGLE_TEST_TEMPLATE,$(test_src))))
# New target for coverage report generation for all tests
coverage: clean
@echo "## Compiling all tests for coverage..."
@for test_src in $(TEST_SRCS); do \
echo "## Compiling $$test_src with coverage flags..."; \
test_bin=$$(basename $$test_src .c); \
$(CC) $(CFLAGS) $(GCOV_CFLAGS) $$test_src -o $$test_bin $(LDFLAGS); \
if [ $$? -ne 0 ]; then \
echo "## Compilation of $$test_src failed."; \
exit 1; \
fi; \
done
@echo "## Running all tests for coverage..."
@for test_bin in $(TEST_BINS); do \
echo "## Running $$test_bin for coverage..."; \
./$$test_bin; \
if [ $$? -ne 0 ]; then \
echo "## $$test_bin failed during coverage run."; \
exit 1; \
fi; \
echo ""; \
done
@echo "## All tests passed for coverage. Generating coverage report..."
# Initialize lcov and capture coverage data
lcov --capture --directory . --output-file coverage.info
# Filter out system headers and test files from the report
lcov --remove coverage.info '/usr/*' '*/wah_test_*.c' --output-file coverage.info
# Generate HTML report
genhtml coverage.info --output-directory coverage_report
@echo "## Coverage report generated in coverage_report/index.html"
# Rule for fuzzing with afl-fuzz
.PHONY: fuzz-afl
fuzz-afl: $(FUZZ_HARNESS_BIN)
@echo "## Starting afl-fuzz with harness $(FUZZ_HARNESS_BIN)..."
@echo "## Input directory: $(IN_DIR)"
@echo "## Output directory: $(OUT_DIR)"
@echo "## Make sure you have an initial seed corpus in $(IN_DIR)!"
@echo "## To stop fuzzing, press Ctrl+C."
afl-fuzz -i $(IN_DIR) -o $(OUT_DIR) -- ./$(FUZZ_HARNESS_BIN)
# Rule to compile the fuzzing harness
$(FUZZ_HARNESS_BIN): $(FUZZ_HARNESS_SRC)
@echo "## Compiling fuzzing harness $< with AFL instrumentation and gcov flags..."
$(AFL_CC) $(CFLAGS) $(GCOV_CFLAGS) $< -o $@ $(LDFLAGS)
# Target to clean up compiled executables
.PHONY: clean
clean:
@echo "## Cleaning up..."
@rm -f $(TEST_BINS)
@rm -f $(FUZZ_HARNESS_BIN) # Remove the afl-fuzz harness executable
@rm -f *.gcda *.gcno coverage.info # Remove gcov data files and lcov info file
@rm -rf cov/ coverage_report/ # Remove coverage report directories
@rm -f *.exe # For Windows compatibility if compiled there (though this Makefile is for WSL)
@echo off
setlocal enabledelayedexpansion
set cflags=
if "%1" == "-g" (
shift
set cflags=-D WAH_DEBUG
)
set run=0
for %%i in (wah_test_%1*.c) do (
echo ## Running %%i...
clang -W -Wall -Wextra %cflags% %%i -o %%~ni && %%~ni
if !errorlevel! neq 0 (
echo.
echo ## %%i failed.
goto end
)
echo.
set run=1
)
if "%run%" == "1" (
echo ## All tests passed.
) else (
echo ## No tests run.
)
:end
// WebAssembly interpreter in a Header file (WAH)
#ifndef WAH_H
#define WAH_H
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <assert.h>
typedef enum {
WAH_OK = 0,
WAH_ERROR_INVALID_MAGIC_NUMBER,
WAH_ERROR_INVALID_VERSION,
WAH_ERROR_UNEXPECTED_EOF,
WAH_ERROR_UNKNOWN_SECTION,
WAH_ERROR_TOO_LARGE,
WAH_ERROR_OUT_OF_MEMORY,
WAH_ERROR_VALIDATION_FAILED,
WAH_ERROR_TRAP,
WAH_ERROR_CALL_STACK_OVERFLOW,
WAH_ERROR_MEMORY_OUT_OF_BOUNDS,
WAH_ERROR_NOT_FOUND,
WAH_ERROR_MISUSE,
} wah_error_t;
// 128-bit vector type
typedef union {
uint8_t u8[16];
uint16_t u16[8];
uint32_t u32[4];
uint64_t u64[2];
int8_t i8[16];
int16_t i16[8];
int32_t i32[4];
int64_t i64[2];
float f32[4];
double f64[2];
} wah_v128_t;
typedef union {
int32_t i32;
int64_t i64;
float f32;
double f64;
wah_v128_t v128;
} wah_value_t;
typedef int32_t wah_type_t;
#define WAH_TYPE_I32 -1
#define WAH_TYPE_I64 -2
#define WAH_TYPE_F32 -3
#define WAH_TYPE_F64 -4
#define WAH_TYPE_V128 -5
#define WAH_TYPE_IS_FUNCTION(t) ((t) == -100)
#define WAH_TYPE_IS_MEMORY(t) ((t) == -200)
#define WAH_TYPE_IS_TABLE(t) ((t) == -300)
#define WAH_TYPE_IS_GLOBAL(t) ((t) >= -5)
typedef uint64_t wah_entry_id_t;
typedef struct {
wah_entry_id_t id;
wah_type_t type;
const char *name;
size_t name_len;
bool is_mutable;
// Semi-private fields:
union {
wah_value_t global_val; // For WAH_TYPE_IS_GLOBAL
struct { // For WAH_TYPE_IS_MEMORY
uint32_t min_pages, max_pages;
} memory;
struct { // For WAH_TYPE_IS_TABLE
wah_type_t elem_type;
uint32_t min_elements, max_elements;
} table;
struct { // For WAH_TYPE_FUNCTION
uint32_t param_count, result_count;
const wah_type_t *param_types, *result_types;
} func;
} u;
} wah_entry_t;
typedef struct wah_module_s {
uint32_t type_count;
uint32_t function_count;
uint32_t code_count;
uint32_t global_count;
uint32_t memory_count;
uint32_t table_count;
uint32_t element_segment_count;
uint32_t data_segment_count;
uint32_t export_count;
uint32_t start_function_idx;
bool has_start_function;
bool has_data_count_section; // True if data count section was present
uint32_t min_data_segment_count_required;
struct wah_func_type_s *types;
uint32_t *function_type_indices; // Index into the types array
struct wah_code_body_s *code_bodies;
struct wah_global_s *globals;
struct wah_memory_type_s *memories;
struct wah_table_type_s *tables;
struct wah_element_segment_s *element_segments;
struct wah_data_segment_s *data_segments;
struct wah_export_s *exports;
} wah_module_t;
typedef struct wah_exec_context_s {
wah_value_t *value_stack; // A single, large stack for operands and locals
uint32_t sp; // Stack pointer for the value_stack (points to next free slot)
uint32_t value_stack_capacity;
struct wah_call_frame_s *call_stack; // The call frame stack
uint32_t call_depth; // Current call depth (top of the call_stack)
uint32_t call_stack_capacity;
uint32_t max_call_depth; // Configurable max call depth
wah_value_t *globals; // Mutable global values
uint32_t global_count;
const struct wah_module_s *module;
// Memory
uint8_t *memory_base; // Pointer to the allocated memory
uint32_t memory_size; // Current size of the memory in bytes
wah_value_t **tables;
uint32_t table_count;
} wah_exec_context_t;
// Convert error code to human-readable string
const char *wah_strerror(wah_error_t err);
wah_error_t wah_parse_module(const uint8_t *wasm_binary, size_t binary_size, wah_module_t *module);
size_t wah_module_num_exports(const wah_module_t *module);
wah_error_t wah_module_export(const wah_module_t *module, size_t idx, wah_entry_t *out);
wah_error_t wah_module_export_by_name(const wah_module_t *module, const char *name, wah_entry_t *out);
wah_error_t wah_module_entry(const wah_module_t *module, wah_entry_id_t entry_id, wah_entry_t *out);
// Creates and initializes an execution context.
wah_error_t wah_exec_context_create(wah_exec_context_t *exec_ctx, const wah_module_t *module);
// Destroys and frees resources of an execution context.
void wah_exec_context_destroy(wah_exec_context_t *exec_ctx);
// The main entry point to call a WebAssembly function.
wah_error_t wah_call(wah_exec_context_t *exec_ctx, const wah_module_t *module, uint32_t func_idx, const wah_value_t *params, uint32_t param_count, wah_value_t *result);
// --- Module Cleanup ---
void wah_free_module(wah_module_t *module);
wah_error_t wah_module_entry(const wah_module_t *module, wah_entry_id_t entry_id, wah_entry_t *out);
// Accessors for wah_entry_t
static inline int32_t wah_entry_i32(const wah_entry_t *entry) {
assert(entry);
return entry->type == WAH_TYPE_I32 ? entry->u.global_val.i32 : 0;
}
static inline int64_t wah_entry_i64(const wah_entry_t *entry) {
assert(entry);
return entry->type == WAH_TYPE_I64 ? entry->u.global_val.i64 : 0;
}
static inline float wah_entry_f32(const wah_entry_t *entry) {
assert(entry);
return entry->type == WAH_TYPE_F32 ? entry->u.global_val.f32 : 0.0f / 0.0f;
}
static inline double wah_entry_f64(const wah_entry_t *entry) {
assert(entry);
return entry->type == WAH_TYPE_F64 ? entry->u.global_val.f64 : 0.0 / 0.0;
}
static inline wah_error_t wah_entry_memory(const wah_entry_t *entry, uint32_t *min_pages, uint32_t *max_pages) {
if (!entry) return WAH_ERROR_MISUSE;
if (!min_pages) return WAH_ERROR_MISUSE;
if (!max_pages) return WAH_ERROR_MISUSE;
if (!WAH_TYPE_IS_MEMORY(entry->type)) return WAH_ERROR_MISUSE;
*min_pages = entry->u.memory.min_pages;
*max_pages = entry->u.memory.max_pages;
return WAH_OK;
}
static inline wah_error_t wah_entry_table(const wah_entry_t *entry, wah_type_t *elem_type, uint32_t *min_elements, uint32_t *max_elements) {
if (!entry) return WAH_ERROR_MISUSE;
if (!elem_type) return WAH_ERROR_MISUSE;
if (!min_elements) return WAH_ERROR_MISUSE;
if (!max_elements) return WAH_ERROR_MISUSE;
if (!WAH_TYPE_IS_TABLE(entry->type)) return WAH_ERROR_MISUSE;
*elem_type = entry->u.table.elem_type; // Directly assign wah_type_t
*min_elements = entry->u.table.min_elements;
*max_elements = entry->u.table.max_elements;
return WAH_OK;
}
static inline wah_error_t wah_entry_func(const wah_entry_t *entry,
uint32_t *out_nargs, const wah_type_t **out_args,
uint32_t *out_nrets, const wah_type_t **out_rets) {
if (!entry) return WAH_ERROR_MISUSE;
if (!out_nargs) return WAH_ERROR_MISUSE;
if (!out_args) return WAH_ERROR_MISUSE;
if (!out_nrets) return WAH_ERROR_MISUSE;
if (!out_rets) return WAH_ERROR_MISUSE;
if (!WAH_TYPE_IS_FUNCTION(entry->type)) return WAH_ERROR_MISUSE;
*out_nargs = entry->u.func.param_count;
*out_args = entry->u.func.param_types;
*out_nrets = entry->u.func.result_count;
*out_rets = entry->u.func.result_types;
return WAH_OK;
}
////////////////////////////////////////////////////////////////////////////////
#ifdef WAH_IMPLEMENTATION
#include <string.h> // For memcpy, memset
#include <stdlib.h> // For malloc, free
#include <assert.h> // For assert
#include <stdint.h> // For INT32_MIN, INT32_MAX
#include <math.h> // For floating-point functions
#if defined(_MSC_VER)
#include <intrin.h> // For MSVC intrinsics
#endif
#ifdef WAH_DEBUG
#include <stdio.h>
#define WAH_LOG(fmt, ...) printf("(%d) " fmt "\n", __LINE__, ##__VA_ARGS__)
#else
#define WAH_LOG(fmt, ...) (void)(0)
#endif
#define WAH_TYPE_FUNCTION -100
#define WAH_TYPE_MEMORY -200
#define WAH_TYPE_TABLE -300
#define WAH_TYPE_FUNCREF WAH_TYPE_FUNCTION
#define WAH_TYPE_ANY -99
#define WAH_ENTRY_KIND_FUNCTION 0
#define WAH_ENTRY_KIND_TABLE 1
#define WAH_ENTRY_KIND_MEMORY 2
#define WAH_ENTRY_KIND_GLOBAL 3
#define WAH_MAKE_ENTRY_ID(kind, index) (((wah_entry_id_t)(kind) << 32) | (index))
#define WAH_GET_ENTRY_KIND(id) ((uint32_t)((id) >> 32))
#define WAH_GET_ENTRY_INDEX(id) ((uint32_t)((id) & 0xFFFFFFFF))
// Opcode-remapping constants for compact lookup table.
#define WAH_FB 0xd7 // 0xFB 0x00..1E -> 0xD7..F5
#define WAH_FC 0xf6 // 0xFC 0x00..11 -> 0xF6..107
#define WAH_FD 0x107 // 0xFD 0x00..113 -> 0x107..21A
#define WAH_FE 0x21b // (sentinel)
// --- WebAssembly Opcodes (subset) ---
#define WAH_OPCODES(X) \
/* Control Flow Operators */ \
X(UNREACHABLE, 0x00) X(NOP, 0x01) X(BLOCK, 0x02) X(LOOP, 0x03) X(IF, 0x04) X(ELSE, 0x05) X(END, 0x0B) \
X(BR, 0x0C) X(BR_IF, 0x0D) X(BR_TABLE, 0x0E) X(RETURN, 0x0F) X(CALL, 0x10) X(CALL_INDIRECT, 0x11) \
\
/* Parametric Operators */ \
X(DROP, 0x1A) X(SELECT, 0x1B) \
\
/* Variable Access */ \
X(LOCAL_GET, 0x20) X(LOCAL_SET, 0x21) X(LOCAL_TEE, 0x22) X(GLOBAL_GET, 0x23) X(GLOBAL_SET, 0x24) \
\
/* Memory Operators */ \
X(I32_LOAD, 0x28) X(I64_LOAD, 0x29) X(F32_LOAD, 0x2A) X(F64_LOAD, 0x2B) \
X(I32_LOAD8_S, 0x2C) X(I32_LOAD8_U, 0x2D) X(I32_LOAD16_S, 0x2E) X(I32_LOAD16_U, 0x2F) \
X(I64_LOAD8_S, 0x30) X(I64_LOAD8_U, 0x31) X(I64_LOAD16_S, 0x32) X(I64_LOAD16_U, 0x33) \
X(I64_LOAD32_S, 0x34) X(I64_LOAD32_U, 0x35) \
X(I32_STORE, 0x36) X(I64_STORE, 0x37) X(F32_STORE, 0x38) X(F64_STORE, 0x39) \
X(I32_STORE8, 0x3A) X(I32_STORE16, 0x3B) X(I64_STORE8, 0x3C) X(I64_STORE16, 0x3D) X(I64_STORE32, 0x3E) \
X(MEMORY_SIZE, 0x3F) X(MEMORY_GROW, 0x40) \
X(MEMORY_INIT, WAH_FC+0x08) X(MEMORY_COPY, WAH_FC+0x0A) X(MEMORY_FILL, WAH_FC+0x0B) \
\
/* Vector Memory Operators */ \
X(V128_LOAD, WAH_FD+0x00) \
X(V128_LOAD8X8_S, WAH_FD+0x01) X(V128_LOAD8X8_U, WAH_FD+0x02) \
X(V128_LOAD16X4_S, WAH_FD+0x03) X(V128_LOAD16X4_U, WAH_FD+0x04) \
X(V128_LOAD32X2_S, WAH_FD+0x05) X(V128_LOAD32X2_U, WAH_FD+0x06) \
X(V128_LOAD8_SPLAT, WAH_FD+0x07) X(V128_LOAD16_SPLAT, WAH_FD+0x08) \
X(V128_LOAD32_SPLAT, WAH_FD+0x09) X(V128_LOAD64_SPLAT, WAH_FD+0x0A) \
X(V128_LOAD32_ZERO, WAH_FD+0x5C) X(V128_LOAD64_ZERO, WAH_FD+0x5D) \
X(V128_LOAD8_LANE, WAH_FD+0x54) X(V128_LOAD16_LANE, WAH_FD+0x55) \
X(V128_LOAD32_LANE, WAH_FD+0x56) X(V128_LOAD64_LANE, WAH_FD+0x57) \
X(V128_STORE, WAH_FD+0x0B) \
\
/* Vector Lane Operations */ \
X(I8X16_SHUFFLE, WAH_FD+0x0D) X(I8X16_SWIZZLE, WAH_FD+0x0E) \
X(I8X16_EXTRACT_LANE_S, WAH_FD+0x15) X(I8X16_EXTRACT_LANE_U, WAH_FD+0x16) X(I8X16_REPLACE_LANE, WAH_FD+0x17) \
X(I16X8_EXTRACT_LANE_S, WAH_FD+0x18) X(I16X8_EXTRACT_LANE_U, WAH_FD+0x19) X(I16X8_REPLACE_LANE, WAH_FD+0x1A) \
X(I32X4_EXTRACT_LANE, WAH_FD+0x1B) X(I32X4_REPLACE_LANE, WAH_FD+0x1C) \
X(I64X2_EXTRACT_LANE, WAH_FD+0x1D) X(I64X2_REPLACE_LANE, WAH_FD+0x1E) \
X(F32X4_EXTRACT_LANE, WAH_FD+0x1F) X(F32X4_REPLACE_LANE, WAH_FD+0x20) \
X(F64X2_EXTRACT_LANE, WAH_FD+0x21) X(F64X2_REPLACE_LANE, WAH_FD+0x22) \
X(I8X16_SPLAT, WAH_FD+0x0F) X(I16X8_SPLAT, WAH_FD+0x10) X(I32X4_SPLAT, WAH_FD+0x11) X(I64X2_SPLAT, WAH_FD+0x12) \
X(F32X4_SPLAT, WAH_FD+0x13) X(F64X2_SPLAT, WAH_FD+0x14) \
\
/* Constants */ \
X(I32_CONST, 0x41) X(I64_CONST, 0x42) X(F32_CONST, 0x43) X(F64_CONST, 0x44) X(V128_CONST, WAH_FD+0x0C) \
\
/* Comparison Operators */ \
X(I32_EQZ, 0x45) X(I32_EQ, 0x46) X(I32_NE, 0x47) \
X(I32_LT_S, 0x48) X(I32_LT_U, 0x49) X(I32_GT_S, 0x4A) X(I32_GT_U, 0x4B) \
X(I32_LE_S, 0x4C) X(I32_LE_U, 0x4D) X(I32_GE_S, 0x4E) X(I32_GE_U, 0x4F) \
X(I64_EQZ, 0x50) X(I64_EQ, 0x51) X(I64_NE, 0x52) \
X(I64_LT_S, 0x53) X(I64_LT_U, 0x54) X(I64_GT_S, 0x55) X(I64_GT_U, 0x56) \
X(I64_LE_S, 0x57) X(I64_LE_U, 0x58) X(I64_GE_S, 0x59) X(I64_GE_U, 0x5A) \
X(F32_EQ, 0x5B) X(F32_NE, 0x5C) X(F32_LT, 0x5D) X(F32_GT, 0x5E) X(F32_LE, 0x5F) X(F32_GE, 0x60) \
X(F64_EQ, 0x61) X(F64_NE, 0x62) X(F64_LT, 0x63) X(F64_GT, 0x64) X(F64_LE, 0x65) X(F64_GE, 0x66) \
\
/* Numeric Operators */ \
X(I32_CLZ, 0x67) X(I32_CTZ, 0x68) X(I32_POPCNT, 0x69) X(I32_ADD, 0x6A) X(I32_SUB, 0x6B) \
X(I32_MUL, 0x6C) X(I32_DIV_S, 0x6D) X(I32_DIV_U, 0x6E) X(I32_REM_S, 0x6F) X(I32_REM_U, 0x70) \
X(I32_AND, 0x71) X(I32_OR, 0x72) X(I32_XOR, 0x73) \
X(I32_SHL, 0x74) X(I32_SHR_S, 0x75) X(I32_SHR_U, 0x76) X(I32_ROTL, 0x77) X(I32_ROTR, 0x78) \
X(I64_CLZ, 0x79) X(I64_CTZ, 0x7A) X(I64_POPCNT, 0x7B) X(I64_ADD, 0x7C) X(I64_SUB, 0x7D) \
X(I64_MUL, 0x7E) X(I64_DIV_S, 0x7F) X(I64_DIV_U, 0x80) X(I64_REM_S, 0x81) X(I64_REM_U, 0x82) \
X(I64_AND, 0x83) X(I64_OR, 0x84) X(I64_XOR, 0x85) \
X(I64_SHL, 0x86) X(I64_SHR_S, 0x87) X(I64_SHR_U, 0x88) X(I64_ROTL, 0x89) X(I64_ROTR, 0x8A) \
X(F32_ABS, 0x8B) X(F32_NEG, 0x8C) X(F32_CEIL, 0x8D) X(F32_FLOOR, 0x8E) X(F32_TRUNC, 0x8F) X(F32_NEAREST, 0x90) \
X(F32_SQRT, 0x91) X(F32_ADD, 0x92) X(F32_SUB, 0x93) X(F32_MUL, 0x94) X(F32_DIV, 0x95) \
X(F32_MIN, 0x96) X(F32_MAX, 0x97) X(F32_COPYSIGN, 0x98) \
X(F64_ABS, 0x99) X(F64_NEG, 0x9A) X(F64_CEIL, 0x9B) X(F64_FLOOR, 0x9C) X(F64_TRUNC, 0x9D) X(F64_NEAREST, 0x9E) \
X(F64_SQRT, 0x9F) X(F64_ADD, 0xA0) X(F64_SUB, 0xA1) X(F64_MUL, 0xA2) X(F64_DIV, 0xA3) \
X(F64_MIN, 0xA4) X(F64_MAX, 0xA5) X(F64_COPYSIGN, 0xA6) \
\
/* Vector Numeric Operators */ \
X(V128_NOT, WAH_FD+0x4D) X(V128_AND, WAH_FD+0x4E) X(V128_ANDNOT, WAH_FD+0x4F) X(V128_OR, WAH_FD+0x50) X(V128_XOR, WAH_FD+0x51) \
X(V128_BITSELECT, WAH_FD+0x52) X(V128_ANY_TRUE, WAH_FD+0x53) \
\
X(I8X16_ABS, WAH_FD+0x60) X(I8X16_NEG, WAH_FD+0x61) X(I8X16_POPCNT, WAH_FD+0x62) \
X(I8X16_ALL_TRUE, WAH_FD+0x63) X(I8X16_BITMASK, WAH_FD+0x64) \
X(I8X16_NARROW_I16X8_S, WAH_FD+0x65) X(I8X16_NARROW_I16X8_U, WAH_FD+0x66) \
X(I8X16_SHL, WAH_FD+0x6B) X(I8X16_SHR_S, WAH_FD+0x6C) X(I8X16_SHR_U, WAH_FD+0x6D) \
X(I8X16_ADD, WAH_FD+0x6E) X(I8X16_ADD_SAT_S, WAH_FD+0x6F) X(I8X16_ADD_SAT_U, WAH_FD+0x70) \
X(I8X16_SUB, WAH_FD+0x71) X(I8X16_SUB_SAT_S, WAH_FD+0x72) X(I8X16_SUB_SAT_U, WAH_FD+0x73) \
X(I8X16_MIN_S, WAH_FD+0x76) X(I8X16_MIN_U, WAH_FD+0x77) X(I8X16_MAX_S, WAH_FD+0x78) X(I8X16_MAX_U, WAH_FD+0x79) \
X(I8X16_AVGR_U, WAH_FD+0x7B) \
\
X(I16X8_EXTADD_PAIRWISE_I8X16_S, WAH_FD+0x7C) X(I16X8_EXTADD_PAIRWISE_I8X16_U, WAH_FD+0x7D) \
X(I16X8_ABS, WAH_FD+0x80) X(I16X8_NEG, WAH_FD+0x81) X(I16X8_Q15MULR_SAT_S, WAH_FD+0x82) \
X(I16X8_ALL_TRUE, WAH_FD+0x83) X(I16X8_BITMASK, WAH_FD+0x84) \
X(I16X8_NARROW_I32X4_S, WAH_FD+0x85) X(I16X8_NARROW_I32X4_U, WAH_FD+0x86) \
X(I16X8_EXTEND_LOW_I8X16_S, WAH_FD+0x87) X(I16X8_EXTEND_HIGH_I8X16_S, WAH_FD+0x88) \
X(I16X8_EXTEND_LOW_I8X16_U, WAH_FD+0x89) X(I16X8_EXTEND_HIGH_I8X16_U, WAH_FD+0x8A) \
X(I16X8_SHL, WAH_FD+0x8B) X(I16X8_SHR_S, WAH_FD+0x8C) X(I16X8_SHR_U, WAH_FD+0x8D) \
X(I16X8_ADD, WAH_FD+0x8E) X(I16X8_ADD_SAT_S, WAH_FD+0x8F) X(I16X8_ADD_SAT_U, WAH_FD+0x90) \
X(I16X8_SUB, WAH_FD+0x91) X(I16X8_SUB_SAT_S, WAH_FD+0x92) X(I16X8_SUB_SAT_U, WAH_FD+0x93) X(I16X8_MUL, WAH_FD+0x95) \
X(I16X8_MIN_S, WAH_FD+0x96) X(I16X8_MIN_U, WAH_FD+0x97) X(I16X8_MAX_S, WAH_FD+0x98) X(I16X8_MAX_U, WAH_FD+0x99) \
X(I16X8_AVGR_U, WAH_FD+0x9B) \
X(I16X8_EXTMUL_LOW_I8X16_S, WAH_FD+0x9C) X(I16X8_EXTMUL_HIGH_I8X16_S, WAH_FD+0x9D) \
X(I16X8_EXTMUL_LOW_I8X16_U, WAH_FD+0x9E) X(I16X8_EXTMUL_HIGH_I8X16_U, WAH_FD+0x9F) \
\
X(I32X4_EXTADD_PAIRWISE_I16X8_S, WAH_FD+0x7E) X(I32X4_EXTADD_PAIRWISE_I16X8_U, WAH_FD+0x7F) \
X(I32X4_ABS, WAH_FD+0xA0) X(I32X4_NEG, WAH_FD+0xA1) X(I32X4_ALL_TRUE, WAH_FD+0xA3) X(I32X4_BITMASK, WAH_FD+0xA4) \
X(I32X4_EXTEND_LOW_I16X8_S, WAH_FD+0xA7) X(I32X4_EXTEND_HIGH_I16X8_S, WAH_FD+0xA8) \
X(I32X4_EXTEND_LOW_I16X8_U, WAH_FD+0xA9) X(I32X4_EXTEND_HIGH_I16X8_U, WAH_FD+0xAA) \
X(I32X4_SHL, WAH_FD+0xAB) X(I32X4_SHR_S, WAH_FD+0xAC) X(I32X4_SHR_U, WAH_FD+0xAD) \
X(I32X4_ADD, WAH_FD+0xAE) X(I32X4_SUB, WAH_FD+0xB1) X(I32X4_MUL, WAH_FD+0xB5) \
X(I32X4_MIN_S, WAH_FD+0xB6) X(I32X4_MIN_U, WAH_FD+0xB7) X(I32X4_MAX_S, WAH_FD+0xB8) X(I32X4_MAX_U, WAH_FD+0xB9) \
X(I32X4_DOT_I16X8_S, WAH_FD+0xBA) \
X(I32X4_EXTMUL_LOW_I16X8_S, WAH_FD+0xBC) X(I32X4_EXTMUL_HIGH_I16X8_S, WAH_FD+0xBD) \
X(I32X4_EXTMUL_LOW_I16X8_U, WAH_FD+0xBE) X(I32X4_EXTMUL_HIGH_I16X8_U, WAH_FD+0xBF) \
\
X(I64X2_ABS, WAH_FD+0xC0) X(I64X2_NEG, WAH_FD+0xC1) X(I64X2_ALL_TRUE, WAH_FD+0xC3) X(I64X2_BITMASK, WAH_FD+0xC4) \
X(I64X2_EXTEND_LOW_I32X4_S, WAH_FD+0xC7) X(I64X2_EXTEND_HIGH_I32X4_S, WAH_FD+0xC8) \
X(I64X2_EXTEND_LOW_I32X4_U, WAH_FD+0xC9) X(I64X2_EXTEND_HIGH_I32X4_U, WAH_FD+0xCA) \
X(I64X2_SHL, WAH_FD+0xCB) X(I64X2_SHR_S, WAH_FD+0xCC) X(I64X2_SHR_U, WAH_FD+0xCD) \
X(I64X2_ADD, WAH_FD+0xCE) X(I64X2_SUB, WAH_FD+0xD1) X(I64X2_MUL, WAH_FD+0xD5) \
X(I64X2_EXTMUL_LOW_I32X4_S, WAH_FD+0xDC) X(I64X2_EXTMUL_HIGH_I32X4_S, WAH_FD+0xDD) \
X(I64X2_EXTMUL_LOW_I32X4_U, WAH_FD+0xDE) X(I64X2_EXTMUL_HIGH_I32X4_U, WAH_FD+0xDF) \
\
X(F32X4_CEIL, WAH_FD+0x67) X(F32X4_FLOOR, WAH_FD+0x68) X(F32X4_TRUNC, WAH_FD+0x69) X(F32X4_NEAREST, WAH_FD+0x6A) \
X(F32X4_ABS, WAH_FD+0xE0) X(F32X4_NEG, WAH_FD+0xE1) X(F32X4_SQRT, WAH_FD+0xE3) \
X(F32X4_ADD, WAH_FD+0xE4) X(F32X4_SUB, WAH_FD+0xE5) X(F32X4_MUL, WAH_FD+0xE6) X(F32X4_DIV, WAH_FD+0xE7) \
X(F32X4_MIN, WAH_FD+0xE8) X(F32X4_MAX, WAH_FD+0xE9) X(F32X4_PMIN, WAH_FD+0xEA) X(F32X4_PMAX, WAH_FD+0xEB) \
\
X(F64X2_CEIL, WAH_FD+0x74) X(F64X2_FLOOR, WAH_FD+0x75) X(F64X2_TRUNC, WAH_FD+0x7A) X(F64X2_NEAREST, WAH_FD+0x94) \
X(F64X2_ABS, WAH_FD+0xEC) X(F64X2_NEG, WAH_FD+0xED) X(F64X2_SQRT, WAH_FD+0xEF) \
X(F64X2_ADD, WAH_FD+0xF0) X(F64X2_SUB, WAH_FD+0xF1) X(F64X2_MUL, WAH_FD+0xF2) X(F64X2_DIV, WAH_FD+0xF3) \
X(F64X2_MIN, WAH_FD+0xF4) X(F64X2_MAX, WAH_FD+0xF5) X(F64X2_PMIN, WAH_FD+0xF6) X(F64X2_PMAX, WAH_FD+0xF7) \
\
X(I32X4_TRUNC_SAT_F32X4_S, WAH_FD+0xF8) X(I32X4_TRUNC_SAT_F32X4_U, WAH_FD+0xF9) \
X(F32X4_CONVERT_I32X4_S, WAH_FD+0xFA) X(F32X4_CONVERT_I32X4_U, WAH_FD+0xFB) \
X(I32X4_TRUNC_SAT_F64X2_S_ZERO, WAH_FD+0xFC) X(I32X4_TRUNC_SAT_F64X2_U_ZERO, WAH_FD+0xFD) \
X(F64X2_CONVERT_LOW_I32X4_S, WAH_FD+0xFE) X(F64X2_CONVERT_LOW_I32X4_U, WAH_FD+0xFF) \
X(F32X4_DEMOTE_F64X2_ZERO, WAH_FD+0x5E) X(F64X2_PROMOTE_LOW_F32X4, WAH_FD+0x5F) \
\
/* Vector Comparison Operators */ \
X(I8X16_EQ, WAH_FD+0x23) X(I8X16_NE, WAH_FD+0x24) \
X(I8X16_LT_S, WAH_FD+0x25) X(I8X16_LT_U, WAH_FD+0x26) X(I8X16_GT_S, WAH_FD+0x27) X(I8X16_GT_U, WAH_FD+0x28) \
X(I8X16_LE_S, WAH_FD+0x29) X(I8X16_LE_U, WAH_FD+0x2A) X(I8X16_GE_S, WAH_FD+0x2B) X(I8X16_GE_U, WAH_FD+0x2C) \
X(I16X8_EQ, WAH_FD+0x2D) X(I16X8_NE, WAH_FD+0x2E) \
X(I16X8_LT_S, WAH_FD+0x2F) X(I16X8_LT_U, WAH_FD+0x30) X(I16X8_GT_S, WAH_FD+0x31) X(I16X8_GT_U, WAH_FD+0x32) \
X(I16X8_LE_S, WAH_FD+0x33) X(I16X8_LE_U, WAH_FD+0x34) X(I16X8_GE_S, WAH_FD+0x35) X(I16X8_GE_U, WAH_FD+0x36) \
X(I32X4_EQ, WAH_FD+0x37) X(I32X4_NE, WAH_FD+0x38) \
X(I32X4_LT_S, WAH_FD+0x39) X(I32X4_LT_U, WAH_FD+0x3A) X(I32X4_GT_S, WAH_FD+0x3B) X(I32X4_GT_U, WAH_FD+0x3C) \
X(I32X4_LE_S, WAH_FD+0x3D) X(I32X4_LE_U, WAH_FD+0x3E) X(I32X4_GE_S, WAH_FD+0x3F) X(I32X4_GE_U, WAH_FD+0x40) \
X(I64X2_EQ, WAH_FD+0xD6) X(I64X2_NE, WAH_FD+0xD7) \
X(I64X2_LT_S, WAH_FD+0xD8) X(I64X2_GT_S, WAH_FD+0xD9) X(I64X2_LE_S, WAH_FD+0xDA) X(I64X2_GE_S, WAH_FD+0xDB) \
X(F32X4_EQ, WAH_FD+0x41) X(F32X4_NE, WAH_FD+0x42) \
X(F32X4_LT, WAH_FD+0x43) X(F32X4_GT, WAH_FD+0x44) X(F32X4_LE, WAH_FD+0x45) X(F32X4_GE, WAH_FD+0x46) \
X(F64X2_EQ, WAH_FD+0x47) X(F64X2_NE, WAH_FD+0x48) \
X(F64X2_LT, WAH_FD+0x49) X(F64X2_GT, WAH_FD+0x4A) X(F64X2_LE, WAH_FD+0x4B) X(F64X2_GE, WAH_FD+0x4C) \
\
/* Conversion Operators */ \
X(I32_WRAP_I64, 0xA7) X(I32_TRUNC_F32_S, 0xA8) X(I32_TRUNC_F32_U, 0xA9) X(I32_TRUNC_F64_S, 0xAA) X(I32_TRUNC_F64_U, 0xAB) \
X(I64_EXTEND_I32_S, 0xAC) X(I64_EXTEND_I32_U, 0xAD) \
X(I64_TRUNC_F32_S, 0xAE) X(I64_TRUNC_F32_U, 0xAF) X(I64_TRUNC_F64_S, 0xB0) X(I64_TRUNC_F64_U, 0xB1) \
X(F32_CONVERT_I32_S, 0xB2) X(F32_CONVERT_I32_U, 0xB3) X(F32_CONVERT_I64_S, 0xB4) X(F32_CONVERT_I64_U, 0xB5) X(F32_DEMOTE_F64, 0xB6) \
X(F64_CONVERT_I32_S, 0xB7) X(F64_CONVERT_I32_U, 0xB8) X(F64_CONVERT_I64_S, 0xB9) X(F64_CONVERT_I64_U, 0xBA) X(F64_PROMOTE_F32, 0xBB) \
X(I32_REINTERPRET_F32, 0xBC) X(I64_REINTERPRET_F64, 0xBD) X(F32_REINTERPRET_I32, 0xBE) X(F64_REINTERPRET_I64, 0xBF) \
X(I32_EXTEND8_S, 0xC0) X(I32_EXTEND16_S, 0xC1) X(I64_EXTEND8_S, 0xC2) X(I64_EXTEND16_S, 0xC3) X(I64_EXTEND32_S, 0xC4) \
X(I32_TRUNC_SAT_F32_S, WAH_FC+0x00) X(I32_TRUNC_SAT_F32_U, WAH_FC+0x01) \
X(I32_TRUNC_SAT_F64_S, WAH_FC+0x02) X(I32_TRUNC_SAT_F64_U, WAH_FC+0x03) \
X(I64_TRUNC_SAT_F32_S, WAH_FC+0x04) X(I64_TRUNC_SAT_F32_U, WAH_FC+0x05) \
X(I64_TRUNC_SAT_F64_S, WAH_FC+0x06) X(I64_TRUNC_SAT_F64_U, WAH_FC+0x07)
typedef enum {
#define WAH_OPCODE_INIT(name, val) WAH_OP_##name = val,
WAH_OPCODES(WAH_OPCODE_INIT)
#undef WAH_OPCODE_INIT
} wah_opcode_t;
// --- Memory Structure ---
#define WAH_WASM_PAGE_SIZE 65536 // 64 KB
typedef struct wah_memory_type_s {
uint32_t min_pages, max_pages;
} wah_memory_type_t;
// --- WebAssembly Table Structures ---
typedef struct wah_table_type_s {
wah_type_t elem_type;
uint32_t min_elements;
uint32_t max_elements; // 0 if no maximum
} wah_table_type_t;
typedef struct wah_data_segment_s {
uint32_t flags;
uint32_t memory_idx; // Only for active segments (flags & 0x02)
uint32_t offset; // Result of the offset_expr (i32.const X end)
uint32_t data_len;
const uint8_t *data; // Pointer to the raw data bytes within the WASM binary
} wah_data_segment_t;
typedef struct wah_export_s {
const char *name;
size_t name_len;
uint8_t kind; // WASM export kind (0=func, 1=table, 2=mem, 3=global)
uint32_t index; // Index into the respective module array (functions, tables, etc.)
} wah_export_t;
// --- WebAssembly Element Segment Structure ---
typedef struct wah_element_segment_s {
uint32_t table_idx;
uint32_t offset; // Result of the offset_expr
uint32_t num_elems;
uint32_t *func_indices; // Array of function indices
}
wah_element_segment_t;
// --- Operand Stack ---
typedef struct {
wah_value_t *data; // Dynamically allocated based on function requirements
uint32_t sp; // Stack pointer
uint32_t capacity; // Allocated capacity
} wah_stack_t;
// --- Type Stack for Validation ---
#define WAH_MAX_TYPE_STACK_SIZE 1024 // Maximum size of the type stack for validation
typedef struct {
wah_type_t data[WAH_MAX_TYPE_STACK_SIZE];
uint32_t sp; // Stack pointer
} wah_type_stack_t;
// --- Execution Context ---
// Represents a single function call's state on the call stack.
typedef struct wah_call_frame_s {
const uint8_t *bytecode_ip; // Instruction pointer into the parsed bytecode
const struct wah_code_body_s *code; // The function body being executed
uint32_t locals_offset; // Offset into the shared value_stack for this frame's locals
uint32_t func_idx; // Index of the function being executed
} wah_call_frame_t;
// The main context for the entire WebAssembly interpretation.
#define WAH_DEFAULT_MAX_CALL_DEPTH 1024
#define WAH_DEFAULT_VALUE_STACK_SIZE (64 * 1024)
// --- Function Type ---
typedef struct wah_func_type_s {
uint32_t param_count;
uint32_t result_count;
wah_type_t *param_types;
wah_type_t *result_types;
} wah_func_type_t;
// --- Pre-parsed Opcode Structure for Optimized Execution ---
typedef struct {
uint8_t *bytecode; // Combined array of opcodes and arguments
uint32_t bytecode_size; // Total size of the bytecode array
} wah_parsed_code_t;
// --- Code Body Structure ---
typedef struct wah_code_body_s {
uint32_t local_count;
wah_type_t *local_types; // Array of types for local variables
uint32_t code_size;
const uint8_t *code; // Pointer to the raw instruction bytes within the WASM binary
uint32_t max_stack_depth; // Maximum operand stack depth required
wah_parsed_code_t parsed_code; // Pre-parsed opcodes and arguments for optimized execution
} wah_code_body_t;
// --- Global Variable Structure ---
typedef struct wah_global_s {
wah_type_t type;
bool is_mutable;
wah_value_t initial_value; // Stored after parsing the init_expr
} wah_global_t;
// --- Validation Context ---
#define WAH_MAX_CONTROL_DEPTH 256
typedef struct {
wah_opcode_t opcode;
uint32_t type_stack_sp; // Type stack pointer at the start of the block
wah_func_type_t block_type; // For if/block/loop
bool else_found; // For if blocks
bool is_unreachable; // True if this control frame is currently unreachable
uint32_t stack_height; // Stack height at the beginning of the block
} wah_validation_control_frame_t;
typedef struct {
wah_type_stack_t type_stack;
const wah_func_type_t *func_type; // Type of the function being validated
wah_module_t *module; // Reference to the module for global/function lookups
uint32_t total_locals; // Total number of locals (params + declared locals)
uint32_t current_stack_depth; // Current stack depth during validation
uint32_t max_stack_depth; // Maximum stack depth seen during validation
bool is_unreachable; // True if the current code path is unreachable
// Control flow validation stack
wah_validation_control_frame_t control_stack[WAH_MAX_CONTROL_DEPTH];
uint32_t control_sp;
} wah_validation_context_t;
// --- Helper Macros ---
#define WAH_CHECK(expr) do { \
wah_error_t _err = (expr); \
if (_err != WAH_OK) { WAH_LOG("WAH_CHECK(%s) failed due to: %s", #expr, wah_strerror(_err)); return _err; } \
} while(0)
#define WAH_CHECK_GOTO(expr, label) do { \
err = (expr); \
if (err != WAH_OK) { WAH_LOG("WAH_CHECK_GOTO(%s, %s) failed due to: %s", #expr, #label, wah_strerror(err)); goto label; } \
} while(0)
#define WAH_ENSURE(cond, error) do { \
if (!(cond)) { WAH_LOG("WAH_ENSURE(%s, %s) failed", #cond, #error); return (error); } \
} while(0)
#define WAH_ENSURE_GOTO(cond, error, label) do { \
if (!(cond)) { err = (error); WAH_LOG("WAH_ENSURE_GOTO(%s, %s, %s) failed", #cond, #error, #label); goto label; } \
} while(0)
// Helper macro to check for __builtin_xxx functions with __has_builtin
#if defined(__has_builtin)
#define WAH_HAS_BUILTIN(x) __has_builtin(x)
#else
#define WAH_HAS_BUILTIN(x) 0
#endif
// --- Safe Memory Allocation ---
static inline wah_error_t wah_malloc(size_t count, size_t elemsize, void** out_ptr) {
*out_ptr = NULL;
if (count == 0) {
return WAH_OK;
}
WAH_ENSURE(elemsize == 0 || count <= SIZE_MAX / elemsize, WAH_ERROR_OUT_OF_MEMORY);
size_t total_size = count * elemsize;
*out_ptr = malloc(total_size);
WAH_ENSURE(*out_ptr, WAH_ERROR_OUT_OF_MEMORY);
return WAH_OK;
}
static inline wah_error_t wah_realloc(size_t count, size_t elemsize, void** p_ptr) {
if (count == 0) {
free(*p_ptr);
*p_ptr = NULL;
return WAH_OK;
}
WAH_ENSURE(elemsize == 0 || count <= SIZE_MAX / elemsize, WAH_ERROR_OUT_OF_MEMORY);
size_t total_size = count * elemsize;
void* new_ptr = realloc(*p_ptr, total_size);
WAH_ENSURE(new_ptr, WAH_ERROR_OUT_OF_MEMORY);
*p_ptr = new_ptr;
return WAH_OK;
}
#define WAH_MALLOC_ARRAY(ptr, count) \
do { \
void *_alloc_ptr; \
wah_error_t _alloc_err = wah_malloc((count), sizeof(*(ptr)), &_alloc_ptr); \
if (_alloc_err != WAH_OK) { \
WAH_LOG("WAH_MALLOC_ARRAY(%s, %s) failed due to OOM", #ptr, #count); \
return _alloc_err; \
} \
(ptr) = _alloc_ptr; \
} while (0)
#define WAH_REALLOC_ARRAY(ptr, count) \
do { \
void *_alloc_ptr = (ptr); \
wah_error_t _alloc_err = wah_realloc((count), sizeof(*(ptr)), &_alloc_ptr); \
if (_alloc_err != WAH_OK) { \
WAH_LOG("WAH_REALLOC_ARRAY(%s, %s) failed due to OOM", #ptr, #count); \
return _alloc_err; \
} \
(ptr) = _alloc_ptr; \
} while (0)
#define WAH_MALLOC_ARRAY_GOTO(ptr, count, label) \
do { \
void* _alloc_ptr; \
err = wah_malloc((count), sizeof(*(ptr)), &_alloc_ptr); \
if (err != WAH_OK) { \
WAH_LOG("WAH_MALLOC_ARRAY_GOTO(%s, %s, %s) failed due to OOM", #ptr, #count, #label); \
goto label; \
} \
(ptr) = _alloc_ptr; \
} while (0)
#define WAH_REALLOC_ARRAY_GOTO(ptr, count, label) \
do { \
void* _alloc_ptr = ptr; \
err = wah_realloc((count), sizeof(*(ptr)), &_alloc_ptr); \
if (err != WAH_OK) { \
WAH_LOG("WAH_REALLOC_ARRAY_GOTO(%s, %s, %s) failed due to OOM", #ptr, #count, #label); \
goto label; \
} \
(ptr) = _alloc_ptr; \
} while (0)
const char *wah_strerror(wah_error_t err) {
switch (err) {
case WAH_OK: return "Success";
case WAH_ERROR_INVALID_MAGIC_NUMBER: return "Invalid WASM magic number";
case WAH_ERROR_INVALID_VERSION: return "Invalid WASM version";
case WAH_ERROR_UNEXPECTED_EOF: return "Unexpected end of file";
case WAH_ERROR_UNKNOWN_SECTION: return "Unknown section or opcode";
case WAH_ERROR_TOO_LARGE: return "exceeding implementation limits (or value too large)";
case WAH_ERROR_OUT_OF_MEMORY: return "Out of memory";
case WAH_ERROR_VALIDATION_FAILED: return "Validation failed";
case WAH_ERROR_TRAP: return "Runtime trap";
case WAH_ERROR_CALL_STACK_OVERFLOW: return "Call stack overflow";
case WAH_ERROR_MEMORY_OUT_OF_BOUNDS: return "Memory access out of bounds";
case WAH_ERROR_NOT_FOUND: return "Item not found";
case WAH_ERROR_MISUSE: return "API misused: invalid arguments";
default: return "Unknown error";
}
}
// --- LEB128 Decoding ---
// Helper function to decode an unsigned LEB128 integer
static inline wah_error_t wah_decode_uleb128(const uint8_t **ptr, const uint8_t *end, uint32_t *result) {
uint64_t val = 0;
uint32_t shift = 0;
uint8_t byte;
for (int i = 0; i < 5; ++i) { // Max 5 bytes for a 32-bit value
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
byte = *(*ptr)++;
val |= (uint64_t)(byte & 0x7F) << shift;
if ((byte & 0x80) == 0) {
WAH_ENSURE(val <= UINT32_MAX, WAH_ERROR_TOO_LARGE);
*result = (uint32_t)val;
return WAH_OK;
}
shift += 7;
}
// If we get here, it means the 5th byte had the continuation bit set.
return WAH_ERROR_TOO_LARGE;
}
// Helper function to decode a signed LEB128 integer (32-bit)
static inline wah_error_t wah_decode_sleb128_32(const uint8_t **ptr, const uint8_t *end, int32_t *result) {
uint64_t val = 0;
uint32_t shift = 0;
uint8_t byte;
for (int i = 0; i < 5; ++i) { // Max 5 bytes for a 32-bit value
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
byte = *(*ptr)++;
val |= (uint64_t)(byte & 0x7F) << shift;
shift += 7;
if ((byte & 0x80) == 0) {
// Sign extend
if (shift < 64) {
uint64_t sign_bit = 1ULL << (shift - 1);
if ((val & sign_bit) != 0) {
val |= ~0ULL << shift;
}
}
WAH_ENSURE((int64_t)val >= INT32_MIN && (int64_t)val <= INT32_MAX, WAH_ERROR_TOO_LARGE);
*result = (int32_t)val;
return WAH_OK;
}
}
// If we get here, it means the 5th byte had the continuation bit set.
return WAH_ERROR_TOO_LARGE;
}
// Helper function to decode an unsigned LEB128 integer (64-bit)
static inline wah_error_t wah_decode_uleb128_64(const uint8_t **ptr, const uint8_t *end, uint64_t *result) {
*result = 0;
uint64_t shift = 0;
uint8_t byte;
do {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
WAH_ENSURE(shift < 64, WAH_ERROR_TOO_LARGE);
byte = *(*ptr)++;
*result |= (uint64_t)(byte & 0x7F) << shift;
shift += 7;
} while (byte & 0x80);
return WAH_OK;
}
// Helper function to decode a signed LEB128 integer (64-bit)
static inline wah_error_t wah_decode_sleb128_64(const uint8_t **ptr, const uint8_t *end, int64_t *result) {
uint64_t val = 0;
uint32_t shift = 0;
uint8_t byte;
do {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
WAH_ENSURE(shift < 64, WAH_ERROR_TOO_LARGE);
byte = *(*ptr)++;
val |= (uint64_t)(byte & 0x7F) << shift;
shift += 7;
} while (byte & 0x80);
if (shift < 64) {
uint64_t sign_bit = 1ULL << (shift - 1);
if ((val & sign_bit) != 0) {
val |= ~0ULL << shift;
}
}
*result = (int64_t)val;
return WAH_OK;
}
// Helper function to validate if a byte sequence is valid UTF-8
static inline bool wah_is_valid_utf8(const char *s, size_t len) {
const unsigned char *bytes = (const unsigned char *)s;
size_t i = 0;
while (i < len) {
unsigned char byte = bytes[i];
if (byte < 0x80) { // 1-byte sequence (0xxxxxxx)
i++;
continue;
}
if ((byte & 0xE0) == 0xC0) { // 2-byte sequence (110xxxxx 10xxxxxx)
if (i + 1 >= len || (bytes[i+1] & 0xC0) != 0x80 || (byte & 0xFE) == 0xC0) return false; // Overlong encoding
i += 2;
continue;
}
if ((byte & 0xF0) == 0xE0) { // 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx)
if (i + 2 >= len || (bytes[i+1] & 0xC0) != 0x80 || (bytes[i+2] & 0xC0) != 0x80 ||
(byte == 0xE0 && bytes[i+1] < 0xA0) || // Overlong encoding
(byte == 0xED && bytes[i+1] >= 0xA0)) return false; // Surrogate pair
i += 3;
continue;
}
if ((byte & 0xF8) == 0xF0) { // 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
if (i + 3 >= len || (bytes[i+1] & 0xC0) != 0x80 || (bytes[i+2] & 0xC0) != 0x80 || (bytes[i+3] & 0xC0) != 0x80 ||
(byte == 0xF0 && bytes[i+1] < 0x90) || // Overlong encoding
(byte == 0xF4 && bytes[i+1] >= 0x90)) return false; // Codepoint > 0x10FFFF
i += 4;
continue;
}
return false; // Invalid start byte
}
return true;
}
// Helper to read a uint8_t from a byte array in little-endian format
static inline uint8_t wah_read_u8_le(const uint8_t *ptr) {
return ptr[0];
}
// Helper to write a uint8_t to a byte array in little-endian format
static inline void wah_write_u8_le(uint8_t *ptr, uint8_t val) {
ptr[0] = val;
}
// Helper to read a uint16_t from a byte array in little-endian format
static inline uint16_t wah_read_u16_le(const uint8_t *ptr) {
return ((uint16_t)ptr[0] << 0) |
((uint16_t)ptr[1] << 8);
}
// Helper to write a uint16_t to a byte array in little-endian format
static inline void wah_write_u16_le(uint8_t *ptr, uint16_t val) {
ptr[0] = (uint8_t)(val >> 0);
ptr[1] = (uint8_t)(val >> 8);
}
// Helper to read a uint32_t from a byte array in little-endian format
static inline uint32_t wah_read_u32_le(const uint8_t *ptr) {
return ((uint32_t)ptr[0] << 0) |
((uint32_t)ptr[1] << 8) |
((uint32_t)ptr[2] << 16) |
((uint32_t)ptr[3] << 24);
}
// Helper to read a uint64_t from a byte array in little-endian format
static inline uint64_t wah_read_u64_le(const uint8_t *ptr) {
return ((uint64_t)ptr[0] << 0) |
((uint64_t)ptr[1] << 8) |
((uint64_t)ptr[2] << 16) |
((uint64_t)ptr[3] << 24) |
((uint64_t)ptr[4] << 32) |
((uint64_t)ptr[5] << 40) |
((uint64_t)ptr[6] << 48) |
((uint64_t)ptr[7] << 56);
}
// Helper to write a uint32_t to a byte array in little-endian format
static inline void wah_write_u32_le(uint8_t *ptr, uint32_t val) {
ptr[0] = (uint8_t)(val >> 0);
ptr[1] = (uint8_t)(val >> 8);
ptr[2] = (uint8_t)(val >> 16);
ptr[3] = (uint8_t)(val >> 24);
}
// Helper to write a uint64_t to a byte array in little-endian format
static inline void wah_write_u64_le(uint8_t *ptr, uint64_t val) {
ptr[0] = (uint8_t)(val >> 0);
ptr[1] = (uint8_t)(val >> 8);
ptr[2] = (uint8_t)(val >> 16);
ptr[3] = (uint8_t)(val >> 24);
ptr[4] = (uint8_t)(val >> 32);
ptr[5] = (uint8_t)(val >> 40);
ptr[6] = (uint8_t)(val >> 48);
ptr[7] = (uint8_t)(val >> 56);
}
// Helper to read a float from a byte array in little-endian format
static inline float wah_read_f32_le(const uint8_t *ptr) {
union { uint32_t i; float f; } u = { .i = wah_read_u32_le(ptr) };
return u.f;
}
// Helper to read a double from a byte array in little-endian format
static inline double wah_read_f64_le(const uint8_t *ptr) {
union { uint64_t i; double d; } u = { .i = wah_read_u64_le(ptr) };
return u.d;
}
// Helper to write a float to a byte array in little-endian format
static inline void wah_write_f32_le(uint8_t *ptr, float val) {
union { uint32_t i; float f; } u = { .f = val };
wah_write_u32_le(ptr, u.i);
}
// Helper to write a double to a byte array in little-endian format
static inline void wah_write_f64_le(uint8_t *ptr, double val) {
union { uint64_t i; double f; } u = { .f = val };
wah_write_u64_le(ptr, u.i);
}
// --- Parser Functions ---
static wah_error_t wah_decode_opcode(const uint8_t **ptr, const uint8_t *end, uint16_t *opcode_val);
static wah_error_t wah_decode_val_type(const uint8_t **ptr, const uint8_t *end, wah_type_t *out_type);
static wah_error_t wah_decode_ref_type(const uint8_t **ptr, const uint8_t *end, wah_type_t *out_type);
// The core interpreter loop (internal).
static wah_error_t wah_run_interpreter(wah_exec_context_t *exec_ctx);
// Validation helper functions
static wah_error_t wah_validate_opcode(uint16_t opcode_val, const uint8_t **code_ptr, const uint8_t *code_end, wah_validation_context_t *vctx, const wah_code_body_t* code_body);
// Pre-parsing functions
static wah_error_t wah_preparse_code(const wah_module_t* module, uint32_t func_idx, const uint8_t *code, uint32_t code_size, wah_parsed_code_t *parsed_code);
static void wah_free_parsed_code(wah_parsed_code_t *parsed_code);
// WebAssembly canonical NaN bit patterns
//
// Note that WebAssembly prior to 3.0 had *two* canonical NaNs varying only by their signs.
// These are canonical in terms of the WebAssembly 3.0 deterministic profile, which needs a positive sign.
static const union { uint32_t i; float f; } WAH_CANONICAL_NAN32 = { .i = 0x7fc00000U };
static const union { uint64_t i; double f; } WAH_CANONICAL_NAN64 = { .i = 0x7ff8000000000000ULL };
static inline float wah_canonicalize_f32(float val) { return val == val ? val : WAH_CANONICAL_NAN32.f; }
static inline double wah_canonicalize_f64(double val) { return val == val ? val : WAH_CANONICAL_NAN64.f; }
// --- Integer Utility Functions ---
// popcnt
static inline uint32_t wah_popcount_u32(uint32_t n) {
#if WAH_HAS_BUILTIN(__builtin_popcount) || defined(__GNUC__)
return __builtin_popcount(n);
#elif defined(_MSC_VER)
return __popcnt(n);
#else
// Generic software implementation
uint32_t count = 0;
while (n > 0) {
n &= (n - 1);
count++;
}
return count;
#endif
}
static inline uint64_t wah_popcount_u64(uint64_t n) {
#if WAH_HAS_BUILTIN(__builtin_popcountll) || defined(__GNUC__)
return __builtin_popcountll(n);
#elif defined(_MSC_VER)
return __popcnt64(n);
#else
// Generic software implementation
uint64_t count = 0;
while (n > 0) {
n &= (n - 1);
count++;
}
return count;
#endif
}
static inline uint8_t wah_popcount_u8(uint8_t n) {
uint8_t count = 0;
while (n > 0) {
n &= (n - 1);
count++;
}
return count;
}
// clz (count leading zeros)
static inline uint32_t wah_clz_u32(uint32_t n) {
#if WAH_HAS_BUILTIN(__builtin_clz) || defined(__GNUC__)
return n == 0 ? 32 : __builtin_clz(n);
#elif defined(_MSC_VER)
unsigned long index;
if (_BitScanReverse(&index, n)) {
return 31 - index;
} else {
return 32; // All bits are zero
}
#else
// Generic software implementation
if (n == 0) return 32;
uint32_t count = 0;
if (n <= 0x0000FFFF) { count += 16; n <<= 16; }
if (n <= 0x00FFFFFF) { count += 8; n <<= 8; }
if (n <= 0x0FFFFFFF) { count += 4; n <<= 4; }
if (n <= 0x3FFFFFFF) { count += 2; n <<= 2; }
if (n <= 0x7FFFFFFF) { count += 1; }
return count;
#endif
}
static inline uint64_t wah_clz_u64(uint64_t n) {
#if WAH_HAS_BUILTIN(__builtin_clzll) || defined(__GNUC__)
return n == 0 ? 64 : __builtin_clzll(n);
#elif defined(_MSC_VER)
unsigned long index;
if (_BitScanReverse64(&index, n)) {
return 63 - index;
} else {
return 64; // All bits are zero
}
#else
// Generic software implementation
if (n == 0) return 64;
uint64_t count = 0;
if (n <= 0x00000000FFFFFFFFULL) { count += 32; n <<= 32; }
if (n <= 0x0000FFFFFFFFFFFFULL) { count += 16; n <<= 16; }
if (n <= 0x00FFFFFFFFFFFFFFULL) { count += 8; n <<= 8; }
if (n <= 0x0FFFFFFFFFFFFFFFULL) { count += 4; n <<= 4; }
if (n <= 0x3FFFFFFFFFFFFFFFULL) { count += 2; n <<= 2; }
if (n <= 0x7FFFFFFFFFFFFFFFULL) { count += 1; }
return count;
#endif
}
// ctz (count trailing zeros)
static inline uint32_t wah_ctz_u32(uint32_t n) {
#if WAH_HAS_BUILTIN(__builtin_ctz) || defined(__GNUC__)
return n == 0 ? 32 : __builtin_ctz(n);
#elif defined(_MSC_VER)
unsigned long index;
if (_BitScanForward(&index, n)) {
return index;
} else {
return 32; // All bits are zero
}
#else
// Generic software implementation
if (n == 0) return 32;
uint32_t count = 0;
while ((n & 1) == 0) {
n >>= 1;
count++;
}
return count;
#endif
}
static inline uint64_t wah_ctz_u64(uint64_t n) {
#if WAH_HAS_BUILTIN(__builtin_ctzll) || defined(__GNUC__)
return n == 0 ? 64 : __builtin_ctzll(n);
#elif defined(_MSC_VER)
unsigned long index;
if (_BitScanForward64(&index, n)) {
return index;
} else {
return 64; // All bits are zero
}
#else
// Generic software implementation
if (n == 0) return 64;
uint64_t count = 0;
while ((n & 1) == 0) {
n >>= 1;
count++;
}
return count;
#endif
}
// rotl (rotate left)
static inline uint32_t wah_rotl_u32(uint32_t n, uint32_t shift) {
#if WAH_HAS_BUILTIN(__builtin_rotateleft32)
return __builtin_rotateleft32(n, shift);
#elif defined(_MSC_VER)
return _rotl(n, shift);
#else
shift &= 31; // Ensure shift is within 0-31
return (n << shift) | (n >> (32 - shift));
#endif
}
static inline uint64_t wah_rotl_u64(uint64_t n, uint64_t shift) {
#if WAH_HAS_BUILTIN(__builtin_rotateleft64)
return __builtin_rotateleft64(n, shift);
#elif defined(_MSC_VER)
return _rotl64(n, shift);
#else
shift &= 63; // Ensure shift is within 0-63
return (n << shift) | (n >> (64 - shift));
#endif
}
// rotr (rotate right)
static inline uint32_t wah_rotr_u32(uint32_t n, uint32_t shift) {
#if WAH_HAS_BUILTIN(__builtin_rotateright32)
return __builtin_rotateright32(n, shift);
#elif defined(_MSC_VER)
return _rotr(n, shift);
#else
shift &= 31; // Ensure shift is within 0-31
return (n >> shift) | (n << (32 - shift));
#endif
}
static inline uint64_t wah_rotr_u64(uint64_t n, uint64_t shift) {
#if WAH_HAS_BUILTIN(__builtin_rotateright64)
return __builtin_rotateright64(n, shift);
#elif defined(_MSC_VER)
return _rotr64(n, shift);
#else
shift &= 63; // Ensure shift is within 0-63
return (n >> shift) | (n << (64 - shift));
#endif
}
// nearest (round to nearest, ties to even)
static inline float wah_nearest_f32(float f) {
#if WAH_HAS_BUILTIN(__builtin_roundevenf) && defined(__clang__)
return __builtin_roundevenf(f);
#else
if (isnan(f) || isinf(f) || f == 0.0f) return f;
float rounded = roundf(f);
if (fabsf(f - rounded) == 0.5f && ((long long)rounded % 2) != 0) return rounded - copysignf(1.0f, f);
return rounded;
#endif
}
static inline double wah_nearest_f64(double d) {
#if WAH_HAS_BUILTIN(__builtin_roundeven) && defined(__clang__)
return __builtin_roundeven(d);
#else
if (isnan(d) || isinf(d) || d == 0.0) return d;
double rounded = round(d);
if (fabs(d - rounded) == 0.5 && ((long long)rounded % 2) != 0) return rounded - copysign(1.0, d);
return rounded;
#endif
}
// Helper functions for floating-point to integer truncations with trap handling
#define DEFINE_TRUNC_F2I(N, fty, T, ity, lo, hi, call) \
static inline wah_error_t wah_trunc_f##N##_to_##T(fty val, ity *result) { \
if (isnan(val) || isinf(val)) return WAH_ERROR_TRAP; \
if (val < (lo) || val >= (hi)) return WAH_ERROR_TRAP; \
*result = (ity)call(val); \
return WAH_OK; \
}
DEFINE_TRUNC_F2I(32, float, i32, int32_t, (float)INT32_MIN, (float) INT32_MAX + 1.0f, truncf)
DEFINE_TRUNC_F2I(32, float, u32, uint32_t, 0, (float)UINT32_MAX + 1.0f, truncf)
DEFINE_TRUNC_F2I(64, double, i32, int32_t, (double)INT32_MIN, (double) INT32_MAX + 1.0, trunc)
DEFINE_TRUNC_F2I(64, double, u32, uint32_t, 0, (double)UINT32_MAX + 1.0, trunc)
DEFINE_TRUNC_F2I(32, float, i64, int64_t, (float)INT64_MIN, (float) INT64_MAX + 1.0f, truncf)
DEFINE_TRUNC_F2I(32, float, u64, uint64_t, 0, (float)UINT64_MAX + 1.0f, truncf)
DEFINE_TRUNC_F2I(64, double, i64, int64_t, (double)INT64_MIN, (double) INT64_MAX + 1.0, trunc)
DEFINE_TRUNC_F2I(64, double, u64, uint64_t, 0, (double)UINT64_MAX + 1.0, trunc)
// Helper functions for floating-point to integer truncations with saturation
#define DEFINE_TRUNC_SAT_F2I(N, fty, T, ity, min_val, max_val, call) \
static inline ity wah_trunc_sat_f##N##_to_##T(fty val) { \
if (isnan(val)) return 0; \
if (val <= (fty)min_val) return min_val; \
if (val >= (fty)max_val) return max_val; \
return (ity)call(val); \
}
DEFINE_TRUNC_SAT_F2I(32, float, i32, int32_t, INT32_MIN, INT32_MAX, truncf)
DEFINE_TRUNC_SAT_F2I(32, float, u32, uint32_t, 0, UINT32_MAX, truncf)
DEFINE_TRUNC_SAT_F2I(64, double, i32, int32_t, INT32_MIN, INT32_MAX, trunc)
DEFINE_TRUNC_SAT_F2I(64, double, u32, uint32_t, 0, UINT32_MAX, trunc)
DEFINE_TRUNC_SAT_F2I(32, float, i64, int64_t, INT64_MIN, INT64_MAX, truncf)
DEFINE_TRUNC_SAT_F2I(32, float, u64, uint64_t, 0, UINT64_MAX, truncf)
DEFINE_TRUNC_SAT_F2I(64, double, i64, int64_t, INT64_MIN, INT64_MAX, trunc)
DEFINE_TRUNC_SAT_F2I(64, double, u64, uint64_t, 0, UINT64_MAX, trunc)
static inline int8_t wah_trunc_sat_i16_to_i8(int16_t val) {
if (val < -128) return -128;
if (val > 127) return 127;
return (int8_t)val;
}
static inline uint8_t wah_trunc_sat_i16_to_u8(int16_t val) {
if (val < 0) return 0;
if (val > 255) return 255;
return (uint8_t)val;
}
static inline int16_t wah_trunc_sat_i32_to_i16(int32_t val) {
if (val < -32768) return -32768;
if (val > 32767) return 32767;
return (int16_t)val;
}
static inline uint16_t wah_trunc_sat_i32_to_u16(int32_t val) {
if (val < 0) return 0;
if (val > 65535) return 65535;
return (uint16_t)val;
}
static inline int16_t WAH_MIN_S_16(int16_t a, int16_t b) { return a < b ? a : b; }
static inline uint16_t WAH_MIN_U_16(uint16_t a, uint16_t b) { return a < b ? a : b; }
static inline int16_t WAH_MAX_S_16(int16_t a, int16_t b) { return a > b ? a : b; }
static inline uint16_t WAH_MAX_U_16(uint16_t a, uint16_t b) { return a > b ? a : b; }
static inline int32_t WAH_MIN_S_32(int32_t a, int32_t b) { return a < b ? a : b; }
static inline uint32_t WAH_MIN_U_32(uint32_t a, uint32_t b) { return a < b ? a : b; }
static inline int32_t WAH_MAX_S_32(int32_t a, int32_t b) { return a > b ? a : b; }
static inline uint32_t WAH_MAX_U_32(uint32_t a, uint32_t b) { return a > b ? a : b; }
static inline int8_t WAH_MIN_S_8(int8_t a, int8_t b) { return a < b ? a : b; }
static inline uint8_t WAH_MIN_U_8(uint8_t a, uint8_t b) { return a < b ? a : b; }
static inline int8_t WAH_MAX_S_8(int8_t a, int8_t b) { return a > b ? a : b; }
static inline uint8_t WAH_MAX_U_8(uint8_t a, uint8_t b) { return a > b ? a : b; }
static inline wah_error_t wah_type_stack_push(wah_type_stack_t *stack, wah_type_t type) {
WAH_ENSURE(stack->sp < WAH_MAX_TYPE_STACK_SIZE, WAH_ERROR_VALIDATION_FAILED);
stack->data[stack->sp++] = type;
return WAH_OK;
}
static inline wah_error_t wah_type_stack_pop(wah_type_stack_t *stack, wah_type_t *out_type) {
WAH_ENSURE(stack->sp > 0, WAH_ERROR_VALIDATION_FAILED);
*out_type = stack->data[--stack->sp];
return WAH_OK;
}
// Stack depth tracking helpers
static inline wah_error_t wah_validation_push_type(wah_validation_context_t *vctx, wah_type_t type) {
WAH_CHECK(wah_type_stack_push(&vctx->type_stack, vctx->is_unreachable ? WAH_TYPE_ANY : type));
vctx->current_stack_depth++;
if (vctx->current_stack_depth > vctx->max_stack_depth) {
vctx->max_stack_depth = vctx->current_stack_depth;
}
return WAH_OK;
}
static inline wah_error_t wah_validation_pop_type(wah_validation_context_t *vctx, wah_type_t *out_type) {
if (vctx->is_unreachable) {
*out_type = WAH_TYPE_ANY;
// In an unreachable state, pop always succeeds conceptually, and stack height still changes.
// We still decrement current_stack_depth to track the conceptual stack height.
// We don't need to pop from type_stack.data as it's already filled with WAH_TYPE_ANY or ignored.
if (vctx->current_stack_depth > 0) {
vctx->current_stack_depth--;
}
return WAH_OK;
}
// If reachable, check for stack underflow
WAH_ENSURE(vctx->current_stack_depth > 0, WAH_ERROR_VALIDATION_FAILED);
WAH_CHECK(wah_type_stack_pop(&vctx->type_stack, out_type));
vctx->current_stack_depth--;
return WAH_OK;
}
// Helper function to validate if an actual type matches an expected type, considering WAH_TYPE_ANY
static inline wah_error_t wah_validate_type_match(wah_type_t actual, wah_type_t expected) {
WAH_ENSURE(actual == expected || actual == WAH_TYPE_ANY, WAH_ERROR_VALIDATION_FAILED);
return WAH_OK;
}
// Helper function to pop a type from the stack and validate it against an expected type
static inline wah_error_t wah_validation_pop_and_match_type(wah_validation_context_t *vctx, wah_type_t expected_type) {
wah_type_t actual_type;
WAH_CHECK(wah_validation_pop_type(vctx, &actual_type));
return wah_validate_type_match(actual_type, expected_type);
}
// Helper to read a section header
static wah_error_t wah_read_section_header(const uint8_t **ptr, const uint8_t *end, uint8_t *id, uint32_t *size) {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
*id = *(*ptr)++;
return wah_decode_uleb128(ptr, end, size);
}
// --- Internal Section Parsing Functions ---
static wah_error_t wah_parse_type_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->type_count = count;
WAH_MALLOC_ARRAY(module->types, count);
memset(module->types, 0, sizeof(wah_func_type_t) * count);
for (uint32_t i = 0; i < count; ++i) {
WAH_ENSURE(**ptr == 0x60, WAH_ERROR_VALIDATION_FAILED);
(*ptr)++;
// Parse parameter types
uint32_t param_count_type;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &param_count_type));
module->types[i].param_count = param_count_type;
WAH_MALLOC_ARRAY(module->types[i].param_types, param_count_type);
for (uint32_t j = 0; j < param_count_type; ++j) {
wah_type_t type;
WAH_CHECK(wah_decode_val_type(ptr, section_end, &type));
module->types[i].param_types[j] = type;
}
// Parse result types
uint32_t result_count_type;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &result_count_type));
WAH_ENSURE(result_count_type <= 1, WAH_ERROR_VALIDATION_FAILED);
module->types[i].result_count = result_count_type;
WAH_MALLOC_ARRAY(module->types[i].result_types, result_count_type);
for (uint32_t j = 0; j < result_count_type; ++j) {
wah_type_t type;
WAH_CHECK(wah_decode_val_type(ptr, section_end, &type));
module->types[i].result_types[j] = type;
}
}
return WAH_OK;
}
static wah_error_t wah_parse_function_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->function_count = count;
WAH_MALLOC_ARRAY(module->function_type_indices, count);
for (uint32_t i = 0; i < count; ++i) {
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->function_type_indices[i]));
WAH_ENSURE(module->function_type_indices[i] < module->type_count, WAH_ERROR_VALIDATION_FAILED);
}
return WAH_OK;
}
// Validation helper function that handles a single opcode
static wah_error_t wah_validate_opcode(uint16_t opcode_val, const uint8_t **code_ptr, const uint8_t *code_end, wah_validation_context_t *vctx, const wah_code_body_t* code_body) {
switch (opcode_val) {
#define LOAD_OP(T, max_lg_align) { \
uint32_t align, offset; \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &align)); \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &offset)); \
WAH_ENSURE(align <= max_lg_align, WAH_ERROR_VALIDATION_FAILED); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); \
return wah_validation_push_type(vctx, WAH_TYPE_##T); \
}
#define STORE_OP(T, max_lg_align) { \
uint32_t align, offset; \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &align)); \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &offset)); \
WAH_ENSURE(align <= max_lg_align, WAH_ERROR_VALIDATION_FAILED); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##T)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); \
return WAH_OK; \
}
#define LOAD_V128_LANE_OP(max_lg_align) { \
uint32_t align, offset; \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &align)); \
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &offset)); \
WAH_ENSURE(*code_ptr < code_end, WAH_ERROR_UNEXPECTED_EOF); \
uint8_t lane_idx = *(*code_ptr)++; \
WAH_ENSURE(align <= max_lg_align, WAH_ERROR_VALIDATION_FAILED); \
WAH_ENSURE(lane_idx < (16 >> max_lg_align), WAH_ERROR_VALIDATION_FAILED); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); \
return wah_validation_push_type(vctx, WAH_TYPE_V128); \
}
#define EXTRACT_LANE_OP(SCALAR_TYPE, LANE_COUNT) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); \
WAH_CHECK(wah_validation_push_type(vctx, WAH_TYPE_##SCALAR_TYPE)); \
WAH_ENSURE(*code_ptr < code_end, WAH_ERROR_UNEXPECTED_EOF); \
uint8_t lane_idx = *(*code_ptr)++; \
WAH_ENSURE(lane_idx < LANE_COUNT, WAH_ERROR_VALIDATION_FAILED); \
break; \
}
#define REPLACE_LANE_OP(SCALAR_TYPE, LANE_COUNT) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##SCALAR_TYPE)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); \
WAH_CHECK(wah_validation_push_type(vctx, WAH_TYPE_V128)); \
WAH_ENSURE(*code_ptr < code_end, WAH_ERROR_UNEXPECTED_EOF); \
uint8_t lane_idx = *(*code_ptr)++; \
WAH_ENSURE(lane_idx < LANE_COUNT, WAH_ERROR_VALIDATION_FAILED); \
break; \
}
#define SPLAT_OP(SCALAR_TYPE) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##SCALAR_TYPE)); \
WAH_CHECK(wah_validation_push_type(vctx, WAH_TYPE_V128)); \
break; \
}
case WAH_OP_I32_LOAD: LOAD_OP(I32, 2)
case WAH_OP_I64_LOAD: LOAD_OP(I64, 3)
case WAH_OP_F32_LOAD: LOAD_OP(F32, 2)
case WAH_OP_F64_LOAD: LOAD_OP(F64, 3)
case WAH_OP_I32_LOAD8_S: case WAH_OP_I32_LOAD8_U: LOAD_OP(I32, 0)
case WAH_OP_I32_LOAD16_S: case WAH_OP_I32_LOAD16_U: LOAD_OP(I32, 1)
case WAH_OP_I64_LOAD8_S: case WAH_OP_I64_LOAD8_U: LOAD_OP(I64, 0)
case WAH_OP_I64_LOAD16_S: case WAH_OP_I64_LOAD16_U: LOAD_OP(I64, 1)
case WAH_OP_I64_LOAD32_S: case WAH_OP_I64_LOAD32_U: LOAD_OP(I64, 2)
case WAH_OP_I32_STORE: STORE_OP(I32, 2)
case WAH_OP_I64_STORE: STORE_OP(I64, 3)
case WAH_OP_F32_STORE: STORE_OP(F32, 2)
case WAH_OP_F64_STORE: STORE_OP(F64, 3)
case WAH_OP_I32_STORE8: STORE_OP(I32, 0)
case WAH_OP_I32_STORE16: STORE_OP(I32, 1)
case WAH_OP_I64_STORE8: STORE_OP(I64, 0)
case WAH_OP_I64_STORE16: STORE_OP(I64, 1)
case WAH_OP_I64_STORE32: STORE_OP(I64, 2)
case WAH_OP_V128_LOAD: LOAD_OP(V128, 4)
case WAH_OP_V128_LOAD8X8_S: case WAH_OP_V128_LOAD8X8_U: LOAD_OP(V128, 3)
case WAH_OP_V128_LOAD16X4_S: case WAH_OP_V128_LOAD16X4_U: LOAD_OP(V128, 3)
case WAH_OP_V128_LOAD32X2_S: case WAH_OP_V128_LOAD32X2_U: LOAD_OP(V128, 3)
case WAH_OP_V128_LOAD8_SPLAT: LOAD_OP(V128, 0)
case WAH_OP_V128_LOAD16_SPLAT: LOAD_OP(V128, 1)
case WAH_OP_V128_LOAD32_SPLAT: LOAD_OP(V128, 2)
case WAH_OP_V128_LOAD64_SPLAT: LOAD_OP(V128, 3)
case WAH_OP_V128_LOAD32_ZERO: LOAD_OP(V128, 2)
case WAH_OP_V128_LOAD64_ZERO: LOAD_OP(V128, 3)
case WAH_OP_V128_STORE: STORE_OP(V128, 4)
case WAH_OP_V128_LOAD8_LANE: LOAD_V128_LANE_OP(0)
case WAH_OP_V128_LOAD16_LANE: LOAD_V128_LANE_OP(1)
case WAH_OP_V128_LOAD32_LANE: LOAD_V128_LANE_OP(2)
case WAH_OP_V128_LOAD64_LANE: LOAD_V128_LANE_OP(3)
/* Vector Lane Operations */
case WAH_OP_I8X16_SHUFFLE: {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // vector2
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // vector1
WAH_CHECK(wah_validation_push_type(vctx, WAH_TYPE_V128));
for (int i = 0; i < 16; i++) WAH_ENSURE((*code_ptr)[i] < 32, WAH_ERROR_VALIDATION_FAILED);
*code_ptr += 16; // 16 immediate bytes for the shuffle mask
break;
}
case WAH_OP_I8X16_SWIZZLE: {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // mask
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // data
WAH_CHECK(wah_validation_push_type(vctx, WAH_TYPE_V128));
break;
}
case WAH_OP_I8X16_EXTRACT_LANE_S: EXTRACT_LANE_OP(I32, 16)
case WAH_OP_I8X16_EXTRACT_LANE_U: EXTRACT_LANE_OP(I32, 16)
case WAH_OP_I8X16_REPLACE_LANE: REPLACE_LANE_OP(I32, 16)
case WAH_OP_I16X8_EXTRACT_LANE_S: EXTRACT_LANE_OP(I32, 8)
case WAH_OP_I16X8_EXTRACT_LANE_U: EXTRACT_LANE_OP(I32, 8)
case WAH_OP_I16X8_REPLACE_LANE: REPLACE_LANE_OP(I32, 8)
case WAH_OP_I32X4_EXTRACT_LANE: EXTRACT_LANE_OP(I32, 4)
case WAH_OP_I32X4_REPLACE_LANE: REPLACE_LANE_OP(I32, 4)
case WAH_OP_I64X2_EXTRACT_LANE: EXTRACT_LANE_OP(I64, 2)
case WAH_OP_I64X2_REPLACE_LANE: REPLACE_LANE_OP(I64, 2)
case WAH_OP_F32X4_EXTRACT_LANE: EXTRACT_LANE_OP(F32, 4)
case WAH_OP_F32X4_REPLACE_LANE: REPLACE_LANE_OP(F32, 4)
case WAH_OP_F64X2_EXTRACT_LANE: EXTRACT_LANE_OP(F64, 2)
case WAH_OP_F64X2_REPLACE_LANE: REPLACE_LANE_OP(F64, 2)
case WAH_OP_I8X16_SPLAT: case WAH_OP_I16X8_SPLAT: case WAH_OP_I32X4_SPLAT: SPLAT_OP(I32)
case WAH_OP_I64X2_SPLAT: SPLAT_OP(I64)
case WAH_OP_F32X4_SPLAT: SPLAT_OP(F32)
case WAH_OP_F64X2_SPLAT: SPLAT_OP(F64)
#undef LOAD_OP
#undef STORE_OP
#undef LOAD_V128_LANE_OP
#undef EXTRACT_LANE_OP
#undef REPLACE_LANE_OP
#undef SPLAT_OP
#undef EXTRACT_LANE_OP
#undef REPLACE_LANE_OP
#undef SPLAT_OP
case WAH_OP_MEMORY_SIZE: {
uint32_t mem_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &mem_idx)); // Expect 0x00 for memory index
WAH_ENSURE(mem_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
return wah_validation_push_type(vctx, WAH_TYPE_I32); // Pushes current memory size in pages
}
case WAH_OP_MEMORY_GROW: {
uint32_t mem_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &mem_idx)); // Expect 0x00 for memory index
WAH_ENSURE(mem_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops pages to grow by (i32)
return wah_validation_push_type(vctx, WAH_TYPE_I32); // Pushes old memory size in pages (i32)
}
case WAH_OP_MEMORY_FILL: {
uint32_t mem_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &mem_idx)); // Expect 0x00 for memory index
WAH_ENSURE(mem_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops size (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops value (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops destination address (i32)
return WAH_OK; // Pushes nothing
}
case WAH_OP_MEMORY_INIT: {
uint32_t data_idx, mem_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &data_idx));
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &mem_idx));
WAH_ENSURE(mem_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops size (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops data segment offset (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops memory offset (i32)
// Update min_data_segment_count_required
if (data_idx + 1 > vctx->module->min_data_segment_count_required) {
vctx->module->min_data_segment_count_required = data_idx + 1;
}
return WAH_OK;
}
case WAH_OP_MEMORY_COPY: {
uint32_t dest_mem_idx, src_mem_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &dest_mem_idx));
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &src_mem_idx));
WAH_ENSURE(dest_mem_idx == 0 && src_mem_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops size (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops src offset (i32)
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32)); // Pops dest offset (i32)
return WAH_OK;
}
case WAH_OP_CALL: {
uint32_t func_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &func_idx));
WAH_ENSURE(func_idx < vctx->module->function_count, WAH_ERROR_VALIDATION_FAILED);
// Type checking for call
const wah_func_type_t *called_func_type = &vctx->module->types[vctx->module->function_type_indices[func_idx]];
// Pop parameters from type stack
for (int32_t j = called_func_type->param_count - 1; j >= 0; --j) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, called_func_type->param_types[j]));
}
// Push results onto type stack
for (uint32_t j = 0; j < called_func_type->result_count; ++j) {
WAH_CHECK(wah_validation_push_type(vctx, called_func_type->result_types[j]));
}
return WAH_OK;
}
case WAH_OP_CALL_INDIRECT: {
uint32_t type_idx, table_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &type_idx));
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &table_idx));
// Validate table index
WAH_ENSURE(table_idx < vctx->module->table_count, WAH_ERROR_VALIDATION_FAILED);
// Only funcref tables are supported for now
WAH_ENSURE(vctx->module->tables[table_idx].elem_type == WAH_TYPE_FUNCREF, WAH_ERROR_VALIDATION_FAILED);
// Validate type index
WAH_ENSURE(type_idx < vctx->module->type_count, WAH_ERROR_VALIDATION_FAILED);
// Pop function index (i32) from stack
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32));
// Get the expected function type
const wah_func_type_t *expected_func_type = &vctx->module->types[type_idx];
// Pop parameters from type stack
for (int32_t j = expected_func_type->param_count - 1; j >= 0; --j) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, expected_func_type->param_types[j]));
}
// Push results onto type stack
for (uint32_t j = 0; j < expected_func_type->result_count; ++j) {
WAH_CHECK(wah_validation_push_type(vctx, expected_func_type->result_types[j]));
}
return WAH_OK;
}
case WAH_OP_LOCAL_GET:
case WAH_OP_LOCAL_SET:
case WAH_OP_LOCAL_TEE: {
uint32_t local_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &local_idx));
WAH_ENSURE(local_idx < vctx->total_locals, WAH_ERROR_VALIDATION_FAILED);
wah_type_t expected_type;
if (local_idx < vctx->func_type->param_count) {
expected_type = vctx->func_type->param_types[local_idx];
} else {
expected_type = code_body->local_types[local_idx - vctx->func_type->param_count];
}
if (opcode_val == WAH_OP_LOCAL_GET) {
return wah_validation_push_type(vctx, expected_type);
} else if (opcode_val == WAH_OP_LOCAL_SET) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, expected_type));
return WAH_OK;
} else { // WAH_OP_LOCAL_TEE
WAH_CHECK(wah_validation_pop_and_match_type(vctx, expected_type));
return wah_validation_push_type(vctx, expected_type);
}
break;
}
case WAH_OP_GLOBAL_GET: {
uint32_t global_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &global_idx));
WAH_ENSURE(global_idx < vctx->module->global_count, WAH_ERROR_VALIDATION_FAILED);
wah_type_t global_type = vctx->module->globals[global_idx].type;
return wah_validation_push_type(vctx, global_type);
}
case WAH_OP_GLOBAL_SET: {
uint32_t global_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &global_idx));
WAH_ENSURE(global_idx < vctx->module->global_count, WAH_ERROR_VALIDATION_FAILED);
WAH_ENSURE(vctx->module->globals[global_idx].is_mutable, WAH_ERROR_VALIDATION_FAILED); // Cannot set immutable global
WAH_CHECK(wah_validation_pop_and_match_type(vctx, vctx->module->globals[global_idx].type));
return WAH_OK;
}
case WAH_OP_I32_CONST: {
int32_t val;
WAH_CHECK(wah_decode_sleb128_32(code_ptr, code_end, &val));
return wah_validation_push_type(vctx, WAH_TYPE_I32);
}
case WAH_OP_I64_CONST: {
int64_t val;
WAH_CHECK(wah_decode_sleb128_64(code_ptr, code_end, &val));
return wah_validation_push_type(vctx, WAH_TYPE_I64);
}
case WAH_OP_F32_CONST: {
WAH_ENSURE(code_end - *code_ptr >= 4, WAH_ERROR_UNEXPECTED_EOF);
*code_ptr += 4;
return wah_validation_push_type(vctx, WAH_TYPE_F32);
}
case WAH_OP_F64_CONST: {
WAH_ENSURE(code_end - *code_ptr >= 8, WAH_ERROR_UNEXPECTED_EOF);
*code_ptr += 8;
return wah_validation_push_type(vctx, WAH_TYPE_F64);
}
case WAH_OP_V128_CONST: {
WAH_ENSURE(code_end - *code_ptr >= 16, WAH_ERROR_UNEXPECTED_EOF);
*code_ptr += 16;
return wah_validation_push_type(vctx, WAH_TYPE_V128);
}
#define NUM_OPS(N) \
/* Binary i32/i64 operations */ \
case WAH_OP_I##N##_ADD: case WAH_OP_I##N##_SUB: case WAH_OP_I##N##_MUL: \
case WAH_OP_I##N##_DIV_S: case WAH_OP_I##N##_DIV_U: case WAH_OP_I##N##_REM_S: case WAH_OP_I##N##_REM_U: \
case WAH_OP_I##N##_AND: case WAH_OP_I##N##_OR: case WAH_OP_I##N##_XOR: \
case WAH_OP_I##N##_SHL: case WAH_OP_I##N##_SHR_S: case WAH_OP_I##N##_SHR_U: { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I##N)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I##N)); \
return wah_validation_push_type(vctx, WAH_TYPE_I##N); \
} \
case WAH_OP_I##N##_EQ: case WAH_OP_I##N##_NE: \
case WAH_OP_I##N##_LT_S: case WAH_OP_I##N##_LT_U: case WAH_OP_I##N##_GT_S: case WAH_OP_I##N##_GT_U: \
case WAH_OP_I##N##_LE_S: case WAH_OP_I##N##_LE_U: case WAH_OP_I##N##_GE_S: case WAH_OP_I##N##_GE_U: { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I##N)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I##N)); \
return wah_validation_push_type(vctx, WAH_TYPE_I32); /* Comparisons return i32 */ \
} \
\
/* Unary i32/i64 operations */ \
case WAH_OP_I##N##_EQZ: { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I##N)); \
return wah_validation_push_type(vctx, WAH_TYPE_I32); \
} \
\
/* Binary f32/f64 operations */ \
case WAH_OP_F##N##_ADD: case WAH_OP_F##N##_SUB: case WAH_OP_F##N##_MUL: case WAH_OP_F##N##_DIV: { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_F##N)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_F##N)); \
return wah_validation_push_type(vctx, WAH_TYPE_F##N); \
} \
case WAH_OP_F##N##_EQ: case WAH_OP_F##N##_NE: \
case WAH_OP_F##N##_LT: case WAH_OP_F##N##_GT: case WAH_OP_F##N##_LE: case WAH_OP_F##N##_GE: { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_F##N)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_F##N)); \
return wah_validation_push_type(vctx, WAH_TYPE_I32); /* Comparisons return i32 */ \
}
NUM_OPS(32)
NUM_OPS(64)
#undef NUM_OPS
#define UNARY_OP(T) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##T)); \
return wah_validation_push_type(vctx, WAH_TYPE_##T); \
}
#define BINARY_OP(T) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##T)); \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##T)); \
return wah_validation_push_type(vctx, WAH_TYPE_##T); \
}
#define POP_PUSH(INPUT_TYPE, OUTPUT_TYPE) { \
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_##INPUT_TYPE)); \
return wah_validation_push_type(vctx, WAH_TYPE_##OUTPUT_TYPE); \
}
// Integer Unary Operations
case WAH_OP_I32_CLZ: case WAH_OP_I32_CTZ: case WAH_OP_I32_POPCNT: UNARY_OP(I32)
case WAH_OP_I64_CLZ: case WAH_OP_I64_CTZ: case WAH_OP_I64_POPCNT: UNARY_OP(I64)
// Integer Binary Operations
case WAH_OP_I32_ROTL: case WAH_OP_I32_ROTR: BINARY_OP(I32)
case WAH_OP_I64_ROTL: case WAH_OP_I64_ROTR: BINARY_OP(I64)
// Floating-Point Unary Operations
case WAH_OP_F32_ABS: case WAH_OP_F32_NEG: case WAH_OP_F32_CEIL: case WAH_OP_F32_FLOOR:
case WAH_OP_F32_TRUNC: case WAH_OP_F32_NEAREST: case WAH_OP_F32_SQRT: UNARY_OP(F32)
case WAH_OP_F64_ABS: case WAH_OP_F64_NEG: case WAH_OP_F64_CEIL: case WAH_OP_F64_FLOOR:
case WAH_OP_F64_TRUNC: case WAH_OP_F64_NEAREST: case WAH_OP_F64_SQRT: UNARY_OP(F64)
// Floating-Point Binary Operations
case WAH_OP_F32_MIN: case WAH_OP_F32_MAX: case WAH_OP_F32_COPYSIGN: BINARY_OP(F32)
case WAH_OP_F64_MIN: case WAH_OP_F64_MAX: case WAH_OP_F64_COPYSIGN: BINARY_OP(F64)
case WAH_OP_V128_BITSELECT: {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // v3
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // v2
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128)); // v1
return wah_validation_push_type(vctx, WAH_TYPE_V128);
}
case WAH_OP_V128_NOT:
case WAH_OP_I8X16_ABS: case WAH_OP_I8X16_NEG: case WAH_OP_I8X16_POPCNT:
case WAH_OP_I16X8_EXTADD_PAIRWISE_I8X16_S: case WAH_OP_I16X8_EXTADD_PAIRWISE_I8X16_U:
case WAH_OP_I16X8_ABS: case WAH_OP_I16X8_NEG: case WAH_OP_I16X8_Q15MULR_SAT_S:
case WAH_OP_I16X8_EXTEND_LOW_I8X16_S: case WAH_OP_I16X8_EXTEND_HIGH_I8X16_S:
case WAH_OP_I16X8_EXTEND_LOW_I8X16_U: case WAH_OP_I16X8_EXTEND_HIGH_I8X16_U:
case WAH_OP_I32X4_EXTADD_PAIRWISE_I16X8_S: case WAH_OP_I32X4_EXTADD_PAIRWISE_I16X8_U:
case WAH_OP_I32X4_ABS: case WAH_OP_I32X4_NEG:
case WAH_OP_I32X4_EXTEND_LOW_I16X8_S: case WAH_OP_I32X4_EXTEND_HIGH_I16X8_S:
case WAH_OP_I32X4_EXTEND_LOW_I16X8_U: case WAH_OP_I32X4_EXTEND_HIGH_I16X8_U:
case WAH_OP_I64X2_ABS: case WAH_OP_I64X2_NEG:
case WAH_OP_I64X2_EXTEND_LOW_I32X4_S: case WAH_OP_I64X2_EXTEND_HIGH_I32X4_S:
case WAH_OP_I64X2_EXTEND_LOW_I32X4_U: case WAH_OP_I64X2_EXTEND_HIGH_I32X4_U:
case WAH_OP_F32X4_CEIL: case WAH_OP_F32X4_FLOOR: case WAH_OP_F32X4_TRUNC: case WAH_OP_F32X4_NEAREST:
case WAH_OP_F32X4_ABS: case WAH_OP_F32X4_NEG: case WAH_OP_F32X4_SQRT:
case WAH_OP_F64X2_CEIL: case WAH_OP_F64X2_FLOOR: case WAH_OP_F64X2_TRUNC: case WAH_OP_F64X2_NEAREST:
case WAH_OP_F64X2_ABS: case WAH_OP_F64X2_NEG: case WAH_OP_F64X2_SQRT: UNARY_OP(V128)
case WAH_OP_V128_ANY_TRUE:
case WAH_OP_I8X16_ALL_TRUE: case WAH_OP_I8X16_BITMASK:
case WAH_OP_I16X8_ALL_TRUE: case WAH_OP_I16X8_BITMASK:
case WAH_OP_I32X4_ALL_TRUE: case WAH_OP_I32X4_BITMASK:
case WAH_OP_I64X2_ALL_TRUE: case WAH_OP_I64X2_BITMASK: POP_PUSH(V128, I32)
case WAH_OP_V128_AND: case WAH_OP_V128_ANDNOT: case WAH_OP_V128_OR: case WAH_OP_V128_XOR:
case WAH_OP_I8X16_NARROW_I16X8_S: case WAH_OP_I8X16_NARROW_I16X8_U:
case WAH_OP_I8X16_ADD: case WAH_OP_I8X16_ADD_SAT_S: case WAH_OP_I8X16_ADD_SAT_U:
case WAH_OP_I8X16_SUB: case WAH_OP_I8X16_SUB_SAT_S: case WAH_OP_I8X16_SUB_SAT_U:
case WAH_OP_I8X16_MIN_S: case WAH_OP_I8X16_MIN_U: case WAH_OP_I8X16_MAX_S: case WAH_OP_I8X16_MAX_U: case WAH_OP_I8X16_AVGR_U:
case WAH_OP_I16X8_NARROW_I32X4_S: case WAH_OP_I16X8_NARROW_I32X4_U:
case WAH_OP_I16X8_ADD: case WAH_OP_I16X8_ADD_SAT_S: case WAH_OP_I16X8_ADD_SAT_U:
case WAH_OP_I16X8_SUB: case WAH_OP_I16X8_SUB_SAT_S: case WAH_OP_I16X8_SUB_SAT_U:
case WAH_OP_I16X8_MUL:
case WAH_OP_I16X8_MIN_S: case WAH_OP_I16X8_MIN_U: case WAH_OP_I16X8_MAX_S: case WAH_OP_I16X8_MAX_U: case WAH_OP_I16X8_AVGR_U:
case WAH_OP_I16X8_EXTMUL_LOW_I8X16_S: case WAH_OP_I16X8_EXTMUL_HIGH_I8X16_S:
case WAH_OP_I16X8_EXTMUL_LOW_I8X16_U: case WAH_OP_I16X8_EXTMUL_HIGH_I8X16_U:
case WAH_OP_I32X4_ADD: case WAH_OP_I32X4_SUB: case WAH_OP_I32X4_MUL:
case WAH_OP_I32X4_MIN_S: case WAH_OP_I32X4_MIN_U: case WAH_OP_I32X4_MAX_S: case WAH_OP_I32X4_MAX_U:
case WAH_OP_I32X4_DOT_I16X8_S:
case WAH_OP_I32X4_EXTMUL_LOW_I16X8_S: case WAH_OP_I32X4_EXTMUL_HIGH_I16X8_S:
case WAH_OP_I32X4_EXTMUL_LOW_I16X8_U: case WAH_OP_I32X4_EXTMUL_HIGH_I16X8_U:
case WAH_OP_I64X2_ADD: case WAH_OP_I64X2_SUB: case WAH_OP_I64X2_MUL:
case WAH_OP_I64X2_EXTMUL_LOW_I32X4_S: case WAH_OP_I64X2_EXTMUL_HIGH_I32X4_S:
case WAH_OP_I64X2_EXTMUL_LOW_I32X4_U: case WAH_OP_I64X2_EXTMUL_HIGH_I32X4_U:
case WAH_OP_F32X4_ADD: case WAH_OP_F32X4_SUB: case WAH_OP_F32X4_MUL: case WAH_OP_F32X4_DIV:
case WAH_OP_F32X4_MIN: case WAH_OP_F32X4_MAX: case WAH_OP_F32X4_PMIN: case WAH_OP_F32X4_PMAX:
case WAH_OP_F64X2_ADD: case WAH_OP_F64X2_SUB: case WAH_OP_F64X2_MUL: case WAH_OP_F64X2_DIV:
case WAH_OP_F64X2_MIN: case WAH_OP_F64X2_MAX: case WAH_OP_F64X2_PMIN: case WAH_OP_F64X2_PMAX:
case WAH_OP_I8X16_EQ: case WAH_OP_I8X16_NE: case WAH_OP_I8X16_LT_S: case WAH_OP_I8X16_LT_U:
case WAH_OP_I8X16_GT_S: case WAH_OP_I8X16_GT_U: case WAH_OP_I8X16_LE_S: case WAH_OP_I8X16_LE_U:
case WAH_OP_I8X16_GE_S: case WAH_OP_I8X16_GE_U:
case WAH_OP_I16X8_EQ: case WAH_OP_I16X8_NE: case WAH_OP_I16X8_LT_S: case WAH_OP_I16X8_LT_U:
case WAH_OP_I16X8_GT_S: case WAH_OP_I16X8_GT_U: case WAH_OP_I16X8_LE_S: case WAH_OP_I16X8_LE_U:
case WAH_OP_I16X8_GE_S: case WAH_OP_I16X8_GE_U:
case WAH_OP_I32X4_EQ: case WAH_OP_I32X4_NE: case WAH_OP_I32X4_LT_S: case WAH_OP_I32X4_LT_U:
case WAH_OP_I32X4_GT_S: case WAH_OP_I32X4_GT_U: case WAH_OP_I32X4_LE_S: case WAH_OP_I32X4_LE_U:
case WAH_OP_I32X4_GE_S: case WAH_OP_I32X4_GE_U:
case WAH_OP_I64X2_EQ: case WAH_OP_I64X2_NE: case WAH_OP_I64X2_LT_S: case WAH_OP_I64X2_GT_S:
case WAH_OP_I64X2_LE_S: case WAH_OP_I64X2_GE_S:
case WAH_OP_F32X4_EQ: case WAH_OP_F32X4_NE: case WAH_OP_F32X4_LT: case WAH_OP_F32X4_GT:
case WAH_OP_F32X4_LE: case WAH_OP_F32X4_GE:
case WAH_OP_F64X2_EQ: case WAH_OP_F64X2_NE: case WAH_OP_F64X2_LT: case WAH_OP_F64X2_GT:
case WAH_OP_F64X2_LE: case WAH_OP_F64X2_GE: BINARY_OP(V128)
case WAH_OP_I8X16_SHL: case WAH_OP_I8X16_SHR_S: case WAH_OP_I8X16_SHR_U:
case WAH_OP_I16X8_SHL: case WAH_OP_I16X8_SHR_S: case WAH_OP_I16X8_SHR_U:
case WAH_OP_I32X4_SHL: case WAH_OP_I32X4_SHR_S: case WAH_OP_I32X4_SHR_U:
case WAH_OP_I64X2_SHL: case WAH_OP_I64X2_SHR_S: case WAH_OP_I64X2_SHR_U: {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32));
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_V128));
return wah_validation_push_type(vctx, WAH_TYPE_V128);
}
// Conversion Operators
case WAH_OP_I32_WRAP_I64: POP_PUSH(I64, I32)
case WAH_OP_I32_TRUNC_F32_S: case WAH_OP_I32_TRUNC_F32_U: POP_PUSH(F32, I32)
case WAH_OP_I32_TRUNC_F64_S: case WAH_OP_I32_TRUNC_F64_U: POP_PUSH(F64, I32)
case WAH_OP_I64_EXTEND_I32_S: case WAH_OP_I64_EXTEND_I32_U: POP_PUSH(I32, I64)
case WAH_OP_I64_TRUNC_F32_S: case WAH_OP_I64_TRUNC_F32_U: POP_PUSH(F32, I64)
case WAH_OP_I64_TRUNC_F64_S: case WAH_OP_I64_TRUNC_F64_U: POP_PUSH(F64, I64)
case WAH_OP_F32_CONVERT_I32_S: case WAH_OP_F32_CONVERT_I32_U: POP_PUSH(I32, F32)
case WAH_OP_F32_CONVERT_I64_S: case WAH_OP_F32_CONVERT_I64_U: POP_PUSH(I64, F32)
case WAH_OP_F32_DEMOTE_F64: POP_PUSH(F64, F32)
case WAH_OP_F64_CONVERT_I32_S: case WAH_OP_F64_CONVERT_I32_U: POP_PUSH(I32, F64)
case WAH_OP_F64_CONVERT_I64_S: case WAH_OP_F64_CONVERT_I64_U: POP_PUSH(I64, F64)
case WAH_OP_F64_PROMOTE_F32: POP_PUSH(F32, F64)
case WAH_OP_I32_REINTERPRET_F32: POP_PUSH(F32, I32)
case WAH_OP_I64_REINTERPRET_F64: POP_PUSH(F64, I64)
case WAH_OP_F32_REINTERPRET_I32: POP_PUSH(I32, F32)
case WAH_OP_F64_REINTERPRET_I64: POP_PUSH(I64, F64)
case WAH_OP_I32_EXTEND8_S: case WAH_OP_I32_EXTEND16_S: POP_PUSH(I32, I32)
case WAH_OP_I64_EXTEND8_S: case WAH_OP_I64_EXTEND16_S: case WAH_OP_I64_EXTEND32_S: POP_PUSH(I64, I64)
case WAH_OP_I32_TRUNC_SAT_F32_S: case WAH_OP_I32_TRUNC_SAT_F32_U: POP_PUSH(F32, I32)
case WAH_OP_I32_TRUNC_SAT_F64_S: case WAH_OP_I32_TRUNC_SAT_F64_U: POP_PUSH(F64, I32)
case WAH_OP_I64_TRUNC_SAT_F32_S: case WAH_OP_I64_TRUNC_SAT_F32_U: POP_PUSH(F32, I64)
case WAH_OP_I64_TRUNC_SAT_F64_S: case WAH_OP_I64_TRUNC_SAT_F64_U: POP_PUSH(F64, I64)
case WAH_OP_I32X4_TRUNC_SAT_F32X4_S: case WAH_OP_I32X4_TRUNC_SAT_F32X4_U:
case WAH_OP_F32X4_CONVERT_I32X4_S: case WAH_OP_F32X4_CONVERT_I32X4_U:
case WAH_OP_I32X4_TRUNC_SAT_F64X2_S_ZERO: case WAH_OP_I32X4_TRUNC_SAT_F64X2_U_ZERO:
case WAH_OP_F64X2_CONVERT_LOW_I32X4_S: case WAH_OP_F64X2_CONVERT_LOW_I32X4_U:
case WAH_OP_F32X4_DEMOTE_F64X2_ZERO: case WAH_OP_F64X2_PROMOTE_LOW_F32X4: POP_PUSH(V128, V128)
#undef UNARY_OP
#undef BINARY_OP
#undef POP_PUSH
// Parametric operations
case WAH_OP_DROP: {
wah_type_t type;
return wah_validation_pop_type(vctx, &type);
}
case WAH_OP_SELECT: {
wah_type_t b_type, a_type;
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32));
WAH_CHECK(wah_validation_pop_type(vctx, &b_type));
WAH_CHECK(wah_validation_pop_type(vctx, &a_type));
// If a_type and b_type are different, and neither is ANY, then it's an error.
WAH_ENSURE(a_type == b_type || a_type == WAH_TYPE_ANY || b_type == WAH_TYPE_ANY, WAH_ERROR_VALIDATION_FAILED);
// If either is ANY, the result is ANY. Otherwise, it's a_type (which equals b_type).
if (a_type == WAH_TYPE_ANY || b_type == WAH_TYPE_ANY) {
return wah_validation_push_type(vctx, WAH_TYPE_ANY);
} else {
return wah_validation_push_type(vctx, a_type);
}
}
// Control flow operations
case WAH_OP_NOP:
return WAH_OK;
case WAH_OP_UNREACHABLE:
vctx->is_unreachable = true;
return WAH_OK;
case WAH_OP_BLOCK:
case WAH_OP_LOOP:
case WAH_OP_IF: {
wah_type_t cond_type;
if (opcode_val == WAH_OP_IF) {
WAH_CHECK(wah_validation_pop_type(vctx, &cond_type));
WAH_CHECK(wah_validate_type_match(cond_type, WAH_TYPE_I32));
}
int32_t block_type_val;
WAH_CHECK(wah_decode_sleb128_32(code_ptr, code_end, &block_type_val));
WAH_ENSURE(vctx->control_sp < WAH_MAX_CONTROL_DEPTH, WAH_ERROR_VALIDATION_FAILED);
wah_validation_control_frame_t* frame = &vctx->control_stack[vctx->control_sp++];
frame->opcode = (wah_opcode_t)opcode_val;
frame->else_found = false;
frame->is_unreachable = vctx->is_unreachable; // Initialize with current reachability
frame->stack_height = vctx->current_stack_depth; // Store current stack height
wah_func_type_t* bt = &frame->block_type;
memset(bt, 0, sizeof(wah_func_type_t));
if (block_type_val < 0) { // Value type
wah_type_t result_type = 0; // Initialize to a default invalid value
switch(block_type_val) {
case -1: result_type = WAH_TYPE_I32; break;
case -2: result_type = WAH_TYPE_I64; break;
case -3: result_type = WAH_TYPE_F32; break;
case -4: result_type = WAH_TYPE_F64; break;
case -0x40: break; // empty
default: return WAH_ERROR_VALIDATION_FAILED;
}
if (result_type != 0) { // If not empty
bt->result_count = 1;
WAH_MALLOC_ARRAY(bt->result_types, 1);
bt->result_types[0] = result_type;
}
} else { // Function type index
uint32_t type_idx = (uint32_t)block_type_val;
WAH_ENSURE(type_idx < vctx->module->type_count, WAH_ERROR_VALIDATION_FAILED);
const wah_func_type_t* referenced_type = &vctx->module->types[type_idx];
bt->param_count = referenced_type->param_count;
if (bt->param_count > 0) {
WAH_MALLOC_ARRAY(bt->param_types, bt->param_count);
memcpy(bt->param_types, referenced_type->param_types, sizeof(wah_type_t) * bt->param_count);
}
bt->result_count = referenced_type->result_count;
if (bt->result_count > 0) {
WAH_MALLOC_ARRAY(bt->result_types, bt->result_count);
memcpy(bt->result_types, referenced_type->result_types, sizeof(wah_type_t) * bt->result_count);
}
}
// Pop params
for (int32_t i = bt->param_count - 1; i >= 0; --i) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, bt->param_types[i]));
}
frame->type_stack_sp = vctx->type_stack.sp;
return WAH_OK;
}
case WAH_OP_ELSE: {
WAH_ENSURE(vctx->control_sp > 0, WAH_ERROR_VALIDATION_FAILED);
wah_validation_control_frame_t* frame = &vctx->control_stack[vctx->control_sp - 1];
WAH_ENSURE(frame->opcode == WAH_OP_IF && !frame->else_found, WAH_ERROR_VALIDATION_FAILED);
frame->else_found = true;
// Pop results of 'if' branch and verify
for (int32_t i = frame->block_type.result_count - 1; i >= 0; --i) {
// If the stack was unreachable, any type is fine. Otherwise, it must match.
WAH_CHECK(wah_validation_pop_and_match_type(vctx, frame->block_type.result_types[i]));
}
// Reset stack to the state before the 'if' block
vctx->type_stack.sp = frame->type_stack_sp;
vctx->current_stack_depth = frame->type_stack_sp;
// The 'else' branch is now reachable
vctx->is_unreachable = false;
return WAH_OK;
}
case WAH_OP_END: {
if (vctx->control_sp == 0) {
// This is the final 'end' of the function body.
// Final validation for the function's result types.
// The stack should contain exactly the function's result types.
if (vctx->func_type->result_count == 0) {
WAH_ENSURE(vctx->type_stack.sp == 0, WAH_ERROR_VALIDATION_FAILED);
} else { // result_count == 1
WAH_ENSURE(vctx->type_stack.sp == 1, WAH_ERROR_VALIDATION_FAILED);
WAH_CHECK(wah_validation_pop_and_match_type(vctx, vctx->func_type->result_types[0]));
}
// Reset unreachable state for the function's end
vctx->is_unreachable = false;
return WAH_OK;
}
wah_validation_control_frame_t* frame = &vctx->control_stack[vctx->control_sp - 1];
// Pop results from the executed branch and verify
for (int32_t i = frame->block_type.result_count - 1; i >= 0; --i) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, frame->block_type.result_types[i]));
}
// Reset stack to the state before the block
vctx->type_stack.sp = frame->type_stack_sp;
vctx->current_stack_depth = frame->type_stack_sp;
// Push final results of the block
for (uint32_t i = 0; i < frame->block_type.result_count; ++i) {
WAH_CHECK(wah_validation_push_type(vctx, frame->block_type.result_types[i]));
}
// Free memory allocated for the block type in the control frame
free(frame->block_type.param_types);
free(frame->block_type.result_types);
vctx->control_sp--;
// Restore the unreachable state from the parent control frame
if (vctx->control_sp > 0) {
vctx->is_unreachable = vctx->control_stack[vctx->control_sp - 1].is_unreachable;
} else {
vctx->is_unreachable = false; // Function level is reachable by default
}
return WAH_OK;
}
case WAH_OP_BR: {
uint32_t label_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &label_idx));
WAH_ENSURE(label_idx < vctx->control_sp, WAH_ERROR_VALIDATION_FAILED);
wah_validation_control_frame_t *target_frame = &vctx->control_stack[vctx->control_sp - 1 - label_idx];
// Pop the expected result types of the target block from the current stack
// The stack must contain these values for the branch to be valid.
for (int32_t i = target_frame->block_type.result_count - 1; i >= 0; --i) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, target_frame->block_type.result_types[i]));
}
// Discard any remaining values on the stack above the target frame's stack height
while (vctx->current_stack_depth > target_frame->stack_height) {
wah_type_t temp_type;
WAH_CHECK(wah_validation_pop_type(vctx, &temp_type));
}
vctx->is_unreachable = true; // br makes the current path unreachable
return WAH_OK;
}
case WAH_OP_BR_IF: {
uint32_t label_idx;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &label_idx));
WAH_ENSURE(label_idx < vctx->control_sp, WAH_ERROR_VALIDATION_FAILED);
// Pop condition (i32) from stack for br_if
WAH_CHECK(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32));
// If the current path is unreachable, the stack is polymorphic, so type checks are trivial.
// We only need to ensure the conceptual stack height is maintained.
if (vctx->is_unreachable) {
return WAH_OK;
}
wah_validation_control_frame_t *target_frame = &vctx->control_stack[vctx->control_sp - 1 - label_idx];
// Check if there are enough values on the stack for the target block's results
// (which become parameters to the target block if branched to).
WAH_ENSURE(vctx->current_stack_depth >= target_frame->block_type.result_count, WAH_ERROR_VALIDATION_FAILED);
// Check the types of these values without popping them
for (uint32_t i = 0; i < target_frame->block_type.result_count; ++i) {
// Access the type stack directly to peek at values
wah_type_t actual_type = vctx->type_stack.data[vctx->type_stack.sp - target_frame->block_type.result_count + i];
WAH_CHECK(wah_validate_type_match(actual_type, target_frame->block_type.result_types[i]));
}
// The stack state for the fall-through path is now correct (condition popped).
// The current path remains reachable.
return WAH_OK;
}
case WAH_OP_BR_TABLE: {
wah_error_t err = WAH_OK; // Declare err here for goto cleanup
uint32_t num_targets;
WAH_CHECK(wah_decode_uleb128(code_ptr, code_end, &num_targets));
// Store all label_idx values to process them after decoding all
uint32_t* label_indices = NULL;
WAH_MALLOC_ARRAY_GOTO(label_indices, num_targets + 1, cleanup_br_table); // +1 for default target
// Decode target table (vector of label_idx)
for (uint32_t i = 0; i < num_targets; ++i) {
WAH_CHECK_GOTO(wah_decode_uleb128(code_ptr, code_end, &label_indices[i]), cleanup_br_table);
WAH_ENSURE_GOTO(label_indices[i] < vctx->control_sp, WAH_ERROR_VALIDATION_FAILED, cleanup_br_table);
}
// Decode default target (label_idx)
WAH_CHECK_GOTO(wah_decode_uleb128(code_ptr, code_end, &label_indices[num_targets]), cleanup_br_table); // Last element is default
WAH_ENSURE_GOTO(label_indices[num_targets] < vctx->control_sp, WAH_ERROR_VALIDATION_FAILED, cleanup_br_table);
// Pop index (i32) from stack
WAH_CHECK_GOTO(wah_validation_pop_and_match_type(vctx, WAH_TYPE_I32), cleanup_br_table);
// Get the block type of the default target as the reference
wah_validation_control_frame_t *default_target_frame = &vctx->control_stack[vctx->control_sp - 1 - label_indices[num_targets]];
const wah_func_type_t *expected_block_type = &default_target_frame->block_type;
// Check type consistency for all targets
for (uint32_t i = 0; i < num_targets + 1; ++i) {
wah_validation_control_frame_t *current_target_frame = &vctx->control_stack[vctx->control_sp - 1 - label_indices[i]];
const wah_func_type_t *current_block_type = &current_target_frame->block_type;
// All target blocks must have the same result count and types
WAH_ENSURE_GOTO(current_block_type->result_count == expected_block_type->result_count, WAH_ERROR_VALIDATION_FAILED, cleanup_br_table);
for (uint32_t j = 0; j < expected_block_type->result_count; ++j) {
WAH_ENSURE_GOTO(current_block_type->result_types[j] == expected_block_type->result_types[j], WAH_ERROR_VALIDATION_FAILED, cleanup_br_table);
}
}
// Pop the expected result types of the target block from the current stack
// The stack must contain these values for the branch to be valid.
for (int32_t i = expected_block_type->result_count - 1; i >= 0; --i) {
WAH_CHECK_GOTO(wah_validation_pop_and_match_type(vctx, expected_block_type->result_types[i]), cleanup_br_table);
}
// Discard any remaining values on the stack above the target frame's stack height
while (vctx->current_stack_depth > default_target_frame->stack_height) {
wah_type_t temp_type;
WAH_CHECK_GOTO(wah_validation_pop_type(vctx, &temp_type), cleanup_br_table);
}
vctx->is_unreachable = true; // br_table makes the current path unreachable
err = WAH_OK; // Set err to WAH_OK before cleanup
cleanup_br_table:
free(label_indices);
return err;
}
case WAH_OP_RETURN:
// Pop the function's result types from the stack
for (int32_t j = vctx->func_type->result_count - 1; j >= 0; --j) {
WAH_CHECK(wah_validation_pop_and_match_type(vctx, vctx->func_type->result_types[j]));
}
vctx->is_unreachable = true; // After return, the current path becomes unreachable
return WAH_OK;
default:
return WAH_ERROR_VALIDATION_FAILED;
}
return WAH_OK;
}
static wah_error_t wah_parse_code_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
wah_error_t err = WAH_OK;
wah_validation_context_t vctx;
memset(&vctx, 0, sizeof(wah_validation_context_t));
uint32_t count;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, section_end, &count), cleanup);
WAH_ENSURE_GOTO(count == module->function_count, WAH_ERROR_VALIDATION_FAILED, cleanup);
module->code_count = count;
WAH_MALLOC_ARRAY_GOTO(module->code_bodies, count, cleanup);
memset(module->code_bodies, 0, sizeof(wah_code_body_t) * count);
for (uint32_t i = 0; i < count; ++i) {
uint32_t body_size;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, section_end, &body_size), cleanup);
const uint8_t *code_body_end = *ptr + body_size;
// Parse locals
uint32_t num_local_entries;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, code_body_end, &num_local_entries), cleanup);
uint32_t current_local_count = 0;
const uint8_t* ptr_count = *ptr;
for (uint32_t j = 0; j < num_local_entries; ++j) {
uint32_t local_type_count;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr_count, code_body_end, &local_type_count), cleanup);
ptr_count++; // Skip the actual type byte
WAH_ENSURE_GOTO(UINT32_MAX - current_local_count >= local_type_count, WAH_ERROR_TOO_LARGE, cleanup);
current_local_count += local_type_count;
}
module->code_bodies[i].local_count = current_local_count;
WAH_MALLOC_ARRAY_GOTO(module->code_bodies[i].local_types, current_local_count, cleanup);
uint32_t local_idx = 0;
for (uint32_t j = 0; j < num_local_entries; ++j) {
uint32_t local_type_count;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, code_body_end, &local_type_count), cleanup);
wah_type_t type;
WAH_CHECK_GOTO(wah_decode_val_type(ptr, code_body_end, &type), cleanup);
for (uint32_t k = 0; k < local_type_count; ++k) {
module->code_bodies[i].local_types[local_idx++] = type;
}
}
module->code_bodies[i].code_size = code_body_end - *ptr;
module->code_bodies[i].code = *ptr;
// --- Validation Pass for Code Body ---
const wah_func_type_t *func_type = &module->types[module->function_type_indices[i]];
memset(&vctx, 0, sizeof(wah_validation_context_t));
vctx.is_unreachable = false; // Functions start in a reachable state
vctx.module = module;
vctx.func_type = func_type;
vctx.total_locals = func_type->param_count + module->code_bodies[i].local_count;
const uint8_t *code_ptr_validation = module->code_bodies[i].code;
const uint8_t *validation_end = code_ptr_validation + module->code_bodies[i].code_size;
while (code_ptr_validation < validation_end) {
uint16_t current_opcode_val;
WAH_CHECK_GOTO(wah_decode_opcode(&code_ptr_validation, validation_end, &current_opcode_val), cleanup);
if (current_opcode_val == WAH_OP_END) {
if (vctx.control_sp == 0) { // End of function
if (vctx.func_type->result_count == 0) {
WAH_ENSURE_GOTO(vctx.type_stack.sp == 0, WAH_ERROR_VALIDATION_FAILED, cleanup); // Unmatched control frames
} else { // result_count == 1
// If unreachable, the stack is polymorphic, so we don't strictly check sp.
// We still pop to ensure the conceptual stack height is correct.
WAH_CHECK_GOTO(wah_validation_pop_and_match_type(&vctx, vctx.func_type->result_types[0]), cleanup);
}
break; // End of validation loop
}
}
WAH_CHECK_GOTO(wah_validate_opcode(current_opcode_val, &code_ptr_validation, validation_end, &vctx, &module->code_bodies[i]), cleanup);
}
WAH_ENSURE_GOTO(vctx.control_sp == 0, WAH_ERROR_VALIDATION_FAILED, cleanup);
// --- End Validation Pass ---
module->code_bodies[i].max_stack_depth = vctx.max_stack_depth;
// Pre-parse the code for optimized execution
WAH_CHECK_GOTO(wah_preparse_code(module, i, module->code_bodies[i].code, module->code_bodies[i].code_size, &module->code_bodies[i].parsed_code), cleanup);
*ptr = code_body_end;
}
err = WAH_OK; // Ensure err is WAH_OK if everything succeeded
cleanup:
if (err != WAH_OK) {
// Free memory allocated for control frames during validation
for (int32_t j = vctx.control_sp - 1; j >= 0; --j) {
wah_validation_control_frame_t* frame = &vctx.control_stack[j];
free(frame->block_type.param_types);
free(frame->block_type.result_types);
}
if (module->code_bodies) {
for (uint32_t i = 0; i < module->code_count; ++i) {
free(module->code_bodies[i].local_types);
wah_free_parsed_code(&module->code_bodies[i].parsed_code);
}
free(module->code_bodies);
module->code_bodies = NULL;
}
}
return err;
}
static wah_error_t wah_parse_global_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->global_count = count;
WAH_MALLOC_ARRAY(module->globals, count);
memset(module->globals, 0, sizeof(wah_global_t) * count);
for (uint32_t i = 0; i < count; ++i) {
wah_type_t global_declared_type;
WAH_CHECK(wah_decode_val_type(ptr, section_end, &global_declared_type));
module->globals[i].type = global_declared_type;
// Mutability
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
module->globals[i].is_mutable = (*(*ptr)++ == 1);
// Init Expr (only const expressions are supported for initial values)
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
wah_opcode_t opcode = (wah_opcode_t)*(*ptr)++;
switch (opcode) {
case WAH_OP_I32_CONST: {
WAH_ENSURE(global_declared_type == WAH_TYPE_I32, WAH_ERROR_VALIDATION_FAILED);
int32_t val;
WAH_CHECK(wah_decode_sleb128_32(ptr, section_end, &val));
module->globals[i].initial_value.i32 = val;
break;
}
case WAH_OP_I64_CONST: {
WAH_ENSURE(global_declared_type == WAH_TYPE_I64, WAH_ERROR_VALIDATION_FAILED);
int64_t val;
WAH_CHECK(wah_decode_sleb128_64(ptr, section_end, &val));
module->globals[i].initial_value.i64 = val;
break;
}
case WAH_OP_F32_CONST: {
WAH_ENSURE(global_declared_type == WAH_TYPE_F32, WAH_ERROR_VALIDATION_FAILED);
WAH_ENSURE(*ptr + 4 <= section_end, WAH_ERROR_UNEXPECTED_EOF);
module->globals[i].initial_value.f32 = wah_read_f32_le(*ptr);
*ptr += 4;
break;
}
case WAH_OP_F64_CONST: {
WAH_ENSURE(global_declared_type == WAH_TYPE_F64, WAH_ERROR_VALIDATION_FAILED);
WAH_ENSURE(*ptr + 8 <= section_end, WAH_ERROR_UNEXPECTED_EOF);
module->globals[i].initial_value.f64 = wah_read_f64_le(*ptr);
*ptr += 8;
break;
}
default: {
// Only const expressions supported for global initializers for now
return WAH_ERROR_VALIDATION_FAILED;
}
}
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
WAH_ENSURE(*(*ptr)++ == WAH_OP_END, WAH_ERROR_VALIDATION_FAILED); // Expect END opcode after init_expr
}
return WAH_OK;
}
static wah_error_t wah_parse_memory_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->memory_count = count;
if (count > 0) {
WAH_MALLOC_ARRAY(module->memories, count);
memset(module->memories, 0, sizeof(wah_memory_type_t) * count);
for (uint32_t i = 0; i < count; ++i) {
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
uint8_t flags = *(*ptr)++; // Flags for memory type (0x00 for fixed, 0x01 for resizable)
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->memories[i].min_pages));
if (flags & 0x01) { // If resizable, read max_pages
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->memories[i].max_pages));
} else {
module->memories[i].max_pages = module->memories[i].min_pages; // Fixed size, max is same as min
}
}
}
return WAH_OK;
}
static wah_error_t wah_parse_table_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->table_count = count;
if (count > 0) {
WAH_MALLOC_ARRAY(module->tables, count);
memset(module->tables, 0, sizeof(wah_table_type_t) * count);
for (uint32_t i = 0; i < count; ++i) {
wah_type_t elem_type;
WAH_CHECK(wah_decode_ref_type(ptr, section_end, &elem_type));
module->tables[i].elem_type = elem_type;
WAH_ENSURE(elem_type == WAH_TYPE_FUNCREF, WAH_ERROR_VALIDATION_FAILED); // Only funcref is supported for now
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
uint8_t flags = *(*ptr)++; // Flags for table type (0x00 for fixed, 0x01 for resizable)
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->tables[i].min_elements));
if (flags & 0x01) { // If resizable, read max_elements
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->tables[i].max_elements));
} else {
module->tables[i].max_elements = module->tables[i].min_elements; // Fixed size, max is same as min
}
}
}
return WAH_OK;
}
// Placeholder for unimplemented sections
static wah_error_t wah_parse_unimplemented_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
(void)module; // Suppress unused parameter warning
*ptr = section_end; // Skip the section
return WAH_OK;
}
static wah_error_t wah_parse_custom_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
return wah_parse_unimplemented_section(ptr, section_end, module);
}
static wah_error_t wah_parse_import_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
return wah_parse_unimplemented_section(ptr, section_end, module);
}
static wah_error_t wah_parse_export_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
wah_error_t err = WAH_OK;
uint32_t count = 0;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, section_end, &count), cleanup);
module->export_count = count;
WAH_MALLOC_ARRAY_GOTO(module->exports, count, cleanup);
memset(module->exports, 0, sizeof(wah_export_t) * count);
for (uint32_t i = 0; i < count; ++i) {
wah_export_t *export_entry = &module->exports[i];
// Name length
uint32_t name_len;
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, section_end, &name_len), cleanup);
export_entry->name_len = name_len;
// Name string
WAH_ENSURE_GOTO(*ptr + name_len <= section_end, WAH_ERROR_UNEXPECTED_EOF, cleanup);
// Allocate memory for the name and copy it, ensuring null-termination
WAH_MALLOC_ARRAY_GOTO(export_entry->name, name_len + 1, cleanup);
memcpy((void*)export_entry->name, *ptr, name_len);
((char*)export_entry->name)[name_len] = '\0';
WAH_ENSURE_GOTO(wah_is_valid_utf8(export_entry->name, export_entry->name_len), WAH_ERROR_VALIDATION_FAILED, cleanup);
// Check for duplicate export names
for (uint32_t j = 0; j < i; ++j) {
if (module->exports[j].name_len == export_entry->name_len &&
strncmp(module->exports[j].name, export_entry->name, export_entry->name_len) == 0) {
err = WAH_ERROR_VALIDATION_FAILED; // Duplicate export name
goto cleanup;
}
}
*ptr += name_len;
// Export kind
WAH_ENSURE_GOTO(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF, cleanup);
export_entry->kind = *(*ptr)++;
// Export index
WAH_CHECK_GOTO(wah_decode_uleb128(ptr, section_end, &export_entry->index), cleanup);
// Basic validation of index based on kind
switch (export_entry->kind) {
case 0: // Function
WAH_ENSURE_GOTO(export_entry->index < module->function_count, WAH_ERROR_VALIDATION_FAILED, cleanup);
break;
case 1: // Table
WAH_ENSURE_GOTO(export_entry->index < module->table_count, WAH_ERROR_VALIDATION_FAILED, cleanup);
break;
case 2: // Memory
WAH_ENSURE_GOTO(export_entry->index < module->memory_count, WAH_ERROR_VALIDATION_FAILED, cleanup);
break;
case 3: // Global
WAH_ENSURE_GOTO(export_entry->index < module->global_count, WAH_ERROR_VALIDATION_FAILED, cleanup);
break;
default:
err = WAH_ERROR_VALIDATION_FAILED; // Unknown export kind
goto cleanup;
}
}
cleanup:
if (err != WAH_OK) {
if (module->exports) {
// Free names that were already allocated
for (uint32_t k = 0; k < count; ++k) {
if (module->exports[k].name) {
free((void*)module->exports[k].name);
}
}
free(module->exports);
module->exports = NULL;
module->export_count = 0;
}
}
return err;
}
static wah_error_t wah_parse_start_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->start_function_idx));
WAH_ENSURE(module->start_function_idx < module->function_count, WAH_ERROR_VALIDATION_FAILED);
module->has_start_function = true;
return WAH_OK;
}
static wah_error_t wah_parse_element_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
module->element_segment_count = count;
if (count > 0) {
WAH_MALLOC_ARRAY(module->element_segments, count);
memset(module->element_segments, 0, sizeof(wah_element_segment_t) * count);
for (uint32_t i = 0; i < count; ++i) {
wah_element_segment_t *segment = &module->element_segments[i];
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->table_idx));
WAH_ENSURE(segment->table_idx == 0, WAH_ERROR_VALIDATION_FAILED); // For now, only table 0 is supported
// Parse offset_expr (expected to be i32.const X end)
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
wah_opcode_t opcode = (wah_opcode_t)*(*ptr)++;
WAH_ENSURE(opcode == WAH_OP_I32_CONST, WAH_ERROR_VALIDATION_FAILED);
int32_t offset_val;
WAH_CHECK(wah_decode_sleb128_32(ptr, section_end, &offset_val));
segment->offset = (uint32_t)offset_val;
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
WAH_ENSURE(*(*ptr)++ == WAH_OP_END, WAH_ERROR_VALIDATION_FAILED);
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->num_elems));
// Validate that the segment fits within the table's limits
WAH_ENSURE(segment->table_idx < module->table_count, WAH_ERROR_VALIDATION_FAILED);
WAH_ENSURE((uint64_t)segment->offset + segment->num_elems <= module->tables[segment->table_idx].min_elements, WAH_ERROR_VALIDATION_FAILED);
if (segment->num_elems > 0) {
WAH_MALLOC_ARRAY(segment->func_indices, segment->num_elems);
for (uint32_t j = 0; j < segment->num_elems; ++j) {
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->func_indices[j]));
WAH_ENSURE(segment->func_indices[j] < module->function_count, WAH_ERROR_VALIDATION_FAILED);
}
}
}
}
return WAH_OK;
}
static wah_error_t wah_parse_data_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
uint32_t count;
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &count));
// If a datacount section was present, validate that the data section count matches.
// Otherwise, set the data_segment_count from this section.
if (module->has_data_count_section) {
WAH_ENSURE(count == module->data_segment_count, WAH_ERROR_VALIDATION_FAILED);
} else {
module->data_segment_count = count;
}
if (count > 0) {
WAH_MALLOC_ARRAY(module->data_segments, count);
memset(module->data_segments, 0, sizeof(wah_data_segment_t) * count);
for (uint32_t i = 0; i < count; ++i) {
wah_data_segment_t *segment = &module->data_segments[i];
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->flags));
if (segment->flags == 0x00) { // Active segment, memory index 0
segment->memory_idx = 0;
} else if (segment->flags == 0x01) { // Passive segment
// No memory index or offset expression for passive segments
} else if (segment->flags == 0x02) { // Active segment, with memory index
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->memory_idx));
WAH_ENSURE(segment->memory_idx == 0, WAH_ERROR_VALIDATION_FAILED); // Only memory 0 supported
} else {
return WAH_ERROR_VALIDATION_FAILED; // Unknown data segment flags
}
if (segment->flags == 0x00 || segment->flags == 0x02) { // Active segments have offset expression
// Parse offset_expr (expected to be i32.const X end)
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
wah_opcode_t opcode = (wah_opcode_t)*(*ptr)++;
WAH_ENSURE(opcode == WAH_OP_I32_CONST, WAH_ERROR_VALIDATION_FAILED);
int32_t offset_val;
WAH_CHECK(wah_decode_sleb128_32(ptr, section_end, &offset_val));
segment->offset = (uint32_t)offset_val;
WAH_ENSURE(*ptr < section_end, WAH_ERROR_UNEXPECTED_EOF);
WAH_ENSURE(*(*ptr)++ == WAH_OP_END, WAH_ERROR_VALIDATION_FAILED);
}
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &segment->data_len));
WAH_ENSURE(*ptr + segment->data_len <= section_end, WAH_ERROR_UNEXPECTED_EOF);
segment->data = *ptr;
*ptr += segment->data_len;
}
}
return WAH_OK;
}
static wah_error_t wah_parse_datacount_section(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module) {
WAH_CHECK(wah_decode_uleb128(ptr, section_end, &module->data_segment_count));
module->has_data_count_section = true;
return WAH_OK;
}
// Pre-parsing function to convert bytecode to optimized structure
static wah_error_t wah_preparse_code(const wah_module_t* module, uint32_t func_idx, const uint8_t *code, uint32_t code_size, wah_parsed_code_t *parsed_code) {
(void)module; (void)func_idx; // Suppress unused parameter warnings
wah_error_t err = WAH_OK;
memset(parsed_code, 0, sizeof(wah_parsed_code_t));
typedef struct { wah_opcode_t opcode; uint32_t target_idx; } wah_control_frame_t;
uint32_t* block_targets = NULL;
uint32_t block_target_count = 0, block_target_capacity = 0;
wah_control_frame_t control_stack[WAH_MAX_CONTROL_DEPTH];
uint32_t control_sp = 0;
uint32_t preparsed_size = 0;
const uint8_t *ptr = code, *end = code + code_size;
// --- Pass 1: Find block boundaries and calculate preparsed size ---
while (ptr < end) {
const uint8_t* instr_ptr = ptr;
uint16_t opcode;
WAH_CHECK_GOTO(wah_decode_opcode(&ptr, end, &opcode), cleanup);
uint32_t preparsed_instr_size = sizeof(uint16_t);
#define GROW_BLOCK_TARGETS() do { \
if (block_target_count >= block_target_capacity) { \
block_target_capacity = block_target_capacity == 0 ? 16 : block_target_capacity * 2; \
WAH_REALLOC_ARRAY_GOTO(block_targets, block_target_capacity, cleanup); \
} \
} while (0)
switch (opcode) {
case WAH_OP_BLOCK: case WAH_OP_LOOP: case WAH_OP_IF: {
int32_t block_type;
WAH_CHECK_GOTO(wah_decode_sleb128_32(&ptr, end, &block_type), cleanup);
GROW_BLOCK_TARGETS();
WAH_ENSURE_GOTO(control_sp < WAH_MAX_CONTROL_DEPTH, WAH_ERROR_VALIDATION_FAILED, cleanup);
uint32_t target_idx = block_target_count++;
block_targets[target_idx] = preparsed_size; // To be overwritten for WAH_OP_IF
control_stack[control_sp++] = (wah_control_frame_t){.opcode=(wah_opcode_t)opcode, .target_idx=target_idx};
preparsed_instr_size = (opcode == WAH_OP_IF) ? sizeof(uint16_t) + sizeof(uint32_t) : 0;
break;
}
case WAH_OP_ELSE: {
WAH_ENSURE_GOTO(control_sp > 0 && control_stack[control_sp - 1].opcode == WAH_OP_IF, WAH_ERROR_VALIDATION_FAILED, cleanup);
preparsed_instr_size = sizeof(uint16_t) + sizeof(uint32_t);
block_targets[control_stack[control_sp - 1].target_idx] = preparsed_size + preparsed_instr_size;
GROW_BLOCK_TARGETS();
uint32_t target_idx = block_target_count++;
control_stack[control_sp - 1] = (wah_control_frame_t){.opcode=(wah_opcode_t)opcode, .target_idx=target_idx};
break;
}
case WAH_OP_END: {
if (control_sp > 0) {
wah_control_frame_t frame = control_stack[--control_sp];
if (frame.opcode != WAH_OP_LOOP) { // BLOCK, IF, ELSE
block_targets[frame.target_idx] = preparsed_size;
}
preparsed_instr_size = 0;
} else { // Final END
preparsed_instr_size = sizeof(uint16_t);
}
break;
}
case WAH_OP_BR: case WAH_OP_BR_IF: {
uint32_t d;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &d), cleanup);
preparsed_instr_size += sizeof(uint32_t);
break;
}
case WAH_OP_BR_TABLE: {
uint32_t num_targets;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &num_targets), cleanup);
preparsed_instr_size += sizeof(uint32_t) * (num_targets + 2);
for (uint32_t i = 0; i < num_targets + 1; ++i) {
uint32_t d;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &d), cleanup);
}
break;
}
case WAH_OP_LOCAL_GET: case WAH_OP_LOCAL_SET: case WAH_OP_LOCAL_TEE:
case WAH_OP_GLOBAL_GET: case WAH_OP_GLOBAL_SET: case WAH_OP_CALL: {
uint32_t v;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &v), cleanup);
preparsed_instr_size += sizeof(uint32_t);
break;
}
case WAH_OP_CALL_INDIRECT: {
uint32_t t, i;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &t), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &i), cleanup);
preparsed_instr_size += sizeof(uint32_t) * 2;
break;
}
case WAH_OP_I32_CONST: {
int32_t v;
WAH_CHECK_GOTO(wah_decode_sleb128_32(&ptr, end, &v), cleanup);
preparsed_instr_size += sizeof(int32_t);
break;
}
case WAH_OP_I64_CONST: {
int64_t v;
WAH_CHECK_GOTO(wah_decode_sleb128_64(&ptr, end, &v), cleanup);
preparsed_instr_size += sizeof(int64_t);
break;
}
case WAH_OP_F32_CONST: ptr += 4; preparsed_instr_size += 4; break;
case WAH_OP_F64_CONST: ptr += 8; preparsed_instr_size += 8; break;
case WAH_OP_V128_CONST: ptr += 16; preparsed_instr_size += 16; break;
case WAH_OP_I32_LOAD: case WAH_OP_I64_LOAD: case WAH_OP_F32_LOAD: case WAH_OP_F64_LOAD:
case WAH_OP_I32_LOAD8_S: case WAH_OP_I32_LOAD8_U: case WAH_OP_I32_LOAD16_S: case WAH_OP_I32_LOAD16_U:
case WAH_OP_I64_LOAD8_S: case WAH_OP_I64_LOAD8_U: case WAH_OP_I64_LOAD16_S: case WAH_OP_I64_LOAD16_U:
case WAH_OP_I64_LOAD32_S: case WAH_OP_I64_LOAD32_U:
case WAH_OP_I32_STORE: case WAH_OP_I64_STORE: case WAH_OP_F32_STORE: case WAH_OP_F64_STORE:
case WAH_OP_I32_STORE8: case WAH_OP_I32_STORE16: case WAH_OP_I64_STORE8: case WAH_OP_I64_STORE16: case WAH_OP_I64_STORE32:
case WAH_OP_V128_LOAD: case WAH_OP_V128_STORE:
case WAH_OP_V128_LOAD8X8_S: case WAH_OP_V128_LOAD8X8_U:
case WAH_OP_V128_LOAD16X4_S: case WAH_OP_V128_LOAD16X4_U:
case WAH_OP_V128_LOAD32X2_S: case WAH_OP_V128_LOAD32X2_U:
case WAH_OP_V128_LOAD8_SPLAT: case WAH_OP_V128_LOAD16_SPLAT:
case WAH_OP_V128_LOAD32_SPLAT: case WAH_OP_V128_LOAD64_SPLAT:
case WAH_OP_V128_LOAD32_ZERO: case WAH_OP_V128_LOAD64_ZERO: {
uint32_t a, o;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &a), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &o), cleanup);
preparsed_instr_size += sizeof(uint32_t); // For offset (ignore align)
break;
}
case WAH_OP_V128_LOAD8_LANE: case WAH_OP_V128_LOAD16_LANE:
case WAH_OP_V128_LOAD32_LANE: case WAH_OP_V128_LOAD64_LANE: {
uint32_t a, o, l;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &a), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &o), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &l), cleanup);
preparsed_instr_size += sizeof(uint32_t) * 2; // For offset and lane_idx
break;
}
case WAH_OP_I8X16_SHUFFLE: ptr += 16; preparsed_instr_size += 16; break;
case WAH_OP_I8X16_EXTRACT_LANE_S: case WAH_OP_I8X16_EXTRACT_LANE_U: case WAH_OP_I8X16_REPLACE_LANE:
case WAH_OP_I16X8_EXTRACT_LANE_S: case WAH_OP_I16X8_EXTRACT_LANE_U: case WAH_OP_I16X8_REPLACE_LANE:
case WAH_OP_I32X4_EXTRACT_LANE: case WAH_OP_I32X4_REPLACE_LANE:
case WAH_OP_I64X2_EXTRACT_LANE: case WAH_OP_I64X2_REPLACE_LANE:
case WAH_OP_F32X4_EXTRACT_LANE: case WAH_OP_F32X4_REPLACE_LANE:
case WAH_OP_F64X2_EXTRACT_LANE: case WAH_OP_F64X2_REPLACE_LANE: {
ptr += 1;
preparsed_instr_size += 1;
break;
}
case WAH_OP_MEMORY_SIZE: case WAH_OP_MEMORY_GROW: case WAH_OP_MEMORY_FILL: {
uint32_t m;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &m), cleanup);
break;
}
case WAH_OP_MEMORY_INIT: {
uint32_t data_idx, mem_idx;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &data_idx), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &mem_idx), cleanup);
preparsed_instr_size += sizeof(uint32_t) * 2; // For data_idx and mem_idx
break;
}
case WAH_OP_MEMORY_COPY: {
uint32_t dest_mem_idx, src_mem_idx;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &dest_mem_idx), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &src_mem_idx), cleanup);
preparsed_instr_size += sizeof(uint32_t) * 2; // For dest_mem_idx and src_mem_idx
break;
}
}
preparsed_size += preparsed_instr_size;
ptr = instr_ptr + (ptr - instr_ptr); // This is not a bug; it's to make it clear that ptr is advanced inside the switch
}
WAH_ENSURE_GOTO(control_sp == 0, WAH_ERROR_VALIDATION_FAILED, cleanup);
// --- Allocate and perform Pass 2: Generate preparsed bytecode ---
WAH_MALLOC_ARRAY_GOTO(parsed_code->bytecode, preparsed_size, cleanup);
parsed_code->bytecode_size = preparsed_size;
ptr = code; control_sp = 0;
uint8_t* write_ptr = parsed_code->bytecode;
uint32_t current_block_idx = 0;
while (ptr < end) {
uint16_t opcode;
const uint8_t* saved_ptr = ptr;
WAH_CHECK_GOTO(wah_decode_opcode(&ptr, end, &opcode), cleanup);
if (opcode == WAH_OP_BLOCK || opcode == WAH_OP_LOOP) {
int32_t block_type; WAH_CHECK_GOTO(wah_decode_sleb128_32(&ptr, end, &block_type), cleanup);
WAH_ENSURE_GOTO(control_sp < WAH_MAX_CONTROL_DEPTH, WAH_ERROR_VALIDATION_FAILED, cleanup);
control_stack[control_sp++] = (wah_control_frame_t){.opcode=(wah_opcode_t)opcode, .target_idx=current_block_idx++};
continue;
}
if (opcode == WAH_OP_END) {
if (control_sp > 0) { control_sp--; continue; }
}
wah_write_u16_le(write_ptr, opcode);
write_ptr += sizeof(uint16_t);
switch (opcode) {
case WAH_OP_IF: {
int32_t block_type;
WAH_CHECK_GOTO(wah_decode_sleb128_32(&ptr, end, &block_type), cleanup);
WAH_ENSURE_GOTO(control_sp < WAH_MAX_CONTROL_DEPTH, WAH_ERROR_VALIDATION_FAILED, cleanup);
control_stack[control_sp++] = (wah_control_frame_t){.opcode=WAH_OP_IF, .target_idx=current_block_idx};
wah_write_u32_le(write_ptr, block_targets[current_block_idx++]);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_ELSE: {
WAH_ENSURE_GOTO(control_sp > 0 && control_stack[control_sp - 1].opcode == WAH_OP_IF, WAH_ERROR_VALIDATION_FAILED, cleanup);
control_stack[control_sp - 1] = (wah_control_frame_t){.opcode=WAH_OP_ELSE, .target_idx=current_block_idx};
wah_write_u32_le(write_ptr, block_targets[current_block_idx++]);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_BR: case WAH_OP_BR_IF: {
uint32_t relative_depth;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &relative_depth), cleanup);
WAH_ENSURE_GOTO(relative_depth < control_sp, WAH_ERROR_VALIDATION_FAILED, cleanup);
wah_control_frame_t* frame = &control_stack[control_sp - 1 - relative_depth];
wah_write_u32_le(write_ptr, block_targets[frame->target_idx]);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_BR_TABLE: {
uint32_t num_targets;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &num_targets), cleanup);
wah_write_u32_le(write_ptr, num_targets);
write_ptr += sizeof(uint32_t);
for (uint32_t i = 0; i < num_targets + 1; ++i) {
uint32_t relative_depth;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &relative_depth), cleanup);
WAH_ENSURE_GOTO(relative_depth < control_sp, WAH_ERROR_VALIDATION_FAILED, cleanup);
wah_control_frame_t* frame = &control_stack[control_sp - 1 - relative_depth];
wah_write_u32_le(write_ptr, block_targets[frame->target_idx]);
write_ptr += sizeof(uint32_t);
}
break;
}
case WAH_OP_LOCAL_GET: case WAH_OP_LOCAL_SET: case WAH_OP_LOCAL_TEE:
case WAH_OP_GLOBAL_GET: case WAH_OP_GLOBAL_SET: case WAH_OP_CALL: {
uint32_t v;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &v), cleanup);
wah_write_u32_le(write_ptr, v);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_CALL_INDIRECT: {
uint32_t t, i;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &t), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &i), cleanup);
wah_write_u32_le(write_ptr, t);
write_ptr += sizeof(uint32_t);
wah_write_u32_le(write_ptr, i);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_I32_CONST: {
int32_t v;
WAH_CHECK_GOTO(wah_decode_sleb128_32(&ptr, end, &v), cleanup);
wah_write_u32_le(write_ptr, (uint32_t)v);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_I64_CONST: {
int64_t v;
WAH_CHECK_GOTO(wah_decode_sleb128_64(&ptr, end, &v), cleanup);
wah_write_u64_le(write_ptr, (uint64_t)v);
write_ptr += sizeof(uint64_t);
break;
}
case WAH_OP_F32_CONST: memcpy(write_ptr, ptr, 4); ptr += 4; write_ptr += 4; break;
case WAH_OP_F64_CONST: memcpy(write_ptr, ptr, 8); ptr += 8; write_ptr += 8; break;
case WAH_OP_V128_CONST: memcpy(write_ptr, ptr, 16); ptr += 16; write_ptr += 16; break;
case WAH_OP_I32_LOAD: case WAH_OP_I64_LOAD: case WAH_OP_F32_LOAD: case WAH_OP_F64_LOAD:
case WAH_OP_I32_LOAD8_S: case WAH_OP_I32_LOAD8_U: case WAH_OP_I32_LOAD16_S: case WAH_OP_I32_LOAD16_U:
case WAH_OP_I64_LOAD8_S: case WAH_OP_I64_LOAD8_U: case WAH_OP_I64_LOAD16_S: case WAH_OP_I64_LOAD16_U:
case WAH_OP_I64_LOAD32_S: case WAH_OP_I64_LOAD32_U:
case WAH_OP_I32_STORE: case WAH_OP_I64_STORE: case WAH_OP_F32_STORE: case WAH_OP_F64_STORE:
case WAH_OP_I32_STORE8: case WAH_OP_I32_STORE16: case WAH_OP_I64_STORE8: case WAH_OP_I64_STORE16: case WAH_OP_I64_STORE32:
case WAH_OP_V128_LOAD: case WAH_OP_V128_STORE:
case WAH_OP_V128_LOAD8X8_S: case WAH_OP_V128_LOAD8X8_U:
case WAH_OP_V128_LOAD16X4_S: case WAH_OP_V128_LOAD16X4_U:
case WAH_OP_V128_LOAD32X2_S: case WAH_OP_V128_LOAD32X2_U:
case WAH_OP_V128_LOAD8_SPLAT: case WAH_OP_V128_LOAD16_SPLAT:
case WAH_OP_V128_LOAD32_SPLAT: case WAH_OP_V128_LOAD64_SPLAT:
case WAH_OP_V128_LOAD32_ZERO: case WAH_OP_V128_LOAD64_ZERO: {
uint32_t a, o;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &a), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &o), cleanup);
wah_write_u32_le(write_ptr, o);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_V128_LOAD8_LANE: case WAH_OP_V128_LOAD16_LANE:
case WAH_OP_V128_LOAD32_LANE: case WAH_OP_V128_LOAD64_LANE: {
uint32_t a, o, l;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &a), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &o), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &l), cleanup);
wah_write_u32_le(write_ptr, o);
write_ptr += sizeof(uint32_t);
wah_write_u32_le(write_ptr, l);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_I8X16_SHUFFLE: {
memcpy(write_ptr, ptr, 16);
ptr += 16;
write_ptr += 16;
break;
}
case WAH_OP_I8X16_EXTRACT_LANE_S: case WAH_OP_I8X16_EXTRACT_LANE_U: case WAH_OP_I8X16_REPLACE_LANE:
case WAH_OP_I16X8_EXTRACT_LANE_S: case WAH_OP_I16X8_EXTRACT_LANE_U: case WAH_OP_I16X8_REPLACE_LANE:
case WAH_OP_I32X4_EXTRACT_LANE: case WAH_OP_I32X4_REPLACE_LANE:
case WAH_OP_I64X2_EXTRACT_LANE: case WAH_OP_I64X2_REPLACE_LANE:
case WAH_OP_F32X4_EXTRACT_LANE: case WAH_OP_F32X4_REPLACE_LANE:
case WAH_OP_F64X2_EXTRACT_LANE: case WAH_OP_F64X2_REPLACE_LANE: {
*write_ptr++ = *ptr++;
break;
}
case WAH_OP_MEMORY_SIZE: case WAH_OP_MEMORY_GROW: case WAH_OP_MEMORY_FILL: {
uint32_t m;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &m), cleanup);
break;
}
case WAH_OP_MEMORY_INIT: {
uint32_t data_idx, mem_idx;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &data_idx), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &mem_idx), cleanup);
wah_write_u32_le(write_ptr, data_idx);
write_ptr += sizeof(uint32_t);
wah_write_u32_le(write_ptr, mem_idx);
write_ptr += sizeof(uint32_t);
break;
}
case WAH_OP_MEMORY_COPY: {
uint32_t dest_mem_idx, src_mem_idx;
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &dest_mem_idx), cleanup);
WAH_CHECK_GOTO(wah_decode_uleb128(&ptr, end, &src_mem_idx), cleanup);
wah_write_u32_le(write_ptr, dest_mem_idx);
write_ptr += sizeof(uint32_t);
wah_write_u32_le(write_ptr, src_mem_idx);
write_ptr += sizeof(uint32_t);
break;
}
}
ptr = saved_ptr + (ptr - saved_ptr);
}
cleanup:
free(block_targets);
if (err != WAH_OK && parsed_code->bytecode) {
free(parsed_code->bytecode);
parsed_code->bytecode = NULL;
parsed_code->bytecode_size = 0;
}
return err;
}
static void wah_free_parsed_code(wah_parsed_code_t *parsed_code) {
if (!parsed_code) return;
free(parsed_code->bytecode);
}
static wah_error_t wah_decode_opcode(const uint8_t **ptr, const uint8_t *end, uint16_t *opcode_val) {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
uint8_t first_byte = *(*ptr)++;
uint32_t sub_opcode_val;
switch (first_byte) {
case 0xFB:
WAH_CHECK(wah_decode_uleb128(ptr, end, &sub_opcode_val));
WAH_ENSURE(sub_opcode_val < WAH_FC - WAH_FB, WAH_ERROR_VALIDATION_FAILED);
*opcode_val = WAH_FB + (uint16_t)sub_opcode_val;
break;
case 0xFC:
WAH_CHECK(wah_decode_uleb128(ptr, end, &sub_opcode_val));
WAH_ENSURE(sub_opcode_val < WAH_FD - WAH_FC, WAH_ERROR_VALIDATION_FAILED);
*opcode_val = WAH_FC + (uint16_t)sub_opcode_val;
break;
case 0xFD:
WAH_CHECK(wah_decode_uleb128(ptr, end, &sub_opcode_val));
WAH_ENSURE(sub_opcode_val < WAH_FE - WAH_FD, WAH_ERROR_VALIDATION_FAILED);
*opcode_val = WAH_FD + (uint16_t)sub_opcode_val;
break;
default:
WAH_ENSURE(first_byte < WAH_FB, WAH_ERROR_VALIDATION_FAILED);
*opcode_val = (uint16_t)first_byte;
break;
}
return WAH_OK;
}
// Helper function to decode a raw byte representing a value type into a wah_type_t
static wah_error_t wah_decode_val_type(const uint8_t **ptr, const uint8_t *end, wah_type_t *out_type) {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
uint8_t byte = *(*ptr)++;
switch (byte) {
case 0x7F: *out_type = WAH_TYPE_I32; return WAH_OK;
case 0x7E: *out_type = WAH_TYPE_I64; return WAH_OK;
case 0x7D: *out_type = WAH_TYPE_F32; return WAH_OK;
case 0x7C: *out_type = WAH_TYPE_F64; return WAH_OK;
case 0x7B: *out_type = WAH_TYPE_V128; return WAH_OK;
default: return WAH_ERROR_VALIDATION_FAILED; // Unknown value type
}
}
// Helper function to decode a raw byte representing a reference type into a wah_type_t
static wah_error_t wah_decode_ref_type(const uint8_t **ptr, const uint8_t *end, wah_type_t *out_type) {
WAH_ENSURE(*ptr < end, WAH_ERROR_UNEXPECTED_EOF);
uint8_t byte = *(*ptr)++;
switch (byte) {
case 0x70: *out_type = WAH_TYPE_FUNCREF; return WAH_OK;
default: return WAH_ERROR_VALIDATION_FAILED; // Unknown value type
}
}
// Global array of section handlers, indexed by the section ID
static const struct wah_section_handler_s {
int8_t order; // Expected order of the section (0 for custom, 1 for Type, etc.)
wah_error_t (*parser_func)(const uint8_t **ptr, const uint8_t *section_end, wah_module_t *module);
} wah_section_handlers[] = {
[0] = { .order = 0, .parser_func = wah_parse_custom_section },
[1] = { .order = 1, .parser_func = wah_parse_type_section },
[2] = { .order = 2, .parser_func = wah_parse_import_section },
[3] = { .order = 3, .parser_func = wah_parse_function_section },
[4] = { .order = 4, .parser_func = wah_parse_table_section },
[5] = { .order = 5, .parser_func = wah_parse_memory_section },
[6] = { .order = 6, .parser_func = wah_parse_global_section },
[7] = { .order = 7, .parser_func = wah_parse_export_section },
[8] = { .order = 8, .parser_func = wah_parse_start_section },
[9] = { .order = 9, .parser_func = wah_parse_element_section },
[12] = { .order = 10, .parser_func = wah_parse_datacount_section },
[10] = { .order = 11, .parser_func = wah_parse_code_section },
[11] = { .order = 12, .parser_func = wah_parse_data_section },
};
wah_error_t wah_parse_module(const uint8_t *wasm_binary, size_t binary_size, wah_module_t *module) {
wah_error_t err = WAH_OK;
WAH_ENSURE(wasm_binary && module && binary_size >= 8, WAH_ERROR_UNEXPECTED_EOF);
memset(module, 0, sizeof(wah_module_t)); // Initialize module struct
const uint8_t *ptr = wasm_binary;
const uint8_t *end = wasm_binary + binary_size;
// For section order validation
int8_t last_parsed_order = 0; // Start with 0, as Type section is 1. Custom sections are 0 in map.
// 1. Check Magic Number
uint32_t magic = wah_read_u32_le(ptr);
ptr += 4;
WAH_ENSURE(magic == 0x6D736100, WAH_ERROR_INVALID_MAGIC_NUMBER);
// 2. Check Version
WAH_ENSURE(ptr + 4 <= end, WAH_ERROR_UNEXPECTED_EOF);
uint32_t version = wah_read_u32_le(ptr);
ptr += 4;
WAH_ENSURE(version == 0x01, WAH_ERROR_INVALID_VERSION);
// 3. Parse Sections
while (ptr < end) {
uint8_t section_id;
uint32_t section_size;
WAH_CHECK_GOTO(wah_read_section_header(&ptr, end, &section_id, &section_size), cleanup_parse);
// Get section handler from lookup table
WAH_ENSURE_GOTO(section_id < sizeof(wah_section_handlers) / sizeof(*wah_section_handlers), WAH_ERROR_UNKNOWN_SECTION, cleanup_parse);
const struct wah_section_handler_s *handler = &wah_section_handlers[section_id];
// Section order validation
if (section_id != 0) { // Custom sections do not affect the order
WAH_ENSURE_GOTO(handler->order > last_parsed_order, WAH_ERROR_VALIDATION_FAILED, cleanup_parse);
last_parsed_order = handler->order;
}
const uint8_t *section_payload_end = ptr + section_size;
WAH_ENSURE_GOTO(section_payload_end <= end, WAH_ERROR_UNEXPECTED_EOF, cleanup_parse);
WAH_LOG("Parsing section ID: %d, size: %u", section_id, section_size);
WAH_CHECK_GOTO(handler->parser_func(&ptr, section_payload_end, module), cleanup_parse);
// Ensure we consumed exactly the section_size bytes
WAH_ENSURE_GOTO(ptr == section_payload_end, WAH_ERROR_VALIDATION_FAILED, cleanup_parse); // Indicate a parsing error within the section
}
// After all sections are parsed, validate that function_count matches code_count
WAH_ENSURE_GOTO(module->function_count == module->code_count, WAH_ERROR_VALIDATION_FAILED, cleanup_parse);
// Validate data segment references
WAH_ENSURE_GOTO(module->data_segment_count >= module->min_data_segment_count_required, WAH_ERROR_VALIDATION_FAILED, cleanup_parse);
return WAH_OK;
cleanup_parse:
wah_free_module(module);
return err;
}
// --- Interpreter Implementation ---
wah_error_t wah_exec_context_create(wah_exec_context_t *exec_ctx, const wah_module_t *module) {
memset(exec_ctx, 0, sizeof(wah_exec_context_t));
wah_error_t err = WAH_OK;
exec_ctx->value_stack_capacity = WAH_DEFAULT_VALUE_STACK_SIZE;
WAH_MALLOC_ARRAY_GOTO(exec_ctx->value_stack, exec_ctx->value_stack_capacity, cleanup);
exec_ctx->call_stack_capacity = WAH_DEFAULT_MAX_CALL_DEPTH;
WAH_MALLOC_ARRAY_GOTO(exec_ctx->call_stack, exec_ctx->call_stack_capacity, cleanup);
if (module->global_count > 0) {
WAH_MALLOC_ARRAY_GOTO(exec_ctx->globals, module->global_count, cleanup);
// Initialize globals from the module definition
for (uint32_t i = 0; i < module->global_count; ++i) {
exec_ctx->globals[i] = module->globals[i].initial_value;
}
}
exec_ctx->module = module;
exec_ctx->global_count = module->global_count;
exec_ctx->max_call_depth = WAH_DEFAULT_MAX_CALL_DEPTH;
exec_ctx->sp = 0;
exec_ctx->call_depth = 0;
if (module->memory_count > 0) {
// Check for potential overflow before calculating memory_size
WAH_ENSURE_GOTO(module->memories[0].min_pages <= UINT32_MAX / WAH_WASM_PAGE_SIZE, WAH_ERROR_TOO_LARGE, cleanup);
exec_ctx->memory_size = module->memories[0].min_pages * WAH_WASM_PAGE_SIZE;
WAH_MALLOC_ARRAY_GOTO(exec_ctx->memory_base, exec_ctx->memory_size, cleanup);
memset(exec_ctx->memory_base, 0, exec_ctx->memory_size);
}
if (module->table_count > 0) {
WAH_MALLOC_ARRAY_GOTO(exec_ctx->tables, module->table_count, cleanup);
memset(exec_ctx->tables, 0, sizeof(wah_value_t*) * module->table_count);
exec_ctx->table_count = module->table_count;
for (uint32_t i = 0; i < exec_ctx->table_count; ++i) {
uint32_t min_elements = module->tables[i].min_elements;
WAH_MALLOC_ARRAY_GOTO(exec_ctx->tables[i], min_elements, cleanup);
memset(exec_ctx->tables[i], 0, sizeof(wah_value_t) * min_elements); // Initialize to null (0)
}
// Initialize tables with element segments
for (uint32_t i = 0; i < module->element_segment_count; ++i) {
const wah_element_segment_t *segment = &module->element_segments[i];
// Validation should be done at parse time. Assert here as a safety net.
assert(segment->table_idx < exec_ctx->table_count);
assert((uint64_t)segment->offset + segment->num_elems <= module->tables[segment->table_idx].min_elements);
for (uint32_t j = 0; j < segment->num_elems; ++j) {
exec_ctx->tables[segment->table_idx][segment->offset + j].i32 = (int32_t)segment->func_indices[j];
}
}
}
// Initialize active data segments
for (uint32_t i = 0; i < module->data_segment_count; ++i) {
const wah_data_segment_t *segment = &module->data_segments[i];
if (segment->flags == 0x00 || segment->flags == 0x02) { // Active segments
WAH_ENSURE_GOTO(segment->memory_idx == 0, WAH_ERROR_VALIDATION_FAILED, cleanup); // Only memory 0 supported
WAH_ENSURE_GOTO((uint64_t)segment->offset + segment->data_len <= exec_ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
memcpy(exec_ctx->memory_base + segment->offset, segment->data, segment->data_len);
}
}
// If a start function is defined, call it.
if (module->has_start_function) {
WAH_CHECK_GOTO(wah_call(exec_ctx, module, module->start_function_idx, NULL, 0, NULL), cleanup);
}
return WAH_OK;
cleanup:
if (err != WAH_OK) wah_exec_context_destroy(exec_ctx);
return err;
}
void wah_exec_context_destroy(wah_exec_context_t *exec_ctx) {
if (!exec_ctx) return;
free(exec_ctx->value_stack);
free(exec_ctx->call_stack);
free(exec_ctx->globals);
free(exec_ctx->memory_base);
if (exec_ctx->tables) {
for (uint32_t i = 0; i < exec_ctx->table_count; ++i) {
free(exec_ctx->tables[i]);
}
free(exec_ctx->tables);
}
memset(exec_ctx, 0, sizeof(wah_exec_context_t));
}
// Pushes a new call frame. This is an internal helper.
static wah_error_t wah_push_frame(wah_exec_context_t *ctx, uint32_t func_idx, uint32_t locals_offset) {
WAH_ENSURE(ctx->call_depth < ctx->max_call_depth, WAH_ERROR_CALL_STACK_OVERFLOW);
const wah_code_body_t *code_body = &ctx->module->code_bodies[func_idx];
wah_call_frame_t *frame = &ctx->call_stack[ctx->call_depth++];
frame->code = code_body;
frame->bytecode_ip = code_body->parsed_code.bytecode;
frame->locals_offset = locals_offset;
frame->func_idx = func_idx;
return WAH_OK;
}
#define RELOAD_FRAME() \
do { \
if (ctx->call_depth == 0) goto cleanup; \
frame = &ctx->call_stack[ctx->call_depth - 1]; \
bytecode_ip = frame->bytecode_ip; \
bytecode_base = frame->code->parsed_code.bytecode; \
} while (0)
#ifdef WAH_FORCE_MUSTTAIL
#define WAH_USE_MUSTTAIL
#elif defined(__has_attribute)
#if __has_attribute(musttail)
#define WAH_USE_MUSTTAIL // clang 13+, GCC 15+
#endif
#endif
#ifdef WAH_FORCE_COMPUTED_GOTO
#define WAH_USE_COMPUTED_GOTO
#elif defined(__GNUC__) || defined(__clang__)
#define WAH_USE_COMPUTED_GOTO
#endif
#if defined(WAH_USE_MUSTTAIL) // --- Tail recursion dispatch ---
#define WAH_RUN(opcode) \
static wah_error_t wah_run_##opcode(wah_exec_context_t *ctx, wah_call_frame_t *frame, \
const uint8_t *bytecode_ip, const uint8_t *bytecode_base, wah_error_t err)
#define WAH_NEXT() do { \
(void)bytecode_base; (void)err; \
__attribute__((musttail)) return wah_run_single(ctx, frame, bytecode_ip, bytecode_base, err); \
} while (0)
#define WAH_CLEANUP() goto cleanup; cleanup: do { \
/* Before returning, store the final IP back into the (potentially last) frame */ \
if (ctx->call_depth > 0) { \
frame->bytecode_ip = bytecode_ip; \
} \
return (err); \
} while (0)
static wah_error_t wah_run_single(wah_exec_context_t *ctx, wah_call_frame_t *frame,
const uint8_t *bytecode_ip, const uint8_t *bytecode_base, wah_error_t err);
#elif defined(WAH_USE_COMPUTED_GOTO) // --- Computed GOTO dispatch ---
static wah_error_t wah_run_interpreter(wah_exec_context_t *ctx) {
wah_error_t err = WAH_OK;
// These are pointers to the current frame's state for faster access.
wah_call_frame_t *frame = &ctx->call_stack[ctx->call_depth - 1];
const uint8_t *bytecode_ip = frame->bytecode_ip;
const uint8_t *bytecode_base = frame->code->parsed_code.bytecode;
// Computed goto jump table
static const void* wah_opcode_labels[] = {
#define WAH_OPCODE_LABEL(name, val) [WAH_OP_##name] = &&wah_op_##name,
WAH_OPCODES(WAH_OPCODE_LABEL)
#undef WAH_OPCODE_LABEL
};
goto *wah_opcode_labels[wah_read_u16_le(bytecode_ip)];
#define WAH_RUN(opcode) wah_op_##opcode: bytecode_ip += sizeof(uint16_t);
#define WAH_NEXT() goto *wah_opcode_labels[wah_read_u16_le(bytecode_ip)]
#define WAH_CLEANUP() goto cleanup
#else // --- Default switch-based interpreter ---
static wah_error_t wah_run_interpreter(wah_exec_context_t *ctx) {
wah_error_t err = WAH_OK;
// These are pointers to the current frame's state for faster access.
wah_call_frame_t *frame = &ctx->call_stack[ctx->call_depth - 1];
const uint8_t *bytecode_ip = frame->bytecode_ip;
const uint8_t *bytecode_base = frame->code->parsed_code.bytecode;
while (1) {
uint16_t opcode = wah_read_u16_le(bytecode_ip);
bytecode_ip += sizeof(uint16_t);
switch (opcode) {
#define WAH_RUN(opcode) break; case WAH_OP_##opcode:
#define WAH_NEXT() break
#define WAH_CLEANUP() goto cleanup
#endif
//------------------------------------------------------------------------------
WAH_RUN(BLOCK) { // Should not appear in preparsed code
(void)bytecode_base;
err = WAH_ERROR_VALIDATION_FAILED;
WAH_CLEANUP();
}
WAH_RUN(LOOP) { // Should not appear in preparsed code
(void)bytecode_base;
err = WAH_ERROR_VALIDATION_FAILED;
WAH_CLEANUP();
}
WAH_RUN(IF) {
uint32_t offset = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
if (ctx->value_stack[--ctx->sp].i32 == 0) {
bytecode_ip = bytecode_base + offset;
}
WAH_NEXT();
}
WAH_RUN(ELSE) { // This is an unconditional jump
uint32_t offset = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
bytecode_ip = bytecode_base + offset;
WAH_NEXT();
}
WAH_RUN(BR) {
uint32_t offset = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
bytecode_ip = bytecode_base + offset;
WAH_NEXT();
}
WAH_RUN(BR_IF) {
uint32_t offset = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
if (ctx->value_stack[--ctx->sp].i32 != 0) {
bytecode_ip = bytecode_base + offset;
}
WAH_NEXT();
}
WAH_RUN(BR_TABLE) {
uint32_t index = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t num_targets = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
uint32_t target_offset;
if (index < num_targets) {
// Jump to the specified target
target_offset = wah_read_u32_le(bytecode_ip + index * sizeof(uint32_t));
} else {
// Jump to the default target (the last one in the list)
target_offset = wah_read_u32_le(bytecode_ip + num_targets * sizeof(uint32_t));
}
bytecode_ip = bytecode_base + target_offset;
WAH_NEXT();
}
WAH_RUN(I32_CONST) { ctx->value_stack[ctx->sp++].i32 = (int32_t)wah_read_u32_le(bytecode_ip); bytecode_ip += sizeof(uint32_t); WAH_NEXT(); }
WAH_RUN(I64_CONST) { ctx->value_stack[ctx->sp++].i64 = (int64_t)wah_read_u64_le(bytecode_ip); bytecode_ip += sizeof(uint64_t); WAH_NEXT(); }
WAH_RUN(F32_CONST) { ctx->value_stack[ctx->sp++].f32 = wah_read_f32_le(bytecode_ip); bytecode_ip += sizeof(float); WAH_NEXT(); }
WAH_RUN(F64_CONST) { ctx->value_stack[ctx->sp++].f64 = wah_read_f64_le(bytecode_ip); bytecode_ip += sizeof(double); WAH_NEXT(); }
WAH_RUN(V128_CONST) {
memcpy(&ctx->value_stack[ctx->sp++].v128, bytecode_ip, sizeof(wah_v128_t));
bytecode_ip += sizeof(wah_v128_t);
WAH_NEXT();
}
WAH_RUN(LOCAL_GET) {
uint32_t local_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
ctx->value_stack[ctx->sp++] = ctx->value_stack[frame->locals_offset + local_idx];
WAH_NEXT();
}
WAH_RUN(LOCAL_SET) {
uint32_t local_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
ctx->value_stack[frame->locals_offset + local_idx] = ctx->value_stack[--ctx->sp];
WAH_NEXT();
}
WAH_RUN(LOCAL_TEE) {
uint32_t local_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
wah_value_t val = ctx->value_stack[ctx->sp - 1];
ctx->value_stack[frame->locals_offset + local_idx] = val;
WAH_NEXT();
}
WAH_RUN(GLOBAL_GET) {
uint32_t global_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
ctx->value_stack[ctx->sp++] = ctx->globals[global_idx];
WAH_NEXT();
}
WAH_RUN(GLOBAL_SET) {
uint32_t global_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
ctx->globals[global_idx] = ctx->value_stack[--ctx->sp];
WAH_NEXT();
}
WAH_RUN(CALL) {
uint32_t called_func_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
const wah_func_type_t *called_func_type = &ctx->module->types[ctx->module->function_type_indices[called_func_idx]];
const wah_code_body_t *called_code = &ctx->module->code_bodies[called_func_idx];
uint32_t new_locals_offset = ctx->sp - called_func_type->param_count;
frame->bytecode_ip = bytecode_ip;
WAH_CHECK_GOTO(wah_push_frame(ctx, called_func_idx, new_locals_offset), cleanup);
uint32_t num_locals = called_code->local_count;
if (num_locals > 0) {
WAH_ENSURE_GOTO(ctx->sp + num_locals <= ctx->value_stack_capacity, WAH_ERROR_CALL_STACK_OVERFLOW, cleanup);
memset(&ctx->value_stack[ctx->sp], 0, sizeof(wah_value_t) * num_locals);
ctx->sp += num_locals;
}
RELOAD_FRAME();
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(CALL_INDIRECT) {
uint32_t type_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
uint32_t table_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
// Pop function index from stack
uint32_t func_table_idx = (uint32_t)ctx->value_stack[--ctx->sp].i32;
// Validate table_idx
WAH_ENSURE_GOTO(table_idx < ctx->table_count, WAH_ERROR_TRAP, cleanup); // Table index out of bounds
// Validate func_table_idx against table size, Use min_elements as current size
WAH_ENSURE_GOTO(func_table_idx < ctx->module->tables[table_idx].min_elements, WAH_ERROR_TRAP, cleanup); // Function index out of table bounds
// Validate actual_func_idx against module's function count
uint32_t actual_func_idx = (uint32_t)ctx->tables[table_idx][func_table_idx].i32;
WAH_ENSURE_GOTO(actual_func_idx < ctx->module->function_count, WAH_ERROR_TRAP, cleanup); // Invalid function index in table
// Get expected function type (from instruction)
const wah_func_type_t *expected_func_type = &ctx->module->types[type_idx];
// Get actual function type (from module's function type indices) (function_type_indices stores type_idx for each function)
const wah_func_type_t *actual_func_type = &ctx->module->types[ctx->module->function_type_indices[actual_func_idx]];
// Type check: compare expected and actual function types
WAH_ENSURE_GOTO(expected_func_type->param_count == actual_func_type->param_count &&
expected_func_type->result_count == actual_func_type->result_count,
WAH_ERROR_TRAP, cleanup); // Type mismatch (param/result count)
for (uint32_t i = 0; i < expected_func_type->param_count; ++i) {
// Type mismatch (param type)
WAH_ENSURE_GOTO(expected_func_type->param_types[i] == actual_func_type->param_types[i], WAH_ERROR_TRAP, cleanup);
}
for (uint32_t i = 0; i < expected_func_type->result_count; ++i) {
// Type mismatch (result type)
WAH_ENSURE_GOTO(expected_func_type->result_types[i] == actual_func_type->result_types[i], WAH_ERROR_TRAP, cleanup);
}
// Perform the call using actual_func_idx
const wah_code_body_t *called_code = &ctx->module->code_bodies[actual_func_idx];
uint32_t new_locals_offset = ctx->sp - expected_func_type->param_count; // Use expected_func_type for stack manipulation
frame->bytecode_ip = bytecode_ip;
WAH_CHECK_GOTO(wah_push_frame(ctx, actual_func_idx, new_locals_offset), cleanup);
uint32_t num_locals = called_code->local_count;
if (num_locals > 0) {
WAH_ENSURE_GOTO(ctx->sp + num_locals <= ctx->value_stack_capacity, WAH_ERROR_CALL_STACK_OVERFLOW, cleanup);
memset(&ctx->value_stack[ctx->sp], 0, sizeof(wah_value_t) * num_locals);
ctx->sp += num_locals;
}
RELOAD_FRAME();
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(RETURN) {
const wah_func_type_t *func_type = &ctx->module->types[ctx->module->function_type_indices[frame->func_idx]];
uint32_t results_to_keep = func_type->result_count;
wah_value_t result_val;
if (results_to_keep == 1) {
result_val = ctx->value_stack[ctx->sp - 1];
}
ctx->sp = frame->locals_offset;
ctx->call_depth--;
if (results_to_keep == 1) {
ctx->value_stack[ctx->sp++] = result_val;
}
RELOAD_FRAME();
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(END) { // End of function
const wah_func_type_t *func_type = &ctx->module->types[ctx->module->function_type_indices[frame->func_idx]];
uint32_t results_to_keep = func_type->result_count;
wah_value_t result_val;
if (results_to_keep == 1) {
if (ctx->sp > frame->locals_offset) {
result_val = ctx->value_stack[ctx->sp - 1];
} else {
results_to_keep = 0;
}
}
ctx->sp = frame->locals_offset;
ctx->call_depth--;
if (results_to_keep == 1) {
ctx->value_stack[ctx->sp++] = result_val;
}
RELOAD_FRAME();
WAH_NEXT();
WAH_CLEANUP();
}
#define VSTACK_TOP (ctx->value_stack[ctx->sp - 1])
#define VSTACK_B (ctx->value_stack[ctx->sp - 1])
#define VSTACK_A (ctx->value_stack[ctx->sp - 2])
#define BINOP_I(N,op) { VSTACK_A.i##N = (int##N##_t)((uint##N##_t)VSTACK_A.i##N op (uint##N##_t)VSTACK_B.i##N); ctx->sp--; WAH_NEXT(); }
#define CMP_I_S(N,op) { VSTACK_A.i32 = VSTACK_A.i##N op VSTACK_B.i##N ? 1 : 0; ctx->sp--; WAH_NEXT(); }
#define CMP_I_U(N,op) { VSTACK_A.i32 = (uint##N##_t)VSTACK_A.i##N op (uint##N##_t)VSTACK_B.i##N ? 1 : 0; ctx->sp--; WAH_NEXT(); }
#define BINOP_F(N,op) { VSTACK_A.f##N = wah_canonicalize_f##N(VSTACK_A.f##N op VSTACK_B.f##N); ctx->sp--; WAH_NEXT(); }
#define CMP_F(N,op) { VSTACK_A.i32 = VSTACK_A.f##N op VSTACK_B.f##N ? 1 : 0; ctx->sp--; WAH_NEXT(); }
#define UNOP_I_FN(N,fn) { VSTACK_TOP.i##N = (int##N##_t)fn((uint##N##_t)VSTACK_TOP.i##N); WAH_NEXT(); }
#define BINOP_I_FN(N,fn) { VSTACK_A.i##N = (int##N##_t)fn((uint##N##_t)VSTACK_A.i##N, (uint##N##_t)VSTACK_B.i##N); ctx->sp--; WAH_NEXT(); }
#define UNOP_F_FN(N,fn) { VSTACK_TOP.f##N = wah_canonicalize_f##N(fn(VSTACK_TOP.f##N)); WAH_NEXT(); }
#define BINOP_F_FN(N,fn) { VSTACK_A.f##N = wah_canonicalize_f##N(fn(VSTACK_A.f##N, VSTACK_B.f##N)); ctx->sp--; WAH_NEXT(); }
#define NUM_OPS(N,_F) \
WAH_RUN(I##N##_CLZ) UNOP_I_FN(N, wah_clz_u##N) \
WAH_RUN(I##N##_CTZ) UNOP_I_FN(N, wah_ctz_u##N) \
WAH_RUN(I##N##_POPCNT) UNOP_I_FN(N, wah_popcount_u##N) \
WAH_RUN(I##N##_ADD) BINOP_I(N,+) \
WAH_RUN(I##N##_SUB) BINOP_I(N,-) \
WAH_RUN(I##N##_MUL) BINOP_I(N,*) \
WAH_RUN(I##N##_DIV_S) { \
WAH_ENSURE_GOTO(VSTACK_B.i##N != 0, WAH_ERROR_TRAP, cleanup); \
WAH_ENSURE_GOTO(VSTACK_A.i##N != INT##N##_MIN || VSTACK_B.i##N != -1, WAH_ERROR_TRAP, cleanup); \
VSTACK_A.i##N /= VSTACK_B.i##N; ctx->sp--; WAH_NEXT(); \
WAH_CLEANUP(); \
} \
WAH_RUN(I##N##_DIV_U) { \
WAH_ENSURE_GOTO(VSTACK_B.i##N != 0, WAH_ERROR_TRAP, cleanup); \
VSTACK_A.i##N = (int##N##_t)((uint##N##_t)VSTACK_A.i##N / (uint##N##_t)VSTACK_B.i##N); \
ctx->sp--; \
WAH_NEXT(); \
WAH_CLEANUP(); \
} \
WAH_RUN(I##N##_REM_S) { \
WAH_ENSURE_GOTO(VSTACK_B.i##N != 0, WAH_ERROR_TRAP, cleanup); \
if (VSTACK_A.i##N == INT##N##_MIN && VSTACK_B.i##N == -1) VSTACK_A.i##N = 0; else VSTACK_A.i##N %= VSTACK_B.i##N; \
ctx->sp--; \
WAH_NEXT(); \
WAH_CLEANUP(); \
} \
WAH_RUN(I##N##_REM_U) { \
WAH_ENSURE_GOTO(VSTACK_B.i##N != 0, WAH_ERROR_TRAP, cleanup); \
VSTACK_A.i##N = (int##N##_t)((uint##N##_t)VSTACK_A.i##N % (uint##N##_t)VSTACK_B.i##N); \
ctx->sp--; \
WAH_NEXT(); \
WAH_CLEANUP(); \
} \
WAH_RUN(I##N##_AND) BINOP_I(N,&) \
WAH_RUN(I##N##_OR) BINOP_I(N,|) \
WAH_RUN(I##N##_XOR) BINOP_I(N,^) \
WAH_RUN(I##N##_SHL) { VSTACK_A.i##N = (int##N##_t)((uint##N##_t)VSTACK_A.i##N << (VSTACK_B.i##N & (N-1))); ctx->sp--; WAH_NEXT(); } \
WAH_RUN(I##N##_SHR_S) { VSTACK_A.i##N >>= (VSTACK_B.i##N & (N-1)); ctx->sp--; WAH_NEXT(); } \
WAH_RUN(I##N##_SHR_U) { VSTACK_A.i##N = (int##N##_t)((uint##N##_t)VSTACK_A.i##N >> (VSTACK_B.i##N & (N-1))); ctx->sp--; WAH_NEXT(); } \
WAH_RUN(I##N##_ROTL) BINOP_I_FN(N, wah_rotl_u##N) \
WAH_RUN(I##N##_ROTR) BINOP_I_FN(N, wah_rotr_u##N) \
\
WAH_RUN(I##N##_EQ) CMP_I_S(N,==) \
WAH_RUN(I##N##_NE) CMP_I_S(N,!=) \
WAH_RUN(I##N##_LT_S) CMP_I_S(N,<) \
WAH_RUN(I##N##_LT_U) CMP_I_U(N,<) \
WAH_RUN(I##N##_GT_S) CMP_I_S(N,>) \
WAH_RUN(I##N##_GT_U) CMP_I_U(N,>) \
WAH_RUN(I##N##_LE_S) CMP_I_S(N,<=) \
WAH_RUN(I##N##_LE_U) CMP_I_U(N,<=) \
WAH_RUN(I##N##_GE_S) CMP_I_S(N,>=) \
WAH_RUN(I##N##_GE_U) CMP_I_U(N,>=) \
WAH_RUN(I##N##_EQZ) { VSTACK_A.i32 = (VSTACK_A.i##N == 0) ? 1 : 0; WAH_NEXT(); } \
\
WAH_RUN(F##N##_ABS) UNOP_F_FN(N, fabs##_F) \
WAH_RUN(F##N##_NEG) UNOP_F_FN(N, -) \
WAH_RUN(F##N##_CEIL) UNOP_F_FN(N, ceil##_F) \
WAH_RUN(F##N##_FLOOR) UNOP_F_FN(N, floor##_F) \
WAH_RUN(F##N##_TRUNC) UNOP_F_FN(N, trunc##_F) \
WAH_RUN(F##N##_NEAREST) UNOP_F_FN(N, wah_nearest_f##N) \
WAH_RUN(F##N##_SQRT) UNOP_F_FN(N, sqrt##_F) \
WAH_RUN(F##N##_ADD) BINOP_F(N,+) \
WAH_RUN(F##N##_SUB) BINOP_F(N,-) \
WAH_RUN(F##N##_MUL) BINOP_F(N,*) \
WAH_RUN(F##N##_DIV) BINOP_F(N,/) /* Let hardware handle division by zero (NaN/inf) */ \
WAH_RUN(F##N##_EQ) CMP_F(N,==) \
WAH_RUN(F##N##_NE) CMP_F(N,!=) \
WAH_RUN(F##N##_LT) CMP_F(N,<) \
WAH_RUN(F##N##_GT) CMP_F(N,>) \
WAH_RUN(F##N##_LE) CMP_F(N,<=) \
WAH_RUN(F##N##_GE) CMP_F(N,>=) \
WAH_RUN(F##N##_MIN) BINOP_F_FN(N, fmin##_F) \
WAH_RUN(F##N##_MAX) BINOP_F_FN(N, fmax##_F) \
WAH_RUN(F##N##_COPYSIGN) BINOP_F_FN(N, copysign##_F)
#define LOAD_OP(N, T, value_field, cast) { \
uint32_t offset = wah_read_u32_le(bytecode_ip); \
bytecode_ip += sizeof(uint32_t); \
uint32_t addr = (uint32_t)ctx->value_stack[--ctx->sp].i32; \
uint64_t effective_addr = (uint64_t)addr + offset; \
\
WAH_ENSURE_GOTO(effective_addr < ctx->memory_size && ctx->memory_size - effective_addr >= N/8, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup); \
ctx->value_stack[ctx->sp++].value_field = cast wah_read_##T##_le(ctx->memory_base + effective_addr); \
WAH_NEXT(); \
WAH_CLEANUP(); \
}
#define STORE_OP(N, T, value_field, value_type, cast) { \
uint32_t offset = wah_read_u32_le(bytecode_ip); \
bytecode_ip += sizeof(uint32_t); \
value_type val = ctx->value_stack[--ctx->sp].value_field; \
uint32_t addr = (uint32_t)ctx->value_stack[--ctx->sp].i32; \
uint64_t effective_addr = (uint64_t)addr + offset; \
\
WAH_ENSURE_GOTO(effective_addr < ctx->memory_size && ctx->memory_size - effective_addr >= N/8, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup); \
wah_write_##T##_le(ctx->memory_base + effective_addr, cast (val)); \
WAH_NEXT(); \
WAH_CLEANUP(); \
}
#define CONVERT(from_field, cast, to_field) { \
VSTACK_TOP.to_field = cast (VSTACK_TOP.from_field); \
WAH_NEXT(); \
}
#define CONVERT_CHECK(from_field, call, ty, cast, to_field) { \
ty res; \
WAH_CHECK(call(VSTACK_TOP.from_field, &res)); \
VSTACK_TOP.to_field = cast (res); \
WAH_NEXT(); \
}
#define REINTERPRET(from_field, from_ty, to_field, to_ty) { \
union { from_ty from; to_ty to; } u = { .from = VSTACK_TOP.from_field }; \
VSTACK_TOP.to_field = u.to; \
WAH_NEXT(); \
}
NUM_OPS(32,f)
NUM_OPS(64,)
WAH_RUN(I32_LOAD) LOAD_OP(32, u32, i32, (int32_t))
WAH_RUN(I64_LOAD) LOAD_OP(64, u64, i64, (int64_t))
WAH_RUN(F32_LOAD) LOAD_OP(32, f32, f32, )
WAH_RUN(F64_LOAD) LOAD_OP(64, f64, f64, )
WAH_RUN(I32_LOAD8_S) LOAD_OP(8, u8, i32, (int32_t)(int8_t))
WAH_RUN(I32_LOAD8_U) LOAD_OP(8, u8, i32, (int32_t))
WAH_RUN(I32_LOAD16_S) LOAD_OP(16, u16, i32, (int32_t)(int16_t))
WAH_RUN(I32_LOAD16_U) LOAD_OP(16, u16, i32, (int32_t))
WAH_RUN(I64_LOAD8_S) LOAD_OP(8, u8, i64, (int64_t)(int8_t))
WAH_RUN(I64_LOAD8_U) LOAD_OP(8, u8, i64, (int64_t))
WAH_RUN(I64_LOAD16_S) LOAD_OP(16, u16, i64, (int64_t)(int16_t))
WAH_RUN(I64_LOAD16_U) LOAD_OP(16, u16, i64, (int64_t))
WAH_RUN(I64_LOAD32_S) LOAD_OP(32, u32, i64, (int64_t)(int32_t))
WAH_RUN(I64_LOAD32_U) LOAD_OP(32, u32, i64, (int64_t))
WAH_RUN(I32_STORE) STORE_OP(32, u32, i32, int32_t, (uint32_t))
WAH_RUN(I64_STORE) STORE_OP(64, u64, i64, int64_t, (uint64_t))
WAH_RUN(F32_STORE) STORE_OP(32, f32, f32, float, )
WAH_RUN(F64_STORE) STORE_OP(64, f64, f64, double, )
WAH_RUN(I32_STORE8) STORE_OP(8, u8, i32, int32_t, (uint8_t))
WAH_RUN(I32_STORE16) STORE_OP(16, u16, i32, int32_t, (uint16_t))
WAH_RUN(I64_STORE8) STORE_OP(8, u8, i64, int64_t, (uint8_t))
WAH_RUN(I64_STORE16) STORE_OP(16, u16, i64, int64_t, (uint16_t))
WAH_RUN(I64_STORE32) STORE_OP(32, u32, i64, int64_t, (uint32_t))
WAH_RUN(I32_WRAP_I64) CONVERT(i64, (int32_t), i32)
WAH_RUN(I32_TRUNC_F32_S) CONVERT_CHECK(f32, wah_trunc_f32_to_i32, int32_t, , i32)
WAH_RUN(I32_TRUNC_F32_U) CONVERT_CHECK(f32, wah_trunc_f32_to_u32, uint32_t, (int32_t), i32)
WAH_RUN(I32_TRUNC_F64_S) CONVERT_CHECK(f64, wah_trunc_f64_to_i32, int32_t, , i32)
WAH_RUN(I32_TRUNC_F64_U) CONVERT_CHECK(f64, wah_trunc_f64_to_u32, uint32_t, (int32_t), i32)
WAH_RUN(I64_EXTEND_I32_S) CONVERT(i32, (int64_t), i64)
WAH_RUN(I64_EXTEND_I32_U) CONVERT(i32, (int64_t)(uint32_t), i64)
WAH_RUN(I64_TRUNC_F32_S) CONVERT_CHECK(f32, wah_trunc_f32_to_i64, int64_t, , i64)
WAH_RUN(I64_TRUNC_F32_U) CONVERT_CHECK(f32, wah_trunc_f32_to_u64, uint64_t, (int64_t), i64)
WAH_RUN(I64_TRUNC_F64_S) CONVERT_CHECK(f64, wah_trunc_f64_to_i64, int64_t, , i64)
WAH_RUN(I64_TRUNC_F64_U) CONVERT_CHECK(f64, wah_trunc_f64_to_u64, uint64_t, (int64_t), i64)
WAH_RUN(F32_CONVERT_I32_S) CONVERT(i32, (float), f32)
WAH_RUN(F32_CONVERT_I32_U) CONVERT(i32, (float)(uint32_t), f32)
WAH_RUN(F32_CONVERT_I64_S) CONVERT(i64, (float), f32)
WAH_RUN(F32_CONVERT_I64_U) CONVERT(i64, (float)(uint64_t), f32)
WAH_RUN(F32_DEMOTE_F64) { VSTACK_TOP.f32 = wah_canonicalize_f32((float)VSTACK_TOP.f64); WAH_NEXT(); }
WAH_RUN(F64_CONVERT_I32_S) CONVERT(i32, (double), f64)
WAH_RUN(F64_CONVERT_I32_U) CONVERT(i32, (double)(uint32_t), f64)
WAH_RUN(F64_CONVERT_I64_S) CONVERT(i64, (double), f64)
WAH_RUN(F64_CONVERT_I64_U) CONVERT(i64, (double)(uint64_t), f64)
WAH_RUN(F64_PROMOTE_F32) { VSTACK_TOP.f64 = wah_canonicalize_f64((double)VSTACK_TOP.f32); WAH_NEXT(); }
WAH_RUN(I32_REINTERPRET_F32) REINTERPRET(f32, float, i32, int32_t)
WAH_RUN(I64_REINTERPRET_F64) REINTERPRET(f64, double, i64, int64_t)
WAH_RUN(F32_REINTERPRET_I32) REINTERPRET(i32, int32_t, f32, float)
WAH_RUN(F64_REINTERPRET_I64) REINTERPRET(i64, int64_t, f64, double)
WAH_RUN(I32_EXTEND8_S) { VSTACK_TOP.i32 = (int32_t) (int8_t)VSTACK_TOP.i32; WAH_NEXT(); }
WAH_RUN(I32_EXTEND16_S) { VSTACK_TOP.i32 = (int32_t)(int16_t)VSTACK_TOP.i32; WAH_NEXT(); }
WAH_RUN(I64_EXTEND8_S) { VSTACK_TOP.i64 = (int64_t) (int8_t)VSTACK_TOP.i64; WAH_NEXT(); }
WAH_RUN(I64_EXTEND16_S) { VSTACK_TOP.i64 = (int64_t)(int16_t)VSTACK_TOP.i64; WAH_NEXT(); }
WAH_RUN(I64_EXTEND32_S) { VSTACK_TOP.i64 = (int64_t)(int32_t)VSTACK_TOP.i64; WAH_NEXT(); }
WAH_RUN(I32_TRUNC_SAT_F32_S) { VSTACK_TOP.i32 = wah_trunc_sat_f32_to_i32(VSTACK_TOP.f32); WAH_NEXT(); }
WAH_RUN(I32_TRUNC_SAT_F32_U) { VSTACK_TOP.i32 = (int32_t)wah_trunc_sat_f32_to_u32(VSTACK_TOP.f32); WAH_NEXT(); }
WAH_RUN(I32_TRUNC_SAT_F64_S) { VSTACK_TOP.i32 = wah_trunc_sat_f64_to_i32(VSTACK_TOP.f64); WAH_NEXT(); }
WAH_RUN(I32_TRUNC_SAT_F64_U) { VSTACK_TOP.i32 = (int32_t)wah_trunc_sat_f64_to_u32(VSTACK_TOP.f64); WAH_NEXT(); }
WAH_RUN(I64_TRUNC_SAT_F32_S) { VSTACK_TOP.i64 = wah_trunc_sat_f32_to_i64(VSTACK_TOP.f32); WAH_NEXT(); }
WAH_RUN(I64_TRUNC_SAT_F32_U) { VSTACK_TOP.i64 = (int64_t)wah_trunc_sat_f32_to_u64(VSTACK_TOP.f32); WAH_NEXT(); }
WAH_RUN(I64_TRUNC_SAT_F64_S) { VSTACK_TOP.i64 = wah_trunc_sat_f64_to_i64(VSTACK_TOP.f64); WAH_NEXT(); }
WAH_RUN(I64_TRUNC_SAT_F64_U) { VSTACK_TOP.i64 = (int64_t)wah_trunc_sat_f64_to_u64(VSTACK_TOP.f64); WAH_NEXT(); }
#undef VSTACK_TOP
#undef VSTACK_B
#undef VSTACK_A
#undef BINOP_I
#undef CMP_I_S
#undef CMP_I_U
#undef BINOP_F
#undef CMP_F
#undef UNOP_I_FN
#undef BINOP_I_FN
#undef UNOP_F_FN
#undef BINOP_F_FN
#undef NUM_OPS
#undef LOAD_OP
#undef STORE_OP
#undef CONVERT
#undef CONVERT_CHECK
#undef REINTERPRET
WAH_RUN(MEMORY_SIZE) {
// memory index (always 0x00) is consumed by preparse, no need to read here
ctx->value_stack[ctx->sp++].i32 = (int32_t)(ctx->memory_size / WAH_WASM_PAGE_SIZE);
WAH_NEXT();
}
WAH_RUN(MEMORY_GROW) {
// memory index (always 0x00) is consumed by preparse, no need to read here
int32_t pages_to_grow = ctx->value_stack[--ctx->sp].i32;
if (pages_to_grow < 0) {
ctx->value_stack[ctx->sp++].i32 = -1; // Cannot grow by negative pages
WAH_NEXT();
}
uint32_t old_pages = ctx->memory_size / WAH_WASM_PAGE_SIZE;
uint64_t new_pages = (uint64_t)old_pages + pages_to_grow;
// Check against max_pages if defined (module->memories[0].max_pages)
// For now, we assume no max_pages or effectively unlimited if not set
if (ctx->module->memory_count > 0 && ctx->module->memories[0].max_pages > 0 && new_pages > ctx->module->memories[0].max_pages) {
ctx->value_stack[ctx->sp++].i32 = -1; // Exceeds max memory
WAH_NEXT();
}
size_t new_memory_size = (size_t)new_pages * WAH_WASM_PAGE_SIZE;
WAH_REALLOC_ARRAY_GOTO(ctx->memory_base, new_memory_size, cleanup);
// Initialize newly allocated memory to zero
if (new_memory_size > ctx->memory_size) {
memset(ctx->memory_base + ctx->memory_size, 0, new_memory_size - ctx->memory_size);
}
WAH_ENSURE_GOTO(new_memory_size <= UINT32_MAX, WAH_ERROR_TOO_LARGE, cleanup);
ctx->memory_size = (uint32_t)new_memory_size;
ctx->value_stack[ctx->sp++].i32 = (int32_t)old_pages;
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(MEMORY_FILL) {
// memory index (always 0x00) is consumed by preparse, no need to read here
uint32_t size = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint8_t val = (uint8_t)ctx->value_stack[--ctx->sp].i32;
uint32_t dst = (uint32_t)ctx->value_stack[--ctx->sp].i32;
WAH_ENSURE_GOTO((uint64_t)dst + size <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
memset(ctx->memory_base + dst, val, size);
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(MEMORY_INIT) {
uint32_t data_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
uint32_t mem_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
WAH_ENSURE_GOTO(mem_idx == 0, WAH_ERROR_TRAP, cleanup); // Only memory 0 supported
WAH_ENSURE_GOTO(data_idx < ctx->module->data_segment_count, WAH_ERROR_TRAP, cleanup);
uint32_t size = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t src_offset = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t dest_offset = (uint32_t)ctx->value_stack[--ctx->sp].i32;
const wah_data_segment_t *segment = &ctx->module->data_segments[data_idx];
WAH_ENSURE_GOTO((uint64_t)dest_offset + size <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
WAH_ENSURE_GOTO((uint64_t)src_offset + size <= segment->data_len, WAH_ERROR_TRAP, cleanup); // Ensure source data is within segment bounds
WAH_ENSURE_GOTO(size <= segment->data_len, WAH_ERROR_TRAP, cleanup); // Cannot initialize more than available data
memcpy(ctx->memory_base + dest_offset, segment->data + src_offset, size);
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(MEMORY_COPY) {
uint32_t dest_mem_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
uint32_t src_mem_idx = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
WAH_ENSURE_GOTO(dest_mem_idx == 0 && src_mem_idx == 0, WAH_ERROR_TRAP, cleanup); // Only memory 0 supported
uint32_t size = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t src = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t dest = (uint32_t)ctx->value_stack[--ctx->sp].i32;
WAH_ENSURE_GOTO((uint64_t)dest + size <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
WAH_ENSURE_GOTO((uint64_t)src + size <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
memmove(ctx->memory_base + dest, ctx->memory_base + src, size);
WAH_NEXT();
WAH_CLEANUP();
}
WAH_RUN(DROP) { ctx->sp--; WAH_NEXT(); }
WAH_RUN(SELECT) {
wah_value_t c = ctx->value_stack[--ctx->sp];
wah_value_t b = ctx->value_stack[--ctx->sp];
wah_value_t a = ctx->value_stack[--ctx->sp];
ctx->value_stack[ctx->sp++] = c.i32 ? a : b;
WAH_NEXT();
}
WAH_RUN(NOP) { WAH_NEXT(); }
WAH_RUN(UNREACHABLE) {
(void)bytecode_base;
err = WAH_ERROR_TRAP;
WAH_CLEANUP();
}
// --- Vector instructions ---
#define V128_LOAD_COMMON(read_size) \
uint32_t offset = wah_read_u32_le(bytecode_ip); \
bytecode_ip += sizeof(uint32_t); \
uint32_t addr = (uint32_t)ctx->value_stack[--ctx->sp].i32; \
uint32_t effective_addr = addr + offset; \
WAH_ENSURE(effective_addr + (read_size) <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS)
#define V128_LOAD_HALF_OP(N, elem_ty, cast) { \
V128_LOAD_COMMON(8); \
wah_v128_t *v = &ctx->value_stack[ctx->sp++].v128; \
for (int i = 0; i < 64/N; ++i) { \
v->elem_ty[i] = cast(wah_read_u##N##_le(ctx->memory_base + effective_addr + i * (N/8))); \
} \
WAH_NEXT(); \
}
#define V128_LOAD_SPLAT_OP(N) { \
V128_LOAD_COMMON(N/8); \
wah_v128_t *v = &ctx->value_stack[ctx->sp++].v128; \
uint##N##_t val = wah_read_u##N##_le(ctx->memory_base + effective_addr); \
for (int i = 0; i < 128/N; ++i) v->u##N[i] = val; \
WAH_NEXT(); \
}
#define V128_LOAD_LANE_OP(N) { \
uint32_t offset = wah_read_u32_le(bytecode_ip); \
bytecode_ip += sizeof(uint32_t); \
uint32_t lane_idx = wah_read_u32_le(bytecode_ip); \
bytecode_ip += sizeof(uint32_t); \
wah_v128_t val = ctx->value_stack[--ctx->sp].v128; /* Existing vector */ \
uint32_t addr = (uint32_t)ctx->value_stack[--ctx->sp].i32; \
uint32_t effective_addr = addr + offset; \
WAH_ENSURE_GOTO(effective_addr + N/8 <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup); \
WAH_ENSURE_GOTO(lane_idx < 128/N, WAH_ERROR_TRAP, cleanup); \
val.u##N[lane_idx] = wah_read_u##N##_le(ctx->memory_base + effective_addr); \
ctx->value_stack[ctx->sp++].v128 = val; \
WAH_NEXT(); \
WAH_CLEANUP(); \
}
WAH_RUN(V128_LOAD) {
V128_LOAD_COMMON(sizeof(wah_v128_t));
memcpy(&ctx->value_stack[ctx->sp++].v128, ctx->memory_base + effective_addr, sizeof(wah_v128_t));
WAH_NEXT();
}
WAH_RUN(V128_LOAD8X8_S) V128_LOAD_HALF_OP(8, i16, (int16_t)(int8_t))
WAH_RUN(V128_LOAD8X8_U) V128_LOAD_HALF_OP(8, u16, (uint16_t))
WAH_RUN(V128_LOAD16X4_S) V128_LOAD_HALF_OP(16, i32, (int32_t)(int16_t))
WAH_RUN(V128_LOAD16X4_U) V128_LOAD_HALF_OP(16, u32, (uint32_t))
WAH_RUN(V128_LOAD32X2_S) V128_LOAD_HALF_OP(32, i64, (int64_t)(int32_t))
WAH_RUN(V128_LOAD32X2_U) V128_LOAD_HALF_OP(32, u64, (uint64_t))
WAH_RUN(V128_LOAD8_SPLAT) V128_LOAD_SPLAT_OP(8)
WAH_RUN(V128_LOAD16_SPLAT) V128_LOAD_SPLAT_OP(16)
WAH_RUN(V128_LOAD32_SPLAT) V128_LOAD_SPLAT_OP(32)
WAH_RUN(V128_LOAD64_SPLAT) V128_LOAD_SPLAT_OP(64)
WAH_RUN(V128_LOAD32_ZERO) {
V128_LOAD_COMMON(4);
wah_v128_t *v = &ctx->value_stack[ctx->sp++].v128;
memset(v, 0, sizeof(wah_v128_t)); // Zero out the entire vector
v->u32[0] = wah_read_u32_le(ctx->memory_base + effective_addr);
WAH_NEXT();
}
WAH_RUN(V128_LOAD64_ZERO) {
V128_LOAD_COMMON(8);
wah_v128_t *v = &ctx->value_stack[ctx->sp++].v128;
memset(v, 0, sizeof(wah_v128_t)); // Zero out the entire vector
v->u64[0] = wah_read_u64_le(ctx->memory_base + effective_addr);
WAH_NEXT();
}
WAH_RUN(V128_LOAD8_LANE) V128_LOAD_LANE_OP(8)
WAH_RUN(V128_LOAD16_LANE) V128_LOAD_LANE_OP(16)
WAH_RUN(V128_LOAD32_LANE) V128_LOAD_LANE_OP(32)
WAH_RUN(V128_LOAD64_LANE) V128_LOAD_LANE_OP(64)
#undef V128_LOAD_COMMON
#undef V128_LOAD_HALF_OP
#undef V128_LOAD_SPLAT_OP
#undef V128_LOAD_LANE_OP
WAH_RUN(V128_STORE) {
uint32_t offset = wah_read_u32_le(bytecode_ip);
bytecode_ip += sizeof(uint32_t);
wah_v128_t val = ctx->value_stack[--ctx->sp].v128;
uint32_t addr = (uint32_t)ctx->value_stack[--ctx->sp].i32;
uint32_t effective_addr = addr + offset;
WAH_ENSURE_GOTO(effective_addr + sizeof(wah_v128_t) <= ctx->memory_size, WAH_ERROR_MEMORY_OUT_OF_BOUNDS, cleanup);
memcpy(ctx->memory_base + effective_addr, &val, sizeof(wah_v128_t));
WAH_NEXT();
WAH_CLEANUP();
}
#define EXTRACT_LANE_OP(VEC_TYPE, SCALAR_TYPE, LANE_COUNT) { \
wah_v128_t vec = ctx->value_stack[--ctx->sp].v128; \
uint8_t laneidx = *bytecode_ip++; \
WAH_ENSURE_GOTO(laneidx < LANE_COUNT, WAH_ERROR_TRAP, cleanup); \
wah_value_t result; \
result.SCALAR_TYPE = vec.VEC_TYPE[laneidx]; \
ctx->value_stack[ctx->sp++] = result; \
WAH_NEXT(); \
WAH_CLEANUP(); \
}
#define REPLACE_LANE_OP(VEC_TYPE, C_VEC_TYPE, SCALAR_TYPE, LANE_COUNT) { \
wah_value_t scalar_val = ctx->value_stack[--ctx->sp]; \
wah_v128_t vec = ctx->value_stack[--ctx->sp].v128; \
uint8_t laneidx = *bytecode_ip++; \
WAH_ENSURE_GOTO(laneidx < LANE_COUNT, WAH_ERROR_TRAP, cleanup); \
vec.VEC_TYPE[laneidx] = (C_VEC_TYPE)scalar_val.SCALAR_TYPE; \
ctx->value_stack[ctx->sp++].v128 = vec; \
WAH_NEXT(); \
WAH_CLEANUP(); \
}
#define SPLAT_OP(VEC_TYPE, C_VEC_TYPE, SCALAR_TYPE) { \
wah_value_t scalar_val = ctx->value_stack[--ctx->sp]; \
wah_v128_t result; \
for (uint32_t i = 0; i < sizeof(wah_v128_t) / sizeof(result.VEC_TYPE[0]); ++i) { \
result.VEC_TYPE[i] = (C_VEC_TYPE)scalar_val.SCALAR_TYPE; \
} \
ctx->value_stack[ctx->sp++].v128 = result; \
WAH_NEXT(); \
}
WAH_RUN(I8X16_SHUFFLE) {
wah_v128_t vec2 = ctx->value_stack[--ctx->sp].v128;
wah_v128_t vec1 = ctx->value_stack[--ctx->sp].v128;
wah_v128_t result;
for (uint32_t i = 0; i < 16; ++i) {
uint8_t lane_idx = bytecode_ip[i];
result.u8[i] = lane_idx < 16 ? vec1.u8[lane_idx] : vec2.u8[lane_idx - 16];
}
bytecode_ip += 16; // Advance past the shuffle mask
ctx->value_stack[ctx->sp++].v128 = result;
WAH_NEXT();
}
WAH_RUN(I8X16_SWIZZLE) {
wah_v128_t mask = ctx->value_stack[--ctx->sp].v128;
wah_v128_t data = ctx->value_stack[--ctx->sp].v128;
wah_v128_t result;
for (uint32_t i = 0; i < 16; ++i) {
uint8_t lane_idx = mask.u8[i];
result.u8[i] = lane_idx < 16 ? data.u8[lane_idx] : 0;
}
ctx->value_stack[ctx->sp++].v128 = result;
WAH_NEXT();
}
WAH_RUN(I8X16_EXTRACT_LANE_S) EXTRACT_LANE_OP(i8, i32, 16)
WAH_RUN(I8X16_EXTRACT_LANE_U) EXTRACT_LANE_OP(u8, i32, 16)
WAH_RUN(I8X16_REPLACE_LANE) REPLACE_LANE_OP(i8, int8_t, i32, 16)
WAH_RUN(I16X8_EXTRACT_LANE_S) EXTRACT_LANE_OP(i16, i32, 8)
WAH_RUN(I16X8_EXTRACT_LANE_U) EXTRACT_LANE_OP(u16, i32, 8)
WAH_RUN(I16X8_REPLACE_LANE) REPLACE_LANE_OP(i16, int16_t, i32, 8)
WAH_RUN(I32X4_EXTRACT_LANE) EXTRACT_LANE_OP(i32, i32, 4)
WAH_RUN(I32X4_REPLACE_LANE) REPLACE_LANE_OP(i32, int32_t, i32, 4)
WAH_RUN(I64X2_EXTRACT_LANE) EXTRACT_LANE_OP(i64, i64, 2)
WAH_RUN(I64X2_REPLACE_LANE) REPLACE_LANE_OP(i64, int64_t, i64, 2)
WAH_RUN(F32X4_EXTRACT_LANE) EXTRACT_LANE_OP(f32, f32, 4)
WAH_RUN(F32X4_REPLACE_LANE) REPLACE_LANE_OP(f32, float, f32, 4)
WAH_RUN(F64X2_EXTRACT_LANE) EXTRACT_LANE_OP(f64, f64, 2)
WAH_RUN(F64X2_REPLACE_LANE) REPLACE_LANE_OP(f64, double, f64, 2)
WAH_RUN(I8X16_SPLAT) SPLAT_OP(i8, int8_t, i32)
WAH_RUN(I16X8_SPLAT) SPLAT_OP(i16, int16_t, i32)
WAH_RUN(I32X4_SPLAT) SPLAT_OP(i32, int32_t, i32)
WAH_RUN(I64X2_SPLAT) SPLAT_OP(i64, int64_t, i64)
WAH_RUN(F32X4_SPLAT) SPLAT_OP(f32, float, f32)
WAH_RUN(F64X2_SPLAT) SPLAT_OP(f64, double, f64)
#undef EXTRACT_LANE_OP
#undef REPLACE_LANE_OP
#undef SPLAT_OP
#define VSTACK_V128_TOP (ctx->value_stack[ctx->sp - 1].v128)
#define VSTACK_V128_B (ctx->value_stack[ctx->sp - 1].v128)
#define VSTACK_V128_A (ctx->value_stack[ctx->sp - 2].v128)
#define V128_UNARY_OP(op) { VSTACK_V128_TOP = op(VSTACK_V128_TOP); WAH_NEXT(); }
#define V128_BINARY_OP(op) { VSTACK_V128_A = op(VSTACK_V128_A, VSTACK_V128_B); ctx->sp--; WAH_NEXT(); }
#define V128_BINARY_OP_LANE(N, op, field) { \
for (int i = 0; i < 128/N; ++i) { \
VSTACK_V128_A.field[i] = VSTACK_V128_A.field[i] op VSTACK_V128_B.field[i]; \
} \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_BINARY_OP_LANE_SAT_S(N, op, field, min_val, max_val) { \
for (int i = 0; i < 128/N; ++i) { \
int64_t res = (int64_t)VSTACK_V128_A.field[i] op (int64_t)VSTACK_V128_B.field[i]; \
if (res < min_val) res = min_val; \
if (res > max_val) res = max_val; \
VSTACK_V128_A.field[i] = (int##N##_t)res; \
} \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_BINARY_OP_LANE_SAT_U(N, op, field, max_val) { \
for (int i = 0; i < 128/N; ++i) { \
int64_t res = (uint64_t)VSTACK_V128_A.field[i] op (uint64_t)VSTACK_V128_B.field[i]; \
if (res < 0) res = 0; \
if (res > max_val) res = max_val; \
VSTACK_V128_A.field[i] = (uint##N##_t)res; \
} \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_BINARY_OP_LANE_F(N, op, field) { \
for (int i = 0; i < 128/N; ++i) { \
VSTACK_V128_A.field[i] = wah_canonicalize_##field(VSTACK_V128_A.field[i] op VSTACK_V128_B.field[i]); \
} \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_CMP_I_LANE(N, op, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) { \
a.field[i] = (a.field[i] op b.field[i]) ? (~0ULL >> (64 - N)) : 0; \
} \
VSTACK_V128_A = a; ctx->sp--; WAH_NEXT(); \
}
#define V128_CMP_I_LANE_S(N, op, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) { \
a.field[i] = (((int##N##_t)a.field[i]) op ((int##N##_t)b.field[i])) ? (~0ULL >> (64 - N)) : 0; \
} \
VSTACK_V128_A = a; ctx->sp--; WAH_NEXT(); \
}
#define V128_CMP_I_LANE_U(N, op, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) { \
a.field[i] = (((uint##N##_t)a.field[i]) op ((uint##N##_t)b.field[i])) ? (~0ULL >> (64 - N)) : 0; \
} \
VSTACK_V128_A = a; ctx->sp--; WAH_NEXT(); \
}
#define V128_CMP_F_LANE(N, op, field) { \
wah_v128_t *a_ptr = &VSTACK_V128_A; \
wah_v128_t b = VSTACK_V128_B; \
for (int i = 0; i < 128/N; ++i) a_ptr->i##N[i] = (a_ptr->field[i] op b.field[i]) ? -1 : 0; \
ctx->sp--; WAH_NEXT(); \
}
WAH_RUN(V128_NOT) {
wah_v128_t val = VSTACK_V128_TOP;
for (int i = 0; i < 16; ++i) val.u8[i] = ~val.u8[i];
VSTACK_V128_TOP = val;
WAH_NEXT();
}
WAH_RUN(V128_AND) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 16; ++i) a.u8[i] &= b.u8[i];
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(V128_ANDNOT) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 16; ++i) a.u8[i] &= ~b.u8[i];
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(V128_OR) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 16; ++i) a.u8[i] |= b.u8[i];
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(V128_XOR) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 16; ++i) a.u8[i] ^= b.u8[i];
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I8X16_ADD) V128_BINARY_OP_LANE(8, +, i8)
WAH_RUN(I8X16_ADD_SAT_S) V128_BINARY_OP_LANE_SAT_S(8, +, i8, -128, 127)
WAH_RUN(I8X16_ADD_SAT_U) V128_BINARY_OP_LANE_SAT_U(8, +, u8, 255)
WAH_RUN(I8X16_SUB) V128_BINARY_OP_LANE(8, -, i8)
WAH_RUN(I8X16_SUB_SAT_S) V128_BINARY_OP_LANE_SAT_S(8, -, i8, -128, 127)
WAH_RUN(I8X16_SUB_SAT_U) V128_BINARY_OP_LANE_SAT_U(8, -, u8, 255)
WAH_RUN(I16X8_ADD) V128_BINARY_OP_LANE(16, +, i16)
WAH_RUN(I16X8_ADD_SAT_S) V128_BINARY_OP_LANE_SAT_S(16, +, i16, -32768, 32767)
WAH_RUN(I16X8_ADD_SAT_U) V128_BINARY_OP_LANE_SAT_U(16, +, u16, 65535)
WAH_RUN(I16X8_SUB) V128_BINARY_OP_LANE(16, -, i16)
WAH_RUN(I16X8_SUB_SAT_S) V128_BINARY_OP_LANE_SAT_S(16, -, i16, -32768, 32767)
WAH_RUN(I16X8_SUB_SAT_U) V128_BINARY_OP_LANE_SAT_U(16, -, u16, 65535)
WAH_RUN(I16X8_MUL) V128_BINARY_OP_LANE(16, *, i16)
WAH_RUN(I32X4_ADD) V128_BINARY_OP_LANE(32, +, i32)
WAH_RUN(I32X4_SUB) V128_BINARY_OP_LANE(32, -, i32)
WAH_RUN(I32X4_MUL) V128_BINARY_OP_LANE(32, *, i32)
WAH_RUN(I64X2_ADD) V128_BINARY_OP_LANE(64, +, i64)
WAH_RUN(I64X2_SUB) V128_BINARY_OP_LANE(64, -, i64)
WAH_RUN(I64X2_MUL) V128_BINARY_OP_LANE(64, *, i64)
WAH_RUN(F32X4_ADD) V128_BINARY_OP_LANE_F(32, +, f32)
WAH_RUN(F32X4_SUB) V128_BINARY_OP_LANE_F(32, -, f32)
WAH_RUN(F32X4_MUL) V128_BINARY_OP_LANE_F(32, *, f32)
WAH_RUN(F32X4_DIV) V128_BINARY_OP_LANE_F(32, /, f32)
WAH_RUN(F64X2_ADD) V128_BINARY_OP_LANE_F(64, +, f64)
WAH_RUN(F64X2_SUB) V128_BINARY_OP_LANE_F(64, -, f64)
WAH_RUN(F64X2_MUL) V128_BINARY_OP_LANE_F(64, *, f64)
WAH_RUN(F64X2_DIV) V128_BINARY_OP_LANE_F(64, /, f64)
#define V128_UNARY_OP_LANE(N, op, field) { \
wah_v128_t val = VSTACK_V128_TOP; \
for (int i = 0; i < 128/N; ++i) val.field[i] = op(val.field[i]); \
VSTACK_V128_TOP = val; \
WAH_NEXT(); \
}
#define V128_UNARY_OP_LANE_FN(N, fn, field) { \
wah_v128_t val = VSTACK_V128_TOP; \
for (int i = 0; i < 128/N; ++i) val.field[i] = fn(val.field[i]); \
VSTACK_V128_TOP = val; \
WAH_NEXT(); \
}
#define V128_UNARY_OP_LANE_CAST_FN(N, fn, field, cast_type) { \
wah_v128_t val = VSTACK_V128_TOP; \
for (int i = 0; i < 128/N; ++i) val.field[i] = (field)fn((cast_type)val.field[i]); \
VSTACK_V128_TOP = val; \
WAH_NEXT(); \
}
#define V128_BINARY_OP_LANE_FN(N, fn, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) a.field[i] = fn(a.field[i], b.field[i]); \
VSTACK_V128_A = a; \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_SHIFT_OP_LANE(N, op, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) a.field[i] = a.field[i] op (b.field[0] & (N - 1)); \
VSTACK_V128_A = a; \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_SHIFT_OP_LANE_U(N, op, field) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
for (int i = 0; i < 128/N; ++i) { \
a.field[i] = (uint##N##_t)((uint##N##_t)a.field[i] op (b.field[0] & (N - 1))); \
} \
VSTACK_V128_A = a; \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_ABS_OP(N, field, abs_func) { \
wah_v128_t val = VSTACK_V128_TOP; \
for (int i = 0; i < 128/N; ++i) val.field[i] = (int##N##_t)abs_func(val.field[i]); \
VSTACK_V128_TOP = val; \
WAH_NEXT(); \
}
#define V128_ALL_TRUE_OP(N, field) { \
wah_v128_t val = VSTACK_V128_TOP; \
int32_t result = 1; \
for (int i = 0; i < 128/N; ++i) { \
if (val.field[i] == 0) { \
result = 0; \
break; \
} \
} \
ctx->value_stack[ctx->sp++].i32 = result; \
WAH_NEXT(); \
}
#define V128_BITMASK_OP(N, field) \
{ \
wah_v128_t val = VSTACK_V128_TOP; \
int32_t result = 0; \
for (int i = 0; i < 128/N; ++i) { \
if (val.field[i] < 0) { \
result |= (1 << i); \
} \
} \
ctx->value_stack[ctx->sp++].i32 = result; \
WAH_NEXT(); \
}
#define V128_EXTEND_LOW_OP(DST_N, DST_FIELD, SRC_N, SRC_FIELD, SIGN_TYPE) { \
wah_v128_t val = VSTACK_V128_TOP; \
wah_v128_t result; \
for (int i = 0; i < 128/DST_N; ++i) result.DST_FIELD[i] = (SIGN_TYPE##DST_N##_t)val.SRC_FIELD[i]; \
VSTACK_V128_TOP = result; \
WAH_NEXT(); \
}
#define V128_EXTEND_HIGH_OP(DST_N, DST_FIELD, SRC_N, SRC_FIELD, SIGN_TYPE) { \
wah_v128_t val = VSTACK_V128_TOP; \
wah_v128_t result; \
for (int i = 0; i < 128/DST_N; ++i) result.DST_FIELD[i] = (SIGN_TYPE##DST_N##_t)val.SRC_FIELD[i + (128/SRC_N)/2]; \
VSTACK_V128_TOP = result; \
WAH_NEXT(); \
}
#define V128_EXTMUL_LOW_OP(DST_N, DST_FIELD, INTERM_T, SRC_N, SRC_FIELD, SIGN_TYPE) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
wah_v128_t result; \
for (int i = 0; i < 128/DST_N; ++i) { \
result.DST_FIELD[i] = (SIGN_TYPE##DST_N##_t)((INTERM_T)a.SRC_FIELD[i] * (INTERM_T)b.SRC_FIELD[i]); \
} \
VSTACK_V128_A = result; \
ctx->sp--; \
WAH_NEXT(); \
}
#define V128_EXTMUL_HIGH_OP(DST_N, DST_FIELD, INTERM_T, SRC_N, SRC_FIELD, SIGN_TYPE) { \
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A; \
wah_v128_t result; \
int offset = (128/SRC_N)/2; \
for (int i = 0; i < 128/DST_N; ++i) { \
result.DST_FIELD[i] = (SIGN_TYPE##DST_N##_t)((INTERM_T)a.SRC_FIELD[i + offset] * (INTERM_T)b.SRC_FIELD[i + offset]); \
} \
VSTACK_V128_A = result; \
ctx->sp--; \
WAH_NEXT(); \
}
WAH_RUN(I8X16_ABS) V128_ABS_OP(8, i8, abs)
WAH_RUN(I8X16_NEG) V128_UNARY_OP_LANE(8, -, i8)
WAH_RUN(I8X16_POPCNT) V128_UNARY_OP_LANE_FN(8, wah_popcount_u8, u8)
WAH_RUN(I8X16_ALL_TRUE) V128_ALL_TRUE_OP(8, u8)
WAH_RUN(I8X16_BITMASK) V128_BITMASK_OP(8, i8)
WAH_RUN(I8X16_NARROW_I16X8_S) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
wah_v128_t result;
for (int i = 0; i < 8; ++i) {
result.i8[i] = (int8_t)wah_trunc_sat_i16_to_i8(a.i16[i]);
result.i8[i+8] = (int8_t)wah_trunc_sat_i16_to_i8(b.i16[i]);
}
VSTACK_V128_A = result;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I8X16_NARROW_I16X8_U) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
wah_v128_t result;
for (int i = 0; i < 8; ++i) {
result.u8[i] = (uint8_t)wah_trunc_sat_i16_to_u8(a.i16[i]);
result.u8[i+8] = (uint8_t)wah_trunc_sat_i16_to_u8(b.i16[i]);
}
VSTACK_V128_A = result;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I8X16_SHL) V128_SHIFT_OP_LANE(8, <<, i8)
WAH_RUN(I8X16_SHR_S) V128_SHIFT_OP_LANE(8, >>, i8)
WAH_RUN(I8X16_SHR_U) V128_SHIFT_OP_LANE_U(8, >>, u8)
WAH_RUN(I8X16_MIN_S) V128_BINARY_OP_LANE_FN(8, WAH_MIN_S_8, i8)
WAH_RUN(I8X16_MIN_U) V128_BINARY_OP_LANE_FN(8, WAH_MIN_U_8, u8)
WAH_RUN(I8X16_MAX_S) V128_BINARY_OP_LANE_FN(8, WAH_MAX_S_8, i8)
WAH_RUN(I8X16_MAX_U) V128_BINARY_OP_LANE_FN(8, WAH_MAX_U_8, u8)
WAH_RUN(I8X16_AVGR_U) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 16; ++i) {
a.u8[i] = (uint8_t)(((uint16_t)a.u8[i] + (uint16_t)b.u8[i] + 1) >> 1);
}
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I16X8_EXTADD_PAIRWISE_I8X16_S) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result;
for (int i = 0; i < 8; ++i) {
result.i16[i] = (int16_t)val.i8[i*2] + (int16_t)val.i8[i*2+1];
}
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(I16X8_EXTADD_PAIRWISE_I8X16_U) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result;
for (int i = 0; i < 8; ++i) {
result.u16[i] = (uint16_t)val.u8[i*2] + (uint16_t)val.u8[i*2+1];
}
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(I16X8_ABS) V128_ABS_OP(16, i16, abs)
WAH_RUN(I16X8_NEG) V128_UNARY_OP_LANE(16, -, i16)
WAH_RUN(I16X8_Q15MULR_SAT_S) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 8; ++i) {
int32_t prod = (int32_t)a.i16[i] * b.i16[i];
int32_t res = (prod + 16384) >> 15; // Rounding and shift
if (res > 32767) res = 32767;
if (res < -32768) res = -32768;
a.i16[i] = (int16_t)res;
}
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I16X8_ALL_TRUE) V128_ALL_TRUE_OP(16, u16)
WAH_RUN(I16X8_BITMASK) V128_BITMASK_OP(16, i16)
WAH_RUN(I16X8_NARROW_I32X4_S) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
wah_v128_t result;
for (int i = 0; i < 4; ++i) {
result.i16[i] = (int16_t)wah_trunc_sat_i32_to_i16(a.i32[i]);
result.i16[i+4] = (int16_t)wah_trunc_sat_i32_to_i16(b.i32[i]);
}
VSTACK_V128_A = result;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I16X8_NARROW_I32X4_U) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
wah_v128_t result;
for (int i = 0; i < 4; ++i) {
result.u16[i] = (uint16_t)wah_trunc_sat_i32_to_u16(a.i32[i]);
result.u16[i+4] = (uint16_t)wah_trunc_sat_i32_to_u16(b.i32[i]);
}
VSTACK_V128_A = result;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I16X8_EXTEND_LOW_I8X16_S) V128_EXTEND_LOW_OP(16, i16, 8, i8, int)
WAH_RUN(I16X8_EXTEND_HIGH_I8X16_S) V128_EXTEND_HIGH_OP(16, i16, 8, i8, int)
WAH_RUN(I16X8_EXTEND_LOW_I8X16_U) V128_EXTEND_LOW_OP(16, u16, 8, u8, uint)
WAH_RUN(I16X8_EXTEND_HIGH_I8X16_U) V128_EXTEND_HIGH_OP(16, u16, 8, u8, uint)
WAH_RUN(I16X8_SHL) V128_SHIFT_OP_LANE(16, <<, i16)
WAH_RUN(I16X8_SHR_S) V128_SHIFT_OP_LANE(16, >>, i16)
WAH_RUN(I16X8_SHR_U) V128_SHIFT_OP_LANE_U(16, >>, u16)
WAH_RUN(I16X8_MIN_S) V128_BINARY_OP_LANE_FN(16, WAH_MIN_S_16, i16)
WAH_RUN(I16X8_MIN_U) V128_BINARY_OP_LANE_FN(16, WAH_MIN_U_16, u16)
WAH_RUN(I16X8_MAX_S) V128_BINARY_OP_LANE_FN(16, WAH_MAX_S_16, i16)
WAH_RUN(I16X8_MAX_U) V128_BINARY_OP_LANE_FN(16, WAH_MAX_U_16, u16)
WAH_RUN(I16X8_AVGR_U) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
for (int i = 0; i < 8; ++i) {
a.u16[i] = (uint16_t)(((uint32_t)a.u16[i] + (uint32_t)b.u16[i] + 1) >> 1);
}
VSTACK_V128_A = a;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I16X8_EXTMUL_LOW_I8X16_S) V128_EXTMUL_LOW_OP(16, i16, int32_t, 8, i8, int)
WAH_RUN(I16X8_EXTMUL_HIGH_I8X16_S) V128_EXTMUL_HIGH_OP(16, i16, int32_t, 8, i8, int)
WAH_RUN(I16X8_EXTMUL_LOW_I8X16_U) V128_EXTMUL_LOW_OP(16, u16, uint32_t, 8, u8, uint)
WAH_RUN(I16X8_EXTMUL_HIGH_I8X16_U) V128_EXTMUL_HIGH_OP(16, u16, uint32_t, 8, u8, uint)
WAH_RUN(I32X4_EXTADD_PAIRWISE_I16X8_S) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result;
for (int i = 0; i < 4; ++i) {
result.i32[i] = (int32_t)val.i16[i*2] + (int32_t)val.i16[i*2+1];
}
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(I32X4_EXTADD_PAIRWISE_I16X8_U) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result;
for (int i = 0; i < 4; ++i) {
result.u32[i] = (uint32_t)val.u16[i*2] + (uint32_t)val.u16[i*2+1];
}
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(I32X4_ABS) V128_ABS_OP(32, i32, abs)
WAH_RUN(I32X4_NEG) V128_UNARY_OP_LANE(32, -, i32)
WAH_RUN(I32X4_ALL_TRUE) V128_ALL_TRUE_OP(32, u32)
WAH_RUN(I32X4_BITMASK) V128_BITMASK_OP(32, i32)
WAH_RUN(I32X4_EXTEND_LOW_I16X8_S) V128_EXTEND_LOW_OP(32, i32, 16, i16, int)
WAH_RUN(I32X4_EXTEND_HIGH_I16X8_S) V128_EXTEND_HIGH_OP(32, i32, 16, i16, int)
WAH_RUN(I32X4_EXTEND_LOW_I16X8_U) V128_EXTEND_LOW_OP(32, u32, 16, u16, uint)
WAH_RUN(I32X4_EXTEND_HIGH_I16X8_U) V128_EXTEND_HIGH_OP(32, u32, 16, u16, uint)
WAH_RUN(I32X4_SHL) V128_SHIFT_OP_LANE(32, <<, i32)
WAH_RUN(I32X4_SHR_S) V128_SHIFT_OP_LANE(32, >>, i32)
WAH_RUN(I32X4_SHR_U) V128_SHIFT_OP_LANE_U(32, >>, u32)
WAH_RUN(I32X4_MIN_S) V128_BINARY_OP_LANE_FN(32, WAH_MIN_S_32, i32)
WAH_RUN(I32X4_MIN_U) V128_BINARY_OP_LANE_FN(32, WAH_MIN_U_32, u32)
WAH_RUN(I32X4_MAX_S) V128_BINARY_OP_LANE_FN(32, WAH_MAX_S_32, i32)
WAH_RUN(I32X4_MAX_U) V128_BINARY_OP_LANE_FN(32, WAH_MAX_U_32, u32)
WAH_RUN(I32X4_DOT_I16X8_S) {
wah_v128_t b = VSTACK_V128_B, a = VSTACK_V128_A;
wah_v128_t result;
for (int i = 0; i < 4; ++i) {
result.i32[i] = (int32_t)a.i16[i*2] * b.i16[i*2] + (int32_t)a.i16[i*2+1] * b.i16[i*2+1];
}
VSTACK_V128_A = result;
ctx->sp--;
WAH_NEXT();
}
WAH_RUN(I32X4_EXTMUL_LOW_I16X8_S) V128_EXTMUL_LOW_OP(32, i32, int64_t, 16, i16, int)
WAH_RUN(I32X4_EXTMUL_HIGH_I16X8_S) V128_EXTMUL_HIGH_OP(32, i32, int64_t, 16, i16, int)
WAH_RUN(I32X4_EXTMUL_LOW_I16X8_U) V128_EXTMUL_LOW_OP(32, u32, uint64_t, 16, u16, uint)
WAH_RUN(I32X4_EXTMUL_HIGH_I16X8_U) V128_EXTMUL_HIGH_OP(32, u32, uint64_t, 16, u16, uint)
WAH_RUN(I64X2_ABS) V128_ABS_OP(64, i64, llabs)
WAH_RUN(I64X2_NEG) V128_UNARY_OP_LANE(64, -, i64)
WAH_RUN(I64X2_ALL_TRUE) V128_ALL_TRUE_OP(64, u64)
WAH_RUN(I64X2_BITMASK) V128_BITMASK_OP(64, i64)
WAH_RUN(I64X2_EXTEND_LOW_I32X4_S) V128_EXTEND_LOW_OP(64, i64, 32, i32, int)
WAH_RUN(I64X2_EXTEND_HIGH_I32X4_S) V128_EXTEND_HIGH_OP(64, i64, 32, i32, int)
WAH_RUN(I64X2_EXTEND_LOW_I32X4_U) V128_EXTEND_LOW_OP(64, u64, 32, u32, uint)
WAH_RUN(I64X2_EXTEND_HIGH_I32X4_U) V128_EXTEND_HIGH_OP(64, u64, 32, u32, uint)
WAH_RUN(I64X2_SHL) V128_SHIFT_OP_LANE(64, <<, i64)
WAH_RUN(I64X2_SHR_S) V128_SHIFT_OP_LANE(64, >>, i64)
WAH_RUN(I64X2_SHR_U) V128_SHIFT_OP_LANE_U(64, >>, u64)
WAH_RUN(I64X2_EXTMUL_LOW_I32X4_S) V128_EXTMUL_LOW_OP(64, i64, int64_t, 32, i32, int)
WAH_RUN(I64X2_EXTMUL_HIGH_I32X4_S) V128_EXTMUL_HIGH_OP(64, i64, int64_t, 32, i32, int)
WAH_RUN(I64X2_EXTMUL_LOW_I32X4_U) V128_EXTMUL_LOW_OP(64, u64, uint64_t, 32, u32, uint)
WAH_RUN(I64X2_EXTMUL_HIGH_I32X4_U) V128_EXTMUL_HIGH_OP(64, u64, uint64_t, 32, u32, uint)
WAH_RUN(F32X4_CEIL) V128_UNARY_OP_LANE_FN(32, ceilf, f32)
WAH_RUN(F32X4_FLOOR) V128_UNARY_OP_LANE_FN(32, floorf, f32)
WAH_RUN(F32X4_TRUNC) V128_UNARY_OP_LANE_FN(32, truncf, f32)
WAH_RUN(F32X4_NEAREST) V128_UNARY_OP_LANE_FN(32, wah_nearest_f32, f32)
WAH_RUN(F32X4_ABS) V128_UNARY_OP_LANE_FN(32, fabsf, f32)
WAH_RUN(F32X4_NEG) V128_UNARY_OP_LANE(32, -, f32)
WAH_RUN(F32X4_SQRT) V128_UNARY_OP_LANE_FN(32, sqrtf, f32)
WAH_RUN(F32X4_MIN) V128_BINARY_OP_LANE_FN(32, fminf, f32)
WAH_RUN(F32X4_MAX) V128_BINARY_OP_LANE_FN(32, fmaxf, f32)
WAH_RUN(F32X4_PMIN) V128_BINARY_OP_LANE_FN(32, fminf, f32) // pmin is same as min for floats
WAH_RUN(F32X4_PMAX) V128_BINARY_OP_LANE_FN(32, fmaxf, f32) // pmax is same as max for floats
WAH_RUN(F64X2_CEIL) V128_UNARY_OP_LANE_FN(64, ceil, f64)
WAH_RUN(F64X2_FLOOR) V128_UNARY_OP_LANE_FN(64, floor, f64)
WAH_RUN(F64X2_TRUNC) V128_UNARY_OP_LANE_FN(64, trunc, f64)
WAH_RUN(F64X2_NEAREST) V128_UNARY_OP_LANE_FN(64, wah_nearest_f64, f64)
WAH_RUN(F64X2_ABS) V128_UNARY_OP_LANE_FN(64, fabs, f64)
WAH_RUN(F64X2_NEG) V128_UNARY_OP_LANE(64, -, f64)
WAH_RUN(F64X2_SQRT) V128_UNARY_OP_LANE_FN(64, sqrt, f64)
WAH_RUN(F64X2_MIN) V128_BINARY_OP_LANE_FN(64, fmin, f64)
WAH_RUN(F64X2_MAX) V128_BINARY_OP_LANE_FN(64, fmax, f64)
WAH_RUN(F64X2_PMIN) V128_BINARY_OP_LANE_FN(64, fmin, f64) // pmin is same as min for floats
WAH_RUN(F64X2_PMAX) V128_BINARY_OP_LANE_FN(64, fmax, f64) // pmax is same as max for floats
WAH_RUN(V128_BITSELECT) {
wah_v128_t v3 = ctx->value_stack[--ctx->sp].v128;
wah_v128_t v2 = ctx->value_stack[--ctx->sp].v128;
wah_v128_t v1 = ctx->value_stack[--ctx->sp].v128;
wah_v128_t result;
for (int i = 0; i < 16; ++i) {
result.u8[i] = (v1.u8[i] & v2.u8[i]) | (~v1.u8[i] & v3.u8[i]);
}
ctx->value_stack[ctx->sp++].v128 = result;
WAH_NEXT();
}
WAH_RUN(V128_ANY_TRUE) {
wah_v128_t val = ctx->value_stack[--ctx->sp].v128;
int32_t result = 0;
for (int i = 0; i < 16; ++i) {
if (val.u8[i] != 0) {
result = 1;
break;
}
}
ctx->value_stack[ctx->sp++].i32 = result;
WAH_NEXT();
}
WAH_RUN(I8X16_EQ) V128_CMP_I_LANE(8, ==, u8)
WAH_RUN(I8X16_NE) V128_CMP_I_LANE(8, !=, u8)
WAH_RUN(I8X16_LT_S) V128_CMP_I_LANE_S(8, <, i8)
WAH_RUN(I8X16_LT_U) V128_CMP_I_LANE_U(8, <, u8)
WAH_RUN(I8X16_GT_S) V128_CMP_I_LANE_S(8, >, i8)
WAH_RUN(I8X16_GT_U) V128_CMP_I_LANE_U(8, >, u8)
WAH_RUN(I8X16_LE_S) V128_CMP_I_LANE_S(8, <=, i8)
WAH_RUN(I8X16_LE_U) V128_CMP_I_LANE_U(8, <=, u8)
WAH_RUN(I8X16_GE_S) V128_CMP_I_LANE_S(8, >=, i8)
WAH_RUN(I8X16_GE_U) V128_CMP_I_LANE_U(8, >=, u8)
WAH_RUN(I16X8_EQ) V128_CMP_I_LANE(16, ==, u16)
WAH_RUN(I16X8_NE) V128_CMP_I_LANE(16, !=, u16)
WAH_RUN(I16X8_LT_S) V128_CMP_I_LANE_S(16, <, i16)
WAH_RUN(I16X8_LT_U) V128_CMP_I_LANE_U(16, <, u16)
WAH_RUN(I16X8_GT_S) V128_CMP_I_LANE_S(16, >, i16)
WAH_RUN(I16X8_GT_U) V128_CMP_I_LANE_U(16, >, u16)
WAH_RUN(I16X8_LE_S) V128_CMP_I_LANE_S(16, <=, i16)
WAH_RUN(I16X8_LE_U) V128_CMP_I_LANE_U(16, <=, u16)
WAH_RUN(I16X8_GE_S) V128_CMP_I_LANE_S(16, >=, i16)
WAH_RUN(I16X8_GE_U) V128_CMP_I_LANE_U(16, >=, u16)
WAH_RUN(I32X4_EQ) V128_CMP_I_LANE(32, ==, u32)
WAH_RUN(I32X4_NE) V128_CMP_I_LANE(32, !=, u32)
WAH_RUN(I32X4_LT_S) V128_CMP_I_LANE_S(32, <, i32)
WAH_RUN(I32X4_LT_U) V128_CMP_I_LANE_U(32, <, u32)
WAH_RUN(I32X4_GT_S) V128_CMP_I_LANE_S(32, >, i32)
WAH_RUN(I32X4_GT_U) V128_CMP_I_LANE_U(32, >, u32)
WAH_RUN(I32X4_LE_S) V128_CMP_I_LANE_S(32, <=, i32)
WAH_RUN(I32X4_LE_U) V128_CMP_I_LANE_U(32, <=, u32)
WAH_RUN(I32X4_GE_S) V128_CMP_I_LANE_S(32, >=, i32)
WAH_RUN(I32X4_GE_U) V128_CMP_I_LANE_U(32, >=, u32)
WAH_RUN(I64X2_EQ) V128_CMP_I_LANE(64, ==, u64)
WAH_RUN(I64X2_NE) V128_CMP_I_LANE(64, !=, u64)
WAH_RUN(I64X2_LT_S) V128_CMP_I_LANE_S(64, <, i64)
WAH_RUN(I64X2_GT_S) V128_CMP_I_LANE_S(64, >, i64)
WAH_RUN(I64X2_LE_S) V128_CMP_I_LANE_S(64, <=, i64)
WAH_RUN(I64X2_GE_S) V128_CMP_I_LANE_S(64, >=, i64)
WAH_RUN(F32X4_EQ) V128_CMP_F_LANE(32, ==, f32)
WAH_RUN(F32X4_NE) V128_CMP_F_LANE(32, !=, f32)
WAH_RUN(F32X4_LT) V128_CMP_F_LANE(32, <, f32)
WAH_RUN(F32X4_GT) V128_CMP_F_LANE(32, >, f32)
WAH_RUN(F32X4_LE) V128_CMP_F_LANE(32, <=, f32)
WAH_RUN(F32X4_GE) V128_CMP_F_LANE(32, >=, f32)
WAH_RUN(F64X2_EQ) V128_CMP_F_LANE(64, ==, f64)
WAH_RUN(F64X2_NE) V128_CMP_F_LANE(64, !=, f64)
WAH_RUN(F64X2_LT) V128_CMP_F_LANE(64, <, f64)
WAH_RUN(F64X2_GT) V128_CMP_F_LANE(64, >, f64)
WAH_RUN(F64X2_LE) V128_CMP_F_LANE(64, <=, f64)
WAH_RUN(F64X2_GE) V128_CMP_F_LANE(64, >=, f64)
WAH_RUN(I32X4_TRUNC_SAT_F32X4_S) {
wah_v128_t val = VSTACK_V128_TOP;
for (int i = 0; i < 4; ++i) val.i32[i] = wah_trunc_sat_f32_to_i32(val.f32[i]);
VSTACK_V128_TOP = val;
WAH_NEXT();
}
WAH_RUN(I32X4_TRUNC_SAT_F32X4_U) {
wah_v128_t val = VSTACK_V128_TOP;
for (int i = 0; i < 4; ++i) val.u32[i] = wah_trunc_sat_f32_to_u32(val.f32[i]);
VSTACK_V128_TOP = val;
WAH_NEXT();
}
WAH_RUN(F32X4_CONVERT_I32X4_S) {
wah_v128_t val = VSTACK_V128_TOP;
for (int i = 0; i < 4; ++i) val.f32[i] = (float)val.i32[i];
VSTACK_V128_TOP = val;
WAH_NEXT();
}
WAH_RUN(F32X4_CONVERT_I32X4_U) {
wah_v128_t val = VSTACK_V128_TOP;
for (int i = 0; i < 4; ++i) val.f32[i] = (float)val.u32[i];
VSTACK_V128_TOP = val;
WAH_NEXT();
}
WAH_RUN(I32X4_TRUNC_SAT_F64X2_S_ZERO) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .i32 = {wah_trunc_sat_f64_to_i32(val.f64[0]), wah_trunc_sat_f64_to_i32(val.f64[1])} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(I32X4_TRUNC_SAT_F64X2_U_ZERO) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .u32 = {wah_trunc_sat_f64_to_u32(val.f64[0]), wah_trunc_sat_f64_to_u32(val.f64[1])} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(F64X2_CONVERT_LOW_I32X4_S) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .f64 = {(double)val.i32[0], (double)val.i32[1]} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(F64X2_CONVERT_LOW_I32X4_U) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .f64 = {(double)val.u32[0], (double)val.u32[1]} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(F32X4_DEMOTE_F64X2_ZERO) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .f32 = {wah_canonicalize_f32((float)val.f64[0]), wah_canonicalize_f32((float)val.f64[1])} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
WAH_RUN(F64X2_PROMOTE_LOW_F32X4) {
wah_v128_t val = VSTACK_V128_TOP;
wah_v128_t result = { .f64 = {wah_canonicalize_f64((double)val.f32[0]), wah_canonicalize_f64((double)val.f32[1])} };
VSTACK_V128_TOP = result;
WAH_NEXT();
}
#undef VSTACK_V128_TOP
#undef VSTACK_V128_B
#undef VSTACK_V128_A
#undef V128_UNARY_OP
#undef V128_BINARY_OP
#undef V128_BINARY_OP_LANE
#undef V128_BINARY_OP_LANE_SAT_S
#undef V128_BINARY_OP_LANE_SAT_U
#undef V128_BINARY_OP_LANE_F
#undef V128_CMP_I_LANE
#undef V128_CMP_I_LANE_S
#undef V128_CMP_I_LANE_U
#undef V128_CMP_F_LANE
//------------------------------------------------------------------------------
#ifdef WAH_USE_MUSTTAIL
static wah_error_t wah_run_single(wah_exec_context_t *ctx, wah_call_frame_t *frame,
const uint8_t *bytecode_ip, const uint8_t *bytecode_base, wah_error_t err) {
uint16_t opcode = wah_read_u16_le(bytecode_ip);
bytecode_ip += sizeof(uint16_t);
switch (opcode) {
#define WAH_OPCODE_CASES(opcode, _) \
case WAH_OP_##opcode: __attribute__((musttail)) return wah_run_##opcode(ctx, frame, bytecode_ip, bytecode_base, err);
WAH_OPCODES(WAH_OPCODE_CASES)
#undef WAH_OPCODE_CASES
default:
return WAH_ERROR_VALIDATION_FAILED;
}
}
static wah_error_t wah_run_interpreter(wah_exec_context_t *ctx) {
// These are pointers to the current frame's state for faster access.
wah_call_frame_t *frame = &ctx->call_stack[ctx->call_depth - 1];
const uint8_t *bytecode_ip = frame->bytecode_ip;
const uint8_t *bytecode_base = frame->code->parsed_code.bytecode;
return wah_run_single(ctx, frame, bytecode_ip, bytecode_base, WAH_OK);
}
#else
#ifndef WAH_USE_COMPUTED_GOTO
}
}
#endif
cleanup:
if (ctx->call_depth > 0) {
frame->bytecode_ip = bytecode_ip;
}
return err;
} // End of wah_run_interpreter
#endif
wah_error_t wah_call(wah_exec_context_t *exec_ctx, const wah_module_t *module, uint32_t func_idx, const wah_value_t *params, uint32_t param_count, wah_value_t *result) {
WAH_ENSURE(func_idx < module->function_count, WAH_ERROR_UNKNOWN_SECTION);
const wah_func_type_t *func_type = &module->types[module->function_type_indices[func_idx]];
WAH_ENSURE(param_count == func_type->param_count, WAH_ERROR_VALIDATION_FAILED);
// Push initial params onto the value stack
for (uint32_t i = 0; i < param_count; ++i) {
WAH_ENSURE(exec_ctx->sp < exec_ctx->value_stack_capacity, WAH_ERROR_CALL_STACK_OVERFLOW); // Value stack overflow
exec_ctx->value_stack[exec_ctx->sp++] = params[i];
}
// Push the first frame. Locals offset is the current stack pointer before parameters.
WAH_CHECK(wah_push_frame(exec_ctx, func_idx, exec_ctx->sp - func_type->param_count));
// Reserve space for the function's own locals and initialize them to zero
uint32_t num_locals = exec_ctx->call_stack[0].code->local_count;
if (num_locals > 0) {
WAH_ENSURE(exec_ctx->sp + num_locals <= exec_ctx->value_stack_capacity, WAH_ERROR_OUT_OF_MEMORY);
memset(&exec_ctx->value_stack[exec_ctx->sp], 0, sizeof(wah_value_t) * num_locals);
exec_ctx->sp += num_locals;
}
// Run the main interpreter loop
WAH_CHECK(wah_run_interpreter(exec_ctx));
// After execution, if a result is expected, it's on top of the stack.
if (result && func_type->result_count > 0) {
// Check if the stack has any value to pop. It might be empty if the function trapped before returning a value.
if (exec_ctx->sp > 0) {
*result = exec_ctx->value_stack[exec_ctx->sp - 1];
}
}
return WAH_OK;
}
// --- Module Cleanup Implementation ---
void wah_free_module(wah_module_t *module) {
if (!module) {
return;
}
if (module->types) {
for (uint32_t i = 0; i < module->type_count; ++i) {
free(module->types[i].param_types);
free(module->types[i].result_types);
}
free(module->types);
}
free(module->function_type_indices);
if (module->code_bodies) {
for (uint32_t i = 0; i < module->code_count; ++i) {
free(module->code_bodies[i].local_types);
wah_free_parsed_code(&module->code_bodies[i].parsed_code);
}
free(module->code_bodies);
}
free(module->globals);
free(module->memories);
free(module->tables);
if (module->element_segments) {
for (uint32_t i = 0; i < module->element_segment_count; ++i) {
free(module->element_segments[i].func_indices);
}
free(module->element_segments);
}
free(module->data_segments); // Free data segments
if (module->exports) {
for (uint32_t i = 0; i < module->export_count; ++i) {
free((void*)module->exports[i].name);
}
free(module->exports); // Free exports
}
// Reset all fields to 0/NULL
memset(module, 0, sizeof(wah_module_t));
}
// --- Export API Implementation ---
size_t wah_module_num_exports(const wah_module_t *module) {
if (!module) return 0;
return module->export_count;
}
wah_error_t wah_module_export(const wah_module_t *module, size_t idx, wah_entry_t *out) {
WAH_ENSURE(module, WAH_ERROR_MISUSE);
WAH_ENSURE(out, WAH_ERROR_MISUSE);
WAH_ENSURE(idx < module->export_count, WAH_ERROR_NOT_FOUND);
const wah_export_t *export_entry = &module->exports[idx];
wah_entry_id_t entry_id;
switch (export_entry->kind) {
case 0: // Function
entry_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_FUNCTION, export_entry->index);
break;
case 1: // Table
entry_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_TABLE, export_entry->index);
break;
case 2: // Memory
entry_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_MEMORY, export_entry->index);
break;
case 3: // Global
entry_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_GLOBAL, export_entry->index);
break;
default:
return WAH_ERROR_VALIDATION_FAILED; // Should not happen if parsing is correct
}
WAH_CHECK(wah_module_entry(module, entry_id, out));
out->name = export_entry->name;
out->name_len = export_entry->name_len;
return WAH_OK;
}
wah_error_t wah_module_export_by_name(const wah_module_t *module, const char *name, wah_entry_t *out) {
WAH_ENSURE(module, WAH_ERROR_MISUSE);
WAH_ENSURE(name, WAH_ERROR_MISUSE);
WAH_ENSURE(out, WAH_ERROR_MISUSE);
size_t lookup_name_len = strlen(name);
for (uint32_t i = 0; i < module->export_count; ++i) {
const wah_export_t *export_entry = &module->exports[i];
if (export_entry->name_len == lookup_name_len && strncmp(export_entry->name, name, lookup_name_len) == 0) {
return wah_module_export(module, i, out);
}
}
return WAH_ERROR_NOT_FOUND;
}
wah_error_t wah_module_entry(const wah_module_t *module, wah_entry_id_t entry_id, wah_entry_t *out) {
WAH_ENSURE(module, WAH_ERROR_MISUSE);
WAH_ENSURE(out, WAH_ERROR_MISUSE);
uint32_t kind = WAH_GET_ENTRY_KIND(entry_id);
uint32_t index = WAH_GET_ENTRY_INDEX(entry_id);
out->id = entry_id;
out->name = NULL; // No name for non-exported entries
out->name_len = 0;
out->is_mutable = false; // Default to false
switch (kind) {
case WAH_ENTRY_KIND_FUNCTION:
WAH_ENSURE(index < module->function_count, WAH_ERROR_NOT_FOUND);
out->type = WAH_TYPE_FUNCTION;
const wah_func_type_t *func_type = &module->types[module->function_type_indices[index]];
out->u.func.param_count = func_type->param_count;
out->u.func.param_types = func_type->param_types;
out->u.func.result_count = func_type->result_count;
out->u.func.result_types = func_type->result_types;
break;
case WAH_ENTRY_KIND_TABLE:
WAH_ENSURE(index < module->table_count, WAH_ERROR_NOT_FOUND);
out->type = WAH_TYPE_TABLE;
out->u.table.elem_type = module->tables[index].elem_type;
out->u.table.min_elements = module->tables[index].min_elements;
out->u.table.max_elements = module->tables[index].max_elements;
break;
case WAH_ENTRY_KIND_MEMORY:
WAH_ENSURE(index < module->memory_count, WAH_ERROR_NOT_FOUND);
out->type = WAH_TYPE_MEMORY;
out->u.memory.min_pages = module->memories[index].min_pages;
out->u.memory.max_pages = module->memories[index].max_pages;
break;
case WAH_ENTRY_KIND_GLOBAL:
WAH_ENSURE(index < module->global_count, WAH_ERROR_NOT_FOUND);
out->type = module->globals[index].type;
out->is_mutable = module->globals[index].is_mutable;
out->u.global_val = module->globals[index].initial_value;
break;
default:
return WAH_ERROR_NOT_FOUND; // Unknown entry kind
}
return WAH_OK;
}
#endif // WAH_IMPLEMENTATION
#endif // WAH_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h> // For offsetof
#include <assert.h> // For assert
#include <stdbool.h> // For bool
#include <stdint.h> // For int32_t, uint32_t, int64_t, uint64_t
// --- Minimal WAH-like definitions for this experiment ---
// Define wah_value_type_t
typedef enum {
WAH_TYPE_BOOL,
WAH_TYPE_INT,
WAH_TYPE_UINT,
WAH_TYPE_LONG,
WAH_TYPE_ULONG,
WAH_TYPE_LLONG,
WAH_TYPE_ULLONG,
WAH_TYPE_I32,
WAH_TYPE_U32,
WAH_TYPE_I64,
WAH_TYPE_U64,
WAH_TYPE_FLOAT,
WAH_TYPE_DOUBLE,
WAH_TYPE_VOID, // For no return
WAH_TYPE_UNKNOWN
} wah_value_type_t;
// Untyped wah_value_t
typedef union {
bool b;
int i;
unsigned int I;
long l;
unsigned long L;
long long q;
unsigned long long Q;
int32_t i32;
uint32_t u32;
int64_t i64;
uint64_t u64;
float f;
double d;
} wah_value_t;
// C struct field descriptor
typedef struct {
wah_value_type_t wasm_type;
size_t offset;
size_t size; // Size of the C type
} wah_c_struct_field_desc_t;
// C struct descriptor
typedef struct {
wah_c_struct_field_desc_t* fields;
size_t num_fields;
size_t struct_size;
} wah_c_struct_desc_t;
// Minimal wah_exec_context_t for host functions
typedef struct {
// Placeholder for actual context
int dummy;
} wah_exec_context_t;
// Host function pointer type (generic for the internal wrapper)
// The user's function will be cast to this.
typedef int (*experiment_host_fn_ptr_t)(wah_exec_context_t* ctx, void* params_struct, void* returns_struct, void* user_data);
// --- Mini-language parsing ---
typedef struct {
wah_value_type_t* param_types;
size_t num_params;
wah_value_type_t* return_types;
size_t num_returns;
} parsed_signature_t;
// Helper to get size of a wah_value_type_t
size_t get_type_size(wah_value_type_t type) {
switch (type) {
case WAH_TYPE_BOOL: return sizeof(bool);
case WAH_TYPE_INT: return sizeof(int);
case WAH_TYPE_UINT: return sizeof(unsigned int);
case WAH_TYPE_LONG: return sizeof(long);
case WAH_TYPE_ULONG: return sizeof(unsigned long);
case WAH_TYPE_LLONG: return sizeof(long long);
case WAH_TYPE_ULLONG: return sizeof(unsigned long long);
case WAH_TYPE_I32: return sizeof(int32_t);
case WAH_TYPE_U32: return sizeof(uint32_t);
case WAH_TYPE_I64: return sizeof(int64_t);
case WAH_TYPE_U64: return sizeof(uint64_t);
case WAH_TYPE_FLOAT: return sizeof(float);
case WAH_TYPE_DOUBLE: return sizeof(double);
case WAH_TYPE_VOID: return 0;
case WAH_TYPE_UNKNOWN: return 0;
}
return 0;
}
// Helper to get alignment of a wah_value_type_t (simplified for common types)
size_t get_type_alignment(wah_value_type_t type) {
// For simplicity, assume alignment equals size for basic types,
// or a common alignment like 8 for larger types.
// In a real scenario, this would need to be more precise.
size_t size = get_type_size(type);
if (size == 0) return 1; // Or some default
if (size <= 4) return size;
return 8; // Common alignment for 64-bit types
}
wah_value_type_t char_to_wah_type(const char* s, int* len) {
if (s[0] == 'b') { *len = 1; return WAH_TYPE_BOOL; }
if (s[0] == 'i' && s[1] == '3' && s[2] == '2') { *len = 3; return WAH_TYPE_I32; }
if (s[0] == 'u' && s[1] == '3' && s[2] == '2') { *len = 3; return WAH_TYPE_U32; }
if (s[0] == 'i' && s[1] == '6' && s[2] == '4') { *len = 3; return WAH_TYPE_I64; }
if (s[0] == 'u' && s[1] == '6' && s[2] == '4') { *len = 3; return WAH_TYPE_U64; }
if (s[0] == 'i') { *len = 1; return WAH_TYPE_INT; }
if (s[0] == 'I') { *len = 1; return WAH_TYPE_UINT; }
if (s[0] == 'l') { *len = 1; return WAH_TYPE_LONG; }
if (s[0] == 'L') { *len = 1; return WAH_TYPE_ULONG; }
if (s[0] == 'q') { *len = 1; return WAH_TYPE_LLONG; }
if (s[0] == 'Q') { *len = 1; return WAH_TYPE_ULLONG; }
if (s[0] == 'f') { *len = 1; return WAH_TYPE_FLOAT; }
if (s[0] == 'd') { *len = 1; return WAH_TYPE_DOUBLE; }
*len = 0;
return WAH_TYPE_UNKNOWN;
}
parsed_signature_t parse_signature_string(const char* signature_str) {
parsed_signature_t sig = {0};
const char* p = signature_str;
const char* returns_start = NULL;
int count = 0;
int len;
// First pass: count params and find '>'
const char* temp_p = signature_str;
while (*temp_p) {
if (*temp_p == '>') {
returns_start = temp_p + 1;
break;
}
if (*temp_p == ' ' || *temp_p == '\t') { temp_p++; continue; } // Ignore whitespace
wah_value_type_t type = char_to_wah_type(temp_p, &len);
if (type == WAH_TYPE_UNKNOWN) {
fprintf(stderr, "Error: Unknown type in signature string: %s\n", temp_p);
return (parsed_signature_t){0};
}
sig.num_params++;
temp_p += len;
}
// Allocate param_types
if (sig.num_params > 0) {
sig.param_types = (wah_value_type_t*)malloc(sig.num_params * sizeof(wah_value_type_t));
assert(sig.param_types);
}
// Second pass: populate param_types
p = signature_str;
count = 0;
while (*p) {
if (*p == '>') break;
if (*p == ' ' || *p == '\t') { p++; continue; }
sig.param_types[count++] = char_to_wah_type(p, &len);
p += len;
}
// Parse return types
if (returns_start) {
p = returns_start;
// First pass: count returns
temp_p = returns_start;
while (*temp_p) {
if (*temp_p == ' ' || *temp_p == '\t') { temp_p++; continue; }
wah_value_type_t type = char_to_wah_type(temp_p, &len);
if (type == WAH_TYPE_UNKNOWN) {
fprintf(stderr, "Error: Unknown type in signature string: %s\n", temp_p);
free(sig.param_types);
return (parsed_signature_t){0};
}
sig.num_returns++;
temp_p += len;
}
// Allocate return_types
if (sig.num_returns > 0) {
sig.return_types = (wah_value_type_t*)malloc(sig.num_returns * sizeof(wah_value_type_t));
assert(sig.return_types);
}
// Second pass: populate return_types
p = returns_start;
count = 0;
while (*p) {
if (*p == ' ' || *p == '\t') { p++; continue; }
sig.return_types[count++] = char_to_wah_type(p, &len);
p += len;
}
} else {
// No '>' means no return types
sig.num_returns = 0;
sig.return_types = NULL;
}
return sig;
}
// --- C struct descriptor generation ---
// Function to calculate struct layout
wah_c_struct_desc_t generate_struct_desc(const wah_value_type_t* types, size_t num_types) {
wah_c_struct_desc_t desc = {0};
if (num_types == 0) {
desc.struct_size = 1; // Smallest possible struct for empty
return desc;
}
desc.fields = (wah_c_struct_field_desc_t*)malloc(num_types * sizeof(wah_c_struct_field_desc_t));
assert(desc.fields);
desc.num_fields = num_types;
size_t current_offset = 0;
size_t max_alignment = 1;
for (size_t i = 0; i < num_types; ++i) {
wah_value_type_t type = types[i];
size_t type_size = get_type_size(type);
size_t type_alignment = get_type_alignment(type);
// Apply padding for alignment
current_offset = (current_offset + type_alignment - 1) & ~(type_alignment - 1);
desc.fields[i].wasm_type = type;
desc.fields[i].offset = current_offset;
desc.fields[i].size = type_size;
current_offset += type_size;
if (type_alignment > max_alignment) {
max_alignment = type_alignment;
}
}
// Apply padding for overall struct alignment
desc.struct_size = (current_offset + max_alignment - 1) & ~(max_alignment - 1);
if (desc.struct_size == 0 && num_types > 0) { // Handle case where all types are 0-size (e.g., void)
desc.struct_size = 1; // Smallest possible struct
}
return desc;
}
// --- Test values generation ---
wah_value_t get_test_value(wah_value_type_t type, int index) {
wah_value_t val = {0};
switch (type) {
case WAH_TYPE_BOOL: val.b = (index % 2 == 0); break;
case WAH_TYPE_INT: val.i = 100 + index; break;
case WAH_TYPE_UINT: val.I = 200 + index; break;
case WAH_TYPE_LONG: val.l = 300L + index; break;
case WAH_TYPE_ULONG: val.L = 400UL + index; break;
case WAH_TYPE_LLONG: val.q = 500LL + index; break;
case WAH_TYPE_ULLONG: val.Q = 600ULL + index; break;
case WAH_TYPE_I32: val.i32 = 700 + index; break;
case WAH_TYPE_U32: val.u32 = 800 + index; break;
case WAH_TYPE_I64: val.i64 = 900LL + index; break;
case WAH_TYPE_U64: val.u64 = 1000ULL + index; break;
case WAH_TYPE_FLOAT: val.f = 10.5f + (float)index; break;
case WAH_TYPE_DOUBLE: val.d = 20.7 + (double)index; break;
case WAH_TYPE_VOID: break;
case WAH_TYPE_UNKNOWN: break;
}
return val;
}
void print_wah_value(wah_value_t val, wah_value_type_t type) {
switch (type) {
case WAH_TYPE_BOOL: printf("%s", val.b ? "true" : "false"); break;
case WAH_TYPE_INT: printf("%d", val.i); break;
case WAH_TYPE_UINT: printf("%u", val.I); break;
case WAH_TYPE_LONG: printf("%ld", val.l); break;
case WAH_TYPE_ULONG: printf("%lu", val.L); break;
case WAH_TYPE_LLONG: printf("%lld", val.q); break;
case WAH_TYPE_ULLONG: printf("%llu", val.Q); break;
case WAH_TYPE_I32: printf("%d", val.i32); break;
case WAH_TYPE_U32: printf("%u", val.u32); break;
case WAH_TYPE_I64: printf("%lld", val.i64); break;
case WAH_TYPE_U64: printf("%llu", val.u64); break;
case WAH_TYPE_FLOAT: printf("%f", val.f); break;
case WAH_TYPE_DOUBLE: printf("%lf", val.d); break;
case WAH_TYPE_VOID: printf("void"); break;
case WAH_TYPE_UNKNOWN: printf("UNKNOWN"); break;
}
}
// --- The core test function ---
int call_arb_func(const char* signature_str, experiment_host_fn_ptr_t user_fn_ptr, void* user_data) {
printf("\n--- Testing signature: \"%s\" ---\n", signature_str);
parsed_signature_t sig = parse_signature_string(signature_str);
if (sig.param_types == NULL && sig.num_params > 0) { // Error during parsing
return 1;
}
wah_c_struct_desc_t params_desc = generate_struct_desc(sig.param_types, sig.num_params);
wah_c_struct_desc_t returns_desc = generate_struct_desc(sig.return_types, sig.num_returns);
printf(" Parsed Params: %zu types, Struct Size: %zu\n", sig.num_params, params_desc.struct_size);
for (size_t i = 0; i < sig.num_params; ++i) {
printf(" [%zu] Type: %d, Offset: %zu, Size: %zu\n", i, sig.param_types[i], params_desc.fields[i].offset, params_desc.fields[i].size);
}
printf(" Parsed Returns: %zu types, Struct Size: %zu\n", sig.num_returns, returns_desc.struct_size);
for (size_t i = 0; i < sig.num_returns; ++i) {
printf(" [%zu] Type: %d, Offset: %zu, Size: %zu\n", i, sig.return_types[i], returns_desc.fields[i].offset, returns_desc.fields[i].size);
}
// --- Simulate WAH's internal wrapper logic ---
// 1. Allocate and populate parameter struct
void* params_struct_ptr = NULL;
if (params_desc.struct_size > 0) {
params_struct_ptr = calloc(1, params_desc.struct_size);
assert(params_struct_ptr);
}
printf(" Input values (WAH_value_t -> C struct):\n");
for (size_t i = 0; i < sig.num_params; ++i) {
wah_value_t test_val = get_test_value(sig.param_types[i], i);
printf(" Param %zu (Type %d): ", i, sig.param_types[i]);
print_wah_value(test_val, sig.param_types[i]);
printf(" -> ");
// Copy test_val into the correct offset in params_struct_ptr
switch (sig.param_types[i]) {
case WAH_TYPE_BOOL: *(bool*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.b; break;
case WAH_TYPE_INT: *(int*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.i; break;
case WAH_TYPE_UINT: *(unsigned int*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.I; break;
case WAH_TYPE_LONG: *(long*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.l; break;
case WAH_TYPE_ULONG: *(unsigned long*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.L; break;
case WAH_TYPE_LLONG: *(long long*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.q; break;
case WAH_TYPE_ULLONG: *(unsigned long long*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.Q; break;
case WAH_TYPE_I32: *(int32_t*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.i32; break;
case WAH_TYPE_U32: *(uint32_t*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.u32; break;
case WAH_TYPE_I64: *(int64_t*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.i64; break;
case WAH_TYPE_U64: *(uint64_t*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.u64; break;
case WAH_TYPE_FLOAT: *(float*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.f; break;
case WAH_TYPE_DOUBLE: *(double*)((char*)params_struct_ptr + params_desc.fields[i].offset) = test_val.d; break;
case WAH_TYPE_VOID: case WAH_TYPE_UNKNOWN: break;
}
printf("Stored at offset %zu\n", params_desc.fields[i].offset);
}
// 2. Allocate return struct
void* returns_struct_ptr = NULL;
if (returns_desc.struct_size > 0) {
returns_struct_ptr = calloc(1, returns_desc.struct_size);
assert(returns_struct_ptr);
}
// 3. Call user's host function
wah_exec_context_t ctx = {0}; // Dummy context
int host_fn_result = user_fn_ptr(&ctx, params_struct_ptr, returns_struct_ptr, user_data);
printf(" Host function returned: %d (0=success)\n", host_fn_result);
// 4. Verify return values (C struct -> WAH_value_t)
printf(" Output values (C struct -> WAH_value_t):\n");
for (size_t i = 0; i < sig.num_returns; ++i) {
wah_value_t actual_return_val = {0};
printf(" Return %zu (Type %d): ", i, sig.return_types[i]);
// Copy from returns_struct_ptr into actual_return_val
switch (sig.return_types[i]) {
case WAH_TYPE_BOOL: actual_return_val.b = *(bool*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_INT: actual_return_val.i = *(int*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_UINT: actual_return_val.I = *(unsigned int*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_LONG: actual_return_val.l = *(long*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_ULONG: actual_return_val.L = *(unsigned long*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_LLONG: actual_return_val.q = *(long long*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_ULLONG: actual_return_val.Q = *(unsigned long long*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_I32: actual_return_val.i32 = *(int32_t*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_U32: actual_return_val.u32 = *(uint32_t*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_I64: actual_return_val.i64 = *(int64_t*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_U64: actual_return_val.u64 = *(uint64_t*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_FLOAT: actual_return_val.f = *(float*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_DOUBLE: actual_return_val.d = *(double*)((char*)returns_struct_ptr + returns_desc.fields[i].offset); break;
case WAH_TYPE_VOID: case WAH_TYPE_UNKNOWN: break;
}
print_wah_value(actual_return_val, sig.return_types[i]);
printf("\n");
}
// --- Cleanup ---
free(sig.param_types);
free(sig.return_types);
free(params_desc.fields);
free(returns_desc.fields);
free(params_struct_ptr);
free(returns_struct_ptr);
return host_fn_result;
}
// --- Example Host Functions ---
// Example 1: i32, f -> i32
typedef struct { int32_t p0; float p1; } Ex1Params;
typedef struct { int32_t r0; } Ex1Returns;
int ex1_host_func(wah_exec_context_t* ctx, const Ex1Params* params, Ex1Returns* returns, void* user_data) {
(void)ctx; (void)user_data;
printf(" [Host] Ex1: p0=%d, p1=%f\n", params->p0, params->p1);
returns->r0 = params->p0 + (int32_t)params->p1;
return 0;
}
// Example 2: I, Q -> d
typedef struct { unsigned int p0; unsigned long long p1; } Ex2Params;
typedef struct { double r0; } Ex2Returns;
int ex2_host_func(wah_exec_context_t* ctx, const Ex2Params* params, Ex2Returns* returns, void* user_data) {
(void)ctx; (void)user_data;
printf(" [Host] Ex2: p0=%u, p1=%llu\n", params->p0, params->p1);
returns->r0 = (double)params->p0 + (double)params->p1;
return 0;
}
// Example 3: b, i, l, q -> I
typedef struct { bool p0; int p1; long p2; long long p3; } Ex3Params;
typedef struct { unsigned int r0; } Ex3Returns;
int ex3_host_func(wah_exec_context_t* ctx, const Ex3Params* params, Ex3Returns* returns, void* user_data) {
(void)ctx; (void)user_data;
printf(" [Host] Ex3: p0=%s, p1=%d, p2=%ld, p3=%lld\n", params->p0 ? "true" : "false", params->p1, params->p2, params->p3);
returns->r0 = (unsigned int)(params->p0 + params->p1 + params->p2 + params->p3);
return 0;
}
// Example 4: No params, no returns
typedef struct {} Ex4Params;
typedef struct {} Ex4Returns;
int ex4_host_func(wah_exec_context_t* ctx, const Ex4Params* params, Ex4Returns* returns, void* user_data) {
(void)ctx; (void)params; (void)returns; (void)user_data;
printf(" [Host] Ex4: No params, no returns. \n");
return 0;
}
// Example 5: All types
typedef struct {
bool b_val; int i_val; unsigned int I_val; long l_val; unsigned long L_val;
long long q_val; unsigned long long Q_val;
int32_t i32_val; uint32_t u32_val; int64_t i64_val; uint64_t u64_val;
float f_val; double d_val;
} Ex5Params;
typedef struct { double r0; } Ex5Returns;
int ex5_host_func(wah_exec_context_t* ctx, const Ex5Params* params, Ex5Returns* returns, void* user_data) {
(void)ctx; (void)user_data;
printf(" [Host] Ex5: All types received. \n");
printf(" b:%s i:%d I:%u l:%ld L:%lu q:%lld Q:%llu\n", params->b_val?"T":"F", params->i_val, params->I_val, params->l_val, params->L_val, params->q_val, params->Q_val);
printf(" i32:%d u32:%u i64:%lld u64:%llu f:%f d:%lf\n", params->i32_val, params->u32_val, params->i64_val, params->u64_val, params->f_val, params->d_val);
returns->r0 = params->d_val + params->f_val;
return 0;
}
int main() {
// Test cases
call_arb_func("i32 f > i32", (experiment_host_fn_ptr_t)ex1_host_func, NULL);
call_arb_func("I Q > d", (experiment_host_fn_ptr_t)ex2_host_func, NULL);
call_arb_func("b i l q > I", (experiment_host_fn_ptr_t)ex3_host_func, NULL);
call_arb_func(" > ", (experiment_host_fn_ptr_t)ex4_host_func, NULL); // No params, no returns
call_arb_func("b i I l L q Q i32 u32 i64 u64 f d > d", (experiment_host_fn_ptr_t)ex5_host_func, NULL);
return 0;
}
#define WAH_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // For memcpy, memset
#include "wah.h"
// Define maximum WASM input size to prevent excessive memory allocation.
// afl-fuzz typically handles this by limiting input size.
#define MAX_WASM_INPUT_SIZE (10 * 1024 * 1024) // 10 MB
int main(void) {
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err = WAH_OK;
// afl-fuzz typically provides input via standard input (stdin).
// Here, we read the WASM binary from stdin until EOF.
size_t current_size = 0;
size_t capacity = 4096; // Initial buffer capacity
uint8_t *wasm_binary = (uint8_t *)malloc(capacity);
if (!wasm_binary) {
fprintf(stderr, "Error: Failed to allocate initial buffer for WASM.\n");
return 1;
}
int c;
while ((c = getchar()) != EOF) {
if (current_size >= capacity) {
if (capacity >= MAX_WASM_INPUT_SIZE) {
fprintf(stderr, "Error: WASM input size exceeds maximum allowed (%u bytes).\n", MAX_WASM_INPUT_SIZE);
err = WAH_ERROR_TOO_LARGE;
goto cleanup_binary;
}
capacity *= 2;
if (capacity > MAX_WASM_INPUT_SIZE) { // Adjust to not exceed MAX_WASM_INPUT_SIZE
capacity = MAX_WASM_INPUT_SIZE;
}
uint8_t *new_wasm_binary = (uint8_t *)realloc(wasm_binary, capacity);
if (!new_wasm_binary) {
fprintf(stderr, "Error: Failed to reallocate buffer for WASM.\n");
err = WAH_ERROR_OUT_OF_MEMORY;
goto cleanup_binary;
}
wasm_binary = new_wasm_binary;
}
wasm_binary[current_size++] = (uint8_t)c;
}
if (current_size == 0) {
// fprintf(stderr, "Error: No WASM input provided.\n"); // afl-fuzz does not require verbose output.
err = WAH_ERROR_UNEXPECTED_EOF;
goto cleanup_binary;
}
// 1. Parse the WASM module
memset(&module, 0, sizeof(wah_module_t)); // Initialize module struct
err = wah_parse_module(wasm_binary, current_size, &module);
if (err != WAH_OK) {
goto cleanup_binary; // Return non-zero for afl-fuzz to detect a crash/bug
}
// 2. Create execution context
memset(&exec_ctx, 0, sizeof(wah_exec_context_t)); // Initialize context struct
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
goto cleanup_module;
}
// 3. Attempt to call the _start function if it exists
// This is a common entry point for WASM modules.
wah_entry_t start_func_entry;
wah_error_t find_start_err = wah_module_export_by_name(&module, "_start", &start_func_entry);
if (find_start_err == WAH_OK && start_func_entry.type == WAH_TYPE_FUNCTION) {
err = wah_call(&exec_ctx, &module, WAH_GET_ENTRY_INDEX(start_func_entry.id), NULL, 0, NULL);
if (err != WAH_OK) {
goto cleanup_exec_ctx;
}
} else if (find_start_err != WAH_ERROR_NOT_FOUND) {
// If another error occurred while finding _start, or it's not a function
err = find_start_err;
goto cleanup_exec_ctx;
}
// If _start is not found, it's not necessarily an error for fuzzing,
// as we're primarily testing parsing and context creation.
cleanup_exec_ctx:
wah_exec_context_destroy(&exec_ctx);
cleanup_module:
wah_free_module(&module);
cleanup_binary:
free(wasm_binary);
// Return 0 for success, non-zero for any error/trap
return (err == WAH_OK) ? 0 : 1;
}
#define WAH_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include "wah.h"
// A simple WebAssembly binary for:
// (module
// (func (param i32 i32) (result i32)
// (i32.add (local.get 0) (local.get 1))
// )
// )
const uint8_t simple_add_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x07, // section size
0x01, // num types
0x60, // func type
0x02, // num params
0x7f, 0x7f, // i32, i32
0x01, // num results
0x7f, // i32
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section ID
0x09, // section size
0x01, // num code bodies
0x07, // code body size (for the first function)
0x00, // num locals
0x20, 0x00, // local.get 0
0x20, 0x01, // local.get 1
0x6a, // i32.add
0x0b, // end
};
// A semantically invalid WebAssembly binary:
// Attempts to local.get an out-of-bounds index (2) for a function with 2 parameters and 0 locals.
const uint8_t invalid_local_get_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x07, // section size
0x01, // num types
0x60, // func type
0x02, // num params
0x7f, 0x7f, // i32, i32
0x01, // num results
0x7f, // i32
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section ID
0x09, // section size
0x01, // num code bodies
0x07, // code body size (for the first function)
0x00, // num locals
0x20, 0x02, // local.get 2 (INVALID: out of bounds for 2 params, 0 locals)
0x20, 0x01, // local.get 1
0x6a, // i32.add
0x0b, // end
};
// A WebAssembly binary that causes stack underflow:
// Tries to i32.add with only one value on the stack
const uint8_t stack_underflow_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x07, // section size
0x01, // num types
0x60, // func type
0x01, // num params
0x7f, // i32
0x01, // num results
0x7f, // i32
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section ID
0x08, // section size
0x01, // num code bodies
0x06, // code body size
0x00, // num locals
0x20, 0x00, // local.get 0 (puts one i32 on stack)
0x6a, // i32.add (tries to pop two i32s - UNDERFLOW!)
0x0b, // end
};
int main() {
wah_module_t module;
wah_exec_context_t ctx; // Add this line
wah_error_t err;
printf("--- Testing Valid Module (simple_add_wasm) ---\n");
printf("Parsing simple_add_wasm module...\n");
err = wah_parse_module((const uint8_t *)simple_add_wasm, sizeof(simple_add_wasm), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing valid module: %s\n", wah_strerror(err));
return 1;
}
printf("Module parsed successfully.\n");
// Create execution context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context: %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
printf("Function max stack depth: %u\n", module.code_bodies[0].max_stack_depth);
uint32_t func_idx = 0;
wah_value_t params[2];
wah_value_t result;
params[0].i32 = 10;
params[1].i32 = 20;
printf("Interpreting function %u with params %d and %d...\n", func_idx, params[0].i32, params[1].i32);
err = wah_call(&ctx, &module, func_idx, params, 2, &result);
if (err != WAH_OK) {
fprintf(stderr, "Error interpreting function: %s\n", wah_strerror(err));
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
printf("Function interpreted successfully.\n");
printf("Result: %d\n", result.i32);
params[0].i32 = 5;
params[1].i32 = 7;
printf("Interpreting function %u with params %d and %d...\n", func_idx, params[0].i32, params[1].i32);
err = wah_call(&ctx, &module, func_idx, params, 2, &result);
if (err != WAH_OK) {
fprintf(stderr, "Error interpreting function: %s\n", wah_strerror(err));
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
printf("Function interpreted successfully.\n");
printf("Result: %d\n", result.i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("Valid module freed.\n");
printf("\n--- Testing Invalid Module (invalid_local_get_wasm) ---\n");
printf("Parsing invalid_local_get_wasm module...\n");
err = wah_parse_module((const uint8_t *)invalid_local_get_wasm, sizeof(invalid_local_get_wasm), &module);
if (err == WAH_ERROR_VALIDATION_FAILED) {
printf("Successfully detected invalid module during parsing (expected WAH_ERROR_VALIDATION_FAILED).\n");
} else if (err != WAH_OK) {
fprintf(stderr, "Error parsing invalid module: %s (Expected parsing to succeed)\n", wah_strerror(err));
return 1;
} else {
fprintf(stderr, "Invalid module parsed successfully (Expected WAH_ERROR_VALIDATION_FAILED)\n");
wah_free_module(&module);
return 1;
}
printf("\n--- Testing Stack Underflow Module (stack_underflow_wasm) ---\n");
printf("Parsing stack_underflow_wasm module...\n");
err = wah_parse_module((const uint8_t *)stack_underflow_wasm, sizeof(stack_underflow_wasm), &module);
if (err == WAH_ERROR_VALIDATION_FAILED) {
printf("Successfully detected stack underflow during parsing (expected WAH_ERROR_VALIDATION_FAILED).\n");
} else if (err != WAH_OK) {
printf("Successfully detected stack underflow during parsing (%s).\n", wah_strerror(err));
} else {
fprintf(stderr, "Stack underflow module parsed successfully (Expected WAH_ERROR_VALIDATION_FAILED)\n");
wah_free_module(&module);
return 1;
}
return 0;
}
#define WAH_IMPLEMENTATION
#include "wah.h"
#include <stdio.h>
#include <assert.h>
// Test just a block with no branches: block { i32.const 42 } end
static const uint8_t simple_block_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x09, 0x01, // section 10, size 9, 1 body
0x07, 0x00, // body size 7, 0 locals
// Function body: block { i32.const 42 } end
0x02, 0x7f, // block (result i32)
0x41, 0x2a, // i32.const 42
0x0b, // end block
0x0b // end function
};
// Based on working simple_block_wasm, create a simple if
static const uint8_t simple_if_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x0e, 0x01, // section 10, size 14, 1 body
0x0c, 0x00, // body size 12, 0 locals
// Function body: if (1) { 42 } else { 99 }
0x41, 0x01, // i32.const 1
0x04, 0x7f, // if (result i32)
0x41, 0x2a, // i32.const 42
0x05, // else
0x41, 0x63, // i32.const 99
0x0b, // end if
0x0b // end function
};
// Test a simple if-else: if (param == 42) return 1; else return 0;
static const uint8_t if_else_wasm[] = {
// WASM header
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version 1
// Type section (section 1)
0x01, // section id: type
0x06, // section size: 6 bytes
0x01, // 1 type
0x60, // func type
0x01, 0x7f, // 1 param: i32
0x01, 0x7f, // 1 result: i32
// Function section (section 3)
0x03, // section id: function
0x02, // section size: 2 bytes
0x01, // 1 function
0x00, // function 0 uses type 0
// Code section (section 10)
0x0a, // section id: code
0x11, // section size: 17 bytes total
0x01, // 1 function body
// Function body
0x0f, // body size: 15 bytes
0x00, // 0 local declarations
// Code: if (local.get 0 == 42) { return 1 } else { return 0 }
0x20, 0x00, // local.get 0 (get parameter)
0x41, 0x2a, // i32.const 42
0x46, // i32.eq (compare)
0x04, 0x7f, // if (result type i32)
0x41, 0x01, // i32.const 1
0x05, // else
0x41, 0x00, // i32.const 0
0x0b, // end if
0x0b // end function
};
// Test a simple loop: for(i=0; i<param; i++) sum+=i; return sum;
static const uint8_t loop_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x06, 0x01, // section 1, size 6, 1 type
0x60, 0x01, 0x7f, // func type: 1 param (i32)
0x01, 0x7f, // 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x2d, 0x01, // section 10, size 45, 1 body
0x2b, // body size 43
0x01, // 1 local declaration entry
0x02, 0x7f, // 2 locals of type i32 (sum, i)
// Function body: sum=0, i=0; loop { if i>=param break; sum+=i; i++; br 0 }
0x41, 0x00, // i32.const 0
0x21, 0x01, // local.set 1 (sum = 0)
0x41, 0x00, // i32.const 0
0x21, 0x02, // local.set 2 (i = 0)
0x02, 0x40, // block
0x03, 0x40, // loop
0x20, 0x02, // local.get 2 (i)
0x20, 0x00, // local.get 0 (param)
0x4e, // i32.ge_s
0x0d, 0x01, // br_if 1 (break outer block)
0x20, 0x01, // local.get 1 (sum)
0x20, 0x02, // local.get 2 (i)
0x6a, // i32.add
0x21, 0x01, // local.set 1 (sum += i)
0x20, 0x02, // local.get 2 (i)
0x41, 0x01, // i32.const 1
0x6a, // i32.add
0x21, 0x02, // local.set 2 (i++)
0x0c, 0x00, // br 0 (continue loop)
0x0b, // end loop
0x0b, // end block
0x20, 0x01, // local.get 1 (return sum)
0x0b // end function
};
static const uint8_t unreachable_i32_add_underflow_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x06, 0x01, // section 10, size 6, 1 body
0x04, 0x00, // body size 4, 0 locals
// Function body: unreachable i32.add
0x00, // unreachable
0x6a, // i32.add
0x0b // end function
};
// Function: (func (result i32) (unreachable i32.const 0 i32.add))
// This should pass validation because i32.add expects i32s, and i32.const pushes an i32.
static const uint8_t unreachable_i32_i32_add_pass_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x08, 0x01, // section 10, size 8, 1 body
0x06, 0x00, // body size 6, 0 locals
// Function body: unreachable i32.const 0 i32.add
0x00, // unreachable
0x41, 0x00, // i32.const 0
0x6a, // i32.add (expects i32, i32)
0x0b // end function
};
// Function: (func (result i32) (block (result i32) (br 0)))
// This should fail validation because 'br 0' branches to a block that expects an i32 result,
// but the stack is empty when 'br 0' is executed.
static const uint8_t br_empty_stack_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x09, 0x01, // section 10, size 9, 1 body
0x07, 0x00, // body size 7, 0 locals
// Function body: block (result i32) (br 0)
0x02, 0x7f, // block (result i32)
0x0c, 0x00, // br 0
0x0b, // end block
0x0b // end function
};
// Function: (func (result i32) (block (result i32) (i32.const 42) (br 0)))
// This should pass validation because 'br 0' branches to a block that expects an i32 result,
// and an i32 is on the stack when 'br 0' is executed.
static const uint8_t br_correct_stack_pass_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x0b, 0x01, // section 10, size 11, 1 body
0x09, 0x00, // body size 9, 0 locals
// Function body: block (result i32) (i32.const 42) (br 0)
0x02, 0x7f, // block (result i32)
0x41, 0x2a, // i32.const 42
0x0c, 0x00, // br 0
0x0b, // end block
0x0b // end function
};
// Function: (func (result i32) (i64.const 0) (return))
// This should fail validation because the function expects an i32 result,
// but an i64 is on the stack when 'return' is executed.
static const uint8_t return_i64_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x07, 0x01, // section 10, size 7, 1 body
0x05, 0x00, // body size 5, 0 locals
// Function body: i64.const 0 return
0x42, 0x00, // i64.const 0
0x0f, // return
0x0b // end function
};
// Function: (func (result i32) (i32.const 42) (return))
// This should pass validation because the function expects an i32 result,
// and an i32 is on the stack when 'return' is executed.
static const uint8_t return_i32_pass_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x07, 0x01, // section 10, size 7, 1 body
0x05, 0x00, // body size 5, 0 locals
// Function body: i32.const 42 return
0x41, 0x2a, // i32.const 42
0x0f, // return
0x0b // end function
};
static void test_simple_block() {
printf("Testing simple block...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(simple_block_wasm, sizeof(simple_block_wasm), &module);
assert(err == WAH_OK);
printf(" - Simple block parsed successfully\n");
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
wah_value_t result;
err = wah_call(&ctx, &module, 0, NULL, 0, &result);
assert(err == WAH_OK);
assert(result.i32 == 42);
printf(" - Simple block executed, result: %d\n", result.i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
static void test_simple_if_const() {
printf("Testing simple if (constant)...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(simple_if_wasm, sizeof(simple_if_wasm), &module);
assert(err == WAH_OK);
printf(" - Simple if parsed successfully\n");
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
wah_value_t result;
err = wah_call(&ctx, &module, 0, NULL, 0, &result);
assert(err == WAH_OK);
assert(result.i32 == 42);
printf(" - Execution succeeded, result: %d\n", result.i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
static void test_if_else() {
printf("Testing if-else (parameter)...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(if_else_wasm, sizeof(if_else_wasm), &module);
assert(err == WAH_OK);
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
// Test if branch (param == 42)
wah_value_t params[1] = {{.i32 = 42}};
wah_value_t result;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 1);
printf(" - If branch works (42 -> 1)\n");
// Test else branch (param != 42)
params[0].i32 = 99;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 0);
printf(" - Else branch works (99 -> 0)\n");
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
static void test_loop() {
printf("Testing loop control flow...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(loop_wasm, sizeof(loop_wasm), &module);
if (err != WAH_OK) {
printf(" ERROR: Failed to parse loop module: %s\n", wah_strerror(err));
assert(false);
return;
}
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
// Test loop: sum of 0..4 = 0+1+2+3 = 6
wah_value_t params[1] = {{.i32 = 4}};
wah_value_t result;
err = wah_call(&ctx, &module, 0, params, 1, &result);
if (err != WAH_OK) {
printf(" ERROR: Loop function call failed: %s\n", wah_strerror(err));
assert(false);
} else if (result.i32 != 6) {
printf(" ERROR: Expected 6, got %d\n", result.i32);
assert(false);
} else {
printf(" - Loop works (sum 0..3 = 6)\n");
}
// Test empty loop
params[0].i32 = 0;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 0);
printf(" - Empty loop works (sum 0.. = 0)\n");
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
// block $a end block $b br $a end (should fail validation)
static const uint8_t br_to_outer_block_fail_wasm[] = {
// Module header
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section
0x01, // Section ID
0x04, // Section size
0x01, // Number of types
0x60, // Function type
0x00, // No parameters
0x00, // No results
// Function section
0x03, // Section ID
0x02, // Section size
0x01, // Number of functions
0x00, // Type index 0 (void -> void)
// Code section
0x0a, // Section ID
0x0c, // Section size
0x01, // Number of code entries
0x0a, // Code body size
0x00, // Local count
0x02, // block (void)
0x40, // block type (void)
0x0b, // end (of block $a)
0x02, // block (void)
0x40, // block type (void)
0x0c, // br 1 (branch to block $a) - This should fail as block $a is not on the control stack
0x01, // label_idx 1
0x0b, // end (of block $b)
0x0b, // end (of function)
};
// block $a end block $b br $b end (should pass validation)
static const uint8_t br_to_current_block_pass_wasm[] = {
// Module header
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section
0x01, // Section ID
0x04, // Section size
0x01, // Number of types
0x60, // Function type
0x00, // No parameters
0x00, // No results
// Function section
0x03, // Section ID
0x02, // Section size
0x01, // Number of functions
0x00, // Type index 0 (void -> void)
// Code section
0x0a, // Section ID
0x0c, // Section size
0x01, // Number of code entries
0x0a, // Code body size
0x00, // Local count
0x02, // block (void)
0x40, // block type (void)
0x0b, // end (of block $a)
0x02, // block (void)
0x40, // block type (void)
0x0c, // br 0 (branch to block $b) - This should pass
0x00, // label_idx 0
0x0b, // end (of block $b)
0x0b, // end (of function)
};
static const uint8_t br_if_pass_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x06, 0x01, // section 1, size 6, 1 type
0x60, 0x01, 0x7f, // func type: 1 param (i32)
0x01, 0x7f, // 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x0f, 0x01, // section 10, size 15, 1 body
0x0d, 0x00, // body size 13, 0 locals
// Function body: block (result i32) (i32.const 10) (local.get 0) (br_if 0) (i32.const 20)
0x02, 0x7f, // block (result i32)
0x41, 0x0a, // i32.const 10
0x20, 0x00, // local.get 0 (condition)
0x0d, 0x00, // br_if 0 (branch to block)
0x41, 0x14, // i32.const 20 (this will be on stack if br_if doesn't branch)
0x0b, // end block
0x0b // end function
};
static const uint8_t br_if_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x06, 0x01, // section 1, size 6, 1 type
0x60, 0x01, 0x7e, // func type: 1 param (i64)
0x01, 0x7f, // 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x0f, 0x01, // section 10, size 15, 1 body
0x0d, 0x00, // body size 13, 0 locals
// Function body: block (result i32) (i32.const 10) (local.get 0) (br_if 0) (i32.const 20)
0x02, 0x7f, // block (result i32)
0x41, 0x0a, // i32.const 10
0x20, 0x00, // local.get 0 (condition - i64)
0x0d, 0x00, // br_if 0 (branch to block)
0x41, 0x14, // i32.const 20
0x0b, // end block
0x0b // end function
};
static void test_validation_unreachable_br_return() {
printf("Testing validation for unreachable, br, and return...\n");
wah_module_t module;
wah_error_t err;
// --- Failure Case 1: unreachable i32.add (stack underflow) ---
err = wah_parse_module(unreachable_i32_add_underflow_fail_wasm, sizeof(unreachable_i32_add_underflow_fail_wasm), &module);
assert(err == WAH_OK);
printf(" - Unreachable i32.add (fail) - Validation passed as expected (unreachable context)\n");
wah_free_module(&module); // Free module even if parsing failed, to clean up any allocated parts
// --- Success Case 1: unreachable i32.const 0 i32.add (expects i32, got i32) ---
err = wah_parse_module(unreachable_i32_i32_add_pass_wasm, sizeof(unreachable_i32_i32_add_pass_wasm), &module);
assert(err == WAH_OK);
printf(" - Unreachable i32.const 0 i32.add (pass) - Validation passed as expected\n");
wah_free_module(&module);
// --- Failure Case 2: br 0 with empty stack for i32 result ---
err = wah_parse_module(br_empty_stack_fail_wasm, sizeof(br_empty_stack_fail_wasm), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
printf(" - br 0 with empty stack for i32 result (fail) - Validation failed as expected\n");
wah_free_module(&module);
// --- Success Case 2: br 0 with i32 on stack for i32 result ---
err = wah_parse_module(br_correct_stack_pass_wasm, sizeof(br_correct_stack_pass_wasm), &module);
assert(err == WAH_OK);
printf(" - br 0 with i32 on stack for i32 result (pass) - Validation passed as expected\n");
wah_free_module(&module);
// --- Failure Case 3: return with i64 on stack for i32 result ---
err = wah_parse_module(return_i64_fail_wasm, sizeof(return_i64_fail_wasm), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
printf(" - return with i64 on stack for i32 result (fail) - Validation failed as expected\n");
wah_free_module(&module);
// --- Success Case 3: return with i32 on stack for i32 result ---
err = wah_parse_module(return_i32_pass_wasm, sizeof(return_i32_pass_wasm), &module);
assert(err == WAH_OK);
printf(" - return with i32 on stack for i32 result (pass) - Validation passed as expected\n");
wah_free_module(&module);
// --- Failure Case 4: br to an outer block that is no longer on the control stack ---
err = wah_parse_module(br_to_outer_block_fail_wasm, sizeof(br_to_outer_block_fail_wasm), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
printf(" - br to outer block (fail) - Validation failed as expected\n");
wah_free_module(&module);
// --- Success Case 4: br to current block ---
err = wah_parse_module(br_to_current_block_pass_wasm, sizeof(br_to_current_block_pass_wasm), &module);
assert(err == WAH_OK);
printf(" - br to current block (pass) - Validation passed as expected\n");
wah_free_module(&module);
}
static void test_br_if_validation() {
printf("Testing br_if validation...\n");
wah_module_t module;
wah_error_t err;
// --- Success Case: br_if with correct i32 condition and i32 result ---
err = wah_parse_module(br_if_pass_wasm, sizeof(br_if_pass_wasm), &module);
if (err != WAH_OK) {
printf("ERROR: br_if_pass_wasm validation failed with error: %s (%d)\n", wah_strerror(err), err);
}
assert(err == WAH_OK);
printf(" - br_if with i32 condition and i32 result (pass) - Validation passed as expected\n");
wah_free_module(&module);
// --- Failure Case: br_if with i64 condition (expects i32) ---
err = wah_parse_module(br_if_fail_wasm, sizeof(br_if_fail_wasm), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
printf(" - br_if with i64 condition (fail) - Validation failed as expected\n");
wah_free_module(&module);
}
// Test br_table, which acts like a switch statement
static const uint8_t br_table_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // magic, version
// Type section
0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f,
// Function section
0x03, 0x02, 0x01, 0x00,
// Export section
0x07, 0x11, 0x01, 0x0d, 0x62, 0x72, 0x5f, 0x74, 0x61, 0x62, 0x6c,
0x65, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x00,
// Code section
0x0a, 0x23, 0x01, // section 10, size 35, 1 body
0x21, // func body size (33 bytes)
0x00, // locals
// main logic
0x02, 0x40, // block $l3 (empty result)
0x02, 0x40, // block $l2
0x02, 0x40, // block $l1
0x02, 0x40, // block $l0
0x20, 0x00, // local.get 0 (index)
0x0e, // br_table
0x03, // 3 targets in vec
0x00, // target 0 -> $l0
0x01, // target 1 -> $l1
0x02, // target 2 -> $l2
0x03, // default target -> $l3
0x0b, // end $l0
0x41, 0x0a, // i32.const 10
0x0f, // return
0x0b, // end $l1
0x41, 0x14, // i32.const 20
0x0f, // return
0x0b, // end $l2
0x41, 0x1e, // i32.const 30
0x0f, // return
0x0b, // end $l3
0x41, 0x28, // i32.const 40
0x0b // end func
};
static void test_br_table() {
printf("Testing br_table...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(br_table_wasm, sizeof(br_table_wasm), &module);
if (err != WAH_OK) {
printf(" ERROR: Failed to parse br_table module: %s\n", wah_strerror(err));
assert(false);
}
assert(err == WAH_OK);
printf(" - br_table module parsed successfully\n");
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
wah_value_t params[1];
wah_value_t result;
// Test case 0
params[0].i32 = 0;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 10);
printf(" - Case 0 -> 10 PASSED\n");
// Test case 1
params[0].i32 = 1;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 20);
printf(" - Case 1 -> 20 PASSED\n");
// Test case 2
params[0].i32 = 2;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 30);
printf(" - Case 2 -> 30 PASSED\n");
// Test case 3 (default)
params[0].i32 = 3;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 40);
printf(" - Case 3 (default) -> 40 PASSED\n");
// Test case 4 (default, out of bounds)
params[0].i32 = 4;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 40);
printf(" - Case 4 (default) -> 40 PASSED\n");
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
// Test for br_table type consistency
static void test_br_table_type_consistency() {
printf("Testing br_table type consistency...\n");
// Valid case: all targets have the same result type (i32)
// (module
// (func (result i32)
// (block (result i32) ;; target 0
// (block (result i32) ;; target 1
// (i32.const 0)
// (br_table 0 1 0) ;; targets 0, 1, default 0
// )
// (i32.const 1)
// )
// (i32.const 2)
// )
// )
uint8_t wasm_valid[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section id
0x05, // section size
0x01, // num types
0x60, // func type
0x00, // num params
0x01, // num results
0x7f, // i32
// Function section
0x03, // section id
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section id
0x17, // section size (23 bytes)
0x01, // num code bodies
0x15, // code body size (21 bytes)
0x00, // num locals
// Function body
0x02, // block
0x7f, // block type (i32)
0x02, // block
0x7f, // block type (i32)
0x41, 0x00, // i32.const 0 (index)
0x41, 0x00, // i32.const 0 (value for target block)
0x0e, // br_table
0x02, // num_targets (2)
0x00, // target 0 (label_idx)
0x01, // target 1 (label_idx)
0x00, // default target 0 (label_idx)
0x0b, // end
0x41, 0x01, // i32.const 1
0x0b, // end
0x41, 0x02, // i32.const 2
0x0b, // end
};
wah_module_t module_valid;
wah_error_t err_valid = wah_parse_module(wasm_valid, sizeof(wasm_valid), &module_valid);
assert(err_valid == WAH_OK);
wah_free_module(&module_valid);
// Invalid case: targets have different result types
// (module
// (func (result i32)
// (block (result i32) ;; target 0 (i32)
// (block (result i64) ;; target 1 (i64) -- THIS IS THE DIFFERENCE
// (i32.const 0)
// (br_table 0 1 0) ;; targets 0, 1, default 0
// )
// (i32.const 1)
// )
// (i32.const 2)
// )
// )
uint8_t wasm_invalid[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section id
0x05, // section size
0x01, // num types
0x60, // func type
0x00, // num params
0x01, // num results
0x7f, // i32
// Function section
0x03, // section id
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section id
0x15, // section size (21 bytes)
0x01, // num code bodies
0x13, // code body size (19 bytes)
0x00, // num locals
// Function body
0x02, // block
0x7f, // block type (i32)
0x02, // block
0x7e, // block type (i64) -- THIS IS THE DIFFERENCE
0x41, 0x00, // i32.const 0
0x0e, // br_table
0x02, // num_targets (2)
0x00, // target 0 (label_idx)
0x01, // target 1 (label_idx)
0x00, // default target 0 (label_idx)
0x0b, // end
0x41, 0x01, // i32.const 1
0x0b, // end
0x41, 0x02, // i32.const 2
0x0b, // end
};
wah_module_t module_invalid;
wah_error_t err_invalid = wah_parse_module(wasm_invalid, sizeof(wasm_invalid), &module_invalid);
assert(err_invalid == WAH_ERROR_VALIDATION_FAILED);
wah_free_module(&module_invalid); // Should still free even if parsing fails
}
// Function: (func (param i32) (result i32) (block (param i32) (result i32) local.get 0 i32.const 1 i32.add end))
// Type 0: (func (param i32) (result i32)) - for the block and the main function
static const uint8_t block_type_with_params_pass_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section (section 1)
0x01, // section id: type
0x06, // section size: 6 bytes
0x01, // 1 type
// Type 0: (func (param i32) (result i32))
0x60, // func type
0x01, 0x7f, // 1 param: i32
0x01, 0x7f, // 1 result: i32
// Function section (section 3)
0x03, // section id: function
0x02, // section size: 2 bytes
0x01, // 1 function
0x00, // function 0 uses type 0
// Code section (section 10)
0x0a, // section id: code
0x0a, // section size: 10 bytes total
0x01, // 1 function body
// Function body 0
0x08, // body size: 8 bytes
0x00, // 0 local declarations
// Code: block (type 0) (local.get 0 i32.const 1 i32.add) end
// The i32 parameter for the block will be taken from the stack before the block.
// The function's parameter will be pushed onto the stack before the block.
0x20, 0x00, // local.get 0 (get function's parameter)
0x41, 0x01, // i32.const 1
0x6a, // i32.add
0x0b, // end block
0x0b // end function
};
static void test_block_type_with_params_pass() {
printf("Testing block type with parameters (should pass validation and execute correctly)...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(block_type_with_params_pass_wasm, sizeof(block_type_with_params_pass_wasm), &module);
assert(err == WAH_OK);
printf(" - Block type with parameters parsed successfully\n");
wah_exec_context_t ctx;
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK);
wah_value_t params[1] = {{.i32 = 10}};
wah_value_t result;
err = wah_call(&ctx, &module, 0, params, 1, &result);
assert(err == WAH_OK);
assert(result.i32 == 11);
printf(" - Block type with parameters executed, result: %d\n", result.i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
}
// Function: (func (block (result i32) unreachable drop drop br 0))
// Since unreachable's type can be assumed to be `() -> (i32, i32)`, this should pass the validation.
static const uint8_t unreachable_drop_br_underflow_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Code section
0x0a, 0x0b, 0x01, // section 10, size 11, 1 body
0x09, 0x00, // body size 9, 0 locals
// Function body: block (result i32) i32.const 0 unreachable drop drop br 0
0x02, 0x7f, // block (result i32)
0x00, // unreachable
0x1a, // drop
0x0d, 0x00, // br 0
0x0b, // end block
0x0b // end function
};
static void test_unreachable_drop_br_underflow_fail() {
printf("Testing unreachable drop and br underflow...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(unreachable_drop_br_underflow_fail_wasm, sizeof(unreachable_drop_br_underflow_fail_wasm), &module);
assert(err == WAH_OK);
printf(" - Unreachable drop and br underflow (pass) - Validation passed as expected\n");
wah_free_module(&module);
}
// Function: (func (result i32) (unreachable) (if (result i32) (i32.const 1) (else) (i32.const 0) end))
// This should fail validation because the if expects an i32 on the stack, but unreachable leaves the stack in a bottom state.
// The current bug is that it fails validation, but it should pass.
static const uint8_t unreachable_if_fail_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // WASM magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x05, 0x01, // section 1, size 5, 1 type
0x60, 0x00, 0x01, 0x7f, // func type: 0 params, 1 result (i32)
// Function section
0x03, 0x02, 0x01, 0x00, // section 3, size 2, 1 func, type 0
// Export section
0x07, 0x08, 0x01, // section 7, size 8, 1 export
0x04, 0x74, 0x65, 0x73, 0x74, // "test"
0x00, 0x00, // func 0
// Code section
0x0a, 0x0d, 0x01, // section 10, size 13, 1 body
0x0b, 0x00, // body size 11, 0 locals
// Function body: unreachable if (result i32) i32.const 1 else i32.const 0 end
0x00, // unreachable
0x04, 0x7f, // if (result i32)
0x41, 0x01, // i32.const 1
0x05, // else
0x41, 0x00, // i32.const 0
0x0b, // end if
0x0b // end function
};
static void test_unreachable_if_validation() {
printf("Testing unreachable followed by if validation...\n");
wah_module_t module;
wah_error_t err;
err = wah_parse_module(unreachable_if_fail_wasm, sizeof(unreachable_if_fail_wasm), &module);
assert(err == WAH_OK);
printf(" - Unreachable followed by if (pass) - Validation passed as expected\n");
wah_free_module(&module);
}
int main() {
printf("=== Control Flow Tests ===\n");
test_simple_block();
test_simple_if_const();
test_if_else();
test_loop();
test_validation_unreachable_br_return();
test_br_if_validation();
test_br_table();
test_br_table_type_consistency();
test_block_type_with_params_pass();
test_unreachable_drop_br_underflow_fail();
test_unreachable_if_validation();
printf("=== Control Flow Tests Complete ===\n");
return 0;
}
#define WAH_IMPLEMENTATION
#include "wah.h"
#include <stdio.h>
#include <string.h>
#include <math.h> // For isnan, though we'll use bit patterns for comparison
#include <stdlib.h> // For malloc, free
#include <inttypes.h>
// Helper to convert float to uint32_t bit pattern
static uint32_t float_to_bits(float f) {
union { uint32_t i; float f; } u = { .f = f };
return u.i;
}
// Helper to convert double to uint64_t bit pattern
static uint64_t double_to_bits(double d) {
union { uint64_t i; double d; } u = { .d = d };
return u.i;
}
// WebAssembly canonical NaN bit patterns (from wah.h)
#define TEST_WASM_F32_CANONICAL_NAN_BITS 0x7fc00000U
#define TEST_WASM_F64_CANONICAL_NAN_BITS 0x7ff8000000000000ULL
static const uint64_t NON_CANONICAL_F64_NAN_BITS_1 = 0x7ff0000000000001ULL;
static const uint64_t NON_CANONICAL_F64_NAN_BITS_2 = 0x7ff0000000000002ULL;
// A non-canonical NaN for testing (e.g., quiet NaN with some payload bits)
// Sign: 0, Exponent: all 1s, Mantissa: 010...0 (bit 22 is 0, bit 21 is 1)
// Canonical would be 0x7fc00000 (bit 22 is 1, rest 0)
static const uint32_t NON_CANONICAL_F32_NAN_BITS = 0x7fa00000U; // Example: qNaN with payload
static const union { uint32_t i; float f; } non_canonical_f32_nan_union = { .i = NON_CANONICAL_F32_NAN_BITS };
#define NON_CANONICAL_F32_NAN non_canonical_f32_nan_union.f
// Test WASM binary for f32.store and f32.const
// (module
// (memory (export "mem") 1)
// (func (export "test_store") (param f32) (result f32)
// local.get 0
// i32.const 0
// f32.store
// i32.const 0
// f32.load
// )
// (func (export "test_const") (result f32)
// f32.const <NON_CANONICAL_F32_NAN>
// )
// )
// This is a hand-assembled WASM binary.
// Magic: 0x6d736100 (WASM_MAGIC_NUMBER)
// Version: 0x01000000 (WASM_VERSION)
static const uint8_t test_wasm_binary[] = {
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type Section (1)
0x01, // Section ID
0x30, // Section Size (48 bytes)
0x09, // Number of types
// Type 0: (func (param f32) (result f32))
0x60, 0x01, 0x7d, 0x01, 0x7d,
// Type 1: (func (result f32))
0x60, 0x00, 0x01, 0x7d,
// Type 2: (func (param i32) (result i32))
0x60, 0x01, 0x7f, 0x01, 0x7f,
// Type 3: (func (param i64 i64) (result i64))
0x60, 0x02, 0x7e, 0x7e, 0x01, 0x7e,
// Type 4: (func (param f32 f32) (result f32))
0x60, 0x02, 0x7d, 0x7d, 0x01, 0x7d,
// Type 5: (func (param f32) (result f32)) - same as Type 0
0x60, 0x01, 0x7d, 0x01, 0x7d,
// Type 6: (func (param f64 f64) (result f64))
0x60, 0x02, 0x7c, 0x7c, 0x01, 0x7c,
// Type 7: (func (param f32) (result f64))
0x60, 0x01, 0x7d, 0x01, 0x7c,
// Type 8: (func (param f64) (result f32))
0x60, 0x01, 0x7c, 0x01, 0x7d,
// Function Section (3)
0x03, // Section ID
0x0a, // Section Size (10 bytes) - UPDATED
0x09, // Number of functions
0x00, // Function 0 (test_store) uses type 0
0x01, // Function 1 (test_const) uses type 1
0x02, // Function 2 (test_f32_reinterpret_nan) uses type 2
0x03, // Function 3 (test_f64_add_nan) uses type 3
0x04, // Function 4 (test_f32_add_nan) uses type 4
0x05, // Function 5 (test_f32_sqrt_nan) uses type 5
0x06, // Function 6 (test_f64_min_nan) uses type 6
0x07, // Function 7 (test_f64_promote_f32_nan_canonicalization) uses type 7
0x08, // Function 8 (test_f32_demote_f64_nan_canonicalization) uses type 8
// Memory Section (5)
0x05, // Section ID
0x03, // Section Size (3 bytes)
0x01, // Number of memories
0x00, // Flags (min only)
0x01, // Min pages (1)
// Export Section (7)
0x07, // Section ID
0xe0, 0x01, // Section Size (224 bytes)
0x0a, // Number of exports
// Export 0: "mem" (memory 0)
0x03, 'm', 'e', 'm', 0x02, 0x00,
// Export 1: "test_store" (func 0)
0x0a, 't', 'e', 's', 't', '_', 's', 't', 'o', 'r', 'e', 0x00, 0x00,
// Export 2: "test_const" (func 1)
0x0a, 't', 'e', 's', 't', '_', 'c', 'o', 'n', 's', 't', 0x00, 0x01,
// Export 3: "test_f32_reinterpret_nan" (func 2)
0x18, 't', 'e', 's', 't', '_', 'f', '3', '2', '_', 'r', 'e', 'i', 'n', 't', 'e', 'r', 'p', 'r', 'e', 't', '_', 'n', 'a', 'n', 0x00, 0x02,
// Export 4: "test_f64_add_nan" (func 3)
0x10, 't', 'e', 's', 't', '_', 'f', '6', '4', '_', 'a', 'd', 'd', '_', 'n', 'a', 'n', 0x00, 0x03,
// Export 5: "test_f32_add_nan" (func 4)
0x10, 't', 'e', 's', 't', '_', 'f', '3', '2', '_', 'a', 'd', 'd', '_', 'n', 'a', 'n', 0x00, 0x04,
// Export 6: "test_f32_sqrt_nan" (func 5)
0x11, 't', 'e', 's', 't', '_', 'f', '3', '2', '_', 's', 'q', 'r', 't', '_', 'n', 'a', 'n', 0x00, 0x05,
// Export 7: "test_f64_min_nan" (func 6)
0x10, 't', 'e', 's', 't', '_', 'f', '6', '4', '_', 'm', 'i', 'n', '_', 'n', 'a', 'n', 0x00, 0x06,
// Export 8: "test_f64_promote_f32_nan_canonicalization" (func 7)
0x29, 't', 'e', 's', 't', '_', 'f', '6', '4', '_', 'p', 'r', 'o', 'm', 'o', 't', 'e', '_', 'f', '3', '2', '_', 'n', 'a', 'n', '_', 'c', 'a', 'n', 'o', 'n', 'i', 'c', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', 0x00, 0x07,
// Export 9: "test_f32_demote_f64_nan_canonicalization" (func 8)
0x28, 't', 'e', 's', 't', '_', 'f', '3', '2', '_', 'd', 'e', 'm', 'o', 't', 'e', '_', 'f', '6', '4', '_', 'n', 'a', 'n', '_', 'c', 'a', 'n', 'o', 'n', 'i', 'c', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n', 0x00, 0x08,
// Code Section (10)
0x0A, // Section ID
0x4c, // Section Size (76 bytes)
0x09, // Number of code bodies
// Code Body 0: test_store
0x0E, 0x00, 0x41, 0x00, 0x20, 0x00, 0x38, 0x00, 0x00, 0x41, 0x00, 0x2a, 0x00, 0x00, 0x0b,
// Code Body 1: test_const
0x07, 0x00, 0x43,
(NON_CANONICAL_F32_NAN_BITS >> 0) & 0xFF,
(NON_CANONICAL_F32_NAN_BITS >> 8) & 0xFF,
(NON_CANONICAL_F32_NAN_BITS >> 16) & 0xFF,
(NON_CANONICAL_F32_NAN_BITS >> 24) & 0xFF,
0x0b,
// Code Body 2: test_f32_reinterpret_nan
0x06, // Body Size (6 bytes)
0x00, // Local count (0)
0x20, 0x00, // local.get 0
0xbe, // f32.reinterpret_i32
0xbc, // i32.reinterpret_f32
0x0b, // end
// Code Body 3: test_f64_add_nan
0x0a, // Body Size (10 bytes)
0x00, // Local count (0)
0x20, 0x00, // local.get 0
0xbf, // f64.reinterpret_i64
0x20, 0x01, // local.get 1
0xbf, // f64.reinterpret_i64
0xa0, // f64.add
0xbd, // i64.reinterpret_f64
0x0b, // end
// Code Body 4: test_f32_add_nan
0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x92, 0x0b,
// Code Body 5: test_f32_sqrt_nan
0x05, 0x00, 0x20, 0x00, 0x91, 0x0b,
// Code Body 6: test_f64_min_nan
0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0xa4, 0x0b,
// Code Body 7: test_f64_promote_f32_nan_canonicalization
0x05, 0x00, 0x20, 0x00, 0xbb, 0x0b,
// Code Body 8: test_f32_demote_f64_nan_canonicalization
0x05, 0x00, 0x20, 0x00, 0xb6, 0x0b,
};
static wah_error_t test_f32_store_canonicalization() {
printf("Testing f32.store canonicalization...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing module: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t param;
param.f32 = NON_CANONICAL_F32_NAN;
err = wah_call(&exec_ctx, &module, 0, &param, 1, &result); // Call "test_store" (func_idx 0)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_store: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint32_t result_bits = float_to_bits(result.f32);
if (result_bits == NON_CANONICAL_F32_NAN_BITS) {
printf("f32.store test PASSED: Stored non-canonical NaN was preserved.\n");
} else {
printf("f32.store test FAILED: Stored non-canonical NaN was NOT preserved.\n");
printf(" Expected non-canonical bits: 0x%08X\n", NON_CANONICAL_F32_NAN_BITS);
printf(" Actual result bits: 0x%08X\n", result_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f32_const_canonicalization() {
printf("\nTesting f32.const canonicalization...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for test_const: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for test_const: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
err = wah_call(&exec_ctx, &module, 1, NULL, 0, &result); // Call "test_const" (func_idx 1)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_const: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint32_t result_bits = float_to_bits(result.f32);
if (result_bits == NON_CANONICAL_F32_NAN_BITS) {
printf("f32.const test PASSED: Constant non-canonical NaN was preserved.\n");
} else {
printf("f32.const test FAILED: Constant non-canonical NaN was NOT preserved.\n");
printf(" Expected non-canonical bits: 0x%08X\n", NON_CANONICAL_F32_NAN_BITS);
printf(" Actual result bits: 0x%08X\n", result_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f32_reinterpret_nan_canonicalization() {
printf("\nTesting f32.reinterpret_i32 and i32.reinterpret_f32 canonicalization...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for f32_reinterpret_nan: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for f32_reinterpret_nan: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t param;
param.i32 = NON_CANONICAL_F32_NAN_BITS; // Pass non-canonical NaN bit pattern as i32
err = wah_call(&exec_ctx, &module, 2, &param, 1, &result); // Call "test_f32_reinterpret_nan" (func_idx 2)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f32_reinterpret_nan: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint32_t result_i32_bits = (uint32_t)result.i32;
if (result_i32_bits == NON_CANONICAL_F32_NAN_BITS) {
printf("f32.reinterpret_i32/i32.reinterpret_f32 test PASSED: Reinterpreted non-canonical NaN was preserved with the absence of any interleaving operations.\n");
} else {
printf("f32.reinterpret_i32/i32.reinterpret_f32 test FAILED: Reinterpreted non-canonical NaN was NOT preserved.\n");
printf(" Expected non-canonical bits: 0x%08X\n", NON_CANONICAL_F32_NAN_BITS);
printf(" Actual result bits: 0x%08X\n", result_i32_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f64_add_nan_canonicalization() {
printf("\nTesting f64.add canonicalization with non-canonical NaNs...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for f64_add_nan: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for f64_add_nan: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t params_f64[2];
params_f64[0].i64 = (int64_t)NON_CANONICAL_F64_NAN_BITS_1;
params_f64[1].i64 = (int64_t)NON_CANONICAL_F64_NAN_BITS_2;
err = wah_call(&exec_ctx, &module, 3, params_f64, 2, &result); // Call "test_f64_add_nan" (func_idx 3)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f64_add_nan: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint64_t result_i64_bits = (uint64_t)result.i64;
if (result_i64_bits == TEST_WASM_F64_CANONICAL_NAN_BITS) {
printf("f64.add test PASSED: Adding non-canonical NaNs resulted in canonical NaN.\n");
} else {
printf("f64.add test FAILED: Adding non-canonical NaNs did NOT result in canonical NaN.\n");
printf(" Expected canonical bits: 0x%llX\n", TEST_WASM_F64_CANONICAL_NAN_BITS);
printf(" Actual result bits: 0x%" PRIx64 "\n", result_i64_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f32_add_nan_canonicalization() {
printf("\nTesting f32.add canonicalization with non-canonical NaNs...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for f32_add_nan: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for f32_add_nan: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t params_f32[2];
params_f32[0].f32 = NON_CANONICAL_F32_NAN;
params_f32[1].f32 = NON_CANONICAL_F32_NAN;
err = wah_call(&exec_ctx, &module, 4, params_f32, 2, &result); // Call "test_f32_add_nan" (func_idx 4)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f32_add_nan: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint32_t result_bits = float_to_bits(result.f32);
if (result_bits == TEST_WASM_F32_CANONICAL_NAN_BITS) {
printf("f32.add test PASSED: Adding non-canonical NaNs resulted in canonical NaN.\n");
} else {
printf("f32.add test FAILED: Adding non-canonical NaNs did NOT result in canonical NaN.\n");
printf(" Expected canonical bits: 0x%08X\n", TEST_WASM_F32_CANONICAL_NAN_BITS);
printf(" Actual result bits: 0x%08X\n", result_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f32_sqrt_nan_canonicalization() {
printf("\nTesting f32.sqrt canonicalization with non-canonical NaN...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
wah_value_t result;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for f32_sqrt_nan: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for f32_sqrt_nan: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t param;
param.f32 = NON_CANONICAL_F32_NAN;
err = wah_call(&exec_ctx, &module, 5, &param, 1, &result); // Call "test_f32_sqrt_nan" (func_idx 5)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f32_sqrt_nan: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
uint32_t result_bits = float_to_bits(result.f32);
if (result_bits == TEST_WASM_F32_CANONICAL_NAN_BITS) {
printf("f32.sqrt test PASSED: Sqrt of non-canonical NaN resulted in canonical NaN.\n");
} else {
printf("f32.sqrt test FAILED: Sqrt of non-canonical NaN did NOT result in canonical NaN.\n");
printf(" Expected canonical bits: 0x%08X\n", TEST_WASM_F32_CANONICAL_NAN_BITS);
printf(" Actual result bits: 0x%08X\n", result_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
static wah_error_t test_f64_min_nan_canonicalization() {
printf("\nTesting f64.min canonicalization with non-canonical NaNs...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error re-parsing module for f64_min_nan: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for f64_min_nan: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t params_f64_min[2];
params_f64_min[0].i64 = (int64_t)NON_CANONICAL_F64_NAN_BITS_1;
params_f64_min[1].i64 = (int64_t)NON_CANONICAL_F64_NAN_BITS_2;
wah_value_t result;
err = wah_call(&exec_ctx, &module, 6, params_f64_min, 2, &result); // Call "test_f64_min_nan" (func_idx 6)
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f64_min_nan: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
union { uint64_t i; double d; } u = { .d = result.f64 };
uint64_t result_i64_bits = u.i;
if (result_i64_bits == TEST_WASM_F64_CANONICAL_NAN_BITS) {
printf("f64.min test PASSED: Min of non-canonical NaNs resulted in canonical NaN.\n");
} else {
printf("f64.min test FAILED: Min of non-canonical NaNs did NOT result in canonical NaN.\n");
printf(" Expected canonical bits: 0x%llX\n", TEST_WASM_F64_CANONICAL_NAN_BITS);
printf(" Actual result bits: 0x%" PRIx64 "\n", result_i64_bits);
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
// Test function for f64.promote_f32 with NaN canonicalization
static wah_error_t test_f64_promote_f32_nan_canonicalization() {
printf("Running test_f64_promote_f32_nan_canonicalization...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing module: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t param;
param.i32 = (int32_t)0x7f800001; // Signaling NaN with payload 1
wah_value_t result;
err = wah_call(&exec_ctx, &module, 7, &param, 1, &result); // Call func_idx 7
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f64_promote_f32_nan_canonicalization: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
// Check if the result is the canonical f64 NaN
if (double_to_bits(result.f64) == TEST_WASM_F64_CANONICAL_NAN_BITS) {
printf(" PASS: f64.promote_f32 produced canonical NaN.\n");
} else {
printf(" FAIL: f64.promote_f32 did not produce canonical NaN. Expected 0x%llx, Got 0x%" PRIx64 "\n",
TEST_WASM_F64_CANONICAL_NAN_BITS, double_to_bits(result.f64));
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
// Test function for f32.demote_f64 with NaN canonicalization
static wah_error_t test_f32_demote_f64_nan_canonicalization() {
printf("Running test_f32_demote_f64_nan_canonicalization...\n");
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
err = wah_parse_module(test_wasm_binary, sizeof(test_wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing module: %s\n", wah_strerror(err));
return err;
}
err = wah_exec_context_create(&exec_ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context: %s\n", wah_strerror(err));
wah_free_module(&module);
return err;
}
wah_value_t param;
param.i64 = (int64_t)0x7ff0000000000001ULL; // Signaling NaN with payload 1
wah_value_t result;
err = wah_call(&exec_ctx, &module, 8, &param, 1, &result); // Call func_idx 8
if (err != WAH_OK) {
fprintf(stderr, "Error calling test_f32_demote_f64_nan_canonicalization: %s\n", wah_strerror(err));
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
// Check if the result is the canonical f32 NaN
if (float_to_bits(result.f32) == TEST_WASM_F32_CANONICAL_NAN_BITS) {
printf(" PASS: f32.demote_f64 produced canonical NaN.\n");
} else {
printf(" FAIL: f32.demote_f64 did not produce canonical NaN. Expected 0x%x, Got 0x%x\n",
TEST_WASM_F32_CANONICAL_NAN_BITS, float_to_bits(result.f32));
err = WAH_ERROR_VALIDATION_FAILED;
}
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
return err;
}
int main() {
wah_error_t err = WAH_OK;
err = test_f32_store_canonicalization();
if (err != WAH_OK) return 1;
err = test_f32_const_canonicalization();
if (err != WAH_OK) return 1;
err = test_f32_reinterpret_nan_canonicalization();
if (err != WAH_OK) return 1;
err = test_f64_add_nan_canonicalization();
if (err != WAH_OK) return 1;
err = test_f32_add_nan_canonicalization();
if (err != WAH_OK) return 1;
err = test_f32_sqrt_nan_canonicalization();
if (err != WAH_OK) return 1;
err = test_f64_min_nan_canonicalization();
if (err != WAH_OK) return 1;
// NEW TESTS FOR NAN CANONICALIZATION
err = test_f64_promote_f32_nan_canonicalization();
if (err != WAH_OK) {
fprintf(stderr, "test_f64_promote_f32_nan_canonicalization FAILED: %s\n", wah_strerror(err));
return 1;
}
err = test_f32_demote_f64_nan_canonicalization();
if (err != WAH_OK) {
fprintf(stderr, "test_f32_demote_f64_nan_canonicalization FAILED: %s\n", wah_strerror(err));
return 1;
}
printf("\nAll NaN canonicalization tests passed!\n");
return 0;
}
#define WAH_IMPLEMENTATION
#include "wah.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define TEST_ASSERT(condition, message) do { \
if (!(condition)) { \
fprintf(stderr, "Assertion failed: %s\n", message); \
return 1; \
} \
} while(0)
#define TEST_ASSERT_EQ(expected, actual, message) do { \
if ((expected) != (actual)) { \
fprintf(stderr, "Assertion failed: %s (Expected: %lld, Actual: %lld)\n", message, (long long)(expected), (long long)(actual)); \
return 1; \
} \
} while(0)
#define TEST_ASSERT_STREQ(expected, actual, message) do { \
if (strcmp(expected, actual) != 0) { \
fprintf(stderr, "Assertion failed: %s (Expected: \"%s\", Actual: \"%s\")\n", message, expected, actual); \
return 1; \
} \
} while(0)
#define TEST_ASSERT_WAH_OK(err, message) do { \
if ((err) != WAH_OK) { \
fprintf(stderr, "WAH Error: %s (Code: %d, Message: %s)\n", message, (int)(err), wah_strerror(err)); \
return 1; \
} \
} while(0)
#define TEST_ASSERT_WAH_ERROR(expected_err, actual_err, message) do { \
if ((expected_err) != (actual_err)) { \
fprintf(stderr, "WAH Error Mismatch: %s (Expected: %d (%s), Actual: %d (%s))\n", message, (int)(expected_err), wah_strerror(expected_err), (int)(actual_err), wah_strerror(actual_err)); \
return 1; \
} \
} while(0)
// Helper to build a minimal WASM binary for testing
// Magic (4 bytes) + Version (4 bytes)
#define WASM_HEADER 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00
// --- Test Cases ---
int test_basic_exports() {
printf("Running test_basic_exports...\n");
// (func $add (param i32 i32) (result i32) (i32.add))
// (global $g (mut i32) (i32.const 0))
// (memory (export "mem") 1)
// (table (export "tbl") 1 funcref)
// (export "add" (func $add))
// (export "g" (global $g))
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, // section id, size
0x01, // count
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, // section id, size
0x01, // count
0x00, // type index 0
// Table Section (1 table, min 1 funcref)
0x04, 0x04, // section id, size
0x01, // count
0x70, // elem_type (funcref)
0x00, // flags (no max)
0x01, // min elements
// Memory Section (1 memory, min 1 page)
0x05, 0x03, // section id, size
0x01, // count
0x00, // flags (no max)
0x01, // min pages
// Global Section (1 global: i32 mutable, init 0)
0x06, 0x06, // section id, size
0x01, // count
0x7F, 0x01, 0x41, 0x00, 0x0B,
// Export Section (4 exports: "add" func, "g" global, "mem" memory, "tbl" table)
0x07, 0x17, // section id, size
0x04, // count
// Export 0: "add" func 0
0x03, 'a', 'd', 'd', 0x00, 0x00,
// Export 1: "g" global 0
0x01, 'g', 0x03, 0x00,
// Export 2: "mem" memory 0
0x03, 'm', 'e', 'm', 0x02, 0x00,
// Export 3: "tbl" table 0
0x03, 't', 'b', 'l', 0x01, 0x00,
// Code Section (1 code body)
0x0A, 0x09, // section id, size
0x01, // count
0x07, // code body size (0x00 for locals count + 7 bytes for instructions)
0x00, // locals count
0x20, 0x00, // local.get 0
0x20, 0x01, // local.get 1
0x6A, // i32.add
0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_OK(err, "Failed to parse basic exports module");
TEST_ASSERT_EQ(wah_module_num_exports(&module), 4, "Incorrect export count");
wah_entry_t entry;
// Test export by index
err = wah_module_export(&module, 0, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export 0");
TEST_ASSERT(WAH_TYPE_IS_FUNCTION(entry.type), "WAH_TYPE_IS_FUNCTION failed for Export 0");
TEST_ASSERT_EQ(WAH_GET_ENTRY_KIND(entry.id), WAH_ENTRY_KIND_FUNCTION, "Export 0 ID kind mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_INDEX(entry.id), 0, "Export 0 ID index mismatch");
TEST_ASSERT_STREQ(entry.name, "add", "Export 0 name mismatch");
TEST_ASSERT_EQ(entry.name_len, 3, "Export 0 name_len mismatch");
err = wah_module_export(&module, 1, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export 1");
TEST_ASSERT_EQ(entry.type, WAH_TYPE_I32, "Export 1 type mismatch");
TEST_ASSERT(WAH_TYPE_IS_GLOBAL(entry.type), "WAH_TYPE_IS_GLOBAL failed for Export 1");
TEST_ASSERT_EQ(WAH_GET_ENTRY_KIND(entry.id), WAH_ENTRY_KIND_GLOBAL, "Export 1 ID kind mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_INDEX(entry.id), 0, "Export 1 ID index mismatch");
TEST_ASSERT_STREQ(entry.name, "g", "Export 1 name mismatch");
TEST_ASSERT_EQ(entry.name_len, 1, "Export 1 name_len mismatch");
err = wah_module_export(&module, 2, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export 2");
TEST_ASSERT(WAH_TYPE_IS_MEMORY(entry.type), "WAH_TYPE_IS_MEMORY failed for Export 2");
TEST_ASSERT_EQ(WAH_GET_ENTRY_KIND(entry.id), WAH_ENTRY_KIND_MEMORY, "Export 2 ID kind mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_INDEX(entry.id), 0, "Export 2 ID index mismatch");
TEST_ASSERT_STREQ(entry.name, "mem", "Export 2 name mismatch");
TEST_ASSERT_EQ(entry.name_len, 3, "Export 2 name_len mismatch");
err = wah_module_export(&module, 3, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export 3");
TEST_ASSERT(WAH_TYPE_IS_TABLE(entry.type), "WAH_TYPE_IS_TABLE failed for Export 3");
TEST_ASSERT_EQ(WAH_GET_ENTRY_KIND(entry.id), WAH_ENTRY_KIND_TABLE, "Export 3 ID kind mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_INDEX(entry.id), 0, "Export 3 ID index mismatch");
TEST_ASSERT_STREQ(entry.name, "tbl", "Export 3 name mismatch");
TEST_ASSERT_EQ(entry.name_len, 3, "Export 3 name_len mismatch");
// Test export by name
err = wah_module_export_by_name(&module, "add", &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export by name 'add'");
TEST_ASSERT(WAH_TYPE_IS_FUNCTION(entry.type), "WAH_TYPE_IS_FUNCTION failed for Export by name 'add'");
TEST_ASSERT_STREQ(entry.name, "add", "Export by name 'add' name mismatch");
err = wah_module_export_by_name(&module, "g", &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export by name 'g'");
TEST_ASSERT_EQ(entry.type, WAH_TYPE_I32, "Export by name 'g' type mismatch");
TEST_ASSERT(WAH_TYPE_IS_GLOBAL(entry.type), "WAH_TYPE_IS_GLOBAL failed for Export by name 'g'");
TEST_ASSERT_STREQ(entry.name, "g", "Export by name 'g' name mismatch");
err = wah_module_export_by_name(&module, "nonexistent", &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Found nonexistent export by name");
// Test wah_module_entry for exported function
wah_entry_id_t func_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_FUNCTION, 0);
err = wah_module_entry(&module, func_id, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get entry for exported function");
TEST_ASSERT(WAH_TYPE_IS_FUNCTION(entry.type), "WAH_TYPE_IS_FUNCTION failed for exported function");
TEST_ASSERT_EQ(entry.id, func_id, "Entry ID mismatch for exported function");
TEST_ASSERT(entry.name == NULL, "Entry name should be NULL for non-exported lookup"); // wah_module_entry doesn't return name for non-exported
wah_free_module(&module);
printf("test_basic_exports passed.\n");
return 0;
}
int test_duplicate_export_names() {
printf("Running test_duplicate_export_names...\n");
// Export "add" twice
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, // section id, size
0x01, // count
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, // section id, size
0x01, // count
0x00, // type index 0
// Export Section (2 exports: "add" func 0, "add" func 0)
0x07, 0x0D, // section id, size
0x02, // count
// Export 0: "add" func 0
0x03, 'a', 'd', 'd', 0x00, 0x00,
// Export 1: "add" func 0 (duplicate)
0x03, 'a', 'd', 'd', 0x00, 0x00,
// Code Section (1 code body)
0x0A, 0x09, // section id, size
0x01, // count
0x07, // code body size
0x00, // locals count
0x20, 0x00, // local.get 0
0x20, 0x01, // local.get 1
0x6A, // i32.add
0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_VALIDATION_FAILED, err, "Parsing module with duplicate export names should fail");
wah_free_module(&module); // Should be safe to call even if parsing failed
printf("test_duplicate_export_names passed.\n");
return 0;
}
int test_invalid_export_kind_or_index() {
printf("Running test_invalid_export_kind_or_index...\n");
// Test 1: Invalid export kind (e.g., 0x04 which is not defined)
uint8_t wasm_binary_invalid_kind[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Export Section (1 export: "bad" kind 0x04, index 0)
0x07, 0x07, // section id, size
0x01, // count
0x03, 'b', 'a', 'd', 0x04, 0x00, // Invalid kind 0x04
// Code Section (1 code body)
0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module_invalid_kind;
wah_error_t err = wah_parse_module(wasm_binary_invalid_kind, sizeof(wasm_binary_invalid_kind), &module_invalid_kind);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_VALIDATION_FAILED, err, "Parsing module with invalid export kind should fail");
wah_free_module(&module_invalid_kind);
// Test 2: Export function with out-of-bounds index
uint8_t wasm_binary_invalid_func_idx[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Export Section (1 export: "bad_func" func 1 (out of bounds))
0x07, 0x0C, // section id, size
0x01, // count
0x08, 'b', 'a', 'd', '_', 'f', 'u', 'n', 'c', 0x00, 0x01, // Index 1, but only 1 function (index 0)
// Code Section (1 code body)
0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module_invalid_func_idx;
err = wah_parse_module(wasm_binary_invalid_func_idx, sizeof(wasm_binary_invalid_func_idx), &module_invalid_func_idx);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_VALIDATION_FAILED, err, "Parsing module with out-of-bounds function export index should fail");
wah_free_module(&module_invalid_func_idx);
printf("test_invalid_export_kind_or_index passed.\n");
return 0;
}
int test_non_utf8_export_name() {
printf("Running test_non_utf8_export_name...\n");
// Export name with invalid UTF-8 sequence (e.g., 0xFF)
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Export Section (1 export: "bad\xFFname" func 0)
0x07, 0x0B, // section id, size
0x01, // count
0x07, 'b', 'a', 'd', 0xFF, 'n', 'a', 'm', 'e', 0x00, 0x00,
// Code Section (1 code body)
0x0A, 0x06, 0x01, 0x04, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_VALIDATION_FAILED, err, "Parsing module with non-UTF8 export name should fail");
wah_free_module(&module);
printf("test_non_utf8_export_name passed.\n");
return 0;
}
int test_module_no_exports() {
printf("Running test_module_no_exports...\n");
// Module with no export section
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Code Section (1 code body)
0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_OK(err, "Failed to parse module with no exports");
TEST_ASSERT_EQ(wah_module_num_exports(&module), 0, "Export count should be 0 for module with no exports");
wah_entry_t entry;
err = wah_module_export(&module, 0, &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Getting export by index 0 should fail for module with no exports");
err = wah_module_export_by_name(&module, "any", &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Getting export by name should fail for module with no exports");
wah_free_module(&module);
printf("test_module_no_exports passed.\n");
return 0;
}
int test_wah_module_entry_non_exported() {
printf("Running test_wah_module_entry_non_exported...\n");
// Test wah_module_entry for a function that is not exported
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Code Section (1 code body)
0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_OK(err, "Failed to parse module for non-exported entry test");
wah_entry_t entry;
wah_entry_id_t func_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_FUNCTION, 0);
err = wah_module_entry(&module, func_id, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get entry for non-exported function");
TEST_ASSERT(WAH_TYPE_IS_FUNCTION(entry.type), "WAH_TYPE_IS_FUNCTION failed for non-exported function");
TEST_ASSERT_EQ(entry.id, func_id, "Entry ID mismatch for non-exported function");
TEST_ASSERT(entry.name == NULL, "Entry name should be NULL for non-exported entry");
TEST_ASSERT_EQ(entry.name_len, 0, "Entry name_len should be 0 for non-exported entry");
wah_free_module(&module);
printf("test_wah_module_entry_non_exported passed.\n");
return 0;
}
int test_wah_module_entry_invalid_id() {
printf("Running test_wah_module_entry_invalid_id...\n");
// Test wah_module_entry with invalid entry IDs
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func(i32, i32) -> i32)
0x01, 0x07, 0x01, 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Code Section (1 code body)
0x0A, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6A, 0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_OK(err, "Failed to parse module for invalid entry ID test");
wah_entry_t entry;
// Invalid function index
wah_entry_id_t invalid_func_id = WAH_MAKE_ENTRY_ID(WAH_ENTRY_KIND_FUNCTION, 999);
err = wah_module_entry(&module, invalid_func_id, &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Getting entry with invalid function index should fail");
// Invalid kind
wah_entry_id_t invalid_kind_id = WAH_MAKE_ENTRY_ID(0xFF, 0); // 0xFF is an unknown kind
err = wah_module_entry(&module, invalid_kind_id, &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Getting entry with invalid kind should fail");
wah_free_module(&module);
printf("test_wah_module_entry_invalid_id passed.\n");
return 0;
}
int test_export_name_with_null_byte() {
printf("Running test_export_name_with_null_byte...\n");
// Export name: "bad\x00name" (length 8)
uint8_t wasm_binary[] = {
WASM_HEADER,
// Type Section (1 type: func() -> void)
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function Section (1 function, type index 0)
0x03, 0x02, 0x01, 0x00,
// Export Section (1 export: "bad\x00name" func 0)
0x07, 0x0C, // section id, size
0x01, // count
0x08, 'b', 'a', 'd', 0x00, 'n', 'a', 'm', 'e', 0x00, 0x00,
// Code Section (1 code body)
0x0A, 0x04, 0x01, 0x02, 0x00, 0x0B,
};
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
TEST_ASSERT_WAH_OK(err, "Failed to parse module with null byte in export name");
TEST_ASSERT_EQ(wah_module_num_exports(&module), 1, "Incorrect export count");
wah_entry_t entry;
// Verify by index
err = wah_module_export(&module, 0, &entry);
TEST_ASSERT_WAH_OK(err, "Failed to get export 0");
TEST_ASSERT(WAH_TYPE_IS_FUNCTION(entry.type), "Export type mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_KIND(entry.id), WAH_ENTRY_KIND_FUNCTION, "Export ID kind mismatch");
TEST_ASSERT_EQ(WAH_GET_ENTRY_INDEX(entry.id), 0, "Export ID index mismatch");
TEST_ASSERT_EQ(entry.name_len, 8, "Export name_len mismatch");
TEST_ASSERT(memcmp(entry.name, "bad\0name", 8) == 0, "Export name content mismatch");
// Attempt lookup by "bad" (shorter, stops at null)
err = wah_module_export_by_name(&module, "bad", &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Lookup by 'bad' should fail");
// Attempt lookup by "bad\0name" (exact length, but strlen stops at null)
char lookup_name_with_null[] = {'b', 'a', 'd', 0x00, 'n', 'a', 'm', 'e', '\0'}; // Ensure it's null-terminated for strlen
err = wah_module_export_by_name(&module, lookup_name_with_null, &entry);
TEST_ASSERT_WAH_ERROR(WAH_ERROR_NOT_FOUND, err, "Lookup by 'bad\0name' should fail");
wah_free_module(&module);
printf("test_export_name_with_null_byte passed.\n");
return 0;
}
int main() {
int result = 0;
result |= test_basic_exports();
result |= test_duplicate_export_names();
result |= test_invalid_export_kind_or_index();
result |= test_non_utf8_export_name();
result |= test_module_no_exports();
result |= test_wah_module_entry_non_exported();
result |= test_wah_module_entry_invalid_id();
result |= test_export_name_with_null_byte();
if (result == 0) {
printf("All export tests passed!\n");
} else {
printf("Some export tests failed.\n");
}
return result;
}
#define WAH_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include "wah.h"
// A WebAssembly binary with a start section that sets a global variable.
// (module
// (global $g (mut i32) (i32.const 0))
// (func $start_func
// (global.set $g (i32.const 42))
// )
// (start $start_func)
// )
const uint8_t start_section_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x04, // section size
0x01, // num types
0x60, // func type (void -> void)
0x00, // num params
0x00, // num results
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0 (void -> void)
// Global section
0x06, // section ID
0x06, // section size
0x01, // num globals
0x7f, // i32
0x01, // mutable
0x41, 0x00, // i32.const 0
0x0b, // end
// Start section
0x08, // section ID
0x01, // section size
0x00, // start function index (function 0)
// Code section
0x0a, // section ID
0x08, // section size
0x01, // num code bodies
0x06, // code body size (for the first function)
0x00, // num locals
0x41, 0x2a, // i32.const 42
0x24, 0x00, // global.set 0
0x0b, // end
};
int main() {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
printf("--- Testing Start Section ---\n");
printf("Parsing start_section_wasm module...\n");
err = wah_parse_module((const uint8_t *)start_section_wasm, sizeof(start_section_wasm), &module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing module with start section: %s\n", wah_strerror(err));
return 1;
}
printf("Module parsed successfully.\n");
// Create execution context. This should trigger the start function.
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for start section: %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
printf("Execution context created. Checking global variable...\n");
// Verify that the global variable was set by the start function
if (ctx.globals[0].i32 == 42) {
printf("Global variable $g is %d (expected 42). Start section executed successfully.\n", ctx.globals[0].i32);
} else {
fprintf(stderr, "Global variable $g is %d (expected 42). Start section FAILED to execute or set value.\n", ctx.globals[0].i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("Module with start section freed.\n");
return 0;
}
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <math.h> // For fabsf
#define WAH_IMPLEMENTATION
#include "wah.h"
// This WASM module was crafted by hand to test globals.
// It defines two mutable globals: an i64 and an f32.
// It uses function indices: 0:get_i64, 1:set_i64, 2:get_f32, 3:set_f32
//
// WAT equivalent:
// (module
// (global $g_i64 (mut i64) (i64.const 200))
// (global $g_f32 (mut f32) (f32.const 1.5))
//
// (func $get_i64 (result i64) global.get $g_i64)
// (func $set_i64 (param i64) local.get 0 global.set $g_i64)
//
// (func $get_f32 (result f32) global.get $g_f32)
// (func $set_f32 (param f32) local.get 0 global.set $g_f32)
// )
static const uint8_t wasm_binary[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section (id 1), size 17 (0x11)
0x01, 0x11, 0x04,
0x60, 0x00, 0x01, 0x7e, // 0: () -> i64
0x60, 0x01, 0x7e, 0x00, // 1: (i64) -> ()
0x60, 0x00, 0x01, 0x7d, // 2: () -> f32
0x60, 0x01, 0x7d, 0x00, // 3: (f32) -> ()
// Function Section (id 3), size 5 (0x05)
0x03, 0x05, 0x04,
0x00, 0x01, 0x02, 0x03,
// Global Section (id 6), size 15 (0x0f)
0x06, 0x0f, 0x02,
// global 0: i64, mutable, init: i64.const 200
0x7e, 0x01, 0x42, 0xc8, 0x01, 0x0b,
// global 1: f32, mutable, init: f32.const 1.5
0x7d, 0x01, 0x43, 0x00, 0x00, 0xc0, 0x3f, 0x0b,
// Code Section (id 10), size 25 (0x19)
0x0a, 0x19, 0x04,
// func 0 body (get_i64): size 4, locals 0, code: global.get 0; end
0x04, 0x00, 0x23, 0x00, 0x0b,
// func 1 body (set_i64): size 6, locals 0, code: local.get 0; global.set 0; end
0x06, 0x00, 0x20, 0x00, 0x24, 0x00, 0x0b,
// func 2 body (get_f32): size 4, locals 0, code: global.get 1; end
0x04, 0x00, 0x23, 0x01, 0x0b,
// func 3 body (set_f32): size 6, locals 0, code: local.get 0; global.set 1; end
0x06, 0x00, 0x20, 0x00, 0x24, 0x01, 0x0b,
};
static const uint8_t wasm_binary_type_mismatch[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section (empty for this test, as we only care about global init)
0x01, 0x01, 0x00, // Section ID, Size, Number of Types (0)
// Global Section (id 6), size 10 (0x0a)
0x06, 0x09, 0x01, // Section ID, Size, Number of Globals
// global 0: i64, mutable, init: f32.const 1.5 (TYPE MISMATCH)
0x7e, // Value Type (i64) - Declared as i64
0x01, // Mutable
0x43, // Opcode for f32.const - Initialized with f32.const
0x00, 0x00, 0xc0, 0x3f, // IEEE 754 for 1.5f
0x0b, // Opcode for end
};
void test_i64_global(wah_exec_context_t* exec_ctx, wah_module_t* module) {
wah_value_t result;
wah_error_t err;
printf("Testing i64 global...\n");
// Get initial value
err = wah_call(exec_ctx, module, 0, NULL, 0, &result);
assert(err == WAH_OK);
printf("Initial i64 value: %lld\n", (long long)result.i64);
assert(result.i64 == 200);
// Set new value
wah_value_t params_i64[1];
params_i64[0].i64 = -5000;
err = wah_call(exec_ctx, module, 1, params_i64, 1, NULL);
assert(err == WAH_OK);
// Get new value
err = wah_call(exec_ctx, module, 0, NULL, 0, &result);
assert(err == WAH_OK);
printf("New i64 value: %lld\n", (long long)result.i64);
assert(result.i64 == -5000);
}
void test_f32_global(wah_exec_context_t* exec_ctx, wah_module_t* module) {
wah_value_t result;
wah_error_t err;
printf("Testing f32 global...\n");
// Get initial value
err = wah_call(exec_ctx, module, 2, NULL, 0, &result);
assert(err == WAH_OK);
printf("Initial f32 value: %f\n", result.f32);
assert(fabsf(result.f32 - 1.5f) < 1e-6);
// Set new value
wah_value_t params_f32[1];
params_f32[0].f32 = 9.99f;
err = wah_call(exec_ctx, module, 3, params_f32, 1, NULL);
assert(err == WAH_OK);
// Get new value
err = wah_call(exec_ctx, module, 2, NULL, 0, &result);
assert(err == WAH_OK);
printf("New f32 value: %f\n", result.f32);
assert(fabsf(result.f32 - 9.99f) < 1e-6);
}
void test_global_type_mismatch(void) {
wah_module_t module_mismatch;
wah_error_t err_mismatch;
printf("\n--- Running Global Type Mismatch Test ---\n");
// Parse the module - this should fail with WAH_ERROR_TYPE_MISMATCH
err_mismatch = wah_parse_module(wasm_binary_type_mismatch, sizeof(wasm_binary_type_mismatch), &module_mismatch);
printf("Expected error: %s, Actual error: %s\n", wah_strerror(WAH_ERROR_VALIDATION_FAILED), wah_strerror(err_mismatch));
assert(err_mismatch == WAH_ERROR_VALIDATION_FAILED);
printf("--- Global Type Mismatch Test Passed (as expected failure) ---\n");
}
int main(void) {
wah_module_t module;
wah_exec_context_t exec_ctx;
wah_error_t err;
printf("--- Running Globals Test ---\n");
// Parse the module
err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
if (err != WAH_OK) {
printf("Failed to parse module: %s\n", wah_strerror(err));
assert(err == WAH_OK);
}
// Create execution context
err = wah_exec_context_create(&exec_ctx, &module);
assert(err == WAH_OK);
test_i64_global(&exec_ctx, &module);
test_f32_global(&exec_ctx, &module);
// Cleanup for regular globals test
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
printf("--- Globals Test Passed ---\n");
test_global_type_mismatch();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define WAH_IMPLEMENTATION
#include "wah.h"
// A simple WebAssembly binary for testing i32.load and i32.store
// This binary defines:
// - A memory section with 1 page (64KB)
// - Two function types:
// - (i32, i32) -> () for store_val
// - (i32) -> (i32) for load_val
// - Two functions:
// - func 0: store_val (address, value)
// - func 1: load_val (address)
// - Export section to export "store" and "load" functions
static const uint8_t wasm_binary_memory_test[] = {
0x00, 0x61, 0x73, 0x6D, // Magic number
0x01, 0x00, 0x00, 0x00, // Version
// Type Section (1)
0x01, // Section ID
0x0B, // Section size (11 bytes)
0x02, // Number of types
// Type 0: (i32, i32) -> ()
0x60, // Function type
0x02, // 2 parameters
0x7F, // i32
0x7F, // i32
0x00, // 0 results
// Type 1: (i32) -> (i32)
0x60, // Function type
0x01, // 1 parameter
0x7F, // i32
0x01, // 1 result
0x7F, // i32
// Import Section (2) - None for this test
// Function Section (3)
0x03, // Section ID
0x03, // Section size (3 bytes)
0x02, // Number of functions
0x00, // Function 0 uses type 0 (store_val)
0x01, // Function 1 uses type 1 (load_val)
// Table Section (4) - None for this test
// Memory Section (5)
0x05, // Section ID
0x03, // Section size (3 bytes)
0x01, // Number of memories
0x00, // Flags (0x00 for fixed size)
0x01, // Initial pages (1 page = 64KB)
// Global Section (6) - None for this test
// Export Section (7)
0x07, // Section ID
0x10, // Section size (16 bytes)
0x02, // Number of exports
// Export "store" (func 0)
0x05, // Length of name "store"
's', 't', 'o', 'r', 'e',
0x00, // Export kind: Function
0x00, // Function index 0
// Export "load" (func 1)
0x04, // Length of name "load"
'l', 'o', 'a', 'd',
0x00, // Export kind: Function
0x01, // Function index 1
// Start Section (8) - None for this test
// Element Section (9) - None for this test
// Code Section (10)
0x0A, // Section ID
0x13, // Section size (19 bytes)
0x02, // Number of code bodies
// Code body 0: store_val (address, value)
0x09, // Body size (9 bytes)
0x00, // No local variables
0x20, 0x00, // local.get 0 (address)
0x20, 0x01, // local.get 1 (value)
0x36, 0x02, 0x00, // i32.store align=2 (2^2=4), offset=0
0x0B, // end
// Code body 1: load_val (address)
0x07, // Body size (7 bytes)
0x00, // No local variables
0x20, 0x00, // local.get 0 (address)
0x28, 0x02, 0x00, // i32.load align=2 (2^2=4), offset=0
0x0B, // end
};
static const uint8_t wasm_binary_memory_ops_test[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x03, 0x60,
0x00, 0x01, 0x7f, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x03, 0x7f, 0x7f,
0x7f, 0x00, 0x03, 0x04, 0x03, 0x00, 0x01, 0x02, 0x05, 0x04, 0x01, 0x01,
0x01, 0x02, 0x07, 0x35, 0x04, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x0f,
0x67, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x73,
0x69, 0x7a, 0x65, 0x00, 0x00, 0x0b, 0x67, 0x72, 0x6f, 0x77, 0x5f, 0x6d,
0x65, 0x6d, 0x6f, 0x72, 0x79, 0x00, 0x01, 0x0b, 0x66, 0x69, 0x6c, 0x6c,
0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x00, 0x02, 0x0a, 0x19, 0x03,
0x04, 0x00, 0x3f, 0x00, 0x0b, 0x06, 0x00, 0x20, 0x00, 0x40, 0x00, 0x0b,
0x0b, 0x00, 0x20, 0x00, 0x20, 0x01, 0x20, 0x02, 0xfc, 0x0b, 0x00, 0x0b
};
static const uint8_t wasm_binary_data_and_bulk_memory_test[] = {
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, // Magic + Version
// Type Section (1)
0x01, 0x0C, // Section ID (1), Size (12)
0x02, // Num Types (2)
// Type 0: (i32, i32, i32) -> () for copy_mem, init_mem_data0, init_mem_data1
0x60, 0x03, 0x7F, 0x7F, 0x7F, 0x00,
// Type 1: (i32) -> (i32) for get_byte
0x60, 0x01, 0x7F, 0x01, 0x7F,
// Function Section (3)
0x03, 0x05, // Section ID (3), Size (5)
0x04, // Num Functions (4) - Changed from 0x03 to 0x04
0x00, // Function 0 (init_mem_data0) uses Type 0
0x00, // Function 1 (init_mem_data1) uses Type 0
0x00, // Function 2 (copy_mem) uses Type 0
0x01, // Function 3 (get_byte) uses Type 1
// Memory Section (5)
0x05, 0x03, // Section ID (5), Size (3)
0x01, // Num Memories (1)
0x00, // Flags (0x00 for fixed size)
0x01, // Initial pages (1 page)
// Export Section (7)
0x07, 0x39, // Section ID (7), Size (57)
0x04, // Num Exports (4)
0x0E, 'i', 'n', 'i', 't', '_', 'm', 'e', 'm', '_', 'd', 'a', 't', 'a', '0', // "init_mem_data0"
0x00, // Export Kind: Function
0x00, // Function Index 0
0x08, 'c', 'o', 'p', 'y', '_', 'm', 'e', 'm', // "copy_mem"
0x00, // Export Kind: Function
0x01, // Function Index 1
0x08, 'g', 'e', 't', '_', 'b', 'y', 't', 'e', // "get_byte"
0x00, // Export Kind: Function
0x02, // Function Index 2
0x0E, 'i', 'n', 'i', 't', '_', 'm', 'e', 'm', '_', 'd', 'a', 't', 'a', '1', // "init_mem_data1"
0x00, // Export Kind: Function
0x03, // Function Index 3
// Data Count Section (12)
0x0C, 0x01, // Section ID (12), Size (1)
0x02, // Data Count (2)
// Code Section (10)
0x0A, 0x30, // Section ID (10), Size (48)
0x04, // Num Code Bodies (4)
// Code Body 0: init_mem_data0 (dest_offset, src_offset, size)
0x0C, // Body Size (12)
0x00, // No Local Variables
0x20, 0x00, // local.get 0 (dest_offset)
0x20, 0x01, // local.get 1 (src_offset)
0x20, 0x02, // local.get 2 (size)
0xFC, 0x08, 0x00, 0x00, // memory.init data_idx 0, mem_idx 0
0x0B, // end
// Code Body 1: init_mem_data1 (dest_offset, src_offset, size)
0x0C, // Body Size (12)
0x00, // No Local Variables
0x20, 0x00, // local.get 0 (dest_offset)
0x20, 0x01, // local.get 1 (src_offset)
0x20, 0x02, // local.get 2 (size)
0xFC, 0x08, 0x01, 0x00, // memory.init data_idx 1, mem_idx 0
0x0B, // end
// Code Body 2: copy_mem (dest, src, size)
0x0C, // Body Size (12)
0x00, // No Local Variables
0x20, 0x00, // local.get 0 (dest)
0x20, 0x01, // local.get 1 (src)
0x20, 0x02, // local.get 2 (size)
0xFC, 0x0A, 0x00, 0x00, // memory.copy dest_mem_idx 0, src_mem_idx 0
0x0B, // end
// Code Body 3: get_byte (addr)
0x07, // Body Size (7)
0x00, // No Local Variables
0x20, 0x00, // local.get 0 (addr)
0x2C, 0x00, 0x00, // i32.load8_u align=0, offset=0
0x0B, // end
// Data Section (11)
0x0B, 0x10, // Section ID (11), Size (16)
0x02, // Num Data Segments (2)
// Data Segment 0: Active, offset 0, data [1, 2, 3, 4]
0x00, // Flags (active, memory index 0)
0x41, 0x00, // i32.const 0 (offset)
0x0B, // end
0x04, // Data Length (4)
0x01, 0x02, 0x03, 0x04, // Data Bytes
// Data Segment 1: Passive, data [5, 6, 7, 8]
0x01, // Flags (passive)
0x04, // Data Length (4)
0x05, 0x06, 0x07, 0x08, // Data Bytes
};
void wah_test_data_and_bulk_memory_ops() {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
wah_value_t params[3]; // params array size is 3, as max args for any function is 3
wah_value_t result;
printf("\nRunning data segments and bulk memory operations tests...\n");
// Test 1: Parse module
err = wah_parse_module(wasm_binary_data_and_bulk_memory_test, sizeof(wasm_binary_data_and_bulk_memory_test), &module);
assert(err == WAH_OK && "Failed to parse data and bulk memory ops module");
printf("Data and bulk memory ops module parsed successfully.\n");
assert(module.memory_count == 1 && "Expected 1 memory section");
assert(module.data_segment_count == 2 && "Expected 2 data segments");
assert(module.has_data_count_section == true && "Expected data count section to be present");
// Test 2: Create execution context
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK && "Failed to create execution context for data and bulk memory ops");
printf("Execution context created successfully.\n");
assert(ctx.memory_base != NULL && "Memory base should not be NULL");
assert(ctx.memory_size == WAH_WASM_PAGE_SIZE && "Memory size should be 1 page");
// Verify initial memory state (active data segment 0 should be initialized)
assert(ctx.memory_base[0] == 0x01 && "Memory byte 0 should be 0x01");
assert(ctx.memory_base[1] == 0x02 && "Memory byte 1 should be 0x02");
assert(ctx.memory_base[2] == 0x03 && "Memory byte 2 should be 0x03");
assert(ctx.memory_base[3] == 0x04 && "Memory byte 3 should be 0x04");
assert(ctx.memory_base[4] == 0x00 && "Memory byte 4 should be 0x00 (not initialized by active segment)");
printf("Initial memory state (active data segment 0) verified.\n");
// Test 3: memory.init - initialize data segment 0 at memory offset 100, data segment offset 0
uint32_t dest_offset_1 = 100;
uint32_t src_offset_1 = 0;
uint32_t size_1 = 4;
params[0].i32 = dest_offset_1; // dest_offset
params[1].i32 = src_offset_1; // src_offset
params[2].i32 = size_1; // size
err = wah_call(&ctx, &module, 0, params, 3, NULL); // Call init_mem_data0 (func 0)
assert(err == WAH_OK && "Failed to call init_mem_data0 with src_offset 0");
printf("memory.init successful. Initialized data segment 0 at memory offset %u from data segment offset %u.\n", dest_offset_1, src_offset_1);
// Verify memory contents after memory.init
params[0].i32 = dest_offset_1;
err = wah_call(&ctx, &module, 3, params, 1, &result); // Call get_byte (func 3)
assert(err == WAH_OK && result.i32 == 0x01 && "Memory byte at offset 100 should be 0x01");
params[0].i32 = dest_offset_1 + 1;
err = wah_call(&ctx, &module, 3, params, 1, &result);
assert(err == WAH_OK && result.i32 == 0x02 && "Memory byte at offset 101 should be 0x02");
printf("memory.init with src_offset 0 verification successful.\n");
// Test 4: memory.init - initialize data segment 1 at memory offset 110, data segment offset 1
uint32_t dest_offset_2 = 110;
uint32_t src_offset_2 = 1; // Start from the second byte of data segment 1 (value 0x06)
uint32_t size_2 = 2; // Copy 2 bytes (0x06, 0x07)
params[0].i32 = dest_offset_2; // dest_offset
params[1].i32 = src_offset_2; // src_offset
params[2].i32 = size_2; // size
err = wah_call(&ctx, &module, 1, params, 3, NULL); // Call init_mem_data1 (func 1)
assert(err == WAH_OK && "Failed to call init_mem_data1 with non-zero src_offset");
printf("memory.init successful. Initialized data segment 1 at memory offset %u from data segment offset %u.\n", dest_offset_2, src_offset_2);
// Verify memory contents after memory.init with non-zero src_offset
params[0].i32 = dest_offset_2;
err = wah_call(&ctx, &module, 3, params, 1, &result); // Call get_byte (func 3)
assert(err == WAH_OK && result.i32 == 0x06 && "Memory byte at offset 110 should be 0x06");
params[0].i32 = dest_offset_2 + 1;
err = wah_call(&ctx, &module, 3, params, 1, &result);
assert(err == WAH_OK && result.i32 == 0x07 && "Memory byte at offset 111 should be 0x07");
printf("memory.init with non-zero src_offset verification successful.\n");
// Test 5: memory.copy - copy 4 bytes from offset 100 to offset 200
uint32_t copy_dest = 200;
uint32_t copy_src = 100;
uint32_t copy_size = 4;
params[0].i32 = copy_dest; // dest
params[1].i32 = copy_src; // src
params[2].i32 = copy_size; // size
err = wah_call(&ctx, &module, 2, params, 3, NULL); // Call copy_mem (func 2)
assert(err == WAH_OK && "Failed to call copy_mem");
printf("memory.copy successful. Copied %u bytes from %u to %u.\n", copy_size, copy_src, copy_dest);
// Verify memory contents after memory.copy
params[0].i32 = copy_dest;
err = wah_call(&ctx, &module, 3, params, 1, &result); // Call get_byte (func 3)
assert(err == WAH_OK && result.i32 == 0x01 && "Memory byte at offset 200 should be 0x01");
params[0].i32 = copy_dest + 1;
err = wah_call(&ctx, &module, 3, params, 1, &result);
assert(err == WAH_OK && result.i32 == 0x02 && "Memory byte at offset 201 should be 0x02");
printf("memory.copy verification successful.\n");
// Test 6: memory.init - out of bounds (dest_offset + size > memory_size)
params[0].i32 = WAH_WASM_PAGE_SIZE - 2; // dest_offset (2 bytes before end)
params[1].i32 = 0; // src_offset
params[2].i32 = 4; // size (will go out of bounds)
err = wah_call(&ctx, &module, 0, params, 3, NULL); // Call init_mem_data0 (func 0)
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for memory.init (dest)");
printf("memory.init out-of-bounds (dest) test successful.\n");
// Test 7: memory.init - out of bounds (src_offset + size > segment->data_len)
params[0].i32 = 0; // dest_offset
params[1].i32 = 3; // src_offset (data segment 0 has 4 bytes, 3 + 2 = 5 > 4)
params[2].i32 = 2; // size
err = wah_call(&ctx, &module, 0, params, 3, NULL); // Call init_mem_data0 (func 0)
assert(err == WAH_ERROR_TRAP && "Expected trap for memory.init (src_offset out of bounds)");
printf("memory.init out-of-bounds (src) test successful.\n");
// Test 8: memory.copy - out of bounds (dest)
params[0].i32 = WAH_WASM_PAGE_SIZE - 2; // dest
params[1].i32 = 0; // src
params[2].i32 = 4; // size
err = wah_call(&ctx, &module, 2, params, 3, NULL); // Call copy_mem (func 2)
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for memory.copy (dest)");
printf("memory.copy out-of-bounds (dest) test successful.\n");
// Test 9: memory.copy - out of bounds (src)
params[0].i32 = 0; // dest
params[1].i32 = WAH_WASM_PAGE_SIZE - 2; // src
params[2].i32 = 4; // size
err = wah_call(&ctx, &module, 2, params, 3, NULL); // Call copy_mem (func 2)
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for memory.copy (src)");
printf("memory.copy out-of-bounds (src) test successful.\n");
// Final Cleanup
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("Data segments and bulk memory operations tests passed!\n");
}
int main() {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
wah_value_t params[3];
wah_value_t result;
printf("Running memory tests...\n");
// Test 1: Parse module
err = wah_parse_module(wasm_binary_memory_test, sizeof(wasm_binary_memory_test), &module);
assert(err == WAH_OK && "Failed to parse module");
printf("Module parsed successfully.\n");
assert(module.memory_count == 1 && "Expected 1 memory section");
assert(module.memories[0].min_pages == 1 && "Expected 1 min page");
// Test 2: Create execution context
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK && "Failed to create execution context");
printf("Execution context created successfully.\n");
assert(ctx.memory_base != NULL && "Memory base should not be NULL");
assert(ctx.memory_size == WAH_WASM_PAGE_SIZE && "Memory size should be 1 page");
// Test 3: Store a value
uint32_t test_address = 1024;
int32_t test_value = 0xDEADBEEF;
params[0].i32 = test_address;
params[1].i32 = test_value;
err = wah_call(&ctx, &module, 0, params, 2, NULL); // Call store_val (func 0)
assert(err == WAH_OK && "Failed to call store_val");
printf("Stored 0x%X at address %u.\n", test_value, test_address);
// Verify directly in memory
int32_t *mem_ptr = (int32_t*)(ctx.memory_base + test_address);
assert(*mem_ptr == test_value && "Value not correctly stored in memory");
printf("Direct memory verification successful.\n");
// Test 4: Load the value
params[0].i32 = test_address;
err = wah_call(&ctx, &module, 1, params, 1, &result); // Call load_val (func 1)
assert(err == WAH_OK && "Failed to call load_val");
assert(result.i32 == test_value && "Loaded value does not match stored value");
printf("Loaded 0x%X from address %u. Verification successful.\n", result.i32, test_address);
// Test 5: Memory out-of-bounds store
uint32_t oob_address_store = WAH_WASM_PAGE_SIZE - 2; // 2 bytes before end, trying to store 4 bytes
params[0].i32 = oob_address_store;
params[1].i32 = 0x12345678;
err = wah_call(&ctx, &module, 0, params, 2, NULL);
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for store");
printf("Memory out-of-bounds store test successful.\n");
// Test 6: Memory out-of-bounds load
uint32_t oob_address_load = WAH_WASM_PAGE_SIZE - 2; // 2 bytes before end, trying to load 4 bytes
params[0].i32 = oob_address_load;
err = wah_call(&ctx, &module, 1, params, 1, &result);
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for load");
printf("Memory out-of-bounds load test successful.\n");
// Test 7: Offset overflow with wrap-around
printf("Testing offset overflow with wrap-around...\n");
uint32_t base_addr_overflow = 0xFFFFFFF0; // A high address
uint32_t offset_overflow = 0x20; // An offset that causes overflow
uint32_t expected_effective_addr = (base_addr_overflow + offset_overflow); // Expected wrapped address
// Ensure the expected_effective_addr is within bounds for our test
// We want it to wrap around to a valid address within the first page
assert(expected_effective_addr < WAH_WASM_PAGE_SIZE - 4 && "Expected effective address out of test bounds");
int32_t test_value_overflow = 0xCAFEBABE;
// Store the value using the overflowed address
params[0].i32 = base_addr_overflow;
params[1].i32 = test_value_overflow;
err = wah_call(&ctx, &module, 0, params, 2, NULL); // Call store_val (func 0)
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for store with overflow address");
printf("Memory out-of-bounds store test with overflow address successful.\n");
// Load the value using the overflowed address
params[0].i32 = base_addr_overflow;
err = wah_call(&ctx, &module, 1, params, 1, &result); // Call load_val (func 1)
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for load with overflow address");
printf("Memory out-of-bounds load test with overflow address successful.\n");
// Cleanup for first module
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("\nRunning memory operations tests (size, grow, fill)...\n");
// Test 8: Parse new module for memory operations
err = wah_parse_module(wasm_binary_memory_ops_test, sizeof(wasm_binary_memory_ops_test), &module);
if (err != WAH_OK) {
printf("Failed to parse memory ops module. Error: %s\n", wah_strerror(err));
}
assert(err == WAH_OK && "Failed to parse memory ops module");
printf("Memory ops module parsed successfully.\n");
assert(module.memory_count == 1 && "Expected 1 memory section in ops module");
assert(module.memories[0].min_pages == 1 && "Expected 1 min page in ops module");
assert(module.memories[0].max_pages == 2 && "Expected 2 max pages in ops module");
// Test 9: Create execution context for memory operations
err = wah_exec_context_create(&ctx, &module);
assert(err == WAH_OK && "Failed to create execution context for memory ops");
printf("Execution context for memory ops created successfully.\n");
assert(ctx.memory_base != NULL && "Memory base should not be NULL for ops");
assert(ctx.memory_size == WAH_WASM_PAGE_SIZE && "Memory size should be 1 page for ops");
// Test 10: memory.size - initial
err = wah_call(&ctx, &module, 0, NULL, 0, &result); // Call get_memory_size (func 0)
assert(err == WAH_OK && "Failed to call get_memory_size");
assert(result.i32 == 1 && "Initial memory size should be 1 page");
printf("Initial memory size: %d pages. Test successful.\n", result.i32);
// Test 11: memory.grow - success
params[0].i32 = 1; // Grow by 1 page
err = wah_call(&ctx, &module, 1, params, 1, &result); // Call grow_memory (func 1)
assert(err == WAH_OK && "Failed to call grow_memory");
assert(result.i32 == 1 && "grow_memory should return old size (1)");
assert(ctx.memory_size == (2 * WAH_WASM_PAGE_SIZE) && "Memory size should be 2 pages");
printf("Memory grown by 1 page. New size: %d pages. Test successful.\n", ctx.memory_size / WAH_WASM_PAGE_SIZE);
// Test 12: memory.size - after grow
err = wah_call(&ctx, &module, 0, NULL, 0, &result); // Call get_memory_size (func 0)
assert(err == WAH_OK && "Failed to call get_memory_size after grow");
assert(result.i32 == 2 && "Memory size should be 2 pages after grow");
printf("Memory size after grow: %d pages. Test successful.\n", result.i32);
// Test 13: memory.grow - failure (exceed max_pages)
params[0].i32 = 1; // Grow by 1 page (total 3, max 2)
err = wah_call(&ctx, &module, 1, params, 1, &result); // Call grow_memory (func 1)
assert(err == WAH_OK && "Failed to call grow_memory for failure test"); // Should return -1, not trap
assert(result.i32 == -1 && "grow_memory should return -1 on failure");
assert(ctx.memory_size == (2 * WAH_WASM_PAGE_SIZE) && "Memory size should remain 2 pages");
printf("Memory grow failure test successful (returned -1). Current size: %d pages.\n", ctx.memory_size / WAH_WASM_PAGE_SIZE);
// Test 14: memory.fill - basic
uint32_t fill_offset = 100;
uint8_t fill_value = 0xAA;
uint32_t fill_size = 256;
params[0].i32 = fill_offset; // offset
params[1].i32 = fill_value; // value
params[2].i32 = fill_size; // size
err = wah_call(&ctx, &module, 2, params, 3, NULL); // Call fill_memory (func 2)
assert(err == WAH_OK && "Failed to call fill_memory");
printf("Memory fill basic test successful. Filled %u bytes from offset %u with 0x%02X.\n", fill_size, fill_offset, fill_value);
// Verify filled memory directly
for (uint32_t i = 0; i < fill_size; ++i) {
assert(ctx.memory_base[fill_offset + i] == fill_value && "Memory fill verification failed");
}
printf("Memory fill verification successful.\n");
// Test 15: memory.fill - out of bounds
uint32_t oob_fill_offset = ctx.memory_size - 100; // Near end of memory
uint32_t oob_fill_size = 200; // Will go out of bounds
params[0].i32 = oob_fill_offset;
params[1].i32 = 0xBB;
params[2].i32 = oob_fill_size;
err = wah_call(&ctx, &module, 2, params, 3, NULL);
assert(err == WAH_ERROR_MEMORY_OUT_OF_BOUNDS && "Expected memory out-of-bounds error for fill");
printf("Memory fill out-of-bounds test successful.\n");
// Final Cleanup
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("All memory tests passed!\n");
wah_test_data_and_bulk_memory_ops();
return 0;
}
#define WAH_IMPLEMENTATION
#include "wah.h"
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <inttypes.h>
#define FLOAT_TOLERANCE 1e-6
// --- Test Helper Functions ---
// Generic test runner
#define DEFINE_RUN_TEST(T, ty, fmt, compare) \
int run_test_##T(const char* test_name, const uint8_t* wasm_bytecode, size_t bytecode_size, \
wah_value_t* params, uint32_t param_count, ty expected_result, bool expect_trap) { \
wah_module_t module; \
wah_exec_context_t ctx; \
wah_error_t err; \
wah_value_t result; \
int test_status = 0; /* 0 for pass, 1 for fail */ \
\
err = wah_parse_module(wasm_bytecode, bytecode_size, &module); \
if (err != WAH_OK) { \
printf(" %s: Failed to parse module: %s\n", test_name, wah_strerror(err)); \
return 1; \
} \
err = wah_exec_context_create(&ctx, &module); \
if (err != WAH_OK) { \
fprintf(stderr, " %s: Error creating execution context: %s\n", test_name, wah_strerror(err)); \
wah_free_module(&module); \
return 1; \
} \
\
err = wah_call(&ctx, &module, 0, params, param_count, &result); \
if (expect_trap) { \
if (err == WAH_ERROR_TRAP) { \
printf(" %s: PASSED. Expected trap.\n", test_name); \
} else { \
printf(" %s: FAILED! Expected trap, but got %s\n", test_name, wah_strerror(err)); \
test_status = 1; \
} \
} else { \
if (err != WAH_OK) { \
printf(" %s: Failed to execute: %s\n", test_name, wah_strerror(err)); \
test_status = 1; \
} else { \
if (compare) { \
printf(" %s: PASSED. Result: " fmt "\n", test_name, result.T); \
} else { \
printf(" %s: FAILED! Expected " fmt ", got " fmt "\n", test_name, expected_result, result.T); \
test_status = 1; \
} \
} \
} \
\
wah_exec_context_destroy(&ctx); \
wah_free_module(&module); \
return test_status; \
}
#define FLOAT_COMPARE(result_val, expected_val, type) \
((isnan(result_val) && isnan(expected_val)) || \
(isinf(result_val) && isinf(expected_val) && (signbit(result_val) == signbit(expected_val))) || \
(fabs(result_val - expected_val) <= FLOAT_TOLERANCE))
DEFINE_RUN_TEST(i32, int32_t, "%" PRId32, result.i32 == expected_result)
DEFINE_RUN_TEST(i64, int64_t, "%" PRId64, result.i64 == expected_result)
DEFINE_RUN_TEST(f32, float, "%f", FLOAT_COMPARE(result.f32, expected_result, float))
DEFINE_RUN_TEST(f64, double, "%f", FLOAT_COMPARE(result.f64, expected_result, double))
#define run_test_i32(n,b,p,e,t) run_test_i32(n,b,sizeof(b),p,sizeof(p)/sizeof(*p),e,t)
#define run_test_i64(n,b,p,e,t) run_test_i64(n,b,sizeof(b),p,sizeof(p)/sizeof(*p),e,t)
#define run_test_f32(n,b,p,e,t) run_test_f32(n,b,sizeof(b),p,sizeof(p)/sizeof(*p),e,t)
#define run_test_f64(n,b,p,e,t) run_test_f64(n,b,sizeof(b),p,sizeof(p)/sizeof(*p),e,t)
// --- Templates ---
// (module (func (param T) (result U) (OPCODE (local.get 0))))
#define UNARY_TEST_WASM(arg_ty, ret_ty, opcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x06, 0x01, 0x60, 0x01, arg_ty, 0x01, ret_ty, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x07, 0x01, 0x05, 0x00, 0x20, 0x00, opcode, 0x0b \
}
#define UNARY_TEST_WASM_FC(arg_ty, ret_ty, subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x06, 0x01, 0x60, 0x01, arg_ty, 0x01, ret_ty, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x08, 0x01, 0x06, 0x00, 0x20, 0x00, 0xfc, subopcode, 0x0b \
};
// (module (func (param T1 T2) (result U) (OPCODE (local.get 0) (local.get 1))))
#define BINARY_TEST_WASM(lhs_ty, rhs_ty, ret_ty, opcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x07, 0x01, 0x60, 0x02, lhs_ty, rhs_ty, 0x01, ret_ty, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, opcode, 0x0b \
}
#define CHECK_UNARY(label, arg_ty, arg, ret_ty, ret) do { \
wah_value_t params[1] = { { .arg_ty = arg } }; \
failures += run_test_##ret_ty(label, test_wasm, params, ret, false); \
} while (0)
#define CHECK_UNARY_TRAP(label, arg_ty, arg, ret_ty) do { \
wah_value_t params[1] = { { .arg_ty = arg } }; \
failures += run_test_##ret_ty(label, test_wasm, params, 0, true); \
} while (0)
#define CHECK_BINARY(label, lhs_ty, lhs, rhs_ty, rhs, ret_ty, ret) do { \
wah_value_t params[2] = { { .lhs_ty = lhs }, { .rhs_ty = rhs } }; \
failures += run_test_##ret_ty(label, test_wasm, params, ret, false); \
} while (0)
#define CHECK_BINARY_TRAP(label, lhs_ty, lhs, rhs_ty, rhs, ret_ty) do { \
wah_value_t params[2] = { { .lhs_ty = lhs }, { .rhs_ty = rhs } }; \
failures += run_test_##ret_ty(label, test_wasm, params, 0, true); \
} while (0)
// --- Individual Test Functions ---
int test_i32_and() {
int failures = 0;
static const uint8_t test_wasm[] = BINARY_TEST_WASM(0x7f, 0x7f, 0x7f, 0x71);
printf("\n=== Testing I32.AND ===\n");
CHECK_BINARY("I32.AND (0xFF & 0x0F)", i32, 0xFF, i32, 0x0F, i32, 0x0F);
return failures;
}
int test_i32_eq() {
int failures = 0;
static const uint8_t test_wasm[] = BINARY_TEST_WASM(0x7f, 0x7f, 0x7f, 0x46);
printf("\n=== Testing I32.EQ ===\n");
CHECK_BINARY("I32.EQ (42 == 42)", i32, 42, i32, 42, i32, 1);
CHECK_BINARY("I32.EQ (42 == 24)", i32, 42, i32, 24, i32, 0);
return failures;
}
int test_i32_popcnt() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7f, 0x69);
printf("\n=== Testing I32.POPCNT ===\n");
CHECK_UNARY("I32.POPCNT (0xAA)", i32, 0b10101010 /*0xAA*/, i32, 4);
return failures;
}
int test_i64_clz() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7e, 0x79);
printf("\n=== Testing I64.CLZ ===\n");
CHECK_UNARY("I64.CLZ (0x00...0FF)", i64, 0x00000000000000FFULL /*56 leading zeros*/, i64, 56);
return failures;
}
int test_f64_nearest() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7c, 0x9e);
printf("\n=== Testing F64.NEAREST ===\n");
CHECK_UNARY("F64.NEAREST (2.5)", f64, 2.5, f64, 2.0);
CHECK_UNARY("F64.NEAREST (3.5)", f64, 3.5, f64, 4.0);
CHECK_UNARY("F64.NEAREST (-2.5)", f64, -2.5, f64, -2.0);
CHECK_UNARY("F64.NEAREST (-3.5)", f64, -3.5, f64, -4.0);
return failures;
}
int test_i32_rotl() {
int failures = 0;
static const uint8_t test_wasm[] = BINARY_TEST_WASM(0x7f, 0x7f, 0x7f, 0x77);
printf("\n=== Testing I32.ROTL ===\n");
CHECK_BINARY("I32.ROTL (0x80000001, 1)", i32, 0x80000001 /*1000...0001*/, i32, 1, i32, 0x00000003);
return failures;
}
int test_f32_min() {
int failures = 0;
const uint8_t test_wasm[] = BINARY_TEST_WASM(0x7d, 0x7d, 0x7d, 0x96);
printf("\n=== Testing F32.MIN ===\n");
CHECK_BINARY("F32.MIN (10.0f, 20.0f)", f32, 10.0f, f32, 20.0f, f32, 10.0f);
CHECK_BINARY("F32.MIN (5.0f, -5.0f)", f32, 5.0f, f32, -5.0f, f32, -5.0f);
return failures;
}
int test_i32_wrap_i64() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7f, 0xa7);
printf("\n=== Testing I32.WRAP_I64 ===\n");
CHECK_UNARY("I32.WRAP_I64 (0x123456789ABCDEF0)", i64, 0x123456789ABCDEF0LL, i32, (int32_t)0x9ABCDEF0);
CHECK_UNARY("I32.WRAP_I64 (0xFFFFFFFF12345678)", i64, 0xFFFFFFFF12345678LL, i32, (int32_t)0x12345678);
return failures;
}
int test_i32_trunc_f32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7f, 0xa8);
printf("\n=== Testing I32.TRUNC_F32_S ===\n");
CHECK_UNARY("I32.TRUNC_F32_S (10.5f)", f32, 10.5f, i32, 10);
CHECK_UNARY("I32.TRUNC_F32_S (-10.5f)", f32, -10.5f, i32, -10);
CHECK_UNARY_TRAP("I32.TRUNC_F32_S (NaN)", f32, NAN, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F32_S (Infinity)", f32, INFINITY, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F32_S (too large)", f32, 2147483648.0f /*INT32_MAX + 1*/, i32);
return failures;
}
int test_i32_trunc_f32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7f, 0xa9);
printf("\n=== Testing I32.TRUNC_F32_U ===\n");
CHECK_UNARY("I32.TRUNC_F32_U (10.5f)", f32, 10.5f, i32, 10);
CHECK_UNARY_TRAP("I32.TRUNC_F32_U (-10.5f)", f32, -10.5f, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F32_U (too large)", f32, 4294967296.0f /*UINT32_MAX + 1*/, i32);
return failures;
}
int test_i32_trunc_f64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7f, 0xaa);
printf("\n=== Testing I32.TRUNC_F64_S ===\n");
CHECK_UNARY("I32.TRUNC_F64_S (10.5)", f64, 10.5, i32, 10);
CHECK_UNARY("I32.TRUNC_F64_S (-10.5)", f64, -10.5, i32, -10);
CHECK_UNARY_TRAP("I32.TRUNC_F64_S (NaN)", f64, NAN, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F64_S (Infinity)", f64, INFINITY, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F64_S (too large)", f64, 2147483648.0 /*INT32_MAX + 1*/, i32);
return failures;
}
int test_i32_trunc_f64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7f, 0xab);
printf("\n=== Testing I32.TRUNC_F64_U ===\n");
CHECK_UNARY("I32.TRUNC_F64_U (10.5)", f64, 10.5, i32, 10);
CHECK_UNARY_TRAP("I32.TRUNC_F64_U (-10.5)", f64, -10.5, i32);
CHECK_UNARY_TRAP("I32.TRUNC_F64_U (too large)", f64, 4294967296.0 /*UINT32_MAX + 1*/, i32);
return failures;
}
int test_i64_extend_i32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7e, 0xac);
printf("\n=== Testing I64.EXTEND_I32_S ===\n");
CHECK_UNARY("I64.EXTEND_I32_S (12345)", i32, 12345, i64, 12345LL);
CHECK_UNARY("I64.EXTEND_I32_S (-12345)", i32, -12345, i64, -12345LL);
CHECK_UNARY("I64.EXTEND_I32_S (0x80000000)", i32, 0x80000000, i64, (int64_t)0xFFFFFFFF80000000LL); // Smallest signed 32-bit integer
return failures;
}
int test_i64_extend_i32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7e, 0xad);
printf("\n=== Testing I64.EXTEND_I32_U ===\n");
CHECK_UNARY("I64.EXTEND_I32_U (12345)", i32, 12345, i64, 12345LL);
CHECK_UNARY("I64.EXTEND_I32_U (0xFFFFFFFF)", i32, 0xFFFFFFFF, i64, 0x00000000FFFFFFFFLL); // Largest unsigned 32-bit integer
return failures;
}
int test_i64_trunc_f32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7e, 0xae);
printf("\n=== Testing I64.TRUNC_F32_S ===\n");
CHECK_UNARY("I64.TRUNC_F32_S (10.5f)", f32, 10.5f, i64, 10LL);
CHECK_UNARY("I64.TRUNC_F32_S (-10.5f)", f32, -10.5f, i64, -10LL);
CHECK_UNARY_TRAP("I64.TRUNC_F32_S (NaN)", f32, NAN, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F32_S (Infinity)", f32, INFINITY, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F32_S (too large)", f32, 9223372036854775808.0f /*INT64_MAX + 1*/, i64);
return failures;
}
int test_i64_trunc_f32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7e, 0xaf);
printf("\n=== Testing I64.TRUNC_F32_U ===\n");
CHECK_UNARY("I64.TRUNC_F32_U (10.5f)", f32, 10.5f, i64, 10LL);
CHECK_UNARY_TRAP("I64.TRUNC_F32_U (-10.5f)", f32, -10.5f, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F32_U (too large)", f32, 18446744073709551616.0f /*UINT64_MAX + 1*/, i64);
return failures;
}
int test_i64_trunc_f64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7e, 0xb0);
printf("\n=== Testing I64.TRUNC_F64_S ===\n");
CHECK_UNARY("I64.TRUNC_F64_S (10.5)", f64, 10.5, i64, 10LL);
CHECK_UNARY("I64.TRUNC_F64_S (-10.5)", f64, -10.5, i64, -10LL);
CHECK_UNARY_TRAP("I64.TRUNC_F64_S (NaN)", f64, NAN, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F64_S (Infinity)", f64, INFINITY, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F64_S (too large)", f64, 9223372036854775808.0 /*INT64_MAX + 1*/, i64);
return failures;
}
int test_i64_trunc_f64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7e, 0xb1);
printf("\n=== Testing I64.TRUNC_F64_U ===\n");
CHECK_UNARY("I64.TRUNC_F64_U (10.5)", f64, 10.5, i64, 10LL);
CHECK_UNARY_TRAP("I64.TRUNC_F64_U (-10.5)", f64, -10.5, i64);
CHECK_UNARY_TRAP("I64.TRUNC_F64_U (too large)", f64, 18446744073709551616.0 /*UINT64_MAX + 1*/, i64);
return failures;
}
int test_f32_convert_i32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7d, 0xb2);
printf("\n=== Testing F32.CONVERT_I32_S ===\n");
CHECK_UNARY("F32.CONVERT_I32_S (12345)", i32, 12345, f32, 12345.0f);
CHECK_UNARY("F32.CONVERT_I32_S (-12345)", i32, -12345, f32, -12345.0f);
return failures;
}
int test_f32_convert_i32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7d, 0xb3);
printf("\n=== Testing F32.CONVERT_I32_U ===\n");
CHECK_UNARY("F32.CONVERT_I32_U (12345)", i32, 12345, f32, 12345.0f);
CHECK_UNARY("F32.CONVERT_I32_U (0xFFFFFFFF)", i32, 0xFFFFFFFF /*UINT32_MAX*/, f32, 4294967295.0f);
return failures;
}
int test_f32_convert_i64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7d, 0xb4);
printf("\n=== Testing F32.CONVERT_I64_S ===\n");
CHECK_UNARY("F32.CONVERT_I64_S (123456789012345)", i64, 123456789012345LL, f32, 123456788103168.0f); // Precision loss expected
CHECK_UNARY("F32.CONVERT_I64_S (-123456789012345)", i64, -123456789012345LL, f32, -123456788103168.0f); // Precision loss expected
return failures;
}
int test_f32_convert_i64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7d, 0xb5);
printf("\n=== Testing F32.CONVERT_I64_U ===\n");
CHECK_UNARY("F32.CONVERT_I64_U (123456789012345)", i64, 123456789012345ULL, f32, 123456788103168.0f); // Precision loss expected
CHECK_UNARY("F32.CONVERT_I64_U (0xFFFFFFFFFFFFFFFF)", i64, 0xFFFFFFFFFFFFFFFFULL /*UINT64_MAX*/, f32, 1.8446744073709552e+19f); // Precision loss expected
return failures;
}
int test_f32_demote_f64() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7d, 0xb6);
printf("\n=== Testing F32.DEMOTE_F64 ===\n");
CHECK_UNARY("F32.DEMOTE_F64 (123.456)", f64, 123.456, f32, 123.456f);
CHECK_UNARY("F32.DEMOTE_F64 (large double to float)", f64, 1.2345678901234567e+300, f32, INFINITY); // A large double that will become infinity in float
return failures;
}
int test_f64_convert_i32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7c, 0xb7);
printf("\n=== Testing F64.CONVERT_I32_S ===\n");
CHECK_UNARY("F64.CONVERT_I32_S (12345)", i32, 12345, f64, 12345.0);
CHECK_UNARY("F64.CONVERT_I32_S (-12345)", i32, -12345, f64, -12345.0);
return failures;
}
int test_f64_convert_i32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7c, 0xb8);
printf("\n=== Testing F64.CONVERT_I32_U ===\n");
CHECK_UNARY("F64.CONVERT_I32_U (12345)", i32, 12345, f64, 12345.0);
CHECK_UNARY("F64.CONVERT_I32_U (0xFFFFFFFF)", i32, 0xFFFFFFFF /*UINT32_MAX*/, f64, 4294967295.0);
return failures;
}
int test_f64_convert_i64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7c, 0xb9);
printf("\n=== Testing F64.CONVERT_I64_S ===\n");
CHECK_UNARY("F64.CONVERT_I64_S (1234567890123456789)", i64, 1234567890123456789LL, f64, 1234567890123456768.0); // Precision loss expected
CHECK_UNARY("F64.CONVERT_I64_S (-1234567890123456789)", i64, -1234567890123456789LL, f64, -1234567890123456768.0); // Precision loss expected
return failures;
}
int test_f64_convert_i64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7c, 0xba);
printf("\n=== Testing F64.CONVERT_I64_U ===\n");
CHECK_UNARY("F64.CONVERT_I64_U (1234567890123456789)", i64, 1234567890123456789ULL, f64, 1234567890123456768.0); // Precision loss expected
CHECK_UNARY("F64.CONVERT_I64_U (0xFFFFFFFFFFFFFFFF)", i64, 0xFFFFFFFFFFFFFFFFULL /*UINT64_MAX*/, f64, 1.8446744073709552e+19); // Precision loss expected
return failures;
}
int test_f64_promote_f32() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7c, 0xbb);
printf("\n=== Testing F64.PROMOTE_F32 ===\n");
CHECK_UNARY("F64.PROMOTE_F32 (123.456f)", f32, 123.456f, f64, (double)123.456f);
return failures;
}
int test_i32_reinterpret_f32() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7d, 0x7f, 0xbc);
printf("\n=== Testing I32.REINTERPRET_F32 ===\n");
CHECK_UNARY("I32.REINTERPRET_F32 (1.0f)", f32, 1.0f, i32, 0x3F800000);
return failures;
}
int test_i64_reinterpret_f64() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7c, 0x7e, 0xbd);
printf("\n=== Testing I64.REINTERPRET_F64 ===\n");
CHECK_UNARY("I64.REINTERPRET_F64 (1.0)", f64, 1.0, i64, 0x3FF0000000000000ULL);
return failures;
}
int test_f32_reinterpret_i32() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7d, 0xbe);
printf("\n=== Testing F32.REINTERPRET_I32 ===\n");
CHECK_UNARY("F32.REINTERPRET_I32 (0x3F800000)", i32, 0x3F800000, f32, 1.0f);
return failures;
}
int test_f64_reinterpret_i64() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7c, 0xbf);
printf("\n=== Testing F64.REINTERPRET_I64 ===\n");
CHECK_UNARY("F64.REINTERPRET_I64 (0x3FF0000000000000)", i64, 0x3FF0000000000000ULL, f64, 1.0);
return failures;
}
int test_i32_trunc_sat_f32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7d, 0x7f, 0x00);
printf("\n=== Testing I32.TRUNC_SAT_F32_S ===\n");
CHECK_UNARY("I32.TRUNC_SAT_F32_S (10.5f)", f32, 10.5f, i32, 10);
CHECK_UNARY("I32.TRUNC_SAT_F32_S (-10.5f)", f32, -10.5f, i32, -10);
CHECK_UNARY("I32.TRUNC_SAT_F32_S (NaN)", f32, NAN, i32, 0); // NaN should result in 0
CHECK_UNARY("I32.TRUNC_SAT_F32_S (Infinity)", f32, INFINITY, i32, INT32_MAX); // Positive Infinity should saturate to INT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F32_S (-Infinity)", f32, -INFINITY, i32, INT32_MIN); // Negative Infinity should saturate to INT32_MIN
CHECK_UNARY("I32.TRUNC_SAT_F32_S (too large)", f32, 2147483648.0f /*INT32_MAX + 1*/, i32, INT32_MAX); // Value greater than INT32_MAX should saturate to INT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F32_S (too small)", f32, -2147483649.0f /*INT32_MIN - 1*/, i32, INT32_MIN); // Value less than INT32_MIN should saturate to INT32_MIN
return failures;
}
int test_i32_trunc_sat_f32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7d, 0x7f, 0x01);
printf("\n=== Testing I32.TRUNC_SAT_F32_U ===\n");
CHECK_UNARY("I32.TRUNC_SAT_F32_U (10.5f)", f32, 10.5f, i32, 10);
CHECK_UNARY("I32.TRUNC_SAT_F32_U (NaN)", f32, NAN, i32, 0); // NaN should result in 0
CHECK_UNARY("I32.TRUNC_SAT_F32_U (Infinity)", f32, INFINITY, i32, (int32_t)UINT32_MAX); // Positive Infinity should saturate to UINT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F32_U (-Infinity)", f32, -INFINITY, i32, 0); // Negative Infinity should saturate to 0
CHECK_UNARY("I32.TRUNC_SAT_F32_U (too large)", f32, 4294967296.0f /*UINT32_MAX + 1*/, i32, (int32_t)UINT32_MAX); // Value greater than UINT32_MAX should saturate to UINT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F32_U (negative)", f32, -0.5f, i32, 0); // Value less than 0 should saturate to 0
return failures;
}
int test_i32_trunc_sat_f64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7c, 0x7f, 0x02);
printf("\n=== Testing I32.TRUNC_SAT_F64_S ===\n");
CHECK_UNARY("I32.TRUNC_SAT_F64_S (10.5)", f64, 10.5, i32, 10);
CHECK_UNARY("I32.TRUNC_SAT_F64_S (-10.5)", f64, -10.5, i32, -10);
CHECK_UNARY("I32.TRUNC_SAT_F64_S (NaN)", f64, NAN, i32, 0); // NaN should result in 0
CHECK_UNARY("I32.TRUNC_SAT_F64_S (Infinity)", f64, INFINITY, i32, INT32_MAX); // Positive Infinity should saturate to INT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F64_S (-Infinity)", f64, -INFINITY, i32, INT32_MIN); // Negative Infinity should saturate to INT32_MIN
CHECK_UNARY("I32.TRUNC_SAT_F64_S (too large)", f64, 2147483648.0 /*INT32_MAX + 1*/, i32, INT32_MAX); // Value greater than INT32_MAX should saturate to INT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F64_S (too small)", f64, -2147483649.0 /*INT32_MIN - 1*/, i32, INT32_MIN); // Value less than INT32_MIN should saturate to INT32_MIN
return failures;
}
int test_i32_trunc_sat_f64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7c, 0x7f, 0x03);
printf("\n=== Testing I32.TRUNC_SAT_F64_U ===\n");
CHECK_UNARY("I32.TRUNC_SAT_F64_U (10.5)", f64, 10.5, i32, 10);
CHECK_UNARY("I32.TRUNC_SAT_F64_U (NaN)", f64, NAN, i32, 0); // NaN should result in 0
CHECK_UNARY("I32.TRUNC_SAT_F64_U (Infinity)", f64, INFINITY, i32, (int32_t)UINT32_MAX); // Positive Infinity should saturate to UINT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F64_U (-Infinity)", f64, -INFINITY, i32, 0); // Negative Infinity should saturate to 0
CHECK_UNARY("I32.TRUNC_SAT_F64_U (too large)", f64, 4294967296.0 /*UINT32_MAX + 1*/, i32, (int32_t)UINT32_MAX); // Value greater than UINT32_MAX should saturate to UINT32_MAX
CHECK_UNARY("I32.TRUNC_SAT_F64_U (negative)", f64, -0.5, i32, 0); // Value less than 0 should saturate to 0
return failures;
}
int test_i32_extend8_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7f, 0xC0);
printf("\n=== Testing I32.EXTEND8_S ===\n");
CHECK_UNARY("I32.EXTEND8_S (0x7F)", i32, 0x7F, i32, 0x7F);
CHECK_UNARY("I32.EXTEND8_S (0x80)", i32, 0x80, i32, (int32_t)0xFFFFFF80);
CHECK_UNARY("I32.EXTEND8_S (0xFF)", i32, 0xFF, i32, (int32_t)0xFFFFFFFF);
CHECK_UNARY("I32.EXTEND8_S (0x00000001)", i32, 0x00000001, i32, 0x00000001);
CHECK_UNARY("I32.EXTEND8_S (0x12345678)", i32, 0x12345678, i32, 0x00000078);
CHECK_UNARY("I32.EXTEND8_S (0x90ABCDEF)", i32, 0x90ABCDEF, i32, 0xFFFFFFEF);
CHECK_UNARY("I32.EXTEND8_S (0xFFFFFFFF)", i32, 0xFFFFFFFF, i32, (int32_t)0xFFFFFFFF);
return failures;
}
int test_i32_extend16_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7f, 0x7f, 0xC1);
printf("\n=== Testing I32.EXTEND16_S ===\n");
CHECK_UNARY("I32.EXTEND16_S (0x7FFF)", i32, 0x7FFF, i32, 0x7FFF);
CHECK_UNARY("I32.EXTEND16_S (0x8000)", i32, 0x8000, i32, (int32_t)0xFFFF8000);
CHECK_UNARY("I32.EXTEND16_S (0xFFFF)", i32, 0xFFFF, i32, (int32_t)0xFFFFFFFF);
CHECK_UNARY("I32.EXTEND16_S (0x00000001)", i32, 0x00000001, i32, 0x00000001);
CHECK_UNARY("I32.EXTEND16_S (0x12345678)", i32, 0x12345678, i32, 0x00005678);
CHECK_UNARY("I32.EXTEND16_S (0x90ABCDEF)", i32, 0x90ABCDEF, i32, 0xFFFFCDEF);
CHECK_UNARY("I32.EXTEND16_S (0xFFFFFFFF)", i32, 0xFFFFFFFF, i32, (int32_t)0xFFFFFFFF);
return failures;
}
int test_i64_extend8_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7e, 0xC2);
printf("\n=== Testing I64.EXTEND8_S ===\n");
CHECK_UNARY("I64.EXTEND8_S (0x7F)", i64, 0x7F, i64, 0x7FLL);
CHECK_UNARY("I64.EXTEND8_S (0x80)", i64, 0x80, i64, (int64_t)0xFFFFFFFFFFFFFF80LL);
CHECK_UNARY("I64.EXTEND8_S (0xFF)", i64, 0xFF, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
CHECK_UNARY("I64.EXTEND8_S (0x0000000000000001)", i64, 0x0000000000000001LL, i64, 0x0000000000000001LL);
CHECK_UNARY("I64.EXTEND8_S (0x1234567890ABCDEF)", i64, 0x1234567890ABCDEFLL, i64, (int64_t)0xFFFFFFFFFFFFFFEFLL);
CHECK_UNARY("I64.EXTEND8_S (0xFDECBA9876543210)", i64, 0xFDECBA9876543210LL, i64, 0x0000000000000010LL);
CHECK_UNARY("I64.EXTEND8_S (0xFFFFFFFFFFFFFFFF)", i64, 0xFFFFFFFFFFFFFFFFLL, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
return failures;
}
int test_i64_extend16_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7e, 0xC3);
printf("\n=== Testing I64.EXTEND16_S ===\n");
CHECK_UNARY("I64.EXTEND16_S (0x7FFF)", i64, 0x7FFF, i64, 0x7FFFLL);
CHECK_UNARY("I64.EXTEND16_S (0x8000)", i64, 0x8000, i64, (int64_t)0xFFFFFFFFFFFF8000LL);
CHECK_UNARY("I64.EXTEND16_S (0xFFFF)", i64, 0xFFFF, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
CHECK_UNARY("I64.EXTEND16_S (0x0000000000000001)", i64, 0x0000000000000001LL, i64, 0x0000000000000001LL);
CHECK_UNARY("I64.EXTEND16_S (0x1234567890ABCDEF)", i64, 0x1234567890ABCDEFLL, i64, (int64_t)0xFFFFFFFFFFFFCDEFLL);
CHECK_UNARY("I64.EXTEND16_S (0xFDECBA9876543210)", i64, 0xFDECBA9876543210LL, i64, 0x0000000000003210LL);
CHECK_UNARY("I64.EXTEND16_S (0xFFFFFFFFFFFFFFFF)", i64, 0xFFFFFFFFFFFFFFFFLL, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
return failures;
}
int test_i64_extend32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM(0x7e, 0x7e, 0xC4);
printf("\n=== Testing I64.EXTEND32_S ===\n");
CHECK_UNARY("I64.EXTEND32_S (0x7FFFFFFF)", i64, 0x7FFFFFFF, i64, 0x7FFFFFFFLL);
CHECK_UNARY("I64.EXTEND32_S (0x80000000)", i64, 0x80000000, i64, (int64_t)0xFFFFFFFF80000000LL);
CHECK_UNARY("I64.EXTEND32_S (0xFFFFFFFF)", i64, 0xFFFFFFFF, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
CHECK_UNARY("I64.EXTEND32_S (0x0000000000000001)", i64, 0x0000000000000001LL, i64, 0x0000000000000001LL);
CHECK_UNARY("I64.EXTEND32_S (0x1234567890ABCDEF)", i64, 0x1234567890ABCDEFLL, i64, (int64_t)0xFFFFFFFF90ABCDEFLL);
CHECK_UNARY("I64.EXTEND32_S (0xFDECBA9876543210)", i64, 0xFDECBA9876543210LL, i64, 0x0000000076543210LL);
CHECK_UNARY("I64.EXTEND32_S (0xFFFFFFFFFFFFFFFF)", i64, 0xFFFFFFFFFFFFFFFFLL, i64, (int64_t)0xFFFFFFFFFFFFFFFFLL);
return failures;
}
int test_i64_trunc_sat_f32_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7d, 0x7e, 0x04);
printf("\n=== Testing I64.TRUNC_SAT_F32_S ===\n");
CHECK_UNARY("I64.TRUNC_SAT_F32_S (10.5f)", f32, 10.5f, i64, 10LL);
CHECK_UNARY("I64.TRUNC_SAT_F32_S (-10.5f)", f32, -10.5f, i64, -10LL);
CHECK_UNARY("I64.TRUNC_SAT_F32_S (NaN)", f32, NAN, i64, 0LL); // NaN should result in 0
CHECK_UNARY("I64.TRUNC_SAT_F32_S (Infinity)", f32, INFINITY, i64, INT64_MAX); // Positive Infinity should saturate to INT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F32_S (-Infinity)", f32, -INFINITY, i64, INT64_MIN); // Negative Infinity should saturate to INT64_MIN
CHECK_UNARY("I64.TRUNC_SAT_F32_S (too large)", f32, (float)INT64_MAX + 100.0f, i64, INT64_MAX); // Value greater than INT64_MAX should saturate to INT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F32_S (too small)", f32, (float)INT64_MIN - 100.0f, i64, INT64_MIN); // Value less than INT64_MIN should saturate to INT64_MIN
return failures;
}
int test_i64_trunc_sat_f32_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7d, 0x7e, 0x05);
printf("\n=== Testing I64.TRUNC_SAT_F32_U ===\n");
CHECK_UNARY("I64.TRUNC_SAT_F32_U (10.5f)", f32, 10.5f, i64, 10ULL);
CHECK_UNARY("I64.TRUNC_SAT_F32_U (NaN)", f32, NAN, i64, 0ULL); // NaN should result in 0
CHECK_UNARY("I64.TRUNC_SAT_F32_U (Infinity)", f32, INFINITY, i64, (int64_t)UINT64_MAX); // Positive Infinity should saturate to UINT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F32_U (-Infinity)", f32, -INFINITY, i64, 0ULL); // Negative Infinity should saturate to 0
CHECK_UNARY("I64.TRUNC_SAT_F32_U (too large)", f32, (float)UINT64_MAX + 100.0f, i64, (int64_t)UINT64_MAX); // Value greater than UINT64_MAX should saturate to UINT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F32_U (negative)", f32, -0.5f, i64, 0ULL); // Value less than 0 should saturate to 0
return failures;
}
int test_i64_trunc_sat_f64_s() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7c, 0x7e, 0x06);
printf("\n=== Testing I64.TRUNC_SAT_F64_S ===\n");
CHECK_UNARY("I64.TRUNC_SAT_F64_S (10.5)", f64, 10.5, i64, 10LL);
CHECK_UNARY("I64.TRUNC_SAT_F64_S (-10.5)", f64, -10.5, i64, -10LL);
CHECK_UNARY("I64.TRUNC_SAT_F64_S (NaN)", f64, NAN, i64, 0LL); // NaN should result in 0
CHECK_UNARY("I64.TRUNC_SAT_F64_S (Infinity)", f64, INFINITY, i64, INT64_MAX); // Positive Infinity should saturate to INT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F64_S (-Infinity)", f64, -INFINITY, i64, INT64_MIN); // Negative Infinity should saturate to INT64_MIN
CHECK_UNARY("I64.TRUNC_SAT_F64_S (too large)", f64, (double)INT64_MAX + 100.0, i64, INT64_MAX); // Value greater than INT64_MAX should saturate to INT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F64_S (too small)", f64, (double)INT64_MIN - 100.0, i64, INT64_MIN); // Value less than INT64_MIN should saturate to INT64_MIN
return failures;
}
int test_i64_trunc_sat_f64_u() {
int failures = 0;
static const uint8_t test_wasm[] = UNARY_TEST_WASM_FC(0x7c, 0x7e, 0x07);
printf("\n=== Testing I64.TRUNC_SAT_F64_U ===\n");
CHECK_UNARY("I64.TRUNC_SAT_F64_U (10.5)", f64, 10.5, i64, 10ULL);
CHECK_UNARY("I64.TRUNC_SAT_F64_U (NaN)", f64, NAN, i64, 0ULL); // NaN should result in 0
CHECK_UNARY("I64.TRUNC_SAT_F64_U (Infinity)", f64, INFINITY, i64, (int64_t)UINT64_MAX); // Positive Infinity should saturate to UINT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F64_U (-Infinity)", f64, -INFINITY, i64, 0ULL); // Negative Infinity should saturate to 0
CHECK_UNARY("I64.TRUNC_SAT_F64_U (too large)", f64, (double)UINT64_MAX + 100.0, i64, (int64_t)UINT64_MAX); // Value greater than UINT64_MAX should saturate to UINT64_MAX
CHECK_UNARY("I64.TRUNC_SAT_F64_U (negative)", f64, -0.5, i64, 0ULL); // Value less than 0 should saturate to 0
return failures;
}
int main() {
int total_failures = 0;
total_failures += test_i32_and();
total_failures += test_i32_eq();
total_failures += test_i32_popcnt();
total_failures += test_i64_clz();
total_failures += test_i32_rotl();
total_failures += test_f64_nearest();
total_failures += test_f32_min();
total_failures += test_i32_wrap_i64();
total_failures += test_i32_trunc_f32_s();
total_failures += test_i32_trunc_f32_u();
total_failures += test_i32_trunc_f64_s();
total_failures += test_i32_trunc_f64_u();
total_failures += test_i64_extend_i32_s();
total_failures += test_i64_extend_i32_u();
total_failures += test_i64_trunc_f32_s();
total_failures += test_i64_trunc_f32_u();
total_failures += test_i64_trunc_f64_s();
total_failures += test_i64_trunc_f64_u();
total_failures += test_f32_convert_i32_s();
total_failures += test_f32_convert_i32_u();
total_failures += test_f32_convert_i64_s();
total_failures += test_f32_convert_i64_u();
total_failures += test_f32_demote_f64();
total_failures += test_f64_convert_i32_s();
total_failures += test_f64_convert_i32_u();
total_failures += test_f64_convert_i64_s();
total_failures += test_f64_convert_i64_u();
total_failures += test_f64_promote_f32();
total_failures += test_i32_reinterpret_f32();
total_failures += test_i64_reinterpret_f64();
total_failures += test_f32_reinterpret_i32();
total_failures += test_f64_reinterpret_i64();
total_failures += test_i32_trunc_sat_f32_s();
total_failures += test_i32_trunc_sat_f32_u();
total_failures += test_i32_trunc_sat_f64_s();
total_failures += test_i32_trunc_sat_f64_u();
total_failures += test_i64_trunc_sat_f32_s();
total_failures += test_i64_trunc_sat_f32_u();
total_failures += test_i64_trunc_sat_f64_s();
total_failures += test_i64_trunc_sat_f64_u();
total_failures += test_i32_extend8_s();
total_failures += test_i32_extend16_s();
total_failures += test_i64_extend8_s();
total_failures += test_i64_extend16_s();
total_failures += test_i64_extend32_s();
if (total_failures > 0) {
printf("\nSUMMARY: %d test(s) FAILED!\n", total_failures);
return 1;
} else {
printf("\nSUMMARY: All tests PASSED!\n");
return 0;
}
}
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define WAH_IMPLEMENTATION
#include "wah.h"
// --- Test Case 1: ULEB128 value overflow ---
static const uint8_t wasm_binary_u32_overflow[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> ()
0x03, 0x02, 0x01, 0x00, // Func: type 0
0x0a, 0x08, 0x01, // Code Section size 8, 1 func
0x80, 0x80, 0x80, 0x80, 0x10, // Body size: ULEB128 for UINT32_MAX + 1
0x00, 0x0b, // Body content
};
// --- Test Case 2: SLEB128 value overflow ---
static const uint8_t wasm_binary_s32_overflow[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> ()
0x03, 0x02, 0x01, 0x00, // Func: type 0
0x0a, 0x09, 0x01, // Code Section size 9, 1 func
0x07, 0x00, // body size 7, 0 locals
0x41, // i32.const
0x80, 0x80, 0x80, 0x80, 0x08, // Value > INT32_MAX
0x0b, // end
};
// --- Test Case 3: SLEB128 value underflow ---
static const uint8_t wasm_binary_s32_underflow[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> ()
0x03, 0x02, 0x01, 0x00, // Func: type 0
0x0a, 0x09, 0x01, // Code Section size 9, 1 func
0x07, 0x00, // body size 7, 0 locals
0x41, // i32.const
0xff, 0xff, 0xff, 0xff, 0x77, // Value < INT32_MIN
0x0b, // end
};
// --- Test Case 4: Element segment address overflow ---
// This tests if (offset + num_elems) overflows the table bounds.
// The offset is a valid sleb128, but the resulting address is out of bounds.
static const uint8_t wasm_binary_elem_overflow[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> ()
0x03, 0x02, 0x01, 0x00, // Func: type 0
// Table: 1 table, min size 1000
0x04, 0x05, 0x01, 0x70, 0x00, 0xe8, 0x07,
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, // Code: 1 func, size 2, 0 locals, end
// Element Section
0x09, 0x1b, 0x01,
0x00, // table index 0
// offset expression: i32.const 990
0x41, 0xf6, 0x07, 0x0b,
// vector of 20 function indices
0x14, // 20 elements
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
// --- Test Case 5: Local count overflow in wah_parse_code_section ---
// This tests if current_local_count += local_type_count overflows uint32_t
// leading to an incorrect local_count and subsequent allocation failure or
// out-of-bounds access.
static const uint8_t wasm_binary_local_count_overflow[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // Magic + Version
// Type Section
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // Type: () -> ()
// Function Section
0x03, 0x02, 0x01, 0x00, // Func: type 0
// Code Section
0x0a, 0x0c, 0x01, // Code Section ID, size 12, 1 func
0x0a, // Code Body 0 size (10 bytes)
0x02, // num_local_entries = 2
// Entry 1: local_type_count = 0xFFFFFFFF (5 bytes ULEB128)
0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x7F, // i32 type
// Entry 2: local_type_count = 1 (1 byte ULEB128)
0x01, 0x7F, // i32 type
0x0B // Function Body: END opcode
};
int main(void) {
wah_module_t module;
wah_error_t err;
printf("--- Running Overflow Tests ---\n");
printf("1. Testing ULEB128 value overflow...\n");
err = wah_parse_module(wasm_binary_u32_overflow, sizeof(wasm_binary_u32_overflow), &module);
assert(err == WAH_ERROR_TOO_LARGE);
printf(" - PASSED\n");
printf("2. Testing SLEB128 value overflow...\n");
err = wah_parse_module(wasm_binary_s32_overflow, sizeof(wasm_binary_s32_overflow), &module);
assert(err == WAH_ERROR_TOO_LARGE);
printf(" - PASSED\n");
printf("3. Testing SLEB128 value underflow...\n");
err = wah_parse_module(wasm_binary_s32_underflow, sizeof(wasm_binary_s32_underflow), &module);
assert(err == WAH_ERROR_TOO_LARGE);
printf(" - PASSED\n");
printf("4. Testing element segment address overflow...\n");
err = wah_parse_module(wasm_binary_elem_overflow, sizeof(wasm_binary_elem_overflow), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
if (err == WAH_OK) {
printf(" - FAILED: Module parsing succeeded unexpectedly!\n");
wah_free_module(&module);
return 1;
} else {
printf(" - PASSED: Module parsing failed as expected with error: %s\n", wah_strerror(err));
}
printf("5. Testing local count overflow...\n");
err = wah_parse_module(wasm_binary_local_count_overflow, sizeof(wasm_binary_local_count_overflow), &module);
assert(err == WAH_ERROR_TOO_LARGE); // Expecting TOO_LARGE due to overflow check
printf(" - PASSED\n");
printf("--- All Overflow Tests Passed ---\n");
return 0;
}
#include <stdio.h>
#include <string.h>
#include <assert.h>
#define WAH_IMPLEMENTATION
#include "wah.h"
// This WASM module contains a function type with zero parameters and zero results.
// This is used to test that the parser correctly handles zero-count vectors,
// specifically avoiding `malloc(0)` which has implementation-defined behavior.
static const uint8_t wasm_binary_zero_params[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section: 1 type, () -> ()
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function Section: 1 function, type 0
0x03, 0x02, 0x01, 0x00,
// Code Section: 1 function body
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, // size 2, 0 locals, end
};
// This WASM module has a function section but no code section.
// This should result in WAH_ERROR_VALIDATION_FAILED because module->function_count will be > 0
// but module->code_count will be 0.
static const uint8_t wasm_binary_func_no_code_section[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section: 1 type, () -> ()
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function Section: 1 function, type 0
0x03, 0x02, 0x01, 0x00,
};
// This WASM module has a code section but no function section.
// This should result in WAH_ERROR_VALIDATION_FAILED because module->function_count will be 0
// but the code section count will be > 0.
static const uint8_t wasm_binary_code_no_func_section[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section: 1 type, () -> ()
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Code Section: 1 function body (but no function section declared)
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b, // size 2, 0 locals, end
};
// This WASM module has the Memory Section before the Table Section (invalid order)
static const uint8_t wasm_binary_invalid_section_order_mem_table[] = {
// Magic + Version
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type Section
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Memory Section (ID 5) - Should come after Table Section (ID 4)
0x05, 0x04, // Section ID 5, size 4
0x01, // 1 memory
0x00, // flags (fixed size)
0x01, // min_pages = 1
// Table Section (ID 4)
0x04, 0x04, // Section ID 4, size 4
0x01, // 1 table
0x70, // elem_type = funcref
0x00, // flags (fixed size)
0x01, // min_elements = 1
// Function Section
0x03, 0x02, 0x01, 0x00,
// Code Section
0x0a, 0x04, 0x01, 0x02, 0x00, 0x0b,
};
int test_invalid_element_segment_func_idx() {
printf("Running test_invalid_element_segment_func_idx...\n");
// Minimal WASM binary with an element section referencing an out-of-bounds function index
// (module
// (type $0 (func))
// (func $f0 (type $0) nop)
// (table $0 1 funcref)
// (elem $0 (i32.const 0) $f0 $f1) ;; $f1 does not exist, func_idx 1 is out of bounds
// )
const uint8_t wasm_binary[] = {
0x00, 0x61, 0x73, 0x6D, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type Section (id 1)
0x01, // Section ID
0x04, // Section Size
0x01, // Count of types
0x60, // Func type
0x00, // Param count
0x00, // Result count
// Function Section (id 3)
0x03, // Section ID
0x02, // Section Size
0x01, // Count of functions
0x00, // Type index 0 for function 0
// Table Section (id 4)
0x04, // Section ID
0x04, // Section Size
0x01, // Count of tables
0x70, // funcref type
0x00, // flags: not resizable
0x01, // min elements: 1
// Element Section (id 9)
0x09, // Section ID
0x08, // Section Size
0x01, // Count of element segments
0x00, // Table index 0
0x41, // opcode i32.const
0x00, // value 0
0x0B, // opcode end
0x02, // num_elems: 2
0x00, // func_idx 0 (valid)
0x01, // func_idx 1 (INVALID - only 1 function exists)
// Code Section (id 10)
0x0A, // Section ID
0x05, // Section Size
0x01, // Count of code bodies
0x02, // Code body size for function 0
0x00, // Num locals
0x01, // Nop opcode
0x0B, // End opcode
};
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
// Expecting validation failure due to out-of-bounds function index
if (err != WAH_ERROR_VALIDATION_FAILED) {
fprintf(stderr, "Assertion failed: Expected WAH_ERROR_VALIDATION_FAILED for invalid function index in element segment, got %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
printf(" - PASSED: Invalid element segment function index correctly failed validation.\n");
wah_free_module(&module);
return 0;
}
int test_code_section_no_function_section() {
printf("Running test_code_section_no_function_section...\n");
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary_code_no_func_section, sizeof(wasm_binary_code_no_func_section), &module);
if (err != WAH_ERROR_VALIDATION_FAILED) {
fprintf(stderr, "Assertion failed: Expected WAH_ERROR_VALIDATION_FAILED for code section without function section, got %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
printf(" - PASSED: Code section without function section correctly failed validation.\n");
wah_free_module(&module);
return 0;
}
int test_function_section_no_code_section() {
printf("Running test_function_section_no_code_section...\n");
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary_func_no_code_section, sizeof(wasm_binary_func_no_code_section), &module);
if (err != WAH_ERROR_VALIDATION_FAILED) {
fprintf(stderr, "Assertion failed: Expected WAH_ERROR_VALIDATION_FAILED for function section without code section, got %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
printf(" - PASSED: Function section without code section correctly failed validation.\n");
wah_free_module(&module);
return 0;
}
// Test case for parsing a module with a data section but no datacount section,
// and a code section using memory.init. This should fail validation currently.
static int test_parse_data_no_datacount_memory_init_fails() {
printf("Running test_parse_data_no_datacount_memory_init_fails...\n");
const uint8_t wasm_binary[] = {
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section (1)
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function section (3)
0x03, 0x02, 0x01, 0x00,
// Memory section (5)
0x05, 0x03, 0x01, 0x00, 0x01,
// Export section (7)
0x07, 0x16, 0x02, // Section ID, size, count
0x06, 'm', 'e', 'm', 'o', 'r', 'y', 0x02, 0x00, // Export "memory"
0x09, 't', 'e', 's', 't', '_', 'f', 'u', 'n', 'c', 0x00, 0x00, // Export "test_func"
// Code section (10)
0x0a, 0x0e, 0x01, // Section ID, size, count
0x0c, // Code size for func 0
0x00, // 0 locals
0x41, 0x00, // i32.const 0
0x41, 0x00, // i32.const 0
0x41, 0x05, // i32.const 5
0xfc, 0x08, 0x00, 0x00, // memory.init 0 0
0x0b, // end
// Data section (11)
0x0b, 0x0b, 0x01, // Section ID, size, count
0x00, // Flags (active, memory 0)
0x41, 0x00, 0x0b, // Offset expression (i32.const 0 end)
0x05, // Data length
'h', 'e', 'l', 'l', 'o' // Data bytes
};
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
if (err != WAH_OK) {
fprintf(stderr, "ERROR: test_parse_data_no_datacount_memory_init_fails FAILED, but should have PASSED! Error: %s\n", wah_strerror(err));
exit(1);
}
wah_free_module(&module);
printf("test_parse_data_no_datacount_memory_init_fails passed (as expected, it passed validation).\n");
return 0;
}
// Test case for deferred data segment validation failure.
// No datacount section, one data segment (index 0), but memory.init tries to use data_idx 1.
static int test_deferred_data_validation_failure() {
printf("Running test_deferred_data_validation_failure...\n");
const uint8_t wasm_binary[] = {
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section (1)
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function section (3)
0x03, 0x02, 0x01, 0x00,
// Memory section (5)
0x05, 0x03, 0x01, 0x00, 0x01,
// Export section (7)
0x07, 0x16, 0x02, // Section ID, size, count
0x06, 'm', 'e', 'm', 'o', 'r', 'y', 0x02, 0x00, // Export "memory"
0x09, 't', 'e', 's', 't', '_', 'f', 'u', 'n', 'c', 0x00, 0x00, // Export "test_func"
// Code section (10)
0x0a, 0x0e, 0x01, // Section ID, size, count
0x0c, // Code body size for func 0
0x00, // 0 locals
0x41, 0x00, // i32.const 0 (dest_offset)
0x41, 0x00, // i32.const 0 (src_offset)
0x41, 0x05, // i32.const 5 (size)
0xfc, 0x08, 0x01, 0x00, // memory.init data_idx=1, mem_idx=0 (data_idx 1 is out of bounds)
0x0b, // end
// Data section (11)
0x0b, 0x0b, 0x01, // Section ID, size, count (only 1 data segment)
0x00, // Flags (active, memory 0)
0x41, 0x00, 0x0b, // Offset expression (i32.const 0 end)
0x05, // Data length
'h', 'e', 'l', 'l', 'o' // Data bytes
};
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
if (err != WAH_ERROR_VALIDATION_FAILED) {
fprintf(stderr, "ERROR: test_deferred_data_validation_failure FAILED: Expected WAH_ERROR_VALIDATION_FAILED, got %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
wah_free_module(&module);
printf(" - PASSED: Deferred data validation failure correctly detected.\n");
return 0;
}
// Test case for an unused opcode (0x09) in the code section
static int test_unused_opcode_validation_failure() {
printf("Running test_unused_opcode_validation_failure...\n");
const uint8_t wasm_binary[] = {
0x00, 0x61, 0x73, 0x6d, // Magic
0x01, 0x00, 0x00, 0x00, // Version
// Type section (1)
0x01, 0x04, 0x01, 0x60, 0x00, 0x00, // (func) -> ()
// Function section (3)
0x03, 0x02, 0x01, 0x00, // func 0 uses type 0
// Code section (10)
0x0a, 0x05, 0x01, // Section ID, size, count
0x03, // Code body size for func 0 (0 locals + 2 bytes for opcode + end)
0x00, // 0 locals
0x09, // Unused opcode (0x09)
0x0b, // End opcode
};
wah_module_t module;
memset(&module, 0, sizeof(wah_module_t));
wah_error_t err = wah_parse_module(wasm_binary, sizeof(wasm_binary), &module);
if (err != WAH_ERROR_VALIDATION_FAILED) {
fprintf(stderr, "ERROR: test_unused_opcode_validation_failure FAILED: Expected WAH_ERROR_VALIDATION_FAILED, got %s\n", wah_strerror(err));
wah_free_module(&module);
return 1;
}
wah_free_module(&module);
printf(" - PASSED: Unused opcode correctly failed validation.\n");
return 0;
}
int main(void) {
int result = 0;
wah_module_t module;
wah_error_t err;
printf("--- Running Parser Correctness Test ---\n");
printf("Testing function type with 0 params and 0 results...\n");
err = wah_parse_module(wasm_binary_zero_params, sizeof(wasm_binary_zero_params), &module);
assert(err == WAH_OK);
if (err == WAH_OK) {
printf(" - PASSED: Module with zero-count types parsed successfully.\n");
wah_free_module(&module);
} else {
printf(" - FAILED: Module parsing failed with error: %s\n", wah_strerror(err));
return 1;
}
printf("Testing invalid section order (Memory before Table)...\n");
err = wah_parse_module(wasm_binary_invalid_section_order_mem_table, sizeof(wasm_binary_invalid_section_order_mem_table), &module);
assert(err == WAH_ERROR_VALIDATION_FAILED);
result |= test_invalid_element_segment_func_idx();
result |= test_code_section_no_function_section();
result |= test_function_section_no_code_section();
result |= test_parse_data_no_datacount_memory_init_fails();
result |= test_deferred_data_validation_failure();
result |= test_unused_opcode_validation_failure();
if (result == 0) {
printf("All parser tests passed!\n");
} else {
printf("Parser tests failed!\n");
}
return result;
}
#define WAH_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // For memcmp
#include "wah.h"
// Helper to encode a 32-bit signed integer as LEB128. Returns number of bytes written.
static size_t encode_s32_leb128(int32_t value, uint8_t* out) {
size_t i = 0;
while (true) {
uint8_t byte = value & 0x7f;
value >>= 7;
if ((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0)) {
out[i++] = byte;
break;
}
out[i++] = byte | 0x80;
}
return i;
}
// Helper to encode a 64-bit signed integer as LEB128. Returns number of bytes written.
static size_t encode_s64_leb128(int64_t value, uint8_t* out) {
size_t i = 0;
while (true) {
uint8_t byte = value & 0x7f;
value >>= 7;
if ((value == 0 && (byte & 0x40) == 0) || (value == -1 && (byte & 0x40) != 0)) {
out[i++] = byte;
break;
}
out[i++] = byte | 0x80;
}
return i;
}
// Helper to patch a scalar constant (xxx.const) into a WASM binary.
// wasm_binary_ptr points to the location where the opcode (0x41, 0x42, 0x43, 0x44) should be written.
// It assumes 10 bytes are reserved for the opcode + value (1 byte for opcode, 9 for value).
static wah_error_t patch_scalar_const(uint8_t* wasm_binary_ptr, const wah_value_t* operand_scalar, wah_type_t scalar_type) {
// Clear the 10-byte value placeholder with NOPs (0x01)
memset(wasm_binary_ptr, 0x01, 10);
switch (scalar_type) {
case WAH_TYPE_I32:
wasm_binary_ptr[0] = 0x41; // i32.const
encode_s32_leb128(operand_scalar->i32, wasm_binary_ptr + 1);
break;
case WAH_TYPE_I64:
wasm_binary_ptr[0] = 0x42; // i64.const
encode_s64_leb128(operand_scalar->i64, wasm_binary_ptr + 1);
break;
case WAH_TYPE_F32:
wasm_binary_ptr[0] = 0x43; // f32.const
memcpy(wasm_binary_ptr + 1, &operand_scalar->f32, 4);
break;
case WAH_TYPE_F64:
wasm_binary_ptr[0] = 0x44; // f64.const
memcpy(wasm_binary_ptr + 1, &operand_scalar->f64, 8);
break;
default:
fprintf(stderr, "Unsupported scalar type for patch_scalar_const: %d\n", scalar_type);
return WAH_ERROR_MISUSE;
}
return WAH_OK;
}
// Helper to compare v128 results and print byte-by-byte if they don't match.
static wah_error_t compare_and_print_v128_result(const char* test_name, const wah_v128_t* actual, const wah_v128_t* expected) {
if (memcmp(actual, expected, sizeof(wah_v128_t)) == 0) {
printf("Result v128 matches expected value for %s.\n", test_name);
return WAH_OK;
} else {
fprintf(stderr, "Result v128 does NOT match expected value for %s.\n", test_name);
fprintf(stderr, "Expected: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", expected->u8[i]);
}
fprintf(stderr, "\nActual: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", actual->u8[i]);
}
fprintf(stderr, "\n");
return WAH_ERROR_VALIDATION_FAILED; // Indicate test failure
}
}
// Placeholders for v128 arguments (16 bytes)
#define V128_ARG1_PLACEHOLDER '(', 'v', '1', '2', '8', '_', 'a', 'r', 'g', 'u', 'm', 'e', 'n', 't', '1', ')'
#define V128_ARG2_PLACEHOLDER '(', 'v', '1', '2', '8', '_', 'a', 'r', 'g', 'u', 'm', 'e', 'n', 't', '2', ')'
#define V128_ARG3_PLACEHOLDER '(', 'v', '1', '2', '8', '_', 'a', 'r', 'g', 'u', 'm', 'e', 'n', 't', '3', ')'
// Placeholder for scalar operand (10 bytes; 1 byte opcode + up to 9 bytes LEB128)
#define SCALAR_OP_PLACEHOLDER '(', 's', 'c', 'a', 'l', 'a', 'r', 'o', 'p', ')'
static const uint8_t v128_arg1_placeholder[] = {V128_ARG1_PLACEHOLDER};
static const uint8_t v128_arg2_placeholder[] = {V128_ARG2_PLACEHOLDER};
static const uint8_t v128_arg3_placeholder[] = {V128_ARG3_PLACEHOLDER};
static const uint8_t scalar_op_placeholder[] = {SCALAR_OP_PLACEHOLDER};
// Helper to find the offset of a byte sequence (placeholder) in a WASM binary.
// Returns a pointer to the found location on success, or NULL if the placeholder is not found.
static uint8_t* find_placeholder_location(uint8_t* wasm_binary, size_t wasm_size,
const uint8_t* placeholder, size_t placeholder_size) {
for (size_t i = 0; i <= wasm_size - placeholder_size; ++i) {
if (memcmp(wasm_binary + i, placeholder, placeholder_size) == 0) {
return wasm_binary + i;
}
}
return NULL; // Placeholder not found
}
// Helper to set up module and execution context
static wah_error_t wah_test_setup_context(
const char* test_name,
const uint8_t* wasm_binary,
size_t wasm_size,
wah_module_t* out_module,
wah_exec_context_t* out_ctx
) {
wah_error_t err;
printf("\n--- Testing %s ---\n", test_name);
printf("Parsing module...\n");
err = wah_parse_module(wasm_binary, wasm_size, out_module);
if (err != WAH_OK) {
fprintf(stderr, "Error parsing %s module: %s\n", test_name, wah_strerror(err));
return err;
}
printf("Module parsed successfully.\n");
err = wah_exec_context_create(out_ctx, out_module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for %s: %s\n", test_name, wah_strerror(err));
wah_free_module(out_module);
return err;
}
return WAH_OK;
}
// Helper to execute a WASM function and return its result
static wah_error_t wah_test_execute_function(
const char* test_name,
wah_exec_context_t* ctx,
wah_module_t* module,
wah_value_t* out_result
) {
wah_error_t err;
uint32_t func_idx = 0; // Assuming the test function is always at index 0
printf("Interpreting function %u...\n", func_idx);
err = wah_call(ctx, module, func_idx, NULL, 0, out_result);
if (err != WAH_OK) {
fprintf(stderr, "Error interpreting function for %s: %s\n", test_name, wah_strerror(err));
return err;
}
printf("Function interpreted successfully.\n");
return WAH_OK;
}
// A simple WebAssembly binary for:
// (module
// (func (result v128)
// (v128.const i8x16 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0A 0x0B 0x0C 0x0D 0x0E 0x0F 0x10)
// )
// )
const uint8_t v128_const_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x05, // section size
0x01, // num types
0x60, // func type
0x00, // num params
0x01, // num results
0x7b, // v128
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Code section
0x0a, // section ID
0x16, // section size
0x01, // num code bodies
0x14, // code body size (for the first function)
0x00, // num locals
0xfd, 0x0c, // v128.const
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // 16-byte constant (little-endian)
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10,
0x0b, // end
};
// (module
// (memory 1)
// (func (result v128)
// i32.const 0
// v128.const i8x16 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 0x99 0xAA 0xBB 0xCC 0xDD 0xEE 0xFF 0x00
// v128.store 0 0 ;; align=0, offset=0
// i32.const 0
// v128.load 0 0 ;; align=0, offset=0
// )
// )
const uint8_t v128_load_store_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, // section ID
0x05, // section size
0x01, // num types
0x60, // func type
0x00, // num params
0x01, // num results
0x7b, // v128
// Function section
0x03, // section ID
0x02, // section size
0x01, // num functions
0x00, // type index 0
// Memory section
0x05, // section ID
0x03, // section size
0x01, // num memories
0x00, // memory type (min 1 page, max not present)
0x01, // min pages
// Code section
0x0a, // section ID
0x22, // section size
0x01, // num code bodies
0x20, // code body size (for the first function)
0x00, // num locals
0x41, 0x00, // i32.const 0
0xfd, 0x0c, // v128.const
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88,
0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00,
0xfd, 0x0b, 0x00, 0x00, // v128.store align=0, offset=0
0x41, 0x00, // i32.const 0
0xfd, 0x00, 0x00, 0x00, // v128.load align=0, offset=0
0x0b, // end
};
void test_v128_load_store() {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
const char* test_name = "v128.load and v128.store";
err = wah_test_setup_context(test_name, v128_load_store_wasm, sizeof(v128_load_store_wasm), &module, &ctx);
if (err != WAH_OK) {
exit(1);
}
wah_value_t result;
uint8_t expected_v128_val[] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00};
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
exit(1);
}
if (memcmp(&result.v128, expected_v128_val, sizeof(wah_v128_t)) == 0) {
printf("Result v128 matches expected value.\n");
} else {
fprintf(stderr, "Result v128 does NOT match expected value.\n");
fprintf(stderr, "Expected: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", expected_v128_val[i]);
}
fprintf(stderr, "\nActual: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", result.v128.u8[i]);
}
fprintf(stderr, "\n");
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
exit(1);
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
}
void test_v128_const() {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
const char* test_name = "v128.const";
err = wah_test_setup_context(test_name, v128_const_wasm, sizeof(v128_const_wasm), &module, &ctx);
if (err != WAH_OK) {
exit(1);
}
wah_value_t result;
uint8_t expected_v128_val[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
exit(1);
}
if (memcmp(&result.v128, expected_v128_val, sizeof(wah_v128_t)) == 0) {
printf("Result v128 matches expected value.\n");
} else {
fprintf(stderr, "Result v128 does NOT match expected value.\n");
fprintf(stderr, "Expected: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", expected_v128_val[i]);
}
fprintf(stderr, "\nActual: ");
for (int i = 0; i < 16; ++i) {
fprintf(stderr, "%02x ", result.v128.u8[i]);
}
fprintf(stderr, "\n");
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
exit(1);
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
}
#define LOAD_TEST_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x05, 0x03, 0x01, 0x00, 0x01, \
0x0a, 0x0a, 0x01, 0x08, 0x00, 0x41, 0x00, 0xfd, subopcode, 0x00, 0x00, 0x0b, \
}
const uint8_t v128_load8x8_s_wasm[] = LOAD_TEST_WASM(0x01);
const uint8_t v128_load8x8_u_wasm[] = LOAD_TEST_WASM(0x02);
const uint8_t v128_load16x4_s_wasm[] = LOAD_TEST_WASM(0x03);
const uint8_t v128_load16x4_u_wasm[] = LOAD_TEST_WASM(0x04);
const uint8_t v128_load32x2_s_wasm[] = LOAD_TEST_WASM(0x05);
const uint8_t v128_load32x2_u_wasm[] = LOAD_TEST_WASM(0x06);
const uint8_t v128_load8_splat_wasm[] = LOAD_TEST_WASM(0x07);
const uint8_t v128_load16_splat_wasm[] = LOAD_TEST_WASM(0x08);
const uint8_t v128_load32_splat_wasm[] = LOAD_TEST_WASM(0x09);
const uint8_t v128_load64_splat_wasm[] = LOAD_TEST_WASM(0x0a);
const uint8_t v128_load32_zero_wasm[] = LOAD_TEST_WASM(0x5c);
const uint8_t v128_load64_zero_wasm[] = LOAD_TEST_WASM(0x5d);
// (i32.const 0) (v128.const 0 ... 0) (i32.const 0) (v128.loadN_lane align=0 offset=0 lane_idx=0)
#define LOAD_LANE_TEST_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x05, 0x03, 0x01, 0x00, 0x01, \
0x0a, 0x1f, 0x01, 0x1d, 0x00, \
0x41, 0x00, \
0xfd, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
0x41, 0x00, \
0xfd, subopcode, 0x00, 0x00, 0x00, \
0x0b, \
}
const uint8_t v128_load8_lane_wasm[] = LOAD_LANE_TEST_WASM(0x54);
const uint8_t v128_load16_lane_wasm[] = LOAD_LANE_TEST_WASM(0x55);
const uint8_t v128_load32_lane_wasm[] = LOAD_LANE_TEST_WASM(0x56);
const uint8_t v128_load64_lane_wasm[] = LOAD_LANE_TEST_WASM(0x57);
// Generic WASM template for binary SIMD operations (pop 2 v128, push 1 v128)
// (module
// (func (result v128)
// (v128.const ...)
// (v128.const ...)
// (subopcode)
// )
// )
#define BINARY_OP_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x2b, 0x01, 0x29, 0x00, \
0xfd, 0x0c, V128_ARG1_PLACEHOLDER, \
0xfd, 0x0c, V128_ARG2_PLACEHOLDER, \
0xfd, (subopcode & 0x7f) | 0x80, subopcode >> 7, \
0x0b, \
}
// Generic WASM template for ternary SIMD operations (pop 3 v128, push 1 v128)
// (module
// (func (result v128)
// (v128.const ...)
// (v128.const ...)
// (v128.const ...)
// (subopcode)
// )
// )
#define TERNARY_OP_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x3d, 0x01, 0x3b, 0x00, \
0xfd, 0x0c, V128_ARG1_PLACEHOLDER, \
0xfd, 0x0c, V128_ARG2_PLACEHOLDER, \
0xfd, 0x0c, V128_ARG3_PLACEHOLDER, \
0xfd, (subopcode & 0x7f) | 0x80, subopcode >> 7, \
0x0b, \
}
wah_error_t run_v128_load_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const uint8_t* expected_val, const uint8_t* initial_memory, size_t initial_memory_size) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
err = wah_test_setup_context(test_name, wasm_binary, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
if (initial_memory && initial_memory_size > 0) {
if (initial_memory_size > ctx.memory_size) {
fprintf(stderr, "Error: Initial memory size (%zu) exceeds module memory size (%zu) for %s\n",
initial_memory_size, (size_t)ctx.memory_size, test_name);
err = WAH_ERROR_MISUSE;
} else {
memcpy(ctx.memory_base, initial_memory, initial_memory_size);
}
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_v128_load_test(n,b,v,m,s) run_v128_load_test(n,b,sizeof(b),v,m,s)
wah_error_t run_simd_binary_op_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand1, const wah_v128_t* operand2, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand1, sizeof(wah_v128_t));
uint8_t* loc2 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg2_placeholder, sizeof(v128_arg2_placeholder));
if (!loc2) { fprintf(stderr, "Error: V128_ARG2_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc2, operand2, sizeof(wah_v128_t));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_binary_op_test(n,b,o1,o2,e) run_simd_binary_op_test(n,b,sizeof(b),o1,o2,e)
wah_error_t run_simd_ternary_op_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand1, const wah_v128_t* operand2, const wah_v128_t* operand3, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand1, sizeof(wah_v128_t));
uint8_t* loc2 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg2_placeholder, sizeof(v128_arg2_placeholder));
if (!loc2) { fprintf(stderr, "Error: V128_ARG2_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc2, operand2, sizeof(wah_v128_t));
uint8_t* loc3 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg3_placeholder, sizeof(v128_arg3_placeholder));
if (!loc3) { fprintf(stderr, "Error: V128_ARG3_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc3, operand3, sizeof(wah_v128_t));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_ternary_op_test(n,b,o1,o2,o3,e) run_simd_ternary_op_test(n,b,sizeof(b),o1,o2,o3,e)
// Generic WASM template for unary SIMD operations (pop 1 v128, push 1 v128)
// (module
// (func (result v128)
// (v128.const ...)
// (subopcode)
// )
// )
#define UNARY_OP_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x19, 0x01, 0x17, 0x00, \
0xfd, 0x0c, V128_ARG1_PLACEHOLDER, \
0xfd, (subopcode & 0x7f) | 0x80, subopcode >> 7, \
0x0b, \
}
// WASM template for _splat (pop 1 scalar, push 1 v128)
#define SPLAT_WASM(subopcode) { \
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, \
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b, \
0x03, 0x02, 0x01, 0x00, \
0x0a, 0x10, 0x01, 0x0e, 0x00, \
SCALAR_OP_PLACEHOLDER, \
0xfd, subopcode, \
0x0b, \
}
wah_error_t run_simd_unary_op_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand, sizeof(wah_v128_t));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_unary_op_test(n,b,o,e) run_simd_unary_op_test(n,b,sizeof(b),o,e)
wah_error_t run_simd_binary_op_i32_shift_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand1, const wah_value_t* operand_scalar, wah_type_t scalar_type, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand1, sizeof(wah_v128_t));
uint8_t* loc2 = find_placeholder_location(wasm_binary_copy, wasm_size, scalar_op_placeholder, sizeof(scalar_op_placeholder));
if (!loc2) { fprintf(stderr, "Error: SCALAR_OP_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
WAH_CHECK(patch_scalar_const(loc2, operand_scalar, scalar_type));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_binary_op_i32_shift_test(n,b,o1,o_scalar,s_type,e) run_simd_binary_op_i32_shift_test(n,b,sizeof(b),o1,o_scalar,s_type,e)
wah_error_t run_simd_shuffle_swizzle_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
err = wah_test_setup_context(test_name, wasm_binary, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_shuffle_swizzle_test(n,b,e) run_simd_shuffle_swizzle_test(n,b,sizeof(b),e)
wah_error_t run_simd_extract_lane_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand, const wah_value_t* expected_val, wah_type_t expected_type) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, (const uint8_t*)v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand, sizeof(wah_v128_t));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
bool match = false;
switch (expected_type) {
case WAH_TYPE_I32: match = (result.i32 == expected_val->i32); break;
case WAH_TYPE_I64: match = (result.i64 == expected_val->i64); break;
case WAH_TYPE_F32: match = (result.f32 == expected_val->f32); break;
case WAH_TYPE_F64: match = (result.f64 == expected_val->f64); break;
default: match = false; break;
}
if (match) {
printf("Result scalar matches expected value for %s.\n", test_name);
} else {
fprintf(stderr, "Result scalar does NOT match expected value for %s.\n", test_name);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return WAH_ERROR_VALIDATION_FAILED; // Indicate test failure
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return WAH_OK;
}
#define run_simd_extract_lane_test(n,b,o,e,t) run_simd_extract_lane_test(n,b,sizeof(b),o,e,t)
wah_error_t run_simd_replace_lane_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand_v128, const wah_value_t* operand_scalar, wah_type_t scalar_type, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, (const uint8_t*)v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand_v128, sizeof(wah_v128_t));
uint8_t* loc2 = find_placeholder_location(wasm_binary_copy, wasm_size, scalar_op_placeholder, sizeof(scalar_op_placeholder));
if (!loc2) { fprintf(stderr, "Error: SCALAR_OP_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
WAH_CHECK(patch_scalar_const(loc2, operand_scalar, scalar_type));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_replace_lane_test(n,b,o_v128,o_scalar,s_type,e) run_simd_replace_lane_test(n,b,sizeof(b),o_v128,o_scalar,s_type,e)
wah_error_t run_simd_splat_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_value_t* operand_scalar, wah_type_t scalar_type, const wah_v128_t* expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, scalar_op_placeholder, sizeof(scalar_op_placeholder));
if (!loc1) { fprintf(stderr, "Error: SCALAR_OP_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
WAH_CHECK(patch_scalar_const(loc1, operand_scalar, scalar_type));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
err = compare_and_print_v128_result(test_name, &result.v128, (const wah_v128_t*)expected_val);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_splat_test(n,b,o_scalar,s_type,e) run_simd_splat_test(n,b,sizeof(b),o_scalar,s_type,e)
void test_all_v128_loads() {
wah_error_t err;
// Initial memory for all load tests
uint8_t initial_mem[16] = {0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88, 0x09, 0x8A, 0x0B, 0x8C, 0x0D, 0x8E, 0x0F, 0x90};
// v128.load8x8_s
uint8_t expected_8x8_s[16] = {
0x01, 0x00, 0x82, 0xFF, 0x03, 0x00, 0x84, 0xFF, 0x05, 0x00, 0x86, 0xFF, 0x07, 0x00, 0x88, 0xFF
}; // 0x01, 0x82(-126), 0x03, 0x84(-124), 0x05, 0x86(-122), 0x07, 0x88(-120) sign-extended to 16-bit
err = run_v128_load_test("v128.load8x8_s", v128_load8x8_s_wasm, expected_8x8_s, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load8x8_u
uint8_t expected_8x8_u[16] = {
0x01, 0x00, 0x82, 0x00, 0x03, 0x00, 0x84, 0x00, 0x05, 0x00, 0x86, 0x00, 0x07, 0x00, 0x88, 0x00
}; // 0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88
err = run_v128_load_test("v128.load8x8_u", v128_load8x8_u_wasm, expected_8x8_u, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load16x4_s
uint8_t expected_16x4_s[16] = {
0x01, 0x82, 0xFF, 0xFF, 0x03, 0x84, 0xFF, 0xFF, 0x05, 0x86, 0xFF, 0xFF, 0x07, 0x88, 0xFF, 0xFF
}; // 0x8201, 0x8403, 0x8605, 0x8807 sign-extended to 32-bit
err = run_v128_load_test("v128.load16x4_s", v128_load16x4_s_wasm, expected_16x4_s, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load16x4_u
uint8_t expected_16x4_u[16] = {
0x01, 0x82, 0x00, 0x00, 0x03, 0x84, 0x00, 0x00, 0x05, 0x86, 0x00, 0x00, 0x07, 0x88, 0x00, 0x00
}; // 0x8201, 0x8403, 0x8605, 0x8807 zero-extended to 32-bit
err = run_v128_load_test("v128.load16x4_u", v128_load16x4_u_wasm, expected_16x4_u, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load32x2_s
uint8_t expected_32x2_s[16] = {
0x01, 0x82, 0x03, 0x84, 0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x86, 0x07, 0x88, 0xFF, 0xFF, 0xFF, 0xFF
}; // 0x84038201, 0x88078605 sign-extended to 64-bit
err = run_v128_load_test("v128.load32x2_s", v128_load32x2_s_wasm, expected_32x2_s, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load32x2_u
uint8_t expected_32x2_u[16] = {
0x01, 0x82, 0x03, 0x84, 0x00, 0x00, 0x00, 0x00, 0x05, 0x86, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00
}; // 0x84038201, 0x88078605 zero-extended to 64-bit
err = run_v128_load_test("v128.load32x2_u", v128_load32x2_u_wasm, expected_32x2_u, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load8_splat
uint8_t expected_8_splat[16] = {
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
}; // 0x01 splatted
err = run_v128_load_test("v128.load8_splat", v128_load8_splat_wasm, expected_8_splat, initial_mem, 1);
if (err != WAH_OK) { exit(1); }
// v128.load16_splat
uint8_t expected_16_splat[16] = {
0x01, 0x82, 0x01, 0x82, 0x01, 0x82, 0x01, 0x82, 0x01, 0x82, 0x01, 0x82, 0x01, 0x82, 0x01, 0x82
}; // 0x8201 splatted
err = run_v128_load_test("v128.load16_splat", v128_load16_splat_wasm, expected_16_splat, initial_mem, 2);
if (err != WAH_OK) { exit(1); }
// v128.load32_splat
uint8_t expected_32_splat[16] = {
0x01, 0x82, 0x03, 0x84, 0x01, 0x82, 0x03, 0x84, 0x01, 0x82, 0x03, 0x84, 0x01, 0x82, 0x03, 0x84
}; // 0x84038201 splatted
err = run_v128_load_test("v128.load32_splat", v128_load32_splat_wasm, expected_32_splat, initial_mem, 4);
if (err != WAH_OK) { exit(1); }
// v128.load64_splat
uint8_t expected_64_splat[16] = {
0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88, 0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88
}; // 0x8807860584038201 splatted
err = run_v128_load_test("v128.load64_splat", v128_load64_splat_wasm, expected_64_splat, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load32_zero
uint8_t expected_32_zero[16] = {
0x01, 0x82, 0x03, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // 0x84038201, rest zero
err = run_v128_load_test("v128.load32_zero", v128_load32_zero_wasm, expected_32_zero, initial_mem, 4);
if (err != WAH_OK) { exit(1); }
// v128.load64_zero
uint8_t expected_64_zero[16] = {
0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // 0x8807860584038201, rest zero
err = run_v128_load_test("v128.load64_zero", v128_load64_zero_wasm, expected_64_zero, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
// v128.load8_lane
uint8_t expected_8_lane[16] = {
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // initial all zeros, lane 0 gets 0x01
err = run_v128_load_test("v128.load8_lane", v128_load8_lane_wasm, expected_8_lane, initial_mem, 1);
if (err != WAH_OK) { exit(1); }
// v128.load16_lane
uint8_t expected_16_lane[16] = {
0x01, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // initial all zeros, lane 0 gets 0x8201
err = run_v128_load_test("v128.load16_lane", v128_load16_lane_wasm, expected_16_lane, initial_mem, 2);
if (err != WAH_OK) { exit(1); }
// v128.load32_lane
uint8_t expected_32_lane[16] = {
0x01, 0x82, 0x03, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // initial all zeros, lane 0 gets 0x84038201
err = run_v128_load_test("v128.load32_lane", v128_load32_lane_wasm, expected_32_lane, initial_mem, 4);
if (err != WAH_OK) { exit(1); }
// v128.load64_lane
uint8_t expected_64_lane[16] = {
0x01, 0x82, 0x03, 0x84, 0x05, 0x86, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
}; // initial all zeros, lane 0 gets 0x8807860584038201
err = run_v128_load_test("v128.load64_lane", v128_load64_lane_wasm, expected_64_lane, initial_mem, 8);
if (err != WAH_OK) { exit(1); }
}
// Test cases for v128.not
const uint8_t v128_not_wasm[] = UNARY_OP_WASM(0x4d);
void test_v128_not() {
wah_v128_t operand = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t expected = {{0xFE, 0xFD, 0xFC, 0xFB, 0xFA, 0xF9, 0xF8, 0xF7, 0xF6, 0xF5, 0xF4, 0xF3, 0xF2, 0xF1, 0xF0, 0xEF}};
if (run_simd_unary_op_test("v128.not", v128_not_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for v128.bitselect
const uint8_t v128_bitselect_wasm[] = TERNARY_OP_WASM(0x52);
void test_v128_bitselect() {
wah_v128_t v1 = {{0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0}};
wah_v128_t v2 = {{0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}};
wah_v128_t v3 = {{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
wah_v128_t expected = {{0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A}};
if (run_simd_ternary_op_test("v128.bitselect", v128_bitselect_wasm, &v1, &v2, &v3, &expected) != WAH_OK) { exit(1); }
}
wah_error_t run_simd_any_true_test(const char* test_name, const uint8_t* wasm_binary, size_t wasm_size, const wah_v128_t* operand, int32_t expected_val) {
wah_module_t module;
wah_exec_context_t ctx;
wah_error_t err;
uint8_t wasm_binary_copy[wasm_size];
memcpy(wasm_binary_copy, wasm_binary, wasm_size);
uint8_t* loc1 = find_placeholder_location(wasm_binary_copy, wasm_size, v128_arg1_placeholder, sizeof(v128_arg1_placeholder));
if (!loc1) { fprintf(stderr, "Error: V128_ARG1_PLACEHOLDER not found in %s\n", test_name); return WAH_ERROR_MISUSE; }
memcpy(loc1, operand, sizeof(wah_v128_t));
err = wah_test_setup_context(test_name, wasm_binary_copy, wasm_size, &module, &ctx);
if (err != WAH_OK) {
return err;
}
wah_value_t result;
err = wah_test_execute_function(test_name, &ctx, &module, &result);
if (err != WAH_OK) {
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return err;
}
if (result.i32 == expected_val) {
printf("Result scalar matches expected value for %s.\n", test_name);
} else {
fprintf(stderr, "Result scalar does NOT match expected value for %s.\n", test_name);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return WAH_ERROR_VALIDATION_FAILED; // Indicate test failure
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("%s test completed successfully.\n", test_name);
return err;
}
#define run_simd_any_true_test(n,b,o,e) run_simd_any_true_test(n,b,sizeof(b),o,e)
const uint8_t v128_any_true_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x18, 0x01, 0x16, 0x00,
0xfd, 0x0c, V128_ARG1_PLACEHOLDER,
0xfd, 0x53,
0x0b,
};
void test_v128_any_true() {
wah_v128_t operand_true = {{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand_false = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_any_true_test("v128.any_true (true)", v128_any_true_wasm, &operand_true, 1) != WAH_OK) { exit(1); }
if (run_simd_any_true_test("v128.any_true (false)", v128_any_true_wasm, &operand_false, 0) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.add
const uint8_t i8x16_add_wasm[] = BINARY_OP_WASM(0x6e);
void test_i8x16_add() {
wah_v128_t operand1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t operand2 = {{0x10, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}};
wah_v128_t expected = {{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11}};
if (run_simd_binary_op_test("i8x16.add", i8x16_add_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.add
const uint8_t f32x4_add_wasm[] = BINARY_OP_WASM(0xe4);
void test_f32x4_add() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} }, operand2 = { .f32 = {5.0, 6.0, 7.0, 8.0} };
wah_v128_t expected = { .f32 = {6.0f, 8.0f, 10.0f, 12.0f} };
if (run_simd_binary_op_test("f32x4.add", f32x4_add_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for v128.and
const uint8_t v128_and_wasm[] = BINARY_OP_WASM(0x4E);
void test_v128_and() {
wah_v128_t operand1 = {{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}};
wah_v128_t operand2 = {{0xF0, 0xF0, 0x0F, 0x0F, 0xAA, 0x55, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}};
wah_v128_t expected = {{0xF0, 0x00, 0x0F, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0x01, 0x00, 0x04, 0x00, 0x10, 0x00, 0x40, 0x00}};
if (run_simd_binary_op_test("v128.and", v128_and_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for v128.andnot
const uint8_t v128_andnot_wasm[] = BINARY_OP_WASM(0x4F);
void test_v128_andnot() {
wah_v128_t operand1 = {{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}};
wah_v128_t operand2 = {{0xF0, 0xF0, 0x0F, 0x0F, 0xAA, 0x55, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}};
wah_v128_t expected = {{0x0F, 0x00, 0xF0, 0x00, 0x55, 0x00, 0x55, 0x00, 0xFE, 0x00, 0xFB, 0x00, 0xEF, 0x00, 0xBF, 0x00}};
if (run_simd_binary_op_test("v128.andnot", v128_andnot_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for v128.or
const uint8_t v128_or_wasm[] = BINARY_OP_WASM(0x50);
void test_v128_or() {
wah_v128_t operand1 = {{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}};
wah_v128_t operand2 = {{0xF0, 0xF0, 0x0F, 0x0F, 0xAA, 0x55, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}};
wah_v128_t expected = {{0xFF, 0xF0, 0xFF, 0x0F, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x02, 0xFF, 0x08, 0xFF, 0x20, 0xFF, 0x80}};
if (run_simd_binary_op_test("v128.or", v128_or_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for v128.xor
const uint8_t v128_xor_wasm[] = BINARY_OP_WASM(0x51);
void test_v128_xor() {
wah_v128_t operand1 = {{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}};
wah_v128_t operand2 = {{0xF0, 0xF0, 0x0F, 0x0F, 0xAA, 0x55, 0xAA, 0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}};
wah_v128_t expected = {{0x0F, 0xF0, 0xF0, 0x0F, 0x55, 0x55, 0x55, 0x55, 0xFE, 0x02, 0xFB, 0x08, 0xEF, 0x20, 0xBF, 0x80}};
if (run_simd_binary_op_test("v128.xor", v128_xor_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.add_sat_s
const uint8_t i8x16_add_sat_s_wasm[] = BINARY_OP_WASM(0x6F);
void test_i8x16_add_sat_s() {
wah_v128_t operand1 = {{0x01, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand2 = {{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x02, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.add_sat_s", i8x16_add_sat_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.add_sat_u
const uint8_t i8x16_add_sat_u_wasm[] = BINARY_OP_WASM(0x70);
void test_i8x16_add_sat_u() {
wah_v128_t operand1 = {{0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand2 = {{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.add_sat_u", i8x16_add_sat_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.sub
const uint8_t i8x16_sub_wasm[] = BINARY_OP_WASM(0x71);
void test_i8x16_sub() {
wah_v128_t operand1 = {{0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand2 = {{0x02, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x03, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.sub", i8x16_sub_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.sub_sat_s
const uint8_t i8x16_sub_sat_s_wasm[] = BINARY_OP_WASM(0x72);
void test_i8x16_sub_sat_s() {
wah_v128_t operand1 = {{0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand2 = {{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.sub_sat_s", i8x16_sub_sat_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.sub_sat_u
const uint8_t i8x16_sub_sat_u_wasm[] = BINARY_OP_WASM(0x73);
void test_i8x16_sub_sat_u() {
wah_v128_t operand1 = {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t operand2 = {{0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.sub_sat_u", i8x16_sub_sat_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.sub
const uint8_t i16x8_sub_wasm[] = BINARY_OP_WASM(0x91);
void test_i16x8_sub() {
wah_v128_t operand1 = {{0x01, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 0x0001, 0x0005
wah_v128_t operand2 = {{0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 0x0002, 0x0003
wah_v128_t expected = {{0xFF, 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 0xFFFF (-1), 0x0002
if (run_simd_binary_op_test("i16x8.sub", i16x8_sub_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.mul
const uint8_t i32x4_mul_wasm[] = BINARY_OP_WASM(0xb5);
void test_i32x4_mul() {
wah_v128_t operand1 = {{0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 3
wah_v128_t operand2 = {{0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 3, 4
wah_v128_t expected = {{0x06, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 6, 12
if (run_simd_binary_op_test("i8x16.ge_u", i32x4_mul_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.eq
const uint8_t i16x8_eq_wasm[] = BINARY_OP_WASM(0x2D);
void test_i16x8_eq() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00}};
wah_v128_t operand2 = {{0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}};
if (run_simd_binary_op_test("i16x8.eq", i16x8_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.ne
const uint8_t i16x8_ne_wasm[] = BINARY_OP_WASM(0x2E);
void test_i16x8_ne() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00}};
wah_v128_t operand2 = {{0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF}};
if (run_simd_binary_op_test("i16x8.ne", i16x8_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.lt_s
const uint8_t i16x8_lt_s_wasm[] = BINARY_OP_WASM(0x2F);
void test_i16x8_lt_s() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -32768, 32767
wah_v128_t operand2 = {{0x02, 0x00, 0x01, 0x00, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 32767, -32768
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1<2, 2<1(F), -32768<32767, 32767<-32768(F)
if (run_simd_binary_op_test("i16x8.lt_s", i16x8_lt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.lt_u
const uint8_t i16x8_lt_u_wasm[] = BINARY_OP_WASM(0x30);
void test_i16x8_lt_u() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 32768, 32767
wah_v128_t operand2 = {{0x02, 0x00, 0x01, 0x00, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 32767, 32768
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1<2, 2<1(F), 32768<32767(F), 32767<32768
if (run_simd_binary_op_test("i16x8.lt_u", i16x8_lt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.gt_s
const uint8_t i16x8_gt_s_wasm[] = BINARY_OP_WASM(0x31);
void test_i16x8_gt_s() {
wah_v128_t operand1 = {{0x02, 0x00, 0x01, 0x00, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 32767, -32768
wah_v128_t operand2 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2>1, 1>2(F), 32767>-32768, -32768>32767(F)
if (run_simd_binary_op_test("i16x8.gt_s", i16x8_gt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.gt_u
const uint8_t i16x8_gt_u_wasm[] = BINARY_OP_WASM(0x32);
void test_i16x8_gt_u() {
wah_v128_t operand1 = {{0x02, 0x00, 0x01, 0x00, 0xFF, 0x7F, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 32767, 32768
wah_v128_t operand2 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2>1, 1>2(F), 32767>32768(F), 32768>32767
if (run_simd_binary_op_test("i16x8.gt_u", i16x8_gt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.le_s
const uint8_t i16x8_le_s_wasm[] = BINARY_OP_WASM(0x33);
void test_i16x8_le_s() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -32768, 32767
wah_v128_t operand2 = {{0x01, 0x00, 0x01, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, -32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1<=1, 2<=1(F), -32768<=-32768, 32767<=32767, 0<=0 (remaining 4 lanes)
if (run_simd_binary_op_test("i16x8.le_s", i16x8_le_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.le_u
const uint8_t i16x8_le_u_wasm[] = BINARY_OP_WASM(0x34);
void test_i16x8_le_u() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 32768, 32767
wah_v128_t operand2 = {{0x01, 0x00, 0x01, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, 32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1<=1, 2<=1(F), 32768<=32768, 32767<=32767, 0<=0 (remaining 4 lanes)
if (run_simd_binary_op_test("i16x8.le_u", i16x8_le_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.ge_s
const uint8_t i16x8_ge_s_wasm[] = BINARY_OP_WASM(0x35);
void test_i16x8_ge_s() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -32768, 32767
wah_v128_t operand2 = {{0x01, 0x00, 0x01, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, -32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1>=1, 2>=1, -32768>=-32768, 32767>=32767, 0>=0 (remaining 4 lanes)
if (run_simd_binary_op_test("i16x8.ge_s", i16x8_ge_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.ge_u
const uint8_t i16x8_ge_u_wasm[] = BINARY_OP_WASM(0x36);
void test_i16x8_ge_u() {
wah_v128_t operand1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 32768, 32767
wah_v128_t operand2 = {{0x01, 0x00, 0x01, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, 32768, 32767
wah_v128_t expected = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1>=1, 2>=1, 32768>=32768, 32767>=32767, 0>=0 (remaining 4 lanes)
if (run_simd_binary_op_test("i16x8.ge_u", i16x8_ge_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.eq
const uint8_t i8x16_eq_wasm[] = BINARY_OP_WASM(0x23);
void test_i8x16_eq() {
wah_v128_t operand1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t operand2 = {{0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00}};
wah_v128_t expected = {{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}};
if (run_simd_binary_op_test("i8x16.eq", i8x16_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.ne
const uint8_t i8x16_ne_wasm[] = BINARY_OP_WASM(0x24);
void test_i8x16_ne() {
wah_v128_t operand1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t operand2 = {{0x01, 0x00, 0x03, 0x00, 0x05, 0x00, 0x07, 0x00, 0x09, 0x00, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00}};
wah_v128_t expected = {{0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF}};
if (run_simd_binary_op_test("i8x16.ne", i8x16_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.lt_s
const uint8_t i8x16_lt_s_wasm[] = BINARY_OP_WASM(0x25);
void test_i8x16_lt_s() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -128, 127
wah_v128_t operand2 = {{0x02, 0x01, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 127, -128
wah_v128_t expected = {{0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1<2, 2<1(F), -128<127, 127<-128(F)
if (run_simd_binary_op_test("i8x16.lt_s", i8x16_lt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.lt_u
const uint8_t i8x16_lt_u_wasm[] = BINARY_OP_WASM(0x26);
void test_i8x16_lt_u() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 128, 127
wah_v128_t operand2 = {{0x02, 0x01, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 127, 128
wah_v128_t expected = {{0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1<2, 2<1(F), 128<127(F), 127<128
if (run_simd_binary_op_test("i8x16.lt_u", i8x16_lt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.gt_s
const uint8_t i8x16_gt_s_wasm[] = BINARY_OP_WASM(0x27);
void test_i8x16_gt_s() {
wah_v128_t operand1 = {{0x02, 0x01, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 127, -128
wah_v128_t operand2 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -128, 127
wah_v128_t expected = {{0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2>1, 1>2(F), 127>-128, -128>127(F)
if (run_simd_binary_op_test("i8x16.gt_s", i8x16_gt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.gt_u
const uint8_t i8x16_gt_u_wasm[] = BINARY_OP_WASM(0x28);
void test_i8x16_gt_u() {
wah_v128_t operand1 = {{0x02, 0x01, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2, 1, 127, 128
wah_v128_t operand2 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 128, 127
wah_v128_t expected = {{0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 2>1, 1>2(F), 127>128(F), 128>127
if (run_simd_binary_op_test("i8x16.gt_u", i8x16_gt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.le_s
const uint8_t i8x16_le_s_wasm[] = BINARY_OP_WASM(0x29);
void test_i8x16_le_s() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -128, 127
wah_v128_t operand2 = {{0x01, 0x01, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, -128, 127
wah_v128_t expected = {{0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1<=1, 2<=1(F), -128<=-128, 127<=127, 0<=0 (remaining 12 bytes)
if (run_simd_binary_op_test("i8x16.le_s", i8x16_le_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.le_u
const uint8_t i8x16_le_u_wasm[] = BINARY_OP_WASM(0x2A);
void test_i8x16_le_u() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 128, 127
wah_v128_t operand2 = {{0x01, 0x01, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, 128, 127
wah_v128_t expected = {{0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1<=1, 2<=1(F), 128<=128, 127<=127, 0<=0 (remaining 12 bytes)
if (run_simd_binary_op_test("i8x16.le_u", i8x16_le_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.ge_s
const uint8_t i8x16_ge_s_wasm[] = BINARY_OP_WASM(0x2B);
void test_i8x16_ge_s() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, -128, 127
wah_v128_t operand2 = {{0x01, 0x01, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, -128, 127
wah_v128_t expected = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1>=1, 2>=1, -128>=-128, 127>=127, 0>=0 (remaining 12 bytes)
if (run_simd_binary_op_test("i8x16.ge_s", i8x16_ge_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.ge_u
const uint8_t i8x16_ge_u_wasm[] = BINARY_OP_WASM(0x2C);
void test_i8x16_ge_u() {
wah_v128_t operand1 = {{0x01, 0x02, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 2, 128, 127
wah_v128_t operand2 = {{0x01, 0x01, 0x80, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // 1, 1, 128, 127
wah_v128_t expected = {{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // 1>=1, 2>=1, 128>=128, 127>=127, 0>=0 (remaining 12 bytes)
if (run_simd_binary_op_test("i8x16.ge_u", i8x16_ge_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.eq
const uint8_t i32x4_eq_wasm[] = BINARY_OP_WASM(0x37);
void test_i32x4_eq() {
wah_v128_t operand1 = { .i32 = {1, 2, 3, 4} }, operand2 = { .i32 = {1, 0, 3, 0} }, expected = { .u32 = {~0U, 0, ~0U, 0} };
if (run_simd_binary_op_test("i32x4.eq", i32x4_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.ne
const uint8_t i32x4_ne_wasm[] = BINARY_OP_WASM(0x38);
void test_i32x4_ne() {
wah_v128_t operand1 = { .i32 = {1, 2, 3, 4} }, operand2 = { .i32 = {1, 0, 3, 0} }, expected = { .u32 = {0, ~0U, 0, ~0U} };
if (run_simd_binary_op_test("i32x4.ne", i32x4_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.lt_s
const uint8_t i32x4_lt_s_wasm[] = BINARY_OP_WASM(0x39);
void test_i32x4_lt_s() {
wah_v128_t operand1 = { .i32 = {1, 2, -2147483648, 2147483647} }, operand2 = { .i32 = {2, 1, 2147483647, -2147483648} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, 0} }; // 1<2, 2<1(F), -2147483648<2147483647, 2147483647<-2147483648(F)
if (run_simd_binary_op_test("i32x4.lt_s", i32x4_lt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.lt_u
const uint8_t i32x4_lt_u_wasm[] = BINARY_OP_WASM(0x3A);
void test_i32x4_lt_u() {
wah_v128_t operand1 = { .u32 = {1, 2, 2147483648U, 2147483647U} }, operand2 = { .u32 = {2, 1, 2147483647U, 2147483648U} };
wah_v128_t expected = { .u32 = {~0U, 0, 0, ~0U} }; // 1<2, 2<1(F), 2147483648<2147483647(F), 2147483647<2147483648
if (run_simd_binary_op_test("i32x4.lt_u", i32x4_lt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.gt_s
const uint8_t i32x4_gt_s_wasm[] = BINARY_OP_WASM(0x3B);
void test_i32x4_gt_s() {
wah_v128_t operand1 = { .i32 = {2, 1, 2147483647, -2147483648} }, operand2 = { .i32 = {1, 2, -2147483648, 2147483647} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, 0} }; // 2>1, 1>2(F), 2147483647>-2147483648, -2147483648>2147483647(F)
if (run_simd_binary_op_test("i32x4.gt_s", i32x4_gt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.gt_u
const uint8_t i32x4_gt_u_wasm[] = BINARY_OP_WASM(0x3C);
void test_i32x4_gt_u() {
wah_v128_t operand1 = { .u32 = {2, 1, 2147483647U, 2147483648U} }, operand2 = { .u32 = {1, 2, 2147483648U, 2147483647U} };
wah_v128_t expected = { .u32 = {~0U, 0, 0, ~0U} }; // 2>1, 1>2(F), 2147483647>2147483648(F), 2147483648>2147483647
if (run_simd_binary_op_test("i32x4.gt_u", i32x4_gt_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.le_s
const uint8_t i32x4_le_s_wasm[] = BINARY_OP_WASM(0x3D);
void test_i32x4_le_s() {
wah_v128_t operand1 = { .i32 = {1, 2, -2147483648, 2147483647} }, operand2 = { .i32 = {1, 1, -2147483648, 2147483647} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, ~0U} }; // 1<=1, 2<=1(F), -2147483648<=-2147483648, 2147483647<=2147483647
if (run_simd_binary_op_test("i32x4.le_s", i32x4_le_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.le_u
const uint8_t i32x4_le_u_wasm[] = BINARY_OP_WASM(0x3E);
void test_i32x4_le_u() {
wah_v128_t operand1 = { .u32 = {1, 2, 2147483648U, 2147483647U} }, operand2 = { .u32 = {1, 1, 2147483648U, 2147483647U} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, ~0U} }; // 1<=1, 2<=1(F), 2147483648<=2147483648, 2147483647<=2147483647
if (run_simd_binary_op_test("i32x4.le_u", i32x4_le_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.ge_s
const uint8_t i32x4_ge_s_wasm[] = BINARY_OP_WASM(0x3F);
void test_i32x4_ge_s() {
wah_v128_t operand1 = { .i32 = {1, 2, -2147483648, 2147483647} }, operand2 = { .i32 = {1, 1, -2147483648, 2147483647} };
wah_v128_t expected = { .u32 = {~0U, ~0U, ~0U, ~0U} }; // 1>=1, 2>=1, -2147483648>=-2147483648, 2147483647>=2147483647
if (run_simd_binary_op_test("i32x4.ge_s", i32x4_ge_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.ge_u
const uint8_t i32x4_ge_u_wasm[] = BINARY_OP_WASM(0x40);
void test_i32x4_ge_u() {
wah_v128_t operand1 = { .u32 = {1, 2, 2147483648U, 2147483647U} }, operand2 = { .u32 = {1, 1, 2147483648U, 2147483647U} };
wah_v128_t expected = { .u32 = {~0U, ~0U, ~0U, ~0U} }; // 1>=1, 2>=1, 2147483648>=2147483648, 2147483647>=2147483647
if (run_simd_binary_op_test("i32x4.ge_u", i32x4_ge_u_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.eq
const uint8_t i64x2_eq_wasm[] = BINARY_OP_WASM(0xD6);
void test_i64x2_eq() {
wah_v128_t operand1 = { .i64 = {1, 2} }, operand2 = { .i64 = {1, 0} }, expected = { .u64 = {~0ULL, 0} };
if (run_simd_binary_op_test("i64x2.eq", i64x2_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.ne
const uint8_t i64x2_ne_wasm[] = BINARY_OP_WASM(0xD7);
void test_i64x2_ne() {
wah_v128_t operand1 = { .i64 = {1, 2} }, operand2 = { .i64 = {1, 0} }, expected = { .u64 = {0, ~0ULL} };
if (run_simd_binary_op_test("i64x2.ne", i64x2_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.lt_s
const uint8_t i64x2_lt_s_wasm[] = BINARY_OP_WASM(0xD8);
void test_i64x2_lt_s() {
wah_v128_t operand1 = { .i64 = {1, 2} }, operand2 = { .i64 = {2, 1} }, expected = { .u64 = {~0ULL, 0} }; // 1<2, 2<1(F)
if (run_simd_binary_op_test("i64x2.lt_s", i64x2_lt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.gt_s
const uint8_t i64x2_gt_s_wasm[] = BINARY_OP_WASM(0xD9);
void test_i64x2_gt_s() {
wah_v128_t operand1 = { .i64 = {2, 1} }, operand2 = { .i64 = {1, 2} }, expected = { .u64 = {~0ULL, 0} }; // 2>1, 1>2(F)
if (run_simd_binary_op_test("i64x2.gt_s", i64x2_gt_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.le_s
const uint8_t i64x2_le_s_wasm[] = BINARY_OP_WASM(0xDA);
void test_i64x2_le_s() {
wah_v128_t operand1 = { .i64 = {1, 2} }, operand2 = { .i64 = {1, 2} }, expected = { .u64 = {~0ULL, ~0ULL} }; // 1<=1, 2<=2
if (run_simd_binary_op_test("i64x2.le_s", i64x2_le_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.ge_s
const uint8_t i64x2_ge_s_wasm[] = BINARY_OP_WASM(0xDB);
void test_i64x2_ge_s() {
wah_v128_t operand1 = { .i64 = {1, 2} }, operand2 = { .i64 = {1, 2} }, expected = { .u64 = {~0ULL, ~0ULL} }; // 1>=1, 2>=2
if (run_simd_binary_op_test("i64x2.ge_s", i64x2_ge_s_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.eq
const uint8_t f32x4_eq_wasm[] = BINARY_OP_WASM(0x41);
void test_f32x4_eq() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} }, operand2 = { .f32 = {1.0f, 0.0f, 3.0f, 0.0f} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, 0} };
if (run_simd_binary_op_test("f32x4.eq", f32x4_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.ne
const uint8_t f32x4_ne_wasm[] = BINARY_OP_WASM(0x42);
void test_f32x4_ne() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} }, operand2 = { .f32 = {1.0f, 0.0f, 3.0f, 0.0f} };
wah_v128_t expected = { .u32 = {0, ~0U, 0, ~0U} };
if (run_simd_binary_op_test("f32x4.ne", f32x4_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.lt
const uint8_t f32x4_lt_wasm[] = BINARY_OP_WASM(0x43);
void test_f32x4_lt() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} }, operand2 = { .f32 = {2.0f, 1.0f, 3.0f, 5.0f} };
wah_v128_t expected = { .u32 = {~0U, 0, 0, ~0U} }; // 1<2, 2<1(F), 3<3(F), 4<5
if (run_simd_binary_op_test("f32x4.lt", f32x4_lt_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.gt
const uint8_t f32x4_gt_wasm[] = BINARY_OP_WASM(0x44);
void test_f32x4_gt() {
wah_v128_t operand1 = { .f32 = {2.0f, 1.0f, 3.0f, 5.0f} }, operand2 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} };
wah_v128_t expected = { .u32 = {~0U, 0, 0, ~0U} }; // 2>1, 1>2(F), 3>3(F), 5>4
if (run_simd_binary_op_test("f32x4.gt", f32x4_gt_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.le
const uint8_t f32x4_le_wasm[] = BINARY_OP_WASM(0x45);
void test_f32x4_le() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} }, operand2 = { .f32 = {1.0f, 1.0f, 3.0f, 5.0f} };
wah_v128_t expected = { .u32 = {~0U, 0, ~0U, ~0U} }; // 1<=1, 2<=1(F), 3<=3, 4<=5
if (run_simd_binary_op_test("f32x4.le", f32x4_le_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.ge
const uint8_t f32x4_ge_wasm[] = BINARY_OP_WASM(0x46);
void test_f32x4_ge() {
wah_v128_t operand1 = { .f32 = {1.0f, 2.0f, 3.0f, 5.0f} }, operand2 = { .f32 = {1.0f, 1.0f, 3.0f, 4.0f} };
wah_v128_t expected = { .u32 = {~0U, ~0U, ~0U, ~0U} }; // 1>=1, 2>=1, 3>=3, 5>=4
if (run_simd_binary_op_test("f32x4.ge", f32x4_ge_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.eq
const uint8_t f64x2_eq_wasm[] = BINARY_OP_WASM(0x47);
void test_f64x2_eq() {
wah_v128_t operand1 = { .f64 = {1.0, 2.0} }, operand2 = { .f64 = {1.0, 0.0} }, expected = { .u64 = {~0ULL, 0} };
if (run_simd_binary_op_test("f64x2.eq", f64x2_eq_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.ne
const uint8_t f64x2_ne_wasm[] = BINARY_OP_WASM(0x48);
void test_f64x2_ne() {
wah_v128_t operand1 = { .f64 = {1.0, 2.0} }, operand2 = { .f64 = {1.0, 0.0} }, expected = { .u64 = {0, ~0ULL} };
if (run_simd_binary_op_test("f64x2.ne", f64x2_ne_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.lt
const uint8_t f64x2_lt_wasm[] = BINARY_OP_WASM(0x49);
void test_f64x2_lt() {
wah_v128_t operand1 = { .f64 = {1.0, 2.0} }, operand2 = { .f64 = {2.0, 1.0} }, expected = { .u64 = {~0ULL, 0} }; // 1<2, 2<1(F)
if (run_simd_binary_op_test("f64x2.lt", f64x2_lt_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.gt
const uint8_t f64x2_gt_wasm[] = BINARY_OP_WASM(0x4A);
void test_f64x2_gt() {
wah_v128_t operand1 = { .f64 = {2.0, 1.0} }, operand2 = { .f64 = {1.0, 2.0} }, expected = { .u64 = {~0ULL, 0} }; // 2>1, 1>2(F)
if (run_simd_binary_op_test("f64x2.gt", f64x2_gt_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.le
const uint8_t f64x2_le_wasm[] = BINARY_OP_WASM(0x4B);
void test_f64x2_le() {
wah_v128_t operand1 = { .f64 = {1.0, 2.0} }, operand2 = { .f64 = {1.0, 1.0} }, expected = { .u64 = {~0ULL, 0} }; // 1<=1, 2<=1(F)
if (run_simd_binary_op_test("f64x2.le", f64x2_le_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.ge
const uint8_t f64x2_ge_wasm[] = BINARY_OP_WASM(0x4C);
void test_f64x2_ge() {
wah_v128_t operand1 = { .f64 = {1.0, 2.0} }, operand2 = { .f64 = {1.0, 1.0} }, expected = { .u64 = {~0ULL, ~0ULL} }; // 1>=1, 2>=1
if (run_simd_binary_op_test("f64x2.ge", f64x2_ge_wasm, &operand1, &operand2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.shuffle
const uint8_t i8x16_shuffle_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x3a, 0x01, 0x38, 0x00,
0xfd, 0x0c, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // v128.const operand1...
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0xfd, 0x0c, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, // v128.const operand2...
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0xfd, 0x0d, 0x00, 0x01, 0x10, 0x11, 0x02, 0x03, 0x12, 0x13, // i8x16.shuffle mask...
0x04, 0x05, 0x14, 0x15, 0x06, 0x07, 0x16, 0x17,
0x0b,
};
void test_i8x16_shuffle() {
// mask: 0, 1, 16, 17, 2, 3, 18, 19, 4, 5, 20, 21, 6, 7, 22, 23
// result: vec1[0], vec1[1], vec2[0], vec2[1], vec1[2], vec1[3], vec2[2], vec2[3], ...
wah_v128_t expected = {{0x00, 0x01, 0x10, 0x11, 0x02, 0x03, 0x12, 0x13, 0x04, 0x05, 0x14, 0x15, 0x06, 0x07, 0x16, 0x17}};
if (run_simd_shuffle_swizzle_test("i8x16.shuffle", i8x16_shuffle_wasm, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.extract_lane_s
const uint8_t i8x16_extract_lane_s_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x19, 0x01, 0x17, 0x00,
0xfd, 0x0c, V128_ARG1_PLACEHOLDER,
0xfd, 0x15, 0x01,
0x0b,
};
void test_i8x16_extract_lane_s() {
wah_v128_t operand = {{0x00, 0x81, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}};
wah_value_t expected = {.i32 = -127}; // operand.i8[1] is 0x81 = -127
if (run_simd_extract_lane_test("i8x16.extract_lane_s", i8x16_extract_lane_s_wasm, &operand, &expected, WAH_TYPE_I32) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.replace_lane
const uint8_t i8x16_replace_lane_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x23, 0x01, 0x21, 0x00,
0xfd, 0x0c, V128_ARG1_PLACEHOLDER,
SCALAR_OP_PLACEHOLDER,
0xfd, 0x17, 0x01,
0x0b,
};
void test_i8x16_replace_lane() {
wah_v128_t operand_v128 = {{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}};
wah_value_t operand_scalar = {.i32 = 0xAA}; // Replace with 0xAA
wah_v128_t expected = {{0x00, 0xAA, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F}};
if (run_simd_replace_lane_test("i8x16.replace_lane", i8x16_replace_lane_wasm, &operand_v128, &operand_scalar, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.swizzle
const uint8_t i8x16_swizzle_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x2a, 0x01, 0x28, 0x00,
0xfd, 0x0c, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, // v128.const data...
0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xfd, 0x0c, 0x00, 0x02, 0x10, 0x12, 0x01, 0x03, 0x11, 0x13, // v128.const mask...
0x04, 0x05, 0x14, 0x15, 0x06, 0x07, 0x16, 0x17,
0xfd, 0x0e, // i8x16.swizzle
0x0b,
};
void test_i8x16_swizzle() {
// mask: 0, 2, 16, 18, 1, 3, 17, 19, 4, 5, 20, 21, 6, 7, 22, 23
// result: data[0], data[2], 0, 0, data[1], data[3], 0, 0, ...
wah_v128_t expected = {{0xA0, 0xA2, 0x00, 0x00, 0xA1, 0xA3, 0x00, 0x00, 0xA4, 0xA5, 0x00, 0x00, 0xA6, 0xA7, 0x00, 0x00}};
if (run_simd_shuffle_swizzle_test("i8x16.swizzle", i8x16_swizzle_wasm, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i8x16.splat
const uint8_t i8x16_splat_wasm[] = SPLAT_WASM(0x0F);
void test_i8x16_splat() {
wah_value_t operand_scalar = {.i32 = 0xBE};
wah_v128_t expected = {{0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE, 0xBE}};
if (run_simd_splat_test("i8x16.splat", i8x16_splat_wasm, &operand_scalar, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i16x8.splat
const uint8_t i16x8_splat_wasm[] = SPLAT_WASM(0x10);
void test_i16x8_splat() {
wah_value_t operand_scalar = {.i32 = 0xBEAF};
wah_v128_t expected = {{0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE, 0xAF, 0xBE}};
if (run_simd_splat_test("i16x8.splat", i16x8_splat_wasm, &operand_scalar, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.splat
const uint8_t i32x4_splat_wasm[] = SPLAT_WASM(0x11);
void test_i32x4_splat() {
wah_value_t operand_scalar = {.i32 = 0xDEADBEEF};
wah_v128_t expected = {{0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE, 0xEF, 0xBE, 0xAD, 0xDE}};
if (run_simd_splat_test("i32x4.splat", i32x4_splat_wasm, &operand_scalar, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i64x2.splat
const uint8_t i64x2_splat_wasm[] = SPLAT_WASM(0x12);
void test_i64x2_splat() {
wah_value_t operand_scalar = {.i64 = 0xDEADBEEFCAFEBABEULL};
wah_v128_t expected = {{0xBE, 0xBA, 0xFE, 0xCA, 0xEF, 0xBE, 0xAD, 0xDE, 0xBE, 0xBA, 0xFE, 0xCA, 0xEF, 0xBE, 0xAD, 0xDE}};
if (run_simd_splat_test("i64x2.splat", i64x2_splat_wasm, &operand_scalar, WAH_TYPE_I64, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.splat
const uint8_t f32x4_splat_wasm[] = SPLAT_WASM(0x13);
void test_f32x4_splat() {
wah_value_t operand_scalar = {.f32 = 123.456f};
wah_v128_t expected;
for (int i = 0; i < 4; ++i) expected.f32[i] = 123.456f;
if (run_simd_splat_test("f32x4.splat", f32x4_splat_wasm, &operand_scalar, WAH_TYPE_F32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.splat
const uint8_t f64x2_splat_wasm[] = SPLAT_WASM(0x14);
void test_f64x2_splat() {
wah_value_t operand_scalar = {.f64 = 123.456789};
wah_v128_t expected;
for (int i = 0; i < 2; ++i) expected.f64[i] = 123.456789;
if (run_simd_splat_test("f64x2.splat", f64x2_splat_wasm, &operand_scalar, WAH_TYPE_F64, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.trunc_sat_f32x4_s
const uint8_t i32x4_trunc_sat_f32x4_s_wasm[] = UNARY_OP_WASM(0xF8);
void test_i32x4_trunc_sat_f32x4_s() {
wah_v128_t operand = { .f32 = {1.5f, -2.5f, 2147483647.0f, -2147483648.0f} };
wah_v128_t expected = { .i32 = {1, -2, 2147483647, -2147483648} };
if (run_simd_unary_op_test("i32x4.trunc_sat_f32x4_s", i32x4_trunc_sat_f32x4_s_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.trunc_sat_f32x4_u
const uint8_t i32x4_trunc_sat_f32x4_u_wasm[] = UNARY_OP_WASM(0xF9);
void test_i32x4_trunc_sat_f32x4_u() {
wah_v128_t operand = { .f32 = {1.5f, -2.5f, 4294967295.0f, 0.0f} };
wah_v128_t expected = { .u32 = {1, 0, 4294967295U, 0} };
if (run_simd_unary_op_test("i32x4.trunc_sat_f32x4_u", i32x4_trunc_sat_f32x4_u_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.convert_i32x4_s
const uint8_t f32x4_convert_i32x4_s_wasm[] = UNARY_OP_WASM(0xFA);
void test_f32x4_convert_i32x4_s() {
wah_v128_t operand = { .i32 = {1, -2, 3, -4} };
wah_v128_t expected = { .f32 = {1.0f, -2.0f, 3.0f, -4.0f} };
if (run_simd_unary_op_test("f32x4.convert_i32x4_s", f32x4_convert_i32x4_s_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.convert_i32x4_u
const uint8_t f32x4_convert_i32x4_u_wasm[] = UNARY_OP_WASM(0xFB);
void test_f32x4_convert_i32x4_u() {
wah_v128_t operand = { .u32 = {1, 2, 3, 4} };
wah_v128_t expected = { .f32 = {1.0f, 2.0f, 3.0f, 4.0f} };
if (run_simd_unary_op_test("f32x4.convert_i32x4_u", f32x4_convert_i32x4_u_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.trunc_sat_f64x2_s_zero
const uint8_t i32x4_trunc_sat_f64x2_s_zero_wasm[] = UNARY_OP_WASM(0xFC);
void test_i32x4_trunc_sat_f64x2_s_zero() {
wah_v128_t operand = { .f64 = {1.5, -2.5} };
wah_v128_t expected = { .i32 = {1, -2, 0, 0} };
if (run_simd_unary_op_test("i32x4.trunc_sat_f64x2_s_zero", i32x4_trunc_sat_f64x2_s_zero_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for i32x4.trunc_sat_f64x2_u_zero
const uint8_t i32x4_trunc_sat_f64x2_u_zero_wasm[] = UNARY_OP_WASM(0xFD);
void test_i32x4_trunc_sat_f64x2_u_zero() {
wah_v128_t operand = { .f64 = {1.5, -2.5} };
wah_v128_t expected = { .u32 = {1, 0, 0, 0} };
if (run_simd_unary_op_test("i32x4.trunc_sat_f64x2_u_zero", i32x4_trunc_sat_f64x2_u_zero_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.convert_low_i32x4_s
const uint8_t f64x2_convert_low_i32x4_s_wasm[] = UNARY_OP_WASM(0xFE);
void test_f64x2_convert_low_i32x4_s() {
wah_v128_t operand = { .i32 = {1, -2, 3, -4} };
wah_v128_t expected = { .f64 = {1.0, -2.0} };
if (run_simd_unary_op_test("f64x2.convert_low_i32x4_s", f64x2_convert_low_i32x4_s_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.convert_low_i32x4_u
const uint8_t f64x2_convert_low_i32x4_u_wasm[] = UNARY_OP_WASM(0xFF);
void test_f64x2_convert_low_i32x4_u() {
wah_v128_t operand = { .u32 = {1, 2, 3, 4} };
wah_v128_t expected = { .f64 = {1.0, 2.0} };
if (run_simd_unary_op_test("f64x2.convert_low_i32x4_u", f64x2_convert_low_i32x4_u_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f32x4.demote_f64x2_zero
const uint8_t f32x4_demote_f64x2_zero_wasm[] = UNARY_OP_WASM(0x5E);
void test_f32x4_demote_f64x2_zero() {
wah_v128_t operand = { .f64 = {1.5, 2.5} };
wah_v128_t expected = { .f32 = {1.5f, 2.5f, 0.0f, 0.0f} };
if (run_simd_unary_op_test("f32x4.demote_f64x2_zero", f32x4_demote_f64x2_zero_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for f64x2.promote_low_f32x4
const uint8_t f64x2_promote_low_f32x4_wasm[] = UNARY_OP_WASM(0x5F);
void test_f64x2_promote_low_f32x4() {
wah_v128_t operand = { .f32 = {1.5f, 2.5f, 3.5f, 4.5f} };
wah_v128_t expected = { .f64 = {1.5, 2.5} };
if (run_simd_unary_op_test("f64x2.promote_low_f32x4", f64x2_promote_low_f32x4_wasm, &operand, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I8X16_ABS
const uint8_t i8x16_abs_wasm[] = UNARY_OP_WASM(0x60);
void test_i8x16_abs() {
wah_v128_t v = {{0x01, 0xFF, 0x00, 0x7F, 0x80, 0x0A, 0xF6, 0x00, 0x01, 0xFF, 0x00, 0x7F, 0x80, 0x0A, 0xF6, 0x00}};
wah_v128_t expected = {{0x01, 0x01, 0x00, 0x7F, 0x80, 0x0A, 0x0A, 0x00, 0x01, 0x01, 0x00, 0x7F, 0x80, 0x0A, 0x0A, 0x00}};
if (run_simd_unary_op_test("i8x16.abs", i8x16_abs_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I8X16_SHL
const uint8_t i8x16_shl_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7b,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x22, 0x01, 0x20, 0x00,
0xfd, 0x0c, V128_ARG1_PLACEHOLDER, // v128.const ...
SCALAR_OP_PLACEHOLDER,
0xfd, 0x6b, // i8x16.shl
0x0b,
};
void test_i8x16_shl() {
wah_v128_t v = {{0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80}};
wah_v128_t expected = {{0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00}};
wah_value_t shift_amount = {.i32 = 1};
if (run_simd_binary_op_i32_shift_test("i8x16.shl", i8x16_shl_wasm, &v, &shift_amount, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
v = (wah_v128_t){{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}};
expected = (wah_v128_t){{0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}};
shift_amount = (wah_value_t){.i32 = 2};
if (run_simd_binary_op_i32_shift_test("i8x16.shl", i8x16_shl_wasm, &v, &shift_amount, WAH_TYPE_I32, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I8X16_MIN_S
const uint8_t i8x16_min_s_wasm[] = BINARY_OP_WASM(0x76);
void test_i8x16_min_s() {
wah_v128_t v1 = {{0x01, 0x05, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x05, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t v2 = {{0x02, 0x03, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x01, 0x03, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00}};
if (run_simd_binary_op_test("i8x16.min_s", i8x16_min_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I8X16_AVGR_U
const uint8_t i8x16_avgr_u_wasm[] = BINARY_OP_WASM(0x7B);
void test_i8x16_avgr_u() {
wah_v128_t v1 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t v2 = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
wah_v128_t expected = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}};
if (run_simd_binary_op_test("i8x16.avgr_u", i8x16_avgr_u_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
v1 = (wah_v128_t){{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
v2 = (wah_v128_t){{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}};
expected = (wah_v128_t){{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}};
if (run_simd_binary_op_test("i8x16.avgr_u", i8x16_avgr_u_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I16X8_EXTEND_LOW_I8X16_S
const uint8_t i16x8_extend_low_i8x16_s_wasm[] = UNARY_OP_WASM(0x87);
void test_i16x8_extend_low_i8x16_s() {
wah_v128_t v = {{0x01, 0xFF, 0x00, 0x7F, 0x80, 0x0A, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
wah_v128_t expected = {{0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x7F, 0x00, 0x80, 0xFF, 0x0A, 0x00, 0xF6, 0xFF, 0x00, 0x00}}; // i16 values
if (run_simd_unary_op_test("i16x8.extend_low_i8x16_s", i16x8_extend_low_i8x16_s_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I16X8_NARROW_I32X4_S
const uint8_t i16x8_narrow_i32x4_s_wasm[] = BINARY_OP_WASM(0x85);
void test_i16x8_narrow_i32x4_s() {
wah_v128_t v1 = {{0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i32: 1, -1
wah_v128_t v2 = {{0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i32: -32768, 65535 (saturates to 32767)
wah_v128_t expected = {{0x01, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00}}; // i16: 1, -1, 0, 0, -32768, 32767, 0, 0
if (run_simd_binary_op_test("i16x8.narrow_i32x4_s", i16x8_narrow_i32x4_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I16X8_Q15MULR_SAT_S
const uint8_t i16x8_q15mulr_sat_s_wasm[] = BINARY_OP_WASM(0x82);
void test_i16x8_q15mulr_sat_s() {
wah_v128_t v1 = {{0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40}}; // 0.5 in Q15
wah_v128_t v2 = {{0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40}}; // 0.5 in Q15
wah_v128_t expected = {{0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20}}; // 0.25 in Q15
if (run_simd_binary_op_test("i16x8.q15mulr_sat_s", i16x8_q15mulr_sat_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
v1 = (wah_v128_t){{0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F}}; // 1.0 in Q15
v2 = (wah_v128_t){{0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F, 0xFF, 0x7F}}; // 1.0 in Q15
expected = (wah_v128_t){{0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F}}; // 32766 in Q15 (saturated)
if (run_simd_binary_op_test("i16x8.q15mulr_sat_s", i16x8_q15mulr_sat_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I32X4_DOT_I16X8_S
const uint8_t i32x4_dot_i16x8_s_wasm[] = BINARY_OP_WASM(0xBA);
void test_i32x4_dot_i16x8_s() {
wah_v128_t v1 = {{0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00}}; // i16: 1, 2, 3, 4, 5, 6, 7, 8
wah_v128_t v2 = {{0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0x09, 0x00}}; // i16: 2, 3, 4, 5, 6, 7, 8, 9
wah_v128_t expected = {{0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}}; // i32: 8, 32, 72, 128
if (run_simd_binary_op_test("i32x4.dot_i16x8_s", i32x4_dot_i16x8_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I32X4_EXTMUL_LOW_I16X8_S
const uint8_t i32x4_extmul_low_i16x8_s_wasm[] = BINARY_OP_WASM(0xBC);
void test_i32x4_extmul_low_i16x8_s() {
wah_v128_t v1 = {{0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i16: 1, 2
wah_v128_t v2 = {{0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i16: 2, 3
wah_v128_t expected = {{0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i32: 2, 6
if (run_simd_binary_op_test("i32x4.extmul_low_i16x8_s", i32x4_extmul_low_i16x8_s_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I64X2_ABS
const uint8_t i64x2_abs_wasm[] = UNARY_OP_WASM(0xC0);
void test_i64x2_abs() {
wah_v128_t v = {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}; // i64: 1, -1
wah_v128_t expected = {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // i64: 1, 1
if (run_simd_unary_op_test("i64x2.abs", i64x2_abs_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for I64X2_EXTEND_HIGH_I32X4_U
const uint8_t i64x2_extend_high_i32x4_u_wasm[] = UNARY_OP_WASM(0xCA);
void test_i64x2_extend_high_i32x4_u() {
wah_v128_t v = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}}; // u32: ?, ?, 0xFFFFFFFF, 0
wah_v128_t expected = {{0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // u64: 0xFFFFFFFF, 0
if (run_simd_unary_op_test("i64x2.extend_high_i32x4_u", i64x2_extend_high_i32x4_u_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for F32X4_ABS
const uint8_t f32x4_abs_wasm[] = UNARY_OP_WASM(0xE0);
void test_f32x4_abs() {
wah_v128_t v = {{0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f32: 1.0, -1.0, 0.0
wah_v128_t expected = {{0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f32: 1.0, 1.0, 0.0
if (run_simd_unary_op_test("f32x4.abs", f32x4_abs_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for F32X4_CEIL
const uint8_t f32x4_ceil_wasm[] = UNARY_OP_WASM(0x67);
void test_f32x4_ceil() {
wah_v128_t v = {{0x00, 0x00, 0x20, 0x40, 0x00, 0x00, 0x20, 0xC0, 0xCD, 0xCC, 0xCC, 0x3F, 0x00, 0x00, 0x00, 0x00}}; // f32: 2.5, -2.5, 1.6
wah_v128_t expected = {{0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00}}; // f32: 3.0, -2.0, 2.0
if (run_simd_unary_op_test("f32x4.ceil", f32x4_ceil_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for F32X4_MIN
const uint8_t f32x4_min_wasm[] = BINARY_OP_WASM(0xE8);
void test_f32x4_min() {
wah_v128_t v1 = {{0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00}}; // f32: 1.0, 0.0, 1.0
wah_v128_t v2 = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f32: 0.0, 1.0, 0.0
wah_v128_t expected = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f32: 0.0, 0.0, 0.0
if (run_simd_binary_op_test("f32x4.min", f32x4_min_wasm, &v1, &v2, &expected) != WAH_OK) { exit(1); }
}
// Test cases for F64X2_NEG
const uint8_t f64x2_neg_wasm[] = UNARY_OP_WASM(0xED);
void test_f64x2_neg() {
wah_v128_t v = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF}}; // f64: 1.0, -1.0
wah_v128_t expected = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F}}; // f64: -1.0, 1.0
if (run_simd_unary_op_test("f64x2.neg", f64x2_neg_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
// Test cases for F64X2_SQRT
const uint8_t f64x2_sqrt_wasm[] = UNARY_OP_WASM(0xEF);
void test_f64x2_sqrt() {
wah_v128_t v = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f64: 4.0, 0.0
wah_v128_t expected = {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}; // f64: 2.0, 0.0
if (run_simd_unary_op_test("f64x2.sqrt", f64x2_sqrt_wasm, &v, &expected) != WAH_OK) { exit(1); }
}
int main() {
test_v128_const();
test_v128_load_store();
test_all_v128_loads();
test_v128_not();
test_i8x16_add();
test_f32x4_add();
test_v128_and();
test_v128_andnot();
test_v128_or();
test_v128_xor();
test_i8x16_add_sat_s();
test_i8x16_add_sat_u();
test_i8x16_sub();
test_i8x16_sub_sat_s();
test_i8x16_sub_sat_u();
test_i16x8_sub();
test_i32x4_mul();
test_i16x8_eq();
test_i16x8_ne();
test_i16x8_lt_s();
test_i16x8_lt_u();
test_i16x8_gt_s();
test_i16x8_gt_u();
test_i16x8_le_s();
test_i16x8_le_u();
test_i16x8_ge_s();
test_i16x8_ge_u();
test_i8x16_eq();
test_i8x16_ne();
test_i8x16_lt_s();
test_i8x16_lt_u();
test_i8x16_gt_s();
test_i8x16_gt_u();
test_i8x16_le_s();
test_i8x16_le_u();
test_i8x16_ge_s();
test_i8x16_ge_u();
test_i32x4_eq();
test_i32x4_ne();
test_i32x4_lt_s();
test_i32x4_lt_u();
test_i32x4_gt_s();
test_i32x4_gt_u();
test_i32x4_le_s();
test_i32x4_le_u();
test_i32x4_ge_s();
test_i32x4_ge_u();
test_i64x2_eq();
test_i64x2_ne();
test_i64x2_lt_s();
test_i64x2_gt_s();
test_i64x2_le_s();
test_i64x2_ge_s();
test_f32x4_eq();
test_f32x4_ne();
test_f32x4_lt();
test_f32x4_gt();
test_f32x4_le();
test_f32x4_ge();
test_f64x2_eq();
test_f64x2_ne();
test_f64x2_lt();
test_f64x2_gt();
test_f64x2_le();
test_f64x2_ge();
test_i8x16_shuffle();
test_i8x16_extract_lane_s();
test_i8x16_replace_lane();
test_i8x16_swizzle();
test_i8x16_splat();
test_i16x8_splat();
test_i32x4_splat();
test_i64x2_splat();
test_f32x4_splat();
test_f64x2_splat();
test_v128_bitselect();
test_v128_any_true();
test_i32x4_trunc_sat_f32x4_s();
test_i32x4_trunc_sat_f32x4_u();
test_f32x4_convert_i32x4_s();
test_f32x4_convert_i32x4_u();
test_i32x4_trunc_sat_f64x2_s_zero();
test_i32x4_trunc_sat_f64x2_u_zero();
test_f64x2_convert_low_i32x4_s();
test_f64x2_convert_low_i32x4_u();
test_f32x4_demote_f64x2_zero();
test_f64x2_promote_low_f32x4();
test_i8x16_abs();
test_i8x16_shl();
test_i8x16_min_s();
test_i8x16_avgr_u();
test_i16x8_extend_low_i8x16_s();
test_i16x8_narrow_i32x4_s();
test_i16x8_q15mulr_sat_s();
test_i32x4_dot_i16x8_s();
test_i32x4_extmul_low_i16x8_s();
test_i64x2_abs();
test_i64x2_extend_high_i32x4_u();
test_f32x4_abs();
test_f32x4_ceil();
test_f32x4_min();
test_f64x2_neg();
test_f64x2_sqrt();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#define WAH_IMPLEMENTATION
#include "wah.h"
// A simple WASM binary with a table and call_indirect
// (module
// (type $void_i32 (func (param i32)))
// (type $i32_i32 (func (param i32) (result i32)))
// (func $add_one (type $i32_i32) (param $p0 i32) (result i32)
// local.get $p0
// i32.const 1
// i32.add)
// (func $sub_one (type $i32_i32) (param $p0 i32) (result i32)
// local.get $p0
// i32.const 1
// i32.sub)
// (table $t0 2 funcref)
// (elem (i32.const 0) func $add_one func $sub_one)
// (func $call_indirect_add (type $i32_i32) (param $p0 i32) (result i32)
// local.get $p0
// i32.const 0 ;; function index in table
// call_indirect (type $i32_i32))
// (func $call_indirect_sub (type $i32_i32) (param $p0 i32) (result i32)
// local.get $p0
// i32.const 1 ;; function index in table
// call_indirect (type $i32_i32))
// (export "add_one" (func $add_one))
// (export "sub_one" (func $sub_one))
// (export "call_indirect_add" (func $call_indirect_add))
// (export "call_indirect_sub" (func $call_indirect_sub))
// )
// Manually crafted WASM binary for the above WAT
const uint8_t wasm_binary_table_indirect_call[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section (1)
0x01, // section id
0x0b, // section size
0x02, // num types
// Type 0: (func (param i32) (result i32))
0x60, 0x01, 0x7f, 0x01, 0x7f,
// Type 1: (func (param i32) (result i32)) - same as type 0 for simplicity
0x60, 0x01, 0x7f, 0x01, 0x7f,
// Function section (3)
0x03, // section id
0x05, // section size
0x04, // num functions (add_one, sub_one, call_indirect_add, call_indirect_sub)
0x00, // func 0 (add_one) -> type 0
0x00, // func 1 (sub_one) -> type 0
0x00, // func 2 (call_indirect_add) -> type 0
0x00, // func 3 (call_indirect_sub) -> type 0
// Table section (4)
0x04, // section id
0x04, // section size
0x01, // num tables
0x70, // element type: funcref
0x00, // flags: 0x00 (fixed size)
0x02, // min elements: 2
// max elements is min_elements if flags is 0x00
// Export section (7)
0x07, // section id
0x3d, // section size (61 bytes)
0x04, // num exports
// export "add_one" func 0
0x07, 0x61, 0x64, 0x64, 0x5f, 0x6f, 0x6e, 0x65, 0x00, 0x00,
// export "sub_one" func 1
0x07, 0x73, 0x75, 0x62, 0x5f, 0x6f, 0x6e, 0x65, 0x00, 0x01,
// export "call_indirect_add" func 2
0x11, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x61, 0x64, 0x64, 0x00, 0x02,
// export "call_indirect_sub" func 3
0x11, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x75, 0x62, 0x00, 0x03,
// Element section (9)
0x09, // section id
0x08, // section size (8 bytes)
0x01, // num elements
0x00, // table index 0
0x41, 0x00, 0x0b, // offset expr: i32.const 0 end
0x02, // num_elems
0x00, // func index 0 ($add_one)
0x01, // func index 1 ($sub_one)
// Code section (10)
0x0a, // section id
0x25, // section size (37 bytes)
0x04, // num code bodies
// Code body for func 0 ($add_one)
0x07, // body size
0x00, // num locals
0x20, 0x00, // local.get 0
0x41, 0x01, // i32.const 1
0x6a, // i32.add
0x0b, // end
// Code body for func 1 ($sub_one)
0x07, // body size
0x00, // num locals
0x20, 0x00, // local.get 0
0x41, 0x01, // i32.const 1
0x6b, // i32.sub
0x0b, // end
0x09, // body size (9 bytes)
0x00, // num locals
0x20, 0x00, // local.get 0 (param for indirect call)
0x41, 0x00, // i32.const 0 (function index in table)
0x11, 0x00, 0x00, // call_indirect (type 0, table 0)
0x0b, // end
// Code body for func 3 ($call_indirect_sub)
0x09, // body size (9 bytes)
0x00, // num locals
0x20, 0x00, // local.get 0 (param for indirect call)
0x41, 0x01, // i32.const 1 (function index in table)
0x11, 0x00, 0x00, // call_indirect (type 0, table 0)
0x0b, // end
};
void wah_test_table_indirect_call() {
printf("Running wah_test_table_indirect_call...\n");
wah_module_t module;
wah_error_t err = wah_parse_module(wasm_binary_table_indirect_call, sizeof(wasm_binary_table_indirect_call), &module);
assert(err == WAH_OK);
wah_exec_context_t exec_ctx;
err = wah_exec_context_create(&exec_ctx, &module);
assert(err == WAH_OK);
// Find the exported function indices
uint32_t add_one_func_idx = (uint32_t)-1;
uint32_t sub_one_func_idx = (uint32_t)-1;
uint32_t call_indirect_add_func_idx = (uint32_t)-1;
uint32_t call_indirect_sub_func_idx = (uint32_t)-1;
// NOTE: Export section parsing is not implemented yet, so we'll hardcode for now
// Based on the WASM binary, func 0 is add_one, func 1 is sub_one, func 2 is call_indirect_add, func 3 is call_indirect_sub
add_one_func_idx = 0;
sub_one_func_idx = 1;
call_indirect_add_func_idx = 2;
call_indirect_sub_func_idx = 3;
(void)add_one_func_idx; // Suppress warning
(void)sub_one_func_idx; // Suppress warning
// Test call_indirect_add (calls add_one indirectly)
wah_value_t params_add[1] = {{.i32 = 10}};
wah_value_t result_add;
err = wah_call(&exec_ctx, &module, call_indirect_add_func_idx, params_add, 1, &result_add);
assert(err == WAH_OK);
assert(result_add.i32 == 11);
// Test call_indirect_sub (calls sub_one indirectly)
wah_value_t params_sub[1] = {{.i32 = 10}};
wah_value_t result_sub;
err = wah_call(&exec_ctx, &module, call_indirect_sub_func_idx, params_sub, 1, &result_sub);
assert(err == WAH_OK);
assert(result_sub.i32 == 9);
wah_exec_context_destroy(&exec_ctx);
wah_free_module(&module);
printf("wah_test_table_indirect_call passed.\n");
}
int main() {
wah_test_table_indirect_call();
return 0;
}
#define WAH_IMPLEMENTATION
#include "wah.h"
#include <stdio.h>
#include <stdint.h>
// Test for division by zero: (module (func (param i32 i32) (result i32) (i32.div_s (local.get 0) (local.get 1))))
const uint8_t div_by_zero_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
// Function section
0x03, 0x02, 0x01, 0x00,
// Code section - i32.div_s opcode is 0x6D
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6d, 0x0b
};
// Test for signed overflow: (module (func (param i32 i32) (result i32) (i32.div_s (local.get 0) (local.get 1))))
// Same binary as above, will test with INT_MIN / -1
const uint8_t signed_overflow_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
// Function section
0x03, 0x02, 0x01, 0x00,
// Code section - i32.div_s opcode is 0x6D
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6d, 0x0b
};
// Test for unsigned division by zero: (module (func (param i32 i32) (result i32) (i32.div_u (local.get 0) (local.get 1))))
const uint8_t div_u_by_zero_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
// Function section
0x03, 0x02, 0x01, 0x00,
// Code section - i32.div_u opcode is 0x6E
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6e, 0x0b
};
// Test for remainder by zero: (module (func (param i32 i32) (result i32) (i32.rem_s (local.get 0) (local.get 1))))
const uint8_t rem_by_zero_wasm[] = {
0x00, 0x61, 0x73, 0x6d, // magic
0x01, 0x00, 0x00, 0x00, // version
// Type section
0x01, 0x07, 0x01, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f,
// Function section
0x03, 0x02, 0x01, 0x00,
// Code section - i32.rem_s opcode is 0x6F
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6f, 0x0b
};
int main() {
wah_module_t module;
wah_exec_context_t ctx; // Add this line
wah_error_t err;
wah_value_t params[2], result;
printf("=== Testing Division by Zero Traps ===\n");
// Test signed division by zero
printf("\n1. Testing i32.div_s with division by zero:\n");
err = wah_parse_module(div_by_zero_wasm, sizeof(div_by_zero_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse div_by_zero module: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for div_by_zero test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = 42;
params[1].i32 = 0; // Division by zero
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_ERROR_TRAP) {
printf("- Correctly trapped on division by zero (error %d)\n", err);
} else {
printf("x Expected trap but got error %d\n", err);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
// Test signed integer overflow
printf("\n2. Testing i32.div_s with signed integer overflow:\n");
err = wah_parse_module(signed_overflow_wasm, sizeof(signed_overflow_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse signed_overflow module: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for signed_overflow test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = INT32_MIN;
params[1].i32 = -1; // This causes overflow: INT_MIN / -1 = +2^31 (unrepresentable)
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_ERROR_TRAP) {
printf("- Correctly trapped on signed integer overflow (error %d)\n", err);
} else {
printf("x Expected trap but got error %d\n", err);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
// Test unsigned division by zero
printf("\n3. Testing i32.div_u with division by zero:\n");
err = wah_parse_module(div_u_by_zero_wasm, sizeof(div_u_by_zero_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse div_u_by_zero module: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for div_u_by_zero test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = 100;
params[1].i32 = 0; // Division by zero
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_ERROR_TRAP) {
printf("- Correctly trapped on unsigned division by zero (error %d)\n", err);
} else {
printf("x Expected trap but got error %d\n", err);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
// Test remainder by zero
printf("\n4. Testing i32.rem_s with division by zero:\n");
err = wah_parse_module(rem_by_zero_wasm, sizeof(rem_by_zero_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse rem_by_zero module: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for rem_by_zero test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = 7;
params[1].i32 = 0; // Division by zero
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_ERROR_TRAP) {
printf("- Correctly trapped on remainder by zero (error %d)\n", err);
} else {
printf("x Expected trap but got error %d\n", err);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
// Test that valid operations still work
printf("\n5. Testing that valid operations still work:\n");
err = wah_parse_module(div_by_zero_wasm, sizeof(div_by_zero_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse module for valid test: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for valid test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = 20;
params[1].i32 = 4; // Valid division: 20 / 4 = 5
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_OK) {
printf("- Valid division works: 20 / 4 = %d\n", result.i32);
} else {
printf("x Valid division failed with error %d\n", err);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
// Test that INT_MIN % -1 = 0 (doesn't trap per spec)
printf("\n6. Testing i32.rem_s with INT_MIN %% -1 (should return 0, not trap):\n");
err = wah_parse_module(rem_by_zero_wasm, sizeof(rem_by_zero_wasm), &module);
if (err != WAH_OK) {
printf("Failed to parse rem module: %d\n", err);
return 1;
}
// Create context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "Error creating execution context for INT_MIN %% -1 test: %d\n", err);
wah_free_module(&module);
return 1;
}
params[0].i32 = INT32_MIN;
params[1].i32 = -1; // This should return 0, not trap
err = wah_call(&ctx, &module, 0, params, 2, &result);
if (err == WAH_OK && result.i32 == 0) {
printf("- INT_MIN %% -1 correctly returns 0 (no trap)\n");
} else {
printf("x INT_MIN %% -1 failed: error %d, result %d\n", err, result.i32);
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
return 1;
}
wah_exec_context_destroy(&ctx);
wah_free_module(&module);
printf("\n=== All trap tests passed! ===\n");
return 0;
}
#define WAH_IMPLEMENTATION
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include "wah.h"
#define CHECK(condition, message) do { \
if (!(condition)) { \
fprintf(stderr, "FAIL: %s\n", message); \
return 1; \
} else { \
printf("PASS: %s\n", message); \
} \
} while(0)
#define CHECK_ERR(err, expected, message) do { \
if (err != expected) { \
fprintf(stderr, "FAIL: %s (got %d, expected %d)\n", message, err, expected); \
return 1; \
} else { \
printf("PASS: %s\n", message); \
} \
} while(0)
#define CHECK_FLOAT(val, exp, epsilon, message) do { \
if (fabsf(val - exp) > epsilon) { \
fprintf(stderr, "FAIL: %s (got %f, expected %f)\n", message, val, exp); \
return 1; \
} else { \
printf("PASS: %s\n", message); \
} \
} while(0)
#define CHECK_DOUBLE(val, exp, epsilon, message) do { \
if (fabs(val - exp) > epsilon) { \
fprintf(stderr, "FAIL: %s (got %f, expected %f)\n", message, val, exp); \
return 1; \
} else { \
printf("PASS: %s\n", message); \
} \
} while(0)
// (module
// (func (param i64 i64) (result i64)
// (i64.add (local.get 0) (local.get 1))
// )
// )
const uint8_t i64_add_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x07, 0x01, 0x60, 0x02, 0x7e, 0x7e, 0x01, 0x7e,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x7c, 0x0b
};
// (module
// (func (param f32 f32) (result f32)
// (f32.mul (local.get 0) (local.get 1))
// )
// )
const uint8_t f32_mul_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x07, 0x01, 0x60, 0x02, 0x7d, 0x7d, 0x01, 0x7d,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x94, 0x0b
};
// (module
// (func (param f64 f64) (result f64)
// (f64.sub (local.get 0) (local.get 1))
// )
// )
const uint8_t f64_sub_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x07, 0x01, 0x60, 0x02, 0x7c, 0x7c, 0x01, 0x7c,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x09, 0x01, 0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0xa1, 0x0b
};
// (module
// (func (result i32)
// (i64.const 9223372036854775807)
// (i64.const 1)
// (i64.add)
// (i64.const 0)
// (i64.lt_s)
// )
// )
const uint8_t i64_overflow_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7f,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x15, 0x01, 0x13, 0x00, // Corrected: section size 21, body size 19
0x42, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, // i64.const MAX
0x42, 0x01, // i64.const 1
0x7c, // i64.add
0x42, 0x00, // i64.const 0
0x53, // i64.lt_s
0x0b
};
// (module
// (func (result f64)
// (f64.const 1.0)
// (f64.const 0.0)
// (f64.div)
// )
// )
const uint8_t f64_div_zero_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7c,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x17, 0x01, 0x15, 0x00, // Corrected: section size 23, body size 21
0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3f, // f64.const 1.0
0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // f64.const 0.0
0xa3, // f64.div
0x0b
};
// (module
// (func (result i64)
// (i64.const -9223372036854775808) // INT64_MIN
// (i64.const -1)
// (i64.div_s)
// )
// )
const uint8_t i64_div_overflow_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
0x01, 0x05, 0x01, 0x60, 0x00, 0x01, 0x7e,
0x03, 0x02, 0x01, 0x00,
0x0a, 0x12, 0x01, 0x10, 0x00, // Corrected: section size 18, body size 16
0x42, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f, // i64.const MIN
0x42, 0x7f, // i64.const -1
0x7f, // i64.div_s
0x0b
};
// (module (func (i64.const 123) (drop)))
const uint8_t i64_const_drop_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, // magic, version
// Type section
0x01, 0x04, 0x01, 0x60, 0x00, 0x00,
// Function section
0x03, 0x02, 0x01, 0x00,
// Code section
0x0a, 0x07, 0x01, 0x05, 0x00, // Corrected: section size 7, body size 5
0x42, 0x7b, // i64.const 123
0x1a, // drop
0x0b, // end
};
// (module
// (memory (export "mem") 1)
// (func (export "store_i8") (param $addr i32) (param $val i32) local.get $addr local.get $val i32.store8 offset=0 align=1)
// (func (export "load_i32_8s") (param $addr i32) (result i32) local.get $addr i32.load8_s offset=0 align=1)
// (func (export "load_i32_8u") (param $addr i32) (result i32) local.get $addr i32.load8_u offset=0 align=1)
// (func (export "store_i16") (param $addr i32) (param $val i32) local.get $addr local.get $val i32.store16 offset=0 align=2)
// (func (export "load_i32_16s") (param $addr i32) (result i32) local.get $addr i32.load16_s offset=0 align=2)
// (func (export "load_i32_16u") (param $addr i32) (result i32) local.get $addr i32.load16_u offset=0 align=2)
// (func (export "store_i32") (param $addr i32) (param $val i32) local.get $addr local.get $val i32.store offset=0 align=4)
// (func (export "load_i32") (param $addr i32) (result i32) local.get $addr i32.load offset=0 align=4)
// (func (export "store_i64") (param $addr i32) (param $val i64) local.get $addr local.get $val i64.store offset=0 align=8)
// (func (export "load_i64") (param $addr i32) (result i64) local.get $addr i64.load offset=0 align=8)
// (func (export "store_i64_8") (param $addr i32) (param $val i64) local.get $addr local.get $val i64.store8 offset=0 align=1)
// (func (export "load_i64_8s") (param $addr i32) (result i64) local.get $addr i64.load8_s offset=0 align=1)
// (func (export "load_i64_8u") (param $addr i32) (result i64) local.get $addr i64.load8_u offset=0 align=1)
// (func (export "store_i64_16") (param $addr i32) (param $val i64) local.get $addr local.get $val i64.store16 offset=0 align=2)
// (func (export "load_i64_16s") (param $addr i32) (result i64) local.get $addr i64.load16_s offset=0 align=2)
// (func (export "load_i64_16u") (param $addr i32) (result i64) local.get $addr i64.load16_u offset=0 align=2)
// (func (export "store_i64_32") (param $addr i32) (param $val i64) local.get $addr local.get $val i64.store32 offset=0 align=4)
// (func (export "load_i64_32s") (param $addr i32) (result i64) local.get $addr i64.load32_s offset=0 align=4)
// (func (export "load_i64_32u") (param $addr i32) (result i64) local.get $addr i64.load32_u offset=0 align=4)
// (func (export "store_f32") (param $addr i32) (param $val f32) local.get $addr local.get $val f32.store offset=0 align=4)
// (func (export "load_f32") (param $addr i32) (result f32) local.get $addr f32.load offset=0 align=4)
// (func (export "store_f64") (param $addr i32) (param $val f64) local.get $addr local.get $val f64.store offset=0 align=8)
// (func (export "load_f64") (param $addr i32) (result f64) local.get $addr f64.load offset=0 align=8)
// )
const uint8_t memory_access_wasm[] = {
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x29, 0x08, 0x60, 0x02, 0x7f, 0x7f, 0x00,
0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x02, 0x7f, 0x7e, 0x00, 0x60, 0x01, 0x7f, 0x01, 0x7e, 0x60,
0x02, 0x7f, 0x7d, 0x00, 0x60, 0x01, 0x7f, 0x01, 0x7d, 0x60, 0x02, 0x7f, 0x7c, 0x00, 0x60, 0x01,
0x7f, 0x01, 0x7c, 0x03, 0x18, 0x17, 0x00, 0x01, 0x01, 0x00, 0x01, 0x01, 0x00, 0x01, 0x02, 0x03,
0x02, 0x03, 0x03, 0x02, 0x03, 0x03, 0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x05, 0x03, 0x01,
0x00, 0x01, 0x07, 0xb8, 0x02, 0x18, 0x03, 0x6d, 0x65, 0x6d, 0x02, 0x00, 0x08, 0x73, 0x74, 0x6f,
0x72, 0x65, 0x5f, 0x69, 0x38, 0x00, 0x00, 0x0b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x33, 0x32,
0x5f, 0x38, 0x73, 0x00, 0x01, 0x0b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x5f, 0x38,
0x75, 0x00, 0x02, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x31, 0x36, 0x00, 0x03, 0x0c,
0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x5f, 0x31, 0x36, 0x73, 0x00, 0x04, 0x0c, 0x6c,
0x6f, 0x61, 0x64, 0x5f, 0x69, 0x33, 0x32, 0x5f, 0x31, 0x36, 0x75, 0x00, 0x05, 0x09, 0x73, 0x74,
0x6f, 0x72, 0x65, 0x5f, 0x69, 0x33, 0x32, 0x00, 0x06, 0x08, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69,
0x33, 0x32, 0x00, 0x07, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x36, 0x34, 0x00, 0x08,
0x08, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34, 0x00, 0x09, 0x0b, 0x73, 0x74, 0x6f, 0x72,
0x65, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x38, 0x00, 0x0a, 0x0b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69,
0x36, 0x34, 0x5f, 0x38, 0x73, 0x00, 0x0b, 0x0b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34,
0x5f, 0x38, 0x75, 0x00, 0x0c, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x36, 0x34, 0x5f,
0x31, 0x36, 0x00, 0x0d, 0x0c, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x31, 0x36,
0x73, 0x00, 0x0e, 0x0c, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x31, 0x36, 0x75,
0x00, 0x0f, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x33, 0x32, 0x00,
0x10, 0x0c, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x33, 0x32, 0x73, 0x00, 0x11,
0x0c, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x36, 0x34, 0x5f, 0x33, 0x32, 0x75, 0x00, 0x12, 0x09,
0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x66, 0x33, 0x32, 0x00, 0x13, 0x08, 0x6c, 0x6f, 0x61, 0x64,
0x5f, 0x66, 0x33, 0x32, 0x00, 0x14, 0x09, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x66, 0x36, 0x34,
0x00, 0x15, 0x08, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x66, 0x36, 0x34, 0x00, 0x16, 0x0a, 0xcb, 0x01,
0x17, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x3a, 0x00, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x2c,
0x00, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x2d, 0x00, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20,
0x01, 0x3b, 0x01, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x2e, 0x01, 0x00, 0x0b, 0x07, 0x00, 0x20,
0x00, 0x2f, 0x01, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x36, 0x02, 0x00, 0x0b, 0x07,
0x00, 0x20, 0x00, 0x28, 0x02, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x37, 0x03, 0x00,
0x0b, 0x07, 0x00, 0x20, 0x00, 0x29, 0x03, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x3c,
0x00, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x30, 0x00, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x31,
0x00, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x3d, 0x01, 0x00, 0x0b, 0x07, 0x00, 0x20,
0x00, 0x32, 0x01, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x33, 0x01, 0x00, 0x0b, 0x09, 0x00, 0x20,
0x00, 0x20, 0x01, 0x3e, 0x02, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x34, 0x02, 0x00, 0x0b, 0x07,
0x00, 0x20, 0x00, 0x35, 0x02, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x38, 0x02, 0x00,
0x0b, 0x07, 0x00, 0x20, 0x00, 0x2a, 0x02, 0x00, 0x0b, 0x09, 0x00, 0x20, 0x00, 0x20, 0x01, 0x39,
0x03, 0x00, 0x0b, 0x07, 0x00, 0x20, 0x00, 0x2b, 0x03, 0x00, 0x0b
};
#define FUNC_store_i8 0
#define FUNC_load_i32_8s 1
#define FUNC_load_i32_8u 2
#define FUNC_store_i16 3
#define FUNC_load_i32_16s 4
#define FUNC_load_i32_16u 5
#define FUNC_store_i32 6
#define FUNC_load_i32 7
#define FUNC_store_i64 8
#define FUNC_load_i64 9
#define FUNC_store_i64_8 10
#define FUNC_load_i64_8s 11
#define FUNC_load_i64_8u 12
#define FUNC_store_i64_16 13
#define FUNC_load_i64_16s 14
#define FUNC_load_i64_16u 15
#define FUNC_store_i64_32 16
#define FUNC_load_i64_32s 17
#define FUNC_load_i64_32u 18
#define FUNC_store_f32 19
#define FUNC_load_f32 20
#define FUNC_store_f64 21
#define FUNC_load_f64 22
int run_test(const char* test_name, const uint8_t* wasm_binary, size_t binary_size, int (*test_func)(wah_module_t*, wah_exec_context_t*)) {
printf("\n--- Testing %s ---\n", test_name);
wah_module_t module;
wah_exec_context_t ctx; // Declare context
wah_error_t err = wah_parse_module(wasm_binary, binary_size, &module);
if (err != WAH_OK) {
fprintf(stderr, "FAIL: Parsing %s (error: %s)\n", test_name, wah_strerror(err));
return 1;
}
printf("PASS: Parsing %s\n", test_name);
// Create execution context
err = wah_exec_context_create(&ctx, &module);
if (err != WAH_OK) {
fprintf(stderr, "FAIL: Creating execution context for %s (error: %d)\n", test_name, err);
wah_free_module(&module);
return 1;
}
int result = test_func(&module, &ctx); // Pass context to test_func
wah_exec_context_destroy(&ctx); // Destroy context
wah_free_module(&module);
return result;
}
int test_i64_add(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
params[0].i64 = 10000000000LL; // 10^10
params[1].i64 = 25000000000LL; // 2.5 * 10^10
wah_error_t err = wah_call(ctx, module, 0, params, 2, &result);
CHECK_ERR(err, WAH_OK, "i64_add wah_call");
CHECK(result.i64 == 35000000000LL, "i64_add result");
return 0;
}
int test_i32_store_unaligned(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
int failed_checks = 0;
// Initialize memory to zeros
for (int i = 0; i < 8; ++i) {
params[0].i32 = i;
params[1].i32 = 0;
err = wah_call(ctx, module, FUNC_store_i8, params, 2, NULL);
CHECK(err == WAH_OK, "store_i8 0");
}
// Store a value at an unaligned address (e.g., address 1)
params[0].i32 = 1; // Unaligned address
params[1].i32 = 0xAABBCCDD; // Value
err = wah_call(ctx, module, FUNC_store_i32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i32 0xAABBCCDD at unaligned address 1");
// Verify memory content by loading bytes
// Expected: memory[0]=00, memory[1]=DD, memory[2]=CC, memory[3]=BB, memory[4]=AA, memory[5]=00
params[0].i32 = 0;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 0");
CHECK(result.i32 == 0x00, "memory[0] is 0x00");
params[0].i32 = 1;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 1");
CHECK(result.i32 == 0xDD, "memory[1] is 0xDD");
params[0].i32 = 2;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 2");
CHECK(result.i32 == 0xCC, "memory[2] is 0xCC");
params[0].i32 = 3;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 3");
CHECK(result.i32 == 0xBB, "memory[3] is 0xBB");
params[0].i32 = 4;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 4");
CHECK(result.i32 == 0xAA, "memory[4] is 0xAA");
params[0].i32 = 5;
err = wah_call(ctx, module, FUNC_load_i32_8u, params, 1, &result);
CHECK(err == WAH_OK, "load_i32_8u from address 5");
CHECK(result.i32 == 0x00, "memory[5] is 0x00");
return failed_checks;
}
int test_f32_mul(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
params[0].f32 = 12.5f;
params[1].f32 = -4.0f;
wah_error_t err = wah_call(ctx, module, 0, params, 2, &result);
CHECK_ERR(err, WAH_OK, "f32_mul wah_call");
CHECK_FLOAT(result.f32, -50.0f, 1e-6, "f32_mul result");
return 0;
}
int test_f64_sub(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
params[0].f64 = 3.1415926535;
params[1].f64 = 0.0000000005;
wah_error_t err = wah_call(ctx, module, 0, params, 2, &result);
CHECK_ERR(err, WAH_OK, "f64_sub wah_call");
CHECK_DOUBLE(result.f64, 3.1415926530, 1e-9, "f64_sub result");
return 0;
}
int test_i64_overflow(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t result;
wah_error_t err = wah_call(ctx, module, 0, NULL, 0, &result);
CHECK_ERR(err, WAH_OK, "i64_overflow wah_call");
// INT64_MAX + 1 wraps around to INT64_MIN, which is < 0, so comparison should be true (1)
CHECK(result.i32 == 1, "i64_overflow result");
return 0;
}
int test_f64_div_zero(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t result;
wah_error_t err = wah_call(ctx, module, 0, NULL, 0, &result);
CHECK_ERR(err, WAH_OK, "f64_div_zero wah_call");
CHECK(isinf(result.f64) && result.f64 > 0, "f64_div_zero result is +inf");
return 0;
}
int test_i64_div_overflow(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t result;
wah_error_t err = wah_call(ctx, module, 0, NULL, 0, &result);
CHECK_ERR(err, WAH_ERROR_TRAP, "i64_div_overflow wah_call traps");
return 0;
}
int test_i64_const_drop(wah_module_t* module, wah_exec_context_t* ctx) {
// This test only needs to parse successfully.
(void)module;
(void)ctx;
return 0;
}
int test_i32_load8_s_sign_extension(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
// Test 1: Store -1 (0xFF) and load with sign extension
params[0].i32 = 0; // address
params[1].i32 = -1; // value (0xFF as byte)
err = wah_call(ctx, module, FUNC_store_i8, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i8 -1");
params[0].i32 = 0; // address
err = wah_call(ctx, module, FUNC_load_i32_8s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_8s -1");
CHECK(result.i32 == -1, "i32.load8_s -1 result");
// Test 2: Store 0x7F (127) and load with sign extension
params[0].i32 = 1; // address
params[1].i32 = 127; // value (0x7F as byte)
err = wah_call(ctx, module, FUNC_store_i8, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i8 127");
params[0].i32 = 1; // address
err = wah_call(ctx, module, FUNC_load_i32_8s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_8s 127");
CHECK(result.i32 == 127, "i32.load8_s 127 result");
// Test 3: Store 0x80 (-128) and load with sign extension
params[0].i32 = 2; // address
params[1].i32 = -128; // value (0x80 as byte)
err = wah_call(ctx, module, FUNC_store_i8, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i8 -128");
params[0].i32 = 2; // address
err = wah_call(ctx, module, FUNC_load_i32_8s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_8s -128");
CHECK(result.i32 == -128, "i32.load8_s -128 result");
return 0;
}
int test_i64_load8_s_sign_extension(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
int failed_checks = 0;
// Test case 1: -1 (0xFF) should sign extend to -1 (0xFFFFFFFFFFFFFFFF)
params[0].i32 = 0; // Address
params[1].i64 = -1; // Value to store (will be truncated to 0xFF)
err = wah_call(ctx, module, FUNC_store_i64_8, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_8 -1");
err = wah_call(ctx, module, FUNC_load_i64_8s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_8s -1");
CHECK(result.i64 == -1, "i64.load8_s -1 result");
// Test case 2: 127 (0x7F) should not sign extend
params[0].i32 = 0; // Address
params[1].i64 = 127; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_8, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_8 127");
err = wah_call(ctx, module, FUNC_load_i64_8s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_8s 127");
CHECK(result.i64 == 127, "i64.load8_s 127 result");
// Test case 3: -128 (0x80) should sign extend to -128
params[0].i32 = 0; // Address
params[1].i64 = -128; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_8, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_8 -128");
err = wah_call(ctx, module, FUNC_load_i64_8s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_8s -128");
CHECK(result.i64 == -128, "i64.load8_s -128 result");
return failed_checks;
}
int test_i32_load16_s_sign_extension(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
// Test 1: Store -1 (0xFFFF) and load with sign extension
params[0].i32 = 0; // address
params[1].i32 = -1; // value (0xFFFF as 2 bytes)
err = wah_call(ctx, module, FUNC_store_i16, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i16 -1");
params[0].i32 = 0; // address
err = wah_call(ctx, module, FUNC_load_i32_16s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_16s -1");
CHECK(result.i32 == -1, "i32.load16_s -1 result");
// Test 2: Store 0x7FFF (32767) and load with sign extension
params[0].i32 = 2; // address
params[1].i32 = 32767; // value (0x7FFF as 2 bytes)
err = wah_call(ctx, module, FUNC_store_i16, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i16 32767");
params[0].i32 = 2; // address
err = wah_call(ctx, module, FUNC_load_i32_16s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_16s 32767");
CHECK(result.i32 == 32767, "i32.load16_s 32767 result");
// Test 3: Store 0x8000 (-32768) and load with sign extension
params[0].i32 = 4; // address
params[1].i32 = -32768; // value (0x8000 as 2 bytes)
err = wah_call(ctx, module, FUNC_store_i16, params, 2, NULL);
CHECK_ERR(err, WAH_OK, "store_i16 -32768");
params[0].i32 = 4; // address
err = wah_call(ctx, module, FUNC_load_i32_16s, params, 1, &result);
CHECK_ERR(err, WAH_OK, "load_i32_16s -32768");
CHECK(result.i32 == -32768, "i32.load16_s -32768 result");
return 0;
}
int test_i64_load16_s_sign_extension(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
int failed_checks = 0;
// Test case 1: -1 (0xFFFF) should sign extend to -1 (0xFFFFFFFFFFFFFFFF)
params[0].i32 = 0; // Address
params[1].i64 = -1; // Value to store (will be truncated to 0xFFFF)
err = wah_call(ctx, module, FUNC_store_i64_16, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_16 -1");
err = wah_call(ctx, module, FUNC_load_i64_16s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_16s -1");
CHECK(result.i64 == -1, "i64.load16_s -1 result");
// Test case 2: 32767 (0x7FFF) should not sign extend
params[0].i32 = 0; // Address
params[1].i64 = 32767; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_16, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_16 32767");
err = wah_call(ctx, module, FUNC_load_i64_16s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_16s 32767");
CHECK(result.i64 == 32767, "i64.load16_s 32767 result");
// Test case 3: -32768 (0x8000) should sign extend to -32768
params[0].i32 = 0; // Address
params[1].i64 = -32768; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_16, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_16 -32768");
err = wah_call(ctx, module, FUNC_load_i64_16s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_16s -32768");
CHECK(result.i64 == -32768, "i64.load16_s -32768 result");
return failed_checks;
}
int test_i64_load32_s_sign_extension(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
int failed_checks = 0;
// Test case 1: -1 (0xFFFFFFFF) should sign extend to -1 (0xFFFFFFFFFFFFFFFF)
params[0].i32 = 0; // Address
params[1].i64 = -1; // Value to store (will be truncated to 0xFFFFFFFF)
err = wah_call(ctx, module, FUNC_store_i64_32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_32 -1");
err = wah_call(ctx, module, FUNC_load_i64_32s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_32s -1");
CHECK(result.i64 == -1, "i64.load32_s -1 result");
// Test case 2: 2147483647 (0x7FFFFFFF) should not sign extend
params[0].i32 = 0; // Address
params[1].i64 = 2147483647LL; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_32 2147483647");
err = wah_call(ctx, module, FUNC_load_i64_32s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_32s 2147483647");
CHECK(result.i64 == 2147483647LL, "i64.load32_s 2147483647 result");
// Test case 3: -2147483648 (0x80000000) should sign extend to -2147483648
params[0].i32 = 0; // Address
params[1].i64 = -2147483648LL; // Value to store
err = wah_call(ctx, module, FUNC_store_i64_32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i64_32 -2147483648");
err = wah_call(ctx, module, FUNC_load_i64_32s, params, 1, &result);
CHECK(err == WAH_OK, "load_i64_32s -2147483648");
CHECK(result.i64 == -2147483648LL, "i64.load32_s -2147483648 result");
return failed_checks;
}
int test_i32_load_unaligned(wah_module_t* module, wah_exec_context_t* ctx) {
wah_value_t params[2];
wah_value_t result;
wah_error_t err;
int failed_checks = 0;
// Store a value at an aligned address (e.g., address 0)
params[0].i32 = 0; // Address
params[1].i32 = 0x12345678; // Value
err = wah_call(ctx, module, FUNC_store_i32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i32 0x12345678 at address 0");
// Load from an unaligned address (e.g., address 1)
params[0].i32 = 1; // Unaligned address
err = wah_call(ctx, module, FUNC_load_i32, params, 1, &result);
CHECK(err == WAH_OK, "load_i32 from unaligned address 1");
// Expected value: 0xXX123456 (assuming little-endian and memory[0]=78, memory[1]=56, memory[2]=34, memory[3]=12)
// If we load from address 1, we get memory[1], memory[2], memory[3], memory[4]
// Since memory[4] is 0, the value should be 0x00123456
CHECK(result.i32 == 0x00123456, "i32.load from unaligned address 1 result");
// Store another value at an aligned address (e.g., address 4)
params[0].i32 = 4; // Address
params[1].i32 = 0xAABBCCDD; // Value
err = wah_call(ctx, module, FUNC_store_i32, params, 2, NULL);
CHECK(err == WAH_OK, "store_i32 0xAABBCCDD at address 4");
// Load from an unaligned address (e.g., address 2)
params[0].i32 = 2; // Unaligned address
err = wah_call(ctx, module, FUNC_load_i32, params, 1, &result);
CHECK(err == WAH_OK, "load_i32 from unaligned address 2");
// Expected value: 0xCCDD1234 (assuming little-endian and memory[0]=78, memory[1]=56, memory[2]=34, memory[3]=12, memory[4]=DD, memory[5]=CC, memory[6]=BB, memory[7]=AA)
// If we load from address 2, we get memory[2], memory[3], memory[4], memory[5]
// So, 0xCCDD1234
CHECK(result.i32 == (int32_t)0xCCDD1234, "i32.load from unaligned address 2 result");
return failed_checks;
}
int main() {
int failed = 0;
failed |= run_test("i64_add", i64_add_wasm, sizeof(i64_add_wasm), test_i64_add);
failed |= run_test("f32_mul", f32_mul_wasm, sizeof(f32_mul_wasm), test_f32_mul);
failed |= run_test("f64_sub", f64_sub_wasm, sizeof(f64_sub_wasm), test_f64_sub);
failed |= run_test("i64_overflow", i64_overflow_wasm, sizeof(i64_overflow_wasm), test_i64_overflow);
failed |= run_test("f64_div_zero", f64_div_zero_wasm, sizeof(f64_div_zero_wasm), test_f64_div_zero);
failed |= run_test("i64_div_overflow", i64_div_overflow_wasm, sizeof(i64_div_overflow_wasm), test_i64_div_overflow);
failed |= run_test("i64_const_drop", i64_const_drop_wasm, sizeof(i64_const_drop_wasm), test_i64_const_drop);
failed |= run_test("i32_load8_s_sign_extension", memory_access_wasm, sizeof(memory_access_wasm), test_i32_load8_s_sign_extension);
failed |= run_test("i32_load16_s_sign_extension", memory_access_wasm, sizeof(memory_access_wasm), test_i32_load16_s_sign_extension);
failed |= run_test("i64_load8_s_sign_extension", memory_access_wasm, sizeof(memory_access_wasm), test_i64_load8_s_sign_extension);
failed |= run_test("i64_load16_s_sign_extension", memory_access_wasm, sizeof(memory_access_wasm), test_i64_load16_s_sign_extension);
failed |= run_test("i64_load32_s_sign_extension", memory_access_wasm, sizeof(memory_access_wasm), test_i64_load32_s_sign_extension);
failed |= run_test("i32_load_unaligned", memory_access_wasm, sizeof(memory_access_wasm), test_i32_load_unaligned);
failed |= run_test("i32_store_unaligned", memory_access_wasm, sizeof(memory_access_wasm), test_i32_store_unaligned);
if (failed) {
printf("\n--- SOME TESTS FAILED ---\n");
} else {
printf("\n--- ALL TESTS PASSED ---\n");
}
return failed;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment