Skip to content

Add wamr test app - #6

Open
listepo wants to merge 60 commits into
mainfrom
add-wamr-test-app
Open

Add wamr test app#6
listepo wants to merge 60 commits into
mainfrom
add-wamr-test-app

Conversation

@listepo

@listepo listepo commented Aug 2, 2026

Copy link
Copy Markdown
Owner

No description provided.

listepo added 30 commits August 2, 2026 22:50
Create a NativeScript test application (apps/nativescript-wamr-test/)
that exercises @org/nativescript-wamr against the real WAMR runtime,
mirroring the existing nativescript-wasm-test app.

Key additions:
- Demo page and mocha specs sharing the same fixture-suite checks
- WAMR-specific coverage: execution tiers (Interpreter, FastJIT),
  WASI mode, custom stack sizes
- Adapted types: WamrRuntime, WamrModule, WamrFunction, WamrError,
  WamrImports vs their wasm3 counterparts
- Nx targets: typecheck (passes), test.ios, test.android
- README.md and AGENTS.md documenting design decisions
- nx.json: exclude new app from @nx/js/typescript plugin
Add comprehensive tier and WASI coverage to both spec files.

fixture-module.spec.ts:
- New "execution tiers" describe block with:
  - Parametrized loop over all 4 tiers (Interpreter, FastJIT,
    LLVMJIT, AOT) running the full shared fixture suite
  - Individual tests per tier with graceful skip when a tier
    is not compiled into the native build
- New "WASI mode" describe block with:
  - Full fixture suite under WASI + Interpreter
  - Full fixture suite under WASI + FastJIT (graceful skip)
  - WASI-disabled sanity check
  - WASI-enabled globals module load
- New runtime option tests:
  - Default options (no explicit config)
  - loadModule from Uint8Array
  - loadModule from plain number[]
  - WamrExecutionTier enum values and reverse-mapping

globals-module.spec.ts:
- New "execution tiers" block: reads/writes globals under
  every available tier with round-trip verification
- New "WASI mode" block: globals under WASI + Interpreter,
  and WASI + FastJIT (graceful skip)
Extend the CI workflow to run the nativescript-wamr-test app mocha suite
on the iOS Simulator and Android emulator, mirroring the existing
wasm-test-ios / wasm-test-android jobs.

Changes:
- unit-tests job: install wamr-test dependencies for typecheck coverage
- changes job: add wamr_app output, gated on changes to
  apps/nativescript-wamr-test/, packages/nativescript-wamr/,
  packages/nativescript-wasm-fixture/, or ci.yml
- New wamr-test-ios job (macos-latest): builds the wamr plugin,
  runs "ns test ios --emulator"
- New wamr-test-android job (ubuntu-latest): builds the wamr plugin,
  runs "ns test android --emulator" via android-emulator-runner

Both device jobs are gated behind the wamr_app change detection to avoid
burning macOS/emulator minutes on unrelated PRs.
…R sources

wasm-test-android:
- Build both plugins (wasm3 + wamr) since the test app depends on both
- Add WAMR C source check with warning when sources are missing
- Update comment to explain WAMR tests may fail without the .aar

wamr-test-android:
- Add WAMR C source check with warning when sources are missing
- Update comment to reflect that the .aar is only present when
  C sources are committed and the Gradle build succeeds

Both jobs already have continue-on-error: true, so runtime failures
from missing native runtimes won't block CI.
…h wasm3 and WAMR

Refactor the test app so a single NativeScript app exercises both
@org/nativescript-wasm3 and @org/nativescript-wamr against the same
fixture modules, from both the demo page and mocha specs.

Core change — fixture-suite.ts is now runtime-agnostic:
- Replaces direct Wasm3Module/Wasm3Imports imports with structural
  interfaces (WasmModuleLike, HostImports, WasmRuntimeLike) that both
  plugins satisfy
- Exports WasmValue, WasmArg, HostFunction for callers
- No imports from either plugin package

Demo page — runs both runtimes side by side:
- main-view-model.ts creates both Wasm3Runtime and WamrRuntime
  (Interpreter tier) and runs the full fixture suite on each
- Each runtime is independently guarded so a missing native layer
  on one doesn't hide the other's results
- Status line shows combined pass/fail across both runtimes

Tests restructured by runtime:
- app/tests/wasm3/ — wasm3-specific specs (unchanged logic)
- app/tests/wamr/ — wamr-specific specs with tier/WASI coverage
- Both import from the shared runtime-agnostic fixture-suite.ts

