2026-04-29 18:50:55 +02:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# scripts/parity-check.sh — run nisps_parity_check natively AND via WASM,
|
|
|
|
|
# then float32-diff the resulting blobs.
|
|
|
|
|
#
|
|
|
|
|
# Prerequisites:
|
|
|
|
|
# 1. scripts/build-cpp-tests.sh has run (need nisps_parity_check binary).
|
2026-07-13 23:14:23 +02:00
|
|
|
# 2. scripts/build-wasm.sh has run (need manifold/public/nisps.{js,wasm}).
|
2026-04-29 18:50:55 +02:00
|
|
|
#
|
|
|
|
|
# This script can run either step on demand if the artifacts are missing.
|
|
|
|
|
# Skip auto-build with NISPS_PARITY_NO_BUILD=1.
|
|
|
|
|
#
|
|
|
|
|
# Exit codes:
|
|
|
|
|
# 0 — outputs match within 1e-5 absolute tolerance
|
|
|
|
|
# 1 — mismatch
|
|
|
|
|
# 2 — file/format error or missing artifacts
|
|
|
|
|
# 3 — build failure
|
|
|
|
|
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
|
TESTS_DIR="$ROOT/tests/cpp"
|
|
|
|
|
BUILD_DIR="${NISPS_BUILD_DIR:-$ROOT/nisps/build}"
|
|
|
|
|
NATIVE_BIN="$BUILD_DIR/nisps_parity_check"
|
2026-07-13 23:14:23 +02:00
|
|
|
WASM_GLUE="$ROOT/manifold/public/nisps.js"
|
|
|
|
|
WASM_MOD="$ROOT/manifold/public/nisps.wasm"
|
2026-04-29 18:50:55 +02:00
|
|
|
NATIVE_OUT="$TESTS_DIR/parity_native.bin"
|
|
|
|
|
WASM_OUT="$TESTS_DIR/parity_wasm.bin"
|
|
|
|
|
TOL="${NISPS_PARITY_TOL:-1e-5}"
|
|
|
|
|
NO_BUILD="${NISPS_PARITY_NO_BUILD:-0}"
|
|
|
|
|
|
|
|
|
|
ensure_native() {
|
|
|
|
|
if [[ -x "$NATIVE_BIN" ]]; then return 0; fi
|
|
|
|
|
if [[ "$NO_BUILD" == "1" ]]; then
|
|
|
|
|
echo "[parity-check] missing $NATIVE_BIN and NISPS_PARITY_NO_BUILD=1; aborting" >&2
|
|
|
|
|
exit 2
|
|
|
|
|
fi
|
|
|
|
|
echo "[parity-check] native binary missing — running build-cpp-tests.sh"
|
|
|
|
|
NISPS_RUN_TESTS=0 "$ROOT/scripts/build-cpp-tests.sh" >/dev/null || {
|
|
|
|
|
echo "[parity-check] C++ build failed" >&2
|
|
|
|
|
exit 3
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure_wasm() {
|
|
|
|
|
if [[ -f "$WASM_GLUE" && -f "$WASM_MOD" ]]; then return 0; fi
|
|
|
|
|
if [[ "$NO_BUILD" == "1" ]]; then
|
|
|
|
|
echo "[parity-check] missing $WASM_GLUE / $WASM_MOD and NISPS_PARITY_NO_BUILD=1; aborting" >&2
|
|
|
|
|
exit 2
|
|
|
|
|
fi
|
|
|
|
|
echo "[parity-check] WASM artifacts missing — running build-wasm.sh"
|
|
|
|
|
"$ROOT/scripts/build-wasm.sh" >/dev/null 2>&1 || {
|
|
|
|
|
echo "[parity-check] WASM build failed" >&2
|
|
|
|
|
exit 3
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ensure_native
|
|
|
|
|
ensure_wasm
|
|
|
|
|
|
|
|
|
|
echo "[parity-check] running native..."
|
|
|
|
|
"$NATIVE_BIN" "$NATIVE_OUT"
|
|
|
|
|
|
|
|
|
|
echo "[parity-check] running WASM..."
|
|
|
|
|
node "$TESTS_DIR/parity_wasm.mjs" "$WASM_OUT"
|
|
|
|
|
|
|
|
|
|
echo "[parity-check] diffing (tolerance=$TOL)..."
|
fix(gates): close the no-heap lint's false negatives and parity's silent FAIL
Phase 2 (S30, L52, L51). These are the gates that are supposed to protect the
core's headline constraints, so the fix is demonstrated rather than asserted.
- S30: lint-cpp.sh's heap audit hardcoded dsp/engines/ml/modes, so nisps/core/
and nisps/pipeline/ — the P4 control-rate hot path — were NEVER scanned. Its
comment handling also post-filtered with `grep -v ' *//'`, which misses
trailing comments. Coverage is now exclusion-based (everything under nisps/
except wasm/, tests/, build/), comments are STRIPPED before matching using the
same perl strip audit_float_suffix already used, and the pattern set is
extended. dynamic_storage.hpp remains deliberately allowlisted — it is a real,
documented heap user that #errors on RP2350.
PROOF (run by me, not just reported): planted `new float[4]` in
nisps/pipeline/input_chain.hpp plus a std::malloc in the trailing-comment form
the old filter skipped.
old lint: "[lint-cpp] clean", exit 0 <- the false negative, live
new lint: FAIL, both lines named, exit 1
Plants removed; lint clean and exit 0 again; tree verified unmodified.
- L52: parity-check.sh's FAIL branch was unreachable — under `set -e` the script
died at the diff command before it could print anything. A mismatch therefore
failed silently. The diff now runs inside the `if` condition, where set -e does
not apply. Verified by forcing NISPS_PARITY_TOL=0: old script exited silently,
new one prints FAIL with the exit code.
- L51: parity_check.cpp and parity_wasm.mjs headers documented 4 stages / blob
v1 against a real 7-stage / v5 implementation. Comment-only rewrite; parity
behaviour unchanged (re-ran the gate: PASS, 1273 floats within 1e-5).
Note for future work: the lint now permanently scans nisps/core/ and
nisps/pipeline/, so changes there are enforced that were not before.
2026-07-21 13:23:11 +02:00
|
|
|
# The node invocation must live in the `if` condition: under `set -e` a bare
|
|
|
|
|
# failing command would kill the script before any FAIL line could print.
|
|
|
|
|
if node "$TESTS_DIR/parity_diff.mjs" "$NATIVE_OUT" "$WASM_OUT" "$TOL"; then
|
2026-04-29 18:50:55 +02:00
|
|
|
echo "[parity-check] PASS"
|
|
|
|
|
else
|
fix(gates): close the no-heap lint's false negatives and parity's silent FAIL
Phase 2 (S30, L52, L51). These are the gates that are supposed to protect the
core's headline constraints, so the fix is demonstrated rather than asserted.
- S30: lint-cpp.sh's heap audit hardcoded dsp/engines/ml/modes, so nisps/core/
and nisps/pipeline/ — the P4 control-rate hot path — were NEVER scanned. Its
comment handling also post-filtered with `grep -v ' *//'`, which misses
trailing comments. Coverage is now exclusion-based (everything under nisps/
except wasm/, tests/, build/), comments are STRIPPED before matching using the
same perl strip audit_float_suffix already used, and the pattern set is
extended. dynamic_storage.hpp remains deliberately allowlisted — it is a real,
documented heap user that #errors on RP2350.
PROOF (run by me, not just reported): planted `new float[4]` in
nisps/pipeline/input_chain.hpp plus a std::malloc in the trailing-comment form
the old filter skipped.
old lint: "[lint-cpp] clean", exit 0 <- the false negative, live
new lint: FAIL, both lines named, exit 1
Plants removed; lint clean and exit 0 again; tree verified unmodified.
- L52: parity-check.sh's FAIL branch was unreachable — under `set -e` the script
died at the diff command before it could print anything. A mismatch therefore
failed silently. The diff now runs inside the `if` condition, where set -e does
not apply. Verified by forcing NISPS_PARITY_TOL=0: old script exited silently,
new one prints FAIL with the exit code.
- L51: parity_check.cpp and parity_wasm.mjs headers documented 4 stages / blob
v1 against a real 7-stage / v5 implementation. Comment-only rewrite; parity
behaviour unchanged (re-ran the gate: PASS, 1273 floats within 1e-5).
Note for future work: the lint now permanently scans nisps/core/ and
nisps/pipeline/, so changes there are enforced that were not before.
2026-07-21 13:23:11 +02:00
|
|
|
status=$?
|
2026-04-29 18:50:55 +02:00
|
|
|
echo "[parity-check] FAIL (exit=$status)" >&2
|
fix(gates): close the no-heap lint's false negatives and parity's silent FAIL
Phase 2 (S30, L52, L51). These are the gates that are supposed to protect the
core's headline constraints, so the fix is demonstrated rather than asserted.
- S30: lint-cpp.sh's heap audit hardcoded dsp/engines/ml/modes, so nisps/core/
and nisps/pipeline/ — the P4 control-rate hot path — were NEVER scanned. Its
comment handling also post-filtered with `grep -v ' *//'`, which misses
trailing comments. Coverage is now exclusion-based (everything under nisps/
except wasm/, tests/, build/), comments are STRIPPED before matching using the
same perl strip audit_float_suffix already used, and the pattern set is
extended. dynamic_storage.hpp remains deliberately allowlisted — it is a real,
documented heap user that #errors on RP2350.
PROOF (run by me, not just reported): planted `new float[4]` in
nisps/pipeline/input_chain.hpp plus a std::malloc in the trailing-comment form
the old filter skipped.
old lint: "[lint-cpp] clean", exit 0 <- the false negative, live
new lint: FAIL, both lines named, exit 1
Plants removed; lint clean and exit 0 again; tree verified unmodified.
- L52: parity-check.sh's FAIL branch was unreachable — under `set -e` the script
died at the diff command before it could print anything. A mismatch therefore
failed silently. The diff now runs inside the `if` condition, where set -e does
not apply. Verified by forcing NISPS_PARITY_TOL=0: old script exited silently,
new one prints FAIL with the exit code.
- L51: parity_check.cpp and parity_wasm.mjs headers documented 4 stages / blob
v1 against a real 7-stage / v5 implementation. Comment-only rewrite; parity
behaviour unchanged (re-ran the gate: PASS, 1273 floats within 1e-5).
Note for future work: the lint now permanently scans nisps/core/ and
nisps/pipeline/, so changes there are enforced that were not before.
2026-07-21 13:23:11 +02:00
|
|
|
exit "$status"
|
2026-04-29 18:50:55 +02:00
|
|
|
fi
|
fix(gates): close the no-heap lint's false negatives and parity's silent FAIL
Phase 2 (S30, L52, L51). These are the gates that are supposed to protect the
core's headline constraints, so the fix is demonstrated rather than asserted.
- S30: lint-cpp.sh's heap audit hardcoded dsp/engines/ml/modes, so nisps/core/
and nisps/pipeline/ — the P4 control-rate hot path — were NEVER scanned. Its
comment handling also post-filtered with `grep -v ' *//'`, which misses
trailing comments. Coverage is now exclusion-based (everything under nisps/
except wasm/, tests/, build/), comments are STRIPPED before matching using the
same perl strip audit_float_suffix already used, and the pattern set is
extended. dynamic_storage.hpp remains deliberately allowlisted — it is a real,
documented heap user that #errors on RP2350.
PROOF (run by me, not just reported): planted `new float[4]` in
nisps/pipeline/input_chain.hpp plus a std::malloc in the trailing-comment form
the old filter skipped.
old lint: "[lint-cpp] clean", exit 0 <- the false negative, live
new lint: FAIL, both lines named, exit 1
Plants removed; lint clean and exit 0 again; tree verified unmodified.
- L52: parity-check.sh's FAIL branch was unreachable — under `set -e` the script
died at the diff command before it could print anything. A mismatch therefore
failed silently. The diff now runs inside the `if` condition, where set -e does
not apply. Verified by forcing NISPS_PARITY_TOL=0: old script exited silently,
new one prints FAIL with the exit code.
- L51: parity_check.cpp and parity_wasm.mjs headers documented 4 stages / blob
v1 against a real 7-stage / v5 implementation. Comment-only rewrite; parity
behaviour unchanged (re-ran the gate: PASS, 1273 floats within 1e-5).
Note for future work: the lint now permanently scans nisps/core/ and
nisps/pipeline/, so changes there are enforced that were not before.
2026-07-21 13:23:11 +02:00
|
|
|
exit 0
|