Skip to content

Commit c2cc32b

Browse files
schmonzclaude
andcommitted
feat(sparc): ENGINE VERDICT GREEN — quickjs-ng runs 32-bit big-endian; cpool 8-alignment patch
Four-run campaign on NetBSD 10.1/sparc (qemu sun4m SS-20, TCG, snapshot=on; anita-installed image; no-cmake direct-cc qjs recipe): quickjs-ng v0.15.1 is CORRECT on 32-bit BE — all S3 micro-probes pass, probe.js byte-identical to the darwin control, in-guest bytecode round-trips — carrying two patches: js_exepath (existing) and NEW quickjs-ng-cpool-align.patch. The new patch fixes the campaign's real find: JSFunctionBytecode's trailing cpool array of 8-byte NAN-boxed JSValues sits at sizeof(*b) with no rounding, which is 4-mod-8 on 32-bit ABIs — universal latent UB, loud only on strict-alignment targets (sparc ldd SIGBUS; gdb: quickjs.c:38005, O2+O0 both fault, empty-cpool inputs pass; twin bug in JS_ReadFunctionTag). Two lines, in-memory layout only, zero serialized-format impact. Strongest upstream candidate in the batch. Complete S0 table (32-bit KB vs 64-bit darwin): control 2.4MB/2.5MB; run-from-source 154MB,182s/208MB; compile 213MB,93s/268MB; run-from-bytecode 74MB,2.4s/80MB. Every RAM-ladder rung fits 480MB avail (bytecode at 6.5x headroom — fits even an SS-5). Walls ledger in the results doc: LE-baked gen/*.c bytecode in release tarballs (host-order format; regen natively — evidence for the canonical-LE serializer candidate); no libatomic in NetBSD/sparc base (pthread __atomic shim); bc_csum's missing case-4 (last word unhashed when len%4==0). Scripts: run-sparc-gates.py + guest-sparc-gates.sh (gates), run-sparc-debug.py + guest-sparc-debug.sh (the gdb O2/O0/FNSA matrix that produced the file:line:instruction root cause). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0116gtPwm38DKDofwSPYvoXC
1 parent b625bdb commit c2cc32b

7 files changed

Lines changed: 769 additions & 0 deletions

File tree

‎spike/quickjs/PINS.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,13 @@ quickjs-ng-js_exepath-netbsd patch 2026-07-06
145145
# promises.open -> FileHandle{fd,close,...}) and child_process.cjs (numeric-fd stdio
146146
# passthrough). Locked by node-shim-fs.test.cjs "fs.promises.open + O_* constants"
147147
# and the live tjs Bash tool repro (echo ZQ returns ZQ). Upstream-txiki candidate.
148+
quickjs-ng-cpool-align patch 2026-07-09
149+
# patch: patches/quickjs-ng-cpool-align.patch rounds JSFunctionBytecode's cpool_offset
150+
# up to 8 in js_create_function AND JS_ReadFunctionTag. sizeof(JSFunctionBytecode)
151+
# is 4-mod-8 on 32-bit ABIs, so the trailing NAN-boxed JSValue cpool array is
152+
# permanently misaligned; strict-alignment targets (sparc32 ldd) SIGBUS in the
153+
# bytecode writer (gdb: quickjs.c:38005, ldd [%g1] with %g1 4-aligned; O2+O0 both
154+
# fault; empty-cpool inputs pass). Latent UB on every 32-bit platform, loud only on
155+
# strict alignment. In-memory layout only — zero effect on the serialized format.
156+
# Root-caused + validated on NetBSD/sparc 10.1 (results/phase3-sparc-engine-verdict.md);
157+
# darwin control unchanged. UPSTREAM CANDIDATE (strongest in batch).
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--- a/quickjs.c
2+
+++ b/quickjs.c
3+
@@ -36207,6 +36207,10 @@
4+
goto fail;
5+
6+
function_size = sizeof(*b);
7+
+ /* JSValue (8-byte under JS_NAN_BOXING) trailing array must be 8-aligned:
8+
+ sizeof(*b) is only 4-mod-8 on 32-bit ABIs and strict-alignment
9+
+ targets (sparc) SIGBUS on the 8-byte cpool loads. */
10+
+ function_size = (function_size + 7) & ~7;
11+
cpool_offset = function_size;
12+
function_size += fd->cpool_count * sizeof(*fd->cpool);
13+
vardefs_offset = function_size;
14+
@@ -38909,6 +38913,8 @@
15+
goto fail;
16+
17+
function_size = sizeof(*b);
18+
+ /* keep the JSValue cpool 8-aligned; see js_create_function */
19+
+ function_size = (function_size + 7) & ~7;
20+
cpool_offset = function_size;
21+
function_size += bc.cpool_count * sizeof(*bc.cpool);
22+
vardefs_offset = function_size;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
#!/bin/sh
2+
# guest-sparc-debug.sh — DEBUG run for the qjsc bytecode-writer SIGBUS on
3+
# NetBSD/sparc 10.1 (run-2 finding: `qjsc -ss -o gen/repl.c -m repl.js`
4+
# exits 138 = 128+SIGBUS, deterministically, while the same-recipe qjs
5+
# interpreter runs 19MB of JS fine). Goal: come back with a FILE:LINE
6+
# backtrace, not a pass. Authorized as a one-off diagnostic run.
7+
#
8+
# A/B/C matrix (all with -g, all linked with the atomic _8 shim):
9+
# A: -O2 (the faulting configuration, now with symbols)
10+
# B: -O0 (does the fault survive without optimization?
11+
# no -> gcc alignment-assumption strength-reduction;
12+
# yes -> genuine unaligned access in source logic)
13+
# C: -O2 -fno-strict-aliasing (cheap aliasing-UB discriminator)
14+
# Each variant: plain run (exit code), tiny-module + tiny-script repros
15+
# (how minimal is the repro?), then gdb -batch backtrace on the repl.js case.
16+
# Markers: dbg-<v>-exit= / dbg-<v>-tinym-exit= / dbg-<v>-tinys-exit= /
17+
# dbg-<v>-gdb-exit= ; sections === DBG-<v> === ; === GUEST-DONE === at end.
18+
set -ux
19+
H=http://10.0.2.2:8180
20+
W=/root/sparcdbg
21+
mkdir -p "$W"; cd "$W" || exit 1
22+
23+
echo "=== HOSTINFO ==="
24+
date; uname -a
25+
cc --version 2>&1 | head -1
26+
command -v gdb; gdb --version 2>&1 | head -1; echo "gdb-present-exit=$?"
27+
ulimit -d unlimited 2>/dev/null || ulimit -d "$(ulimit -H -d)" 2>/dev/null
28+
ulimit -c unlimited
29+
ulimit -a | head -12
30+
31+
echo "=== FETCH ==="
32+
f1() { n=0; while [ "$n" -lt 3 ]; do ftp -o "$1" "$2" && return 0; n=$((n+1)); sleep 10; done; echo "FETCH-FAILED $2"; return 1; }
33+
f1 qjs.tgz "$H/vendor/dist/quickjs-ng-v0.15.1.tar.gz"; echo "fetch-qjs-src-exit=$?"
34+
f1 exepath.patch "$H/patches/quickjs-ng-js_exepath-netbsd.patch"; echo "fetch-patch-exit=$?"
35+
tar xzf qjs.tgz && mv quickjs-0.15.1 qjs-src
36+
cd "$W/qjs-src" || exit 1
37+
patch -p1 < ../exepath.patch; echo "patch-exit=$?"
38+
39+
CFBASE="-g -std=gnu11 -funsigned-char -D_GNU_SOURCE -DQUICKJS_NG_BUILD -DNDEBUG -I."
40+
SRCS="quickjs libregexp libunicode dtoa quickjs-libc qjsc"
41+
42+
cat > atomic-shim.c <<'EOF'
43+
#include <pthread.h>
44+
#include <stdint.h>
45+
#include <stddef.h>
46+
static pthread_mutex_t L = PTHREAD_MUTEX_INITIALIZER;
47+
#define OPS(n, t) \
48+
t __atomic_load_##n(const volatile void *p, int mo){ t v; pthread_mutex_lock(&L); v = *(const volatile t*)p; pthread_mutex_unlock(&L); return v; } \
49+
void __atomic_store_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); *(volatile t*)p = v; pthread_mutex_unlock(&L); } \
50+
t __atomic_exchange_##n(volatile void *p, t v, int mo){ t o; pthread_mutex_lock(&L); o = *(volatile t*)p; *(volatile t*)p = v; pthread_mutex_unlock(&L); return o; } \
51+
_Bool __atomic_compare_exchange_##n(volatile void *p, void *e, t d, _Bool w, int s, int f){ _Bool r; pthread_mutex_lock(&L); t o = *(volatile t*)p; if (o == *(t*)e) { *(volatile t*)p = d; r = 1; } else { *(t*)e = o; r = 0; } pthread_mutex_unlock(&L); return r; } \
52+
t __atomic_fetch_add_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o + v; pthread_mutex_unlock(&L); return o; } \
53+
t __atomic_fetch_sub_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o - v; pthread_mutex_unlock(&L); return o; } \
54+
t __atomic_fetch_and_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o & v; pthread_mutex_unlock(&L); return o; } \
55+
t __atomic_fetch_or_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o | v; pthread_mutex_unlock(&L); return o; } \
56+
t __atomic_fetch_xor_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o ^ v; pthread_mutex_unlock(&L); return o; }
57+
OPS(1, uint8_t) OPS(2, uint16_t) OPS(4, uint32_t) OPS(8, uint64_t)
58+
_Bool __atomic_is_lock_free(size_t sz, const volatile void *p){ (void)sz; (void)p; return 0; }
59+
EOF
60+
cc -O2 -c atomic-shim.c -o atomic-shim.o; echo "shim-cc-exit=$?"
61+
62+
printf 'export default 1;\n' > tiny-module.js
63+
printf 'var x = 1;\n' > tiny-script.js
64+
65+
# build one variant: $1 = name, $2... = extra cflags
66+
buildv() {
67+
name="$1"; shift
68+
echo "=== BUILD-$name ==="
69+
date
70+
ok=0
71+
for f in $SRCS; do
72+
if ! (ulimit -v 409600; cc "$@" $CFBASE -c "$f.c" -o "$f-$name.o"); then
73+
echo "dbg-$name-cc-$f-FAILED"; ok=1
74+
fi
75+
done
76+
cc -o "qjsc-$name" qjsc-$name.o quickjs-$name.o libregexp-$name.o \
77+
libunicode-$name.o dtoa-$name.o quickjs-libc-$name.o atomic-shim.o \
78+
-lm -lpthread || ok=1
79+
echo "dbg-$name-build-exit=$ok"
80+
date
81+
return $ok
82+
}
83+
84+
# probe one variant
85+
probev() {
86+
name="$1"
87+
echo "=== DBG-$name ==="
88+
rm -f ./*.core /tmp/out-$name-*.c
89+
./"qjsc-$name" -ss -o "/tmp/out-$name-repl.c" -m repl.js
90+
echo "dbg-$name-exit=$?"
91+
./"qjsc-$name" -ss -o "/tmp/out-$name-tinym.c" -m tiny-module.js
92+
echo "dbg-$name-tinym-exit=$?"
93+
./"qjsc-$name" -ss -o "/tmp/out-$name-tinys.c" tiny-script.js
94+
echo "dbg-$name-tinys-exit=$?"
95+
ls -l ./*.core 2>/dev/null
96+
echo "--- dbg-$name-gdb-bt (repl.js case) ---"
97+
gdb -batch \
98+
-ex 'set pagination off' \
99+
-ex run \
100+
-ex 'bt full' \
101+
-ex 'info registers' \
102+
-ex 'x/3i $pc' \
103+
--args ./"qjsc-$name" -ss -o "/tmp/out-$name-gdb.c" -m repl.js
104+
echo "dbg-$name-gdb-exit=$?"
105+
}
106+
107+
buildv O2 -O2 && probev O2
108+
buildv O0 -O0 && probev O0
109+
buildv FNSA -O2 -fno-strict-aliasing && probev FNSA
110+
111+
echo "=== GUEST-DONE ==="
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/bin/sh
2+
# guest-sparc-gates.sh — ENGINE VERDICT for the sparc rung (RUNBOOK-sparc.md
3+
# gates S0 + S3). Runs INSIDE the NetBSD/sparc 10.1 sun4m guest (SS-20,
4+
# -m 512M, snapshot=on). Fetches payload from the host at 10.0.2.2:8180,
5+
# builds bare qjs/qjsc from quickjs-ng v0.15.1 WITHOUT cmake (host-derived
6+
# recipe, see below), runs the S3 big-endian correctness micro-probes FIRST
7+
# (fail-fast: a broken engine invalidates the expensive S0 rows), then the
8+
# S0 RAM-fit measurements (/usr/bin/time -l; NetBSD reports KB).
9+
#
10+
# No-cmake recipe (derived + verified on darwin host 2026-07-09; mirrors
11+
# CMakeLists.txt: qjs lib = dtoa.c libregexp.c libunicode.c quickjs.c,
12+
# qjs-libc = quickjs-libc.c, qjs_exe = qjs.c gen/repl.c gen/standalone.c,
13+
# qjsc = qjsc.c; defines _GNU_SOURCE QUICKJS_NG_BUILD; project-wide
14+
# -funsigned-char; C11 gnu; Release => -DNDEBUG; libs m + pthread).
15+
#
16+
# Markers: s1-*-exit=N (build), s3-<n>-*-exit=N (probes),
17+
# s0-*-exit=N (memory rows), '=== GUEST-DONE ===' at the end.
18+
# Build failures are findings — keep going and report.
19+
set -ux
20+
H=http://10.0.2.2:8180
21+
W=/root/sparcwork
22+
mkdir -p "$W"; cd "$W" || exit 1
23+
24+
echo "=== HOSTINFO ==="
25+
date
26+
uname -a
27+
cc --version 2>&1 | head -2
28+
sysctl hw.model machdep.cpu_arch 2>/dev/null
29+
ulimit -a
30+
# raise the data-size soft limit to its hard limit (32-bit NetBSD defaults
31+
# can be far below physmem; a low limit would masquerade as engine OOM)
32+
ulimit -d unlimited 2>/dev/null || ulimit -d "$(ulimit -H -d)" 2>/dev/null
33+
echo "datasize-now=$(ulimit -d)"
34+
35+
echo "=== RAMPROBE ==="
36+
# what does NetBSD actually map at -m 512M on qemu SS-20? (S0 first question)
37+
dmesg | grep -i 'memory'
38+
sysctl hw.physmem hw.usermem 2>/dev/null
39+
swapctl -l; echo "ramprobe-swap-exit=$?"
40+
df -m /tmp /root
41+
42+
echo "=== FETCH ==="
43+
f1() { # $1=out $2=url ; 3 tries
44+
n=0
45+
while [ "$n" -lt 3 ]; do
46+
ftp -o "$1" "$2" && return 0
47+
n=$((n+1)); sleep 10
48+
done
49+
echo "FETCH-FAILED $2"; return 1
50+
}
51+
f1 qjs.tgz "$H/vendor/dist/quickjs-ng-v0.15.1.tar.gz"; echo "fetch-qjs-src-exit=$?"
52+
f1 exepath.patch "$H/patches/quickjs-ng-js_exepath-netbsd.patch"; echo "fetch-patch-exit=$?"
53+
# run-3 root cause (gdb: SIGBUS `ldd` at quickjs.c:38005, %g1 4-mod-8):
54+
# JSFunctionBytecode's trailing JSValue cpool array sits at sizeof(*b),
55+
# which is 4-mod-8 on sparc32 — 8-byte nan-boxed loads fault. Patch rounds
56+
# cpool_offset to 8 at both layout sites (writer + reader); host-validated.
57+
f1 cpool-align.patch "$H/vendor/dist/quickjs-ng-cpool-align.diff"; echo "fetch-patch2-exit=$?"
58+
f1 probe.js "$H/probe.js"; echo "fetch-probe-exit=$?"
59+
f1 cli.cjs "$H/vendor/dist/cli.cjs"; echo "fetch-cli-exit=$?"
60+
wc -c qjs.tgz exepath.patch cpool-align.patch probe.js cli.cjs
61+
62+
echo "=== BUILD-QJS ==="
63+
date
64+
tar xzf qjs.tgz && mv quickjs-0.15.1 qjs-src
65+
cd "$W/qjs-src" || exit 1
66+
patch -p1 < ../exepath.patch; echo "s1-patch-exit=$?"
67+
patch -p1 < ../cpool-align.patch; echo "s1-patch2-exit=$?"
68+
69+
# ccg: compile one file with an address-space cap so gcc FAILS FAST instead
70+
# of swap-thrashing for hours under TCG (512M guest; clang needed 336MB RSS
71+
# for quickjs.c -O2 on the host). Fallback ladder -O2 -> -O1 -> -O0; the
72+
# chosen level is a marker, not a failure.
73+
CFBASE="-std=gnu11 -funsigned-char -D_GNU_SOURCE -DQUICKJS_NG_BUILD -DNDEBUG -I."
74+
ccg() { # $1 = src (no .c), out $1.o
75+
for opt in -O2 -O1 -O0; do
76+
if (ulimit -v 409600; /usr/bin/time cc $opt $CFBASE -c "$1.c" -o "$1.o"); then
77+
echo "s1-cc-$1-opt=$opt"
78+
return 0
79+
fi
80+
echo "s1-cc-$1-$opt-failed (retrying lower)"
81+
done
82+
echo "s1-cc-$1-FAILED-ALL"
83+
return 1
84+
}
85+
FAIL=0
86+
for f in quickjs libregexp libunicode dtoa quickjs-libc qjs qjsc; do
87+
ccg "$f" || FAIL=1
88+
done
89+
LIBOBJS="quickjs.o libregexp.o libunicode.o dtoa.o quickjs-libc.o"
90+
91+
# RUN-1 FINDING (2026-07-09): the tarball's gen/repl.c + gen/standalone.c
92+
# are qjsc bytecode PRE-GENERATED on upstream's little-endian 64-bit host;
93+
# JS_ReadObjectAtoms' host-order checksum can never match on BE, so
94+
# `qjs -c` (which JS_ReadObject's qjsc_standalone first) dies with
95+
# "SyntaxError: checksum error" (risk #3 biting the build inputs, not the
96+
# engine). Fix: link qjsc FIRST (qjsc.c has no gen/ dependency),
97+
# REGENERATE the gen files natively (= big-endian), then compile them and
98+
# link qjs. Regen commands are upstream's own (Makefile lines 77-78);
99+
# flow validated on the darwin host with the no-cmake objects.
100+
#
101+
# Link ladder (run-1: plain and -latomic both failed — NetBSD/sparc base
102+
# has no libatomic; shim WORKED): sparc32 (v8) __atomic_* are not
103+
# lock-free -> gcc emits libatomic calls. pthread-mutex shim preserves
104+
# semantics for these single-threaded gates.
105+
cat > atomic-shim.c <<'EOF'
106+
#include <pthread.h>
107+
#include <stdint.h>
108+
#include <stddef.h>
109+
static pthread_mutex_t L = PTHREAD_MUTEX_INITIALIZER;
110+
#define OPS(n, t) \
111+
t __atomic_load_##n(const volatile void *p, int mo){ t v; pthread_mutex_lock(&L); v = *(const volatile t*)p; pthread_mutex_unlock(&L); return v; } \
112+
void __atomic_store_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); *(volatile t*)p = v; pthread_mutex_unlock(&L); } \
113+
t __atomic_exchange_##n(volatile void *p, t v, int mo){ t o; pthread_mutex_lock(&L); o = *(volatile t*)p; *(volatile t*)p = v; pthread_mutex_unlock(&L); return o; } \
114+
_Bool __atomic_compare_exchange_##n(volatile void *p, void *e, t d, _Bool w, int s, int f){ _Bool r; pthread_mutex_lock(&L); t o = *(volatile t*)p; if (o == *(t*)e) { *(volatile t*)p = d; r = 1; } else { *(t*)e = o; r = 0; } pthread_mutex_unlock(&L); return r; } \
115+
t __atomic_fetch_add_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o + v; pthread_mutex_unlock(&L); return o; } \
116+
t __atomic_fetch_sub_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o - v; pthread_mutex_unlock(&L); return o; } \
117+
t __atomic_fetch_and_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o & v; pthread_mutex_unlock(&L); return o; } \
118+
t __atomic_fetch_or_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o | v; pthread_mutex_unlock(&L); return o; } \
119+
t __atomic_fetch_xor_##n(volatile void *p, t v, int mo){ pthread_mutex_lock(&L); t o = *(volatile t*)p; *(volatile t*)p = o ^ v; pthread_mutex_unlock(&L); return o; }
120+
OPS(1, uint8_t) OPS(2, uint16_t) OPS(4, uint32_t) OPS(8, uint64_t)
121+
_Bool __atomic_is_lock_free(size_t sz, const volatile void *p){ (void)sz; (void)p; return 0; }
122+
EOF
123+
cc -O2 -c atomic-shim.c -o atomic-shim.o; echo "s1-shim-cc-exit=$?"
124+
125+
linkbin() { # $1 = output, $2... = objects; ladder: plain -> -latomic -> shim
126+
out="$1"; shift
127+
if cc -o "$out" "$@" -lm -lpthread 2> "link-$out-1.err"; then
128+
echo "s1-link-$out-variant=plain"
129+
elif cc -o "$out" "$@" -lm -lpthread -latomic 2> "link-$out-2.err"; then
130+
echo "s1-link-$out-variant=latomic"
131+
elif cc -o "$out" "$@" atomic-shim.o -lm -lpthread 2> "link-$out-3.err"; then
132+
echo "s1-link-$out-variant=shim"
133+
else
134+
sed -n 1,10p "link-$out-1.err" "link-$out-3.err"
135+
echo "s1-link-$out-variant=none"
136+
return 1
137+
fi
138+
}
139+
140+
# 1. qjsc (no gen/ dependency)
141+
linkbin qjsc qjsc.o $LIBOBJS || FAIL=1
142+
# 2. regenerate the gen bytecode natively (big-endian)
143+
./qjsc -ss -o gen/repl.c -m repl.js; echo "s1-regen-repl-exit=$?"
144+
./qjsc -ss -o gen/standalone.c -m standalone.js; echo "s1-regen-standalone-exit=$?"
145+
head -5 gen/standalone.c | tail -2
146+
# 3. compile regenerated gen files, link qjs
147+
ccg gen/repl || FAIL=1
148+
ccg gen/standalone || FAIL=1
149+
QJSOBJS="qjs.o gen/repl.o gen/standalone.o"
150+
linkbin qjs $QJSOBJS $LIBOBJS || FAIL=1
151+
[ -x ./qjs ] || FAIL=1
152+
ls -l qjs qjsc
153+
echo "s1-build-qjs-exit=$FAIL"
154+
date
155+
Q="$W/qjs-src/qjs"
156+
cd "$W" || exit 1
157+
158+
echo "=== S3 (BE correctness micro-probes, bare qjs) ==="
159+
# 3.1 minimal life sign — if 32-bit-BE quickjs-ng is broken, fail here first
160+
(ulimit -t 900; "$Q" --eval 'print(1+1)' < /dev/null); echo "s3-1-print-exit=$?"
161+
# 3.2 endianness self-check: host order must be BIG; DataView explicit-endian
162+
(ulimit -t 900; "$Q" --eval 'const u8=new Uint8Array(new Uint16Array([1]).buffer);const b=new ArrayBuffer(4);const dv=new DataView(b);dv.setUint32(0,0x11223344,false);const v8=new Uint8Array(b);print("host-BE="+(u8[0]===0)+" dv-be-byte0=0x"+v8[0].toString(16)+" dv-le-read=0x"+dv.getUint32(0,true).toString(16));if(u8[0]!==0)throw new Error("host order not big-endian");if(v8[0]!==0x11)throw new Error("DataView BE write broken");if(dv.getUint32(0,true)!==0x44332211)throw new Error("DataView LE read broken")' < /dev/null); echo "s3-2-endian-exit=$?"
163+
# 3.3 regexp char classes + unicode (historically BE-buggy lre paths)
164+
(ulimit -t 900; "$Q" --eval 'if(!/[a-z]+/u.test("abc"))throw new Error("u-flag charclass");if(!/\p{L}/u.test("\u00e9"))throw new Error("p{L} u-flag");if("a1b2".replace(/[0-9]/g,"")!=="ab")throw new Error("global charclass");print("regexp-ok");try{print("vflag-informational="+/[\p{L}]/v.test("x"))}catch(e){print("vflag-informational-ERR "+e)}' < /dev/null); echo "s3-3-regexp-exit=$?"
165+
# 3.4 bytecode round-trip IN-GUEST (BE-produced, BE-consumed; js_exepath patch
166+
# is what makes the standalone actually run its payload on NetBSD)
167+
printf 'print("bc-ok",6*7);\n' > hello.js
168+
(ulimit -t 1800; "$Q" -c hello.js -o hello-exe && ./hello-exe < /dev/null); echo "s3-4-bytecode-exit=$?"
169+
# 3.5 dtoa / Date / JSON spot checks
170+
(ulimit -t 900; "$Q" --eval 'const s=(0.1+0.2).toString();print("sum="+s);if(s!=="0.30000000000000004")throw new Error("dtoa");const n=Date.now();print("now="+n);if(!(n>1600000000000&&n<9999999999999))throw new Error("Date.now");const j=JSON.parse("{\"a\":1e300}");print("j.a="+j.a);if(j.a!==1e300)throw new Error("json 1e300")' < /dev/null); echo "s3-5-dtoa-date-json-exit=$?"
171+
# 3.6 full capability probe (diff against gate2 darwin control)
172+
(ulimit -t 1800; "$Q" probe.js < /dev/null); echo "s3-6-probe-exit=$?"
173+
echo "s3-7-inventory=SKIPPED (inventory.cjs is node/tjs-only CommonJS; S2-prime scope)"
174+
175+
echo "=== S0 (RAM-fit measurements; /usr/bin/time -l, NetBSD units = KB) ==="
176+
date
177+
# control: interpreter floor
178+
(ulimit -t 900; /usr/bin/time -l "$Q" --eval '1+1' < /dev/null > /dev/null); echo "s0-control-exit=$?"
179+
# ladder rung (a): run-from-source peak on the 19MB bundle. Crash at first
180+
# missing Node API is EXPECTED evidence; OOM/timeout is a DATA POINT.
181+
(ulimit -t 5400; /usr/bin/time -l "$Q" cli.cjs < /dev/null > /dev/null); echo "s0-run-from-source-exit=$?"
182+
# ladder rung (b/c) producer: compile peak (bytecode-embedding standalone)
183+
(ulimit -t 5400; /usr/bin/time -l "$Q" -c cli.cjs -o /tmp/bundle-exe < /dev/null); echo "s0-compile-exit=$?"
184+
ls -l /tmp/bundle-exe 2>/dev/null
185+
# ladder rung (b/c) consumer: run-from-bytecode peak
186+
if [ -x /tmp/bundle-exe ]; then
187+
(ulimit -t 1800; /usr/bin/time -l /tmp/bundle-exe < /dev/null > /dev/null); echo "s0-run-from-bytecode-exit=$?"
188+
else
189+
echo "s0-run-from-bytecode-exit=SKIP (no /tmp/bundle-exe)"
190+
fi
191+
date
192+
echo "=== GUEST-DONE ==="

0 commit comments

Comments
 (0)