# 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:

```rail
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`


### `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`



## `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 and from_char_code — needs FFI or builtin
> For now, use ordinal values for ASCII
> These are placeholders that work via shell

### `from_char_code n`


### `hex_byte n`


### `hex_digit n`



## `stdlib/bignum_n.rail`

### `bn_new n_limbs`


### `bn_from_be bytes n_limbs`

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

### `bn_fb_at bytes x i n_limbs`


### `bn_to_be x n_limbs`

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

### `bn_tb_at x b i n_limbs`


### `bn_copy dst src n_limbs`


### `bn_copy_at dst src i n_limbs`


### `bn_zero a n_limbs`


### `bn_zero_at a i n_limbs`


### `bn_cmp a b n_limbs`


### `bn_cmp_at a b i`


### `bn_is_zero a n_limbs`


### `bn_iz_at a i n_limbs`


### `bn_uadd c a b n_limbs`


### `bn_uadd_at c a b i carry n_limbs`


### `bn_usub c a b n_limbs`


### `bn_usub_at c a b i borrow n_limbs`


### `bn_mod_add c a b m n_limbs`


### `bn_mod_sub c a b m n_limbs`


### `bn_mul_raw c a b n_limbs`


### `bn_mul_zero c i n`


### `bn_mul_outer c a b i n_limbs`


### `bn_mul_inner c a b i j carry n_limbs`


### `bn_carry_fix c i max_i`


### `bn_wide_mod c a m n_limbs`


### `bn_wm_loop r a m n_limbs bit_idx`


### `bn_shl1_out r n_limbs`


### `bn_shl1_at r i carry n_limbs`


### `bn_mul_mod c a b m n_limbs`


### `bn_sqr_mod c a m n_limbs`


### `bn_modexp_small result base e m n_limbs`


### `bn_high_bit_int e`

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

### `bn_high_bit_loop e acc`


### `bn_modexp_loop result base e m n_limbs bit_idx`



## `stdlib/bpe.rail`

### `bpe_pair_key a b`


### `bpe_key_a k`


### `bpe_key_b k`


### `bpe_nth n xs`

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

### `bpe_drop n xs`


### `bpe_contains_char cs c`


### `bpe_collect_unique input acc`


### `bpe_char_id_loop cs c i`

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

### `bpe_init_encode_loop_n cs n vocab_list acc`

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

### `bpe_init_encode_loop cs vocab_list acc`


### `bpe_init_encode_arr_loop cs n vocab_list arr i`

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

### `bpe_init_encode_arr cs vocab_list`


### `bpe_count_pairs_arr_loop arr n i m`

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

### `bpe_count_pairs_arr arr n m`


### `bpe_find_best_key_loop keys m best_k best_c`

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

### `bpe_apply_merge_n tokens n a b nid acc`

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

### `bpe_apply_merge tokens a b nid acc`


### `bpe_apply_merge_arr_loop arr n i w a b nid`

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

### `bpe_apply_merge_arr arr n a b nid`


### `bpe_best_pair_arr arr n`

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

### `bpe_train_loop_arr_old arr n merges vocab size target`

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

### `bpe_map_best_pair m best_k best_c`

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

### `bpe_best_pair_tree arr n`


### `bpe_train_loop_deferred arr n merges_rev size target`

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

### `bpe_bump counts key delta`

> Adjust one pair's count by delta.

### `bpe_delta_close counts a b nid L R k`

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

### `bpe_is_match arr n a b i`

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

### `bpe_delta_scan arr n a b nid counts i run_k L`

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

### `bpe_best_pair_persistent counts`

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

### `bpe_train_loop_inc arr n counts merges_rev size target`

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

### `bpe_fill_base_arr arr base i`

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

### `bpe_fill_merged_arr arr merges`


### `bpe_arr_to_list_rev arr i acc`


### `bpe_build_vocab base_vocab merges final_size`


### `bpe_train text target`


### `bpe_train_inc_wip text target`

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

### `bpe_train_legacy text target`

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

### `bpe_apply_all_merges tokens merges`


### `bpe_encode bpe text`


### `bpe_decode_loop ids vocab acc`


### `bpe_decode bpe ids`


### `bpe_vocab_size bpe`


### `bpe_escape_chars cs acc`


