boot2

Playing with the boostrap
git clone https://git.ryansepassi.com/git/boot2.git
Log | Files | Refs

commit ce7dbc4c1ff6509f28aa721ebffdffda6b6bb051
parent a52dc7712af059af01946616204fd6aebefc7652
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 23 Apr 2026 16:48:36 -0700

m1pp/m1pp.M1: scrub evolutionary comments

Comments now describe current state and intent only. No phase numbers,
no track / layer / oracle references, no "this used to do X" narratives.

Section dividers renamed from "Phase N STUBS:" to topic-named blocks
(e.g. "Stream stack + expansion-pool lifetime", "Argument parsing").
Inline `Oracle: m1pp.c:NAME.` per-function refs deleted; oracle-anchored
prose ("matches C oracle", "the oracle spells xor as $", "the C oracle
uses 512-byte tmp") rephrased to state the rule directly. Stale sizing
notes (32 macros, 256 expand slots) updated to match current caps.

Behavior unchanged: all 11 m1pp oracle-parity fixtures and the P1 hello
fixture still pass.

Diffstat:
Mm1pp/m1pp.M1 | 184+++++++++++++++++++++++++++++++++++--------------------------------------------
1 file changed, 82 insertions(+), 102 deletions(-)

diff --git a/m1pp/m1pp.M1 b/m1pp/m1pp.M1 @@ -1,21 +1,23 @@ -## m1pp.M1 — bootstrap M1 macro-expander, P1v2 port. +## m1pp.M1 — bootstrap M1 macro-expander, P1v2. ## ## Runtime shape: m1pp input.M1 output.M1 ## ## Pipeline: -## _start argv/argc from kernel SP; openat+read into input_buf -## -> call lex_source -## -> call process_tokens -## -> openat+write output_buf to argv[2]; exit -## lex_source input_buf -> source_tokens[] (via append_text + push_source_token) -## process_tokens source_tokens[] -> output_buf (via emit_token / emit_newline, -## branching to define_macro at -## line-start %macro). Phase 3 -## rewrites this into a stream- -## driven loop that dispatches -## the Phase 4+ stubs. -## define_macro parse %macro header+body; record in macros[] + macro_body_tokens[]; -## consume through the %endm line without emitting output +## _start argv/argc from kernel SP; openat+read into input_buf; +## call lex_source, then process_tokens; openat+write +## output_buf to argv[2]; exit. +## lex_source input_buf -> source_tokens[] (via append_text + +## push_source_token). +## process_tokens Stream-driven loop. Pushes source_tokens as the initial +## stream and walks it token-by-token, dispatching to +## define_macro at line-start %macro, emit_newline / +## emit_token for pass-through, expand_builtin_call for +## !@%$ and %select, and expand_call for user macros. +## Macro expansions and %select push fresh streams onto +## streams[]; popping rewinds the expansion pool. +## define_macro Parse %macro header+body; record in macros[] + +## macro_body_tokens[]; consume through the %endm line +## without emitting output. ## ## P1v2 ABI: a0..a3 arg/return, t0..t2 caller-saved temps, s0..s3 callee-saved ## (unused here). Non-leaf functions use enter_0 / leave. _start has no frame; @@ -28,8 +30,8 @@ DEFINE M1PP_OUTPUT_CAP 0000010000000000 DEFINE M1PP_TEXT_CAP 0080000000000000 DEFINE M1PP_TOKENS_END 0080010000000000 ## Macro record is 296 bytes: name (16) + param_count (8) + params[16]*16 (256) -## + body_start (8) + body_end (8). 32 macros × 296 = 9472 bytes = MACROS_CAP. -## Body-token arena: 256 × 24 = 6144 bytes = MACRO_BODY_CAP. +## + body_start (8) + body_end (8). MACROS_CAP fits 256 records (75776 B). +## Body-token arena fits 4096 tokens (98304 B = 0x18000). DEFINE M1PP_MACRO_RECORD_SIZE 2801000000000000 DEFINE M1PP_MACRO_BODY_START_OFF 1801000000000000 DEFINE M1PP_MACRO_BODY_END_OFF 2001000000000000 @@ -52,7 +54,7 @@ DEFINE TOK_PASTE 0600000000000000 ## Token record stride (kind + text_ptr + text_len). Advance a Token* by this. DEFINE M1PP_TOK_SIZE 1800000000000000 -## --- Phase 3+ data structure sizes (stubbed functions reference these) ------- +## --- Stream / expansion-pool / expression-frame sizes ------------------------ ## Stream record: 40 bytes. Fields (each 8 bytes): ## +0 start Token* ## +8 end Token* (exclusive) @@ -68,7 +70,7 @@ DEFINE M1PP_STREAM_MARK_OFF 2000000000000000 ## Stream stack cap: 16 streams × 40 = 640 bytes. DEFINE M1PP_STREAM_STACK_CAP 8002000000000000 -## Expansion pool: 256 Token slots × 24 bytes = 6144 bytes. +## Expansion pool fits 4096 Token slots × 24 bytes = 98304 bytes (0x18000). DEFINE M1PP_EXPAND_CAP 0080010000000000 ## ExprFrame record: 144 bytes. Fields: @@ -769,9 +771,9 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## --- Main processor ---------------------------------------------------------- -## Phase-3+ stream-driven loop. Pushes source_tokens as the initial stream, then -## drives the streams[] stack until it empties. Per iteration: -## pop the stream if exhausted, otherwise dispatch on the current token: +## Stream-driven loop. Pushes source_tokens as the initial stream, then drives +## the streams[] stack until it empties. Per iteration: pop the stream if +## exhausted, otherwise dispatch on the current token: ## - line-start %macro -> shim into define_macro via proc_pos ## - TOK_NEWLINE -> emit_newline, advance, set line_start = 1 ## - WORD + LPAREN follow + name in {! @ % $ %select} @@ -1313,11 +1315,12 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 3 STUBS: stream stack + expansion-pool lifetime ------------------- +## --- Stream stack + expansion-pool lifetime --------------------------------- ## ============================================================================ -## Phase 3 isolates lifecycle plumbing with no semantic change: after Phase 3 -## lands, process_tokens is rewritten to be stream-driven but only exercises -## the pass-through + %macro paths, so Phase 2 parity still holds. +## process_tokens drives a stack of token streams. The source token array is +## pushed first; each macro expansion or %select chosen-branch pushes a fresh +## stream backed by a slice of expand_pool, popping rewinds pool_used to the +## stream's pool_mark. ## push_stream_span(a0=start_tok, a1=end_tok, a2=pool_mark) -> void (fatal on overflow) ## Push Stream { start = pos = a0, end = a1, line_start = 1, pool_mark = a2 } @@ -1326,7 +1329,7 @@ DEFINE EXPR_INVALID 1100000000000000 ## ## stream_top is maintained as a byte offset into streams[] (count * 40), ## matching the running-tail-pointer pattern used by source_end / macros_end. -## Reads/writes: streams, stream_top. Leaf. Oracle: m1pp.c:push_stream_span. +## Reads/writes: streams, stream_top. Leaf. :push_stream_span # new_top = stream_top + STREAM_SIZE; if (cap < new_top) fatal la_t0 &stream_top @@ -1355,7 +1358,7 @@ DEFINE EXPR_INVALID 1100000000000000 ## current_stream() -> a0 = &streams[stream_top-1], or 0 if empty. Leaf. ## stream_top is a byte offset, so &streams[top-1] = streams + stream_top - 40. -## Reads: streams, stream_top. Oracle: m1pp.c:current_stream. +## Reads: streams, stream_top. :current_stream la_a0 &stream_top ld_t0,a0,0 @@ -1373,7 +1376,7 @@ DEFINE EXPR_INVALID 1100000000000000 ## pop_stream() -> void. Leaf. ## Decrement stream_top. If the popped stream's pool_mark >= 0, restore ## pool_used = pool_mark (reclaim the expansion-pool space it used). -## Reads/writes: streams, stream_top, pool_used. Oracle: m1pp.c:pop_stream. +## Reads/writes: streams, stream_top, pool_used. :pop_stream la_a0 &stream_top ld_t0,a0,0 @@ -1400,7 +1403,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## Append each 24-byte Token in [start, end) to expand_pool at pool_used, ## advancing pool_used accordingly. ## Reads/writes: expand_pool, pool_used. Leaf. -## Oracle: m1pp.c:copy_span_to_pool. :copy_span_to_pool :cstp_loop # if (start == end) done @@ -1441,7 +1443,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## Otherwise push_stream_span(expand_pool+mark, expand_pool+pool_used, mark). ## Reads/writes: expand_pool, pool_used, streams, stream_top. Non-leaf: ## needs a frame so the call to push_stream_span doesn't clobber LR. -## Oracle: m1pp.c:push_pool_stream_from_mark. :push_pool_stream_from_mark enter_0 # if (pool_used == mark) return @@ -1463,7 +1464,7 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 4 STUB: argument parsing ------------------------------------------ +## --- Argument parsing ------------------------------------------------------- ## ============================================================================ ## parse_args(a0=lparen_tok, a1=limit_tok) -> void (fatal on unterminated/overflow) @@ -1478,7 +1479,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## call_end_pos = one past the closing RPAREN ## ## Fatal on: > 16 args, reaching limit without matching RPAREN. -## Oracle: m1pp.c:parse_args. :parse_args # tok = lparen + 1; arg_start = tok; depth = 1; arg_index = 0 addi_a0,a0,24 @@ -1648,13 +1648,13 @@ DEFINE EXPR_INVALID 1100000000000000 b ## ============================================================================ -## --- Phase 5 STUBS: macro lookup + call expansion ---------------------------- +## --- Macro lookup + call expansion ------------------------------------------ ## ============================================================================ ## find_macro(a0=tok) -> a0 = Macro* or 0. Leaf. ## Non-zero only if tok is TOK_WORD, text.len >= 2, text[0] == '%', and ## (text+1, len-1) equals macros[i].name for some i. First match wins. -## Reads: macros, macros_end. Oracle: m1pp.c:find_macro. +## Reads: macros, macros_end. :find_macro # if (tok.kind != TOK_WORD) return 0 ld_a1,a0,0 @@ -1731,7 +1731,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## find_param(a0=macro_ptr, a1=tok) -> a0 = (index+1) or 0. Leaf. ## Linear search over macro->params[0..param_count). Non-WORD tok -> 0, so ## callers can test the return against zero without pre-filtering. -## Oracle: m1pp.c:find_param. :find_param # if (tok.kind != TOK_WORD) return 0 ld_a2,a1,0 @@ -1826,7 +1825,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## copy_arg_tokens_to_pool(a0=arg_start, a1=arg_end) -> void (fatal if empty) ## Non-leaf (calls copy_span_to_pool). Empty arg is an error. -## Oracle: m1pp.c:copy_arg_tokens_to_pool. :copy_arg_tokens_to_pool enter_0 # if (arg_start == arg_end) fatal @@ -1839,7 +1837,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## copy_paste_arg_to_pool(a0=arg_start, a1=arg_end) -> void (fatal unless len 1) ## Enforces the single-token-argument rule for params adjacent to ##. -## Oracle: m1pp.c:copy_paste_arg_to_pool. :copy_paste_arg_to_pool enter_0 # if ((arg_end - arg_start) != 24) fatal @@ -1864,7 +1861,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## emt_after_pos = token one past the matching ')' (= call_end_pos) ## emt_mark = pool_used as of entry (start of expansion slice) ## -## Oracle: m1pp.c:expand_macro_tokens. :expand_macro_tokens enter_0 @@ -1905,10 +1901,8 @@ DEFINE EXPR_INVALID 1100000000000000 la_br &err_bad_macro_header bne_t0,t1 - # Snapshot call_end_pos -> emt_after_pos now (matches C oracle: end_pos - # is captured before the body walk, which is technically safe in this - # implementation but spares us a re-read in case future helpers write - # call_end_pos). + # Snapshot call_end_pos -> emt_after_pos before the body walk, so + # nothing in the substitution loop can clobber the resume position. la_a0 &call_end_pos ld_t0,a0,0 la_a1 &emt_after_pos @@ -2108,7 +2102,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## Calls expand_macro_tokens for the call at stream->pos, sets ## stream->pos = emt_after_pos, stream->line_start = 0, and ## push_pool_stream_from_mark(emt_mark) to rescan the expansion. -## Oracle: m1pp.c:expand_call. :expand_call enter_8 @@ -2146,17 +2139,15 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 6 STUBS: ## token paste compaction -------------------------------- +## --- ## token paste compaction ---------------------------------------------- ## ============================================================================ ## append_pasted_token(a0=dst_tok, a1=left_tok, a2=right_tok) -> void (fatal) ## Concatenate left->text and right->text into paste_scratch, then call ## append_text(&paste_scratch, total_len) for stable storage in text_buf, -## and write *dst = { TOK_WORD, text_ptr, total_len }. The C oracle uses a -## 512-byte tmp buffer; we use 256 to fit M0's quoted-literal cap. Fatal -## (err_text_overflow) if combined length exceeds 256 bytes; append_text -## handles its own text_buf overflow check. -## Oracle: m1pp.c:append_pasted_token. +## and write *dst = { TOK_WORD, text_ptr, total_len }. paste_scratch is +## 256 bytes (M0's quoted-literal cap). Fatal err_text_overflow if combined +## length exceeds 256 bytes; append_text handles its own text_buf overflow. :append_pasted_token enter_0 @@ -2257,7 +2248,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## PASTE and the next token. Copy other tokens forward. Update pool_used to ## the new end. Fatal (err_bad_macro_header — closest "bad input" label) if ## ## is first, last, or adjacent to NEWLINE/PASTE. -## Oracle: m1pp.c:paste_pool_range. :paste_pool_range enter_0 @@ -2393,14 +2383,13 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 7 STUBS: integer atoms + S-expression evaluator ------------------- +## --- Integer atoms + S-expression evaluator --------------------------------- ## ============================================================================ ## parse_int_token(a0=tok) -> a0 = i64 (fatal on bad). Leaf. -## Accepts decimal (optional leading '-') and 0x-prefixed hex. For positive -## inputs the oracle goes through strtoull and reinterprets as i64, so u64 -## values with the high bit set wrap to negative i64. -## Oracle: m1pp.c:parse_int_token. +## Accepts decimal (optional leading '-') and 0x-prefixed hex. Positive +## values are accumulated as u64 and reinterpreted as i64, so values with +## the high bit set wrap to negative i64. ## ## Register usage (leaf, no calls): ## t0 = src ptr (cursor into text) @@ -2558,12 +2547,10 @@ DEFINE EXPR_INVALID 1100000000000000 :pit_done ret -## expr_op_code(a0=tok) -> a0 = EXPR_ADD..EXPR_GE, or EXPR_INVALID. Leaf. +## expr_op_code(a0=tok) -> a0 = EXPR_ADD..EXPR_GE, or EXPR_INVALID. ## Accepts operator tokens: + - * / % << >> & | $ ~ = == != -## < <= > >=. Note: the oracle spells XOR as "$" (not "^"). Keep parity -## with the oracle unless docs/M1M-IMPL.md is updated. Non-WORD tok or -## unknown operator -> EXPR_INVALID. -## Oracle: m1pp.c:expr_op_code. +## < <= > >=. XOR is spelled `$`, not `^`. Non-WORD tok or unknown +## operator -> EXPR_INVALID. ## ## tok_eq_const is a leaf but clobbers a0..a3,t0..t2; spill tok to eoc_tok ## once, reload before each compare. Needs an enter_0 frame because it @@ -2668,7 +2655,7 @@ DEFINE EXPR_INVALID 1100000000000000 la_br &eoc_or bnez_a0 - # "$" -> EXPR_XOR (oracle spells xor as "$") + # "$" -> EXPR_XOR (XOR is spelled `$`, not `^`) la_a0 &eoc_tok ld_a0,a0,0 la_a1 &op_dollar @@ -2832,14 +2819,14 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## apply_expr_op(a0=op_code, a1=args_ptr, a2=argc) -> a0 = i64 result -## Reduce args[0..argc) per op (see docs/M1M-IMPL.md "Operators"): +## Reduce args[0..argc) per op: ## + * & | $ variadic, argc >= 1 ## - argc >= 1 (argc == 1 is negate, else left-assoc subtract) ## / % binary, div-by-zero fatal ## << >> binary (>> is arithmetic) ## ~ unary ## = == != < <= > >= binary -## Fatal on wrong argc or EXPR_INVALID. Oracle: m1pp.c:apply_expr_op. +## Fatal on wrong argc or EXPR_INVALID. ## ## Calls aeo_require_* helpers via `call`, so it needs a frame. ## State held in BSS scratch (aeo_op/args/argc/acc/i) since loops trash registers. @@ -3322,7 +3309,7 @@ DEFINE EXPR_INVALID 1100000000000000 ## skip_expr_newlines(a0=pos, a1=end) -> a0 = new pos. Leaf. ## Advance pos past consecutive TOK_NEWLINE tokens so expressions may span -## lines. Oracle: m1pp.c:skip_expr_newlines. +## lines. :skip_expr_newlines :sen_loop # if (pos == end) done @@ -3355,7 +3342,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## CAVEAT: this path can recurse through eval_expr_range. Callers MUST ## snapshot eval_after_pos / eval_value into local stack slots (via ## enter_N) before any further call that might overwrite them. -## Oracle: m1pp.c:eval_expr_atom. ## ## Stack-local layout (enter_40): ## sp+16 saved tok @@ -3459,14 +3445,14 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## eval_expr_range(a0=start_tok, a1=end_tok) -> a0 = i64 result (fatal on bad) -## Main S-expression evaluator loop, driven by an explicit ExprFrame stack -## (expr_frames[], expr_frame_top) — NOT by P1 recursion. See -## docs/M1M-IMPL.md "Layer 7: expression evaluator" for the step-by-step -## loop. Enforces exactly one top-level value and no trailing tokens. +## Main S-expression evaluator loop, driven by the explicit ExprFrame stack +## in expr_frames[] / expr_frame_top — NOT by P1 recursion (eval_expr_atom +## can re-enter eval_expr_range through expand_macro_tokens, and a P1 +## recursion would defeat the bounded frame budget). Enforces exactly one +## top-level value and no trailing tokens. ## Fatal on: unmatched parens, > 16 frames deep, > 16 args per frame, ## bad atom, bad operator. ## Reads/writes: expr_frames, expr_frame_top. -## Oracle: m1pp.c:eval_expr_range. ## ## Stack-local layout (enter_56): ## sp+16 pos Token* @@ -3700,7 +3686,7 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 8 STUB: hex emit for !@%$ ----------------------------------------- +## --- Hex emit for !@%$ ------------------------------------------------------ ## ============================================================================ ## emit_hex_value(a0=value_u64, a1=byte_count) -> void (fatal on overflow) @@ -3710,7 +3696,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## treats it as a hex-byte string literal rather than parsing it as a ## decimal numeric token. Total emitted text length = 2 + 2 * byte_count; ## emitted as a TOK_STRING via append_text + emit_token. -## Oracle: m1pp.c:emit_hex_value. :emit_hex_value enter_0 @@ -3822,20 +3807,20 @@ DEFINE EXPR_INVALID 1100000000000000 ret ## ============================================================================ -## --- Phase 8-9 STUB: builtin dispatcher ( ! @ % $ %select ) ------------------ +## --- Builtin dispatcher ( ! @ % $ %select ) --------------------------------- ## ============================================================================ ## expand_builtin_call(a0=stream_ptr, a1=builtin_tok) -> void (fatal on bad) ## Requires builtin_tok+1 is TOK_LPAREN. Runs parse_args(lparen, stream->end), ## then dispatches on builtin_tok->text: ## -## "!" "@" "%" "$" [Phase 8] +## "!" "@" "%" "$" ## require arg_count == 1 ## eval_expr_range(arg_starts[0], arg_ends[0]) -> value ## stream->pos = call_end_pos; stream->line_start = 0 ## emit_hex_value(value, 1 / 2 / 4 / 8 respectively) ## -## "%select" [Phase 9] +## "%select" ## require arg_count == 3 ## eval_expr_range(cond_arg) -> value ## chosen = (value != 0) ? arg1 : arg2 @@ -3845,7 +3830,6 @@ DEFINE EXPR_INVALID 1100000000000000 ## The unchosen branch is NOT evaluated, validated, or expanded. ## ## Any other text under a builtin slot -> fatal "bad builtin". -## Oracle: m1pp.c:expand_builtin_call. :expand_builtin_call enter_0 @@ -3881,12 +3865,10 @@ DEFINE EXPR_INVALID 1100000000000000 la_a1 &ebc_call_end_pos st_t0,a1,0 - # dispatch on builtin_tok->text. We've lost a1 (builtin_tok) across calls, - # so reload from stream: builtin_tok = lparen - 24 = (parse_args's a0 input) - # — easier path: builtin_tok was passed in originally; it's not snapshotted. - # Solution: snapshot builtin_tok at function entry too (but we don't have a - # BSS slot for it). We DO know stream->pos still points at the builtin_tok - # because expand_builtin_call hasn't moved it yet. Use that. + # dispatch on builtin_tok->text. a1 (builtin_tok) is gone after parse_args, + # but stream->pos still points at the builtin token (we don't advance it + # until the dispatched branch sets stream->pos = call_end_pos), so reload + # builtin_tok from stream->pos. la_a0 &ebc_stream ld_a0,a0,0 ld_t0,a0,16 # stream->pos -> builtin_tok @@ -4263,9 +4245,9 @@ DEFINE EXPR_INVALID 1100000000000000 :const_select "%select" ## Operator strings for expr_op_code. Each is a raw byte literal; lengths -## are passed separately to tok_eq_const. Note: XOR is "$" (oracle parity), -## not "^". "==" must be tested before "=" so the longer match wins; same -## for "<=" before "<" and ">=" before ">". +## are passed separately to tok_eq_const. XOR is "$", not "^". +## "==" must be tested before "=" so the longer match wins; same for +## "<=" before "<" and ">=" before ">". :op_plus "+" :op_minus "-" :op_star "*" @@ -4359,9 +4341,9 @@ ZERO8 :err_saved_len ZERO8 -## Phase 3+ scalars. Each is one u64 (ZERO8). +## Stream / pool / arg / expression scalars. Each is one u64 (ZERO8). ## pool_used — byte offset into expand_pool (i.e. next write slot). -## stream_top — stream stack depth (0 == empty). +## stream_top — stream stack depth in bytes (count × 40; 0 == empty). ## arg_count — number of args produced by the most recent parse_args. ## call_end_pos — Token* one past the ')' of that call. ## expr_frame_top — ExprFrame stack depth inside eval_expr_range. @@ -4389,9 +4371,9 @@ ZERO8 :eval_value ZERO8 -## Phase 6 paste-pass spill slots. Both append_pasted_token and -## paste_pool_range call other functions, so all locals must round-trip -## through BSS across the call. +## Paste-pass spill slots. Both append_pasted_token and paste_pool_range +## call other functions, so all locals must round-trip through BSS +## across the call. ## paste_dst_save — dst Token* spilled across append_text ## paste_left_ptr/_len, paste_right_ptr/_len — operand spans for the ## byte-copy loops in append_pasted_token @@ -4425,16 +4407,14 @@ ZERO8 ## paste_scratch — 256-byte working buffer for append_pasted_token. ## We assemble left.text ++ right.text here, then call ## append_text(&paste_scratch, total_len) to copy into the durable -## text_buf arena. The cap is the M0 quoted-literal limit; the C oracle -## uses 512. +## text_buf arena. 256 bytes is M0's quoted-literal cap. :paste_scratch ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 ZERO32 -## Track C scalars: parse_args + expand_macro_tokens + find_param locals -## (P1v2 has no callee-save spill on enter, and find_param's inner byte -## compare needs every caller-saved register; parse_args + expand_macro_tokens -## write the parse_args output globals across iterations and across calls). -## One u64 each (ZERO8). +## parse_args + expand_macro_tokens + find_param spill slots (P1v2 has +## no callee-save spill on enter, and find_param's inner byte compare +## needs every caller-saved register; parse_args + expand_macro_tokens +## carry state across iterations and nested calls). One u64 each (ZERO8). :pa_pos ZERO8 :pa_arg_start @@ -4466,10 +4446,10 @@ ZERO8 :fp_idx ZERO8 -## Phase 7 scratch slots (leaf-call BSS scratch — see expr_op_code and -## apply_expr_op). expr_op_code spills its tok argument to eoc_tok across -## tok_eq_const calls. apply_expr_op spills op/args/argc and uses acc/i -## as the accumulator and loop induction var inside the variadic folds. +## Expression-evaluator scratch slots. expr_op_code spills its tok +## argument to eoc_tok across tok_eq_const calls. apply_expr_op spills +## op/args/argc and uses acc/i as the accumulator and loop induction var +## inside the variadic folds. :eoc_tok ZERO8 :aeo_op @@ -4483,7 +4463,7 @@ ZERO8 :aeo_i ZERO8 -## Layer 8 (Phase 8/9) scratch. +## Builtin scratch. ## emit_hex_value: ehv_value/bytes hold the args; ehv_scratch is a 24-byte ## buffer (max 18 chars used: 2 quotes + 16 hex chars; rounded up to keep ## the next slot 8-byte aligned); ehv_token is a synthesized 24-byte