commit fbf22f4bdb7af44f88f457580832a4d6b925229d
parent 4d156315d79d8c10153cdde943b0edb4bd13c603
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 23 Apr 2026 15:36:42 -0700
Merge branch 'worktree-agent-a04674a83d5e97483' into integrate-m1pp
# Conflicts:
# m1pp/m1pp.M1
Diffstat:
| M | m1pp/m1pp.M1 | | | 1320 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
1 file changed, 1296 insertions(+), 24 deletions(-)
diff --git a/m1pp/m1pp.M1 b/m1pp/m1pp.M1
@@ -2401,39 +2401,944 @@ DEFINE EXPR_INVALID 1100000000000000
## 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.
+##
+## Register usage (leaf, no calls):
+## t0 = src ptr (cursor into text)
+## t1 = end ptr (text + len)
+## t2 = current byte
+## a0 = accumulator (return)
+## a1 = negative flag (0/1)
+## a2 = scratch (digit, multiplier)
+## a3 = scratch (compare value)
:parse_int_token
- la_br &err_not_implemented
+ # if (tok->kind != TOK_WORD) fatal
+ ld_t0,a0,0
+ li_t1 TOK_WORD
+ la_br &err_bad_macro_header
+ bne_t0,t1
+
+ # src = tok->text_ptr; len = tok->text_len; end = src + len
+ ld_t0,a0,8
+ ld_t1,a0,16
+
+ # if (len <= 0) fatal
+ la_br &err_bad_macro_header
+ beqz_t1
+ add_t1,t0,t1
+
+ # negative = 0
+ li_a1 %0 %0
+
+ # if (*src == '-') { negative = 1; src++; if (src == end) fatal }
+ lb_t2,t0,0
+ li_a3 %45 %0
+ la_br &pit_after_sign
+ bne_t2,a3
+ li_a1 %1 %0
+ addi_t0,t0,1
+ la_br &err_bad_macro_header
+ beq_t0,t1
+
+:pit_after_sign
+ # accumulator = 0
+ li_a0 %0 %0
+
+ # check for 0x / 0X prefix: need at least 2 chars left
+ mov_a2,t1
+ sub_a2,a2,t0
+ li_a3 %2 %0
+ la_br &pit_decimal
+ blt_a2,a3
+
+ # if (src[0] == '0' && (src[1] == 'x' || src[1] == 'X')) -> hex
+ lb_t2,t0,0
+ li_a3 %48 %0
+ la_br &pit_decimal
+ bne_t2,a3
+ addi_a2,t0,1
+ lb_a2,a2,0
+ li_a3 %120 %0
+ la_br &pit_hex_start
+ beq_a2,a3
+ li_a3 %88 %0
+ la_br &pit_hex_start
+ beq_a2,a3
+ la_br &pit_decimal
+ b
+
+:pit_hex_start
+ # consume "0x"; require at least one hex digit after
+ addi_t0,t0,2
+ la_br &err_bad_macro_header
+ beq_t0,t1
+:pit_hex_loop
+ la_br &pit_finish
+ beq_t0,t1
+ lb_t2,t0,0
+
+ # 0..9
+ li_a3 %48 %0
+ la_br &pit_hex_check_lower
+ blt_t2,a3
+ li_a3 %57 %0
+ la_br &pit_hex_check_lower
+ blt_a3,t2
+ # digit = c - '0'
+ addi_a2,t2,-48
+ la_br &pit_hex_accum
+ b
+
+:pit_hex_check_lower
+ # 'a'..'f'
+ li_a3 %97 %0
+ la_br &pit_hex_check_upper
+ blt_t2,a3
+ li_a3 %102 %0
+ la_br &pit_hex_check_upper
+ blt_a3,t2
+ # digit = (c - 'a') + 10
+ li_a3 %97 %0
+ sub_a2,t2,a3
+ addi_a2,a2,8
+ addi_a2,a2,2
+ la_br &pit_hex_accum
+ b
+
+:pit_hex_check_upper
+ # 'A'..'F'
+ li_a3 %65 %0
+ la_br &err_bad_macro_header
+ blt_t2,a3
+ li_a3 %70 %0
+ la_br &err_bad_macro_header
+ blt_a3,t2
+ # digit = (c - 'A') + 10
+ li_a3 %65 %0
+ sub_a2,t2,a3
+ addi_a2,a2,8
+ addi_a2,a2,2
+
+:pit_hex_accum
+ # accum = (accum << 4) | digit
+ shli_a0,a0,4
+ or_a0,a0,a2
+ addi_t0,t0,1
+ la_br &pit_hex_loop
+ b
+
+:pit_decimal
+ # decimal loop: accum = accum * 10 + digit
+ # (caller already ensured len > 0 and that src points to first digit)
+:pit_decimal_loop
+ la_br &pit_finish
+ beq_t0,t1
+ lb_t2,t0,0
+ li_a3 %48 %0
+ la_br &err_bad_macro_header
+ blt_t2,a3
+ li_a3 %57 %0
+ la_br &err_bad_macro_header
+ blt_a3,t2
+ # accum = accum * 10
+ li_a3 %10 %0
+ mul_a0,a0,a3
+ # digit = c - '0'; accum += digit
+ addi_a2,t2,-48
+ add_a0,a0,a2
+ addi_t0,t0,1
+ la_br &pit_decimal_loop
+ b
+
+:pit_finish
+ # if (negative) accum = 0 - accum
+ la_br &pit_done
+ beqz_a1
+ li_a3 %0 %0
+ sub_a0,a3,a0
+:pit_done
+ ret
+
+## expr_op_code(a0=tok) -> a0 = EXPR_ADD..EXPR_GE, or EXPR_INVALID. Leaf.
+## 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.
+##
+## 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
+## issues `call` instructions (aarch64 CALL writes LR).
+:expr_op_code
+ enter_0
+ # spill tok; reject non-WORD up front
+ la_a1 &eoc_tok
+ st_a0,a1,0
+ ld_t0,a0,0
+ li_t1 TOK_WORD
+ la_br &eoc_invalid
+ bne_t0,t1
+
+ # "+" -> EXPR_ADD
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_plus
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_add
+ bnez_a0
+
+ # "-" -> EXPR_SUB
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_minus
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_sub
+ bnez_a0
+
+ # "*" -> EXPR_MUL
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_star
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_mul
+ bnez_a0
+
+ # "/" -> EXPR_DIV
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_slash
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_div
+ bnez_a0
+
+ # "%" -> EXPR_MOD
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_percent
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_mod
+ bnez_a0
+
+ # "<<" -> EXPR_SHL
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_shl
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_shl
+ bnez_a0
+
+ # ">>" -> EXPR_SHR
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_shr
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_shr
+ bnez_a0
+
+ # "&" -> EXPR_AND
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_amp
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_and
+ bnez_a0
+
+ # "|" -> EXPR_OR
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_bar
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_or
+ bnez_a0
+
+ # "$" -> EXPR_XOR (oracle spells xor as "$")
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_dollar
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_xor
+ bnez_a0
+
+ # "~" -> EXPR_NOT
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_tilde
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_not
+ bnez_a0
+
+ # "==" -> EXPR_EQ (check before single "=" to honor longer match cleanly)
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_eq_eq
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_eq
+ bnez_a0
+
+ # "=" -> EXPR_EQ
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_eq
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_eq
+ bnez_a0
+
+ # "!=" -> EXPR_NE
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_ne
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_ne
+ bnez_a0
+
+ # "<=" -> EXPR_LE (check before single "<")
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_le
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_le
+ bnez_a0
+
+ # "<" -> EXPR_LT
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_lt
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_lt
+ bnez_a0
+
+ # ">=" -> EXPR_GE (check before single ">")
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_ge
+ li_a2 %2 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_ge
+ bnez_a0
+
+ # ">" -> EXPR_GT
+ la_a0 &eoc_tok
+ ld_a0,a0,0
+ la_a1 &op_gt
+ li_a2 %1 %0
+ la_br &tok_eq_const
+ call
+ la_br &eoc_gt
+ bnez_a0
+
+:eoc_invalid
+ li_a0 EXPR_INVALID
+ leave
+ ret
+:eoc_add
+ li_a0 EXPR_ADD
+ leave
+ ret
+:eoc_sub
+ li_a0 EXPR_SUB
+ leave
+ ret
+:eoc_mul
+ li_a0 EXPR_MUL
+ leave
+ ret
+:eoc_div
+ li_a0 EXPR_DIV
+ leave
+ ret
+:eoc_mod
+ li_a0 EXPR_MOD
+ leave
+ ret
+:eoc_shl
+ li_a0 EXPR_SHL
+ leave
+ ret
+:eoc_shr
+ li_a0 EXPR_SHR
+ leave
+ ret
+:eoc_and
+ li_a0 EXPR_AND
+ leave
+ ret
+:eoc_or
+ li_a0 EXPR_OR
+ leave
+ ret
+:eoc_xor
+ li_a0 EXPR_XOR
+ leave
+ ret
+:eoc_not
+ li_a0 EXPR_NOT
+ leave
+ ret
+:eoc_eq
+ li_a0 EXPR_EQ
+ leave
+ ret
+:eoc_ne
+ li_a0 EXPR_NE
+ leave
+ ret
+:eoc_lt
+ li_a0 EXPR_LT
+ leave
+ ret
+:eoc_le
+ li_a0 EXPR_LE
+ leave
+ ret
+:eoc_gt
+ li_a0 EXPR_GT
+ leave
+ ret
+:eoc_ge
+ li_a0 EXPR_GE
+ leave
+ 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"):
+## + * & | $ 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.
+##
+## 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.
+:apply_expr_op
+ enter_0
+ # spill op, args, argc to BSS
+ la_a3 &aeo_op
+ st_a0,a3,0
+ la_a3 &aeo_args
+ st_a1,a3,0
+ la_a3 &aeo_argc
+ st_a2,a3,0
+
+ # dispatch: compare op against each EXPR_* and branch to its handler
+ li_t0 EXPR_ADD
+ la_br &aeo_do_add
+ beq_a0,t0
+ li_t0 EXPR_SUB
+ la_br &aeo_do_sub
+ beq_a0,t0
+ li_t0 EXPR_MUL
+ la_br &aeo_do_mul
+ beq_a0,t0
+ li_t0 EXPR_DIV
+ la_br &aeo_do_div
+ beq_a0,t0
+ li_t0 EXPR_MOD
+ la_br &aeo_do_mod
+ beq_a0,t0
+ li_t0 EXPR_SHL
+ la_br &aeo_do_shl
+ beq_a0,t0
+ li_t0 EXPR_SHR
+ la_br &aeo_do_shr
+ beq_a0,t0
+ li_t0 EXPR_AND
+ la_br &aeo_do_and
+ beq_a0,t0
+ li_t0 EXPR_OR
+ la_br &aeo_do_or
+ beq_a0,t0
+ li_t0 EXPR_XOR
+ la_br &aeo_do_xor
+ beq_a0,t0
+ li_t0 EXPR_NOT
+ la_br &aeo_do_not
+ beq_a0,t0
+ li_t0 EXPR_EQ
+ la_br &aeo_do_eq
+ beq_a0,t0
+ li_t0 EXPR_NE
+ la_br &aeo_do_ne
+ beq_a0,t0
+ li_t0 EXPR_LT
+ la_br &aeo_do_lt
+ beq_a0,t0
+ li_t0 EXPR_LE
+ la_br &aeo_do_le
+ beq_a0,t0
+ li_t0 EXPR_GT
+ la_br &aeo_do_gt
+ beq_a0,t0
+ li_t0 EXPR_GE
+ la_br &aeo_do_ge
+ beq_a0,t0
+ # EXPR_INVALID or unknown
+ la_br &err_bad_macro_header
+ b
+
+## --- shared helpers for variadic folds ----------------------------------
+## aeo_require_argc_ge1: branch to err if argc < 1
+## aeo_require_argc_eq2: branch to err if argc != 2
+## aeo_load_arg0_to_acc: acc = args[0]; i = 1
+
+:aeo_do_add
+ la_br &aeo_require_argc_ge1
+ call
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_add_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ # acc += args[i]
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ add_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_add_loop
+ b
+
+:aeo_do_sub
+ la_br &aeo_require_argc_ge1
+ call
+ # if (argc == 1) acc = -args[0]; else acc = args[0]
+ la_a0 &aeo_argc
+ ld_t0,a0,0
+ li_t1 %1 %0
+ la_br &aeo_sub_unary
+ beq_t0,t1
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_sub_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ sub_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_sub_loop
+ b
+:aeo_sub_unary
+ # acc = 0 - args[0]
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ li_a3 %0 %0
+ sub_a3,a3,a2
+ la_a0 &aeo_acc
+ st_a3,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_mul
+ la_br &aeo_require_argc_ge1
+ call
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_mul_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ mul_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_mul_loop
+ b
+
+:aeo_do_div
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ # if (args[1] == 0) fatal
+ la_br &err_bad_macro_header
+ beqz_a3
+ div_a2,a2,a3
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_mod
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ la_br &err_bad_macro_header
+ beqz_a3
+ rem_a2,a2,a3
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_shl
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ shl_a2,a2,a3
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_shr
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ sar_a2,a2,a3
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_and
+ la_br &aeo_require_argc_ge1
+ call
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_and_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ and_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_and_loop
+ b
+
+:aeo_do_or
+ la_br &aeo_require_argc_ge1
+ call
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_or_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ or_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_or_loop
+ b
+
+:aeo_do_xor
+ la_br &aeo_require_argc_ge1
+ call
+ la_br &aeo_load_arg0_to_acc
+ call
+:aeo_xor_loop
+ la_a0 &aeo_i
+ ld_t0,a0,0
+ la_a1 &aeo_argc
+ ld_t1,a1,0
+ la_br &aeo_finish
+ beq_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ shli_t2,t0,3
+ add_t2,a1,t2
+ ld_a2,t2,0
+ la_a0 &aeo_acc
+ ld_a3,a0,0
+ xor_a3,a3,a2
+ st_a3,a0,0
+ addi_t0,t0,1
+ la_a1 &aeo_i
+ st_t0,a1,0
+ la_br &aeo_xor_loop
b
-## expr_op_code(a0=tok) -> a0 = EXPR_ADD..EXPR_GE, or EXPR_INVALID. Leaf.
-## 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.
-:expr_op_code
- la_br &err_not_implemented
+:aeo_do_not
+ # require argc == 1
+ la_a0 &aeo_argc
+ ld_t0,a0,0
+ li_t1 %1 %0
+ la_br &err_bad_macro_header
+ bne_t0,t1
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ # ~x = x XOR -1
+ li_a3 %1 %0
+ li_t0 %0 %0
+ sub_a3,t0,a3
+ xor_a2,a2,a3
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ la_br &aeo_finish
b
-## 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"):
-## + * & | $ 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.
-:apply_expr_op
- la_br &err_not_implemented
+## --- comparison ops: return 0 or 1 ----------------------------------------
+## EQ: args[0] == args[1]
+## NE: args[0] != args[1]
+## LT: args[0] < args[1] (signed)
+## LE: args[0] <= args[1] (signed)
+## GT: args[0] > args[1] (signed)
+## GE: args[0] >= args[1] (signed)
+
+:aeo_do_eq
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %0 %0
+ la_br &aeo_cmp_store_zero
+ bne_a2,a3
+ li_t0 %1 %0
+:aeo_cmp_store_zero
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_ne
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %0 %0
+ la_br &aeo_cmp_store_zero1
+ beq_a2,a3
+ li_t0 %1 %0
+:aeo_cmp_store_zero1
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_lt
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %1 %0
+ la_br &aeo_cmp_store_one
+ blt_a2,a3
+ li_t0 %0 %0
+:aeo_cmp_store_one
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_le
+ # a[0] <= a[1] <=> !(a[1] < a[0])
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %0 %0
+ la_br &aeo_cmp_store_two
+ blt_a3,a2
+ li_t0 %1 %0
+:aeo_cmp_store_two
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_gt
+ # a[0] > a[1] <=> a[1] < a[0]
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %1 %0
+ la_br &aeo_cmp_store_three
+ blt_a3,a2
+ li_t0 %0 %0
+:aeo_cmp_store_three
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
+ b
+
+:aeo_do_ge
+ # a[0] >= a[1] <=> !(a[0] < a[1])
+ la_br &aeo_require_argc_eq2
+ call
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ ld_a3,a1,8
+ li_t0 %0 %0
+ la_br &aeo_cmp_store_four
+ blt_a2,a3
+ li_t0 %1 %0
+:aeo_cmp_store_four
+ la_a0 &aeo_acc
+ st_t0,a0,0
+ la_br &aeo_finish
b
+:aeo_finish
+ la_a0 &aeo_acc
+ ld_a0,a0,0
+ leave
+ ret
+
+## helper: validate argc >= 1; fatal otherwise. (Returns to caller.)
+:aeo_require_argc_ge1
+ la_a0 &aeo_argc
+ ld_t0,a0,0
+ li_t1 %1 %0
+ la_br &err_bad_macro_header
+ blt_t0,t1
+ ret
+
+## helper: validate argc == 2; fatal otherwise.
+:aeo_require_argc_eq2
+ la_a0 &aeo_argc
+ ld_t0,a0,0
+ li_t1 %2 %0
+ la_br &err_bad_macro_header
+ bne_t0,t1
+ ret
+
+## helper: acc = args[0]; i = 1.
+:aeo_load_arg0_to_acc
+ la_a0 &aeo_args
+ ld_a1,a0,0
+ ld_a2,a1,0
+ la_a0 &aeo_acc
+ st_a2,a0,0
+ li_t0 %1 %0
+ la_a0 &aeo_i
+ st_t0,a0,0
+ ret
+
## 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.
:skip_expr_newlines
- la_br &err_not_implemented
+:sen_loop
+ # if (pos == end) done
+ la_br &sen_done
+ beq_a0,a1
+ # if (pos->kind != TOK_NEWLINE) done
+ ld_t0,a0,0
+ li_t1 TOK_NEWLINE
+ la_br &sen_done
+ bne_t0,t1
+ # pos += 24
+ addi_a0,a0,24
+ la_br &sen_loop
b
+:sen_done
+ ret
## eval_expr_atom(a0=tok, a1=limit) -> void
## Outputs via globals:
@@ -2451,9 +3356,107 @@ DEFINE EXPR_INVALID 1100000000000000
## 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
+## sp+24 saved limit
+## sp+32 macro_ptr (find_macro result)
+## sp+40 saved emt_after_pos
+## sp+48 saved emt_mark
:eval_expr_atom
- la_br &err_not_implemented
- b
+ enter_40
+ st_a0,sp,16
+ st_a1,sp,24
+
+ # macro_ptr = find_macro(tok)
+ la_br &find_macro
+ call
+ st_a0,sp,32
+
+ # if (macro_ptr == 0) -> integer atom branch
+ la_br &eea_int_atom
+ beqz_a0
+
+ # Need (tok + 1 < limit) AND (tok+1)->kind == TOK_LPAREN
+ ld_t0,sp,16
+ addi_t0,t0,24
+ ld_t1,sp,24
+ la_br &eea_int_atom
+ blt_t1,t0
+ la_br &eea_int_atom
+ beq_t0,t1
+ ld_t2,t0,0
+ li_a3 TOK_LPAREN
+ la_br &eea_int_atom
+ bne_t2,a3
+
+ # Macro call branch:
+ # expand_macro_tokens(tok, limit, macro_ptr)
+ ld_a0,sp,16
+ ld_a1,sp,24
+ ld_a2,sp,32
+ la_br &expand_macro_tokens
+ call
+
+ # Snapshot emt outputs immediately.
+ la_a0 &emt_after_pos
+ ld_t0,a0,0
+ st_t0,sp,40
+ la_a0 &emt_mark
+ ld_t0,a0,0
+ st_t0,sp,48
+
+ # If pool was not extended (pool_used == mark) -> bad expression.
+ la_a0 &pool_used
+ ld_t0,a0,0
+ ld_t1,sp,48
+ la_br &err_bad_macro_header
+ beq_t0,t1
+
+ # eval_expr_range(expand_pool + mark, expand_pool + pool_used)
+ la_a0 &expand_pool
+ ld_t1,sp,48
+ add_a0,a0,t1
+ la_a1 &expand_pool
+ la_a2 &pool_used
+ ld_a2,a2,0
+ add_a1,a1,a2
+ la_br &eval_expr_range
+ call
+
+ # eval_value = result
+ la_a1 &eval_value
+ st_a0,a1,0
+
+ # restore pool_used = mark
+ la_a0 &pool_used
+ ld_t0,sp,48
+ st_t0,a0,0
+
+ # eval_after_pos = saved emt_after_pos
+ la_a0 &eval_after_pos
+ ld_t0,sp,40
+ st_t0,a0,0
+
+ leave
+ ret
+
+:eea_int_atom
+ # parse_int_token(tok) -> i64
+ ld_a0,sp,16
+ la_br &parse_int_token
+ call
+ la_a1 &eval_value
+ st_a0,a1,0
+
+ # eval_after_pos = tok + 24
+ ld_t0,sp,16
+ addi_t0,t0,24
+ la_a0 &eval_after_pos
+ st_t0,a0,0
+
+ leave
+ 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
@@ -2464,10 +3467,238 @@ DEFINE EXPR_INVALID 1100000000000000
## 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*
+## sp+24 end Token*
+## sp+32 value i64 (most recent atom or rparen result)
+## sp+40 result i64 (set when have_result transitions to 1)
+## sp+48 have_value 0/1
+## sp+56 have_result 0/1
+## sp+64 entry_frame_top i64 (snapshot at entry; restored on exit;
+## used as the local base for stack checks)
:eval_expr_range
- la_br &err_not_implemented
+ enter_56
+ st_a0,sp,16
+ st_a1,sp,24
+ li_t0 %0 %0
+ st_t0,sp,32
+ st_t0,sp,40
+ st_t0,sp,48
+ st_t0,sp,56
+ # entry_frame_top = expr_frame_top
+ la_a0 &expr_frame_top
+ ld_t0,a0,0
+ st_t0,sp,64
+
+:eer_loop
+ # If have_value, deliver it.
+ ld_t0,sp,48
+ la_br &eer_no_have_value
+ beqz_t0
+
+ # have_value: feed into top frame, or set result.
+ la_a0 &expr_frame_top
+ ld_t0,a0,0
+ ld_t1,sp,64
+ la_br &eer_set_result
+ beq_t0,t1
+ # frame = &expr_frames[frame_top - 1]
+ addi_t0,t0,-1
+ li_a1 M1PP_EXPR_FRAME_SIZE
+ mul_t0,t0,a1
+ la_a0 &expr_frames
+ add_a0,a0,t0
+ # if (frame->argc >= MAX_PARAMS) fatal
+ li_a1 M1PP_EXPR_ARGC_OFF
+ add_a1,a0,a1
+ ld_t1,a1,0
+ li_a2 M1PP_MAX_PARAMS
+ la_br &err_bad_macro_header
+ blt_a2,t1
+ la_br &err_bad_macro_header
+ beq_t1,a2
+ # frame->args[argc] = value
+ li_a2 M1PP_EXPR_ARGS_OFF
+ add_a3,a0,a2
+ shli_a2,t1,3
+ add_a3,a3,a2
+ ld_t2,sp,32
+ st_t2,a3,0
+ # frame->argc++
+ addi_t1,t1,1
+ st_t1,a1,0
+ # have_value = 0
+ li_t0 %0 %0
+ st_t0,sp,48
+ la_br &eer_loop
+ b
+
+:eer_set_result
+ # No frame open; this value is the top-level result.
+ ld_t0,sp,56
+ la_br &err_bad_macro_header
+ bnez_t0
+ ld_t0,sp,32
+ st_t0,sp,40
+ li_t0 %1 %0
+ st_t0,sp,56
+ li_t0 %0 %0
+ st_t0,sp,48
+ la_br &eer_loop
+ b
+
+:eer_no_have_value
+ # skip_expr_newlines(pos, end)
+ ld_a0,sp,16
+ ld_a1,sp,24
+ la_br &skip_expr_newlines
+ call
+ st_a0,sp,16
+
+ # if (pos >= end) break
+ ld_t0,sp,16
+ ld_t1,sp,24
+ la_br &eer_loop_done
+ beq_t0,t1
+
+ # Dispatch on token kind.
+ ld_t2,t0,0
+ li_a3 TOK_LPAREN
+ la_br &eer_lparen
+ beq_t2,a3
+ li_a3 TOK_RPAREN
+ la_br &eer_rparen
+ beq_t2,a3
+
+ # atom: eval_expr_atom(pos, end); value = eval_value; pos = eval_after_pos
+ ld_a0,sp,16
+ ld_a1,sp,24
+ la_br &eval_expr_atom
+ call
+ la_a0 &eval_value
+ ld_t0,a0,0
+ st_t0,sp,32
+ la_a0 &eval_after_pos
+ ld_t0,a0,0
+ st_t0,sp,16
+ li_t0 %1 %0
+ st_t0,sp,48
+ la_br &eer_loop
+ b
+
+:eer_lparen
+ # pos++
+ addi_t0,t0,24
+ st_t0,sp,16
+ # skip_expr_newlines
+ ld_a0,sp,16
+ ld_a1,sp,24
+ la_br &skip_expr_newlines
+ call
+ st_a0,sp,16
+ # if (pos >= end) fatal
+ ld_t0,sp,16
+ ld_t1,sp,24
+ la_br &err_bad_macro_header
+ beq_t0,t1
+ # op = expr_op_code(pos)
+ ld_a0,sp,16
+ la_br &expr_op_code
+ call
+ # if (op == EXPR_INVALID) fatal
+ li_t0 EXPR_INVALID
+ la_br &err_bad_macro_header
+ beq_a0,t0
+ # frame stack overflow check: if (expr_frame_top >= 16) fatal
+ # (the global expr_frames[] array has 16 slots, shared across recursive
+ # eval_expr_range calls)
+ la_a1 &expr_frame_top
+ ld_t0,a1,0
+ li_a2 M1PP_MAX_PARAMS
+ la_br &err_bad_macro_header
+ blt_a2,t0
+ la_br &err_bad_macro_header
+ beq_t0,a2
+ # frames[frame_top].op = op; frames[frame_top].argc = 0
+ li_a2 M1PP_EXPR_FRAME_SIZE
+ mul_t2,t0,a2
+ la_a3 &expr_frames
+ add_a3,a3,t2
+ st_a0,a3,0
+ li_a2 M1PP_EXPR_ARGC_OFF
+ add_a2,a3,a2
+ li_t2 %0 %0
+ st_t2,a2,0
+ # frame_top++
+ addi_t0,t0,1
+ st_t0,a1,0
+ # pos++ (skip operator token)
+ ld_t0,sp,16
+ addi_t0,t0,24
+ st_t0,sp,16
+ la_br &eer_loop
+ b
+
+:eer_rparen
+ # if (frame_top <= entry_frame_top) fatal
+ la_a0 &expr_frame_top
+ ld_t0,a0,0
+ ld_t1,sp,64
+ la_br &err_bad_macro_header
+ beq_t0,t1
+ la_br &err_bad_macro_header
+ blt_t0,t1
+ # frame = &expr_frames[frame_top - 1]
+ addi_t0,t0,-1
+ li_a1 M1PP_EXPR_FRAME_SIZE
+ mul_t0,t0,a1
+ la_a3 &expr_frames
+ add_a3,a3,t0
+ # apply_expr_op(op, args, argc) -> a0
+ ld_a0,a3,0
+ li_a1 M1PP_EXPR_ARGS_OFF
+ add_a1,a3,a1
+ li_a2 M1PP_EXPR_ARGC_OFF
+ add_a2,a3,a2
+ ld_a2,a2,0
+ la_br &apply_expr_op
+ call
+ # value = result; frame_top--; pos++; have_value = 1
+ st_a0,sp,32
+ la_a1 &expr_frame_top
+ ld_t0,a1,0
+ addi_t0,t0,-1
+ st_t0,a1,0
+ ld_t0,sp,16
+ addi_t0,t0,24
+ st_t0,sp,16
+ li_t0 %1 %0
+ st_t0,sp,48
+ la_br &eer_loop
b
+:eer_loop_done
+ # frame_top must equal entry_frame_top
+ la_a0 &expr_frame_top
+ ld_t0,a0,0
+ ld_t1,sp,64
+ la_br &err_bad_macro_header
+ bne_t0,t1
+ # have_result must be 1
+ ld_t0,sp,56
+ la_br &err_bad_macro_header
+ beqz_t0
+ # pos must equal end
+ ld_t0,sp,16
+ ld_t1,sp,24
+ la_br &err_bad_macro_header
+ bne_t0,t1
+ # return result
+ ld_a0,sp,40
+ leave
+ ret
+
## ============================================================================
## --- Phase 8 STUB: hex emit for !@%$ -----------------------------------------
## ============================================================================
@@ -2637,6 +3868,29 @@ DEFINE EXPR_INVALID 1100000000000000
:const_dlr "$"
: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 ">".
+:op_plus "+"
+:op_minus "-"
+:op_star "*"
+:op_slash "/"
+:op_percent "%"
+:op_shl "<<"
+:op_shr ">>"
+:op_amp "&"
+:op_bar "|"
+:op_dollar "$"
+:op_tilde "~"
+:op_eq "="
+:op_eq_eq "=="
+:op_ne "!="
+:op_lt "<"
+:op_le "<="
+:op_gt ">"
+:op_ge ">="
+
:msg_prefix "m1pp: "
:msg_newline "
"
@@ -2738,6 +3992,7 @@ 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.
@@ -2815,6 +4070,23 @@ 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.
+:eoc_tok
+ZERO8
+:aeo_op
+ZERO8
+:aeo_args
+ZERO8
+:aeo_argc
+ZERO8
+:aeo_acc
+ZERO8
+:aeo_i
+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