### `bpe_escape s`


### `bpe_unescape_chars cs acc`


### `bpe_unescape s`


### `bpe_escape_line v`


### `bpe_merge_to_line m`


### `bpe_save path bpe`


### `bpe_digit_val c`

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

### `bpe_parse_int_acc cs acc`


### `bpe_parse_int s`


### `bpe_nonempty s`


### `bpe_parse_merge_line line`

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

### `bpe_load path`



## `stdlib/bytes.rail`

### `mask32`


### `two32`


### `and32 a b`


### `or32 a b`


### `xor32 a b`


### `add32 a b`


### `rotr32 x n`

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

### `shr32 x n`

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

### `hex_digit c`


### `hex_to_bytes_at cs arr idx`


### `hex_to_bytes s`


### `hex_char_of n`


### `bytes_to_hex_at arr n i acc`


### `bytes_to_hex arr n`


### `be32_read arr offset`


### `be32_write arr offset value`


### `le32_read arr offset`


### `le32_write arr offset value`


### `rotl32 x n`

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

### `string_to_bytes_at cs arr i`


### `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 "<rank> <dim0> <dim1> ...".

### `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`

> "<size> <step>" 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 "<size> <step>" → 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/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.


## `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/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_resolve_a 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, 2**255-19)) % (2**255-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`


### `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`


### `ed25519_verify_step2 pub_bytes msg_bytes msg_len sig_bytes R_enc R`


### `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/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

### `close fp`

> Close file

### `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).


## `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/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/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:
> <hex-len>\r\n <bytes>\r\n <hex-len>\r\n ... 0\r\n\r\n

### `dechunk_loop body pos acc`


### `has_host_header hdrs`

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

### `build_request method ip port path ctype body extra_hdrs`


### `http_request method ip port path ctype body extra_hdrs`


### `http_get ip port path`


### `http_post ip port path ctype body`


### `http_post_json ip port path body`


### `http_get_hdr ip port path hdrs`


### `http_post_hdr ip port path ctype body hdrs`



## `stdlib/http_server.rail`

### `http_serve port handler`

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

### `write_handler_wrapper handler`

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

### `http_parse_path req`

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

### `http_response status content_type body`

> Format HTTP response

### `serve_static dir req`

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

### `guess_content_type path`

> Content type from file extension

### `quick_serve port handler_src`

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

### `http_serve_raw port`

> Raw server loop: assumes /tmp/rail_http_handler exists


## `stdlib/https_client.rail`

### `hc_read_random n`


### `hc_arr_to_cbuf arr p n`


### `hc_a2c_at arr p i n`


### `hc_cbuf_to_arr p arr n`


### `hc_c2a_at p arr i n`


### `hc_send_bytes fd arr n`


### `hc_recv_into fd p off want`


### `hc_recv_exact fd n`


### `hc_recv_exact_loop fd p off want`


### `hc_scratch_copy src dst src_off dst_off n`


### `hc_read_record fd`


### `hc_rec_fail _`


### `hc_connect_tcp ip port`


### `hc_x25519_base _`


### `hc_gen_keypair _`


### `hc_scan_finished buf n off`


### `hc_flight_append acc acc_len frag frag_len`


### `hc_read_server_flight fd key iv`


### `hc_rsf_loop fd key iv seq acc acc_len`


### `hc_rsf_err _`


### `hc_exchange_hello fd ch_msg ch_msg_len priv`


### `hc_xh_fail _`


### `hc_crlf _`


### `hc_build_req host path`


### `hc_buf_to_chars buf i acc`


### `hc_bytes_to_str buf n`


### `hc_bytes_to_str_loop arr off n acc`

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

### `hc_recv_response fd key iv seq acc`

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

### `hc_recv_loop fd key iv seq chunks total`


### `hc_recv_finalize chunks total`


### `hc_copy_chunks buf chunks write_pos`


### `hc_parse_status raw`


### `hc_parse_int s`


### `hc_pi cs acc`


### `https_get_unsafe_noverify host ip port path`


### `https_get_on_fd fd host path`


### `https_get_post_hello fd host path priv ch_msg ch_msg_len xh`


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


### `hc_err msg`


### `https_get_url_unsafe_noverify url`


### `https_get_url_dispatch parts`


