From 5a4e299c5808e2fe01aeaf01aee358744eb7404a Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 2 May 2026 19:44:41 -0700 Subject: [PATCH 01/11] Introduce WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS for V128 load/store Split V128 memory access (LOAD_V128/STORE_V128) into a separate three-way guard independent of the scalar unaligned-access flag, and add a target_supports_unaligned_simd field to AOTCompContext. Signed-off-by: Brian Cain --- core/config.h | 13 ++ core/iwasm/common/wasm_runtime_common.h | 214 +++++++++++------- core/iwasm/compilation/aot_llvm.c | 15 ++ core/iwasm/compilation/aot_llvm.h | 5 + core/iwasm/compilation/simd/simd_load_store.c | 38 +++- 5 files changed, 193 insertions(+), 92 deletions(-) diff --git a/core/config.h b/core/config.h index 31404deb95..3f629c199a 100644 --- a/core/config.h +++ b/core/config.h @@ -276,6 +276,19 @@ #endif #endif +/* Whether the CPU supports unaligned SIMD/vector memory access. + * Some architectures have dedicated unaligned-load vector instructions, + * allowing V128 access at any alignment even when scalar loads require + * natural alignment. */ +#ifndef WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS +#if defined(BUILD_TARGET_X86_32) || defined(BUILD_TARGET_X86_64) \ + || defined(BUILD_TARGET_AARCH64) +#define WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS 1 +#else +#define WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS 0 +#endif +#endif + /* WASM Interpreter labels-as-values feature */ #ifndef WASM_ENABLE_LABELS_AS_VALUES #ifdef __GNUC__ diff --git a/core/iwasm/common/wasm_runtime_common.h b/core/iwasm/common/wasm_runtime_common.h index b6056eba66..f13fb6fca0 100644 --- a/core/iwasm/common/wasm_runtime_common.h +++ b/core/iwasm/common/wasm_runtime_common.h @@ -272,93 +272,10 @@ STORE_U16(void *addr, uint16_t value) ((uint8_t *)(addr))[1] = u.u8[1]; } -static inline void -STORE_V128(void *addr, V128 value) -{ - uintptr_t addr_ = (uintptr_t)(addr); - union { - V128 val; - uint64 u64[2]; - uint32 u32[4]; - uint16 u16[8]; - uint8 u8[16]; - } u; - - if ((addr_ & (uintptr_t)15) == 0) { - *(V128 *)addr = value; - } - else if ((addr_ & (uintptr_t)7) == 0) { - u.val = value; - ((uint64 *)(addr))[0] = u.u64[0]; - ((uint64 *)(addr))[1] = u.u64[1]; - } - else if ((addr_ & (uintptr_t)3) == 0) { - u.val = value; - ((uint32 *)addr)[0] = u.u32[0]; - ((uint32 *)addr)[1] = u.u32[1]; - ((uint32 *)addr)[2] = u.u32[2]; - ((uint32 *)addr)[3] = u.u32[3]; - } - else if ((addr_ & (uintptr_t)1) == 0) { - u.val = value; - ((uint16 *)addr)[0] = u.u16[0]; - ((uint16 *)addr)[1] = u.u16[1]; - ((uint16 *)addr)[2] = u.u16[2]; - ((uint16 *)addr)[3] = u.u16[3]; - ((uint16 *)addr)[4] = u.u16[4]; - ((uint16 *)addr)[5] = u.u16[5]; - ((uint16 *)addr)[6] = u.u16[6]; - ((uint16 *)addr)[7] = u.u16[7]; - } - else { - u.val = value; - for (int i = 0; i < 16; i++) - ((uint8 *)addr)[i] = u.u8[i]; - } -} +/* STORE_V128 / LOAD_V128 are defined separately below, guarded by + * WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS (see Block after line 474). */ /* For LOAD opcodes */ -static inline V128 -LOAD_V128(void *addr) -{ - uintptr_t addr1 = (uintptr_t)addr; - union { - V128 val; - uint64 u64[2]; - uint32 u32[4]; - uint16 u16[8]; - uint8 u8[16]; - } u; - if ((addr1 & (uintptr_t)15) == 0) - return *(V128 *)addr; - - if ((addr1 & (uintptr_t)7) == 0) { - u.u64[0] = ((uint64 *)addr)[0]; - u.u64[1] = ((uint64 *)addr)[1]; - } - else if ((addr1 & (uintptr_t)3) == 0) { - u.u32[0] = ((uint32 *)addr)[0]; - u.u32[1] = ((uint32 *)addr)[1]; - u.u32[2] = ((uint32 *)addr)[2]; - u.u32[3] = ((uint32 *)addr)[3]; - } - else if ((addr1 & (uintptr_t)1) == 0) { - u.u16[0] = ((uint16 *)addr)[0]; - u.u16[1] = ((uint16 *)addr)[1]; - u.u16[2] = ((uint16 *)addr)[2]; - u.u16[3] = ((uint16 *)addr)[3]; - u.u16[4] = ((uint16 *)addr)[4]; - u.u16[5] = ((uint16 *)addr)[5]; - u.u16[6] = ((uint16 *)addr)[6]; - u.u16[7] = ((uint16 *)addr)[7]; - } - else { - for (int i = 0; i < 16; i++) - u.u8[i] = ((uint8 *)addr)[i]; - } - return u.val; -} - static inline int64 LOAD_I64(void *addr) { @@ -473,6 +390,133 @@ LOAD_I16(void *addr) #endif /* WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0 */ +/* + * LOAD_V128 / STORE_V128 — WASM linear memory V128 access. + * + * These are guarded by WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS rather than + * WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS because some architectures have + * different alignment rules for scalar vs vector memory operations. + * + * PUT_V128_TO_ADDR / GET_V128_FROM_ADDR (frame-local access) remain + * guarded by the scalar flag above since frame locals are accessed via + * scalar C operations, not vector instructions. + */ +#if WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0 + +/* Already defined: LOAD_V128, STORE_V128 as direct pointer casts */ + +#elif WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS != 0 + +/* The target's SIMD unit supports unaligned vector access, but scalar loads + * require natural alignment. Use memcpy which is safe at any alignment and + * allows the compiler to select the best instruction sequence for the + * target. */ +static inline V128 +LOAD_V128(void *addr) +{ + V128 v; + memcpy(&v, addr, sizeof(V128)); + return v; +} + +static inline void +STORE_V128(void *addr, V128 value) +{ + memcpy(addr, &value, sizeof(V128)); +} + +#else /* !UNALIGNED_ADDR_ACCESS && !UNALIGNED_SIMD_ACCESS */ + +/* Neither scalar nor vector unaligned access is supported. + * Check alignment at runtime and use the widest safe access. */ +static inline void +STORE_V128(void *addr, V128 value) +{ + uintptr_t addr_ = (uintptr_t)(addr); + union { + V128 val; + uint64 u64[2]; + uint32 u32[4]; + uint16 u16[8]; + uint8 u8[16]; + } u; + + if ((addr_ & (uintptr_t)15) == 0) { + *(V128 *)addr = value; + } + else if ((addr_ & (uintptr_t)7) == 0) { + u.val = value; + ((uint64 *)(addr))[0] = u.u64[0]; + ((uint64 *)(addr))[1] = u.u64[1]; + } + else if ((addr_ & (uintptr_t)3) == 0) { + u.val = value; + ((uint32 *)addr)[0] = u.u32[0]; + ((uint32 *)addr)[1] = u.u32[1]; + ((uint32 *)addr)[2] = u.u32[2]; + ((uint32 *)addr)[3] = u.u32[3]; + } + else if ((addr_ & (uintptr_t)1) == 0) { + u.val = value; + ((uint16 *)addr)[0] = u.u16[0]; + ((uint16 *)addr)[1] = u.u16[1]; + ((uint16 *)addr)[2] = u.u16[2]; + ((uint16 *)addr)[3] = u.u16[3]; + ((uint16 *)addr)[4] = u.u16[4]; + ((uint16 *)addr)[5] = u.u16[5]; + ((uint16 *)addr)[6] = u.u16[6]; + ((uint16 *)addr)[7] = u.u16[7]; + } + else { + u.val = value; + for (int i = 0; i < 16; i++) + ((uint8 *)addr)[i] = u.u8[i]; + } +} + +static inline V128 +LOAD_V128(void *addr) +{ + uintptr_t addr1 = (uintptr_t)addr; + union { + V128 val; + uint64 u64[2]; + uint32 u32[4]; + uint16 u16[8]; + uint8 u8[16]; + } u; + if ((addr1 & (uintptr_t)15) == 0) + return *(V128 *)addr; + + if ((addr1 & (uintptr_t)7) == 0) { + u.u64[0] = ((uint64 *)addr)[0]; + u.u64[1] = ((uint64 *)addr)[1]; + } + else if ((addr1 & (uintptr_t)3) == 0) { + u.u32[0] = ((uint32 *)addr)[0]; + u.u32[1] = ((uint32 *)addr)[1]; + u.u32[2] = ((uint32 *)addr)[2]; + u.u32[3] = ((uint32 *)addr)[3]; + } + else if ((addr1 & (uintptr_t)1) == 0) { + u.u16[0] = ((uint16 *)addr)[0]; + u.u16[1] = ((uint16 *)addr)[1]; + u.u16[2] = ((uint16 *)addr)[2]; + u.u16[3] = ((uint16 *)addr)[3]; + u.u16[4] = ((uint16 *)addr)[4]; + u.u16[5] = ((uint16 *)addr)[5]; + u.u16[6] = ((uint16 *)addr)[6]; + u.u16[7] = ((uint16 *)addr)[7]; + } + else { + for (int i = 0; i < 16; i++) + u.u8[i] = ((uint8 *)addr)[i]; + } + return u.val; +} + +#endif /* WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS */ + #if WASM_ENABLE_SHARED_MEMORY != 0 #define SHARED_MEMORY_LOCK(memory) shared_memory_lock(memory) #define SHARED_MEMORY_UNLOCK(memory) shared_memory_unlock(memory) diff --git a/core/iwasm/compilation/aot_llvm.c b/core/iwasm/compilation/aot_llvm.c index e9f86817f0..f0baa9b7a6 100644 --- a/core/iwasm/compilation/aot_llvm.c +++ b/core/iwasm/compilation/aot_llvm.c @@ -3433,6 +3433,21 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option) } } + /* Determine whether the target's SIMD/vector unit handles unaligned + * access natively (so LLVMSetAlignment(load, 1) is safe and the backend + * will select the right unaligned instruction). Targets NOT listed here + * receive 1< (128b), + * not to HVX vectors (1024b). The HVX vmemu instruction is only selected + * for 1024b types, so align=1 on <2 x i64> produces byte-by-byte memub + * loads rather than unaligned HVX stores. Using 1<target_supports_unaligned_simd = + !strcmp(comp_ctx->target_arch, "x86_64") + || !strncmp(comp_ctx->target_arch, "aarch64", 7); + if (!(target_data_ref = LLVMCreateTargetDataLayout(comp_ctx->target_machine))) { aot_set_last_error("create LLVM target data layout failed."); diff --git a/core/iwasm/compilation/aot_llvm.h b/core/iwasm/compilation/aot_llvm.h index 5bd75a38ce..d89e776eb4 100644 --- a/core/iwasm/compilation/aot_llvm.h +++ b/core/iwasm/compilation/aot_llvm.h @@ -494,6 +494,11 @@ typedef struct AOTCompContext { bool enable_segue_f64_store; bool enable_segue_v128_store; + /* Whether the target's SIMD/vector unit supports unaligned access. + * When true, SIMD load/store IR can use align 1 without the backend + * decomposing to byte-by-byte access. */ + bool target_supports_unaligned_simd; + /* Whether optimize the JITed code */ bool optimize; diff --git a/core/iwasm/compilation/simd/simd_load_store.c b/core/iwasm/compilation/simd/simd_load_store.c index d3bbcc9650..fed2dc6cd6 100644 --- a/core/iwasm/compilation/simd/simd_load_store.c +++ b/core/iwasm/compilation/simd/simd_load_store.c @@ -17,9 +17,18 @@ simd_load(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, uint32 align, LLVMTypeRef data_type, bool enable_segue) { LLVMValueRef maddr, data; - - if (!(maddr = aot_check_memory_overflow(comp_ctx, func_ctx, offset, - data_length, enable_segue, NULL))) { + unsigned int known_align; + + (void)align; + + /* The WASM alignment immediate (align) is only a hint and must NOT be + * trusted for codegen: a module may legally specify a large alignment + * while accessing an unaligned address. Use the alignment that + * aot_check_memory_overflow can actually prove from the effective + * address instead (1 unless statically known to be more aligned). */ + if (!(maddr = + aot_check_memory_overflow(comp_ctx, func_ctx, offset, data_length, + enable_segue, &known_align))) { HANDLE_FAILURE("aot_check_memory_overflow"); return NULL; } @@ -35,7 +44,15 @@ simd_load(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, uint32 align, return NULL; } - LLVMSetAlignment(data, 1); + /* On targets whose SIMD unit natively handles misaligned access (e.g. + * x86_64, aarch64), align=1 lets the backend pick the unaligned vector + * instruction directly. On targets where it does not (e.g. Hexagon), + * align=1 expands to a byte-by-byte scalar sequence; use the proven + * alignment so the backend can select a wider load (e.g. memd on Hexagon) + * when the address is statically known to be aligned, while still being + * correct (no fault) for unaligned accesses. */ + LLVMSetAlignment( + data, comp_ctx->target_supports_unaligned_simd ? 1 : known_align); return data; } @@ -285,9 +302,13 @@ simd_store(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, uint32 align, LLVMTypeRef value_ptr_type, bool enable_segue) { LLVMValueRef maddr, result; + unsigned int known_align; + + (void)align; - if (!(maddr = aot_check_memory_overflow(comp_ctx, func_ctx, offset, - data_length, enable_segue, NULL))) + if (!(maddr = + aot_check_memory_overflow(comp_ctx, func_ctx, offset, data_length, + enable_segue, &known_align))) return false; if (!(maddr = LLVMBuildBitCast(comp_ctx->builder, maddr, value_ptr_type, @@ -301,7 +322,10 @@ simd_store(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx, uint32 align, return false; } - LLVMSetAlignment(result, 1); + /* See simd_load for the alignment rationale: use the proven alignment, + * never the untrusted WASM alignment hint. */ + LLVMSetAlignment( + result, comp_ctx->target_supports_unaligned_simd ? 1 : known_align); return true; } From 2cf770bb4f1aec59b7293f2dcf42d01ba8e80abc Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 2 May 2026 19:44:41 -0700 Subject: [PATCH 02/11] feat(hexagon): add Hexagon ISA target support Add interpreter and AOT execution support for Qualcomm Hexagon DSP: invokeNative trampoline, ELF relocation handler with PLT stubs, wamrc --target=hexagon with auto -small-data, SIMD enablement, and cross-compilation platform cmake. Signed-off-by: Brian Cain --- build-scripts/build_llvm.py | 1 + build-scripts/config_common.cmake | 2 + .../toolchains/hexagon-linux-musl.cmake | 22 + core/config.h | 7 +- core/iwasm/aot/aot_loader.c | 4 + core/iwasm/aot/arch/aot_reloc_hexagon.c | 752 ++++++++++++++++++ core/iwasm/aot/iwasm_aot.cmake | 2 + core/iwasm/common/arch/invokeNative_hexagon.s | 142 ++++ core/iwasm/common/iwasm_common.cmake | 2 + core/iwasm/compilation/aot_llvm.c | 51 +- core/iwasm/compilation/aot_llvm_extra.cpp | 3 + doc/build_wamr.md | 2 +- product-mini/platforms/hexagon/CMakeLists.txt | 144 ++++ .../wamr-test-suites/spec-test-script/all.py | 47 +- 14 files changed, 1171 insertions(+), 10 deletions(-) create mode 100644 build-scripts/toolchains/hexagon-linux-musl.cmake create mode 100644 core/iwasm/aot/arch/aot_reloc_hexagon.c create mode 100644 core/iwasm/common/arch/invokeNative_hexagon.s create mode 100644 product-mini/platforms/hexagon/CMakeLists.txt diff --git a/build-scripts/build_llvm.py b/build-scripts/build_llvm.py index e2221b8e54..d52afed404 100755 --- a/build-scripts/build_llvm.py +++ b/build-scripts/build_llvm.py @@ -243,6 +243,7 @@ def main(): "AArch64", "ARC", "ARM", + "Hexagon", "Mips", "RISCV", "WebAssembly", diff --git a/build-scripts/config_common.cmake b/build-scripts/config_common.cmake index ee00203b28..e9d9848e3e 100644 --- a/build-scripts/config_common.cmake +++ b/build-scripts/config_common.cmake @@ -45,6 +45,8 @@ elseif (WAMR_BUILD_TARGET STREQUAL "RISCV32_ILP32") add_definitions(-DBUILD_TARGET_RISCV32_ILP32) elseif (WAMR_BUILD_TARGET STREQUAL "ARC") add_definitions(-DBUILD_TARGET_ARC) +elseif (WAMR_BUILD_TARGET STREQUAL "HEXAGON") + add_definitions(-DBUILD_TARGET_HEXAGON) else () message (FATAL_ERROR "-- WAMR build target isn't set") endif () diff --git a/build-scripts/toolchains/hexagon-linux-musl.cmake b/build-scripts/toolchains/hexagon-linux-musl.cmake new file mode 100644 index 0000000000..7465f80350 --- /dev/null +++ b/build-scripts/toolchains/hexagon-linux-musl.cmake @@ -0,0 +1,22 @@ +# Copyright (C) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +# CMake toolchain file for cross-compiling to Hexagon Linux (musl) +# Requires CodeLinaro hexagon-unknown-linux-musl toolchain and clang-22/lld-22. + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR hexagon) + +set(CMAKE_C_COMPILER hexagon-unknown-linux-musl-clang) +set(CMAKE_CXX_COMPILER hexagon-unknown-linux-musl-clang++) +set(CMAKE_ASM_COMPILER hexagon-unknown-linux-musl-clang) +set(CMAKE_AR llvm-ar-22) +set(CMAKE_RANLIB llvm-ranlib-22) + +set(CMAKE_C_FLAGS_INIT "-mv68 -G0") +set(CMAKE_CXX_FLAGS_INIT "-mv68 -G0") +set(CMAKE_EXE_LINKER_FLAGS_INIT "-static") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) diff --git a/core/config.h b/core/config.h index 3f629c199a..6b2d8b6694 100644 --- a/core/config.h +++ b/core/config.h @@ -22,7 +22,8 @@ && !defined(BUILD_TARGET_RISCV32_ILP32D) \ && !defined(BUILD_TARGET_RISCV32_ILP32F) \ && !defined(BUILD_TARGET_RISCV32_ILP32) \ - && !defined(BUILD_TARGET_ARC) + && !defined(BUILD_TARGET_ARC) \ + && !defined(BUILD_TARGET_HEXAGON) /* clang-format on */ #if defined(__x86_64__) || defined(__x86_64) #define BUILD_TARGET_X86_64 @@ -52,6 +53,8 @@ #define BUILD_TARGET_RISCV32_ILP32D #elif defined(__arc__) #define BUILD_TARGET_ARC +#elif defined(__hexagon__) +#define BUILD_TARGET_HEXAGON #else #error "Build target isn't set" #endif @@ -282,7 +285,7 @@ * natural alignment. */ #ifndef WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS #if defined(BUILD_TARGET_X86_32) || defined(BUILD_TARGET_X86_64) \ - || defined(BUILD_TARGET_AARCH64) + || defined(BUILD_TARGET_AARCH64) || defined(BUILD_TARGET_HEXAGON) #define WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS 1 #else #define WASM_CPU_SUPPORTS_UNALIGNED_SIMD_ACCESS 0 diff --git a/core/iwasm/aot/aot_loader.c b/core/iwasm/aot/aot_loader.c index 121708d669..d7efed490b 100644 --- a/core/iwasm/aot/aot_loader.c +++ b/core/iwasm/aot/aot_loader.c @@ -275,6 +275,7 @@ GET_U16_FROM_ADDR(const uint8 *p) #define E_MACHINE_ARC_COMPACT 93 /* ARC International ARCompact */ #define E_MACHINE_ARC_COMPACT2 195 /* Synopsys ARCompact V2 */ #define E_MACHINE_XTENSA 94 /* Tensilica Xtensa Architecture */ +#define E_MACHINE_HEXAGON 164 /* Qualcomm Hexagon */ #define E_MACHINE_RISCV 243 /* RISC-V 32/64 */ #define E_MACHINE_WIN_I386 0x14c /* Windows i386 architecture */ #define E_MACHINE_WIN_X86_64 0x8664 /* Windows x86-64 architecture */ @@ -432,6 +433,9 @@ get_aot_file_target(AOTTargetInfo *target_info, char *target_buf, case E_MACHINE_ARC_COMPACT2: machine_type = "arc"; break; + case E_MACHINE_HEXAGON: + machine_type = "hexagon"; + break; default: set_error_buf_v(error_buf, error_buf_size, "unknown machine type %d", target_info->e_machine); diff --git a/core/iwasm/aot/arch/aot_reloc_hexagon.c b/core/iwasm/aot/arch/aot_reloc_hexagon.c new file mode 100644 index 0000000000..3fbce66858 --- /dev/null +++ b/core/iwasm/aot/arch/aot_reloc_hexagon.c @@ -0,0 +1,752 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + +#include "aot_reloc.h" + +/* + * Hexagon ELF relocation types. + * Reference: "Qualcomm Hexagon Application Binary Interface User Guide" + * https://docs.qualcomm.com/doc/80-N2040-23/80-N2040-23_REV_K_Qualcomm_Hexagon_Application_Binary_Interface_User_Guide.pdf + */ +#define R_HEX_NONE 0 +#define R_HEX_B22_PCREL 1 +#define R_HEX_B15_PCREL 2 +#define R_HEX_B7_PCREL 3 +#define R_HEX_LO16 4 +#define R_HEX_HI16 5 +#define R_HEX_32 6 +#define R_HEX_16 7 +#define R_HEX_8 8 +#define R_HEX_GPREL16_0 9 +#define R_HEX_GPREL16_1 10 +#define R_HEX_GPREL16_2 11 +#define R_HEX_GPREL16_3 12 +#define R_HEX_B13_PCREL 14 +#define R_HEX_B9_PCREL 15 +#define R_HEX_B32_PCREL_X 16 +#define R_HEX_32_6_X 17 +#define R_HEX_B22_PCREL_X 18 +#define R_HEX_B15_PCREL_X 19 +#define R_HEX_B13_PCREL_X 20 +#define R_HEX_B9_PCREL_X 21 +#define R_HEX_B7_PCREL_X 22 +#define R_HEX_16_X 23 +#define R_HEX_12_X 24 +#define R_HEX_11_X 25 +#define R_HEX_10_X 26 +#define R_HEX_9_X 27 +#define R_HEX_8_X 28 +#define R_HEX_7_X 29 +#define R_HEX_6_X 30 +#define R_HEX_32_PCREL 31 +#define R_HEX_6_PCREL_X 65 + +/* Instruction bit-field masks for relocations. */ +#define MASK_B22 0x01ff3ffe +#define MASK_B15 0x00df20fe +#define MASK_B13 0x00202ffe +#define MASK_B9 0x003000fe +#define MASK_B7 0x00001f18 +#define MASK_LO16 0x00c03fff +#define MASK_X26 0x0fff3fff + +/* Compiler runtime helpers required by AOT modules. */ +/* clang-format off */ +void __hexagon_divsi3(void); +void __hexagon_modsi3(void); +void __hexagon_udivsi3(void); +void __hexagon_umodsi3(void); +void __hexagon_divdi3(void); +void __hexagon_moddi3(void); +void __hexagon_udivdi3(void); +void __hexagon_umoddi3(void); +void __hexagon_udivmodsi4(void); +void __hexagon_udivmoddi4(void); + +void __hexagon_divsf3(void); +void __hexagon_divdf3(void); +void __hexagon_adddf3(void); +void __hexagon_subdf3(void); +void __hexagon_muldf3(void); +void __hexagon_sqrtf(void); +void __hexagon_sqrtdf2(void); +void __hexagon_fmadf4(void); +void __hexagon_fmadf5(void); + +void __hexagon_memcpy_likely_aligned_min32bytes_mult8bytes(void); +/* clang-format on */ + +/* clang-format off */ +static SymbolMap target_sym_map[] = { + REG_COMMON_SYMBOLS + /* Integer division/modulo helpers */ + REG_SYM(__hexagon_divsi3), + REG_SYM(__hexagon_modsi3), + REG_SYM(__hexagon_udivsi3), + REG_SYM(__hexagon_umodsi3), + REG_SYM(__hexagon_divdi3), + REG_SYM(__hexagon_moddi3), + REG_SYM(__hexagon_udivdi3), + REG_SYM(__hexagon_umoddi3), + REG_SYM(__hexagon_udivmodsi4), + REG_SYM(__hexagon_udivmoddi4), + /* Floating-point helpers */ + REG_SYM(__hexagon_divsf3), + REG_SYM(__hexagon_divdf3), + REG_SYM(__hexagon_adddf3), + REG_SYM(__hexagon_subdf3), + REG_SYM(__hexagon_muldf3), + REG_SYM(__hexagon_sqrtf), + REG_SYM(__hexagon_sqrtdf2), + REG_SYM(__hexagon_fmadf4), + REG_SYM(__hexagon_fmadf5), + /* Optimized memory operations */ + REG_SYM(__hexagon_memcpy_likely_aligned_min32bytes_mult8bytes), +}; +/* clang-format on */ + +static void +set_error_buf(char *error_buf, uint32 error_buf_size, const char *string) +{ + if (error_buf != NULL) + snprintf(error_buf, error_buf_size, "%s", string); +} + +SymbolMap * +get_target_symbol_map(uint32 *sym_num) +{ + *sym_num = sizeof(target_sym_map) / sizeof(SymbolMap); + return target_sym_map; +} + +void +get_current_target(char *target_buf, uint32 target_buf_size) +{ + snprintf(target_buf, target_buf_size, "hexagon"); +} + +#define INST_PARSE_PACKET_END 0x0000c000 + +static bool +is_duplex(uint32 insn) +{ + return (INST_PARSE_PACKET_END & insn) == 0; +} + +/* Instruction opcode → relocation mask table for R_HEX_6_X */ +/* clang-format off */ +static const struct { uint32 cmp_mask; uint32 reloc_mask; } r6_masks[] = { + { 0x38000000, 0x0000201f }, { 0x39000000, 0x0000201f }, + { 0x3e000000, 0x00001f80 }, { 0x3f000000, 0x00001f80 }, + { 0x40000000, 0x000020f8 }, { 0x41000000, 0x000007e0 }, + { 0x42000000, 0x000020f8 }, { 0x43000000, 0x000007e0 }, + { 0x44000000, 0x000020f8 }, { 0x45000000, 0x000007e0 }, + { 0x46000000, 0x000020f8 }, { 0x47000000, 0x000007e0 }, + { 0x6a000000, 0x00001f80 }, { 0x7c000000, 0x001f2000 }, + { 0x9a000000, 0x00000f60 }, { 0x9b000000, 0x00000f60 }, + { 0x9c000000, 0x00000f60 }, { 0x9d000000, 0x00000f60 }, + { 0x9f000000, 0x001f0100 }, { 0xab000000, 0x0000003f }, + { 0xad000000, 0x0000003f }, { 0xaf000000, 0x00030078 }, + { 0xd7000000, 0x006020e0 }, { 0xd8000000, 0x006020e0 }, + { 0xdb000000, 0x006020e0 }, { 0xdf000000, 0x006020e0 }, +}; +/* clang-format on */ + +static uint32 +get_mask_r6(uint32 insn, char *error_buf, uint32 error_buf_size) +{ + uint32 i; + + if (is_duplex(insn)) + return 0x03f00000; + + for (i = 0; i < sizeof(r6_masks) / sizeof(r6_masks[0]); i++) { + if ((insn & 0xff000000) == r6_masks[i].cmp_mask) + return r6_masks[i].reloc_mask; + } + + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "unrecognized instruction for 6_X relocation."); + return 0; +} + +static uint32 +get_mask_r8(uint32 insn) +{ + if ((0xff000000 & insn) == 0xde000000) + return 0x00e020e8; + if ((0xff000000 & insn) == 0x3c000000) + return 0x0000207f; + return 0x00001fe0; +} + +static uint32 +get_mask_r11(uint32 insn) +{ + if (is_duplex(insn)) + return 0x03f00000; + if ((0xff000000 & insn) == 0xa1000000) + return 0x060020ff; + return 0x06003fe0; +} + +static uint32 +get_mask_r16(uint32 insn, char *error_buf, uint32 error_buf_size) +{ + uint32 i; + + if (is_duplex(insn)) + return 0x03f00000; + + /* Clear end-packet-parse bits for matching */ + insn = insn & ~INST_PARSE_PACKET_END; + + if ((0xff000000 & insn) == 0x48000000) + return 0x061f20ff; + if ((0xff000000 & insn) == 0x49000000) + return 0x061f3fe0; + if ((0xff000000 & insn) == 0x78000000) + return 0x00df3fe0; + if ((0xff000000 & insn) == 0xb0000000) + return 0x0fe03fe0; + + if ((0xff802000 & insn) == 0x74000000) + return 0x00001fe0; + if ((0xff802000 & insn) == 0x74002000) + return 0x00001fe0; + if ((0xff802000 & insn) == 0x74800000) + return 0x00001fe0; + if ((0xff802000 & insn) == 0x74802000) + return 0x00001fe0; + + /* Fall back to r6 table */ + for (i = 0; i < sizeof(r6_masks) / sizeof(r6_masks[0]); i++) { + if ((insn & 0xff000000) == r6_masks[i].cmp_mask) + return r6_masks[i].reloc_mask; + } + + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "unrecognized instruction for 16_X relocation."); + return 0; +} + +/* Scatter bits from 'data' into positions indicated by set bits in 'mask'. */ +static uint32 +apply_mask(uint32 mask, uint32 data) +{ + uint32 result = 0; + uint32 off = 0; + uint32 bit; + + for (bit = 0; bit < 32; bit++) { + uint32 val_bit = (data >> off) & 1; + uint32 mask_bit = (mask >> bit) & 1; + if (mask_bit) { + result |= (val_bit << bit); + off++; + } + } + return result; +} + +#define PLT_ITEM_SIZE 12 + +/* Instruction templates with address = 0. + * The constant extender uses parse bits 0b01 (not-end-of-packet); 0b10 would + * mark the "{ immext; r28 = ##addr }" packet as :endloop0, so a trampoline + * entered while a hardware loop is active would decrement LC0 and branch to + * SA0 instead of jumping. The r28 transfer and the jumpr each end their + * packet (parse bits 0b11). These words match llvm-mc output exactly. */ +#define PLT_IMMEXT_TEMPLATE 0x00004000 +#define PLT_R28_TEMPLATE 0x7800c01c +#define PLT_JUMPR_R28 0x529cc000 + +#define MASK_R28_IMM 0x00df3fe0 + +uint32 +get_plt_item_size(void) +{ + return PLT_ITEM_SIZE; +} + +uint32 +get_plt_table_size(void) +{ + return get_plt_item_size() * (sizeof(target_sym_map) / sizeof(SymbolMap)); +} + +void +init_plt_table(uint8 *plt) +{ + uint32 i, num = sizeof(target_sym_map) / sizeof(SymbolMap); + + for (i = 0; i < num; i++) { + uint32 addr = (uint32)(uintptr_t)target_sym_map[i].symbol_addr; + uint32 *p = (uint32 *)plt; + + p[0] = PLT_IMMEXT_TEMPLATE | apply_mask(MASK_X26, addr >> 6); + p[1] = PLT_R28_TEMPLATE | apply_mask(MASK_R28_IMM, addr & 0x3F); + p[2] = PLT_JUMPR_R28; + + plt += PLT_ITEM_SIZE; + } +} + +static bool +check_reloc_offset(uint32 target_section_size, uint64 reloc_offset, + uint32 reloc_data_size, char *error_buf, + uint32 error_buf_size) +{ + if (!(reloc_offset < (uint64)target_section_size + && reloc_offset + reloc_data_size <= (uint64)target_section_size)) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: invalid relocation offset."); + return false; + } + return true; +} + +bool +apply_relocation(AOTModule *module, uint8 *target_section_addr, + uint32 target_section_size, uint64 reloc_offset, + int64 reloc_addend, uint32 reloc_type, void *symbol_addr, + int32 symbol_index, char *error_buf, uint32 error_buf_size) +{ + switch (reloc_type) { + case R_HEX_32: + { + /* Direct 32-bit relocation: S + A */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) = val; + break; + } + + case R_HEX_32_PCREL: + { + /* 32-bit PC-relative: S + A - P */ + int32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (int32)((intptr_t)symbol_addr + (intptr_t)reloc_addend + - (intptr_t)(target_section_addr + reloc_offset)); + *(int32 *)(target_section_addr + reloc_offset) = val; + break; + } + + case R_HEX_B22_PCREL: + { + /* + * 22-bit PC-relative branch: (S + A - P) >> 2 + * 22-bit signed field, word-aligned: +-8MB byte range. + * If the direct branch is out of range and the symbol has a + * PLT entry (symbol_index >= 0), fall back to a PLT trampoline, + * which can reach an arbitrary 32-bit absolute address. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + + if (result >= (8 * BH_MB) || result < -(8 * BH_MB)) { + /* Direct branch out of range: try PLT trampoline. The PLT + * entry is a fixed 3-instruction packet that jumps to the + * resolved symbol address, so a non-zero addend cannot be + * carried through it. */ + if (symbol_index < 0 || reloc_addend != 0) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B22_PCREL target out of range."); + return false; + } + uint8 *plt = (uint8 *)module->code + module->code_size + - get_plt_table_size() + + get_plt_item_size() * symbol_index; + result = (intptr_t)((uintptr_t)plt + - (uintptr_t)(target_section_addr + + reloc_offset)); + if (result >= (8 * BH_MB) || result < -(8 * BH_MB)) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B22_PCREL PLT trampoline out of range."); + return false; + } + } + + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B22, (uint32)(result >> 2)); + break; + } + + case R_HEX_B15_PCREL: + { + /* 15-bit PC-relative branch: (S + A - P) >> 2, +-64KB range */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + + if (result >= 0x10000 || result < -0x10000) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B15_PCREL target out of range."); + return false; + } + + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B15, (uint32)(result >> 2)); + break; + } + + case R_HEX_B13_PCREL: + { + /* 13-bit PC-relative branch: (S + A - P) >> 2, +-16KB range */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + + if (result >= 0x4000 || result < -0x4000) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B13_PCREL target out of range."); + return false; + } + + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B13, (uint32)(result >> 2)); + break; + } + + case R_HEX_B9_PCREL: + { + /* 9-bit PC-relative branch: (S + A - P) >> 2, +-1KB range */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + + if (result >= 0x400 || result < -0x400) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B9_PCREL target out of range."); + return false; + } + + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B9, (uint32)(result >> 2)); + break; + } + + case R_HEX_B7_PCREL: + { + /* 7-bit PC-relative branch: (S + A - P) >> 2, +-256B range */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + + if (result >= 0x100 || result < -0x100) { + set_error_buf(error_buf, error_buf_size, + "AOT module load failed: " + "B7_PCREL target out of range."); + return false; + } + + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B7, (uint32)(result >> 2)); + break; + } + + case R_HEX_LO16: + { + /* Low 16 bits of absolute address: (S + A) & 0xFFFF */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_LO16, val & 0xFFFF); + break; + } + + case R_HEX_HI16: + { + /* High 16 bits of absolute address: (S + A) >> 16 */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_LO16, val >> 16); + break; + } + + case R_HEX_B32_PCREL_X: + { + /* + * Extended 32-bit PC-relative for constant extender. + * Upper 26 bits: (S + A - P) >> 6 + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_X26, (uint32)(result >> 6)); + break; + } + + case R_HEX_32_6_X: + { + /* + * Extended 32-bit absolute for constant extender. + * Upper 26 bits: (S + A) >> 6 + */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_X26, val >> 6); + break; + } + + case R_HEX_B22_PCREL_X: + { + /* + * Extended 22-bit PC-relative: low 6 bits of (S + A - P). + * Paired with R_HEX_B32_PCREL_X on the constant extender. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B22, (uint32)result & 0x3F); + break; + } + + case R_HEX_B15_PCREL_X: + { + /* + * Extended 15-bit PC-relative: low 6 bits of (S + A - P). + * Paired with R_HEX_B32_PCREL_X on the constant extender. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B15, (uint32)result & 0x3F); + break; + } + + case R_HEX_B13_PCREL_X: + { + /* + * Extended 13-bit PC-relative: low 6 bits of (S + A - P). + * Paired with R_HEX_B32_PCREL_X on the constant extender. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B13, (uint32)result & 0x3F); + break; + } + + case R_HEX_B9_PCREL_X: + { + /* + * Extended 9-bit PC-relative: low 6 bits of (S + A - P). + * Paired with R_HEX_B32_PCREL_X on the constant extender. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B9, (uint32)result & 0x3F); + break; + } + + case R_HEX_B7_PCREL_X: + { + /* + * Extended 7-bit PC-relative: low 6 bits of (S + A - P). + * Paired with R_HEX_B32_PCREL_X on the constant extender. + */ + intptr_t result; + CHECK_RELOC_OFFSET(sizeof(uint32)); + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(MASK_B7, (uint32)result & 0x3F); + break; + } + + case R_HEX_6_X: + { + /* Low 6 bits for constant-extended absolute */ + uint32 val, insn, mask_r6; + CHECK_RELOC_OFFSET(sizeof(uint32)); + insn = *(uint32 *)(target_section_addr + reloc_offset); + mask_r6 = get_mask_r6(insn, error_buf, error_buf_size); + if (!mask_r6) + return false; + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(mask_r6, val & 0x3F); + break; + } + + case R_HEX_6_PCREL_X: + { + /* Low 6 bits for constant-extended PC-relative */ + intptr_t result; + uint32 insn, mask_r6; + CHECK_RELOC_OFFSET(sizeof(uint32)); + insn = *(uint32 *)(target_section_addr + reloc_offset); + mask_r6 = get_mask_r6(insn, error_buf, error_buf_size); + if (!mask_r6) + return false; + result = + (intptr_t)((uintptr_t)symbol_addr + (intptr_t)reloc_addend + - (uintptr_t)(target_section_addr + reloc_offset)); + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(mask_r6, (uint32)result & 0x3F); + break; + } + + case R_HEX_16_X: + { + uint32 val, insn, mask_r16; + CHECK_RELOC_OFFSET(sizeof(uint32)); + insn = *(uint32 *)(target_section_addr + reloc_offset); + mask_r16 = get_mask_r16(insn, error_buf, error_buf_size); + if (!mask_r16) + return false; + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(mask_r16, val & 0x3F); + break; + } + + case R_HEX_12_X: + { + /* Extended 12-bit absolute: (S + A) with fixed mask */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(0x000007e0, val); + break; + } + + case R_HEX_11_X: + { + /* Extended 11-bit absolute: low 6 bits of (S + A) */ + uint32 val, insn; + CHECK_RELOC_OFFSET(sizeof(uint32)); + insn = *(uint32 *)(target_section_addr + reloc_offset); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(get_mask_r11(insn), val & 0x3F); + break; + } + + case R_HEX_10_X: + { + /* Extended 10-bit absolute: low 6 bits of (S + A) */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(0x00203fe0, val & 0x3F); + break; + } + + case R_HEX_9_X: + { + /* Extended 9-bit absolute: low 6 bits of (S + A) */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(0x00003fe0, val & 0x3F); + break; + } + + case R_HEX_8_X: + { + /* Extended 8-bit absolute: (S + A) */ + uint32 val, insn; + CHECK_RELOC_OFFSET(sizeof(uint32)); + insn = *(uint32 *)(target_section_addr + reloc_offset); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(get_mask_r8(insn), val); + break; + } + + case R_HEX_7_X: + { + /* Extended 7-bit absolute: low 6 bits of (S + A) go in the base + * instruction; the constant extender carries bits [31:6]. */ + uint32 val; + CHECK_RELOC_OFFSET(sizeof(uint32)); + val = (uint32)(uintptr_t)symbol_addr + (int32)reloc_addend; + *(uint32 *)(target_section_addr + reloc_offset) |= + apply_mask(0x00001f18, val & 0x3F); + break; + } + + case R_HEX_16: + { + /* Direct 16-bit relocation */ + uint16 val; + CHECK_RELOC_OFFSET(sizeof(uint16)); + val = (uint16)((uintptr_t)symbol_addr + (int32)reloc_addend); + *(uint16 *)(target_section_addr + reloc_offset) = val; + break; + } + + case R_HEX_8: + { + /* Direct 8-bit relocation: (S + A) truncated to 8 bits */ + uint8 val; + CHECK_RELOC_OFFSET(sizeof(uint8)); + val = (uint8)((uintptr_t)symbol_addr + (int32)reloc_addend); + *(uint8 *)(target_section_addr + reloc_offset) = val; + break; + } + + case R_HEX_NONE: + break; + + default: + if (error_buf != NULL) + snprintf(error_buf, error_buf_size, + "Load relocation section failed: " + "invalid relocation type %" PRIu32 ".", + reloc_type); + return false; + } + + return true; +} diff --git a/core/iwasm/aot/iwasm_aot.cmake b/core/iwasm/aot/iwasm_aot.cmake index 1084681aca..b89173a41a 100644 --- a/core/iwasm/aot/iwasm_aot.cmake +++ b/core/iwasm/aot/iwasm_aot.cmake @@ -49,6 +49,8 @@ elseif (WAMR_BUILD_TARGET MATCHES "RISCV*") set (arch_source ${IWASM_AOT_DIR}/arch/aot_reloc_riscv.c) elseif (WAMR_BUILD_TARGET STREQUAL "ARC") set (arch_source ${IWASM_AOT_DIR}/arch/aot_reloc_arc.c) +elseif (WAMR_BUILD_TARGET STREQUAL "HEXAGON") + set (arch_source ${IWASM_AOT_DIR}/arch/aot_reloc_hexagon.c) else () message (FATAL_ERROR "Build target isn't set") endif () diff --git a/core/iwasm/common/arch/invokeNative_hexagon.s b/core/iwasm/common/arch/invokeNative_hexagon.s new file mode 100644 index 0000000000..03f15efc0d --- /dev/null +++ b/core/iwasm/common/arch/invokeNative_hexagon.s @@ -0,0 +1,142 @@ +/* + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + */ + + .text + .align 2 + .globl invokeNative + .type invokeNative, @function + +/* + * void invokeNative(void (*native_code)(), uint32 argv[], uint32 argc) + * + * r0 = native_code, r1 = argv, r2 = argc + * Loads up to 6 words into r0-r5; remaining args are pushed to stack. + */ + +invokeNative: + { + allocframe(#16) + } + { + memd(r29+#0) = r17:16 + } + { + memd(r29+#8) = r19:18 + } + + // Save arguments to callee-saved registers. + // Also clear r0 so that if argc == 0 we don't call the native + // function with r0 still holding the function pointer. Reads + // happen before writes within a Hexagon packet, so r16 receives + // the original r0. + { + r16 = r0 // r16 = function_ptr + r17 = r1 // r17 = argv + r0 = #0 + } + { + r18 = r2 // r18 = argc + p0 = cmp.gt(r2, #0) + } + { + if (!p0) jump .Lcall // argc == 0: skip arg loading + } + + // Load register arguments from argv (up to 6 words in r0-r5) + { + r0 = memw(r17+#0) // r0 = argv[0] (exec_env) + p0 = cmp.gt(r18, #1) + } + { if (!p0) jump .Lcall } + + { + r1 = memw(r17+#4) // r1 = argv[1] + p0 = cmp.gt(r18, #2) + } + { if (!p0) jump .Lcall } + + { + r2 = memw(r17+#8) // r2 = argv[2] + p0 = cmp.gt(r18, #3) + } + { if (!p0) jump .Lcall } + + { + r3 = memw(r17+#12) // r3 = argv[3] + p0 = cmp.gt(r18, #4) + } + { if (!p0) jump .Lcall } + + { + r4 = memw(r17+#16) // r4 = argv[4] + p0 = cmp.gt(r18, #5) + } + { if (!p0) jump .Lcall } + + { + r5 = memw(r17+#20) // r5 = argv[5] + p0 = cmp.gt(r18, #6) + } + { if (!p0) jump .Lcall } + + // Stack arguments: argc > 6. + // Copy argv[6..argc-1] to the stack, maintaining 8-byte alignment. + { + r19 = add(r18, #-6) // r19 = number of stack args + } + { + r7 = asl(r19, #2) // r7 = stack_args * 4 (bytes) + } + { + r7 = add(r7, #7) // round up to 8-byte alignment + } + { + r7 = and(r7, #-8) + } + { + r29 = sub(r29, r7) // allocate aligned stack space + } + { + r6 = add(r17, #24) // r6 = &argv[6] (source) + r8 = r29 // r8 = stack destination + } + +.Lcopy_loop: + { + r9 = memw(r6++#4) // load next arg from argv + } + { + memw(r8++#4) = r9 // store to stack + r19 = add(r19, #-1) // decrement counter + } + { + p0 = cmp.gt(r19, #0) + if (p0.new) jump:t .Lcopy_loop + } + +.Lcall: + { + callr r16 // call native function + } + +.Lreturn: + { + r17:16 = memd(r30+#-16) + } + { + r19:18 = memd(r30+#-8) + } + { + deallocframe + } + { + jumpr r31 + } + + .size invokeNative, .-invokeNative + +#if defined(__linux__) && defined(__ELF__) +.section .note.GNU-stack,"",%progbits +#endif diff --git a/core/iwasm/common/iwasm_common.cmake b/core/iwasm/common/iwasm_common.cmake index c3653f156c..7d51bbfbde 100644 --- a/core/iwasm/common/iwasm_common.cmake +++ b/core/iwasm/common/iwasm_common.cmake @@ -155,6 +155,8 @@ elseif (WAMR_BUILD_TARGET MATCHES "RISCV*") set (source_all ${c_source_all} ${IWASM_COMMON_DIR}/arch/invokeNative_riscv.S) elseif (WAMR_BUILD_TARGET STREQUAL "ARC") set (source_all ${c_source_all} ${IWASM_COMMON_DIR}/arch/invokeNative_arc.s) +elseif (WAMR_BUILD_TARGET STREQUAL "HEXAGON") + set (source_all ${c_source_all} ${IWASM_COMMON_DIR}/arch/invokeNative_hexagon.s) else () message (FATAL_ERROR "Build target isn't set") endif () diff --git a/core/iwasm/compilation/aot_llvm.c b/core/iwasm/compilation/aot_llvm.c index f0baa9b7a6..8a06fb2990 100644 --- a/core/iwasm/compilation/aot_llvm.c +++ b/core/iwasm/compilation/aot_llvm.c @@ -191,7 +191,7 @@ aot_target_precheck_can_use_musttail(const AOTCompContext *comp_ctx) return false; } /* - * x86-64/i386: true + * x86-64/i386/hexagon: true * * others: assume true for now */ @@ -2286,7 +2286,8 @@ static ArchItem valid_archs[] = { { "thumbv8.1m.main", true }, { "riscv32", true }, { "riscv64", true }, - { "arc", true } + { "arc", true }, + { "hexagon", false } }; static const char *valid_abis[] = { @@ -2635,7 +2636,7 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option) char *triple_norm_new = NULL, *cpu_new = NULL; char *err = NULL, *fp_round = "round.tonearest", *fp_exce = "fpexcept.strict"; - char triple_buf[128] = { 0 }, features_buf[128] = { 0 }; + char triple_buf[128] = { 0 }, features_buf[512] = { 0 }; uint32 opt_level, size_level, i; LLVMCodeModel code_model; LLVMTargetDataRef target_data_ref; @@ -3189,6 +3190,47 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option) } #endif + /* Hexagon: disable small-data addressing; AOT code has no GP + * register set up so GP-relative relocations cannot be resolved. */ + { + bool is_hexagon = false; + if (arch && !strcmp(arch, "hexagon")) + is_hexagon = true; + else if (triple_norm && !strncmp(triple_norm, "hexagon", 7) + && (triple_norm[7] == '-' || triple_norm[7] == '\0')) + is_hexagon = true; + + if (is_hexagon) { + if (features[0] != '\0') { + /* Check for "-small-data" as a complete comma-delimited + * token to avoid false matches on "-small-data-limit=N". */ + const char *sd = features; + bool has_small_data = false; + while ((sd = strstr(sd, "-small-data")) != NULL) { + if ((sd == features || sd[-1] == ',') + && (sd[11] == '\0' || sd[11] == ',')) { + has_small_data = true; + break; + } + sd++; + } + if (!has_small_data) { + int ret = snprintf(features_buf, sizeof(features_buf), + "%s,-small-data", features); + if (ret < 0 || (size_t)ret >= sizeof(features_buf)) { + aot_set_last_error( + "Hexagon features string too long"); + goto fail; + } + features = features_buf; + } + } + else { + features = "-small-data"; + } + } + } + /* Get target with triple, note that LLVMGetTargetFromTriple() return 0 when success, but not true. */ if (LLVMGetTargetFromTriple(triple_norm, &target, &err) != 0) { @@ -3406,7 +3448,8 @@ aot_create_comp_context(const AOTCompData *comp_data, aot_comp_option_t option) if (option->enable_simd && strcmp(comp_ctx->target_arch, "x86_64") != 0 && strncmp(comp_ctx->target_arch, "aarch64", 7) != 0 - && strcmp(comp_ctx->target_arch, "arc") != 0) { + && strcmp(comp_ctx->target_arch, "arc") != 0 + && strcmp(comp_ctx->target_arch, "hexagon") != 0) { /* Disable simd if it isn't supported by target arch */ option->enable_simd = false; } diff --git a/core/iwasm/compilation/aot_llvm_extra.cpp b/core/iwasm/compilation/aot_llvm_extra.cpp index 3a3dc29a32..2a43a160cd 100644 --- a/core/iwasm/compilation/aot_llvm_extra.cpp +++ b/core/iwasm/compilation/aot_llvm_extra.cpp @@ -179,6 +179,9 @@ aot_check_simd_compatibility(const char *arch_c_str, const char *cpu_c_str) else if (targetArch == llvm::Triple::arc) { return true; } + else if (targetArch == llvm::Triple::hexagon) { + return true; + } else { return false; } diff --git a/doc/build_wamr.md b/doc/build_wamr.md index cd931a4662..61dcd8b406 100644 --- a/doc/build_wamr.md +++ b/doc/build_wamr.md @@ -132,7 +132,7 @@ Above compilation flags map to macros in `config.h`. For example, `WAMR_BUILD_AO - **WAMR_BUILD_PLATFORM**: set the target platform. Match the platform folder name under [core/shared/platform](../core/shared/platform). -- **WAMR_BUILD_TARGET**: set the target CPU architecture. Supported targets: X86_64, X86_32, AARCH64, ARM, THUMB, XTENSA, ARC, RISCV32, RISCV64, and MIPS. +- **WAMR_BUILD_TARGET**: set the target CPU architecture. Supported targets: X86_64, X86_32, AARCH64, ARM, THUMB, XTENSA, ARC, RISCV32, RISCV64, MIPS, and HEXAGON. - For ARM and THUMB, use `[][_VFP]`. `` is the ARM sub-architecture. `_VFP` means arguments and returns use VFP coprocessor registers s0-s15 (d0-d7). Both are optional, for example ARMV7, ARMV7_VFP, THUMBV7, or THUMBV7_VFP. - For AARCH64, use `[]`. VFP is on by default. `` is optional, for example AARCH64, AARCH64V8, or AARCH64V8.1. - For RISCV64, use `[_abi]`. `_abi` is optional. Supported: RISCV64, RISCV64_LP64D, and RISCV64_LP64. RISCV64 and RISCV64_LP64D both use [LP64D](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc#named-abis) (LP64 with hardware floating-point for FLEN=64). RISCV64_LP64 uses [LP64](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-cc.adoc#named-abis) (integer calling convention only; no hardware floating-point calling convention). diff --git a/product-mini/platforms/hexagon/CMakeLists.txt b/product-mini/platforms/hexagon/CMakeLists.txt new file mode 100644 index 0000000000..dc2c46a161 --- /dev/null +++ b/product-mini/platforms/hexagon/CMakeLists.txt @@ -0,0 +1,144 @@ +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +cmake_minimum_required (VERSION 3.14) + +include(CheckPIESupported) + +project (iwasm) + +option(BUILD_SHARED_LIBS "Build using shared libraries" OFF) + +set (CMAKE_VERBOSE_MAKEFILE OFF) + +# Hexagon uses the Linux kernel +set (WAMR_BUILD_PLATFORM "linux") +set (WAMR_BUILD_TARGET "HEXAGON") + +# Reset default linker flags +set (CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +set (CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS "") + +set (CMAKE_C_STANDARD 99) +set (CMAKE_CXX_STANDARD 17) + +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif () + +if (NOT DEFINED WAMR_BUILD_INTERP) + # Enable Interpreter by default + set (WAMR_BUILD_INTERP 1) +endif () + +if (NOT DEFINED WAMR_BUILD_AOT) + # Enable AOT by default + set (WAMR_BUILD_AOT 1) +endif () + +if (NOT DEFINED WAMR_BUILD_JIT) + # Disable JIT by default + set (WAMR_BUILD_JIT 0) +endif () + +if (NOT DEFINED WAMR_BUILD_FAST_JIT) + # Fast JIT not supported on Hexagon + set (WAMR_BUILD_FAST_JIT 0) +endif () + +if (NOT DEFINED WAMR_BUILD_LIBC_BUILTIN) + set (WAMR_BUILD_LIBC_BUILTIN 1) +endif () + +if (NOT DEFINED WAMR_BUILD_LIBC_WASI) + set (WAMR_BUILD_LIBC_WASI 1) +endif () + +if (NOT DEFINED WAMR_BUILD_FAST_INTERP) + set (WAMR_BUILD_FAST_INTERP 1) +endif () + +if (NOT DEFINED WAMR_BUILD_MULTI_MODULE) + set (WAMR_BUILD_MULTI_MODULE 0) +endif () + +if (NOT DEFINED WAMR_BUILD_LIB_PTHREAD) + set (WAMR_BUILD_LIB_PTHREAD 0) +endif () + +if (NOT DEFINED WAMR_BUILD_LIB_WASI_THREADS) + set (WAMR_BUILD_LIB_WASI_THREADS 0) +endif() + +if (NOT DEFINED WAMR_BUILD_MINI_LOADER) + set (WAMR_BUILD_MINI_LOADER 0) +endif () + +if (NOT DEFINED WAMR_BUILD_SIMD) + set (WAMR_BUILD_SIMD 1) +endif () + +if (NOT DEFINED WAMR_BUILD_REF_TYPES) + set (WAMR_BUILD_REF_TYPES 1) +endif () + +if (NOT DEFINED WAMR_BUILD_DEBUG_INTERP) + set (WAMR_BUILD_DEBUG_INTERP 0) +endif () + +if (WAMR_BUILD_DEBUG_INTERP EQUAL 1) + set (WAMR_BUILD_FAST_INTERP 0) + set (WAMR_BUILD_MINI_LOADER 0) + set (WAMR_BUILD_SIMD 0) +endif () + +set (WAMR_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) + +include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake) + +check_pie_supported() + +set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") + +include (${SHARED_DIR}/utils/uncommon/shared_uncommon.cmake) + +# Share main.c with Linux platform since Hexagon runs Linux kernel +add_executable (iwasm + ${CMAKE_CURRENT_SOURCE_DIR}/../linux/main.c + ${UNCOMMON_SHARED_SOURCE} +) + +set_version_info (iwasm) + +target_link_libraries(iwasm vmlib) + +install (TARGETS iwasm DESTINATION bin) + +add_library (vmlib ${WAMR_RUNTIME_LIB_SOURCE}) + +set_version_info (vmlib) + +target_include_directories(vmlib INTERFACE + $ +) + +set (WAMR_PUBLIC_HEADERS + ${WAMR_ROOT_DIR}/core/iwasm/include/wasm_c_api.h + ${WAMR_ROOT_DIR}/core/iwasm/include/wasm_export.h + ${WAMR_ROOT_DIR}/core/iwasm/include/lib_export.h +) + +set_target_properties (vmlib PROPERTIES + OUTPUT_NAME iwasm + PUBLIC_HEADER "${WAMR_PUBLIC_HEADERS}" +) + +target_link_libraries (vmlib ${LLVM_AVAILABLE_LIBS} ${UV_A_LIBS} -lm -lpthread) + +install (TARGETS vmlib + EXPORT iwasmTargets + DESTINATION lib + PUBLIC_HEADER DESTINATION include +) + +install_iwasm_package () diff --git a/tests/wamr-test-suites/spec-test-script/all.py b/tests/wamr-test-suites/spec-test-script/all.py index 970127d0b2..bc4df3559b 100644 --- a/tests/wamr-test-suites/spec-test-script/all.py +++ b/tests/wamr-test-suites/spec-test-script/all.py @@ -58,6 +58,7 @@ def get_iwasm_cmd(platform: str) -> str: "AARCH64_VFP", "ARMV7", "ARMV7_VFP", + "HEXAGON", "RISCV32", "RISCV32_ILP32F", "RISCV32_ILP32D", @@ -95,6 +96,25 @@ def ignore_the_case( if "i386" == target and case_name in ["float_exprs", "conversions"]: return True + # Hexagon-specific failures. These skips are workarounds for + # toolchain/spec gaps, not WAMR bugs: + # - float_exprs/conversions/f32_bitwise: Hexagon does not canonicalize + # NaN payloads (sNaN propagation differs from spec expectations). + # - simd_f32x4_pmin_pmax / simd_f64x2_pmin_pmax: NaN propagation differs. + # - simd_lane / simd_splat: NaN-bearing lane/splat edge cases. + # - i32 / i64: clang miscompiles rotl/rotr on Hexagon because asl/asr + # use signed shift amounts; observed as e.g. rotl returning + # 0xed3 instead of 0x579beed3. These cases are spread across the + # suites so we skip the whole suite until the clang fix lands. + # TODO: when the underlying clang fix is in, narrow this skip to the + # specific rotl/rotr asserts and re-enable the remaining arithmetic. + if "hexagon" == target and case_name in [ + "float_exprs", "conversions", "f32_bitwise", "i32", "i64", + "simd_f32x4_pmin_pmax", "simd_f64x2_pmin_pmax", + "simd_lane", "simd_splat", + ]: + return True + # esp32s3 qemu doesn't have PSRAM emulation if qemu_flag and target == 'xtensa' and case_name in ["memory_size"]: return True @@ -220,9 +240,19 @@ def test_case( CMD.append("--eh") if qemu_flag: - CMD.append("--qemu") - CMD.append("--qemu-firmware") - CMD.append(qemu_firmware) + if qemu_firmware: + CMD.append("--qemu") + CMD.append("--qemu-firmware") + CMD.append(qemu_firmware) + # Increase timeouts for QEMU emulation. Defaults: 30s start, 20s + # test, 2s start-fail. Under qemu user-mode (e.g. qemu-hexagon), + # both startup and trap-on-startup can take >>2s, so bump all three. + CMD.append("--start-timeout") + CMD.append("120") + CMD.append("--test-timeout") + CMD.append("120") + CMD.append("--start-fail-timeout") + CMD.append("30") if not clean_up_flag: CMD.append("--no_cleanup") @@ -598,9 +628,20 @@ def main(): ) parser.add_argument('--no-pty', action='store_true', help="Use direct pipes instead of pseudo-tty") + parser.add_argument( + "--interpreter", + default="", + dest="interpreter", + help="Specify the iwasm interpreter path (overrides the default)", + ) options = parser.parse_args() + # Override global IWASM_CMD if --interpreter is specified + global IWASM_CMD + if options.interpreter: + IWASM_CMD = options.interpreter + # Convert target to lower case for internal use, e.g. X86_64 -> x86_64 # target is always exist, so no need to check it options.target = options.target.lower() From 49737a7f73d506b9bad35a75dc3f8e624fddec43 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Sat, 2 May 2026 19:44:41 -0700 Subject: [PATCH 03/11] ci(hexagon): add spec test workflow under qemu-hexagon user-mode Signed-off-by: Brian Cain --- .github/workflows/compilation_on_hexagon.yml | 209 ++++++++++++++++++ ...ux-musl.cmake => hexagon_linux_musl.cmake} | 2 +- core/iwasm/common/wasm_runtime_common.c | 6 +- .../wamr-test-suites/spec-test-script/all.py | 6 +- .../spec-test-script/runtest.py | 23 +- tests/wamr-test-suites/test_wamr.sh | 4 +- 6 files changed, 234 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/compilation_on_hexagon.yml rename build-scripts/toolchains/{hexagon-linux-musl.cmake => hexagon_linux_musl.cmake} (90%) diff --git a/.github/workflows/compilation_on_hexagon.yml b/.github/workflows/compilation_on_hexagon.yml new file mode 100644 index 0000000000..f47b66d9e4 --- /dev/null +++ b/.github/workflows/compilation_on_hexagon.yml @@ -0,0 +1,209 @@ +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +name: compilation on hexagon (qemu user-mode) + +on: + pull_request: + types: + - opened + - synchronize + paths: + - ".github/workflows/build_llvm_libraries.yml" + - ".github/workflows/compilation_on_hexagon.yml" + - "build-scripts/**" + - "core/**" + - "!core/deps/**" + - "product-mini/**" + - "tests/wamr-test-suites/**" + - "wamr-compiler/**" + push: + branches: + - main + - "dev/**" + paths: + - ".github/workflows/build_llvm_libraries.yml" + - ".github/workflows/compilation_on_hexagon.yml" + - "build-scripts/**" + - "core/**" + - "!core/deps/**" + - "product-mini/**" + - "tests/wamr-test-suites/**" + - "wamr-compiler/**" + workflow_dispatch: + +# Cancel any in-flight jobs for the same PR/branch so there's only one active +# at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + build_llvm_libraries: + permissions: + contents: read + actions: write + uses: ./.github/workflows/build_llvm_libraries.yml + with: + os: "ubuntu-22.04" + arch: "X86 Hexagon" + + build_wamrc: + needs: [build_llvm_libraries] + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@v6.0.2 + + - name: Get LLVM libraries + id: retrieve_llvm_libs + uses: actions/cache@v5 + with: + path: | + ./core/deps/llvm/build/bin + ./core/deps/llvm/build/include + ./core/deps/llvm/build/lib + ./core/deps/llvm/build/libexec + ./core/deps/llvm/build/share + key: ${{ needs.build_llvm_libraries.outputs.cache_key }} + + - name: Quit if cache miss + if: steps.retrieve_llvm_libs.outputs.cache-hit != 'true' + run: echo "::error::can not get prebuilt llvm libraries" && exit 1 + + - name: Build wamrc + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --config Release --parallel $(nproc) + working-directory: wamr-compiler + + - name: Upload wamrc + uses: actions/upload-artifact@v7.0.1 + with: + name: wamrc-hexagon + path: wamr-compiler/build/wamrc + + build_iwasm: + runs-on: ubuntu-22.04 + steps: + - name: checkout + uses: actions/checkout@v6.0.2 + + - name: Install QEMU user-mode + run: sudo apt-get update && sudo apt-get install -y qemu-user + + - name: Install clang-22 and lld-22 + run: | + wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc + echo "deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-22 main" | sudo tee /etc/apt/sources.list.d/llvm-22.list + sudo apt-get update + sudo apt-get install -y clang-22 lld-22 + + - name: Install CodeLinaro Hexagon toolchain + run: | + wget -q https://artifacts.codelinaro.org/artifactory/codelinaro-toolchain-for-hexagon/22.1.4_/hexagon-debs-22.1.4_.tar.gz + mkdir hexagon-debs + tar xf hexagon-debs-22.1.4_.tar.gz -C hexagon-debs + sudo dpkg -i hexagon-debs/*.deb + + - name: Cross-compile iwasm for Hexagon + run: | + cmake -S product-mini/platforms/hexagon \ + -B build-hexagon \ + -DCMAKE_TOOLCHAIN_FILE=${GITHUB_WORKSPACE}/build-scripts/toolchains/hexagon_linux_musl.cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DWAMR_DISABLE_HW_BOUND_CHECK=1 \ + -DWAMR_BUILD_SHRUNK_MEMORY=0 \ + -DWAMR_BUILD_SPEC_TEST=1 \ + -DWAMR_BUILD_LIBC_WASI=0 + cmake --build build-hexagon --parallel $(nproc) + + - name: Verify iwasm binary + run: | + file build-hexagon/iwasm + qemu-hexagon -cpu v67 build-hexagon/iwasm --version + + - name: Upload iwasm + uses: actions/upload-artifact@v7.0.1 + with: + name: iwasm-hexagon + path: build-hexagon/iwasm + + spec_test: + needs: [build_iwasm, build_wamrc] + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + test_option: + - "-s spec -b -P" + - "-s spec -b -S -P" + running_mode: + - "classic-interp" + - "fast-interp" + - "aot" + exclude: + # Known unsupported combination: SIMD in AOT mode is not yet + # validated on Hexagon. The Hexagon AOT relocation/codegen path for + # V128 has not been confirmed under qemu-hexagon, so this cell is + # deliberately excluded rather than left failing. SIMD is exercised + # in the interpreter modes above; AOT is exercised for non-SIMD. + # Re-enable this cell once SIMD AOT is validated on hardware/QEMU. + - test_option: "-s spec -b -S -P" + running_mode: "aot" + steps: + - name: checkout + uses: actions/checkout@v6.0.2 + + - name: Install QEMU user-mode + run: sudo apt-get update && sudo apt-get install -y qemu-user + + - name: Download iwasm artifact + uses: actions/download-artifact@v4.2.0 + with: + name: iwasm-hexagon + path: ./ + + - name: Download wamrc artifact + if: matrix.running_mode == 'aot' + uses: actions/download-artifact@v4.2.0 + with: + name: wamrc-hexagon + path: ./ + + - name: Set up iwasm wrapper + run: | + chmod +x ./iwasm + # Place the real Hexagon binary at a known path + mv ./iwasm ./iwasm-hexagon-real + # Create a wrapper that invokes iwasm via qemu-hexagon user-mode. + # Use the linux platform path since Hexagon runs Linux and this + # avoids needing to teach every host-tool lookup about "hexagon". + mkdir -p product-mini/platforms/linux/build + printf '#!/bin/sh\nexec qemu-hexagon -cpu v67 %s/iwasm-hexagon-real "$@"\n' \ + "${GITHUB_WORKSPACE}" > product-mini/platforms/linux/build/iwasm + chmod +x product-mini/platforms/linux/build/iwasm + # Verify it works + product-mini/platforms/linux/build/iwasm --version + + - name: Set up wamrc + if: matrix.running_mode == 'aot' + run: | + chmod +x ./wamrc + mkdir -p wamr-compiler/build + cp ./wamrc wamr-compiler/build/wamrc + + - name: Run spec tests + timeout-minutes: 60 + run: | + ./test_wamr.sh ${{ matrix.test_option }} \ + -m HEXAGON \ + -t ${{ matrix.running_mode }} \ + -j linux \ + -Q \ + ${{ matrix.running_mode == 'aot' && format('-A {0}/wamr-compiler/build/wamrc', github.workspace) || '' }} + working-directory: ./tests/wamr-test-suites diff --git a/build-scripts/toolchains/hexagon-linux-musl.cmake b/build-scripts/toolchains/hexagon_linux_musl.cmake similarity index 90% rename from build-scripts/toolchains/hexagon-linux-musl.cmake rename to build-scripts/toolchains/hexagon_linux_musl.cmake index 7465f80350..06b5b2a977 100644 --- a/build-scripts/toolchains/hexagon-linux-musl.cmake +++ b/build-scripts/toolchains/hexagon_linux_musl.cmake @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Qualcomm Innovation Center, Inc. All rights reserved. +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception # CMake toolchain file for cross-compiling to Hexagon Linux (musl) diff --git a/core/iwasm/common/wasm_runtime_common.c b/core/iwasm/common/wasm_runtime_common.c index 6590684c4f..72d037a0b0 100644 --- a/core/iwasm/common/wasm_runtime_common.c +++ b/core/iwasm/common/wasm_runtime_common.c @@ -5722,7 +5722,7 @@ wasm_runtime_invoke_native(WASMExecEnv *exec_env, void *func_ptr, #if defined(BUILD_TARGET_X86_32) || defined(BUILD_TARGET_ARM) \ || defined(BUILD_TARGET_THUMB) || defined(BUILD_TARGET_MIPS) \ - || defined(BUILD_TARGET_XTENSA) + || defined(BUILD_TARGET_XTENSA) || defined(BUILD_TARGET_HEXAGON) typedef void (*GenericFunctionPointer)(void); void invokeNative(GenericFunctionPointer f, uint32 *args, uint32 sz); @@ -5850,8 +5850,8 @@ wasm_runtime_invoke_native(WASMExecEnv *exec_env, void *func_ptr, case VALUE_TYPE_I64: case VALUE_TYPE_F64: #if !defined(BUILD_TARGET_X86_32) - /* 64-bit data must be 8 bytes aligned in arm, thumb, mips - and xtensa */ + /* 64-bit data must be 8 bytes aligned in arm, thumb, mips, + xtensa and hexagon */ if (j & 1) j++; #endif diff --git a/tests/wamr-test-suites/spec-test-script/all.py b/tests/wamr-test-suites/spec-test-script/all.py index bc4df3559b..2c6add47cf 100644 --- a/tests/wamr-test-suites/spec-test-script/all.py +++ b/tests/wamr-test-suites/spec-test-script/all.py @@ -206,9 +206,13 @@ def test_case( CMD.append("--interpreter") if sgx_flag: CMD.append(IWASM_SGX_CMD) - elif qemu_flag: + elif qemu_flag and qemu_firmware: + # System-emulation (e.g. NuttX): iwasm is a built-in command inside + # the emulated OS, so use the bare name. CMD.append(IWASM_QEMU_CMD) else: + # Host execution or QEMU user-mode: use the host path to the iwasm + # binary (which may be a qemu-hexagon wrapper). CMD.append(IWASM_CMD) if no_pty: CMD.append("--no-pty") diff --git a/tests/wamr-test-suites/spec-test-script/runtest.py b/tests/wamr-test-suites/spec-test-script/runtest.py index fa9f5eb7da..d50bc8de38 100755 --- a/tests/wamr-test-suites/spec-test-script/runtest.py +++ b/tests/wamr-test-suites/spec-test-script/runtest.py @@ -62,6 +62,7 @@ "riscv64_lp64f": ["--target=riscv64", "--target-abi=lp64f", "--cpu=generic-rv64", "--cpu-features=+m,+a,+c,+f", "--size-level=1"], "riscv64_lp64d": ["--target=riscv64", "--target-abi=lp64d", "--cpu=generic-rv64", "--cpu-features=+m,+a,+c,+f,+d", "--size-level=1"], "xtensa": ["--target=xtensa"], + "hexagon": ["--target=hexagon"], } # AOT compilation options mapping for XIP mode @@ -267,7 +268,7 @@ def assert_prompt(runner, prompts, timeout, is_need_execute_result): log("Started with:\n%s" % header) else: log("Did not one of following prompt(s): %s" % repr(prompts)) - log(" Got : %s" % repr(r.buf)) + log(" Got : %s" % repr(runner.buf)) raise Exception("Did not one of following prompt(s)") @@ -780,6 +781,8 @@ def is_result_match_expected(out, expected): def test_assert(r, opts, mode, cmd, expected): log("Testing(%s) %s = %s" % (mode, cmd, expected)) out = invoke(r, opts, cmd) + if out is None: + raise Exception("Timed out waiting for response to: %s" % cmd) if '\n' in out or ' ' in out: outs = [''] + out.split('\n')[1:] out = outs[-1] @@ -821,34 +824,34 @@ def test_assert_return(r, opts, form): n. to search a pattern like (assert_return (invoke $module_name function_name ... ) ...) """ # params, return - m = re.search(r'^\(assert_return\s+\(invoke\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S) + m = re.search(r'^\(assert_return\s*\(invoke\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S) # judge if assert_return cmd includes the module name - n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S) + n = re.search(r'^\(assert_return\s*\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s+(\(.*\))\s*\)\s*(\(.*\))\s*\)\s*$', form, re.S) # print("assert_return with {}".format(form)) if not m: # no params, return - m = re.search(r'^\(assert_return\s+\(invoke\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S) + m = re.search(r'^\(assert_return\s*\(invoke\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S) if not m: # params, no return - m = re.search(r'^\(assert_return\s+\(invoke\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S) + m = re.search(r'^\(assert_return\s*\(invoke\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S) if not m: # no params, no return - m = re.search(r'^\(assert_return\s+\(invoke\s+"([^"]*)"\s*()()\)\s*\)\s*$', form, re.S) + m = re.search(r'^\(assert_return\s*\(invoke\s+"([^"]*)"\s*()()\)\s*\)\s*$', form, re.S) if not m: # params, return if not n: # no params, return - n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S) + n = re.search(r'^\(assert_return\s*\(invoke\s+\$((?:[^\s])*)\s+"((?:[^"]|\\\")*)"\s*\)\s+()(\(.*\))\s*\)\s*$', form, re.S) if not n: # params, no return - n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S) + n = re.search(r'^\(assert_return\s*\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"\s+(\(.*\))()\s*\)\s*\)\s*$', form, re.S) if not n: # no params, no return - n = re.search(r'^\(assert_return\s+\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"*()()\)\s*\)\s*$', form, re.S) + n = re.search(r'^\(assert_return\s*\(invoke\s+\$((?:[^\s])*)\s+"([^"]*)"*()()\)\s*\)\s*$', form, re.S) if not m and not n: - if re.search(r'^\(assert_return\s+\(get.*\).*\)$', form, re.S): + if re.search(r'^\(assert_return\s*\(get.*\).*\)$', form, re.S): log("ignoring assert_return get") return else: diff --git a/tests/wamr-test-suites/test_wamr.sh b/tests/wamr-test-suites/test_wamr.sh index 97dc84d548..eae0c5934d 100755 --- a/tests/wamr-test-suites/test_wamr.sh +++ b/tests/wamr-test-suites/test_wamr.sh @@ -634,7 +634,9 @@ function spec_test() if [[ ${ENABLE_QEMU} == 1 ]]; then ARGS_FOR_SPEC_TEST+="--qemu " - ARGS_FOR_SPEC_TEST+="--qemu-firmware ${QEMU_FIRMWARE} " + if [[ -n ${QEMU_FIRMWARE} ]]; then + ARGS_FOR_SPEC_TEST+="--qemu-firmware ${QEMU_FIRMWARE} " + fi fi if [[ ${PLATFORM} == "windows" ]]; then From bc2f45eaa0fcd9c8d06dc42c53a6876484a363c2 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 9 Jul 2026 23:05:38 -0500 Subject: [PATCH 04/11] fix(interpreter): keep WASMTableInstance instances 8-byte aligned WASMTableInstance contains 64-bit members (the elem_ref_type union), so alignof(WASMTableInstance) == 8, and the compiler may legitimately emit 64-bit stores against it (e.g. merging the adjacent 32-bit cur_size and max_size stores in tables_instantiate). But the table region layout packed each instance at offsetof(elems) + elem_count * 4 bytes on 32-bit targets, so any table with an odd element count left the next instance only 4-byte aligned, and the global data preceding the region could leave even the first one misaligned. On strict-alignment targets (Hexagon) instantiating such a module faults with SIGBUS; elsewhere it is silent undefined behavior. Round each table instance size and the table region start up to 8 bytes in the allocation sizing, first_table derivation, and both tables_instantiate walk loops, which must all stay in sync. Found running the WASM spec suite (call_indirect, multiple-tables module) under qemu-hexagon user-mode with unaligned-access faulting enabled. --- core/iwasm/interpreter/wasm_runtime.c | 48 ++++++++++++++++++--------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/core/iwasm/interpreter/wasm_runtime.c b/core/iwasm/interpreter/wasm_runtime.c index 879df37c89..ad1a3d49ea 100644 --- a/core/iwasm/interpreter/wasm_runtime.c +++ b/core/iwasm/interpreter/wasm_runtime.c @@ -702,7 +702,10 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, table->max_size = max_size_fixed; } - table = (WASMTableInstance *)((uint8 *)table + (uint32)total_size); + /* keep each WASMTableInstance 8-byte aligned (64-bit members); + the allocation reserves this padding, see wasm_instantiate() */ + table = (WASMTableInstance *)((uint8 *)table + + align_uint((uint32)total_size, 8)); #if WASM_ENABLE_MULTI_MODULE != 0 table_linked++; #endif @@ -747,7 +750,10 @@ tables_instantiate(const WASMModule *module, WASMModuleInstance *module_inst, table->cur_size = module->tables[i].table_type.init_size; table->max_size = max_size_fixed; - table = (WASMTableInstance *)((uint8 *)table + (uint32)total_size); + /* keep each WASMTableInstance 8-byte aligned (64-bit members); + the allocation reserves this padding, see wasm_instantiate() */ + table = (WASMTableInstance *)((uint8 *)table + + align_uint((uint32)total_size, 8)); } bh_assert(table_index == table_count); @@ -2467,33 +2473,41 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, total_size = (uint64)module_inst_struct_size + module_inst_mem_inst_size + module->global_data_size; - /* Calculate the size of table data */ + /* Calculate the size of table data. Each table instance size is + rounded up to 8 bytes so every WASMTableInstance in the region is + naturally aligned for its 64-bit members; keep this in sync with + tables_instantiate() and the first_table derivation. */ for (i = 0; i < module->import_table_count; i++) { WASMTableImport *import_table = &module->import_tables[i].u.table; - table_size += offsetof(WASMTableInstance, elems); + uint64 cur_table_size = offsetof(WASMTableInstance, elems); #if WASM_ENABLE_MULTI_MODULE != 0 - table_size += (uint64)sizeof(table_elem_type_t) - * import_table->table_type.max_size; + cur_table_size += (uint64)sizeof(table_elem_type_t) + * import_table->table_type.max_size; #else - table_size += (uint64)sizeof(table_elem_type_t) - * (import_table->table_type.possible_grow - ? import_table->table_type.max_size - : import_table->table_type.init_size); + cur_table_size += (uint64)sizeof(table_elem_type_t) + * (import_table->table_type.possible_grow + ? import_table->table_type.max_size + : import_table->table_type.init_size); #endif + table_size += align_uint64(cur_table_size, 8); } for (i = 0; i < module->table_count; i++) { WASMTable *table = module->tables + i; - table_size += offsetof(WASMTableInstance, elems); + uint64 cur_table_size = offsetof(WASMTableInstance, elems); #if WASM_ENABLE_MULTI_MODULE != 0 - table_size += + cur_table_size += (uint64)sizeof(table_elem_type_t) * table->table_type.max_size; #else - table_size += + cur_table_size += (uint64)sizeof(table_elem_type_t) * (table->table_type.possible_grow ? table->table_type.max_size : table->table_type.init_size); #endif + table_size += align_uint64(cur_table_size, 8); } + /* Pad the global data so the table region starts 8-byte aligned */ + total_size += align_uint64(module->global_data_size, 8) + - module->global_data_size; total_size += table_size; /* The offset of WASMModuleInstanceExtra, make it 8-byte aligned */ @@ -2607,8 +2621,12 @@ wasm_instantiate(WASMModule *module, WASMModuleInstance *parent, module_inst->global_data = (uint8 *)module_inst + module_inst_struct_size + module_inst_mem_inst_size; module_inst->global_data_size = module->global_data_size; - first_table = (WASMTableInstance *)(module_inst->global_data - + module->global_data_size); + /* global data may end 4-byte aligned only; pad so every + WASMTableInstance is naturally aligned for its 64-bit members + (the size calculation above reserves this padding) */ + first_table = + (WASMTableInstance *)(module_inst->global_data + + align_uint64(module->global_data_size, 8)); module_inst->memory_count = module->import_memory_count + module->memory_count; From b6ce2c9092f04bef76ff1c63f985f4541c692fba Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 9 Jul 2026 23:36:34 -0500 Subject: [PATCH 05/11] fix(fast-interp): avoid misaligned stores in loader emit paths Three fast-interp loader paths did raw multi-byte stores to addresses that are not guaranteed naturally aligned, faulting (SIGBUS) on strict-alignment targets such as Hexagon: - The WASM_OP_SELECT / SELECT_128 label fixup wrote the replacement label address with a raw *(uint32 *) / *(int32 *) cast into the compiled-code stream at p_code_compiled_tmp - 4. The stream packs 1- and 2-byte items, so that address can be 2-byte aligned. Use the alignment-safe STORE_U32 helper, matching wasm_loader_emit_uint32. - reserve_block_ret and copy_params_to_dynamic_space carve a uint8 cells[] array and int16 offset arrays out of one allocation with no padding between them, so an odd element count leaves the int16 arrays on odd addresses. Round the cells region up to 2 bytes. Found running the WASM spec suite (left-to-right, block/br/fac/func/if and const) under qemu-hexagon user-mode with unaligned-access faulting enabled. --- core/iwasm/interpreter/wasm_loader.c | 52 +++++++++++++++------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/core/iwasm/interpreter/wasm_loader.c b/core/iwasm/interpreter/wasm_loader.c index a2c67bea2c..110e79be33 100644 --- a/core/iwasm/interpreter/wasm_loader.c +++ b/core/iwasm/interpreter/wasm_loader.c @@ -10564,16 +10564,19 @@ reserve_block_ret(WASMLoaderContext *loader_ctx, uint8 opcode, uint8 *emit_data = NULL, *cells = NULL; int16 *src_offsets = NULL; uint16 *dst_offsets = NULL; - uint64 size = - (uint64)value_count - * (sizeof(*cells) + sizeof(*src_offsets) + sizeof(*dst_offsets)); + /* Pad the cells array to 2 bytes so the int16/uint16 arrays that + follow it stay naturally aligned (required on strict-alignment + targets) */ + uint64 size = (uint64)align_uint(value_count, 2) * sizeof(*cells) + + (uint64)value_count + * (sizeof(*src_offsets) + sizeof(*dst_offsets)); /* Allocate memory for the emit data */ if (!(emit_data = loader_malloc(size, error_buf, error_buf_size))) return false; cells = emit_data; - src_offsets = (int16 *)(cells + value_count); + src_offsets = (int16 *)(cells + align_uint(value_count, 2)); dst_offsets = (uint16 *)(src_offsets + value_count); /* insert op_copy before else opcode */ @@ -11472,19 +11475,20 @@ copy_params_to_dynamic_space(WASMLoaderContext *loader_ctx, char *error_buf, bool is_if_block = (block->label_type == LABEL_TYPE_IF ? true : false); int16 operand_offset = 0; - uint64 size = (uint64)param_count * (sizeof(*cells) + sizeof(*src_offsets)); - bh_assert(size > 0); - /* For if block, we also need copy the condition operand offset. */ - if (is_if_block) - size += sizeof(*cells) + sizeof(*src_offsets); + uint32 cell_count = is_if_block ? param_count + 1 : param_count; + /* Pad the cells array to 2 bytes so the int16 array that follows it + stays naturally aligned (required on strict-alignment targets) */ + uint64 size = (uint64)align_uint(cell_count, 2) * sizeof(*cells) + + (uint64)cell_count * sizeof(*src_offsets); + bh_assert(size > 0); /* Allocate memory for the emit data */ if (!(emit_data = loader_malloc(size, error_buf, error_buf_size))) return false; cells = emit_data; - src_offsets = (int16 *)(cells + param_count); + src_offsets = (int16 *)(cells + align_uint(cell_count, 2)); if (is_if_block) condition_offset = *loader_ctx->frame_offset; @@ -13385,13 +13389,13 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, int32 offset = (int32)((uint8 *)handle_table[opcode_tmp] - (uint8 *)handle_table[0]); - *(int32 *)(p_code_compiled_tmp - - sizeof(int32)) = offset; + STORE_U32(p_code_compiled_tmp - sizeof(int32), + (uint32)offset); #else /* emit uint32 label address in 32-bit target */ - *(uint32 *)(p_code_compiled_tmp - - sizeof(uint32)) = - (uint32)(uintptr_t)handle_table[opcode_tmp]; + STORE_U32(p_code_compiled_tmp - sizeof(uint32), + (uint32)(uintptr_t) + handle_table[opcode_tmp]); #endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */ #else /* else of WASM_ENABLE_LABELS_AS_VALUES */ #if WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0 @@ -13421,13 +13425,13 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, int32 offset = (int32)((uint8 *)handle_table[opcode_tmp] - (uint8 *)handle_table[0]); - *(int32 *)(p_code_compiled_tmp - - sizeof(int32)) = offset; + STORE_U32(p_code_compiled_tmp - sizeof(int32), + (uint32)offset); #else /* emit uint32 label address in 32-bit target */ - *(uint32 *)(p_code_compiled_tmp - - sizeof(uint32)) = - (uint32)(uintptr_t)handle_table[opcode_tmp]; + STORE_U32(p_code_compiled_tmp - sizeof(uint32), + (uint32)(uintptr_t) + handle_table[opcode_tmp]); #endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */ #else /* else of WASM_ENABLE_LABELS_AS_VALUES */ #if WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS != 0 @@ -13558,12 +13562,12 @@ wasm_loader_prepare_bytecode(WASMModule *module, WASMFunction *func, /* emit int32 relative offset in 64-bit target */ int32 offset = (int32)((uint8 *)handle_table[opcode_tmp] - (uint8 *)handle_table[0]); - *(int32 *)(p_code_compiled_tmp - sizeof(int32)) = - offset; + STORE_U32(p_code_compiled_tmp - sizeof(int32), + (uint32)offset); #else /* emit uint32 label address in 32-bit target */ - *(uint32 *)(p_code_compiled_tmp - sizeof(uint32)) = - (uint32)(uintptr_t)handle_table[opcode_tmp]; + STORE_U32(p_code_compiled_tmp - sizeof(uint32), + (uint32)(uintptr_t)handle_table[opcode_tmp]); #endif #endif /* end of WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS */ #else /* else of WASM_ENABLE_LABELS_AS_VALUES */ From 7b62a8bbac17bfeeb24c6db6415741626af4875f Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 10 Jul 2026 09:07:47 -0500 Subject: [PATCH 06/11] fix(application): avoid misaligned 64-bit access in V128 arg handling execute_func marshals wasm arguments into a uint32 cell array, which is only guaranteed 4-byte aligned. The V128 parameter and result paths cast &argv1[p] to uint64* and dereferenced it directly, faulting (SIGBUS) on strict-alignment targets such as Hexagon whenever the V128 lands on a 4-mod-8 cell boundary (e.g. any export taking (i32, v128)). The i64 and f64 paths already store word-by-word through a union; do the same for V128 argument parsing and result printing. Found running the WASM SIMD spec suite (simd_align etc., 21 cases) under qemu-hexagon user-mode with unaligned-access faulting enabled. --- core/iwasm/common/wasm_application.c | 30 ++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/core/iwasm/common/wasm_application.c b/core/iwasm/common/wasm_application.c index 7bbd43d166..57d6331288 100644 --- a/core/iwasm/common/wasm_application.c +++ b/core/iwasm/common/wasm_application.c @@ -539,13 +539,22 @@ execute_func(WASMModuleInstanceCommon *module_inst, const char *name, case VALUE_TYPE_V128: { /* it likes 0x123\0x234 or 123\234 */ + union { + uint64 val; + uint32 parts[2]; + } u; + /* argv1 + p is only guaranteed 4-byte aligned, so store + the two i64 halves word by word */ /* retrieve first i64 */ - *(uint64 *)(argv1 + p) = strtoull(argv[i], &endptr, 0); + u.val = strtoull(argv[i], &endptr, 0); + argv1[p++] = u.parts[0]; + argv1[p++] = u.parts[1]; /* skip \ */ endptr++; /* retrieve second i64 */ - *(uint64 *)(argv1 + p + 2) = strtoull(endptr, &endptr, 0); - p += 4; + u.val = strtoull(endptr, &endptr, 0); + argv1[p++] = u.parts[0]; + argv1[p++] = u.parts[1]; break; } #endif /* WASM_ENABLE_SIMD != 0 */ @@ -768,9 +777,18 @@ execute_func(WASMModuleInstanceCommon *module_inst, const char *name, #if WASM_ENABLE_SIMD != 0 case VALUE_TYPE_V128: { - uint64 *v = (uint64 *)(argv1 + k); - os_printf("<0x%016" PRIx64 " 0x%016" PRIx64 ">:v128", *v, - *(v + 1)); + /* argv1 + k is only guaranteed 4-byte aligned, so read + the two i64 halves word by word */ + union { + uint64 val; + uint32 parts[2]; + } lo, hi; + lo.parts[0] = argv1[k]; + lo.parts[1] = argv1[k + 1]; + hi.parts[0] = argv1[k + 2]; + hi.parts[1] = argv1[k + 3]; + os_printf("<0x%016" PRIx64 " 0x%016" PRIx64 ">:v128", lo.val, + hi.val); k += 4; break; } From 4b5f7645c82ab405d6e1215bf3d9f52d9ed52638 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Fri, 10 Jul 2026 09:56:13 -0500 Subject: [PATCH 07/11] fix(fast-interp): don't deref v128.const immediate as aligned V128 The SIMD_v128_const handler read its 16-byte immediate with *(V128 *)orig_ip, but frame_ip points into the compiled code stream, which only guarantees 2-byte alignment. On strict-alignment targets (Hexagon) the resulting 64-bit loads fault with SIGBUS as soon as a function containing v128.const executes (e.g. every *_with_const_* export in the SIMD spec suite). Use LOAD_V128, which is alignment-safe on such targets, like the v128.load handler already does; the v8x16.shuffle handler already copies its immediate with bh_memcpy_s. Found running the WASM SIMD spec suite (simd_const and the *_arith2 suites) under qemu-hexagon user-mode with unaligned-access faulting enabled. --- core/iwasm/interpreter/wasm_interp_fast.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index 937a7fdecf..06fd78eb69 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -6001,7 +6001,10 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, frame_ip += sizeof(V128); addr_ret = GET_OFFSET(); - PUT_V128_TO_ADDR(frame_lp + addr_ret, *(V128 *)orig_ip); + /* the immediate lives in the compiled code stream, + which is not guaranteed V128-aligned */ + PUT_V128_TO_ADDR(frame_lp + addr_ret, + LOAD_V128(orig_ip)); break; } /* TODO: Add a faster SIMD implementation */ From 28eb638c31dc0b05010882c0b19f2ef486e3fea1 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 16 Jul 2026 11:44:15 -0500 Subject: [PATCH 08/11] fix(runtime): apply table-instance alignment in jit offsets and AOT layout Commit bc2f45ea ("fix(interpreter): keep WASMTableInstance instances 8-byte aligned") padded the table region in wasm_runtime.c (global data rounded up to 8 bytes, each table instance rounded up to 8 bytes) so instances stay naturally aligned for their 64-bit members on strict-alignment targets. But that layout is duplicated in three more places that were not updated: - fast-jit computes table offsets statically in jit_frontend_get_table_inst_offset()/get_first_table_inst_offset(); with the runtime layout shifted by the new padding, any module with global_data_size % 8 != 0 read table slots at stale offsets. Reproduced on x86-64 fast-jit with a module holding one i32 global and a 2-entry funcref table: call_indirect raised "indirect call type mismatch" for element 0 and called the wrong function for element 1. - LLVM-JIT bakes the same offsets into generated code through get_tbl_inst_offset() in aot_emit_table.c, with the same failure mode. - aot_runtime.c still packed AOTTableInstance records with no padding, leaving AOT mode exposed to the same misaligned 64-bit accesses on strict-alignment targets that motivated the interpreter fix. Apply the identical alignment rule in all three places, so the four sites that duplicate this layout (wasm_runtime.c, aot_runtime.c, aot_emit_table.c, jit_frontend.c) agree again. get_tbl_inst_offset() computes the padding from the target's pointer size, so cross-compiled AOT output matches the target runtime's layout. Also fix the pre-existing sizeof(uint32) elem size in jit_frontend's import-table walk, which under-counted the stride of imported tables on 64-bit (elems are table_elem_type_t, i.e. pointer-sized). Note: generated AOT/JIT code and the runtime must come from the same tree, as the in-memory instance layout changed; the AOT file format is unaffected. Verified: the reproducer now passes under fast-jit, host AOT and hexagon AOT (qemu user-mode); full spec suite green in aot mode on hexagon and the layout is a no-op wherever global data size and table strides were already multiples of 8. Signed-off-by: Brian Cain --- core/iwasm/aot/aot_runtime.c | 39 ++++++++++++++++--------- core/iwasm/compilation/aot_emit_table.c | 18 ++++++++---- core/iwasm/fast-jit/jit_frontend.c | 30 ++++++++++++------- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/core/iwasm/aot/aot_runtime.c b/core/iwasm/aot/aot_runtime.c index d1ec873c58..bae2016765 100644 --- a/core/iwasm/aot/aot_runtime.c +++ b/core/iwasm/aot/aot_runtime.c @@ -774,6 +774,7 @@ tables_instantiate(AOTModuleInstance *module_inst, AOTModule *module, uint32 error_buf_size) { uint32 i, global_index, global_data_offset, base_offset, length; + uint32 cur_table_size; uint64 total_size; AOTTableInitData *table_seg; AOTTableInstance *tbl_inst = first_tbl_inst; @@ -827,10 +828,13 @@ tables_instantiate(AOTModuleInstance *module_inst, AOTModule *module, #endif module_inst->tables[i] = tbl_inst; + /* keep each AOTTableInstance 8-byte aligned (64-bit members); + the allocation reserves this padding, see aot_instantiate() */ + cur_table_size = + (uint32)offsetof(AOTTableInstance, elems) + + (uint32)sizeof(table_elem_type_t) * tbl_inst->max_size; tbl_inst = (AOTTableInstance *)((uint8 *)tbl_inst - + offsetof(AOTTableInstance, elems) - + sizeof(table_elem_type_t) - * tbl_inst->max_size); + + align_uint(cur_table_size, 8)); } /* fill table with element segment content */ @@ -1926,24 +1930,31 @@ aot_instantiate(AOTModule *module, AOTModuleInstance *parent, if (heap_size > APP_HEAP_SIZE_MAX) heap_size = APP_HEAP_SIZE_MAX; + /* Pad the global data so the table region starts 8-byte aligned */ total_size = (uint64)module_inst_struct_size + module_inst_mem_inst_size - + module->global_data_size; + + align_uint64(module->global_data_size, 8); /* - * calculate size of table data + * calculate size of table data. Each table instance size is rounded + * up to 8 bytes so every AOTTableInstance in the region is naturally + * aligned for its 64-bit members; keep this in sync with + * tables_instantiate(), get_tbl_inst_offset() in aot_emit_table.c + * and the table region derivation below. */ for (i = 0; i != module->import_table_count; ++i) { - table_size += offsetof(AOTTableInstance, elems); - table_size += (uint64)sizeof(table_elem_type_t) - * (uint64)aot_get_imp_tbl_data_slots( - module->import_tables + i, false); + uint64 cur_table_size = offsetof(AOTTableInstance, elems); + cur_table_size += (uint64)sizeof(table_elem_type_t) + * (uint64)aot_get_imp_tbl_data_slots( + module->import_tables + i, false); + table_size += align_uint64(cur_table_size, 8); } for (i = 0; i != module->table_count; ++i) { - table_size += offsetof(AOTTableInstance, elems); - table_size += + uint64 cur_table_size = offsetof(AOTTableInstance, elems); + cur_table_size += (uint64)sizeof(table_elem_type_t) * (uint64)aot_get_tbl_data_slots(module->tables + i, false); + table_size += align_uint64(cur_table_size, 8); } total_size += table_size; @@ -2065,8 +2076,10 @@ aot_instantiate(AOTModule *module, AOTModuleInstance *parent, if (!global_instantiate(module_inst, module, error_buf, error_buf_size)) goto fail; - /* Initialize table info */ - p += module->global_data_size; + /* Initialize table info. Global data may end 4-byte aligned only; + pad so every AOTTableInstance is naturally aligned for its 64-bit + members (the size calculation above reserves this padding) */ + p += align_uint(module->global_data_size, 8); module_inst->table_count = module->table_count + module->import_table_count; if (!tables_instantiate(module_inst, module, (AOTTableInstance *)p, error_buf, error_buf_size)) diff --git a/core/iwasm/compilation/aot_emit_table.c b/core/iwasm/compilation/aot_emit_table.c index e8dae410bd..12fbb94b98 100644 --- a/core/iwasm/compilation/aot_emit_table.c +++ b/core/iwasm/compilation/aot_emit_table.c @@ -97,13 +97,19 @@ get_tbl_inst_offset(const AOTCompContext *comp_ctx, + (comp_ctx->pointer_size == sizeof(uint64) ? comp_ctx->comp_data->global_data_size_64bit : comp_ctx->comp_data->global_data_size_32bit); + /* global data is padded so the table region starts 8-byte aligned + and each table instance is padded to 8 bytes below, so every + AOTTableInstance stays naturally aligned for its 64-bit members; + keep in sync with the instance layout in aot_runtime.c/wasm_runtime.c */ + offset = align_uint64(offset, 8); while (i < tbl_idx && i < comp_ctx->comp_data->import_table_count) { - offset += offsetof(AOTTableInstance, elems); + uint64 tbl_inst_size = offsetof(AOTTableInstance, elems); /* avoid loading from current AOTTableInstance */ - offset += + tbl_inst_size += (uint64)comp_ctx->pointer_size * aot_get_imp_tbl_data_slots(imp_tbls + i, comp_ctx->is_jit_mode); + offset += align_uint64(tbl_inst_size, 8); ++i; } @@ -114,10 +120,12 @@ get_tbl_inst_offset(const AOTCompContext *comp_ctx, tbl_idx -= comp_ctx->comp_data->import_table_count; i -= comp_ctx->comp_data->import_table_count; while (i < tbl_idx && i < comp_ctx->comp_data->table_count) { - offset += offsetof(AOTTableInstance, elems); + uint64 tbl_inst_size = offsetof(AOTTableInstance, elems); /* avoid loading from current AOTTableInstance */ - offset += (uint64)comp_ctx->pointer_size - * aot_get_tbl_data_slots(tbls + i, comp_ctx->is_jit_mode); + tbl_inst_size += + (uint64)comp_ctx->pointer_size + * aot_get_tbl_data_slots(tbls + i, comp_ctx->is_jit_mode); + offset += align_uint64(tbl_inst_size, 8); ++i; } diff --git a/core/iwasm/fast-jit/jit_frontend.c b/core/iwasm/fast-jit/jit_frontend.c index c96b5410ba..9bbb00c8e3 100644 --- a/core/iwasm/fast-jit/jit_frontend.c +++ b/core/iwasm/fast-jit/jit_frontend.c @@ -44,7 +44,10 @@ get_global_base_offset(const WASMModule *module) static uint32 get_first_table_inst_offset(const WASMModule *module) { - return get_global_base_offset(module) + module->global_data_size; + /* global data is padded so the table region starts 8-byte aligned, + keep in sync with wasm_instantiate() */ + return get_global_base_offset(module) + + align_uint(module->global_data_size, 8); } uint32 @@ -73,16 +76,20 @@ jit_frontend_get_table_inst_offset(const WASMModule *module, uint32 tbl_idx) while (i < tbl_idx && i < module->import_table_count) { WASMTableImport *import_table = &module->import_tables[i].u.table; + uint32 tbl_inst_size = (uint32)offsetof(WASMTableInstance, elems); - offset += (uint32)offsetof(WASMTableInstance, elems); #if WASM_ENABLE_MULTI_MODULE != 0 - offset += (uint32)sizeof(uint32) * import_table->table_type.max_size; + tbl_inst_size += (uint32)sizeof(table_elem_type_t) + * import_table->table_type.max_size; #else - offset += (uint32)sizeof(uint32) - * (import_table->table_type.possible_grow - ? import_table->table_type.max_size - : import_table->table_type.init_size); + tbl_inst_size += (uint32)sizeof(table_elem_type_t) + * (import_table->table_type.possible_grow + ? import_table->table_type.max_size + : import_table->table_type.init_size); #endif + /* each table instance is padded to 8 bytes, keep in sync with + wasm_instantiate() */ + offset += align_uint(tbl_inst_size, 8); i++; } @@ -95,17 +102,20 @@ jit_frontend_get_table_inst_offset(const WASMModule *module, uint32 tbl_idx) i -= module->import_table_count; while (i < tbl_idx && i < module->table_count) { WASMTable *table = module->tables + i; + uint32 tbl_inst_size = (uint32)offsetof(WASMTableInstance, elems); - offset += (uint32)offsetof(WASMTableInstance, elems); #if WASM_ENABLE_MULTI_MODULE != 0 - offset += + tbl_inst_size += (uint32)sizeof(table_elem_type_t) * table->table_type.max_size; #else - offset += + tbl_inst_size += (uint32)sizeof(table_elem_type_t) * (table->table_type.possible_grow ? table->table_type.max_size : table->table_type.init_size); #endif + /* each table instance is padded to 8 bytes, keep in sync with + wasm_instantiate() */ + offset += align_uint(tbl_inst_size, 8); i++; } From 6a5b19587e9835084936ab9b9ac28d76b80149fb Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 16 Jul 2026 11:44:46 -0500 Subject: [PATCH 09/11] fix(interpreter): use alignment-safe access for unaligned memory operands Wasm linear-memory addresses can have any alignment -- the memarg align field is only a hint -- but two interpreter paths dereferenced them with helpers that require alignment, faulting with SIGBUS on strict-alignment targets (found on Hexagon under qemu user-mode with unaligned-access faulting; silent UB elsewhere): - classic interpreter, plain i64.store/f64.store: wrote through PUT_I64_TO_ADDR, which performs two 32-bit stores and is only meant for the 4-byte-aligned operand stack. memory.wast ("cast" performs i64.store align=1 at address 9), float_memory.wast and call_indirect.wast all crashed. Switch to the alignment-safe STORE_I64. This was the only remaining unsafe linear-memory access in wasm_interp_classic.c (all other maddr uses already go through LOAD_*/STORE_*). - fast interpreter, SIMD lane/zero loads and lane stores: SIMD_LOAD_LANE_COMMON (shared by v128.loadN_lane and v128.loadN_zero) read 16/32-bit lanes with direct dereferences and 64-bit lanes with the stack-only GET_I64_FROM_ADDR; SIMD_STORE_LANE_OP wrote 16/32-bit lanes directly. simd_load16/32/64_lane, simd_load_zero and simd_store16/32_lane crashed, while simd_store64_lane -- already using STORE_I64 -- passed, confirming the diagnosis. Switch to LOAD_U16/LOAD_U32/LOAD_I64 and STORE_U16/STORE_U32. On targets with WASM_CPU_SUPPORTS_UNALIGNED_ADDR_ACCESS the helpers compile to the same direct accesses as before, so this is a no-op there (re-verified on x86-64). With these fixes the memory, call_indirect, float_memory and all nine simd lane/zero spec suites pass on Hexagon in both interpreters. Signed-off-by: Brian Cain --- core/iwasm/interpreter/wasm_interp_classic.c | 11 +-- core/iwasm/interpreter/wasm_interp_fast.c | 70 ++++++++++++-------- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/core/iwasm/interpreter/wasm_interp_classic.c b/core/iwasm/interpreter/wasm_interp_classic.c index abde189ebc..3501758595 100644 --- a/core/iwasm/interpreter/wasm_interp_classic.c +++ b/core/iwasm/interpreter/wasm_interp_classic.c @@ -4621,16 +4621,19 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, addr = POP_MEM_OFFSET(); CHECK_MEMORY_OVERFLOW(8); + /* maddr points into linear memory and may have any + alignment (the memarg align field is only a hint), so + use the alignment-safe STORE_I64 rather than + PUT_I64_TO_ADDR, which requires a 4-byte aligned + destination */ #if WASM_ENABLE_MEMORY64 != 0 if (is_memory64) { - PUT_I64_TO_ADDR((mem_offset_t *)maddr, - GET_I64_FROM_ADDR(frame_sp + 2)); + STORE_I64(maddr, GET_I64_FROM_ADDR(frame_sp + 2)); } else #endif { - PUT_I64_TO_ADDR((uint32 *)maddr, - GET_I64_FROM_ADDR(frame_sp + 1)); + STORE_I64(maddr, GET_I64_FROM_ADDR(frame_sp + 1)); } CHECK_WRITE_WATCHPOINT(addr, offset); HANDLE_OP_END(); diff --git a/core/iwasm/interpreter/wasm_interp_fast.c b/core/iwasm/interpreter/wasm_interp_fast.c index 06fd78eb69..9cfb1de952 100644 --- a/core/iwasm/interpreter/wasm_interp_fast.c +++ b/core/iwasm/interpreter/wasm_interp_fast.c @@ -6525,17 +6525,26 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, break; } -#define SIMD_LOAD_LANE_COMMON(vec, register, lane, width) \ - do { \ - addr_ret = GET_OFFSET(); \ - CHECK_MEMORY_OVERFLOW(width / 8); \ - if (width == 64) { \ - vec.register[lane] = GET_I64_FROM_ADDR((uint32 *)maddr); \ - } \ - else { \ - vec.register[lane] = *(uint##width *)(maddr); \ - } \ - PUT_V128_TO_ADDR(frame_lp + addr_ret, vec); \ +/* maddr points into linear memory and may have any alignment (the + memarg align field is only a hint), so use the alignment-safe + LOAD_* helpers rather than direct dereferences */ +#define SIMD_LOAD_LANE_COMMON(vec, register, lane, width) \ + do { \ + addr_ret = GET_OFFSET(); \ + CHECK_MEMORY_OVERFLOW(width / 8); \ + if (width == 64) { \ + vec.register[lane] = LOAD_I64(maddr); \ + } \ + else if (width == 32) { \ + vec.register[lane] = LOAD_U32(maddr); \ + } \ + else if (width == 16) { \ + vec.register[lane] = LOAD_U16(maddr); \ + } \ + else { \ + vec.register[lane] = *(uint8 *)(maddr); \ + } \ + PUT_V128_TO_ADDR(frame_lp + addr_ret, vec); \ } while (0) #define SIMD_LOAD_LANE_OP(register, width) \ @@ -6569,21 +6578,30 @@ wasm_interp_call_func_bytecode(WASMModuleInstance *module, SIMD_LOAD_LANE_OP(i64x2, 64); break; } -#define SIMD_STORE_LANE_OP(register, width) \ - do { \ - uint32 offset, addr; \ - offset = read_uint32(frame_ip); \ - V128 vec = POP_V128(); \ - addr = POP_I32(); \ - int lane = *frame_ip++; \ - SIMD_LANE_HANDLE_UNALIGNED_ACCESS(); \ - CHECK_MEMORY_OVERFLOW(width / 8); \ - if (width == 64) { \ - STORE_I64(maddr, vec.register[lane]); \ - } \ - else { \ - *(uint##width *)(maddr) = vec.register[lane]; \ - } \ +/* maddr points into linear memory and may have any alignment (the + memarg align field is only a hint), so use the alignment-safe + STORE_* helpers rather than direct dereferences */ +#define SIMD_STORE_LANE_OP(register, width) \ + do { \ + uint32 offset, addr; \ + offset = read_uint32(frame_ip); \ + V128 vec = POP_V128(); \ + addr = POP_I32(); \ + int lane = *frame_ip++; \ + SIMD_LANE_HANDLE_UNALIGNED_ACCESS(); \ + CHECK_MEMORY_OVERFLOW(width / 8); \ + if (width == 64) { \ + STORE_I64(maddr, vec.register[lane]); \ + } \ + else if (width == 32) { \ + STORE_U32(maddr, (uint32)vec.register[lane]); \ + } \ + else if (width == 16) { \ + STORE_U16(maddr, (uint16)vec.register[lane]); \ + } \ + else { \ + *(uint8 *)(maddr) = (uint8)(vec.register[lane]); \ + } \ } while (0) case SIMD_v128_store8_lane: From c56322483d7cd078fc3a2aa454ae56f3ff53ea18 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 16 Jul 2026 11:45:00 -0500 Subject: [PATCH 10/11] fix(application): normalize NaN float arguments from strtof/strtod The CLI argument parser trusted the bit pattern strtof/strtod return for NaN inputs, only patching in the sign for a leading '-' and an explicit payload after ':'. That breaks on platforms whose libc does not return the canonical quiet NaN: musl on Hexagon returns the hardware default NaN 0xffffffff from strtof("nan") -- negative sign, all-ones payload -- because the parsed double is narrowed by a convert instruction that canonicalizes NaNs. Any spec assert passing a positive NaN argument to a sign-sensitive op then failed, e.g. (assert_return (invoke "copysign" (f32.const -0x1p-149) (f32.const nan)) (f32.const 0x1p-149)) returned -0x1p-149 because the "nan" argument arrived negative. An MSVC-only workaround for the same class of problem (strtof returning a non-canonical payload) already existed; make it unconditional: on isnan, reset the value to nanf("")/nan("") (0x7fc00000 / 0x7ff8000000000000) before applying the requested sign and payload. On glibc this is a behavioral no-op since strtof("nan") already returns exactly that value. Fixes the f32_bitwise and conversions spec suites on Hexagon; they are removed from the skip list in the follow-up test-script commit. Signed-off-by: Brian Cain --- core/iwasm/common/wasm_application.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/core/iwasm/common/wasm_application.c b/core/iwasm/common/wasm_application.c index 57d6331288..2a58565e96 100644 --- a/core/iwasm/common/wasm_application.c +++ b/core/iwasm/common/wasm_application.c @@ -455,18 +455,19 @@ execute_func(WASMModuleInstanceCommon *module_inst, const char *name, { float32 f32 = strtof(argv[i], &endptr); if (isnan(f32)) { -#ifdef _MSC_VER /* * Spec tests require the binary representation of NaN to be - * 0x7fc00000 for float and 0x7ff8000000000000 for float; - * however, in MSVC compiler, strtof doesn't return this - * exact value, causing some of the spec test failures. We - * use the value returned by nan/nanf as it is the one - * expected by spec tests. - * + * 0x7fc00000 for float and 0x7ff8000000000000 for double, + * however, some strtof implementations don't return this + * exact value: MSVC returns a different payload, and + * musl on Hexagon returns the hardware default NaN + * (0xffffffff, i.e. negative with an all-ones payload) + * because the parsed double is narrowed by a convert + * instruction that canonicalizes NaNs. Normalize to the + * value returned by nanf(""), then apply the requested + * sign and payload below. */ f32 = nanf(""); -#endif if (argv[i][0] == '-') { union ieee754_float u; u.f = f32; @@ -501,9 +502,9 @@ execute_func(WASMModuleInstanceCommon *module_inst, const char *name, } u; u.val = strtod(argv[i], &endptr); if (isnan(u.val)) { -#ifdef _MSC_VER + /* normalize to the canonical quiet NaN, see the + VALUE_TYPE_F32 case above */ u.val = nan(""); -#endif if (argv[i][0] == '-') { union ieee754_double ud; ud.d = u.val; From fa8dfe547125eb6d34dacd075361daf8a3e6bbb1 Mon Sep 17 00:00:00 2001 From: Brian Cain Date: Thu, 16 Jul 2026 11:45:16 -0500 Subject: [PATCH 11/11] ci(hexagon): test classic-interp with a real classic build; refine skips The spec-test workflow built a single iwasm (the product-mini defaults: fast-interp with SIMD) and ran it for every matrix cell, so the classic-interp cells never actually exercised the classic interpreter -- and SIMD + classic-interp is not a supported build combination in the first place. Build two artifacts instead (fast-interp+SIMD, and classic-interp with -DWAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SIMD=0), download the right one per running mode, and exclude the SIMD x classic-interp cell with a comment. The aot cell keeps the fast-interp binary; both builds have AOT support and the interpreter kind does not matter there. Update the hexagon skip list in all.py to match verified reality (fast-interp, classic-interp and aot all green under qemu-hexagon user-mode with these skips): - drop f32_bitwise and conversions: they pass after the NaN CLI-argument normalization in wasm_application.c. - float_exprs: keep, now attributed correctly -- Hexagon FP arithmetic produces the default NaN (all-ones payload) rather than the wasm-canonical quiet NaN, so the *_nan_bitpattern checks fail (same class as the x87 skip for i386). - simd pmin/pmax: keep, now attributed correctly -- clang's Hexagon backend lowers the strict-IEEE "(b < a) ? b : a" pattern to sfmin/sfmax (minNum NaN semantics) without nnan, so lanes with a NaN first operand come back wrong. - simd_lane / simd_splat: keep -- musl (LD64) strtod parses long hex-float CLI arguments 1 ulp low (e.g. 0x0123456789ABCDEFabcdef); a test-harness artifact, not an engine bug. - i32 / i64: keep skipped for the clang rotl/rotr miscompile (fixed by llvm-project 006429da6ab9); re-enabling needs the fix in BOTH the Hexagon cross toolchain that builds iwasm (interp modes pass with a fixed 22.1.8 build) AND the LLVM wamrc links (aot fails with any unfixed LLVM, observed from 18.1.x through 22.x). Signed-off-by: Brian Cain --- .github/workflows/compilation_on_hexagon.yml | 26 +++++++++++--- .../wamr-test-suites/spec-test-script/all.py | 35 ++++++++++++------- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/.github/workflows/compilation_on_hexagon.yml b/.github/workflows/compilation_on_hexagon.yml index f47b66d9e4..d810916897 100644 --- a/.github/workflows/compilation_on_hexagon.yml +++ b/.github/workflows/compilation_on_hexagon.yml @@ -89,6 +89,17 @@ jobs: build_iwasm: runs-on: ubuntu-22.04 + strategy: + matrix: + include: + # fast-interp with SIMD (the product-mini/platforms/hexagon + # defaults); used by the fast-interp and aot spec test cells + - interp_variant: "fast-interp" + build_options: "" + # classic-interp; SIMD is disabled because SIMD in the + # interpreter requires fast-interp + - interp_variant: "classic-interp" + build_options: "-DWAMR_BUILD_FAST_INTERP=0 -DWAMR_BUILD_SIMD=0" steps: - name: checkout uses: actions/checkout@v6.0.2 @@ -119,7 +130,8 @@ jobs: -DWAMR_DISABLE_HW_BOUND_CHECK=1 \ -DWAMR_BUILD_SHRUNK_MEMORY=0 \ -DWAMR_BUILD_SPEC_TEST=1 \ - -DWAMR_BUILD_LIBC_WASI=0 + -DWAMR_BUILD_LIBC_WASI=0 \ + ${{ matrix.build_options }} cmake --build build-hexagon --parallel $(nproc) - name: Verify iwasm binary @@ -130,7 +142,7 @@ jobs: - name: Upload iwasm uses: actions/upload-artifact@v7.0.1 with: - name: iwasm-hexagon + name: iwasm-hexagon-${{ matrix.interp_variant }} path: build-hexagon/iwasm spec_test: @@ -151,10 +163,14 @@ jobs: # validated on Hexagon. The Hexagon AOT relocation/codegen path for # V128 has not been confirmed under qemu-hexagon, so this cell is # deliberately excluded rather than left failing. SIMD is exercised - # in the interpreter modes above; AOT is exercised for non-SIMD. + # in the fast-interp mode above; AOT is exercised for non-SIMD. # Re-enable this cell once SIMD AOT is validated on hardware/QEMU. - test_option: "-s spec -b -S -P" running_mode: "aot" + # Unsupported build combination: SIMD in the interpreter requires + # fast-interp, so the classic-interp binary is built without SIMD. + - test_option: "-s spec -b -S -P" + running_mode: "classic-interp" steps: - name: checkout uses: actions/checkout@v6.0.2 @@ -165,7 +181,9 @@ jobs: - name: Download iwasm artifact uses: actions/download-artifact@v4.2.0 with: - name: iwasm-hexagon + # the aot cell runs the fast-interp binary (both have AOT + # support; the interpreter kind does not matter for aot runs) + name: iwasm-hexagon-${{ matrix.running_mode == 'classic-interp' && 'classic-interp' || 'fast-interp' }} path: ./ - name: Download wamrc artifact diff --git a/tests/wamr-test-suites/spec-test-script/all.py b/tests/wamr-test-suites/spec-test-script/all.py index 2c6add47cf..bbc917a9fc 100644 --- a/tests/wamr-test-suites/spec-test-script/all.py +++ b/tests/wamr-test-suites/spec-test-script/all.py @@ -97,19 +97,30 @@ def ignore_the_case( return True # Hexagon-specific failures. These skips are workarounds for - # toolchain/spec gaps, not WAMR bugs: - # - float_exprs/conversions/f32_bitwise: Hexagon does not canonicalize - # NaN payloads (sNaN propagation differs from spec expectations). - # - simd_f32x4_pmin_pmax / simd_f64x2_pmin_pmax: NaN propagation differs. - # - simd_lane / simd_splat: NaN-bearing lane/splat edge cases. - # - i32 / i64: clang miscompiles rotl/rotr on Hexagon because asl/asr - # use signed shift amounts; observed as e.g. rotl returning - # 0xed3 instead of 0x579beed3. These cases are spread across the - # suites so we skip the whole suite until the clang fix lands. - # TODO: when the underlying clang fix is in, narrow this skip to the - # specific rotl/rotr asserts and re-enable the remaining arithmetic. + # hardware/toolchain/libc traits, not WAMR bugs: + # - float_exprs: Hexagon FP arithmetic produces the default NaN + # (all-ones payload) instead of the wasm-canonical quiet NaN, so + # the *_nan_bitpattern checks fail (same class as the x87 skip for + # i386 above). + # - simd_f32x4_pmin_pmax / simd_f64x2_pmin_pmax: clang Hexagon backend + # miscompiles the strict-IEEE pattern "(b < a) ? b : a" into + # sfmin/sfmax (minNum NaN semantics) without nnan, so pmin/pmax with + # a NaN first operand return the wrong lane. + # - simd_lane / simd_splat: musl (LD64) strtod parses long hex-float + # CLI arguments 1 ulp low (e.g. 0x0123456789ABCDEFabcdef), a test + # harness artifact, not an engine bug. + # - i32 / i64: clang miscompiles variable-count rotl/rotr on Hexagon + # (llvm.fshl/fshr lowered without reducing the count mod the width; + # present at least since clang 18). Fixed by llvm-project + # 006429da6ab9. Re-enabling needs the fix in BOTH the Hexagon cross + # toolchain that builds iwasm (interp modes) AND the LLVM that wamrc + # links (aot mode) -- verified: interp passes with a fixed 22.1.8 + # toolchain, and aot fails with any unfixed LLVM (18.1.8 .. 22.x). + # (f32_bitwise and conversions were skipped here until the NaN + # CLI-argument normalization fix in wasm_application.c; both pass + # now.) if "hexagon" == target and case_name in [ - "float_exprs", "conversions", "f32_bitwise", "i32", "i64", + "float_exprs", "i32", "i64", "simd_f32x4_pmin_pmax", "simd_f64x2_pmin_pmax", "simd_lane", "simd_splat", ]: