boot2

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

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:
Mm1pp/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