### `https_parse_url url`

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

### `https_url_fail _`


### `hc_build_post host path ctype body hdrs`


### `https_post_unsafe_noverify host ip port path ctype body hdrs`

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

### `https_post_on_fd fd host path ctype body hdrs`


### `https_post_post_hello fd host path ctype body hdrs priv ch_msg ch_msg_len xh`


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


### `https_post_url_unsafe_noverify url ctype body hdrs`

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

### `https_post_url_dispatch parts ctype body hdrs`


### `hc_stream_fail _`


### `hc_arr_off_to_cbuf arr arr_off p n`

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

### `hc_aoc_loop arr arr_off p p_off n`


### `hc_scan_blank_line buf start n`

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

### `hc_arr_to_str arr n`

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

### `hc_parse_hex_str s`

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

### `hc_phs_loop cs acc`


### `hc_is_chunked_resp raw`

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

### `hc_read_response_headers fd key iv`

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

### `hc_rrh_loop fd key iv seq buf buf_len`


### `hc_rrh_fail _`


### `hc_body_state_init sha_st fp cbuf cbuf_size chunked`


### `hc_emit_raw_bulk frag off n body_state`

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

### `hc_emit_body frag off n body_state`

> Top-level chunk emitter — dispatches on chunked flag.

### `hc_emit_chunked_dispatch frag off n body_state`


### `hc_chunk_read_len frag off n body_state`


### `hc_chunk_read_body frag off n body_state`


### `hc_chunk_read_trailer frag off n body_state`


### `hc_stream_body_loop fd key iv seq body_state`

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

### `https_get_to_file_unsafe_noverify host ip port path out_path`


### `hc_stream_on_fd fd host path out_path`


### `hc_stream_post_hello fd host path priv ch_msg ch_msg_len xh out_path`


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


### `hc_stream_recv fd key iv out_path`


### `https_get_url_to_file_unsafe_noverify url out_path`

> URL convenience wrapper.


## `stdlib/https_session.rail`

### `hss_fail msg`


### `hs_valid s`

> Accessors

### `hs_fd    s`


### `hs_err   s`


### `hs_host  s`


### `hss_open_core fd host store strict`


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

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

### `hss_build_handle fd host c_hs fsm`


### `https_session_open host ip port`


### `https_session_open_strict host ip port`


### `hss_crlf _`


### `hss_build_get host path`


### `hss_hdr_list_to_str hdrs`


### `hss_build_post host path ctype body hdrs`


### `hss_send_one fd pending pending_n wire wire_n`


### `hss_recv fd s_key s_iv seq`


### `hss_recv_loop fd s_key s_iv seq chunks total header_end frame_kind frame_arg`

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

### `hss_recv_step fd s_key s_iv seq chunks total header_end frame_kind frame_arg`


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

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

### `hss_chunks_to_buf chunks total`

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

### `hss_locate_header_end chunks total`

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

### `hss_find_crlf2 buf n i`


### `hss_parse_framing chunks total hend`

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

### `hss_find_cl hdrs`

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

### `hss_is_chunked hdrs`


### `hss_to_lower s`


### `hss_tl cs acc`


### `hss_pi_skip_ws cs seen_digit acc`

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

### `hss_scan_chunked_done chunks total hend`

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

### `hss_find_chunk_end buf n start`


### `hss_is_crlf buf n i`


### `hss_recv_finalize chunks total seq ok`


### `hss_split_status_body buf total`


### `hss_clear_pending s`


### `hss_advance_seqs s new_c new_s`


### `hss_invalidate s msg`


### `https_session_get s path`


### `https_session_post s path ctype body hdrs`


### `hss_issue s req_str`


### `fst p`


### `snd p`


### `https_session_close s`



## `stdlib/https_strict.rail`

### `hs_trust_store_path _`


### `hs_chain_ok store fb`


### `hs_drive_get fd host path store`

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

### `hs_drive_post fd host path ctype body hdrs store`


### `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_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/json.rail`

### `json_skip_ws cs`

> Skip whitespace characters

### `json_parse_str cs acc`

> Parse a JSON string value (after opening quote)

### `json_parse_num cs acc`

> Parse a JSON number (integer only for now)

### `json_parse cs`

> Parse a JSON value, returns (value, remaining_chars)

