portal-rng: add (random-seed-from-os!) across all three tiers
Ticket 0002 — reads 8 bytes from /dev/urandom (little-endian u64) and seeds xoshiro256**. Opt-in kernel entropy for stochastic runs; the default stays deterministic (k=0 at startup), so ticket 0001's portal-reproducibility contract is unchanged. Real-world flow now one call away: Machine A: (random-seed-from-os!) + run simulation + portal-save Machine B: portal-resume — same stream, bit-for-bit All three impls fail loud on /dev/urandom trouble (LispErr in Python and C, stderr + exit(1) in asm) — no silent fallback to a weak seed. Tests: - tests/functional.lsp: 2 new shared asserts (entropic + replay) - asm/test.sh: 2 new asm-local checks (149 total, was 147) - make test-all green across Python (205), C (205), asm (149) Whitepaper §7.5 gains one sentence noting the OS-seed path. unmoad: zero new findings in added code.
This commit is contained in:
parent
e84c5eede3
commit
4960381c67
13 changed files with 306 additions and 81 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.
69
asm/lumbda.s
69
asm/lumbda.s
|
|
@ -199,15 +199,16 @@
|
|||
.equ BI_RANDOMINT, 111
|
||||
.equ BI_RANDOMSTATE, 112
|
||||
.equ BI_RANDOMSTATESET, 113
|
||||
.equ BI_RANDOMSEEDFROMOS, 114
|
||||
.ifdef GC_NAIVE
|
||||
.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
|
||||
.equ BI_GC_COLLECT, 115
|
||||
.equ BI_GC_STATS, 116
|
||||
.equ BI_WITH_ARENA, 117
|
||||
.equ BI_ARENA_STATS, 118
|
||||
.equ BI_ARENA_SET_MODE, 119
|
||||
.equ BI_COUNT, 120
|
||||
.else
|
||||
.equ BI_COUNT, 114
|
||||
.equ BI_COUNT, 115
|
||||
.endif
|
||||
|
||||
# ============================================================
|
||||
|
|
@ -309,6 +310,7 @@ 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_randomseedfromos: .byte 20; .ascii "random-seed-from-os!"
|
||||
bn_integerp: .byte 8; .ascii "integer?"
|
||||
bn_portalsave: .byte 11; .ascii "portal-save"
|
||||
bn_portalresume:.byte 13; .ascii "portal-resume"
|
||||
|
|
@ -404,6 +406,7 @@ bi_names:
|
|||
.quad bn_tcpsendfile
|
||||
.quad bn_isqrt
|
||||
.quad bn_randomseed, bn_randomint, bn_randomstate, bn_randomstateset
|
||||
.quad bn_randomseedfromos
|
||||
.ifdef GC_NAIVE
|
||||
.quad bn_gccollect, bn_gcstats, bn_witharena, bn_arenastats, bn_arenamode
|
||||
.endif
|
||||
|
|
@ -421,6 +424,9 @@ 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
|
||||
err_rng_urandom: .ascii "Error: random-seed-from-os!: /dev/urandom unavailable\n"
|
||||
.equ err_rng_urandom_len, . - err_rng_urandom
|
||||
s_dev_urandom: .asciz "/dev/urandom"
|
||||
|
||||
# Print strings
|
||||
s_true: .ascii "#t"
|
||||
|
|
@ -3397,6 +3403,8 @@ eval_list:
|
|||
je bi_random_state
|
||||
cmpq $BI_RANDOMSTATESET, %rax
|
||||
je bi_random_state_bang
|
||||
cmpq $BI_RANDOMSEEDFROMOS, %rax
|
||||
je bi_random_seed_from_os
|
||||
cmpq $BI_INTEGERP, %rax
|
||||
je bi_integerp
|
||||
cmpq $BI_PORTALSAVE, %rax
|
||||
|
|
@ -4953,6 +4961,53 @@ bi_random_state_bang:
|
|||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
|
||||
# bi_random_seed_from_os: (random-seed-from-os!) -> void
|
||||
# Reads 8 bytes from /dev/urandom, interprets as little-endian u64,
|
||||
# seeds xoshiro256**. Opt-in entropy for stochastic runs; determinism
|
||||
# remains the default. See docs/tickets/0002-os-entropy-seed.md.
|
||||
bi_random_seed_from_os:
|
||||
# open("/dev/urandom", O_RDONLY, 0)
|
||||
movq $SYS_OPEN, %rax
|
||||
leaq s_dev_urandom(%rip), %rdi
|
||||
movq $O_RDONLY, %rsi
|
||||
xorq %rdx, %rdx
|
||||
syscall
|
||||
testq %rax, %rax
|
||||
js .brsfo_fail
|
||||
movq %rax, %rbx # fd
|
||||
# read 8 bytes into stack slot
|
||||
subq $8, %rsp
|
||||
movq $SYS_READ, %rax
|
||||
movq %rbx, %rdi
|
||||
movq %rsp, %rsi
|
||||
movq $8, %rdx
|
||||
syscall
|
||||
cmpq $8, %rax
|
||||
jne .brsfo_short
|
||||
movq (%rsp), %rdi # seed = little-endian u64
|
||||
addq $8, %rsp
|
||||
pushq %rbx # save fd across rng_seed call
|
||||
call rng_seed
|
||||
popq %rbx
|
||||
# close(fd)
|
||||
movq $SYS_CLOSE, %rax
|
||||
movq %rbx, %rdi
|
||||
syscall
|
||||
movq $VAL_VOID, %rax
|
||||
RET_VAL
|
||||
.brsfo_short:
|
||||
addq $8, %rsp
|
||||
# fall through to fail with same error (urandom unavailable / short)
|
||||
.brsfo_fail:
|
||||
movq $SYS_WRITE, %rax
|
||||
movq $2, %rdi
|
||||
leaq err_rng_urandom(%rip), %rsi
|
||||
movq $err_rng_urandom_len, %rdx
|
||||
syscall
|
||||
movq $SYS_EXIT, %rax
|
||||
movq $1, %rdi
|
||||
syscall
|
||||
|
||||
bi_vector:
|
||||
# (vector e1 e2 ...) — build from remaining args in %r12
|
||||
# Count args
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@ check "random-int-2" "(begin (random-seed! 42) (random-int 1000000) (random-i
|
|||
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 "random-os-entropic" "(begin (random-seed-from-os!) (define a (random-state)) (random-seed-from-os!) (not (equal? a (random-state))))" "#t"
|
||||
check "random-os-replay" "(begin (random-seed-from-os!) (define s (random-state)) (define v (random-int 1000000)) (random-state! s) (= v (random-int 1000000)))" "#t"
|
||||
check "odd?" "(odd? 3)" "#t"
|
||||
check "odd?-even" "(odd? 4)" "#f"
|
||||
check "even?" "(even? 4)" "#t"
|
||||
|
|
|
|||
17
c/portal.c
17
c/portal.c
|
|
@ -5,6 +5,7 @@
|
|||
* Serializes environment bindings and full continuations.
|
||||
*/
|
||||
#include "lumbda.h"
|
||||
#include <fcntl.h>
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Portal checkpoint — thread-local signal for mid-execution save
|
||||
|
|
@ -801,10 +802,26 @@ static Value builtin_random_state_bang(Value *args, int nargs, Env *env) {
|
|||
return VAL_VOID;
|
||||
}
|
||||
|
||||
/* Opt-in kernel entropy seed. See docs/tickets/0002-os-entropy-seed.md. */
|
||||
static Value builtin_random_seed_from_os(Value *args, int nargs, Env *env) {
|
||||
(void)args; (void)env;
|
||||
if (nargs != 0) lisp_error("random-seed-from-os!: expected 0 args");
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd < 0) lisp_error("random-seed-from-os!: cannot open /dev/urandom");
|
||||
uint64_t k = 0;
|
||||
ssize_t n = read(fd, &k, sizeof k);
|
||||
close(fd);
|
||||
if (n != (ssize_t)sizeof k)
|
||||
lisp_error("random-seed-from-os!: short read from /dev/urandom");
|
||||
rng_seed(k);
|
||||
return VAL_VOID;
|
||||
}
|
||||
|
||||
void register_portal_builtins(Env *env) {
|
||||
env_define(env, intern("portal-checkpoint!"), VAL_BUILTIN(builtin_portal_checkpoint));
|
||||
env_define(env, intern("portal-save!"), VAL_BUILTIN(builtin_portal_checkpoint));
|
||||
env_define(env, intern("random-seed!"), VAL_BUILTIN(builtin_random_seed_bang));
|
||||
env_define(env, intern("random-seed-from-os!"), VAL_BUILTIN(builtin_random_seed_from_os));
|
||||
env_define(env, intern("random"), VAL_BUILTIN(builtin_random));
|
||||
env_define(env, intern("random-int"), VAL_BUILTIN(builtin_random_int));
|
||||
env_define(env, intern("random-state"), VAL_BUILTIN(builtin_random_state));
|
||||
|
|
|
|||
110
docs/tickets/0002-os-entropy-seed.md
Normal file
110
docs/tickets/0002-os-entropy-seed.md
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
# 0002 — `(random-seed-from-os!)` pulls kernel entropy
|
||||
|
||||
**Status:** open
|
||||
**Reporter:** fox (response to 0001 follow-up)
|
||||
**Implementer:** blackops
|
||||
**Opened:** 2026-04-20
|
||||
|
||||
## Problem
|
||||
|
||||
Ticket 0001 gave us xoshiro256\*\* with deterministic startup seed `k = 0`
|
||||
and explicit `(random-seed! k)`. Two processes running the same program
|
||||
produce the same stream. That is the point — it is what lets a portal
|
||||
round-trip reproduce state across machines.
|
||||
|
||||
But determinism has a dual cost: a program that wants a *different*
|
||||
stream per run must seed itself from outside. Without a kernel-entropy
|
||||
hook, users have to hand-roll one by opening `/dev/urandom` and folding
|
||||
bytes into an integer. That is defect-prone (wrong endianness, short
|
||||
read not handled, FD left open) and duplicates in every caller.
|
||||
|
||||
fox's note: *"a random seed from os on at least one side is very
|
||||
important"* — because the common real-world flow is:
|
||||
|
||||
1. Machine A seeds from kernel entropy (`random-seed-from-os!`).
|
||||
2. Machine A runs a stochastic simulation for a while.
|
||||
3. Machine A portal-saves — state captured, including xoshiro256\*\* state.
|
||||
4. Machine B portal-resumes — continues the *same* stream bit-for-bit.
|
||||
|
||||
Step 1 is missing today. This ticket adds it.
|
||||
|
||||
## Goals
|
||||
|
||||
1. One builtin, all three impls: `(random-seed-from-os!)`.
|
||||
2. Reads 8 bytes from `/dev/urandom`, interprets as little-endian `u64`,
|
||||
calls existing `rng_seed`. No new PRNG, just a different seed source.
|
||||
3. Returns void; user captures `(random-state)` if they want to replay.
|
||||
4. Fail-loud on I/O error (missing `/dev/urandom`, short read) — we do
|
||||
not silently fall back to a weak seed.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Blocking `/dev/random` (entropy-exhaustion semantics). `urandom` is the
|
||||
right default; `man 4 urandom` confirms it is cryptographically strong
|
||||
after boot.
|
||||
- `getrandom(2)` syscall. Portable to older kernels matters more than
|
||||
saving one open/read/close.
|
||||
- Any silent use of OS entropy on startup. The existing `k = 0` default
|
||||
stays; determinism remains the default, kernel entropy is opt-in.
|
||||
- `/dev/urandom` emulation on non-Linux. Lumbda targets Linux x86_64;
|
||||
other platforms earn their own ticket.
|
||||
|
||||
## API
|
||||
|
||||
```
|
||||
(random-seed-from-os!) → void
|
||||
```
|
||||
|
||||
Reads exactly 8 bytes from `/dev/urandom`, little-endian, passes the
|
||||
resulting `u64` to the same `rng_seed` path as `(random-seed!)`. After
|
||||
return, `(random-state)` reflects the new state, and the portal system
|
||||
captures it unchanged.
|
||||
|
||||
Errors (fail-loud):
|
||||
- `/dev/urandom` cannot be opened → error + exit(1) in asm; `LispErr`
|
||||
in Python + C.
|
||||
- Short read (fewer than 8 bytes) → same treatment.
|
||||
|
||||
## Implementation
|
||||
|
||||
Python (~3 lines):
|
||||
|
||||
```python
|
||||
def _rng_seed_from_os():
|
||||
with open('/dev/urandom', 'rb') as f:
|
||||
b = f.read(8)
|
||||
if len(b) != 8: raise LispErr('random-seed-from-os!: short read')
|
||||
_rng_seed(int.from_bytes(b, 'little', signed=False))
|
||||
```
|
||||
|
||||
C (~10 lines): `open` / `read` / `close` on `/dev/urandom`, then call
|
||||
existing `rng_seed(uint64_t)`.
|
||||
|
||||
Asm (~30 lines): `SYS_OPEN`, `SYS_READ`, `SYS_CLOSE` with an 8-byte
|
||||
stack slot, then `call rng_seed`. New data:
|
||||
`s_dev_urandom: .asciz "/dev/urandom"`. Error message follows the
|
||||
existing `err_rng_*` pattern.
|
||||
|
||||
## Test plan
|
||||
|
||||
1. **Unit** — `(random-seed-from-os!)` runs without error in each tier.
|
||||
2. **Entropic** — two consecutive `(random-seed-from-os!)` calls in
|
||||
the *same* process produce different streams (with overwhelming
|
||||
probability). Asserted via `(not (equal? state-a state-b))`.
|
||||
3. **Determinism preserved** — `(random-seed-from-os!)` + capture via
|
||||
`(random-state)` + reseed with `(random-state! saved)` produces the
|
||||
original stream. This proves the OS seed plugs cleanly into the
|
||||
portal path from 0001.
|
||||
|
||||
## Deliverables
|
||||
|
||||
- [ ] `docs/tickets/0002-os-entropy-seed.md` (this ticket)
|
||||
- [ ] Python: `lumbda.py` — helper + builtin binding
|
||||
- [ ] C: `c/portal.c` — helper + builtin + `register_portal_builtins`
|
||||
- [ ] Asm: `asm/lumbda.s` — new `BI_RANDOMSEEDFROMOS`, dispatch,
|
||||
`bi_random_seed_from_os`, data strings
|
||||
- [ ] `tests/functional.lsp` — 2 shared assertions
|
||||
- [ ] `asm/test.sh` — 2 asm-local checks
|
||||
- [ ] Whitepaper §7.5 — one sentence noting the OS-seed option
|
||||
- [ ] `make test-all` green
|
||||
- [ ] `unmoad` clean on changed files
|
||||
12
lumbda.py
12
lumbda.py
|
|
@ -2026,6 +2026,17 @@ def _rng_state_from_halves(halves):
|
|||
_rng_state[i] = (hi << 32) | lo
|
||||
|
||||
|
||||
def _rng_seed_from_os():
|
||||
"""Read 8 bytes from /dev/urandom and seed xoshiro256**. Opt-in entropy
|
||||
for stochastic runs; determinism remains the default (seed=0 at startup).
|
||||
See docs/tickets/0002-os-entropy-seed.md."""
|
||||
with open('/dev/urandom', 'rb') as f:
|
||||
b = f.read(8)
|
||||
if len(b) != 8:
|
||||
raise LispErr('random-seed-from-os!: short read from /dev/urandom')
|
||||
_rng_seed(int.from_bytes(b, 'little', signed=False))
|
||||
|
||||
|
||||
# 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)
|
||||
|
|
@ -3550,6 +3561,7 @@ def make_global_env():
|
|||
|
||||
# ── Random (xoshiro256**) — portal-serialized across all three impls ────
|
||||
d(S('random-seed!'), lambda a, _: _rng_seed(int(_num(a[0]))) or VOID)
|
||||
d(S('random-seed-from-os!'), lambda a, _: _rng_seed_from_os() or VOID)
|
||||
d(S('random'), lambda a, _: _rng_random_float())
|
||||
d(S('random-int'), lambda a, _: _rng_random_int(int(_num(a[0]))))
|
||||
d(S('random-state'), lambda a, _: _P(_rng_state_to_halves()))
|
||||
|
|
|
|||
|
|
@ -82,6 +82,20 @@
|
|||
(random-state! saved-state)
|
||||
(assert-equal "random-state-restore" (random-int 1000000) 558742)
|
||||
|
||||
;; random-seed-from-os! pulls kernel entropy; two calls differ w/ overwhelming
|
||||
;; probability. Ticket: docs/tickets/0002-os-entropy-seed.md.
|
||||
(random-seed-from-os!)
|
||||
(define os-state-a (random-state))
|
||||
(random-seed-from-os!)
|
||||
(define os-state-b (random-state))
|
||||
(assert-true "random-seed-from-os-entropic" (not (equal? os-state-a os-state-b)))
|
||||
;; Capture via (random-state) still works after OS seed — ties 0002 to 0001.
|
||||
(random-seed-from-os!)
|
||||
(define os-snapshot (random-state))
|
||||
(define os-next1 (random-int 1000000))
|
||||
(random-state! os-snapshot)
|
||||
(assert-equal "random-seed-from-os-replay" (random-int 1000000) os-next1)
|
||||
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
;;; Comparison
|
||||
;;; ═══════════════════════════════════════════════════════════════
|
||||
|
|
|
|||
|
|
@ -1833,6 +1833,7 @@ bi_portal_resume:
|
|||
<li><p><strong>S-expression portal:</strong> one line — <span class="docutils literal"><span class="pre">(random-state!</span> '(w0_lo w0_hi …))</span> — which every tier already evaluates. No format change. The language really is the wire format.</p></li>
|
||||
</ul>
|
||||
<p>Builtins shared across all three: <span class="docutils literal"><span class="pre">(random-seed!</span> k)</span>, <span class="docutils literal"><span class="pre">(random-int</span> n)</span>, <span class="docutils literal"><span class="pre">(random-state)</span></span>, <span class="docutils literal"><span class="pre">(random-state!</span> lst)</span>. Python and C also export <span class="docutils literal">(random)</span> returning a float in <span class="docutils literal">[0, 1)</span>; the asm tier has no floats, so this one builtin is omitted by construction. All three tiers seed with <span class="docutils literal">k = 0</span> at process startup, so a program that never calls <span class="docutils literal"><span class="pre">random-seed!</span></span> still gets a non-zero, deterministic stream.</p>
|
||||
<p>A fifth shared builtin — <span class="docutils literal"><span class="pre">(random-seed-from-os!)</span></span> — reads 8 bytes from <span class="docutils literal">/dev/urandom</span> and seeds xoshiro256** with them, so the typical real-world flow <em>(Machine A seeds from kernel entropy → runs simulation → portal-saves → Machine B resumes the same stream bit-for-bit)</em> is one call away. Determinism stays the default; kernel entropy is opt-in and fails loud if <span class="docutils literal">/dev/urandom</span> is unavailable. Ticket: <span class="docutils literal"><span class="pre">docs/tickets/0002-os-entropy-seed.md</span></span>.</p>
|
||||
<p>Verification: <span class="docutils literal"><span class="pre">tests/portal-cross-test.sh</span></span> runs a 3×3 producer × consumer matrix where each tier writes a portal file capturing the RNG state, every other tier reads it, draws the next 5 values, and the values must match bit-for-bit against a baseline the producer also wrote into the file. All 9 cells green. The baseline for seed=42 is <span class="docutils literal">558742 543102 559009 124193 317476 …</span> — identical in Python, C, and asm, identical after any portal round-trip through any format.</p>
|
||||
<p><strong>Ticket:</strong> <span class="docutils literal"><span class="pre">docs/tickets/0001-portal-rng.md</span></span>. Contributed by <strong>Zoë Trout</strong>, whose question — <em>does the portal tech keep the random list seed which will allow transferring random entropy between processes when continuing a simulation?</em> — surfaced a gap we had not noticed. The earlier claim <em>portal = portable machine state</em> carried a silent asterisk: <em>deterministic code only</em>. That asterisk is now gone.</p>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -622,7 +622,7 @@ endobj
|
|||
endobj
|
||||
73 0 obj
|
||||
<<
|
||||
/Author () /CreationDate (D:20260420112032-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260420112032-04'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author () /CreationDate (D:20260420155007-04'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260420155007-04'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (lumbda.) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
|
|
@ -805,12 +805,12 @@ endobj
|
|||
endobj
|
||||
108 0 obj
|
||||
<<
|
||||
/Dest [ 42 0 R /XYZ 57.02362 765.0236 0 ] /Next 109 0 R /Parent 101 0 R /Prev 107 0 R /Title (7.6 Cross-Process Benchmarks)
|
||||
/Dest [ 42 0 R /XYZ 57.02362 723.0236 0 ] /Next 109 0 R /Parent 101 0 R /Prev 107 0 R /Title (7.6 Cross-Process Benchmarks)
|
||||
>>
|
||||
endobj
|
||||
109 0 obj
|
||||
<<
|
||||
/Dest [ 42 0 R /XYZ 57.02362 443.8236 0 ] /Next 110 0 R /Parent 101 0 R /Prev 108 0 R /Title (7.7 Mismatch Cases: Graceful Degradation)
|
||||
/Dest [ 42 0 R /XYZ 57.02362 401.8236 0 ] /Next 110 0 R /Parent 101 0 R /Prev 108 0 R /Title (7.7 Mismatch Cases: Graceful Degradation)
|
||||
>>
|
||||
endobj
|
||||
110 0 obj
|
||||
|
|
@ -7147,7 +7147,7 @@ endstream
|
|||
endobj
|
||||
156 0 obj
|
||||
<<
|
||||
/Length 8366
|
||||
/Length 8989
|
||||
>>
|
||||
stream
|
||||
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
|
||||
|
|
@ -7278,13 +7278,19 @@ Q
|
|||
q
|
||||
1 0 0 1 57.02362 179.8236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 50 Tm 1.829104 Tw 12 TL /F1 10 Tf 0 0 0 rg (A fifth shared builtin \227 ) Tj /F5 10 Tf (\(random-seed-from-os!\)) Tj /F1 10 Tf ( \227 reads 8 bytes from ) Tj /F5 10 Tf (/dev/urandom) Tj /F1 10 Tf ( and seeds) Tj T* 0 Tw 2.747223 Tw (xoshiro256** with them, so the typical real-world flow ) Tj /F4 10 Tf (\(Machine A seeds from kernel entropy ) Tj /F6 10 Tf 12 TL (\256) Tj /F4 10 Tf 12 TL ( runs) Tj T* 0 Tw .313223 Tw (simulation ) Tj /F6 10 Tf 12 TL (\256) Tj /F4 10 Tf 12 TL ( portal-saves ) Tj /F6 10 Tf 12 TL (\256) Tj /F4 10 Tf 12 TL ( Machine B resumes the same stream bit-for-bit\)) Tj /F1 10 Tf ( is one call away. Determinism) Tj T* 0 Tw 4.219168 Tw (stays the default; kernel entropy is opt-in and fails loud if ) Tj /F5 10 Tf (/dev/urandom) Tj /F1 10 Tf ( is unavailable. Ticket:) Tj T* 0 Tw /F5 10 Tf (docs/tickets/0002-os-entropy-seed.md) Tj /F1 10 Tf (.) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 113.8236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 50 Tm 1.928941 Tw 12 TL /F1 10 Tf 0 0 0 rg (Verification: ) Tj /F5 10 Tf (tests/portal-cross-test.sh) Tj /F1 10 Tf ( runs a 3\3273 producer \327 consumer matrix where each tier) Tj T* 0 Tw .217417 Tw (writes a portal file capturing the RNG state, every other tier reads it, draws the next 5 values, and the values) Tj T* 0 Tw .365464 Tw (must match bit-for-bit against a baseline the producer also wrote into the file. All 9 cells green. The baseline) Tj T* 0 Tw .232397 Tw (for seed=42 is ) Tj /F5 10 Tf (558742) Tj ( ) Tj (543102) Tj ( ) Tj (559009) Tj ( ) Tj (124193) Tj ( ) Tj (317476) Tj ( ) Tj (\205) Tj /F1 10 Tf ( \227 identical in Python, C, and asm, identical) Tj T* 0 Tw (after any portal round-trip through any format.) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 125.8236 cm
|
||||
1 0 0 1 57.02362 83.82362 cm
|
||||
q
|
||||
BT 1 0 0 1 0 38 Tm 1.177835 Tw 12 TL /F3 10 Tf 0 0 0 rg (Ticket:) Tj /F1 10 Tf ( ) Tj /F5 10 Tf (docs/tickets/0001-portal-rng.md) Tj /F1 10 Tf (. Contributed by ) Tj /F3 10 Tf (Zo\353 Trout) Tj /F1 10 Tf (, whose question \227 ) Tj /F4 10 Tf (does the) Tj T* 0 Tw .328556 Tw (portal tech keep the random list seed which will allow transferring random entropy between processes when) Tj T* 0 Tw .598726 Tw (continuing a simulation?) Tj /F1 10 Tf ( \227 surfaced a gap we had not noticed. The earlier claim ) Tj /F4 10 Tf (portal = portable machine) Tj T* 0 Tw (state) Tj /F1 10 Tf ( carried a silent asterisk: ) Tj /F4 10 Tf (deterministic code only) Tj /F1 10 Tf (. That asterisk is now gone.) Tj T* ET
|
||||
BT 1 0 0 1 0 14 Tm 1.177835 Tw 12 TL /F3 10 Tf 0 0 0 rg (Ticket:) Tj /F1 10 Tf ( ) Tj /F5 10 Tf (docs/tickets/0001-portal-rng.md) Tj /F1 10 Tf (. Contributed by ) Tj /F3 10 Tf (Zo\353 Trout) Tj /F1 10 Tf (, whose question \227 ) Tj /F4 10 Tf (does the) Tj T* 0 Tw .328556 Tw (portal tech keep the random list seed which will allow transferring random entropy between processes when) Tj T* 0 Tw ET
|
||||
Q
|
||||
Q
|
||||
|
||||
|
|
@ -7292,27 +7298,33 @@ endstream
|
|||
endobj
|
||||
157 0 obj
|
||||
<<
|
||||
/Length 9926
|
||||
/Length 10316
|
||||
>>
|
||||
stream
|
||||
1 0 0 1 0 0 cm BT /F1 12 Tf 14.4 TL ET
|
||||
q
|
||||
1 0 0 1 57.02362 751.8236 cm
|
||||
1 0 0 1 57.02362 741.0236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 14 Tm .598726 Tw 12 TL /F4 10 Tf 0 0 0 rg (continuing a simulation?) Tj /F1 10 Tf ( \227 surfaced a gap we had not noticed. The earlier claim ) Tj /F4 10 Tf (portal = portable machine) Tj T* 0 Tw (state) Tj /F1 10 Tf ( carried a silent asterisk: ) Tj /F4 10 Tf (deterministic code only) Tj /F1 10 Tf (. That asterisk is now gone.) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 709.8236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 2.2 Tm 13.2 TL /F2 11 Tf .133333 .133333 .133333 rg (7.6 Cross-Process Benchmarks) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 707.8236 cm
|
||||
1 0 0 1 57.02362 665.8236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 26 Tm 2.703647 Tw 12 TL /F1 10 Tf 0 0 0 rg (Producer process A saves state to a file; consumer process B starts fresh, loads the file, continues.) Tj T* 0 Tw 10.67759 Tw (Wall-clock time for both processes end-to-end, 50 iterations, same-laptop. ) Tj /F3 10 Tf (Reproduce:) Tj /F1 10 Tf T* 0 Tw /F5 10 Tf (make) Tj ( ) Tj (bench-portal) Tj /F1 10 Tf ( \(source: ) Tj /F5 10 Tf (tests/portal-benchmark.sh) Tj /F1 10 Tf (\).) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 701.8236 cm
|
||||
1 0 0 1 57.02362 659.8236 cm
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 503.8236 cm
|
||||
1 0 0 1 57.02362 461.8236 cm
|
||||
q
|
||||
1 1 1 rg
|
||||
n 0 198 481.2283 -18 re f*
|
||||
|
|
@ -7518,32 +7530,32 @@ Q
|
|||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 503.8236 cm
|
||||
1 0 0 1 57.02362 461.8236 cm
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 461.8236 cm
|
||||
1 0 0 1 57.02362 419.8236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 26 Tm .698334 Tw 12 TL /F1 10 Tf 0 0 0 rg (The asm) Tj /F6 10 Tf 12 TL (\256) Tj /F1 10 Tf 12 TL (asm cross-process is ~160\327 faster than Python) Tj /F6 10 Tf 12 TL (\256) Tj /F1 10 Tf 12 TL (Python. The binary & S-expression portals are) Tj T* 0 Tw .812256 Tw (within 10% of each other on this workload \227 the bottleneck is process startup, not serialization. For larger) Tj T* 0 Tw (heaps the binary format pulls further ahead; for portability, S-expression always wins.) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 430.6236 cm
|
||||
1 0 0 1 57.02362 388.6236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 2.2 Tm 13.2 TL /F2 11 Tf .133333 .133333 .133333 rg (7.7 Mismatch Cases: Graceful Degradation) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 410.6236 cm
|
||||
1 0 0 1 57.02362 368.6236 cm
|
||||
q
|
||||
0 0 0 rg
|
||||
BT 1 0 0 1 0 2 Tm /F1 10 Tf 12 TL (A usable persistence layer fails well. What happens when the consumer meets unexpected input?) Tj T* ET
|
||||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 404.6236 cm
|
||||
1 0 0 1 57.02362 362.6236 cm
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 254.6236 cm
|
||||
1 0 0 1 57.02362 212.6236 cm
|
||||
q
|
||||
1 1 1 rg
|
||||
n 0 150 481.2283 -18 re f*
|
||||
|
|
@ -7776,10 +7788,10 @@ Q
|
|||
Q
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 254.6236 cm
|
||||
1 0 0 1 57.02362 212.6236 cm
|
||||
Q
|
||||
q
|
||||
1 0 0 1 57.02362 188.6236 cm
|
||||
1 0 0 1 57.02362 146.6236 cm
|
||||
q
|
||||
BT 1 0 0 1 0 50 Tm 1.476556 Tw 12 TL /F1 10 Tf 0 0 0 rg (All defects surfaced & fixed during benchmark development: an asm segfault on ) Tj /F5 10 Tf (\(define) Tj ( ) Tj (x\)) Tj /F1 10 Tf ( without a) Tj T* 0 Tw 1.838453 Tw (value \(now binds to ) Tj /F5 10 Tf (VOID) Tj /F1 10 Tf (\), an asm portal-resume that accepted short headers \(now verifies ) Tj /F5 10 Tf (sys_read) Tj /F1 10 Tf T* 0 Tw 1.204272 Tw (returned a full 48 bytes & sanity-checks heap metadata\), a Python ) Tj /F5 10 Tf (file) Tj ( ) Tj (not) Tj ( ) Tj (found) Tj /F1 10 Tf ( error reporting the) Tj T* 0 Tw 1.304862 Tw (outer script path instead of the inner missing file \(now uses ) Tj /F5 10 Tf (FileNotFoundError.filename) Tj /F1 10 Tf (\). Graceful) Tj T* 0 Tw (degradation is not free; it is tested.) Tj T* ET
|
||||
Q
|
||||
|
|
@ -12724,63 +12736,63 @@ xref
|
|||
0002525313 00000 n
|
||||
0002536448 00000 n
|
||||
0002543919 00000 n
|
||||
0002552338 00000 n
|
||||
0002562317 00000 n
|
||||
0002567970 00000 n
|
||||
0002578510 00000 n
|
||||
0002588031 00000 n
|
||||
0002597336 00000 n
|
||||
0002599378 00000 n
|
||||
0002612868 00000 n
|
||||
0002628356 00000 n
|
||||
0002638138 00000 n
|
||||
0002647160 00000 n
|
||||
0002654073 00000 n
|
||||
0002663708 00000 n
|
||||
0002671841 00000 n
|
||||
0002679929 00000 n
|
||||
0002687227 00000 n
|
||||
0002689430 00000 n
|
||||
0002691714 00000 n
|
||||
0002692140 00000 n
|
||||
0002692175 00000 n
|
||||
0002692210 00000 n
|
||||
0002692245 00000 n
|
||||
0002692280 00000 n
|
||||
0002692315 00000 n
|
||||
0002692350 00000 n
|
||||
0002692385 00000 n
|
||||
0002692420 00000 n
|
||||
0002692455 00000 n
|
||||
0002692491 00000 n
|
||||
0002692527 00000 n
|
||||
0002692563 00000 n
|
||||
0002692599 00000 n
|
||||
0002692635 00000 n
|
||||
0002692671 00000 n
|
||||
0002692707 00000 n
|
||||
0002692743 00000 n
|
||||
0002692779 00000 n
|
||||
0002692815 00000 n
|
||||
0002692851 00000 n
|
||||
0002692887 00000 n
|
||||
0002692923 00000 n
|
||||
0002692959 00000 n
|
||||
0002692995 00000 n
|
||||
0002693031 00000 n
|
||||
0002693067 00000 n
|
||||
0002693103 00000 n
|
||||
0002693139 00000 n
|
||||
0002693175 00000 n
|
||||
0002693211 00000 n
|
||||
0002693247 00000 n
|
||||
0002693283 00000 n
|
||||
0002693319 00000 n
|
||||
0002693355 00000 n
|
||||
0002552961 00000 n
|
||||
0002563331 00000 n
|
||||
0002568984 00000 n
|
||||
0002579524 00000 n
|
||||
0002589045 00000 n
|
||||
0002598350 00000 n
|
||||
0002600392 00000 n
|
||||
0002613882 00000 n
|
||||
0002629370 00000 n
|
||||
0002639152 00000 n
|
||||
0002648174 00000 n
|
||||
0002655087 00000 n
|
||||
0002664722 00000 n
|
||||
0002672855 00000 n
|
||||
0002680943 00000 n
|
||||
0002688241 00000 n
|
||||
0002690444 00000 n
|
||||
0002692728 00000 n
|
||||
0002693154 00000 n
|
||||
0002693189 00000 n
|
||||
0002693224 00000 n
|
||||
0002693259 00000 n
|
||||
0002693294 00000 n
|
||||
0002693329 00000 n
|
||||
0002693364 00000 n
|
||||
0002693399 00000 n
|
||||
0002693434 00000 n
|
||||
0002693469 00000 n
|
||||
0002693505 00000 n
|
||||
0002693541 00000 n
|
||||
0002693577 00000 n
|
||||
0002693613 00000 n
|
||||
0002693649 00000 n
|
||||
0002693685 00000 n
|
||||
0002693721 00000 n
|
||||
0002693757 00000 n
|
||||
0002693793 00000 n
|
||||
0002693829 00000 n
|
||||
0002693865 00000 n
|
||||
0002693901 00000 n
|
||||
0002693937 00000 n
|
||||
0002693973 00000 n
|
||||
0002694009 00000 n
|
||||
0002694045 00000 n
|
||||
0002694081 00000 n
|
||||
0002694117 00000 n
|
||||
0002694153 00000 n
|
||||
0002694189 00000 n
|
||||
0002694225 00000 n
|
||||
0002694261 00000 n
|
||||
0002694297 00000 n
|
||||
0002694333 00000 n
|
||||
0002694369 00000 n
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<0317565ca3c256fb6011c65bb1ae607c><0317565ca3c256fb6011c65bb1ae607c>]
|
||||
[<fcb62d2a2e02cd4b2f0741a9f988a5e3><fcb62d2a2e02cd4b2f0741a9f988a5e3>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 73 0 R
|
||||
|
|
@ -12788,5 +12800,5 @@ trailer
|
|||
/Size 210
|
||||
>>
|
||||
startxref
|
||||
2693391
|
||||
2694405
|
||||
%%EOF
|
||||
|
|
|
|||
|
|
@ -805,6 +805,8 @@ Lumbda addresses this across all three tiers with one shared PRNG: **xoshiro256\
|
|||
|
||||
Builtins shared across all three: ``(random-seed! k)``, ``(random-int n)``, ``(random-state)``, ``(random-state! lst)``. Python and C also export ``(random)`` returning a float in ``[0, 1)``; the asm tier has no floats, so this one builtin is omitted by construction. All three tiers seed with ``k = 0`` at process startup, so a program that never calls ``random-seed!`` still gets a non-zero, deterministic stream.
|
||||
|
||||
A fifth shared builtin — ``(random-seed-from-os!)`` — reads 8 bytes from ``/dev/urandom`` and seeds xoshiro256\*\* with them, so the typical real-world flow *(Machine A seeds from kernel entropy → runs simulation → portal-saves → Machine B resumes the same stream bit-for-bit)* is one call away. Determinism stays the default; kernel entropy is opt-in and fails loud if ``/dev/urandom`` is unavailable. Ticket: ``docs/tickets/0002-os-entropy-seed.md``.
|
||||
|
||||
Verification: ``tests/portal-cross-test.sh`` runs a 3×3 producer × consumer matrix where each tier writes a portal file capturing the RNG state, every other tier reads it, draws the next 5 values, and the values must match bit-for-bit against a baseline the producer also wrote into the file. All 9 cells green. The baseline for seed=42 is ``558742 543102 559009 124193 317476 …`` — identical in Python, C, and asm, identical after any portal round-trip through any format.
|
||||
|
||||
**Ticket:** ``docs/tickets/0001-portal-rng.md``. Contributed by **Zoë Trout**, whose question — *does the portal tech keep the random list seed which will allow transferring random entropy between processes when continuing a simulation?* — surfaced a gap we had not noticed. The earlier claim *portal = portable machine state* carried a silent asterisk: *deterministic code only*. That asterisk is now gone.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue