Rail stdlib reference

_Auto-generated by tools/docs/gen_stdlib_ref.rail._ _Each entry shows the function signature; lines prefixed with > are the leading source comments._

Import a module with:


import "stdlib/<name>.rail"

stdlib/aead.rail

aead_mask32

aead_pad16_len n

aead_write_le64_from_arr buf offset n_arr

aead_write_le64 buf offset n

aead_derive_poly_key chacha_key nonce

aead_build_mac_input aad aad_len ct ct_len

aead_mac_input_len aad_len ct_len

aead_encrypt key nonce aad aad_len pt pt_len

aead_tags_match a b

aead_tag_cmp_at a b i acc

aead_decrypt key nonce aad aad_len ct ct_len tag

stdlib/anthropic_client.rail

a_trim_line s

a_esc s

Minimal JSON escape for a user prompt (backslash, quotes, newline).

anthropic_payload model prompt max_tokens

a_extract_text body

Extract message.content[0].text from a successful response body.

a_strip_headers raw

Strip HTTP response header block, return just the body.

anthropic_chat model prompt max_tokens key_path

anthropic_chat_unsafe_noverify model prompt max_tokens key_path

Explicit opt-in for debug: bypasses TLS chain validation. DO NOT USE for
production API-key-bearing calls. Same signature as anthropic_chat.

stdlib/args.rail

parse_args argv

parse_args_acc argv flags opts positionals

starts_with_dd s

Check if arg starts with --

strip_dd s

Strip leading --

contains_eq s

Check if string contains =

has_ch c cs

split_eq s

Split at first =

split_eq_acc cs acc

from_chars_acc cs acc

get_opt opts key default

Get option value by key (returns default if not found)

has_flag flags name

Check if flag is set

stdlib/asn1.rail

asn1_read_tlv buf buf_len off

asn1_tlv_fail _

asn1_tlv_ok tag content_off clen next_off

asn1_read_u buf off n acc

Read an unsigned big-endian integer of n bytes from buf starting at off.

asn1_skip buf buf_len off

Skip the TLV at off; return the offset of the next TLV. Returns -1 on error.

asn1_cert_fields buf buf_len

asn1_cf_parse_spki buf buf_len tbs_off tbs_len sa_oid_off sa_oid_len sv_val_off sv_val_len spki_off

asn1_cf_fail _

asn1_cf_ok tbs_off tbs_len sa_oid_off sa_oid_len sv_val_off sv_val_len pa_oid_off pa_oid_len params_off params_len pk_val_off pk_val_len

asn1_oid_eq buf off len ref ref_len

asn1_oid_eq_at buf off ref i n

asn1_oid_ec_pubkey _

Reference OID byte sequences (OID contents only, no tag/length).
ecPublicKey (1.2.840.10045.2.1) = 2A 86 48 CE 3D 02 01

asn1_oid_p256_curve _

prime256v1 / secp256r1 (1.2.840.10045.3.1.7) = 2A 86 48 CE 3D 03 01 07

asn1_oid_p384_curve _

secp384r1 (1.3.132.0.34) = 2B 81 04 00 22

asn1_oid_p521_curve _

secp521r1 (1.3.132.0.35) = 2B 81 04 00 23

asn1_oid_ecdsa_sha256 _

ecdsa-with-SHA256 (1.2.840.10045.4.3.2) = 2A 86 48 CE 3D 04 03 02

asn1_oid_ecdsa_sha384 _

ecdsa-with-SHA384 (1.2.840.10045.4.3.3) = 2A 86 48 CE 3D 04 03 03

asn1_oid_ecdsa_sha512 _

ecdsa-with-SHA512 (1.2.840.10045.4.3.4) = 2A 86 48 CE 3D 04 03 04

asn1_oid_sha256_rsa _

sha256WithRSAEncryption (1.2.840.113549.1.1.11) = 2A 86 48 86 F7 0D 01 01 0B

asn1_oid_rsa_pubkey _

rsaEncryption (1.2.840.113549.1.1.1) = 2A 86 48 86 F7 0D 01 01 01

asn1_oid_rsa_pss _

rsassa-pss (1.2.840.113549.1.1.10) = 2A 86 48 86 F7 0D 01 01 0A

asn1_parse_ecdsa_sig buf buf_len off len

asn1_ec_fail _

asn1_find_extension cert cert_len oid_ref oid_ref_len

asn1_find_ext_walk cert cert_len oid_ref oid_ref_len off tbs_e

Skip optional issuerUniqueID [1] / subjectUniqueID [2] (rare in practice
but allowed by RFC 5280) until we find the [3] EXPLICIT extensions tag.

asn1_scan_extensions cert cert_len oid_ref oid_ref_len off end

asn1_ext_fail _

asn1_oid_san _

subjectAltName OID = 2.5.29.17 = 55 1d 11

asn1_find_validity cert cert_len

asn1_val_parse cert cert_len off

asn1_val_fail _

asn1_find_issuer cert cert_len

asn1_find_subject cert cert_len

asn1_find_spki cert cert_len

Return the TLV range (header + content) of the SubjectPublicKeyInfo
SEQUENCE inside the TBSCertificate. Used by the trust-store loader
to cache SHA-256(SPKI) for SPKI-keyed chain termination. The SPKI
TLV is self-contained DER — hashing its bytes gives a canonical
public-key identifier independent of issuer name.
Returns [spki_off, spki_len, valid].

asn1_find_sub_spki cert cert_len

Single-pass finder: walks TBSCertificate once and returns both the
Subject TLV range and the SPKI TLV range. Used by
pem_load_trust_store to cache both in one parse per store cert.
Returns [sub_off, sub_len, spki_off, spki_len, valid].

asn1_ss_finish cert cert_len sub_off

asn1_ss_fail _

asn1_name_at cert cert_len off

asn1_name_fail _

asn1_name_eq buf_a off_a len_a buf_b off_b len_b

Byte-equality of two Name regions (used to match Issuer of cert N
against Subject of cert N+1, or against a trust-store entry).

asn1_name_eq_at a a_off b b_off i n

stdlib/autograd.rail

ag_tape_new _

ag_tape_new: create an empty tape

ag_tape_push tape entry

ag_tape_push: append an entry, return (new_tape, index_of_new_entry)
Index = length of tape before push (0-based).

ag_tape_get tape i

ag_tape_get: retrieve entry at index i

ag_tape_get_acc tape i cur

ag_param t

ag_param: wrap a Tensor as a tracked leaf parameter (no backward, tape_idx = -1).
Leaves are the starting points of the graph. Their gradients are what we want.

ag_param_on_tape tape t

ag_param_on_tape: register a leaf on the tape so we can accumulate its gradient.
Returns (tape, TrackedTensor).

ag_value tt

ag_value: extract the raw Tensor from a TrackedTensor

ag_tape_idx tt

ag_tape_idx: extract the tape index from a TrackedTensor

tracked_matmul tape a b

ag_backward_matmul captures upstream

Backward for matmul: given upstream gradient dC, produce [dA, dB]
dA = dC @ B^T
dB = A^T @ dC

tracked_add tape a b

ag_backward_add captures upstream

Backward for add: gradient passes through unchanged to both inputs

tracked_sub tape a b

ag_backward_sub captures upstream

Backward for sub: dA = dC, dB = -dC

tracked_mul tape a b

ag_backward_mul captures upstream

Backward for Hadamard product
dA_ij = upstream_ij * B_ij
dB_ij = upstream_ij * A_ij

tracked_relu tape x

ag_backward_relu captures upstream

Backward for ReLU: mask = (X > 0), dX = upstream * mask
tensor_relu_mask returns a tensor of 1.0 where X > 0, 0.0 elsewhere

tracked_gelu tape x

ag_backward_gelu captures upstream

Backward for GELU: dX = upstream * gelu_derivative(X)
gelu_derivative is computed elementwise by tensor_gelu_backward

tracked_softmax tape x

ag_backward_softmax captures upstream

Backward for softmax:
dX_i = Y_i * (dY_i - sum_j(dY_j * Y_j))
This is the efficient form that avoids materializing the full Jacobian.

tracked_layer_norm tape x gamma beta

ag_inv_std_fn v eps

Helper: compute 1/sqrt(var + eps) for a single element

ag_backward_layer_norm captures upstream

Backward for layer_norm:
This implements the full backward pass through the normalization.

last_dim shape

Helper: get last dimension from shape list

tracked_cross_entropy tape logits targets

ag_backward_cross_entropy captures upstream

Backward for cross-entropy:
dLogits = softmax(logits) - one_hot(targets)
This is the famous "softmax - targets" gradient, one of the most elegant
results in all of deep learning. The softmax and cross-entropy losses
cancel out most of their complexity when combined.

tracked_embedding tape weights ids

ag_backward_embedding captures upstream

Backward for embedding: scatter-add upstream gradients back to weight rows.
For each token position i, add upstream[i,:] to dW[ids[i],:].
Most rows of dW remain zero (sparse gradient).

ag_scatter_add dw ids upstream pos

Scatter-add helper: iterate over positions, accumulating gradients

ag_backward tape loss_idx

ag_backward_walk tape grad_table i

Walk the tape backwards from index i down to 0

ag_dispatch_backward entry upstream

Dispatch to the correct backward function based on the tape entry tag

ag_entry_parents entry

Extract parents list from a TapeEntry

ag_accumulate_grads grad_table parents grads j

Accumulate gradients into parent slots
grads and parents are parallel lists: grads[j] goes to parents[j]

ag_get_output_shape tape idx

Get the output shape for a tape entry (for seeding the loss gradient)

ag_collect_leaf_grads tape grad_table i n

Collect leaf gradients: iterate through tape, return list of (idx, grad) pairs
for entries tagged "leaf" that have nonzero gradients

ag_is_leaf entry

Check if a tape entry is a leaf

ag_backward_loss tape loss

ag_backward_loss: given tape and a tracked loss tensor, compute all parameter gradients.
Returns list of (tape_idx, gradient_tensor) pairs for all leaf parameters.
Usage:
let (tape, loss) = tracked_cross_entropy tape logits targets
let grads = ag_backward_loss tape loss
-- grads is [(param1_idx, param1_grad), (param2_idx, param2_grad), ...]

ag_get_grad grads tt

ag_get_grad: look up the gradient for a specific tracked parameter.
Returns the gradient Tensor, or 0 if no gradient was computed.

ag_find_grad grads target_idx

ag_grad_check_param build_graph param tape analytical eps

ag_grad_check_loop build_graph param_val n analytical eps i max_err

Loop over each element, compute numerical gradient, compare

ag_max3_float a b c

Helper: max of three floats (no short-circuit needed, pure comparison)

ag_zero_grad tt

ag_zero_grad: create a zero gradient tensor matching the shape of a TrackedTensor

ag_ones_grad tt

ag_ones_grad: create a ones gradient tensor (for seeding loss backward)

ag_grad_norm grad

ag_grad_norm: compute L2 norm of a gradient tensor (for gradient clipping)
||g||_2 = sqrt(sum(g_i^2))

ag_clip_grad_norm grad max_norm

ag_clip_grad_norm: clip gradient by its L2 norm.
If ||g|| > max_norm, scale g by max_norm / ||g||.
This prevents exploding gradients in deep networks.

ag_clip_all_grads grads max_norm

ag_clip_all_grads: clip a list of (idx, grad) pairs by norm

stdlib/b64.rail

b64_value c

b64_value_lower c

b64_value_digit c

b64_value_special c

b64_decode_to_arr s

b64_loop cs out wpos buf nb pad

cs: remaining chars; out: byte arr; wpos: next write index.
buf: 24-bit accumulator; nb: chars in buf (0..3); pad: '=' count.

b64_flush out wpos buf nb pad

End-of-input: if buf has 2 or 3 base64 chars accumulated (with implicit
padding to 4), emit the corresponding 1 or 2 bytes.

b64_loop_data cs out wpos buf nb pad c

b64_emit cs out wpos new_buf pad

stdlib/base64.rail

b64_chars

Base64 alphabet

b64_char_at idx

Get character at index from b64 alphabet

drop_str n cs

b64_index c

Find index of char in b64 alphabet (returns -1 if not found)

b64_index_acc c cs idx

base64_encode str

Encode a string to base64
Works on byte values (ASCII only)

b64_enc bytes acc

Encode bytes (list of ints) to base64 string

base64_decode str

Decode base64 string back to string

b64_dec indices acc

char_code c

char_code / from_char_code: use the runtime primitives directly.
The previous version shelled out via printf '%d' per character,
which made any base64 op O(N) forks (a 64-byte sig = 64 forks).

from_char_code n

b64_idx_of_byte c

b64_idx_of_byte_lo n

b64_idx_of_byte_dig n

b64_idx_of_byte_sym n

b64_filter_bytes cs acc

b64_decoded_byte_len n

b64_dec_full_bytes cs out off

b64_dec_tail3_bytes cs out off

b64_dec_tail2_bytes cs out off

b64_dec_loop_bytes cs out off

base64_decode_bytes s

stdlib/bignum_n.rail

bn_new n_limbs

bn_from_be bytes n_limbs

Parse n*2 big-endian bytes into an n_limbs little-endian limb array.

bn_fb_at bytes x i n_limbs

bn_to_be x n_limbs

Serialize an n_limbs limb array to n_limbs*2 big-endian bytes.

bn_tb_at x b i n_limbs

bn_copy dst src n_limbs

bn_copy_at dst src i n_limbs

bn_zero a n_limbs

bn_zero_at a i n_limbs

bn_cmp a b n_limbs

bn_cmp_at a b i

bn_is_zero a n_limbs

bn_iz_at a i n_limbs

bn_uadd c a b n_limbs

bn_uadd_at c a b i carry n_limbs

bn_usub c a b n_limbs

bn_usub_at c a b i borrow n_limbs

bn_mod_add c a b m n_limbs

bn_mod_sub c a b m n_limbs

bn_mul_raw c a b n_limbs

bn_mul_zero c i n

bn_mul_outer c a b i n_limbs

bn_mul_inner c a b i j carry n_limbs

bn_carry_fix c i max_i

bn_wide_mod c a m n_limbs

bn_wm_loop r a m n_limbs bit_idx

bn_shl1_out r n_limbs

bn_shl1_at r i carry n_limbs

bn_mul_mod c a b m n_limbs

bn_sqr_mod c a m n_limbs

bn_modexp_small result base e m n_limbs

bn_high_bit_int e

Find the highest set bit position of an int (for e). Up to 31 bits is
plenty for RSA exponents (max e = 65537 = 17 bits).

bn_high_bit_loop e acc

bn_modexp_loop result base e m n_limbs bit_idx

stdlib/bpe.rail

bpe_pair_key a b

bpe_key_a k

bpe_key_b k

bpe_nth n xs

Local nth / drop so bpe.rail doesn't require stdlib/list.rail.

bpe_drop n xs

bpe_contains_char cs c

bpe_collect_unique input acc

bpe_char_id_loop cs c i

Linear-scan char → id in the base-vocab prefix. Merged tokens have
length ≥ 2 so they never false-match a 1-char query.

bpe_init_encode_loop_n cs n vocab_list acc