### `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

### `json_items val`

> Get array items

### `json_encode val`

> Serialize JSON value to string

### `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 — good enough for small lists)

### `insert x sorted`



## `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
> <tmpdir>/req_<idx>.json, writes raw content to <tmpdir>/resp_<idx>.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/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


## `stdlib/metal_kernel.rail`

### `metal_kernel_header`

> Standard header every Metal kernel needs.

### `metal_kernel_sig`

> The standard parameter signature for a unary kernel.

### `emit_metal_unary name body_expr`

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

### `metal_apply_unary src name x_arr`

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


## `stdlib/mlx_client.rail`

### `json_quote s`


### `mlx_json_esc s`

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

### `show_temp t`

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

### `mlx_payload model prompt max_tokens temperature`


### `extract_field body key`


### `mlx_chat ip port model prompt max_tokens temperature`



## `stdlib/mmap.rail`

### `prot_none`

> Protection flags

### `prot_read`


### `prot_write`


### `prot_exec`


### `prot_rw`


### `map_shared`

> Map flags

### `map_private`


### `map_anon`


### `alloc_pages len`

> Map anonymous memory (no file backing)

### `free_pages addr len`

> Unmap memory


## `stdlib/optim.rail`

### `adam_state n`

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

### `adam_hyp lr b1 b2 eps t`

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

### `cpu_adam_loop w g m v h n i`

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

### `adam_update_raw w g state lr b1 b2 eps`

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

### `adam_update param grad state lr b1 b2 eps`

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

### `apply_decay_loop w lr_wd n i`

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

### `adamw_update_raw w g state lr b1 b2 eps wd`


### `adamw_update param grad state lr b1 b2 eps wd`


### `adam_lr_mult_gamma`


### `adam_lr_mult_tied_embed`


### `adam_lr_mult_default`


### `adam_default_wd`


### `adam_default_lr`

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

### `adam_default_b1`


### `adam_default_b2`


### `adam_default_eps`


### `cosine_decay step warmup max_steps base_lr`


### `sum_sq_tensor g`


### `total_sq_into grads acc_arr`

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

### `scale_each grads factor acc`

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

### `clip_grad_norm grads max_norm`

> Returns (clipped_grads, total_norm).


## `stdlib/oracle.rail`

### `oracle_compile_at code path`

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

### `oracle_compile code`


### `count_error_lines lines acc`

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

### `oracle_errors code`


### `oracle_ok code`

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

### `oracle_compile_and_run_at code path`

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

### `oracle_compile_and_run code`


### `is_ws c`


### `count_nw_loop cs acc`


### `count_non_whitespace code`


### `dedupe_contains xs target`

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

### `dedupe_loop cs acc`


### `count_unique_chars code`


### `oracle_stats_impl code`

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

### `oracle_min_nw_for_bonus`

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

### `oracle_min_unique_chars`

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

### `oracle_min_decls_for_bonus`

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

### `is_ident_alpha c`

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

### `is_decl_line line`

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

### `count_decl_lines_loop lines acc`


### `count_top_level_decls code`


### `oracle_quality code`


### `ob_write_codes tmpdir codes idx`


### `ob_make_cmd tmpdir idx`

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

### `ob_build_cmds tmpdir n idx acc`


### `ob_score_one code out`

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

### `ob_score_loop codes pairs acc`


### `oracle_compile_batch codes max_conc`



## `stdlib/parallel.rail`

### `parallel_default_conc_mini`

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

### `parallel_default_conc_studio`


### `parallel_default_timeout`

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

### `par_rstrip_nl s`

> Strip trailing newline left by shell command substitution.

### `par_digit_val c`

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

### `par_parse_int_loop cs acc`


### `par_parse_int s`


### `par_init_tmpdir _`

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

### `par_write_cmds tmpdir cmds idx`

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

### `par_driver_header tmpdir n max_conc to_s`

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

### `par_driver_worker _`


### `par_driver_loop _`


### `par_driver_emit _`


### `par_make_driver tmpdir n max_conc to_s`


### `par_strip_trailing_nl s`

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

### `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/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/sha256.rail`

### `sha256_k_hex`


### `sha256_h_hex`


### `sha256_k_table _`


### `sha256_h_init _`


### `load_words bytes dst i n`


### `sha_ch x y z`


### `sha_maj x y z`


### `sha_big_sigma0 x`


### `sha_big_sigma1 x`


### `sha_small_sigma0 x`


### `sha_small_sigma1 x`


### `expand_schedule w t`


### `sha_round state w k_tbl t`


### `sha_process_block h state block k_tbl`


### `sha_copy_bytes src dst src_off dst_off n`


### `sha_process_from msg_bytes msg_len h state k_tbl off`


### `sha_final_block msg_bytes msg_len h state k_tbl remain_off`


### `arr_slice src start n`


### `sha256 msg`


### `sha256_hex msg`


### `sha256_init _`


### `sha256_update_arr st arr off n`

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

### `sha256_stream_full st arr off remaining k_tbl h`


### `sha256_update_str st s`

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

### `sha256_finalize st`

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

### `sha256_finalize_hex st`



## `stdlib/sha512.rail`

### `s5_and32 a b`


### `s5_or32  a b`


### `s5_xor32 a b`


### `s5_add32 a b`


### `s5_shr32 x n`


### `s5_shl32 x n`


### `s5_w_xor dst di a ai b bi`


### `s5_w_and dst di a ai b bi`


### `s5_w_add dst di a ai b bi`

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

### `s5_w_rotr dst di a ai n`

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

### `s5_w_shr dst di a ai n`

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

### `s5_k_hex _`


### `s5_h512_hex _`


### `s5_h384_hex _`


### `s5_bytes_to_words bytes off dst base n_words`

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

### `s5_load_k _`


### `s5_load_h hex_fn`


### `s5_sigma0 tmp ti w wi`

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

### `s5_sigma1 tmp ti w wi`


### `s5_bigsig0 tmp ti w wi`


### `s5_bigsig1 tmp ti w wi`


### `s5_ch tmp ti x xi y yi z zi`

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

### `s5_maj tmp ti x xi y yi z zi`

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

### `s5_process_block h block k`

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

### `s5_extend_w w i`


### `s5_round_loop v w k i`


### `s5_add_state h v`


### `s5_add_state_at h v i`


### `s5_padded_len n`


### `s5_pad_bytes buf n`


### `s5_run_blocks h padded total k off`


### `s5_process_block_at h padded off k`


### `s5_state_to_bytes h`

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

### `s5_state_serialize h out i`


### `sha512_bytes buf n`


### `sha384_bytes buf n`



## `stdlib/signal.rail`

### `sighup`

> Signal numbers (macOS/BSD)

### `sigint`


### `sigquit`


### `sigterm`


### `sigusr1`


### `sigusr2`


### `sig_dfl`

> Special handlers

### `sig_ign`


### `ignore_signal sig`

> Ignore a signal

### `default_signal sig`

> Restore default handler

### `raise_signal sig`

> Send signal to self


## `stdlib/slack_client.rail`

### `sk_trim_line s`


### `sk_esc s`

> Minimal JSON escape: backslash, quote, newline.

### `sk_body channel text`


### `sk_ok body`

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

### `slack_post_text channel text token_path`



## `stdlib/socket.rail`

### `af_inet`


### `sock_stream`


### `sock_dgram`


### `sockaddr_in_size`


### `parse_int s`

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

### `parse_int_acc cs acc`


### `parse_ip s`

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

### `render_ip n`

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

### `make_sockaddr_in port ip_str`

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

### `sa_init_zero p i`


### `sa_port sa`

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

### `sa_addr sa`

> Read addr (host order int) out of a sockaddr_in

### `tcp_socket _`


### `udp_socket _`


### `close_socket fd`


### `sol_socket_mac`

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

### `so_reuseaddr_mac`


### `write_u32_le p v`

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

### `set_reuseaddr fd`

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

### `set_tcp_nodelay fd`

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

### `bind_tcp ip_str port`

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

### `accept_tcp listen_fd`

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

### `tcp_bytes_to_str buf off n acc`

> Copy `n` bytes from buf[off..off+n) into a Rail string.
> Uses char_from_int + join; O(N²) alloc, fine for small reads.

### `recv_http_request fd`

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

### `tcp_crlf_crlf _`


### `recv_headers_loop fd buf acc`


### `send_all_tcp fd s`

> Send a full response string, looping on partial sends.

### `send_all_tcp_loop fd s off total`


### `bind_udp ip_str port`

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

### `send_to fd buf ip_str port`

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

### `recv_from fd bufsize`

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


## `stdlib/sqlite.rail`

### `sqlite_ok`

> Return codes

### `sqlite_error`


### `sqlite_busy`


### `exec db sql`

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

### `close_db db`

> Close database


## `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)

### `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_bias_relu_f16 a b bias`

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

### `matmul_bias_gelu_f16 a b bias`

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

### `half_storage_slots n`

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

### `half_tensor_new shape`

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

### `half_of_tensor t`

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

### `tensor_of_half h`

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

### `matmul_half a b`

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

### `matmul_mixed x w_h`

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

### `add_half a b`

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

### `scale_half t s`

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

### `transpose_half t`

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

### `softmax_half t`

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

### `add_loop a_data b_data c_data n i`

> Add loop

### `sub_loop a_data b_data c_data n i`

> Sub loop

### `hmul_loop a_data b_data c_data n i`

> Hadamard (element-wise multiply) loop

### `scale_loop src dst n i s`

> Scalar multiply loop

### `gpu_binop_dispatch op a_data b_data n`

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

### `gpu_unop_dispatch op src_data n`

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

### `tensor_add a b`

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

### `tensor_sub a b`

> tensor_sub: element-wise subtraction

### `tensor_mul a b`

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

### `tensor_scale t s`

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

### `relu_loop src dst n i`

> ReLU loop: max(0, x)

### `tensor_relu t`


### `gelu_loop src dst n i`

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

### `tensor_gelu t`


### `tensor_exp_gpu t`

> GPU-accelerated exp, tanh, sigmoid

### `exp_loop src dst n i`


### `tensor_tanh_gpu t`


### `tanh_loop src dst n i`


### `tensor_sigmoid t`


### `sigmoid_loop src dst n i`


### `sum_loop data n i acc_arr`

> Sum all elements (uses mutable accumulator array)

### `tensor_sum t`


### `tensor_mean t`

> Mean of all elements

### `relu_mask_loop src dst n i`

> tensor_relu_mask: 1.0 where x > 0, 0.0 otherwise

### `tensor_relu_mask t`


### `gelu_back_loop x_data grad_data dst n i`

> tensor_gelu_backward: dGELU/dx * upstream gradient

### `tensor_gelu_backward x grad`


### `softmax_row src dst cols offset`

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

### `softmax_find_max src cols offset acc j`


### `softmax_exp_sum src dst cols offset mx acc j`


### `softmax_normalize dst cols offset sm j`


### `softmax_rows src dst rows cols r`


### `gpu_softmax_dispatch src_data rows cols`

> GPU softmax dispatch: daemon or file

### `tensor_softmax t`


### `sum_last_loop src dst rows cols r`

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

### `sum_last_row src cols offset acc j`


### `tensor_sum_last t`


### `sum_batch_loop src dst rows cols r`

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

### `sum_batch_add_row src dst cols offset j`


### `tensor_sum_batch t`


### `tensor_mean_last t`

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

### `broadcast_last_loop src dst rows cols r`

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

### `broadcast_last_fill dst offset cols v j`


### `tensor_broadcast_last t cols`


### `mul_bcast_loop a_data b_data dst rows cols r`

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

### `mul_bcast_row a_data b_data dst cols offset j`


### `tensor_mul_broadcast a b`


### `one_hot_loop indices dst batch vocab i`

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

### `tensor_one_hot t vocab_size`


### `embed_lookup_loop weights indices dst batch dim i`

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

### `embed_copy_row src dst dim src_off dst_off j`


### `tensor_embedding_lookup weights indices`


### `tensor_slice_row t row_idx`

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

### `accum_row_loop grad weights indices batch dim i`

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

### `accum_row_add dst src dim dst_off src_off j`


### `tensor_accumulate_row weights grad indices`


### `tensor_scale_by_loss t loss_grad`

> tensor_scale_by_loss: scale all elements by scalar loss gradient

### `tensor_copy t`

> tensor_copy: alias for tensor_clone

### `tensor_matmul a b`

> tensor_matmul: alias for matmul (autograd uses this name)

### `tensor_sum_all t`

> tensor_sum_all: alias for tensor_sum

### `tensor_ones_like_shape shape`

> tensor_ones_like_shape: ones tensor with given shape

### `tensor_scalar_val t`

> tensor_scalar_val: extract scalar from a 1-element tensor

### `tensor_from_int n`

> tensor_from_int: wrap an int as a 1-element tensor

### `tensor_to_int t`

> tensor_to_int: extract int from a 1-element tensor

### `tensor_get_int t i`

> tensor_get_int: get element as int (for indexing)

### `map_scalar_loop src dst n i fn_tag`

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

### `tensor_map_scalar fn_tag t`


### `ce_loss_loop probs targets batch vocab i acc`

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

### `tensor_cross_entropy_loss probs targets`


### `transpose_copy_loop src dst rows cols i`

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

### `transpose_copy_row src dst rows cols i j`


### `gpu_transpose_dispatch src_data rows cols`

> GPU transpose dispatch: daemon or file

### `tensor_transpose t`


### `tensor_reshape t new_shape`

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

### `tensor_rank t`

> tensor_rank: number of dimensions

### `copy_range src dst src_off n i`

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

### `tensor_slice t start end`

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

### `ln_row x y gamma beta row dim eps`

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

### `ln_row_sum x base dim i acc`


### `ln_row_sq x base dim i mean acc`


### `ln_row_apply x y gamma beta base dim i mean rstd`


### `ln_rows x y gamma beta rows dim eps r`


### `tensor_layer_norm t gamma beta eps`


### `print_row t n_cols i j`


### `print_rows t n_rows n_cols i`


### `tensor_print_2d t`


### `tensor_clone t`

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

### `tensor_clone_copy src dst n i`


### `read_binary path`

> Read binary file as float_arr of bytes

### `read_binary_fill arr hex n i`


### `hex_val c`


### `tensor_test_main _`


### `grad_eps`


### `sum_sq_grad t`


### `sum_sq_grad_acc t n i acc`


### `sum_relu_grad t`


### `sum_relu_grad_acc t n i acc`


### `sum_exp_grad t`


### `sum_exp_grad_acc t n i acc`


### `numgrad_sq t i`


### `numgrad_relu_fn t i`


### `numgrad_exp_fn t i`


### `abs_f x`


### `check_g name i numerical analytical`


### `gradient_test_main _`



## `stdlib/tensord.rail`

### `tensord_port`


### `tensord_host_str`


### `tensord_open _`


### `tensord_has_nl buf len i`

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

### `tensord_recv_line_loop fd buf max off`


### `tensord_exec cmd`


### `tensord_available _`


### `tensord_exec_on fd cmd`

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

### `tensord_matmul_on fd a_data b_data m k n`


### `tensord_close_fd fd`


### `tensord_build_cmd m k n`


### `tensord_matmul a_data b_data m k n`



## `stdlib/test.rail`

### `assert_eq name expected actual`

> Assert two values are equal

### `assert_eq_str name expected actual`

> Assert two strings are equal

### `assert_true name cond`

> Assert condition is true

### `assert_false name cond`

> Assert condition is false

### `run_suite name results`

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


## `stdlib/thumb2.rail`

### `t2_hw buf off`


### `t2_is_32bit_hw hw`


### `t2_inst_len buf off`


### `t2_sext_n v nbits`


### `t2_make_result kind target length op_hi op_lo`


### `t2_branch32 hi lo addr`


### `t2_b16 hw addr`

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

### `t2_bcond16 hw addr`

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

### `t2_bxblx hw`

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

### `t2_pop16 hw`

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

### `t2_push16 hw`

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

### `t2_cb16 hw addr`

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

### `t2_decode buf off`

> Top-level decoder.

### `t2_kind_name k`


### `t2_cond_name c`


### `t2_hex8_acc n i acc`


### `t2_hex8 n`


### `t2_hex4_acc n i acc`


### `t2_hex4 n`


### `t2_hex2 n`


### `t2_decode_str buf off`


### `t2_disasm_loop buf base limit max i`

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

### `t2_disasm buf base limit max`



## `stdlib/thumb2_asm.rail`

### `asm_new base_addr`


### `asm_base s`


### `asm_buf s`


### `asm_cur_off s`


