← board

Random library — HW/OS/software tiered RNG (cross-target capability test)

Goal

A Random unit that gives good random numbers with no per-platform code from the caller: hardware RNG by default when available, OS CSPRNG otherwise, software PRNG as the fallback — and the deterministic software PRNG whenever the user seeds. Modern 256-bit-state / 64-bit-output internals, not a legacy 16/32-bit LCG. FPC-compatible surface so existing code (and the Lazarus line) is unaffected.

Three-tier entropy source (chosen once at unit init)

Tier Source Selected when
1 — HW instruction x86 RDRAND/RDSEED; aarch64 RNDR (FEAT_RNG); ESP RNG register / esp_random capability probe says present
2 — OS CSPRNG getrandom(2) syscall (Linux, kernel-ABI — fits the no-libc design); /dev/urandom fallback hosted, no usable HW instruction
3 — Software PRNG seeded from the best available tier above fallback, and forced whenever the user seeds

The init probe selects a backend and stores it in a proc-typed var (indirect dispatch — exercises procedural types); the initialization section runs the probe once.

Seed forces software (key rule)

Hardware RNG is not reproducible or seedable. Therefore:

PRNG choice

xoshiro256++ (256-bit state, 64-bit output) — modern, fast, well-tested. (PCG64 acceptable alternative; decide in design.) No legacy LCG. The seeded software stream is identical across all targets → the cross-target oracle. Seed expansion via SplitMix64 from the user seed.

API surface

FPC-compatible (keep the Lazarus line):

PXX extensions (on top):

Per-target capability detail / landmines

Testing strategy

Design mandate: ONE elegant library file (arch hidden in the compiler)

Hard requirement: the unit is a single elegant .pas file, no per-arch {$ifdef} soup. The per-target instruction mess (RDRAND opcodes, CPUID, RNDR MRS, ESP RNG-reg address) lives in builtinheap compiler intrinsics, not in the library. The library then reads clean:

unit Random;
// 1. software PRNG core — pure Pascal (xoshiro256++ + SplitMix64). The bulk.
// 2. thin source dispatch (one-liners over intrinsics / syscall):
function HWRand64(out v: UInt64): Boolean;  // = __rdrand intrinsic; compiler emits per target
function OSRand64: UInt64;                  // = getrandom syscall
// software path = the PRNG core

__rdrand/__cpuid/__rndr resolve differently per target inside the compiler; the .pas never sees an arch branch. (More elegant than FPC, whose System-unit random is scattered per platform.)

Compiler switch: preference, NOT a replacement for the safety fallback

A switch picks the default UNSEEDED source — e.g. {$RNG AUTO|HARDWARE| SOFTWARE} (or --rng=):

Critical safety rule: on portable (multi-CPU) binaries the switch must NOT make HW purely compile-time — a HARDWARE binary on a CPU without RDRAND would hit an illegal instruction. So in portable builds, HARDWARE still keeps the runtime probe + software fallback. The switch tunes preference; it does not delete the safety net.

Exception — fixed platforms (ESP32): the HW RNG is known to exist, so the switch MAY hard-select HW and elide the probe + software path entirely → smaller code. This is the embedded win: one library source, but on a known target it compiles down to just the HW read.

Orthogonal: the seed rule (RandSeed/RandomSeed → software) is runtime state, independent of the switch. The switch sets the unseeded default source; seeding always flips to the deterministic PRNG. No conflict.

Dependency: HW-instruction emission (and how to avoid blocking on it)

Tiers 1 (HW) need a few fixed CPU instructions emitted (CPUID, RDRAND, MRS RNDR, ESP RNG-reg / MMIO read). Tiers 2 (getrandom) and 3 (software) need no asm at all — so slices 1–3 ship a useful library before any asm work. Only slices 4–6 touch instructions.

Two ways to satisfy the HW tier; prefer the second:

  1. General inline asm (feature-inline-asm-depth) — currently x86-64-only + rudimentary. Heavy: needs a per-target asm frontend to mature. Over-kill for ~4 fixed sequences.
  2. Dedicated compiler intrinsics in builtinheap (RECOMMENDED) — emit the fixed sequences directly via EmitB as builtins (__rdrand, __cpuid, __rndr, raw MMIO read), exactly like the existing __pxxrawsyscall. No asm-frontend dependency; reuses a proven mechanism; scoped to the handful of ops the lib needs.

So this ticket is not hard-blocked on feature-inline-asm-depth: slices 1–3 are asm-free, and slices 4–6 use intrinsics (option 2). Full inline-asm-depth is only needed if a user writes arbitrary asm — out of scope here.

Slices

  1. Software PRNG core — xoshiro256++ + SplitMix64 seed; deterministic; cross-target byte-identical oracle. Pure, no platform code. Lands first.
  2. FPC surfaceRandom/Random(L)/Randomize/RandSeed over the core.
  3. OS tiergetrandom syscall (+ /dev/urandom fallback); used by Randomize.
  4. HW tier x86 — CPUID probe + RDRAND (+ retry); inline asm.
  5. HW tier aarch64 — RNDR probe + read.
  6. ESP tier — RNG register (bare) / esp_random (IDF).
  7. Thread-safe state — per-thread or locked (with the threadsafe-io work).

Acceptance

Random* works with zero caller platform code; init auto-selects the best tier per platform; seeding switches to a reproducible software stream that is byte-identical across all 6 targets; HW/OS tiers pass statistical smoke; the capability matrix shows correct selection + fallback chaining. FPC-surface programs compile and run unmodified.

Log

Log

Blocked (2026-07-20)

Slices 1-3 and 7 are done. Slices 4-6 are the HW entropy tiers and every one of them needs an instruction the asm frontend cannot encode: rdrand/rdseed (x86), rndr (aarch64 FEAT_RNG), and on ESP the RNG register read. The frontend has no cpuid either, so the capability probe those tiers need is equally unreachable — this is not "not written yet", it is not expressible.

blocked-by: feature-inline-asm-xmm-operands, which now tracks the whole missing mnemonic surface including cpuid. Nothing further is available in this lane.