Length-threaded inner loop — avoids O(N) length cs per step (#3).

bpe_init_encode_loop cs vocab_list acc

bpe_init_encode_arr_loop cs n vocab_list arr i

Array-emitter version — produces a mutable int array of char ids.
Used by the training hot path to avoid O(target × N) cons allocations.

bpe_init_encode_arr cs vocab_list

bpe_count_pairs_arr_loop arr n i m

Array-based pair counting — zero cons allocations per step. For training
on large corpora (#3, memory half): the earlier cons-list scan allocated
a fresh tail cell per step, saturating the arena at ~50K tokens.

bpe_count_pairs_arr arr n m

bpe_find_best_key_loop keys m best_k best_c

Walk map keys, track (best_key, best_count). Tie-break: keep smaller
key so the tokenizer is deterministic for a given corpus.

bpe_apply_merge_n tokens n a b nid acc

Length-threaded inner loop — avoids O(N) length tokens per step (#3).
When a merge fires we consume two tokens, so n decreases by 2; otherwise 1.

bpe_apply_merge tokens a b nid acc

bpe_apply_merge_arr_loop arr n i w a b nid

In-place array merge. Writes to position w <= read position i, so no
forward corruption. Returns new active length. Zero cons allocations.

bpe_apply_merge_arr arr n a b nid

bpe_best_pair_arr arr n

Find best pair. Uses a BST pair-count map as scratch; wraps the whole
scratch in arena_mark/reset so the transient map + keys list don't
accumulate across training iterations (Rail's GC doesn't reliably
reclaim hundreds of BSTs built in tight loops — empirically verified).

bpe_train_loop_arr_old arr n merges vocab size target

LEGACY (pre-v2.4): preserved for regression comparison against the
fast path below. O(K²) per iter because map_keys does `append (map_keys
l) (cons k (map_keys r)), and append vocab [...] / append merges
[...]` are O(V)+O(M) every iter. Observable on 50 KB × target=512:
11.7 s on the sample profile, dominated by .Lapp_list.

bpe_map_best_pair m best_k best_c

In-order tree walk that returns (best_key, best_count). Because we
walk left-root-right, lower keys are visited first, so the strict
v > best_c check preserves the legacy tie-break ("prefer smaller
key on equal count").

bpe_best_pair_tree arr n

bpe_train_loop_deferred arr n merges_rev size target

Deferred-vocab loop: accumulates (a,b,nid) triples in reverse and
returns (merges_forward, final_size) once done.

bpe_bump counts key delta

Adjust one pair's count by delta.

bpe_delta_close counts a b nid L R k

Apply the run's delta to counts. Called when we close a run.

bpe_is_match arr n a b i

Detect a match at position i. Guarded: no short-circuit in &&, so
nested ifs do the bounds-safe check.

bpe_delta_scan arr n a b nid counts i run_k L

Scan the array and fold run deltas into counts. Returns the updated
counts map. Does NOT mutate arr — the actual merge rewrite happens
in bpe_apply_merge_arr afterwards on the (still-current) arr.

bpe_best_pair_persistent counts

Tree-walk max over a persistent counts map. Same semantics as
bpe_map_best_pair; no arena wrap because the caller holds counts
across iterations and we don't want to free it.

bpe_train_loop_inc arr n counts merges_rev size target

Incremental training loop: persistent counts, delta-updated per merge.

bpe_fill_base_arr arr base i

Build the final vocab list once training is done. Layout:
vocab[0..base_size-1] = base_vocab (1-char strings)
vocab[base_size..size-1] = vocab[a] ++ vocab[b] for each (a,b,nid)
Mutable string array is O(1)-index and lets us build forward without
an O(V²) append chain. Converted to a cons list at the very end to
match the existing BPE ADT.

bpe_fill_merged_arr arr merges

bpe_arr_to_list_rev arr i acc

bpe_build_vocab base_vocab merges final_size

bpe_train text target

bpe_train_inc_wip text target

Incremental-counts entry point (WIP, NOT wired into bpe_train).
Semantically correct: the determinism test and 10 KB smoke both show
it produces byte-identical (merges, vocab, size) to the legacy path.
Performance, however, degrades badly above ~50 KB / target=512 — the
persistent Map + per-bump map_put allocations fill the 512 MB arena
fast enough that GC dominates. On the 540 KB × target=1024 run it had
not finished after 30 minutes, vs 6.5 minutes for the non-incremental
deferred path in the same configuration.
The real fix is an open-addressed mutable hash table of ints (two
parallel float_arrs for keys + counts) so each bump is O(1) with zero
allocation. That's the next session's first job — queued along with
an explicit max-heap so best_pair is O(log K) instead of O(K). Until
then, the deferred-vocab + tree-walk-max path ships as bpe_train,
worth ~1.7–3× over the legacy loop and correct at every corpus size
we've tried.

bpe_train_legacy text target

Legacy entry point for regression tests — lets callers verify the fast
path produces identical (merges, vocab, size) to the old loop.

bpe_apply_all_merges tokens merges

bpe_encode bpe text

bpe_decode_loop ids vocab acc

bpe_decode bpe ids

bpe_vocab_size bpe

bpe_escape_chars cs acc

bpe_escape s

bpe_unescape_chars cs acc

bpe_unescape s

bpe_escape_line v

bpe_merge_to_line m

bpe_save path bpe

bpe_digit_val c

Pure-Rail string→int (self-contained so bpe.rail doesn't pull socket.rail).
NOTE: Rail's char_to_int doesn't return the ASCII code byte — per
tools/train/hyperagent.rail, the convention in this repo is to
decode digits via direct string comparison.

bpe_parse_int_acc cs acc

bpe_parse_int s

bpe_nonempty s

bpe_parse_merge_line line

Parse "a b c" → (a, b, c) tuple.

bpe_load path

stdlib/bytes.rail

mask32

two32

and32 a b

or32 a b

xor32 a b

add32 a b

rotr32 x n

32-bit right rotation: ((x >> n) | (x << (32 - n))) & 0xFFFFFFFF
For SHA-256 Σ, σ, Ch, Maj functions.

shr32 x n

32-bit logical shift right (just shr + mask for safety).

hex_digit c

hex_to_bytes_at cs arr idx

hex_to_bytes s

hex_char_of n

bytes_to_hex_at arr n i acc

bytes_to_hex arr n

be32_read arr offset

be32_write arr offset value

le32_read arr offset

le32_write arr offset value

rotl32 x n

32-bit left rotation. The naive bit_and (rotl x n) mask32 fails because
Layer 0's rotl is a 64-bit rotate — bits rotated from position 31 and
below land above bit 32, where the mask clears them. Use the same
shift-OR pattern as rotr32.

string_to_bytes_at cs arr i

WARNING: do not check length cs == 0 as the loop guard here.
length on a Rail cons-list is O(N), so the recursive walk would
be O(N^2) and a 360 KB string took *hours* before this was fixed.
Pattern-match on the empty list shape instead, which is O(1).

string_to_bytes s

string_length_bytes s

stdlib/cert_chain.rail

cc_walk_chain store certs lens count

cc_walk_at store certs lens count i

cc_check_spki store certs lens count i cur cur_len spki

cc_check_issuer store certs lens count i cur cur_len

Step 2: issuer-name match against the trust store. If this cert's
Issuer equals a store root's Subject, verify this cert against
that root — covers the common case where the server's chain does
not include the root itself (e.g. slack.com's 2-cert chain
terminates at ISRG Root X1 in the store).

cc_verify_against_root store cur cur_len ridx

cc_try_inter store certs lens count i cur cur_len

Step 3: neither SPKI match nor issuer match — try the next cert in
the server-supplied chain.

stdlib/cert_p384.rail

cv_extract_p384_pubkey cert cert_len

cvp384_check_alg cert fields

cvp384_check_curve cert fields

cvp384_extract_xy cert fields

cvp384_fail _

cv_chain_p384 tbs tbs_len sig sig_len upper upper_len

Verify lower's signature (ECDSA-with-SHA384) using upper's P-384 pubkey.

cvp384_verify tbs tbs_len sig sig_len pk

cvp384_run tbs tbs_len sig parsed px py

cvp384_pad48 src off len

stdlib/cert_p521.rail

cv_extract_p521_pubkey cert cert_len

cvp521_check_alg cert fields

cvp521_check_curve cert fields

cvp521_extract_xy cert fields

cvp521_fail _

cv_chain_p521 tbs tbs_len sig sig_len upper upper_len

Verify lower's signature (ECDSA-with-SHA512) using upper's P-521 pubkey.

cvp521_verify tbs tbs_len sig sig_len pk

cvp521_run tbs tbs_len sig parsed px py

cvp521_pad66 src off len

stdlib/chacha20.rail

cc_mask32

cc_rotl32 x n

cc_add32 a b

cc_le32_read arr offset

cc_le32_write arr offset value

cc_copy_bytes src dst src_off dst_off n

chacha20_c0

chacha20_c1

chacha20_c2

chacha20_c3

chacha20_qr state ia ib ic id

chacha20_double_round state

chacha20_rounds state n

chacha20_init_state state key nonce counter

chacha20_copy_state src dst

chacha20_copy_at src dst i

chacha20_add_states acc src

chacha20_add_at acc src i

chacha20_serialize state out

chacha20_serialize_at state out i

chacha20_block key nonce counter

chacha20_xor_segment pt off ks ct n

chacha20_xor_seg_at pt off ks ct i n

chacha20_encrypt_loop key nonce counter pt pt_len ct off

chacha20_encrypt key nonce counter pt pt_len

stdlib/checkpoint.rail

shape_line_loop shape acc

Format a tensor's shape as " ...".

shape_line shape

save_loop prefix tensors i

Write manifest + N binary files. Tensors are written in list order.

build_manifest tensors acc

save_model prefix tensors

parse_dims_loop parts acc

Parse one shape line "rank d0 d1 ..." into a list of ints.
split is single-char so " " as a separator is fine here.

parse_shape_line line

load_loop prefix lines i count acc

Load a previously-saved model into fresh Tensors. Returns a list.

load_model prefix

check_vocab_matches V w_e_tensor

Sanity-check that the loaded embedding's row count matches the vocab
size derived from the corpus. Returns 1 on match, 0 on mismatch (with
an error message printed). Catches the V_corpus vs W[0].rows drift
that silently OOB-reads in matmul_cpu and produces "garbage finite"
heisenbug values. Per memory vocab_embedding_shape_mismatch_2026-05-10.md
— every CPU-substrate bench number on a drifted corpus was
OOB-garbage-dependent.
Usage:
let weights = load_model prefix
let w_e = list_nth 0 weights
if check_vocab_matches V w_e == 0 then 1
else ...

adam_manifest_line state

" " manifest line for one state.

save_adam_loop prefix states i

Write the i'th (and onward) Adam state's m/v payloads.

build_adam_manifest states acc

save_adam_states prefix states

parse_adam_line_ints parts acc

Parse " " → 2-int list.

parse_adam_line line

load_adam_loop prefix lines i count acc

load_adam_states prefix

save_meta prefix step best_val

find_meta_loop lines key

load_meta_text prefix

meta_step prefix

meta_best_val prefix

Returns best_val_loss or -1.0 sentinel if key missing.

ckpt_exists prefix

clear_committed prefix

mark_committed prefix

save_checkpoint prefix weights adam_states step best_val

Full checkpoint save (atomic via commit sentinel).

load_checkpoint prefix

Returns [weights, adam_states, step, best_val] or [] if no committed ckpt.

save_half_loop prefix halftensors i

build_half_manifest halftensors acc

save_half_model prefix halftensors

load_half_loop prefix lines i count acc

load_half_model prefix

save_half_checkpoint prefix halftensors adam_states step best_val

Full checkpoint save with HalfTensor weights (atomic via commit sentinel).
Adam states are f64 on both the training side and on disk (unchanged
from save_checkpoint) — only the weights round-trip through the half
pack.

load_half_checkpoint prefix

overwrite_half_model_loop prefix halftensors i

In-place variant for training resume: HalfTensor weights are already
allocated by the caller (same shape); overwrite their packed-half
buffers in place. Mirrors load_model_into but with the f32→f64→half
round-trip per tensor.

load_half_model_into prefix halftensors

overwrite_model_loop prefix tensors i

load_model_into prefix tensors

overwrite_adam_loop prefix states lines i

load_adam_states_into prefix states

corpus_split text val_pct

stdlib/codesign.rail

cs_magic_embedded_signature

cs_magic_codedirectory

cs_magic_requirements

cs_slot_codedirectory

cs_slot_requirements

cs_adhoc_flag

cs_hashtype_sha256

cs_hashsize_sha256

cs_execseg_main_binary

cd_version_20400

CodeDirectory version that supports execSeg fields (Apple Silicon).

cd_pagesize_log2_16k

Page size as log2. Canonical codesign uses 14 (= 16 KB page).

cs_u32_be buf off v

cs_u64_be buf off v

cs_ident_string

cs_ident_len

cs_cd_fixed_size

cs_cd_special_slots

cs_cd_code_slots

cs_cd_hashes_size

cs_cd_size

cs_req_size

cs_sb_header_size

cs_sb_index_size

cs_sig_total_size

cs_off_cd

Offsets within the signature blob.

cs_off_req

cs_cd_ident_off

Offsets within the CodeDirectory.

cs_cd_hashes_start

cs_cd_hash_off_in_cd

emit_requirements buf off

============================================================================
Emit the empty Requirements wrapper into buf at off. Returns off+12.
============================================================================

emit_cd_header buf off code_limit text_seg_base text_seg_limit

============================================================================
Emit the CodeDirectory fixed-size header into buf at off.
============================================================================

emit_cd_identifier buf cd_start

Write the identifier string at the right offset within the CD. Returns
offset of byte after the trailing NUL.

emit_cd_ident_chars buf off cs

cs_sha256_range src off n

Compute SHA-256 of bytes [off, off+n) in src buffer. Returns a 32-byte
array (the hash digest).

cs_copy_hash dst dst_off src

Copy 32 bytes from src arr into dst buf at dst_off.

cs_copy_hash_loop dst dst_off src i

emit_signature binary_buf sig_off code_limit text_base text_limit

============================================================================
Top-level: write the full signature blob into buf starting at sig_off.
binary_buf - the Mach-O buffer being signed
sig_off - file offset where the signature starts (= start of __LINKEDIT)
code_limit - bytes hashed (= sig_off, conventionally)
text_base - __TEXT segment base in the file (0)
text_limit - __TEXT segment file size
Caller must have written LC_CODE_SIGNATURE referencing
(dataoff = sig_off, datasize = cs_sig_total_size) BEFORE calling this,
so the page hash captures the LC entry itself.
============================================================================

cs_n_code_pages code_limit

============================================================================
GENERALIZED ad-hoc signer — N code pages (16K each). The fixed emit_signature
above only handles a 1-page binary; emit_signature_n handles any size by
sizing the CodeDirectory + SuperBlob dynamically and hashing each 16K page
into its own code slot. Returns sig_off + total_size.
n_code = ceil(code_limit / 16384)
cd_size = 96 + (2 + n_code) * 32 (96 = 88 header + 8 ident; 2 special slots)
total = 12 + 16 + cd_size + 12 = cd_size + 40
off_req = 28 + cd_size
Call emit_signature_total_n first to size LC_CODE_SIGNATURE.datasize + file.
============================================================================

cs_cd_size_n n_code

cs_total_size_n code_limit

emit_cd_header_n buf off cd_size n_code code_limit text_base text_limit

emit_code_slots_n buf cd_start code_limit n_code i

hash each 16K page [i*16384, min(code_limit,(i+1)*16384)) into code slot i

emit_signature_n binary_buf sig_off code_limit text_base text_limit

stdlib/concurrent.rail

rc_chan_make capacity

Create a bounded blocking channel with capacity slots.

rc_chan_send ch value

Send a value; blocks if the channel is full. Returns 1 ok / 0 closed.

rc_chan_recv ch

Receive a value; blocks if the channel is empty.

rc_chan_close ch

Close a channel; wakes all blocked threads.

rc_chan_count ch

Current buffered count.

rc_spawn_producer ch n

Spawn a demo producer that sends 1..n then closes the channel. Returns
a task handle for rc_join.

rc_spawn_consumer ch

Spawn a demo consumer that drains the channel, summing recvd values.
The sum is returned by rc_join.

rc_join task_id

Join a task handle; blocks until the thread exits. Returns the int64
result the worker stored on exit.

chan_send_v ch v

chan_recv_v ch

chan_send_float ch f

Float convenience aliases (ARM64: floats are register-raw doubles, so
the v1 box path bit-preserves them; on x86 floats are heap-boxed
pointers and ride the same path — the box ends up holding the boxed-
float pointer rather than raw d-bits, which is identical from the
recv-side semantics).

chan_recv_float ch

select handles

select is the typed-value variant: assumes channels carry boxed
values (sent via chan_send_v / chan_send_float). Returns the logical
Rail value, not the wire box pointer.

select_with_default handles default_val

select_with_default is the non-blocking typed-value variant.
Returns (-1, default_val) immediately if no channel has a value.

select_int handles

select_int is the raw int64 variant for channels used via the v0
rc_chan_send / rc_chan_recv API. Returns the wire value as a tagged
int. The C side pre-retags the wire bits before writing the out-slot
so Rail can use arr_get directly without further bit manipulation.

select_int_with_default handles default_val

arr_from_list xs n

Small helper: copy a list into a freshly-allocated mutable array of the
given length. Used to flatten the handle list into a layout the C bridge
can index by offset.

arr_fill_from_list a xs i

stdlib/cortexm_runtime.rail

cm_emit_halt s halt_label

cm_emit_reset s main_label halt_label

cm_load_imm32 s rd value

cm_mmio_w32 s addr value

cm_mmio_r32 s rd addr

cm_emit_word s value

Helper: write a 32-bit word raw to the buffer. Bypasses the assembler.

cm_emit_word_pair s lo16 hi16

cm_emit_vector_table s sp_top reset_label halt_label irq_count

cm_queue_label_word s label_id

cm_emit_vt_handler_loop s label_id i n

cm_post_process s

cm_post_loop s i n

cm_finalize s

stdlib/csv.rail

csv_dq

Single-character constants synthesized at runtime (no \r literal in Rail).

csv_cr

csv_lf

csv_rev xs

Reverse a list (local; avoids importing list.rail).

csv_rev_acc xs acc

csv_finish_field rev_chars

Turn an accumulated (reversed) list of single-char strings into a field.

csv_parse_line line

csv_unq cs cur fields

Outside quotes.

csv_inq cs cur fields

Inside quotes. A "" is an escaped quote; a lone " closes the field.

csv_parse text

csv_push_row cur fields rows

Close the current record and push it onto rows (reversing its fields).

csv_doc_unq cs cur fields rows

Outside quotes (document level).

csv_doc_inq cs cur fields rows

Inside quotes (document level): newlines/commas are literal data.

csv_doc_end cur fields rows

End of input: only emit a final record if there's pending content. A file
ending in a newline leaves cur=[] and fields=[], which we drop.

csv_needs_quote f

Does a field need quoting? True if it contains comma, quote, CR, or LF.

csv_scan_special cs

csv_escape_chars cs

Double every embedded quote.

csv_quote_field f

csv_field_out f

csv_fields_out fields

Map each field through csv_field_out and join with commas.

csv_row_to_string fields

stdlib/date.rail

is_leap_year y

── Leap year ───────────────────────────────────────────────────────────
Gregorian rule: divisible by 4, except centuries, except multiples of 400.
No short-circuit in Rail, so the nested-if form keeps the modulos honest.

days_from_civil y m d

── days_from_civil (Hinnant) ───────────────────────────────────────────
(y, m, d) -> days since 1970-01-01. Inverse of civil_from_days.
y is the proleptic Gregorian year; the algorithm shifts so that the year
"starts" in March, which makes the leap day fall at the end of the cycle.

civil_from_days z

── civil_from_days (Hinnant) ───────────────────────────────────────────
days since 1970-01-01 -> [y, m, d]. Inverse of days_from_civil.

day_of_week y m d

── Day of week ─────────────────────────────────────────────────────────
1970-01-01 (day 0) was a Thursday. Sunday-based index: shift by +4 then
take mod 7. Rail's % can yield a negative result for negative operands,
so we add 7 and re-mod to land in 0..6 for pre-epoch dates too.

date_pad2 n

── Formatting ──────────────────────────────────────────────────────────

date_format y m d

unix_to_civil secs

── Unix seconds → civil date-time ──────────────────────────────────────
Returns [y, m, d, h, mi, s]. Floor-divides so negative (pre-epoch) seconds
still produce a valid date with 0 <= h,mi,s in range. UTC, ignores leap secs.

datetime_format secs

stdlib/deque.rail

deque_new _

Construct an empty deque

deque_is_empty dq

True when the deque holds no elements

deque_size dq

Number of elements (O(n))

push_front x dq

Push x onto the FRONT

push_back x dq

Push x onto the BACK

pop_front dq

Pop from the FRONT -> (value, newDeque)
If front is empty, reverse back into front first (rebalance).

deque_pop_front_rebalanced f

After rebalance, front = reverse(back) and back = [].

pop_back dq

Pop from the BACK -> (value, newDeque)
If back is empty, reverse front into back first (rebalance).

deque_pop_back_rebalanced b

After rebalance, back = reverse(front) and front = [].

deque_to_list dq

Convert to an ordinary list in front-to-back order

stdlib/dirent.rail

list_dir path

List directory contents (returns list of filenames)
NOTE: readdir returns struct dirent* — extracting d_name requires
pointer arithmetic. For now, use shell ls as a workaround.

stdlib/dlopen.rail

rtld_lazy

Flags

rtld_now

rtld_global

load_lib path

Load a shared library

find_symbol handle name

Look up a symbol

unload_lib handle

Unload library

stdlib/dns.rail

dns_default_server _

Read /etc/resolv.conf, find first "nameserver X.Y.Z.W" line.

split_lines s

Split a string on newlines.

dns_scan_lines lines

dns_is_nameserver line

dns_extract_ns line

dns_encode_name host

Encode a hostname as DNS labels: "api.anthropic.com" → [3]api[9]anthropic[3]com[0]

dns_labels_total_len labels acc

dns_write_labels buf labels off

dns_write_str buf off s i n

chars_at s i

chars_at s i returns the chars list starting at index i.

dns_drop xs n

dns_build_query host

Build the full DNS query packet: 12-byte header + encoded question.
ID = 0x1234 (deterministic — fine for one-shot UDP). Flags = 0x0100 (RD=1).
QDCOUNT=1, others=0. QTYPE=A=1, QCLASS=IN=1.

dns_copy_cbuf src dst src_off dst_off n

dns_parse_response p got

dns_skip_qname p off limit

dns_walk_answers p off limit count

dns_walk_next p off limit count

dns_skip_ans_name p off limit

Skip the name field of an answer record. Could be a label sequence
ending in 0, or a pointer (first byte's top 2 bits set).

dns_set_recv_timeout fd secs

Set SO_RCVTIMEO on the UDP socket so a missing reply doesn't hang
the process forever (Tailscale's MagicDNS occasionally drops UDP
packets; without this, dns_resolve_a blocks indefinitely on retry
queries). macOS BSD: SOL_SOCKET = 0xFFFF, SO_RCVTIMEO = 0x1006.
Argument is struct timeval { time_t tv_sec; suseconds_t tv_usec; }
which is 16 bytes on 64-bit darwin (8 + 8).

dns_resolve_a host

dns_resolve_a_try host attempt

Up to 3 attempts at 2-second timeout each. Total worst-case 6 s.

dns_resolve_a_once host

dns_send_udp fd buf len ip port

dns_recv_udp fd bufsize

stdlib/ecdsa_p256.rail

p256_limbs _

p256_from_bytes bytes

Build a 16-limb bignum from a 32-byte big-endian byte array.

p256_fb bytes x i

p256_to_bytes x

Serialize a 16-limb bignum to 32-byte big-endian array.

p256_tb x b i

p256_new _

Allocate a new zero bignum.

p256_copy dst src

Copy src → dst.

p256_copy_at dst src i

p256_cmp a b

Compare a vs b: returns -1 / 0 / 1.

p256_cmp_at a b i

p256_is_zero a

Is zero?

p256_iz_at a i

p256_load_p _

Load curve constants. Built lazily via hex_to_bytes + p256_from_bytes.

p256_load_n _

p256_load_a _

p256_load_b _

p256_load_gx _

p256_load_gy _

p256_uadd c a b

c = a + b. Returns carry-out (0 or 1).

p256_uadd_at c a b i carry

p256_usub c a b

c = a - b. Returns borrow-out (0 or 1). If b > a, result wraps.

p256_usub_at c a b i borrow

p256_mod_add c a b m

c = (a + b) mod m.

p256_mod_sub c a b m

c = (a - b) mod m. Assumes a, b in [0, m).

p256_wide_mod c a m

c = a mod m, where a is a "wide" bignum (32 limbs). Bit-by-bit long division.
Mutates c.

p256_wm_loop r a m bit_idx

Shift r left by 1, add bit; conditionally subtract m.

p256_shl1_out r

Shift r (16 limbs) left by 1 bit. Returns carry-out (bit pushed out of limb 15).

p256_shl1_at r i carry

p256_carry_fix r i

Propagate a potentially-overflowing value at limb i up through subsequent limbs.

p256_mul_raw c a b

p256_mul_zero c i

p256_mul_outer c a b i

p256_mul_inner c a b i j carry

For row i of a: accumulate a[i]*b[j] into c[i+j], propagating carry.

p256_mul_mod c a b m

c = (a * b) mod m (m is 16-limb modulus).

p256_sqr_mod c a m

c = (a * a) mod m.

p256_mod_inv c a m

p256_zero_from c i

p256_pow_loop c base exp m bit_idx

p256_point_new _

p256_point_copy dst src

p256_point_is_inf pt

p256_point_dbl out pin

p256_point_add out pin qin

p256_to_affine ax ay pt

Convert Jacobian (X, Y, Z) back to affine (x, y). Fails if Z == 0.

p256_scalar_mul out k pt

Scalar multiplication via double-and-add (walking k MSB→LSB).
out = k * P. k is a 16-limb bignum.

p256_sm_loop out k pt bit_idx

ecdsa_p256_verify pub_x_bytes pub_y_bytes hash_bytes r_bytes s_bytes

ecdsa_p256_verify_core e r s pub_x_bytes pub_y_bytes

stdlib/ecdsa_p384.rail

p384_nl _

p384_load_p _

Load curve constants (48 bytes each, 24 limbs).

p384_load_n _

p384_load_gx _

p384_load_gy _

p384_mod_inv c a m

p384_pow_loop c base exp m nl bit_idx

p384_point_new _

p384_point_copy dst src

p384_point_is_inf pt

p384_point_dbl out pin

Jacobian doubling with a=-3 (EFD dbl-2001-b), parameterised.

p384_dbl_core out X Y Z p

p384_point_add out pin qin

Full Jacobian add (EFD add-2007-bl).

p384_add_core out pin qin p

p384_add_normal out X1 X2 Y1 Y2 Z1 Z2 U1 S1 H r p

p384_to_affine ax ay pt

Convert Jacobian to affine; returns 1 on success, 0 if at infinity.

p384_scalar_mul out k pt

Scalar multiplication: out = k * P. k is a 24-limb bignum.

p384_sm_loop out k pt bit_idx

ecdsa_p384_verify pub_x_bytes pub_y_bytes hash_bytes r_bytes s_bytes

ecdsa_p384_verify_core e r s pub_x_bytes pub_y_bytes

stdlib/ecdsa_p521.rail

p521_nl _

p521_load_p _

Load curve constants (66 bytes each, 33 limbs with the top limb
holding only the 9 most-significant bits of the 521-bit value).

p521_load_n _

p521_load_gx _

p521_load_gy _

p521_mod_inv c a m

p521_pow_loop c base exp m nl bit_idx

p521_point_new _

p521_point_copy dst src

p521_point_is_inf pt

p521_point_dbl out pin

Jacobian doubling with a=-3 (EFD dbl-2001-b).

p521_dbl_core out X Y Z p

p521_point_add out pin qin

Full Jacobian add (EFD add-2007-bl).

p521_add_core out pin qin p

p521_add_normal out X1 X2 Y1 Y2 Z1 Z2 U1 S1 H r p

p521_to_affine ax ay pt

Convert Jacobian to affine; returns 1 on success, 0 if at infinity.

p521_scalar_mul out k pt

Scalar multiplication: out = k * P. k is a 33-limb bignum. Bit 520 → 0.

p521_sm_loop out k pt bit_idx

ecdsa_p521_verify pub_x_bytes pub_y_bytes hash_bytes r_bytes s_bytes

ecdsa_p521_verify_core e r s pub_x_bytes pub_y_bytes

stdlib/ed25519.rail

ed_d_bytes _

Ed25519 "d" curve param = -121665 / 121666 (mod p), verified via
Python: (-121665 * pow(121666, -1, 2255-19)) % (2255-19). Canonical
32-byte little-endian:

ed_sqrt_m1_bytes _

sqrt(-1) mod p; needed when recovering x whose square root candidate
differs by sqrt(-1). Value = 2^((p-1)/4) mod p
= 19681161376707505956807428261136721352453178828470896037035617363440128182579
LE: B0 A0 0E 4A 27 1B EE C4 78 E4 2F AD 06 18 43 2F
A7 D7 FB 3D 99 00 4D 2B 0B DF C1 4F 80 24 83 2B

ed_l_bytes _

L (group order) = 2^252 + 27742317777372353535851937790883648493
LE 32 bytes: ED D3 F5 5C 1A 63 12 58 D6 9C F7 A2 DE F9 DE 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10

ed_b_bytes _

Base point B: compressed encoding 0x5866666666...66 (33 bytes — wait, 32).
Standard encoding: 58 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66
66 66 66 66 66 66 66 66 66 66 66 66 66 66 66 66

ed_gf_from_le32 bytes

Load a 32-byte LE bytestring as a field element, masking high bit
(RFC 8032 §5.1.2 step 1 / §5.1.3 step 1 for decoded y).

ed_gf_from_le32_raw bytes

Load a 32-byte LE bytestring as a field element WITHOUT masking the
high bit (used for x25519-like elements and when we need the raw 256
bits — the caller decides semantic masking).

ed_gf_d _

Convenience constants as packed gf (malloc'd once per call — callers
that care about allocator pressure can hoist these above the verify
loop, but for a one-shot verify the 4 extra arrays per call are fine).

ed_gf_sqrt_m1 _

ed_gf_neg out a

Neg: out = -a mod p. Implemented as p - a then reduce.

ed_gf_eq a b

Equality on canonical packed encodings (32-byte LE).

ed_bytes_eq a b i n

Constant-time byte-array equality. XOR-OR-folds across all n bytes
regardless of where the first mismatch is. Used for ed25519 signature
final-equality (R_lhs == R_rhs) and provenance /verify/ compares.

ed_bytes_eq_acc a b i n acc

ed_gf_parity a

LSB of a (after packing to canonical 32-byte LE).

ed_decode bytes

ed_decode_finish u v y sign_bit

x candidate = u * v^3 * (u * v^7)^((p-5)/8). The exponent (p-5)/8 has
a known closed-form that TweetNaCl / ref10 implement via a fixed
addition chain. We use x25519_inv for v^(-1) and derive v^3, v^7
explicitly. Then run the (p-5)/8 exponentiation inline as an
addition chain. To keep this module short we reuse the inverse
helper: v^((p-5)/8) = v^(p-2)^((-1)*((p-5)/8 * (p-2)^-1)) — no, that
hurts. Simpler: exponentiate via repeated squaring over a known bit
pattern.
(p-5)/8 in binary: 250 ones followed by 2 zeros. Implemented below
as ed_pow_p58.

ed_decode_sign_and_build x y sign_bit

ed_build_point x y

ed_decode_fail _

ed_gf_is_zero a

ed_bytes_all_zero p i

ed_pow_p58 out base

Exponent (p-5)/8 — 250 bits all set, then 2 bits of zero (bit positions
250, 251 are zero in standard big-endian view since p is 2^255 - 19).
p-5 = 2^255 - 24 = 2^252 * (2^3 - 3 * 2^-252). Simpler: just walk bits
of (p-5)/8 directly. (p-5)/8 = 2^252 - 3. Its 252-bit binary form
is: 250 ones, 0, 1 (LSB first: 1 1 0 1 1 1 ... 1). Let me just do
exponent 2^252 - 3.

ed_gf_p58_bytes _

(p-5)/8 = 2^252 - 3 has the LE byte pattern:
byte 0 = 0xFD (bits 0-7: 11111101 → LSB-first, so value 0xFD)
bytes 1-31 = 0xFF except byte 31 = 0x0F (bits 252-255 = 0001 LE-wise)
Let me verify: 2^252 - 3 = 0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffd
LE bytes (byte 0 is LSB): FD FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF 0F

ed_pow_bytes_iter out base e bit_total

Self-tail-recursive driver: bit_total = byte_idx * 8 + bit_idx. Rail
does NOT TCO mutual recursion — must stay single-function (handoff
rule #3). Scan MSB → LSB: start at bit_total = n_bytes*8 - 1, decrement
to 0.

ed_point_new _

ed_point_copy dst src

ed_point_identity out

bn_zero_at_gf a

bn_zero_gf_loop a i

ed_point_add out P Q

ed_scalar_mul out k n_bytes P

Scalar-mult: out = k*P where k is a LE byte array of len n_bytes.
Double-and-add MSB→LSB. For h (SHA-512 = 64 bytes) we iterate 512
bits; 32-byte S is 256. Self-tail-recursive driver only — Rail does
NOT TCO mutual recursion (handoff rule #3), and a 512-deep call
stack WILL blow up.

ed_sm_iter out k P bit_total

ed_encode out pt

ed_le_lt a b

── S < L check ───────────────────────────────────────────────────────
Compare two 32-byte LE integers; returns 1 if a < b, 0 otherwise.

ed_le_lt_loop a b i

ed25519_verify pub_bytes msg_bytes msg_len sig_bytes

ed_r_canonical R R_enc

Re-encode R and compare to R_enc (which carries the original sign bit
in bit 7 of byte 31). Returns 1 if canonical, 0 if non-canonical.

ed25519_verify_step2 pub_bytes msg_bytes msg_len sig_bytes R_enc R

ed_a_in_small_subgroup A

Returns 1 if 8*A equals the identity point. Identity-encoded bytes:
byte 0 = 0x01, bytes 1..31 = 0x00.

ed_is_identity_enc enc i acc

Identity check: byte 0 must be 1, all other bytes must be 0.
Uses XOR-OR-fold so we look at all 32 bytes regardless of where the
first deviation is.

ed25519_verify_step3 pub_bytes msg_bytes msg_len sig_bytes R_enc R A

ed25519_verify_step4 pub_bytes msg_bytes msg_len R_enc R A S

ed25519_verify_final R A S k B

stdlib/ed25519_scalar.rail

sc_l_bytes _

── L as 32-byte LE constant ──────────────────────────────────────────

sc_bn_add_inplace a b n

a += b, both n-byte LE buffers. Returns carry-out (0 or 1).

sc_bn_add_loop a b n i carry

sc_bn_sub_inplace a b n

a -= b, both n-byte LE buffers. Returns borrow (0 or 1). Caller is
responsible for ensuring a >= b before calling (use sc_bn_ge).

sc_bn_sub_loop a b n i borrow

sc_bn_ge a b n

Returns 1 if a >= b (n-byte LE), else 0.

sc_bn_ge_loop a b i

sc_bn_shl1_inplace a n

Shift n-byte LE buffer left by 1 bit, in-place. Returns the bit shifted
out of the top.

sc_bn_shl1_loop a n i carry

sc_bn_shr1_inplace a n

Shift n-byte LE buffer right by 1 bit, in-place. Bit shifted in from
the top is 0.

sc_bn_shr1_from_top a i carry

sc_bn_mul a alen b blen out

Schoolbook multiply: out[0..(alen+blen)] = a * b. out must be pre-zeroed.

sc_bn_mul_outer a alen b blen out i

sc_bn_mul_inner a b blen out i j carry

sc_bn_mul_carry_propagate out idx carry

sc_bn_copy src dst n

Copy n bytes from src[0..n] to dst[0..n].

sc_bn_copy_loop src dst n i

sc_bn_zero buf n

Zero n bytes of buf.

sc_bn_zero_loop buf n i

sc_reduce_bytes_n input n

── sc_reduce_bytes_n: input mod L, where input is n-byte LE ──────────
Method: classical shift-and-subtract long division.
Build shifted_L = L << max_shift in an n-byte buffer (with L's top
bit landing in input's top bit position). Then for k = max_shift
down to 0, conditionally subtract shifted_L if working >= shifted_L,
then shift shifted_L right by 1 bit. After the loop, working is in
[0, L), i.e. the bottom 32 bytes are the result and bytes 32.. are
guaranteed zero.
L's top bit is at position 252 (bit 4 of byte 31). For an n-byte input
(top bit at most at position 8n-1), max_shift = (8n - 1) - 252 = 8n - 253.

sc_bn_shl_n_times a n k

sc_reduce_loop working shifted n k

sc_reduce input

sc_reduce: 64-byte LE input -> 32-byte LE (input mod L).

sc_muladd a b c

sc_muladd: (a*b + c) mod L. All inputs are 32-byte LE; output is 32-byte LE.

stdlib/ed25519_sign.rail

ed25519_sk_expand seed

ed25519_scalar_mult_base a

Encode [a]B for a 32-byte LE scalar a. Returns a fresh 32-byte array.

ed25519_pk_from_sk seed

ed25519_sign seed msg msg_len

stdlib/elf.rail

elf_magic_0

elf_magic_1

elf_magic_2

elf_magic_3

elf_class_64

elf_data_2lsb

elf_version

elf_osabi_sysv

et_exec

em_aarch64

pt_load

Program-header types

pt_phdr

pf_x

Program-header flags

pf_w

pf_r

pf_rx

pf_rw

ehdr_size

Sizes

phdr_size

elf_text_vaddr

Conventional base virtual address for static aarch64 ELFs.
(The linker default; the kernel doesn't care as long as PIE isn't set
and the address is page-aligned.)

elf_page_size

elf_emit_ehdr buf off entry phnum

entry = virtual address of the first instruction
phoff = file offset of the first program header (always 64 for us)
phnum = number of program headers

elf_emit_phdr buf off p_type p_flags p_offset p_vaddr p_filesz p_memsz p_align

elf_emit_tiny buf code_buf code_size

elf_copy_bytes dst dst_off src src_off n

elf_align_up x a

elf_emit_full buf text_buf text_size data_buf data_size bss_size

Emit a binary with up to two PT_LOADs (text RX, data RW). The data
segment's memsz extends into bss territory if bss_size > 0.

elf_write_file path buf n

stdlib/env.rail

get_env name

Get environment variable (returns string or "")

set_env name value

Set environment variable

stdlib/file.rail

open_read path

Open file for reading, returns file pointer (0 on failure)

open_write path

Open file for writing

open_append path

Open file for appending

slurp path

Read entire file as string (uses read_file builtin)

spit path content

Write string to file (uses write_file builtin)

append_line file text

Append a single line (string + newline) to a file. Used by ledger
writers and harvest collectors. Goes through a temp file + shell cat
because Rail's builtins don't expose append-mode write directly.
Promoted from inline copies in self_train.rail / s0_pcfg/tick.rail /
s0_pcfg/harvest.rail (originally Empire transplant Session 1).

read_file_size path

Returns the size in bytes; -1 if the file can't be opened.

read_file_bytes path

Returns an int array of length file_size with the file's raw bytes,
one byte per element. Empty array on missing file. Walks the
malloc'd C buffer with byte_at -> arr_set so the binary contents
survive intact (no NUL truncation, no chars-of-string detour).

read_file_bytes_open fp path

file_copy_bytes_to_arr buf arr i n

stdlib/fit.rail

fit_epoch_offset

── FIT epoch ───────────────────────────────────────────────────────────
1989-12-31T00:00:00Z = unix 631065600.

fit_unix_of ts

fit_civil_from_days z

fit_pad2 n

fit_fmt_unix_utc unix

fit_parse_uint_acc cs acc

String-to-uint, stops at first non-digit. Used for stat output parsing.
Strings are short here so the O(length) recompute is irrelevant.

fit_parse_uint s

fit_file_size path

File size in bytes via stat. Note: stdlib/stat.rail's file_size uses
to_int (which is float→int, not string-parse) so it always returns 0;
this is the working version.

fit_hex_digit c

Count-based hex decoder. Caller supplies n_bytes so we never call
length cs inside the loop.

fit_hex_decode_n cs arr i n

read_bin_file path

le16_read buf off

be16_read buf off

fit_be32_read buf off

32-bit big-endian read (be32_read in bytes.rail predates this; same impl).

fit_read_u buf off n arch

Read N-byte unsigned int, given arch (0=LE, 1=BE). N up to 4.

fit_s16 v

Sign-extend a 16-bit value.

fit_s32 v

Sign-extend a 32-bit value (Rail ints are 63-bit, no overflow worry).

fit_load path

fit_buf fit

fit_total fit

fit_data_off fit

fit_data_end fit

fit_valid fit

fit_header_size fit

fit_protocol fit

fit_profile fit

fit_data_size fit

schema_global s

schema_arch s

schema_nfields s

schema_total s

schema_field_def s i

schema_field_size s i

schema_field_type s i

schema_field_offset s i

parse_def_fields schema buf off n i acc

Build a schema by reading num_fields field-definition triples from buf
starting at off. Returns [schema, total_body_size].

parse_def buf off has_dev

Parse a definition message starting at off (one byte past record header).
has_dev = 1 if record header bit 5 set. Returns [schema, after_off].

fit_decode_field buf off sz base_type arch

state_new _

state_observe state schema buf record_off

Look at a decoded data message of global type g and pull out the
well-known fields (file_id type, session totals, record timestamp, etc.).
schema = the def schema; record_off = first byte of data body.

state_observe_file_id state schema buf rec_off nf arch i

state_observe_session state schema buf rec_off nf arch i

state_observe_record state schema buf rec_off nf arch i

fit_walk fit table state

fit_summarize fit

stdlib/fit_emit.rail

fe_new capacity

fe_buf s

fe_off s

fe_cap s

fe_write_u8 s v

fe_write_u16_le s v

fe_write_u32_le s v

fe_write_bytes_loop s bytes i n

fe_write_bytes s bytes n

fe_emit_header s placeholder_data_size

fe_patch_data_size s data_size

Patch the data_size field (offset 4-7) after the body has been written.

fe_emit_def_field_loop s fields

fe_emit_def s local_id global_id arch fields

fe_emit_data s local_id payload n

fe_emit_trailing_crc s value

fe_finalize s

fe_field fdef sz base_type

fe_to_hex s

fe_to_hex_loop buf n i acc

fe_save s path

Write the byte buffer to disk via shell + xxd (avoids NUL-truncation
in write_file).

stdlib/float_arr.rail

fam_go fn a out i n

float_arr_map fn a

float_arr_map fn a : apply fn elementwise, returning a fresh same-length array.

fam2_go fn a b out i n

float_arr_map2 fn a b

float_arr_map2 fn a b : apply binary fn elementwise over two same-length arrays.

fas_go a n i acc

fas_go: reduce-add over the array. acc is a float (threads via ordinary
recursion -- float accumulators are not loop-optimized, which is correct here;
depth = array length). Mirrors fmv_dot's accumulator convention.

float_arr_sum a

float_arr_sum a : sum of all elements. The scalar readout the P3 V-series AD
detection keys on (f ... = float_arr_sum (float_arr_map g ...)); d(sum)/dx_i = 1.

fmv_dot w xv n j i acc

fmv_dot: row j dotted with the input vector. acc is a float (threads via
ordinary recursion -- float accumulators are not loop-optimized, which is
correct here; depth = n, the input width).

fmv_go w xv out n j z

float_mat_vec w xv

float_mat_vec w xv : (out x n) flat matrix times length-n vector -> length-out
vector. z_j = sum_i w[j*n + i] * xv_i. n = len xv ; out = len w / n.

fmvt_acc w ctz n out i j acc

fmvt_acc: column i of w^T dotted with ctz (sum over the out rows).

fmvt_go w ctz n out i r

float_mat_vec_t w ctz

float_mat_vec_t w ctz : transpose-matvec. r_i = sum_j ctz_j * w[j*n + i].
out = len ctz ; n = len w / out. Returns the length-n input-space gradient.

fo_inner a b nb j i r

fo_inner: fill row j of the outer product (a_j scaled across all of b).

fo_go a b na nb j r

float_outer a b

float_outer a b : flat row-major outer product. r[j*nb + i] = a_j * b_i.
Length na*nb -- the natural shape of the matvec weight gradient outer(ct,x).

fd_go a b n i acc

fd_go: dot accumulator (float threads via ordinary recursion).

float_dot a b

float_dot a b : scalar inner product sum_i a_i * b_i. The generalized readout
L = float_dot y g treats g as a fixed upstream cotangent on the net output y;
its VJP wrt y is g, so net__rgrad seeds __cty_i = g_i * __gy. With g = ones this
reduces to float_arr_sum (the proven slice-5b readout), so it strictly generalizes.

stdlib/fmt.rail

format template values

Printf-style formatting (simplified)
Supports: {} for positional replacement
Usage: format "Hello, {}! You have {} items." ["world", "42"]

format_acc cs vals acc

zero_pad n width

Left-pad number with zeros

repeat_char c n

rpad s width

Right-pad string with spaces to width

lpad s width

Left-pad string with spaces to width

unlines xs

Join with newlines

unwords xs

Join with spaces

indent n text

Indent each line by n spaces

format_table rows

Simple table formatting
rows: list of lists of strings
Pads each column to max width

format_row row widths

col_widths rows

merge_widths acc row

max_int a b

stdlib/gcd.rail

gcd_parse_uint_acc cs acc

gcd_parse_uint s

gcd_file_size path

gcd_hex_digit c

gcd_hex_decode_n cs arr i n

gcd_read_bin path

gcd_le16 buf off

gcd_le32 buf off

gcd_load path

gcd_buf g

gcd_total g

gcd_version g

gcd_valid g

gcd_record_name id

gcd_hex4 n

gcd_idx cs i

gcd_ascii_acc buf off n i acc

gcd_ascii buf off n

gcd_walk_loop g off i fw_total

gcd_walk g

gcd_print_record buf id rlen body_off i

One line per record; light interpretation for known types.

gcd_hex2 n

gcd_hex8 n

gcd_hex_n_acc buf off n i acc

gcd_hex_n buf off n

gcd_descr_type_str_acc buf off n i acc

gcd_descr_type_str buf off n

gcd_extract_streams g out_prefix

gcd_collect_loop g off ids lens count_arr

gcd_accum ids lens count_arr id rlen

gcd_find_id ids cnt id i

gcd_print_streams ids lens cnt i

stdlib/hash.rail

hash_buckets

hash_mask

djb2 s

DJB2 string hash. Pure multiply + add. No bitwise ops needed.

djb2_acc cs h

ht_idx key

set_new _

set_add s key

Note: skips duplicate-check on insert. For loading deduplicated sources
like oisd this is safe and ~O(bucket) faster per insert. Callers that
need dedup semantics should call set_contains first or use a wrapper.

set_contains s key

set_member_in bucket key

ht_new _

ht_put ht key value

ht_get ht key

ht_has ht key

ht_find_pair pairs key

ht_pair_exists pairs key

ht_remove_pair pairs key

stdlib/heap.rail

heap_swap a i j

swap slots i and j of the backing array.

heap_sift_up a i

sift-up: bubble the value at slot i toward the root while it is smaller than
its parent. Single-arg loop on i; parent index is a let-binding.

heap_smaller_child a n i

pick the smaller of node i and its two children (returns the index to swap
with, or i itself when i is already <= both children / has no children).

heap_sift_down a n i

sift-down: push the value at slot i toward the leaves while a child is
smaller. Single-arg loop on i; child indices live inside heap_smaller_child.

heap_new cap

heap_new cap : empty min-heap with capacity for cap ints. Slot 0 = size.

heap_size h

heap_size h : number of elements currently in the heap.

heap_peek h

heap_peek h : the minimum element, or 0 when the heap is empty.

heap_push h v

heap_push h v : insert v and restore the heap property. Returns h.

heap_pop h

heap_pop h : remove and return the minimum. Returns 0 if empty. The last
element is moved to the root and sifted down to restore the invariant.

heapify_go h xs

heapify_from_list xs : build a heap holding every int in xs (insert each).

heapify_from_list xs

stdlib/hkdf.rail

hkdf_extract salt salt_len ikm ikm_len

HKDF-Extract is trivially HMAC(salt, IKM). If salt is empty, RFC 5869
§2.2 says use a 32-byte all-zero salt.

hkdf_expand_t prk info info_len prev prev_len counter

hkdf_expand_loop prk info info_len okm out_off remaining counter prev prev_len

hkdf_expand prk info info_len length

hkdf salt salt_len ikm ikm_len info info_len length

Convenience: one-shot HKDF = Extract then Expand.

stdlib/hmac.rail

hmac_normalize_key key key_len

sha256_bytes bytes n

hmac_xor_block kblock pad_byte dst

hmac_xor_block_at kblock pad_byte dst i

hmac_sha256 key key_len msg msg_len

hmac_sha256_str key msg

hmac_sha256_hex key msg

stdlib/http.rail

parse_request_line line

Parse HTTP request line: "GET /path HTTP/1.1" -> (method, path, version)

parse_headers lines

Parse HTTP headers from lines -> list of (name, value) pairs

parse_request raw

Parse full HTTP request -> (method, path, headers, body)

find_body lines

get_header headers name

Get header value by name

response status_code status_text content_type body

Build HTTP response

response_ok body

Common responses

response_json body

response_404

response_500 msg

stdlib/http_client.rail

connect_tcp ip_str port

send_all fd buf

send_all_loop fd buf off total

recv_all fd

recv_all_loop fd buf acc

bytes_to_str buf off n acc

Copy bytes [start .. start+n) out of buf into a Rail string.
Uses char_from_int + join. O(N) work, O(N²) alloc; fine for responses up to ~64KB.

parse_response raw

---- response parsing ----
Find the status from "HTTP/1.1 200 OK", crlf, "..." and the body after ", crlf, "", crlf, ".

parse_int_s s

Dedicated int parser (socket.rail has one too, but we stay self-contained).

pi_acc cs acc

crlf _

CRLF must be synthesized at runtime because Rail's string literal parser
doesn't escape \r (known bug). We build CR and LF via char_from_int.

crlf_crlf _

find_body_after_blank raw

Split off body after the first empty line (CRLF CRLF separator).

is_chunked_resp raw

Detect HTTP/1.1 Transfer-Encoding: chunked in response headers.

parse_hex s

Parse a hex string (e.g. "1ce") into an int.

parse_hex_acc cs acc

decode_chunked body

Decode HTTP/1.1 chunked-transfer body. Format:
\r\n \r\n \r\n ... 0\r\n\r\n

dechunk_loop body pos acc

has_host_header hdrs

Return true if any header in the list starts with "Host:".

build_request method ip port path ctype body extra_hdrs

http_request method ip port path ctype body extra_hdrs

http_get ip port path

http_post ip port path ctype body

http_post_json ip port path body

http_get_hdr ip port path hdrs

http_post_hdr ip port path ctype body hdrs

stdlib/http_server.rail

http_serve port handler

Start HTTP server on given port with handler function.
handler: string → (int, string, string) [request → (status, content_type, body)]

write_handler_wrapper handler

Write a Rail program that reads the request and calls the handler

http_parse_path req

Parse HTTP request line: "GET /path HTTP/1.1\r\n..." → path

http_response status content_type body

Format HTTP response

serve_static dir req

Simple static file server. Rejects any path containing ".." (and
the rare backslash variant seen in some crawlers) before
concatenating with the server root, so GET /../../etc/passwd
style traversals can't escape dir.

guess_content_type path

Content type from file extension

quick_serve port handler_src

Quick serve: compile a handler and start the server
handler_src: Rail source code for the handler program

http_serve_raw port

Raw server loop: assumes /tmp/rail_http_handler exists

stdlib/https_client.rail

hc_read_random n

hc_arr_to_cbuf arr p n

hc_a2c_at arr p i n

hc_cbuf_to_arr p arr n

hc_c2a_at p arr i n

hc_send_bytes fd arr n

hc_recv_into fd p off want

hc_recv_exact fd n

hc_recv_exact_loop fd p off want

hc_scratch_copy src dst src_off dst_off n

hc_read_record fd

hc_rec_fail _

hc_connect_tcp ip port

hc_x25519_base _

hc_gen_keypair _

hc_scan_finished buf n off

hc_flight_append acc acc_len frag frag_len

hc_read_server_flight fd key iv

hc_rsf_loop fd key iv seq acc acc_len

hc_rsf_err _

hc_exchange_hello fd ch_msg ch_msg_len priv

hc_xh_fail _

hc_crlf _

hc_build_req host path

hc_buf_to_chars buf i acc

hc_bytes_to_str buf n

hc_bytes_to_str_loop arr off n acc

hc_bytes_to_str_loop retained as an O(n²) shim for any external
caller that depends on the old signature.

hc_recv_response fd key iv seq acc

Stream-accumulating response reader. Fragments are kept as raw
byte-array chunks (each paired with its length) in a cons list; on
terminate we allocate one buffer of the exact total size and copy
each chunk in place, then convert bytes→string once. Overall work
is O(body_len) instead of the old O(body_len²).

hc_recv_loop fd key iv seq chunks total

hc_recv_finalize chunks total

hc_copy_chunks buf chunks write_pos

hc_parse_status raw

hc_parse_int s

hc_pi cs acc

https_get_unsafe_noverify host ip port path

https_get_on_fd fd host path

https_get_post_hello fd host path priv ch_msg ch_msg_len xh

https_get_app_phase fd host path priv ch_msg ch_msg_len sh_msg sh_msg_len fb fl c_hs

hc_err msg

https_get_url_unsafe_noverify url

https_get_url_dispatch parts

https_parse_url url

Parse "https://host[:port][/path]" → [host, port, path, "", valid].
Only supports https scheme; http or anything else returns valid=0.

https_url_fail _

hc_build_post host path ctype body hdrs

https_post_unsafe_noverify host ip port path ctype body hdrs

Top-level POST. Leaf-signature-only — same threat model as
https_get_unsafe_noverify. Prefer https_post from
stdlib/https_strict.rail for chain-to-root trust walking.

https_post_on_fd fd host path ctype body hdrs

https_post_post_hello fd host path ctype body hdrs priv ch_msg ch_msg_len xh

https_post_app_phase fd host path ctype body hdrs priv ch_msg ch_msg_len sh_msg sh_msg_len fb fl c_hs

hc_build_put host path ctype body hdrs

https_put_app_phase fd host path ctype body hdrs priv ch_msg ch_msg_len sh_msg sh_msg_len fb fl c_hs

https_post_url_unsafe_noverify url ctype body hdrs

URL-form POST: https_post_url_unsafe_noverify url content_type body extra_hdrs.
Same threat model as https_post_unsafe_noverify — leaf-only.

https_post_url_dispatch parts ctype body hdrs

hc_stream_fail _

hc_arr_off_to_cbuf arr arr_off p n

Copy n bytes of Rail int-array arr starting at arr_off into the
C-buffer p starting at offset 0. Used for fwrite calls in the
streaming body emitter.

hc_aoc_loop arr arr_off p p_off n

hc_scan_blank_line buf start n

Scan buf[start..n) for the byte sequence 13 10 13 10 (\r\n\r\n).
Returns the index of the first byte AFTER the sequence, or -1.

hc_arr_to_str arr n

Convert N bytes of a Rail int-array (only used on small header
buffers, capped at 64 KB) into a Rail string for header parsing.

hc_parse_hex_str s

Parse a hex-digit-only string into an integer. Tolerant: stops at
the first non-hex char. Used for chunked-transfer chunk-length lines.

hc_phs_loop cs acc

hc_is_chunked_resp raw

Detect Transfer-Encoding: chunked in a header string (case-insensitive
on the value).

hc_read_response_headers fd key iv

── Header phase: read TLS records until we have the full HTTP header.
Returns a 5-element array on success:
[header_str, next_seq, body_initial_arr, body_initial_len, valid]
On failure: valid=0.

hc_rrh_loop fd key iv seq buf buf_len

hc_rrh_fail _

hc_body_state_init sha_st fp cbuf cbuf_size chunked

hc_emit_raw_bulk frag off n body_state

Bulk-write n bytes from frag[off..off+n) to disk + sha update.

hc_emit_body frag off n body_state

Top-level chunk emitter — dispatches on chunked flag.

hc_emit_chunked_dispatch frag off n body_state

hc_chunk_read_len frag off n body_state

hc_chunk_read_body frag off n body_state

hc_chunk_read_trailer frag off n body_state

hc_stream_body_loop fd key iv seq body_state

Body recv loop: reads TLS records, decrypts, feeds into emitter.

https_get_to_file_unsafe_noverify host ip port path out_path

hc_stream_on_fd fd host path out_path

hc_stream_post_hello fd host path priv ch_msg ch_msg_len xh out_path

hc_stream_app_phase fd host path priv ch_msg ch_msg_len sh_msg sh_msg_len fb fl c_hs out_path

hc_stream_recv fd key iv out_path

https_get_url_to_file_unsafe_noverify url out_path

URL convenience wrapper.

stdlib/https_session.rail

hss_fail msg

hs_valid s

Accessors

hs_fd s

hs_err s

hs_host s

hss_open_core fd host store strict

hss_open_after_flight fd host store strict priv ch_msg ch_msg_len sh_msg sh_msg_len fb fl c_hs

Split to keep each function body under the nesting/complexity Rail's
parser likes. Chain gate is a separate if-else level above the FSM.

hss_build_handle fd host c_hs fsm

https_session_open host ip port

https_session_open_strict host ip port

hss_crlf _

hss_build_get host path

hss_hdr_list_to_str hdrs

hss_build_post host path ctype body hdrs

hss_send_one fd pending pending_n wire wire_n

hss_recv fd s_key s_iv seq

hss_recv_loop fd s_key s_iv seq chunks total header_end frame_kind frame_arg

frame_kind: 0 = header not parsed yet, 1 = content-length (frame_arg = CL),
2 = chunked (frame_arg unused), 3 = read-until-close

hss_recv_step fd s_key s_iv seq chunks total header_end frame_kind frame_arg

hss_recv_app fd s_key s_iv seq chunks total header_end frame_kind frame_arg frag flen

Append fragment, then — if header end not yet known — try to locate
\r\n\r\n in the assembled buffer so far and parse framing.

hss_chunks_to_buf chunks total

Materialise chunks-list (cons in reverse order) into one flat buffer.

hss_locate_header_end chunks total

Return index of "\r\n\r\n" start in the assembled buffer, or -1.

hss_find_crlf2 buf n i

hss_parse_framing chunks total hend

After \r\n\r\n seen at index hend, look at headers for Content-Length
or Transfer-Encoding: chunked. Case-insensitive match on field names.
Returns [kind, arg].

hss_find_cl hdrs

Case-insensitive "content-length:" lookup; return the numeric value or -1.

hss_is_chunked hdrs

hss_to_lower s

hss_tl cs acc

hss_pi_skip_ws cs seen_digit acc

Parse int after optional whitespace, stop at CR/LF/non-digit.

hss_scan_chunked_done chunks total hend

Scan the assembled body for the chunked terminator "0\r\n\r\n" (with
optional trailers — we accept "0\r\n" followed by "\r\n" with anything
in between that doesn't itself contain "\r\n\r\n" … but most servers
emit the plain form, so we look for \r\n0\r\n\r\n and also the initial
case where the body starts with 0\r\n\r\n).

hss_find_chunk_end buf n start

hss_is_crlf buf n i

hss_recv_finalize chunks total seq ok

hss_split_status_body buf total

hss_clear_pending s

hss_advance_seqs s new_c new_s

hss_invalidate s msg

https_session_get s path

https_session_post s path ctype body hdrs

hss_issue s req_str

fst p

snd p

https_session_close s

stdlib/https_strict.rail

hs_trust_store_path _

hs_chain_ok store fb

hs_drive_get fd host path store

Strict driver: mirrors https_get_on_fd but gates on chain walk
before the FSM. Single flat function body to keep compile cost down.

hs_drive_post fd host path ctype body hdrs store

hs_drive_put fd host path ctype body hdrs store

https_get_with_store store host ip port path

https_get host ip port path

https_get_url url

https_post_with_store store host ip port path ctype body hdrs

https_post host ip port path ctype body hdrs

https_post_url url ctype body hdrs

https_put_with_store store host ip port path ctype body hdrs

https_put host ip port path ctype body hdrs

https_put_url url ctype body hdrs

https_get_strict host ip port path

https_get_url_strict url

https_post_strict host ip port path ctype body hdrs

https_post_url_strict url ctype body hdrs

stdlib/ijit.rail

ijit_compile msl

Compile MSL source -> pipeline id. Writes the int JIT tmp file the
dylib reads, then JIT-compiles.

ijit_run kid a b o sizeA sizeB sizeO nthreads

Dispatch a shaped kernel. Thin pass-through; kept so callers read as
ijit_run (mirrors jit_run_*), and so a future arg-packing change stays
local to this module.

ibuf_upload arr n

ijit_run_wx kid wid x o sizeX sizeO nthreads

stdlib/jit.rail

jit_compile_rmsnorm_qkv _

Compile the fused rmsnorm+QKV kernel. Source is emitted by jit_emit.

jit_run_rmsnorm_qkv kid x_arr g_arr wq_arr wk_arr wv_arr qkv_packed seq d

Dispatch. Packs SEQ+D into one int (Rail FFI's 8-arg cap).

jit_compile_silu_hadamard _

Compile the fused silu+hadamard kernel. Source is emitted by jit_emit.

jit_run_silu_hadamard kid g_arr u_arr out_sig n

Dispatch. out_sig is a packed buffer of 2*N doubles: h_act in
[0, N), sigmoid(gate) in [N, 2N).

jit_compile_rmsnorm_qkv_shaped seq d

───────────────────────────────────────────────────────────────────────
Phase 2.9 v1.5 — shape-specialized compile entry points. Take concrete
dimensions, emit shape-baked MSL via jit_emit.rail's *_shaped emitters,
JIT-compile. Returns a kid specialized to those dims; Metal can fully
unroll the D-loops. Dispatch wrappers (jit_run_*) stay shape-agnostic.
───────────────────────────────────────────────────────────────────────

jit_compile_silu_hadamard_shaped n

stdlib/jit_emit.rail

emit_msl_rmsnorm_qkv _

───────────────────────────────────────────────────────────────────────
Generic-shape emitters (Phase 2.5). Kept for v5.1.0 callers and for
the byte-verification fixtures.
───────────────────────────────────────────────────────────────────────

emit_msl_silu_hadamard _

emit_msl_rmsnorm_qkv_shaped seq d

───────────────────────────────────────────────────────────────────────
Phase 2.9 v1.5 — shape-specialized emitters. Each call returns MSL
where the dimensions are top-level constant uint declarations
instead of buffer arguments. Metal can fully unroll the D-loops.
Different (SEQ, D) produces different bytes, validated in the
jit_emit_shaped_smoke fuzz check.
───────────────────────────────────────────────────────────────────────

emit_msl_silu_hadamard_shaped n

emit_msl_idot_shaped rows d

───────────────────────────────────────────────────────────────────────
Integer-exact kernels (attested-GPU track, 2026-07-03).
int32 inputs, int64 accumulate -- no floats anywhere, so the result
is the SAME BITS on every device that runs it. That is what lets a
GPU dispatch join the attestation chain: the CPU reference (plain
Rail 63-bit integers) and the emitted GPU kernel are twin
implementations of one exact computation, and agreement is
byte-equality, not "close enough".
Caller contract: |A[i]|, |B[j]| < 2^15 and d <= 2^16 keeps every
accumulator below 2^46 -- exact in Metal's int64 AND in Rail's
63-bit tagged ints, with headroom.
───────────────────────────────────────────────────────────────────────

emit_msl_fx_matvec_shaped rows d shift

Fixed-point matvec with mul_shr rescale (attested-GPU track, act 2).
Mirrors Rail's mul_shr primitive EXACTLY: full 128-bit product
(mul = low 64, smulh/mulhi = high 64), logical-shift the low half,
arithmetic-shift-combine the high half. Same formula as the ARM64
emit in tools/compile.rail (search "mul_shr"), so CPU twin and GPU
twin agree bit-for-bit even when a*b overflows int64 -- the
massive-activation regime that motivated mul_shr in the first
place (t173: 3.2e11 * 3.2e11 >> 24).
Caller contract: 1 <= shift < 64, and |mul_shr result| * d must fit
int64 (and Rail's 63-bit ints) for the row accumulator.

emit_msl_fx_rmsnorm_shaped rows d

Fixed-point RMSNorm (attested-GPU track, act 3).
The last transformer-layer op that needed a discovery step: rsqrt.
Computed as a pure-integer binary search -- find the largest r with
mul_shr(mul_shr(r,r,S), v, S) <= ONE -- the same move as Path-B's
bx4_fxsqrt. Every operation is mul_shr / add / compare / shift, so
a CPU twin using the mul_shr primitive lands on identical bits.
Q32.32 fixed point (S=32). Caller contract: |x| < 2^34, |g| < 2^33,
d <= 2^12; the search bound hi=2^45 keeps every intermediate inside
int64. One thread per row; the search is ~45 exact iterations.

emit_fx_body u j

Unroll-parameterized fx matvec (attested-GPU track, act 5: kernel
tournament). Integer addition is associative, so EVERY unroll
factor produces byte-identical output -- the twin oracle stays
exact while an optimization search explores variants. (A float
kernel can never promise that.) Caller: d divisible by unroll.

emit_msl_fx_matvec_unrolled rows d shift unroll

emit_msl_fx_softmax_shaped rows d

Fixed-point softmax (attested-GPU track, act 7 -- the LAST
discovery item).
exp is the one transcendental a transformer needs (softmax AND
silu). Fixed-point spec, exact on both twins:
range-reduce: n = (-x)/LN2 (trunc), r = x + n*LN2 in (-ln2, 0]
exp(x) = taylor(r) >> n -- the 2^-n scaling is a pure shift
taylor: 1 + r + r^2/2 + ... + r^7/5040, every power via mul_shr,
every divisor a truncating int64 division -- semantics identical
on ARM64 sdiv and Metal long division.
Softmax rows: m = max, e_j = fx_exp(s_j - m) (arg <= 0 always),
p_j = (e_j << 16) / (sum >> 16) ~ e_j * 2^32 / sum. sum >= ONE
(the max element contributes exp(0) = exactly ONE), so no div-by-0.
Q32.32. Approximation error ~1e-6 vs real exp; twin agreement is
exact by construction regardless -- the spec IS the computation.

jit_isqrt_walk n i

integer sqrt for emit-time constants (d <= 2^12, trivial walk)

jit_isqrt n

emit_msl_fx_attention_shaped seq d

Fused fixed-point single-head attention (attested-GPU track, act 8:
THE COMPOSITION). No new math -- rmsnorm, matvec, softmax are the
proven acts 2/3/7 device functions, fused into one kernel:
xn = rmsnorm(x) -> q,k = Wq,Wk projections -> scores/sqrt(D)
-> softmax -> out = x + sum_j p_j * (Wv . xn_j) (residual)
Params ride in ONE packed buffer (X | G | WQ | WK | WV), the same
shape as a real weights blob. One thread per query position.
Bounds (|x|<2^29, |g|,|W|<2^27, d<=64, seq<=16) keep every
accumulator under 2^56 -- inside int64 AND Rail's 63-bit ints, so
twin agreement is guaranteed worst-case, not statistically.

emit_msl_fx_ffn_shaped seq d

Fused fixed-point SwiGLU FFN (attested-GPU track, act 9). No new
discovery: silu is x * sigmoid(x), sigmoid reduces to fx_exp (act
7), the rest is matvec (act 2) + rmsnorm (act 3). Fused:
xn = rmsnorm(x) -> gate = WG.xn, up = WU.xn
act = silu(gate) * up -> down = WD.act -> out = x + down
Hidden dim = D (square FFN keeps the packed layout simple; the
twin-exactness point is identical at h != D).
Params packed: X | G | WG(DxD) | WU(DxD) | WD(DxD).
sigmoid is the ONLY division; done overflow-safe as
(num << 16) / (denom >> 16) -- the act-8 lesson: any path that can
overflow int64 is where Rail's 63-bit and Metal's 64-bit wrap
diverge. This form keeps every intermediate < 2^48.

emit_msl_fx_sgd_step d lr_num

Fused fixed-point SGD step (attested-GPU track, act 10: the BACKWARD
pass -- attested TRAINING). One linear layer y=W.x, MSE loss, one
SGD update, all mul_shr/add/shift (no division -> no rounding
trap). Per review: bounds EXERCISE the 128-bit path (products
>2^63) while results stay <2^62; LR baked at emit time is bound via
the msl hash in the record. One thread per row i:
y_i = sum_j mul_shr(W_ij, x_j, 32) (forward, inline)
g_i = (y_i - t_i) << 1 (MSE grad, x2 = shift)
W'_ij = W_ij - mul_shr(LR, mul_shr(g_i, x_j, 32), 32)
Params packed W(d*d) | x(d) | t(d); output = updated W (d*d).
Every transformer weight update is this shape (upstream grad
replaces the MSE term); this proves the training step stays exact
and attestable. lr_num is the Q32.32 learning-rate constant.

emit_msl_fx_layernorm24_shaped rows d

Fixed-point GPT-style LayerNorm (attested-GPU track, act 11 -- the
REAL-MODEL wiring). The 138M attested base model is GPT-2 family
(LayerNorm+bias, GELU, learned pos-emb) at F=24, NOT the Llama
family (RMSNorm/SwiGLU, F=32) of acts 3/8/9. This emitter ports
the model's ACTUAL gp_layernorm (pathb_gpt_forward.rail) exactly:
mean-subtract, population variance, fxrsqrt (128-bit binary-search
sqrt at F=24 with the exact fractional tie-break), affine WITH bias.
S=2^24, eps=168 (round(1e-5 * 2^24)). One thread per row.
Params packed X(rows*D) | gamma(D) | beta(D).

emit_msl_fx_matmul24_shaped rows k

GPT matvec with idot semantics (attested-GPU track, act 12). The
138M model's projections use idot = (sum_i W_i*x_i) >> 24 -- a
FULL-precision accumulate then ONE arithmetic shift (NOT per-element
mul_shr). GPU: int64 accumulate, then >>24 (arithmetic floor,
matches Rail's smulh-based shift). Bound: the raw sum must stay
< 2^63; for d<=1024 with normalized activations it stays ~2^55.
Packed W(rows*K) | x(K). One thread per output row.

emit_msl_fx_matmul24_batched rows k seqL

BATCHED matvec (act 20 -- the perf item). Same idot math as fx_matmul but
Y[t,o] = (sum_i W[o,i]*X[t,i])>>24 for ALL L tokens in ONE dispatch: the
weight W is uploaded ONCE (packed P = W(ROWS*K) | X(L*K)) instead of
re-uploaded per token. Bit-for-bit identical to L separate fx_matmul
dispatches; kills the per-token weight-reupload bottleneck. One thread per
(t,o) output; O laid out [t][o] = t*ROWS + o (same as the per-token path).

emit_msl_fx_matmul_bwd_batched out k seqL

BATCHED linear-backward (act 20). dx[t,i] = (sum_o W[o,i]*dy[t,o])>>24 for
all L tokens in one dispatch. Packed P = W(OUT*K) | dy(L*OUT), OFF_DY=OUT*K.
One thread per (t,i); O laid out [t][i] = t*K + i. Bit-for-bit identical to
L separate fx_matmul_bwd dispatches.

emit_msl_fx_matmul24_wx rows k seqL

PERSISTENT-WEIGHT variants (act 23): W in buffer(0) (resident, uploaded once
via tgl_ibuf_upload), X/dy in buffer(1) (small, uploaded per dispatch).
Same math as the _batched kernels; bit-for-bit identical. Eliminates the
per-dispatch re-pack + re-upload of the (unchanging) weight.

emit_msl_fx_matmul_bwd_wx out k seqL

emit_msl_fx_gelu24_shaped n

GPT GELU (tanh-approx) device function + elementwise kernel (act 12).
Faithful MSL port of bx4_gelu -> bx4_tanh -> bx4_fxexp -> bx4_exp_poly
(bx_fixed.rail, F=24). Uses truncating / (NOT >>) everywhere to
match Rail's division rounding exactly on negatives.

emit_msl_fx_attn24_shaped seqL qdim nh hd

GPT causal multi-head attention, F=24 (attested-GPU track, act 13).
Faithful port of the model's pf_heads: per query token i, per head h,
causal scores (idot(Q_h,K_h)>>24, then *scaleq trunc-div, mask j>i to
-1000*S), softmax over positions (rowmax, fxexp, normalize), then
weighted-V (sum_j P[j]*V_h >>24). scaleq = 2097152 = 1/sqrt(64) in
F=24. Q|K|V packed (each L*QDIM, group=1 so kvdim=qdim). One thread
per query token. L small (<=64) -> thread-local score/exp arrays fit.

emit_msl_fx_attn24_par seqL qdim nh hd

Attention forward, PARALLELIZED per (query, head) (act 22 -- the perf item).
Identical math to fx_attn but ONE thread per (query i, head h) = L*NH threads
instead of L threads each looping all NH heads. On the model shape (L=9,
NH=12) that is 108 threads vs 9 -- 12x more GPU occupancy for the single most
expensive op. Bit-for-bit identical (each thread does the SAME per-(i,h)
sequential work). gid -> i = gid/NH, h = gid%NH.

emit_msl_fx_matmul_bwd_shaped out k

dx = W^T . dy ; W is [OUT x K] row-major, packed W(OUT*K) | dy(OUT).
One thread per input index i. Same idot semantics as the forward.

emit_msl_fx_gelu_grad24_shaped n

gelu'(x), F=24 -- faithful port of bx4_gelu_grad -> bx4_tanh ->
bx4_fxexp. All truncating /. Elementwise, one thread per element.

emit_msl_fx_wupdate24_shaped out k lr_num

Fused weight-gradient + SGD update for a linear layer, F=24 (attested-
GPU track, act 16 -- completes the training step). For y = W.x with
upstream dy (out), the weight gradient is the outer product
dW[o,i] = dy[o]*x[i] and the SGD step is W'[o,i] = W[o,i] - lr*dW[o,i].
Both products go through mul_shr_gpu (the exact 128-bit-reconstruct port
of Rail's mul_shr, already twin-verified in acts 10/11), so the result
is bit-identical to the CPU twin's mul_shr on every sign. This is the
F=24 adaptation of the Q32 act-10 sgd_step, split out as update-only so
it composes with the separately-attested matmul24 forward and
matmul_bwd (dx) rather than re-deriving y internally.
Packed P = W(OUT*K) | x(K) | dy(OUT). One thread per output row; each
writes its whole K-length weight row. FX_LR is the learning rate in F=24.

emit_msl_fx_layernorm_bwd_shaped rows d

LayerNorm-backward, F=24 (attested-GPU track, act 17 -- the first of the
two backward ops the full 16-layer backprop needs). Input gradient dx for
GPT-style LayerNorm y = gamma*xhat + beta, xhat = (x-mean)/sqrt(var+eps)
(mean-subtract + POPULATION variance, matching gp_layernorm exactly).
The standard row-local result:
dxhat_i = dy_i*gamma_i
dx_i = inv * (dxhat_i - mean(dxhat) - xhat_i*mean(dxhat*xhat))
Recomputes mean/var/inv/xhat/dxhat on the fly (NO d-length thread-local
arrays -- d=768 would blow the per-thread stack), so it is a pure one-
thread-per-row kernel like the forward. fxrsqrt24 = the exact 128-bit
binary-search rsqrt (same helper the forward uses). beta does not affect
dx. Packed P = X(rows*d) | gamma(d) | dy(rows*d). eps=168.

emit_msl_fx_attn_bwd_shaped seqL qdim nh hd

Attention-backward, F=24 (attested-GPU track, act 18 -- the last backward
op the full 16-layer backprop needs). dQ, dK, dV through the causal MHA
forward (emit_msl_fx_attn24_shaped / pf_heads):
O[i,c] = (sum_{j<=i} p[i,j]*V[j,c]) >> 24, p = softmax(scaled causal QK^T)
Backward (dm cancels in the softmax Jacobian, so no max-grad):
dp[i,j] = sum_c dO[i,c]*V[j,c] (>>24 per term)
dsc[i,j] = p[i,j]*(dp[i,j] - sum_k p[i,k]*dp[i,k]) (softmax Jacobian)
draw[i,j] = dsc[i,j]*SCALEQ/S (scale chain)
dQ[i,c] = sum_{j<=i} draw[i,j]*K[j,c] (>>24 per term)
dK[j,c] = sum_{i>=j} draw[i,j]*Q[i,c]
dV[j,c] = sum_{i>=j} dO[i,c]*p[i,j]
SCHEDULE-INDEPENDENT / bit-exact: one thread per position, each doing its
OWN sequential accumulation (dQ per query, dK/dV per key recompute every
query i>=j's softmax) -- no atomics, no cross-thread reduction. 3*L threads:
region gid/L in {0=dQ,1=dK,2=dV}, pos gid%L. P = Q|K|V, U = dO,
O = dQ|dK|dV (each L*QDIM). Thread-local arrays sized L,HD -> keep L modest.

emit_msl_fx_attn_bwd_par seqL qdim nh hd

Attention-backward, PARALLELIZED per (region, pos, head) (act 24 -- perf).
Identical math to fx_attn_bwd but ONE thread per (region, position, head) =
3*L*NH threads instead of 3*L threads each looping all NH heads. On the
model shape that is 3*9*12=324 threads vs 27 -- 12x occupancy for the most
expensive backward op (it recomputes a softmax per output). Bit-for-bit
identical. gid -> region = gid/(L*NH), rem = gid%(L*NH), pos = rem/NH, h = rem%NH.

emit_msl_fx_elemul24 n

Elementwise fixed-point multiply O[i]=A[i]*B[i]>>24 (A=buf0, B=buf1).
Used for dh1 = dh2 (x) gelu'(h1) in the MLP backward.

emit_msl_fx_wgrad24_shaped out k seqL

Weight-gradient accumulate over tokens: dW[o,i] = sum_t dy[t,o]*x[t,i] >>24.
Packed P = dy(L*OUT) | x(L*K); one thread per output row o. This is the
multi-token weight gradient (the act-16 wupdate was single-token fused).

emit_msl_fx_sgd_apply n lr_num

Elementwise SGD apply: O[i] = W[i] - lr*dW[i]>>24 (W=buf0, dW=buf1).

emit_msl_fx_adam_step n b1 b2 eps lr

F=24 AdamW update, twin of the CPU fx_adam (fx_adam_gate.rail). One thread per
weight element. Bias-correction c1,c2 (=1-beta^t) change per step, so they ride
in the packed buffer rather than being baked; lr/beta1/beta2/eps are constants.
fx_sqrt24 = pure-integer binary search matching Path-B bx4_fxsqrt bit-for-bit
(same move as fx_rsqrt above), so a CPU twin using mul_shr lands identical bits.
P (buffer0), size 3N+2: W[0..N) | m[N..2N) | v[2N..3N) | c1@[3N] | c2@[3N+1]
G (buffer1), size N: gradient (already summed; caller means it by scaling lr)
O (buffer2), size 3N: W'[0..N) | m'[N..2N) | v'[2N..3N)
CRITICAL (measured in fx_adam_gate): EPS must be ~1e-3 (16777) in F=24, not the
fp32-default 1e-8 -- else v=g^2 underflows to 0 and the update mhat/eps explodes.

node_seq node

───────────────────────────────────────────────────────────────────────
Shape extraction helpers. Pull SEQ/D out of a JitNode's .shape field.
The rmsnorm node's shape is [seq, d]. The silu node's shape is [seq, d_ff];
its N = seq * d_ff.
───────────────────────────────────────────────────────────────────────

node_d node

node_n node

emit_msl_for_match _ m

───────────────────────────────────────────────────────────────────────
Pattern dispatch — Phase 2.5 generic stubs.
───────────────────────────────────────────────────────────────────────

emit_msl_for_match_shaped tape m

───────────────────────────────────────────────────────────────────────
Phase 2.9 v1.5 — shape-aware dispatch. Looks up the matched node's
shape and emits specialized MSL.
───────────────────────────────────────────────────────────────────────

stdlib/jit_match.rail

match_kind m

find_matmul_children tape p i n acc

───────────────────────────────────────────────────────────────────────
Children search.
find_matmul_children tape p i n acc:
walk tape from index i to n, accumulating tape indices of "matmul"
nodes whose parents[0] == p. Returns the list in tape order.
───────────────────────────────────────────────────────────────────────

find_hadamard_child tape p i n

Returns -1 if no matching hadamard child found.

try_qkv_match tape rms_idx n acc

───────────────────────────────────────────────────────────────────────
Per-pattern emitters. Each appends 0 or 1 FuseMatch to acc.
───────────────────────────────────────────────────────────────────────

try_silu_match tape silu_idx n acc

walk_tape_loop tape i n acc

───────────────────────────────────────────────────────────────────────
Main walker.
───────────────────────────────────────────────────────────────────────

walk_tape tape

dump_match m

───────────────────────────────────────────────────────────────────────
Debug helper: print all matches one per line.
───────────────────────────────────────────────────────────────────────

dump_matches_loop ms

dump_matches ms

stdlib/jit_node.rail

jit_tape_new _

jit_tape_push tape node

jit_tape_get tape i

jit_tape_get_acc tape i cur

jit_nth n xs

Same as stdlib/tensor.rail's list_nth. Named jit_nth so consumers of
jit_node can avoid importing tensor.rail (which would re-introduce a
duplicate-symbol path through transformer for callers like
lm_v3_chunked_jit_long that import transformer directly).

traced_value t

traced_idx t

node_tag n

node_shape n

node_dtype n

node_parents n

jit_tape_dump_loop tape n i

jit_tape_dump tape

stdlib/jit_tape.rail

jit_leaf tape t dtype

Wrap an existing Tensor as a leaf TracedTensor. Pushes a "leaf" node
onto the tape that codegen treats as a kernel input buffer.

traced_rmsnorm tape x g dtype

traced_rmsnorm: trace + execute rmsnorm. Returns updated tape + a
TracedTensor for the y output (rstd is dropped for now; Phase 3 may
need it as a separate tape entry for backward).

traced_matmul tape a b dtype

traced_matmul: trace + execute (default f64) matmul.

stdlib/json.rail

json_skip_ws cs

Skip whitespace characters

json_parse_str cs acc

Parse a JSON string value (after opening quote)

json_num_char c

Recognize a character that can appear inside a JSON number token.
Accumulating the WHOLE token (digits, sign, decimal point, exponent)
is what lets json_float recover the real value -- the old version only
kept digits and '-', so "0.0003" / "1.5" / "1e5" silently truncated.

json_parse_num cs acc

Parse a JSON number token. Captures the full numeric lexeme so the
accessors can interpret it as either int (json_int) or float (json_float).

json_parse cs

Parse a JSON value, returns (value, remaining_chars)

json_str_to_int s

Pure-Rail string->int. to_int is float-only and silently returns 0
for plain integer strings.

json_str_to_int_acc cs acc sign

json_parse_array cs acc

Parse JSON array elements

json_parse_object cs acc

Parse JSON object key-value pairs

parse_json str

Parse a JSON string, returns a JSON value

json_get obj key

Get value from JSON object by key

json_lookup pairs key

json_str val

Get string value

json_int val

Get int value (truncated integer part of the number)

json_float val

Get float value. Re-parses the raw numeric token captured at parse
time, so fractional/exponent fields ("0.0003", "1.5", "1e5") survive.
Falls back to the integer slot promoted to float for older two-element
["num", n] values.

json_items val

Get array items

json_encode val

Serialize JSON value to string. Split into per-tag helpers because
a single if/then/else chain with let-bindings under several arms,
plus a multi-line lambda inside map, defeated the parser; flat
helpers parse cleanly and read better anyway.

json_encode_str val

json_encode_num val

json_encode_bool val

json_encode_arr val

json_encode_pair p

json_encode_obj val

drop n xs

Helper: drop first n elements from a list

stdlib/list.rail

add_fn a b

Helpers for fold

mul_fn a b

sum xs

Sum a list of integers

product xs

Product of a list of integers

take n xs

Take first n elements

drop n xs

Drop first n elements

zip xs ys

Zip two lists into pairs

nth n xs

Nth element (0-indexed)

any pred xs

Any element satisfies predicate

all pred xs

All elements satisfy predicate

flatten xss

Flatten list of lists

mapi f xs

Map with index

mapi_acc f xs i

sort xs

Sort (insertion sort — ascending, good enough for small lists)

insert x sorted

cmp tag a b

sort_by: insertion sort driven by a NAMED comparator passed as a
function-tag string. The tag is dispatched internally via match, so no
lambda or function value crosses an argument boundary (lambda-comparator
args can segfault). Built-in tags: "asc" (a<=b) and "desc" (a>=b).
cmp tag a b returns true iff a should come before b.

sort_by tag xs

insert_by tag x sorted

msort xs

msort: true merge sort, ascending, O(n log n) — for large lists.
The list is split into two halves by alternating elements
(msplit_a / msplit_b mutual recursion — a self-loop divide that
changes two accumulators would miscompile). Each half is sorted
recursively and the two sorted halves are merged.

msplit_a xs ls rs

Alternating split: even-index elements to ls, odd-index to rs.
Mutual recursion avoids the two-accumulator self-loop trap.

msplit_b xs ls rs

merge_sorted a b

Merge two ascending-sorted lists into one ascending-sorted list.

stdlib/llm.rail

llm_esc_bs s

JSON escape helpers (backslash must be first)

llm_esc_q s

llm_esc_nl s

llm_esc_cr s

llm_esc_tab s

llm_json_esc s

llm_mk_body sys_prompt user_prompt

Build the JSON request body

llm_call port sys_prompt user_prompt

Main LLM call function
port: port number as string (e.g. "8080")
sys_prompt: system prompt string
user_prompt: user prompt string
Returns: response content string

pll_write_reqs tmpdir sys users idx

pll_par_cmd port tmpdir idx

One worker's pipeline: curl → jq → perl cleanup. Reads request from
/req_.json, writes raw content to /resp_.txt,
then emits the cleaned content to stdout (which parallel_shell captures).

pll_build_cmds tmpdir port n idx acc

pll_extract_outs pairs acc

Extract the stdout from each [exit_code, stdout] pair returned by
parallel_shell. Preserves ordering. Drops the exit code (callers who
need it can use parallel_shell directly; for LLM text we treat a
non-zero exit as "empty response" via the captured stdout state).

parallel_llm_timeout_s

Per-worker timeout for LLM calls — large models under concurrent load
can legitimately take minutes. The default parallel_shell timeout of
60s TERM-killed 12/16 workers at N=16 on Qwen3.5-27B (verified
2026-04-20); 300s leaves headroom for even slow reasoning models.

parallel_llm_calls port sys users max_conc

stdlib/macho.rail

mh_magic_64

64-bit Mach-O magic, little-endian on disk.

cpu_type_arm64

CPU types

cpu_subtype_arm64_all

mh_execute

File types

mh_noundefs

Header flags

mh_dyldlink

mh_twolevel

mh_pie

mh_flags_pie_exec

Composite flag used by our exit-42 reference: 0x00200085

lc_segment_64

Load command codes

lc_symtab

lc_dysymtab

lc_load_dylinker

lc_uuid

lc_main

lc_build_version

lc_code_signature

vm_prot_none

VM protection flags

vm_prot_read

vm_prot_write

vm_prot_execute

vm_prot_rx

vm_prot_r

s_attr_pure_instructions

Section attribute flags for __text

s_attr_some_instructions

section_text_flags

platform_macos

Platform IDs for LC_BUILD_VERSION

pagezero_vmsize

Standard macOS layout

text_vmaddr

page_size

mw_u8 buf off v

Write one byte at off. Returns next offset.

mw_u16 buf off v

little-endian u16 (used by nlist_64 n_desc)

mw_u32 buf off v

Write 4 bytes little-endian.

mw_u64 buf off v

Write 8 bytes little-endian. Rail's int is tagged 63-bit, so values up to
~9.2e18 fit; for our purposes (offsets, sizes, vmaddrs up to 0x1_0000_4000)
this is fine.

mw_str_padded buf off str len

Write a fixed-length zero-padded string. Used for segname (16 bytes)
and sectname (16 bytes). String content is copied; remainder zero-fills.

mw_str_copy buf off cs i n

mw_zeros buf off n

mw_copy_bytes dst dst_off src src_off n

Copy n bytes from src buffer @ src_off into dst buffer @ dst_off.

mw_cstr buf off str

Write a NUL-terminated C string at off. Returns offset after the NUL.

mw_cstr_copy buf off cs

emit_mach_header buf off ncmds sizeofcmds flags

emit_segment_64_nosects buf off segname vmaddr vmsize fileoff filesize maxprot initprot

Emit a no-section segment (used by __PAGEZERO and __LINKEDIT).
cmdsize = 72.

emit_segment_64_text buf off vmaddr vmsize fileoff filesize sect_addr sect_size sect_offset sect_align

Emit an LC_SEGMENT_64 with a single __text section. cmdsize = 72 + 80 = 152.
Used for __TEXT.

emit_symtab buf off symoff nsyms stroff strsize

============================================================================
LC_SYMTAB (24 bytes)
============================================================================

emit_dysymtab_empty buf off

============================================================================
LC_DYSYMTAB (80 bytes — all zeros except cmd+size for a no-dylib binary)
============================================================================

emit_dysymtab_zeros buf off n

emit_load_dylinker buf off

============================================================================
LC_LOAD_DYLINKER (32 bytes for "/usr/lib/dyld\0" + padding)
Header is 12 bytes; name starts at offset 12; total 32 bytes; remainder zero.
============================================================================

emit_uuid buf off

============================================================================
LC_UUID (24 bytes) — 16-byte UUID
Phase 1: emit a deterministic placeholder (all zeros except version nibble).
Phase 2 will hash the binary content into the UUID for traceability.
============================================================================

pack_version major minor patch

============================================================================
LC_BUILD_VERSION (32 bytes; one tool entry)
Version encoding: X.Y.Z → (X << 16) | (Y << 8) | Z
============================================================================

emit_build_version buf off minos_packed sdk_packed

emit_main buf off entryoff stacksize

============================================================================
LC_MAIN (24 bytes)
============================================================================

emit_code_signature_lc buf off dataoff datasize

============================================================================
LC_CODE_SIGNATURE (16 bytes) — points at the embedded SuperBlob in __LINKEDIT
============================================================================

hex_digits

hex_char_at i

drop_chars n cs

macho_byte_to_hex b

macho_buf_to_hex_chars buf i acc

macho_buf_to_hex buf n

macho_write_file path buf n

Write the byte buffer to disk via hex+xxd pipeline. The resulting
file is also chmod +x so it's ready to codesign + run.
Returns 0 on success, non-zero on shell failure.

stdlib/map.rail

map_new _

map_put m k v

map_get m k

map_has m k

map_del m k

map_merge l r

map_pop_min m

map_keys m

map_size m

stdlib/math.rail

pi

Constants (computed via FFI)

e

abs_int n

Convenience: absolute value for ints

min a b

Min/max for ints

max a b

clamp lo hi x

Clamp int to range

clamp_i lo hi x

Clamp int to range (explicit name; same as clamp, kept for clarity)

is_nan x

Float classification --------------------------------------------------------
NaN is the only IEEE 754 value not equal to itself. The float == path lowers
to fcmp ONLY when the operand is statically known to be a float. A bare
function parameter x is untyped, so x == x would lower to an integer/tag
compare (boxed pointer == itself -> always true) and miss NaN. We force the
float domain with let xf = x +. 0.0; that marks xf as float so xf == xf
correctly lowers to fcmp. Verified: is_nan (0.0 /. 0.0) -> 1, is_nan 1.0 -> 0.

is_inf x

Largest finite double sentinels. 1.0e308 is safe to write as a literal; we
keep the comparison threshold as 1.0e308 itself. We do NOT write a +inf
literal (no such literal form, and big-literal codegen is a trap) -- real
infinities arise at runtime via overflowing arithmetic and compare strictly
greater than 1.0e308. The +. 0.0 promotion is applied so the dotted float
comparisons lower to fcmp across the function boundary.

is_finite x

Finite = not NaN and not infinite. Avoids && (no short-circuit) by nesting.

stdlib/metal_kernel.rail

metal_kernel_header

Standard header every Metal kernel needs.

metal_kernel_sig

The standard parameter signature for a unary kernel.

emit_metal_unary name body_expr

Wrap a single C expression over x into a full unary kernel.
body_expr should reference x (the input element) and evaluate
to a float. Example: emit_metal_unary "relu2" "x > 0.0f ? x : 0.0f"

metal_apply_unary src name x_arr

Apply a Metal kernel to a float_arr. Returns a fresh float_arr.

stdlib/mhd_axisym.rail

ma_nr

ma_nz

ma_nrnz

ma_nfields

ma_state_size

ma_nrm1

Hoisted boundary-arithmetic constants.
Working around a Rail compiler bug where (ma_nz - 2) and similar
inline (named_int - small_int) expressions, when passed as a
positional function argument alongside another integer parameter
whose value is 0, produce wrong index computation in the callee.
Pattern verified 2026-04-28: ma_get s f j (ma_nz - 2) fails for
j=0 (returns 0 instead of the cell value), but ma_get s f j 62
and ma_get s f j ma_nzm2 both work. Investigation deferred to
a separate compiler-level session; meanwhile, hoist all such
expressions to named module-level constants.

ma_nrm2

ma_nzm1

ma_nzm2

ma_f_rho

ma_f_mr

ma_f_mz

ma_f_en

ma_f_bt

ma_idx f j i

ma_get s f j i

ma_put s f j i v

ma_pressure s j i

ma_flux_r s j i fa

ma_flux_z s j i fa

ma_cell_speed s j i

ma_speed_check s i acc

ma_speed_loop s i acc

ma_max_speed s

ma_is_boundary j i

ma_inlet_b_theta j ctx

ma_apply_bc s ns ctx j i

ma_lxf_apply s ns ctx frp frm fzp fzm j i f

ma_lxf_cell s ns ctx frp frm fzp fzm j i

ma_step_one s ns ctx frp frm fzp fzm i

Per-cell dispatch. ctx carries inlet + coeffs; this keeps the outer
loop's recursive call at exactly 8 args (Rail's TCO sweet spot).

ma_step_loop s ns ctx frp frm fzp fzm i

ma_lxf_step s ctx dt

ma_lxf_step now takes the ctx array directly (caller builds it once,
reuses across many steps). See ma_make_ctx below for the helper.

ma_make_ctx rho_in vz_in p_in i_arc r_min r_max dr dz

Construct ctx from operating-point parameters. dt-derived coeffs
are filled later by ma_lxf_step.

ma_init_cell s ctx i

ma_init_loop s ctx i

ma_init_state ctx

ma_sum_field s f i acc

ma_total_mass s

ma_total_energy s

ma_max_bt_check s i acc

ma_max_bt_loop s i acc

ma_max_b_theta s

stdlib/mhd_kernel.rail

mk_nn

mk_nn2

mk_state_size

mk_f_rho

mk_f_mx

mk_f_my

mk_f_bx

mk_f_by

mk_f_en

mk_wrap i

mk_idx f x y

mk_get state f x y

mk_put state f x y v

mk_init_cell state i

mk_init_loop state i

mk_init_state

mk_pressure state x y

mk_x_flux state x y fa

mk_y_flux state x y fa

mk_cell_speed state x y

mk_speed_check state i acc

mk_speed_loop state i acc

mk_max_speed state

mk_lxf_update_fields state ns coeffs fxr fxl fyu fyd x y f

mk_lxf_update_cell state ns coeffs fxr fxl fyu fyd x y

mk_lxf_step_cell state ns coeffs fxr fxl fyu fyd i

mk_lxf_loop state ns coeffs fxr fxl fyu fyd i

mk_lxf_step state dt

mk_lxf_step_into state ns dt coeffs fxr fxl fyu fyd

In-place variant: writes the next state into caller-owned ns, plus
caller-owned scratch buffers. Returns 0. Use this in long-running
drivers (e.g. the entropy beacon) so per-frame allocation is bounded
and the conservative GC doesn't have to chase 768 KB state buffers
every step. coeffs must be float_arr_new 2; fxr/fxl/fyu/fyd must be
float_arr_new 6 each. ns must be float_arr_new mk_state_size.

mk_compute_dt state

CFL: dt = 0.2 * dx / max_speed = 0.00981747704246810 / smax

mk_minmod a b

minmod: zero at extrema, smaller-magnitude argument otherwise.

mk_slope_x_at state f x y

mk_slope_y_at state f x y

mk_slope_cell_fields state slx sly x y f

mk_slope_loop state slx sly i

mk_recon_xL state slx f x y

Reconstruct conserved field f at the right side of cell x (interface x+1/2):
Left state of interface = cell x + half slope.
Right state of interface = cell x+1 - half slope.

mk_recon_xR state slx f x y

mk_recon_yL state sly f x y

mk_recon_yR state sly f x y

mk_pack_xL state slx x y u f

Pack reconstructed conserved vectors into 6-element arrays at an x-interface.

mk_pack_xR state slx x y u f

mk_pack_yL state sly x y u f

mk_pack_yR state sly x y u f

mk_x_flux_from_u u fa

Ideal MHD flux from a 6-element conserved vector.
Includes positivity floors on density and pressure — drives the
positivity-preserving property of the whole MUSCL scheme.

mk_y_flux_from_u u fa

mk_wave_speed_u u

mk_rusanov_assemble uL uR fL fR fi alpha f

Rusanov / local-Lax-Friedrichs interface flux assembler.
F_iface = ½(F(uL) + F(uR)) - ½α(uR - uL), α = max(c_L, c_R).

mk_rusanov_x uL uR fL fR fi

mk_rusanov_y uL uR fL fR fi

mk_muscl_apply state ns dt_dx dt_dy fxR fxL fyU fyD x y f

Apply the interface-flux divergence to one cell, all 6 fields.
Density floor enforced post-update; pressure floor lives in fluxes.

mk_muscl_cell state slx sly ns dt_dx dt_dy uL uR fL fR fxR fxL fyU fyD x y

mk_muscl_loop state slx sly ns dt_dx dt_dy uL uR fL fR fxR fxL fyU fyD i

mk_muscl_step state dt

mk_compute_dt_muscl state

MUSCL + Rusanov + forward-Euler is stable up to CFL ≈ 0.5 in 2D;
0.3 leaves comfortable headroom and matches the LF dt scale.

mk_sum_field state f i acc

mk_total_mass state

mk_total_energy state

mk_divb_cell state i

mk_divb_check state i acc

mk_divb_loop state i acc

mk_max_div_b state

mk_minrho_check state i acc

mk_minrho_loop state i acc

mk_min_density state

mk_dump_row state field y i acc

mk_dump_field_rows state field y acc

mk_dump_field state field path

mk_prim_buffer_size

mk_fill_primitives state out i

mk_dump_primitives_bin state out path

Caller provides out (a float_arr_new mk_prim_buffer_size 0.0) once at
daemon start and reuses it across frames.

mk_print_diag step state m0 e0 dt t

stdlib/mhd_mpd.rail

mpd_gaussian z z0 sigma

Smooth (1 - x^2)^2 polynomial bump on |x| <= 1, zero outside.
Avoids the exp foreign call (which has had segfault interaction with
the GC under arena pressure) while staying C2-continuous and matching
a Gaussian envelope to within ~10% on the |x| <= 1.5 sigma region.

mpd_radial_falloff r r_anode

mpd_bz_at_z coils n_coils idx z acc

Sum contributions of all coils at axial position z.

mpd_build_bz_field bz coils n_coils r_min r_max dr dz i

mpd_build_br_field br bz dr dz i

B_r from solenoid continuity: B_r ≈ -(r/2) * dB_z/dz (paraxial).

mpd_build_applied bz br coils n_coils r_min r_max dr dz

Top-level builder: takes coil pack + geometry, fills bz and br.

mpd_jz_self s r_min dr j i

mpd_jr_self s dz j i

mpd_jt_app bz br dr dz j i

mpd_apply_geometry s ns r_min dr dt j i

mpd_apply_lorentz s ns bz br r_min dr dz dt j i

mpd_apply_ohmic s ns bz br r_min dr dz dt m_ion j i

mpd_apply_cell s ns bz br params dt j i

mpd_apply_loop s ns bz br params dt i

mpd_apply_sources s ns bz br params dt

mpd_thrust_cell s bz br r_min dr dz j i acc

mpd_thrust_loop s bz br r_min dr dz acc i

mpd_thrust_integral s bz br params

stdlib/mlx_client.rail

json_quote s

mlx_json_esc s

Escape the two characters that break JSON in chat prompts.
(Newlines are serialized as literal \n — mlx_lm tolerates both \n and \\n.)

show_temp t

Show a float safely. Rail has show for ints; we format temperature as
"X.Y" by multiplying into an int. 0.3 → "0.3", 0.75 → "0.75".

mlx_payload model prompt max_tokens temperature

extract_field body key

mlx_chat ip port model prompt max_tokens temperature

stdlib/mmap.rail

prot_none

Protection flags

prot_read

prot_write

prot_exec

prot_rw

map_shared

Map flags

map_private

map_anon

alloc_pages len

Map anonymous memory (no file backing)

free_pages addr len

Unmap memory

stdlib/optim.rail

adam_state n

Build a fresh AdamState matching an existing float_arr (weights) in size.
m and v start at zeros; step counter starts at 0.

adam_hyp lr b1 b2 eps t

Pack the six hyperparameters for this step into a float_arr the dylib
can read. Bias correction is recomputed every step because pow is cheap.

cpu_adam_loop w g m v h n i

CPU Adam inner loop. Hyperparameter layout matches adam_hyp:
h[0]=lr, h[1]=b1, h[2]=b2, h[3]=eps, h[4]=bc1=1-b1^t, h[5]=bc2=1-b2^t.

adam_update_raw w g state lr b1 b2 eps

One fused Adam update on a raw float_arr (weights data). Mutates w, m, v.
Returns the new step counter. Dylib path when gpu_available; pure-Rail
CPU fallback otherwise (machines where the Metal dylib can't be loaded).

adam_update param grad state lr b1 b2 eps

Tensor-level wrapper: unwraps the Tensor ADT and calls the raw path.
param and grad must share shape; state must have been built with
adam_state matching that shape's element count.

apply_decay_loop w lr_wd n i

─────────────────────────────────────────────────────────────
AdamW: Adam + decoupled weight decay (Loshchilov & Hutter 2019).
Instead of L2 regularization embedded in the gradient, we shrink
weights toward zero by a small factor (1 - lr*wd) each step, then
run the normal Adam step on the unregularized gradient.
Used by every modern transformer (GPT / Llama / Qwen) with wd
typically 0.01-0.1.
─────────────────────────────────────────────────────────────

adamw_update_raw w g state lr b1 b2 eps wd

adamw_update param grad state lr b1 b2 eps wd

adam_lr_mult_gamma

adam_lr_mult_tied_embed

adam_lr_mult_default

adam_default_wd

adam_default_lr

Default hyperparameters (Kingma & Ba 2015). Use as:
let lr = adam_default_lr
let b1 = adam_default_b1
etc. Not a record because Rail lacks record-of-floats destructure.

adam_default_b1

adam_default_b2

adam_default_eps

cosine_decay step warmup max_steps base_lr

sum_sq_tensor g

total_sq_into grads acc_arr

Accumulate sum-of-squares across a list of tensors into a mutable
float_arr slot. Rail's cross-function float-param inference can
misclassify a float accumulator passed recursively, so we route
the running total through a float_arr instead of an acc arg.

scale_each grads factor acc

Fold helper: scale every tensor in a list by a constant factor.
factor is a float but only participates in arithmetic inside
tensor_scale, which already handles float scalars correctly via
its own float_arr dispatch.

clip_grad_norm grads max_norm

Returns (clipped_grads, total_norm).

stdlib/oracle.rail

oracle_compile_at code path

Runs the rail_native compiler on code, returns the combined
stdout+stderr. The compiler prints one "file:line:col: error: msg"
per parse/type/link error plus a trailing "N error(s) found" line
on failure, or "as: OK" + "ld: OK" on success.
oracle_compile_at takes an explicit input path, which callers MUST
use when invoking the oracle concurrently — the shared default path
races under parallel load (harvest_filter, oracle_compile_batch, etc.).
Stream 2's bench_railnative.rail also dodges by supplying its own path
+ flock + size-gated retry.

oracle_compile code

count_error_lines lines acc

Counts lines containing "error:" in the compiler output. Returns
0 for clean compilation. The N-errors-found summary line itself
contains "error" but not "error:", so it doesn't double-count.

oracle_errors code

oracle_ok code

True iff code compiled + linked cleanly — the "self-hosting passed"
signal. Useful as a per-sample boolean for pass-rate metrics.

oracle_compile_and_run_at code path

Full pass: compile AND run, capture the program's exit behavior.
Returns the captured stdout of the generated program (or the compile
error output if compile failed).

oracle_compile_and_run code

is_ws c

count_nw_loop cs acc

count_non_whitespace code

dedupe_contains xs target

Build a set (as list) of distinct chars, return length.

dedupe_loop cs acc

count_unique_chars code

oracle_stats_impl code

Composite score. Rewards structured substantive output AND compile
success. Degenerate inputs (short or low-diversity) score 0.
Compiling output scores 10× the baseline-substance score.
Non-compiling substantive output scores proportional to substance,
penalized by error count — so a tiny Rail-shaped output with 1 error
scores better than the same length with 5 errors, giving training
a gradient even before the model reaches clean compile.
Single oracle_compile call shared between error-count parse and
compile-OK check — keeps the shell-out cost to one per scoring.

oracle_min_nw_for_bonus

Min non-whitespace for the 10× compile-pass bonus to apply. Below this
threshold the output is considered trivial even if the compiler
accepts it — prevents an all-whitespace-with-30-stray-chars baseline
from outscoring substantive-but-imperfect trained output.
Raised 64 → 128 (2026-04-20) after shorter trivia like
main = let _ = print (show 5) in 0
was inheriting the bonus despite being a one-liner.

oracle_min_unique_chars

Min distinct characters. A program with fewer than this many unique
chars is either a repeated token or a single primitive — not a
training signal. Bumped 4 → 8 (2026-04-20) because main = 0\n
(7 unique chars) was squeaking past the old floor.

oracle_min_decls_for_bonus

Min top-level declarations for the compile-bonus to apply at all.
Counts lines starting with an identifier alpha + " =" (the Rail
top-level decl shape). Enforces ≥1 helper beyond main = …, so
single-line main = <expr> trivia no longer gets a compile bonus.

is_ident_alpha c

Heuristic "is this character an identifier start" (Rail idents are
lowercase + underscore at top level). Rail's char_to_int does NOT
return ASCII, so we enumerate (same idiom as stdlib/bpe.rail).

is_decl_line line

A top-level decl line: starts with an ident alpha (no leading space),
contains " =" somewhere. match arms (| Ctor -> …), comments
(-- …), continuations, and body-let lines (which are indented)
are excluded by the no-leading-whitespace + no---start rules.
Using " =" (without trailing space) so main =\n — where the body
starts on the next line — still counts as a decl.

count_decl_lines_loop lines acc

count_top_level_decls code

oracle_quality code

ob_write_codes tmpdir codes idx

ob_make_cmd tmpdir idx

Per-worker --out-prefix eliminates the /tmp/rail_out race; no lock needed.

ob_build_cmds tmpdir n idx acc

ob_score_one code out

MIRRORS oracle_quality. Given captured compile output, replay the
scoring formula so we don't re-shell once per candidate. Keep in
sync with oracle_quality's body (thresholds + decls gate).

ob_score_loop codes pairs acc

oracle_compile_batch codes max_conc

stdlib/parallel.rail

parallel_default_conc_mini

Default concurrency. Tune per host via the explicit max_conc arg;
Mini M4 Pro: 10 (leave headroom for MLX server on GPU). Studio
M1 Ultra: 16 (matches the P-core budget under the TB hub).

parallel_default_conc_studio

parallel_default_timeout

Default worker timeout (seconds). TERM at T, KILL at T+2. Escalation
is handled by a per-worker killer subshell (macOS ships no timeout(1)).

par_rstrip_nl s

Strip trailing newline left by shell command substitution.

par_digit_val c

Parse a non-negative integer from a string. Stops on first non-digit.
Handles leading whitespace and an optional leading minus.

par_parse_int_loop cs acc

par_parse_int s

par_init_tmpdir _

Create a unique tempdir + its cmds/ and out/ subdirs.

par_write_cmds tmpdir cmds idx

Write one command-per-file into tmpdir/cmds/$idx. Returns count.

par_driver_header tmpdir n max_conc to_s

Assemble the bash driver. Fires workers behind &, throttled by
$MAX via wait -n. Each worker runs under an in-script portable
timeout (background killer subshell), captures stdout+stderr to
$TMPDIR/out/$i.out and exit code to $TMPDIR/out/$i.ec. Final pass
emits sentinel-delimited chunks that Rail parses back.
Split into single-line fragments — Rail's parser rejects multi-line
list literals, so join/cat must feed same-line string expressions.

par_driver_worker _

par_driver_loop _

par_driver_emit _

par_make_driver tmpdir n max_conc to_s

par_strip_trailing_nl s

Pull (ec_int, out_str) out of one inter-sentinel chunk of form:
"===RAIL_PAR_===\n\n===RAIL_PAR_OUT===\n"

par_parse_one chunk

par_parse_all_loop chunks acc

Parse the driver's aggregated output into a list of [ec, out] pairs.

par_parse_all text n

parallel_shell_timeout cmds max_conc to_s

Run each cmd in cmds up to max_conc at a time. Returns a list of
[exit_code, stdout] pairs in the same order as cmds. Per-worker
timeout defaults to parallel_default_timeout seconds.

parallel_shell cmds max_conc

Convenience wrapper with default timeout.

stdlib/pem.rail

pem_begin _

pem_end _

pem_extract_blocks s

Find all PEM CERTIFICATE blocks in s. Returns a list of base64 body
strings (everything between the BEGIN/END markers).

pem_extract_loop s acc

pem_max_certs _

Cap the trust store at this many certs. macOS /etc/ssl/cert.pem is
~170 entries, so 256 is comfortable headroom.

pem_load_trust_store path

pem_precompute certs lens sub_offs sub_lens spki_hashes n i

pem_decode_into certs lens blocks i n

ts_find_by_subject store name_buf name_off name_len

ts_find_loop certs n sub_offs sub_lens name_buf name_off name_len i

ts_has_spki_hash store h32

ts_hsh_loop hashes n target i

bytes32_eq a b

bytes32_eq_at a b i

stdlib/poly1305.rail

po_mask26

po_mask32

po_copy_bytes src dst src_off dst_off n

po_le32_read arr offset

po_le32_write arr offset value

poly1305_clamp_r key

bytes_to_limbs5 bytes with_high_bit

Split 16 bytes (LE) + optional bit-128 marker into 5×26-bit limbs.

poly1305_prep_block msg offset block_len

poly1305_add acc other

poly1305_mul_r acc r

poly1305_final_reduce acc

poly1305_serialize_tag acc s tag

poly1305_process_block msg offset block_len acc r

poly1305_loop msg msg_len acc r offset

poly1305_mac key msg msg_len

stdlib/prng.rail

prng_mask48

48-bit mask: (1 << 48) - 1. Computed (not a literal) to avoid baking a
>=2^47 constant; shl on a small literal is safe.

prng_mask24

24-bit mask, used to extract a clean mantissa for rng_float.

prng_unit24

Magnitude of a 24-bit unit, as a float, for the [0,1) scaling.

rng_new seed

Seed -> state. Never let the state be 0 (xorshift with 0 state is a fixed
point that only ever yields 0). A nonzero seed-mix guarantees liveness.

prng_step state

One xorshift step on the 48-bit state. Shifts 21/35/4 give a full-period
orbit over the masked width; mask after each xor keeps it in 48 bits.

rng_next state

Advance the generator. Returns (raw_output, new_state). The raw output is
the post-step state itself (a 48-bit nonnegative int).

rng_int state n

Uniform-ish integer in [0, n). For n <= 0 we return 0 (no valid range).
Modulo of a 48-bit value by a small n has negligible bias for typical n.

rng_float state

Float in [0.0, 1.0). Uses the top 24 bits of the raw output as a mantissa
and divides by 2^24, so the result never reaches 1.0.

stdlib/quartz.rail

qz_ev_none

Event-type codes (returned by qb_next_event)

qz_ev_mouse_move

qz_ev_mouse_down

qz_ev_mouse_up

qz_ev_key_down

qz_ev_key_up

qz_ev_scroll

qz_ev_flags_change

qz_mask_mouse_move

Event-mask bits (passed to qb_init). OR them together for what to capture.

qz_mask_mouse_button

qz_mask_key

qz_mask_scroll

qz_mask_flags

qz_mask_all

qz_btn_left

Mouse buttons (matches CGMouseButton values).

qz_btn_right

qz_btn_middle

qz_mod_shift

Modifier-flag bits (NSEventModifierFlag values shifted to fit in low bits).

qz_mod_ctrl

qz_mod_alt

qz_mod_cmd

qz_mod_fn

qz_mod_caps

qz_init event_mask

Initialize. Recommended event_mask: qz_mask_all unless you have a reason.

qz_next_event timeout_ms

Block up to timeout_ms for the next event, decode into an ADT.

qz_decode_event ty buf

qz_move_to x y

Inject helpers — same args as the foreign decls but Rail-friendly names.

qz_click button down

qz_key keycode down mods

qz_scroll dx dy

qz_screen_count _

Display geometry helpers.

qz_screen_bounds idx

Returns [ox, oy, w, h] for screen index idx, or empty on error.

qz_shutdown _

qz_event_show ev

cat parts

stdlib/regex.rail

reg_extended

Flags (POSIX REG_EXTENDED = 1, REG_ICASE = 2, REG_NOSUB = 8)

reg_icase

reg_nosub

matches pattern str

Simple match: does string match pattern? (extended regex, no sub-matches)
Returns true if match found

regexec_simple pattern str

Low-level: compile + exec + free in one shot

stdlib/rsa_pss.rail

rsa_hlen _

rsa_slen _

rsa_mgf1 seed seed_len out_len

rsa_mgf1_loop seed seed_len out out_len counter written

rsa_mgf1_block seed seed_len counter

One MGF1 block = SHA-256(seed || 4-byte big-endian counter).

rsa_pss_verify_em em em_len m_hash

rsa_pss_em_decode em em_len m_hash

Decode EM, recover salt, recompute H', compare. All assumed non-trivial
side effects (arr_new + sha_copy_bytes + sha256_bytes) happen here in a
LINEAR let-chain — no nested-if ladder around the side effects.

rsa_xor a b out n

rsa_xor_at a b out i n

rsa_check_zeros buf off n

rsa_bytes_eq a b n

rsa_be_at a b i n

rsa_pss_verify_sha256 mod_bytes mod_len e msg msg_len sig sig_len

rsa_pss_em_check db ps_len h h_prime h_len s_len

After the linear decode, do the layout + hash compare in a single
chained boolean. Pulled out so the decode body has no nested-if pain.

rsa_pkcs1_v15_verify_sha256 mod_bytes mod_len e msg msg_len sig sig_len

rsa_pkcs1_digestinfo_sha256 _

DigestInfo for SHA-256 = 19 bytes that prefix the hash in EM.

rsa_pkcs1_check em em_len m_hash

rsa_pkcs1_check_ff em i end

rsa_bytes_eq_at a a_off b b_off n

stdlib/sampling.rail

cumul_walk probs V target i cum

Categorical sample via cumulative-sum binary walk.
Returns the smallest index i where prefix_sum >= u.

sample_argmax probs V

argmax_loop probs V i best best_v

sample_categorical probs V u

find_kth_largest probs V k

Top-k sampling: find the k largest probabilities, renormalise over
them, sample one. k must be ≥ 1. For k=1 this reduces to argmax.
The simple O(V·k) selection is fine for small vocabs (≤256).

find_kth_loop probs V k prev_max iters

max_below probs V prev_max i best_v iters

First iteration (iters=0): ignore prev_max, take global max.
Later iterations: take largest value strictly less than prev_max.

sample_topk probs V k u

topk_mask src dst V thresh tot_arr i

renorm_loop arr V tot i

sample_temperature logits V tau u

Temperature sampling: rescale raw LOGITS by tau, softmax, sample.
Caller passes logits (not probs). tau<1 sharpens, tau>1 flattens.

temp_div src dst V tau i

max_val arr V

max_val_loop arr V i best

exp_shift arr V m sum_arr i

fill_uniforms n seed

Pre-generate n uniform samples in [0,1) via awk. Deterministic given
the seed. awk's rand() is seeded once, produces doubles in [0,1).
Output parses as one float per line; empty trailing line tolerated.

parse_uniforms_loop parts arr n i

stdlib/set.rail

set_buckets_n

set_hash_mask

set_djb2 s

set_djb2_acc cs h

set_idx key

set_new _

set_elems s

set_add s key

Idempotent insert. Membership-checked so elems stays deduped.

set_contains s key

set_member_in bucket key

set_remove s key

Rebuild from the surviving members so buckets and elems stay consistent.

set_list_without xs key

set_size s

set_to_list s

set_from_list xs

Fold a list of strings into a fresh set (dedups along the way).

set_add_all s xs

set_union a b

set_intersect a b

set_keep_if_in xs other

set_difference a b

set_drop_if_in xs other

stdlib/sha256.rail

sha256_k_hex

sha256_h_hex

sha256_k_table _

sha256_h_init _

load_words bytes dst i n

sha_ch x y z

sha_maj x y z

sha_big_sigma0 x

sha_big_sigma1 x

sha_small_sigma0 x

sha_small_sigma1 x

expand_schedule w t

sha_round state w k_tbl t

sha_process_block h state block k_tbl

sha_copy_bytes src dst src_off dst_off n

sha_process_from msg_bytes msg_len h state k_tbl off

sha_final_block msg_bytes msg_len h state k_tbl remain_off

arr_slice src start n

sha256 msg

sha256_hex msg

sha256_init _

sha256_update_arr st arr off n

Feed n bytes from arr starting at off into the running hash.
Returns the same state array (mutated in place).

sha256_stream_full st arr off remaining k_tbl h

sha256_update_str st s

Convenience: feed a Rail string. Allocates a temporary byte array;
not appropriate for huge strings — use sha256_update_arr from a streaming
producer instead.

sha256_finalize st

Pad and produce the 32-byte digest. After finalize the state is spent.

sha256_finalize_hex st

stdlib/sha512.rail

s5_and32 a b

s5_or32 a b

s5_xor32 a b

s5_add32 a b

s5_shr32 x n

s5_shl32 x n

s5_w_xor dst di a ai b bi

s5_w_and dst di a ai b bi

s5_w_add dst di a ai b bi

64-bit ADD with carry from low to high half.

s5_w_rotr dst di a ai n

64-bit ROTR(n) for 1 <= n <= 63. Splits into <32 / ==32 / >32 cases.

s5_w_shr dst di a ai n

64-bit logical SHR(n) for 1 <= n <= 63.

s5_k_hex _

s5_h512_hex _

s5_h384_hex _

s5_bytes_to_words bytes off dst base n_words

Convert n_words 64-bit big-endian byte words at bytes[off..] into limb-pair
slots in dst[base..base+2*n_words).

s5_load_k _

s5_load_h hex_fn

s5_sigma0 tmp ti w wi

Compute σ0 of W[i_in_words], store in tmp[ti].

s5_sigma1 tmp ti w wi

s5_bigsig0 tmp ti w wi

s5_bigsig1 tmp ti w wi

s5_ch tmp ti x xi y yi z zi

Ch(x,y,z) = (x AND y) XOR ((NOT x) AND z) — uses (x AND y) XOR (z AND NOT x).

s5_maj tmp ti x xi y yi z zi

Maj(x,y,z) = (x AND y) XOR (x AND z) XOR (y AND z)

s5_process_block h block k

Process one 128-byte block. Updates state h (16 limbs) in place.

s5_extend_w w i

s5_round_loop v w k i

s5_add_state h v

s5_add_state_at h v i

s5_padded_len n

s5_pad_bytes buf n

s5_run_blocks h padded total k off

s5_process_block_at h padded off k

s5_state_to_bytes h

Serialize state h (16 limbs of 32-bit) into a 64-byte big-endian array.

s5_state_serialize h out i

sha512_bytes buf n

sha384_bytes buf n

stdlib/signal.rail

sighup

Signal numbers (macOS/BSD)

sigint

sigquit

sigterm

sigusr1

sigusr2

sig_dfl

Special handlers

sig_ign

ignore_signal sig

Ignore a signal

default_signal sig

Restore default handler

raise_signal sig

Send signal to self

stdlib/slack_client.rail

sk_trim_line s

sk_esc s

Minimal JSON escape: backslash, quote, newline.

sk_body channel text

sk_ok body

Detect {"ok":true} in the response body.

slack_post_text channel text token_path

stdlib/socket.rail

af_inet

sock_stream

sock_dgram

sockaddr_in_size

parse_int s

Pure-Rail string→int parser. (The builtin to_int is float→int, NOT
string→int — that misled the first version of parse_ip into always
returning 0, which made bind_udp/sendto silently use 0.0.0.0 as the
destination. Also: through ~2026-04-15 char_to_int itself was
broken on runtime strings and this parser silently returned 0 for
every digit; fixed in compile.rail with bl _str_unwrap.)

parse_int_acc cs acc

parse_ip s

Parse "127.0.0.1" → 0x7F000001 (host byte order int)

render_ip n

Render an int (host order) → "a.b.c.d"

make_sockaddr_in port ip_str

Allocate a 16-byte sockaddr_in for the given (port, "a.b.c.d") pair.
Caller owns the pointer. Free with free when done.
Layout (16 bytes):
offset 0-1: sin_family (host order, AF_INET)
offset 2-3: sin_port (network order)
offset 4-7: sin_addr (network order)
offset 8-15: sin_zero (zeros)

sa_init_zero p i

sa_port sa

Inverse: read port (host order) out of a sockaddr_in

sa_addr sa

Read addr (host order int) out of a sockaddr_in

tcp_socket _

udp_socket _

close_socket fd

sol_socket_mac

setsockopt constants (macOS/BSD values; Linux values differ — sol_socket=1
on Linux, so_reuseaddr=2. When cross-compiling to Pi, replace below).

so_reuseaddr_mac

write_u32_le p v

Write a 4-byte int (little-endian) to a buffer

set_reuseaddr fd

Enable SO_REUSEADDR on a TCP socket so restarts don't hit TIME_WAIT lockout.

set_tcp_nodelay fd

Disable Nagle's algorithm on a TCP socket. Required for TLS records
that are sent in small separate calls (e.g. ClientFinished followed by
the first HTTP request on a keep-alive session) — otherwise the kernel
may coalesce/delay and the server times out the bare CF without ever
seeing the request. Constants IPPROTO_TCP=6 / TCP_NODELAY=1 agree
between macOS and Linux.
Inline the IPPROTO_TCP=6, TCP_NODELAY=1 constants at the call site to
avoid polluting the arity-0 namespace (top-level name = lit
definitions appear to interfere with how downstream callers link when
the module is re-imported alongside a large transitive graph — see
v3.3.0 track notes).

bind_tcp ip_str port

Open a TCP listener bound to ip:port with SO_REUSEADDR + listen(32).
Returns listen fd on success, -1 on failure.

accept_tcp listen_fd

Accept one incoming connection. Blocks until a client arrives.
Returns client fd on success, -1 on failure.

tcp_bytes_to_str buf off n acc

Copy n bytes from buf[off..off+n) into a Rail string.
Builds a list of single-char strings then joins once: O(N) allocation
instead of O(N²) from repeated prefix-copying concat.

tcp_bytes_to_list buf off n

recv_http_request fd

Receive bytes until the HTTP header terminator \r\n\r\n appears, then
continue reading Content-Length bytes of body if present. Returns the
full request as a Rail string. Caps at 64KB.

tcp_crlf_crlf _

recv_headers_loop fd buf acc

send_all_tcp fd s

Send a full response string, looping on partial sends.

send_all_tcp_loop fd s off total

bind_udp ip_str port

Open a UDP socket and bind it to the given address+port.
Returns fd on success, -1 on failure.

send_to fd buf ip_str port

Send buf (a Rail string — its bytes are the wire payload) to (ip, port).
Returns bytes sent on success, -1 on failure.

recv_from fd bufsize

Receive a UDP datagram into a freshly malloc'd byte buffer.
Returns (bytes_received, buf_ptr, src_sa_ptr).
Caller MUST free both buf_ptr and src_sa_ptr when done.
bufsize is the max bytes the caller will accept (e.g. 512 for DNS).

stdlib/sqlite.rail

sqlite_ok

Return codes

sqlite_error

sqlite_busy

exec db sql

Execute SQL (no callback version — pass 0 for callback and ctx)

close_db db

Close database

sqlite_row

sqlite_null_type

sq_handle buf

Reassemble an 8-byte little-endian handle from a malloc'd out-param buffer.

sq_open path

Open a DB; returns the db handle, or 0 on failure. path is copied to a C
buffer (sq_strdup) because a runtime-built path string (e.g. from argv or cat)
passed straight to the foreign sqlite3_open is the heap-object pointer, not the
char data — sqlite would open a garbage filename (silently creating an EMPTY db
whose every query returns 0 rows). Literals happen to pass cleanly, masking it.

sq_prepare db sql

Prepare a statement on db; returns the stmt handle, or 0 on failure. sql is
copied to a C buffer for the same reason as sq_open (robust for runtime SQL).

sq_strdup s

Copy a Rail string into a fresh malloc'd, NUL-terminated C buffer and return
the raw pointer. Needed because Rail passes a runtime-built string (from
str_sub/str_replace/cat) to a foreign char* arg as its HEAP-OBJECT pointer, not
the raw byte data — so sqlite reads the object header as text and the bind
matches nothing. A malloc'd buffer (raw -> ptr) marshals correctly. String
LITERALS happen to pass as a clean .asciz pointer, which masked this for an
hour on 2026-06-17 (recon worked with literal db-times, returned 0 with
reconstructed ones).

sq_strdup_into buf_s buf i n

sq_bind st idx s

Bind a 1-indexed text param. nbyte = explicit byte length; destructor -1 =
SQLITE_TRANSIENT so sqlite copies the bytes immediately (safe to free after).

stdlib/stat.rail

file_exists path

Check if file exists (access with F_OK = 0)

is_readable path

Check if file is readable (access with R_OK = 4)

is_writable path

Check if file is writable (access with W_OK = 2)

stat_parse_int s

Pure-Rail string→int (the to_int builtin is float-only — to_int "910"
returns 0, which silently broke file_size for years). Local copy of the
pattern from stdlib/socket.rail.

stat_parse_int_acc cs acc

file_size path

Get file size in bytes (via shell).

file_mtime path

Get file modification time as Unix timestamp (via shell).

stdlib/strbuf.rail

buf_new _

buf_append b s

buf_append_int b n

buf_str b

buf_len b

buf_clear b

stdlib/string.rail

starts_with prefix s

Check if string starts with prefix

starts_with_acc pcs scs

contains_char c s

Check if string contains substring (single char only for now)

repeat_str s n

Repeat string n times

pad_right s width

Pad string to width with spaces

pad_left s width

cat parts

Concatenate list of strings (used everywhere, defined here so all programs get it)

intercalate sep xs

Join list with separator (alias for builtin join)

from_chars cs

Convert list of chars back to string

stdlib/tensor.rail

mul_acc a b

Product of a list of ints

list_product xs

compute_strides_rev rev_shape

Compute row-major strides from shape
[2,3,4] -> [12, 4, 1]

compute_strides shape

compute_offset indices strides

Compute flat offset from multi-dim indices and strides

list_nth n xs

Nth element of a list (0-indexed)

tensor_new shape init_val

Create tensor with given shape, all elements set to init_val

tensor_zeros shape

Convenience constructors

tensor_ones shape

tensor_shape t

Get tensor shape

tensor_size t

Get total number of elements

dim t axis

Get size of dimension at axis

tensor_get t indices

Get element at multi-dimensional indices (returns float)

tensor_set t indices val

Set element at multi-dimensional indices

tensor_get_flat t i

Get/set by flat index

tensor_set_flat t i val

gpu_binary_path

gpu_flag_arr

Cached GPU availability flag (0 = unchecked, 1 = available, -1 = unavailable)

gpu_available _

tools/metal/.no_gpu sentinel forces the CPU path for every tensor op.
Motivation: on machines where the Metal dylib loads but silently returns
zeros (Studio M1 Ultra w/ stale Xcode CLT is the current case), touch
tools/metal/.no_gpu once and CPU fallbacks stay engaged until it's
removed — no per-run binary shuffling. File-based over env-based because
foreign getenv doesn't propagate reliably through rail_native run's
/bin/sh -c child (see docs/ffi-str-return-bug.md).

gpu_write_floats path arr n

Write float_arr to text file for GPU consumption

gpu_write_loop arr n i acc

gpu_read_floats path n

Read float_arr from GPU output text file

gpu_parse_lines arr lines i n

gpu_cleanup _

Cleanup tmp files after GPU ops

gpu_daemon_flag

Check if tensor daemon is running on :9300

gpu_daemon_available _

gpu_format_floats arr n

Format float_arr as space-separated string

gpu_format_loop arr n i acc

gpu_parse_inline str n

Parse space-separated floats into float_arr

gpu_parse_inline_loop arr parts i n

ensure_dylib _

Has dylib been initialized this process?
Note: the previous top-level float_arr cache was broken by Rail's
re-evaluate-per-reference rule for nullary bindings (float_arr_new
runs fresh on every reference), so ensure_dylib paid a shell + tgl_init
on EVERY matmul call. Fix: tgl_init is idempotent (the dylib has its
own g_initialized flag); call it unconditionally and trust it. Shell
existence check is compile-time anyway — if the dylib weren't linked,
ld would have failed, so this symbol exists by construction.

gpu_matmul_dispatch a_data b_data m k n

GPU matmul: in-process dylib. Zero-copy path, ~1ms per 128x128 matmul.
The historical binary-file and text-file fallbacks were dropped from the
hot path — if the dylib isn't linked, link would fail at build time.
Callers needing a non-dylib path should call gpu_matmul_file (below)
explicitly. This keeps the common case branch-free.

gpu_matmul_file a_data b_data m k n

Legacy binary-file dispatch, kept for debugging. ~50ms/call.

gpu_relu_dispatch src_data n

GPU relu: daemon (fast) or file-mode (fallback)

gpu_unop_ffi op src_data n

Unary activation (relu, sigmoid, exp, tanh). Op string maps to FFI.

gpu_binop_ffi op a_data b_data n

Binary elementwise (add, mul).

gpu_scale_ffi src_data s n

Scalar multiply. Scalar wrapped into a tiny float_arr so we can pass by pointer.

gpu_softmax_ffi src_data rows cols

Softmax (row-wise). 2D input [rows,cols]. Shape-preserving.

gpu_transpose_ffi src_data m n

Transpose: A[M,N]^T returns a new flat buffer of shape [N,M].

gpu_relu_backward_ffi x_data grad_data n

ReLU backward: ∂L/∂x = (x > 0) ? ∂L/∂y : 0

gpu_sgd_ffi w_data g_data lr n

In-place SGD step: w -= lr * grad

gpu_matmul_relu_ffi a_data b_data bias_data m k n

Fused matmul+bias+relu

matmul_k a_data b_data acc_arr k_dim n_dim i j kk

Innermost loop: accumulate a[i,k]*b[k,j] for k

matmul_j a_data b_data c_data acc_arr m_dim n_dim k_dim i j

Middle loop: iterate over columns j

matmul_i a_data b_data c_data acc_arr m_dim n_dim k_dim i

Outer loop: iterate over rows i

matmul a b

Matrix multiply: a(M,K) @ b(K,N) -> c(M,N)
Tries Metal GPU first (269 GFLOPS), falls back to CPU loops

matmul_f16 a b

fp16 matmul: Tensor wrapper around tgl_matmul_f16. Dylib stages f64→f16
on the way in and f16→f64 on the way out; accumulator stays fp32 inside
the kernel. GPU-only (no CPU fallback — callers should gate on
gpu_available if they need graceful degradation). ~1.7-1.8× speedup vs
tgl_matmul_f64 on M1 Ultra per fp16_drafts/RESULTS.md; Phase 4b training
uses this in lm_v3_chunked_fp16.rail.

matmul_bf16 a b

bf16 matmul (added 2026-05-14): same Rail signature as matmul_f16.
Dylib stages f64→bf16 on input and bf16→f64 on output; accumulator
stays fp32 inside the kernel. bf16's exponent range matches f32
(~3.4e38) — fp16's step-2759 overflow simply cannot happen here.

matmul_bias_relu_f16 a b bias

fp16 fused matmul+bias+relu. bias is a float_arr (length n) — stays
fp32 on the GPU per the fp32-bias hint that landed the fused kernels.
Semantics: out[i,j] = max(0, sum_k a[i,k]*b[k,j] + bias[j]).

matmul_bias_gelu_f16 a b bias

fp16 fused matmul+bias+gelu (same pattern, GELU activation).

half_storage_slots n

Packed-half storage size: one 8-byte float_arr slot holds 4 halfs.
For n half elements we need ceil(n/4) slots. Slot 0 of the underlying
float_arr still holds the slot-count header (set by float_arr_new).

half_tensor_new shape

Allocate an uninitialized HalfTensor with the given shape. Content
is zero-bit-pattern, which in fp16 is 0.0 — so this is effectively
half_zeros, same as tensor_zeros for Tensor.

half_of_tensor t

f64 Tensor → packed HalfTensor (host-side, one cast). Used at init,
checkpoint load, and eval boundaries — NOT inside the training loop.

tensor_of_half h

Packed HalfTensor → f64 Tensor. Inverse of half_of_tensor; used at
measurement / checkpoint save time.

matmul_half a b

fp16 matmul on already-packed HalfTensors. Zero cast at the host
boundary — memcpy in, GPU kernel runs on fp16, memcpy out. This is
the Phase 4b follow-on: the language stops paying the cast on every
call; it pays once at init/checkpoint.

matmul_mixed x w_h

Rail-native mixed precision matmul: f64 activations × fp16 weights → f64.
Acts arrive as a Tensor (f64) and leave as a Tensor (f64) — Rail-side code
never sees fp32 directly. GPU keeps acts in fp32 and weights in fp16; the
dot product runs in fp32 (Apple Silicon GPU's native compute precision).
This is the precision-preserving sibling of matmul_half: a HalfTensor of
weights composes with f64 activations through every block boundary
without the half-cast precision loss that flipped argmax in v3_half.

add_half a b

fp16 element-wise add on already-packed HalfTensors. Same zero-cast
contract as matmul_half: storage stays fp16, GPU keeps an fp32
accumulator per element.

scale_half t s

Scalar × HalfTensor. Scalar passed as a 1-element float_arr (ABI
match with tgl_scale_f64). GPU narrows the scalar to fp32.

transpose_half t

Transpose a 2-D HalfTensor. Shape [M,N] → [N,M]; storage stays fp16
so the op is bit-exact vs its f64 cast-path counterpart (shape
permutation only, no arithmetic).

softmax_half t

Row-wise softmax on a 2-D HalfTensor. GPU keeps the fp32 max-subtract
and exp-sum in fp32 so raw logits up to ~88 (exp overflow in fp32)
are safe — far above fp16's ~65504 ceiling. Output stored in fp16,
in [0, 1] by construction.

add_loop a_data b_data c_data n i

Add loop

sub_loop a_data b_data c_data n i

Sub loop

hmul_loop a_data b_data c_data n i

Hadamard (element-wise multiply) loop

scale_loop src dst n i s

Scalar multiply loop

gpu_binop_dispatch op a_data b_data n

GPU binary elementwise dispatch (add, mul) via daemon or file

gpu_unop_dispatch op src_data n

GPU unary elementwise dispatch (relu, exp, tanh, sigmoid)

tensor_add a b

tensor_add: element-wise addition (GPU via dylib, CPU fallback)

tensor_sub a b

tensor_sub: element-wise subtraction

tensor_mul a b

tensor_mul: element-wise (Hadamard) multiplication (GPU via dylib, CPU fallback)

tensor_scale t s

tensor_scale: scalar multiply (GPU via dylib, CPU fallback)

relu_loop src dst n i

ReLU loop: max(0, x)

tensor_relu t

gelu_loop src dst n i

GELU loop: 0.5 * x * (1 + tanh(sqrt(2/pi) * (x + 0.044715 * x^3)))

tensor_gelu t

tensor_exp_gpu t

GPU-accelerated exp, tanh, sigmoid

exp_loop src dst n i

tensor_tanh_gpu t

tanh_loop src dst n i

tensor_sigmoid t

sigmoid_loop src dst n i

sum_loop data n i acc_arr

Sum all elements (uses mutable accumulator array)

tensor_sum t

tensor_mean t

Mean of all elements

relu_mask_loop src dst n i

tensor_relu_mask: 1.0 where x > 0, 0.0 otherwise

tensor_relu_mask t

gelu_back_loop x_data grad_data dst n i

tensor_gelu_backward: dGELU/dx * upstream gradient

tensor_gelu_backward x grad

softmax_row src dst cols offset

tensor_softmax: numerically stable softmax along last axis
For 2D [batch, classes]: softmax each row

softmax_find_max src cols offset acc j

softmax_exp_sum src dst cols offset mx acc j

softmax_normalize dst cols offset sm j

softmax_rows src dst rows cols r

gpu_softmax_dispatch src_data rows cols

GPU softmax dispatch: daemon or file

tensor_softmax t

sum_last_loop src dst rows cols r

tensor_sum_last: sum along last axis. [batch, N] → [batch, 1]

sum_last_row src cols offset acc j

tensor_sum_last t

sum_batch_loop src dst rows cols r

tensor_sum_batch: sum along first axis. [batch, N] → [1, N]

sum_batch_add_row src dst cols offset j

tensor_sum_batch t

tensor_mean_last t

tensor_mean_last: mean along last axis. [batch, N] → [batch, 1]

broadcast_last_loop src dst rows cols r

tensor_broadcast_last: [batch, 1] → [batch, N] by repeating

broadcast_last_fill dst offset cols v j

tensor_broadcast_last t cols

mul_bcast_loop a_data b_data dst rows cols r

tensor_mul_broadcast: [batch, N] * [1, N] element-wise (broadcast first dim)

mul_bcast_row a_data b_data dst cols offset j

tensor_mul_broadcast a b

one_hot_loop indices dst batch vocab i

tensor_one_hot: indices tensor [batch] → [batch, vocab_size]

tensor_one_hot t vocab_size

embed_lookup_loop weights indices dst batch dim i

tensor_embedding_lookup: weights [vocab, dim], indices [batch] → [batch, dim]

embed_copy_row src dst dim src_off dst_off j

tensor_embedding_lookup weights indices

tensor_slice_row t row_idx

tensor_slice_row: extract row i from 2D tensor [rows, cols] → [1, cols]

accum_row_loop grad weights indices batch dim i

tensor_accumulate_row: scatter-add grad into weight rows (for embedding backward)

accum_row_add dst src dim dst_off src_off j

tensor_accumulate_row weights grad indices

tensor_scale_by_loss t loss_grad

tensor_scale_by_loss: scale all elements by scalar loss gradient

tensor_copy t

tensor_copy: alias for tensor_clone

tensor_matmul a b

tensor_matmul: alias for matmul (autograd uses this name)

tensor_sum_all t

tensor_sum_all: alias for tensor_sum

tensor_ones_like_shape shape

tensor_ones_like_shape: ones tensor with given shape

tensor_scalar_val t

tensor_scalar_val: extract scalar from a 1-element tensor

tensor_from_int n

tensor_from_int: wrap an int as a 1-element tensor

tensor_to_int t

tensor_to_int: extract int from a 1-element tensor

tensor_get_int t i

tensor_get_int: get element as int (for indexing)

map_scalar_loop src dst n i fn_tag

tensor_map_scalar: apply a scalar function to all elements
Since Rail lambdas can segfault, this takes a function tag string
and dispatches. Use named functions instead.

tensor_map_scalar fn_tag t

ce_loss_loop probs targets batch vocab i acc

tensor_cross_entropy_loss: -sum(targets * log(probs)) / batch
probs: [batch, vocab] (after softmax), targets: [batch] (indices)

tensor_cross_entropy_loss probs targets

transpose_copy_loop src dst rows cols i

Transpose: for 2D tensors, copies data into new row-major layout
This is a REAL transpose (data copy), not just stride swap,
because matmul assumes contiguous row-major data.

transpose_copy_row src dst rows cols i j

gpu_transpose_dispatch src_data rows cols

GPU transpose dispatch: daemon or file

tensor_transpose t

tensor_reshape t new_shape

Reshape: new shape, same data, recompute strides
Caller must ensure same total element count

tensor_rank t

tensor_rank: number of dimensions

copy_range src dst src_off n i

Helper: copy [start*block..end*block) from src to dst starting at dst offset 0.

tensor_slice t start end

tensor_slice: extract a contiguous slice along axis 0 (outermost).
For a tensor with shape [D0, D1, D2, ...], returns shape [end-start, D1, D2, ...].
start is inclusive, end is exclusive. Caller must ensure 0 <= start < end <= D0.
This is the common case for attention KV-cache rolling, batch splits, etc.
For slicing along inner axes, compose with transpose first.

ln_row x y gamma beta row dim eps

Inner: per-row normalization. Writes y[row*dim..(row+1)*dim] in place.
Returns 0 (float-result-via-mutation convention — see module header).

ln_row_sum x base dim i acc

ln_row_sq x base dim i mean acc

ln_row_apply x y gamma beta base dim i mean rstd

ln_rows x y gamma beta rows dim eps r

tensor_layer_norm t gamma beta eps

print_row t n_cols i j

print_rows t n_rows n_cols i

tensor_print_2d t

tensor_clone t

Clone: deep copy a tensor (new buffer, same shape/strides)

tensor_clone_copy src dst n i

read_binary path

Read binary file as float_arr of bytes

read_binary_fill arr hex n i

hex_val c

tensor_test_main _

grad_eps

sum_sq_grad t

sum_sq_grad_acc t n i acc

sum_relu_grad t

sum_relu_grad_acc t n i acc

sum_exp_grad t

sum_exp_grad_acc t n i acc

numgrad_sq t i

numgrad_relu_fn t i

numgrad_exp_fn t i

abs_f x

check_g name i numerical analytical

gradient_test_main _

stdlib/tensord.rail

tensord_port

tensord_host_str

tensord_open _

tensord_has_nl buf len i

Read until newline or buffer fills. Returns bytes read (>= 1) or -1.

tensord_recv_line_loop fd buf max off

tensord_exec cmd

tensord_available _

tensord_exec_on fd cmd

exec on an already-open fd. Returns true on "OK" reply, false otherwise.

tensord_matmul_on fd a_data b_data m k n

tensord_close_fd fd

tensord_build_cmd m k n

tensord_matmul a_data b_data m k n

stdlib/test.rail

assert_eq name expected actual

Assert two values are equal

assert_eq_str name expected actual

Assert two strings are equal

assert_true name cond

Assert condition is true

assert_false name cond

Assert condition is false

run_suite name results

Run a suite of test results (list of bools), print summary

stdlib/thumb2.rail

t2_hw buf off

t2_is_32bit_hw hw

t2_inst_len buf off

t2_sext_n v nbits

t2_make_result kind target length op_hi op_lo

t2_branch32 hi lo addr

t2_b16 hw addr

16-bit B unconditional: bits 15..11 = 11100, imm11 in bits 10..0

t2_bcond16 hw addr

16-bit Bcond: bits 15..12 = 1101, bits 11..8 = cond (0..13), imm8 in bits 7..0.
cond = 14 means uncond (always); cond = 15 means SVC (handled separately).

t2_bxblx hw

16-bit BX/BLX register: 010001 11 L Rm (Rm 4 bits including high)
Encoding: 0100 0111 0LMM Mmmm where L=0 -> BX, L=1 -> BLX

t2_pop16 hw

16-bit POP: 1011 110P reglist8. P=1 means PC included -> return.

t2_push16 hw

16-bit PUSH: 1011 010M reglist8. M=1 means LR included -> prologue.

t2_cb16 hw addr

16-bit CBZ/CBNZ encoding: 1011 op 0 1 i imm5[4:0] Rn[2:0]
bits 15..12 = 1011, bit 11 = op (0 CBZ, 1 CBNZ), bits 10..9 = "01",
bit 8 = i (high bit of immediate), bits 7..3 = imm5, bits 2..0 = Rn.
target = PC + (i:imm5:0 << 1). Forward only.

t2_decode buf off

Top-level decoder.

t2_kind_name k

t2_cond_name c

t2_hex8_acc n i acc

t2_hex8 n

t2_hex4_acc n i acc

t2_hex4 n

t2_hex2 n

t2_decode_str buf off

t2_disasm_loop buf base limit max i

Print up to max instructions starting at byte offset base.
limit is the buffer end; we stop early if we'd run off the end.

t2_disasm buf base limit max

stdlib/thumb2_asm.rail

asm_new base_addr

asm_base s

asm_buf s

asm_cur_off s

asm_capacity s

asm_insts s

asm_labels s

asm_label_count s

asm_inst_count_ref s

asm_decl_label s

asm_decl_label: declare a fresh label, get back its id. Initially
"unbound" (sentinel -1). The labels arr stores INST-INDEX values
after asm_label_here, NOT byte offsets — pass-1 layout resolves them
to byte offsets.

asm_label_here s id

asm_label_here: bind label id to the offset of the NEXT instruction
to be queued. We record the current inst count; layout converts that
to a byte offset.

asm_define_label s id offset_or_index

Legacy: explicit offset binding (still useful for raw-data labels).

asm_label_offset s id

After pass-1 layout, label_offset is meaningful (byte offset). Before:
it's an inst-index sentinel.

asm_inst_count s

asm_push_inst s kind op0 op1 op2 op3 est_len

asm_inst_get s i

asm_b s label_id

asm_bl s label_id

asm_bcond s label_id cond

asm_bx s rm

asm_push s reglist8 lr

asm_pop s reglist8 pc

asm_nop s

asm_movs_imm8 s rd imm8

asm_adds_imm8 s rd imm8

asm_subs_imm8 s rd imm8

asm_cmp_imm8 s rd imm8

asm_mov_reg s rd rm

asm_movw s rd imm16

asm_movt s rd imm16

asm_ldr_w s rt rn imm12

asm_str_w s rt rn imm12

asm_mov32 s rd value

For setting an arbitrary 32-bit value into a register, emit the
canonical MOVW+MOVT pair. (Track A's literal pool is for v0.1.)

asm_layout s

asm_layout_loop s i off n

asm_resolve_labels labels insts n_labels n_inst total i

asm_emit_one s inst self_off

asm_emit_loop s i n

asm_finalize s

asm_size s

stdlib/thumb2_emit.rail

te_emit_hw buf off hw

te_emit32 buf off hi lo

te_b buf off addr target

te_bcond buf off addr target cond

te_bx buf off rm

BX Rm: 0100 0111 0 Rm[3:0] 000. Encoding: 0x4700 | (Rm << 3)

te_bl buf off addr target

te_b_w buf off addr target

te_bcond_w buf off addr target cond

te_push buf off reglist8 lr

te_pop buf off reglist8 pc

te_nop buf off

te_movs_imm8 buf off rd imm8

te_cmp_imm8 buf off rd imm8

te_adds_imm8 buf off rd imm8

te_subs_imm8 buf off rd imm8

te_ldr_pc buf off rt imm8

LDR Rt, [PC, #imm8*4]: 0100 1 Rt[2:0] imm8 -> 0x4800 | (Rt<<8) | imm8

te_mov_reg buf off rd rm

MOV Rd, Rm (high regs allowed): 0100 0110 D Rm[3:0] Rd[2:0]
For Rd 0..7, Rm 0..15: 0x4600 | (D=Rd[3])<<7 | Rm<<3 | Rd[2:0]

te_movw buf off rd imm16

te_movt buf off rd imm16

te_ldr_w buf off rt rn imm12

te_str_w buf off rt rn imm12

stdlib/time.rail

now _

Get current Unix timestamp (seconds since epoch)

wait n

Sleep for n seconds (libc sleep(3), int seconds only).
For sub-second precision use wait_us.

wait_us n

Sleep for n microseconds (libc usleep, max ~1e6 per call).
Deprecated on macOS but still functional; nanosleep wrapper deferred.

cpu_us _

Process CPU time in microseconds (CLOCKS_PER_SEC = 1e6 on macOS + Linux).
Does NOT advance during sleep — measure compute durations only.

stdlib/tls13.rail

tls13_build_label L label context context_len

tls13_hkdf_expand_label secret label context context_len length

tls13_derive_secret secret label messages messages_len

tls13_derive_secret_empty secret label

When messages is empty, the transcript hash is Hash(""), a fixed value
used in the "derived" step of the key schedule (RFC 8446 §7.1).

tls13_derive_schedule ecdhe ecdhe_len hs_hash ap_hash

tls13_derive_key traffic_secret

tls13_derive_iv traffic_secret

tls13_record_nonce iv seq

tls13_finished_key traffic_secret

tls13_compute_finished traffic_secret transcript_hash

stdlib/tls13_cert_verify.rail

cv_context_str _

cv_context_len _

cv_build_signed transcript_hash

Returns a 98-byte array: 64 spaces + 33-byte context + 0x00 = 98; then
append the 32-byte transcript_hash externally.

cv_fill_spaces buf i

cv_extract_p256_pubkey cert cert_len

cv_check_p256_params cert params_off

Check params region: tag 0x06 (OID), length 0x08, content matches prime256v1.

cv_pk_fail _

tls13_cert_verify_sig cert cert_len sig_alg sig sig_len transcript_hash

cv_verify_ecdsa_p521 cert cert_len sig sig_len signed_data

ECDSA-P521 CertificateVerify over TLS 1.3 signed_data.

cv_verify_ecdsa cert cert_len sig sig_len signed_data

cv_verify_rsa_pss cert cert_len sig sig_len signed_data

cv_pad32 src off len

Left-pad a (<=32 byte) slice of src starting at off into a 32-byte
big-endian array.

cv_validate_hostname cert cert_len host

cv_san_walk cert cert_len host host_len off end

cv_dns_match cert v_off v_len host host_len

Match a single SAN dNSName entry (cert[v_off..v_off+v_len)) against host.
Note: the wildcard branch is evaluated FIRST when the cert SAN starts
with *., otherwise we fall through to exact-equality. The older
ordering short-circuited to cv_bytes_ieq whenever v_len == host_len,
which made wildcard certs whose total byte length happened to equal
the hostname's (e.g. SAN "*.co.uk" vs host "x.co.uk", both 7 bytes)
unmatchable. Flagged by security-audit-2026-05-12 (Fixer-A lane).

cv_count_dots cert off end_off acc

Count the number of '.' (0x2e) bytes inside cert[off..end_off).

cv_bytes_ieq a a_off b b_off n

cv_bie_at a a_off b b_off i n

cv_to_lower c

cv_find_byte buf n off target

cv_extract_rsa_pubkey cert cert_len

cv_extract_rsa_split cert pk_off pk_len

cv_rsa_assemble cert nt et

cv_read_int_be buf off len

cv_rib_at buf off len acc

cv_rk_fail _

cv_validate_period cert cert_len

cv_decode_time buf off len tag

Decode a Time TLV into a 14-digit YYYYMMDDHHMMSS integer.
UTCTime (tag 0x17) = "YYMMDDHHMMSSZ" (13 bytes); YY pivot is 2000+ for
YY < 50 (RFC 5280 §4.1.2.5.1). GeneralizedTime (tag 0x18) =
"YYYYMMDDHHMMSSZ" (15 bytes). Returns 0 on malformed input.

cv_dt_utctime buf off len

cv_dt_gentime buf off len

cv_dt_d2 buf off

cv_dt_d4 buf off

cv_dt_pack y mo d h mi s

cv_now_yyyymmddhhmmss _

cv_parse_int_str s acc

Parse leading digits as an int (stop at non-digit, e.g. trailing newline).

cv_str_drop s n

cv_post_sig_checks cert cert_len expected_host

Combined period + hostname check, returning 1 only if both pass.
Empty expected_host skips the hostname check (back-compat for the
RFC 8448 §3 trace test which has no SNI binding).

cv_verify_cert_by lower lower_len upper upper_len

cv_verify_dispatch cert sa_off sa_len tbs tbs_len sig sig_len upper upper_len

cv_chain_rsa_pkcs1 tbs tbs_len sig sig_len upper upper_len

cv_chain_ecdsa tbs tbs_len sig sig_len upper upper_len

cv_chain_ecdsa_sha384 tbs tbs_len sig sig_len upper upper_len

ECDSA-with-SHA384 chain edge: for chains where the issuer uses SHA-384.
Per FIPS 186-4 §6.4: hash = SHA-384(TBS) → 48 bytes; for P-256 verify
we use leftmost 32 bytes (= first 32 bytes of the 48-byte big-endian
digest).
Dispatch ECDSA-with-SHA384 by upper's curve. Try P-384 first (common
for CA roots: GTS R4, DigiCert Global Root G3). Fall back to P-256 if
the issuer's curve is prime256v1.

cv_chain_p256_sha384 tbs tbs_len sig sig_len upper upper_len

stdlib/tls13_client.rail

tcl_concat a a_len b b_len

tcl_hash buf n

tcl_fail _

tls13_client_handshake_offline client_priv client_hello_msg ch_len server_hello_msg sh_len server_flight fl_len verify_cert expected_host

tcl_tag_cmp a b i

tcl_tag_cmp_acc a b i acc

stdlib/tls13_hs.rail

ch_w_u8 buf off v

ch_w_u16 buf off v

ch_w_u24 buf off v

ch_w_bytes buf off src src_len

ch_ext_sni_len sni_len

ch_write_ext_sni buf off sni_bytes sni_len

ch_ext_versions_len

ch_write_ext_versions buf off

ch_ext_groups_len

ch_write_ext_groups buf off

ch_ext_sigalgs_len

ch_write_ext_sigalgs buf off

ch_ext_keyshare_len

ch_write_ext_keyshare buf off pub

ch_extensions_total sni_len

ch_body_len sni_len

ch_write_body buf off random session_id pub sni_bytes sni_len

tls13_client_hello random session_id pub sni

sh_r_u8 buf off

sh_r_u16 buf off

sh_r_u24 buf off

sh_copy buf off n

sh_fail _

sh_find_keyshare buf off end

tls13_parse_server_hello buf len

tls13_hs_read_msg buf off

tls13_parse_encrypted_extensions buf body_off body_len

tls13_parse_certificate buf body_off body_len

tls13_parse_certificate_verify buf body_off body_len

tls13_parse_finished buf body_off body_len

tls13_parse_cert_chain buf body_off body_len

tls13_cc_loop buf off end certs lens i

tls13_cc_fail _

stdlib/tls13_record.rail

rec_w_u8 buf off v

rec_w_u16 buf off v

rec_build_header hdr inner_len

rec_build_inner fragment frag_len real_type

tls13_record_encrypt key iv seq real_type fragment frag_len

rec_fail _

rec_strip_pad inner i

tls13_record_decrypt key iv seq wire wire_len

stdlib/tokenizer.rail

tok_nth n xs

Local list_nth (tokenizer.rail is independent of tensor.rail).

contains_char cs c

Linear scan: is c already in cs?

contains_char_n cs c n

Counted-scan variant: caller passes the vocab size, so no O(V) length cs
per call. Called from the counted collect_unique loop — keeps the inner
check O(V) without the repeated length walk.

collect_unique_old input acc

LEGACY (pre-v2.4): kept for the determinism regression test so we can
assert the new counted loop preserves first-appearance vocab order.
O(n²): length input == 0 is O(n), run n times. 85M ops at n=9.2KB,
~140B ops at n=518KB — the hard wall this file lifts.

build_vocab_old text

collect_unique_iter cs n i acc acc_n

Counted inner loop: i/n avoids the O(n) length cs == 0 termination
check that dominated the old version. acc_n threads vocab size so
contains_char_n is O(V), not O(V)+O(length). Total: O(n·V) with
n=corpus chars, V=vocab size (~100 for ASCII text) → ~52M ops at 518KB.

collect_unique input acc

Public API: same signature, same first-appearance semantics, no O(n²).

build_vocab text

Build a vocab from a corpus string. Deterministic by first-appearance
order so a saved vocab + corpus reproduce the same IDs.

char_id_loop cs c i

Find the integer ID of a 1-char string c in the vocab list.
Returns -1 if c is not present (caller should treat that as ).

char_id vocab c

id_char vocab id

Retrieve the 1-char string for an ID. Assumes id is valid.

vocab_size vocab

encode_loop cs vocab acc

Encode a string into a list of int IDs. Unknown characters produce -1
in the output so downstream code can detect vocab mismatches.

encode vocab text

decode_loop ids vocab acc

Decode a list of int IDs back into a string.

decode vocab ids

cat_chars_nl cs acc

save_vocab path vocab

Save a vocab to disk as a single file: one character per line.
The vocab is regenerable from the corpus via build_vocab, so saving
is optional — useful only when the corpus itself is not stored.

stdlib/transformer.rail

add_bias_row_loop dst bias cols n i

Add a row-shaped bias to every row of a 2D tensor (in place via new arr).

linear x w b

linear_gelu x w b

Fused linear+gelu using the matmul_bias_gelu kernel.
x: [rows, in_dim], w: [in_dim, out_dim], b: [out_dim] -> [rows, out_dim]

linear_relu x w b

Fused linear+relu using the matmul_bias_relu kernel.
x: [rows, in_dim], w: [in_dim, out_dim], b: [out_dim] -> [rows, out_dim]

layernorm_row_accum data offset dim j sum sum_sq

layernorm_row_apply dst src offset dim j mean std gamma beta

layernorm_rows src dst gamma beta dim eps n_rows r

layernorm x gamma beta

layernorm_rows_save src dst gamma beta dim eps n_rows mean_arr rstd_arr r

layernorm_save x gamma beta

Returns a 3-element list [y, mean, rstd]:
y : Tensor with same shape as x (the layernorm output)
mean : float_arr of length n_rows (raw, no Tensor wrapper)
rstd : float_arr of length n_rows

rms_row_sumsq xd base dim j acc

rms_row_apply xd yd gd base dim j rstd

rms_rows_save xd yd gd rstd_arr dim eps n_rows r

rmsnorm_save x gamma

Forward RMSNorm that saves rstd for backward. Returns [y, rstd].
Signature intentionally mirrors layernorm_save minus the mean output.

cpu_rms_back_accum xd gd dyd base dim j rstd s

RMSNorm backward. Per-row:
xhat_i = x_i * rstd
dxhat_i = dy_i * gamma_i
s = Σ_i dxhat_i * xhat_i / dim
dx_i = rstd * (dxhat_i - xhat_i * s)
gamma grad: dg_i += dy_i * xhat_i (accumulated across rows by caller
if learnable; for fixed-gamma models we skip it).

cpu_rms_back_write xd gd dyd dxd base dim j rstd s

cpu_rms_back xd gd dyd dxd rstd_arr n_rows dim r

rms_gamma_col xd dyd dgd base dim rstd j

RMSNorm backward w.r.t. γ. y[r,j] = (x[r,j] * rstd[r]) * γ[j], so
dγ[j] = Σ_r dy[r,j] * x[r,j] * rstd[r]. Returns a Tensor of shape
[dim] matching γ.

rms_gamma_row xd dyd rd dgd n_rows dim r

init_scaled_loop arr n fan_in seed i

Kaiming-scaled deterministic init for a weight matrix of fan_in
input dims. Produces values with std = sqrt(2/fan_in) which is the
right target variance for SiLU / ReLU-family activations. Uniform
over [-sqrt(6/fan_in), sqrt(6/fan_in)] achieves that std.
Same deterministic-scatter PRNG as the existing init_weight helpers
so runs are reproducible.

init_scaled arr n fan_in seed

rmsnorm_backward_gamma x rstd_arr dy

rmsnorm_backward_dx x gamma rstd_arr dy

Returns a Tensor of dx with the same shape as x. CPU only for now;
Metal kernel can come later if this shows up hot (it's dim-parallel,
O(rows × dim) with one reduction).

rope_row_pairs xd base d p j sign

rope_positions xd seq d p sign

silu_fwd_loop xd yd sig_out n i

silu_forward x

silu_bwd_loop xd dyd sig dxd n i

silu_backward x dy sig_arr

Given saved sig (σ(x)) from forward, compute dx = dy * silu'(x).

rope_apply x

Forward rotation. Mutates x_data in place.

rope_apply_inverse x

Inverse rotation (transpose). Used in backward pass: dq_unrotated =
rope_apply_inverse(dq_rotated).

cpu_ln_accum xd gd dyd base dim j mean rstd acc

CPU LayerNorm backward. Per-row reductions are collected into a small
scratch float_arr (row_stats: [sum_dxhat, sum_dxhat_xhat]) so we never
need to carry more than ~8 params in any helper — Rail's ARM64 codegen
gets unhappy with wide mixed int/float parameter lists.

cpu_ln_write xd gd dyd dxd base dim j mean rstd acc

cpu_ln_back xd md rd gd dyd dxd n_rows dim r

layernorm_backward_dx x mean_arr rstd_arr gamma dy

ln_gb_row x_data dy_data dg_data db_data mean_arr rstd_arr dim n_rows r

dgamma[j] = Σ_r dy[r,j] * x_hat[r,j] where x_hat = (x - mean) * rstd
dbeta[j] = Σ_r dy[r,j]

ln_gb_inner x_data dy_data dg_data db_data off dim mean rstd j

layernorm_backward_gb x mean_arr rstd_arr dy

Compute dgamma AND dbeta in one pass. Returns [dgamma_t, dbeta_t].

copy_cols_out src_data n_rows full_dim col_start block_dim dst_data r

copy_cols_row src_data src_off dst_data dst_off len j

copy_cols_in dst_data n_rows full_dim col_start block_dim src_data r

Scatter a [n_rows, block_dim] buffer INTO column block of [n_rows, full_dim].
Overwrites the destination columns.

extract_head t n_rows full_dim head_dim h

Extract head h (columns [h*head_dim .. (h+1)*head_dim)) from a
[n_rows, full_dim] tensor into a fresh [n_rows, head_dim] tensor.

insert_head dst n_rows full_dim head_dim h src

Scatter a per-head result tensor into column block h of a
[n_rows, full_dim] destination tensor (in place).

scaled_dot_attention q k v

apply_causal_mask_loop data seq i j

Causal mask: sets upper-triangle (future positions) to -1e9 so softmax
assigns them ~0. Applied to scores before softmax.

scaled_dot_attention_causal q k v

cpu_sm_sum yd dyd cols base j s

CPU softmax backward per-row: dx[j] = y[j] * (dy[j] - Σ_k y[k]*dy[k]).

cpu_sm_write yd dyd dxd cols base j sum

cpu_sm_back yd dyd dxd rows cols r

attention_backward q k v attn d_out

feedforward x w1 b1 w2 b2

sinusoidal_pe_fill arr seq dim p

Sinusoidal PE (Vaswani-style):
PE[pos, 2i] = sin(pos / 10000^(2i/d))
PE[pos, 2i+1] = cos(pos / 10000^(2i/d))

sinusoidal_pe_row arr dim p i

sinusoidal_pe_fill_next arr p

sinusoidal_pe seq dim

sinusoidal_pe_build arr seq dim p

apply_pe x pe

Apply: x[seq, dim] + pe[seq, dim]

transformer_block_prenorm x wq wk wv wo ln1_g ln1_b ln2_g ln2_b w1 b1 w2 b2

stdlib/url.rail

parse_url url

Parse URL into (scheme, host, port, path, query)
Handles: http://host:port/path?query

parse_scheme cs

parse_host cs

parse_port cs

url_parse_int_chars cs acc

Pure-Rail digit-list -> int. The builtin to_int is float-only and
silently returns 0 for plain integer strings; that broke port parsing
for any explicit ":" URL.

parse_path cs

parse_query cs

split_at_char c cs

Split char list at first occurrence of char

split_at_char_acc c cs acc

split_at_any cs stops

Split at any of the given chars

split_at_any_acc cs stops acc

has_char c lst

from_chars cs

Convert list of chars to string

url_hex_digit n

Map a nibble (0..15) to its uppercase hex character.

url_hex_val c

Map a hex character ('0'-'9','a'-'f','A'-'F') to its value, or -1.

url_is_unreserved b

An unreserved byte may appear literally; everything else is %-encoded.
Unreserved set = ALPHA / DIGIT / "-" / "_" / "." / "~"

url_enc_byte b

Encode a single byte: literal if unreserved, else "%HH".

pct_encode s

pct_encode: percent-encode every reserved/unsafe byte of a string.

pct_encode_acc cs acc

pct_decode s

pct_decode: reverse of pct_encode. "%HH" -> byte, "+" -> space.
A malformed trailing "%" (no two hex digits) is passed through literally.

pct_decode_acc cs acc

parse_query_str q

parse_query_str: "x=1&y=hello%20world" -> [(x,1),(y,hello world)]
Each key and value is percent-decoded. Empty pairs are skipped.
(parse_url's helper parse_query strips the leading "?"; this works on the
raw query body, so callers can compose: parse_query_str (parse_url ...)._5)

parse_pairs parts

parse_pair p

stdlib/x25519.rail

x25519_limb_count

x25519_a24

x25519_copy_bytes src dst src_off dst_off n

x25519_asr v n

Arithmetic shift right. Rail's shr is LOGICAL (zero-fill), which gives
wrong answers for negative intermediates produced by gf_sub. X25519's
car25519 needs arithmetic semantics (sign-fill). Identity:
asr(v, n) = ~shr(~v, n) where ~ is bitwise complement (XOR all-ones).

x25519_gf_new _

x25519_gf_copy dst src

x25519_gf_copy_at dst src i

x25519_gf_set1 g

x25519_gf_set1_at g i

x25519_gf_add o a b

Addition, subtraction — elementwise.

x25519_gf_add_at o a b i

x25519_gf_sub o a b

x25519_gf_sub_at o a b i

x25519_car o

Carry propagation. Reduce each limb to 16 bits; overflow to next,
with limb 15's overflow × 38 folded back into limb 0 (since 2^256 ≡
38 mod p; we carry 2^256 = 2 × 2^255 and 2^255 ≡ 19 so 2×19 = 38).

x25519_car_pass o i

x25519_sel p q b

Constant-time conditional swap. If b==1, swap p and q; if b==0, leave.
Uses the standard bitmask-XOR trick: c = (b==0 ? 0 : -1); t = c & (p^q);
p ^= t; q ^= t.

x25519_sel_at p q c i

x25519_gf_mul o a b

Multiplication with schoolbook 16×16 + 2-pass reduce.

x25519_mul_outer t a b i

x25519_mul_inner t a b i j

x25519_mul_fold t i

x25519_mul_copy o t i

x25519_gf_sqr o a

x25519_inv o input

Modular inverse via a^(p-2) where p-2 = 2^255 - 21. Fixed exponent,
standard double-and-multiply based on the binary representation of
2^255 - 21. Ported from TweetNaCl's inv25519 — all bits of p-2 are 1
except positions 2 and 4.

x25519_inv_loop c input a

x25519_unpack25519 o n

Unpack 32-byte little-endian u-coord into 16 limbs; mask top bit.

x25519_unpack_at o n i

x25519_pack25519 o n

Pack a gf into 32 little-endian bytes, first reducing to canonical form.

x25519_pack_reduce t _

x25519_pack_reduce_mid m t i

x25519_pack_out o t i

x25519_scalarmult q n p

x25519_q_nonzero q i acc

XOR-OR-fold across q[0..32]; returns 1 if any byte is non-zero, 0 if
every byte is zero. Constant-time scan over the full 32 bytes.

x25519_ladder a b c d e f x1 z i

x25519_mul_small o a k

Multiply gf by a small scalar constant (used for *121665 in the ladder).

x25519 scalar u

x25519_safe scalar u

Explicit-status variant. Returns [q, ok] where ok=1 on success and
ok=0 if the input was a low-order point (RFC 7748 §6.1). Strict TLS
callers should prefer this and abort the handshake on ok==0.
Rail — self-hosted compiler · All docs · GitHub