commit 84ebd78783a6bbc55059ea0cac96188ac7334c3d
parent fbf22f4bdb7af44f88f457580832a4d6b925229d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 23 Apr 2026 15:37:46 -0700
Merge branch 'worktree-agent-aecb01d58b541506d' into integrate-m1pp
# Conflicts:
# m1pp/m1pp.M1
Diffstat:
| M | m1pp/m1pp.M1 | | | 421 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
1 file changed, 418 insertions(+), 3 deletions(-)
diff --git a/m1pp/m1pp.M1 b/m1pp/m1pp.M1
@@ -3709,9 +3709,97 @@ DEFINE EXPR_INVALID 1100000000000000
## 2i, 2i+1). Emit as a synthesized TOK_WORD via append_text + emit_token.
## Oracle: m1pp.c:emit_hex_value.
:emit_hex_value
- la_br &err_not_implemented
+ enter_0
+
+ # ehv_value = value; ehv_bytes = byte_count
+ la_a2 &ehv_value
+ st_a0,a2,0
+ la_a2 &ehv_bytes
+ st_a1,a2,0
+
+ # i = 0
+ li_t0 %0 %0
+:emit_hex_value_loop
+ # if (i == bytes) done
+ la_a1 &ehv_bytes
+ ld_t1,a1,0
+ la_br &emit_hex_value_emit
+ beq_t0,t1
+
+ # byte = ehv_value & 0xFF
+ la_a1 &ehv_value
+ ld_t2,a1,0
+ andi_a3,t2,255
+
+ # high = (byte >> 4) & 0x0F (byte is already in a3)
+ shri_a2,a3,4
+ andi_a2,a2,15
+
+ # low = byte & 0x0F
+ andi_a3,a3,15
+
+ # scratch[2*i] = hex_chars[high]
+ la_a1 &hex_chars
+ add_a1,a1,a2
+ lb_a2,a1,0
+ la_a1 &ehv_scratch
+ shli_a3,t0,1
+ add_a1,a1,a3
+ sb_a2,a1,0
+
+ # scratch[2*i+1] = hex_chars[low] (reload low from byte & 0x0F)
+ la_a1 &ehv_value
+ ld_t2,a1,0
+ andi_a3,t2,255
+ andi_a3,a3,15
+ la_a1 &hex_chars
+ add_a1,a1,a3
+ lb_a2,a1,0
+ la_a1 &ehv_scratch
+ shli_a3,t0,1
+ add_a1,a1,a3
+ addi_a1,a1,1
+ sb_a2,a1,0
+
+ # ehv_value >>= 8
+ la_a1 &ehv_value
+ ld_t2,a1,0
+ shri_t2,t2,8
+ st_t2,a1,0
+
+ # i++
+ addi_t0,t0,1
+ la_br &emit_hex_value_loop
b
+:emit_hex_value_emit
+ # text_ptr = append_text(&ehv_scratch, 2 * ehv_bytes)
+ la_a0 &ehv_scratch
+ la_a1 &ehv_bytes
+ ld_a1,a1,0
+ shli_a1,a1,1
+ la_br &append_text
+ call
+
+ # ehv_token.kind = TOK_WORD; ehv_token.text_ptr = text_ptr;
+ # ehv_token.text_len = 2 * ehv_bytes
+ la_a2 &ehv_token
+ li_a3 TOK_WORD
+ st_a3,a2,0
+ st_a0,a2,8
+ la_a1 &ehv_bytes
+ ld_a1,a1,0
+ shli_a1,a1,1
+ st_a1,a2,16
+
+ # emit_token(&ehv_token)
+ la_a0 &ehv_token
+ la_br &emit_token
+ call
+
+ leave
+ ret
+
## ============================================================================
## --- Phase 8-9 STUB: builtin dispatcher ( ! @ % $ %select ) ------------------
## ============================================================================
@@ -3738,9 +3826,294 @@ DEFINE EXPR_INVALID 1100000000000000
## Any other text under a builtin slot -> fatal "bad builtin".
## Oracle: m1pp.c:expand_builtin_call.
:expand_builtin_call
- la_br &err_not_implemented
+ enter_0
+
+ # ebc_stream = stream_ptr; also stash builtin_tok via a register reload path
+ la_a2 &ebc_stream
+ st_a0,a2,0
+
+ # lparen = builtin_tok + 24; if (lparen >= stream->end) fatal
+ addi_t0,a1,24
+ ld_t1,a0,8 # stream->end
+ la_br &err_bad_macro_header
+ beq_t0,t1
+ la_br &err_bad_macro_header
+ blt_t1,t0
+
+ # if (lparen->kind != TOK_LPAREN) fatal
+ ld_a3,t0,0
+ li_a2 TOK_LPAREN
+ la_br &err_bad_macro_header
+ bne_a3,a2
+
+ # parse_args(lparen, stream->end)
+ mov_a0,t0
+ la_a2 &ebc_stream
+ ld_a2,a2,0
+ ld_a1,a2,8 # stream->end
+ la_br &parse_args
+ call
+
+ # snapshot call_end_pos -> ebc_call_end_pos
+ la_a0 &call_end_pos
+ ld_t0,a0,0
+ 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.
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ ld_t0,a0,16 # stream->pos -> builtin_tok
+
+ # if tok_eq_const(tok, "!", 1) -> bytes=1
+ mov_a0,t0
+ la_a1 &const_bang
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &ebc_arg_set_1
+ bnez_a0
+
+ # if tok_eq_const(tok, "@", 1) -> bytes=2
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ ld_a0,a0,16
+ la_a1 &const_at
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &ebc_arg_set_2
+ bnez_a0
+
+ # if tok_eq_const(tok, "%", 1) -> bytes=4
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ ld_a0,a0,16
+ la_a1 &const_pct
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &ebc_arg_set_4
+ bnez_a0
+
+ # if tok_eq_const(tok, "$", 1) -> bytes=8
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ ld_a0,a0,16
+ la_a1 &const_dlr
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &ebc_arg_set_8
+ bnez_a0
+
+ # if tok_eq_const(tok, "%select", 7) -> select path
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ ld_a0,a0,16
+ la_a1 &const_select
+ li_a2 %7 %0
+ la_br &tok_eq_const
+ call
+ la_br &ebc_select
+ bnez_a0
+
+ # else: fatal
+ la_br &err_bad_macro_header
b
+:ebc_arg_set_1
+ li_a0 %1 %0
+ la_a1 &ebc_bytes
+ st_a0,a1,0
+ la_br &ebc_arg_path
+ b
+:ebc_arg_set_2
+ li_a0 %2 %0
+ la_a1 &ebc_bytes
+ st_a0,a1,0
+ la_br &ebc_arg_path
+ b
+:ebc_arg_set_4
+ li_a0 %4 %0
+ la_a1 &ebc_bytes
+ st_a0,a1,0
+ la_br &ebc_arg_path
+ b
+:ebc_arg_set_8
+ li_a0 %8 %0
+ la_a1 &ebc_bytes
+ st_a0,a1,0
+ la_br &ebc_arg_path
+ b
+
+:ebc_arg_path
+ # require arg_count == 1
+ la_a0 &arg_count
+ ld_t0,a0,0
+ li_t1 %1 %0
+ la_br &err_bad_macro_header
+ bne_t0,t1
+
+ # snapshot arg_starts[0], arg_ends[0]
+ la_a0 &arg_starts
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_start
+ st_t0,a1,0
+ la_a0 &arg_ends
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_end
+ st_t0,a1,0
+
+ # value = eval_expr_range(arg0_start, arg0_end)
+ la_a0 &ebc_arg0_start
+ ld_a0,a0,0
+ la_a1 &ebc_arg0_end
+ ld_a1,a1,0
+ la_br &eval_expr_range
+ call
+
+ # ebc_value = a0
+ la_a1 &ebc_value
+ st_a0,a1,0
+
+ # stream->pos = ebc_call_end_pos; stream->line_start = 0
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ la_a1 &ebc_call_end_pos
+ ld_t0,a1,0
+ st_t0,a0,16
+ li_t1 %0 %0
+ st_t1,a0,24
+
+ # emit_hex_value(ebc_value, ebc_bytes)
+ la_a0 &ebc_value
+ ld_a0,a0,0
+ la_a1 &ebc_bytes
+ ld_a1,a1,0
+ la_br &emit_hex_value
+ call
+
+ leave
+ ret
+
+:ebc_select
+ # require arg_count == 3
+ la_a0 &arg_count
+ ld_t0,a0,0
+ li_t1 %3 %0
+ la_br &err_bad_macro_header
+ bne_t0,t1
+
+ # snapshot arg_starts[0..2] / arg_ends[0..2]
+ la_a0 &arg_starts
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_start
+ st_t0,a1,0
+ la_a0 &arg_starts
+ ld_t0,a0,8
+ la_a1 &ebc_then_start
+ st_t0,a1,0
+ la_a0 &arg_starts
+ ld_t0,a0,16
+ la_a1 &ebc_else_start
+ st_t0,a1,0
+
+ la_a0 &arg_ends
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_end
+ st_t0,a1,0
+ la_a0 &arg_ends
+ ld_t0,a0,8
+ la_a1 &ebc_then_end
+ st_t0,a1,0
+ la_a0 &arg_ends
+ ld_t0,a0,16
+ la_a1 &ebc_else_end
+ st_t0,a1,0
+
+ # value = eval_expr_range(arg0_start, arg0_end)
+ la_a0 &ebc_arg0_start
+ ld_a0,a0,0
+ la_a1 &ebc_arg0_end
+ ld_a1,a1,0
+ la_br &eval_expr_range
+ call
+
+ # if (value != 0) chosen = then; else chosen = else
+ la_br &ebc_select_then
+ bnez_a0
+
+ # chosen = else
+ la_a0 &ebc_else_start
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_start
+ st_t0,a1,0
+ la_a0 &ebc_else_end
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_end
+ st_t0,a1,0
+ la_br &ebc_select_after_pick
+ b
+
+:ebc_select_then
+ # chosen = then
+ la_a0 &ebc_then_start
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_start
+ st_t0,a1,0
+ la_a0 &ebc_then_end
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_end
+ st_t0,a1,0
+
+:ebc_select_after_pick
+ # stream->pos = ebc_call_end_pos; stream->line_start = 0
+ la_a0 &ebc_stream
+ ld_a0,a0,0
+ la_a1 &ebc_call_end_pos
+ ld_t0,a1,0
+ st_t0,a0,16
+ li_t1 %0 %0
+ st_t1,a0,24
+
+ # if (chosen_start == chosen_end) return
+ la_a0 &ebc_arg0_start
+ ld_t0,a0,0
+ la_a1 &ebc_arg0_end
+ ld_t1,a1,0
+ la_br &ebc_select_done
+ beq_t0,t1
+
+ # mark = pool_used
+ la_a0 &pool_used
+ ld_t0,a0,0
+ la_a1 &ebc_mark
+ st_t0,a1,0
+
+ # copy_span_to_pool(chosen_start, chosen_end)
+ la_a0 &ebc_arg0_start
+ ld_a0,a0,0
+ la_a1 &ebc_arg0_end
+ ld_a1,a1,0
+ la_br ©_span_to_pool
+ call
+
+ # push_pool_stream_from_mark(mark)
+ la_a0 &ebc_mark
+ ld_a0,a0,0
+ la_br &push_pool_stream_from_mark
+ call
+
+:ebc_select_done
+ leave
+ ret
+
## --- Error paths -------------------------------------------------------------
## Each err_* loads a (msg, len) pair for fatal; fatal writes "m1pp: <msg>\n"
## to stderr and exits 1. Error labels are branched to from range/overflow
@@ -3891,6 +4264,9 @@ DEFINE EXPR_INVALID 1100000000000000
:op_gt ">"
:op_ge ">="
+## Nibble-to-hex lookup table for emit_hex_value.
+:hex_chars "0123456789ABCDEF"
+
:msg_prefix "m1pp: "
:msg_newline "
"
@@ -3992,7 +4368,6 @@ ZERO8
:eval_value
ZERO8
-<<<<<<< HEAD
## 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.
@@ -4087,6 +4462,46 @@ ZERO8
:aeo_i
ZERO8
+## Layer 8 (Phase 8/9) scratch.
+## emit_hex_value: ehv_value/bytes hold the args; ehv_scratch is a 16-byte
+## buffer (max 8 bytes -> 16 hex chars); ehv_token is a synthesized 24-byte
+## Token { kind, text_ptr, text_len }.
+:ehv_value
+ZERO8
+:ehv_bytes
+ZERO8
+:ehv_scratch
+ZERO8 ZERO8
+:ehv_token
+ZERO8 ZERO8 ZERO8
+
+## expand_builtin_call: snapshots the stream pointer, the post-call resume
+## position, the byte count for !@%$, the eval_expr_range result, the chosen
+## arg span (start/end), the unchosen-side spans for %select, and the
+## pool mark used to push the chosen-stream slice.
+:ebc_stream
+ZERO8
+:ebc_call_end_pos
+ZERO8
+:ebc_bytes
+ZERO8
+:ebc_value
+ZERO8
+:ebc_arg0_start
+ZERO8
+:ebc_arg0_end
+ZERO8
+:ebc_then_start
+ZERO8
+:ebc_then_end
+ZERO8
+:ebc_else_start
+ZERO8
+:ebc_else_end
+ZERO8
+:ebc_mark
+ZERO8
+
## arg_starts[16] / arg_ends[16]: 16 × 8 = 128 bytes each, i.e. 4 ZERO32.
## Written by parse_args; read by expand_macro_tokens and expand_builtin_call.
:arg_starts