Phase 2, S35. Two real defects from one root cause, both confirmed by trace rather than taken from the audit: 1. Divergence. WasmIML built its TS Dataset mirror with a cap of 100 while every addExample() ALSO pushed into the C++ FIFO ring, capped at 128. Since train() reads the C++ ring and trainAsync() reads the TS mirror, past 100 examples the two trained on different datasets — silently. 2. Latent OOB read. nisps_ml_train sizes its sample-weight span by the C++ side's example_count() (up to 128), but wasm-iml.ts allocates that heap buffer from the TS dataset's size (<=100). Once the ring exceeds the mirror, the span reads past the end of the caller's allocation. Fix: name the capacity ONCE as nisps::ml::kDefaultMaxExamples = 128, used by FixedStorage's default template arg, DynamicStorage's default ctor arg, and the MLP<> alias (which is the only real FixedStorage instantiation path and carried its own independent 128 literal — the last copy of this dual truth). Expose it through nisps_ml_describe and have the TS side read it instead of hardcoding. Dataset's constructor default is removed entirely: a default was what invited this bug class, and the sole call site now always supplies the describe() value. ABI NOTE: this extends nisps_ml_describe from a 6-int to a 7-int descriptor. nisps_ml_describe always writes 7 ints regardless of the caller's buffer, so every call site had to grow in the same change or it would overflow the WASM heap by 4 bytes per call. All five sites updated: three in wasm-iml.ts (init defaults, init per-instance, reshape re-describe — the finding said there were two), one in wasm-worker.ts, one in tests/cpp/parity_wasm.mjs. The parity harness's expected-dims check now also pins the new max_examples slot. Regression test: tests/cpp/test_mlp_storage_defaults.cpp — pins the two storage policies to one constant, and drives MLPCore<DynamicStorage> exactly as bindings.cpp does past the old TS cap, asserting it saturates at 128 and not at 100. Fail-before/pass-after confirmed by temporarily setting the constant to 100: 2 failures, named. Reverted: green. Audit correction: the cited dataset.ts:81 is the FIFO eviction check; the hardcoded default was at dataset.ts:45. Gates: run-all-tests.sh ALL GREEN, parity PASS.
185 lines
8 KiB
CMake
185 lines
8 KiB
CMake
cmake_minimum_required(VERSION 3.20)
|
|
project(nisps_core CXX)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Toolchain / standard
|
|
# ---------------------------------------------------------------------------
|
|
set(CMAKE_CXX_STANDARD 20)
|
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
|
|
|
# Default to a Release build when invoked without -DCMAKE_BUILD_TYPE so host
|
|
# tests get optimized math; flip with `-DCMAKE_BUILD_TYPE=Debug` for stepping.
|
|
if(NOT CMAKE_BUILD_TYPE)
|
|
set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core interface library — header-only. Other parts of the build (ml/, dsp/,
|
|
# engines/, modes/) will link against this once they exist.
|
|
# ---------------------------------------------------------------------------
|
|
add_library(nisps_core INTERFACE)
|
|
target_include_directories(nisps_core
|
|
INTERFACE
|
|
${CMAKE_CURRENT_SOURCE_DIR}
|
|
)
|
|
target_compile_features(nisps_core INTERFACE cxx_std_20)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Emscripten / WASM target detection
|
|
# ---------------------------------------------------------------------------
|
|
# When building for WASM via emcmake, ${EMSCRIPTEN} is set automatically. We
|
|
# don't actually emit a WASM binary from this CMakeLists yet — the WASM build
|
|
# script (scripts/build-wasm.sh) assembles its own
|
|
# Emscripten link command. Here we just gate the host-only test executable so
|
|
# `emcmake cmake -S nisps -B build-wasm` configures cleanly.
|
|
if(EMSCRIPTEN)
|
|
message(STATUS "nisps_core: configuring for Emscripten/WASM target")
|
|
endif()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test scaffold — only built on the host. We use a hand-rolled assertion-
|
|
# based harness (see tests/cpp/test_helpers.hpp) rather than Catch2/doctest;
|
|
# the rationale is in test_helpers.hpp.
|
|
# ---------------------------------------------------------------------------
|
|
if(NOT EMSCRIPTEN)
|
|
set(NISPS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../tests/cpp)
|
|
|
|
add_executable(nisps_core_tests
|
|
${NISPS_TEST_DIR}/test_main.cpp
|
|
${NISPS_TEST_DIR}/test_ring_buffer.cpp
|
|
${NISPS_TEST_DIR}/test_rng.cpp
|
|
${NISPS_TEST_DIR}/test_math.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_init.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_inference.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_training.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_loss.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_rl.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_storage_parity.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_storage_defaults.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_jolt.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_ou_noise.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_feedback.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_geo_dislike.cpp
|
|
${NISPS_TEST_DIR}/test_mlp_serialize.cpp
|
|
${NISPS_TEST_DIR}/test_pipeline.cpp
|
|
${NISPS_TEST_DIR}/test_vcv_iml_parity.cpp
|
|
)
|
|
target_link_libraries(nisps_core_tests PRIVATE nisps_core)
|
|
|
|
# Chris's rule: the core compiles cleanly under -Wall -Wextra -Werror.
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|
target_compile_options(nisps_core_tests PRIVATE
|
|
-Wall -Wextra -Werror -Wpedantic
|
|
)
|
|
elseif(MSVC)
|
|
target_compile_options(nisps_core_tests PRIVATE /W4 /WX)
|
|
endif()
|
|
|
|
enable_testing()
|
|
add_test(NAME nisps_core_tests COMMAND nisps_core_tests)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# DSP + engines tests (stream 3). Built as a separate executable so
|
|
# failures here don't take core/ML tests with them.
|
|
# ---------------------------------------------------------------------
|
|
add_executable(nisps_dsp_engine_tests
|
|
${NISPS_TEST_DIR}/test_main.cpp
|
|
${NISPS_TEST_DIR}/test_dsp_biquad.cpp
|
|
${NISPS_TEST_DIR}/test_dsp_delay.cpp
|
|
${NISPS_TEST_DIR}/test_dsp_reverb.cpp
|
|
${NISPS_TEST_DIR}/test_dsp_pitch_shift.cpp
|
|
${NISPS_TEST_DIR}/test_engine_no_op.cpp
|
|
${NISPS_TEST_DIR}/test_engine_paf_synth.cpp
|
|
${NISPS_TEST_DIR}/test_engine_channel_strip.cpp
|
|
${NISPS_TEST_DIR}/test_engine_xiasri.cpp
|
|
${NISPS_TEST_DIR}/test_engine_verb_fx.cpp
|
|
${NISPS_TEST_DIR}/test_engine_memlcelium.cpp
|
|
${NISPS_TEST_DIR}/test_engine_breakor.cpp
|
|
${NISPS_TEST_DIR}/test_engine_elysiamorf.cpp
|
|
${NISPS_TEST_DIR}/test_engine_analysis.cpp
|
|
)
|
|
target_link_libraries(nisps_dsp_engine_tests PRIVATE nisps_core)
|
|
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|
target_compile_options(nisps_dsp_engine_tests PRIVATE
|
|
-Wall -Wextra -Werror -Wpedantic
|
|
)
|
|
elseif(MSVC)
|
|
target_compile_options(nisps_dsp_engine_tests PRIVATE /W4 /WX)
|
|
endif()
|
|
|
|
add_test(NAME nisps_dsp_engine_tests COMMAND nisps_dsp_engine_tests)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Mode tests (stream 4). Cover concept satisfaction, voice space
|
|
# dispatch, control-event pumping, and end-to-end inference→audio.
|
|
# ---------------------------------------------------------------------
|
|
add_executable(nisps_modes_tests
|
|
${NISPS_TEST_DIR}/test_main.cpp
|
|
${NISPS_TEST_DIR}/test_mode_concepts.cpp
|
|
${NISPS_TEST_DIR}/test_mode_paf_synth.cpp
|
|
${NISPS_TEST_DIR}/test_mode_voice_space.cpp
|
|
${NISPS_TEST_DIR}/test_mode_breakor_events.cpp
|
|
${NISPS_TEST_DIR}/test_mode_learning.cpp
|
|
)
|
|
target_link_libraries(nisps_modes_tests PRIVATE nisps_core)
|
|
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|
target_compile_options(nisps_modes_tests PRIVATE
|
|
-Wall -Wextra -Werror -Wpedantic
|
|
)
|
|
elseif(MSVC)
|
|
target_compile_options(nisps_modes_tests PRIVATE /W4 /WX)
|
|
endif()
|
|
|
|
add_test(NAME nisps_modes_tests COMMAND nisps_modes_tests)
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Stream 11 verification suite (golden vectors + engine impulse).
|
|
# Lives in tests/cpp/, registered separately so a regression here can
|
|
# be diagnosed without rebuilding the world.
|
|
# ---------------------------------------------------------------------
|
|
add_executable(nisps_golden_tests
|
|
${NISPS_TEST_DIR}/test_main.cpp
|
|
${NISPS_TEST_DIR}/ml_golden_vectors.cpp
|
|
${NISPS_TEST_DIR}/engine_impulse.cpp
|
|
)
|
|
target_link_libraries(nisps_golden_tests PRIVATE nisps_core)
|
|
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|
target_compile_options(nisps_golden_tests PRIVATE
|
|
-Wall -Wextra -Werror -Wpedantic
|
|
)
|
|
elseif(MSVC)
|
|
target_compile_options(nisps_golden_tests PRIVATE /W4 /WX)
|
|
endif()
|
|
|
|
add_test(NAME nisps_golden_tests COMMAND nisps_golden_tests)
|
|
# Run the impulse test from the repo root so the relative baseline path
|
|
# in `engine_impulse.cpp` resolves to tests/cpp/engine_impulse_baseline.bin.
|
|
set_tests_properties(nisps_golden_tests PROPERTIES
|
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..
|
|
)
|
|
|
|
# Standalone parity-check runner. NOT registered with ctest — it's
|
|
# invoked from scripts/parity-check.sh which orchestrates native+WASM
|
|
# together.
|
|
add_executable(nisps_parity_check
|
|
${NISPS_TEST_DIR}/parity_check.cpp
|
|
)
|
|
target_link_libraries(nisps_parity_check PRIVATE nisps_core)
|
|
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang")
|
|
target_compile_options(nisps_parity_check PRIVATE
|
|
-Wall -Wextra -Werror -Wpedantic
|
|
# Disable FP multiply-add contraction so native matches the WASM
|
|
# build, which has no FMA instruction. Without this, native clang/gcc
|
|
# fuses MACs in the training backprop and the (chaotic) loop amplifies
|
|
# the rounding difference past the 1e-5 parity tolerance — pronounced
|
|
# since the input layer widened to 32 for mix-and-match inputs.
|
|
-ffp-contract=off
|
|
)
|
|
elseif(MSVC)
|
|
target_compile_options(nisps_parity_check PRIVATE /W4 /WX /fp:precise)
|
|
endif()
|
|
endif()
|