### `asm_capacity s`


### `asm_insts s`


### `asm_labels s`


### `asm_label_count s`


### `asm_inst_count_ref s`


### `asm_decl_label s`

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

### `asm_label_here s id`

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

### `asm_define_label s id offset_or_index`

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

### `asm_label_offset s id`

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

### `asm_inst_count s`


### `asm_push_inst s kind op0 op1 op2 op3 est_len`


### `asm_inst_get s i`


### `asm_b s label_id`


### `asm_bl s label_id`


### `asm_bcond s label_id cond`


### `asm_bx s rm`


### `asm_push s reglist8 lr`


### `asm_pop s reglist8 pc`


### `asm_nop s`


### `asm_movs_imm8 s rd imm8`


### `asm_adds_imm8 s rd imm8`


### `asm_subs_imm8 s rd imm8`


### `asm_cmp_imm8  s rd imm8`


### `asm_mov_reg   s rd rm`


### `asm_movw s rd imm16`


### `asm_movt s rd imm16`


### `asm_ldr_w s rt rn imm12`


### `asm_str_w s rt rn imm12`


### `asm_mov32 s rd value`

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

### `asm_layout s`


### `asm_layout_loop s i off n`


### `asm_resolve_labels labels insts n_labels n_inst total i`


### `asm_emit_one s inst self_off`


### `asm_emit_loop s i n`


### `asm_finalize s`


### `asm_size s`



## `stdlib/thumb2_emit.rail`

### `te_emit_hw buf off hw`


### `te_emit32 buf off hi lo`


### `te_b buf off addr target`


### `te_bcond buf off addr target cond`


### `te_bx buf off rm`

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

### `te_bl buf off addr target`


### `te_b_w buf off addr target`


### `te_bcond_w buf off addr target cond`


### `te_push buf off reglist8 lr`


### `te_pop buf off reglist8 pc`


### `te_nop buf off`


### `te_movs_imm8 buf off rd imm8`


### `te_cmp_imm8 buf off rd imm8`


### `te_adds_imm8 buf off rd imm8`


### `te_subs_imm8 buf off rd imm8`


### `te_ldr_pc buf off rt imm8`

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

### `te_mov_reg buf off rd rm`

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

### `te_movw buf off rd imm16`


### `te_movt buf off rd imm16`


### `te_ldr_w buf off rt rn imm12`


### `te_str_w buf off rt rn imm12`



## `stdlib/time.rail`

### `now _`

> Get current Unix timestamp (seconds since epoch)

### `wait n`

> Sleep for n seconds


## `stdlib/tls13.rail`

### `tls13_build_label L label context context_len`


### `tls13_hkdf_expand_label secret label context context_len length`


### `tls13_derive_secret secret label messages messages_len`


### `tls13_derive_secret_empty secret label`

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

### `tls13_derive_schedule ecdhe ecdhe_len hs_hash ap_hash`


### `tls13_derive_key traffic_secret`


### `tls13_derive_iv traffic_secret`


### `tls13_record_nonce iv seq`


### `tls13_finished_key traffic_secret`


### `tls13_compute_finished traffic_secret transcript_hash`



## `stdlib/tls13_cert_verify.rail`

### `cv_context_str _`


### `cv_context_len _`


### `cv_build_signed transcript_hash`

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

### `cv_fill_spaces buf i`


### `cv_extract_p256_pubkey cert cert_len`


### `cv_check_p256_params cert params_off`

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

### `cv_pk_fail _`


### `tls13_cert_verify_sig cert cert_len sig_alg sig sig_len transcript_hash`


### `cv_verify_ecdsa_p521 cert cert_len sig sig_len signed_data`

> ECDSA-P521 CertificateVerify over TLS 1.3 signed_data.

### `cv_verify_ecdsa cert cert_len sig sig_len signed_data`


### `cv_verify_rsa_pss cert cert_len sig sig_len signed_data`


### `cv_pad32 src off len`

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

### `cv_validate_hostname cert cert_len host`


### `cv_san_walk cert cert_len host host_len off end`


### `cv_dns_match cert v_off v_len host host_len`

> Match a single SAN dNSName entry (cert[v_off..v_off+v_len)) against host.

### `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`



## `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 <unk>).

### `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`


### `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


## `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_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`


