M1pp.P1 (173944B)
1 ## m1pp.M1 — bootstrap M1 macro-expander, P1. 2 ## 3 ## Runtime shape: m1pp input.M1 output.M1 4 ## 5 ## Pipeline: 6 ## p1_main argc/argv from the backend :_start stub; stash argv[1..2] 7 ## into input_path / output_path; openat+read into 8 ## input_buf; call lex_source, then process_tokens; 9 ## openat+write output_buf to output_path; return 0. 10 ## lex_source input_buf -> source_tokens[] (via append_text + 11 ## push_source_token). 12 ## process_tokens Stream-driven loop. Pushes source_tokens as the initial 13 ## stream and walks it token-by-token, dispatching to 14 ## define_macro at line-start %macro, emit_newline / 15 ## emit_token for pass-through, expand_builtin_call for 16 ## !@%$, %select, %str, %local, %reset-output, and expand_call 17 ## for user macros. Macro expansions and %select push 18 ## fresh streams onto streams[]; popping rewinds the 19 ## expansion pool. 20 ## 21 ## Output is consumed directly by hex2pp -- there is no intermediate M0/hex2 22 ## stage. Lexical scoping for control-flow labels is delegated to hex2pp's 23 ## nestable .scope / .endscope; M1pp itself only handles per-expansion 24 ## macro hygiene labels (:@name / &@name). 25 ## define_macro Parse %macro header+body; record in macros[] + 26 ## macro_body_tokens[]; consume through the %endm line 27 ## without emitting output. 28 ## 29 ## P1 ABI: a0..a3 arg/return, t0..t2 caller-saved temps, s0..s3 callee-saved 30 ## (unused here). Non-leaf functions use enter_0 / eret. Entry is the portable 31 ## p1_main (a0=argc, a1=argv); the backend-owned :_start stub captures 32 ## argc/argv from the native entry state and sys_exits p1_main's return value. 33 34 ## --- Constants & sizing ------------------------------------------------------ 35 36 ## Caps bumped 2025-04 for tcc-boot2: cc.scm against the flattened 37 ## tcc.flat.c emits ~6.5 MB of macro-rich .P1pp; m1pp's old caps were 38 ## sized for the scheme1 build at ~256 KiB. New caps cover the larger 39 ## working set the cc-bootstrap path exercises: 40 ## 41 ## 16 MiB input — combined prelude + tcc.flat.P1pp 42 ## 128 MiB output — final .M1 (~8x source after expansion) 43 ## 64 MiB text-arena — pasted names, hex literals from %(...) eval, 44 ## per-call @local label rewrites (cc.scm 45 ## triggers ~hundreds of K of each) 46 ## 256 MiB source-toks — 8 M Token slots × 32 B 47 DEFINE M1PP_INPUT_CAP 0000000100000000 48 DEFINE M1PP_OUTPUT_CAP 0000000800000000 49 DEFINE M1PP_TEXT_CAP 0000000400000000 50 ## source_tokens cap: 256 MB / 32-byte tokens = ~8.4 M tokens. cc.scm 51 ## emits one source token per ~3 input bytes (heavy macro use), so a 52 ## 6.5 MB tcc.flat.P1pp tokenises to ~2.5 M; 8 M leaves comfortable 53 ## headroom for the larger TUs the harness will exercise next. 54 DEFINE M1PP_TOKENS_END 0000001000000000 55 ## Macro record is 304 bytes: name (16) + param_count (8) + params[16]*16 (256) 56 ## + body_start (8) + body_end (8) + has_paste (8). MACROS_CAP fits 1024 57 ## records (311296 B). The 2x BSS slot for macros (0x94000 B) still has 58 ## ample headroom past the new cap. has_paste is a 0/1 flag set during 59 ## define_macro when the body contains TOK_PASTE; expand_macro_tokens 60 ## reads it (along with args_have_paste from parse_args) to skip the 61 ## paste_pool_range scan when neither side contributes a `##`. 62 ## Body-token arena fits 65536 tokens (2 MB = 0x200000). 63 DEFINE M1PP_MACRO_RECORD_SIZE 3001000000000000 64 DEFINE M1PP_MACRO_BODY_START_OFF 1801000000000000 65 DEFINE M1PP_MACRO_BODY_END_OFF 2001000000000000 66 DEFINE M1PP_MACRO_HAS_PASTE_OFF 2801000000000000 67 DEFINE M1PP_MACROS_CAP 00C0040000000000 68 DEFINE M1PP_MACRO_BODY_CAP 0000200000000000 69 DEFINE O_WRONLY_CREAT_TRUNC 4102000000000000 70 DEFINE MODE_0644 A401000000000000 71 DEFINE AT_FDCWD 9CFFFFFFFFFFFFFF 72 DEFINE ZERO32 '0000000000000000000000000000000000000000000000000000000000000000' 73 DEFINE ZERO8 '0000000000000000' 74 DEFINE ZERO4 '00000000' 75 76 DEFINE TOK_WORD 0000000000000000 77 DEFINE TOK_STRING 0100000000000000 78 DEFINE TOK_NEWLINE 0200000000000000 79 DEFINE TOK_LPAREN 0300000000000000 80 DEFINE TOK_RPAREN 0400000000000000 81 DEFINE TOK_COMMA 0500000000000000 82 DEFINE TOK_PASTE 0600000000000000 83 DEFINE TOK_LBRACE 0700000000000000 84 DEFINE TOK_RBRACE 0800000000000000 85 86 ## Token record stride (kind + text_ptr + text_len + tight). Advance by this. 87 ## Layout: +0 kind (8), +8 text_ptr (8), +16 text_len (8), +24 tight (8) = 32. 88 ## Only byte 0 of the tight word is meaningful (0/1); upper bytes are zero. 89 DEFINE M1PP_TOK_SIZE 2000000000000000 90 DEFINE M1PP_TOK_TIGHT_OFF 1800000000000000 91 92 ## --- Stream / expansion-pool / expression-frame sizes ------------------------ 93 ## Stream record: 40 bytes. Fields (each 8 bytes): 94 ## +0 start Token* 95 ## +8 end Token* (exclusive) 96 ## +16 pos Token* 97 ## +24 line_start u64 (1 at entry, 0 after first non-newline emit) 98 ## +32 pool_mark i64 (byte offset into expand_pool; -1 for source) 99 DEFINE M1PP_STREAM_SIZE 2800000000000000 100 DEFINE M1PP_STREAM_END_OFF 0800000000000000 101 DEFINE M1PP_STREAM_POS_OFF 1000000000000000 102 DEFINE M1PP_STREAM_LS_OFF 1800000000000000 103 DEFINE M1PP_STREAM_MARK_OFF 2000000000000000 104 105 ## Stream stack cap: 128 streams × 40 = 5120 bytes (was 16 × 40 = 640). 106 ## Each nested macro / expression eval pushes a stream; cc.scm's macro 107 ## chains (%ld → %p1_mem → %select → %aa64_mem → %aa64_mem_after_nonneg 108 ## → %aa64_ldst_uimm12 → %(EXPR), plus %fn / %ifelse_* / @local label 109 ## scopes around the call site) routinely exceed 16 deep on the 110 ## tcc-boot2 path. Note: the runtime overflow message ("token buffer 111 ## overflow") at line 2504 is misleadingly shared with EXPAND/TOKENS; 112 ## hitting the cap there means *stream* stack exhaustion. 113 DEFINE M1PP_STREAM_STACK_CAP 0014000000000000 114 115 ## Expansion pool fits 524288 Token slots × 32 bytes = 16 MB (0x1000000). 116 ## cc.scm emits %fn(name, sz, { body }), and m1pp eagerly copies the 117 ## body argument into the pool when expanding %fn — so the entire body 118 ## of a long function lives in the pool until %fn pops. tcc.c's 119 ## next_nomacro1 (~5900 lines × ~13 tokens/line ≈ 77 K tokens, ~2.5 MiB) 120 ## plus nested expansions sit comfortably under 16 MiB. 121 DEFINE M1PP_EXPAND_CAP 0000000100000000 122 123 ## ExprFrame record: 144 bytes. Fields: 124 ## +0 op_code u64 125 ## +8 argc u64 126 ## +16 args i64[16] (16 × 8 = 128 bytes) 127 DEFINE M1PP_EXPR_FRAME_SIZE 9000000000000000 128 DEFINE M1PP_EXPR_ARGC_OFF 0800000000000000 129 DEFINE M1PP_EXPR_ARGS_OFF 1000000000000000 130 131 ## Expr frame stack cap: 16 frames × 144 = 2304 bytes. 132 DEFINE M1PP_EXPR_FRAMES_CAP 0009000000000000 133 134 ## Common cap used by macro params, call args, and expression args. 135 DEFINE M1PP_MAX_PARAMS 1000000000000000 136 137 ## ExprOp codes (indexed by apply_expr_op). 138 DEFINE EXPR_ADD 0000000000000000 139 DEFINE EXPR_SUB 0100000000000000 140 DEFINE EXPR_MUL 0200000000000000 141 DEFINE EXPR_DIV 0300000000000000 142 DEFINE EXPR_MOD 0400000000000000 143 DEFINE EXPR_SHL 0500000000000000 144 DEFINE EXPR_SHR 0600000000000000 145 DEFINE EXPR_AND 0700000000000000 146 DEFINE EXPR_OR 0800000000000000 147 DEFINE EXPR_XOR 0900000000000000 148 DEFINE EXPR_NOT 0A00000000000000 149 DEFINE EXPR_EQ 0B00000000000000 150 DEFINE EXPR_NE 0C00000000000000 151 DEFINE EXPR_LT 0D00000000000000 152 DEFINE EXPR_LE 0E00000000000000 153 DEFINE EXPR_GT 0F00000000000000 154 DEFINE EXPR_GE 1000000000000000 155 DEFINE EXPR_STRLEN 1100000000000000 156 DEFINE EXPR_INVALID 1200000000000000 157 ## --- BSS layout (offsets from ELF_end) ------------------------------------- 158 ## Total BSS ~499 MB, under the 512 MB segment memsz set by 159 ## vendor/seed/<arch>/ELF.hex2. (If a future bump pushes past 512 MB, 160 ## raise p_memsz in vendor/seed/<arch>/ELF.hex2 to 1 GiB and bump here.) 161 ## 162 ## Per-region BSS slot sizes (offset diffs): 163 ## input_buf 32 MB (2x INPUT_CAP, headroom for trailing NUL etc.) 164 ## output_buf 128 MB (1x OUTPUT_CAP) 165 ## text_buf 64 MB (1x TEXT_CAP) 166 ## source_tokens 256 MB (1x TOKENS_END) 167 ## macros 592 KB (2x MACROS_CAP, unchanged) 168 ## macro_body_tokens 2 MB 169 ## streams 5120 B (128 streams × 40 B; was 16 × 40 = 640) 170 ## expand_pool 16 MB (1x EXPAND_CAP) 171 ## expr_frames 2304 B 172 DEFINE OFF_paste_scratch 0000000000000000 173 DEFINE OFF_local_label_scratch 0001000000000000 174 DEFINE OFF_df_name_scratch 8003000000000000 175 DEFINE OFF_ebc_str_scratch 8004000000000000 176 DEFINE OFF_arg_starts 8005000000000000 177 DEFINE OFF_arg_ends 0006000000000000 178 DEFINE OFF_input_buf 8006000000000000 179 DEFINE OFF_output_buf 8006000200000000 180 DEFINE OFF_text_buf 8006000A00000000 181 DEFINE OFF_source_tokens 8006000E00000000 182 DEFINE OFF_macros 8006001E00000000 183 DEFINE OFF_macro_body_tokens 8046091E00000000 184 DEFINE OFF_streams 8046291E00000000 185 DEFINE OFF_expand_pool 805A291E00000000 186 DEFINE OFF_expr_frames 805A291F00000000 187 ## local_lookup_scratch — 256-byte working buffer used by 188 ## expand_local_into_pool to assemble "<frame>_FRAME.<field>" before 189 ## the macro-table linear search. Placed past expr_frames (BSS end) so 190 ## adding it does not shift any existing OFF_*. 191 DEFINE OFF_local_lookup_scratch 0052850000000000 192 ## macro_body_param_idx / macro_body_is_local_label — 1 byte per body 193 ## token slot (M1PP_MACRO_BODY_CAP / 32 = 65536 slots, so each region 194 ## is 0x10000 bytes). Populated at %macro definition time so the 195 ## expand_macro_tokens body loop reads cached classifications instead 196 ## of re-running find_param + is_local_label_token per body token per 197 ## expansion. Placed past the existing expr_frames (BSS end ≈ 0x1F296380) 198 ## so adding them does not shift any other OFF_*. 199 DEFINE OFF_macro_body_param_idx 8063291F00000000 200 DEFINE OFF_macro_body_is_local_label 80632A1F00000000 201 202 203 ## --- Runtime shell: argv, read input, call pipeline, write output, exit ------ 204 205 :p1_main 206 enter_0 207 # --- init BSS pointer slots from ELF_end via table walk ------------------ 208 # Each bss_init_tbl entry is 16 bytes: 209 # +0 slot ptr (&label + 4 zero pad = 8-byte absolute address) 210 # +8 offset (8-byte OFF_* constant) 211 # For each entry: *slot_ptr = ELF_end + offset. 212 la_t0 &ELF_end 213 la_t1 &bss_init_tbl 214 la_t2 &bss_init_tbl_end 215 :bss_init_loop 216 la_br &bss_init_done 217 beq_t1,t2 218 ld_a2,t1,0 219 ld_a3,t1,8 220 add_a3,a3,t0 221 st_a3,a2,0 222 addi_t1,t1,16 223 la_br &bss_init_loop 224 b 225 :bss_init_done 226 # --- end BSS init ------------------------------------------------------- 227 228 # a0 = argc, a1 = argv (pointer to argv[0]). 229 # if (argc < 3) usage 230 li_a2 %3 %0 231 la_br &err_usage 232 blt_a0,a2 233 234 # Stash argv[1] and argv[2] before anything clobbers a1. Native argv 235 # entries are one target word; internal M1pp records remain padded to 8. 236 li_t2 p1wordbytes 237 add_t1,a1,t2 238 ld_t0,t1,0 239 la_a2 &input_path 240 st_t0,a2,0 241 add_t1,t1,t2 242 ld_t0,t1,0 243 la_a2 &output_path 244 st_t0,a2,0 245 246 # source_end = &source_tokens (running tail pointer) 247 la_a0 &source_tokens_ptr 248 ld_a0,a0,0 249 la_a2 &source_end 250 st_a0,a2,0 251 252 # macros_end = ¯os; macro_body_end = ¯o_body_tokens 253 la_a0 ¯os_ptr 254 ld_a0,a0,0 255 la_a2 ¯os_end 256 st_a0,a2,0 257 la_a0 ¯o_body_tokens_ptr 258 ld_a0,a0,0 259 la_a2 ¯o_body_end 260 st_a0,a2,0 261 262 # input_fd = openat(AT_FDCWD, input_path, O_RDONLY, 0) 263 li_a0 sys_openat 264 li_a1 AT_FDCWD 265 la_a2 &input_path 266 ld_a2,a2,0 267 li_a3 %0 %0 268 li_t0 %0 %0 269 syscall 270 la_br &err_open_input 271 bltz_a0 272 la_a1 &input_fd 273 st_a0,a1,0 274 275 :read_loop 276 # while (input_len < INPUT_CAP) 277 la_a0 &input_len 278 ld_t1,a0,0 279 li_t2 M1PP_INPUT_CAP 280 la_br &read_done 281 beq_t1,t2 282 283 # n = read(input_fd, &input_buf[input_len], INPUT_CAP - input_len) 284 la_a0 &input_fd 285 ld_a1,a0,0 286 la_a2 &input_buf_ptr 287 ld_a2,a2,0 288 add_a2,a2,t1 289 sub_a3,t2,t1 290 li_a0 sys_read 291 syscall 292 293 # if (n == 0) break; if (n < 0) fatal 294 la_br &read_done 295 beqz_a0 296 la_br &err_read 297 bltz_a0 298 299 # input_len += n 300 la_a1 &input_len 301 ld_a2,a1,0 302 add_a2,a2,a0 303 st_a2,a1,0 304 la_br &read_loop 305 b 306 307 :read_done 308 # if (input_len == INPUT_CAP) fatal (no room for null terminator) 309 la_a0 &input_len 310 ld_t0,a0,0 311 li_t1 M1PP_INPUT_CAP 312 la_br &err_input_too_big 313 beq_t0,t1 314 315 # input_buf[input_len] = '\0' 316 la_a0 &input_buf_ptr 317 ld_a0,a0,0 318 add_a0,a0,t0 319 li_t1 %0 %0 320 sb_t1,a0,0 321 322 # lex_source(); process_tokens() 323 la_br &lex_source 324 call 325 la_br &process_tokens 326 call 327 328 la_br &write_output 329 b 330 331 :write_output 332 # output_fd = openat(AT_FDCWD, output_path, O_WRONLY|O_CREAT|O_TRUNC, 0644) 333 la_a0 &output_path 334 ld_a2,a0,0 335 li_a0 sys_openat 336 li_a1 AT_FDCWD 337 li_a3 O_WRONLY_CREAT_TRUNC 338 li_t0 MODE_0644 339 syscall 340 la_br &err_open_output 341 bltz_a0 342 la_a1 &output_fd 343 st_a0,a1,0 344 345 :write_loop 346 # while (output_written < output_used) 347 la_a0 &output_written 348 ld_t0,a0,0 349 la_a1 &output_used 350 ld_t1,a1,0 351 la_br &write_done 352 beq_t0,t1 353 354 # n = write(output_fd, &output_buf[output_written], output_used - output_written) 355 la_a0 &output_fd 356 ld_a1,a0,0 357 la_a2 &output_buf_ptr 358 ld_a2,a2,0 359 add_a2,a2,t0 360 sub_a3,t1,t0 361 li_a0 sys_write 362 syscall 363 364 # n <= 0 is fatal (short write or error) 365 la_br &err_write 366 bltz_a0 367 la_br &err_write 368 beqz_a0 369 370 # output_written += n 371 la_a1 &output_written 372 ld_a2,a1,0 373 add_a2,a2,a0 374 st_a2,a1,0 375 la_br &write_loop 376 b 377 378 :write_done 379 # return 0 (backend :_start stub sys_exits with a0) 380 li_a0 %0 %0 381 eret 382 383 ## --- Helpers: text arena + token array + equality ---------------------------- 384 ## append_text appends bytes to text_buf (used for synthesized token text, 385 ## e.g. single-char parens/commas and the paste `##`). Source-word and string 386 ## tokens point directly into input_buf and skip this arena. 387 388 ## append_text(a0=src, a1=len) -> a0=text ptr. Leaf. 389 :append_text 390 # a3 = text_used 391 la_a2 &text_used 392 ld_a3,a2,0 393 394 # if (text_used + len + 1) > TEXT_CAP: fatal 395 add_t0,a3,a1 396 addi_t0,t0,1 397 li_t1 M1PP_TEXT_CAP 398 la_br &err_text_overflow 399 blt_t1,t0 400 401 # dst = &text_buf[text_used] 402 la_t0 &text_buf_ptr 403 ld_t0,t0,0 404 add_t0,t0,a3 405 406 # for (i = 0; i < len; i++) dst[i] = src[i] 407 li_t1 %0 %0 408 :append_text_loop 409 la_br &append_text_done 410 beq_t1,a1 411 add_t2,a0,t1 412 lb_t2,t2,0 413 add_a2,t0,t1 414 sb_t2,a2,0 415 addi_t1,t1,1 416 la_br &append_text_loop 417 b 418 :append_text_done 419 # dst[len] = '\0' 420 add_a2,t0,t1 421 li_t2 %0 %0 422 sb_t2,a2,0 423 424 # text_used += len + 1 425 la_a2 &text_used 426 ld_a3,a2,0 427 add_a3,a3,a1 428 addi_a3,a3,1 429 st_a3,a2,0 430 431 # return dst 432 mov_a0,t0 433 ret 434 435 ## push_source_token(a0=kind, a1=text_ptr, a2=text_len, a3=tight). Leaf. 436 ## Token layout: +0 kind, +8 text_ptr, +16 text_len, +24 tight (32B total). 437 ## tight=1 means "no whitespace before this token"; consulted only on LPAREN 438 ## by the paren-call recognizers (%FOO(...), !(...), @(...), %(...), $(...), 439 ## %select(...), %str(...)). All other kinds carry the bit but ignore it. 440 :push_source_token 441 # tok = source_end 442 la_t2 &source_end 443 ld_t0,t2,0 444 445 # if (tok == &source_tokens[0] + TOKENS_END) fatal 446 la_t1 &source_tokens_ptr 447 ld_t1,t1,0 448 li_t2 M1PP_TOKENS_END 449 add_t1,t1,t2 450 la_br &err_token_overflow 451 beq_t0,t1 452 453 # tok->kind = kind; tok->text_ptr = text_ptr; tok->text_len = text_len; 454 # tok->tight = tight (8-byte stores zero the rest of each slot). 455 st_a0,t0,0 456 st_a1,t0,8 457 st_a2,t0,16 458 st_a3,t0,24 459 460 # source_end = tok + 1 (advance 32 bytes) 461 la_t2 &source_end 462 addi_t0,t0,32 463 st_t0,t2,0 464 ret 465 466 ## tok_eq_const(a0=token_ptr, a1=const_ptr, a2=const_len) -> a0=0/1. Leaf. 467 ## Compares a token's text against a constant byte string. 468 :tok_eq_const 469 # if (tok->text_len != const_len) return 0 470 ld_a3,a0,16 471 la_br &tok_eq_false 472 bne_a3,a2 473 474 # src = tok->text_ptr; i = 0 475 ld_t0,a0,8 476 li_t1 %0 %0 477 :tok_eq_loop 478 # if (i == const_len) return 1 479 la_br &tok_eq_true 480 beq_t1,a2 481 482 # if (src[i] != const_ptr[i]) return 0 483 add_t2,t0,t1 484 lb_t2,t2,0 485 add_a3,a1,t1 486 lb_a3,a3,0 487 la_br &tok_eq_false 488 bne_t2,a3 489 490 # i++ 491 addi_t1,t1,1 492 la_br &tok_eq_loop 493 b 494 :tok_eq_true 495 li_a0 %1 %0 496 ret 497 :tok_eq_false 498 li_a0 %0 %0 499 ret 500 501 ## --- Lexer ------------------------------------------------------------------- 502 ## Dispatches on the first byte at lex_ptr: 503 ## whitespace (sp/tab/cr/ff/vt) -> lex_skip_one 504 ## newline (\n) -> lex_newline -> TOK_NEWLINE 505 ## quote (" or ') -> lex_string -> TOK_STRING 506 ## `#` -> lex_hash -> TOK_PASTE on ##, else comment 507 ## `;` -> lex_comment (drop to end of line) 508 ## `(` `)` `,` -> lex_lparen / rparen / comma 509 ## otherwise -> lex_word -> TOK_WORD 510 ## 511 ## All branches loop back to lex_loop. lex_done exits once lex_ptr hits 512 ## the terminating NUL that _start writes past the end of input_buf. 513 514 ## lex_source(): fills source_tokens[] from input_buf. 515 ## lex_saw_separator tracks whether whitespace (sp/tab/CR/etc., newline, 516 ## or `;`/`#` line comment) precedes the next token. Each non-newline push 517 ## passes tight = !lex_saw_separator and then sets lex_saw_separator = 0. 518 ## Initialized to 1 at start of file so the first token is never tight. 519 :lex_source 520 enter_0 521 la_a0 &input_buf_ptr 522 ld_a0,a0,0 523 la_a1 &lex_ptr 524 st_a0,a1,0 525 la_a0 &lex_saw_separator 526 li_t0 %1 %0 527 st_t0,a0,0 528 :lex_loop 529 # c = *lex_ptr; dispatch on lex_char_class[c]. 530 # 0 word, 1 skip ws, 2 newline, 3 string, 4 hash, 5 comment, 531 # 6 '(', 7 ')', 8 ',', 9 '{', 10 '}', 11 NUL (fall through to done). 532 la_a0 &lex_ptr 533 ld_t0,a0,0 534 lb_a0,t0,0 535 la_a1 &lex_char_class 536 add_a1,a1,a0 537 lb_a2,a1,0 538 539 la_br &lex_word 540 beqz_a2 541 li_a1 %1 %0 542 la_br &lex_skip_one 543 beq_a2,a1 544 li_a1 %2 %0 545 la_br &lex_newline 546 beq_a2,a1 547 li_a1 %3 %0 548 la_br &lex_string 549 beq_a2,a1 550 li_a1 %4 %0 551 la_br &lex_hash 552 beq_a2,a1 553 li_a1 %5 %0 554 la_br &lex_comment 555 beq_a2,a1 556 li_a1 %6 %0 557 la_br &lex_lparen 558 beq_a2,a1 559 li_a1 %7 %0 560 la_br &lex_rparen 561 beq_a2,a1 562 li_a1 %8 %0 563 la_br &lex_comma 564 beq_a2,a1 565 li_a1 %9 %0 566 la_br &lex_lbrace 567 beq_a2,a1 568 li_a1 %10 %0 569 la_br &lex_rbrace 570 beq_a2,a1 571 ## class 11 (NUL) — fall through 572 la_br &lex_done 573 b 574 575 :lex_skip_one 576 # whitespace separator: lex_saw_separator = 1; lex_ptr++ 577 la_a1 &lex_saw_separator 578 li_a2 %1 %0 579 st_a2,a1,0 580 addi_t0,t0,1 581 la_a0 &lex_ptr 582 st_t0,a0,0 583 la_br &lex_loop 584 b 585 586 :lex_newline 587 # push_source_token(TOK_NEWLINE, lex_ptr, 1, tight=0); newline acts as a 588 # separator for the NEXT token, so lex_saw_separator = 1 afterwards. 589 mov_a1,t0 590 li_a0 TOK_NEWLINE 591 li_a2 %1 %0 592 li_a3 %0 %0 593 la_br &push_source_token 594 call 595 596 la_a0 &lex_saw_separator 597 li_t0 %1 %0 598 st_t0,a0,0 599 600 # lex_ptr++ 601 la_a0 &lex_ptr 602 ld_t0,a0,0 603 addi_t0,t0,1 604 st_t0,a0,0 605 la_br &lex_loop 606 b 607 608 :lex_string 609 # lex_start = lex_ptr; lex_quote = c; lex_ptr++ 610 la_a1 &lex_start 611 st_t0,a1,0 612 la_a1 &lex_quote 613 st_a0,a1,0 614 addi_t0,t0,1 615 :lex_string_scan 616 # c = *lex_ptr 617 lb_a0,t0,0 618 # if (c == '\0') finish (unterminated; keep what we have) 619 la_br &lex_string_finish 620 beqz_a0 621 # if (c == '\\' && lex_ptr[1] != '\0') skip both bytes as a unit so 622 # `\"` and `\\` don't accidentally terminate the string. Decoding 623 # the escape's *meaning* (e.g. for %bytes) happens later — here we 624 # only care about token boundaries. 625 li_a1 %92 %0 626 la_br &lex_string_check_backslash 627 beq_a0,a1 628 la_br &lex_string_no_escape 629 b 630 :lex_string_check_backslash 631 addi_a1,t0,1 632 lb_a1,a1,0 633 la_br &lex_string_no_escape 634 beqz_a1 635 addi_t0,t0,2 636 la_br &lex_string_scan 637 b 638 :lex_string_no_escape 639 # if (c == quote) consume closing quote and finish 640 la_a1 &lex_quote 641 ld_a1,a1,0 642 la_br &lex_string_after_quote 643 beq_a0,a1 644 # else lex_ptr++ 645 addi_t0,t0,1 646 la_br &lex_string_scan 647 b 648 :lex_string_after_quote 649 addi_t0,t0,1 650 :lex_string_finish 651 # lex_ptr = t0 652 la_a1 &lex_ptr 653 st_t0,a1,0 654 655 # text_ptr = append_text(lex_start, lex_ptr - lex_start) 656 la_a1 &lex_start 657 ld_a0,a1,0 658 sub_a1,t0,a0 659 la_br &append_text 660 call 661 662 # push_source_token(TOK_STRING, text_ptr, lex_ptr - lex_start, 663 # tight = !lex_saw_separator); then lex_saw_separator = 0. 664 la_a1 &lex_ptr 665 ld_t0,a1,0 666 la_a1 &lex_start 667 ld_t1,a1,0 668 sub_a2,t0,t1 669 mov_a1,a0 670 li_a0 TOK_STRING 671 la_a3 &lex_saw_separator 672 ld_a3,a3,0 673 li_t1 %1 %0 674 sub_a3,t1,a3 675 la_br &push_source_token 676 call 677 la_a0 &lex_saw_separator 678 li_t0 %0 %0 679 st_t0,a0,0 680 la_br &lex_loop 681 b 682 683 :lex_hash 684 # if (lex_ptr[1] == '#') goto lex_paste, else lex_comment 685 addi_a1,t0,1 686 lb_a1,a1,0 687 li_a2 %35 %0 688 la_br &lex_paste 689 beq_a1,a2 690 la_br &lex_comment 691 b 692 693 :lex_paste 694 # text_ptr = append_text("##", 2) 695 la_a0 &const_paste 696 li_a1 %2 %0 697 la_br &append_text 698 call 699 700 # push_source_token(TOK_PASTE, text_ptr, 2, tight = !lex_saw_separator). 701 mov_a1,a0 702 li_a0 TOK_PASTE 703 li_a2 %2 %0 704 la_a3 &lex_saw_separator 705 ld_a3,a3,0 706 li_t1 %1 %0 707 sub_a3,t1,a3 708 la_br &push_source_token 709 call 710 711 la_a0 &lex_saw_separator 712 li_t0 %0 %0 713 st_t0,a0,0 714 715 # lex_ptr += 2 716 la_a0 &lex_ptr 717 ld_t0,a0,0 718 addi_t0,t0,2 719 st_t0,a0,0 720 la_br &lex_loop 721 b 722 723 :lex_comment 724 # skip to end of line: while (c != '\0' && c != '\n') lex_ptr++ 725 la_a0 &lex_ptr 726 ld_t0,a0,0 727 :lex_comment_loop 728 lb_a0,t0,0 729 la_br &lex_comment_done 730 beqz_a0 731 li_a1 %10 %0 732 la_br &lex_comment_done 733 beq_a0,a1 734 addi_t0,t0,1 735 la_br &lex_comment_loop 736 b 737 :lex_comment_done 738 # `;` / `#` line comment counts as a separator for the next token. 739 la_a0 &lex_ptr 740 st_t0,a0,0 741 la_a0 &lex_saw_separator 742 li_t0 %1 %0 743 st_t0,a0,0 744 la_br &lex_loop 745 b 746 747 ## lex_lparen / lex_rparen / lex_comma all share the same shape: 748 ## append the single-char constant, push a 1-byte token of the right kind, 749 ## then fall through to lex_advance_one_then_loop to bump lex_ptr. 750 751 :lex_lparen 752 li_a0 TOK_LPAREN 753 la_a1 &const_lparen 754 la_br &lex_punct1 755 b 756 :lex_rparen 757 li_a0 TOK_RPAREN 758 la_a1 &const_rparen 759 la_br &lex_punct1 760 b 761 :lex_comma 762 li_a0 TOK_COMMA 763 la_a1 &const_comma 764 la_br &lex_punct1 765 b 766 :lex_lbrace 767 li_a0 TOK_LBRACE 768 la_a1 &const_lbrace 769 la_br &lex_punct1 770 b 771 :lex_rbrace 772 li_a0 TOK_RBRACE 773 la_a1 &const_rbrace 774 ## fall through into lex_punct1 775 776 ## lex_punct1(a0=kind, a1=const_ptr): append 1 byte to text arena, push a 777 ## 1-byte token of the given kind, advance lex_ptr by 1, branch back to 778 ## lex_loop. Called by tail-branch from the single-char lex_X blocks, which 779 ## all share lex_source's frame. Spills `kind` since append_text clobbers 780 ## a0..a3. 781 :lex_punct1 782 la_t0 &lex_punct_kind 783 st_a0,t0,0 784 mov_a0,a1 785 li_a1 %1 %0 786 la_br &append_text 787 call 788 mov_a1,a0 789 la_t0 &lex_punct_kind 790 ld_a0,t0,0 791 li_a2 %1 %0 792 # tight = !lex_saw_separator (the load is consumed before push_source_token). 793 la_a3 &lex_saw_separator 794 ld_a3,a3,0 795 li_t1 %1 %0 796 sub_a3,t1,a3 797 la_br &push_source_token 798 call 799 la_a0 &lex_saw_separator 800 li_t0 %0 %0 801 st_t0,a0,0 802 ## fall through to lex_advance_one_then_loop 803 804 :lex_advance_one_then_loop 805 # lex_ptr++ 806 la_a0 &lex_ptr 807 ld_t0,a0,0 808 addi_t0,t0,1 809 st_t0,a0,0 810 la_br &lex_loop 811 b 812 813 :lex_word 814 # lex_start = lex_ptr 815 la_a1 &lex_start 816 st_t0,a1,0 817 :lex_word_scan 818 # c = *lex_ptr; terminate the word if lex_char_class[c] is non-WORD, 819 # but treat class 3 (string-quote `"`/`'`) as part of the word too — 820 # quotes only start a STRING token at token start, not mid-word. 821 # That matches M1pp.c, where the WORD scanner ignores `"`/`'` and 822 # so `\`"hi"\`` (backtick-quote-...-quote-backtick with no spaces) 823 # lexes as a single WORD. 824 lb_a2,t0,0 825 la_a1 &lex_char_class 826 add_a1,a1,a2 827 lb_a2,a1,0 828 la_br &lex_word_continue 829 beqz_a2 830 li_a1 %3 %0 831 la_br &lex_word_finish 832 bne_a2,a1 833 :lex_word_continue 834 addi_t0,t0,1 835 la_br &lex_word_scan 836 b 837 :lex_word_finish 838 # lex_ptr = t0 839 la_a1 &lex_ptr 840 st_t0,a1,0 841 842 # text_ptr = append_text(lex_start, lex_ptr - lex_start) 843 la_a1 &lex_start 844 ld_a0,a1,0 845 sub_a1,t0,a0 846 la_br &append_text 847 call 848 849 # push_source_token(TOK_WORD, text_ptr, lex_ptr - lex_start, 850 # tight = !lex_saw_separator); lex_saw_separator = 0. 851 la_a1 &lex_ptr 852 ld_t0,a1,0 853 la_a1 &lex_start 854 ld_t1,a1,0 855 sub_a2,t0,t1 856 mov_a1,a0 857 li_a0 TOK_WORD 858 la_a3 &lex_saw_separator 859 ld_a3,a3,0 860 li_t1 %1 %0 861 sub_a3,t1,a3 862 la_br &push_source_token 863 call 864 la_a0 &lex_saw_separator 865 li_t0 %0 %0 866 st_t0,a0,0 867 la_br &lex_loop 868 b 869 870 :lex_done 871 eret 872 873 ## --- Output: normalized token stream to output_buf --------------------------- 874 ## emit_newline writes '\n' and clears output_need_space. 875 ## emit_token prefixes a space when output_need_space is set, copies the 876 ## token text, then sets output_need_space. This is how source whitespace 877 ## gets normalized: one '\n' per TOK_NEWLINE, one ' ' between consecutive 878 ## non-newline tokens. 879 880 ## emit_newline(). Leaf. 881 :emit_newline 882 # if (output_used == OUTPUT_CAP) fatal 883 la_a0 &output_used 884 ld_t0,a0,0 885 li_t1 M1PP_OUTPUT_CAP 886 la_br &err_output_overflow 887 beq_t0,t1 888 889 # output_buf[output_used] = '\n'; output_used++ 890 la_a1 &output_buf_ptr 891 ld_a1,a1,0 892 add_a1,a1,t0 893 li_t2 %10 %0 894 sb_t2,a1,0 895 addi_t0,t0,1 896 st_t0,a0,0 897 898 # output_need_space = 0 899 la_a0 &output_need_space 900 li_a1 %0 %0 901 st_a1,a0,0 902 ret 903 904 ## emit_token(a0=token_ptr). Tail-calls emit_string_as_bytes for 905 ## TOK_STRING (which has its own enter_0/eret frame), so emit_token 906 ## itself stays leaf for the WORD path. 907 :emit_token 908 # brace tokens are no-ops at emit time (belt-and-braces with arg-strip) 909 ld_t0,a0,0 910 li_t1 TOK_LBRACE 911 la_br &emit_token_skip 912 beq_t0,t1 913 li_t1 TOK_RBRACE 914 la_br &emit_token_skip 915 beq_t0,t1 916 # Bare TOK_STRING decodes to raw bytes via emit_string_as_bytes. 917 # Branch (not call): the tail call returns to emit_token's caller. 918 li_t1 TOK_STRING 919 la_br &emit_string_as_bytes 920 beq_t0,t1 921 922 # if (output_need_space) emit ' ' (skip the space for the first token on a line) 923 la_a1 &output_need_space 924 ld_t0,a1,0 925 la_br &emit_token_copy 926 beqz_t0 927 928 la_a1 &output_used 929 ld_t0,a1,0 930 li_t1 M1PP_OUTPUT_CAP 931 la_br &err_output_overflow 932 beq_t0,t1 933 la_a2 &output_buf_ptr 934 ld_a2,a2,0 935 add_a2,a2,t0 936 li_t1 %32 %0 937 sb_t1,a2,0 938 addi_t0,t0,1 939 st_t0,a1,0 940 941 :emit_token_copy 942 # src = tok->text_ptr; len = tok->text_len; i = 0 943 ld_t0,a0,8 944 ld_t1,a0,16 945 li_t2 %0 %0 946 :emit_token_loop 947 # if (i == len) done 948 la_br &emit_token_done 949 beq_t2,t1 950 951 # if (output_used == OUTPUT_CAP) fatal 952 la_a1 &output_used 953 ld_a2,a1,0 954 li_a3 M1PP_OUTPUT_CAP 955 la_br &err_output_overflow 956 beq_a2,a3 957 958 # output_buf[output_used++] = src[i] 959 add_a3,t0,t2 960 lb_a3,a3,0 961 la_a0 &output_buf_ptr 962 ld_a0,a0,0 963 add_a0,a0,a2 964 sb_a3,a0,0 965 addi_a2,a2,1 966 st_a2,a1,0 967 968 # i++ 969 addi_t2,t2,1 970 la_br &emit_token_loop 971 b 972 :emit_token_done 973 # output_need_space = 1 974 la_a0 &output_need_space 975 li_a1 %1 %0 976 st_a1,a0,0 977 ret 978 :emit_token_skip 979 ret 980 981 982 ## --- Main processor ---------------------------------------------------------- 983 ## Stream-driven loop. Pushes source_tokens as the initial stream, then drives 984 ## the streams[] stack until it empties. Per iteration: pop the stream if 985 ## exhausted, otherwise dispatch on the current token: 986 ## - line-start %macro -> shim into define_macro via proc_pos 987 ## - TOK_NEWLINE -> emit_newline, advance, set line_start = 1 988 ## - WORD + LPAREN follow + name in {! @ % $ %select} 989 ## -> expand_builtin_call(s, tok) 990 ## - find_macro(tok) hit + LPAREN follow 991 ## -> expand_call(s, macro) 992 ## - otherwise -> emit_token, advance, clear line_start 993 ## 994 ## Stack frame: enter_16 reserves two 8-byte slots so we can preserve the 995 ## current Stream* (sp+16) and the current Token* (sp+24) across calls 996 ## (a0..a3, t0..t2 are caller-saved). 997 998 ## process_tokens(): stream-driven main loop. 999 :process_tokens 1000 enter_16 1001 1002 # push_stream_span(source_tokens, source_end, -1) 1003 la_a0 &source_tokens_ptr 1004 ld_a0,a0,0 1005 la_a1 &source_end 1006 ld_a1,a1,0 1007 sub_a2,a2,a2 1008 addi_a2,a2,neg1 1009 la_br &push_stream_span 1010 call 1011 1012 ## proc_loop dispatch refactor: 1013 ## 1014 ## A — first-byte gate: most pass-through tokens (plain identifiers, 1015 ## hex literals, the synthetic WORDs emitted by !@%$ evaluation) 1016 ## don't begin with %, !, @, or $ and exit through proc_emit after 1017 ## a single byte compare. The old cascade ran 5 directive + up to 1018 ## 7 builtin tok_eq_const probes for *every* WORD. 1019 ## B — second-byte dispatch: within the c0=='%' branch, a single 1020 ## c1-byte switch picks at most one directive/builtin to actually 1021 ## compare against (e.g. c1='s' selects %struct/%select/%str only). 1022 ## A user macro %FOO with c1 outside {m,s,e,f,b,l,r} skips every 1023 ## tok_eq_const and goes straight to find_macro. 1024 :proc_loop 1025 # s = current_stream(); if (s == 0) done 1026 la_br ¤t_stream 1027 call 1028 la_br &proc_done 1029 beqz_a0 1030 st_a0,sp,0 1031 1032 # if (s->pos == s->end) pop and continue 1033 ld_t0,a0,16 1034 ld_t1,a0,8 1035 la_br &proc_pop_continue 1036 beq_t0,t1 1037 1038 # tok = s->pos 1039 st_t0,sp,8 1040 1041 # ---- TOK_NEWLINE fast path ---- 1042 ld_a1,t0,0 1043 li_a2 TOK_NEWLINE 1044 la_br &proc_handle_newline 1045 beq_a1,a2 1046 1047 # Non-WORD tokens (LPAREN, RPAREN, COMMA, LBRACE, RBRACE, STRING, 1048 # PASTE) skip the whole dispatch and emit literally. 1049 li_a2 TOK_WORD 1050 la_br &proc_emit 1051 bne_a1,a2 1052 1053 # tok->text.len, ptr — needed for the byte gate. 1054 ld_t1,t0,16 1055 la_br &proc_emit 1056 beqz_t1 1057 ld_t2,t0,8 1058 lb_a3,t2,0 1059 1060 # has_paren = (tok+1 < s->end && (tok+1)->kind == TOK_LPAREN 1061 # && (tok+1)->tight). Stash to proc_has_paren. 1062 li_a0 %0 %0 1063 la_a1 &proc_has_paren 1064 st_a0,a1,0 1065 addi_a2,t0,32 1066 ld_a1,sp,0 1067 ld_a1,a1,8 1068 la_br &proc_byte_gate 1069 blt_a1,a2 1070 la_br &proc_byte_gate 1071 beq_a2,a1 1072 ld_a0,a2,0 1073 li_a1 TOK_LPAREN 1074 la_br &proc_byte_gate 1075 bne_a0,a1 1076 ld_a0,a2,24 1077 la_br &proc_byte_gate 1078 beqz_a0 1079 li_a0 %1 %0 1080 la_a1 &proc_has_paren 1081 st_a0,a1,0 1082 1083 :proc_byte_gate 1084 # c0 == '%' (37) -> percent branch 1085 li_a0 %37 %0 1086 la_br &proc_c0_pct 1087 beq_a3,a0 1088 # Arith builtins ! @ $ require len == 1 + has_paren. All other 1089 # first-byte values fall through to proc_emit. 1090 li_a0 %1 %0 1091 la_br &proc_emit 1092 bne_t1,a0 1093 la_a1 &proc_has_paren 1094 ld_a1,a1,0 1095 la_br &proc_emit 1096 beqz_a1 1097 li_a0 %33 %0 1098 la_br &proc_do_builtin 1099 beq_a3,a0 1100 li_a0 %64 %0 1101 la_br &proc_do_builtin 1102 beq_a3,a0 1103 li_a0 %36 %0 1104 la_br &proc_do_builtin 1105 beq_a3,a0 1106 la_br &proc_emit 1107 b 1108 1109 :proc_c0_pct 1110 # c0 == '%'. Bare '%' (len == 1) + tight-paren -> arith builtin. 1111 li_a0 %1 %0 1112 la_br &proc_c0_pct_one 1113 beq_t1,a0 1114 # len >= 2: dispatch on c1 (text[1]). 1115 lb_a3,t2,1 1116 1117 li_a0 %109 %0 1118 la_br &proc_c1_m 1119 beq_a3,a0 1120 li_a0 %115 %0 1121 la_br &proc_c1_s 1122 beq_a3,a0 1123 li_a0 %101 %0 1124 la_br &proc_c1_e 1125 beq_a3,a0 1126 li_a0 %102 %0 1127 la_br &proc_c1_f 1128 beq_a3,a0 1129 li_a0 %98 %0 1130 la_br &proc_c1_b 1131 beq_a3,a0 1132 li_a0 %108 %0 1133 la_br &proc_c1_l 1134 beq_a3,a0 1135 li_a0 %114 %0 1136 la_br &proc_c1_r 1137 beq_a3,a0 1138 # No directive/builtin candidate — try user macro. 1139 la_br &proc_check_macro 1140 b 1141 1142 :proc_c0_pct_one 1143 la_a1 &proc_has_paren 1144 ld_a1,a1,0 1145 la_br &proc_emit 1146 beqz_a1 1147 la_br &proc_do_builtin 1148 b 1149 1150 :proc_c1_m 1151 # %macro 1152 ld_a0,sp,8 1153 la_a1 &const_macro 1154 li_a2 %6 %0 1155 la_br &tok_eq_const 1156 call 1157 la_br &proc_check_macro 1158 beqz_a0 1159 ld_a0,sp,0 1160 ld_a1,sp,8 1161 la_br &proc_save_pos_and_ls 1162 call 1163 la_br &define_macro 1164 call 1165 la_br &proc_restore_and_loop 1166 b 1167 1168 :proc_c1_s 1169 # %struct (no paren); %select / %str (paren). 1170 ld_a0,sp,8 1171 la_a1 &const_struct 1172 li_a2 %7 %0 1173 la_br &tok_eq_const 1174 call 1175 la_br &proc_c1_s_not_struct 1176 beqz_a0 1177 ld_a0,sp,0 1178 ld_a1,sp,8 1179 la_br &proc_save_pos_and_ls 1180 call 1181 li_a0 %8 %0 1182 la_a1 &const_size 1183 li_a2 %4 %0 1184 la_br &define_fielded 1185 call 1186 la_br &proc_restore_and_loop 1187 b 1188 :proc_c1_s_not_struct 1189 la_a1 &proc_has_paren 1190 ld_a1,a1,0 1191 la_br &proc_check_macro 1192 beqz_a1 1193 ld_a0,sp,8 1194 la_a1 &const_select 1195 li_a2 %7 %0 1196 la_br &tok_eq_const 1197 call 1198 la_br &proc_c1_s_not_select 1199 beqz_a0 1200 la_br &proc_do_builtin 1201 b 1202 :proc_c1_s_not_select 1203 ld_a0,sp,8 1204 la_a1 &const_str 1205 li_a2 %4 %0 1206 la_br &tok_eq_const 1207 call 1208 la_br &proc_check_macro 1209 beqz_a0 1210 la_br &proc_do_builtin 1211 b 1212 1213 :proc_c1_e 1214 # %enum (no paren); %endframe (no paren). 1215 ld_a0,sp,8 1216 la_a1 &const_enum 1217 li_a2 %5 %0 1218 la_br &tok_eq_const 1219 call 1220 la_br &proc_c1_e_not_enum 1221 beqz_a0 1222 ld_a0,sp,0 1223 ld_a1,sp,8 1224 la_br &proc_save_pos_and_ls 1225 call 1226 li_a0 %1 %0 1227 la_a1 &const_count 1228 li_a2 %5 %0 1229 la_br &define_fielded 1230 call 1231 la_br &proc_restore_and_loop 1232 b 1233 :proc_c1_e_not_enum 1234 ld_a0,sp,8 1235 la_a1 &const_endframe 1236 li_a2 %9 %0 1237 la_br &tok_eq_const 1238 call 1239 la_br &proc_check_macro 1240 beqz_a0 1241 ld_a0,sp,0 1242 ld_a1,sp,8 1243 la_br &proc_save_pos_and_ls 1244 call 1245 ld_a0,sp,0 1246 ld_a0,a0,8 1247 la_br &pop_frame 1248 call 1249 la_br &proc_restore_and_loop 1250 b 1251 1252 :proc_c1_f 1253 # %frame (no paren). 1254 ld_a0,sp,8 1255 la_a1 &const_frame 1256 li_a2 %6 %0 1257 la_br &tok_eq_const 1258 call 1259 la_br &proc_check_macro 1260 beqz_a0 1261 ld_a0,sp,0 1262 ld_a1,sp,8 1263 la_br &proc_save_pos_and_ls 1264 call 1265 ld_a0,sp,0 1266 ld_a0,a0,8 1267 la_br &push_frame 1268 call 1269 la_br &proc_restore_and_loop 1270 b 1271 1272 :proc_c1_b 1273 # %bytes (paren). 1274 la_a1 &proc_has_paren 1275 ld_a1,a1,0 1276 la_br &proc_check_macro 1277 beqz_a1 1278 ld_a0,sp,8 1279 la_a1 &const_bytes 1280 li_a2 %6 %0 1281 la_br &tok_eq_const 1282 call 1283 la_br &proc_check_macro 1284 beqz_a0 1285 la_br &proc_do_builtin 1286 b 1287 1288 :proc_c1_l 1289 # %local (paren). 1290 la_a1 &proc_has_paren 1291 ld_a1,a1,0 1292 la_br &proc_check_macro 1293 beqz_a1 1294 ld_a0,sp,8 1295 la_a1 &const_local 1296 li_a2 %6 %0 1297 la_br &tok_eq_const 1298 call 1299 la_br &proc_check_macro 1300 beqz_a0 1301 la_br &proc_do_builtin 1302 b 1303 1304 :proc_c1_r 1305 # %reset-output BASE (no paren). Discard everything emitted by the 1306 # macro/runtime prelude and seed a disjoint macro-hygiene id range. 1307 ld_a0,sp,8 1308 la_a1 &const_reset_output 1309 li_a2 %13 %0 1310 la_br &tok_eq_const 1311 call 1312 la_br &proc_check_macro 1313 beqz_a0 1314 ld_a0,sp,0 1315 ld_a1,sp,8 1316 la_br &proc_save_pos_and_ls 1317 call 1318 ld_a0,sp,0 1319 ld_a0,a0,8 1320 la_br &reset_output 1321 call 1322 la_br &proc_restore_and_loop 1323 b 1324 1325 :proc_handle_newline 1326 ld_a0,sp,0 1327 ld_t0,sp,8 1328 addi_t0,t0,32 1329 st_t0,a0,16 1330 li_t1 %1 %0 1331 st_t1,a0,24 1332 la_br &emit_newline 1333 call 1334 la_br &proc_loop 1335 b 1336 1337 :proc_do_builtin 1338 # expand_builtin_call(s, tok) 1339 ld_a0,sp,0 1340 ld_a1,sp,8 1341 la_br &expand_builtin_call 1342 call 1343 la_br &proc_loop 1344 b 1345 1346 :proc_check_macro 1347 # macro = find_macro(tok); if non-zero AND 1348 # ((tok+1 < s->end AND (tok+1)->kind == TOK_LPAREN) OR macro->param_count == 0) 1349 # then expand_call. Paren-less form is reserved for 0-arg macros. 1350 ld_a0,sp,8 1351 la_br &find_macro 1352 call 1353 la_br &proc_emit 1354 beqz_a0 1355 mov_t2,a0 1356 ld_a0,sp,0 1357 ld_t0,sp,8 1358 addi_t1,t0,32 1359 ld_a1,a0,8 1360 la_br &proc_macro_has_next 1361 blt_t1,a1 1362 la_br &proc_macro_zero_arg 1363 b 1364 :proc_macro_has_next 1365 ld_a1,t1,0 1366 li_a2 TOK_LPAREN 1367 la_br &proc_macro_zero_arg 1368 bne_a1,a2 1369 # require (tok+1)->tight — `%FOO ( ... )` with whitespace is the 1370 # paren-less form (zero-arg only) followed by a literal `(`. 1371 ld_a1,t1,24 1372 la_br &proc_macro_zero_arg 1373 beqz_a1 1374 ld_a0,sp,0 1375 mov_a1,t2 1376 la_br &expand_call 1377 call 1378 la_br &proc_loop 1379 b 1380 :proc_macro_zero_arg 1381 # No trailing LPAREN (or LPAREN not tight). Expand only if param_count == 0. 1382 ld_t0,t2,16 1383 la_br &proc_emit 1384 bnez_t0 1385 ld_a0,sp,0 1386 mov_a1,t2 1387 la_br &expand_call 1388 call 1389 la_br &proc_loop 1390 b 1391 1392 :proc_emit 1393 # emit_token(tok); s->pos += 24; s->line_start = 0 1394 ld_a0,sp,8 1395 la_br &emit_token 1396 call 1397 ld_a0,sp,0 1398 ld_t0,a0,16 1399 addi_t0,t0,32 1400 st_t0,a0,16 1401 li_t1 %0 %0 1402 st_t1,a0,24 1403 la_br &proc_loop 1404 b 1405 1406 :proc_pop_continue 1407 la_br &pop_stream 1408 call 1409 la_br &proc_loop 1410 b 1411 1412 :proc_done 1413 # Every %frame must be matched by an %endframe before EOF. 1414 la_a0 &frame_active 1415 ld_t0,a0,0 1416 la_br &err_frame_not_closed 1417 bnez_t0 1418 eret 1419 1420 ## proc_save_pos_and_ls(a0=s, a1=tok): publish the directive's position into 1421 ## proc_pos / proc_line_start so the directive handler can drive against the 1422 ## source stream. Leaf — preserves the caller's other state. 1423 :proc_save_pos_and_ls 1424 la_t0 &proc_pos 1425 st_a1,t0,0 1426 ld_t1,a0,24 1427 la_t0 &proc_line_start 1428 st_t1,t0,0 1429 ret 1430 1431 ## proc_restore_and_loop: reached only via `b` (sp must be intact). Reads 1432 ## sp,0 = s; copies proc_pos into s->pos, sets s->line_start=1, jumps to 1433 ## proc_loop. Tail of every directive shim above. 1434 :proc_restore_and_loop 1435 ld_a0,sp,0 1436 la_a1 &proc_pos 1437 ld_t0,a1,0 1438 st_t0,a0,16 1439 li_t1 %1 %0 1440 st_t1,a0,24 1441 la_br &proc_loop 1442 b 1443 1444 ## --- %frame / %endframe handlers -------------------------------------------- 1445 ## Single-slot frame state used by %local. push_frame(a0=stream_end) parses 1446 ## `%frame NAME`, stashes name's TextSpan in current_frame_ptr/_len, and 1447 ## sets frame_active = 1. pop_frame(a0=stream_end) clears frame_active. 1448 ## Frames do not nest — a second push without an intervening pop is fatal. 1449 1450 :push_frame 1451 enter_0 1452 1453 # proc_pos += 32 (skip past the `%frame` token). 1454 la_t0 &proc_pos 1455 ld_t1,t0,0 1456 addi_t1,t1,32 1457 st_t1,t0,0 1458 1459 # Skip newlines between `%frame` and NAME. 1460 la_a1 &pf_stream_end 1461 st_a0,a1,0 1462 la_br &proc_skip_newlines 1463 call 1464 la_a1 &pf_stream_end 1465 ld_a0,a1,0 1466 la_t0 &proc_pos 1467 ld_t1,t0,0 1468 1469 # Require a WORD name token within the stream. 1470 la_br &err_bad_frame_header 1471 beq_t1,a0 1472 ld_t2,t1,0 1473 la_br &err_bad_frame_header 1474 bnez_t2 1475 1476 # !frame_active (cannot nest) 1477 la_a1 &frame_active 1478 ld_a2,a1,0 1479 la_br &err_frame_already_active 1480 bnez_a2 1481 1482 # current_frame_ptr = name.text_ptr; current_frame_len = name.text_len 1483 la_a3 ¤t_frame_ptr 1484 ld_t2,t1,8 1485 st_t2,a3,0 1486 la_a3 ¤t_frame_len 1487 ld_t2,t1,16 1488 st_t2,a3,0 1489 1490 # frame_active = 1 1491 li_a2 %1 %0 1492 st_a2,a1,0 1493 1494 # proc_pos += 32 (past the name). 1495 la_t0 &proc_pos 1496 ld_t1,t0,0 1497 addi_t1,t1,32 1498 st_t1,t0,0 1499 1500 # Newlines between `%frame NAME` and the body content are insignificant. 1501 la_br &proc_skip_newlines 1502 call 1503 eret 1504 1505 ## pop_frame(a0 = stream_end): consume `%endframe` followed by a strict 1506 ## TOK_NEWLINE. Fatal if no frame is active. 1507 :pop_frame 1508 enter_0 1509 1510 # frame_active? 1511 la_a1 &frame_active 1512 ld_a2,a1,0 1513 la_br &err_frame_underflow 1514 beqz_a2 1515 li_a2 %0 %0 1516 st_a2,a1,0 1517 1518 # proc_pos += 32 (past the `%endframe` token). 1519 la_t0 &proc_pos 1520 ld_t1,t0,0 1521 addi_t1,t1,32 1522 st_t1,t0,0 1523 1524 # Strict: the token immediately after `%endframe` must be TOK_NEWLINE. 1525 la_br &err_bad_frame_header 1526 beq_t1,a0 1527 ld_t2,t1,0 1528 li_t0 TOK_NEWLINE 1529 la_br &err_bad_frame_header 1530 bne_t2,t0 1531 # Consume the trailing newline only when %endframe sat at line-start; 1532 # mid-line %endframe leaves the newline so it can be emitted. 1533 la_t0 &proc_line_start 1534 ld_a1,t0,0 1535 la_br &pop_frame_done 1536 beqz_a1 1537 addi_t1,t1,32 1538 la_t0 &proc_pos 1539 st_t1,t0,0 1540 :pop_frame_done 1541 eret 1542 1543 ## reset_output(a0 = stream_end): consume a line-start `%reset-output BASE`, 1544 ## clear the buffered output, and install BASE as next_expansion_id. The next 1545 ## macro expansion receives BASE+1. This lets partitioned builds replay a 1546 ## shared macro prelude without duplicating its runtime output or hygiene ids. 1547 :reset_output 1548 enter_16 1549 st_a0,sp,0 1550 1551 # Directive is valid only at line start. 1552 la_t0 &proc_line_start 1553 ld_t1,t0,0 1554 la_br &err_bad_directive 1555 beqz_t1 1556 1557 # Advance to BASE and require a WORD token within the stream. 1558 la_t0 &proc_pos 1559 ld_t1,t0,0 1560 addi_t1,t1,32 1561 st_t1,t0,0 1562 ld_a0,sp,0 1563 la_br &err_bad_directive 1564 beq_t1,a0 1565 ld_t2,t1,0 1566 la_br &err_bad_directive 1567 bnez_t2 1568 1569 # parse_int_token(BASE), accepting 0..INT_MAX-1. 1570 mov_a0,t1 1571 la_br &parse_int_token 1572 call 1573 la_br &err_bad_directive 1574 bltz_a0 1575 li_t0 %2147483646 %0 1576 la_br &err_bad_directive 1577 blt_t0,a0 1578 la_t0 &next_expansion_id 1579 st_a0,t0,0 1580 1581 # Require and consume the newline immediately following BASE. 1582 la_t0 &proc_pos 1583 ld_t1,t0,0 1584 addi_t1,t1,32 1585 st_t1,t0,0 1586 ld_a0,sp,0 1587 la_br &err_bad_directive 1588 beq_t1,a0 1589 ld_t2,t1,0 1590 li_t0 TOK_NEWLINE 1591 la_br &err_bad_directive 1592 bne_t2,t0 1593 addi_t1,t1,32 1594 la_t0 &proc_pos 1595 st_t1,t0,0 1596 1597 # Drop the replayed prelude's output and reset token spacing. 1598 li_t0 %0 %0 1599 la_t1 &output_used 1600 st_t0,t1,0 1601 la_t1 &output_need_space 1602 st_t0,t1,0 1603 eret 1604 1605 ## --- %macro storage: parse header + body into macros[] / macro_body_tokens -- 1606 ## Called at proc_pos == line-start `%macro`. Leaves proc_pos past the %endm 1607 ## line with proc_line_start = 1. Uses BSS scratch (def_m_ptr, def_param_ptr, 1608 ## def_body_line_start) since P1 enter/eret does not save s* registers. 1609 ## 1610 ## Macro record layout (296 bytes, see M1PP_MACRO_RECORD_SIZE): 1611 ## +0 name.ptr (8) 1612 ## +8 name.len (8) 1613 ## +16 param_count (8) 1614 ## +24 params[16].ptr/.len (16 * 16 = 256) 1615 ## +280 body_start (8) -> *Token into macro_body_tokens[] 1616 ## +288 body_end (8) -> exclusive end 1617 1618 ## define_macro(): consume `%macro NAME(params...)\n ... %endm\n`. 1619 :define_macro 1620 enter_0 1621 1622 # macros_end bounds check: if (macros_end == ¯os + MACROS_CAP) fatal 1623 la_a0 ¯os_end 1624 ld_t0,a0,0 1625 la_a1 ¯os_ptr 1626 ld_a1,a1,0 1627 li_a2 M1PP_MACROS_CAP 1628 add_a1,a1,a2 1629 la_br &err_too_many_macros 1630 beq_t0,a1 1631 1632 # def_m_ptr = macros_end (Macro *m = ¯os[macro_count]) 1633 la_a1 &def_m_ptr 1634 st_t0,a1,0 1635 1636 # advance past the %macro token itself 1637 la_a0 &proc_pos 1638 ld_t0,a0,0 1639 addi_t0,t0,32 1640 st_t0,a0,0 1641 1642 # Header is whitespace-insensitive: newlines between the keyword and 1643 # any header element (NAME, '(', params, ',', ')') are skipped. 1644 la_br &proc_skip_newlines 1645 call 1646 1647 # ---- header: name (WORD) ---- 1648 la_a0 &proc_pos 1649 ld_t0,a0,0 1650 la_a1 &source_end 1651 ld_t1,a1,0 1652 la_br &err_bad_macro_header 1653 beq_t0,t1 1654 ld_a1,t0,0 1655 li_a2 TOK_WORD 1656 la_br &err_bad_macro_header 1657 bne_a1,a2 1658 1659 # m->name.ptr = tok->text_ptr; m->name.len = tok->text_len 1660 ld_a2,t0,8 1661 ld_a3,t0,16 1662 la_a0 &def_m_ptr 1663 ld_t2,a0,0 1664 st_a2,t2,0 1665 st_a3,t2,8 1666 1667 # m->param_count = 0; def_param_ptr = m + 24 (first TextSpan slot in macro) 1668 li_a0 %0 %0 1669 st_a0,t2,16 1670 addi_t2,t2,24 1671 la_a0 &def_param_ptr 1672 st_t2,a0,0 1673 1674 # advance past name 1675 addi_t0,t0,32 1676 la_a0 &proc_pos 1677 st_t0,a0,0 1678 1679 la_br &proc_skip_newlines 1680 call 1681 1682 # ---- header: LPAREN ---- 1683 la_a0 &proc_pos 1684 ld_t0,a0,0 1685 la_a1 &source_end 1686 ld_t1,a1,0 1687 la_br &err_bad_macro_header 1688 beq_t0,t1 1689 ld_a1,t0,0 1690 li_a2 TOK_LPAREN 1691 la_br &err_bad_macro_header 1692 bne_a1,a2 1693 1694 # advance past '(' 1695 addi_t0,t0,32 1696 la_a0 &proc_pos 1697 st_t0,a0,0 1698 1699 la_br &proc_skip_newlines 1700 call 1701 1702 # ---- header: optional param list ---- 1703 # if at end -> fall through to RPAREN check (which will fail) 1704 # if next is RPAREN -> skip the param loop 1705 # else enter param loop 1706 la_a0 &proc_pos 1707 ld_t0,a0,0 1708 la_a1 &source_end 1709 ld_t1,a1,0 1710 la_br &def_header_close 1711 beq_t0,t1 1712 ld_a1,t0,0 1713 li_a2 TOK_RPAREN 1714 la_br &def_header_close 1715 beq_a1,a2 1716 1717 :def_param_loop 1718 # reject > 16 params: if (15 < param_count) fail (param_count capped at 16) 1719 la_a0 &def_m_ptr 1720 ld_t2,a0,0 1721 ld_a1,t2,16 1722 li_a2 %15 %0 1723 la_br &err_bad_macro_header 1724 blt_a2,a1 1725 1726 # tok must be in range and WORD 1727 la_a0 &proc_pos 1728 ld_t0,a0,0 1729 la_a1 &source_end 1730 ld_t1,a1,0 1731 la_br &err_bad_macro_header 1732 beq_t0,t1 1733 ld_a1,t0,0 1734 li_a2 TOK_WORD 1735 la_br &err_bad_macro_header 1736 bne_a1,a2 1737 1738 # *def_param_ptr = (tok.text_ptr, tok.text_len); def_param_ptr += 16 1739 ld_a2,t0,8 1740 ld_a3,t0,16 1741 la_a0 &def_param_ptr 1742 ld_t1,a0,0 1743 st_a2,t1,0 1744 st_a3,t1,8 1745 addi_t1,t1,16 1746 st_t1,a0,0 1747 1748 # m->param_count++ 1749 la_a0 &def_m_ptr 1750 ld_t2,a0,0 1751 ld_a1,t2,16 1752 addi_a1,a1,1 1753 st_a1,t2,16 1754 1755 # advance past the param word 1756 addi_t0,t0,32 1757 la_a0 &proc_pos 1758 st_t0,a0,0 1759 1760 # Skip newlines between a param and the following ',' or ')'. 1761 la_br &proc_skip_newlines 1762 call 1763 1764 # if next is COMMA, consume and loop; else break 1765 la_a0 &proc_pos 1766 ld_t0,a0,0 1767 la_a1 &source_end 1768 ld_t1,a1,0 1769 la_br &def_header_close 1770 beq_t0,t1 1771 ld_a1,t0,0 1772 li_a2 TOK_COMMA 1773 la_br &def_header_close 1774 bne_a1,a2 1775 addi_t0,t0,32 1776 la_a0 &proc_pos 1777 st_t0,a0,0 1778 # Skip newlines after ',' so the next param can be on a new line. 1779 la_br &proc_skip_newlines 1780 call 1781 la_br &def_param_loop 1782 b 1783 1784 :def_header_close 1785 # ---- header: RPAREN ---- 1786 la_a0 &proc_pos 1787 ld_t0,a0,0 1788 la_a1 &source_end 1789 ld_t1,a1,0 1790 la_br &err_bad_macro_header 1791 beq_t0,t1 1792 ld_a1,t0,0 1793 li_a2 TOK_RPAREN 1794 la_br &err_bad_macro_header 1795 bne_a1,a2 1796 1797 addi_t0,t0,32 1798 la_a0 &proc_pos 1799 st_t0,a0,0 1800 1801 # ---- header self-terminates at ')'. Newlines between the header line 1802 # ---- and the body content are insignificant — skip them. 1803 la_br &proc_skip_newlines 1804 call 1805 1806 # ---- body: m->body_start = macro_body_end ---- 1807 la_a1 ¯o_body_end 1808 ld_t2,a1,0 1809 la_a0 &def_m_ptr 1810 ld_t1,a0,0 1811 li_a0 M1PP_MACRO_BODY_START_OFF 1812 add_a0,t1,a0 1813 st_t2,a0,0 1814 1815 :def_body_loop 1816 # if proc_pos == source_end: unterminated %macro 1817 la_a0 &proc_pos 1818 ld_t0,a0,0 1819 la_a1 &source_end 1820 ld_t1,a1,0 1821 la_br &err_unterminated_macro 1822 beq_t0,t1 1823 1824 # %endm is recognized anywhere in the body (no line-start gating). If 1825 # (tok.kind == TOK_WORD) and (tok eq "%endm"), break; else copy token. 1826 ld_a1,t0,0 1827 li_a2 TOK_WORD 1828 la_br &def_body_copy 1829 bne_a1,a2 1830 1831 mov_a0,t0 1832 la_a1 &const_endm 1833 li_a2 %5 %0 1834 la_br &tok_eq_const 1835 call 1836 la_br &def_body_copy 1837 beqz_a0 1838 1839 # matched %endm: advance past it and require a TOK_NEWLINE next. 1840 la_br &def_endm_after 1841 b 1842 1843 :def_body_copy 1844 # bounds: if (macro_body_end - macro_body_tokens + 32 > MACRO_BODY_CAP) fail 1845 la_a0 ¯o_body_end 1846 ld_t1,a0,0 1847 la_a2 ¯o_body_tokens_ptr 1848 ld_a2,a2,0 1849 sub_a3,t1,a2 1850 addi_a3,a3,32 1851 li_t2 M1PP_MACRO_BODY_CAP 1852 la_br &err_macro_body_overflow 1853 blt_t2,a3 1854 1855 # === C: cache classification for this body token, so expand_macro_tokens 1856 # === doesn't re-run find_param + is_local_label_token per body token per 1857 # === expansion. Computed once at definition; written to byte-stride 1858 # === parallel arrays indexed by (slot - macro_body_tokens_ptr) / 32. 1859 # offset = (t1 - macro_body_tokens_ptr) / 32 (t1 still = macro_body_end) 1860 la_a0 ¯o_body_tokens_ptr 1861 ld_a0,a0,0 1862 sub_a0,t1,a0 1863 shri_a0,a0,5 1864 la_a1 &def_body_meta_idx 1865 st_a0,a1,0 1866 1867 # macro_body_param_idx[idx] = find_param(def_m_ptr, proc_pos) 1868 la_a0 &def_m_ptr 1869 ld_a0,a0,0 1870 la_a1 &proc_pos 1871 ld_a1,a1,0 1872 la_br &find_param 1873 call 1874 la_a1 &def_body_meta_idx 1875 ld_a1,a1,0 1876 la_a2 ¯o_body_param_idx_ptr 1877 ld_a2,a2,0 1878 add_a2,a2,a1 1879 sb_a0,a2,0 1880 1881 # macro_body_is_local_label[idx] = (kind==WORD && len>=3 && p[0] in ':&' && p[1]=='@') 1882 # Inlined — there's no separate is_local_label_token function in P1; the 1883 # full predicate is replicated here in def-time, replacing the per-expansion 1884 # emt_check_local_label sequence. 1885 la_a0 &proc_pos 1886 ld_t0,a0,0 1887 li_a3 %0 %0 1888 ld_a1,t0,0 1889 la_br &def_body_copy_ill_store 1890 bnez_a1 1891 ld_a1,t0,16 1892 li_a2 %3 %0 1893 la_br &def_body_copy_ill_store 1894 blt_a1,a2 1895 ld_a2,t0,8 1896 lb_a1,a2,0 1897 li_a0 %58 %0 1898 la_br &def_body_copy_ill_at 1899 beq_a1,a0 1900 li_a0 %38 %0 1901 la_br &def_body_copy_ill_store 1902 bne_a1,a0 1903 :def_body_copy_ill_at 1904 lb_a1,a2,1 1905 li_a0 %64 %0 1906 la_br &def_body_copy_ill_store 1907 bne_a1,a0 1908 li_a3 %1 %0 1909 :def_body_copy_ill_store 1910 la_a1 &def_body_meta_idx 1911 ld_a1,a1,0 1912 la_a2 ¯o_body_is_local_label_ptr 1913 ld_a2,a2,0 1914 add_a2,a2,a1 1915 sb_a3,a2,0 1916 # === end C === 1917 1918 # If the body token is TOK_PASTE, set m->has_paste = 1. expand_macro_tokens 1919 # uses this (combined with args_have_paste) to skip paste_pool_range when 1920 # neither the body nor the call's args contribute a `##`. Defaulted to 0 1921 # by BSS init when macros_end advanced into this slot. 1922 la_a0 &proc_pos 1923 ld_t0,a0,0 1924 ld_a1,t0,0 1925 li_a2 TOK_PASTE 1926 la_br &def_body_copy_after_paste_chk 1927 bne_a1,a2 1928 la_a0 &def_m_ptr 1929 ld_a2,a0,0 1930 li_a3 M1PP_MACRO_HAS_PASTE_OFF 1931 add_a2,a2,a3 1932 li_a3 %1 %0 1933 st_a3,a2,0 1934 1935 :def_body_copy_after_paste_chk 1936 # Reload t1 = macro_body_end — clobbered by find_param above. 1937 la_a0 ¯o_body_end 1938 ld_t1,a0,0 1939 1940 # copy 32 bytes from *proc_pos to *macro_body_end (preserves tight at +24) 1941 la_a0 &proc_pos 1942 ld_t0,a0,0 1943 ld_a1,t0,0 1944 st_a1,t1,0 1945 ld_a1,t0,8 1946 st_a1,t1,8 1947 ld_a1,t0,16 1948 st_a1,t1,16 1949 ld_a1,t0,24 1950 st_a1,t1,24 1951 1952 # macro_body_end += 32 1953 addi_t1,t1,32 1954 la_a0 ¯o_body_end 1955 st_t1,a0,0 1956 1957 # proc_pos += 32 1958 addi_t0,t0,32 1959 la_a0 &proc_pos 1960 st_t0,a0,0 1961 la_br &def_body_loop 1962 b 1963 1964 :def_endm_after 1965 # advance past the %endm token; the next token MUST be TOK_NEWLINE. 1966 la_a0 &proc_pos 1967 ld_t0,a0,0 1968 addi_t0,t0,32 1969 st_t0,a0,0 1970 la_a1 &source_end 1971 ld_t1,a1,0 1972 la_br &err_bad_macro_header 1973 beq_t0,t1 1974 ld_a1,t0,0 1975 li_a2 TOK_NEWLINE 1976 la_br &err_bad_macro_header 1977 bne_a1,a2 1978 # Consume the trailing NEWLINE only when the directive started at 1979 # line-start; mid-line directives leave the newline in the stream so 1980 # the outer loop emits it (preserving line layout). 1981 la_a0 &proc_line_start 1982 ld_a1,a0,0 1983 la_br &def_finish 1984 beqz_a1 1985 addi_t0,t0,32 1986 la_a0 &proc_pos 1987 st_t0,a0,0 1988 1989 :def_finish 1990 # m->body_end = macro_body_end 1991 la_a1 ¯o_body_end 1992 ld_t2,a1,0 1993 la_a0 &def_m_ptr 1994 ld_t1,a0,0 1995 li_a0 M1PP_MACRO_BODY_END_OFF 1996 add_a0,t1,a0 1997 st_t2,a0,0 1998 1999 # macros_end += MACRO_RECORD_SIZE 2000 la_a0 ¯os_end 2001 ld_t0,a0,0 2002 li_a1 M1PP_MACRO_RECORD_SIZE 2003 add_t0,t0,a1 2004 st_t0,a0,0 2005 2006 # caller resumes at line start 2007 la_a0 &proc_line_start 2008 li_a1 %1 %0 2009 st_a1,a0,0 2010 eret 2011 2012 ## --- %struct / %enum directive ---------------------------------------------- 2013 ## define_fielded(a0=stride, a1=total_name_ptr, a2=total_name_len). 2014 ## Parses `%struct NAME { f1 f2 ... }` or `%enum NAME { ... }` (caller has 2015 ## already detected %struct / %enum at line start and primed proc_pos to 2016 ## that token). Synthesizes N+1 zero-parameter macros — NAME.field_k -> k*stride 2017 ## and NAME.<total_name> -> N*stride — by appending each {name, body-token} 2018 ## pair into macros[] / macro_body_tokens[]. 2019 ## 2020 ## All working state lives in BSS (df_* slots + df_name_scratch / df_digit_scratch) 2021 ## because df_emit_field calls append_text, which clobbers caller-saved regs. 2022 :define_fielded 2023 enter_0 2024 2025 # Save directive args to BSS. 2026 la_a3 &df_stride 2027 st_a0,a3,0 2028 la_a3 &df_total_name_ptr 2029 st_a1,a3,0 2030 la_a3 &df_total_name_len 2031 st_a2,a3,0 2032 2033 # advance past the %struct / %enum directive token 2034 la_a0 &proc_pos 2035 ld_t0,a0,0 2036 addi_t0,t0,32 2037 st_t0,a0,0 2038 2039 # Header is whitespace-insensitive: newlines between %struct/%enum and 2040 # the NAME (and between NAME and '{') are skipped. 2041 la_br &proc_skip_newlines 2042 call 2043 2044 # ---- header: name (WORD) ---- 2045 la_a0 &proc_pos 2046 ld_t0,a0,0 2047 la_a1 &source_end 2048 ld_t1,a1,0 2049 la_br &err_bad_directive 2050 beq_t0,t1 2051 ld_a1,t0,0 2052 li_a2 TOK_WORD 2053 la_br &err_bad_directive 2054 bne_a1,a2 2055 2056 # df_base_ptr = tok.text_ptr; df_base_len = tok.text_len 2057 ld_a2,t0,8 2058 la_a3 &df_base_ptr 2059 st_a2,a3,0 2060 ld_a2,t0,16 2061 la_a3 &df_base_len 2062 st_a2,a3,0 2063 2064 # advance past the base name 2065 addi_t0,t0,32 2066 la_a0 &proc_pos 2067 st_t0,a0,0 2068 2069 ## skip NEWLINE tokens before '{' (tolerates `%struct NAME\n{ ... }`) 2070 :df_skip_nl_before_lbrace 2071 la_a0 &proc_pos 2072 ld_t0,a0,0 2073 la_a1 &source_end 2074 ld_t1,a1,0 2075 la_br &err_bad_directive 2076 beq_t0,t1 2077 ld_a1,t0,0 2078 li_a2 TOK_NEWLINE 2079 la_br &df_require_lbrace 2080 bne_a1,a2 2081 addi_t0,t0,32 2082 la_a0 &proc_pos 2083 st_t0,a0,0 2084 la_br &df_skip_nl_before_lbrace 2085 b 2086 2087 :df_require_lbrace 2088 # expect LBRACE 2089 li_a2 TOK_LBRACE 2090 la_br &err_bad_directive 2091 bne_a1,a2 2092 2093 # advance past '{' 2094 addi_t0,t0,32 2095 la_a0 &proc_pos 2096 st_t0,a0,0 2097 2098 # df_index = 0 2099 li_a0 %0 %0 2100 la_a1 &df_index 2101 st_a0,a1,0 2102 2103 ## field loop: skip comma/newline separators, stop at '}', else consume a WORD. 2104 :df_field_loop 2105 la_a0 &proc_pos 2106 ld_t0,a0,0 2107 la_a1 &source_end 2108 ld_t1,a1,0 2109 la_br &err_unterminated_directive 2110 beq_t0,t1 2111 ld_a1,t0,0 2112 2113 # separator: COMMA or NEWLINE -> advance and reloop 2114 li_a2 TOK_COMMA 2115 la_br &df_field_skip_sep 2116 beq_a1,a2 2117 li_a2 TOK_NEWLINE 2118 la_br &df_field_skip_sep 2119 beq_a1,a2 2120 2121 # end-of-list marker '}' -> break 2122 li_a2 TOK_RBRACE 2123 la_br &df_fields_done 2124 beq_a1,a2 2125 2126 # else must be a WORD 2127 li_a2 TOK_WORD 2128 la_br &err_bad_directive 2129 bne_a1,a2 2130 2131 # df_suffix_ptr = tok.text_ptr; df_suffix_len = tok.text_len 2132 ld_a2,t0,8 2133 la_a3 &df_suffix_ptr 2134 st_a2,a3,0 2135 ld_a2,t0,16 2136 la_a3 &df_suffix_len 2137 st_a2,a3,0 2138 2139 # df_value = df_index * df_stride 2140 la_a0 &df_index 2141 ld_t1,a0,0 2142 la_a0 &df_stride 2143 ld_t2,a0,0 2144 mul_a0,t1,t2 2145 la_a1 &df_value 2146 st_a0,a1,0 2147 2148 # synthesize the field macro 2149 la_br &df_emit_field 2150 call 2151 2152 # df_index++ 2153 la_a0 &df_index 2154 ld_t1,a0,0 2155 addi_t1,t1,1 2156 st_t1,a0,0 2157 2158 # advance past the field word 2159 la_a0 &proc_pos 2160 ld_t0,a0,0 2161 addi_t0,t0,32 2162 st_t0,a0,0 2163 la_br &df_field_loop 2164 b 2165 2166 :df_field_skip_sep 2167 addi_t0,t0,32 2168 la_a0 &proc_pos 2169 st_t0,a0,0 2170 la_br &df_field_loop 2171 b 2172 2173 :df_fields_done 2174 # advance past '}' 2175 addi_t0,t0,32 2176 la_a0 &proc_pos 2177 st_t0,a0,0 2178 2179 # ---- emit totalizer: df_suffix <- df_total_name; df_value = N * stride ---- 2180 la_a0 &df_total_name_ptr 2181 ld_t0,a0,0 2182 la_a1 &df_suffix_ptr 2183 st_t0,a1,0 2184 la_a0 &df_total_name_len 2185 ld_t0,a0,0 2186 la_a1 &df_suffix_len 2187 st_t0,a1,0 2188 2189 la_a0 &df_index 2190 ld_t1,a0,0 2191 la_a0 &df_stride 2192 ld_t2,a0,0 2193 mul_a0,t1,t2 2194 la_a1 &df_value 2195 st_a0,a1,0 2196 2197 la_br &df_emit_field 2198 call 2199 2200 # Strict: the closing '}' must be immediately followed by TOK_NEWLINE. 2201 # Consume that newline only when the directive started at line-start, 2202 # mirroring %endm / %endframe. 2203 la_a0 &proc_pos 2204 ld_t0,a0,0 2205 la_a1 &source_end 2206 ld_t1,a1,0 2207 la_br &err_bad_directive 2208 beq_t0,t1 2209 ld_a1,t0,0 2210 li_a2 TOK_NEWLINE 2211 la_br &err_bad_directive 2212 bne_a1,a2 2213 la_a0 &proc_line_start 2214 ld_a1,a0,0 2215 la_br &df_finish 2216 beqz_a1 2217 addi_t0,t0,32 2218 la_a0 &proc_pos 2219 st_t0,a0,0 2220 2221 :df_finish 2222 la_a0 &proc_line_start 2223 li_a1 %1 %0 2224 st_a1,a0,0 2225 eret 2226 2227 ## df_emit_field(): read df_base_*, df_suffix_*, df_value from BSS; synthesize 2228 ## one macro record + one body token. Builds the "NAME.field" identifier in 2229 ## df_name_scratch and the decimal body text via df_render_decimal, then 2230 ## copies both into text_buf via append_text so they outlive the scratch. 2231 :df_emit_field 2232 enter_0 2233 2234 # macros_end capacity check 2235 la_a0 ¯os_end 2236 ld_t0,a0,0 2237 la_a1 ¯os_ptr 2238 ld_a1,a1,0 2239 li_a2 M1PP_MACROS_CAP 2240 add_a1,a1,a2 2241 la_br &err_too_many_macros 2242 beq_t0,a1 2243 2244 # ---- assemble "BASE.SUFFIX" into df_name_scratch ---- 2245 # copy base bytes 2246 la_a0 &df_base_ptr 2247 ld_t0,a0,0 2248 la_a0 &df_base_len 2249 ld_t1,a0,0 2250 la_t2 &df_name_scratch_ptr 2251 ld_t2,t2,0 2252 li_a3 %0 %0 2253 :df_ef_base_loop 2254 la_br &df_ef_base_done 2255 beq_a3,t1 2256 add_a0,t0,a3 2257 lb_a0,a0,0 2258 add_a1,t2,a3 2259 sb_a0,a1,0 2260 addi_a3,a3,1 2261 la_br &df_ef_base_loop 2262 b 2263 :df_ef_base_done 2264 # scratch[base_len] = '.' 2265 add_a1,t2,t1 2266 li_a0 %46 %0 2267 sb_a0,a1,0 2268 2269 # copy suffix bytes into scratch[base_len + 1 ..] 2270 la_a0 &df_suffix_ptr 2271 ld_t0,a0,0 2272 la_a0 &df_suffix_len 2273 ld_t1,a0,0 2274 addi_a1,a1,1 2275 li_a3 %0 %0 2276 :df_ef_suffix_loop 2277 la_br &df_ef_suffix_done 2278 beq_a3,t1 2279 add_a0,t0,a3 2280 lb_a0,a0,0 2281 add_a2,a1,a3 2282 sb_a0,a2,0 2283 addi_a3,a3,1 2284 la_br &df_ef_suffix_loop 2285 b 2286 :df_ef_suffix_done 2287 2288 # name_len = base_len + 1 + suffix_len 2289 la_a0 &df_base_len 2290 ld_t0,a0,0 2291 la_a0 &df_suffix_len 2292 ld_t1,a0,0 2293 add_t0,t0,t1 2294 addi_t0,t0,1 2295 la_a1 &df_name_len 2296 st_t0,a1,0 2297 2298 # durable_name = append_text(&df_name_scratch, name_len) 2299 la_a0 &df_name_scratch_ptr 2300 ld_a0,a0,0 2301 mov_a1,t0 2302 la_br &append_text 2303 call 2304 # a0 = durable_name ptr 2305 2306 # m = macros_end; m->name.ptr = durable_name; m->name.len = name_len 2307 la_a1 ¯os_end 2308 ld_t2,a1,0 2309 st_a0,t2,0 2310 la_a0 &df_name_len 2311 ld_a0,a0,0 2312 st_a0,t2,8 2313 2314 # m->param_count = 0 (params[] left zeroed; not read when count == 0) 2315 li_a0 %0 %0 2316 st_a0,t2,16 2317 2318 # render df_value into df_digit_scratch (reverse fill) 2319 la_br &df_render_decimal 2320 call 2321 2322 # durable_digits = append_text(&df_digit_cursor, df_digit_count) 2323 la_a0 &df_digit_cursor 2324 ld_a0,a0,0 2325 la_a1 &df_digit_count 2326 ld_a1,a1,0 2327 la_br &append_text 2328 call 2329 # a0 = durable_digits 2330 2331 # macro_body_end capacity check 2332 la_a1 ¯o_body_end 2333 ld_t0,a1,0 2334 la_a2 ¯o_body_tokens_ptr 2335 ld_a2,a2,0 2336 sub_a3,t0,a2 2337 addi_a3,a3,32 2338 li_t2 M1PP_MACRO_BODY_CAP 2339 la_br &err_macro_body_overflow 2340 blt_t2,a3 2341 2342 # body_tok = TOK_WORD { durable_digits, df_digit_count, tight=0 } 2343 li_a1 TOK_WORD 2344 st_a1,t0,0 2345 st_a0,t0,8 2346 la_a2 &df_digit_count 2347 ld_a2,a2,0 2348 st_a2,t0,16 2349 li_a1 %0 %0 2350 st_a1,t0,24 2351 2352 # m->body_start = macro_body_end (the slot we just wrote) 2353 la_a0 ¯os_end 2354 ld_t2,a0,0 2355 li_a1 M1PP_MACRO_BODY_START_OFF 2356 add_a1,t2,a1 2357 st_t0,a1,0 2358 2359 # macro_body_end += 24 2360 addi_t0,t0,32 2361 la_a1 ¯o_body_end 2362 st_t0,a1,0 2363 2364 # m->body_end = macro_body_end 2365 li_a1 M1PP_MACRO_BODY_END_OFF 2366 add_a1,t2,a1 2367 st_t0,a1,0 2368 2369 # macros_end += MACRO_RECORD_SIZE 2370 li_a0 M1PP_MACRO_RECORD_SIZE 2371 add_t2,t2,a0 2372 la_a1 ¯os_end 2373 st_t2,a1,0 2374 2375 eret 2376 2377 ## df_render_decimal(): reads df_value; writes a reverse-filled decimal 2378 ## rendering into df_digit_scratch[cursor..end) and stores df_digit_count + 2379 ## df_digit_cursor for a subsequent append_text call. Leaf. 2380 :df_render_decimal 2381 la_a0 &df_value 2382 ld_t0,a0,0 2383 la_t1 &df_digit_scratch 2384 li_a2 %24 %0 2385 add_t1,t1,a2 2386 mov_t2,t1 2387 2388 # special-case v == 0 -> single '0' 2389 la_br &df_rd_loop 2390 bnez_t0 2391 addi_t2,t2,neg1 2392 li_a0 %48 %0 2393 sb_a0,t2,0 2394 la_br &df_rd_done 2395 b 2396 :df_rd_loop 2397 la_br &df_rd_done 2398 beqz_t0 2399 mov_a0,t0 2400 li_a1 %10 %0 2401 rem_a2,a0,a1 2402 addi_a2,a2,48 2403 addi_t2,t2,neg1 2404 sb_a2,t2,0 2405 mov_a0,t0 2406 li_a1 %10 %0 2407 div_a0,a0,a1 2408 mov_t0,a0 2409 la_br &df_rd_loop 2410 b 2411 :df_rd_done 2412 la_a1 &df_digit_scratch 2413 li_a2 %24 %0 2414 add_a1,a1,a2 2415 sub_a0,a1,t2 2416 la_a1 &df_digit_count 2417 st_a0,a1,0 2418 la_a1 &df_digit_cursor 2419 st_t2,a1,0 2420 ret 2421 2422 ## ============================================================================ 2423 ## --- Stream stack + expansion-pool lifetime --------------------------------- 2424 ## ============================================================================ 2425 ## process_tokens drives a stack of token streams. The source token array is 2426 ## pushed first; each macro expansion or %select chosen-branch pushes a fresh 2427 ## stream backed by a slice of expand_pool, popping rewinds pool_used to the 2428 ## stream's pool_mark. 2429 2430 ## push_stream_span(a0=start_tok, a1=end_tok, a2=pool_mark) -> void (fatal on overflow) 2431 ## Push Stream { start = pos = a0, end = a1, line_start = 1, pool_mark = a2 } 2432 ## onto streams[]. Bumps stream_top. pool_mark is a byte offset into 2433 ## expand_pool, or -1 for a source-owned stream (pop_stream won't rewind). 2434 ## 2435 ## stream_top is maintained as a byte offset into streams[] (count * 40), 2436 ## matching the running-tail-pointer pattern used by source_end / macros_end. 2437 ## Reads/writes: streams, stream_top. Leaf. 2438 :push_stream_span 2439 # new_top = stream_top + STREAM_SIZE; if (cap < new_top) fatal 2440 la_t0 &stream_top 2441 ld_t1,t0,0 2442 li_t2 M1PP_STREAM_SIZE 2443 add_t2,t1,t2 2444 li_a3 M1PP_STREAM_STACK_CAP 2445 la_br &err_token_overflow 2446 blt_a3,t2 2447 2448 # s = &streams[stream_top] 2449 la_a3 &streams_ptr 2450 ld_a3,a3,0 2451 add_a3,a3,t1 2452 2453 # s->start = a0; s->end = a1; s->pos = a0; s->line_start = 1; s->pool_mark = a2 2454 st_a0,a3,0 2455 st_a1,a3,8 2456 st_a0,a3,16 2457 li_t1 %1 %0 2458 st_t1,a3,24 2459 st_a2,a3,32 2460 2461 # stream_top = new_top 2462 st_t2,t0,0 2463 ret 2464 2465 ## current_stream() -> a0 = &streams[stream_top-1], or 0 if empty. Leaf. 2466 ## stream_top is a byte offset, so &streams[top-1] = streams + stream_top - 40. 2467 ## Reads: streams, stream_top. 2468 :current_stream 2469 la_a0 &stream_top 2470 ld_t0,a0,0 2471 la_br ¤t_stream_empty 2472 beqz_t0 2473 la_a0 &streams_ptr 2474 ld_a0,a0,0 2475 add_a0,a0,t0 2476 li_t1 M1PP_STREAM_SIZE 2477 sub_a0,a0,t1 2478 ret 2479 :current_stream_empty 2480 li_a0 %0 %0 2481 ret 2482 2483 ## pop_stream() -> void. Leaf. 2484 ## Decrement stream_top. If the popped stream's pool_mark >= 0, restore 2485 ## pool_used = pool_mark (reclaim the expansion-pool space it used). 2486 ## Reads/writes: streams, stream_top, pool_used. 2487 :pop_stream 2488 la_a0 &stream_top 2489 ld_t0,a0,0 2490 la_br &pop_stream_done 2491 beqz_t0 2492 li_t1 M1PP_STREAM_SIZE 2493 sub_t0,t0,t1 2494 st_t0,a0,0 2495 2496 # mark = popped->pool_mark 2497 la_a1 &streams_ptr 2498 ld_a1,a1,0 2499 add_a1,a1,t0 2500 ld_t0,a1,32 2501 2502 # if (mark < 0) skip; else pool_used = mark 2503 la_br &pop_stream_done 2504 bltz_t0 2505 la_a1 &pool_used 2506 st_t0,a1,0 2507 :pop_stream_done 2508 ret 2509 2510 ## copy_span_to_pool(a0=start_tok, a1=end_tok) -> void (fatal on pool overflow) 2511 ## Append each 32-byte Token in [start, end) to expand_pool at pool_used, 2512 ## advancing pool_used accordingly. Preserves tight bit at +24. 2513 ## Reads/writes: expand_pool, pool_used. Leaf. 2514 :copy_span_to_pool 2515 :cstp_loop 2516 # if (start == end) done 2517 la_br &cstp_done 2518 beq_a0,a1 2519 2520 # bounds: pool_used + 32 must fit in EXPAND_CAP 2521 la_a2 &pool_used 2522 ld_t0,a2,0 2523 addi_t1,t0,32 2524 li_t2 M1PP_EXPAND_CAP 2525 la_br &err_token_overflow 2526 blt_t2,t1 2527 2528 # dst = &expand_pool[pool_used] 2529 la_a3 &expand_pool_ptr 2530 ld_a3,a3,0 2531 add_a3,a3,t0 2532 2533 # copy 32 bytes (4 × u64) 2534 ld_t1,a0,0 2535 st_t1,a3,0 2536 ld_t1,a0,8 2537 st_t1,a3,8 2538 ld_t1,a0,16 2539 st_t1,a3,16 2540 ld_t1,a0,24 2541 st_t1,a3,24 2542 2543 # pool_used += 32; start += 32 2544 addi_t0,t0,32 2545 st_t0,a2,0 2546 addi_a0,a0,32 2547 la_br &cstp_loop 2548 b 2549 :cstp_done 2550 ret 2551 2552 ## push_pool_stream_from_mark(a0=mark) -> void (fatal on overflow) 2553 ## If pool_used == mark (empty expansion), do nothing and return. 2554 ## Otherwise push_stream_span(expand_pool+mark, expand_pool+pool_used, mark). 2555 ## Reads/writes: expand_pool, pool_used, streams, stream_top. Non-leaf: 2556 ## needs a frame so the call to push_stream_span doesn't clobber LR. 2557 :push_pool_stream_from_mark 2558 enter_0 2559 # if (pool_used == mark) return 2560 la_a1 &pool_used 2561 ld_t0,a1,0 2562 la_br &ppsfm_done 2563 beq_t0,a0 2564 2565 # push_stream_span(expand_pool+mark, expand_pool+pool_used, mark) 2566 la_a2 &expand_pool_ptr 2567 ld_a2,a2,0 2568 mov_t1,a0 2569 add_a0,a2,a0 2570 add_a1,a2,t0 2571 mov_a2,t1 2572 la_br &push_stream_span 2573 call 2574 :ppsfm_done 2575 eret 2576 2577 ## ============================================================================ 2578 ## --- Argument parsing ------------------------------------------------------- 2579 ## ============================================================================ 2580 2581 ## parse_args(a0=lparen_tok, a1=limit_tok) -> void (fatal on unterminated/overflow) 2582 ## Scan tokens from lparen+1 up to limit, tracking paren depth. At depth 1 each 2583 ## TOK_COMMA ends one arg and starts the next; the matching TOK_RPAREN at 2584 ## depth 0 ends the last arg. An empty `()` is arg_count = 0. 2585 ## 2586 ## Writes globals: 2587 ## arg_starts[i] = first token of arg i 2588 ## arg_ends[i] = one past last token of arg i 2589 ## arg_count = number of args (0..16) 2590 ## call_end_pos = one past the closing RPAREN 2591 ## 2592 ## Fatal on: > 16 args, reaching limit without matching RPAREN. 2593 :parse_args 2594 # tok = lparen + 1; arg_start = tok; depth = 1; arg_index = 0; brace_depth = 0 2595 addi_a0,a0,32 2596 la_a2 &pa_pos 2597 st_a0,a2,0 2598 la_a2 &pa_arg_start 2599 st_a0,a2,0 2600 la_a2 &pa_limit 2601 st_a1,a2,0 2602 li_a2 %1 %0 2603 la_a3 &pa_depth 2604 st_a2,a3,0 2605 li_a2 %0 %0 2606 la_a3 &pa_arg_index 2607 st_a2,a3,0 2608 li_a2 %0 %0 2609 la_a3 &pa_brace_depth 2610 st_a2,a3,0 2611 2612 # args_have_paste = 0 — set to 1 below if any TOK_PASTE appears in the 2613 # call's argument span. expand_macro_tokens snapshots this right after 2614 # parse_args returns; bare arg copies preserve embedded ## tokens, and 2615 # the snapshot tells us whether we still have to run paste_pool_range 2616 # even when the body itself contains no ##. 2617 li_a2 %0 %0 2618 la_a3 &args_have_paste 2619 st_a2,a3,0 2620 2621 :pa_loop 2622 # if (tok >= limit) fatal unterminated 2623 la_a0 &pa_pos 2624 ld_t0,a0,0 2625 la_a1 &pa_limit 2626 ld_t1,a1,0 2627 la_br &err_unterminated_macro 2628 beq_t0,t1 2629 2630 # kind = tok->kind 2631 ld_a2,t0,0 2632 2633 # if (kind == TOK_PASTE) { args_have_paste = 1; fall through to default-advance } 2634 li_a3 TOK_PASTE 2635 la_br &pa_kind_check 2636 bne_a2,a3 2637 li_a3 %1 %0 2638 la_a0 &args_have_paste 2639 st_a3,a0,0 2640 2641 :pa_kind_check 2642 # if (kind == TOK_LPAREN) { depth++; tok++; loop } 2643 li_a3 TOK_LPAREN 2644 la_br &pa_lparen 2645 beq_a2,a3 2646 li_a3 TOK_RPAREN 2647 la_br &pa_rparen 2648 beq_a2,a3 2649 li_a3 TOK_COMMA 2650 la_br &pa_maybe_comma 2651 beq_a2,a3 2652 li_a3 TOK_LBRACE 2653 la_br &pa_lbrace 2654 beq_a2,a3 2655 li_a3 TOK_RBRACE 2656 la_br &pa_rbrace 2657 beq_a2,a3 2658 2659 # default: tok++ 2660 addi_t0,t0,32 2661 la_a0 &pa_pos 2662 st_t0,a0,0 2663 la_br &pa_loop 2664 b 2665 2666 :pa_lparen 2667 la_a0 &pa_depth 2668 ld_t1,a0,0 2669 addi_t1,t1,1 2670 st_t1,a0,0 2671 addi_t0,t0,32 2672 la_a0 &pa_pos 2673 st_t0,a0,0 2674 la_br &pa_loop 2675 b 2676 2677 :pa_rparen 2678 # depth-- 2679 la_a0 &pa_depth 2680 ld_t1,a0,0 2681 addi_t1,t1,neg1 2682 st_t1,a0,0 2683 # if (depth != 0) tok++; loop 2684 la_br &pa_rparen_close 2685 beqz_t1 2686 addi_t0,t0,32 2687 la_a0 &pa_pos 2688 st_t0,a0,0 2689 la_br &pa_loop 2690 b 2691 2692 :pa_rparen_close 2693 # depth == 0: if brace_depth != 0 -> unbalanced braces 2694 la_a0 &pa_brace_depth 2695 ld_t1,a0,0 2696 la_br &err_unbalanced_braces 2697 bnez_t1 2698 # close out the call. 2699 # arg_start (BSS), arg_index (BSS), tok = current pos. 2700 la_a0 &pa_arg_start 2701 ld_a1,a0,0 2702 la_a0 &pa_arg_index 2703 ld_a2,a0,0 2704 2705 # if (arg_start == tok && arg_index == 0) -> arg_count = 0 2706 la_br &pa_close_with_arg 2707 bne_a1,t0 2708 la_br &pa_close_with_arg 2709 bnez_a2 2710 2711 # empty (): arg_count = 0 2712 li_a3 %0 %0 2713 la_a0 &arg_count 2714 st_a3,a0,0 2715 la_br &pa_finish 2716 b 2717 2718 :pa_close_with_arg 2719 # if (arg_index >= 16) fatal: branch to ok only if arg_index < 16 2720 li_a3 M1PP_MAX_PARAMS 2721 la_br &pa_close_with_arg_ok 2722 blt_a2,a3 2723 la_br &err_bad_macro_header 2724 b 2725 :pa_close_with_arg_ok 2726 # arg_starts[arg_index] = arg_start; arg_ends[arg_index] = tok 2727 la_a3 &arg_starts_ptr 2728 ld_a3,a3,0 2729 shli_t1,a2,3 2730 add_a3,a3,t1 2731 st_a1,a3,0 2732 la_a3 &arg_ends_ptr 2733 ld_a3,a3,0 2734 add_a3,a3,t1 2735 st_t0,a3,0 2736 # arg_count = arg_index + 1 2737 addi_a2,a2,1 2738 la_a0 &arg_count 2739 st_a2,a0,0 2740 2741 :pa_finish 2742 # call_end_pos = tok + 24 2743 addi_t0,t0,32 2744 la_a0 &call_end_pos 2745 st_t0,a0,0 2746 ret 2747 2748 :pa_maybe_comma 2749 # only split at depth == 1 2750 la_a0 &pa_depth 2751 ld_t1,a0,0 2752 li_a3 %1 %0 2753 la_br &pa_default_advance 2754 bne_t1,a3 2755 # and only when brace_depth == 0 2756 la_a0 &pa_brace_depth 2757 ld_t1,a0,0 2758 la_br &pa_default_advance 2759 bnez_t1 2760 2761 # depth == 1 && brace_depth == 0 split: append (arg_start, tok) at arg_index 2762 la_a0 &pa_arg_index 2763 ld_a2,a0,0 2764 li_a3 M1PP_MAX_PARAMS 2765 la_br &pa_comma_ok 2766 blt_a2,a3 2767 la_br &err_bad_macro_header 2768 b 2769 :pa_comma_ok 2770 la_a0 &pa_arg_start 2771 ld_a1,a0,0 2772 la_a3 &arg_starts_ptr 2773 ld_a3,a3,0 2774 shli_t1,a2,3 2775 add_a3,a3,t1 2776 st_a1,a3,0 2777 la_a3 &arg_ends_ptr 2778 ld_a3,a3,0 2779 add_a3,a3,t1 2780 st_t0,a3,0 2781 # arg_index++ 2782 addi_a2,a2,1 2783 la_a0 &pa_arg_index 2784 st_a2,a0,0 2785 # arg_start = tok + 24 2786 addi_t0,t0,32 2787 la_a0 &pa_arg_start 2788 st_t0,a0,0 2789 la_a0 &pa_pos 2790 st_t0,a0,0 2791 la_br &pa_loop 2792 b 2793 2794 :pa_default_advance 2795 # comma at depth != 1: just advance 2796 addi_t0,t0,32 2797 la_a0 &pa_pos 2798 st_t0,a0,0 2799 la_br &pa_loop 2800 b 2801 2802 :pa_lbrace 2803 # brace_depth++; tok++ 2804 la_a0 &pa_brace_depth 2805 ld_t1,a0,0 2806 addi_t1,t1,1 2807 st_t1,a0,0 2808 addi_t0,t0,32 2809 la_a0 &pa_pos 2810 st_t0,a0,0 2811 la_br &pa_loop 2812 b 2813 2814 :pa_rbrace 2815 # if (brace_depth <= 0) fatal unbalanced braces 2816 la_a0 &pa_brace_depth 2817 ld_t1,a0,0 2818 la_br &err_unbalanced_braces 2819 beqz_t1 2820 # brace_depth--; tok++ 2821 addi_t1,t1,neg1 2822 st_t1,a0,0 2823 addi_t0,t0,32 2824 la_a0 &pa_pos 2825 st_t0,a0,0 2826 la_br &pa_loop 2827 b 2828 2829 ## ============================================================================ 2830 ## --- Macro lookup + call expansion ------------------------------------------ 2831 ## ============================================================================ 2832 2833 ## find_macro(a0=tok) -> a0 = Macro* or 0. Leaf. 2834 ## Non-zero only if tok is TOK_WORD, text.len >= 2, text[0] == '%', and 2835 ## (text+1, len-1) equals macros[i].name for some i. First match wins. 2836 ## Reads: macros, macros_end. 2837 :find_macro 2838 # if (tok.kind != TOK_WORD) return 0 2839 ld_a1,a0,0 2840 li_a2 TOK_WORD 2841 la_br &find_macro_zero 2842 bne_a1,a2 2843 2844 # if (tok.text.len < 2) return 0 2845 ld_a2,a0,16 2846 li_a3 %2 %0 2847 la_br &find_macro_zero 2848 blt_a2,a3 2849 2850 # if (tok.text[0] != '%') return 0 2851 ld_a1,a0,8 2852 lb_a3,a1,0 2853 li_t0 %37 %0 2854 la_br &find_macro_zero 2855 bne_a3,t0 2856 2857 # name_ptr = tok.text + 1; name_len = tok.text.len - 1 2858 addi_a1,a1,1 2859 addi_a2,a2,neg1 2860 2861 # m = ¯os[0]; m_end = macros_end 2862 la_a3 ¯os_ptr 2863 ld_a3,a3,0 2864 la_t0 ¯os_end 2865 ld_t0,t0,0 2866 2867 :find_macro_loop 2868 # if (m == macros_end) return 0 2869 la_br &find_macro_zero 2870 beq_a3,t0 2871 2872 # if (m->name.len != name_len) advance 2873 ld_t1,a3,8 2874 la_br &find_macro_next 2875 bne_t1,a2 2876 2877 # byte-compare m->name.ptr vs name_ptr for name_len bytes 2878 ld_t1,a3,0 2879 li_t2 %0 %0 2880 :find_macro_cmp 2881 la_br &find_macro_match 2882 beq_t2,a2 2883 add_a0,t1,t2 2884 lb_a0,a0,0 2885 add_t0,a1,t2 2886 lb_t0,t0,0 2887 la_br &find_macro_next 2888 bne_a0,t0 2889 addi_t2,t2,1 2890 la_br &find_macro_cmp 2891 b 2892 2893 :find_macro_next 2894 # m += M1PP_MACRO_RECORD_SIZE 2895 li_t1 M1PP_MACRO_RECORD_SIZE 2896 add_a3,a3,t1 2897 # reload macros_end (clobbered by the comparisons) 2898 la_t0 ¯os_end 2899 ld_t0,t0,0 2900 la_br &find_macro_loop 2901 b 2902 2903 :find_macro_match 2904 mov_a0,a3 2905 ret 2906 2907 :find_macro_zero 2908 li_a0 %0 %0 2909 ret 2910 2911 ## find_param(a0=macro_ptr, a1=tok) -> a0 = (index+1) or 0. Leaf. 2912 ## Linear search over macro->params[0..param_count). Non-WORD tok -> 0, so 2913 ## callers can test the return against zero without pre-filtering. 2914 :find_param 2915 # if (tok.kind != TOK_WORD) return 0 2916 ld_a2,a1,0 2917 li_a3 TOK_WORD 2918 la_br &find_param_zero 2919 bne_a2,a3 2920 2921 # param_count = macro->param_count 2922 ld_a2,a0,16 2923 la_br &find_param_zero 2924 beqz_a2 2925 2926 # Spill bases into BSS so the cmp loop has free temp regs. 2927 # fp_macro = macro_ptr 2928 # fp_tok = tok ptr 2929 # fp_pcount = param_count 2930 # fp_idx = current param index 2931 la_a3 &fp_macro 2932 st_a0,a3,0 2933 la_a3 &fp_tok 2934 st_a1,a3,0 2935 la_a3 &fp_pcount 2936 st_a2,a3,0 2937 li_a3 %0 %0 2938 la_a0 &fp_idx 2939 st_a3,a0,0 2940 2941 :find_param_outer 2942 # idx, pcount 2943 la_a0 &fp_idx 2944 ld_t0,a0,0 2945 la_a0 &fp_pcount 2946 ld_a1,a0,0 2947 la_br &find_param_zero 2948 beq_t0,a1 2949 2950 # param_ptr = fp_macro + 24 + idx * 16 (macro record params start at +24) 2951 la_a0 &fp_macro 2952 ld_a2,a0,0 2953 addi_a2,a2,24 2954 shli_a3,t0,4 2955 add_a2,a2,a3 2956 2957 # tok ptr 2958 la_a0 &fp_tok 2959 ld_a3,a0,0 2960 2961 # Compare lengths. 2962 ld_t1,a2,8 2963 ld_t2,a3,16 2964 la_br &find_param_next 2965 bne_t1,t2 2966 2967 # Lengths match. Byte-compare param.ptr vs tok.text.ptr for t1 bytes. 2968 # After this point we either return or restart the outer loop, so 2969 # all caller-saved regs are free. 2970 ld_a0,a2,0 2971 ld_a1,a3,8 2972 li_t0 %0 %0 2973 :find_param_cmp 2974 la_br &find_param_match 2975 beq_t0,t1 2976 add_t2,a0,t0 2977 lb_t2,t2,0 2978 add_a2,a1,t0 2979 lb_a2,a2,0 2980 la_br &find_param_next 2981 bne_t2,a2 2982 addi_t0,t0,1 2983 la_br &find_param_cmp 2984 b 2985 2986 :find_param_next 2987 # idx++ 2988 la_a0 &fp_idx 2989 ld_t0,a0,0 2990 addi_t0,t0,1 2991 st_t0,a0,0 2992 la_br &find_param_outer 2993 b 2994 2995 :find_param_match 2996 # return idx + 1 2997 la_a0 &fp_idx 2998 ld_a0,a0,0 2999 addi_a0,a0,1 3000 ret 3001 3002 :find_param_zero 3003 li_a0 %0 %0 3004 ret 3005 3006 ## arg_is_braced(a0=start, a1=end) -> a0 = 1 if the span wraps in a matching 3007 ## outer { ... } pair (outer RBRACE is the same-level mate of the leading 3008 ## LBRACE), else 0. Leaf. 3009 :arg_is_braced 3010 # if (end - start < 2 tokens = 64 bytes) return 0 3011 sub_a2,a1,a0 3012 li_a3 %64 %0 3013 la_br &aib_zero 3014 blt_a2,a3 3015 3016 # if (start->kind != TOK_LBRACE) return 0 3017 ld_a2,a0,0 3018 li_a3 TOK_LBRACE 3019 la_br &aib_zero 3020 bne_a2,a3 3021 3022 # if ((end - 24)->kind != TOK_RBRACE) return 0 3023 addi_t0,a1,neg32 3024 ld_a2,t0,0 3025 li_a3 TOK_RBRACE 3026 la_br &aib_zero 3027 bne_a2,a3 3028 3029 # walk tokens tracking depth; if depth hits 0 before reaching end-24, 3030 # the leading LBRACE doesn't match the trailing RBRACE -> return 0. 3031 # t0 = tok, t1 = depth, t2 = last_tok = end - 24 3032 mov_t0,a0 3033 li_t1 %0 %0 3034 addi_t2,a1,neg32 3035 :aib_loop 3036 la_br &aib_done 3037 beq_t0,a1 3038 ld_a2,t0,0 3039 li_a3 TOK_LBRACE 3040 la_br &aib_incr 3041 beq_a2,a3 3042 li_a3 TOK_RBRACE 3043 la_br &aib_decr 3044 beq_a2,a3 3045 # non-brace: advance 3046 addi_t0,t0,32 3047 la_br &aib_loop 3048 b 3049 :aib_incr 3050 addi_t1,t1,1 3051 addi_t0,t0,32 3052 la_br &aib_loop 3053 b 3054 :aib_decr 3055 addi_t1,t1,neg1 3056 # if (depth == 0 && tok != end - 24) -> not wrapping 3057 la_br &aib_decr_skip 3058 bnez_t1 3059 la_br &aib_zero 3060 bne_t0,t2 3061 :aib_decr_skip 3062 addi_t0,t0,32 3063 la_br &aib_loop 3064 b 3065 :aib_done 3066 # return (depth == 0) ? 1 : 0 3067 la_br &aib_zero 3068 bnez_t1 3069 li_a0 %1 %0 3070 ret 3071 :aib_zero 3072 li_a0 %0 %0 3073 ret 3074 3075 ## copy_arg_tokens_to_pool(a0=arg_start, a1=arg_end) -> void (fatal if empty) 3076 ## Non-leaf (calls copy_span_to_pool). Empty arg is an error. 3077 ## If the span is wrapped in a matching outer { ... } pair, strip the outer 3078 ## braces before copying; an empty inner span is a no-op. 3079 :copy_arg_tokens_to_pool 3080 enter_16 3081 # if (arg_start == arg_end) fatal 3082 la_br &err_bad_macro_header 3083 beq_a0,a1 3084 # spill a0/a1 so arg_is_braced can clobber regs 3085 st_a0,sp,0 3086 st_a1,sp,8 3087 la_br &arg_is_braced 3088 call 3089 la_br &catp_plain 3090 beqz_a0 3091 # braced: strip outer braces (start+24, end-24) 3092 ld_a0,sp,0 3093 ld_a1,sp,8 3094 addi_a0,a0,32 3095 addi_a1,a1,neg32 3096 la_br &catp_done 3097 beq_a0,a1 3098 la_br ©_span_to_pool 3099 call 3100 la_br &catp_done 3101 b 3102 :catp_plain 3103 ld_a0,sp,0 3104 ld_a1,sp,8 3105 la_br ©_span_to_pool 3106 call 3107 :catp_done 3108 eret 3109 3110 ## copy_paste_arg_to_pool(a0=arg_start, a1=arg_end) -> void (fatal unless len 1) 3111 ## Enforces the single-token-argument rule for params adjacent to ##. 3112 ## Braced args are rejected — pasting onto a block is nonsense. 3113 :copy_paste_arg_to_pool 3114 enter_16 3115 # spill a0/a1 for the arg_is_braced call 3116 st_a0,sp,0 3117 st_a1,sp,8 3118 la_br &arg_is_braced 3119 call 3120 la_br &err_bad_macro_header 3121 bnez_a0 3122 ld_a0,sp,0 3123 ld_a1,sp,8 3124 # if ((arg_end - arg_start) != 24) fatal 3125 sub_a2,a1,a0 3126 li_a3 M1PP_TOK_SIZE 3127 la_br &err_bad_macro_header 3128 bne_a2,a3 3129 la_br ©_span_to_pool 3130 call 3131 eret 3132 3133 ## expand_macro_tokens(a0=call_tok, a1=limit, a2=macro_ptr) -> void (fatal on bad) 3134 ## Requires call_tok+1 is TOK_LPAREN. Runs parse_args(call_tok+1, limit), 3135 ## verifies arg_count == macro->param_count, walks macro body, substituting 3136 ## each param token via copy_arg_tokens_to_pool (or copy_paste_arg_to_pool 3137 ## when adjacent to ##), copying other body tokens as-is, then runs 3138 ## paste_pool_range over the newly-written slice. 3139 ## 3140 ## Outputs via globals (callers must snapshot before any nested call that 3141 ## could overwrite them): 3142 ## emt_after_pos = token one past the matching ')' (= call_end_pos) 3143 ## emt_mark = pool_used as of entry (start of expansion slice) 3144 ## 3145 :expand_macro_tokens 3146 enter_0 3147 3148 # Snapshot inputs into BSS (find_param/copy_*/paste_pool_range clobber regs). 3149 la_a3 &emt_call_tok 3150 st_a0,a3,0 3151 la_a3 &emt_limit 3152 st_a1,a3,0 3153 la_a3 &emt_macro 3154 st_a2,a3,0 3155 3156 # lparen = call_tok + 24 3157 addi_a0,a0,32 3158 3159 # Branch split for paren-less 0-arg calls: 3160 # if lparen < limit AND lparen->kind == TOK_LPAREN: parse_args as usual. 3161 # else if macro->param_count == 0: synthesize empty arg list, no parse_args. 3162 # else: fatal "bad macro call". 3163 3164 # if (lparen >= limit) goto emt_try_zero_arg 3165 la_br &emt_try_zero_arg 3166 beq_a0,a1 3167 la_br &emt_try_zero_arg 3168 blt_a1,a0 3169 3170 # if (lparen->kind != TOK_LPAREN) goto emt_try_zero_arg 3171 ld_a2,a0,0 3172 li_a3 TOK_LPAREN 3173 la_br &emt_try_zero_arg 3174 bne_a2,a3 3175 3176 # if (!lparen->tight) goto emt_try_zero_arg — `%FOO ( ... )` with a space 3177 # is a paren-less zero-arg call followed by a literal `(`. 3178 ld_a2,a0,24 3179 la_br &emt_try_zero_arg 3180 beqz_a2 3181 3182 # parse_args(lparen, limit) 3183 # a0 already lparen; a1 already limit 3184 la_br &parse_args 3185 call 3186 3187 # Snapshot args_have_paste -> emt_saw_arg_paste BEFORE the body loop 3188 # potentially runs nested expansions that would clobber the global. This 3189 # snapshot is OR'd with macro->has_paste at emt_done to decide whether 3190 # to run paste_pool_range. 3191 la_a0 &args_have_paste 3192 ld_t0,a0,0 3193 la_a1 &emt_saw_arg_paste 3194 st_t0,a1,0 3195 3196 # Check arg_count == macro->param_count 3197 la_a0 &arg_count 3198 ld_t0,a0,0 3199 la_a0 &emt_macro 3200 ld_t1,a0,0 3201 ld_t1,t1,16 3202 la_br &err_bad_macro_header 3203 bne_t0,t1 3204 3205 # expansion_id = ++next_expansion_id (monotonic; used by local-label 3206 # rewriting in the body-copy path to rename :@name / &@name tokens). 3207 la_a0 &next_expansion_id 3208 ld_t0,a0,0 3209 addi_t0,t0,1 3210 st_t0,a0,0 3211 la_a1 &emt_expansion_id 3212 st_t0,a1,0 3213 3214 # Snapshot call_end_pos -> emt_after_pos before the body walk, so 3215 # nothing in the substitution loop can clobber the resume position. 3216 la_a0 &call_end_pos 3217 ld_t0,a0,0 3218 la_a1 &emt_after_pos 3219 st_t0,a1,0 3220 la_br &emt_after_arg_setup 3221 b 3222 3223 :emt_try_zero_arg 3224 # No trailing LPAREN. Allowed only if macro->param_count == 0. 3225 la_a0 &emt_macro 3226 ld_t1,a0,0 3227 ld_t1,t1,16 3228 la_br &err_bad_macro_header 3229 bnez_t1 3230 3231 # No parse_args ran in this branch; args_have_paste from a stale earlier 3232 # call MUST NOT leak into emt_saw_arg_paste. Force it to 0 so emt_done's 3233 # paste-gate uses only macro->has_paste here. 3234 li_t0 %0 %0 3235 la_a1 &emt_saw_arg_paste 3236 st_t0,a1,0 3237 3238 # arg_count = 0 3239 la_a0 &arg_count 3240 li_t0 %0 %0 3241 st_t0,a0,0 3242 3243 # emt_after_pos = call_tok + 24 3244 la_a0 &emt_call_tok 3245 ld_t0,a0,0 3246 addi_t0,t0,32 3247 la_a1 &emt_after_pos 3248 st_t0,a1,0 3249 3250 :emt_after_arg_setup 3251 3252 # mark = pool_used; emt_mark = mark 3253 la_a0 &pool_used 3254 ld_t0,a0,0 3255 la_a1 &emt_mark 3256 st_t0,a1,0 3257 3258 # body_pos = macro->body_start; body_end = macro->body_end 3259 la_a0 &emt_macro 3260 ld_t1,a0,0 3261 li_a2 M1PP_MACRO_BODY_START_OFF 3262 add_a3,t1,a2 3263 ld_a3,a3,0 3264 la_a0 &emt_body_pos 3265 st_a3,a0,0 3266 la_a0 &emt_body_start 3267 st_a3,a0,0 3268 li_a2 M1PP_MACRO_BODY_END_OFF 3269 add_a3,t1,a2 3270 ld_a3,a3,0 3271 la_a0 &emt_body_end 3272 st_a3,a0,0 3273 3274 :emt_loop 3275 # if (body_pos == body_end) break 3276 la_a0 &emt_body_pos 3277 ld_t0,a0,0 3278 la_a1 &emt_body_end 3279 ld_t1,a1,0 3280 la_br &emt_done 3281 beq_t0,t1 3282 3283 # Cached param_idx = macro_body_param_idx[(body_pos - macro_body_tokens) / 32]. 3284 # Set at %macro define time so the body loop never has to call find_param. 3285 la_a1 ¯o_body_tokens_ptr 3286 ld_a1,a1,0 3287 sub_a0,t0,a1 3288 shri_a0,a0,5 3289 la_a1 ¯o_body_param_idx_ptr 3290 ld_a1,a1,0 3291 add_a1,a1,a0 3292 lb_a0,a1,0 3293 # Spill for emt_do_substitute_paste / _plain (no need to re-derive). 3294 la_a1 &emt_cached_param_idx 3295 st_a0,a1,0 3296 3297 # if (param_idx == 0) body-native token: check for local-label rewrite, 3298 # else fall through to substitute logic. 3299 la_br &emt_check_local_label 3300 beqz_a0 3301 3302 # param_idx != 0: substitute. The emt_do_substitute_* paths read 3303 # emt_cached_param_idx (no re-call to find_param). 3304 3305 # Reload body_pos for the pasted-classification loads. 3306 la_a0 &emt_body_pos 3307 ld_t0,a0,0 3308 3309 # Compute pasted = (body_pos > body_start AND (body_pos - 24)->kind == TOK_PASTE) 3310 # OR (body_pos + 24 < body_end AND (body_pos + 24)->kind == TOK_PASTE) 3311 la_a1 &emt_body_start 3312 ld_t1,a1,0 3313 3314 # Branch to emt_check_after if body_pos == body_start 3315 la_br &emt_check_after 3316 beq_t0,t1 3317 3318 # prev_kind = (body_pos - 24)->kind 3319 addi_t2,t0,neg32 3320 ld_a2,t2,0 3321 li_a3 TOK_PASTE 3322 la_br &emt_pasted 3323 beq_a2,a3 3324 3325 :emt_check_after 3326 # next_pos = body_pos + 24; if (next_pos >= body_end) skip 3327 addi_t2,t0,32 3328 la_a1 &emt_body_end 3329 ld_a3,a1,0 3330 # if (next_pos == body_end) -> not pasted (need next_pos < body_end) 3331 la_br &emt_not_pasted 3332 beq_t2,a3 3333 # next_kind = next_pos->kind 3334 ld_a2,t2,0 3335 li_a3 TOK_PASTE 3336 la_br &emt_pasted 3337 beq_a2,a3 3338 la_br &emt_not_pasted 3339 b 3340 3341 :emt_pasted 3342 # body_pos is a param adjacent to ##: substitute one-token arg. 3343 la_br &emt_do_substitute_paste 3344 b 3345 3346 :emt_not_pasted 3347 # body_pos is a param NOT adjacent to ##: substitute arg span. 3348 la_br &emt_do_substitute_plain 3349 b 3350 3351 ## emt_check_local_label: read the cached macro_body_is_local_label[] 3352 ## flag (set at %macro define time). 0 -> emt_copy_literal copies the 3353 ## body token verbatim; 1 -> falls through to emt_rewrite_local_label. 3354 ## Replaces the per-expansion ':@' / '&@' / '@' predicate that used to 3355 ## live inline here. 3356 :emt_check_local_label 3357 la_a0 &emt_body_pos 3358 ld_t0,a0,0 3359 la_a1 ¯o_body_tokens_ptr 3360 ld_a1,a1,0 3361 sub_a0,t0,a1 3362 shri_a0,a0,5 3363 la_a1 ¯o_body_is_local_label_ptr 3364 ld_a1,a1,0 3365 add_a1,a1,a0 3366 lb_a0,a1,0 3367 la_br &emt_copy_literal 3368 beqz_a0 3369 # Cached flag is 1: fall through to rewrite. 3370 3371 ## emt_rewrite_local_label: build "sigil + tail + __ + decimal(NN)" in 3372 ## local_label_scratch, stash it into text_buf via append_text, and push 3373 ## a TOK_WORD to expand_pool. 3374 :emt_rewrite_local_label 3375 # Stash body_tok text_ptr / text_len into BSS so they survive 3376 # function calls (append_text is non-leaf via its arena bump). 3377 la_a0 &emt_body_pos 3378 ld_t0,a0,0 3379 ld_a1,t0,8 3380 la_a2 &ll_src_ptr 3381 st_a1,a2,0 3382 ld_a1,t0,16 3383 la_a2 &ll_src_len 3384 st_a1,a2,0 3385 3386 # --- Convert emt_expansion_id to decimal, reverse-fill into 3387 # --- local_label_digits[0..24). Write right-to-left starting at 3388 # --- offset 23 so digits are adjacent at [cursor, &scratch+24). 3389 la_a0 &emt_expansion_id 3390 ld_t0,a0,0 # t0 = id (mutated) 3391 la_t1 &local_label_digits 3392 li_a2 %24 %0 3393 add_t1,t1,a2 # t1 = end (one past last slot) 3394 mov_t2,t1 # t2 = cursor (moves left) 3395 3396 # Special-case id == 0 -> single '0' digit. 3397 la_br &emt_rldg_loop 3398 bnez_t0 3399 addi_t2,t2,neg1 3400 li_a0 %48 %0 3401 sb_a0,t2,0 3402 la_br &emt_rldg_done 3403 b 3404 :emt_rldg_loop 3405 la_br &emt_rldg_done 3406 beqz_t0 3407 # digit = id % 10 3408 mov_a0,t0 3409 li_a1 %10 %0 3410 rem_a2,a0,a1 # a2 = id % 10 3411 addi_a2,a2,48 # a2 = '0' + digit 3412 addi_t2,t2,neg1 3413 sb_a2,t2,0 # *--cursor = digit 3414 # id = id / 10 3415 mov_a0,t0 3416 li_a1 %10 %0 3417 div_a0,a0,a1 3418 mov_t0,a0 3419 la_br &emt_rldg_loop 3420 b 3421 :emt_rldg_done 3422 # digit_count = end - cursor 3423 la_a1 &local_label_digits 3424 li_a2 %24 %0 3425 add_a1,a1,a2 # a1 = end 3426 sub_a0,a1,t2 # a0 = digit_count 3427 la_a1 &ll_digit_count 3428 st_a0,a1,0 3429 # Save cursor (start of digits) for the copy step. 3430 la_a1 &ll_digit_cursor 3431 st_t2,a1,0 3432 3433 # --- Build final text in local_label_scratch --- 3434 # Layout: [0]=sigil, [1..1+tail_len)=tail, then "__", then digits. 3435 # tail_len = len - 2 3436 3437 # Write sigil (src_ptr[0]) to scratch[0]. 3438 la_a0 &ll_src_ptr 3439 ld_a1,a0,0 3440 lb_a2,a1,0 3441 la_a3 &local_label_scratch_ptr 3442 ld_a3,a3,0 3443 sb_a2,a3,0 3444 3445 # Copy tail: scratch[1..1+tail_len) <- src_ptr[2..2+tail_len). 3446 la_a0 &ll_src_len 3447 ld_a1,a0,0 3448 li_a2 %2 %0 3449 sub_t0,a1,a2 # t0 = tail_len = src_len - 2 3450 la_a0 &ll_src_ptr 3451 ld_a1,a0,0 # a1 = src_ptr 3452 addi_a1,a1,2 # a1 = src_ptr + 2 (tail start) 3453 la_a2 &local_label_scratch_ptr 3454 ld_a2,a2,0 3455 addi_a2,a2,1 # a2 = scratch + 1 (dst tail start) 3456 li_t1 %0 %0 # t1 = i 3457 :emt_rlbuild_tail_loop 3458 la_br &emt_rlbuild_tail_done 3459 beq_t1,t0 3460 add_a3,a1,t1 3461 lb_a3,a3,0 3462 add_t2,a2,t1 3463 sb_a3,t2,0 3464 addi_t1,t1,1 3465 la_br &emt_rlbuild_tail_loop 3466 b 3467 :emt_rlbuild_tail_done 3468 # Save tail_len for later offset math. 3469 la_a0 &ll_tail_len 3470 st_t0,a0,0 3471 3472 # Write "__" at scratch[1+tail_len], scratch[2+tail_len]. 3473 la_a2 &local_label_scratch_ptr 3474 ld_a2,a2,0 3475 addi_a2,a2,1 3476 add_a2,a2,t0 # a2 = &scratch[1+tail_len] 3477 li_a3 %95 %0 # '_' 3478 sb_a3,a2,0 3479 addi_a2,a2,1 3480 sb_a3,a2,0 3481 3482 # Copy digits: scratch[3+tail_len..3+tail_len+digit_count) <- digit_cursor[0..digit_count). 3483 la_a0 &ll_digit_count 3484 ld_t1,a0,0 # t1 = digit_count 3485 la_a0 &ll_digit_cursor 3486 ld_a1,a0,0 # a1 = digit_cursor (src) 3487 la_a0 &ll_tail_len 3488 ld_t0,a0,0 # t0 = tail_len 3489 la_a2 &local_label_scratch_ptr 3490 ld_a2,a2,0 3491 addi_a2,a2,3 3492 add_a2,a2,t0 # a2 = &scratch[3+tail_len] (dst) 3493 li_t2 %0 %0 # t2 = i 3494 :emt_rlbuild_digits_loop 3495 la_br &emt_rlbuild_digits_done 3496 beq_t2,t1 3497 add_a3,a1,t2 3498 lb_a3,a3,0 3499 add_a0,a2,t2 3500 sb_a3,a0,0 3501 addi_t2,t2,1 3502 la_br &emt_rlbuild_digits_loop 3503 b 3504 :emt_rlbuild_digits_done 3505 3506 # total_len = 1 + tail_len + 2 + digit_count = 3 + tail_len + digit_count 3507 la_a0 &ll_tail_len 3508 ld_a1,a0,0 3509 la_a0 &ll_digit_count 3510 ld_a2,a0,0 3511 add_a1,a1,a2 3512 addi_a1,a1,3 3513 la_a0 &ll_total_len 3514 st_a1,a0,0 3515 3516 # durable_ptr = append_text(&local_label_scratch, total_len) 3517 la_a0 &local_label_scratch_ptr 3518 ld_a0,a0,0 3519 la_br &append_text 3520 call 3521 # a0 = durable_ptr (into text_buf) 3522 3523 # Push TOK_WORD { kind=0, text_ptr=durable_ptr, text_len=total_len } to expand_pool. 3524 la_a1 &pool_used 3525 ld_t0,a1,0 3526 li_a2 M1PP_EXPAND_CAP 3527 la_br &err_token_overflow 3528 beq_t0,a2 3529 la_a3 &expand_pool_ptr 3530 ld_a3,a3,0 3531 add_a3,a3,t0 # a3 = dst slot 3532 # kind = TOK_WORD 3533 li_a2 TOK_WORD 3534 st_a2,a3,0 3535 # text_ptr 3536 st_a0,a3,8 3537 # text_len 3538 la_a0 &ll_total_len 3539 ld_a2,a0,0 3540 st_a2,a3,16 3541 # tight = 0 (synthetic local-label token) 3542 li_a2 %0 %0 3543 st_a2,a3,24 3544 # pool_used += 32 3545 addi_t0,t0,32 3546 la_a1 &pool_used 3547 st_t0,a1,0 3548 3549 # body_pos += 32 3550 la_a0 &emt_body_pos 3551 ld_t0,a0,0 3552 addi_t0,t0,32 3553 st_t0,a0,0 3554 la_br &emt_loop 3555 b 3556 3557 :emt_copy_literal 3558 # Append *body_pos to expand_pool. Check overflow. 3559 la_a0 &pool_used 3560 ld_t0,a0,0 3561 li_a1 M1PP_EXPAND_CAP 3562 la_br &err_token_overflow 3563 beq_t0,a1 3564 # dst = &expand_pool + pool_used 3565 la_a2 &expand_pool_ptr 3566 ld_a2,a2,0 3567 add_a2,a2,t0 3568 # src = body_pos 3569 la_a0 &emt_body_pos 3570 ld_a3,a0,0 3571 # copy 32 bytes (4 x 8) — preserves tight at +24 3572 ld_a0,a3,0 3573 st_a0,a2,0 3574 ld_a0,a3,8 3575 st_a0,a2,8 3576 ld_a0,a3,16 3577 st_a0,a2,16 3578 ld_a0,a3,24 3579 st_a0,a2,24 3580 # pool_used += 32 3581 addi_t0,t0,32 3582 la_a0 &pool_used 3583 st_t0,a0,0 3584 # body_pos += 32 3585 addi_a3,a3,32 3586 la_a0 &emt_body_pos 3587 st_a3,a0,0 3588 la_br &emt_loop 3589 b 3590 3591 :emt_do_substitute_paste 3592 # Use the cached param_idx (set at the top of emt_loop) instead of 3593 # re-running find_param. 3594 la_a0 &emt_cached_param_idx 3595 ld_a0,a0,0 3596 addi_a0,a0,neg1 3597 shli_a0,a0,3 3598 la_a1 &arg_starts_ptr 3599 ld_a1,a1,0 3600 add_a1,a1,a0 3601 ld_t0,a1,0 3602 la_a1 &arg_ends_ptr 3603 ld_a1,a1,0 3604 add_a1,a1,a0 3605 ld_t1,a1,0 3606 mov_a0,t0 3607 mov_a1,t1 3608 la_br ©_paste_arg_to_pool 3609 call 3610 # body_pos += 24 3611 la_a0 &emt_body_pos 3612 ld_t0,a0,0 3613 addi_t0,t0,32 3614 st_t0,a0,0 3615 la_br &emt_loop 3616 b 3617 3618 :emt_do_substitute_plain 3619 # Use the cached param_idx (set at the top of emt_loop) instead of 3620 # re-running find_param. 3621 la_a0 &emt_cached_param_idx 3622 ld_a0,a0,0 3623 addi_a0,a0,neg1 3624 shli_a0,a0,3 3625 la_a1 &arg_starts_ptr 3626 ld_a1,a1,0 3627 add_a1,a1,a0 3628 ld_t0,a1,0 3629 la_a1 &arg_ends_ptr 3630 ld_a1,a1,0 3631 add_a1,a1,a0 3632 ld_t1,a1,0 3633 mov_a0,t0 3634 mov_a1,t1 3635 la_br ©_arg_tokens_to_pool 3636 call 3637 # body_pos += 24 3638 la_a0 &emt_body_pos 3639 ld_t0,a0,0 3640 addi_t0,t0,32 3641 st_t0,a0,0 3642 la_br &emt_loop 3643 b 3644 3645 :emt_done 3646 # Gate paste_pool_range(mark) on (macro->has_paste OR emt_saw_arg_paste). 3647 # When neither side contains TOK_PASTE the pool sweep is wasted work — the 3648 # whole point of has_paste / args_have_paste is to skip it for the common 3649 # case of a `##`-free expansion. 3650 la_a0 &emt_macro 3651 ld_t0,a0,0 3652 li_a1 M1PP_MACRO_HAS_PASTE_OFF 3653 add_t0,t0,a1 3654 ld_t0,t0,0 3655 la_a0 &emt_saw_arg_paste 3656 ld_t1,a0,0 3657 or_t0,t0,t1 3658 la_br &emt_done_skip_paste 3659 beqz_t0 3660 la_a0 &emt_mark 3661 ld_a0,a0,0 3662 la_br &paste_pool_range 3663 call 3664 3665 :emt_done_skip_paste 3666 eret 3667 3668 ## expand_call(a0=stream_ptr, a1=macro_ptr) -> void (fatal on bad call) 3669 ## Calls expand_macro_tokens for the call at stream->pos, sets 3670 ## stream->pos = emt_after_pos, stream->line_start = 0, and 3671 ## push_pool_stream_from_mark(emt_mark) to rescan the expansion. 3672 :expand_call 3673 enter_8 3674 3675 # spill stream_ptr to local frame slot (sp+16 is the first local; sp+0/+8 3676 # hold the saved return address and saved caller sp). 3677 st_a0,sp,0 3678 3679 # expand_macro_tokens(stream->pos, stream->end, macro) 3680 # stream->pos at +16, stream->end at +8 3681 ld_t0,a0,16 3682 ld_t1,a0,8 3683 mov_a2,a1 3684 mov_a0,t0 3685 mov_a1,t1 3686 la_br &expand_macro_tokens 3687 call 3688 3689 # stream->pos = emt_after_pos 3690 ld_a0,sp,0 3691 la_a1 &emt_after_pos 3692 ld_t0,a1,0 3693 st_t0,a0,16 3694 3695 # stream->line_start = 0 3696 li_t0 %0 %0 3697 st_t0,a0,24 3698 3699 # push_pool_stream_from_mark(emt_mark) 3700 la_a0 &emt_mark 3701 ld_a0,a0,0 3702 la_br &push_pool_stream_from_mark 3703 call 3704 3705 eret 3706 3707 ## ============================================================================ 3708 ## --- ## token paste compaction ---------------------------------------------- 3709 ## ============================================================================ 3710 3711 ## append_pasted_token(a0=dst_tok, a1=left_tok, a2=right_tok) -> void (fatal) 3712 ## Concatenate left->text and right->text into paste_scratch, then call 3713 ## append_text(&paste_scratch, total_len) for stable storage in text_buf, 3714 ## and write *dst = { TOK_WORD, text_ptr, total_len }. paste_scratch is 3715 ## 256 bytes (M0's quoted-literal cap). Fatal err_text_overflow if combined 3716 ## length exceeds 256 bytes; append_text handles its own text_buf overflow. 3717 :append_pasted_token 3718 enter_0 3719 3720 # ---- Spill all three operands to BSS so we can survive append_text. ---- 3721 la_t0 &paste_dst_save 3722 st_a0,t0,0 3723 la_t0 &paste_left_ptr 3724 ld_t1,a1,8 3725 st_t1,t0,0 3726 la_t0 &paste_left_len 3727 ld_t1,a1,16 3728 st_t1,t0,0 3729 la_t0 &paste_right_ptr 3730 ld_t1,a2,8 3731 st_t1,t0,0 3732 la_t0 &paste_right_len 3733 ld_t1,a2,16 3734 st_t1,t0,0 3735 3736 # ---- total_len = left.len + right.len; fatal if > 256 ---- 3737 la_t0 &paste_left_len 3738 ld_t1,t0,0 3739 la_t0 &paste_right_len 3740 ld_t2,t0,0 3741 add_a0,t1,t2 3742 li_a1 %256 %0 3743 la_br &err_text_overflow 3744 blt_a1,a0 3745 # save total_len for the append_text call below 3746 la_t0 &paste_total_len 3747 st_a0,t0,0 3748 3749 # ---- Copy left bytes: paste_scratch[0..left.len) <- left.text_ptr ---- 3750 la_t0 &paste_left_ptr 3751 ld_t0,t0,0 3752 la_t1 &paste_left_len 3753 ld_t1,t1,0 3754 la_t2 &paste_scratch_ptr 3755 ld_t2,t2,0 3756 li_a0 %0 %0 3757 :append_pasted_left_loop 3758 la_br &append_pasted_left_done 3759 beq_a0,t1 3760 add_a1,t0,a0 3761 lb_a1,a1,0 3762 add_a2,t2,a0 3763 sb_a1,a2,0 3764 addi_a0,a0,1 3765 la_br &append_pasted_left_loop 3766 b 3767 :append_pasted_left_done 3768 3769 # ---- Copy right bytes: paste_scratch[left.len..total_len) <- right.text_ptr ---- 3770 la_t0 &paste_right_ptr 3771 ld_t0,t0,0 3772 la_t1 &paste_right_len 3773 ld_t1,t1,0 3774 la_t2 &paste_scratch_ptr 3775 ld_t2,t2,0 3776 la_a3 &paste_left_len 3777 ld_a3,a3,0 3778 add_t2,t2,a3 # t2 = &paste_scratch[left.len] 3779 li_a0 %0 %0 3780 :append_pasted_right_loop 3781 la_br &append_pasted_right_done 3782 beq_a0,t1 3783 add_a1,t0,a0 3784 lb_a1,a1,0 3785 add_a2,t2,a0 3786 sb_a1,a2,0 3787 addi_a0,a0,1 3788 la_br &append_pasted_right_loop 3789 b 3790 :append_pasted_right_done 3791 3792 # ---- text_ptr = append_text(&paste_scratch, total_len) ---- 3793 la_a0 &paste_scratch_ptr 3794 ld_a0,a0,0 3795 la_a1 &paste_total_len 3796 ld_a1,a1,0 3797 la_br &append_text 3798 call 3799 # a0 = text_ptr (returned) 3800 3801 # ---- *dst = { TOK_WORD, text_ptr, total_len, tight=0 } ---- 3802 la_t0 &paste_dst_save 3803 ld_t0,t0,0 3804 li_a2 TOK_WORD 3805 st_a2,t0,0 3806 st_a0,t0,8 3807 la_a1 &paste_total_len 3808 ld_a1,a1,0 3809 st_a1,t0,16 3810 li_a1 %0 %0 3811 st_a1,t0,24 3812 3813 eret 3814 3815 ## paste_pool_range(a0=mark) -> void (fatal on bad paste) 3816 ## In-place compactor over expand_pool[mark..pool_used). For each TOK_PASTE, 3817 ## walk back from `out` over already-copied NEWLINE tokens to find the left 3818 ## operand, walk forward from `in+1` over NEWLINE tokens to find the right 3819 ## operand, then paste left+right into the left slot. The discarded newlines 3820 ## on either side are dropped. Copy other tokens forward. Update pool_used 3821 ## to the new end. Fatal if ## has no operand on a side, or its operand is 3822 ## itself PASTE. 3823 :paste_pool_range 3824 enter_0 3825 3826 # ---- start = expand_pool + mark ---- 3827 la_t0 &expand_pool_ptr 3828 ld_t0,t0,0 3829 add_t0,t0,a0 3830 la_t1 &paste_start 3831 st_t0,t1,0 3832 # paste_in = start 3833 la_t1 &paste_in 3834 st_t0,t1,0 3835 # paste_out = start 3836 la_t1 &paste_out 3837 st_t0,t1,0 3838 3839 # ---- end = expand_pool + pool_used ---- 3840 la_t1 &pool_used 3841 ld_t2,t1,0 3842 la_t1 &expand_pool_ptr 3843 ld_t1,t1,0 3844 add_t2,t1,t2 3845 la_t1 &paste_end 3846 st_t2,t1,0 3847 3848 :paste_pool_loop 3849 # in = paste_in; end = paste_end; if (in == end) done 3850 la_a0 &paste_in 3851 ld_t0,a0,0 3852 la_a1 &paste_end 3853 ld_t1,a1,0 3854 la_br &paste_pool_done 3855 beq_t0,t1 3856 3857 # kind = in->kind 3858 ld_a2,t0,0 3859 li_a3 TOK_PASTE 3860 la_br &paste_pool_handle_paste 3861 beq_a2,a3 3862 3863 # ---- non-PASTE: copy *in to *out, advance both by 32 ---- 3864 la_a0 &paste_out 3865 ld_t2,a0,0 3866 # if (in == out) skip the copy 3867 la_br &paste_pool_skip_copy 3868 beq_t0,t2 3869 ld_a3,t0,0 3870 st_a3,t2,0 3871 ld_a3,t0,8 3872 st_a3,t2,8 3873 ld_a3,t0,16 3874 st_a3,t2,16 3875 ld_a3,t0,24 3876 st_a3,t2,24 3877 :paste_pool_skip_copy 3878 addi_t0,t0,32 3879 addi_t2,t2,32 3880 la_a0 &paste_in 3881 st_t0,a0,0 3882 la_a0 &paste_out 3883 st_t2,a0,0 3884 la_br &paste_pool_loop 3885 b 3886 3887 :paste_pool_handle_paste 3888 # ---- TOK_PASTE handling ---- 3889 # Find left operand: start at out, walk back over NEWLINE tokens, then 3890 # step one more to land on the actual left operand. If we cannot step 3891 # back past start, the ## has no left operand -> fatal. 3892 la_a0 &paste_out 3893 ld_t1,a0,0 # t1 = left (initially = out) 3894 la_a1 &paste_start 3895 ld_t2,a1,0 # t2 = start 3896 :paste_pool_left_skip_nl 3897 la_br &paste_pool_left_step 3898 beq_t1,t2 3899 ld_a2,t1,neg32 3900 li_a3 TOK_NEWLINE 3901 la_br &paste_pool_left_step 3902 bne_a2,a3 3903 addi_t1,t1,neg32 3904 la_br &paste_pool_left_skip_nl 3905 b 3906 :paste_pool_left_step 3907 # left == start? then ## is first (fatal). 3908 la_br &err_bad_macro_header 3909 beq_t1,t2 3910 addi_t1,t1,neg32 # left now points at the operand 3911 # Validate left->kind != TOK_PASTE. 3912 ld_a2,t1,0 3913 li_a3 TOK_PASTE 3914 la_br &err_bad_macro_header 3915 beq_a2,a3 3916 3917 # Find right operand: start at in+1, walk forward over NEWLINE tokens. 3918 # If we run out of tokens, ## is last (fatal). If right is PASTE, fatal. 3919 addi_t0,t0,32 # t0 = right (initially = in + 1) 3920 la_a1 &paste_end 3921 ld_a2,a1,0 # a2 = end 3922 :paste_pool_right_skip_nl 3923 la_br &err_bad_macro_header 3924 beq_t0,a2 3925 ld_a3,t0,0 3926 li_a1 TOK_NEWLINE 3927 la_br &paste_pool_right_step 3928 bne_a3,a1 3929 addi_t0,t0,32 3930 la_br &paste_pool_right_skip_nl 3931 b 3932 :paste_pool_right_step 3933 # Validate right->kind != TOK_PASTE. 3934 li_a1 TOK_PASTE 3935 la_br &err_bad_macro_header 3936 beq_a3,a1 3937 3938 # Pre-publish out = left + 1 and in = right + 1, since the call below 3939 # clobbers the t* / a* registers we used to track them. 3940 addi_a3,t1,32 3941 la_a1 &paste_out 3942 st_a3,a1,0 3943 addi_a3,t0,32 3944 la_a1 &paste_in 3945 st_a3,a1,0 3946 3947 # append_pasted_token(left, left, right) 3948 mov_a0,t1 3949 mov_a1,t1 3950 mov_a2,t0 3951 la_br &append_pasted_token 3952 call 3953 3954 la_br &paste_pool_loop 3955 b 3956 3957 :paste_pool_done 3958 # pool_used = (out - expand_pool) 3959 la_a0 &paste_out 3960 ld_t0,a0,0 3961 la_a1 &expand_pool_ptr 3962 ld_a1,a1,0 3963 sub_t0,t0,a1 3964 la_a1 &pool_used 3965 st_t0,a1,0 3966 eret 3967 3968 ## ============================================================================ 3969 ## --- Integer atoms + S-expression evaluator --------------------------------- 3970 ## ============================================================================ 3971 3972 ## parse_int_token(a0=tok) -> a0 = i64 (fatal on bad). Leaf. 3973 ## Accepts decimal (optional leading '-') and 0x-prefixed hex. Positive 3974 ## values are accumulated as u64 and reinterpreted as i64, so values with 3975 ## the high bit set wrap to negative i64. 3976 ## 3977 ## Register usage (leaf, no calls): 3978 ## t0 = src ptr (cursor into text) 3979 ## t1 = end ptr (text + len) 3980 ## t2 = current byte 3981 ## a0 = accumulator (return) 3982 ## a1 = negative flag (0/1) 3983 ## a2 = scratch (digit, multiplier) 3984 ## a3 = scratch (compare value) 3985 :parse_int_token 3986 # if (tok->kind != TOK_WORD) fatal 3987 ld_t0,a0,0 3988 li_t1 TOK_WORD 3989 la_br &err_bad_macro_header 3990 bne_t0,t1 3991 3992 # src = tok->text_ptr; len = tok->text_len; end = src + len 3993 ld_t0,a0,8 3994 ld_t1,a0,16 3995 3996 # if (len <= 0) fatal 3997 la_br &err_bad_macro_header 3998 beqz_t1 3999 add_t1,t0,t1 4000 4001 # negative = 0 4002 li_a1 %0 %0 4003 4004 # if (*src == '-') { negative = 1; src++; if (src == end) fatal } 4005 lb_t2,t0,0 4006 li_a3 %45 %0 4007 la_br &pit_after_sign 4008 bne_t2,a3 4009 li_a1 %1 %0 4010 addi_t0,t0,1 4011 la_br &err_bad_macro_header 4012 beq_t0,t1 4013 4014 :pit_after_sign 4015 # accumulator = 0 4016 li_a0 %0 %0 4017 4018 # check for 0x / 0X prefix: need at least 2 chars left 4019 mov_a2,t1 4020 sub_a2,a2,t0 4021 li_a3 %2 %0 4022 la_br &pit_decimal 4023 blt_a2,a3 4024 4025 # if (src[0] == '0' && (src[1] == 'x' || src[1] == 'X')) -> hex 4026 lb_t2,t0,0 4027 li_a3 %48 %0 4028 la_br &pit_decimal 4029 bne_t2,a3 4030 addi_a2,t0,1 4031 lb_a2,a2,0 4032 li_a3 %120 %0 4033 la_br &pit_hex_start 4034 beq_a2,a3 4035 li_a3 %88 %0 4036 la_br &pit_hex_start 4037 beq_a2,a3 4038 la_br &pit_decimal 4039 b 4040 4041 :pit_hex_start 4042 # consume "0x"; require at least one hex digit after 4043 addi_t0,t0,2 4044 la_br &err_bad_macro_header 4045 beq_t0,t1 4046 :pit_hex_loop 4047 la_br &pit_finish 4048 beq_t0,t1 4049 lb_t2,t0,0 4050 4051 # 0..9 4052 li_a3 %48 %0 4053 la_br &pit_hex_check_lower 4054 blt_t2,a3 4055 li_a3 %57 %0 4056 la_br &pit_hex_check_lower 4057 blt_a3,t2 4058 # digit = c - '0' 4059 addi_a2,t2,neg48 4060 la_br &pit_hex_accum 4061 b 4062 4063 :pit_hex_check_lower 4064 # 'a'..'f' 4065 li_a3 %97 %0 4066 la_br &pit_hex_check_upper 4067 blt_t2,a3 4068 li_a3 %102 %0 4069 la_br &pit_hex_check_upper 4070 blt_a3,t2 4071 # digit = (c - 'a') + 10 4072 li_a3 %97 %0 4073 sub_a2,t2,a3 4074 addi_a2,a2,8 4075 addi_a2,a2,2 4076 la_br &pit_hex_accum 4077 b 4078 4079 :pit_hex_check_upper 4080 # 'A'..'F' 4081 li_a3 %65 %0 4082 la_br &err_bad_macro_header 4083 blt_t2,a3 4084 li_a3 %70 %0 4085 la_br &err_bad_macro_header 4086 blt_a3,t2 4087 # digit = (c - 'A') + 10 4088 li_a3 %65 %0 4089 sub_a2,t2,a3 4090 addi_a2,a2,8 4091 addi_a2,a2,2 4092 4093 :pit_hex_accum 4094 # accum = (accum << 4) | digit 4095 shli_a0,a0,4 4096 or_a0,a0,a2 4097 addi_t0,t0,1 4098 la_br &pit_hex_loop 4099 b 4100 4101 :pit_decimal 4102 # decimal loop: accum = accum * 10 + digit 4103 # (caller already ensured len > 0 and that src points to first digit) 4104 :pit_decimal_loop 4105 la_br &pit_finish 4106 beq_t0,t1 4107 lb_t2,t0,0 4108 li_a3 %48 %0 4109 la_br &err_bad_macro_header 4110 blt_t2,a3 4111 li_a3 %57 %0 4112 la_br &err_bad_macro_header 4113 blt_a3,t2 4114 # accum = accum * 10 4115 li_a3 %10 %0 4116 mul_a0,a0,a3 4117 # digit = c - '0'; accum += digit 4118 addi_a2,t2,neg48 4119 add_a0,a0,a2 4120 addi_t0,t0,1 4121 la_br &pit_decimal_loop 4122 b 4123 4124 :pit_finish 4125 # if (negative) accum = 0 - accum 4126 la_br &pit_done 4127 beqz_a1 4128 li_a3 %0 %0 4129 sub_a0,a3,a0 4130 :pit_done 4131 ret 4132 4133 ## expr_op_code(a0=tok) -> a0 = EXPR_ADD..EXPR_STRLEN, or EXPR_INVALID. 4134 ## Accepts operator tokens: + - * / % << >> & | ^ ~ = != 4135 ## < <= > >= strlen. Non-WORD tok or unknown operator -> EXPR_INVALID. 4136 ## 4137 ## tok_eq_const is a leaf but clobbers a0..a3,t0..t2; spill tok to eoc_tok 4138 ## once, reload before each compare. Needs an enter_0 frame because it 4139 ## issues `call` instructions (aarch64 CALL writes LR). 4140 :expr_op_code 4141 enter_0 4142 # spill tok; reject non-WORD up front 4143 la_a1 &eoc_tok 4144 st_a0,a1,0 4145 ld_t0,a0,0 4146 li_t1 TOK_WORD 4147 la_br &eoc_invalid 4148 bne_t0,t1 4149 4150 # "+" -> EXPR_ADD 4151 la_a0 &eoc_tok 4152 ld_a0,a0,0 4153 la_a1 &op_plus 4154 li_a2 %1 %0 4155 la_br &tok_eq_const 4156 call 4157 la_br &eoc_add 4158 bnez_a0 4159 4160 # "-" -> EXPR_SUB 4161 la_a0 &eoc_tok 4162 ld_a0,a0,0 4163 la_a1 &op_minus 4164 li_a2 %1 %0 4165 la_br &tok_eq_const 4166 call 4167 la_br &eoc_sub 4168 bnez_a0 4169 4170 # "*" -> EXPR_MUL 4171 la_a0 &eoc_tok 4172 ld_a0,a0,0 4173 la_a1 &op_star 4174 li_a2 %1 %0 4175 la_br &tok_eq_const 4176 call 4177 la_br &eoc_mul 4178 bnez_a0 4179 4180 # "/" -> EXPR_DIV 4181 la_a0 &eoc_tok 4182 ld_a0,a0,0 4183 la_a1 &op_slash 4184 li_a2 %1 %0 4185 la_br &tok_eq_const 4186 call 4187 la_br &eoc_div 4188 bnez_a0 4189 4190 # "%" -> EXPR_MOD 4191 la_a0 &eoc_tok 4192 ld_a0,a0,0 4193 la_a1 &op_percent 4194 li_a2 %1 %0 4195 la_br &tok_eq_const 4196 call 4197 la_br &eoc_mod 4198 bnez_a0 4199 4200 # "<<" -> EXPR_SHL 4201 la_a0 &eoc_tok 4202 ld_a0,a0,0 4203 la_a1 &op_shl 4204 li_a2 %2 %0 4205 la_br &tok_eq_const 4206 call 4207 la_br &eoc_shl 4208 bnez_a0 4209 4210 # ">>" -> EXPR_SHR 4211 la_a0 &eoc_tok 4212 ld_a0,a0,0 4213 la_a1 &op_shr 4214 li_a2 %2 %0 4215 la_br &tok_eq_const 4216 call 4217 la_br &eoc_shr 4218 bnez_a0 4219 4220 # "&" -> EXPR_AND 4221 la_a0 &eoc_tok 4222 ld_a0,a0,0 4223 la_a1 &op_amp 4224 li_a2 %1 %0 4225 la_br &tok_eq_const 4226 call 4227 la_br &eoc_and 4228 bnez_a0 4229 4230 # "|" -> EXPR_OR 4231 la_a0 &eoc_tok 4232 ld_a0,a0,0 4233 la_a1 &op_bar 4234 li_a2 %1 %0 4235 la_br &tok_eq_const 4236 call 4237 la_br &eoc_or 4238 bnez_a0 4239 4240 # "^" -> EXPR_XOR 4241 la_a0 &eoc_tok 4242 ld_a0,a0,0 4243 la_a1 &op_caret 4244 li_a2 %1 %0 4245 la_br &tok_eq_const 4246 call 4247 la_br &eoc_xor 4248 bnez_a0 4249 4250 # "~" -> EXPR_NOT 4251 la_a0 &eoc_tok 4252 ld_a0,a0,0 4253 la_a1 &op_tilde 4254 li_a2 %1 %0 4255 la_br &tok_eq_const 4256 call 4257 la_br &eoc_not 4258 bnez_a0 4259 4260 # "=" -> EXPR_EQ 4261 la_a0 &eoc_tok 4262 ld_a0,a0,0 4263 la_a1 &op_eq 4264 li_a2 %1 %0 4265 la_br &tok_eq_const 4266 call 4267 la_br &eoc_eq 4268 bnez_a0 4269 4270 # "!=" -> EXPR_NE 4271 la_a0 &eoc_tok 4272 ld_a0,a0,0 4273 la_a1 &op_ne 4274 li_a2 %2 %0 4275 la_br &tok_eq_const 4276 call 4277 la_br &eoc_ne 4278 bnez_a0 4279 4280 # "<=" -> EXPR_LE (check before single "<") 4281 la_a0 &eoc_tok 4282 ld_a0,a0,0 4283 la_a1 &op_le 4284 li_a2 %2 %0 4285 la_br &tok_eq_const 4286 call 4287 la_br &eoc_le 4288 bnez_a0 4289 4290 # "<" -> EXPR_LT 4291 la_a0 &eoc_tok 4292 ld_a0,a0,0 4293 la_a1 &op_lt 4294 li_a2 %1 %0 4295 la_br &tok_eq_const 4296 call 4297 la_br &eoc_lt 4298 bnez_a0 4299 4300 # ">=" -> EXPR_GE (check before single ">") 4301 la_a0 &eoc_tok 4302 ld_a0,a0,0 4303 la_a1 &op_ge 4304 li_a2 %2 %0 4305 la_br &tok_eq_const 4306 call 4307 la_br &eoc_ge 4308 bnez_a0 4309 4310 # ">" -> EXPR_GT 4311 la_a0 &eoc_tok 4312 ld_a0,a0,0 4313 la_a1 &op_gt 4314 li_a2 %1 %0 4315 la_br &tok_eq_const 4316 call 4317 la_br &eoc_gt 4318 bnez_a0 4319 4320 # "strlen" -> EXPR_STRLEN 4321 la_a0 &eoc_tok 4322 ld_a0,a0,0 4323 la_a1 &op_strlen 4324 li_a2 %6 %0 4325 la_br &tok_eq_const 4326 call 4327 la_br &eoc_strlen 4328 bnez_a0 4329 4330 :eoc_invalid 4331 li_a0 EXPR_INVALID 4332 eret 4333 :eoc_add 4334 li_a0 EXPR_ADD 4335 eret 4336 :eoc_sub 4337 li_a0 EXPR_SUB 4338 eret 4339 :eoc_mul 4340 li_a0 EXPR_MUL 4341 eret 4342 :eoc_div 4343 li_a0 EXPR_DIV 4344 eret 4345 :eoc_mod 4346 li_a0 EXPR_MOD 4347 eret 4348 :eoc_shl 4349 li_a0 EXPR_SHL 4350 eret 4351 :eoc_shr 4352 li_a0 EXPR_SHR 4353 eret 4354 :eoc_and 4355 li_a0 EXPR_AND 4356 eret 4357 :eoc_or 4358 li_a0 EXPR_OR 4359 eret 4360 :eoc_xor 4361 li_a0 EXPR_XOR 4362 eret 4363 :eoc_not 4364 li_a0 EXPR_NOT 4365 eret 4366 :eoc_eq 4367 li_a0 EXPR_EQ 4368 eret 4369 :eoc_ne 4370 li_a0 EXPR_NE 4371 eret 4372 :eoc_lt 4373 li_a0 EXPR_LT 4374 eret 4375 :eoc_le 4376 li_a0 EXPR_LE 4377 eret 4378 :eoc_gt 4379 li_a0 EXPR_GT 4380 eret 4381 :eoc_ge 4382 li_a0 EXPR_GE 4383 eret 4384 :eoc_strlen 4385 li_a0 EXPR_STRLEN 4386 eret 4387 4388 ## apply_expr_op(a0=op_code, a1=args_ptr, a2=argc) -> a0 = i64 result 4389 ## Reduce args[0..argc) per op: 4390 ## + * & | $ variadic, argc >= 1 4391 ## - argc >= 1 (argc == 1 is negate, else left-assoc subtract) 4392 ## / % binary, div-by-zero fatal 4393 ## << >> binary (>> is arithmetic) 4394 ## ~ unary 4395 ## = == != < <= > >= binary 4396 ## Fatal on wrong argc or EXPR_INVALID. 4397 ## 4398 ## Calls aeo_require_* helpers via `call`, so it needs a frame. 4399 ## State held in BSS scratch (aeo_op/args/argc/acc/i) since loops trash registers. 4400 :apply_expr_op 4401 enter_0 4402 # spill op, args, argc to BSS 4403 la_a3 &aeo_op 4404 st_a0,a3,0 4405 la_a3 &aeo_args 4406 st_a1,a3,0 4407 la_a3 &aeo_argc 4408 st_a2,a3,0 4409 4410 # dispatch: compare op against each EXPR_* and branch to its handler 4411 li_t0 EXPR_ADD 4412 la_br &aeo_do_add 4413 beq_a0,t0 4414 li_t0 EXPR_SUB 4415 la_br &aeo_do_sub 4416 beq_a0,t0 4417 li_t0 EXPR_MUL 4418 la_br &aeo_do_mul 4419 beq_a0,t0 4420 li_t0 EXPR_DIV 4421 la_br &aeo_do_div 4422 beq_a0,t0 4423 li_t0 EXPR_MOD 4424 la_br &aeo_do_mod 4425 beq_a0,t0 4426 li_t0 EXPR_SHL 4427 la_br &aeo_do_shl 4428 beq_a0,t0 4429 li_t0 EXPR_SHR 4430 la_br &aeo_do_shr 4431 beq_a0,t0 4432 li_t0 EXPR_AND 4433 la_br &aeo_do_and 4434 beq_a0,t0 4435 li_t0 EXPR_OR 4436 la_br &aeo_do_or 4437 beq_a0,t0 4438 li_t0 EXPR_XOR 4439 la_br &aeo_do_xor 4440 beq_a0,t0 4441 li_t0 EXPR_NOT 4442 la_br &aeo_do_not 4443 beq_a0,t0 4444 li_t0 EXPR_EQ 4445 la_br &aeo_do_eq 4446 beq_a0,t0 4447 li_t0 EXPR_NE 4448 la_br &aeo_do_ne 4449 beq_a0,t0 4450 li_t0 EXPR_LT 4451 la_br &aeo_do_lt 4452 beq_a0,t0 4453 li_t0 EXPR_LE 4454 la_br &aeo_do_le 4455 beq_a0,t0 4456 li_t0 EXPR_GT 4457 la_br &aeo_do_gt 4458 beq_a0,t0 4459 li_t0 EXPR_GE 4460 la_br &aeo_do_ge 4461 beq_a0,t0 4462 # EXPR_INVALID or unknown 4463 la_br &err_bad_macro_header 4464 b 4465 4466 ## --- shared helpers for variadic folds ---------------------------------- 4467 ## aeo_require_argc_ge1: branch to err if argc < 1 4468 ## aeo_require_argc_eq2: branch to err if argc != 2 4469 ## aeo_load_arg0_to_acc: acc = args[0]; i = 1 4470 4471 :aeo_do_add 4472 la_br &aeo_require_argc_ge1 4473 call 4474 la_br &aeo_load_arg0_to_acc 4475 call 4476 :aeo_add_loop 4477 la_a0 &aeo_i 4478 ld_t0,a0,0 4479 la_a1 &aeo_argc 4480 ld_t1,a1,0 4481 la_br &aeo_finish 4482 beq_t0,t1 4483 # acc += args[i] 4484 la_a0 &aeo_args 4485 ld_a1,a0,0 4486 shli_t2,t0,3 4487 add_t2,a1,t2 4488 ld_a2,t2,0 4489 la_a0 &aeo_acc 4490 ld_a3,a0,0 4491 add_a3,a3,a2 4492 st_a3,a0,0 4493 addi_t0,t0,1 4494 la_a1 &aeo_i 4495 st_t0,a1,0 4496 la_br &aeo_add_loop 4497 b 4498 4499 :aeo_do_sub 4500 la_br &aeo_require_argc_ge1 4501 call 4502 # if (argc == 1) acc = -args[0]; else acc = args[0] 4503 la_a0 &aeo_argc 4504 ld_t0,a0,0 4505 li_t1 %1 %0 4506 la_br &aeo_sub_unary 4507 beq_t0,t1 4508 la_br &aeo_load_arg0_to_acc 4509 call 4510 :aeo_sub_loop 4511 la_a0 &aeo_i 4512 ld_t0,a0,0 4513 la_a1 &aeo_argc 4514 ld_t1,a1,0 4515 la_br &aeo_finish 4516 beq_t0,t1 4517 la_a0 &aeo_args 4518 ld_a1,a0,0 4519 shli_t2,t0,3 4520 add_t2,a1,t2 4521 ld_a2,t2,0 4522 la_a0 &aeo_acc 4523 ld_a3,a0,0 4524 sub_a3,a3,a2 4525 st_a3,a0,0 4526 addi_t0,t0,1 4527 la_a1 &aeo_i 4528 st_t0,a1,0 4529 la_br &aeo_sub_loop 4530 b 4531 :aeo_sub_unary 4532 # acc = 0 - args[0] 4533 la_a0 &aeo_args 4534 ld_a1,a0,0 4535 ld_a2,a1,0 4536 li_a3 %0 %0 4537 sub_a3,a3,a2 4538 la_a0 &aeo_acc 4539 st_a3,a0,0 4540 la_br &aeo_finish 4541 b 4542 4543 :aeo_do_mul 4544 la_br &aeo_require_argc_ge1 4545 call 4546 la_br &aeo_load_arg0_to_acc 4547 call 4548 :aeo_mul_loop 4549 la_a0 &aeo_i 4550 ld_t0,a0,0 4551 la_a1 &aeo_argc 4552 ld_t1,a1,0 4553 la_br &aeo_finish 4554 beq_t0,t1 4555 la_a0 &aeo_args 4556 ld_a1,a0,0 4557 shli_t2,t0,3 4558 add_t2,a1,t2 4559 ld_a2,t2,0 4560 la_a0 &aeo_acc 4561 ld_a3,a0,0 4562 mul_a3,a3,a2 4563 st_a3,a0,0 4564 addi_t0,t0,1 4565 la_a1 &aeo_i 4566 st_t0,a1,0 4567 la_br &aeo_mul_loop 4568 b 4569 4570 :aeo_do_div 4571 la_br &aeo_require_argc_eq2 4572 call 4573 la_a0 &aeo_args 4574 ld_a1,a0,0 4575 ld_a2,a1,0 4576 ld_a3,a1,8 4577 # if (args[1] == 0) fatal 4578 la_br &err_bad_macro_header 4579 beqz_a3 4580 div_a2,a2,a3 4581 la_a0 &aeo_acc 4582 st_a2,a0,0 4583 la_br &aeo_finish 4584 b 4585 4586 :aeo_do_mod 4587 la_br &aeo_require_argc_eq2 4588 call 4589 la_a0 &aeo_args 4590 ld_a1,a0,0 4591 ld_a2,a1,0 4592 ld_a3,a1,8 4593 la_br &err_bad_macro_header 4594 beqz_a3 4595 rem_a2,a2,a3 4596 la_a0 &aeo_acc 4597 st_a2,a0,0 4598 la_br &aeo_finish 4599 b 4600 4601 :aeo_do_shl 4602 la_br &aeo_require_argc_eq2 4603 call 4604 la_a0 &aeo_args 4605 ld_a1,a0,0 4606 ld_a2,a1,0 4607 ld_a3,a1,8 4608 shl_a2,a2,a3 4609 la_a0 &aeo_acc 4610 st_a2,a0,0 4611 la_br &aeo_finish 4612 b 4613 4614 :aeo_do_shr 4615 la_br &aeo_require_argc_eq2 4616 call 4617 la_a0 &aeo_args 4618 ld_a1,a0,0 4619 ld_a2,a1,0 4620 ld_a3,a1,8 4621 sar_a2,a2,a3 4622 la_a0 &aeo_acc 4623 st_a2,a0,0 4624 la_br &aeo_finish 4625 b 4626 4627 :aeo_do_and 4628 la_br &aeo_require_argc_ge1 4629 call 4630 la_br &aeo_load_arg0_to_acc 4631 call 4632 :aeo_and_loop 4633 la_a0 &aeo_i 4634 ld_t0,a0,0 4635 la_a1 &aeo_argc 4636 ld_t1,a1,0 4637 la_br &aeo_finish 4638 beq_t0,t1 4639 la_a0 &aeo_args 4640 ld_a1,a0,0 4641 shli_t2,t0,3 4642 add_t2,a1,t2 4643 ld_a2,t2,0 4644 la_a0 &aeo_acc 4645 ld_a3,a0,0 4646 and_a3,a3,a2 4647 st_a3,a0,0 4648 addi_t0,t0,1 4649 la_a1 &aeo_i 4650 st_t0,a1,0 4651 la_br &aeo_and_loop 4652 b 4653 4654 :aeo_do_or 4655 la_br &aeo_require_argc_ge1 4656 call 4657 la_br &aeo_load_arg0_to_acc 4658 call 4659 :aeo_or_loop 4660 la_a0 &aeo_i 4661 ld_t0,a0,0 4662 la_a1 &aeo_argc 4663 ld_t1,a1,0 4664 la_br &aeo_finish 4665 beq_t0,t1 4666 la_a0 &aeo_args 4667 ld_a1,a0,0 4668 shli_t2,t0,3 4669 add_t2,a1,t2 4670 ld_a2,t2,0 4671 la_a0 &aeo_acc 4672 ld_a3,a0,0 4673 or_a3,a3,a2 4674 st_a3,a0,0 4675 addi_t0,t0,1 4676 la_a1 &aeo_i 4677 st_t0,a1,0 4678 la_br &aeo_or_loop 4679 b 4680 4681 :aeo_do_xor 4682 la_br &aeo_require_argc_ge1 4683 call 4684 la_br &aeo_load_arg0_to_acc 4685 call 4686 :aeo_xor_loop 4687 la_a0 &aeo_i 4688 ld_t0,a0,0 4689 la_a1 &aeo_argc 4690 ld_t1,a1,0 4691 la_br &aeo_finish 4692 beq_t0,t1 4693 la_a0 &aeo_args 4694 ld_a1,a0,0 4695 shli_t2,t0,3 4696 add_t2,a1,t2 4697 ld_a2,t2,0 4698 la_a0 &aeo_acc 4699 ld_a3,a0,0 4700 xor_a3,a3,a2 4701 st_a3,a0,0 4702 addi_t0,t0,1 4703 la_a1 &aeo_i 4704 st_t0,a1,0 4705 la_br &aeo_xor_loop 4706 b 4707 4708 :aeo_do_not 4709 # require argc == 1 4710 la_a0 &aeo_argc 4711 ld_t0,a0,0 4712 li_t1 %1 %0 4713 la_br &err_bad_macro_header 4714 bne_t0,t1 4715 la_a0 &aeo_args 4716 ld_a1,a0,0 4717 ld_a2,a1,0 4718 # ~x = x XOR -1 4719 li_a3 %1 %0 4720 li_t0 %0 %0 4721 sub_a3,t0,a3 4722 xor_a2,a2,a3 4723 la_a0 &aeo_acc 4724 st_a2,a0,0 4725 la_br &aeo_finish 4726 b 4727 4728 ## --- comparison ops: return 0 or 1 ---------------------------------------- 4729 ## EQ: args[0] == args[1] 4730 ## NE: args[0] != args[1] 4731 ## LT: args[0] < args[1] (signed) 4732 ## LE: args[0] <= args[1] (signed) 4733 ## GT: args[0] > args[1] (signed) 4734 ## GE: args[0] >= args[1] (signed) 4735 4736 :aeo_do_eq 4737 la_br &aeo_require_argc_eq2 4738 call 4739 la_a0 &aeo_args 4740 ld_a1,a0,0 4741 ld_a2,a1,0 4742 ld_a3,a1,8 4743 li_t0 %0 %0 4744 la_br &aeo_cmp_finish 4745 bne_a2,a3 4746 li_t0 %1 %0 4747 la_br &aeo_cmp_finish 4748 b 4749 4750 :aeo_do_ne 4751 la_br &aeo_require_argc_eq2 4752 call 4753 la_a0 &aeo_args 4754 ld_a1,a0,0 4755 ld_a2,a1,0 4756 ld_a3,a1,8 4757 li_t0 %0 %0 4758 la_br &aeo_cmp_finish 4759 beq_a2,a3 4760 li_t0 %1 %0 4761 la_br &aeo_cmp_finish 4762 b 4763 4764 :aeo_do_lt 4765 la_br &aeo_require_argc_eq2 4766 call 4767 la_a0 &aeo_args 4768 ld_a1,a0,0 4769 ld_a2,a1,0 4770 ld_a3,a1,8 4771 li_t0 %1 %0 4772 la_br &aeo_cmp_finish 4773 blt_a2,a3 4774 li_t0 %0 %0 4775 la_br &aeo_cmp_finish 4776 b 4777 4778 :aeo_do_le 4779 # a[0] <= a[1] <=> !(a[1] < a[0]) 4780 la_br &aeo_require_argc_eq2 4781 call 4782 la_a0 &aeo_args 4783 ld_a1,a0,0 4784 ld_a2,a1,0 4785 ld_a3,a1,8 4786 li_t0 %0 %0 4787 la_br &aeo_cmp_finish 4788 blt_a3,a2 4789 li_t0 %1 %0 4790 la_br &aeo_cmp_finish 4791 b 4792 4793 :aeo_do_gt 4794 # a[0] > a[1] <=> a[1] < a[0] 4795 la_br &aeo_require_argc_eq2 4796 call 4797 la_a0 &aeo_args 4798 ld_a1,a0,0 4799 ld_a2,a1,0 4800 ld_a3,a1,8 4801 li_t0 %1 %0 4802 la_br &aeo_cmp_finish 4803 blt_a3,a2 4804 li_t0 %0 %0 4805 la_br &aeo_cmp_finish 4806 b 4807 4808 :aeo_do_ge 4809 # a[0] >= a[1] <=> !(a[0] < a[1]) 4810 la_br &aeo_require_argc_eq2 4811 call 4812 la_a0 &aeo_args 4813 ld_a1,a0,0 4814 ld_a2,a1,0 4815 ld_a3,a1,8 4816 li_t0 %0 %0 4817 la_br &aeo_cmp_finish 4818 blt_a2,a3 4819 li_t0 %1 %0 4820 la_br &aeo_cmp_finish 4821 b 4822 4823 ## Shared tail for the comparison ops: store t0 (the 0/1 result) into 4824 ## aeo_acc, then jump to aeo_finish. Reached only via `b`. 4825 :aeo_cmp_finish 4826 la_a0 &aeo_acc 4827 st_t0,a0,0 4828 la_br &aeo_finish 4829 b 4830 4831 :aeo_finish 4832 la_a0 &aeo_acc 4833 ld_a0,a0,0 4834 eret 4835 4836 ## helper: validate argc >= 1; fatal otherwise. (Returns to caller.) 4837 :aeo_require_argc_ge1 4838 la_a0 &aeo_argc 4839 ld_t0,a0,0 4840 li_t1 %1 %0 4841 la_br &err_bad_macro_header 4842 blt_t0,t1 4843 ret 4844 4845 ## helper: validate argc == 2; fatal otherwise. 4846 :aeo_require_argc_eq2 4847 la_a0 &aeo_argc 4848 ld_t0,a0,0 4849 li_t1 %2 %0 4850 la_br &err_bad_macro_header 4851 bne_t0,t1 4852 ret 4853 4854 ## helper: acc = args[0]; i = 1. 4855 :aeo_load_arg0_to_acc 4856 la_a0 &aeo_args 4857 ld_a1,a0,0 4858 ld_a2,a1,0 4859 la_a0 &aeo_acc 4860 st_a2,a0,0 4861 li_t0 %1 %0 4862 la_a0 &aeo_i 4863 st_t0,a0,0 4864 ret 4865 4866 ## skip_expr_newlines(a0=pos, a1=end) -> a0 = new pos. Leaf. 4867 ## Advance pos past consecutive TOK_NEWLINE tokens so expressions may span 4868 ## lines. Also used by directive header parsers to make whitespace 4869 ## (newlines specifically) insignificant inside %macro/%struct/%frame 4870 ## headers and around `##` paste operands. 4871 :skip_expr_newlines 4872 :sen_loop 4873 # if (pos == end) done 4874 la_br &sen_done 4875 beq_a0,a1 4876 # if (pos->kind != TOK_NEWLINE) done 4877 ld_t0,a0,0 4878 li_t1 TOK_NEWLINE 4879 la_br &sen_done 4880 bne_t0,t1 4881 # pos += 24 4882 addi_a0,a0,32 4883 la_br &sen_loop 4884 b 4885 :sen_done 4886 ret 4887 4888 ## proc_skip_newlines(): advance proc_pos past TOK_NEWLINE tokens, bounded by 4889 ## source_end. Convenience wrapper used by directive header parsers. 4890 :proc_skip_newlines 4891 :psn_loop 4892 la_a0 &proc_pos 4893 ld_t0,a0,0 4894 la_a1 &source_end 4895 ld_t1,a1,0 4896 la_br &psn_done 4897 beq_t0,t1 4898 ld_a2,t0,0 4899 li_a3 TOK_NEWLINE 4900 la_br &psn_done 4901 bne_a2,a3 4902 addi_t0,t0,32 4903 st_t0,a0,0 4904 la_br &psn_loop 4905 b 4906 :psn_done 4907 ret 4908 4909 ## eval_expr_atom(a0=tok, a1=limit) -> void 4910 ## Outputs via globals: 4911 ## eval_after_pos = token one past the consumed atom (or one past ')' for 4912 ## a macro atom) 4913 ## eval_value = the atom's i64 value 4914 ## 4915 ## If tok is a defined macro followed by TOK_LPAREN: expand_macro_tokens into 4916 ## the pool at mark = pool_used, recursively eval_expr_range over the new 4917 ## slice, require exactly one value (no trailing tokens), restore 4918 ## pool_used = mark, and set eval_after_pos = emt_after_pos. Otherwise 4919 ## parse_int_token(tok) and set eval_after_pos = tok + 24 bytes. 4920 ## 4921 ## CAVEAT: this path can recurse through eval_expr_range. Callers MUST 4922 ## snapshot eval_after_pos / eval_value into local stack slots (via 4923 ## enter_N) before any further call that might overwrite them. 4924 ## 4925 ## Stack-local layout (enter_40): 4926 ## sp+16 saved tok 4927 ## sp+24 saved limit 4928 ## sp+32 macro_ptr (find_macro result) 4929 ## sp+40 saved emt_after_pos 4930 ## sp+48 saved emt_mark 4931 :eval_expr_atom 4932 enter_40 4933 st_a0,sp,0 4934 st_a1,sp,8 4935 4936 # ---- tok eq "%local" with tight ( -> expand and recurse over body ---- 4937 # %local is a built-in (not a macro) but expands to an integer-yielding 4938 # token sequence, so eval_expr_atom must handle it before the find_macro 4939 # path. Validation (tight LPAREN, arg shape, frame_active, name lookup) 4940 # is centralized in expand_local_into_pool. 4941 ld_a0,sp,0 4942 la_a1 &const_local 4943 li_a2 %6 %0 4944 la_br &tok_eq_const 4945 call 4946 la_br &eea_skip_local 4947 beqz_a0 4948 4949 # Confirm tight LPAREN follows; otherwise treat %local as opaque text and 4950 # let the integer-atom path fail with a useful error. 4951 ld_t0,sp,0 4952 addi_t0,t0,32 4953 ld_t1,sp,8 4954 la_br &eea_skip_local 4955 blt_t1,t0 4956 la_br &eea_skip_local 4957 beq_t0,t1 4958 ld_a3,t0,0 4959 li_a2 TOK_LPAREN 4960 la_br &eea_skip_local 4961 bne_a3,a2 4962 ld_a3,t0,24 4963 la_br &eea_skip_local 4964 beqz_a3 4965 4966 # Dispatch to the local-expansion path. 4967 ld_a0,sp,0 4968 ld_a1,sp,8 4969 la_br &expand_local_into_pool 4970 call 4971 4972 # Snapshot elp_mark / elp_after before recursing. 4973 la_a0 &elp_after 4974 ld_t0,a0,0 4975 st_t0,sp,24 4976 la_a0 &elp_mark 4977 ld_t0,a0,0 4978 st_t0,sp,32 4979 4980 # If pool was not extended (pool_used == mark) -> bad expression. 4981 la_a0 &pool_used 4982 ld_t0,a0,0 4983 ld_t1,sp,32 4984 la_br &err_bad_macro_header 4985 beq_t0,t1 4986 4987 # eval_expr_range(expand_pool + mark, expand_pool + pool_used) 4988 la_a0 &expand_pool_ptr 4989 ld_a0,a0,0 4990 ld_t1,sp,32 4991 add_a0,a0,t1 4992 la_a1 &expand_pool_ptr 4993 ld_a1,a1,0 4994 la_a2 &pool_used 4995 ld_a2,a2,0 4996 add_a1,a1,a2 4997 la_br &eval_expr_range 4998 call 4999 5000 la_a1 &eval_value 5001 st_a0,a1,0 5002 5003 # restore pool_used = mark 5004 la_a0 &pool_used 5005 ld_t0,sp,32 5006 st_t0,a0,0 5007 5008 # eval_after_pos = saved elp_after 5009 la_a0 &eval_after_pos 5010 ld_t0,sp,24 5011 st_t0,a0,0 5012 5013 eret 5014 5015 :eea_skip_local 5016 # macro_ptr = find_macro(tok) 5017 ld_a0,sp,0 5018 la_br &find_macro 5019 call 5020 st_a0,sp,16 5021 5022 # if (macro_ptr == 0) -> integer atom branch 5023 la_br &eea_int_atom 5024 beqz_a0 5025 5026 # Paren-less 0-arg atom: 5027 # Take the macro-call branch if (tok+1 < limit AND (tok+1)->kind == TOK_LPAREN 5028 # AND (tok+1)->tight) OR macro->param_count == 0. Otherwise fall through 5029 # to int atom (unchanged). 5030 ld_t0,sp,0 5031 addi_t0,t0,32 5032 ld_t1,sp,8 5033 la_br &eea_check_zero_arg 5034 blt_t1,t0 5035 la_br &eea_check_zero_arg 5036 beq_t0,t1 5037 ld_t2,t0,0 5038 li_a3 TOK_LPAREN 5039 la_br &eea_check_zero_arg 5040 bne_t2,a3 5041 ld_t2,t0,24 5042 la_br &eea_check_zero_arg 5043 beqz_t2 5044 la_br &eea_do_macro 5045 b 5046 5047 :eea_check_zero_arg 5048 # No trailing LPAREN. Take the macro branch only if param_count == 0. 5049 ld_t0,sp,16 5050 ld_t1,t0,16 5051 la_br &eea_int_atom 5052 bnez_t1 5053 5054 :eea_do_macro 5055 # Macro call branch: 5056 # expand_macro_tokens(tok, limit, macro_ptr) 5057 ld_a0,sp,0 5058 ld_a1,sp,8 5059 ld_a2,sp,16 5060 la_br &expand_macro_tokens 5061 call 5062 5063 # Snapshot emt outputs immediately. 5064 la_a0 &emt_after_pos 5065 ld_t0,a0,0 5066 st_t0,sp,24 5067 la_a0 &emt_mark 5068 ld_t0,a0,0 5069 st_t0,sp,32 5070 5071 # If pool was not extended (pool_used == mark) -> bad expression. 5072 la_a0 &pool_used 5073 ld_t0,a0,0 5074 ld_t1,sp,32 5075 la_br &err_bad_macro_header 5076 beq_t0,t1 5077 5078 # eval_expr_range(expand_pool + mark, expand_pool + pool_used) 5079 la_a0 &expand_pool_ptr 5080 ld_a0,a0,0 5081 ld_t1,sp,32 5082 add_a0,a0,t1 5083 la_a1 &expand_pool_ptr 5084 ld_a1,a1,0 5085 la_a2 &pool_used 5086 ld_a2,a2,0 5087 add_a1,a1,a2 5088 la_br &eval_expr_range 5089 call 5090 5091 # eval_value = result 5092 la_a1 &eval_value 5093 st_a0,a1,0 5094 5095 # restore pool_used = mark 5096 la_a0 &pool_used 5097 ld_t0,sp,32 5098 st_t0,a0,0 5099 5100 # eval_after_pos = saved emt_after_pos 5101 la_a0 &eval_after_pos 5102 ld_t0,sp,24 5103 st_t0,a0,0 5104 5105 eret 5106 5107 :eea_int_atom 5108 # parse_int_token(tok) -> i64 5109 ld_a0,sp,0 5110 la_br &parse_int_token 5111 call 5112 la_a1 &eval_value 5113 st_a0,a1,0 5114 5115 # eval_after_pos = tok + 24 5116 ld_t0,sp,0 5117 addi_t0,t0,32 5118 la_a0 &eval_after_pos 5119 st_t0,a0,0 5120 5121 eret 5122 5123 ## eval_expr_range(a0=start_tok, a1=end_tok) -> a0 = i64 result (fatal on bad) 5124 ## Main S-expression evaluator loop, driven by the explicit ExprFrame stack 5125 ## in expr_frames[] / expr_frame_top — NOT by P1 recursion (eval_expr_atom 5126 ## can re-enter eval_expr_range through expand_macro_tokens, and a P1 5127 ## recursion would defeat the bounded frame budget). Enforces exactly one 5128 ## top-level value and no trailing tokens. 5129 ## Fatal on: unmatched parens, > 16 frames deep, > 16 args per frame, 5130 ## bad atom, bad operator. 5131 ## Reads/writes: expr_frames, expr_frame_top. 5132 ## 5133 ## Stack-local layout (enter_56): 5134 ## sp+16 pos Token* 5135 ## sp+24 end Token* 5136 ## sp+32 value i64 (most recent atom or rparen result) 5137 ## sp+40 result i64 (set when have_result transitions to 1) 5138 ## sp+48 have_value 0/1 5139 ## sp+56 have_result 0/1 5140 ## sp+64 entry_frame_top i64 (snapshot at entry; restored on exit; 5141 ## used as the local base for stack checks) 5142 :eval_expr_range 5143 enter_56 5144 st_a0,sp,0 5145 st_a1,sp,8 5146 li_t0 %0 %0 5147 st_t0,sp,16 5148 st_t0,sp,24 5149 st_t0,sp,32 5150 st_t0,sp,40 5151 # entry_frame_top = expr_frame_top 5152 la_a0 &expr_frame_top 5153 ld_t0,a0,0 5154 st_t0,sp,48 5155 5156 :eer_loop 5157 # If have_value, deliver it. 5158 ld_t0,sp,32 5159 la_br &eer_no_have_value 5160 beqz_t0 5161 5162 # have_value: feed into top frame, or set result. 5163 la_a0 &expr_frame_top 5164 ld_t0,a0,0 5165 ld_t1,sp,48 5166 la_br &eer_set_result 5167 beq_t0,t1 5168 # frame = &expr_frames[frame_top - 1] 5169 addi_t0,t0,neg1 5170 li_a1 M1PP_EXPR_FRAME_SIZE 5171 mul_t0,t0,a1 5172 la_a0 &expr_frames_ptr 5173 ld_a0,a0,0 5174 add_a0,a0,t0 5175 # if (frame->argc >= MAX_PARAMS) fatal 5176 li_a1 M1PP_EXPR_ARGC_OFF 5177 add_a1,a0,a1 5178 ld_t1,a1,0 5179 li_a2 M1PP_MAX_PARAMS 5180 la_br &err_bad_macro_header 5181 blt_a2,t1 5182 la_br &err_bad_macro_header 5183 beq_t1,a2 5184 # frame->args[argc] = value 5185 li_a2 M1PP_EXPR_ARGS_OFF 5186 add_a3,a0,a2 5187 shli_a2,t1,3 5188 add_a3,a3,a2 5189 ld_t2,sp,16 5190 st_t2,a3,0 5191 # frame->argc++ 5192 addi_t1,t1,1 5193 st_t1,a1,0 5194 # have_value = 0 5195 li_t0 %0 %0 5196 st_t0,sp,32 5197 la_br &eer_loop 5198 b 5199 5200 :eer_set_result 5201 # No frame open; this value is the top-level result. 5202 ld_t0,sp,40 5203 la_br &err_bad_macro_header 5204 bnez_t0 5205 ld_t0,sp,16 5206 st_t0,sp,24 5207 li_t0 %1 %0 5208 st_t0,sp,40 5209 li_t0 %0 %0 5210 st_t0,sp,32 5211 la_br &eer_loop 5212 b 5213 5214 :eer_no_have_value 5215 # skip_expr_newlines(pos, end) 5216 ld_a0,sp,0 5217 ld_a1,sp,8 5218 la_br &skip_expr_newlines 5219 call 5220 st_a0,sp,0 5221 5222 # if (pos >= end) break 5223 ld_t0,sp,0 5224 ld_t1,sp,8 5225 la_br &eer_loop_done 5226 beq_t0,t1 5227 5228 # Dispatch on token kind. 5229 ld_t2,t0,0 5230 li_a3 TOK_LPAREN 5231 la_br &eer_lparen 5232 beq_t2,a3 5233 li_a3 TOK_RPAREN 5234 la_br &eer_rparen 5235 beq_t2,a3 5236 5237 # atom: eval_expr_atom(pos, end); value = eval_value; pos = eval_after_pos 5238 ld_a0,sp,0 5239 ld_a1,sp,8 5240 la_br &eval_expr_atom 5241 call 5242 la_a0 &eval_value 5243 ld_t0,a0,0 5244 st_t0,sp,16 5245 la_a0 &eval_after_pos 5246 ld_t0,a0,0 5247 st_t0,sp,0 5248 li_t0 %1 %0 5249 st_t0,sp,32 5250 la_br &eer_loop 5251 b 5252 5253 :eer_lparen 5254 # pos++ 5255 addi_t0,t0,32 5256 st_t0,sp,0 5257 # skip_expr_newlines 5258 ld_a0,sp,0 5259 ld_a1,sp,8 5260 la_br &skip_expr_newlines 5261 call 5262 st_a0,sp,0 5263 # if (pos >= end) fatal 5264 ld_t0,sp,0 5265 ld_t1,sp,8 5266 la_br &err_bad_macro_header 5267 beq_t0,t1 5268 # op = expr_op_code(pos) 5269 ld_a0,sp,0 5270 la_br &expr_op_code 5271 call 5272 # if (op == EXPR_INVALID) fatal 5273 li_t0 EXPR_INVALID 5274 la_br &err_bad_macro_header 5275 beq_a0,t0 5276 # if (op == EXPR_STRLEN) handle inline — strlen's argument is a 5277 # TOK_STRING atom, not a recursive expression. Yield text.len - 2. 5278 li_t0 EXPR_STRLEN 5279 la_br &eer_strlen 5280 beq_a0,t0 5281 # frame stack overflow check: if (expr_frame_top >= 16) fatal 5282 # (the global expr_frames[] array has 16 slots, shared across recursive 5283 # eval_expr_range calls) 5284 la_a1 &expr_frame_top 5285 ld_t0,a1,0 5286 li_a2 M1PP_MAX_PARAMS 5287 la_br &err_bad_macro_header 5288 blt_a2,t0 5289 la_br &err_bad_macro_header 5290 beq_t0,a2 5291 # frames[frame_top].op = op; frames[frame_top].argc = 0 5292 li_a2 M1PP_EXPR_FRAME_SIZE 5293 mul_t2,t0,a2 5294 la_a3 &expr_frames_ptr 5295 ld_a3,a3,0 5296 add_a3,a3,t2 5297 st_a0,a3,0 5298 li_a2 M1PP_EXPR_ARGC_OFF 5299 add_a2,a3,a2 5300 li_t2 %0 %0 5301 st_t2,a2,0 5302 # frame_top++ 5303 addi_t0,t0,1 5304 st_t0,a1,0 5305 # pos++ (skip operator token) 5306 ld_t0,sp,0 5307 addi_t0,t0,32 5308 st_t0,sp,0 5309 la_br &eer_loop 5310 b 5311 5312 :eer_rparen 5313 # if (frame_top <= entry_frame_top) fatal 5314 la_a0 &expr_frame_top 5315 ld_t0,a0,0 5316 ld_t1,sp,48 5317 la_br &err_bad_macro_header 5318 beq_t0,t1 5319 la_br &err_bad_macro_header 5320 blt_t0,t1 5321 # frame = &expr_frames[frame_top - 1] 5322 addi_t0,t0,neg1 5323 li_a1 M1PP_EXPR_FRAME_SIZE 5324 mul_t0,t0,a1 5325 la_a3 &expr_frames_ptr 5326 ld_a3,a3,0 5327 add_a3,a3,t0 5328 # apply_expr_op(op, args, argc) -> a0 5329 ld_a0,a3,0 5330 li_a1 M1PP_EXPR_ARGS_OFF 5331 add_a1,a3,a1 5332 li_a2 M1PP_EXPR_ARGC_OFF 5333 add_a2,a3,a2 5334 ld_a2,a2,0 5335 la_br &apply_expr_op 5336 call 5337 # value = result; frame_top--; pos++; have_value = 1 5338 st_a0,sp,16 5339 la_a1 &expr_frame_top 5340 ld_t0,a1,0 5341 addi_t0,t0,neg1 5342 st_t0,a1,0 5343 ld_t0,sp,0 5344 addi_t0,t0,32 5345 st_t0,sp,0 5346 li_t0 %1 %0 5347 st_t0,sp,32 5348 la_br &eer_loop 5349 b 5350 5351 :eer_strlen 5352 # (strlen "literal") — degenerate unary op whose argument is a 5353 # TOK_STRING atom, not a recursive expression. 5354 # pos++ past the "strlen" operator word. 5355 ld_t0,sp,0 5356 addi_t0,t0,32 5357 st_t0,sp,0 5358 # skip_expr_newlines(pos, end) 5359 ld_a0,sp,0 5360 ld_a1,sp,8 5361 la_br &skip_expr_newlines 5362 call 5363 st_a0,sp,0 5364 # if (pos >= end) fatal 5365 ld_t0,sp,0 5366 ld_t1,sp,8 5367 la_br &err_bad_macro_header 5368 beq_t0,t1 5369 # if (pos->kind != TOK_STRING) fatal 5370 ld_t2,t0,0 5371 li_a3 TOK_STRING 5372 la_br &err_bad_macro_header 5373 bne_t2,a3 5374 # if (pos->text.len < 2) fatal 5375 ld_a1,t0,16 5376 li_a2 %2 %0 5377 la_br &err_bad_macro_header 5378 blt_a1,a2 5379 # if (pos->text.ptr[0] != '"') fatal — rejects single-quoted '..' hex 5380 ld_a2,t0,8 5381 lb_a3,a2,0 5382 li_a0 %34 %0 5383 la_br &err_bad_macro_header 5384 bne_a3,a0 5385 # value = pos->text.len - 2 5386 addi_a1,a1,neg2 5387 st_a1,sp,16 5388 # pos++ 5389 addi_t0,t0,32 5390 st_t0,sp,0 5391 # skip_expr_newlines(pos, end) 5392 ld_a0,sp,0 5393 ld_a1,sp,8 5394 la_br &skip_expr_newlines 5395 call 5396 st_a0,sp,0 5397 # if (pos >= end) fatal 5398 ld_t0,sp,0 5399 ld_t1,sp,8 5400 la_br &err_bad_macro_header 5401 beq_t0,t1 5402 # if (pos->kind != TOK_RPAREN) fatal 5403 ld_t2,t0,0 5404 li_a3 TOK_RPAREN 5405 la_br &err_bad_macro_header 5406 bne_t2,a3 5407 # pos++ 5408 addi_t0,t0,32 5409 st_t0,sp,0 5410 # have_value = 1 5411 li_t0 %1 %0 5412 st_t0,sp,32 5413 la_br &eer_loop 5414 b 5415 5416 :eer_loop_done 5417 # frame_top must equal entry_frame_top 5418 la_a0 &expr_frame_top 5419 ld_t0,a0,0 5420 ld_t1,sp,48 5421 la_br &err_bad_macro_header 5422 bne_t0,t1 5423 # have_result must be 1 5424 ld_t0,sp,40 5425 la_br &err_bad_macro_header 5426 beqz_t0 5427 # pos must equal end 5428 ld_t0,sp,0 5429 ld_t1,sp,8 5430 la_br &err_bad_macro_header 5431 bne_t0,t1 5432 # return result 5433 ld_a0,sp,24 5434 eret 5435 5436 ## ============================================================================ 5437 ## --- Hex emit for !@%$ ------------------------------------------------------ 5438 ## ============================================================================ 5439 5440 ## emit_hex_value(a0=value_u64, a1=byte_count) -> void (fatal on overflow) 5441 ## byte_count must be 1, 2, 4, or 8. Serialize value into (2 * byte_count) 5442 ## uppercase hex chars, little-endian byte order (byte i at char indices 5443 ## 2i, 2i+1) as bare hex digits. hex2pp's byte-stream parser groups every 5444 ## two hex digits into one byte; no quoting or separators are needed. 5445 ## Total emitted text length = 2 * byte_count; emitted as a TOK_WORD via 5446 ## append_text + emit_token. 5447 :emit_hex_value 5448 enter_0 5449 5450 # ehv_value = value; ehv_bytes = byte_count 5451 la_a2 &ehv_value 5452 st_a0,a2,0 5453 la_a2 &ehv_bytes 5454 st_a1,a2,0 5455 5456 # i = 0 5457 li_t0 %0 %0 5458 :emit_hex_value_loop 5459 # if (i == bytes) done 5460 la_a1 &ehv_bytes 5461 ld_t1,a1,0 5462 la_br &emit_hex_value_emit 5463 beq_t0,t1 5464 5465 # byte = ehv_value & 0xFF 5466 la_a1 &ehv_value 5467 ld_t2,a1,0 5468 andi_a3,t2,255 5469 5470 # high = (byte >> 4) & 0x0F (byte is already in a3) 5471 shri_a2,a3,4 5472 andi_a2,a2,15 5473 5474 # low = byte & 0x0F 5475 andi_a3,a3,15 5476 5477 # scratch[2*i] = hex_chars[high] 5478 la_a1 &hex_chars 5479 add_a1,a1,a2 5480 lb_a2,a1,0 5481 la_a1 &ehv_scratch 5482 shli_a3,t0,1 5483 add_a1,a1,a3 5484 sb_a2,a1,0 5485 5486 # scratch[2*i+1] = hex_chars[low] (reload low from byte & 0x0F) 5487 la_a1 &ehv_value 5488 ld_t2,a1,0 5489 andi_a3,t2,255 5490 andi_a3,a3,15 5491 la_a1 &hex_chars 5492 add_a1,a1,a3 5493 lb_a2,a1,0 5494 la_a1 &ehv_scratch 5495 shli_a3,t0,1 5496 add_a1,a1,a3 5497 addi_a1,a1,1 5498 sb_a2,a1,0 5499 5500 # ehv_value >>= 8 5501 la_a1 &ehv_value 5502 ld_t2,a1,0 5503 shri_t2,t2,8 5504 st_t2,a1,0 5505 5506 # i++ 5507 addi_t0,t0,1 5508 la_br &emit_hex_value_loop 5509 b 5510 5511 :emit_hex_value_emit 5512 # text_ptr = append_text(&ehv_scratch, 2 * ehv_bytes) 5513 la_a0 &ehv_scratch 5514 la_a1 &ehv_bytes 5515 ld_a1,a1,0 5516 shli_a1,a1,1 5517 la_br &append_text 5518 call 5519 5520 # ehv_token.kind = TOK_WORD; ehv_token.text_ptr = text_ptr; 5521 # ehv_token.text_len = 2 * ehv_bytes; ehv_token.tight = 0. 5522 la_a2 &ehv_token 5523 li_a3 TOK_WORD 5524 st_a3,a2,0 5525 st_a0,a2,8 5526 la_a1 &ehv_bytes 5527 ld_a1,a1,0 5528 shli_a1,a1,1 5529 st_a1,a2,16 5530 li_a1 %0 %0 5531 st_a1,a2,24 5532 5533 # emit_token(&ehv_token) 5534 la_a0 &ehv_token 5535 la_br &emit_token 5536 call 5537 5538 eret 5539 5540 ## ============================================================================ 5541 ## --- Builtin dispatcher ( ! @ % $ %select %str %local ) -------------------- 5542 ## ============================================================================ 5543 5544 ## expand_builtin_call(a0=stream_ptr, a1=builtin_tok) -> void (fatal on bad) 5545 ## Requires builtin_tok+1 is TOK_LPAREN. Runs parse_args(lparen, stream->end), 5546 ## then dispatches on builtin_tok->text: 5547 ## 5548 ## "!" "@" "%" "$" 5549 ## require arg_count == 1 5550 ## eval_expr_range(arg_starts[0], arg_ends[0]) -> value 5551 ## stream->pos = call_end_pos; stream->line_start = 0 5552 ## emit_hex_value(value, 1 / 2 / 4 / 8 respectively) 5553 ## 5554 ## "%select" 5555 ## require arg_count == 3 5556 ## eval_expr_range(cond_arg) -> value 5557 ## chosen = (value != 0) ? arg1 : arg2 5558 ## stream->pos = call_end_pos; stream->line_start = 0 5559 ## if chosen is empty, return (no stream push) 5560 ## else copy_span_to_pool(chosen) and push_pool_stream_from_mark(mark) 5561 ## The unchosen branch is NOT evaluated, validated, or expanded. 5562 ## 5563 ## Any other text under a builtin slot -> fatal "bad builtin". 5564 ## expand_local_into_pool(a0=call_tok, a1=limit) -> writes elp_after, elp_mark 5565 ## Resolve %local(NAME) against the current frame: assemble the lookup key 5566 ## "<frame>_FRAME.<NAME>" in local_lookup_scratch, linear-search macros[] 5567 ## for that name, and copy the matching body into the pool. Errors: 5568 ## - call_tok+1 missing / not tight LPAREN: bad_macro_header 5569 ## - parse_args fails: propagated 5570 ## - arg_count != 1, arg span != 1 token, arg kind != WORD: bad_macro_header 5571 ## - frame not active: local_outside_frame 5572 ## - assembled name >= 256 bytes: local_name_too_long 5573 ## - no matching macro: unknown_local 5574 ## 5575 ## On success, elp_mark = pool_used at entry, elp_after = call_end_pos 5576 ## (Token* one past the call's `)`). Both expand_builtin_call's %local 5577 ## branch and eval_expr_atom's %local branch consume those. 5578 :expand_local_into_pool 5579 enter_0 5580 5581 # --- Validate (call_tok+1) is a tight LPAREN within the stream. --- 5582 addi_t0,a0,32 # lparen = call_tok + 32 5583 la_br &err_bad_macro_header 5584 blt_a1,t0 5585 la_br &err_bad_macro_header 5586 beq_t0,a1 5587 ld_a2,t0,0 5588 li_a3 TOK_LPAREN 5589 la_br &err_bad_macro_header 5590 bne_a2,a3 5591 ld_a2,t0,24 5592 la_br &err_bad_macro_header 5593 beqz_a2 5594 5595 # --- parse_args(lparen, limit) --- 5596 mov_a0,t0 5597 la_br &parse_args 5598 call 5599 5600 # --- Validate arg shape: arg_count == 1, single 32-byte token, WORD kind. --- 5601 la_a0 &arg_count 5602 ld_t0,a0,0 5603 li_t1 %1 %0 5604 la_br &err_bad_macro_header 5605 bne_t0,t1 5606 5607 la_a0 &arg_starts_ptr 5608 ld_a0,a0,0 5609 ld_t0,a0,0 # arg_tok = arg_starts[0] 5610 la_a1 &arg_ends_ptr 5611 ld_a1,a1,0 5612 ld_t1,a1,0 # arg_end = arg_ends[0] 5613 sub_t2,t1,t0 5614 li_a2 %32 %0 5615 la_br &err_bad_macro_header 5616 bne_t2,a2 5617 5618 ld_a3,t0,0 # arg_tok->kind 5619 li_a2 TOK_WORD 5620 la_br &err_bad_macro_header 5621 bne_a3,a2 5622 5623 # Stash arg.text.ptr / arg.text.len for the byte-copy loop below. 5624 ld_a0,t0,8 5625 la_a1 &elp_arg_ptr 5626 st_a0,a1,0 5627 ld_a0,t0,16 5628 la_a1 &elp_arg_len 5629 st_a0,a1,0 5630 5631 # --- frame_active? --- 5632 la_a1 &frame_active 5633 ld_a2,a1,0 5634 la_br &err_local_outside_frame 5635 beqz_a2 5636 5637 # --- name_len = current_frame_len + 7 + arg_len; must be < 256. --- 5638 la_a1 ¤t_frame_len 5639 ld_a2,a1,0 5640 la_a1 &elp_arg_len 5641 ld_a3,a1,0 5642 add_t0,a2,a3 5643 addi_t0,t0,7 5644 la_a1 &elp_name_len 5645 st_t0,a1,0 5646 li_t1 %256 %0 5647 la_br &err_local_name_too_long 5648 blt_t1,t0 5649 la_br &err_local_name_too_long 5650 beq_t0,t1 5651 5652 # --- Build lookup name in local_lookup_scratch. --- 5653 # First: copy current_frame_ptr[0..frame_len] -> scratch[0..] 5654 la_a0 ¤t_frame_ptr 5655 ld_t0,a0,0 # frame_ptr 5656 la_a0 ¤t_frame_len 5657 ld_t1,a0,0 # frame_len 5658 la_t2 &local_lookup_scratch_ptr 5659 ld_t2,t2,0 # scratch_base 5660 li_a3 %0 %0 5661 :elp_copy_frame 5662 la_br &elp_copy_frame_done 5663 beq_a3,t1 5664 add_a0,t0,a3 5665 lb_a0,a0,0 5666 add_a1,t2,a3 5667 sb_a0,a1,0 5668 addi_a3,a3,1 5669 la_br &elp_copy_frame 5670 b 5671 :elp_copy_frame_done 5672 5673 # Advance scratch cursor to scratch + frame_len for the suffix copy. 5674 add_t2,t2,t1 5675 5676 # Copy const_frame_suffix (7 bytes "_FRAME.") -> scratch[frame_len..] 5677 la_a0 &const_frame_suffix 5678 li_t1 %7 %0 5679 li_a3 %0 %0 5680 :elp_copy_suffix 5681 la_br &elp_copy_suffix_done 5682 beq_a3,t1 5683 add_a1,a0,a3 5684 lb_a1,a1,0 5685 add_t0,t2,a3 5686 sb_a1,t0,0 5687 addi_a3,a3,1 5688 la_br &elp_copy_suffix 5689 b 5690 :elp_copy_suffix_done 5691 5692 # Advance past suffix (7 bytes). 5693 addi_t2,t2,7 5694 5695 # Copy arg bytes -> scratch[frame_len + 7 ..] 5696 la_a0 &elp_arg_ptr 5697 ld_t0,a0,0 5698 la_a0 &elp_arg_len 5699 ld_t1,a0,0 5700 li_a3 %0 %0 5701 :elp_copy_arg 5702 la_br &elp_copy_arg_done 5703 beq_a3,t1 5704 add_a0,t0,a3 5705 lb_a0,a0,0 5706 add_a1,t2,a3 5707 sb_a0,a1,0 5708 addi_a3,a3,1 5709 la_br &elp_copy_arg 5710 b 5711 :elp_copy_arg_done 5712 5713 # --- Linear search macros[] for an exact name match. --- 5714 # m (a3) walks from macros_ptr to macros_end (each MACRO_RECORD_SIZE). 5715 # Match criterion: m->name.len == name_len AND first name_len bytes of 5716 # m->name.ptr equal local_lookup_scratch. Modeled on find_macro: keep 5717 # m in a3, reload macros_end into t0 after each iteration, and use 5718 # a0/a1/a2/t1/t2 as scratch within the inner byte-compare. 5719 la_a3 ¯os_ptr 5720 ld_a3,a3,0 5721 la_t0 ¯os_end 5722 ld_t0,t0,0 5723 :elp_search_loop 5724 la_br &elp_unknown 5725 beq_a3,t0 5726 5727 # m->name.len == name_len? 5728 ld_t1,a3,8 5729 la_a0 &elp_name_len 5730 ld_a2,a0,0 5731 la_br &elp_search_next 5732 bne_t1,a2 5733 5734 # byte-compare m->name.ptr vs scratch for name_len bytes. 5735 ld_t1,a3,0 # name_ptr 5736 la_a0 &local_lookup_scratch_ptr 5737 ld_a1,a0,0 # lookup_ptr 5738 li_t2 %0 %0 5739 :elp_search_cmp 5740 la_br &elp_search_match 5741 beq_t2,a2 5742 add_a0,t1,t2 5743 lb_a0,a0,0 5744 add_t0,a1,t2 5745 lb_t0,t0,0 5746 la_br &elp_search_next 5747 bne_a0,t0 5748 addi_t2,t2,1 5749 la_br &elp_search_cmp 5750 b 5751 5752 :elp_search_next 5753 li_t1 M1PP_MACRO_RECORD_SIZE 5754 add_a3,a3,t1 5755 la_t0 ¯os_end 5756 ld_t0,t0,0 5757 la_br &elp_search_loop 5758 b 5759 5760 :elp_unknown 5761 la_br &err_unknown_local 5762 b 5763 5764 :elp_search_match 5765 # a3 = matched macro pointer. mark = pool_used; copy body span. 5766 la_a0 &pool_used 5767 ld_t0,a0,0 5768 la_a1 &elp_mark 5769 st_t0,a1,0 5770 5771 li_t0 M1PP_MACRO_BODY_START_OFF 5772 add_t0,a3,t0 5773 ld_a0,t0,0 # body_start 5774 li_t1 M1PP_MACRO_BODY_END_OFF 5775 add_t1,a3,t1 5776 ld_a1,t1,0 # body_end 5777 la_br ©_span_to_pool 5778 call 5779 5780 # elp_after = call_end_pos 5781 la_a0 &call_end_pos 5782 ld_t0,a0,0 5783 la_a1 &elp_after 5784 st_t0,a1,0 5785 5786 eret 5787 5788 :expand_builtin_call 5789 enter_0 5790 5791 # ebc_stream = stream_ptr; also stash builtin_tok via a register reload path 5792 la_a2 &ebc_stream 5793 st_a0,a2,0 5794 5795 # lparen = builtin_tok + 24; if (lparen >= stream->end) fatal 5796 addi_t0,a1,32 5797 ld_t1,a0,8 # stream->end 5798 la_br &err_bad_macro_header 5799 beq_t0,t1 5800 la_br &err_bad_macro_header 5801 blt_t1,t0 5802 5803 # if (lparen->kind != TOK_LPAREN) fatal 5804 ld_a3,t0,0 5805 li_a2 TOK_LPAREN 5806 la_br &err_bad_macro_header 5807 bne_a3,a2 5808 5809 # parse_args(lparen, stream->end) 5810 mov_a0,t0 5811 la_a2 &ebc_stream 5812 ld_a2,a2,0 5813 ld_a1,a2,8 # stream->end 5814 la_br &parse_args 5815 call 5816 5817 # snapshot call_end_pos -> ebc_call_end_pos 5818 la_a0 &call_end_pos 5819 ld_t0,a0,0 5820 la_a1 &ebc_call_end_pos 5821 st_t0,a1,0 5822 5823 # dispatch on builtin_tok->text. a1 (builtin_tok) is gone after parse_args, 5824 # but stream->pos still points at the builtin token (we don't advance it 5825 # until the dispatched branch sets stream->pos = call_end_pos), so reload 5826 # builtin_tok from stream->pos. 5827 la_a0 &ebc_stream 5828 ld_a0,a0,0 5829 ld_t0,a0,16 # stream->pos -> builtin_tok 5830 5831 # if tok_eq_const(tok, "!", 1) -> bytes=1 5832 mov_a0,t0 5833 la_a1 &const_bang 5834 li_a2 %1 %0 5835 la_br &tok_eq_const 5836 call 5837 la_br &ebc_arg_set_1 5838 bnez_a0 5839 5840 # if tok_eq_const(tok, "@", 1) -> bytes=2 5841 la_a0 &ebc_stream 5842 ld_a0,a0,0 5843 ld_a0,a0,16 5844 la_a1 &const_at 5845 li_a2 %1 %0 5846 la_br &tok_eq_const 5847 call 5848 la_br &ebc_arg_set_2 5849 bnez_a0 5850 5851 # if tok_eq_const(tok, "%", 1) -> bytes=4 5852 la_a0 &ebc_stream 5853 ld_a0,a0,0 5854 ld_a0,a0,16 5855 la_a1 &const_pct 5856 li_a2 %1 %0 5857 la_br &tok_eq_const 5858 call 5859 la_br &ebc_arg_set_4 5860 bnez_a0 5861 5862 # if tok_eq_const(tok, "$", 1) -> bytes=8 5863 la_a0 &ebc_stream 5864 ld_a0,a0,0 5865 ld_a0,a0,16 5866 la_a1 &const_dlr 5867 li_a2 %1 %0 5868 la_br &tok_eq_const 5869 call 5870 la_br &ebc_arg_set_8 5871 bnez_a0 5872 5873 # if tok_eq_const(tok, "%select", 7) -> select path 5874 la_a0 &ebc_stream 5875 ld_a0,a0,0 5876 ld_a0,a0,16 5877 la_a1 &const_select 5878 li_a2 %7 %0 5879 la_br &tok_eq_const 5880 call 5881 la_br &ebc_select 5882 bnez_a0 5883 5884 # if tok_eq_const(tok, "%str", 4) -> str path 5885 la_a0 &ebc_stream 5886 ld_a0,a0,0 5887 ld_a0,a0,16 5888 la_a1 &const_str 5889 li_a2 %4 %0 5890 la_br &tok_eq_const 5891 call 5892 la_br &ebc_str 5893 bnez_a0 5894 5895 # if tok_eq_const(tok, "%local", 6) -> local path 5896 la_a0 &ebc_stream 5897 ld_a0,a0,0 5898 ld_a0,a0,16 5899 la_a1 &const_local 5900 li_a2 %6 %0 5901 la_br &tok_eq_const 5902 call 5903 la_br &ebc_local 5904 bnez_a0 5905 5906 # else: fatal 5907 la_br &err_bad_macro_header 5908 b 5909 5910 :ebc_arg_set_1 5911 li_a0 %1 %0 5912 la_a1 &ebc_bytes 5913 st_a0,a1,0 5914 la_br &ebc_arg_path 5915 b 5916 :ebc_arg_set_2 5917 li_a0 %2 %0 5918 la_a1 &ebc_bytes 5919 st_a0,a1,0 5920 la_br &ebc_arg_path 5921 b 5922 :ebc_arg_set_4 5923 li_a0 %4 %0 5924 la_a1 &ebc_bytes 5925 st_a0,a1,0 5926 la_br &ebc_arg_path 5927 b 5928 :ebc_arg_set_8 5929 li_a0 %8 %0 5930 la_a1 &ebc_bytes 5931 st_a0,a1,0 5932 la_br &ebc_arg_path 5933 b 5934 5935 :ebc_arg_path 5936 # require arg_count == 1 5937 la_a0 &arg_count 5938 ld_t0,a0,0 5939 li_t1 %1 %0 5940 la_br &err_bad_macro_header 5941 bne_t0,t1 5942 5943 # snapshot arg_starts[0], arg_ends[0] 5944 la_a0 &arg_starts_ptr 5945 ld_a0,a0,0 5946 ld_t0,a0,0 5947 la_a1 &ebc_arg0_start 5948 st_t0,a1,0 5949 la_a0 &arg_ends_ptr 5950 ld_a0,a0,0 5951 ld_t0,a0,0 5952 la_a1 &ebc_arg0_end 5953 st_t0,a1,0 5954 5955 # value = eval_expr_range(arg0_start, arg0_end) 5956 la_a0 &ebc_arg0_start 5957 ld_a0,a0,0 5958 la_a1 &ebc_arg0_end 5959 ld_a1,a1,0 5960 la_br &eval_expr_range 5961 call 5962 5963 # ebc_value = a0 5964 la_a1 &ebc_value 5965 st_a0,a1,0 5966 5967 # stream->pos = ebc_call_end_pos; stream->line_start = 0 5968 la_a0 &ebc_stream 5969 ld_a0,a0,0 5970 la_a1 &ebc_call_end_pos 5971 ld_t0,a1,0 5972 st_t0,a0,16 5973 li_t1 %0 %0 5974 st_t1,a0,24 5975 5976 # emit_hex_value(ebc_value, ebc_bytes) 5977 la_a0 &ebc_value 5978 ld_a0,a0,0 5979 la_a1 &ebc_bytes 5980 ld_a1,a1,0 5981 la_br &emit_hex_value 5982 call 5983 5984 eret 5985 5986 :ebc_select 5987 # require arg_count == 3 5988 la_a0 &arg_count 5989 ld_t0,a0,0 5990 li_t1 %3 %0 5991 la_br &err_bad_macro_header 5992 bne_t0,t1 5993 5994 # snapshot arg_starts[0..2] / arg_ends[0..2] 5995 la_a0 &arg_starts_ptr 5996 ld_a0,a0,0 5997 ld_t0,a0,0 5998 la_a1 &ebc_arg0_start 5999 st_t0,a1,0 6000 la_a0 &arg_starts_ptr 6001 ld_a0,a0,0 6002 ld_t0,a0,8 6003 la_a1 &ebc_then_start 6004 st_t0,a1,0 6005 la_a0 &arg_starts_ptr 6006 ld_a0,a0,0 6007 ld_t0,a0,16 6008 la_a1 &ebc_else_start 6009 st_t0,a1,0 6010 6011 la_a0 &arg_ends_ptr 6012 ld_a0,a0,0 6013 ld_t0,a0,0 6014 la_a1 &ebc_arg0_end 6015 st_t0,a1,0 6016 la_a0 &arg_ends_ptr 6017 ld_a0,a0,0 6018 ld_t0,a0,8 6019 la_a1 &ebc_then_end 6020 st_t0,a1,0 6021 la_a0 &arg_ends_ptr 6022 ld_a0,a0,0 6023 ld_t0,a0,16 6024 la_a1 &ebc_else_end 6025 st_t0,a1,0 6026 6027 # value = eval_expr_range(arg0_start, arg0_end) 6028 la_a0 &ebc_arg0_start 6029 ld_a0,a0,0 6030 la_a1 &ebc_arg0_end 6031 ld_a1,a1,0 6032 la_br &eval_expr_range 6033 call 6034 6035 # if (value != 0) chosen = then; else chosen = else 6036 la_br &ebc_select_then 6037 bnez_a0 6038 6039 # chosen = else 6040 la_a0 &ebc_else_start 6041 ld_t0,a0,0 6042 la_a1 &ebc_arg0_start 6043 st_t0,a1,0 6044 la_a0 &ebc_else_end 6045 ld_t0,a0,0 6046 la_a1 &ebc_arg0_end 6047 st_t0,a1,0 6048 la_br &ebc_select_after_pick 6049 b 6050 6051 :ebc_select_then 6052 # chosen = then 6053 la_a0 &ebc_then_start 6054 ld_t0,a0,0 6055 la_a1 &ebc_arg0_start 6056 st_t0,a1,0 6057 la_a0 &ebc_then_end 6058 ld_t0,a0,0 6059 la_a1 &ebc_arg0_end 6060 st_t0,a1,0 6061 6062 :ebc_select_after_pick 6063 # stream->pos = ebc_call_end_pos; stream->line_start = 0 6064 la_a0 &ebc_stream 6065 ld_a0,a0,0 6066 la_a1 &ebc_call_end_pos 6067 ld_t0,a1,0 6068 st_t0,a0,16 6069 li_t1 %0 %0 6070 st_t1,a0,24 6071 6072 # if (chosen_start == chosen_end) return 6073 la_a0 &ebc_arg0_start 6074 ld_t0,a0,0 6075 la_a1 &ebc_arg0_end 6076 ld_t1,a1,0 6077 la_br &ebc_select_done 6078 beq_t0,t1 6079 6080 # mark = pool_used 6081 la_a0 &pool_used 6082 ld_t0,a0,0 6083 la_a1 &ebc_mark 6084 st_t0,a1,0 6085 6086 # copy_span_to_pool(chosen_start, chosen_end) 6087 la_a0 &ebc_arg0_start 6088 ld_a0,a0,0 6089 la_a1 &ebc_arg0_end 6090 ld_a1,a1,0 6091 la_br ©_span_to_pool 6092 call 6093 6094 # push_pool_stream_from_mark(mark) 6095 la_a0 &ebc_mark 6096 ld_a0,a0,0 6097 la_br &push_pool_stream_from_mark 6098 call 6099 6100 :ebc_select_done 6101 eret 6102 6103 ## %str(IDENT): stringify a single WORD argument into a TOK_STRING literal. 6104 ## Validation: arg_count == 1, arg span length == 1 token, and that token's 6105 ## kind is TOK_WORD. Output: a freshly-allocated text span built as 6106 ## `"` + arg.text + `"` (len = arg.text.len + 2) and a synthesized TOK_STRING 6107 ## pointing at it. Stream pos advances to call_end_pos; line_start = 0. 6108 :ebc_str 6109 # require arg_count == 1 6110 la_a0 &arg_count 6111 ld_t0,a0,0 6112 li_t1 %1 %0 6113 la_br &err_bad_macro_header 6114 bne_t0,t1 6115 6116 # snapshot arg_starts[0] / arg_ends[0] 6117 la_a0 &arg_starts_ptr 6118 ld_a0,a0,0 6119 ld_t0,a0,0 6120 la_a1 &ebc_arg0_start 6121 st_t0,a1,0 6122 la_a0 &arg_ends_ptr 6123 ld_a0,a0,0 6124 ld_t0,a0,0 6125 la_a1 &ebc_arg0_end 6126 st_t0,a1,0 6127 6128 # require arg0_end - arg0_start == 32 (exactly one token) 6129 la_a0 &ebc_arg0_start 6130 ld_t0,a0,0 6131 la_a1 &ebc_arg0_end 6132 ld_t1,a1,0 6133 sub_t2,t1,t0 6134 li_a2 %32 %0 6135 la_br &err_bad_macro_header 6136 bne_t2,a2 6137 6138 # require arg_tok->kind == TOK_WORD 6139 ld_a3,t0,0 6140 li_a2 TOK_WORD 6141 la_br &err_bad_macro_header 6142 bne_a3,a2 6143 6144 # orig_len = arg_tok->text.len; out_len = orig_len + 2 6145 # fatal if out_len > 256 (scratch cap; text_buf cap checked by append_text) 6146 ld_t1,t0,16 6147 la_a0 &ebc_str_orig_len 6148 st_t1,a0,0 6149 addi_t2,t1,2 6150 la_a0 &ebc_str_out_len 6151 st_t2,a0,0 6152 li_a1 %256 %0 6153 la_br &err_text_overflow 6154 blt_a1,t2 6155 6156 # scratch[0] = '"' 6157 la_t2 &ebc_str_scratch_ptr 6158 ld_t2,t2,0 6159 li_a3 %34 %0 6160 sb_a3,t2,0 6161 6162 # copy arg_tok->text bytes into scratch[1..1+orig_len) 6163 # src = arg_tok->text.ptr; i = 0 6164 la_a0 &ebc_arg0_start 6165 ld_a0,a0,0 6166 ld_t0,a0,8 6167 la_a1 &ebc_str_orig_len 6168 ld_t1,a1,0 6169 li_a0 %0 %0 6170 :ebc_str_copy_loop 6171 la_br &ebc_str_copy_done 6172 beq_a0,t1 6173 add_a1,t0,a0 6174 lb_a1,a1,0 6175 addi_a2,a0,1 6176 add_a2,t2,a2 6177 sb_a1,a2,0 6178 addi_a0,a0,1 6179 la_br &ebc_str_copy_loop 6180 b 6181 :ebc_str_copy_done 6182 6183 # scratch[1 + orig_len] = '"' 6184 la_t2 &ebc_str_scratch_ptr 6185 ld_t2,t2,0 6186 la_a1 &ebc_str_orig_len 6187 ld_a1,a1,0 6188 addi_a1,a1,1 6189 add_a0,t2,a1 6190 li_a3 %34 %0 6191 sb_a3,a0,0 6192 6193 # text_ptr = append_text(&scratch, out_len) 6194 la_a0 &ebc_str_scratch_ptr 6195 ld_a0,a0,0 6196 la_a1 &ebc_str_out_len 6197 ld_a1,a1,0 6198 la_br &append_text 6199 call 6200 6201 # ebc_str_token = { TOK_STRING, text_ptr, out_len, tight=0 } 6202 la_a2 &ebc_str_token 6203 li_a3 TOK_STRING 6204 st_a3,a2,0 6205 st_a0,a2,8 6206 la_a1 &ebc_str_out_len 6207 ld_a1,a1,0 6208 st_a1,a2,16 6209 li_a1 %0 %0 6210 st_a1,a2,24 6211 6212 # stream->pos = ebc_call_end_pos; stream->line_start = 0 6213 la_a0 &ebc_stream 6214 ld_a0,a0,0 6215 la_a1 &ebc_call_end_pos 6216 ld_t0,a1,0 6217 st_t0,a0,16 6218 li_t1 %0 %0 6219 st_t1,a0,24 6220 6221 # emit_token(&ebc_str_token) 6222 la_a0 &ebc_str_token 6223 la_br &emit_token 6224 call 6225 6226 eret 6227 6228 ## emit_string_as_bytes(a0=tok_ptr) -> void (fatal on bad escape). 6229 ## Decode the contents of a TOK_STRING (between the surrounding quotes) 6230 ## and emit each byte as one TOK_WORD via emit_hex_value(byte, 1). The 6231 ## lexer accepts both "..." and '...'; this routine just strips the 6232 ## first/last byte of text and decodes the middle. Recognised escapes: 6233 ## \n \t \r \0 \\ \" and \xNN (two hex digits, case-insensitive). 6234 ## hex2pp's parse_byte_stream coalesces the resulting space-separated 6235 ## hex bytes back into a contiguous byte sequence at link time. 6236 :emit_string_as_bytes 6237 enter_0 6238 6239 # require tok->text.len >= 2 6240 ld_a1,a0,16 6241 li_a2 %2 %0 6242 la_br &err_bad_escape 6243 blt_a1,a2 6244 6245 # src = tok->text.ptr + 1; src_len = tok->text.len - 2 6246 ld_a3,a0,8 6247 addi_a3,a3,1 6248 la_a0 &ebc_b_src_ptr 6249 st_a3,a0,0 6250 addi_a1,a1,neg2 6251 la_a0 &ebc_b_src_len 6252 st_a1,a0,0 6253 6254 # ebc_b_src_i = 0 6255 li_a0 %0 %0 6256 la_a1 &ebc_b_src_i 6257 st_a0,a1,0 6258 6259 :ebc_b_loop 6260 # if (src_i == src_len) done 6261 la_a0 &ebc_b_src_i 6262 ld_t0,a0,0 6263 la_a1 &ebc_b_src_len 6264 ld_t1,a1,0 6265 la_br &ebc_b_done 6266 beq_t0,t1 6267 6268 # c = src_ptr[src_i]; src_i++ 6269 # P1 lacks lb_a3,a0,0 — bounce through a1 (mov_a1,a0; lb_a3,a1,0). 6270 la_a0 &ebc_b_src_ptr 6271 ld_a0,a0,0 6272 add_a0,a0,t0 6273 mov_a1,a0 6274 lb_a3,a1,0 6275 addi_t0,t0,1 6276 la_a1 &ebc_b_src_i 6277 st_t0,a1,0 6278 6279 # if (c == '\\') -> escape path 6280 li_a2 %92 %0 6281 la_br &ebc_b_escape 6282 beq_a3,a2 6283 6284 # literal byte: emit_hex_value(c, 1) and reloop 6285 mov_a0,a3 6286 li_a1 %1 %0 6287 la_br &emit_hex_value 6288 call 6289 la_br &ebc_b_loop 6290 b 6291 6292 :ebc_b_escape 6293 # Read the escape character; require at least one byte left. 6294 la_a0 &ebc_b_src_i 6295 ld_t0,a0,0 6296 la_a1 &ebc_b_src_len 6297 ld_t1,a1,0 6298 la_br &err_bad_escape 6299 beq_t0,t1 6300 la_a0 &ebc_b_src_ptr 6301 ld_a0,a0,0 6302 add_a0,a0,t0 6303 mov_a1,a0 6304 lb_a3,a1,0 # a3 = e 6305 addi_t0,t0,1 6306 la_a1 &ebc_b_src_i 6307 st_t0,a1,0 6308 6309 # Single-char escapes: dispatch via beq chain (matches the existing 6310 # proc_check_<directive> pattern). Each branch loads the resulting 6311 # byte into a3 and falls through to ebc_b_emit_one. 6312 li_a2 %110 %0 # 'n' 6313 la_br &ebc_b_esc_n 6314 beq_a3,a2 6315 li_a2 %116 %0 # 't' 6316 la_br &ebc_b_esc_t 6317 beq_a3,a2 6318 li_a2 %114 %0 # 'r' 6319 la_br &ebc_b_esc_r 6320 beq_a3,a2 6321 li_a2 %48 %0 # '0' 6322 la_br &ebc_b_esc_zero 6323 beq_a3,a2 6324 li_a2 %92 %0 # '\\' 6325 la_br &ebc_b_esc_bs 6326 beq_a3,a2 6327 li_a2 %34 %0 # '"' 6328 la_br &ebc_b_esc_dq 6329 beq_a3,a2 6330 li_a2 %120 %0 # 'x' 6331 la_br &ebc_b_esc_hex 6332 beq_a3,a2 6333 la_br &err_bad_escape 6334 b 6335 6336 :ebc_b_esc_n 6337 li_a3 %10 %0 # 0x0A 6338 la_br &ebc_b_emit_one 6339 b 6340 :ebc_b_esc_t 6341 li_a3 %9 %0 # 0x09 6342 la_br &ebc_b_emit_one 6343 b 6344 :ebc_b_esc_r 6345 li_a3 %13 %0 # 0x0D 6346 la_br &ebc_b_emit_one 6347 b 6348 :ebc_b_esc_zero 6349 li_a3 %0 %0 # 0x00 6350 la_br &ebc_b_emit_one 6351 b 6352 :ebc_b_esc_bs 6353 li_a3 %92 %0 # 0x5C 6354 la_br &ebc_b_emit_one 6355 b 6356 :ebc_b_esc_dq 6357 li_a3 %34 %0 # 0x22 6358 la_br &ebc_b_emit_one 6359 b 6360 6361 :ebc_b_emit_one 6362 # Common tail for single-char escapes: emit_hex_value(a3, 1), reloop. 6363 mov_a0,a3 6364 li_a1 %1 %0 6365 la_br &emit_hex_value 6366 call 6367 la_br &ebc_b_loop 6368 b 6369 6370 :ebc_b_esc_hex 6371 # \xNN: require two hex chars at src[src_i], src[src_i+1]. 6372 la_a0 &ebc_b_src_i 6373 ld_t0,a0,0 6374 la_a1 &ebc_b_src_len 6375 ld_t1,a1,0 6376 sub_t2,t1,t0 # remaining = src_len - src_i 6377 li_a3 %2 %0 6378 la_br &err_bad_escape 6379 blt_t2,a3 6380 6381 # hi char: src[src_i]; decode via hex_digit_table[c]; fail if 0xFF. 6382 la_a0 &ebc_b_src_ptr 6383 ld_a0,a0,0 6384 add_a0,a0,t0 6385 lb_a0,a0,0 # a0 = hi char 6386 la_a1 &hex_digit_table 6387 add_a1,a1,a0 6388 lb_a2,a1,0 # a2 = hi digit (or 0xFF) 6389 li_a3 %255 %0 6390 la_br &err_bad_escape 6391 beq_a2,a3 6392 # Stash hi digit into ebc_b_hex_hi for the (hi << 4) | lo combine 6393 # below — the lo-digit lookup clobbers a2. 6394 la_a0 &ebc_b_hex_hi 6395 st_a2,a0,0 6396 6397 # advance past hi char 6398 la_a0 &ebc_b_src_i 6399 ld_t0,a0,0 6400 addi_t0,t0,1 6401 st_t0,a0,0 6402 6403 # lo char: src[src_i]; decode via hex_digit_table[c]; fail if 0xFF. 6404 la_a0 &ebc_b_src_ptr 6405 ld_a0,a0,0 6406 add_a0,a0,t0 6407 lb_a0,a0,0 # a0 = lo char 6408 la_a1 &hex_digit_table 6409 add_a1,a1,a0 6410 lb_a2,a1,0 # a2 = lo digit (or 0xFF) 6411 li_a3 %255 %0 6412 la_br &err_bad_escape 6413 beq_a2,a3 6414 6415 # advance past lo char 6416 la_a0 &ebc_b_src_i 6417 ld_t0,a0,0 6418 addi_t0,t0,1 6419 st_t0,a0,0 6420 6421 # byte = (hi << 4) | lo. shli_a3,t0,4 puts hi<<4 in a3, then or. 6422 la_a0 &ebc_b_hex_hi 6423 ld_t0,a0,0 6424 shli_a3,t0,4 6425 or_a3,a3,a2 6426 6427 la_br &ebc_b_emit_one 6428 b 6429 6430 :ebc_b_done 6431 eret 6432 6433 ## %local(NAME): emit-time variant. expand_builtin_call has already 6434 ## parse_args'd the call (so arg_starts/arg_ends/arg_count/call_end_pos 6435 ## are set), but expand_local_into_pool re-parses internally so it can 6436 ## also be invoked from eval_expr_atom where parse_args wasn't called. 6437 ## After the helper returns, advance the stream past the call and push 6438 ## the body slice as a fresh stream for rescan. 6439 :ebc_local 6440 # call_tok = stream->pos; limit = stream->end 6441 la_a0 &ebc_stream 6442 ld_a0,a0,0 6443 ld_t0,a0,16 # call_tok 6444 ld_t1,a0,8 # limit 6445 mov_a0,t0 6446 mov_a1,t1 6447 la_br &expand_local_into_pool 6448 call 6449 6450 # stream->pos = elp_after; stream->line_start = 0 6451 la_a0 &ebc_stream 6452 ld_a0,a0,0 6453 la_a1 &elp_after 6454 ld_t0,a1,0 6455 st_t0,a0,16 6456 li_t1 %0 %0 6457 st_t1,a0,24 6458 6459 # push_pool_stream_from_mark(elp_mark) 6460 la_a0 &elp_mark 6461 ld_a0,a0,0 6462 la_br &push_pool_stream_from_mark 6463 call 6464 6465 eret 6466 6467 ## --- Error paths ------------------------------------------------------------- 6468 ## Each err_* loads a (msg, len) pair for fatal; fatal writes "m1pp: <msg>\n" 6469 ## to stderr and exits 1. Error labels are branched to from range/overflow 6470 ## checks throughout the code. 6471 6472 :err_usage 6473 la_a0 &msg_usage 6474 la_br &fatal 6475 b 6476 :err_open_input 6477 la_a0 &msg_open_input 6478 la_br &fatal 6479 b 6480 :err_read 6481 la_a0 &msg_read 6482 la_br &fatal 6483 b 6484 :err_input_too_big 6485 la_a0 &msg_input_too_big 6486 la_br &fatal 6487 b 6488 :err_open_output 6489 la_a0 &msg_open_output 6490 la_br &fatal 6491 b 6492 :err_write 6493 la_a0 &msg_write 6494 la_br &fatal 6495 b 6496 :err_text_overflow 6497 la_a0 &msg_text_overflow 6498 la_br &fatal 6499 b 6500 :err_token_overflow 6501 la_a0 &msg_token_overflow 6502 la_br &fatal 6503 b 6504 :err_output_overflow 6505 la_a0 &msg_output_overflow 6506 la_br &fatal 6507 b 6508 :err_unterminated_macro 6509 la_a0 &msg_unterminated_macro 6510 la_br &fatal 6511 b 6512 :err_bad_macro_header 6513 la_a0 &msg_bad_macro_header 6514 la_br &fatal 6515 b 6516 :err_too_many_macros 6517 la_a0 &msg_too_many_macros 6518 la_br &fatal 6519 b 6520 :err_macro_body_overflow 6521 la_a0 &msg_macro_body_overflow 6522 la_br &fatal 6523 b 6524 :err_unbalanced_braces 6525 la_a0 &msg_unbalanced_braces 6526 la_br &fatal 6527 b 6528 :err_bad_directive 6529 la_a0 &msg_bad_directive 6530 la_br &fatal 6531 b 6532 :err_unterminated_directive 6533 la_a0 &msg_unterminated_directive 6534 la_br &fatal 6535 b 6536 :err_bad_escape 6537 la_a0 &msg_bad_escape 6538 la_br &fatal 6539 b 6540 :err_bad_frame_header 6541 la_a0 &msg_bad_frame_header 6542 la_br &fatal 6543 b 6544 :err_frame_already_active 6545 la_a0 &msg_frame_already_active 6546 la_br &fatal 6547 b 6548 :err_frame_underflow 6549 la_a0 &msg_frame_underflow 6550 la_br &fatal 6551 b 6552 :err_frame_not_closed 6553 la_a0 &msg_frame_not_closed 6554 la_br &fatal 6555 b 6556 :err_local_outside_frame 6557 la_a0 &msg_local_outside_frame 6558 la_br &fatal 6559 b 6560 :err_unknown_local 6561 la_a0 &msg_unknown_local 6562 la_br &fatal 6563 b 6564 :err_local_name_too_long 6565 la_a0 &msg_local_name_too_long 6566 la_br &fatal 6567 b 6568 6569 ## fatal(a0=msg_ptr): writes "m1pp: <msg>\n" to stderr and exits 1. 6570 ## Length is computed inline via a strlen loop (messages are NUL-terminated). 6571 ## Reached by unconditional branch from any err_* stub, so no frame is required. 6572 :fatal 6573 # Stash msg_ptr; compute len inline into err_saved_len. 6574 la_a1 &err_saved_msg 6575 st_a0,a1,0 6576 li_t0 %0 %0 6577 :fatal_strlen 6578 add_t1,a0,t0 6579 lb_t1,t1,0 6580 la_br &fatal_strlen_done 6581 beqz_t1 6582 addi_t0,t0,1 6583 la_br &fatal_strlen 6584 b 6585 :fatal_strlen_done 6586 la_a1 &err_saved_len 6587 st_t0,a1,0 6588 6589 # write(2, "m1pp:", 5) 6590 li_a0 sys_write 6591 li_a1 %2 %0 6592 la_a2 &msg_prefix 6593 li_a3 %5 %0 6594 syscall 6595 6596 # write(2, msg, len) 6597 la_a0 &err_saved_msg 6598 ld_a2,a0,0 6599 la_a0 &err_saved_len 6600 ld_a3,a0,0 6601 li_a0 sys_write 6602 li_a1 %2 %0 6603 syscall 6604 6605 # write(2, "\n", 1) 6606 li_a0 sys_write 6607 li_a1 %2 %0 6608 la_a2 &msg_newline 6609 li_a3 %1 %0 6610 syscall 6611 6612 # exit(1) 6613 li_a0 sys_exit 6614 li_a1 %1 %0 6615 syscall 6616 6617 ## Sentinel: marks the boundary between executable text and rodata. Read by 6618 ## scripts/disasm-elf.sh (via scripts/m1-symbols.py) to bound disassembly 6619 ## so trailing strings don't decode as bogus instructions. 6620 :_text_end 6621 6622 ## --- Rodata: const tokens (for tok_eq_const) and fatal messages -------------- 6623 6624 :const_macro "%macro" 6625 :const_endm "%endm" 6626 :const_paste "##" 6627 :const_lparen "(" 6628 :const_rparen ")" 6629 :const_comma "," 6630 :const_lbrace "{" 6631 :const_rbrace "}" 6632 :const_bang "!" 6633 :const_at "@" 6634 :const_pct "%" 6635 :const_dlr "$" 6636 :const_select "%select" 6637 :const_str "%str" 6638 :const_struct "%struct" 6639 :const_enum "%enum" 6640 :const_size "SIZE" 6641 :const_count "COUNT" 6642 :const_frame "%frame" 6643 :const_endframe "%endframe" 6644 :const_local "%local" 6645 :const_bytes "%bytes" 6646 :const_reset_output "%reset-output" 6647 ## Suffix appended to the frame name when looking up <frame>_FRAME.<field>. 6648 :const_frame_suffix "_FRAME." 6649 6650 ## Operator strings for expr_op_code. Each is a raw byte literal; lengths 6651 ## are passed separately to tok_eq_const. "<=" must be tested before "<" 6652 ## so the longer match wins; same for ">=" before ">". 6653 :op_plus "+" 6654 :op_minus "-" 6655 :op_star "*" 6656 :op_slash "/" 6657 :op_percent "%" 6658 :op_shl "<<" 6659 :op_shr ">>" 6660 :op_amp "&" 6661 :op_bar "|" 6662 :op_caret "^" 6663 :op_tilde "~" 6664 :op_eq "=" 6665 :op_ne "!=" 6666 :op_lt "<" 6667 :op_le "<=" 6668 :op_gt ">" 6669 :op_ge ">=" 6670 :op_strlen "strlen" 6671 6672 ## Nibble-to-hex lookup table for emit_hex_value. 6673 :hex_chars "0123456789ABCDEF" 6674 6675 ## 256-byte hex-digit lookup table for %bytes(\xNN). Indexed by source 6676 ## byte; value is the digit (0..15) for '0'..'9'/'a'..'f'/'A'..'F', or 6677 ## 0xFF for any other input. The escape decoder reads two source bytes 6678 ## and combines (hi << 4) | lo into the emitted byte; either lookup 6679 ## returning 0xFF triggers err_bad_escape. 6680 :hex_digit_table 6681 ## 0x00-0x1F: all invalid 6682 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6683 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6684 ## 0x20-0x2F: invalid 6685 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6686 ## 0x30-0x39 = '0'..'9' -> 0..9; 0x3A-0x3F invalid 6687 '00010203040506070809FFFFFFFFFFFF' 6688 ## 0x40 invalid; 0x41-0x46 = 'A'..'F' -> 10..15; 0x47-0x5F invalid 6689 'FF0A0B0C0D0E0FFFFFFFFFFFFFFFFFFF' 6690 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6691 ## 0x60 invalid; 0x61-0x66 = 'a'..'f' -> 10..15; 0x67-0x7F invalid 6692 'FF0A0B0C0D0E0FFFFFFFFFFFFFFFFFFF' 6693 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6694 ## 0x80-0xFF: all invalid 6695 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6696 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6697 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6698 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6699 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6700 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6701 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6702 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' 6703 6704 ## 256-byte char-class table for lex_loop / lex_word_scan. Indexed by the 6705 ## source byte `c`; value is the class code dispatched by lex_loop: 6706 ## 0 WORD (default; word_scan continues through this byte) 6707 ## 1 SKIP (non-newline whitespace: 0x09 tab, 0x0B-0x0D vt/ff/cr, 0x20 sp) 6708 ## 2 NEWLINE (0x0A) 6709 ## 3 STRING (0x22 ", 0x27 ') 6710 ## 4 HASH (0x23 #) 6711 ## 5 COMMENT (0x3B ;) 6712 ## 6 LPAREN (0x28 () 6713 ## 7 RPAREN (0x29 )) 6714 ## 8 COMMA (0x2C ,) 6715 ## 9 LBRACE (0x7B {) 6716 ## 10 RBRACE (0x7D }) 6717 ## 11 NUL (0x00 — lex_loop fall-through to lex_done) 6718 :lex_char_class 6719 ## bytes 0x00-0x1F: NUL=11, \t=1, \n=2, \v/\f/\r=1, rest=0 6720 '0B000000000000000001020101010000' 6721 '00000000000000000000000000000000' 6722 ## bytes 0x20-0x3F: sp=1, "=3, #=4, '=3, (=6, )=7, ,=8, ;=5 6723 '01000304000000030607000008000000' 6724 '00000000000000000000000500000000' 6725 ## bytes 0x40-0x7F: {=9 (0x7B), }=10 (0x7D) 6726 '00000000000000000000000000000000' 6727 '00000000000000000000000000000000' 6728 '00000000000000000000000000000000' 6729 '000000000000000000000009000A0000' 6730 ## bytes 0x80-0xFF: all 0 (word) 6731 '00000000000000000000000000000000' 6732 '00000000000000000000000000000000' 6733 '00000000000000000000000000000000' 6734 '00000000000000000000000000000000' 6735 '00000000000000000000000000000000' 6736 '00000000000000000000000000000000' 6737 '00000000000000000000000000000000' 6738 '00000000000000000000000000000000' 6739 6740 ## BSS pointer-slot init table (for p1_main's bss_init_loop). 6741 ## Each entry: 8-byte slot ptr (&label + 4 pad) + 8-byte OFF_* constant. 6742 ## Walked linearly; order is irrelevant. 6743 :bss_init_tbl 6744 &paste_scratch_ptr ZERO4 OFF_paste_scratch 6745 &local_label_scratch_ptr ZERO4 OFF_local_label_scratch 6746 &df_name_scratch_ptr ZERO4 OFF_df_name_scratch 6747 &ebc_str_scratch_ptr ZERO4 OFF_ebc_str_scratch 6748 &arg_starts_ptr ZERO4 OFF_arg_starts 6749 &arg_ends_ptr ZERO4 OFF_arg_ends 6750 &input_buf_ptr ZERO4 OFF_input_buf 6751 &output_buf_ptr ZERO4 OFF_output_buf 6752 &text_buf_ptr ZERO4 OFF_text_buf 6753 &source_tokens_ptr ZERO4 OFF_source_tokens 6754 ¯os_ptr ZERO4 OFF_macros 6755 ¯o_body_tokens_ptr ZERO4 OFF_macro_body_tokens 6756 &streams_ptr ZERO4 OFF_streams 6757 &expand_pool_ptr ZERO4 OFF_expand_pool 6758 &expr_frames_ptr ZERO4 OFF_expr_frames 6759 &local_lookup_scratch_ptr ZERO4 OFF_local_lookup_scratch 6760 ¯o_body_param_idx_ptr ZERO4 OFF_macro_body_param_idx 6761 ¯o_body_is_local_label_ptr ZERO4 OFF_macro_body_is_local_label 6762 :bss_init_tbl_end 6763 6764 :msg_prefix "m1pp: " 6765 :msg_newline " 6766 " 6767 ## All err_* messages below are NUL-terminated (trailing '00'); fatal uses an 6768 ## inline strlen loop rather than a caller-supplied length. 6769 :msg_usage "usage: m1pp input.M1 output.M1" '00' 6770 :msg_open_input "failed to open input file" '00' 6771 :msg_read "failed to read input" '00' 6772 :msg_input_too_big "input file too large" '00' 6773 :msg_open_output "failed to open output file" '00' 6774 :msg_write "failed to write output" '00' 6775 :msg_text_overflow "text buffer overflow" '00' 6776 :msg_token_overflow "token buffer overflow" '00' 6777 :msg_output_overflow "output buffer overflow" '00' 6778 :msg_unterminated_macro "unterminated %macro definition" '00' 6779 :msg_bad_macro_header "bad macro header" '00' 6780 :msg_too_many_macros "too many macros" '00' 6781 :msg_macro_body_overflow "macro body overflow" '00' 6782 :msg_unbalanced_braces "unbalanced braces" '00' 6783 :msg_bad_directive "bad %struct/%enum directive" '00' 6784 :msg_unterminated_directive "unterminated %struct/%enum directive" '00' 6785 :msg_bad_escape "bad escape in %bytes" '00' 6786 :msg_bad_frame_header "bad frame header" '00' 6787 :msg_frame_already_active "frame already active" '00' 6788 :msg_frame_underflow "frame underflow" '00' 6789 :msg_frame_not_closed "frame not closed" '00' 6790 :msg_local_outside_frame "local outside frame" '00' 6791 :msg_unknown_local "unknown local" '00' 6792 :msg_local_name_too_long "local name too long" '00' 6793 6794 ## --- BSS --------------------------------------------------------------------- 6795 ## Placed before :ELF_end so filesz/memsz (which this ELF header sets equal) 6796 ## covers the whole zero-initialized region. Bloats the file by the BSS size, 6797 ## but avoids a custom ELF header. 6798 ## 6799 ## Layout: scalars (pointers, counters, lexer/processor state), then the 6800 ## four arenas — input_buf, output_buf, text_buf, source_tokens — whose 6801 ## sizes match the CAP constants above. 6802 6803 ## Scalars (each 8 bytes). 6804 :input_fd 6805 ZERO8 6806 :input_len 6807 ZERO8 6808 :output_fd 6809 ZERO8 6810 :output_used 6811 ZERO8 6812 :output_written 6813 ZERO8 6814 :output_need_space 6815 ZERO8 6816 :input_path 6817 ZERO8 6818 :output_path 6819 ZERO8 6820 :text_used 6821 ZERO8 6822 :source_end 6823 ZERO8 6824 :lex_ptr 6825 ZERO8 6826 :lex_start 6827 ZERO8 6828 :lex_quote 6829 ZERO8 6830 :lex_punct_kind 6831 ZERO8 6832 :lex_saw_separator 6833 ZERO8 6834 :proc_pos 6835 ZERO8 6836 :proc_line_start 6837 ZERO8 6838 ## proc_has_paren — set per iteration of proc_loop to 1 when the next 6839 ## token is a tight TOK_LPAREN (i.e. the current token is a paren-call 6840 ## form). Read by directive/builtin sub-handlers that gate on paren form 6841 ## (%select, %str, %bytes, %local, !@$% arith, user-macro paren call). 6842 :proc_has_paren 6843 ZERO8 6844 :macros_end 6845 ZERO8 6846 :macro_body_end 6847 ZERO8 6848 :def_m_ptr 6849 ZERO8 6850 :def_param_ptr 6851 ZERO8 6852 :def_body_line_start 6853 ZERO8 6854 ## def_body_meta_idx — slot index of the body token currently being copied 6855 ## by def_body_copy, i.e. (macro_body_end - macro_body_tokens) / 32. Used 6856 ## as the parallel-array index for macro_body_param_idx[] / _is_local_label[] 6857 ## across the find_param call, which clobbers caller-saved registers. 6858 :def_body_meta_idx 6859 ZERO8 6860 :pf_stream_end 6861 ZERO8 6862 6863 ## --- Frame state ------------------------------------------------------------- 6864 ## Single-slot "current frame" used by %local. current_frame_ptr/_len point 6865 ## into stable text memory (input_buf or text_buf), borrowed from the WORD 6866 ## token that named the frame. frame_active is 0 / 1. 6867 ## elp_after / elp_mark are expand_local_into_pool's outputs (mirroring 6868 ## emt_after_pos / emt_mark for expand_macro_tokens). 6869 :current_frame_ptr 6870 ZERO8 6871 :current_frame_len 6872 ZERO8 6873 :frame_active 6874 ZERO8 6875 :elp_after 6876 ZERO8 6877 :elp_mark 6878 ZERO8 6879 :elp_arg_ptr 6880 ZERO8 6881 :elp_arg_len 6882 ZERO8 6883 :elp_name_len 6884 ZERO8 6885 :err_saved_msg 6886 ZERO8 6887 :err_saved_len 6888 ZERO8 6889 6890 ## Stream / pool / arg / expression scalars. Each is one u64 (ZERO8). 6891 ## pool_used — byte offset into expand_pool (i.e. next write slot). 6892 ## stream_top — stream stack depth in bytes (count × 40; 0 == empty). 6893 ## arg_count — number of args produced by the most recent parse_args. 6894 ## call_end_pos — Token* one past the ')' of that call. 6895 ## expr_frame_top — ExprFrame stack depth inside eval_expr_range. 6896 ## emt_after_pos, emt_mark — expand_macro_tokens output slots (Token* and 6897 ## byte offset into expand_pool). 6898 ## eval_after_pos, eval_value — eval_expr_atom output slots (Token* and i64). 6899 ## Callers MUST snapshot these before any nested 6900 ## eval_* call that could overwrite them. 6901 :pool_used 6902 ZERO8 6903 :stream_top 6904 ZERO8 6905 :arg_count 6906 ZERO8 6907 :call_end_pos 6908 ZERO8 6909 :expr_frame_top 6910 ZERO8 6911 :emt_after_pos 6912 ZERO8 6913 :emt_mark 6914 ZERO8 6915 :eval_after_pos 6916 ZERO8 6917 :eval_value 6918 ZERO8 6919 6920 ## Paste-pass spill slots. Both append_pasted_token and paste_pool_range 6921 ## call other functions, so all locals must round-trip through BSS 6922 ## across the call. 6923 ## paste_dst_save — dst Token* spilled across append_text 6924 ## paste_left_ptr/_len, paste_right_ptr/_len — operand spans for the 6925 ## byte-copy loops in append_pasted_token 6926 ## paste_total_len — left.len + right.len, reused after append_text 6927 ## paste_start — expand_pool + mark; needed to detect "## is first" 6928 ## after registers are clobbered by append_pasted_token 6929 ## paste_in — current read cursor (Token*) 6930 ## paste_out — current write cursor (Token*) 6931 ## paste_end — exclusive end (Token*), = expand_pool + pool_used 6932 :paste_dst_save 6933 ZERO8 6934 :paste_left_ptr 6935 ZERO8 6936 :paste_left_len 6937 ZERO8 6938 :paste_right_ptr 6939 ZERO8 6940 :paste_right_len 6941 ZERO8 6942 :paste_total_len 6943 ZERO8 6944 :paste_start 6945 ZERO8 6946 :paste_in 6947 ZERO8 6948 :paste_out 6949 ZERO8 6950 :paste_end 6951 ZERO8 6952 6953 ## paste_scratch — 256-byte working buffer for append_pasted_token. 6954 ## We assemble left.text ++ right.text here, then call 6955 ## append_text(&paste_scratch, total_len) to copy into the durable 6956 ## text_buf arena. 256 bytes is M0's quoted-literal cap. 6957 6958 ## parse_args + expand_macro_tokens + find_param spill slots (P1 has 6959 ## no callee-save spill on enter, and find_param's inner byte compare 6960 ## needs every caller-saved register; parse_args + expand_macro_tokens 6961 ## carry state across iterations and nested calls). One u64 each (ZERO8). 6962 :pa_pos 6963 ZERO8 6964 :pa_arg_start 6965 ZERO8 6966 :pa_depth 6967 ZERO8 6968 :pa_arg_index 6969 ZERO8 6970 :pa_limit 6971 ZERO8 6972 :pa_brace_depth 6973 ZERO8 6974 ## args_have_paste — sticky 0/1 set by parse_args when the call's argument 6975 ## span contains TOK_PASTE. expand_macro_tokens snapshots it into 6976 ## emt_saw_arg_paste right after parse_args, then ORs it with 6977 ## macro->has_paste to decide whether to run paste_pool_range. Lets us 6978 ## skip the pool sweep when neither body nor args contribute a `##`. 6979 :args_have_paste 6980 ZERO8 6981 :emt_call_tok 6982 ZERO8 6983 :emt_limit 6984 ZERO8 6985 :emt_macro 6986 ZERO8 6987 :emt_saw_arg_paste 6988 ZERO8 6989 ## emt_cached_param_idx — body token's param index (0 = not a param) read 6990 ## once from macro_body_param_idx[] at the top of emt_loop, then reused in 6991 ## emt_do_substitute_paste / _plain instead of re-running find_param. 6992 :emt_cached_param_idx 6993 ZERO8 6994 :emt_body_pos 6995 ZERO8 6996 :emt_body_end 6997 ZERO8 6998 :emt_body_start 6999 ZERO8 7000 7001 ## Local-label rewrite. next_expansion_id is the monotonic counter 7002 ## (never reset); emt_expansion_id snapshots it at the start of each 7003 ## expand_macro_tokens call so nested-call BSS reuse is safe. 7004 ## ll_* slots hold body-token span + derived sizes while building the 7005 ## renamed text in local_label_scratch. 7006 :next_expansion_id 7007 ZERO8 7008 :emt_expansion_id 7009 ZERO8 7010 :ll_src_ptr 7011 ZERO8 7012 :ll_src_len 7013 ZERO8 7014 :ll_tail_len 7015 ZERO8 7016 :ll_digit_count 7017 ZERO8 7018 :ll_digit_cursor 7019 ZERO8 7020 :ll_total_len 7021 ZERO8 7022 7023 ## local_label_digits: 24-byte reverse-fill scratch for the decimal 7024 ## rendering of emt_expansion_id (fits any u64 value). 7025 :local_label_digits 7026 ZERO8 ZERO8 ZERO8 7027 7028 ## local_label_scratch: 128-byte working buffer for the renamed text 7029 ## (sigil + tail + "__" + digits) before it's copied into text_buf via 7030 ## append_text. Caps the combined tail + digit length at ~125 bytes, 7031 ## which is ample for any realistic local-label name. 7032 7033 ## %struct / %enum scratch. define_fielded calls append_text twice 7034 ## per synthesized macro, so every piece of state that must survive a call 7035 ## lives here rather than in a register. 7036 ## df_stride — 8 for %struct, 1 for %enum 7037 ## df_total_name_ptr/_len — "SIZE" (4) for struct, "COUNT" (5) for enum 7038 ## df_base_ptr/_len — directive's NAME token span 7039 ## df_index — running field index, 0..N 7040 ## df_suffix_ptr/_len — current synthesized field suffix 7041 ## df_value — index * stride for this macro's body 7042 ## df_name_len — base_len + 1 + suffix_len 7043 ## df_digit_count/_cursor — df_render_decimal output 7044 :df_stride 7045 ZERO8 7046 :df_total_name_ptr 7047 ZERO8 7048 :df_total_name_len 7049 ZERO8 7050 :df_base_ptr 7051 ZERO8 7052 :df_base_len 7053 ZERO8 7054 :df_index 7055 ZERO8 7056 :df_suffix_ptr 7057 ZERO8 7058 :df_suffix_len 7059 ZERO8 7060 :df_value 7061 ZERO8 7062 :df_name_len 7063 ZERO8 7064 :df_digit_count 7065 ZERO8 7066 :df_digit_cursor 7067 ZERO8 7068 7069 ## df_name_scratch: 256-byte working buffer for "BASE.SUFFIX" before 7070 ## append_text copies it to text_buf. 256 B matches paste_scratch / 7071 ## ebc_str_scratch; df_emit_field asserts nothing explicit, but realistic 7072 ## struct/enum names stay well under 128 chars. 7073 7074 ## df_digit_scratch: 24-byte reverse-fill buffer for the decimal rendering 7075 ## of df_value (any u64 fits). 7076 :df_digit_scratch 7077 ZERO8 ZERO8 ZERO8 7078 7079 :fp_macro 7080 ZERO8 7081 :fp_tok 7082 ZERO8 7083 :fp_pcount 7084 ZERO8 7085 :fp_idx 7086 ZERO8 7087 7088 ## Expression-evaluator scratch slots. expr_op_code spills its tok 7089 ## argument to eoc_tok across tok_eq_const calls. apply_expr_op spills 7090 ## op/args/argc and uses acc/i as the accumulator and loop induction var 7091 ## inside the variadic folds. 7092 :eoc_tok 7093 ZERO8 7094 :aeo_op 7095 ZERO8 7096 :aeo_args 7097 ZERO8 7098 :aeo_argc 7099 ZERO8 7100 :aeo_acc 7101 ZERO8 7102 :aeo_i 7103 ZERO8 7104 7105 ## Builtin scratch. 7106 ## emit_hex_value: ehv_value/bytes hold the args; ehv_scratch is a 24-byte 7107 ## buffer (max 16 chars used: 16 hex chars for an 8-byte $-emit; rounded 7108 ## up to keep the next slot 8-byte aligned); ehv_token is a synthesized 7109 ## 32-byte Token { kind, text_ptr, text_len, tight }. 7110 :ehv_value 7111 ZERO8 7112 :ehv_bytes 7113 ZERO8 7114 :ehv_scratch 7115 ZERO8 ZERO8 ZERO8 7116 :ehv_token 7117 ZERO8 ZERO8 ZERO8 ZERO8 7118 7119 ## expand_builtin_call: snapshots the stream pointer, the post-call resume 7120 ## position, the byte count for !@%$, the eval_expr_range result, the chosen 7121 ## arg span (start/end), the unchosen-side spans for %select, and the 7122 ## pool mark used to push the chosen-stream slice. 7123 :ebc_stream 7124 ZERO8 7125 :ebc_call_end_pos 7126 ZERO8 7127 :ebc_bytes 7128 ZERO8 7129 :ebc_value 7130 ZERO8 7131 :ebc_arg0_start 7132 ZERO8 7133 :ebc_arg0_end 7134 ZERO8 7135 :ebc_then_start 7136 ZERO8 7137 :ebc_then_end 7138 ZERO8 7139 :ebc_else_start 7140 ZERO8 7141 :ebc_else_end 7142 ZERO8 7143 :ebc_mark 7144 ZERO8 7145 7146 ## %str builtin scratch. ebc_str_orig_len / ebc_str_out_len spill the 7147 ## argument text length and its +2 output length across append_text; 7148 ## ebc_str_token is the synthesized TOK_STRING { kind, text_ptr, text_len, 7149 ## tight } handed to emit_token; ebc_str_scratch is a 256-byte assembly 7150 ## buffer (matches paste_scratch / M0's quoted-literal cap). 7151 :ebc_str_orig_len 7152 ZERO8 7153 :ebc_str_out_len 7154 ZERO8 7155 :ebc_str_token 7156 ZERO8 ZERO8 ZERO8 ZERO8 7157 7158 ## %bytes builtin scratch. ebc_b_src_ptr/_len/_i walk the input string 7159 ## across emit_hex_value calls (which clobber every caller-saved reg). 7160 ## ebc_b_hex_hi spills the high nibble across the second hex_digit_table 7161 ## lookup for the low nibble. Each source byte emits independently via 7162 ## emit_hex_value(byte, 1); hex2pp's parse_byte_stream coalesces the 7163 ## resulting space-separated runs back into a contiguous byte stream. 7164 :ebc_b_src_ptr 7165 ZERO8 7166 :ebc_b_src_len 7167 ZERO8 7168 :ebc_b_src_i 7169 ZERO8 7170 :ebc_b_hex_hi 7171 ZERO8 7172 7173 ## arg_starts[16] / arg_ends[16]: 16 × 8 = 128 bytes each, i.e. 4 ZERO32. 7174 ## Written by parse_args; read by expand_macro_tokens and expand_builtin_call. 7175 7176 ## input_buf: 8 KB (M1PP_INPUT_CAP) 7177 7178 ## output_buf: 8 KB (M1PP_OUTPUT_CAP) 7179 7180 ## text_buf: 1 MB (M1PP_TEXT_CAP) 7181 7182 ## source_tokens (M1PP_TOKENS_END) 7183 7184 ## macros: 32 records × 296 bytes = 9472 bytes (M1PP_MACROS_CAP). 7185 ## 37 lines × 256 bytes = 9472. Each line is 8 × ZERO32 = 256 bytes. 7186 7187 ## macro_body_tokens: 256 slots × 24 bytes = 6 KB (M1PP_MACRO_BODY_CAP). 7188 ## 24 lines × 256 bytes = 6144. Source tokens are copied in 24 bytes at a 7189 ## time as macro bodies are recorded. 7190 7191 ## streams: 16 Stream records × 40 bytes = 640 bytes (M1PP_STREAM_STACK_CAP). 7192 ## 20 ZERO32 = 2 lines of 8 + 1 line of 4. 7193 7194 ## expand_pool: 256 Token slots × 24 bytes = 6144 bytes (M1PP_EXPAND_CAP). 7195 ## 24 lines × 8 ZERO32 = 192 ZERO32. 7196 7197 ## expr_frames: 16 × 144 bytes = 2304 bytes (M1PP_EXPR_FRAMES_CAP). 7198 ## 9 lines × 8 ZERO32 = 72 ZERO32. 7199 7200 ## --- BSS pointer slots (set by p1_main; one per BSS buffer) ----------------- 7201 :paste_scratch_ptr 7202 ZERO8 7203 :local_label_scratch_ptr 7204 ZERO8 7205 :df_name_scratch_ptr 7206 ZERO8 7207 :ebc_str_scratch_ptr 7208 ZERO8 7209 :arg_starts_ptr 7210 ZERO8 7211 :arg_ends_ptr 7212 ZERO8 7213 :input_buf_ptr 7214 ZERO8 7215 :output_buf_ptr 7216 ZERO8 7217 :text_buf_ptr 7218 ZERO8 7219 :source_tokens_ptr 7220 ZERO8 7221 :macros_ptr 7222 ZERO8 7223 :macro_body_tokens_ptr 7224 ZERO8 7225 :streams_ptr 7226 ZERO8 7227 :expand_pool_ptr 7228 ZERO8 7229 :expr_frames_ptr 7230 ZERO8 7231 :local_lookup_scratch_ptr 7232 ZERO8 7233 :macro_body_param_idx_ptr 7234 ZERO8 7235 :macro_body_is_local_label_ptr 7236 ZERO8 7237 7238 :ELF_end