Other:
- package.json: add @org/nativescript-wamr dependency
- tsconfig.json: add wamr project reference
- AGENTS.md, README.md: document dual-runtime design
- Remove stale build/ crate (was an early, unused Node-runner experiment)
- nx.json: restore single test-app exclusion (separate wamr-test app
  reverted in favor of unified approach)
The test app now uses a unified approach: a single NativeScript app
(nativescript-wasm-test) exercises both runtimes through a
runtime-agnostic fixture-suite.ts. The separate wamr-test app is no
longer needed.

Changes:
- Delete apps/nativescript-wamr-test/ entirely (82 files)
- Remove wamr-test-ios and wamr-test-android CI jobs
- Remove wamr_app change-detection output and filter logic
- Restore unit-tests job to single test-app dependency install
- wasm-test-ios: build both plugins (wasm3 + wamr) like wasm-test-android
- wasm-test-android: keep both-plugins build + WAMR source check
Add a script that downloads the WAMR (WebAssembly Micro Runtime) C
sources from the bytecodealliance/wasm-micro-runtime GitHub repo and
extracts the required subset into src/vendors/wamr/.

Usage:
  npm run download.wamr              # default tag (WAMR-2.3.0)
  npm run download.wamr -- WAMR-2.3.0  # specific tag
  npm run download.wamr -- 4c4a0ab     # specific commit
  npx nx run nativescript-wamr:download.wamr

