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)), andappend 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 intobufstarting atsig_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. Preferhttps_postfrom
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-arrayarrstarting atarr_offinto 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-writenbytes fromfrag[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 breturns true iffashould come beforeb.
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 parameterxis untyped, sox == xwould 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_exprshould referencex(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.matcharms (| 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
Feednbytes fromarrstarting atoffinto 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
Sendbuf(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 (theto_intbuiltin 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 tomaxinstructions starting at byte offsetbase.
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 ofsrcstarting atoffinto 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.