portal-rng: asm xoshiro256** + cross-impl tests + default seed=0
Completes ticket 0001 started in27f468c. All three impls now carry bit-identical xoshiro256**; portal state round-trips across process boundaries in every producer x consumer cell (Python <-> C <-> asm). asm impl: - 4 new builtins: random-seed!, random-int, random-state, random-state! - g_rng_state in BSS (4 x u64); rng_splitmix64_step, rng_seed, rng_next - Binary portal header bumped LUMBDAB1/48 -> LUMBDAB2/80; carries 32 bytes of rng state at offsets 40..64, reserved moved to 72 - No float support in asm, so (random) intentionally omitted there - _start seeds with 0 so the stream is deterministic from startup Python + C (supplements27f468c): - rng_seed(0) auto-invoked at module load / register_portal_builtins so (random) without explicit (random-seed!) returns a real value instead of the all-zero xoshiro fixed point Tests: - tests/functional.lsp: 7 new shared assertions (Python + C) - asm/test.sh: 5 new asm-local assertions (142 -> 147) - tests/portal-rng-save.lsp / portal-rng-load.lsp: portable S-expression portal that captures both state AND next-5 baseline so loader self- verifies without a separate harness - tests/portal-cross-test.sh: 9 new producer x consumer RNG cells; all 18 cells pass end-to-end Verified: seed=42, (random-int 1000000) draws 1..10 = 558742 543102 559009 124193 317476 750584 200754 814407 344958 929085 identical in Python, C, and asm. unmoad scan: zero new findings in added code.
This commit is contained in:
parent
27f468c5b7
commit
54c4c651bb
12 changed files with 383 additions and 13 deletions
BIN
asm/lumbda
BIN
asm/lumbda
Binary file not shown.
BIN
asm/lumbda-gc
BIN
asm/lumbda-gc
Binary file not shown.
BIN
asm/lumbda-gc.o
BIN
asm/lumbda-gc.o
Binary file not shown.
BIN
asm/lumbda.o
BIN
asm/lumbda.o
Binary file not shown.
275
asm/lumbda.s
275
asm/lumbda.s
|
|
@ -195,15 +195,19 @@
|
|||
.equ BI_HS_LIST, 107
|
||||
.equ BI_TCPSENDFILE, 108
|
||||
.equ BI_ISQRT, 109
|
||||
.equ BI_RANDOMSEED, 110
|
||||
.equ BI_RANDOMINT, 111
|
||||
.equ BI_RANDOMSTATE, 112
|
||||
.equ BI_RANDOMSTATESET, 113
|
||||
.ifdef GC_NAIVE
|
||||
.equ BI_GC_COLLECT, 110
|
||||
.equ BI_GC_STATS, 111
|
||||
.equ BI_WITH_ARENA, 112
|
||||
.equ BI_ARENA_STATS, 113
|
||||
.equ BI_ARENA_SET_MODE, 114
|
||||
.equ BI_COUNT, 115
|
||||
.equ BI_GC_COLLECT, 114
|
||||
.equ BI_GC_STATS, 115
|
||||
.equ BI_WITH_ARENA, 116
|
||||
.equ BI_ARENA_STATS, 117
|
||||
.equ BI_ARENA_SET_MODE, 118
|
||||
.equ BI_COUNT, 119
|
||||
.else
|
||||
.equ BI_COUNT, 110
|
||||
.equ BI_COUNT, 114
|
||||
.endif
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -301,6 +305,10 @@ bn_substr: .byte 9; .ascii "substring"
|
|||
bn_expt: .byte 4; .ascii "expt"
|
||||
bn_gcd: .byte 3; .ascii "gcd"
|
||||
bn_isqrt: .byte 5; .ascii "isqrt"
|
||||
bn_randomseed: .byte 12; .ascii "random-seed!"
|
||||
bn_randomint: .byte 10; .ascii "random-int"
|
||||
bn_randomstate: .byte 12; .ascii "random-state"
|
||||
bn_randomstateset: .byte 13; .ascii "random-state!"
|
||||
bn_integerp: .byte 8; .ascii "integer?"
|
||||
bn_portalsave: .byte 11; .ascii "portal-save"
|
||||
bn_portalresume:.byte 13; .ascii "portal-resume"
|
||||
|
|
@ -356,9 +364,13 @@ s_hashset: .ascii "#<hash-set>"
|
|||
err_ht_miss: .ascii "Error: hash-table-ref: missing key\n"
|
||||
.equ err_ht_miss_len, . - err_ht_miss
|
||||
|
||||
portal_magic: .ascii "LUMBDAB1"
|
||||
portal_magic: .ascii "LUMBDAB2"
|
||||
.equ PORTAL_MAGIC_LEN, 8
|
||||
.equ PORTAL_HDR_SIZE, 48 # magic(8) + heap_size(8) + heap_base(8) + r14(8) + r15(8) + reserved(8)
|
||||
# magic(8) + heap_size(8) + heap_base(8) + r14(8) + r15(8)
|
||||
# + rng_state[0..3] (32) + reserved(8) = 80 bytes
|
||||
# Bumped from LUMBDAB1/48 to LUMBDAB2/80 for xoshiro256** state capture.
|
||||
# See docs/tickets/0001-portal-rng.md.
|
||||
.equ PORTAL_HDR_SIZE, 80
|
||||
|
||||
# Builtin name table (pointers filled at init)
|
||||
.align 8
|
||||
|
|
@ -391,6 +403,7 @@ bi_names:
|
|||
.quad bn_hsmake, bn_hsp, bn_hsadd, bn_hshas, bn_hssize, bn_hslist
|
||||
.quad bn_tcpsendfile
|
||||
.quad bn_isqrt
|
||||
.quad bn_randomseed, bn_randomint, bn_randomstate, bn_randomstateset
|
||||
.ifdef GC_NAIVE
|
||||
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
|
||||
.endif
|
||||
|
|
@ -404,6 +417,10 @@ err_oom: .ascii "Error: out of memory\n"
|
|||
.equ err_oom_len, . - err_oom
|
||||
err_isqrt_neg: .ascii "Error: isqrt: negative argument\n"
|
||||
.equ err_isqrt_neg_len, . - err_isqrt_neg
|
||||
err_rng_bad_n: .ascii "Error: random-int: n must be positive\n"
|
||||
.equ err_rng_bad_n_len, . - err_rng_bad_n
|
||||
err_rng_short: .ascii "Error: random-state!: expected list of 8 integers\n"
|
||||
.equ err_rng_short_len, . - err_rng_short
|
||||
|
||||
# Print strings
|
||||
s_true: .ascii "#t"
|
||||
|
|
@ -463,6 +480,11 @@ num_buf: .skip 64
|
|||
sym_table: .skip 16384 # 2048 symbol pointers
|
||||
sym_count: .skip 8
|
||||
heap_base: .skip 8
|
||||
# xoshiro256** state — 4 × u64 words. Portal header at LUMBDAB2 layout
|
||||
# offset 40 carries these; save/resume copies to/from here.
|
||||
# See docs/tickets/0001-portal-rng.md.
|
||||
.align 8
|
||||
g_rng_state: .skip 32
|
||||
# Hash table for O(1) symbol interning (MOAD-0001 fix)
|
||||
# 1024 buckets, each a pointer to chain head (or 0 = empty)
|
||||
# Chain nodes: 16 bytes [sym_ptr, next_ptr], allocated from heap
|
||||
|
|
@ -602,6 +624,11 @@ _start:
|
|||
# Register builtins
|
||||
call init_builtins
|
||||
|
||||
# Default xoshiro256** seed = 0 so (random) is deterministic and
|
||||
# non-zero from process start. All three impls agree on this state.
|
||||
xorq %rdi, %rdi
|
||||
call rng_seed
|
||||
|
||||
# Set up a generous stack area (16 KB stack frame for deep recursion)
|
||||
# Actually the OS already gave us a stack, so we're fine.
|
||||
|
||||
|
|
@ -3362,6 +3389,14 @@ eval_list:
|
|||
je bi_gcd
|
||||
cmpq $BI_ISQRT, %rax
|
||||
je bi_isqrt
|
||||
cmpq $BI_RANDOMSEED, %rax
|
||||
je bi_random_seed_bang
|
||||
cmpq $BI_RANDOMINT, %rax
|
||||
je bi_random_int
|
||||
cmpq $BI_RANDOMSTATE, %rax
|
||||
je bi_random_state
|
||||
cmpq $BI_RANDOMSTATESET, %rax
|
||||
je bi_random_state_bang
|
||||
cmpq $BI_INTEGERP, %rax
|
||||
je bi_integerp
|
||||
cmpq $BI_PORTALSAVE, %rax
|
||||
|
|
@ -4722,6 +4757,202 @@ bi_isqrt:
|
|||
movq $1, %rdi
|
||||
syscall
|
||||
|
||||
# ============================================================
|
||||
# xoshiro256** — deterministic, portable PRNG shared with Python
|
||||
# and C impls. Reference: Blackman & Vigna 2018. Portal serializes
|
||||
# g_rng_state so simulations continue across processes with a
|
||||
# bit-identical random stream. See docs/tickets/0001-portal-rng.md.
|
||||
# ============================================================
|
||||
|
||||
# rng_splitmix64_step: %rdi = pointer to z (u64). Output in %rax.
|
||||
# Only increments *z by the constant; mixing happens on a local copy.
|
||||
rng_splitmix64_step:
|
||||
movabs $0x9e3779b97f4a7c15, %rax
|
||||
addq %rax, (%rdi)
|
||||
movq (%rdi), %rax
|
||||
movq %rax, %rcx
|
||||
shrq $30, %rcx
|
||||
xorq %rcx, %rax
|
||||
movabs $0xbf58476d1ce4e5b9, %rcx
|
||||
imulq %rcx, %rax
|
||||
movq %rax, %rcx
|
||||
shrq $27, %rcx
|
||||
xorq %rcx, %rax
|
||||
movabs $0x94d049bb133111eb, %rcx
|
||||
imulq %rcx, %rax
|
||||
movq %rax, %rcx
|
||||
shrq $31, %rcx
|
||||
xorq %rcx, %rax
|
||||
ret
|
||||
|
||||
# rng_seed: %rdi = seed (u64). Fills g_rng_state[0..3] via splitmix64.
|
||||
rng_seed:
|
||||
# Stack: [rsp]=z scratch, [rsp+8]=saved state base
|
||||
subq $16, %rsp
|
||||
movq %rdi, (%rsp) # z = seed
|
||||
leaq g_rng_state(%rip), %rax
|
||||
movq %rax, 8(%rsp)
|
||||
movq %rsp, %rdi
|
||||
call rng_splitmix64_step
|
||||
movq 8(%rsp), %rcx
|
||||
movq %rax, (%rcx)
|
||||
movq %rsp, %rdi
|
||||
call rng_splitmix64_step
|
||||
movq 8(%rsp), %rcx
|
||||
movq %rax, 8(%rcx)
|
||||
movq %rsp, %rdi
|
||||
call rng_splitmix64_step
|
||||
movq 8(%rsp), %rcx
|
||||
movq %rax, 16(%rcx)
|
||||
movq %rsp, %rdi
|
||||
call rng_splitmix64_step
|
||||
movq 8(%rsp), %rcx
|
||||
movq %rax, 24(%rcx)
|
||||
addq $16, %rsp
|
||||
ret
|
||||
|
||||
# rng_next: no args. Returns raw u64 in %rax. Mutates g_rng_state.
|
||||
rng_next:
|
||||
leaq g_rng_state(%rip), %rdi
|
||||
# result = rotl(s[1]*5, 7) * 9
|
||||
movq 8(%rdi), %rax
|
||||
leaq (%rax, %rax, 4), %rax # * 5
|
||||
rolq $7, %rax
|
||||
leaq (%rax, %rax, 8), %rax # * 9
|
||||
pushq %rax # save result
|
||||
# t = s[1] << 17
|
||||
movq 8(%rdi), %rcx
|
||||
shlq $17, %rcx
|
||||
# s[2] ^= s[0]
|
||||
movq (%rdi), %rax
|
||||
xorq %rax, 16(%rdi)
|
||||
# s[3] ^= s[1]
|
||||
movq 8(%rdi), %rax
|
||||
xorq %rax, 24(%rdi)
|
||||
# s[1] ^= s[2]
|
||||
movq 16(%rdi), %rax
|
||||
xorq %rax, 8(%rdi)
|
||||
# s[0] ^= s[3]
|
||||
movq 24(%rdi), %rax
|
||||
xorq %rax, (%rdi)
|
||||
# s[2] ^= t
|
||||
xorq %rcx, 16(%rdi)
|
||||
# s[3] = rotl(s[3], 45)
|
||||
movq 24(%rdi), %rax
|
||||
rolq $45, %rax
|
||||
movq %rax, 24(%rdi)
|
||||
popq %rax # result
|
||||
ret
|
||||
|
||||
# bi_random_seed_bang: (random-seed! k) -> void
|
||||
bi_random_seed_bang:
|
||||
GETARG %rax
|
||||
sarq $3, %rax # untag → raw int64
|
||||
movq %rax, %rdi
|
||||
call rng_seed
|
||||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
|
||||
# bi_random_int: (random-int n) -> int in [0, n)
|
||||
bi_random_int:
|
||||
GETARG %rax
|
||||
sarq $3, %rax # untag → n
|
||||
testq %rax, %rax
|
||||
jle .brni_bad
|
||||
movq %rax, %rbx # save n
|
||||
call rng_next # %rax = u64
|
||||
xorq %rdx, %rdx
|
||||
divq %rbx # %rdx = rax mod rbx (unsigned)
|
||||
movq %rdx, %rdi
|
||||
call make_int
|
||||
RET_VAL
|
||||
.brni_bad:
|
||||
movq $SYS_WRITE, %rax
|
||||
movq $2, %rdi
|
||||
leaq err_rng_bad_n(%rip), %rsi
|
||||
movq $err_rng_bad_n_len, %rdx
|
||||
syscall
|
||||
movq $SYS_EXIT, %rax
|
||||
movq $1, %rdi
|
||||
syscall
|
||||
|
||||
# bi_random_state: () -> (w0_lo w0_hi w1_lo w1_hi w2_lo w2_hi w3_lo w3_hi)
|
||||
# Builds list back-to-front from i=7 down to i=0.
|
||||
bi_random_state:
|
||||
movq $VAL_NIL, %rbx # list accumulator
|
||||
movq $7, %rbp # i
|
||||
.brs_loop:
|
||||
leaq g_rng_state(%rip), %rdx
|
||||
movq %rbp, %rax
|
||||
shrq $1, %rax # word index = i/2
|
||||
movq (%rdx, %rax, 8), %rax # word[i/2]
|
||||
testq $1, %rbp # i odd?
|
||||
jz .brs_lo
|
||||
shrq $32, %rax # hi half
|
||||
jmp .brs_have
|
||||
.brs_lo:
|
||||
movl %eax, %eax # lo half (zero-extend)
|
||||
.brs_have:
|
||||
movq %rax, %rdi
|
||||
call make_int
|
||||
movq %rax, %rdi # car
|
||||
movq %rbx, %rsi # cdr
|
||||
call make_pair
|
||||
movq %rax, %rbx
|
||||
testq %rbp, %rbp
|
||||
jz .brs_done
|
||||
decq %rbp
|
||||
jmp .brs_loop
|
||||
.brs_done:
|
||||
movq %rbx, %rax
|
||||
RET_VAL
|
||||
|
||||
# bi_random_state_bang: (random-state! (list 8 ints)) -> void
|
||||
bi_random_state_bang:
|
||||
GETARG %rbx # list cursor
|
||||
leaq g_rng_state(%rip), %rcx
|
||||
xorq %rsi, %rsi # i = 0
|
||||
.brsb_loop:
|
||||
cmpq $8, %rsi
|
||||
jge .brsb_done
|
||||
cmpq $VAL_NIL, %rbx
|
||||
je .brsb_short
|
||||
# car
|
||||
movq %rbx, %rax
|
||||
andq $-8, %rax
|
||||
movq (%rax), %rdi # car
|
||||
sarq $3, %rdi # untag
|
||||
movl %edi, %edi # mask to 32 bits
|
||||
# store into word[i/2]; i even → lo (overwrite); i odd → hi (OR in)
|
||||
movq %rsi, %rax
|
||||
shrq $1, %rax # word index
|
||||
testq $1, %rsi
|
||||
jz .brsb_lo
|
||||
shlq $32, %rdi # hi half
|
||||
orq %rdi, (%rcx, %rax, 8)
|
||||
jmp .brsb_next
|
||||
.brsb_lo:
|
||||
movq %rdi, (%rcx, %rax, 8) # lo half (overwrites previous contents)
|
||||
.brsb_next:
|
||||
# advance cursor
|
||||
movq %rbx, %rax
|
||||
andq $-8, %rax
|
||||
movq 8(%rax), %rbx # cdr
|
||||
incq %rsi
|
||||
jmp .brsb_loop
|
||||
.brsb_short:
|
||||
movq $SYS_WRITE, %rax
|
||||
movq $2, %rdi
|
||||
leaq err_rng_short(%rip), %rsi
|
||||
movq $err_rng_short_len, %rdx
|
||||
syscall
|
||||
movq $SYS_EXIT, %rax
|
||||
movq $1, %rdi
|
||||
syscall
|
||||
.brsb_done:
|
||||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
|
||||
bi_vector:
|
||||
# (vector e1 e2 ...) — build from remaining args in %r12
|
||||
# Count args
|
||||
|
|
@ -5010,8 +5241,19 @@ bi_portal_save:
|
|||
# r15 (bump pointer)
|
||||
movq %r15, 32(%rsp)
|
||||
|
||||
# Reserved
|
||||
movq $0, 40(%rsp)
|
||||
# xoshiro256** state: 4 × u64 at offsets 40, 48, 56, 64
|
||||
leaq g_rng_state(%rip), %rdi
|
||||
movq (%rdi), %rax
|
||||
movq %rax, 40(%rsp)
|
||||
movq 8(%rdi), %rax
|
||||
movq %rax, 48(%rsp)
|
||||
movq 16(%rdi), %rax
|
||||
movq %rax, 56(%rsp)
|
||||
movq 24(%rdi), %rax
|
||||
movq %rax, 64(%rsp)
|
||||
|
||||
# Reserved (offset 72, last 8 bytes)
|
||||
movq $0, 72(%rsp)
|
||||
|
||||
# Write header
|
||||
movq $SYS_WRITE, %rax
|
||||
|
|
@ -5316,6 +5558,17 @@ bi_portal_resume:
|
|||
movq 24(%rsp), %r14 # restore global env
|
||||
movq 32(%rsp), %r15 # restore bump pointer
|
||||
|
||||
# Restore xoshiro256** state from header offsets 40..64
|
||||
leaq g_rng_state(%rip), %rdi
|
||||
movq 40(%rsp), %rax
|
||||
movq %rax, (%rdi)
|
||||
movq 48(%rsp), %rax
|
||||
movq %rax, 8(%rdi)
|
||||
movq 56(%rsp), %rax
|
||||
movq %rax, 16(%rdi)
|
||||
movq 64(%rsp), %rax
|
||||
movq %rax, 24(%rdi)
|
||||
|
||||
addq $PORTAL_HDR_SIZE, %rsp
|
||||
|
||||
# Remap heap at the saved base address so pointers are valid
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@ check "isqrt-1" "(isqrt 1)" "1"
|
|||
check "isqrt-perfect" "(isqrt 144)" "12"
|
||||
check "isqrt-floor" "(isqrt 10)" "3"
|
||||
check "isqrt-big" "(isqrt 1000000000000)" "1000000"
|
||||
check "random-int-1" "(begin (random-seed! 42) (random-int 1000000))" "558742"
|
||||
check "random-int-2" "(begin (random-seed! 42) (random-int 1000000) (random-int 1000000))" "543102"
|
||||
check "random-reseed" "(begin (random-seed! 42) (random-int 1000000) (random-seed! 42) (random-int 1000000))" "558742"
|
||||
check "random-state-len" "(begin (random-seed! 42) (length (random-state)))" "8"
|
||||
check "random-state-restore" "(begin (random-seed! 42) (define s (random-state)) (random-int 1000000) (random-state! s) (random-int 1000000))" "558742"
|
||||
check "odd?" "(odd? 3)" "#t"
|
||||
check "odd?-even" "(odd? 4)" "#f"
|
||||
check "even?" "(even? 4)" "#t"
|
||||
|
|
|
|||
|
|
@ -809,4 +809,7 @@ void register_portal_builtins(Env *env) {
|
|||
env_define(env, intern("random-int"), VAL_BUILTIN(builtin_random_int));
|
||||
env_define(env, intern("random-state"), VAL_BUILTIN(builtin_random_state));
|
||||
env_define(env, intern("random-state!"), VAL_BUILTIN(builtin_random_state_bang));
|
||||
/* Default seed = 0 so (random) without (random-seed!) is deterministic
|
||||
* and non-zero. All three impls agree on this startup state. */
|
||||
rng_seed(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1969,7 +1969,7 @@ _MASK64 = (1 << 64) - 1
|
|||
_rng_state = [0, 0, 0, 0]
|
||||
|
||||
|
||||
def _rng_splitmix64_step(z):
|
||||
def _rng_splitmix64_step(z): # noqa: E501 forward decl — _rng_seed(0) runs below
|
||||
"""Returns (output, next_counter). Reference: Vigna splitmix64.
|
||||
The persistent counter advances only by the constant; the mixing
|
||||
is on a local copy."""
|
||||
|
|
@ -2026,6 +2026,11 @@ def _rng_state_from_halves(halves):
|
|||
_rng_state[i] = (hi << 32) | lo
|
||||
|
||||
|
||||
# Default seed = 0 at module load so (random) without (random-seed!) is
|
||||
# deterministic and non-zero. All three impls agree on this startup state.
|
||||
_rng_seed(0)
|
||||
|
||||
|
||||
###############################################################################
|
||||
# Portal — Serialize/resume full machine state across machines
|
||||
###############################################################################
|
||||
|
|
|
|||
|
|
@ -58,6 +58,30 @@
|
|||
(assert-equal "isqrt-just-above" (isqrt 101) 10)
|
||||
(assert-equal "isqrt-large" (isqrt 1000000000000) 1000000)
|
||||
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
;;; Random (xoshiro256**) — deterministic stream, portal-serializable
|
||||
;;; across Python + C + asm. See docs/tickets/0001-portal-rng.md.
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
|
||||
(random-seed! 42)
|
||||
(assert-equal "random-int-1" (random-int 1000000) 558742)
|
||||
(assert-equal "random-int-2" (random-int 1000000) 543102)
|
||||
(assert-equal "random-int-3" (random-int 1000000) 559009)
|
||||
|
||||
;; Re-seeding to same seed reproduces the stream.
|
||||
(random-seed! 42)
|
||||
(assert-equal "random-int-reseed-1" (random-int 1000000) 558742)
|
||||
(assert-equal "random-int-reseed-2" (random-int 1000000) 543102)
|
||||
|
||||
;; random-state / random-state! round-trips state exactly.
|
||||
(random-seed! 42)
|
||||
(define saved-state (random-state))
|
||||
(assert-equal "random-state-len" (length saved-state) 8)
|
||||
(random-int 1000000) ; advance
|
||||
(random-int 1000000) ; advance
|
||||
(random-state! saved-state)
|
||||
(assert-equal "random-state-restore" (random-int 1000000) 558742)
|
||||
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
;;; Comparison
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -67,7 +67,40 @@ rm -f "$SEXP" /tmp/cross.sexp
|
|||
|
||||
echo
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo "Cross-impl portal: $PASS passed, $FAIL failed"
|
||||
echo "RNG portal cross-impl (xoshiro256** state via S-expression)"
|
||||
echo "Proves: seed in one impl, resume in another, identical stream."
|
||||
echo "Ticket: docs/tickets/0001-portal-rng.md"
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
|
||||
RNG_SEXP=/tmp/cross-rng-xtest.sexp
|
||||
|
||||
for PRODUCER in "Python|$PY tests/portal-rng-save.lsp" \
|
||||
"C|$C tests/portal-rng-save.lsp" \
|
||||
"Asm|$ASM < tests/portal-rng-save.lsp"; do
|
||||
P_NAME="${PRODUCER%%|*}"
|
||||
P_CMD="${PRODUCER#*|}"
|
||||
echo
|
||||
echo "── producer: $P_NAME ──"
|
||||
rm -f "$RNG_SEXP" /tmp/cross-rng.sexp
|
||||
eval "$P_CMD" >/dev/null 2>&1
|
||||
cp /tmp/cross-rng.sexp "$RNG_SEXP"
|
||||
|
||||
for CONSUMER in "Python|$PY tests/portal-rng-load.lsp" \
|
||||
"C|$C tests/portal-rng-load.lsp" \
|
||||
"Asm|$ASM < tests/portal-rng-load.lsp"; do
|
||||
C_NAME="${CONSUMER%%|*}"
|
||||
C_CMD="${CONSUMER#*|}"
|
||||
cp "$RNG_SEXP" /tmp/cross-rng.sexp
|
||||
OUTPUT=$(eval "$C_CMD" 2>&1)
|
||||
verify "$P_NAME → $C_NAME" "$OUTPUT"
|
||||
done
|
||||
done
|
||||
|
||||
rm -f "$RNG_SEXP" /tmp/cross-rng.sexp
|
||||
|
||||
echo
|
||||
echo "═══════════════════════════════════════════════════════"
|
||||
echo "Cross-impl portal + RNG: $PASS passed, $FAIL failed"
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
17
tests/portal-rng-load.lsp
Normal file
17
tests/portal-rng-load.lsp
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
;;; portal-rng-load.lsp — resume xoshiro256** state from a file written
|
||||
;;; by another (or same) impl, draw 5 values, and compare against the
|
||||
;;; baseline captured by portal-rng-save.lsp.
|
||||
|
||||
(define expected-baseline '())
|
||||
(load "/tmp/cross-rng.sexp") ; sets rng state + binds expected-baseline
|
||||
|
||||
(define actual
|
||||
(list (random-int 1000000) (random-int 1000000) (random-int 1000000)
|
||||
(random-int 1000000) (random-int 1000000)))
|
||||
|
||||
(display "expected: ") (display expected-baseline) (newline)
|
||||
(display "actual: ") (display actual) (newline)
|
||||
|
||||
(if (equal? actual expected-baseline)
|
||||
(display "PASS: cross-impl RNG portal round-trip\n")
|
||||
(display "FAIL: cross-impl RNG portal round-trip\n"))
|
||||
30
tests/portal-rng-save.lsp
Normal file
30
tests/portal-rng-save.lsp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
;;; portal-rng-save.lsp — seed, advance, capture state AND next-5 baseline
|
||||
;;; to /tmp/cross-rng.sexp so another impl can resume and self-verify.
|
||||
;;;
|
||||
;;; Uses S-expression portal (portable across Python, C, asm) rather than
|
||||
;;; impl-specific JSON/binary format. Proves: (random-state) + (random-state!)
|
||||
;;; preserves xoshiro256** state across processes AND implementations.
|
||||
|
||||
(random-seed! 42)
|
||||
|
||||
;; Advance the stream by 5 draws.
|
||||
(define (advance n)
|
||||
(if (= n 0) 'done (begin (random-int 1000000) (advance (- n 1)))))
|
||||
(advance 5)
|
||||
|
||||
;; Snapshot state *before* drawing the 5 baseline values.
|
||||
(define rng-snapshot (random-state))
|
||||
|
||||
;; Compute next 5 draws — these are the baseline the loader must reproduce.
|
||||
(define baseline
|
||||
(list (random-int 1000000) (random-int 1000000) (random-int 1000000)
|
||||
(random-int 1000000) (random-int 1000000)))
|
||||
|
||||
;; Write a portable portal file. Any impl can (load) it.
|
||||
(define port (open-output-file "/tmp/cross-rng.sexp"))
|
||||
(display ";; lumbda cross-impl RNG portal (S-expression)\n" port)
|
||||
(display "(random-state! '" port) (write rng-snapshot port) (display ")\n" port)
|
||||
(display "(define expected-baseline '" port) (write baseline port) (display ")\n" port)
|
||||
(close-port port)
|
||||
|
||||
(display "saved baseline: ") (display baseline) (newline)
|
||||
Loading…
Add table
Add a link
Reference in a new issue