The script uses curl + tar (available on macOS, Linux, and CI runners)
with zero npm dependencies. It extracts only the directories needed
for the iOS/Android native builds (core/iwasm/*, core/shared/*,
LICENSE), keeping the vendored copy small.

Also adds the "download.wamr" npm script and Nx target.
Vendor the WAMR 2.3.0 C sources (core/iwasm/*, core/shared/*) and
generate Rust FFI bindings via bindgen.

src/vendors/wamr/:
- Downloaded from bytecodealliance/wasm-micro-runtime WAMR-2.3.0
- 530 files, the subset needed for iOS/Android native builds
- Committed so no network access is needed for CI or local builds

src/vendors/wamr-rust/ (new):
- wamr-sys crate: raw Rust FFI bindings generated by bindgen 0.72
- build.rs: uses bindgen to regenerate from ../wamr/wrapper.h
- src/bindings.rs: committed snapshot (2649 lines)
- src/lib.rs: uses committed bindings by default, WAMR_BINDGEN_COMMIT=1
  to regenerate via build.rs
- Covers the full public WAMR C API (wasm_export.h, wasm_c_api.h)

Scripts:
- npm run bindings / npx nx bindings nativescript-wamr:
  regenerates and commits the Rust bindings
…via UniFFI

Add a complete Rust→Swift code generation pipeline using uniffi-rs 0.32,
producing a ready-to-use Swift Package (wamr-swift) that wraps the WAMR
WebAssembly Micro Runtime.

Rust workspace (src/vendors/wamr-rust/):
- wamr-sys: raw C FFI bindings (bindgen) + cc-based WAMR C compilation (49 files)
- wamr-ffi: safe Rust API with UniFFI annotations
  - UDL interface: 4 enums, 3 dicts, 1 error, 4 object interfaces
  - Real WAMR C calls: init, load, instantiate, find_function, call_raw,
    read_memory, write_memory, get_global, set_global, version
  - Bundled uniffi-bindgen CLI binary for code generation

Swift Package (src/vendors/wamr-swift/):
- Sources/WamrFFI/wamr_ffi.swift — 55K generated Swift API
- Sources/CWamrFFI/ — C bridging module (header + modulemap + static lib)
- Tests/WamrFFITests/ — 2 XCTests passing (runtime creation, error handling)
- build.sh — one-command rebuild (cargo → uniffi-bindgen → swift)
- build-ios.sh — iOS xcframework generation (device + simulator + macOS)

Build config (src/vendors/wamr/core/config.h):
- Minimal WAMR defines for interpreter-only POSIX build
- Supports macOS aarch64/x86_64 and iOS aarch64

Verified: cargo build ✅ → uniffi-bindgen generate ✅ → swift build ✅ → swift test ✅ (2/2)
…niFFI

Add Kotlin/Android package (wamr-kotlin) with UniFFI-generated bindings,
mirroring the wamr-swift Swift package pattern for Android/JVM targets.

Generated code (2927 lines):
- library/src/main/kotlin/uniffi/wamr_ffi/wamr_ffi.kt
  UniFFI-generated Kotlin API with JNA native loading
  4 enums, 3 data classes, 1 sealed error hierarchy, 4 interface classes

Gradle project (AGP 9.3.1, Kotlin 2.4, Gradle 9.6.1):
- library/ — Android library module (namespace org.nativescript.wamr.ffi)
  Dependencies: net.java.dev.jna:jna:5.17.0
- hosttest/ — JVM host tests (JUnit 5)
  Follows existing pattern: shares library source dirs, avoids AAR variant conflict

Build scripts:
- build.sh — one-command pipeline (cargo → uniffi-bindgen → gradle)
  Supports: rust, generate, gradle, android (cross-compile), all

Native library:
- Library override via uniffi.component.wamr_ffi.libraryOverride=wamr_ffi
- Pre-built .so/.dylib goes in library/src/main/jniLibs/{host,arm64-v8a,...}

Known issue:
- Kotlin compilation succeeds; JVM host test hits SIGILL during JNA
  Native.register() — Rust-native tests pass (cargo test), dlopen works,
  Native.register() with simple interface works. Likely JNA/JVM/macOS 26
  compatibility issue with the full 89-method interface.
…package.json scripts

- Add 'Before writing any TypeScript' section to AGENTS.md
  documents that  /  must be run
  to generate native type declarations before writing platform adapters
- Add  and  scripts
  to package.json
- Document EPERM workaround for macOS permissions on
  ~/.local/share/.nativescript-cli/
…app, add sandbox patch

- Correct ns typings: must be run from apps/nativescript-wasm-test,
  not the plugin directory (needs full NativeScript project context)
- Document macOS sandbox workaround: patch settings-service.js to
  redirect ~/.local/share/.nativescript-cli → /tmp/.nativescript-cli
- Update package.json scripts to cd into the test app first
- Remove the old chmod workaround (ineffective against process sandbox)
The device-level specs (app/tests/wamr/*.spec.ts) require Run ns test --help for more information. and cannot run in CI or sandboxed terminals.

Add wamr-device-mirror.spec.ts — 11 vitest tests that mock the native
layer (same pattern as wamr.spec.ts) and cover the same API surface:
- Runtime creation with all 4 execution tiers
- Module loading
- Function signature inspection
- Missing function lookup → WamrError
- dispose idempotency
- WAMR version as semver
- Default options
- ExecutionTier enum values

Total: 47 vitest tests (11 wire + 25 adapter + 11 mirror), all pass.

Update AGENTS.md: document two-layer test strategy clearly.
…ilds

Replace the JavaCPP-based Android build pipeline with cargo-ndk
cross-compilation across both plugins (wamr, wasm3).

wamr plugin:
- New wamr-jni crate: hand-written Rust JNI functions (22 exports)
  calling nsc_wamr_shim C API, replaces JavaCPP auto-generated JNI
- wamr-sys now compiles nsc_wamr_shim.c alongside WAMR C sources
- New NativeWamr.kt: Kotlin external JNI declarations
- NSCWamr.kt rewritten: Long handles + NativeWamr replace
  JavaCPP BytePointer/LongPointer/PointerPointer + org.wamr.*
- library/build.gradle.kts: cargo ndk replaces javacppParse/buildNative
- hosttest: cargo build for host (macOS) native lib
- include.gradle: removes org.bytedeco:javacpp, .so -> libwamr_jni.so
- getMemory() returns java.nio.ByteBuffer via JNI NewDirectByteBuffer

wasm3 plugin:
- New wasm3-rust workspace (wasm3-sys + wasm3-jni, 7 files)
- wasm3-sys: bindgen FFI + cc compilation of wasm3 C sources
  + nsc_wasm3_shim.c
- wasm3-jni: 30 JNI functions calling wasm3 C API + shim
- New NativeWasm3.kt, rewritten NSCWasm3.kt (same pattern as wamr)
- HostTrampoline properly decodes/encodes via Wire with type info
- libwasm3_jni.so replaces libjniwasm3.so

Also adds wasm3-rust/target/ to .gitignore.
Both wamr-jni and wasm3-jni host trampolines used
from_raw_parts() on potentially-null pointers when n_args==0.
Added n_args==0 guards that skip the unsafe slice creation,
using empty vec for zero-arg calls instead.
Rename package scope from @org/ to @cross-code/ across all
package.json files, documentation (AGENTS.md, README.md),
and test app imports. Also includes minor whitespace fixes.
- wasm3-ffi: safe Rust wrapper around wasm3-sys with UniFFI
  scaffolding (UDL, build.rs, uniffi-bindgen binary, lib.rs)
- wasm3-kotlin: Gradle project for Kotlin bindings with hosttest
- wasm3-swift: SwiftPM package for Swift bindings with XCTest
- Fix dead iOS version-min flag in wasm3-sys/build.rs
The vendored WAMR version uses a module-centric API without
wasm_runtime_t. The shim now defines its own nsc_wamr_runtime_t
abstraction that tracks module instances and exec envs.

Key changes:
- wasm_runtime_t replaced by opaque nsc_wamr_runtime_t struct
- wasm_runtime_create/destroy → wasm_runtime_init/destroy (global)
- wasm_runtime_load/instantiate no longer take runtime arg
- wasm_runtime_create_exec_env takes module_inst, not runtime
- wasm_func_get_param/result_types require module_inst + types buf
- wasm_global_t replaced by wasm_global_inst_t (kind + global_data)
- NativeSymbol.call_conv_raw removed
- register_natives_raw no longer takes module_inst
- get_version takes 3 output pointers
- wasm_runtime_call_wasm_a uses wasm_val_t (not uint32) in new API
- mem_alloc_option is a union, not int
- Func-instance mapping for call/runtime lookup
Replaced nsc_wasm3_shim.c (2 functions) and nsc_wamr_shim.c
(~20 functions) with pure Rust implementations in their
respective -sys crates.

wasm3-sys:
- nsc_global_get/set now in lib.rs as pub unsafe fn
- Removed C shim from build.rs, wrapper.h, include paths

wamr-sys:
- New shim.rs module: NscWamrRuntime, call/globals/memory/host
- extern "C" wrappers in lib.rs preserve JNI compatibility
- Removed C shim from build.rs compilation

Both build.rs no longer compile any C shim code.
Added GLOBAL_FUNC_MAP and GLOBAL_LAST_RESULTS for cross-runtime
function dispatch, matching the old C shim's g_ctx_list pattern.
extern "C" wrappers for nsc_wamr_call/get_results now work.
wamr-sys:
- Unit tests in shim.rs: type conversion, slot_width, convert_signature
- Integration tests: runtime lifecycle, module load, function call,
  convert_signature export

wasm3-sys:
- Integration tests: runtime lifecycle, module load, function call,
  global error handling
wasm3 uses M3ValueType enum with variants c_m3Type_i32=1
(not 0x7F like WAMR). bindgen generates these as enum variants,
not bare i32 constants. Updated nsc_global_get/set and
wasm3-ffi to use M3ValueType::c_m3Type_* qualified names.
bindgen generates: M3ValueType_c_m3Type_i32 (not c_m3Type_i32),
M3TaggedValue.type_ (with underscore), union fields with _ suffix
(i32_, i64_, f32_, f64_), IM3TaggedValue = *mut M3TaggedValue.
Updated all crates: wasm3-sys, wasm3-ffi, wasm3-jni.
listepo added 30 commits August 3, 2026 23:22
- Replace get_primitive_array_critical with get_byte_array_region
- Fix get_array_length to take &JArray (jni 0.21)
- Fix m3_Call/m3_GetResults: *const → *mut pointer casts
- Fix new_direct_byte_buffer: 2-arg API (ptr, capacity)
- Fix m3_GetUserData: takes runtime, not context
- Fix m3_LinkRawFunctionEx: no Option wrapper
- Fix JValue::Object pattern match for jni 0.21
- Fix get_long_array_region to take &JLongArray
- Add unsafe impl Send for HostCtx
- Remove unnecessary unsafe blocks in wasm3-sys lib.rs
- Replace get_primitive_array_critical with get_byte_array_region
- Fix read_long_array to take &JLongArray
- Fix get_array_length to take &JArray refs
- Add JByteArray, JLongArray imports
- Remove unused JavaVM import
- Store HostCtx pointers as usize in HashMap (Sync fix)
- get_byte_array_region takes &mut [i8], transmute from &mut [u8]
- JValueOwned::Object for call_method result pattern
- arg_ptrs/ret_ptrs need let mut for as_mut_ptr()
- new_direct_byte_buffer requires unsafe block
…lation/ from sync

- Package.swift: add core/shared/platform/darwin header search path
  for platform_internal.h on macOS/iOS
- sync-wamr.mjs: exclude compilation/ directory (SIMD/JIT not needed
  for interpreter-only iOS build)
Add all necessary include dirs matching wamr-sys/build.rs:
core/iwasm/common, interpreter, shared/utils, mem-alloc,
platform/common/posix, libc-util, memory.

Add WAMR build defines:
WASM_ENABLE_INTERP=1, FAST_INTERP=0, BH_PLATFORM_DARWIN=1,
WAMR_BUILD_INVOKE_NATIVE_GENERAL=1
…e, underscore unused params

- shim.rs: use super::* instead of use wamr_sys::* (circular ref)
- RUNTIME_REGISTRY stores usize instead of *mut NscWamrRuntime (Sync)
- nsc_wamr_module_name, nsc_wamr_function_name: underscore unused params
- Replace JavaCPP/org.bytedeco references with cargo-ndk + Rust JNI
- Update .so names: libjniwamr→libwamr_jni, libjniwasm3→libwasm3_jni
- Add Rust workspace and UniFFI vendor packages to layout
- Note C shims are now pure Rust
- Remove build-native.mjs references
wasm3-android / wamr-android:
- Install Rust (if missing) instead of clang
- cargo build replaces build-native.mjs + JavaCPP
- Remove clang install step (Rust cc crate handles C compilation)
- Update job descriptions and step names
- Update package.json descriptions: JavaCPP → Rust JNI (cargo-ndk)
- Update code-review.yml description
- Remove src/javacpp/ preset directories (dead code)
- Fix wasm3-ffi: Send+Sync impls, m3_Call/m3_GetResults casts, let mut
- Generated wasm3_ffi.swift + wasm3_ffiFFI.h/modulemap for Swift
- Generated wasm3_ffi.kt for Kotlin
- Copy libwasm3_ffi.a to CWasm3FFI target
- Synced WAMR C sources to CWamr (via sync-wamr.mjs)
The WAMR API changed: NativeSymbol.signature must be a valid
signature string, not NULL. Passing NULL causes
wasm_runtime_register_natives_raw to fail silently with
'failed to link import function'.

- Pass strdup(wamrSig) as symbol.signature
- Add signature field to HostRegistration for cleanup
- Free symSig on error path and unregister
The prebuilt nativescript-wasm3.aar was built before the
JavaCPP→cargo-ndk migration. It still contains
org/wasm3/presets/wasm3 reference causing NoClassDefFoundError
on Android. Must be rebuilt with:
  cd platforms/android/wasm3-android && ./gradlew :library:deployAar
…ain cycle

All 13 WAMR Swift tests now pass (was 4 failures).

Root causes:
1. Host import override test: WAMR resolves module imports at LOAD time
   and caches them — host functions linked afterwards were never seen,
   and stale registrations from earlier runtimes won. Fix: load with
   LoadArgs.no_resolve=true and re-resolve at instantiation
   (resolveSymbolsIfNeeded) after the caller links imports.
2. Memory bounds: wasm_runtime_validate_app_addr checks the MAX growable
   size, not the current size — suite.wasm declares max 4 pages so
   out-of-bounds reads at 64 KiB passed validation. Fix: memorySize now
   reads wasm_memory_get_cur_page_count and read/write validate against
   the current size.
3. Retain cycle: NSCWamrModule.runtime was strong, NSCWamrRuntime.modules
   held modules — runtimes never deallocated, leaking stale host
   registrations into later tests. Fix: weak runtime reference.
4. testUnlinkedImportFails: this WAMR version traps at CALL time, not
   findFunction. Updated the test to assert the call throws.
- instance(): guard let runtime + tornDown check BEFORE
  resolveSymbolsIfNeeded so a freed module is never dereferenced
- teardown() stamps tornDown=true
- exportedGlobal reaches instance() which now throws for
  orphaned/torn-down modules
wasm_func_get_param_types/get_result_types return the
wasm_c_api.h enum (WASM_I32=0..WASM_F64=3), not raw WAMR type
bytes. slot_width and kind_at now document and handle simple
codes; test_slot_width asserts the actual valkind values.
Extern nsc_wamr_to/from_simple_type (byte conversion) is
unchanged for the legacy Kotlin path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant