scheme1.P1pp (233265B)
1 # scheme1.P1pp -- Phase 1 minimal Scheme interpreter on P1. 2 # 3 # Build chain: 4 # catm P1-<arch>.M1pp P1.M1pp P1pp.P1pp scheme1/scheme1.P1pp \ 5 # | m1pp -> hex2pp -> ELF 6 # 7 # Run chain: 8 # catm scheme1/prelude.scm prog.scm | scheme1 9 10 # ========================================================================= 11 # Constants 12 # ========================================================================= 13 14 %enum TAG { FIXNUM PAIR SYM HEAP IMM CHAR } 15 %enum IMM { FALSE TRUE NIL UNSPEC UNBOUND EOF } 16 %enum HDR { BV STRING CLOSURE PRIM TD REC MV HASH HASHDATA ENV } 17 %enum GCKIND { FREE PAIR HEAP RAW } 18 19 # Each managed block begins with two padded 8-byte slots. The first word is 20 # (total_block_bytes << 8) | kind | mark 21 # and the second is an intrusive link, reused by the free list and mark 22 # worklist. Payloads retain their historical layouts and tags. 23 %macro GC_HEADER_BYTES() 16 %endm 24 %macro GC_MARK_BIT() 128 %endm 25 %macro GC_KIND_MASK() 7 %endm 26 27 # imm_val(idx) -> integer-expression for the tagged immediate at IMM index 28 # `idx`. Used both at %li sites (loaded into a register) and at $() emission 29 # sites (baked into a static word). 30 %macro imm_val(idx) (| (<< idx 3) %TAG.IMM) %endm 31 32 # Layout helpers. %struct stride is 8 bytes per field. 33 %struct PAIR { car cdr } # .SIZE = 16 34 %struct SYMENT { name_ptr name_len global_val pad } # .SIZE = 32 35 %struct PRIM { hdr entry_w data } # .SIZE = 24 36 %struct CLOSURE { hdr params body env } # .SIZE = 32 37 %struct TD { hdr name nfields fields } # .SIZE = 32 38 %struct BV { hdr data } # .SIZE = 16 39 %struct REC { hdr td } # .SIZE = 16 (header) 40 %struct HASH { hdr data } # .SIZE = 16 41 %struct HASHDATA { hdr count used } # .SIZE = 24 (header) 42 %struct ENV { hdr sym value parent bloom } # .SIZE = 40 43 # Records are variable width: header + td slot + N field slots. 44 # Hash data is variable width: header + count + used + N 24-byte slots. 45 # A slot is (stored-hash, key, value); 0 means empty and 1 is a tombstone. 46 47 # BSS arenas anchored past :ELF_end. readbuf is 1 MiB (sized to fit 48 # the catm'd cc compiler source incl. prelude), followed by the symbol 49 # table, the exact-root frame stack, and one 256 MiB managed heap. 50 51 %macro SYMTAB_CAP_SLOTS() 8192 %endm 52 %macro READBUF_CAP_BYTES() 1048576 %endm 53 # A block header stores byte size above eight flag bits. P1-32 therefore 54 # caps an individual block below 16 MiB; P1-64 keeps the original 256 MiB 55 # heap. The source/read/symbol arenas are unchanged. 56 %macro HEAP_CAP_BYTES() %p1_scheme_heap_bytes %endm 57 %macro GC_ROOT_CAP_FRAMES() 8192 %endm 58 %macro GC_ROOT_FRAME_BYTES() 24 %endm 59 60 # ========================================================================= 61 # Tag idioms 62 # ========================================================================= 63 64 %macro tagof(rd, rs) %andi(rd, rs, 7) %endm 65 %macro mkfix(rd, rs) %shli(rd, rs, 3) %endm 66 %macro untag_fix(rd, rs) %sari(rd, rs, 3) %endm 67 %macro mkchar(rd, rs) %shli(rd, rs, 3) %ori(rd, rd, %TAG.CHAR) %endm 68 %macro untag_char(rd, rs) %shri(rd, rs, 3) %endm 69 %macro untag_sym(rd, rs) %sari(rd, rs, 3) %endm 70 %macro car(rd, rs) %ld(rd, rs, -1) %endm 71 %macro cdr(rd, rs) %ld(rd, rs, 7) %endm 72 %macro set_car(rs, pair_tagged) %st(rs, pair_tagged, -1) %endm 73 %macro set_cdr(rs, pair_tagged) %st(rs, pair_tagged, 7) %endm 74 %macro hdr_type(rd, rs) %lb(rd, rs, -3) %endm 75 76 # Field access through a tagged HEAP pointer (tag = 3). `field` is a 77 # constant byte offset from the underlying raw object (e.g. %PRIM.data, 78 # %CLOSURE.env). Reader is %ld; writer is %heap_st. 79 %macro heap_ld(rd, rs, field) %ld(rd, rs, (- field 3)) %endm 80 %macro heap_st(rs, rt, field) %st(rs, rt, (- field 3)) %endm 81 82 # ========================================================================= 83 # Scheme1-local helpers 84 # ========================================================================= 85 86 # Load the byte at readbuf_buf[off_reg] into rd. Clobbers rd. `rd` must 87 # be the destination register; the macro reuses it as a scratch pointer 88 # during the la / ld / add chain before the final lb writes the byte. 89 %macro readbuf_byte(rd, off_reg) 90 %ld_global(rd, &readbuf_buf_ptr) 91 %add(rd, rd, off_reg) 92 %lb(rd, rd, 0) 93 %endm 94 95 # Increment cursor and store it back. `addr_reg` is the address of 96 # readbuf_pos as returned by %lda_global (the second output register). 97 %macro readbuf_advance(pos_reg, addr_reg) 98 %addi(pos_reg, pos_reg, 1) 99 %st(pos_reg, addr_reg, 0) 100 %endm 101 102 # Load readbuf_len into len_reg and branch to target if cursor is at EOF. 103 %macro readbuf_at_eof(pos_reg, len_reg, target) 104 %ld_global(len_reg, &readbuf_len) 105 %beq(pos_reg, len_reg, target) 106 %endm 107 108 # Branch character equal/not-equal: if (c == expect) / (c != expect) goto target. 109 # expect passed as -(char_code). scratch is clobbered. 110 %macro bceq(c, neg_cv, target, scratch) 111 %addi(scratch, c, neg_cv) 112 %beqz(scratch, target) 113 %endm 114 115 %macro bcne(c, neg_cv, target, scratch) 116 %addi(scratch, c, neg_cv) 117 %bnez(scratch, target) 118 %endm 119 120 # Branch immediate equal/not-equal: if (reg == value) / (reg != value) goto target. 121 # scratch is clobbered. 122 %macro bieq(reg, value, target, scratch) 123 %li(scratch, value) 124 %beq(reg, scratch, target) 125 %endm 126 127 %macro bine(reg, value, target, scratch) 128 %li(scratch, value) 129 %bne(reg, scratch, target) 130 %endm 131 132 # Branch to `target` if `ch_reg` holds an ASCII whitespace byte (space, 133 # tab, LF, CR). `scratch` is clobbered. 134 %macro is_ws_branch(scratch, ch_reg, target) 135 %bceq(ch_reg, -32, target, scratch) ; SP 136 %bceq(ch_reg, -9, target, scratch) ; HT 137 %bceq(ch_reg, -10, target, scratch) ; LF 138 %bceq(ch_reg, -13, target, scratch) ; CR 139 %endm 140 141 # Branch to `target` if `ch_reg` is an R7RS token delimiter. Some of the 142 # delimited constructs are deliberately unsupported by micro, but they still 143 # terminate an adjacent token so the reader rejects them at their own start. 144 %macro is_token_delimiter_branch(scratch, ch_reg, target) 145 %is_ws_branch(scratch, ch_reg, target) 146 %bceq(ch_reg, -40, target, scratch) ; '(' 147 %bceq(ch_reg, -41, target, scratch) ; ')' 148 %bceq(ch_reg, -59, target, scratch) ; ';' 149 %bceq(ch_reg, -34, target, scratch) ; '"' 150 %bceq(ch_reg, -124, target, scratch) ; '|' 151 %bceq(ch_reg, -39, target, scratch) ; '\'' 152 %bceq(ch_reg, -96, target, scratch) ; '`' 153 %bceq(ch_reg, -44, target, scratch) ; ',' 154 %endm 155 156 # Branch to `target` if lo_neg <= c < lo_neg+count (unsigned). Both 157 # scratch and count_scratch are clobbered. 158 %macro brange(c, lo_neg, count, scratch, count_scratch, target) 159 %addi(scratch, c, lo_neg) 160 %li(count_scratch, count) 161 %bltu(scratch, count_scratch, target) 162 %endm 163 164 # Compute &symtab_buf + idx_reg * SYMENT.SIZE into rd. `scratch` is 165 # clobbered. 166 %macro symtab_entry(rd, idx_reg, scratch) 167 %ld_global(rd, &symtab_buf_ptr) 168 %shli(scratch, idx_reg, 5) 169 %add(rd, rd, scratch) 170 %endm 171 172 # Build the two-bit lexical-environment filter mask for tagged symbol `sym`. 173 # Symbol identity is its stable intern-table index, so two inexpensive affine 174 # projections give deterministic bit positions in one target word. `rd`, 175 # `scratch0`, and `scratch1` must be distinct; both scratches are clobbered. 176 %macro env_sym_mask(rd, sym, scratch0, scratch1) 177 %untag_sym(scratch0, sym) 178 %andi(rd, scratch0, (- %p1_word_bits 1)) 179 %li(scratch1, 1) 180 %shl(rd, scratch1, rd) 181 %li(scratch1, 13) 182 %mul(scratch0, scratch0, scratch1) 183 %addi(scratch0, scratch0, 17) 184 %andi(scratch0, scratch0, (- %p1_word_bits 1)) 185 %li(scratch1, 1) 186 %shl(scratch0, scratch1, scratch0) 187 %or(rd, rd, scratch0) 188 %endm 189 190 # Compute the raw address of hash-data slot `idx_reg`. HASHDATA's three 191 # header words occupy 24 bytes; each open-addressed slot is another three 192 # words (stored hash, key, value). `data_reg` is a tagged HEAP pointer. 193 %macro hash_slot(rd, data_reg, idx_reg, scratch) 194 %shli(rd, idx_reg, 4) 195 %shli(scratch, idx_reg, 3) 196 %add(rd, rd, scratch) 197 %add(rd, rd, data_reg) 198 %addi(rd, rd, 21) ; raw +24 relative to a HEAP-tagged pointer 199 %endm 200 201 # Print msg_label and abort. Never returns. Routes through runtime_error 202 # so every error path lands in one place (stderr + exit 1). 203 %macro die(msg) 204 %la(a0, & ## msg) 205 %call(&runtime_error) 206 %endm 207 208 # Emit an 8-aligned NUL-terminated string. 209 %macro cstr8(str) 210 str 211 00 212 .align 8 213 %endm 214 215 # Intern `str` into `slot` and declare its padded string data inline. 216 # `key` is the label suffix; the data label :name_##key is emitted here. 217 %macro intern_form(key, str, slot) 218 %la(a0, &name_ ## key) 219 %li(a1, (strlen str)) 220 %call(&intern) 221 %st_global(a0, slot, t0) 222 %b(&@end) 223 :name_ ## key 224 %cstr8(str) 225 :@end 226 %endm 227 228 # Special-form dispatch: pointer-compare the head symbol against `slot`'s 229 # cached value (in t0) and branch to `target` on hit. Caller has already 230 # loaded head into t0. 231 %macro dispatch_form(slot, target) 232 %ld_global(t1, slot) 233 %beq(t0, t1, target) 234 %endm 235 236 # Tail-jump from a special-form dispatch label to its handler. Handlers 237 # uniformly take (rest=cdr(expr), env) -> value; expr lives at sp[0], 238 # env at sp[8] in eval's frame. 239 %macro tail_to_handler(handler) 240 %ld(a0, sp, 0) 241 %cdr(a0, a0) 242 %ld(a1, sp, 8) 243 %gctail(handler) 244 %endm 245 246 # Branch to `target` if `val` holds the NIL immediate. `scratch` is 247 # clobbered. 248 %macro if_nil(scratch, val, target) 249 %li(scratch, %imm_val(%IMM.NIL)) 250 %beq(val, scratch, target) 251 %endm 252 253 # Advance a named list-cursor local to its cdr. t0 is the implicit scratch 254 # register; callers must ensure it's free. 255 %macro advance_walk(name) 256 %ldl(t0, name) 257 %cdr(t0, t0) 258 %stl(t0, name) 259 %endm 260 261 # Set a global binding. sym is a tagged symbol, val is the new value. 262 # Untags sym into the idx ABI position and calls sym_set_global. 263 %macro set_global(sym, val) 264 %mov(a1, val) 265 %untag_sym(a0, sym) 266 %call(&sym_set_global) 267 %endm 268 269 # Exact shadow-root frames. Every frame records the portable local base 270 # plus two bitmaps: tagged Scheme-reference slots and temporarily live raw 271 # managed-allocation pointers. Bit N describes native local slot N. The 272 # collector dereferences only those described slots; ordinary machine 273 # locals and the native P1 stack are never scanned. 274 %macro gc_frame_push(scheme_mask, raw_mask) 275 %ld_global(t0, &gc_root_next) 276 %addi(t1, t0, %GC_ROOT_FRAME_BYTES) 277 %ld_global(t2, &gc_root_end) 278 %bltu(t2, t1, &@overflow) 279 # p1_addi sees native sp directly; step over the two-word backend header. 280 %addi(t2, sp, (* 2 %p1_word_bytes)) 281 %st(t2, t0, 0) 282 %li(t2, scheme_mask) 283 %st(t2, t0, 8) 284 %li(t2, raw_mask) 285 %st(t2, t0, 16) 286 %st_global(t1, &gc_root_next, t2) 287 %b(&@done) 288 :@overflow 289 %die(msg_gc_roots_full) 290 :@done 291 %endm 292 293 %macro gc_frame_pop() 294 %ld_global(t0, &gc_root_next) 295 %addi(t0, t0, (- %GC_ROOT_FRAME_BYTES)) 296 # Clear popped descriptors so debugging an overflow never exposes 297 # stale native-stack addresses as apparently active slots. 298 %li(t1, 0) 299 %st(t1, t0, 0) 300 %st(t1, t0, 8) 301 %st(t1, t0, 16) 302 %st_global(t0, &gc_root_next, t1) 303 %endm 304 305 %macro gc_frame_clear(frame_size) 306 %addi(t0, sp, (* 2 %p1_word_bytes)) 307 %li(t1, frame_size) 308 %li(t2, 0) 309 :@loop 310 %beqz(t1, &@done) 311 %st(t2, t0, 0) 312 %addi(t0, t0, 8) 313 %addi(t1, t1, -8) 314 %b(&@loop) 315 :@done 316 %endm 317 318 %macro gceret() 319 %gc_frame_pop 320 %eret 321 %endm 322 323 %macro gctail(target) 324 %gc_frame_pop 325 %tail(target) 326 %endm 327 328 %macro gctailr(target_reg) 329 %mov(a3, target_reg) 330 %gc_frame_pop 331 %tailr(a3) 332 %endm 333 334 # GC-aware counterpart of P1pp's %fn2. Functions using this form must 335 # use %gceret / %gctail / %gctailr for explicit exits; fallthrough runs 336 # the epilogue emitted here. 337 %macro gcfn2(name, locals, scheme_mask, raw_mask, body) 338 %struct name ## _FRAME { locals } 339 : ## name 340 .scope 341 %frame name 342 %enter(% ## name ## _FRAME.SIZE) 343 %gc_frame_clear(% ## name ## _FRAME.SIZE) 344 %gc_frame_push(scheme_mask, raw_mask) 345 body 346 %gc_frame_pop 347 %eret 348 %endframe 349 .endscope 350 %endm 351 352 # car-and-untag-fixnum: rd = car(list) >> 3. 353 %macro car_fix(rd, list) 354 %car(rd, list) 355 %sari(rd, rd, 3) 356 %endm 357 358 # car-then-load-bytevector-data-pointer: rd = (car(list)).data_ptr. 359 %macro car_bvdata(rd, list) 360 %car(rd, list) 361 %ld(rd, rd, 5) 362 %endm 363 364 # Positional list-arg extraction. r_n receives the nth element of `list`; 365 # the last destination register doubles as the in-flight rest cursor 366 # during extraction (its final value is the last argument). 367 %macro args2(r0, r1, list) 368 %car(r0, list) 369 %cdr(r1, list) 370 %car(r1, r1) 371 %endm 372 373 %macro args3(r0, r1, r2, list) 374 %car(r0, list) 375 %cdr(r2, list) 376 %car(r1, r2) 377 %cdr(r2, r2) 378 %car(r2, r2) 379 %endm 380 381 %macro args4(r0, r1, r2, r3, list) 382 %car(r0, list) 383 %cdr(r3, list) 384 %car(r1, r3) 385 %cdr(r3, r3) 386 %car(r2, r3) 387 %cdr(r3, r3) 388 %car(r3, r3) 389 %endm 390 391 # ========================================================================= 392 # p1_main -- runtime spine 393 # ========================================================================= 394 395 %fn(p1_main, 0, { 396 # Stash argc/argv 397 %st_global(a0, &saved_argc, t0) 398 %st_global(a1, &saved_argv, t0) 399 400 # if argc < 2 goto usage 401 %li(t0, 2) 402 %bltu(a0, t0, &.usage) 403 404 # Initialize 405 %la(a0, &ELF_end) 406 %la(a1, &arena_table) 407 %la(a2, &arena_table_end) 408 %call(&init_arenas) 409 %call(&heap_init) 410 %call(&intern_special_forms) 411 %call(®ister_primitives) 412 %call(®ister_globals) 413 414 # load_source(argv[1]) 415 %ld_global(a0, &saved_argv) 416 %ld(a0, a0, %p1_word_bytes) 417 %call(&load_source) 418 419 # read-eval loop 420 %loop_scoped({ 421 # eof = skip_ws() 422 %call(&skip_ws) 423 # if eof break 424 %if_nez(a0, { %break }) 425 # expr = parse_one() 426 %call(&parse_one) 427 # eval(expr, env=nil) 428 %li(a1, %imm_val(%IMM.NIL)) 429 %call(&eval) 430 }) 431 432 # return 0 433 %li(a0, 0) 434 %eret 435 436 :.usage 437 %la(a0, &msg_usage) 438 %call(&print_cstr) 439 %li(a0, 2) 440 }) 441 442 # ========================================================================= 443 # Reader -- parse_one over readbuf with a single byte cursor 444 # ========================================================================= 445 # 446 # Cursor lives in &readbuf_pos; readbuf_len holds the slurped byte count. 447 # The reader is called recursively from parse_list, so every state goes 448 # through frame slots, not s-registers. 449 450 # Skip whitespace (ASCII 32, 9, 10, 13) and `;`-to-LF comments. Returns 451 # a0 = 1 if readbuf_pos >= readbuf_len after skipping (caller hit EOF), 452 # else 0. Leaf. 453 :skip_ws 454 .scope 455 %lda_global(t0, t2, &readbuf_pos) 456 %ld_global(t1, &readbuf_len) 457 :.loop 458 %beq(t0, t1, &.done) 459 %readbuf_byte(a0, t0) 460 %is_ws_branch(a1, a0, &.step) 461 %bceq(a0, -59, &.comment, a1) ; ';' 462 %b(&.done) 463 :.comment 464 # Consume up to and including the next LF, or to EOF. 465 %addi(t0, t0, 1) 466 %beq(t0, t1, &.done) 467 %readbuf_byte(a0, t0) 468 %bcne(a0, -10, &.comment, a1) ; LF 469 :.step 470 %addi(t0, t0, 1) 471 %b(&.loop) 472 :.done 473 474 %st(t0, t2, 0) 475 %li(a0, 1) 476 %beq(t0, t1, &.ret) 477 %li(a0, 0) 478 :.ret 479 %ret 480 .endscope 481 482 # parse_exact_dec(buf=a0, len=a1) -> (raw value=a0, consumed=a1). 483 # Accepts an optional leading '-' (callers strip '+'). The accumulator is 484 # checked before every multiply/add, so even an arbitrarily long token cannot 485 # wrap the host word and re-enter the tagged exact-integer range. 486 %fn2(parse_exact_dec, {start cursor end digits accum negative quotient remainder}, { 487 %stl(a0, start) 488 %stl(a0, cursor) 489 %add(t0, a0, a1) 490 %stl(t0, end) 491 %li(t0, 0) 492 %stl(t0, accum) 493 %stl(t0, negative) 494 495 # Optional minus sign. 496 %ldl(t0, cursor) 497 %ldl(t1, end) 498 %beq(t0, t1, &.after_sign) 499 %lb(t2, t0, 0) 500 %addi(t2, t2, -45) 501 %bnez(t2, &.after_sign) 502 %li(t2, 1) 503 %stl(t2, negative) 504 %addi(t0, t0, 1) 505 %stl(t0, cursor) 506 507 :.after_sign 508 %ldl(t0, cursor) 509 %stl(t0, digits) 510 511 # Magnitude limit is the target word's tagged-fixnum maximum, plus 512 # one for the negative endpoint. 513 %li(t0, -1) 514 %shri(t0, t0, 4) 515 %ldl(t1, negative) 516 %beqz(t1, &.have_limit) 517 %addi(t0, t0, 1) 518 :.have_limit 519 %li(t1, 10) 520 %div(t2, t0, t1) 521 %stl(t2, quotient) 522 %rem(t2, t0, t1) 523 %stl(t2, remainder) 524 525 :.loop 526 %ldl(t0, cursor) 527 %ldl(t1, end) 528 %beq(t0, t1, &.done) 529 %lb(a0, t0, 0) 530 %addi(a0, a0, -48) 531 %bltz(a0, &.done) 532 %li(t2, 9) 533 %bltu(t2, a0, &.done) 534 535 # accum * 10 + digit <= limit, checked without overflowing. 536 %ldl(t0, accum) 537 %ldl(t1, quotient) 538 %bltu(t1, t0, &.overflow) 539 %bne(t0, t1, &.accumulate) 540 %ldl(t2, remainder) 541 %bltu(t2, a0, &.overflow) 542 :.accumulate 543 %li(t1, 10) 544 %mul(t0, t0, t1) 545 %add(t0, t0, a0) 546 %stl(t0, accum) 547 %ldl(t0, cursor) 548 %addi(t0, t0, 1) 549 %stl(t0, cursor) 550 %b(&.loop) 551 552 :.done 553 %ldl(t0, cursor) 554 %ldl(t1, digits) 555 %beq(t0, t1, &.no_digits) 556 %ldl(a0, accum) 557 %ldl(t1, negative) 558 %beqz(t1, &.return) 559 %li(t1, 0) 560 %sub(a0, t1, a0) 561 :.return 562 %ldl(a1, cursor) 563 %ldl(t0, start) 564 %sub(a1, a1, t0) 565 %eret 566 567 :.no_digits 568 %li(a0, 0) 569 %li(a1, 0) 570 %eret 571 572 :.overflow 573 %die(msg_integer_overflow) 574 }) 575 576 # parse_one() -> tagged value in a0 577 %fn2(parse_one, {number_len number_neg}, { 578 %call(&skip_ws) 579 %bnez(a0, &.eof) 580 581 %ld_global(t0, &readbuf_pos) 582 %readbuf_byte(a0, t0) 583 584 %bceq(a0, -40, &.lparen, a1) 585 %bceq(a0, -41, &.rparen, a1) 586 %bceq(a0, -35, &.hash, a1) 587 %bceq(a0, -39, &.quote, a1) 588 %bceq(a0, -44, &.comma, a1) 589 %bceq(a0, -34, &.string, a1) 590 %bceq(a0, -96, &.unsupported, a1) ; backquote 591 %bceq(a0, -124, &.unsupported, a1) ; vertical-bar identifier 592 593 %tail(&parse_atom) 594 595 :.lparen 596 # Consume '(' and read items until ')'. 597 %lda_global(t1, t0, &readbuf_pos) 598 %readbuf_advance(t1, t0) 599 %tail(&parse_list) 600 601 :.rparen 602 %die(msg_unexp_rparen) 603 604 :.string 605 # Consume opening '"' and tail to parse_string. parse_string scans 606 # through the matching '"' (consuming it) and returns a tagged bv. 607 %lda_global(t1, t0, &readbuf_pos) 608 %readbuf_advance(t1, t0) 609 %tail(&parse_string) 610 611 :.hash 612 # Consume '#' plus its type byte; dispatch on the type byte. 613 %lda_global(t0, t2, &readbuf_pos) 614 %addi(t0, t0, 1) 615 %readbuf_at_eof(t0, t1, &.eof) 616 %readbuf_byte(a0, t0) 617 %readbuf_advance(t0, t2) 618 %bceq(a0, -116, &.true_lit, a1) ; 't' 619 %bceq(a0, -102, &.false_lit, a1) ; 'f' 620 %bceq(a0, -120, &.hex_lit, a1) ; 'x' 621 %bceq(a0, -92, &.char_lit, a1) ; '\\' 622 %bceq(a0, -117, &.u8_lit, a1) ; 'u' 623 %die(msg_bad_hash) 624 625 :.true_lit 626 %li(a2, %imm_val(%IMM.TRUE)) 627 %b(&.boolean_lit) 628 629 :.false_lit 630 %li(a2, %imm_val(%IMM.FALSE)) 631 632 :.boolean_lit 633 # A boolean token ends after its one-letter spelling. Without this 634 # check `#true` would silently become two adjacent top-level datums. 635 %ld_global(t0, &readbuf_pos) 636 %ld_global(t1, &readbuf_len) 637 %beq(t0, t1, &.boolean_done) 638 %readbuf_byte(t2, t0) 639 %is_token_delimiter_branch(a3, t2, &.boolean_done) 640 %die(msg_bad_hash) 641 :.boolean_done 642 %mov(a0, a2) 643 %eret 644 645 :.hex_lit 646 # t0 sits at the first hex digit; t1 = readbuf_len. Scan to a token 647 # delimiter, then parse_hex over the slice with an optional sign. 648 %mov(a3, t0) 649 :.hex_scan 650 %beq(t0, t1, &.hex_end) 651 %readbuf_byte(a0, t0) 652 %is_token_delimiter_branch(a1, a0, &.hex_end) 653 %addi(t0, t0, 1) 654 %b(&.hex_scan) 655 :.hex_end 656 657 %st_global(t0, &readbuf_pos, t2) 658 %ld_global(a0, &readbuf_buf_ptr) 659 %add(a0, a0, a3) 660 %sub(a1, t0, a3) 661 %beqz(a1, &.number_bad) 662 %li(t0, 0) 663 %stl(t0, number_neg) 664 %lb(t2, a0, 0) 665 %addi(t2, t2, -45) ; '-' 666 %beqz(t2, &.hex_neg) 667 %lb(t2, a0, 0) 668 %addi(t2, t2, -43) ; '+' 669 %beqz(t2, &.hex_pos_sign) 670 %b(&.hex_parse) 671 :.hex_neg 672 %li(t0, 1) 673 %stl(t0, number_neg) 674 :.hex_pos_sign 675 %addi(a0, a0, 1) 676 %addi(a1, a1, -1) 677 %beqz(a1, &.number_bad) 678 :.hex_parse 679 # Strip redundant leading zeroes so a long but representable spelling 680 # is accepted without allowing parse_hex's machine-word accumulator to 681 # wrap. At most one target word of significant hex digits remains. 682 :.hex_trim_zero 683 %lb(t1, a0, 0) 684 %addi(t1, t1, -48) 685 %bnez(t1, &.hex_count_check) 686 %li(t0, 1) 687 %beq(a1, t0, &.hex_count_check) 688 %addi(a0, a0, 1) 689 %addi(a1, a1, -1) 690 %b(&.hex_trim_zero) 691 :.hex_count_check 692 %li(t0, (+ (/ %p1_word_bits 4) 1)) 693 %bltu(a1, t0, &.hex_parse_go) 694 %b(&.integer_overflow) 695 :.hex_parse_go 696 %stl(a1, number_len) 697 %call(&parse_hex) 698 %ldl(t0, number_len) 699 %bne(a1, t0, &.number_bad) 700 # Compare the unsigned magnitude before applying the sign. Positive 701 # Positive values allow the target tagged-fixnum maximum; one extra 702 # magnitude is valid only for the negative endpoint. 703 %li(t0, -1) 704 %shri(t0, t0, 4) 705 %ldl(t1, number_neg) 706 %beqz(t1, &.hex_limit_ready) 707 %addi(t0, t0, 1) 708 :.hex_limit_ready 709 %bltu(t0, a0, &.integer_overflow) 710 %ldl(t0, number_neg) 711 %beqz(t0, &.hex_tag) 712 %li(t0, 0) 713 %sub(a0, t0, a0) 714 :.hex_tag 715 %mov(t0, a0) 716 %mkfix(a0, t0) 717 %untag_fix(t1, a0) 718 %bne(t0, t1, &.integer_overflow) 719 %eret 720 721 :.quote 722 # Consume the leading '\''; recurse into parse_one for the datum; 723 # then build (quote <datum>). 724 %lda_global(t0, t2, &readbuf_pos) 725 %readbuf_advance(t0, t2) 726 %call(&parse_one) 727 %li(a1, %imm_val(%IMM.NIL)) 728 %call(&cons) 729 %ld_global(t0, &sym_quote) 730 %mov(a1, a0) 731 %mov(a0, t0) 732 %tail(&cons) 733 734 :.comma 735 # Consume the leading ','; recurse into parse_one for the datum; 736 # build (unquote <datum>). The comma sugar exists so pmatch 737 # patterns can be written as `,ident`. Outside pmatch 738 # `(unquote x)` reaches eval as an application of the unbound 739 # `unquote` and dies through the standard unbound-variable path. 740 %lda_global(t0, t2, &readbuf_pos) 741 %readbuf_advance(t0, t2) 742 %call(&parse_one) 743 %li(a1, %imm_val(%IMM.NIL)) 744 %call(&cons) 745 %ld_global(t0, &sym_unquote) 746 %mov(a1, a0) 747 %mov(a0, t0) 748 %tail(&cons) 749 750 :.char_lit 751 # Cursor is already past '#\\'; parse_char scans the body and returns 752 # a disjoint tagged character. 753 %tail(&parse_char) 754 755 :.u8_lit 756 # Cursor is past '#u'. Demand '8' then '('; consume both and tail to 757 # parse_u8_body, which reads the element list and packs it into a bv. 758 %lda_global(t0, t2, &readbuf_pos) 759 %readbuf_at_eof(t0, t1, &.u8_bad) 760 %readbuf_byte(a0, t0) 761 %bcne(a0, -56, &.u8_bad, a1) ; '8' 762 %addi(t0, t0, 1) 763 %beq(t0, t1, &.u8_bad) 764 %readbuf_byte(a0, t0) 765 %bcne(a0, -40, &.u8_bad, a1) ; '(' 766 %readbuf_advance(t0, t2) 767 %tail(&parse_u8_body) 768 769 :.u8_bad 770 %die(msg_bad_hash) 771 772 :.number_bad 773 %die(msg_bad_number) 774 775 :.integer_overflow 776 %die(msg_integer_overflow) 777 778 :.unsupported 779 %die(msg_unsupported_syntax) 780 781 :.eof 782 %die(msg_unexp_eof) 783 }) 784 785 # parse_list() -> tagged list value in a0. Cursor sits past '(' on entry; 786 # returns once ')' is consumed. 787 # 788 # Locals: 789 # head NIL until first item 790 # tail most recent cons (set-cdr! target) 791 %gcfn2(parse_list, {head tail}, 3, 0, { 792 %li(t0, %imm_val(%IMM.NIL)) 793 %stl(t0, head) 794 %stl(t0, tail) 795 796 :.loop 797 %call(&skip_ws) 798 %bnez(a0, &.eof) 799 %ld_global(t0, &readbuf_pos) 800 %ld_global(t1, &readbuf_len) 801 802 %readbuf_byte(a0, t0) 803 %bceq(a0, -41, &.close, a1) 804 805 # Dotted-pair separator: '.' followed by ws/paren/EOF (otherwise the 806 # '.' is part of an identifier and parse_atom handles it). 807 %bcne(a0, -46, &.not_dot, a1) ; '.' 808 %addi(a2, t0, 1) 809 %beq(a2, t1, &.do_dot) 810 %readbuf_byte(a3, a2) 811 %is_ws_branch(a1, a3, &.do_dot) 812 %bceq(a3, -40, &.do_dot, a1) 813 %bceq(a3, -41, &.do_dot, a1) 814 :.not_dot 815 816 # Not ')': parse one item, append. 817 %call(&parse_one) 818 %li(a1, %imm_val(%IMM.NIL)) 819 %call(&cons) 820 821 # If head is NIL, both head and tail = new cons; else set-cdr! tail = new. 822 %ldl(t0, head) 823 %bine(t0, %imm_val(%IMM.NIL), &.link, t1) 824 %stl(a0, head) 825 %stl(a0, tail) 826 %b(&.loop) 827 828 :.link 829 %ldl(t0, tail) 830 # set-cdr! tail = a0 -> store a0 at [tail + 7] (raw + 8) 831 %set_cdr(a0, t0) 832 %stl(a0, tail) 833 %b(&.loop) 834 835 :.do_dot 836 # Consume the '.', read one datum, splice it in as the cdr of the 837 # tail cons. Then expect a closing ')' (with optional ws). 838 %lda_global(t0, t1, &readbuf_pos) 839 %readbuf_advance(t0, t1) 840 %call(&parse_one) 841 %ldl(t0, tail) 842 %set_cdr(a0, t0) 843 %call(&skip_ws) 844 %bnez(a0, &.eof) 845 %lda_global(t0, t1, &readbuf_pos) 846 %readbuf_byte(a0, t0) 847 %bcne(a0, -41, &.eof, a1) ; ')' 848 %readbuf_advance(t0, t1) 849 %ldl(a0, head) 850 %gceret 851 852 :.close 853 # Consume ')' and return head. 854 %lda_global(t1, t0, &readbuf_pos) 855 %readbuf_advance(t1, t0) 856 %ldl(a0, head) 857 %gceret 858 859 :.eof 860 %die(msg_unterm_list) 861 }) 862 863 # parse_u8_body() -> tagged HDR.BV in a0. Cursor sits past '#u8(' on 864 # entry. Reads elements via parse_list; every element must be a fixnum byte 865 # in 0..255, then packs them into a fresh bytevector. 866 # 867 # Locals: 868 # list parsed element list (cursor during fill pass) 869 # result freshly allocated bv 870 %gcfn2(parse_u8_body, {list result}, 3, 0, { 871 %call(&parse_list) 872 %stl(a0, list) 873 874 %call(&list_length) ; clobbers a0 -> count 875 %call(&bv_alloc) ; a0 = bv 876 %stl(a0, result) 877 878 %heap_ld(t0, a0, %BV.data) 879 %ldl(a0, list) ; list cursor 880 881 :.loop 882 %if_nil(t1, a0, &.done) 883 %car(t1, a0) 884 %tagof(t2, t1) 885 %bine(t2, %TAG.FIXNUM, &.bad, a1) 886 %untag_fix(t1, t1) 887 %bltz(t1, &.bad) 888 %li(t2, 256) 889 %bltu(t1, t2, &.store) 890 %b(&.bad) 891 :.store 892 %sb(t1, t0, 0) 893 %addi(t0, t0, 1) 894 %cdr(a0, a0) 895 %b(&.loop) 896 :.done 897 %ldl(a0, result) 898 %gceret 899 900 :.bad 901 %die(msg_bad_byte) 902 }) 903 904 # is_ident_byte(c=a0) -> a1 (1 if c is a valid identifier byte, else 0). 905 # Leaf. Allowed bytes are R7RS-Small's identifier set: ASCII letters, 906 # digits, and the extended chars ! $ % & * + - . / : < = > ? @ ^ _ ~ . 907 # Clobbers t0, t1, a1. 908 :is_ident_byte 909 .scope 910 %brange(a0, -48, 10, t0, t1, &.ok) ; '0'..'9' 911 %brange(a0, -65, 26, t0, t1, &.ok) ; 'A'..'Z' 912 %brange(a0, -97, 26, t0, t1, &.ok) ; 'a'..'z' 913 914 %bceq(a0, -33, &.ok, t0) ; '!' 915 %bceq(a0, -36, &.ok, t0) ; '$' 916 %bceq(a0, -37, &.ok, t0) ; '%' 917 %bceq(a0, -38, &.ok, t0) ; '&' 918 %bceq(a0, -42, &.ok, t0) ; '*' 919 %bceq(a0, -43, &.ok, t0) ; '+' 920 %bceq(a0, -45, &.ok, t0) ; '-' 921 %bceq(a0, -46, &.ok, t0) ; '.' 922 %bceq(a0, -47, &.ok, t0) ; '/' 923 %bceq(a0, -58, &.ok, t0) ; ':' 924 %bceq(a0, -60, &.ok, t0) ; '<' 925 %bceq(a0, -61, &.ok, t0) ; '=' 926 %bceq(a0, -62, &.ok, t0) ; '>' 927 %bceq(a0, -63, &.ok, t0) ; '?' 928 %bceq(a0, -64, &.ok, t0) ; '@' 929 %bceq(a0, -94, &.ok, t0) ; '^' 930 %bceq(a0, -95, &.ok, t0) ; '_' 931 %bceq(a0, -126, &.ok, t0) ; '~' 932 933 %li(a1, 0) 934 %ret 935 936 :.ok 937 %li(a1, 1) 938 %ret 939 .endscope 940 941 # parse_atom() -> tagged value (fixnum or symbol) in a0. 942 # Reads until whitespace or paren or EOF, then dispatches by first byte. 943 # A token whose first byte is a digit (or sign-then-digit) commits to 944 # parse_dec and any non-numeric byte aborts; otherwise the token is a 945 # symbol and every byte is checked against is_ident_byte before intern. 946 # 947 # Locals: 948 # start cursor (byte offset) 949 # end cursor (byte offset) 950 # cursor (scratch slot for the symbol-validation loop) 951 %fn2(parse_atom, {start end cursor}, { 952 %lda_global(t1, t0, &readbuf_pos) 953 %stl(t1, start) 954 955 %ld_global(t2, &readbuf_len) 956 957 :.scan 958 %beq(t1, t2, &.end) 959 %readbuf_byte(a0, t1) 960 961 %is_token_delimiter_branch(a1, a0, &.end) 962 963 %addi(t1, t1, 1) 964 %b(&.scan) 965 966 :.end 967 %stl(t1, end) 968 %st(t1, t0, 0) 969 970 # Dispatch on the first byte. 971 %ldl(t0, start) 972 %ld_global(a0, &readbuf_buf_ptr) 973 %add(a0, a0, t0) 974 %lb(t1, a0, 0) 975 976 # '0'..'9' -> int 977 %addi(a1, t1, -48) 978 %li(a2, 10) 979 %bltu(a1, a2, &.is_int) 980 # '-' or '+' followed by digit -> int. A lone '+' or '-' falls 981 # through to is_sym (those tokens stay valid identifiers). 982 %bceq(t1, -45, &.sign, a1) ; '-' 983 %bceq(t1, -43, &.sign, a1) ; '+' 984 %b(&.is_sym) 985 :.sign 986 %ldl(t2, end) 987 %addi(t0, t0, 1) 988 %beq(t0, t2, &.is_sym) 989 %readbuf_byte(a0, t0) 990 %addi(a1, a0, -48) 991 %bltu(a1, a2, &.is_int) 992 # fall through to is_sym 993 994 :.is_sym 995 # Validate every byte; abort on the first non-ident byte. 996 %ldl(t0, start) 997 %stl(t0, cursor) 998 :.sym_loop 999 %ldl(t0, cursor) 1000 %ldl(t1, end) 1001 %beq(t0, t1, &.sym_intern) 1002 %readbuf_byte(a0, t0) 1003 %call(&is_ident_byte) 1004 %beqz(a1, &.sym_bad) 1005 %ldl(t0, cursor) 1006 %addi(t0, t0, 1) 1007 %stl(t0, cursor) 1008 %b(&.sym_loop) 1009 1010 :.sym_bad 1011 %die(msg_bad_ident) 1012 1013 :.sym_intern 1014 %ldl(a0, start) 1015 %ld_global(t0, &readbuf_buf_ptr) 1016 %add(a0, t0, a0) 1017 %ldl(t1, end) 1018 %ldl(t2, start) 1019 %sub(a1, t1, t2) 1020 %tail(&intern) 1021 1022 :.is_int 1023 %ldl(t0, start) 1024 %ldl(t1, end) 1025 %ld_global(a0, &readbuf_buf_ptr) 1026 %add(a0, a0, t0) 1027 %sub(a1, t1, t0) ; len = end - start 1028 # P1pp's parse_dec handles '-' but not '+'; strip '+' here. 1029 %lb(t2, a0, 0) 1030 %bcne(t2, -43, &.no_plus, t2) ; '+' 1031 %addi(a0, a0, 1) 1032 %addi(a1, a1, -1) 1033 :.no_plus 1034 %stl(a1, cursor) ; save adjusted len (cursor slot is free on int path) 1035 %call(&parse_exact_dec) ; -> (raw_val=a0, consumed=a1) 1036 %ldl(t0, cursor) 1037 %bne(a1, t0, &.int_bad) ; partial parse -> bad 1038 %mov(t0, a0) 1039 %mkfix(a0, t0) 1040 %untag_fix(t1, a0) 1041 %bne(t0, t1, &.int_overflow) 1042 %eret 1043 :.int_bad 1044 %die(msg_bad_number) 1045 :.int_overflow 1046 %die(msg_integer_overflow) 1047 }) 1048 1049 # parse_string() -> tagged string in a0. Cursor sits past the 1050 # opening '"' (consumed by parse_one). Two-pass: pass 1 walks the body, 1051 # counting decoded bytes in a0 and locating the closing '"'; pass 2 1052 # allocates the string and decodes into its data buffer. Each named escape 1053 # (\n \t \r \\ \") yields one byte; an inline-hex escape \xHEX; (1+ 1054 # hex digits, value 0..255, terminated by ';') also yields one byte. 1055 # 1056 # Locals: 1057 # start cursor (first content byte) 1058 # end cursor (closing '"' position) 1059 # bv string wrapper (saved across the data fill loop) 1060 # spill slot (write ptr saved across parse_hex in \x escape) 1061 %fn2(parse_string, {start end bv spill hex_len}, { 1062 %ld_global(t1, &readbuf_pos) 1063 %stl(t1, start) 1064 1065 %ld_global(t2, &readbuf_len) 1066 1067 %li(a0, 0) 1068 :.scan 1069 %beq(t1, t2, &.eof) 1070 %readbuf_byte(a3, t1) 1071 %bceq(a3, -34, &.scan_done, a1) ; '"' 1072 %bceq(a3, -92, &.scan_esc, a1) ; '\\' 1073 %addi(t1, t1, 1) 1074 %addi(a0, a0, 1) 1075 %b(&.scan) 1076 1077 :.scan_esc 1078 # Backslash plus the next byte yield one decoded byte. \xHEX; runs 1079 # until the terminating ';' (validated in pass 2); every other escape 1080 # is exactly two source bytes. 1081 %addi(t1, t1, 1) 1082 %beq(t1, t2, &.eof) 1083 %readbuf_byte(a3, t1) 1084 %bceq(a3, -120, &.scan_hex, a1) ; 'x' 1085 %addi(t1, t1, 1) 1086 %addi(a0, a0, 1) 1087 %b(&.scan) 1088 1089 :.scan_hex 1090 # Skip past 'x' and scan to the terminating ';'. EOF before ';' 1091 # falls into the unterminated-string path below, matching how an 1092 # unterminated body is reported. 1093 %addi(t1, t1, 1) 1094 :.scan_hex_loop 1095 %beq(t1, t2, &.eof) 1096 %readbuf_byte(a3, t1) 1097 %bceq(a3, -59, &.scan_hex_done, a1) ; ';' 1098 %addi(t1, t1, 1) 1099 %b(&.scan_hex_loop) 1100 :.scan_hex_done 1101 %addi(t1, t1, 1) ; consume ';' 1102 %addi(a0, a0, 1) ; +1 output byte 1103 %b(&.scan) 1104 :.scan_done 1105 1106 %stl(t1, end) 1107 %call(&string_alloc) 1108 %stl(a0, bv) 1109 1110 # Pass 2: decode into the freshly allocated data buffer. 1111 %ldl(t1, start) 1112 %ldl(t2, end) 1113 %heap_ld(a3, a0, %BV.data) 1114 1115 :.fill 1116 %beq(t1, t2, &.fill_done) 1117 %readbuf_byte(a1, t1) 1118 %bceq(a1, -92, &.fill_esc, a2) ; '\\' 1119 %sb(a1, a3, 0) 1120 %addi(a3, a3, 1) 1121 %addi(t1, t1, 1) 1122 %b(&.fill) 1123 1124 :.fill_esc 1125 %addi(t1, t1, 1) ; consume backslash 1126 %readbuf_byte(a1, t1) 1127 %bceq(a1, -110, &.esc_n, a2) ; 'n' 1128 %bceq(a1, -116, &.esc_t, a2) ; 't' 1129 %bceq(a1, -114, &.esc_r, a2) ; 'r' 1130 %bceq(a1, -92, &.write_byte, a2) ; '\\' 1131 %bceq(a1, -34, &.write_byte, a2) ; '"' 1132 %bceq(a1, -120, &.esc_hex, a2) ; 'x' 1133 %die(msg_bad_escape) 1134 1135 :.esc_n 1136 %li(a1, 10) 1137 %b(&.write_byte) 1138 :.esc_t 1139 %li(a1, 9) 1140 %b(&.write_byte) 1141 :.esc_r 1142 %li(a1, 13) 1143 :.write_byte 1144 %sb(a1, a3, 0) 1145 %addi(a3, a3, 1) 1146 %addi(t1, t1, 1) 1147 %b(&.fill) 1148 1149 :.esc_hex 1150 # Skip past 'x', locate the immediate ';', and normalize leading zeroes. 1151 # A byte has at most two significant hex digits; capping before parse_hex 1152 # prevents a huge escape from wrapping the host word back into 0..255. 1153 %addi(t1, t1, 1) ; t1 -> first hex digit 1154 %stl(a3, spill) 1155 %mov(t0, t1) 1156 :.esc_hex_scan 1157 %beq(t0, t2, &.hex_bad) 1158 %readbuf_byte(a1, t0) 1159 %bceq(a1, -59, &.esc_hex_found, a2) 1160 %addi(t0, t0, 1) 1161 %b(&.esc_hex_scan) 1162 :.esc_hex_found 1163 %stl(t0, start) ; semicolon cursor 1164 %sub(a1, t0, t1) ; digit count 1165 %beqz(a1, &.hex_bad) 1166 :.esc_hex_trim 1167 %li(a2, 1) 1168 %beq(a1, a2, &.esc_hex_count) 1169 %readbuf_byte(a0, t1) 1170 %bcne(a0, -48, &.esc_hex_count, a2) 1171 %addi(t1, t1, 1) 1172 %addi(a1, a1, -1) 1173 %b(&.esc_hex_trim) 1174 :.esc_hex_count 1175 %li(a2, 3) 1176 %bltu(a1, a2, &.esc_hex_parse) 1177 %b(&.hex_bad) 1178 :.esc_hex_parse 1179 %stl(a1, hex_len) 1180 %ld_global(t0, &readbuf_buf_ptr) 1181 %add(a0, t0, t1) ; ptr to first hex digit 1182 %call(&parse_hex) ; -> (a0=value, a1=consumed) 1183 %ldl(t0, hex_len) 1184 %bne(a1, t0, &.hex_bad) 1185 %ldl(t1, start) ; semicolon cursor 1186 %addi(t1, t1, 1) ; consume ';' 1187 %ldl(t2, end) ; parse_hex clobbered the body end 1188 %ldl(a3, spill) 1189 %sb(a0, a3, 0) 1190 %addi(a3, a3, 1) 1191 %b(&.fill) 1192 1193 :.hex_bad 1194 %die(msg_bad_escape) 1195 1196 :.fill_done 1197 %addi(t1, t1, 1) ; consume closing '"' 1198 %st_global(t1, &readbuf_pos, t0) 1199 %ldl(a0, bv) 1200 %eret 1201 1202 :.eof 1203 %die(msg_unterm_string) 1204 }) 1205 1206 # Emit one named-char arm inside parse_char's multi-byte dispatch. t2 1207 # must hold the slice pointer; ::bad must be in scope. name_label is a 1208 # full label reference (e.g. &name_ch_tab). 1209 %macro match_named_char(name_label, len, value) 1210 %mov(a0, t2) 1211 %la(a1, name_label) 1212 %li(a2, len) 1213 %call(&memcmp) 1214 %bnez(a0, &.bad) 1215 %li(a0, value) 1216 %mkchar(a0, a0) 1217 %eret 1218 %endm 1219 1220 # parse_char() -> tagged character (the u8 value) in a0. Cursor sits 1221 # past '#\\' (consumed by parse_one's hash dispatch). Always consumes 1222 # at least one byte; then continues until ws/paren/EOF. Single-byte 1223 # bodies yield that byte; multi-byte bodies dispatch to hex (#\xNN) or 1224 # named (#\space, #\newline, #\tab, #\return, #\null) forms. 1225 # 1226 # Locals: 1227 # start cursor 1228 # end cursor 1229 %fn2(parse_char, {start end}, { 1230 %lda_global(t1, t0, &readbuf_pos) 1231 %stl(t1, start) 1232 1233 %ld_global(t2, &readbuf_len) 1234 1235 %beq(t1, t2, &.short) 1236 1237 # Always consume the first byte unconditionally — it might itself be 1238 # a delimiter (e.g., '(' in `#\(`) and is still the character value. 1239 %addi(t1, t1, 1) 1240 1241 :.scan 1242 %beq(t1, t2, &.scan_done) 1243 %readbuf_byte(a0, t1) 1244 %is_token_delimiter_branch(a1, a0, &.scan_done) 1245 %addi(t1, t1, 1) 1246 %b(&.scan) 1247 1248 :.scan_done 1249 %stl(t1, end) 1250 %st(t1, t0, 0) 1251 1252 %ldl(t0, start) 1253 %ldl(t1, end) 1254 %sub(a2, t1, t0) ; length 1255 1256 %bieq(a2, 1, &.single, a3) 1257 1258 %ld_global(t2, &readbuf_buf_ptr) 1259 %add(t2, t2, t0) ; t2 = slice ptr 1260 1261 # Hex form: first byte is 'x'. 1262 %lb(a0, t2, 0) 1263 %addi(a1, a0, -120) ; 'x' 1264 %beqz(a1, &.hex_form) 1265 1266 # Named form: dispatch on length. 1267 %bieq(a2, 3, &.try_tab, a3) 1268 %bieq(a2, 4, &.try_null, a3) 1269 %bieq(a2, 5, &.try_space, a3) 1270 %bieq(a2, 6, &.try_return, a3) 1271 %bieq(a2, 7, &.try_newline, a3) 1272 %b(&.bad) 1273 1274 :.single 1275 %ld_global(t2, &readbuf_buf_ptr) 1276 %add(t2, t2, t0) 1277 %lb(a0, t2, 0) 1278 %mkchar(a0, a0) 1279 %eret 1280 1281 :.hex_form 1282 %addi(a0, t2, 1) 1283 %addi(a1, a2, -1) 1284 # Strip leading zeroes and reject more than two significant digits 1285 # before calling the machine-word parser. 1286 :.hex_trim 1287 %li(t0, 1) 1288 %beq(a1, t0, &.hex_count) 1289 %lb(t1, a0, 0) 1290 %addi(t1, t1, -48) 1291 %bnez(t1, &.hex_count) 1292 %addi(a0, a0, 1) 1293 %addi(a1, a1, -1) 1294 %b(&.hex_trim) 1295 :.hex_count 1296 %li(t0, 3) 1297 %bltu(a1, t0, &.hex_parse) 1298 %b(&.bad) 1299 :.hex_parse 1300 %stl(a1, start) 1301 %call(&parse_hex) 1302 %ldl(t0, start) 1303 %bne(a1, t0, &.bad) 1304 %li(t0, 256) 1305 %bltu(a0, t0, &.hex_ok) 1306 %b(&.bad) 1307 :.hex_ok 1308 %mkchar(a0, a0) 1309 %eret 1310 1311 :.try_tab 1312 %match_named_char(&name_ch_tab, 3, 9) 1313 1314 :.try_null 1315 %match_named_char(&name_ch_null, 4, 0) 1316 1317 :.try_space 1318 %match_named_char(&name_ch_space, 5, 32) 1319 1320 :.try_return 1321 %match_named_char(&name_ch_return, 6, 13) 1322 1323 :.try_newline 1324 %match_named_char(&name_ch_newline, 7, 10) 1325 1326 :.bad 1327 %die(msg_bad_char) 1328 1329 :.short 1330 %die(msg_bad_char) 1331 }) 1332 1333 1334 # ========================================================================= 1335 # eval / apply 1336 # ========================================================================= 1337 1338 # eval(expr=a0, env=a1) -> value (a0) 1339 # 1340 # Locals: 1341 # expr 1342 # env 1343 # fn (head value, while args are being evaluated) 1344 # pad (first evaluated argument on the direct record-mutator path) 1345 %gcfn2(eval, {expr env fn pad}, 15, 0, { 1346 %stl(a0, expr) 1347 %stl(a1, env) 1348 1349 %tagof(t0, a0) 1350 %bieq(t0, %TAG.SYM, &.sym, t1) 1351 %bieq(t0, %TAG.PAIR, &.pair, t1) 1352 # FIXNUM, HEAP, IMM all self-evaluate. 1353 %gceret 1354 1355 :.sym 1356 # The cumulative environment filter rejects most global references in 1357 # constant time. A possible lexical hit is resolved by exact symbol 1358 # identity along the parent chain. a0 still holds sym; a1 holds env. 1359 %if_nil(t0, a1, &.env_miss) 1360 %call(&env_find) 1361 %beqz(a0, &.env_miss) 1362 %heap_ld(a0, a0, %ENV.value) 1363 %gceret 1364 1365 :.env_miss 1366 %ldl(a0, expr) 1367 %untag_sym(a0, a0) 1368 %call(&sym_global) 1369 %bieq(a0, %imm_val(%IMM.UNBOUND), &.unbound, t0) 1370 %gceret 1371 1372 :.unbound 1373 %die(msg_unbound) 1374 1375 1376 :.pair 1377 1378 # Special-form dispatch: pointer-compare head against the cached 1379 # special-form symbol values. SYM is a distinct tag, so a head that 1380 # isn't a symbol cannot collide with any sym_* slot. 1381 %ldl(t0, expr) 1382 %car(t0, t0) ; t0 = head 1383 %dispatch_form(&sym_quote, &.do_quote) 1384 %dispatch_form(&sym_if, &.do_if) 1385 %dispatch_form(&sym_lambda, &.do_lambda) 1386 %dispatch_form(&sym_define, &.do_define) 1387 %dispatch_form(&sym_begin, &.do_begin) 1388 %dispatch_form(&sym_cond, &.do_cond) 1389 %dispatch_form(&sym_let, &.do_let) 1390 %dispatch_form(&sym_letstar, &.do_letstar) 1391 %dispatch_form(&sym_let_values, &.do_let_values) 1392 %dispatch_form(&sym_letstar_values, &.do_letstar_values) 1393 %dispatch_form(&sym_and, &.do_and) 1394 %dispatch_form(&sym_or, &.do_or) 1395 %dispatch_form(&sym_when, &.do_when) 1396 %dispatch_form(&sym_case, &.do_case) 1397 %dispatch_form(&sym_setbang, &.do_setbang) 1398 %dispatch_form(&sym_define_record_type, &.do_define_record_type) 1399 %dispatch_form(&sym_pmatch, &.do_pmatch) 1400 %dispatch_form(&sym_do, &.do_do) 1401 1402 # Apply car to cdr 1403 # fn = eval(car(expr), env) 1404 %ldl(a0, expr) 1405 %car(a0, a0) 1406 %ldl(a1, env) 1407 %call(&eval) 1408 %stl(a0, fn) 1409 1410 # Generated record predicates/accessors/mutators dominate cc.scm's 1411 # primitive traffic. For their exact call shapes, evaluate directly into 1412 # registers and skip construction of the transient evaluated-argument 1413 # list. Calls with another arity retain the generic path (and therefore 1414 # its existing evaluation/error behavior). 1415 %tagof(t0, a0) 1416 %bine(t0, %TAG.HEAP, &.generic_apply, t1) 1417 %hdr_type(t0, a0) 1418 %bine(t0, %HDR.PRIM, &.generic_apply, t1) 1419 %heap_ld(t0, a0, %PRIM.entry_w) 1420 %la(t1, &prim_predicate_entry) 1421 %beq(t0, t1, &.fast_record_unary) 1422 %la(t1, &prim_accessor_entry) 1423 %beq(t0, t1, &.fast_record_unary) 1424 %la(t1, &prim_mutator_entry) 1425 %beq(t0, t1, &.fast_record_mutator) 1426 %b(&.generic_apply) 1427 1428 :.fast_record_unary 1429 # Require exactly one expression argument. 1430 %ldl(t0, expr) 1431 %cdr(t0, t0) 1432 %tagof(t1, t0) 1433 %bine(t1, %TAG.PAIR, &.generic_apply, t2) 1434 %cdr(t1, t0) 1435 %if_nil(t2, t1, &.fast_record_unary_eval) 1436 %b(&.generic_apply) 1437 1438 :.fast_record_unary_eval 1439 %car(a0, t0) 1440 %ldl(a1, env) 1441 %call(&eval) 1442 %ldl(t0, fn) 1443 %heap_ld(t1, t0, %PRIM.entry_w) 1444 %la(t2, &prim_accessor_entry) 1445 %beq(t1, t2, &.fast_record_accessor_done) 1446 1447 # Predicate: prim.data is the TD and a0 is the candidate record. 1448 %heap_ld(t1, t0, %PRIM.data) 1449 %tagof(t2, a0) 1450 %li(t0, %imm_val(%IMM.FALSE)) 1451 %bine(t2, %TAG.HEAP, &.fast_record_pred_done, a1) 1452 %hdr_type(t2, a0) 1453 %bine(t2, %HDR.REC, &.fast_record_pred_done, a1) 1454 %heap_ld(t2, a0, %REC.td) 1455 %bne(t2, t1, &.fast_record_pred_done) 1456 %li(t0, %imm_val(%IMM.TRUE)) 1457 :.fast_record_pred_done 1458 %mov(a0, t0) 1459 %gceret 1460 1461 :.fast_record_accessor_done 1462 # Accessor: prim.data is a tagged field index. 1463 %heap_ld(t1, t0, %PRIM.data) 1464 %addi(t1, t1, 13) 1465 %add(t1, t1, a0) 1466 %ld(a0, t1, 0) 1467 %gceret 1468 1469 :.fast_record_mutator 1470 # Require exactly two expression arguments. 1471 %ldl(t0, expr) 1472 %cdr(t0, t0) 1473 %tagof(t1, t0) 1474 %bine(t1, %TAG.PAIR, &.generic_apply, t2) 1475 %cdr(t1, t0) 1476 %tagof(t2, t1) 1477 %bine(t2, %TAG.PAIR, &.generic_apply, a0) 1478 %cdr(t2, t1) 1479 %if_nil(a0, t2, &.fast_record_mutator_eval) 1480 %b(&.generic_apply) 1481 1482 :.fast_record_mutator_eval 1483 %car(a0, t0) 1484 %ldl(a1, env) 1485 %call(&eval) 1486 %stl(a0, pad) 1487 %ldl(t0, expr) 1488 %cdr(t0, t0) 1489 %cdr(t0, t0) 1490 %car(a0, t0) 1491 %ldl(a1, env) 1492 %call(&eval) 1493 %ldl(t0, fn) 1494 %heap_ld(t1, t0, %PRIM.data) 1495 %ldl(t0, pad) 1496 %addi(t1, t1, 13) 1497 %add(t1, t1, t0) 1498 %st(a0, t1, 0) 1499 %li(a0, %imm_val(%IMM.UNSPEC)) 1500 %gceret 1501 1502 :.generic_apply 1503 # args = eval_args(cdr(expr), env) 1504 %ldl(a0, expr) 1505 %cdr(a0, a0) 1506 %ldl(a1, env) 1507 %call(&eval_args) 1508 # apply(fn, args) -- tail call 1509 %mov(a1, a0) 1510 %ldl(a0, fn) 1511 %gctail(&apply) 1512 1513 :.do_quote 1514 %tail_to_handler(&eval_quote) 1515 :.do_if 1516 %tail_to_handler(&eval_if) 1517 :.do_lambda 1518 %tail_to_handler(&eval_lambda) 1519 :.do_define 1520 %tail_to_handler(&eval_define) 1521 :.do_begin 1522 %tail_to_handler(&eval_body) 1523 :.do_cond 1524 %tail_to_handler(&eval_cond) 1525 :.do_let 1526 %tail_to_handler(&eval_let) 1527 :.do_letstar 1528 %tail_to_handler(&eval_letstar) 1529 :.do_let_values 1530 %tail_to_handler(&eval_let_values) 1531 :.do_letstar_values 1532 %tail_to_handler(&eval_letstar_values) 1533 :.do_and 1534 %tail_to_handler(&eval_and) 1535 :.do_or 1536 %tail_to_handler(&eval_or) 1537 :.do_when 1538 %tail_to_handler(&eval_when) 1539 :.do_case 1540 %tail_to_handler(&eval_case) 1541 :.do_setbang 1542 %tail_to_handler(&eval_setbang) 1543 :.do_define_record_type 1544 %tail_to_handler(&eval_define_record_type) 1545 :.do_pmatch 1546 %tail_to_handler(&eval_pmatch) 1547 :.do_do 1548 %tail_to_handler(&eval_do) 1549 }) 1550 1551 # eval_args(args=a0, env=a1) -> evaluated args list (cons-built). 1552 # Iterative head/tail-cdr build: each iteration evals one arg, allocates 1553 # a (val . NIL) cell, and either seeds head/tail or set-cdr!s onto the 1554 # previous tail. Host stack stays O(1) regardless of arg-list length; 1555 # eval order is left-to-right. 1556 # 1557 # Locals: 1558 # args (advances) 1559 # env 1560 # head (NIL until first val is appended) 1561 # tail (most recent cell; set-cdr! target) 1562 %gcfn2(eval_args, {args env head tail}, 15, 0, { 1563 %stl(a0, args) 1564 %stl(a1, env) 1565 %li(t0, %imm_val(%IMM.NIL)) 1566 %stl(t0, head) 1567 %stl(t0, tail) 1568 1569 :.loop 1570 %ldl(t0, args) 1571 %if_nil(t1, t0, &.done) 1572 1573 # val = eval(car(args), env) 1574 %car(a0, t0) 1575 %ldl(a1, env) 1576 %call(&eval) 1577 1578 # cell = cons(val, NIL); append to head/tail. 1579 %li(a1, %imm_val(%IMM.NIL)) 1580 %call(&cons) 1581 1582 %ldl(t0, head) 1583 %if_nil(t1, t0, &.first) 1584 %ldl(t0, tail) 1585 %set_cdr(a0, t0) 1586 %stl(a0, tail) 1587 %b(&.advance) 1588 1589 :.first 1590 %stl(a0, head) 1591 %stl(a0, tail) 1592 1593 :.advance 1594 %advance_walk(args) 1595 %b(&.loop) 1596 :.done 1597 1598 %ldl(a0, head) 1599 }) 1600 1601 1602 # apply(fn=a0, args=a1) -> result (a0) 1603 # 1604 # Locals: 1605 # args 1606 # body (saved across bind_params for the closure path) 1607 %gcfn2(apply, {args body}, 3, 0, { 1608 %stl(a1, args) 1609 1610 %hdr_type(t0, a0) 1611 %bieq(t0, %HDR.PRIM, &.prim, t1) 1612 %bieq(t0, %HDR.CLOSURE, &.closure, t1) 1613 1614 :.prim 1615 # Primitive calling convention: 1616 # a0 = args list (proper list of evaluated args) 1617 # a1 = the PRIM object itself (HEAP-tagged) 1618 # Parameterized PRIMs (e.g. the per-field record accessors built 1619 # by define-record-type) read their closed-over datum from 1620 # a1+13. Plain PRIMs ignore a1. A primitive that needs a1 as a 1621 # working register must save it first; this convention is shared 1622 # across every entry in prim_table and is not negotiable per 1623 # primitive. prim_apply_entry maintains the same contract when it 1624 # tail-calls back into apply. 1625 %mov(a1, a0) 1626 %heap_ld(t0, a0, %PRIM.entry_w) 1627 %ldl(a0, args) 1628 %gctailr(t0) 1629 1630 :.closure 1631 # Closure layout (HEAP-tagged): [hdr][params][body][env] 1632 %heap_ld(t0, a0, %CLOSURE.params) 1633 %heap_ld(t1, a0, %CLOSURE.body) 1634 %heap_ld(t2, a0, %CLOSURE.env) 1635 %stl(t1, body) ; persist body past bind_params 1636 1637 # bind_params(params, args, env) 1638 %mov(a0, t0) 1639 %ldl(a1, args) 1640 %mov(a2, t2) 1641 %call(&bind_params) 1642 1643 # eval_body(body, new_env) -- tail call 1644 %mov(a1, a0) 1645 %ldl(a0, body) 1646 %gctail(&eval_body) 1647 }) 1648 1649 # ========================================================================= 1650 # Special forms 1651 # ========================================================================= 1652 # 1653 # intern_special_forms runs at startup, before register_primitives, so 1654 # the symbols `if`, ... occupy the first sym_idx slots (per LISP-C.md 1655 # §Reservation convention). For now we just cache each one's tagged 1656 # value in a labeled slot; eval's pair branch compares head against 1657 # these slots before falling through to ordinary application. 1658 1659 %fn(intern_special_forms, 0, { 1660 %intern_form(quote, "quote", &sym_quote) 1661 %intern_form(if, "if", &sym_if) 1662 %intern_form(lambda, "lambda", &sym_lambda) 1663 %intern_form(define, "define", &sym_define) 1664 %intern_form(begin, "begin", &sym_begin) 1665 %intern_form(cond, "cond", &sym_cond) 1666 %intern_form(else, "else", &sym_else) 1667 %intern_form(arrow, "=>", &sym_arrow) 1668 %intern_form(let, "let", &sym_let) 1669 %intern_form(letstar, "let*", &sym_letstar) 1670 %intern_form(let_values, "let-values", &sym_let_values) 1671 %intern_form(letstar_values, "let*-values", &sym_letstar_values) 1672 %intern_form(and, "and", &sym_and) 1673 %intern_form(or, "or", &sym_or) 1674 %intern_form(when, "when", &sym_when) 1675 %intern_form(case, "case", &sym_case) 1676 %intern_form(setbang, "set!", &sym_setbang) 1677 %intern_form(define_record_type, "define-record-type", &sym_define_record_type) 1678 %intern_form(pmatch, "pmatch", &sym_pmatch) 1679 %intern_form(do, "do", &sym_do) 1680 %intern_form(unquote, "unquote", &sym_unquote) 1681 %intern_form(guard, "guard", &sym_guard) 1682 %intern_form(underscore, "_", &sym_underscore) 1683 %intern_form(dollar, "$", &sym_dollar) 1684 }) 1685 1686 # eval_quote(rest=a0, env=a1) -> value (a0). rest is (datum); return datum. 1687 %fn(eval_quote, 0, { 1688 %car(a0, a0) 1689 }) 1690 1691 # eval_if(rest=a0, env=a1) -> value (a0). `rest` is (test then) or 1692 # (test then else). Single-arm form returns UNSPEC when test is #f. 1693 # No arity check beyond that -- spec policy: malformed special forms 1694 # are UB. 1695 # 1696 # Locals: 1697 # rest 1698 # env 1699 %gcfn2(eval_if, {rest env}, 3, 0, { 1700 %stl(a0, rest) 1701 %stl(a1, env) 1702 1703 # val = eval(car(rest), env) 1704 %car(a0, a0) 1705 %call(&eval) 1706 1707 %bieq(a0, %imm_val(%IMM.FALSE), &.else_branch, t0) 1708 1709 # then-branch: tail-eval(cadr(rest), env) 1710 %ldl(a0, rest) 1711 %cdr(a0, a0) 1712 %car(a0, a0) 1713 %ldl(a1, env) 1714 %gctail(&eval) 1715 1716 :.else_branch 1717 # If cddr(rest) is NIL, this is single-arm `if` -> UNSPEC. 1718 %ldl(t0, rest) 1719 %cdr(t0, t0) 1720 %cdr(t0, t0) 1721 %if_nil(t1, t0, &.no_else) 1722 1723 # else-branch: tail-eval(car(cddr(rest)), env) 1724 %car(a0, t0) 1725 %ldl(a1, env) 1726 %gctail(&eval) 1727 1728 :.no_else 1729 %li(a0, %imm_val(%IMM.UNSPEC)) 1730 }) 1731 1732 # eval_lambda(rest=a0, env=a1) -> closure (a0). 1733 # rest is (params . body). Allocates a 32-byte CLOSURE on the heap 1734 # and stores params, body, and the captured env directly. 1735 # 1736 # Locals: 1737 # rest 1738 # env 1739 # closure ptr (HEAP-tagged) 1740 %gcfn2(eval_lambda, {rest env closure}, 7, 0, { 1741 %stl(a0, rest) 1742 %stl(a1, env) 1743 1744 %li(a0, %CLOSURE.SIZE) 1745 %li(a1, %HDR.CLOSURE) 1746 %call(&alloc_hdr) 1747 %stl(a0, closure) 1748 1749 # closure[params] = car(rest) 1750 %ldl(t0, rest) 1751 %car(t1, t0) 1752 %ldl(t0, closure) 1753 %heap_st(t1, t0, %CLOSURE.params) 1754 1755 # closure[body] = cdr(rest) 1756 %ldl(t1, rest) 1757 %cdr(t1, t1) 1758 %heap_st(t1, t0, %CLOSURE.body) 1759 1760 # closure[env] = captured env 1761 %ldl(t1, env) 1762 %heap_st(t1, t0, %CLOSURE.env) 1763 1764 %ldl(a0, closure) 1765 }) 1766 1767 # eval_define(rest=a0, env=a1) -> UNSPEC (a0). 1768 # Top-level only. Two surface forms: 1769 # (define <sym> <expr>) ; head of rest is a SYM 1770 # (define (<sym> . <params>) . body) ; head of rest is a PAIR; sugar for 1771 # (define <sym> (lambda <params> . body)) 1772 # Internal `define` is rejected by eval_body before this entry is reached 1773 # (every internal body context routes through eval_body); see the check 1774 # at the head of eval_body's loop. 1775 # 1776 # Locals: 1777 # rest 1778 # env 1779 %gcfn2(eval_define, {rest env}, 3, 0, { 1780 %stl(a0, rest) 1781 %stl(a1, env) 1782 1783 # If car(rest) is a pair, this is the lambda-sugar form. 1784 %car(t0, a0) 1785 %tagof(t1, t0) 1786 %bieq(t1, %TAG.PAIR, &.sugar, t2) 1787 1788 # Plain define: value = eval(car(cdr(rest)), env) 1789 %ldl(t0, rest) 1790 %cdr(a0, t0) 1791 %car(a0, a0) 1792 %ldl(a1, env) 1793 %call(&eval) 1794 1795 %ldl(t0, rest) 1796 %car(t0, t0) 1797 %set_global(t0, a0) 1798 %li(a0, %imm_val(%IMM.UNSPEC)) 1799 %gceret 1800 1801 :.sugar 1802 # rest = ((name . params) . body); build (params . body) for eval_lambda. 1803 %ldl(t0, rest) 1804 %car(t0, t0) 1805 %cdr(a0, t0) ; params 1806 %ldl(t0, rest) 1807 %cdr(a1, t0) ; body 1808 %call(&cons) 1809 %ldl(a1, env) 1810 %call(&eval_lambda) 1811 1812 %ldl(t0, rest) 1813 %car(t0, t0) 1814 %car(t0, t0) ; name 1815 %set_global(t0, a0) 1816 %li(a0, %imm_val(%IMM.UNSPEC)) 1817 }) 1818 1819 # eval_setbang(rest=a0, env=a1) -> UNSPEC (a0). 1820 # rest = (sym value-expr). Evaluates value-expr in env, then finds the target 1821 # ENV node through the same filtered lookup used by eval. On a lexical hit it 1822 # mutates the node's value slot. On a miss it may update an existing global 1823 # binding, but it must not create one. 1824 # 1825 # Locals: 1826 # rest (sym . (value-expr . ())) 1827 # env 1828 # saved value (eval'd value-expr) 1829 %gcfn2(eval_setbang, {rest env saved}, 7, 0, { 1830 %stl(a0, rest) 1831 %stl(a1, env) 1832 1833 # value = eval(cadr(rest), env) 1834 %cdr(a0, a0) 1835 %car(a0, a0) 1836 %ldl(a1, env) 1837 %call(&eval) 1838 %stl(a0, saved) 1839 1840 %ldl(a0, rest) 1841 %car(a0, a0) ; target sym 1842 %ldl(a1, env) 1843 %call(&env_find) 1844 %beqz(a0, &.miss) 1845 %mov(t0, a0) ; lexical ENV node 1846 %ldl(a0, saved) 1847 %heap_st(a0, t0, %ENV.value) 1848 %li(a0, %imm_val(%IMM.UNSPEC)) 1849 %gceret 1850 1851 :.miss 1852 # Miss: require an existing global binding before replacing it. 1853 %ldl(t0, rest) 1854 %car(a0, t0) 1855 %untag_sym(a0, a0) 1856 %call(&sym_global) 1857 %bieq(a0, %imm_val(%IMM.UNBOUND), &.unbound, t0) 1858 %ldl(a0, saved) 1859 %ldl(t0, rest) 1860 %car(t0, t0) 1861 %set_global(t0, a0) 1862 %li(a0, %imm_val(%IMM.UNSPEC)) 1863 %gceret 1864 1865 :.unbound 1866 %die(msg_unbound_set) 1867 }) 1868 1869 # eval_cond(clauses=a0, env=a1) -> value (a0). 1870 # Clause shapes: (else body...), (test body...), (test => proc-expr). 1871 # else / => are literal symbols matched by pointer equality. The => 1872 # arrow is only recognized in non-else clauses. A successful one-expression 1873 # clause returns its test value, and `else` must be final. 1874 # 1875 # Locals: 1876 # clauses (advances) 1877 # env 1878 # test validation cursor, then value (live across the => eval/cons calls) 1879 # proc (live across the => cons call) 1880 %gcfn2(eval_cond, {clauses env test proc}, 15, 0, { 1881 %stl(a0, clauses) 1882 %stl(a1, env) 1883 1884 # Validate the placement of `else` before evaluating any test. Otherwise 1885 # an earlier true clause could make a syntactically invalid later `else` 1886 # silently unreachable. 1887 %stl(a0, test) 1888 :.validate 1889 %ldl(t0, test) 1890 %if_nil(t1, t0, &.loop) 1891 %car(t1, t0) ; clause 1892 %car(t2, t1) ; test expression 1893 %ld_global(a0, &sym_else) 1894 %bne(t2, a0, &.validate_next) 1895 %cdr(t0, t0) 1896 %if_nil(t1, t0, &.loop) 1897 %die(msg_cond_else_not_final) 1898 :.validate_next 1899 %advance_walk(test) 1900 %b(&.validate) 1901 1902 :.loop 1903 %ldl(t0, clauses) 1904 %if_nil(t1, t0, &.no_match) 1905 1906 %car(t1, t0) ; clause 1907 %car(t2, t1) ; test_expr 1908 1909 %ld_global(a0, &sym_else) 1910 %beq(t2, a0, &.else_clause) 1911 1912 %mov(a0, t2) 1913 %ldl(a1, env) 1914 %call(&eval) 1915 %li(t0, %imm_val(%IMM.FALSE)) 1916 %beq(a0, t0, &.next) 1917 1918 # Truthy. Spill test value and inspect cdr(clause): empty -> test value, 1919 # car == => -> arrow path, else regular body. 1920 %stl(a0, test) 1921 %ldl(t0, clauses) 1922 %car(t0, t0) 1923 %cdr(t0, t0) 1924 %if_nil(t1, t0, &.test_only) 1925 %car(t1, t0) 1926 %ld_global(t2, &sym_arrow) 1927 %beq(t1, t2, &.arrow) 1928 1929 %mov(a0, t0) ; regular body 1930 %ldl(a1, env) 1931 %gctail(&eval_body) 1932 1933 :.arrow 1934 %cdr(t0, t0) 1935 %car(a0, t0) ; proc-expr 1936 %ldl(a1, env) 1937 %call(&eval) 1938 %stl(a0, proc) 1939 %ldl(a0, test) 1940 %li(a1, %imm_val(%IMM.NIL)) 1941 %call(&cons) 1942 %mov(a1, a0) 1943 %ldl(a0, proc) 1944 %gctail(&apply) 1945 1946 :.test_only 1947 %ldl(a0, test) 1948 %gceret 1949 1950 :.else_clause 1951 %ldl(t0, clauses) 1952 %cdr(t0, t0) 1953 %if_nil(t1, t0, &.else_final) 1954 %die(msg_cond_else_not_final) 1955 :.else_final 1956 %ldl(t0, clauses) 1957 %car(t0, t0) 1958 %cdr(a0, t0) 1959 %ldl(a1, env) 1960 %gctail(&eval_body) 1961 1962 :.next 1963 %advance_walk(clauses) 1964 %b(&.loop) 1965 1966 :.no_match 1967 %li(a0, %imm_val(%IMM.UNSPEC)) 1968 }) 1969 1970 # eval_let(rest=a0, env=a1) -> value (a0). 1971 # Two surface forms: 1972 # (let ((p v) ...) body...) 1973 # (let name ((p v) ...) body...) ; named let, dispatches to eval_let_named 1974 # Standard `let` evaluates every init in `env`, then extends env with all 1975 # bindings simultaneously and tail-evaluates the body. 1976 # 1977 # Locals: 1978 # rest 1979 # env (original) 1980 # walk (bindings, advances) 1981 # new_env (built up) 1982 %gcfn2(eval_let, {rest env walk new_env}, 15, 0, { 1983 %stl(a0, rest) 1984 %stl(a1, env) 1985 1986 # Named let? 1987 %car(t0, a0) 1988 %tagof(t1, t0) 1989 %bieq(t1, %TAG.SYM, &.named, t2) 1990 1991 %ldl(t0, rest) 1992 %car(t0, t0) ; bindings 1993 %stl(t0, walk) 1994 %ldl(t0, env) 1995 %stl(t0, new_env) ; new_env = env 1996 1997 :.loop 1998 %ldl(t0, walk) 1999 %if_nil(t1, t0, &.done) 2000 2001 %car(t1, t0) ; pair = (name init) 2002 %cdr(t2, t1) 2003 %car(t2, t2) ; init 2004 2005 # val = eval(init, env_orig) 2006 %mov(a0, t2) 2007 %ldl(a1, env) 2008 %call(&eval) 2009 2010 # new_env = env_extend(name, val, new_env) 2011 %ldl(t0, walk) 2012 %car(t1, t0) 2013 %car(t2, t1) 2014 %mov(a1, a0) 2015 %mov(a0, t2) 2016 %ldl(a2, new_env) 2017 %call(&env_extend) 2018 %stl(a0, new_env) 2019 2020 %advance_walk(walk) 2021 %b(&.loop) 2022 2023 :.done 2024 %ldl(a0, rest) 2025 %cdr(a0, a0) ; body 2026 %ldl(a1, new_env) 2027 %gctail(&eval_body) 2028 2029 :.named 2030 %ldl(a0, rest) 2031 %ldl(a1, env) 2032 %gctail(&eval_let_named) 2033 }) 2034 2035 # eval_letstar(rest=a0, env=a1) -> value (a0). 2036 # Like let, but each init is evaluated in the env extended by all prior 2037 # bindings of the same let* form (left-to-right shadowing). 2038 # 2039 # Locals: 2040 # rest 2041 # env 2042 # walk 2043 # new_env 2044 %gcfn2(eval_letstar, {rest env walk new_env}, 15, 0, { 2045 %stl(a0, rest) 2046 %stl(a1, env) 2047 2048 %ldl(t0, rest) 2049 %car(t0, t0) 2050 %stl(t0, walk) 2051 %ldl(t0, env) 2052 %stl(t0, new_env) 2053 2054 :.loop 2055 %ldl(t0, walk) 2056 %if_nil(t1, t0, &.done) 2057 2058 %car(t1, t0) 2059 %cdr(t2, t1) 2060 %car(t2, t2) 2061 2062 # val = eval(init, new_env) 2063 %mov(a0, t2) 2064 %ldl(a1, new_env) 2065 %call(&eval) 2066 2067 %ldl(t0, walk) 2068 %car(t1, t0) 2069 %car(t2, t1) 2070 %mov(a1, a0) 2071 %mov(a0, t2) 2072 %ldl(a2, new_env) 2073 %call(&env_extend) 2074 %stl(a0, new_env) 2075 2076 %advance_walk(walk) 2077 %b(&.loop) 2078 :.done 2079 2080 %ldl(a0, rest) 2081 %cdr(a0, a0) 2082 %ldl(a1, new_env) 2083 %gctail(&eval_body) 2084 }) 2085 2086 # eval_let_values(rest=a0, env=a1) -> value (a0). 2087 # rest = (((formals init) ...) body...). Each init is evaluated in the 2088 # OUTER env; mv_to_list normalizes its result so bind_params can drive 2089 # both list-style and dotted/rest formals identically. Then bodies run 2090 # in the env extended by all clauses. 2091 # 2092 # Locals: 2093 # rest 2094 # env (original) 2095 # walk (clauses, advances) 2096 # new_env (built up) 2097 %gcfn2(eval_let_values, {rest env walk new_env}, 15, 0, { 2098 %stl(a0, rest) 2099 %stl(a1, env) 2100 2101 %ldl(t0, rest) 2102 %car(t0, t0) ; clauses 2103 %stl(t0, walk) 2104 %ldl(t0, env) 2105 %stl(t0, new_env) ; new_env = env 2106 2107 :.loop 2108 %ldl(t0, walk) 2109 %if_nil(t1, t0, &.done) 2110 2111 %car(t1, t0) ; clause = (formals init) 2112 %cdr(t2, t1) 2113 %car(t2, t2) ; init 2114 2115 # val = eval(init, env_orig) 2116 %mov(a0, t2) 2117 %ldl(a1, env) 2118 %call(&eval) 2119 2120 # vals = mv_to_list(val) 2121 %call(&mv_to_list) 2122 2123 # new_env = bind_params(formals, vals, new_env) 2124 %ldl(t0, walk) 2125 %car(t1, t0) 2126 %car(t1, t1) ; formals 2127 %mov(a1, a0) 2128 %mov(a0, t1) 2129 %ldl(a2, new_env) 2130 %call(&bind_params) 2131 %stl(a0, new_env) 2132 2133 %advance_walk(walk) 2134 %b(&.loop) 2135 :.done 2136 2137 %ldl(a0, rest) 2138 %cdr(a0, a0) ; body 2139 %ldl(a1, new_env) 2140 %gctail(&eval_body) 2141 }) 2142 2143 # eval_letstar_values(rest=a0, env=a1) -> value (a0). 2144 # Like let-values but each init is evaluated in new_env (the env extended 2145 # by all prior clauses' bindings), giving sequential / shadowing semantics. 2146 # 2147 # Locals: 2148 # rest 2149 # env 2150 # walk 2151 # new_env 2152 %gcfn2(eval_letstar_values, {rest env walk new_env}, 15, 0, { 2153 %stl(a0, rest) 2154 %stl(a1, env) 2155 2156 %ldl(t0, rest) 2157 %car(t0, t0) 2158 %stl(t0, walk) 2159 %ldl(t0, env) 2160 %stl(t0, new_env) 2161 2162 :.loop 2163 %ldl(t0, walk) 2164 %if_nil(t1, t0, &.done) 2165 2166 %car(t1, t0) 2167 %cdr(t2, t1) 2168 %car(t2, t2) ; init 2169 2170 # val = eval(init, new_env) 2171 %mov(a0, t2) 2172 %ldl(a1, new_env) 2173 %call(&eval) 2174 2175 %call(&mv_to_list) 2176 2177 %ldl(t0, walk) 2178 %car(t1, t0) 2179 %car(t1, t1) ; formals 2180 %mov(a1, a0) 2181 %mov(a0, t1) 2182 %ldl(a2, new_env) 2183 %call(&bind_params) 2184 %stl(a0, new_env) 2185 2186 %advance_walk(walk) 2187 %b(&.loop) 2188 :.done 2189 2190 %ldl(a0, rest) 2191 %cdr(a0, a0) 2192 %ldl(a1, new_env) 2193 %gctail(&eval_body) 2194 }) 2195 2196 # eval_and(rest=a0, env=a1) -> value (a0). 2197 # (and) is #t. Otherwise eval forms left-to-right, short-circuiting to #f 2198 # the moment one yields #f. The last form is tail-evaluated so a tail call 2199 # inside `and` doesn't grow the host stack. 2200 # 2201 # Locals: 2202 # rest 2203 # env 2204 %gcfn2(eval_and, {rest env}, 3, 0, { 2205 %li(t0, %imm_val(%IMM.TRUE)) 2206 %if_nil(t1, a0, &.done_imm) 2207 2208 :.loop 2209 %stl(a0, rest) 2210 %stl(a1, env) 2211 2212 # If cdr(rest) is NIL, the head is the last form -> tail-eval. 2213 %cdr(t0, a0) 2214 %if_nil(t1, t0, &.last) 2215 2216 # Non-last: eval, short-circuit on #f, otherwise advance. 2217 %car(a0, a0) 2218 %call(&eval) 2219 %li(t0, %imm_val(%IMM.FALSE)) 2220 %beq(a0, t0, &.done) 2221 %ldl(a0, rest) 2222 %cdr(a0, a0) 2223 %ldl(a1, env) 2224 %b(&.loop) 2225 2226 :.last 2227 %ldl(a0, rest) 2228 %car(a0, a0) 2229 %ldl(a1, env) 2230 %gctail(&eval) 2231 :.done 2232 2233 %gceret 2234 2235 :.done_imm 2236 %mov(a0, t0) 2237 }) 2238 2239 # eval_or(rest=a0, env=a1) -> value (a0). 2240 # (or) is #f. Otherwise eval forms left-to-right and return the first 2241 # non-#f value; if every form was #f, return #f. The last form is 2242 # tail-evaluated. 2243 # 2244 # Locals: 2245 # rest 2246 # env 2247 %gcfn2(eval_or, {rest env}, 3, 0, { 2248 %li(t0, %imm_val(%IMM.FALSE)) 2249 %if_nil(t1, a0, &.done_imm) 2250 2251 :.loop 2252 %stl(a0, rest) 2253 %stl(a1, env) 2254 2255 %cdr(t0, a0) 2256 %if_nil(t1, t0, &.last) 2257 2258 %car(a0, a0) 2259 %call(&eval) 2260 %bine(a0, %imm_val(%IMM.FALSE), &.done, t0) 2261 %ldl(a0, rest) 2262 %cdr(a0, a0) 2263 %ldl(a1, env) 2264 %b(&.loop) 2265 2266 :.last 2267 %ldl(a0, rest) 2268 %car(a0, a0) 2269 %ldl(a1, env) 2270 %gctail(&eval) 2271 :.done 2272 2273 %gceret 2274 2275 :.done_imm 2276 %mov(a0, t0) 2277 }) 2278 2279 # eval_when(rest=a0, env=a1) -> value (a0). 2280 # (when test body...) -- if test evaluates non-#f, tail-eval body and 2281 # return its last value; otherwise return UNSPEC. Body never enters a 2282 # new scope. 2283 # 2284 # Locals: 2285 # rest 2286 # env 2287 %gcfn2(eval_when, {rest env}, 3, 0, { 2288 %stl(a0, rest) 2289 %stl(a1, env) 2290 2291 %car(a0, a0) ; test 2292 %call(&eval) 2293 2294 %bieq(a0, %imm_val(%IMM.FALSE), &.skip, t0) 2295 2296 %ldl(a0, rest) 2297 %cdr(a0, a0) ; body 2298 %ldl(a1, env) 2299 %gctail(&eval_body) 2300 2301 :.skip 2302 %li(a0, %imm_val(%IMM.UNSPEC)) 2303 }) 2304 2305 # eval_case(rest=a0, env=a1) -> value (a0). 2306 # rest is (key-expr clause...). The key is evaluated once; clauses are 2307 # tried in order. Clause shape: 2308 # ((datum...) body...) ; datums are literal, eq?-compared to key 2309 # (else body...) ; matches unconditionally 2310 # Matching uses pointer equality (eq?), which is correct for fixnums, 2311 # symbols, chars, and booleans -- the values case is meant for. The 2312 # matched clause's body is tail-evaluated via eval_body. No-match (and 2313 # no else) returns UNSPEC, mirroring eval_cond's no-match policy. 2314 # 2315 # Locals: 2316 # subject (evaluated key) 2317 # env 2318 # clauses (advances) 2319 # datums (advances within a clause) 2320 %gcfn2(eval_case, {subject env clauses datums}, 15, 0, { 2321 %stl(a1, env) 2322 2323 # subject = eval(car(rest), env); clauses = cdr(rest). 2324 %mov(t0, a0) 2325 %cdr(t1, t0) 2326 %stl(t1, clauses) 2327 %car(a0, t0) 2328 %ldl(a1, env) 2329 %call(&eval) 2330 %stl(a0, subject) 2331 2332 :.loop 2333 %ldl(t0, clauses) 2334 %if_nil(t1, t0, &.no_match) 2335 2336 %car(t1, t0) ; clause 2337 %car(t2, t1) ; head: datum-list or `else` 2338 2339 %ld_global(a3, &sym_else) 2340 %beq(t2, a3, &.do_else) 2341 2342 # Walk the datum list, eq?-compare each against subject. 2343 %stl(t2, datums) 2344 %ldl(a0, subject) 2345 :.scan 2346 %ldl(t0, datums) 2347 %if_nil(t1, t0, &.next_clause) 2348 %car(t1, t0) ; datum 2349 %beq(t1, a0, &.do_body) 2350 %cdr(t0, t0) 2351 %stl(t0, datums) 2352 %b(&.scan) 2353 2354 :.do_body 2355 %ldl(t0, clauses) 2356 %car(t0, t0) 2357 %cdr(a0, t0) ; body 2358 %ldl(a1, env) 2359 %gctail(&eval_body) 2360 2361 :.do_else 2362 %ldl(t0, clauses) 2363 %car(t0, t0) 2364 %cdr(a0, t0) ; body 2365 %ldl(a1, env) 2366 %gctail(&eval_body) 2367 2368 :.next_clause 2369 %ldl(t0, clauses) 2370 %cdr(t0, t0) 2371 %stl(t0, clauses) 2372 %b(&.loop) 2373 2374 :.no_match 2375 %li(a0, %imm_val(%IMM.UNSPEC)) 2376 }) 2377 2378 # eval_pmatch(rest=a0, env=a1) -> value (a0). 2379 # rest is (subject-expr . clauses). The subject is evaluated once; each 2380 # clause is then tried in order against the same subject value, restarting 2381 # from the outer env per clause. Clause shape: 2382 # (<pat> <body>...) 2383 # (<pat> (guard <g>...) <body>...) 2384 # (else <body>...) 2385 # An `else` clause always matches with no bindings; a guarded clause is 2386 # selected only if every guard expression evaluates non-#f. The matched 2387 # clause's body is tail-evaluated via eval_body so the last form keeps 2388 # tail position. No-match (and no else) dies via runtime_error. 2389 # 2390 # Locals: 2391 # subject 2392 # env_outer (per-clause restart point) 2393 # clauses (current cursor; advances on miss / failed guard) 2394 # env_ext (env extended with the matched clause's bindings) 2395 # guard cursor (advances during the guard AND-fold) 2396 # body (saved across guard evals, tail-evaluated on success) 2397 %gcfn2(eval_pmatch, {subject env_outer clauses env_ext guard body}, 63, 0, { 2398 %stl(a1, env_outer) 2399 2400 # subject = eval(car(rest), env_outer); clauses = cdr(rest). 2401 %mov(t0, a0) 2402 %cdr(t1, t0) 2403 %stl(t1, clauses) 2404 %car(a0, t0) 2405 %ldl(a1, env_outer) 2406 %call(&eval) 2407 %stl(a0, subject) 2408 2409 :.loop 2410 %ldl(t0, clauses) 2411 %if_nil(t1, t0, &.no_match) 2412 2413 %car(t1, t0) ; clause 2414 %car(t2, t1) ; pat 2415 2416 %ld_global(a3, &sym_else) 2417 %beq(t2, a3, &.do_else) 2418 2419 # pmatch_match(pat, subject, env_outer) -> (a0=env_ext, a1=ok) 2420 %mov(a0, t2) 2421 %ldl(a1, subject) 2422 %ldl(a2, env_outer) 2423 %call(&pmatch_match) 2424 %beqz(a1, &.next) 2425 2426 %stl(a0, env_ext) ; env_ext 2427 2428 # tail = cdr(clause) 2429 %ldl(t0, clauses) 2430 %car(t0, t0) 2431 %cdr(t0, t0) ; tail = (body...) or ((guard ...) body...) 2432 2433 # Guard form? tail is a pair, car(tail) is a pair, head of car(tail) 2434 # eq? sym_guard. 2435 %tagof(t1, t0) 2436 %bine(t1, %TAG.PAIR, &.body_simple, t2) 2437 %car(t1, t0) ; first form of tail 2438 %tagof(t2, t1) 2439 %bine(t2, %TAG.PAIR, &.body_simple, a0) 2440 %car(a0, t1) ; head of first form 2441 %ld_global(a1, &sym_guard) 2442 %bne(a0, a1, &.body_simple) 2443 2444 # Guard clause. guards = cdr(car(tail)); body = cdr(tail). 2445 %cdr(a0, t1) 2446 %stl(a0, guard) 2447 %cdr(t0, t0) 2448 %stl(t0, body) 2449 2450 :.g_loop 2451 %ldl(t0, guard) 2452 %if_nil(t1, t0, &.body_run) 2453 2454 %car(a0, t0) ; guard expr 2455 %ldl(a1, env_ext) ; env_ext 2456 %call(&eval) 2457 %bieq(a0, %imm_val(%IMM.FALSE), &.next, t0) 2458 2459 %ldl(t0, guard) 2460 %cdr(t0, t0) 2461 %stl(t0, guard) 2462 %b(&.g_loop) 2463 2464 :.body_run 2465 %ldl(a0, body) 2466 %ldl(a1, env_ext) 2467 %gctail(&eval_body) 2468 2469 :.body_simple 2470 # tail itself is the body (no guard wrapper). Tail-call eval_body 2471 # with the extended env; tail position of the matched clause's body 2472 # is preserved. 2473 %mov(a0, t0) 2474 %ldl(a1, env_ext) 2475 %gctail(&eval_body) 2476 2477 :.do_else 2478 %ldl(t0, clauses) 2479 %car(t0, t0) 2480 %cdr(a0, t0) ; body 2481 %ldl(a1, env_outer) ; env_outer (no bindings introduced) 2482 %gctail(&eval_body) 2483 2484 :.next 2485 %ldl(t0, clauses) 2486 %cdr(t0, t0) 2487 %stl(t0, clauses) 2488 %b(&.loop) 2489 2490 :.no_match 2491 %die(msg_pmatch_no_match) 2492 }) 2493 2494 # eval_do(rest=a0, env=a1) -> value (a0). 2495 # rest = (((var init step?) ...) (test result?...) body...). 2496 # 2497 # Phase 1 (init): walk binding-specs in order, eval each `init` in the 2498 # outer env, build new_env by prepending ENV nodes. A parallel list 2499 # `pairs_head` records those nodes in spec order so the iteration can mutate 2500 # their value slots. A second parallel list 2501 # `vals_head` is preallocated (one cell per spec) to hold each iteration's 2502 # computed step values without per-iteration cell allocation. 2503 # 2504 # Phase 2 (loop): eval test in new_env. Truthy -> tail-eval result body 2505 # (UNSPEC if no result forms). Falsy -> eval each command form in 2506 # new_env (discard), collect new step values into vals_head (parallel 2507 # semantics: every step is evaluated against the iteration's pre-update 2508 # bindings; specs without a step keep their current value), then walk 2509 # pairs_head/vals_head together and update each ENV node to its 2510 # new value. Loop. 2511 # 2512 # Locals: 2513 # rest original rest pointer 2514 # env outer env 2515 # new_env env extended with ENV nodes (mutated each iter) 2516 # walk generic cdr-cursor (binding-specs / commands / steps) 2517 # pairs_head list of ENV-node refs in spec order 2518 # pairs_tail append point during init 2519 # vals_head parallel list of cells holding each iteration's step vals 2520 # vals_tail append point during init 2521 # body body command-forms (cddr of rest) 2522 # pair_walk cdr-cursor over pairs_head during step/update 2523 # val_walk cdr-cursor over vals_head during step/update 2524 %gcfn2(eval_do, {rest env new_env walk pairs_head pairs_tail vals_head vals_tail body pair_walk val_walk}, 2047, 0, { 2525 %stl(a0, rest) 2526 %stl(a1, env) 2527 2528 %ldl(t0, rest) 2529 %car(t0, t0) 2530 %stl(t0, walk) 2531 %ldl(t0, env) 2532 %stl(t0, new_env) 2533 %li(t0, %imm_val(%IMM.NIL)) 2534 %stl(t0, pairs_head) 2535 %stl(t0, pairs_tail) 2536 %stl(t0, vals_head) 2537 %stl(t0, vals_tail) 2538 2539 :.init_loop 2540 %ldl(t0, walk) 2541 %if_nil(t1, t0, &.init_done) 2542 2543 # spec = car(walk); init-expr = car(cdr(spec)). 2544 %car(t1, t0) 2545 %cdr(a0, t1) 2546 %car(a0, a0) ; init expression 2547 2548 # val = eval(init, env) 2549 %ldl(a1, env) 2550 %call(&eval) 2551 2552 # node = env_extend(var, val, new_env); var = car(car(walk)). 2553 %ldl(t0, walk) 2554 %car(t1, t0) 2555 %car(t2, t1) ; var 2556 %mov(a1, a0) 2557 %mov(a0, t2) 2558 %ldl(a2, new_env) 2559 %call(&env_extend) ; a0 = new ENV head/node 2560 %stl(a0, new_env) 2561 2562 # pcell = cons(node, NIL). a0 still holds the new ENV node. 2563 %li(a1, %imm_val(%IMM.NIL)) 2564 %call(&cons) 2565 2566 %ldl(t0, pairs_head) 2567 %if_nil(t1, t0, &.pairs_first) 2568 %ldl(t0, pairs_tail) 2569 %set_cdr(a0, t0) 2570 %stl(a0, pairs_tail) 2571 %b(&.vals_alloc) 2572 2573 :.pairs_first 2574 %stl(a0, pairs_head) 2575 %stl(a0, pairs_tail) 2576 2577 :.vals_alloc 2578 # vcell = cons(NIL, NIL). Append onto vals list. 2579 %li(a0, %imm_val(%IMM.NIL)) 2580 %li(a1, %imm_val(%IMM.NIL)) 2581 %call(&cons) 2582 2583 %ldl(t0, vals_head) 2584 %if_nil(t1, t0, &.vals_first) 2585 %ldl(t0, vals_tail) 2586 %set_cdr(a0, t0) 2587 %stl(a0, vals_tail) 2588 %b(&.init_advance) 2589 2590 :.vals_first 2591 %stl(a0, vals_head) 2592 %stl(a0, vals_tail) 2593 2594 :.init_advance 2595 %advance_walk(walk) 2596 %b(&.init_loop) 2597 :.init_done 2598 2599 # body = cddr(rest). 2600 %ldl(t0, rest) 2601 %cdr(t0, t0) 2602 %cdr(t0, t0) 2603 %stl(t0, body) 2604 2605 :.iter_loop 2606 # test = car(car(cdr(rest))). Eval in new_env. 2607 %ldl(t0, rest) 2608 %cdr(t0, t0) 2609 %car(t0, t0) ; (test result?...) 2610 %car(a0, t0) ; test 2611 %ldl(a1, new_env) 2612 %call(&eval) 2613 2614 %bieq(a0, %imm_val(%IMM.FALSE), &.commands, t0) 2615 2616 # Truthy: results = cdr(car(cdr(rest))). 2617 %ldl(t0, rest) 2618 %cdr(t0, t0) 2619 %car(t0, t0) 2620 %cdr(t0, t0) ; results 2621 %if_nil(t1, t0, &.no_results) 2622 %mov(a0, t0) 2623 %ldl(a1, new_env) 2624 %gctail(&eval_body) 2625 2626 :.no_results 2627 %li(a0, %imm_val(%IMM.UNSPEC)) 2628 %gceret 2629 2630 :.commands 2631 %ldl(t0, body) 2632 %stl(t0, walk) 2633 2634 :.cmd_loop 2635 %ldl(t0, walk) 2636 %if_nil(t1, t0, &.step_phase) 2637 %car(a0, t0) 2638 %ldl(a1, new_env) 2639 %call(&eval) 2640 %advance_walk(walk) 2641 %b(&.cmd_loop) 2642 2643 :.step_phase 2644 # Compute new step values. walk = specs, pair_walk = pairs_head, 2645 # val_walk = vals_head. For each spec: if spec has step (cddr non-NIL), 2646 # val = eval(step, new_env); else val = ENV.value (current). 2647 # Store val into car(val_walk). 2648 %ldl(t0, rest) 2649 %car(t0, t0) 2650 %stl(t0, walk) 2651 %ldl(t0, pairs_head) 2652 %stl(t0, pair_walk) 2653 %ldl(t0, vals_head) 2654 %stl(t0, val_walk) 2655 2656 :.step_loop 2657 %ldl(t0, walk) 2658 %if_nil(t1, t0, &.update_phase) 2659 2660 %car(t1, t0) ; spec 2661 %cdr(t2, t1) 2662 %cdr(t2, t2) ; (step?) or NIL 2663 %if_nil(t1, t2, &.no_step) 2664 2665 %car(a0, t2) ; step 2666 %ldl(a1, new_env) 2667 %call(&eval) 2668 %b(&.store_val) 2669 2670 :.no_step 2671 %ldl(t0, pair_walk) 2672 %car(t0, t0) ; ENV node 2673 %heap_ld(a0, t0, %ENV.value) 2674 2675 :.store_val 2676 %ldl(t0, val_walk) 2677 %set_car(a0, t0) 2678 2679 %advance_walk(walk) 2680 %advance_walk(pair_walk) 2681 %advance_walk(val_walk) 2682 %b(&.step_loop) 2683 2684 :.update_phase 2685 # Walk pairs_head and vals_head; update ENV.value for each node. 2686 %ldl(t0, pairs_head) 2687 %stl(t0, pair_walk) 2688 %ldl(t0, vals_head) 2689 %stl(t0, val_walk) 2690 2691 :.update_loop 2692 %ldl(t0, pair_walk) 2693 %if_nil(t1, t0, &.iter_loop) 2694 %car(t1, t0) ; ENV node 2695 %ldl(t0, val_walk) 2696 %car(t2, t0) ; new val 2697 %heap_st(t2, t1, %ENV.value) 2698 %advance_walk(pair_walk) 2699 %advance_walk(val_walk) 2700 %b(&.update_loop) 2701 }) 2702 2703 # pmatch_match(pat=a0, subj=a1, env=a2) -> (env=a0, ok=a1) 2704 # 2705 # Walks pat and subj structurally. On success returns the (possibly 2706 # extended) env in a0 and 1 in a1; on failure returns 0 in a1 (a0 is 2707 # undefined and callers must not use it). Pattern shapes: 2708 # 2709 # - pair (car eq? sym_unquote): binder `,ident` or wildcard `,_`. The 2710 # pattern must be exactly (unquote <sym>) — any other shape dies 2711 # with msg_bad_unquote_pattern (the only carve-out from the spec's 2712 # primitive-failure UB policy, since pattern shape is a syntax 2713 # error in the user's source). 2714 # - pair (car eq? sym_dollar): record pattern `($ pred (f1 p1) ...)`. 2715 # Looks up `pred` in the current env; expects a record predicate 2716 # PRIM (the one bound by define-record-type). The TD pulled from 2717 # PRIM.data drives the type check on subj and the field-name -> idx 2718 # lookup. Each clause matches recursively. Listed fields only; 2719 # missing fields are unconstrained. Malformed pattern shape, an 2720 # unknown field name, a non-record subject, or a TD mismatch fall 2721 # through as ::no. 2722 # - pair (otherwise): subj must be a pair; recurse on car, then cdr. 2723 # - atomic (fixnum, sym, immediate, identical heap pointer): raw 2724 # word equality. 2725 # - HEAP-tagged HDR.BV / HDR.STRING: structural byte-for-byte equality 2726 # via bv_equal_check, including mixed string/bytevector matches. 2727 # 2728 # Locals: 2729 # pat 2730 # subj 2731 # env 2732 # td (record-pattern: TD pulled from the predicate PRIM) 2733 # flw (record-pattern: cursor over remaining (fname pat) clauses) 2734 %gcfn2(pmatch_match, {pat subj env td flw}, 31, 0, { 2735 %stl(a0, pat) 2736 %stl(a1, subj) 2737 %stl(a2, env) 2738 2739 %tagof(t0, a0) 2740 %li(t1, %TAG.PAIR) 2741 %beq(t0, t1, &.pair_pat) 2742 2743 # Atomic pattern. Identity covers fixnum / symbol / immediate / same 2744 # heap pointer. 2745 %beq(a0, a1, &.ok) 2746 2747 # Byte-literal equality. pmatch intentionally widens this one literal 2748 # rule so a string pattern can match a bytevector token and vice versa. 2749 %bine(t0, %TAG.HEAP, &.no, t1) 2750 %hdr_type(t1, a0) 2751 %li(t2, %HDR.BV) 2752 %beq(t1, t2, &.pat_bytes) 2753 %li(t2, %HDR.STRING) 2754 %bne(t1, t2, &.no) 2755 :.pat_bytes 2756 %tagof(t1, a1) 2757 %bine(t1, %TAG.HEAP, &.no, t2) 2758 %hdr_type(t1, a1) 2759 %li(t2, %HDR.BV) 2760 %beq(t1, t2, &.subj_bytes) 2761 %li(t2, %HDR.STRING) 2762 %bne(t1, t2, &.no) 2763 :.subj_bytes 2764 %call(&bv_equal_check) 2765 %bieq(a0, %imm_val(%IMM.TRUE), &.ok, t0) 2766 %b(&.no) 2767 2768 :.pair_pat 2769 %car(t0, a0) ; phead 2770 %ld_global(t1, &sym_unquote) 2771 %beq(t0, t1, &.binder) 2772 %ld_global(t1, &sym_dollar) 2773 %beq(t0, t1, &.record_pat) 2774 2775 # Structural pair. subj must be a pair too. 2776 %tagof(t0, a1) 2777 %bine(t0, %TAG.PAIR, &.no, t1) 2778 2779 # Recurse on the cars; on success, recurse on the cdrs as a tail call. 2780 %ldl(t0, pat) 2781 %car(a0, t0) 2782 %ldl(t0, subj) 2783 %car(a1, t0) 2784 %ldl(a2, env) 2785 %call(&pmatch_match) 2786 %beqz(a1, &.no) 2787 2788 %mov(a2, a0) ; env_after_car 2789 %ldl(t0, pat) 2790 %cdr(a0, t0) 2791 %ldl(t0, subj) 2792 %cdr(a1, t0) 2793 %gctail(&pmatch_match) 2794 2795 :.binder 2796 # Validate (unquote <sym>): cdr(pat) is a pair, cdr(cdr(pat)) is NIL, 2797 # car(cdr(pat)) is a symbol. 2798 %ldl(t0, pat) 2799 %cdr(t1, t0) ; cdr(pat) 2800 %tagof(t0, t1) 2801 %bine(t0, %TAG.PAIR, &.bad, t2) 2802 %cdr(t0, t1) ; cdr(cdr(pat)) 2803 %bine(t0, %imm_val(%IMM.NIL), &.bad, t2) 2804 %car(t0, t1) ; pident (kept in t0) 2805 %tagof(t2, t0) 2806 %bine(t2, %TAG.SYM, &.bad, a3) 2807 2808 # Wildcard? Compare against sym_underscore; if so, no binding. 2809 %ld_global(t1, &sym_underscore) 2810 %beq(t0, t1, &.ok) 2811 2812 # Bind: env' = env_extend(pident, subj, env). pident lives in t0; 2813 # env_extend clobbers t0..t2, so move it into a0 right away. 2814 %mov(a0, t0) 2815 %ldl(a1, subj) 2816 %ldl(a2, env) 2817 %call(&env_extend) 2818 %li(a1, 1) 2819 %gceret 2820 2821 :.record_pat 2822 # pat = ($ pred-sym (f1 p1) (f2 p2) ...). Resolve pred-sym -> PRIM via 2823 # eval, pull TD from PRIM.data, type-check subj, then iterate the 2824 # (fname pat_i) clauses. Clobbers `pat` local once we begin the loop: 2825 # we stash each pat_i there before recursing so the recursion has the 2826 # right argument and the local stays usable as scratch. 2827 %ldl(t0, pat) 2828 %cdr(t1, t0) ; (pred-sym . clauses) 2829 %tagof(t0, t1) 2830 %bine(t0, %TAG.PAIR, &.no, t2) 2831 %car(t0, t1) ; t0 = pred-sym 2832 %tagof(t2, t0) 2833 %bine(t2, %TAG.SYM, &.no, a3) 2834 %cdr(t2, t1) ; t2 = clauses 2835 %stl(t2, flw) 2836 2837 # eval(pred-sym, env) -> a0 = pred PRIM (or dies "unbound"). 2838 %mov(a0, t0) 2839 %ldl(a1, env) 2840 %call(&eval) 2841 2842 # Verify HEAP / HDR.PRIM, entry == &prim_predicate_entry; extract TD 2843 # from PRIM.data; sanity-check TD is HEAP / HDR.TD. 2844 %tagof(t0, a0) 2845 %bine(t0, %TAG.HEAP, &.no, t1) 2846 %hdr_type(t0, a0) 2847 %bine(t0, %HDR.PRIM, &.no, t1) 2848 %heap_ld(t1, a0, %PRIM.entry_w) 2849 %la(t2, &prim_predicate_entry) 2850 %bne(t1, t2, &.no) 2851 %heap_ld(t1, a0, %PRIM.data) ; t1 = TD 2852 %tagof(t0, t1) 2853 %li(t2, %TAG.HEAP) 2854 %bne(t0, t2, &.no) 2855 %hdr_type(t0, t1) 2856 %li(t2, %HDR.TD) 2857 %bne(t0, t2, &.no) 2858 %stl(t1, td) 2859 2860 # Verify subj is HDR.REC with REC.td == TD. 2861 %ldl(a0, subj) 2862 %tagof(t0, a0) 2863 %li(t1, %TAG.HEAP) 2864 %bne(t0, t1, &.no) 2865 %hdr_type(t0, a0) 2866 %li(t1, %HDR.REC) 2867 %bne(t0, t1, &.no) 2868 %heap_ld(t0, a0, %REC.td) 2869 %ldl(t1, td) 2870 %bne(t0, t1, &.no) 2871 2872 :.record_field_loop 2873 # flw points at remaining (fname pat_i) clauses; NIL ends the loop. 2874 %ldl(t0, flw) 2875 %if_nil(t1, t0, &.ok) 2876 %car(t1, t0) ; t1 = clause 2877 %tagof(t0, t1) 2878 %bine(t0, %TAG.PAIR, &.no, t2) 2879 %car(t2, t1) ; t2 = fname 2880 %cdr(t1, t1) ; t1 = (pat_i) 2881 %tagof(t0, t1) 2882 %bine(t0, %TAG.PAIR, &.no, a3) 2883 %car(a3, t1) ; a3 = pat_i 2884 %stl(a3, pat) ; reuse `pat` local for pat_i across recursion 2885 2886 # Linear scan of TD.fields for fname; idx accumulated in t1. t2 holds 2887 # fname (still live); a3 is scratch (since we no longer need pat_i in 2888 # a register — it's in the local). 2889 %ldl(t0, td) 2890 %heap_ld(t0, t0, %TD.fields) 2891 %li(t1, 0) 2892 :.record_field_idx_loop 2893 %if_nil(a3, t0, &.no) 2894 %car(a3, t0) 2895 %beq(a3, t2, &.record_field_found) 2896 %cdr(t0, t0) 2897 %addi(t1, t1, 1) 2898 %b(&.record_field_idx_loop) 2899 2900 :.record_field_found 2901 # val = ld(subj_tagged + (idx<<3) + 13). Same offset arithmetic as 2902 # prim_accessor_entry. Compute the address into a1 directly so the 2903 # recursive call's val arg is in place. 2904 %ldl(a1, subj) 2905 %shli(t1, t1, 3) 2906 %add(a1, a1, t1) 2907 %ld(a1, a1, 13) 2908 2909 # Recurse: pmatch_match(pat_i, val, env). pat_i is in `pat` local. 2910 %ldl(a0, pat) 2911 %ldl(a2, env) 2912 %call(&pmatch_match) 2913 %beqz(a1, &.no) 2914 %stl(a0, env) 2915 2916 %ldl(t0, flw) 2917 %cdr(t0, t0) 2918 %stl(t0, flw) 2919 %b(&.record_field_loop) 2920 2921 :.ok 2922 %ldl(a0, env) 2923 %li(a1, 1) 2924 %gceret 2925 2926 :.no 2927 %li(a1, 0) 2928 %gceret 2929 2930 :.bad 2931 %die(msg_bad_unquote_pattern) 2932 }) 2933 2934 # eval_let_named(rest=a0, env=a1) -> value (a0). 2935 # rest = (name bindings . body). Builds a closure whose captured env 2936 # contains a self-binding that resolves `name` to the closure itself 2937 # (set after the closure is allocated by patching the placeholder ENV 2938 # node). Inits are evaluated in the *original* env (matches let 2939 # semantics), then we apply the closure. 2940 # 2941 # Locals: 2942 # rest 2943 # env_orig 2944 # self_binding (the placeholder ENV node, patched at the end) 2945 # self_env (same node; its parent is env_orig) 2946 # walk (advances; reset between passes) 2947 # head (current pass's list head — params, then args) 2948 # tail (current pass's list tail) 2949 # params (saved between passes) 2950 %gcfn2(eval_let_named, {rest env_orig self_binding self_env walk head tail params}, 255, 0, { 2951 %stl(a0, rest) 2952 %stl(a1, env_orig) 2953 2954 # 1. self_env = env_extend(name, UNSPEC, env_orig). Keep the same node 2955 # separately as self_binding so its value can be patched after closure 2956 # allocation. 2957 %car(t0, a0) 2958 %mov(a0, t0) 2959 %li(a1, %imm_val(%IMM.UNSPEC)) 2960 %ldl(a2, env_orig) 2961 %call(&env_extend) 2962 %stl(a0, self_binding) 2963 %stl(a0, self_env) 2964 2965 # 2. Pass 1: build params list (cdr-tail trick) by walking bindings. 2966 %li(t0, %imm_val(%IMM.NIL)) 2967 %stl(t0, head) 2968 %stl(t0, tail) 2969 %ldl(t0, rest) 2970 %cdr(t0, t0) 2971 %car(t0, t0) ; bindings 2972 %stl(t0, walk) 2973 2974 :.p1_loop 2975 %ldl(t0, walk) 2976 %if_nil(t1, t0, &.p1_done) 2977 2978 %car(t1, t0) 2979 %car(t2, t1) ; name 2980 %mov(a0, t2) 2981 %li(a1, %imm_val(%IMM.NIL)) 2982 %call(&cons) ; cell = (name . NIL) 2983 2984 %ldl(t0, head) 2985 %if_nil(t1, t0, &.p1_first) 2986 %ldl(t0, tail) 2987 %set_cdr(a0, t0) 2988 %stl(a0, tail) 2989 %b(&.p1_advance) 2990 2991 :.p1_first 2992 %stl(a0, head) 2993 %stl(a0, tail) 2994 2995 :.p1_advance 2996 %advance_walk(walk) 2997 %b(&.p1_loop) 2998 2999 :.p1_done 3000 %ldl(t0, head) 3001 %stl(t0, params) ; save params 3002 3003 # 3. Pass 2: build args list (eval inits in env_orig). 3004 %li(t0, %imm_val(%IMM.NIL)) 3005 %stl(t0, head) 3006 %stl(t0, tail) 3007 %ldl(t0, rest) 3008 %cdr(t0, t0) 3009 %car(t0, t0) 3010 %stl(t0, walk) 3011 3012 :.p2_loop 3013 %ldl(t0, walk) 3014 %if_nil(t1, t0, &.p2_done) 3015 3016 %car(t1, t0) 3017 %cdr(t2, t1) 3018 %car(t2, t2) ; init 3019 %mov(a0, t2) 3020 %ldl(a1, env_orig) 3021 %call(&eval) ; val 3022 3023 %li(a1, %imm_val(%IMM.NIL)) 3024 %call(&cons) ; cell = (val . NIL) 3025 3026 %ldl(t0, head) 3027 %if_nil(t1, t0, &.p2_first) 3028 %ldl(t0, tail) 3029 %set_cdr(a0, t0) 3030 %stl(a0, tail) 3031 %b(&.p2_advance) 3032 3033 :.p2_first 3034 %stl(a0, head) 3035 %stl(a0, tail) 3036 3037 :.p2_advance 3038 %advance_walk(walk) 3039 %b(&.p2_loop) 3040 3041 :.p2_done 3042 # 4. Closure: eval_lambda((params . body), self_env). 3043 %ldl(a0, params) ; params 3044 %ldl(t0, rest) 3045 %cdr(t0, t0) 3046 %cdr(a1, t0) ; body 3047 %call(&cons) 3048 %ldl(a1, self_env) 3049 %call(&eval_lambda) 3050 3051 # 5. Patch self_binding value to closure. 3052 %ldl(t0, self_binding) 3053 %heap_st(a0, t0, %ENV.value) 3054 3055 # 6. apply(closure, args). 3056 %ldl(a1, head) 3057 %gctail(&apply) 3058 }) 3059 3060 # bind_params(params=a0, args=a1, env=a2) -> extended env (a0). 3061 # Walks params and args in lockstep, prepending ENV nodes to env. 3062 # Variadic `.`-tail: when params terminates with a SYM (rather than NIL), 3063 # bind it to the remaining args list and stop. 3064 # 3065 # Locals: 3066 # params (advanced each iteration) 3067 # args (advanced each iteration) 3068 # env (extended each iteration) 3069 %gcfn2(bind_params, {params args env}, 7, 0, { 3070 %stl(a0, params) 3071 %stl(a1, args) 3072 %stl(a2, env) 3073 3074 :.loop 3075 %ldl(t0, params) 3076 %tagof(t1, t0) 3077 %li(t2, %TAG.PAIR) 3078 %beq(t1, t2, &.pair) 3079 %li(t2, %TAG.SYM) 3080 %beq(t1, t2, &.rest_bind) 3081 %if_nil(t2, t0, &.fixed_done) 3082 %b(&.arity_error) 3083 3084 :.pair 3085 # A fixed parameter requires a corresponding argument. 3086 %ldl(t0, args) 3087 %tagof(t1, t0) 3088 %bine(t1, %TAG.PAIR, &.arity_error, t2) 3089 # env = env_extend(car(params), car(args), env) 3090 %ldl(t0, params) 3091 %car(a0, t0) 3092 %ldl(t0, args) 3093 %car(a1, t0) 3094 %ldl(a2, env) 3095 %call(&env_extend) 3096 %stl(a0, env) 3097 3098 # advance params and args 3099 %advance_walk(params) 3100 %advance_walk(args) 3101 %b(&.loop) 3102 3103 :.rest_bind 3104 # env = env_extend(params_sym, args_list, env) 3105 %ldl(a0, params) 3106 %ldl(a1, args) 3107 %ldl(a2, env) 3108 %call(&env_extend) 3109 %stl(a0, env) 3110 %b(&.done) 3111 3112 :.fixed_done 3113 # Fixed arity also rejects surplus arguments. 3114 %ldl(t0, args) 3115 %if_nil(t1, t0, &.done) 3116 %b(&.arity_error) 3117 3118 :.done 3119 %ldl(a0, env) 3120 %gceret 3121 3122 :.arity_error 3123 %die(msg_arity) 3124 }) 3125 3126 # eval_body(body=a0, env=a1) -> value of last form (a0). 3127 # Evaluates each non-last form for effect; tail-evaluates the last so 3128 # that closures used in tail position do not grow the host stack. 3129 # 3130 # Internal `define` is rejected here (this is the single chokepoint for 3131 # every body context: closure body via apply, let / letrec / named-let 3132 # bodies, cond clause bodies, begin's body). Per-form check is one 3133 # tagof + one symbol compare, regardless of body length. 3134 # 3135 # Locals: 3136 # body 3137 # env 3138 %gcfn2(eval_body, {body env}, 3, 0, { 3139 :.loop 3140 %stl(a0, body) 3141 %stl(a1, env) 3142 3143 # Reject internal `define`. Detect (define ...) at the head of any 3144 # form before dispatching to eval; top-level-only. 3145 %car(t0, a0) ; form 3146 %tagof(t1, t0) 3147 %li(t2, %TAG.PAIR) 3148 %bne(t1, t2, &.not_define) 3149 %car(t1, t0) ; head sym 3150 %ld_global(t2, &sym_define) 3151 %beq(t1, t2, &.internal_define) 3152 3153 :.not_define 3154 # If cdr(body) is NIL, body's car is the last form. 3155 %ldl(a0, body) 3156 %cdr(t0, a0) 3157 %if_nil(t1, t0, &.last) 3158 3159 # Non-last form: eval and discard, advance. 3160 %car(a0, a0) 3161 %ldl(a1, env) 3162 %call(&eval) 3163 %ldl(a0, body) 3164 %cdr(a0, a0) 3165 %ldl(a1, env) 3166 %b(&.loop) 3167 3168 :.last 3169 %ldl(a0, body) 3170 %car(a0, a0) 3171 %ldl(a1, env) 3172 %gctail(&eval) 3173 3174 :.internal_define 3175 %die(msg_internal_define) 3176 }) 3177 3178 # ========================================================================= 3179 # Runtime error -- single abort entry point 3180 # ========================================================================= 3181 # 3182 # runtime_error(msg_cstr=a0) -> never returns. Every overflow / bounds / 3183 # unbound / type-failure path lands here so error reporting (and the 3184 # eventual user-facing `error` primitive) only have to be implemented 3185 # once. Today we tail into libp1pp's `panic`, which writes msg + LF to 3186 # stderr and sys_exits 1. 3187 :runtime_error 3188 %tail(&panic) 3189 3190 # ========================================================================= 3191 # Source loading -- argv[1] -> readbuf, length stored in readbuf_len 3192 # ========================================================================= 3193 3194 %fn(load_source, 0, { 3195 %ld_global(a1, &readbuf_buf_ptr) 3196 %li(a2, %READBUF_CAP_BYTES) 3197 %call(&read_file) 3198 %bltz(a0, &.fail) 3199 3200 # If the read filled (or would have filled) the buffer, the source 3201 # is at least cap bytes; refuse rather than silently truncate. 3202 # read_file does a single sys_read so n == cap is the only saturation 3203 # signal we have. We treat n >= cap as overflow defensively. 3204 %li(t0, %READBUF_CAP_BYTES) 3205 %bltu(a0, t0, &.ok) 3206 %die(msg_readbuf_full) 3207 3208 :.ok 3209 %st_global(a0, &readbuf_len, t0) 3210 %li(a0, 0) 3211 %st_global(a0, &readbuf_pos, t0) 3212 %eret 3213 3214 :.fail 3215 %die(msg_load_fail) 3216 }) 3217 3218 # ========================================================================= 3219 # Mark-and-sweep collector 3220 # ========================================================================= 3221 # 3222 # Marking never allocates. A marked allocation header is linked through 3223 # word 1 onto gc_mark_worklist. Only exact shadow-frame slots and explicit 3224 # symbol-table roots seed the traversal. 3225 3226 # gc_mark_payload(raw_payload=a0, expected_kind=a1). Invalid, interior, 3227 # free, or already-marked addresses are ignored. 3228 :gc_mark_payload 3229 .scope 3230 %beqz(a0, &.done) 3231 %andi(t2, a0, 7) 3232 %bnez(t2, &.done) 3233 %ld_global(t0, &heap_base) 3234 %addi(t0, t0, %GC_HEADER_BYTES) 3235 %bltu(a0, t0, &.done) 3236 %ld_global(t1, &heap_tail) 3237 %bltu(a0, t1, &.in_heap) 3238 # A zero-length RAW allocation has its payload exactly at heap_tail; 3239 # its 16-byte header is still a real managed block. 3240 %beq(a0, t1, &.in_heap) 3241 %b(&.done) 3242 :.in_heap 3243 %addi(t0, a0, (- %GC_HEADER_BYTES)) 3244 %ld(t1, t0, 0) 3245 %andi(t2, t1, %GC_KIND_MASK) 3246 %bne(t2, a1, &.done) 3247 # Validate the candidate header before putting it on the worklist. 3248 # Exact roots should always name object starts; these checks reject 3249 # malformed, truncated, and implausible interior candidates safely. 3250 %shri(a2, t1, 8) 3251 %li(a3, %GC_HEADER_BYTES) 3252 %bltu(a2, a3, &.done) 3253 %andi(a3, a2, 7) 3254 %bnez(a3, &.done) 3255 %add(a3, t0, a2) 3256 %ld_global(t2, &heap_tail) 3257 %bltu(t2, a3, &.done) 3258 %andi(t2, t1, %GC_MARK_BIT) 3259 %bnez(t2, &.done) 3260 # An unmarked allocated block keeps its own header address in the 3261 # intrusive word. This canary distinguishes a real object start from 3262 # an aligned interior word without a quadratic physical-chain walk. 3263 %ld(t2, t0, 8) 3264 %bne(t2, t0, &.done) 3265 %ori(t1, t1, %GC_MARK_BIT) 3266 %st(t1, t0, 0) 3267 %ld_global(t1, &gc_mark_worklist) 3268 %st(t1, t0, 8) 3269 %st_global(t0, &gc_mark_worklist, t1) 3270 :.done 3271 %ret 3272 .endscope 3273 3274 # gc_mark_scheme(value=a0). Tags select the exact managed allocation 3275 # kind; fixnums, symbols, and immediates are not heap roots. 3276 :gc_mark_scheme 3277 .scope 3278 %tagof(t0, a0) 3279 %li(t1, %TAG.PAIR) 3280 %beq(t0, t1, &.pair) 3281 %li(t1, %TAG.HEAP) 3282 %beq(t0, t1, &.heap) 3283 %ret 3284 :.pair 3285 %addi(a0, a0, (- %TAG.PAIR)) 3286 %li(a1, %GCKIND.PAIR) 3287 %b(&gc_mark_payload) 3288 :.heap 3289 %addi(a0, a0, (- %TAG.HEAP)) 3290 %li(a1, %GCKIND.HEAP) 3291 %b(&gc_mark_payload) 3292 .endscope 3293 3294 :gc_mark_raw 3295 %li(a1, %GCKIND.RAW) 3296 %b(&gc_mark_payload) 3297 3298 # Mark all described native-frame slots. 3299 %fn2(gc_mark_shadow_roots, {frame end native scheme_mask raw_mask slot}, { 3300 %ld_global(t0, &gc_root_buf_ptr) 3301 %stl(t0, frame) 3302 %ld_global(t0, &gc_root_next) 3303 %stl(t0, end) 3304 :.frame_loop 3305 %ldl(t0, frame) 3306 %ldl(t1, end) 3307 %beq(t0, t1, &.done) 3308 %ld(t1, t0, 0) 3309 %stl(t1, native) 3310 %stl(t1, slot) 3311 %ld(t1, t0, 8) 3312 %stl(t1, scheme_mask) 3313 %ld(t1, t0, 16) 3314 %stl(t1, raw_mask) 3315 :.slot_loop 3316 %ldl(t0, scheme_mask) 3317 %ldl(t1, raw_mask) 3318 %or(t2, t0, t1) 3319 %beqz(t2, &.next_frame) 3320 %andi(t2, t0, 1) 3321 %beqz(t2, &.maybe_raw) 3322 %ldl(t2, slot) 3323 %ld(a0, t2, 0) 3324 %call(&gc_mark_scheme) 3325 :.maybe_raw 3326 %ldl(t0, raw_mask) 3327 %andi(t1, t0, 1) 3328 %beqz(t1, &.advance_slot) 3329 %ldl(t1, slot) 3330 %ld(a0, t1, 0) 3331 %call(&gc_mark_raw) 3332 :.advance_slot 3333 %ldl(t0, scheme_mask) 3334 %shri(t0, t0, 1) 3335 %stl(t0, scheme_mask) 3336 %ldl(t0, raw_mask) 3337 %shri(t0, t0, 1) 3338 %stl(t0, raw_mask) 3339 %ldl(t0, slot) 3340 %addi(t0, t0, 8) 3341 %stl(t0, slot) 3342 %b(&.slot_loop) 3343 :.next_frame 3344 %ldl(t0, frame) 3345 %addi(t0, t0, %GC_ROOT_FRAME_BYTES) 3346 %stl(t0, frame) 3347 %b(&.frame_loop) 3348 :.done 3349 }) 3350 3351 # Symbol entries are permanent roots: the stable RAW name buffers and 3352 # every Scheme value installed in a global binding. 3353 %fn2(gc_mark_symbol_roots, {idx count entry}, { 3354 %li(t0, 0) 3355 %stl(t0, idx) 3356 %ld_global(t0, &symtab_count) 3357 %stl(t0, count) 3358 :.loop 3359 %ldl(t0, idx) 3360 %ldl(t1, count) 3361 %beq(t0, t1, &.done) 3362 %symtab_entry(t1, t0, t2) 3363 %stl(t1, entry) 3364 %ld(a0, t1, %SYMENT.name_ptr) 3365 %call(&gc_mark_raw) 3366 %ldl(t1, entry) 3367 %ld(a0, t1, %SYMENT.global_val) 3368 %call(&gc_mark_scheme) 3369 %ldl(t0, idx) 3370 %addi(t0, t0, 1) 3371 %stl(t0, idx) 3372 %b(&.loop) 3373 :.done 3374 }) 3375 3376 # Trace one marked allocation header. The header remains marked while it 3377 # is off the worklist, so cycles terminate naturally. 3378 %fn2(gc_trace_header, {header payload object_hdr count cursor td}, { 3379 %stl(a0, header) 3380 %addi(t0, a0, %GC_HEADER_BYTES) 3381 %stl(t0, payload) 3382 %ld(t1, a0, 0) 3383 %andi(t1, t1, %GC_KIND_MASK) 3384 %li(t2, %GCKIND.PAIR) 3385 %beq(t1, t2, &.pair) 3386 %li(t2, %GCKIND.HEAP) 3387 %beq(t1, t2, &.heap) 3388 %eret ; RAW has no outgoing references 3389 3390 :.pair 3391 %ld(a0, t0, %PAIR.car) 3392 %call(&gc_mark_scheme) 3393 %ldl(t0, payload) 3394 %ld(a0, t0, %PAIR.cdr) 3395 %tail(&gc_mark_scheme) 3396 3397 :.heap 3398 %ld(t1, t0, 0) 3399 %stl(t1, object_hdr) 3400 %andi(t1, t1, 255) 3401 %li(t2, %HDR.BV) 3402 %beq(t1, t2, &.bv) 3403 %li(t2, %HDR.STRING) 3404 %beq(t1, t2, &.bv) 3405 %li(t2, %HDR.CLOSURE) 3406 %beq(t1, t2, &.closure) 3407 %li(t2, %HDR.PRIM) 3408 %beq(t1, t2, &.prim) 3409 %li(t2, %HDR.TD) 3410 %beq(t1, t2, &.td) 3411 %li(t2, %HDR.REC) 3412 %beq(t1, t2, &.record) 3413 %li(t2, %HDR.MV) 3414 %beq(t1, t2, &.mv) 3415 %li(t2, %HDR.HASH) 3416 %beq(t1, t2, &.hash) 3417 %li(t2, %HDR.HASHDATA) 3418 %beq(t1, t2, &.hashdata) 3419 %li(t2, %HDR.ENV) 3420 %beq(t1, t2, &.env) 3421 %eret 3422 3423 :.bv 3424 %ld(a0, t0, %BV.data) 3425 %tail(&gc_mark_raw) 3426 3427 :.closure 3428 %ld(a0, t0, %CLOSURE.params) 3429 %call(&gc_mark_scheme) 3430 %ldl(t0, payload) 3431 %ld(a0, t0, %CLOSURE.body) 3432 %call(&gc_mark_scheme) 3433 %ldl(t0, payload) 3434 %ld(a0, t0, %CLOSURE.env) 3435 %tail(&gc_mark_scheme) 3436 3437 :.prim 3438 # Plain primitives contain zero in data; parameterized primitives 3439 # contain their TD or tagged field index. 3440 %ld(a0, t0, %PRIM.data) 3441 %tail(&gc_mark_scheme) 3442 3443 :.td 3444 %ld(a0, t0, %TD.fields) 3445 %tail(&gc_mark_scheme) 3446 3447 :.record 3448 %ld(t1, t0, %REC.td) 3449 %stl(t1, td) 3450 %mov(a0, t1) 3451 %call(&gc_mark_scheme) 3452 # A freshly published record may still have its cleared TD slot while 3453 # its constructor is filling fields. In that state it has no outgoing 3454 # references yet and is safe to revisit after construction. 3455 %ldl(t0, td) 3456 %tagof(t1, t0) 3457 %li(t2, %TAG.HEAP) 3458 %bne(t1, t2, &.done) 3459 %hdr_type(t1, t0) 3460 %li(t2, %HDR.TD) 3461 %bne(t1, t2, &.done) 3462 %heap_ld(t1, t0, %TD.nfields) 3463 %stl(t1, count) 3464 %ldl(t0, payload) 3465 %addi(t0, t0, 16) 3466 %stl(t0, cursor) 3467 %b(&.slots) 3468 3469 :.mv 3470 %ldl(t0, object_hdr) 3471 %shri(t0, t0, 8) 3472 %stl(t0, count) 3473 %ldl(t0, payload) 3474 %addi(t0, t0, 8) 3475 %stl(t0, cursor) 3476 %b(&.slots) 3477 3478 :.hash 3479 %ld(a0, t0, %HASH.data) 3480 %tail(&gc_mark_scheme) 3481 3482 :.env 3483 # ENV.sym is an immediate symbol and ENV.bloom is raw. The mutable value 3484 # and parent are the node's only managed outgoing references. 3485 %ld(a0, t0, %ENV.value) 3486 %call(&gc_mark_scheme) 3487 %ldl(t0, payload) 3488 %ld(a0, t0, %ENV.parent) 3489 %tail(&gc_mark_scheme) 3490 3491 :.hashdata 3492 # HASHDATA's header carries capacity. Only live slots (stored hash >= 2) 3493 # contain Scheme references; empty slots and tombstones are skipped. 3494 %ldl(t0, object_hdr) 3495 %shri(t0, t0, 8) 3496 %stl(t0, count) 3497 %ldl(t0, payload) 3498 %addi(t0, t0, %HASHDATA.SIZE) 3499 %stl(t0, cursor) 3500 3501 :.hash_slots 3502 %ldl(t0, count) 3503 %beqz(t0, &.done) 3504 %ldl(t1, cursor) 3505 %ld(t2, t1, 0) 3506 %li(a0, 2) 3507 %bltu(t2, a0, &.hash_next) 3508 %ld(a0, t1, 8) 3509 %call(&gc_mark_scheme) 3510 %ldl(t1, cursor) 3511 %ld(a0, t1, 16) 3512 %call(&gc_mark_scheme) 3513 3514 :.hash_next 3515 %ldl(t0, cursor) 3516 %addi(t0, t0, 24) 3517 %stl(t0, cursor) 3518 %ldl(t0, count) 3519 %addi(t0, t0, -1) 3520 %stl(t0, count) 3521 %b(&.hash_slots) 3522 3523 :.slots 3524 %ldl(t0, count) 3525 %beqz(t0, &.done) 3526 %ldl(t1, cursor) 3527 %ld(a0, t1, 0) 3528 %call(&gc_mark_scheme) 3529 %ldl(t0, cursor) 3530 %addi(t0, t0, 8) 3531 %stl(t0, cursor) 3532 %ldl(t0, count) 3533 %addi(t0, t0, -1) 3534 %stl(t0, count) 3535 %b(&.slots) 3536 :.done 3537 }) 3538 3539 # Sweep the physical block chain, rebuild an address-ordered free list, 3540 # coalesce adjacent garbage, clear survivor marks, and return a trailing 3541 # free run to the unused tail. 3542 %fn2(gc_sweep, {cursor end free_head list_last list_prev run_free allocated size}, { 3543 %ld_global(t0, &heap_base) 3544 %stl(t0, cursor) 3545 %ld_global(t0, &heap_tail) 3546 %stl(t0, end) 3547 %li(t0, 0) 3548 %stl(t0, free_head) 3549 %stl(t0, list_last) 3550 %stl(t0, list_prev) 3551 %stl(t0, run_free) 3552 %stl(t0, allocated) 3553 :.loop 3554 %ldl(t0, cursor) 3555 %ldl(t1, end) 3556 %beq(t0, t1, &.finish) 3557 %ld(t1, t0, 0) 3558 %shri(t2, t1, 8) 3559 %li(a0, %GC_HEADER_BYTES) 3560 %bltu(t2, a0, &.corrupt) 3561 %andi(a0, t2, 7) 3562 %bnez(a0, &.corrupt) 3563 %add(a0, t0, t2) 3564 %ldl(a1, end) 3565 %bltu(a1, a0, &.corrupt) 3566 %stl(t2, size) 3567 %andi(a0, t1, %GC_KIND_MASK) 3568 %beqz(a0, &.garbage) 3569 %andi(a0, t1, %GC_MARK_BIT) 3570 %beqz(a0, &.garbage) 3571 3572 # Survivor: clear mark and break any adjacent-free run. 3573 %li(a0, -129) 3574 %and(t1, t1, a0) 3575 %st(t1, t0, 0) 3576 %st(t0, t0, 8) ; restore allocated-block start canary 3577 %ldl(a0, allocated) 3578 %add(a0, a0, t2) 3579 %stl(a0, allocated) 3580 %li(a0, 0) 3581 %stl(a0, run_free) 3582 %b(&.advance) 3583 3584 :.garbage 3585 %ldl(a0, run_free) 3586 %beqz(a0, &.new_run) 3587 # Extend the preceding physical free run. 3588 # Invalidate the absorbed block's old header so a stale exact slot 3589 # cannot mistake this interior address for an allocated object start. 3590 %li(a1, 0) 3591 %st(a1, t0, 0) 3592 %st(a1, t0, 8) 3593 %ld(a1, a0, 0) 3594 %shri(a1, a1, 8) 3595 %add(a1, a1, t2) 3596 %shli(a1, a1, 8) 3597 %st(a1, a0, 0) 3598 %b(&.advance) 3599 3600 :.new_run 3601 %shli(t1, t2, 8) ; FREE kind, mark clear 3602 %st(t1, t0, 0) 3603 %li(t1, 0) 3604 %st(t1, t0, 8) 3605 %ldl(t1, list_last) 3606 %stl(t1, list_prev) 3607 %beqz(t1, &.first_free) 3608 %st(t0, t1, 8) 3609 %b(&.linked) 3610 :.first_free 3611 %stl(t0, free_head) 3612 :.linked 3613 %stl(t0, list_last) 3614 %stl(t0, run_free) 3615 3616 :.advance 3617 %ldl(t0, cursor) 3618 %ldl(t1, size) 3619 %add(t0, t0, t1) 3620 %stl(t0, cursor) 3621 %b(&.loop) 3622 3623 :.finish 3624 # A final free run is outside the physical chain after trimming. 3625 %ldl(t0, run_free) 3626 %beqz(t0, &.publish) 3627 %st_global(t0, &heap_tail, t1) 3628 %ldl(t1, list_prev) 3629 %beqz(t1, &.trim_only) 3630 %li(t2, 0) 3631 %st(t2, t1, 8) 3632 %b(&.publish) 3633 :.trim_only 3634 %li(t1, 0) 3635 %stl(t1, free_head) 3636 3637 :.publish 3638 %ldl(t0, free_head) 3639 %st_global(t0, &gc_free_list, t1) 3640 %ldl(t0, allocated) 3641 %st_global(t0, &heap_allocated, t1) 3642 %eret 3643 :.corrupt 3644 %die(msg_heap_corrupt) 3645 }) 3646 3647 %fn(gc_collect, 0, { 3648 %li(t0, 0) 3649 %st_global(t0, &gc_mark_worklist, t1) 3650 %call(&gc_mark_shadow_roots) 3651 %call(&gc_mark_symbol_roots) 3652 :.drain 3653 %ld_global(t0, &gc_mark_worklist) 3654 %beqz(t0, &.sweep) 3655 %ld(t1, t0, 8) 3656 %st_global(t1, &gc_mark_worklist, t2) 3657 %mov(a0, t0) 3658 %call(&gc_trace_header) 3659 %b(&.drain) 3660 :.sweep 3661 %tail(&gc_sweep) 3662 }) 3663 3664 # ========================================================================= 3665 # Managed heap allocation 3666 # ========================================================================= 3667 # 3668 # gc_alloc_try performs coalesced-free-list first fit, then uses the 3669 # untouched heap tail. It never collects and returns raw payload 0 on 3670 # failure. gc_alloc collects once and retries. All managed payloads are 3671 # 8-byte aligned; the two-word allocation header is not visible through 3672 # existing Scheme object pointers. 3673 3674 %fn2(gc_alloc_try, {total kind prev cur block_size next}, { 3675 %alignup(a0, a0, 8, t0) 3676 %addi(a0, a0, %GC_HEADER_BYTES) 3677 %stl(a0, total) 3678 %stl(a1, kind) 3679 %li(t0, 0) 3680 %stl(t0, prev) 3681 %ld_global(t0, &gc_free_list) 3682 %stl(t0, cur) 3683 3684 :.free_loop 3685 %ldl(t0, cur) 3686 %beqz(t0, &.tail) 3687 %ld(t1, t0, 0) 3688 %shri(t1, t1, 8) 3689 %stl(t1, block_size) 3690 %ldl(t2, total) 3691 %bltu(t1, t2, &.free_next) 3692 3693 # Found first fit. Split only when the remainder can hold a header 3694 # plus at least one aligned payload word. 3695 %ld(t1, t0, 8) 3696 %stl(t1, next) 3697 %ldl(t1, block_size) 3698 %ldl(t2, total) 3699 %sub(t1, t1, t2) ; remainder bytes 3700 %li(t2, (+ %GC_HEADER_BYTES 8)) 3701 %bltu(t1, t2, &.consume) 3702 3703 # remainder_header = current + requested_total 3704 %ldl(t0, cur) 3705 %ldl(t2, total) 3706 %add(t2, t0, t2) 3707 %shli(t1, t1, 8) ; FREE kind is zero 3708 %st(t1, t2, 0) 3709 %ldl(t1, next) 3710 %st(t1, t2, 8) 3711 %ldl(t1, prev) 3712 %beqz(t1, &.split_head) 3713 %st(t2, t1, 8) 3714 %b(&.split_done) 3715 :.split_head 3716 %st_global(t2, &gc_free_list, t1) 3717 :.split_done 3718 %ldl(t1, total) 3719 %stl(t1, block_size) 3720 %b(&.prepare) 3721 3722 :.consume 3723 %ldl(t1, prev) 3724 %ldl(t2, next) 3725 %beqz(t1, &.consume_head) 3726 %st(t2, t1, 8) 3727 %b(&.prepare) 3728 :.consume_head 3729 %st_global(t2, &gc_free_list, t1) 3730 %b(&.prepare) 3731 3732 :.free_next 3733 %ldl(t0, cur) 3734 %stl(t0, prev) 3735 %ld(t0, t0, 8) 3736 %stl(t0, cur) 3737 %b(&.free_loop) 3738 3739 :.tail 3740 %ld_global(t0, &heap_tail) 3741 %ldl(t1, total) 3742 %add(t2, t0, t1) 3743 %ld_global(a3, &heap_end) 3744 %bltu(a3, t2, &.fail) 3745 %st_global(t2, &heap_tail, a3) 3746 %stl(t0, cur) 3747 %stl(t1, block_size) 3748 3749 :.prepare 3750 # Install the allocated header and account for the entire physical 3751 # block (including header and any unsplittable tail fragment). 3752 %ldl(t0, cur) 3753 %ldl(t1, block_size) 3754 %shli(t2, t1, 8) 3755 %ldl(a1, kind) 3756 %or(t2, t2, a1) 3757 %st(t2, t0, 0) 3758 %st(t0, t0, 8) ; allocated-block start canary 3759 %ld_global(a3, &heap_allocated) 3760 %add(a3, a3, t1) 3761 %st_global(a3, &heap_allocated, t2) 3762 3763 # Clear traced payloads before publishing them. This makes a 3764 # partially constructed HEAP object safe if a later field-setting 3765 # step allocates and triggers collection. 3766 %ldl(a1, kind) 3767 %li(t1, %GCKIND.RAW) 3768 %beq(a1, t1, &.return) 3769 %addi(t1, t0, %GC_HEADER_BYTES) 3770 %ldl(t2, block_size) 3771 %addi(t2, t2, (- %GC_HEADER_BYTES)) 3772 :.clear_loop 3773 %beqz(t2, &.return) 3774 %li(a0, 0) 3775 %st(a0, t1, 0) 3776 %addi(t1, t1, 8) 3777 %addi(t2, t2, -8) 3778 %b(&.clear_loop) 3779 3780 :.return 3781 %addi(a0, t0, %GC_HEADER_BYTES) 3782 %eret 3783 3784 :.fail 3785 %li(a0, 0) 3786 }) 3787 3788 %fn2(gc_alloc, {bytes kind}, { 3789 %stl(a0, bytes) 3790 %stl(a1, kind) 3791 %call(&gc_alloc_try) 3792 %bnez(a0, &.done) 3793 %call(&gc_collect) 3794 %ldl(a0, bytes) 3795 %ldl(a1, kind) 3796 %call(&gc_alloc_try) 3797 %beqz(a0, &.oom) 3798 :.done 3799 %eret 3800 :.oom 3801 %die(msg_heap_full) 3802 }) 3803 3804 # cons roots both arguments inside the allocator frame because gc_alloc 3805 # may synchronously collect before returning a payload. 3806 %gcfn2(cons, {car_value cdr_value}, 3, 0, { 3807 %stl(a0, car_value) 3808 %stl(a1, cdr_value) 3809 %li(a0, %PAIR.SIZE) 3810 %li(a1, %GCKIND.PAIR) 3811 %call(&gc_alloc) 3812 %ldl(t0, car_value) 3813 %st(t0, a0, %PAIR.car) 3814 %ldl(t0, cdr_value) 3815 %st(t0, a0, %PAIR.cdr) 3816 %addi(a0, a0, %TAG.PAIR) 3817 }) 3818 3819 # alloc_hdr(bytes=a0, hdr_word=a1) -> tagged HEAP object. 3820 %fn2(alloc_hdr, {bytes hdr_word}, { 3821 %stl(a0, bytes) 3822 %stl(a1, hdr_word) 3823 %li(a1, %GCKIND.HEAP) 3824 %call(&gc_alloc) 3825 %ldl(t0, hdr_word) 3826 %st(t0, a0, 0) 3827 %addi(a0, a0, %TAG.HEAP) 3828 }) 3829 3830 # env_find(sym=a0, env=a1) -> matching ENV node or 0 (a0). Leaf. 3831 # 3832 # Every ENV node carries a cumulative two-bit filter for all symbols in its 3833 # parent chain. Missing either bit proves the symbol is global, avoiding the 3834 # linear walk. A possible hit (including filter collisions) still walks and 3835 # compares exact interned-symbol identities, preserving lexical shadowing. 3836 :env_find 3837 .scope 3838 %if_nil(t0, a1, &.miss) 3839 %env_sym_mask(t0, a0, t1, t2) 3840 %heap_ld(t1, a1, %ENV.bloom) 3841 %and(t1, t1, t0) 3842 %bne(t1, t0, &.miss) 3843 3844 :.walk 3845 %heap_ld(t0, a1, %ENV.sym) 3846 %beq(t0, a0, &.hit) 3847 %heap_ld(a1, a1, %ENV.parent) 3848 %if_nil(t0, a1, &.miss) 3849 %b(&.walk) 3850 3851 :.hit 3852 %mov(a0, a1) 3853 %ret 3854 3855 :.miss 3856 %li(a0, 0) 3857 %ret 3858 .endscope 3859 3860 # env_extend(sym=a0, value=a1, parent=a2) -> new ENV head (a0). 3861 # One 40-byte headered node replaces the prior binding-pair plus alist-cell 3862 # pair (32 payload bytes across two managed allocations). Its bloom word is 3863 # the parent's cumulative filter OR this symbol's two bits. 3864 %gcfn2(env_extend, {sym value parent}, 7, 0, { 3865 %stl(a0, sym) 3866 %stl(a1, value) 3867 %stl(a2, parent) 3868 3869 %li(a0, %ENV.SIZE) 3870 %li(a1, %HDR.ENV) 3871 %call(&alloc_hdr) 3872 3873 %ldl(t0, sym) 3874 %heap_st(t0, a0, %ENV.sym) 3875 %ldl(t0, value) 3876 %heap_st(t0, a0, %ENV.value) 3877 %ldl(t0, parent) 3878 %heap_st(t0, a0, %ENV.parent) 3879 3880 %if_nil(t1, t0, &.root) 3881 %heap_ld(t0, t0, %ENV.bloom) 3882 %b(&.have_parent_bloom) 3883 :.root 3884 %li(t0, 0) 3885 :.have_parent_bloom 3886 %ldl(t1, sym) 3887 %env_sym_mask(t1, t1, t2, a3) 3888 %or(t0, t0, t1) 3889 %heap_st(t0, a0, %ENV.bloom) 3890 }) 3891 3892 # list_length(list=a0) -> count (a0). Linear walk; clobbers a0 (used as 3893 # the cursor). Callers that need the list afterward must save it first. 3894 :list_length 3895 .scope 3896 %li(t0, 0) 3897 :.loop 3898 %if_nil(t1, a0, &.done) 3899 %addi(t0, t0, 1) 3900 %cdr(a0, a0) 3901 %b(&.loop) 3902 :.done 3903 %mov(a0, t0) 3904 %ret 3905 .endscope 3906 3907 # ========================================================================= 3908 # Multiple-values protocol 3909 # ========================================================================= 3910 # 3911 # An MV-pack is a HEAP-tagged object with header (count << 8) | HDR.MV 3912 # followed by `count` slot words (raw +8, +16, ...). The R7RS protocol 3913 # below treats single values and MV-packs uniformly: a 1-value yield is 3914 # returned as the bare value, while 0 or 2+ values are materialized as 3915 # an MV-pack. mv_to_list normalizes either form to a list so let-values / 3916 # call-with-values can reuse the existing destructuring machinery. 3917 3918 # list_to_mv(list=a0) -> tagged MV-pack (a0). 3919 # Walks `list` to count it, allocates (count+1)*8 bytes with header 3920 # (count<<8)|HDR.MV, then copies elements into consecutive slots in 3921 # order. An empty list yields a 0-pack. 3922 # 3923 # Locals: 3924 # list (preserved across list_length + alloc_hdr) 3925 # count 3926 # mv (tagged MV-pack) 3927 %gcfn2(list_to_mv, {list count mv pad}, 5, 0, { 3928 %stl(a0, list) 3929 %call(&list_length) ; clobbers a0; returns count 3930 %stl(a0, count) 3931 3932 # alloc_hdr((count+1)*8, (count<<8)|HDR.MV) 3933 %addi(a0, a0, 1) 3934 %shli(a0, a0, 3) 3935 %ldl(t0, count) 3936 %shli(t0, t0, 8) 3937 %ori(a1, t0, %HDR.MV) 3938 %call(&alloc_hdr) 3939 %stl(a0, mv) 3940 3941 # Walk list, store at consecutive slots. The first slot's raw byte 3942 # offset from a tagged HEAP pointer is +5 (= raw+8 - 3). 3943 %ldl(t0, list) 3944 %addi(t1, a0, 5) 3945 3946 :.loop 3947 %if_nil(t2, t0, &.done) 3948 %car(t2, t0) 3949 %st(t2, t1, 0) 3950 %addi(t1, t1, 8) 3951 %cdr(t0, t0) 3952 %b(&.loop) 3953 3954 :.done 3955 %ldl(a0, mv) 3956 }) 3957 3958 # mv_to_list(val=a0) -> list (a0). 3959 # If val is HEAP-tagged with HDR.MV, build a fresh list of its slots in 3960 # order. Any other value is wrapped as a single-element list, so callers 3961 # can uniformly reuse list-shaped destructuring. 3962 # 3963 # Locals: 3964 # mv original MV-pack, rooted while fresh list cells are allocated 3965 # ptr (raw cursor into MV slots, walked backward) 3966 # count (remaining slot count) 3967 %gcfn2(mv_to_list, {mv ptr count}, 1, 0, { 3968 %stl(a0, mv) 3969 %tagof(t0, a0) 3970 %bine(t0, %TAG.HEAP, &.single, t1) 3971 %hdr_type(t0, a0) 3972 %bine(t0, %HDR.MV, &.single, t1) 3973 3974 # MV-pack: count = (hdr >> 8); header sits at raw+0 = tagged-3. 3975 %ld(t0, a0, -3) 3976 %shri(t0, t0, 8) 3977 %stl(t0, count) 3978 3979 # Walk slots back-to-front so each cons prepends, yielding original 3980 # left-to-right order. Cursor = (tagged+5) + (count-1)*8. 3981 %addi(t1, a0, 5) 3982 %shli(t2, t0, 3) 3983 %add(t1, t1, t2) 3984 %addi(t1, t1, -8) 3985 %stl(t1, ptr) 3986 3987 %li(a0, %imm_val(%IMM.NIL)) 3988 3989 :.loop 3990 %ldl(t0, count) 3991 %beqz(t0, &.done) 3992 3993 %ldl(t1, ptr) 3994 %ld(t2, t1, 0) 3995 %mov(a1, a0) 3996 %mov(a0, t2) 3997 %call(&cons) 3998 3999 %ldl(t1, ptr) 4000 %addi(t1, t1, -8) 4001 %stl(t1, ptr) 4002 %ldl(t0, count) 4003 %addi(t0, t0, -1) 4004 %stl(t0, count) 4005 %b(&.loop) 4006 4007 :.done 4008 %gceret 4009 4010 :.single 4011 # Non-MV: return (val . NIL). 4012 %li(a1, %imm_val(%IMM.NIL)) 4013 %gctail(&cons) 4014 }) 4015 4016 # ========================================================================= 4017 # Symbol intern -- linear scan, append on miss 4018 # ========================================================================= 4019 # 4020 # Locals: 4021 # name_ptr (input) 4022 # name_len (input) 4023 # idx (loop counter / found index) 4024 # entry_ptr (spilled across memcmp) 4025 %gcfn2(intern, {name_ptr name_len idx entry_ptr}, 0, 1, { 4026 %stl(a0, name_ptr) 4027 %stl(a1, name_len) 4028 4029 %li(t0, 0) 4030 %stl(t0, idx) 4031 4032 :.scan 4033 # idx >= count? -> append 4034 %ldl(t0, idx) 4035 %ld_global(t1, &symtab_count) 4036 %bltu(t0, t1, &.probe) 4037 %b(&.append) 4038 4039 :.probe 4040 %symtab_entry(t1, t0, t2) 4041 %stl(t1, entry_ptr) 4042 4043 # entry.name_len == name_len ? 4044 %ld(t2, t1, %SYMENT.name_len) 4045 %ldl(a2, name_len) 4046 %bne(t2, a2, &.next) 4047 4048 # memcmp(entry.name_ptr, name_ptr, len) 4049 %ld(a0, t1, %SYMENT.name_ptr) 4050 %ldl(a1, name_ptr) 4051 %ldl(a2, name_len) 4052 %call(&memcmp) 4053 %beqz(a0, &.found) 4054 4055 :.next 4056 %ldl(t0, idx) 4057 %addi(t0, t0, 1) 4058 %stl(t0, idx) 4059 %b(&.scan) 4060 4061 :.append 4062 # Bounds check; on overflow exit 5 with a message. 4063 %ldl(t0, idx) 4064 %li(t1, %SYMTAB_CAP_SLOTS) 4065 %bltu(t0, t1, &.append_ok) 4066 %die(msg_symtab_full) 4067 4068 :.append_ok 4069 # Copy the name into a stable managed RAW buffer. The caller-provided 4070 # ptr may live in readbuf_buf (parse_atom), so symtab names must 4071 # outlive source-buffer reuse. The collector roots each name explicitly. 4072 %ldl(a0, name_len) 4073 %call(&alloc_bytes) 4074 %ldl(a1, name_ptr) 4075 %ldl(a2, name_len) 4076 %call(&memcpy) ; returns dst in a0 = stable copy 4077 4078 %ldl(t0, idx) 4079 %symtab_entry(t1, t0, t2) 4080 %st(a0, t1, %SYMENT.name_ptr) ; stable copy 4081 %ldl(a0, name_len) 4082 %st(a0, t1, %SYMENT.name_len) 4083 %li(a0, %imm_val(%IMM.UNBOUND)) 4084 %st(a0, t1, %SYMENT.global_val) 4085 %li(a0, 0) 4086 %st(a0, t1, %SYMENT.pad) 4087 4088 # symtab_count = idx + 1 4089 %addi(a0, t0, 1) 4090 %st_global(a0, &symtab_count, t2) 4091 4092 # fall through with idx in t0 = sp[16] 4093 4094 :.found 4095 %ldl(t0, idx) 4096 %shli(a0, t0, 3) 4097 %ori(a0, a0, %TAG.SYM) 4098 }) 4099 4100 # Lookup by sym_idx (untagged, in a0). Returns symtab[idx].global_val in a0. 4101 # Leaf. 4102 :sym_global 4103 %ld_global(t0, &symtab_buf_ptr) 4104 %ld_array(a0, t0, %SYMENT.SIZE, a0, %SYMENT.global_val, t1) 4105 %ret 4106 4107 # sym_set_global(idx=a0, val=a1). Leaf. 4108 :sym_set_global 4109 %ld_global(t0, &symtab_buf_ptr) 4110 %st_array(a1, t0, %SYMENT.SIZE, a0, %SYMENT.global_val, t1) 4111 %ret 4112 4113 # ========================================================================= 4114 # Primitives 4115 # ========================================================================= 4116 # 4117 # PRIM objects live on the heap so the bump allocator's 8-byte alignment 4118 # is what makes (heap_ptr & 7 == 0) hold; that's what lets `+3` encode 4119 # the HEAP tag cleanly. (A static :prim_sys_exit emitted in the data 4120 # section was at the mercy of preceding code length and could land at 4121 # any 4-byte alignment, producing tag bits 5 or 7 instead of 3.) 4122 # 4123 # register_primitives walks prim_table at startup. Each table entry is 4124 # 24 bytes: 8-byte name_ptr (4-byte label ref + 4 pad), 8-byte name_len, 4125 # 8-byte entry_label (4 ref + 4 pad). For each entry we alloc a 16-byte 4126 # PRIM, write the entry-label into the prim header's entry slot, intern 4127 # the surface name, and bind the symbol's global slot to the HEAP-tagged 4128 # prim pointer. 4129 # 4130 # Locals: 4131 # prim ptr (HEAP-tagged; spilled across intern + sym_set_global) 4132 # walk (current table cursor) 4133 # end (table_end) 4134 %gcfn2(register_primitives, {prim walk end}, 1, 0, { 4135 %la(t0, &prim_table) 4136 %stl(t0, walk) 4137 %la(t0, &prim_table_end) 4138 %stl(t0, end) 4139 4140 :.loop 4141 %ldl(t0, walk) 4142 %ldl(t1, end) 4143 %beq(t0, t1, &.done) 4144 4145 # alloc_hdr(24, HDR.PRIM) -> HEAP-tagged a0. The third slot (offset 13 4146 # from tagged) holds per-instance data and stays zero for the 4147 # primitives registered here -- only parameterized prims (record 4148 # ctor/predicate/accessor/mutator) read it. 4149 %li(a0, 24) 4150 %li(a1, %HDR.PRIM) 4151 %call(&alloc_hdr) 4152 %stl(a0, prim) 4153 4154 # Write entry-label into prim's entry slot. 4155 %ldl(t0, walk) 4156 %ld(t1, t0, 16) 4157 %ldl(t2, prim) 4158 %heap_st(t1, t2, %PRIM.entry_w) 4159 4160 # Intern surface name; bind global to prim ptr. 4161 %ldl(t0, walk) 4162 %ld(a0, t0, 0) 4163 %ld(a1, t0, 8) 4164 %call(&intern) 4165 %untag_sym(a0, a0) 4166 %ldl(a1, prim) 4167 %call(&sym_set_global) 4168 4169 %ldl(t0, walk) 4170 %addi(t0, t0, 24) 4171 %stl(t0, walk) 4172 %b(&.loop) 4173 4174 :.done 4175 }) 4176 4177 %fn(register_globals, 0, { 4178 # Bind `eof` as a direct global -> IMM.EOF value. (Predicate is `eof?`, 4179 # registered via prim_table.) Cheaper and shorter than a 0-arg thunk. 4180 %la(a0, &name_eof) 4181 %li(a1, 3) 4182 %call(&intern) 4183 %untag_sym(a0, a0) 4184 %li(a1, %imm_val(%IMM.EOF)) 4185 %call(&sym_set_global) 4186 }) 4187 4188 # Each primitive is a leaf reached via apply's %tailr: args list is in a0, 4189 # and the result goes back in a0. Most use no frame at all; the few that 4190 # need recursion (apply) carry a small one via %fn. 4191 # 4192 # Arithmetic / compare / bitwise primitives on tagged fixnums take 4193 # advantage of the (n << 3) representation: + / - / signed compare / 4194 # bit-and / bit-or / bit-xor all work directly on the tagged words, so 4195 # the variadic fold loop preserves the tag at every step. Only * has to 4196 # untag each incoming operand to avoid a stray <<6. 4197 4198 # (sys-exit code) -- libp1pp's sys_exit doesn't return; %b, not %call. 4199 :prim_sys_exit_entry 4200 %car(a0, a0) 4201 %untag_fix(a0, a0) 4202 %b(&sys_exit) 4203 4204 # (cons a b) -> tagged pair. 4205 :prim_cons_entry 4206 %car(t0, a0) 4207 %cdr(t1, a0) 4208 %car(t1, t1) 4209 %mov(a0, t0) 4210 %mov(a1, t1) 4211 %b(&cons) 4212 4213 # (car p), (cdr p) 4214 :prim_car_entry 4215 %car(a0, a0) 4216 %car(a0, a0) 4217 %ret 4218 4219 :prim_cdr_entry 4220 %car(a0, a0) 4221 %cdr(a0, a0) 4222 %ret 4223 4224 # Predicate primitives. Same shape: extract the arg, compare, return one 4225 # of the two boolean immediates. 4226 4227 :prim_nullq_entry 4228 .scope 4229 %car(t0, a0) 4230 %li(a0, %imm_val(%IMM.TRUE)) 4231 %if_nil(t1, t0, &.end) 4232 %li(a0, %imm_val(%IMM.FALSE)) 4233 :.end 4234 %ret 4235 .endscope 4236 4237 :prim_pairq_entry 4238 .scope 4239 %car(t0, a0) 4240 %tagof(t1, t0) 4241 %li(t2, %TAG.PAIR) 4242 %li(a0, %imm_val(%IMM.FALSE)) 4243 %bne(t1, t2, &.end) 4244 %li(a0, %imm_val(%IMM.TRUE)) 4245 :.end 4246 %ret 4247 .endscope 4248 4249 # (string? x) -- #t iff x is a HEAP-tagged HDR.STRING. 4250 :prim_stringq_entry 4251 .scope 4252 %car(t0, a0) 4253 %li(a0, %imm_val(%IMM.FALSE)) 4254 %tagof(t1, t0) 4255 %li(t2, %TAG.HEAP) 4256 %bne(t1, t2, &.end) 4257 %hdr_type(t1, t0) 4258 %li(t2, %HDR.STRING) 4259 %bne(t1, t2, &.end) 4260 %li(a0, %imm_val(%IMM.TRUE)) 4261 :.end 4262 %ret 4263 .endscope 4264 4265 # (bytevector? x) -- #t iff x is a HEAP-tagged HDR.BV. 4266 :prim_bytevectorq_entry 4267 .scope 4268 %car(t0, a0) 4269 %li(a0, %imm_val(%IMM.FALSE)) 4270 %tagof(t1, t0) 4271 %li(t2, %TAG.HEAP) 4272 %bne(t1, t2, &.end) 4273 %hdr_type(t1, t0) 4274 %li(t2, %HDR.BV) 4275 %bne(t1, t2, &.end) 4276 %li(a0, %imm_val(%IMM.TRUE)) 4277 :.end 4278 %ret 4279 .endscope 4280 4281 # (bytes? x) -- boot2 bridge predicate: string or bytevector. 4282 :prim_bytesq_entry 4283 .scope 4284 %car(t0, a0) 4285 %li(a0, %imm_val(%IMM.FALSE)) 4286 %tagof(t1, t0) 4287 %li(t2, %TAG.HEAP) 4288 %bne(t1, t2, &.end) 4289 %hdr_type(t1, t0) 4290 %li(t2, %HDR.BV) 4291 %beq(t1, t2, &.true) 4292 %li(t2, %HDR.STRING) 4293 %bne(t1, t2, &.end) 4294 :.true 4295 %li(a0, %imm_val(%IMM.TRUE)) 4296 :.end 4297 %ret 4298 .endscope 4299 4300 # (set-car! pair val) / (set-cdr! pair val) -- in-place pair mutation. 4301 # No type check (matches car/cdr's lax stance); both return UNSPEC. 4302 :prim_set_car_entry 4303 %args2(t0, t1, a0) 4304 %set_car(t1, t0) 4305 %li(a0, %imm_val(%IMM.UNSPEC)) 4306 %ret 4307 4308 :prim_set_cdr_entry 4309 %args2(t0, t1, a0) 4310 %set_cdr(t1, t0) 4311 %li(a0, %imm_val(%IMM.UNSPEC)) 4312 %ret 4313 4314 # (length xs) -- count of pairs in a proper list. Forwards to the 4315 # list_length helper (which clobbers a0 as the cursor) and tags the 4316 # resulting count as a fixnum. Needs a frame because %call(&list_length) 4317 # would otherwise clobber lr and the trailing %ret would loop. 4318 %fn(prim_length_entry, 0, { 4319 %car(a0, a0) 4320 %call(&list_length) 4321 %mkfix(a0, a0) 4322 %eret 4323 }) 4324 4325 # (list-ref xs n) -- 0-indexed nth element. n is a fixnum; we untag, 4326 # advance via cdr, then car. Out-of-range is undefined behavior, same 4327 # as car/cdr on '(). 4328 :prim_list_ref_entry 4329 .scope 4330 %args2(t0, t1, a0) 4331 %sari(t1, t1, 3) 4332 :.loop 4333 %beqz(t1, &.done) 4334 %cdr(t0, t0) 4335 %addi(t1, t1, -1) 4336 %b(&.loop) 4337 :.done 4338 %car(a0, t0) 4339 %ret 4340 .endscope 4341 4342 # (assq key alist) -> matching pair or #f. Walks alist, comparing 4343 # car of each pair to key by identity (eq?); first match wins. Pure 4344 # leaf -- no allocation, no calls. Replaces the interpreted prelude 4345 # define so file-scope alist lookups (e.g. cc.scm scope-bind!'s 4346 # redecl check) don't pay bind_params env-cons cost per step. 4347 :prim_assq_entry 4348 .scope 4349 %args2(t0, t1, a0) ; t0=key, t1=alist 4350 :.loop 4351 %if_nil(t2, t1, &.miss) 4352 %car(t2, t1) ; pair = (car alist) 4353 %car(a0, t2) ; (car pair) 4354 %beq(a0, t0, &.hit) 4355 %cdr(t1, t1) 4356 %b(&.loop) 4357 :.hit 4358 %mov(a0, t2) 4359 %ret 4360 :.miss 4361 %li(a0, %imm_val(%IMM.FALSE)) 4362 %ret 4363 .endscope 4364 4365 # (assoc key alist) -> matching pair or #f. Same shape as assq but 4366 # the key compare goes through equal_recurse, which means we need a 4367 # frame to preserve the key/cursor/current-pair across the call. 4368 # 4369 # Locals: 4370 # key 4371 # cursor 4372 # pair (saved across equal_recurse so we can return it on hit) 4373 %fn2(prim_assoc_entry, {key cursor pair}, { 4374 %args2(t0, t1, a0) 4375 %stl(t0, key) 4376 %stl(t1, cursor) 4377 4378 :.loop 4379 %ldl(t1, cursor) 4380 %if_nil(t2, t1, &.miss) 4381 %car(t2, t1) ; pair = (car cursor) 4382 %stl(t2, pair) 4383 %car(a0, t2) ; (car pair) 4384 %ldl(a1, key) 4385 %call(&equal_recurse) 4386 %bieq(a0, %imm_val(%IMM.FALSE), &.next, t0) 4387 %ldl(a0, pair) 4388 %eret 4389 4390 :.next 4391 %ldl(t1, cursor) 4392 %cdr(t1, t1) 4393 %stl(t1, cursor) 4394 %b(&.loop) 4395 4396 :.miss 4397 %li(a0, %imm_val(%IMM.FALSE)) 4398 }) 4399 4400 # (reverse list) -> fresh reversed list. Walks the input forward, 4401 # consing each element onto an accumulator; result is the accumulator. 4402 # One fresh PAIR per input element, no intermediates. Frame needed 4403 # because cons is a leaf and %call clobbers lr. 4404 # 4405 # Locals: 4406 # xs (cursor; advanced each iteration) 4407 # acc 4408 %gcfn2(prim_reverse_entry, {xs acc}, 3, 0, { 4409 %car(t0, a0) ; t0 = list arg 4410 %stl(t0, xs) 4411 %li(t0, %imm_val(%IMM.NIL)) 4412 %stl(t0, acc) 4413 4414 :.loop 4415 %ldl(t0, xs) 4416 %if_nil(t1, t0, &.done) 4417 %car(a0, t0) 4418 %ldl(a1, acc) 4419 %call(&cons) 4420 %stl(a0, acc) 4421 %ldl(t0, xs) 4422 %cdr(t0, t0) 4423 %stl(t0, xs) 4424 %b(&.loop) 4425 4426 :.done 4427 %ldl(a0, acc) 4428 }) 4429 4430 # (bytevector-append bv ...) -- variadic concatenation. Two passes: 4431 # the first sums the bv lengths so we can size the result up front; the 4432 # second walks the args again and memcpy's each src into the result. 4433 # The args list head is saved at +0 because pass 1 walks a separate 4434 # cursor (t1) and pass 2 needs to re-read the head. memcpy clobbers 4435 # t-regs, so the running write offset and remaining-args cursor live 4436 # in the frame across each call. 4437 # 4438 # Locals: 4439 # args list head (re-read for pass 2; cursor during pass 2) 4440 # total length (raw) 4441 # result bv 4442 # write offset (raw, into result.data) 4443 %gcfn2(prim_bv_append_entry, {args total result write}, 5, 0, { 4444 %stl(a0, args) 4445 4446 %li(t0, 0) 4447 %mov(t1, a0) 4448 :.sum_loop 4449 %if_nil(t2, t1, &.sum_done) 4450 %car(t2, t1) 4451 %heap_ld(a0, t2, %BV.hdr) 4452 %shri(a0, a0, 8) 4453 %add(t0, t0, a0) 4454 %cdr(t1, t1) 4455 %b(&.sum_loop) 4456 :.sum_done 4457 %stl(t0, total) 4458 4459 %mov(a0, t0) 4460 %call(&bv_alloc) 4461 %stl(a0, result) 4462 4463 %li(t0, 0) 4464 %stl(t0, write) 4465 4466 :.copy_loop 4467 %ldl(t0, args) 4468 %if_nil(t1, t0, &.copy_done) 4469 %car(t1, t0) ; src bv 4470 %heap_ld(t2, t1, %BV.hdr) 4471 %shri(t2, t2, 8) ; src length 4472 4473 %ldl(a0, result) 4474 %heap_ld(a0, a0, %BV.data) ; result.data 4475 %ldl(a3, write) 4476 %add(a0, a0, a3) ; dst = result.data + offset 4477 %heap_ld(a1, t1, %BV.data) ; src.data 4478 %mov(a2, t2) ; count 4479 4480 %add(a3, a3, t2) 4481 %stl(a3, write) 4482 %cdr(t0, t0) 4483 %stl(t0, args) 4484 4485 %call(&memcpy) 4486 %b(&.copy_loop) 4487 :.copy_done 4488 4489 %ldl(a0, result) 4490 }) 4491 4492 # (string->symbol bv) -- intern the bytes and return the SYM-tagged 4493 # value. intern copies the name into stable heap storage if it has to 4494 # append, so the bv's data buffer is safe to relocate afterwards. 4495 :prim_string_to_symbol_entry 4496 %car(t0, a0) 4497 %heap_ld(a0, t0, %BV.data) 4498 %heap_ld(a1, t0, %BV.hdr) 4499 %shri(a1, a1, 8) ; length 4500 %b(&intern) 4501 4502 # (symbol->string sym) -- fresh string copy of the symtab name. sym_name 4503 # returns (ptr, len); string_alloc gives us a length-bearing wrapper; 4504 # memcpy fills the data. Frame holds the (ptr, len) pair across 4505 # str_alloc and the resulting bv across memcpy. 4506 4507 %gcfn2(prim_symbol_to_string_entry, {ptr len bv}, 4, 0, { 4508 %car(a0, a0) 4509 %sari(a0, a0, 3) ; raw sym idx 4510 %call(&sym_name) ; -> ptr (a0), len (a1) 4511 %stl(a0, ptr) 4512 %stl(a1, len) 4513 %mov(a0, a1) 4514 %call(&string_alloc) ; tagged string in a0 4515 %stl(a0, bv) 4516 %ldl(a1, ptr) ; src ptr 4517 %ldl(a2, len) ; len 4518 %heap_ld(t0, a0, %BV.data) ; dst = bv.data 4519 %mov(a0, t0) 4520 %call(&memcpy) 4521 %ldl(a0, bv) 4522 }) 4523 4524 # (number->string n [radix]) -- fresh string with the integer's text form. 4525 # Radices other than the micro-supported 10 and 16 fail closed. 4526 4527 %fn2(prim_number_to_string_entry, {value radix}, { 4528 %car(t0, a0) 4529 %sari(t0, t0, 3) ; raw value 4530 %stl(t0, value) 4531 4532 # Default radix = 10. If a second arg is present, untag it. 4533 %li(t0, 10) 4534 %stl(t0, radix) 4535 %cdr(t1, a0) 4536 %if_nil(t0, t1, &.have_radix) 4537 %car(t0, t1) 4538 %sari(t0, t0, 3) 4539 %stl(t0, radix) 4540 :.have_radix 4541 4542 %li(a0, 0) 4543 %call(&str_alloc) 4544 %ldl(a1, value) 4545 %ldl(t0, radix) 4546 %bieq(t0, 16, &.hex, t1) 4547 %bine(t0, 10, &.bad_radix, t1) 4548 %call(&str_putint) 4549 %b(&.finish) 4550 :.hex 4551 %call(&str_puthex) 4552 :.finish 4553 # str_put* builds a NUL-bearing bytevector; retag only the heap header, 4554 # preserving its logical length, data pointer, and capacity. 4555 %heap_ld(t0, a0, %BV.hdr) 4556 %ori(t0, t0, %HDR.STRING) 4557 %heap_st(t0, a0, %BV.hdr) 4558 %eret 4559 :.bad_radix 4560 %die(msg_bad_radix) 4561 }) 4562 4563 # (string->number bv [radix]) -- decimal goes through parse_dec; radix 4564 # 16 strips an optional leading '-' and calls parse_hex over the 4565 # remainder, demanding it consume every byte. Returns #f on 4566 # non-byte input, empty string, lone sign, or any non-recognized byte. 4567 # Radices other than 10 and 16 fail closed. 4568 %fn2(prim_string_to_number_entry, {args ptr len sign}, { 4569 %stl(a0, args) 4570 4571 %car(t2, a0) 4572 %tagof(t0, t2) 4573 %bine(t0, %TAG.HEAP, &.fail, t1) 4574 %hdr_type(t0, t2) 4575 %li(t1, %HDR.BV) 4576 %beq(t0, t1, &.bytes_ok) 4577 %li(t1, %HDR.STRING) 4578 %bne(t0, t1, &.fail) 4579 :.bytes_ok 4580 4581 %heap_ld(t0, t2, %BV.data) 4582 %heap_ld(t1, t2, %BV.hdr) 4583 %shri(t1, t1, 8) ; length 4584 %stl(t0, ptr) 4585 %stl(t1, len) 4586 4587 # Inspect the optional radix arg. 4588 %ldl(t0, args) 4589 %cdr(t0, t0) 4590 %if_nil(t1, t0, &.dec) 4591 %car(t1, t0) 4592 %sari(t1, t1, 3) 4593 %bieq(t1, 16, &.hex, t2) 4594 %bine(t1, 10, &.bad_radix, t2) 4595 4596 :.dec 4597 %ldl(a0, ptr) 4598 %ldl(a1, len) 4599 %beqz(a1, &.fail) 4600 %lb(t0, a0, 0) 4601 %bcne(t0, -43, &.dec_no_plus, t0) ; '+' 4602 %addi(a0, a0, 1) 4603 %addi(a1, a1, -1) 4604 %beqz(a1, &.fail) 4605 :.dec_no_plus 4606 %stl(a1, len) ; save adjusted len 4607 %call(&parse_exact_dec) ; -> (raw_val=a0, consumed=a1) 4608 %ldl(t0, len) 4609 %bne(a1, t0, &.fail) ; partial parse -> fail 4610 %mov(t0, a0) 4611 %mkfix(a0, t0) 4612 %untag_fix(t1, a0) 4613 %bne(t0, t1, &.overflow) 4614 %b(&.end) 4615 4616 :.hex 4617 # Strip optional leading '+' / '-'. 4618 %li(t0, 0) 4619 %stl(t0, sign) 4620 %ldl(t0, len) 4621 %beqz(t0, &.fail) 4622 %ldl(t1, ptr) 4623 %lb(t2, t1, 0) 4624 %addi(t0, t2, -45) ; '-' 4625 %beqz(t0, &.hex_neg) 4626 %addi(t0, t2, -43) ; '+' 4627 %beqz(t0, &.hex_skip_sign) 4628 %b(&.hex_parse) 4629 :.hex_neg 4630 %li(t0, 1) 4631 %stl(t0, sign) 4632 :.hex_skip_sign 4633 %ldl(t0, ptr) 4634 %addi(t0, t0, 1) 4635 %stl(t0, ptr) 4636 %ldl(t0, len) 4637 %addi(t0, t0, -1) 4638 %stl(t0, len) 4639 %beqz(t0, &.fail) 4640 4641 :.hex_parse 4642 # Normalize leading zeroes and cap significant digits before calling 4643 # the machine-word parser, then enforce the signed tagged range. 4644 :.hex_trim_zero 4645 %ldl(t0, len) 4646 %li(t1, 1) 4647 %beq(t0, t1, &.hex_count_check) 4648 %ldl(t1, ptr) 4649 %lb(t2, t1, 0) 4650 %addi(t2, t2, -48) 4651 %bnez(t2, &.hex_count_check) 4652 %addi(t1, t1, 1) 4653 %stl(t1, ptr) 4654 %addi(t0, t0, -1) 4655 %stl(t0, len) 4656 %b(&.hex_trim_zero) 4657 :.hex_count_check 4658 %li(t1, (+ (/ %p1_word_bits 4) 1)) 4659 %bltu(t0, t1, &.hex_parse_go) 4660 %b(&.overflow) 4661 :.hex_parse_go 4662 %ldl(a0, ptr) 4663 %ldl(a1, len) 4664 %call(&parse_hex) ; -> (a0=value, a1=consumed) 4665 %ldl(t0, len) 4666 %bne(a1, t0, &.fail) ; demand full consumption 4667 %li(t0, -1) 4668 %shri(t0, t0, 4) 4669 %ldl(t1, sign) 4670 %beqz(t1, &.hex_limit_ready) 4671 %addi(t0, t0, 1) 4672 :.hex_limit_ready 4673 %bltu(t0, a0, &.overflow) 4674 %ldl(t0, sign) 4675 %beqz(t0, &.hex_pos) 4676 %li(t1, 0) 4677 %sub(a0, t1, a0) 4678 :.hex_pos 4679 %mov(t0, a0) 4680 %mkfix(a0, t0) 4681 %untag_fix(t1, a0) 4682 %bne(t0, t1, &.overflow) 4683 %b(&.end) 4684 4685 :.bad_radix 4686 %die(msg_bad_radix) 4687 4688 :.overflow 4689 %die(msg_integer_overflow) 4690 4691 :.fail 4692 %li(a0, %imm_val(%IMM.FALSE)) 4693 :.end 4694 }) 4695 4696 # (boolean? x) -- #t iff x is the IMM.FALSE or IMM.TRUE singleton. 4697 :prim_booleanq_entry 4698 .scope 4699 %car(t0, a0) 4700 %li(a0, %imm_val(%IMM.TRUE)) 4701 %li(t1, %imm_val(%IMM.FALSE)) 4702 %beq(t0, t1, &.end) 4703 %li(t1, %imm_val(%IMM.TRUE)) 4704 %beq(t0, t1, &.end) 4705 %li(a0, %imm_val(%IMM.FALSE)) 4706 :.end 4707 %ret 4708 .endscope 4709 4710 # (integer? x) -- #t iff x is a fixnum (low 3 tag bits == TAG.FIXNUM == 0). 4711 :prim_integerq_entry 4712 .scope 4713 %car(t0, a0) 4714 %tagof(t1, t0) 4715 %li(a0, %imm_val(%IMM.FALSE)) 4716 %bnez(t1, &.end) 4717 %li(a0, %imm_val(%IMM.TRUE)) 4718 :.end 4719 %ret 4720 .endscope 4721 4722 # Characters are immediate byte values with their own tag, disjoint from 4723 # exact integers. 4724 :prim_charq_entry 4725 .scope 4726 %car(t0, a0) 4727 %tagof(t1, t0) 4728 %li(t2, %TAG.CHAR) 4729 %li(a0, %imm_val(%IMM.FALSE)) 4730 %bne(t1, t2, &.end) 4731 %li(a0, %imm_val(%IMM.TRUE)) 4732 :.end 4733 %ret 4734 .endscope 4735 4736 :prim_char_to_integer_entry 4737 .scope 4738 %car(t0, a0) 4739 %tagof(t1, t0) 4740 %bine(t1, %TAG.CHAR, &.bad, t2) 4741 %untag_char(t0, t0) 4742 %mkfix(a0, t0) 4743 %ret 4744 :.bad 4745 %die(msg_type) 4746 .endscope 4747 4748 :prim_integer_to_char_entry 4749 .scope 4750 %car(t0, a0) 4751 %tagof(t1, t0) 4752 %bine(t1, %TAG.FIXNUM, &.type, t2) 4753 %untag_fix(t0, t0) 4754 %bltz(t0, &.bad) 4755 %li(t1, 256) 4756 %bltu(t0, t1, &.ok) 4757 :.bad 4758 %die(msg_bad_char) 4759 :.type 4760 %die(msg_type) 4761 :.ok 4762 %mkchar(a0, t0) 4763 %ret 4764 .endscope 4765 4766 # (symbol? x) -- #t iff x is TAG.SYM (interned symbol index, not a heap obj). 4767 :prim_symbolq_entry 4768 .scope 4769 %car(t0, a0) 4770 %tagof(t1, t0) 4771 %li(t2, %TAG.SYM) 4772 %li(a0, %imm_val(%IMM.FALSE)) 4773 %bne(t1, t2, &.end) 4774 %li(a0, %imm_val(%IMM.TRUE)) 4775 :.end 4776 %ret 4777 .endscope 4778 4779 # (procedure? x) -- #t iff x is HEAP-tagged with header HDR.CLOSURE or HDR.PRIM. 4780 :prim_procedureq_entry 4781 .scope 4782 %car(t0, a0) 4783 %tagof(t1, t0) 4784 %li(t2, %TAG.HEAP) 4785 %li(a0, %imm_val(%IMM.FALSE)) 4786 %bne(t1, t2, &.end) 4787 %hdr_type(t1, t0) 4788 %li(t2, %HDR.CLOSURE) 4789 %beq(t1, t2, &.yes) 4790 %li(t2, %HDR.PRIM) 4791 %beq(t1, t2, &.yes) 4792 %b(&.end) 4793 :.yes 4794 %li(a0, %imm_val(%IMM.TRUE)) 4795 :.end 4796 %ret 4797 .endscope 4798 4799 :prim_zeroq_entry 4800 .scope 4801 %car(t0, a0) 4802 %li(a0, %imm_val(%IMM.FALSE)) 4803 %bnez(t0, &.end) 4804 %li(a0, %imm_val(%IMM.TRUE)) 4805 :.end 4806 %ret 4807 .endscope 4808 4809 :prim_not_entry 4810 .scope 4811 %car(t0, a0) 4812 %li(t1, %imm_val(%IMM.FALSE)) 4813 %li(a0, %imm_val(%IMM.FALSE)) 4814 %bne(t0, t1, &.end) 4815 %li(a0, %imm_val(%IMM.TRUE)) 4816 :.end 4817 %ret 4818 .endscope 4819 4820 :prim_eqq_entry 4821 .scope 4822 %car(t0, a0) 4823 %cdr(t1, a0) 4824 %car(t1, t1) 4825 %li(a0, %imm_val(%IMM.FALSE)) 4826 %bne(t0, t1, &.end) 4827 %li(a0, %imm_val(%IMM.TRUE)) 4828 :.end 4829 %ret 4830 .endscope 4831 4832 # checked_mul_raw(x=a0, y=a1) -> raw exact result. Inputs are already in 4833 # the tagged-fixnum range. Sign-quadrant bounds checks happen before the 4834 # machine multiply, so host-word wraparound is never used as a Scheme result. 4835 :checked_mul_raw 4836 .scope 4837 %beqz(a0, &.zero) 4838 %beqz(a1, &.zero) 4839 4840 # max/min derive from the target word's three tag bits. 4841 %li(t0, -1) 4842 %shri(t0, t0, 4) ; max 4843 %addi(t1, t0, 1) 4844 %li(t2, 0) 4845 %sub(t1, t2, t1) ; min 4846 4847 %bltz(a0, &.x_negative) 4848 %bltz(a1, &.pos_neg) 4849 # x > 0, y > 0: x <= max / y. 4850 %div(t2, t0, a1) 4851 %blt(t2, a0, &.overflow) 4852 %b(&.multiply) 4853 4854 :.pos_neg 4855 # x > 0, y < 0: y >= min / x. 4856 %div(t2, t1, a0) 4857 %blt(a1, t2, &.overflow) 4858 %b(&.multiply) 4859 4860 :.x_negative 4861 %bltz(a1, &.neg_neg) 4862 # x < 0, y > 0: x >= min / y. 4863 %div(t2, t1, a1) 4864 %blt(a0, t2, &.overflow) 4865 %b(&.multiply) 4866 4867 :.neg_neg 4868 # x < 0, y < 0: x >= max / y (both sides are negative). 4869 %div(t2, t0, a1) 4870 %blt(a0, t2, &.overflow) 4871 4872 :.multiply 4873 %mul(a0, a0, a1) 4874 %ret 4875 4876 :.zero 4877 %li(a0, 0) 4878 %ret 4879 4880 :.overflow 4881 %die(msg_integer_overflow) 4882 .endscope 4883 4884 # Variadic arithmetic. (+ ...) folds with identity 0; (* ...) folds with 4885 # identity 1; (- x) is unary negate, (- x y z ...) folds left. Every 4886 # intermediate result is checked against the tagged exact-integer range. 4887 4888 :prim_plus_entry 4889 .scope 4890 %li(t0, 0) ; raw accumulator 4891 :.loop 4892 %if_nil(t1, a0, &.done) 4893 %car(t1, a0) 4894 %untag_fix(t1, t1) 4895 %add(t0, t0, t1) 4896 %mkfix(t2, t0) 4897 %untag_fix(a1, t2) 4898 %bne(t0, a1, &.overflow) 4899 %cdr(a0, a0) 4900 %b(&.loop) 4901 :.done 4902 %mkfix(a0, t0) 4903 %ret 4904 :.overflow 4905 %die(msg_integer_overflow) 4906 .endscope 4907 4908 # (- x) -> -x; (- x y ...) -> x - y - ... . (-) is undefined behavior 4909 # per the primitive-failure policy. 4910 :prim_minus_entry 4911 .scope 4912 %if_nil(t0, a0, &.arity) 4913 %car(t0, a0) 4914 %untag_fix(t0, t0) ; raw seed 4915 %cdr(a0, a0) 4916 %if_nil(t1, a0, &.neg) 4917 :.loop 4918 %if_nil(t1, a0, &.done) 4919 %car(t1, a0) 4920 %untag_fix(t1, t1) 4921 %sub(t0, t0, t1) 4922 %mkfix(t2, t0) 4923 %untag_fix(a1, t2) 4924 %bne(t0, a1, &.overflow) 4925 %cdr(a0, a0) 4926 %b(&.loop) 4927 :.neg 4928 %li(t1, 0) ; unary: 0 - seed 4929 %sub(t0, t1, t0) 4930 %mkfix(t2, t0) 4931 %untag_fix(a1, t2) 4932 %bne(t0, a1, &.overflow) 4933 :.done 4934 %mkfix(a0, t0) 4935 %ret 4936 :.overflow 4937 %die(msg_integer_overflow) 4938 :.arity 4939 %die(msg_arity) 4940 .endscope 4941 4942 # Multiply uses the checked raw helper because a full-width product 4943 # can overflow the host word before a post-hoc tag check could observe it. 4944 %fn2(prim_mult_entry, {args acc}, { 4945 %stl(a0, args) 4946 %li(t0, 1) 4947 %stl(t0, acc) 4948 :.loop 4949 %ldl(t0, args) 4950 %if_nil(t1, t0, &.done) 4951 %car(t1, t0) 4952 %untag_fix(t1, t1) 4953 %ldl(a0, acc) 4954 %mov(a1, t1) 4955 %call(&checked_mul_raw) 4956 %stl(a0, acc) 4957 %advance_walk(args) 4958 %b(&.loop) 4959 :.done 4960 %ldl(a0, acc) 4961 %mkfix(a0, a0) 4962 }) 4963 4964 # Variadic chained comparisons: (op a b c ...) ⇔ (a op b) ∧ (b op c) ∧ ... 4965 # Walks the tail with a single live `prev` register; a0 is reused as the 4966 # args cursor and finally as the result. <2 args is undefined behavior. 4967 :prim_eq_entry 4968 .scope 4969 %if_nil(t0, a0, &.arity) 4970 %car(t0, a0) ; prev = first 4971 %cdr(a0, a0) 4972 %if_nil(t1, a0, &.arity) 4973 :.loop 4974 %if_nil(t1, a0, &.true) 4975 %car(t1, a0) ; curr 4976 %bne(t0, t1, &.false) 4977 %mov(t0, t1) 4978 %cdr(a0, a0) 4979 %b(&.loop) 4980 :.true 4981 %li(a0, %imm_val(%IMM.TRUE)) 4982 %ret 4983 :.false 4984 %li(a0, %imm_val(%IMM.FALSE)) 4985 %ret 4986 :.arity 4987 %die(msg_arity) 4988 .endscope 4989 4990 :prim_lt_entry 4991 .scope 4992 %if_nil(t0, a0, &.arity) 4993 %car(t0, a0) 4994 %cdr(a0, a0) 4995 %if_nil(t1, a0, &.arity) 4996 :.loop 4997 %if_nil(t1, a0, &.true) 4998 %car(t1, a0) 4999 %blt(t0, t1, &.ok) ; prev < curr -> continue 5000 %li(a0, %imm_val(%IMM.FALSE)) 5001 %ret 5002 :.ok 5003 %mov(t0, t1) 5004 %cdr(a0, a0) 5005 %b(&.loop) 5006 :.true 5007 %li(a0, %imm_val(%IMM.TRUE)) 5008 %ret 5009 :.arity 5010 %die(msg_arity) 5011 .endscope 5012 5013 :prim_gt_entry 5014 .scope 5015 %if_nil(t0, a0, &.arity) 5016 %car(t0, a0) 5017 %cdr(a0, a0) 5018 %if_nil(t1, a0, &.arity) 5019 :.loop 5020 %if_nil(t1, a0, &.true) 5021 %car(t1, a0) 5022 %blt(t1, t0, &.ok) ; curr < prev <=> prev > curr -> continue 5023 %li(a0, %imm_val(%IMM.FALSE)) 5024 %ret 5025 :.ok 5026 %mov(t0, t1) 5027 %cdr(a0, a0) 5028 %b(&.loop) 5029 :.true 5030 %li(a0, %imm_val(%IMM.TRUE)) 5031 %ret 5032 :.arity 5033 %die(msg_arity) 5034 .endscope 5035 5036 # (quotient x y) -- truncating integer division with explicit zero and 5037 # representability checks. 5038 :prim_quotient_entry 5039 .scope 5040 %args2(t0, t1, a0) 5041 %untag_fix(t0, t0) 5042 %untag_fix(t1, t1) 5043 %beqz(t1, &.zero) 5044 # Avoid the architecture-specific signed-divide overflow case before 5045 # issuing the instruction (x86 traps for min / -1). 5046 %li(t2, -1) 5047 %bne(t1, t2, &.divide) 5048 %li(a0, -1) 5049 %shri(a0, a0, 4) 5050 %addi(a0, a0, 1) 5051 %li(a1, 0) 5052 %sub(a0, a1, a0) ; tagged-fixnum minimum, raw 5053 %beq(t0, a0, &.overflow) 5054 :.divide 5055 %div(t0, t0, t1) 5056 %mkfix(a0, t0) 5057 %untag_fix(t1, a0) 5058 %bne(t0, t1, &.overflow) 5059 %ret 5060 :.zero 5061 %die(msg_divide_zero) 5062 :.overflow 5063 %die(msg_integer_overflow) 5064 .endscope 5065 5066 # (remainder x y) -- truncating remainder, sign of dividend. rem(tagged, 5067 # tagged) = 8 * (real_x rem real_y), already in tagged form. 5068 :prim_remainder_entry 5069 .scope 5070 %args2(t0, t1, a0) 5071 %untag_fix(t0, t0) 5072 %untag_fix(t1, t1) 5073 %beqz(t1, &.zero) 5074 # min % -1 is zero, but the underlying x86 divide instruction traps; 5075 # answer it directly on every target. 5076 %li(t2, -1) 5077 %beq(t1, t2, &.result_zero) 5078 %rem(a0, t0, t1) 5079 %mkfix(a0, a0) 5080 %ret 5081 :.result_zero 5082 %li(a0, 0) 5083 %ret 5084 :.zero 5085 %die(msg_divide_zero) 5086 .endscope 5087 5088 # Variadic bitwise folds. Tagged fixnums have low 3 bits = 0, so AND/OR/ 5089 # XOR with another tagged fixnum preserves the tag in the accumulator. 5090 # Identities: bit-and -> -1 (tagged -8), bit-or -> 0, bit-xor -> 0. 5091 :prim_bit_and_entry 5092 .scope 5093 %li(t0, -8) ; tagged -1; AND-identity preserves the tag 5094 :.loop 5095 %if_nil(t1, a0, &.done) 5096 %car(t1, a0) 5097 %and(t0, t0, t1) 5098 %cdr(a0, a0) 5099 %b(&.loop) 5100 :.done 5101 %mov(a0, t0) 5102 %ret 5103 .endscope 5104 5105 :prim_bit_or_entry 5106 .scope 5107 %li(t0, 0) 5108 :.loop 5109 %if_nil(t1, a0, &.done) 5110 %car(t1, a0) 5111 %or(t0, t0, t1) 5112 %cdr(a0, a0) 5113 %b(&.loop) 5114 :.done 5115 %mov(a0, t0) 5116 %ret 5117 .endscope 5118 5119 :prim_bit_xor_entry 5120 .scope 5121 %li(t0, 0) 5122 :.loop 5123 %if_nil(t1, a0, &.done) 5124 %car(t1, a0) 5125 %xor(t0, t0, t1) 5126 %cdr(a0, a0) 5127 %b(&.loop) 5128 :.done 5129 %mov(a0, t0) 5130 %ret 5131 .endscope 5132 5133 # (bit-not n) -- bitwise complement. Untag, XOR with -1 (= ~n), retag. 5134 # Can't XOR the tagged value directly: that would flip the low 3 tag bits. 5135 :prim_bit_not_entry 5136 %car(t0, a0) 5137 %untag_fix(t0, t0) 5138 %li(t1, -1) 5139 %xor(t0, t0, t1) 5140 %mkfix(a0, t0) 5141 %ret 5142 5143 # (arithmetic-shift n k): k > 0 means left shift; k < 0 means arith right. 5144 # Untag both, branch on sign of k, retag. 5145 :prim_arith_shift_entry 5146 .scope 5147 %car(t0, a0) 5148 %cdr(t1, a0) 5149 %car(t1, t1) 5150 %untag_fix(t0, t0) 5151 %untag_fix(t1, t1) 5152 %bltz(t1, &.right) 5153 %beqz(t0, &.zero) 5154 %li(t2, (- %p1_word_bits 3)) 5155 %bltu(t1, t2, &.left_do) 5156 %b(&.overflow) 5157 :.left_do 5158 %shl(t2, t0, t1) 5159 %mkfix(a0, t2) 5160 %untag_fix(a1, a0) 5161 %bne(t2, a1, &.overflow) 5162 %ret 5163 :.right 5164 %li(t2, 0) 5165 %sub(t1, t2, t1) 5166 %li(t2, %p1_word_bits) 5167 %bltu(t1, t2, &.right_do) 5168 %bltz(t0, &.minus_one) 5169 :.zero 5170 %li(a0, 0) 5171 %ret 5172 :.minus_one 5173 %li(a0, -8) 5174 %ret 5175 :.right_do 5176 %sar(a0, t0, t1) 5177 %mkfix(a0, a0) 5178 %ret 5179 :.overflow 5180 %die(msg_integer_overflow) 5181 .endscope 5182 5183 # Bytevectors are 24-byte HEAP-tagged wrappers pointing at a separately 5184 # allocated data buffer; this gives them dynamic-array semantics — capacity 5185 # can grow in place by reallocating just the data buffer (no need to find 5186 # and patch every reference to the wrapper). 5187 # 5188 # word 0 :: (length << 8) | HDR.BV ; length = hdr >> 8 5189 # word 1 :: data_ptr (raw heap address) 5190 # word 2 :: capacity in bytes 5191 # 5192 # Tagged-pointer offsets into the wrapper: 5193 # hdr = ld(bv, -3) 5194 # data_ptr = ld(bv, 5) 5195 # capacity = ld(bv, 13) 5196 # 5197 # bv_capacity_for(n) returns the smallest power-of-two ≥ max(n, 16); bv_grow 5198 # then doubles by repeatedly shifting until cap ≥ requested. Bytevectors 5199 # are raw u8[] and need no headroom for a NUL terminator -- callers that 5200 # build "strings" use the str_* writers, which reserve cap > len AND 5201 # explicitly zero data[len] (reused GC blocks are not assumed zeroed). 5202 5203 # alloc_bytes(size=a0) -> managed RAW payload address (a0). 5204 :alloc_bytes 5205 %li(a1, %GCKIND.RAW) 5206 %b(&gc_alloc) 5207 5208 # bv_capacity_for(n=a0) -> smallest power-of-two ≥ n, minimum 16. Pure 5209 # bytevector sizing -- no NUL slack. Callers building "strings" call 5210 # bv_capacity_for(raw_len + 1) to reserve room for the trailing NUL. 5211 :bv_capacity_for 5212 .scope 5213 %li(t0, 16) 5214 :.loop 5215 %bltu(t0, a0, &.shift) ; t0 < a0: keep doubling 5216 %mov(a0, t0) ; t0 >= a0: done 5217 %ret 5218 :.shift 5219 %shli(t0, t0, 1) 5220 %b(&.loop) 5221 .endscope 5222 5223 # bv_alloc(raw_len=a0) -> tagged bv (a0). Length = raw_len, capacity from 5224 # bv_capacity_for, data buffer uninitialized. data_ptr lives in a frame 5225 # slot because alloc_hdr's alignup clobbers t-regs. 5226 # 5227 # Locals: 5228 # raw_len 5229 # capacity 5230 # data_ptr (raw) 5231 %gcfn2(bv_alloc, {raw_len capacity data_ptr}, 0, 4, { 5232 %stl(a0, raw_len) 5233 5234 %call(&bv_capacity_for) 5235 %stl(a0, capacity) 5236 %call(&alloc_bytes) 5237 %stl(a0, data_ptr) 5238 5239 %ldl(a1, raw_len) 5240 %shli(a1, a1, 8) ; hdr = (raw_len << 8) | HDR.BV (BV == 0) 5241 %li(a0, 24) 5242 %call(&alloc_hdr) 5243 5244 %ldl(t0, data_ptr) 5245 %heap_st(t0, a0, %BV.data) 5246 %ldl(t1, capacity) 5247 %st(t1, a0, 13) ; bv.cap (raw offset 16; not in BV struct) 5248 }) 5249 5250 # bv_grow(bv=a0, min_cap=a1) -> bv (a0). Doubles capacity until ≥ min_cap; 5251 # allocates a fresh data buffer, copies the live bytes (length, not 5252 # capacity), and patches the wrapper's data_ptr/capacity slots in place. 5253 # A no-op when current capacity already satisfies min_cap. 5254 # 5255 # Locals: 5256 # bv 5257 # min_cap (input) / new_cap (during loop) 5258 # new_data_ptr 5259 # raw length 5260 %gcfn2(bv_grow, {bv min_cap new_data_ptr raw}, 1, 4, { 5261 %stl(a0, bv) 5262 %stl(a1, min_cap) 5263 5264 %ld(t0, a0, 13) ; bv.cap (raw offset 16; not in BV struct) 5265 %bltu(t0, a1, &.need) 5266 %ldl(a0, bv) 5267 %gceret 5268 5269 :.need 5270 :.loop 5271 %shli(t0, t0, 1) 5272 %ldl(t1, min_cap) 5273 %bltu(t0, t1, &.loop) 5274 %stl(t0, min_cap) 5275 5276 %mov(a0, t0) 5277 %call(&alloc_bytes) 5278 %stl(a0, new_data_ptr) 5279 5280 %ldl(t0, bv) 5281 %heap_ld(t1, t0, %BV.hdr) 5282 %shri(t1, t1, 8) ; raw length 5283 %stl(t1, raw) 5284 %ldl(a0, new_data_ptr) 5285 %heap_ld(a1, t0, %BV.data) ; old data ptr 5286 %ldl(a2, raw) 5287 %call(&memcpy) 5288 5289 %ldl(t0, bv) 5290 %ldl(t1, new_data_ptr) 5291 %heap_st(t1, t0, %BV.data) 5292 %ldl(t1, min_cap) 5293 %st(t1, t0, 13) ; bv.cap (raw offset 16; not in BV struct) 5294 %ldl(a0, bv) 5295 }) 5296 5297 # (make-bytevector len) or (make-bytevector len fill) 5298 5299 %gcfn2(prim_make_bytevector_entry, {args fill wrapper}, 5, 0, { 5300 %stl(a0, args) 5301 5302 %car(t0, a0) 5303 %tagof(t1, t0) 5304 %bine(t1, %TAG.FIXNUM, &.bad_type, t2) 5305 5306 %li(t2, 0) 5307 %cdr(t0, a0) 5308 %if_nil(t1, t0, &.no_fill) 5309 %car(t0, t0) 5310 %tagof(t1, t0) 5311 %bine(t1, %TAG.FIXNUM, &.bad_type, t2) 5312 %untag_fix(t2, t0) 5313 %bltz(t2, &.bad_fill) 5314 %li(t1, 256) 5315 %bltu(t2, t1, &.no_fill) 5316 %b(&.bad_fill) 5317 :.no_fill 5318 %stl(t2, fill) 5319 5320 %ldl(a0, args) 5321 %car_fix(a0, a0) 5322 %bltz(a0, &.bad_len) 5323 %call(&bv_alloc) 5324 %stl(a0, wrapper) 5325 5326 %ldl(t0, args) 5327 %car_fix(t0, t0) ; raw_len 5328 %ldl(t1, fill) ; fill 5329 %ldl(a1, wrapper) 5330 %heap_ld(t2, a1, %BV.data) 5331 %li(a1, 0) 5332 5333 :.fill_loop 5334 %beq(a1, t0, &.fill_done) 5335 %sb(t1, t2, 0) 5336 %addi(t2, t2, 1) 5337 %addi(a1, a1, 1) 5338 %b(&.fill_loop) 5339 :.fill_done 5340 5341 %ldl(a0, wrapper) 5342 %gceret 5343 5344 :.bad_len 5345 %die(msg_bv_oob) 5346 :.bad_fill 5347 %die(msg_bad_byte) 5348 :.bad_type 5349 %die(msg_type) 5350 }) 5351 5352 # (make-string len) or (make-string len fill-char). Strings have an explicit 5353 # logical length and a spare NUL after it for the boot2 pathname bridge. 5354 %gcfn2(prim_make_string_entry, {args fill wrapper}, 5, 0, { 5355 %stl(a0, args) 5356 5357 %car(t0, a0) 5358 %tagof(t1, t0) 5359 %bine(t1, %TAG.FIXNUM, &.bad_type, t2) 5360 5361 %li(t2, 32) ; implementation-selected default: space 5362 %cdr(t0, a0) 5363 %if_nil(t1, t0, &.no_fill) 5364 %car(t0, t0) 5365 %tagof(t1, t0) 5366 %bine(t1, %TAG.CHAR, &.bad_type, t2) 5367 %untag_char(t2, t0) 5368 :.no_fill 5369 %stl(t2, fill) 5370 5371 %ldl(a0, args) 5372 %car_fix(a0, a0) 5373 %bltz(a0, &.bad_len) 5374 %call(&string_alloc) 5375 %stl(a0, wrapper) 5376 5377 %ldl(t0, args) 5378 %car_fix(t0, t0) 5379 %ldl(t1, fill) 5380 %ldl(a0, wrapper) 5381 %heap_ld(t2, a0, %BV.data) 5382 %li(a1, 0) 5383 :.fill_loop 5384 %beq(a1, t0, &.fill_done) 5385 %sb(t1, t2, 0) 5386 %addi(t2, t2, 1) 5387 %addi(a1, a1, 1) 5388 %b(&.fill_loop) 5389 :.fill_done 5390 %ldl(a0, wrapper) 5391 %gceret 5392 5393 :.bad_len 5394 %die(msg_string_oob) 5395 :.bad_type 5396 %die(msg_type) 5397 }) 5398 5399 :prim_bv_length_entry 5400 .scope 5401 %car(t0, a0) 5402 %tagof(t1, t0) 5403 %bine(t1, %TAG.HEAP, &.bad, t2) 5404 %hdr_type(t1, t0) 5405 %li(t2, %HDR.BV) 5406 %beq(t1, t2, &.ok) 5407 %li(t2, %HDR.STRING) 5408 %bne(t1, t2, &.bad) 5409 :.ok 5410 %heap_ld(t1, t0, %BV.hdr) 5411 %shri(a0, t1, 5) 5412 %ret 5413 :.bad 5414 %die(msg_type) 5415 .endscope 5416 5417 # (string-length s) -- explicit logical length from the string header. 5418 :prim_string_length_entry 5419 .scope 5420 %car(t0, a0) 5421 %tagof(t1, t0) 5422 %bine(t1, %TAG.HEAP, &.bad, t2) 5423 %hdr_type(t1, t0) 5424 %bine(t1, %HDR.STRING, &.bad, t2) 5425 %heap_ld(t1, t0, %BV.hdr) 5426 %shri(a0, t1, 5) 5427 %ret 5428 :.bad 5429 %die(msg_type) 5430 .endscope 5431 5432 # (string-ref s idx) -> disjoint character. 5433 :prim_string_ref_entry 5434 .scope 5435 %args2(t0, t1, a0) 5436 %tagof(t2, t0) 5437 %bine(t2, %TAG.HEAP, &.type, a1) 5438 %hdr_type(t2, t0) 5439 %bine(t2, %HDR.STRING, &.type, a1) 5440 %tagof(t2, t1) 5441 %bine(t2, %TAG.FIXNUM, &.type, a1) 5442 %sari(t1, t1, 3) 5443 %bltz(t1, &.oob) 5444 %heap_ld(a0, t0, %BV.hdr) 5445 %shri(a0, a0, 8) 5446 %bltu(t1, a0, &.ok) 5447 :.oob 5448 %die(msg_string_oob) 5449 :.ok 5450 %heap_ld(t2, t0, %BV.data) 5451 %add(t2, t2, t1) 5452 %lb(a0, t2, 0) 5453 %mkchar(a0, a0) 5454 %ret 5455 :.type 5456 %die(msg_type) 5457 .endscope 5458 5459 # (string-set! s idx char) -> unspecified. 5460 :prim_string_set_entry 5461 .scope 5462 %args3(t0, t2, t1, a0) 5463 %tagof(a1, t0) 5464 %bine(a1, %TAG.HEAP, &.type, a2) 5465 %hdr_type(a1, t0) 5466 %bine(a1, %HDR.STRING, &.type, a2) 5467 %tagof(a1, t2) 5468 %bine(a1, %TAG.FIXNUM, &.type, a2) 5469 %tagof(a1, t1) 5470 %bine(a1, %TAG.CHAR, &.type, a2) 5471 %sari(t2, t2, 3) 5472 %untag_char(t1, t1) 5473 %bltz(t2, &.oob) 5474 %heap_ld(a0, t0, %BV.hdr) 5475 %shri(a0, a0, 8) 5476 %bltu(t2, a0, &.ok) 5477 :.oob 5478 %die(msg_string_oob) 5479 :.ok 5480 %heap_ld(a0, t0, %BV.data) 5481 %add(a0, a0, t2) 5482 %sb(t1, a0, 0) 5483 %li(a0, %imm_val(%IMM.UNSPEC)) 5484 %ret 5485 :.type 5486 %die(msg_type) 5487 .endscope 5488 5489 :prim_bv_u8_ref_entry 5490 .scope 5491 %args2(t0, t1, a0) ; bv, tagged idx 5492 %tagof(t2, t0) 5493 %bine(t2, %TAG.HEAP, &.type, a1) 5494 %hdr_type(t2, t0) 5495 %li(a1, %HDR.BV) 5496 %beq(t2, a1, &.bytes) 5497 %li(a1, %HDR.STRING) 5498 %bne(t2, a1, &.type) 5499 :.bytes 5500 %tagof(t2, t1) 5501 %bine(t2, %TAG.FIXNUM, &.type, a1) 5502 %sari(t1, t1, 3) ; raw idx 5503 %bltz(t1, &.oob) 5504 %heap_ld(a0, t0, %BV.hdr) 5505 %shri(a0, a0, 8) ; length 5506 %bltu(t1, a0, &.ok) 5507 :.oob 5508 %die(msg_bv_oob) 5509 :.ok 5510 %heap_ld(t2, t0, %BV.data) 5511 %add(t2, t2, t1) 5512 %lb(a0, t2, 0) 5513 %mkfix(a0, a0) 5514 %ret 5515 :.type 5516 %die(msg_type) 5517 .endscope 5518 5519 :prim_bv_u8_set_entry 5520 .scope 5521 %args3(t0, t2, t1, a0) ; bv, idx, val 5522 %tagof(a1, t0) 5523 %bine(a1, %TAG.HEAP, &.type, a2) 5524 %hdr_type(a1, t0) 5525 %li(a2, %HDR.BV) 5526 %beq(a1, a2, &.bytes) 5527 %li(a2, %HDR.STRING) 5528 %bne(a1, a2, &.type) 5529 :.bytes 5530 %tagof(a1, t2) 5531 %bine(a1, %TAG.FIXNUM, &.type, a2) 5532 %tagof(a1, t1) 5533 %bine(a1, %TAG.FIXNUM, &.type, a2) 5534 %sari(t2, t2, 3) ; raw idx 5535 %sari(t1, t1, 3) ; raw val 5536 %bltz(t1, &.bad_byte) 5537 %li(a1, 256) 5538 %bltu(t1, a1, &.value_ok) 5539 %b(&.bad_byte) 5540 :.value_ok 5541 %bltz(t2, &.oob) 5542 %heap_ld(a0, t0, %BV.hdr) 5543 %shri(a0, a0, 8) ; length 5544 %bltu(t2, a0, &.ok) 5545 :.oob 5546 %die(msg_bv_oob) 5547 :.ok 5548 %heap_ld(a0, t0, %BV.data) 5549 %add(a0, a0, t2) 5550 %sb(t1, a0, 0) 5551 %li(a0, %imm_val(%IMM.UNSPEC)) 5552 %ret 5553 :.bad_byte 5554 %die(msg_bad_byte) 5555 :.type 5556 %die(msg_type) 5557 .endscope 5558 5559 # (bytevector-copy src start end) -> fresh bv of length end-start. 5560 # Bounds: 0 <= start <= end <= src.length. 5561 # 5562 # Locals: 5563 # args 5564 # src tagged 5565 # wrapper (saved after bv_alloc) 5566 %gcfn2(prim_bv_copy_entry, {args src wrapper}, 7, 0, { 5567 %stl(a0, args) 5568 5569 %args3(t0, t2, t1, a0) ; src, start, end 5570 %stl(t0, src) 5571 %sari(t2, t2, 3) ; raw start 5572 %sari(t1, t1, 3) ; raw end 5573 5574 # Bounds: start >= 0; end >= start (signed catches negative end since 5575 # start is now non-negative); src.length >= end. 5576 %bltz(t2, &.oob) 5577 %blt(t1, t2, &.oob) 5578 %heap_ld(a0, t0, %BV.hdr) 5579 %shri(a0, a0, 8) ; src.length 5580 %blt(a0, t1, &.oob) 5581 5582 %sub(a0, t1, t2) ; count 5583 %call(&bv_alloc) 5584 %stl(a0, wrapper) 5585 5586 # Recompute src ptr at start; dst ptr at 0; count from new bv's hdr. 5587 %ldl(t0, args) 5588 %cdr(t0, t0) 5589 %car_fix(t0, t0) ; raw start 5590 %ldl(t1, src) 5591 %heap_ld(t2, t1, %BV.data) 5592 %add(t2, t2, t0) ; src ptr 5593 %ldl(a3, wrapper) 5594 %heap_ld(a2, a3, %BV.data) ; dst ptr 5595 %heap_ld(a1, a3, %BV.hdr) 5596 %shri(a1, a1, 8) ; count 5597 5598 :.copy_loop 5599 %beqz(a1, &.copy_done) 5600 %lb(t0, t2, 0) 5601 %sb(t0, a2, 0) 5602 %addi(t2, t2, 1) 5603 %addi(a2, a2, 1) 5604 %addi(a1, a1, -1) 5605 %b(&.copy_loop) 5606 5607 :.copy_done 5608 %ldl(a0, wrapper) 5609 %gceret 5610 5611 :.oob 5612 %die(msg_bv_oob) 5613 }) 5614 5615 # (bytevector-copy! dst dst-start src src-start src-end). Bounds: 5616 # 0 <= src-start <= src-end <= src.length and 5617 # 0 <= dst-start && dst-start + (src-end-src-start) <= dst.length. 5618 # 5619 # Locals: 5620 # dst -start (raw) 5621 # dst_start 5622 # src -end (raw) 5623 # src_start 5624 # src_end 5625 %fn2(prim_bv_copy_bang_entry, {dst dst_start src src_start src_end}, { 5626 %car(t0, a0) 5627 %stl(t0, dst) ; dst 5628 %cdr(a0, a0) 5629 %car(t0, a0) 5630 %sari(t0, t0, 3) 5631 %stl(t0, dst_start) ; dst-start 5632 %cdr(a0, a0) 5633 %car(t0, a0) 5634 %stl(t0, src) ; src 5635 %cdr(a0, a0) 5636 %car(t0, a0) 5637 %sari(t0, t0, 3) 5638 %stl(t0, src_start) ; src-start 5639 %cdr(a0, a0) 5640 %car(t0, a0) 5641 %sari(t0, t0, 3) 5642 %stl(t0, src_end) ; src-end 5643 5644 # src-start >= 0 5645 %ldl(t0, src_start) 5646 %bltz(t0, &.oob) 5647 # src-end >= src-start (signed catches negative src-end) 5648 %ldl(t1, src_end) 5649 %blt(t1, t0, &.oob) 5650 # src-end <= src.length 5651 %ldl(t2, src) 5652 %heap_ld(a0, t2, %BV.hdr) 5653 %shri(a0, a0, 8) 5654 %blt(a0, t1, &.oob) 5655 # dst-start >= 0 5656 %ldl(t2, dst_start) 5657 %bltz(t2, &.oob) 5658 # dst-start + count <= dst.length 5659 %sub(a0, t1, t0) ; count = src-end - src-start 5660 %add(a0, a0, t2) ; dst-start + count 5661 %ldl(t1, dst) 5662 %heap_ld(t2, t1, %BV.hdr) 5663 %shri(t2, t2, 8) 5664 %blt(t2, a0, &.oob) 5665 5666 # Set up copy. dst ptr = dst.data + dst-start; src ptr = src.data + 5667 # src-start; count = src-end - src-start. 5668 %ldl(t0, dst) 5669 %heap_ld(t0, t0, %BV.data) 5670 %ldl(a1, dst_start) 5671 %add(t0, t0, a1) ; dst ptr 5672 %ldl(a1, src) 5673 %heap_ld(a1, a1, %BV.data) 5674 %ldl(a2, src_start) 5675 %add(a1, a1, a2) ; src ptr 5676 %ldl(a3, src_end) 5677 %sub(a3, a3, a2) ; count 5678 5679 # memmove semantics: when destination starts inside the source range, 5680 # copy from the end so an overlapping in-place move cannot trample bytes 5681 # that have not been read yet. 5682 %beqz(a3, &.done) 5683 %bltu(a1, t0, &.dst_after_src) 5684 %b(&.forward_loop) 5685 :.dst_after_src 5686 %add(t1, a1, a3) ; source end 5687 %bltu(t0, t1, &.backward_setup) 5688 5689 :.forward_loop 5690 %beqz(a3, &.done) 5691 %lb(t1, a1, 0) 5692 %sb(t1, t0, 0) 5693 %addi(t0, t0, 1) 5694 %addi(a1, a1, 1) 5695 %addi(a3, a3, -1) 5696 %b(&.forward_loop) 5697 5698 :.backward_setup 5699 %add(t0, t0, a3) 5700 %add(a1, a1, a3) 5701 :.backward_loop 5702 %beqz(a3, &.done) 5703 %addi(t0, t0, -1) 5704 %addi(a1, a1, -1) 5705 %lb(t1, a1, 0) 5706 %sb(t1, t0, 0) 5707 %addi(a3, a3, -1) 5708 %b(&.backward_loop) 5709 5710 :.done 5711 %li(a0, %imm_val(%IMM.UNSPEC)) 5712 %eret 5713 5714 :.oob 5715 %die(msg_bv_oob) 5716 }) 5717 5718 # bv_equal_check(a=a0, b=a1) -> a0 (IMM.TRUE / IMM.FALSE). Leaf. Both 5719 # arguments are assumed to be HEAP-tagged HDR.BV values; callers do the 5720 # type check (either bytevector=?'s prim entry or equal_recurse's BV 5721 # branch). Compares lengths first, then walks bytes; %lb is zero-extending 5722 # on every backend, so a single %bne is enough for the byte test. 5723 :bv_equal_check 5724 .scope 5725 %heap_ld(t0, a0, %BV.hdr) 5726 %shri(t0, t0, 8) ; len_a 5727 %heap_ld(t1, a1, %BV.hdr) 5728 %shri(t1, t1, 8) ; len_b 5729 %bne(t0, t1, &.false) 5730 5731 %heap_ld(a2, a0, %BV.data) 5732 %heap_ld(a3, a1, %BV.data) 5733 5734 :.loop 5735 %beqz(t0, &.true) 5736 %lb(t1, a2, 0) 5737 %lb(t2, a3, 0) 5738 %bne(t1, t2, &.false) 5739 %addi(a2, a2, 1) 5740 %addi(a3, a3, 1) 5741 %addi(t0, t0, -1) 5742 %b(&.loop) 5743 5744 :.true 5745 %li(a0, %imm_val(%IMM.TRUE)) 5746 %ret 5747 5748 :.false 5749 %li(a0, %imm_val(%IMM.FALSE)) 5750 %ret 5751 .endscope 5752 5753 # (bytevector=? a b) / (bytes=? a b) -- byte-oriented bridge equality. 5754 # Each operand may be a string or bytevector; other inputs return #f. 5755 :prim_bytevector_eq_entry 5756 .scope 5757 %args2(t0, t1, a0) 5758 %tagof(t2, t0) 5759 %li(a0, %TAG.HEAP) 5760 %bne(t2, a0, &.false) 5761 %tagof(t2, t1) 5762 %bne(t2, a0, &.false) 5763 %hdr_type(t2, t0) 5764 %li(a0, %HDR.BV) 5765 %beq(t2, a0, &.a_bytes) 5766 %li(a0, %HDR.STRING) 5767 %bne(t2, a0, &.false) 5768 :.a_bytes 5769 %hdr_type(t2, t1) 5770 %li(a0, %HDR.BV) 5771 %beq(t2, a0, &.both_bytes) 5772 %li(a0, %HDR.STRING) 5773 %bne(t2, a0, &.false) 5774 :.both_bytes 5775 %mov(a0, t0) 5776 %mov(a1, t1) 5777 %b(&bv_equal_check) 5778 :.false 5779 %li(a0, %imm_val(%IMM.FALSE)) 5780 %ret 5781 .endscope 5782 5783 # ========================================================================= 5784 # Private hash tables -- open addressing for compiler-sized maps 5785 # ========================================================================= 5786 # 5787 # These primitives deliberately use `%`-prefixed boot2 names rather than an 5788 # R7RS spelling. Keys compare by byte content when both are strings or 5789 # bytevectors (the same bridge rule as bytes=?); every other key compares by 5790 # identity. HASH is a stable wrapper around a replaceable HASHDATA object, so 5791 # growing a table never invalidates Scheme references to the table itself. 5792 # 5793 # HASHDATA uses power-of-two capacity and linear probing. Slot hash 0 is empty, 5794 # 1 is a tombstone, and live hashes have bit 1 forced on (therefore >= 2). 5795 # `used` counts live+tombstone slots, which lets insertion rebuild a table 5796 # before tombstones can consume every terminating empty slot. 5797 5798 # hash_key_hash(key=a0) -> raw machine-word hash (a0). Leaf. Byte-sequence 5799 # hashing is content-only, so an equal string and bytevector land in the same 5800 # probe chain. The xorshift fallback spreads sequential symbol/fixnum keys. 5801 :hash_key_hash 5802 .scope 5803 %tagof(t0, a0) 5804 %li(t1, %TAG.HEAP) 5805 %bne(t0, t1, &.identity) 5806 %hdr_type(t0, a0) 5807 %li(t1, %HDR.BV) 5808 %beq(t0, t1, &.bytes) 5809 %li(t1, %HDR.STRING) 5810 %bne(t0, t1, &.identity) 5811 5812 :.bytes 5813 %heap_ld(t0, a0, %BV.hdr) 5814 %shri(t0, t0, 8) ; remaining length 5815 %heap_ld(t1, a0, %BV.data) 5816 %li(a1, 5381) 5817 :.bytes_loop 5818 %beqz(t0, &.bytes_done) 5819 %lb(t2, t1, 0) 5820 %shli(a2, a1, 5) ; hash * 33 5821 %add(a1, a2, a1) 5822 %xor(a1, a1, t2) 5823 %addi(t1, t1, 1) 5824 %addi(t0, t0, -1) 5825 %b(&.bytes_loop) 5826 :.bytes_done 5827 %mov(a0, a1) 5828 %ret 5829 5830 :.identity 5831 %shri(t0, a0, 3) 5832 %shri(t1, t0, 17) 5833 %xor(t0, t0, t1) 5834 %shli(t1, t0, 13) 5835 %xor(t0, t0, t1) 5836 %shri(t1, t0, 29) 5837 %xor(a0, t0, t1) 5838 %ret 5839 .endscope 5840 5841 # hash_key_equal(a=a0, b=a1) -> Scheme boolean. Leaf. Identity is the 5842 # overwhelmingly common path; the only structural case tails into bytes=?'s 5843 # allocation-free worker. 5844 :hash_key_equal 5845 .scope 5846 %beq(a0, a1, &.true) 5847 %tagof(t0, a0) 5848 %li(t1, %TAG.HEAP) 5849 %bne(t0, t1, &.false) 5850 %tagof(t0, a1) 5851 %bne(t0, t1, &.false) 5852 %hdr_type(t0, a0) 5853 %li(t2, %HDR.BV) 5854 %beq(t0, t2, &.a_bytes) 5855 %li(t2, %HDR.STRING) 5856 %bne(t0, t2, &.false) 5857 :.a_bytes 5858 %hdr_type(t0, a1) 5859 %li(t2, %HDR.BV) 5860 %beq(t0, t2, &.both_bytes) 5861 %li(t2, %HDR.STRING) 5862 %bne(t0, t2, &.false) 5863 :.both_bytes 5864 %b(&bv_equal_check) 5865 :.true 5866 %li(a0, %imm_val(%IMM.TRUE)) 5867 %ret 5868 :.false 5869 %li(a0, %imm_val(%IMM.FALSE)) 5870 %ret 5871 .endscope 5872 5873 # hash_data_alloc(capacity=a0 raw) -> HASHDATA object. Capacity is already a 5874 # power of two. Traced allocation clears every slot hash to the empty marker. 5875 %fn2(hash_data_alloc, {capacity}, { 5876 %stl(a0, capacity) 5877 %shli(t0, a0, 4) 5878 %shli(t1, a0, 3) 5879 %add(a0, t0, t1) ; capacity * 24 5880 %addi(a0, a0, %HASHDATA.SIZE) 5881 %ldl(t0, capacity) 5882 %shli(t0, t0, 8) 5883 %ori(a1, t0, %HDR.HASHDATA) 5884 %call(&alloc_hdr) 5885 %li(t0, 0) 5886 %heap_st(t0, a0, %HASHDATA.count) 5887 %heap_st(t0, a0, %HASHDATA.used) 5888 }) 5889 5890 # hash_make(requested_capacity=a0 raw) -> HASH wrapper. Round up to a 5891 # power-of-two with a small floor so every table retains an empty probe slot. 5892 %gcfn2(hash_make, {capacity table}, 2, 0, { 5893 %li(t0, 8) 5894 %stl(t0, capacity) 5895 :.round 5896 %ldl(t0, capacity) 5897 %bltu(t0, a0, &.grow) 5898 %b(&.rounded) 5899 :.grow 5900 %shli(t0, t0, 1) 5901 %stl(t0, capacity) 5902 %b(&.round) 5903 5904 :.rounded 5905 %li(a0, %HASH.SIZE) 5906 %li(a1, %HDR.HASH) 5907 %call(&alloc_hdr) 5908 %stl(a0, table) 5909 %ldl(a0, capacity) 5910 %call(&hash_data_alloc) 5911 %ldl(t0, table) 5912 %heap_st(a0, t0, %HASH.data) 5913 %mov(a0, t0) 5914 %gceret 5915 }) 5916 5917 # Insert a known-unique live entry into fresh HASHDATA. No equality checks or 5918 # allocations; used and count both grow by one. Used only while rehashing. 5919 %fn2(hash_data_insert_known, {data hash key value idx slot}, { 5920 %stl(a0, data) 5921 %stl(a1, hash) 5922 %stl(a2, key) 5923 %stl(a3, value) 5924 %heap_ld(t0, a0, %HASHDATA.hdr) 5925 %shri(t0, t0, 8) 5926 %addi(t0, t0, -1) 5927 %and(t0, t0, a1) 5928 %stl(t0, idx) 5929 5930 :.probe 5931 %ldl(t0, data) 5932 %ldl(t1, idx) 5933 %hash_slot(t2, t0, t1, a0) 5934 %stl(t2, slot) 5935 %ld(a0, t2, 0) 5936 %beqz(a0, &.insert) 5937 %heap_ld(t0, t0, %HASHDATA.hdr) 5938 %shri(t0, t0, 8) 5939 %addi(t0, t0, -1) 5940 %addi(t1, t1, 1) 5941 %and(t1, t1, t0) 5942 %stl(t1, idx) 5943 %b(&.probe) 5944 5945 :.insert 5946 %ldl(t0, slot) 5947 %ldl(t1, hash) 5948 %st(t1, t0, 0) 5949 %ldl(t1, key) 5950 %st(t1, t0, 8) 5951 %ldl(t1, value) 5952 %st(t1, t0, 16) 5953 %ldl(t0, data) 5954 %heap_ld(t1, t0, %HASHDATA.count) 5955 %addi(t1, t1, 1) 5956 %heap_st(t1, t0, %HASHDATA.count) 5957 %heap_ld(t1, t0, %HASHDATA.used) 5958 %addi(t1, t1, 1) 5959 %heap_st(t1, t0, %HASHDATA.used) 5960 }) 5961 5962 # Rebuild a table and discard tombstones. Double when the live entries need 5963 # the space; otherwise retain the capacity so repeated delete/insert churn 5964 # cannot grow a mostly-empty table without bound. The wrapper continues 5965 # pointing at the old data until the new allocation is rooted, so collection 5966 # is safe at every construction point. 5967 %gcfn2(hash_resize, {table old_data new_data capacity idx}, 7, 0, { 5968 %stl(a0, table) 5969 %heap_ld(t0, a0, %HASH.data) 5970 %stl(t0, old_data) 5971 %heap_ld(t1, t0, %HASHDATA.hdr) 5972 %shri(t1, t1, 8) 5973 %stl(t1, capacity) 5974 %heap_ld(t0, t0, %HASHDATA.count) 5975 %addi(t0, t0, 1) 5976 %shli(t0, t0, 2) ; (live + incoming) * 4 5977 %shli(t2, t1, 1) 5978 %add(t2, t2, t1) ; capacity * 3 5979 %bltu(t0, t2, &.alloc_same) 5980 %shli(a0, t1, 1) 5981 %b(&.alloc) 5982 :.alloc_same 5983 %mov(a0, t1) 5984 :.alloc 5985 %call(&hash_data_alloc) 5986 %stl(a0, new_data) 5987 %li(t0, 0) 5988 %stl(t0, idx) 5989 5990 :.loop 5991 %ldl(t0, idx) 5992 %ldl(t1, capacity) 5993 %beq(t0, t1, &.publish) 5994 %ldl(t1, old_data) 5995 %hash_slot(t2, t1, t0, a0) 5996 %ld(a1, t2, 0) 5997 %li(a0, 2) 5998 %bltu(a1, a0, &.next) 5999 %ld(a2, t2, 8) 6000 %ld(a3, t2, 16) 6001 %ldl(a0, new_data) 6002 %call(&hash_data_insert_known) 6003 6004 :.next 6005 %ldl(t0, idx) 6006 %addi(t0, t0, 1) 6007 %stl(t0, idx) 6008 %b(&.loop) 6009 6010 :.publish 6011 %ldl(a0, table) 6012 %ldl(t0, new_data) 6013 %heap_st(t0, a0, %HASH.data) 6014 %gceret 6015 }) 6016 6017 # hash_ref(table=a0, key=a1) -> value or #f. Probe count is bounded by 6018 # capacity as a defensive guarantee even if a corrupted table lacks empties. 6019 %fn2(hash_ref, {table key data hash idx remaining slot}, { 6020 %stl(a0, table) 6021 %stl(a1, key) 6022 %mov(a0, a1) 6023 %call(&hash_key_hash) 6024 %ori(a0, a0, 2) 6025 %stl(a0, hash) 6026 %ldl(t0, table) 6027 %heap_ld(t0, t0, %HASH.data) 6028 %stl(t0, data) 6029 %heap_ld(t1, t0, %HASHDATA.hdr) 6030 %shri(t1, t1, 8) 6031 %stl(t1, remaining) 6032 %addi(t1, t1, -1) 6033 %and(t1, t1, a0) 6034 %stl(t1, idx) 6035 6036 :.probe 6037 %ldl(t0, remaining) 6038 %beqz(t0, &.miss) 6039 %ldl(t0, data) 6040 %ldl(t1, idx) 6041 %hash_slot(t2, t0, t1, a0) 6042 %stl(t2, slot) 6043 %ld(t2, t2, 0) 6044 %beqz(t2, &.miss) 6045 %ldl(a0, hash) 6046 %bne(t2, a0, &.next) 6047 %ldl(t2, slot) 6048 %ld(a0, t2, 8) 6049 %ldl(a1, key) 6050 %call(&hash_key_equal) 6051 %bieq(a0, %imm_val(%IMM.FALSE), &.next, t0) 6052 %ldl(t0, slot) 6053 %ld(a0, t0, 16) 6054 %eret 6055 6056 :.next 6057 %ldl(t0, data) 6058 %heap_ld(t0, t0, %HASHDATA.hdr) 6059 %shri(t0, t0, 8) 6060 %addi(t0, t0, -1) 6061 %ldl(t1, idx) 6062 %addi(t1, t1, 1) 6063 %and(t1, t1, t0) 6064 %stl(t1, idx) 6065 %ldl(t0, remaining) 6066 %addi(t0, t0, -1) 6067 %stl(t0, remaining) 6068 %b(&.probe) 6069 6070 :.miss 6071 %li(a0, %imm_val(%IMM.FALSE)) 6072 }) 6073 6074 # hash_set(table=a0, key=a1, value=a2) -> table. Grow when live+tombstone 6075 # occupancy reaches 3/4. The first tombstone in a probe chain is reused, but 6076 # probing continues until an empty slot so an existing equal key still wins. 6077 %gcfn2(hash_set, {table key value data hash idx remaining tomb slot}, 15, 0, { 6078 %stl(a0, table) 6079 %stl(a1, key) 6080 %stl(a2, value) 6081 %heap_ld(t0, a0, %HASH.data) 6082 %stl(t0, data) 6083 %heap_ld(t1, t0, %HASHDATA.used) 6084 %addi(t1, t1, 1) 6085 %shli(t1, t1, 2) 6086 %heap_ld(t2, t0, %HASHDATA.hdr) 6087 %shri(t2, t2, 8) 6088 %shli(a0, t2, 1) 6089 %add(a0, a0, t2) ; capacity * 3 6090 %bltu(t1, a0, &.ready) 6091 %ldl(a0, table) 6092 %call(&hash_resize) 6093 %ldl(t0, table) 6094 %heap_ld(t0, t0, %HASH.data) 6095 %stl(t0, data) 6096 6097 :.ready 6098 %ldl(a0, key) 6099 %call(&hash_key_hash) 6100 %ori(a0, a0, 2) 6101 %stl(a0, hash) 6102 %ldl(t0, data) 6103 %heap_ld(t1, t0, %HASHDATA.hdr) 6104 %shri(t1, t1, 8) 6105 %stl(t1, remaining) 6106 %addi(t1, t1, -1) 6107 %and(t1, t1, a0) 6108 %stl(t1, idx) 6109 %li(t0, 0) 6110 %stl(t0, tomb) 6111 6112 :.probe 6113 %ldl(t0, remaining) 6114 %beqz(t0, &.use_tomb) 6115 %ldl(t0, data) 6116 %ldl(t1, idx) 6117 %hash_slot(t2, t0, t1, a0) 6118 %stl(t2, slot) 6119 %ld(t2, t2, 0) 6120 %beqz(t2, &.empty) 6121 %li(t0, 1) 6122 %beq(t2, t0, &.remember_tomb) 6123 %ldl(a0, hash) 6124 %bne(t2, a0, &.next) 6125 %ldl(t2, slot) 6126 %ld(a0, t2, 8) 6127 %ldl(a1, key) 6128 %call(&hash_key_equal) 6129 %bieq(a0, %imm_val(%IMM.FALSE), &.next, t0) 6130 %ldl(t0, slot) 6131 %ldl(t1, value) 6132 %st(t1, t0, 16) 6133 %b(&.done) 6134 6135 :.remember_tomb 6136 %ldl(t0, tomb) 6137 %bnez(t0, &.next) 6138 %ldl(t0, slot) 6139 %stl(t0, tomb) 6140 %b(&.next) 6141 6142 :.empty 6143 %ldl(t0, tomb) 6144 %bnez(t0, &.insert_tomb) 6145 %ldl(t0, slot) 6146 %stl(t0, tomb) 6147 # Inserting into a genuinely empty slot increases used. 6148 %ldl(t1, data) 6149 %heap_ld(t2, t1, %HASHDATA.used) 6150 %addi(t2, t2, 1) 6151 %heap_st(t2, t1, %HASHDATA.used) 6152 %b(&.insert) 6153 6154 :.insert_tomb 6155 %stl(t0, tomb) 6156 %b(&.insert) 6157 6158 :.next 6159 %ldl(t0, data) 6160 %heap_ld(t0, t0, %HASHDATA.hdr) 6161 %shri(t0, t0, 8) 6162 %addi(t0, t0, -1) 6163 %ldl(t1, idx) 6164 %addi(t1, t1, 1) 6165 %and(t1, t1, t0) 6166 %stl(t1, idx) 6167 %ldl(t0, remaining) 6168 %addi(t0, t0, -1) 6169 %stl(t0, remaining) 6170 %b(&.probe) 6171 6172 :.use_tomb 6173 %ldl(t0, tomb) 6174 %beqz(t0, &.full) 6175 6176 :.insert 6177 %ldl(t0, tomb) 6178 %ldl(t1, hash) 6179 %st(t1, t0, 0) 6180 %ldl(t1, key) 6181 %st(t1, t0, 8) 6182 %ldl(t1, value) 6183 %st(t1, t0, 16) 6184 %ldl(t0, data) 6185 %heap_ld(t1, t0, %HASHDATA.count) 6186 %addi(t1, t1, 1) 6187 %heap_st(t1, t0, %HASHDATA.count) 6188 6189 :.done 6190 %ldl(a0, table) 6191 %gceret 6192 :.full 6193 %die(msg_hash_full) 6194 }) 6195 6196 # hash_delete(table=a0, key=a1) -> Scheme boolean. Tombstoning preserves 6197 # subsequent entries in the probe chain; key/value slots are cleared so the 6198 # collector can reclaim them immediately. 6199 %fn2(hash_delete, {table key data hash idx remaining slot}, { 6200 %stl(a0, table) 6201 %stl(a1, key) 6202 %mov(a0, a1) 6203 %call(&hash_key_hash) 6204 %ori(a0, a0, 2) 6205 %stl(a0, hash) 6206 %ldl(t0, table) 6207 %heap_ld(t0, t0, %HASH.data) 6208 %stl(t0, data) 6209 %heap_ld(t1, t0, %HASHDATA.hdr) 6210 %shri(t1, t1, 8) 6211 %stl(t1, remaining) 6212 %addi(t1, t1, -1) 6213 %and(t1, t1, a0) 6214 %stl(t1, idx) 6215 6216 :.probe 6217 %ldl(t0, remaining) 6218 %beqz(t0, &.miss) 6219 %ldl(t0, data) 6220 %ldl(t1, idx) 6221 %hash_slot(t2, t0, t1, a0) 6222 %stl(t2, slot) 6223 %ld(t2, t2, 0) 6224 %beqz(t2, &.miss) 6225 %ldl(a0, hash) 6226 %bne(t2, a0, &.next) 6227 %ldl(t2, slot) 6228 %ld(a0, t2, 8) 6229 %ldl(a1, key) 6230 %call(&hash_key_equal) 6231 %bieq(a0, %imm_val(%IMM.FALSE), &.next, t0) 6232 %ldl(t0, slot) 6233 %li(t1, 1) 6234 %st(t1, t0, 0) 6235 %li(t1, 0) 6236 %st(t1, t0, 8) 6237 %st(t1, t0, 16) 6238 %ldl(t0, data) 6239 %heap_ld(t1, t0, %HASHDATA.count) 6240 %addi(t1, t1, -1) 6241 %heap_st(t1, t0, %HASHDATA.count) 6242 %li(a0, %imm_val(%IMM.TRUE)) 6243 %eret 6244 6245 :.next 6246 %ldl(t0, data) 6247 %heap_ld(t0, t0, %HASHDATA.hdr) 6248 %shri(t0, t0, 8) 6249 %addi(t0, t0, -1) 6250 %ldl(t1, idx) 6251 %addi(t1, t1, 1) 6252 %and(t1, t1, t0) 6253 %stl(t1, idx) 6254 %ldl(t0, remaining) 6255 %addi(t0, t0, -1) 6256 %stl(t0, remaining) 6257 %b(&.probe) 6258 6259 :.miss 6260 %li(a0, %imm_val(%IMM.FALSE)) 6261 }) 6262 6263 # Private Scheme entry points. 6264 %fn(prim_make_hash_entry, 0, { 6265 %car(t0, a0) 6266 %tagof(t1, t0) 6267 %bine(t1, %TAG.FIXNUM, &.bad, t2) 6268 %untag_fix(a0, t0) 6269 %bltz(a0, &.bad) 6270 %call(&hash_make) 6271 %eret 6272 :.bad 6273 %die(msg_type) 6274 }) 6275 6276 %fn(prim_hash_ref_entry, 0, { 6277 %args2(t0, t1, a0) 6278 %mov(a0, t0) 6279 %mov(a1, t1) 6280 %call(&hash_ref) 6281 %eret 6282 }) 6283 6284 %fn(prim_hash_set_entry, 0, { 6285 %args3(t0, t1, t2, a0) 6286 %mov(a0, t0) 6287 %mov(a1, t1) 6288 %mov(a2, t2) 6289 %call(&hash_set) 6290 %li(a0, %imm_val(%IMM.UNSPEC)) 6291 %eret 6292 }) 6293 6294 %fn(prim_hash_delete_entry, 0, { 6295 %args2(t0, t1, a0) 6296 %mov(a0, t0) 6297 %mov(a1, t1) 6298 %call(&hash_delete) 6299 %eret 6300 }) 6301 6302 :prim_hash_size_entry 6303 %car(t0, a0) 6304 %heap_ld(t0, t0, %HASH.data) 6305 %heap_ld(a0, t0, %HASHDATA.count) 6306 %mkfix(a0, a0) 6307 %ret 6308 6309 # equal_recurse(a=a0, b=a1) -> a0 (IMM.TRUE / IMM.FALSE). Identity covers 6310 # fixnums, characters, symbols, immediates, and any case where both arguments 6311 # are the same heap or pair pointer. For non-identical pair pointers we recurse 6312 # into car then cdr. Same-type bytevectors and strings compare by content; 6313 # records compare structurally; closures, prims, and TDs are identity-only. 6314 # Tail-calls the cdr-side recursion and the byte-sequence/record checks. 6315 # 6316 # Locals: 6317 # a 6318 # b 6319 %fn2(equal_recurse, {a b}, { 6320 %stl(a0, a) 6321 %stl(a1, b) 6322 6323 %beq(a0, a1, &.true) 6324 6325 %tagof(t0, a0) 6326 %tagof(t1, a1) 6327 %bne(t0, t1, &.false) 6328 6329 %bieq(t0, %TAG.PAIR, &.pair, t1) 6330 %bieq(t0, %TAG.HEAP, &.heap, t1) 6331 %b(&.false) 6332 6333 :.pair 6334 %ldl(t0, a) 6335 %ldl(t1, b) 6336 %car(a0, t0) 6337 %car(a1, t1) 6338 %call(&equal_recurse) 6339 %bieq(a0, %imm_val(%IMM.FALSE), &.done, t0) 6340 %ldl(t0, a) 6341 %ldl(t1, b) 6342 %cdr(a0, t0) 6343 %cdr(a1, t1) 6344 %tail(&equal_recurse) 6345 6346 :.heap 6347 %ldl(t0, a) 6348 %ldl(t1, b) 6349 %hdr_type(t2, t0) 6350 %hdr_type(a0, t1) 6351 %bne(t2, a0, &.false) ; differing heap classes -> #f 6352 %li(a0, %HDR.BV) 6353 %beq(t2, a0, &.heap_bv) 6354 %li(a0, %HDR.STRING) 6355 %beq(t2, a0, &.heap_bv) 6356 %li(a0, %HDR.REC) 6357 %beq(t2, a0, &.heap_rec) 6358 %b(&.false) ; CLOSURE/PRIM/TD: identity-only 6359 6360 :.heap_bv 6361 %mov(a0, t0) 6362 %mov(a1, t1) 6363 %tail(&bv_equal_check) 6364 6365 :.heap_rec 6366 %mov(a0, t0) 6367 %mov(a1, t1) 6368 %tail(&rec_equal_check) 6369 6370 :.true 6371 %li(a0, %imm_val(%IMM.TRUE)) 6372 %b(&.done) 6373 6374 :.false 6375 %li(a0, %imm_val(%IMM.FALSE)) 6376 6377 :.done 6378 }) 6379 6380 # rec_equal_check(a=a0, b=a1) -> a0 (IMM.TRUE / IMM.FALSE). Both args 6381 # are HEAP-tagged HDR.REC. Records are equal iff their TDs are eq? and 6382 # every field is equal? (recursing through equal_recurse). Field i sits 6383 # at tagged + 13 + 8*i; nfields lives at the TD's offset 13 (raw). 6384 # 6385 # Locals: 6386 # a (rec, tagged) 6387 # b (rec, tagged) 6388 # i (raw counter) 6389 # nfields (raw) 6390 %fn2(rec_equal_check, {a b i nfields}, { 6391 %stl(a0, a) 6392 %stl(a1, b) 6393 6394 %heap_ld(t0, a0, %REC.td) ; td_a 6395 %heap_ld(t1, a1, %REC.td) ; td_b 6396 %bne(t0, t1, &.false) 6397 6398 %heap_ld(t1, t0, %TD.nfields) 6399 %stl(t1, nfields) 6400 %li(t0, 0) 6401 %stl(t0, i) ; i = 0 6402 6403 :.loop 6404 %ldl(t0, i) 6405 %ldl(t1, nfields) 6406 %beq(t0, t1, &.true) 6407 6408 %shli(t2, t0, 3) 6409 %addi(t2, t2, 13) ; field offset = 13 + 8*i 6410 %ldl(t1, a) 6411 %add(t1, t1, t2) 6412 %ld(a0, t1, 0) ; a's field i 6413 %ldl(t1, b) 6414 %add(t1, t1, t2) 6415 %ld(a1, t1, 0) ; b's field i 6416 %call(&equal_recurse) 6417 %bieq(a0, %imm_val(%IMM.FALSE), &.done, t0) 6418 6419 %ldl(t0, i) 6420 %addi(t0, t0, 1) 6421 %stl(t0, i) 6422 %b(&.loop) 6423 6424 :.true 6425 %li(a0, %imm_val(%IMM.TRUE)) 6426 %b(&.done) 6427 6428 :.false 6429 %li(a0, %imm_val(%IMM.FALSE)) 6430 6431 :.done 6432 }) 6433 6434 # (equal? a b) -- thin prim wrapper that unpacks the args list and falls 6435 # into equal_recurse. equal_recurse owns the frame; this entry stays a 6436 # leaf so the prim-dispatch tailr lands directly into the frame setup. 6437 :prim_equal_entry 6438 %args2(t0, t1, a0) 6439 %mov(a0, t0) 6440 %mov(a1, t1) 6441 %b(&equal_recurse) 6442 6443 # (apply fn rest...) -- the trailing element of `rest` is a list; any 6444 # leading elements get prepended to it. apply_build_args walks `rest` and 6445 # returns the assembled args list; prim_apply_entry then tail-calls apply. 6446 # 6447 # `apply` is itself a primitive, so on entry here a0 holds (fn . rest) 6448 # and a1 holds the apply PRIM ptr (per the convention documented at 6449 # `apply::prim`). a1 is dead from this primitive's point of view; we 6450 # clobber it freely while assembling args, then tail-call apply, which 6451 # re-derives a1 from the callee fn it dispatches on. Outer convention 6452 # stays intact end-to-end. 6453 6454 %gcfn2(prim_apply_entry, {args pad}, 1, 0, { 6455 %stl(a0, args) 6456 %cdr(a0, a0) 6457 %call(&apply_build_args) 6458 %mov(t0, a0) 6459 %ldl(a0, args) 6460 %car(a0, a0) 6461 %mov(a1, t0) 6462 %gctail(&apply) 6463 }) 6464 6465 # apply_build_args(rest=a0) -> assembled args list. 6466 # `rest` is (a1 a2 ... aN listargs); the trailing element is itself a list 6467 # whose elements get appended after the leading aâ‚–'s. Iterative 6468 # head/tail-cdr build: walk every cell whose cdr isn't NIL into a fresh 6469 # (aâ‚– . NIL) cons; the final element (the trailing list) becomes the 6470 # tail's cdr (or the result itself if there are no leading elements). 6471 # 6472 # Locals: 6473 # walk (advances; current cell of rest) 6474 # head (NIL until first leading arg appended) 6475 # tail (most recent cell; set-cdr! target) 6476 %gcfn2(apply_build_args, {walk head tail}, 7, 0, { 6477 %stl(a0, walk) 6478 %li(t0, %imm_val(%IMM.NIL)) 6479 %stl(t0, head) 6480 %stl(t0, tail) 6481 6482 :.loop 6483 %ldl(t0, walk) 6484 %cdr(t1, t0) 6485 %if_nil(t2, t1, &.last) 6486 6487 # cell = cons(car(walk), NIL); append to head/tail. 6488 %car(a0, t0) 6489 %li(a1, %imm_val(%IMM.NIL)) 6490 %call(&cons) 6491 6492 %ldl(t0, head) 6493 %if_nil(t1, t0, &.first) 6494 %ldl(t0, tail) 6495 %set_cdr(a0, t0) 6496 %stl(a0, tail) 6497 %b(&.advance) 6498 6499 :.first 6500 %stl(a0, head) 6501 %stl(a0, tail) 6502 6503 :.advance 6504 %advance_walk(walk) 6505 %b(&.loop) 6506 6507 :.last 6508 # car(walk) is the trailing list. If head is NIL there were no leading 6509 # args -- return the trailing list directly. Otherwise splice it onto 6510 # the tail and return head. 6511 %car(a0, t0) 6512 %ldl(t1, head) 6513 %if_nil(t2, t1, &.done) 6514 %ldl(t1, tail) 6515 %set_cdr(a0, t1) 6516 %ldl(a0, head) 6517 6518 :.done 6519 }) 6520 6521 # Records: TDs (type descriptors) and instances. A TD is a 24-byte heap 6522 # object [HDR.TD][name_sym][nfields_raw]. A record is a variable-width 6523 # heap object [HDR.REC][td][field_0]...[field_{n-1}], so field i lives at 6524 # tagged + 13 + 8*i. define-record-type allocates one TD plus one 6525 # parameterized PRIM per ctor/predicate/accessor/mutator, all pointing 6526 # into the same TD via the prim's data slot. 6527 6528 # make_param_prim(entry=a0, data=a1) -> prim (a0). Allocates a 24-byte 6529 # PRIM and sets the entry label and data word. Generated primitives stay 6530 # reachable through their global bindings. 6531 6532 %gcfn2(make_param_prim, {entry data}, 2, 0, { 6533 %stl(a0, entry) 6534 %stl(a1, data) 6535 6536 %li(a0, 24) 6537 %li(a1, %HDR.PRIM) 6538 %call(&alloc_hdr) 6539 6540 %ldl(t0, entry) 6541 %heap_st(t0, a0, %PRIM.entry_w) 6542 %ldl(t1, data) 6543 %heap_st(t1, a0, %PRIM.data) 6544 }) 6545 6546 # Parameterized PRIM entries used by define-record-type. Each receives 6547 # args in a0 and the prim itself in a1; the prim's data slot (offset 13 6548 # from tagged) holds either the TD or a tagged field index. The 6549 # constructor inlines record allocation; predicate / accessor / mutator 6550 # inline what would otherwise be %record-is-a? / %record-ref / 6551 # %record-set! bodies. None of these primitives are exposed at the 6552 # user level — R7RS define-record-type binds only ctor / pred / 6553 # accessor / mutator names. 6554 6555 # ctor: prim.data = TD (HEAP); args = (f0 f1 ...). Inlines the 6556 # %make-record body so we don't have to cons (TD . args) first. 6557 6558 %gcfn2(prim_ctor_entry, {args td record}, 7, 0, { 6559 %stl(a0, args) 6560 %heap_ld(t0, a1, %PRIM.data) 6561 %stl(t0, td) 6562 6563 # Count = length(args). 6564 %call(&list_length) 6565 %ldl(t0, td) 6566 %heap_ld(t1, t0, %TD.nfields) 6567 %bne(a0, t1, &.bad_arity) 6568 %shli(a0, a0, 3) 6569 %addi(a0, a0, 16) 6570 %li(a1, %HDR.REC) 6571 %call(&alloc_hdr) 6572 %stl(a0, record) 6573 6574 %ldl(t0, td) 6575 %heap_st(t0, a0, %REC.td) 6576 6577 %ldl(t0, args) 6578 %addi(t1, a0, 13) 6579 6580 :.fill_loop 6581 %if_nil(t2, t0, &.fill_done) 6582 %car(t2, t0) 6583 %st(t2, t1, 0) 6584 %addi(t1, t1, 8) 6585 %cdr(t0, t0) 6586 %b(&.fill_loop) 6587 :.fill_done 6588 6589 %ldl(a0, record) 6590 %gceret 6591 6592 :.bad_arity 6593 %die(msg_record_ctor_arity) 6594 }) 6595 6596 # predicate: prim.data = TD; args = (rec). 6597 :prim_predicate_entry 6598 .scope 6599 %car(t0, a0) 6600 %heap_ld(t1, a1, %PRIM.data) 6601 %tagof(t2, t0) 6602 %li(a0, %imm_val(%IMM.FALSE)) 6603 %bine(t2, %TAG.HEAP, &.end, a2) 6604 %hdr_type(t2, t0) 6605 %bine(t2, %HDR.REC, &.end, a2) 6606 %heap_ld(t2, t0, %REC.td) 6607 %bne(t2, t1, &.end) 6608 %li(a0, %imm_val(%IMM.TRUE)) 6609 :.end 6610 %ret 6611 .endscope 6612 6613 # accessor: prim.data = tagged field index; args = (rec). 6614 :prim_accessor_entry 6615 %car(t0, a0) 6616 %heap_ld(t1, a1, %PRIM.data) 6617 %addi(t1, t1, 13) 6618 %add(t1, t1, t0) 6619 %ld(a0, t1, 0) 6620 %ret 6621 6622 # mutator: prim.data = tagged field index; args = (rec val). 6623 :prim_mutator_entry 6624 .scope 6625 %car(t0, a0) 6626 %cdr(t1, a0) 6627 %car(t1, t1) 6628 %heap_ld(t2, a1, %PRIM.data) 6629 %addi(t2, t2, 13) 6630 %add(t2, t2, t0) 6631 %st(t1, t2, 0) 6632 %li(a0, %imm_val(%IMM.UNSPEC)) 6633 %ret 6634 .endscope 6635 6636 # eval_define_record_type(rest=a0, env=a1) -> UNSPEC. 6637 # rest = (name (ctor f1 ...) pred clause1 clause2 ...) 6638 # Each clause is (field-name accessor) or (field-name accessor mutator). 6639 # Allocates one TD + one parameterized PRIM per name introduced (ctor, 6640 # predicate, accessor, mutator) and binds each to the symbol's global. 6641 # The TD also stores a list of field-name symbols in declaration order; 6642 # pmatch's ($ pred (field pat) ...) record pattern uses this to map 6643 # field names to indices at match time. 6644 # 6645 # Locals: 6646 # rest 6647 # env (unused, but the dispatcher passes it) 6648 # td 6649 # walk (clauses, advancing) 6650 # idx (raw counter) 6651 # nfields 6652 # fl_head (head of field-name list under construction) 6653 # fl_tail (tail cell of field-name list under construction) 6654 # fl_cur (cursor walking clauses for field-name pre-pass) 6655 %gcfn2(eval_define_record_type, {rest env td walk idx nfields fl_head fl_tail fl_cur}, 463, 0, { 6656 %stl(a0, rest) 6657 %stl(a1, env) 6658 6659 # clauses = cdddr(rest). Before allocating or publishing any bindings, 6660 # validate the restricted micro constructor specification: its field 6661 # list must name every declared field exactly once and in declaration 6662 # order. 6663 %ldl(a0, rest) 6664 %cdr(a0, a0) 6665 %cdr(a0, a0) 6666 %cdr(a0, a0) 6667 %stl(a0, walk) 6668 6669 # fl_cur = cdr(cadr(rest)) -- constructor field cursor. 6670 %ldl(t0, rest) 6671 %cdr(t0, t0) 6672 %car(t0, t0) 6673 %cdr(t0, t0) 6674 %stl(t0, fl_cur) 6675 6676 :.validate_ctor 6677 %ldl(t0, fl_cur) 6678 %ldl(t1, walk) 6679 %if_nil(t2, t0, &.ctor_fields_done) 6680 %if_nil(t2, t1, &.bad_ctor_spec) 6681 %car(t2, t0) ; constructor field name 6682 %car(a0, t1) ; declaration clause 6683 %car(a0, a0) ; declared field name 6684 %bne(t2, a0, &.bad_ctor_spec) 6685 %cdr(t0, t0) 6686 %stl(t0, fl_cur) 6687 %cdr(t1, t1) 6688 %stl(t1, walk) 6689 %b(&.validate_ctor) 6690 6691 :.ctor_fields_done 6692 %if_nil(t2, t1, &.ctor_valid) 6693 %b(&.bad_ctor_spec) 6694 6695 :.ctor_valid 6696 # Restore the declaration cursor after validation and count fields. 6697 %ldl(a0, rest) 6698 %cdr(a0, a0) 6699 %cdr(a0, a0) 6700 %cdr(a0, a0) 6701 %stl(a0, walk) 6702 %call(&list_length) 6703 %stl(a0, nfields) 6704 6705 # td = alloc_hdr(TD.SIZE, HDR.TD); td.name = type-name; 6706 # td.nfields = nfields; td.fields = NIL (filled below). 6707 %li(a0, %TD.SIZE) 6708 %li(a1, %HDR.TD) 6709 %call(&alloc_hdr) 6710 %stl(a0, td) 6711 %ldl(t0, rest) 6712 %car(t0, t0) 6713 %heap_st(t0, a0, %TD.name) 6714 %ldl(t1, nfields) 6715 %heap_st(t1, a0, %TD.nfields) 6716 %li(t1, %imm_val(%IMM.NIL)) 6717 %heap_st(t1, a0, %TD.fields) 6718 6719 # Pre-pass: build (field-name-1 ... field-name-N) in declaration order 6720 # via head/tail accumulator, then store at td.fields. Each clause's 6721 # car is the field-name symbol. Uses fl_cur as a separate cursor so 6722 # walk is left intact for the accessor-binding loop below. 6723 %li(t0, %imm_val(%IMM.NIL)) 6724 %stl(t0, fl_head) 6725 %stl(t0, fl_tail) 6726 %ldl(t0, walk) 6727 %stl(t0, fl_cur) 6728 6729 :.fl_loop 6730 %ldl(t0, fl_cur) 6731 %if_nil(t1, t0, &.fl_done) 6732 # cell = cons(car(car(fl_cur)), NIL) 6733 %car(t1, t0) 6734 %car(a0, t1) 6735 %li(a1, %imm_val(%IMM.NIL)) 6736 %call(&cons) 6737 # Splice into list: if head is NIL, head = tail = cell. 6738 # Else set-cdr!(tail, cell); tail = cell. 6739 %ldl(t1, fl_head) 6740 %bine(t1, %imm_val(%IMM.NIL), &.fl_append, t2) 6741 %stl(a0, fl_head) 6742 %stl(a0, fl_tail) 6743 %b(&.fl_next) 6744 :.fl_append 6745 %ldl(t1, fl_tail) 6746 %set_cdr(a0, t1) 6747 %stl(a0, fl_tail) 6748 :.fl_next 6749 %ldl(t0, fl_cur) 6750 %cdr(t0, t0) 6751 %stl(t0, fl_cur) 6752 %b(&.fl_loop) 6753 6754 :.fl_done 6755 %ldl(t0, td) 6756 %ldl(t1, fl_head) 6757 %heap_st(t1, t0, %TD.fields) 6758 6759 # ctor-prim = make_param_prim(prim_ctor_entry, td); bind ctor-name. 6760 %la(a0, &prim_ctor_entry) 6761 %ldl(a1, td) 6762 %call(&make_param_prim) 6763 %ldl(t0, rest) 6764 %cdr(t0, t0) 6765 %car(t0, t0) 6766 %car(t0, t0) 6767 %set_global(t0, a0) 6768 6769 # pred-prim = make_param_prim(prim_predicate_entry, td); bind pred. 6770 %la(a0, &prim_predicate_entry) 6771 %ldl(a1, td) 6772 %call(&make_param_prim) 6773 %ldl(t0, rest) 6774 %cdr(t0, t0) 6775 %cdr(t0, t0) 6776 %car(t0, t0) 6777 %set_global(t0, a0) 6778 6779 # Iterate clauses: bind accessor + optional mutator per clause. 6780 %li(t0, 0) 6781 %stl(t0, idx) 6782 6783 :.clause_loop 6784 %ldl(t0, walk) 6785 %if_nil(t1, t0, &.done) 6786 6787 # accessor-prim with data = tagged idx; bind cadr(clause). 6788 %ldl(a1, idx) 6789 %mkfix(a1, a1) 6790 %la(a0, &prim_accessor_entry) 6791 %call(&make_param_prim) 6792 6793 %ldl(t0, walk) 6794 %car(t0, t0) 6795 %cdr(t0, t0) 6796 %car(t0, t0) 6797 %set_global(t0, a0) 6798 6799 # Mutator? If cddr(clause) is a pair, bind it. 6800 %ldl(t0, walk) 6801 %car(t0, t0) 6802 %cdr(t0, t0) 6803 %cdr(t0, t0) 6804 %if_nil(t1, t0, &.no_mutator) 6805 6806 %ldl(a1, idx) 6807 %mkfix(a1, a1) 6808 %la(a0, &prim_mutator_entry) 6809 %call(&make_param_prim) 6810 6811 %ldl(t0, walk) 6812 %car(t0, t0) 6813 %cdr(t0, t0) 6814 %cdr(t0, t0) 6815 %car(t0, t0) 6816 %set_global(t0, a0) 6817 6818 :.no_mutator 6819 %advance_walk(walk) 6820 %ldl(t0, idx) 6821 %addi(t0, t0, 1) 6822 %stl(t0, idx) 6823 %b(&.clause_loop) 6824 6825 :.done 6826 %li(a0, %imm_val(%IMM.UNSPEC)) 6827 %gceret 6828 6829 :.bad_ctor_spec 6830 %die(msg_record_ctor_spec) 6831 }) 6832 6833 # ========================================================================= 6834 # Writer -- display, write, format, error 6835 # ========================================================================= 6836 # 6837 # All four entry points walk values through a single recursive writer 6838 # that appends bytes into an output bytevector. display / write call the 6839 # writer once, then sys_write the resulting bytes to stdout. error 6840 # prepends `scheme1: error: `, joins irritants with spaces, and tails 6841 # into runtime_error so the prefix stays consistent with every other 6842 # abort path. format walks a template bv, emitting raw bytes verbatim 6843 # and dispatching ~a (display), ~s (write), ~d (decimal), ~% (newline), 6844 # and ~~ (literal '~') against successive args. 6845 # 6846 # Mode flag for write_to_bv: 0 = display (strings and bytevectors emit raw), 6847 # 1 = write (strings use an escaped quoted spelling and bytevectors use 6848 # #u8(...)). 6849 # 6850 # bv_putn / bv_putc / bv_putint append raw bytes to a bv and return the 6851 # (same wrapper, possibly-grown) bv. They do NOT maintain a trailing NUL 6852 # -- callers building "strings" must use the str_* family below. 6853 # bv_grow patches data_ptr/capacity in place, so the wrapper pointer 6854 # never changes -- callers can keep a stable handle in a single frame 6855 # slot. 6856 6857 # bv_putn(bv=a0, src=a1, n=a2) -> bv (a0). Append n bytes from src to bv, 6858 # growing the data buffer when capacity falls short. Raw u8[] semantics: 6859 # the byte at index `length` after append is unspecified. 6860 6861 %gcfn2(bv_putn, {bv src n old_len}, 1, 2, { 6862 %stl(a0, bv) 6863 %stl(a1, src) 6864 %stl(a2, n) 6865 6866 %heap_ld(t0, a0, %BV.hdr) 6867 %shri(t0, t0, 8) ; old_len 6868 %stl(t0, old_len) 6869 6870 # bv_grow ensures cap >= old_len + n. 6871 %add(a1, t0, a2) 6872 %call(&bv_grow) 6873 6874 %ldl(t0, bv) 6875 %heap_ld(a0, t0, %BV.data) 6876 %ldl(t1, old_len) 6877 %add(a0, a0, t1) ; dst = data + old_len 6878 %ldl(a1, src) 6879 %ldl(a2, n) 6880 %call(&memcpy) 6881 6882 # hdr = (old_len + n) << 8 | HDR.BV. HDR.BV is 0. 6883 %ldl(t0, old_len) 6884 %ldl(t1, n) 6885 %add(t0, t0, t1) 6886 %shli(t0, t0, 8) 6887 %ldl(t1, bv) 6888 %heap_st(t0, t1, %BV.hdr) 6889 6890 %ldl(a0, bv) 6891 }) 6892 6893 # bv_putc(bv=a0, byte=a1) -> bv (a0). Append a single byte (low 8 bits 6894 # of a1). Same growth + length-update protocol as bv_putn; no NUL. 6895 6896 %gcfn2(bv_putc, {bv byte}, 1, 0, { 6897 %stl(a0, bv) 6898 %stl(a1, byte) 6899 6900 %heap_ld(t0, a0, %BV.hdr) 6901 %shri(t0, t0, 8) ; old_len 6902 %addi(a1, t0, 1) ; min_cap = old_len + 1 6903 %call(&bv_grow) 6904 6905 %ldl(t0, bv) 6906 %heap_ld(t1, t0, %BV.hdr) 6907 %shri(t1, t1, 8) ; old_len (re-read after grow) 6908 %heap_ld(t2, t0, %BV.data) 6909 %add(t2, t2, t1) 6910 %ldl(a0, byte) 6911 %sb(a0, t2, 0) 6912 6913 %addi(t1, t1, 1) 6914 %shli(t1, t1, 8) 6915 %heap_st(t1, t0, %BV.hdr) 6916 6917 %ldl(a0, bv) 6918 }) 6919 6920 # bv_putint(bv=a0, value=a1) -> bv (a0). Append decimal repr of (raw, 6921 # untagged) value. Uses :writer_num_buf as a 24-byte scratch buffer 6922 # (fmt_dec writes at most 20 bytes for a 64-bit signed integer). 6923 6924 %gcfn2(bv_putint, {bv pad}, 1, 0, { 6925 %stl(a0, bv) 6926 6927 %la(a0, &writer_num_buf) 6928 %call(&fmt_dec) ; n_bytes (a0) 6929 6930 %mov(a2, a0) 6931 %la(a1, &writer_num_buf) 6932 %ldl(a0, bv) 6933 %gctail(&bv_putn) 6934 }) 6935 6936 # String writers: identical to bv_putn / bv_putc / bv_putint except they 6937 # guarantee cap > length AND data[length] == 0 on return. Required for 6938 # any bv whose data_ptr is later read as a C string (syscall paths, 6939 # runtime_error). The explicit zero is necessary because a fresh data 6940 # buffer may come from a reused block carrying stale bytes. 6941 6942 # str_alloc(raw_len=a0) -> tagged bv (a0). Like bv_alloc, but cap > 6943 # raw_len and data[raw_len] = 0. 6944 %gcfn2(str_alloc, {raw_len bv}, 2, 0, { 6945 %stl(a0, raw_len) 6946 %addi(a0, a0, 1) ; reserve a NUL slot 6947 %call(&bv_alloc) 6948 %stl(a0, bv) 6949 6950 # Patch hdr length back down to raw_len. 6951 %ldl(t0, raw_len) 6952 %shli(t0, t0, 8) ; HDR.BV is 0 6953 %heap_st(t0, a0, %BV.hdr) 6954 6955 # Zero data[raw_len]. 6956 %heap_ld(t1, a0, %BV.data) 6957 %ldl(t2, raw_len) 6958 %add(t1, t1, t2) 6959 %li(t0, 0) 6960 %sb(t0, t1, 0) 6961 6962 %ldl(a0, bv) 6963 }) 6964 6965 # string_alloc(raw_len=a0) -> tagged HDR.STRING. Strings share the 6966 # length/data/capacity layout with bytevectors so boot2's byte bridge and 6967 # syscall wrappers can use one compact set of walkers, but the distinct 6968 # header keeps the Scheme types disjoint. A spare trailing NUL is maintained 6969 # for pathname interoperability; it is not part of the logical length. 6970 %gcfn2(string_alloc, {raw_len string}, 2, 0, { 6971 %stl(a0, raw_len) 6972 %addi(a0, a0, 1) 6973 %call(&bv_alloc) 6974 %stl(a0, string) 6975 6976 %ldl(t0, raw_len) 6977 %shli(t0, t0, 8) 6978 %ori(t0, t0, %HDR.STRING) 6979 %heap_st(t0, a0, %BV.hdr) 6980 6981 %heap_ld(t1, a0, %BV.data) 6982 %ldl(t2, raw_len) 6983 %add(t1, t1, t2) 6984 %li(t0, 0) 6985 %sb(t0, t1, 0) 6986 6987 %ldl(a0, string) 6988 }) 6989 6990 # str_putn(bv=a0, src=a1, n=a2) -> bv (a0). Append n bytes; on return 6991 # cap > new_len and data[new_len] == 0. 6992 %gcfn2(str_putn, {bv src n}, 1, 2, { 6993 %stl(a0, bv) 6994 %stl(a1, src) 6995 %stl(a2, n) 6996 6997 # Pre-grow so the post-append buffer has a NUL slot. 6998 %heap_ld(t0, a0, %BV.hdr) 6999 %shri(t0, t0, 8) ; old_len 7000 %add(a1, t0, a2) 7001 %addi(a1, a1, 1) ; min_cap = old_len + n + 1 7002 %call(&bv_grow) 7003 7004 %ldl(a0, bv) 7005 %ldl(a1, src) 7006 %ldl(a2, n) 7007 %call(&bv_putn) ; appends + updates length 7008 7009 # Zero data[new_len]. bv_putn left cap and data_ptr alone, so the 7010 # NUL slot reserved above is still ours. 7011 %heap_ld(t0, a0, %BV.hdr) 7012 %shri(t0, t0, 8) ; new_len 7013 %heap_ld(t1, a0, %BV.data) 7014 %add(t1, t1, t0) 7015 %li(t2, 0) 7016 %sb(t2, t1, 0) 7017 }) 7018 7019 # str_putc(bv=a0, byte=a1) -> bv (a0). Append one byte; cap > new_len 7020 # and data[new_len] == 0 on return. 7021 %gcfn2(str_putc, {bv byte}, 1, 0, { 7022 %stl(a0, bv) 7023 %stl(a1, byte) 7024 7025 %heap_ld(t0, a0, %BV.hdr) 7026 %shri(t0, t0, 8) ; old_len 7027 %addi(a1, t0, 2) ; min_cap = old_len + 1 + 1 7028 %call(&bv_grow) 7029 7030 %ldl(a0, bv) 7031 %ldl(a1, byte) 7032 %call(&bv_putc) 7033 7034 %heap_ld(t0, a0, %BV.hdr) 7035 %shri(t0, t0, 8) ; new_len 7036 %heap_ld(t1, a0, %BV.data) 7037 %add(t1, t1, t0) 7038 %li(t2, 0) 7039 %sb(t2, t1, 0) 7040 }) 7041 7042 # str_putint(bv=a0, value=a1) -> bv (a0). Like bv_putint but tails into 7043 # str_putn, so the result is NUL-terminated. 7044 %gcfn2(str_putint, {bv pad}, 1, 0, { 7045 %stl(a0, bv) 7046 7047 %la(a0, &writer_num_buf) 7048 %call(&fmt_dec) ; n_bytes (a0) 7049 7050 %mov(a2, a0) 7051 %la(a1, &writer_num_buf) 7052 %ldl(a0, bv) 7053 %gctail(&str_putn) 7054 }) 7055 7056 # str_puthex(bv=a0, value=a1) -> bv (a0). Signed hex: emits a leading 7057 # '-' for negatives, then unsigned hex of |value| via fmt_hex. The bv 7058 # wrapper pointer is stable across str_putc / str_putn (only the 7059 # internal data buffer can move), so we reload it from the local. 7060 %gcfn2(str_puthex, {bv value}, 1, 0, { 7061 %stl(a0, bv) 7062 %stl(a1, value) 7063 7064 %bltz(a1, &.neg) 7065 %b(&.pos) 7066 7067 :.neg 7068 %ldl(a0, bv) 7069 %li(a1, 45) ; '-' 7070 %call(&str_putc) 7071 %ldl(t0, value) 7072 %li(t1, 0) 7073 %sub(t0, t1, t0) 7074 %stl(t0, value) 7075 7076 :.pos 7077 %la(a0, &writer_num_buf) 7078 %ldl(a1, value) 7079 %call(&fmt_hex) ; n_bytes (a0) 7080 7081 %mov(a2, a0) 7082 %la(a1, &writer_num_buf) 7083 %ldl(a0, bv) 7084 %gctail(&str_putn) 7085 }) 7086 7087 # sym_name(idx=a0) -> (ptr=a0, len=a1). Leaf. idx is the untagged sym 7088 # slot index; both fields come straight out of the symtab entry. 7089 :sym_name 7090 %ld_global(t0, &symtab_buf_ptr) 7091 %lda_array(a1, t1, t0, %SYMENT.SIZE, a0, %SYMENT.name_len) 7092 %ld(a0, t1, %SYMENT.name_ptr) 7093 %ret 7094 7095 # hex_digit_ascii(nibble=a0) -> lowercase ASCII byte. 7096 :hex_digit_ascii 7097 .scope 7098 %li(t0, 10) 7099 %bltu(a0, t0, &.digit) 7100 %addi(a0, a0, 87) ; 10 -> 'a' 7101 %ret 7102 :.digit 7103 %addi(a0, a0, 48) 7104 %ret 7105 .endscope 7106 7107 # write_string_to_bv(string=a0, bv=a1, mode=a2) -> bv. Display mode emits 7108 # the logical bytes. Write mode emits a reader-compatible quoted spelling 7109 # with the micro escape set, including embedded NUL bytes. 7110 %gcfn2(write_string_to_bv, {string bv index length byte}, 3, 0, { 7111 %stl(a0, string) 7112 %stl(a1, bv) 7113 7114 %heap_ld(t0, a0, %BV.hdr) 7115 %shri(t0, t0, 8) 7116 %stl(t0, length) 7117 %beqz(a2, &.raw) 7118 7119 %ldl(a0, bv) 7120 %li(a1, 34) ; opening quote 7121 %call(&str_putc) 7122 %li(t0, 0) 7123 %stl(t0, index) 7124 7125 :.loop 7126 %ldl(t0, index) 7127 %ldl(t1, length) 7128 %beq(t0, t1, &.close) 7129 %ldl(t1, string) 7130 %heap_ld(t2, t1, %BV.data) 7131 %add(t2, t2, t0) 7132 %lb(t2, t2, 0) 7133 %stl(t2, byte) 7134 7135 %bieq(t2, 10, &.escape_n, t0) 7136 %bieq(t2, 9, &.escape_t, t0) 7137 %bieq(t2, 13, &.escape_r, t0) 7138 %bieq(t2, 92, &.escape_slash, t0) 7139 %bieq(t2, 34, &.escape_quote, t0) 7140 %li(t0, 32) 7141 %bltu(t2, t0, &.escape_hex) 7142 %li(t0, 127) 7143 %bltu(t2, t0, &.plain) 7144 %b(&.escape_hex) 7145 7146 :.plain 7147 %ldl(a0, bv) 7148 %mov(a1, t2) 7149 %call(&str_putc) 7150 %b(&.advance) 7151 7152 :.escape_n 7153 %li(t0, 110) ; 'n' 7154 %b(&.escape_two) 7155 :.escape_t 7156 %li(t0, 116) ; 't' 7157 %b(&.escape_two) 7158 :.escape_r 7159 %li(t0, 114) ; 'r' 7160 %b(&.escape_two) 7161 :.escape_slash 7162 %li(t0, 92) 7163 %b(&.escape_two) 7164 :.escape_quote 7165 %li(t0, 34) 7166 :.escape_two 7167 %stl(t0, byte) 7168 %ldl(a0, bv) 7169 %li(a1, 92) 7170 %call(&str_putc) 7171 %ldl(a0, bv) 7172 %ldl(a1, byte) 7173 %call(&str_putc) 7174 %b(&.advance) 7175 7176 :.escape_hex 7177 %ldl(a0, bv) 7178 %li(a1, 92) 7179 %call(&str_putc) 7180 %ldl(a0, bv) 7181 %li(a1, 120) ; 'x' 7182 %call(&str_putc) 7183 %ldl(t0, byte) 7184 %shri(a0, t0, 4) 7185 %call(&hex_digit_ascii) 7186 %mov(a1, a0) 7187 %ldl(a0, bv) 7188 %call(&str_putc) 7189 %ldl(t0, byte) 7190 %andi(a0, t0, 15) 7191 %call(&hex_digit_ascii) 7192 %mov(a1, a0) 7193 %ldl(a0, bv) 7194 %call(&str_putc) 7195 %ldl(a0, bv) 7196 %li(a1, 59) ; ';' 7197 %call(&str_putc) 7198 7199 :.advance 7200 %ldl(t0, index) 7201 %addi(t0, t0, 1) 7202 %stl(t0, index) 7203 %b(&.loop) 7204 7205 :.close 7206 %ldl(a0, bv) 7207 %li(a1, 34) 7208 %gctail(&str_putc) 7209 7210 :.raw 7211 %ldl(t0, string) 7212 %heap_ld(a1, t0, %BV.data) 7213 %ldl(a2, length) 7214 %ldl(a0, bv) 7215 %gctail(&str_putn) 7216 }) 7217 7218 # write_bytevector_to_bv(bytes=a0, bv=a1, mode=a2) -> bv. Display mode 7219 # retains boot2's raw-byte diagnostic convention; write mode uses #u8(...). 7220 %gcfn2(write_bytevector_to_bv, {bytes bv index length}, 3, 0, { 7221 %stl(a0, bytes) 7222 %stl(a1, bv) 7223 %heap_ld(t0, a0, %BV.hdr) 7224 %shri(t0, t0, 8) 7225 %stl(t0, length) 7226 %beqz(a2, &.raw) 7227 7228 %ldl(a0, bv) 7229 %la(a1, &str_u8_open) 7230 %li(a2, 4) 7231 %call(&str_putn) 7232 %li(t0, 0) 7233 %stl(t0, index) 7234 :.loop 7235 %ldl(t0, index) 7236 %ldl(t1, length) 7237 %beq(t0, t1, &.close) 7238 %beqz(t0, &.value) 7239 %ldl(a0, bv) 7240 %li(a1, 32) 7241 %call(&str_putc) 7242 :.value 7243 %ldl(t0, bytes) 7244 %heap_ld(t1, t0, %BV.data) 7245 %ldl(t0, index) 7246 %add(t1, t1, t0) 7247 %lb(a1, t1, 0) 7248 %ldl(a0, bv) 7249 %call(&str_putint) 7250 %ldl(t0, index) 7251 %addi(t0, t0, 1) 7252 %stl(t0, index) 7253 %b(&.loop) 7254 :.close 7255 %ldl(a0, bv) 7256 %li(a1, 41) 7257 %gctail(&str_putc) 7258 7259 :.raw 7260 %ldl(t0, bytes) 7261 %heap_ld(a1, t0, %BV.data) 7262 %ldl(a2, length) 7263 %ldl(a0, bv) 7264 %gctail(&str_putn) 7265 }) 7266 7267 # write_char_to_bv(char=a0, bv=a1, mode=a2) -> bv. Display emits the 7268 # character byte; write emits a micro character literal. 7269 %gcfn2(write_char_to_bv, {bv value}, 1, 0, { 7270 %stl(a1, bv) 7271 %untag_char(t0, a0) 7272 %stl(t0, value) 7273 %beqz(a2, &.display) 7274 7275 %ldl(a0, bv) 7276 %la(a1, &str_char_open) 7277 %li(a2, 2) 7278 %call(&str_putn) 7279 %ldl(t0, value) 7280 %bieq(t0, 32, &.space, t1) 7281 %bieq(t0, 10, &.newline, t1) 7282 %bieq(t0, 9, &.tab, t1) 7283 %bieq(t0, 13, &.return, t1) 7284 %beqz(t0, &.null) 7285 %li(t1, 32) 7286 %bltu(t0, t1, &.hex) 7287 %li(t1, 127) 7288 %bltu(t0, t1, &.direct) 7289 :.hex 7290 %ldl(a0, bv) 7291 %li(a1, 120) ; 'x' 7292 %call(&str_putc) 7293 %ldl(a0, bv) 7294 %ldl(a1, value) 7295 %gctail(&str_puthex) 7296 :.direct 7297 %ldl(a0, bv) 7298 %mov(a1, t0) 7299 %gctail(&str_putc) 7300 :.space 7301 %la(a1, &name_ch_space) 7302 %li(a2, 5) 7303 %b(&.named) 7304 :.newline 7305 %la(a1, &name_ch_newline) 7306 %li(a2, 7) 7307 %b(&.named) 7308 :.tab 7309 %la(a1, &name_ch_tab) 7310 %li(a2, 3) 7311 %b(&.named) 7312 :.return 7313 %la(a1, &name_ch_return) 7314 %li(a2, 6) 7315 %b(&.named) 7316 :.null 7317 %la(a1, &name_ch_null) 7318 %li(a2, 4) 7319 :.named 7320 %ldl(a0, bv) 7321 %gctail(&str_putn) 7322 7323 :.display 7324 %ldl(a0, bv) 7325 %mov(a1, t0) 7326 %gctail(&str_putc) 7327 }) 7328 7329 # write_to_bv(val=a0, bv=a1, mode=a2) -> bv (a0). Recursively appends 7330 # val's printed representation to bv. Strings and characters use their 7331 # R7RS display/write spellings. Boot2 display keeps bytevectors raw for 7332 # compiler diagnostics, while write emits #u8(...). Pairs are delegated 7333 # to write_pair_to_bv so recursion through PAIR has its own frame. 7334 # 7335 # Output is treated as a string by callers (display / write / error / 7336 # format), so all internal append calls go through the str_* family -- 7337 # the result has cap > length and a trailing NUL. 7338 7339 %gcfn2(write_to_bv, {val bv mode pad}, 3, 0, { 7340 %stl(a0, val) 7341 %stl(a1, bv) 7342 %stl(a2, mode) 7343 7344 %tagof(t0, a0) 7345 %bieq(t0, %TAG.PAIR, &.pair, t1) 7346 %bieq(t0, %TAG.SYM, &.sym, t1) 7347 %bieq(t0, %TAG.HEAP, &.heap, t1) 7348 %bieq(t0, %TAG.IMM, &.imm, t1) 7349 %bieq(t0, %TAG.CHAR, &.char, t1) 7350 7351 # Fall-through: FIXNUM (the only remaining tag). 7352 %ldl(a0, bv) 7353 %ldl(a1, val) 7354 %sari(a1, a1, 3) 7355 %gctail(&str_putint) 7356 7357 :.sym 7358 %ldl(a0, val) 7359 %sari(a0, a0, 3) 7360 %call(&sym_name) 7361 %mov(a2, a1) 7362 %mov(a1, a0) 7363 %ldl(a0, bv) 7364 %gctail(&str_putn) 7365 7366 :.pair 7367 %ldl(a0, val) 7368 %ldl(a1, bv) 7369 %ldl(a2, mode) 7370 %gctail(&write_pair_to_bv) 7371 7372 :.char 7373 %ldl(a0, val) 7374 %ldl(a1, bv) 7375 %ldl(a2, mode) 7376 %gctail(&write_char_to_bv) 7377 7378 :.heap 7379 %hdr_type(t0, a0) 7380 %bieq(t0, %HDR.BV, &.heap_bv, t1) 7381 %bieq(t0, %HDR.STRING, &.heap_string, t1) 7382 %bieq(t0, %HDR.CLOSURE, &.heap_closure, t1) 7383 %bieq(t0, %HDR.PRIM, &.heap_prim, t1) 7384 %bieq(t0, %HDR.TD, &.heap_td, t1) 7385 %bieq(t0, %HDR.REC, &.heap_rec, t1) 7386 %b(&.heap_unknown) 7387 7388 :.heap_bv 7389 %ldl(a0, val) 7390 %ldl(a1, bv) 7391 %ldl(a2, mode) 7392 %gctail(&write_bytevector_to_bv) 7393 7394 :.heap_string 7395 %ldl(a0, val) 7396 %ldl(a1, bv) 7397 %ldl(a2, mode) 7398 %gctail(&write_string_to_bv) 7399 7400 :.heap_closure 7401 %la(a1, &str_closure) 7402 %li(a2, 10) 7403 %ldl(a0, bv) 7404 %gctail(&str_putn) 7405 7406 :.heap_prim 7407 %la(a1, &str_prim) 7408 %li(a2, 7) 7409 %ldl(a0, bv) 7410 %gctail(&str_putn) 7411 7412 :.heap_td 7413 %la(a1, &str_td) 7414 %li(a2, 11) 7415 %ldl(a0, bv) 7416 %gctail(&str_putn) 7417 7418 :.heap_rec 7419 %la(a1, &str_rec) 7420 %li(a2, 9) 7421 %ldl(a0, bv) 7422 %gctail(&str_putn) 7423 7424 :.heap_unknown 7425 %la(a1, &str_unknown) 7426 %li(a2, 10) 7427 %ldl(a0, bv) 7428 %gctail(&str_putn) 7429 7430 :.imm 7431 %ldl(a0, val) 7432 %sari(a0, a0, 3) 7433 %beqz(a0, &.imm_false) 7434 %addi(t0, a0, -1) 7435 %beqz(t0, &.imm_true) 7436 %addi(t0, a0, -2) 7437 %beqz(t0, &.imm_nil) 7438 %addi(t0, a0, -3) 7439 %beqz(t0, &.imm_unspec) 7440 %addi(t0, a0, -4) 7441 %beqz(t0, &.imm_unbound) 7442 # EOF (idx == 5) is the only remaining IMM. 7443 %la(a1, &str_eof) 7444 %li(a2, 5) 7445 %ldl(a0, bv) 7446 %gctail(&str_putn) 7447 7448 :.imm_false 7449 %la(a1, &str_false) 7450 %li(a2, 2) 7451 %ldl(a0, bv) 7452 %gctail(&str_putn) 7453 7454 :.imm_true 7455 %la(a1, &str_true) 7456 %li(a2, 2) 7457 %ldl(a0, bv) 7458 %gctail(&str_putn) 7459 7460 :.imm_nil 7461 %la(a1, &str_nil) 7462 %li(a2, 2) 7463 %ldl(a0, bv) 7464 %gctail(&str_putn) 7465 7466 :.imm_unspec 7467 %la(a1, &str_unspec) 7468 %li(a2, 8) 7469 %ldl(a0, bv) 7470 %gctail(&str_putn) 7471 7472 :.imm_unbound 7473 %la(a1, &str_unbound) 7474 %li(a2, 9) 7475 %ldl(a0, bv) 7476 %gctail(&str_putn) 7477 }) 7478 7479 # write_pair_to_bv(pair=a0, bv=a1, mode=a2) -> bv (a0). Emits `(elt elt 7480 # ...)` form, with `( . )` for non-list cdrs (dotted pair). The walker 7481 # advances `pair` along the spine; cdr's tag determines whether we emit 7482 # a separator and continue, emit ` . val)` for a dotted tail, or just 7483 # emit `)` for a proper-list NIL. 7484 # 7485 # Locals: 7486 # pair walk 7487 # bv (stable wrapper; reused across recursive calls) 7488 # mode 7489 # pad 7490 %gcfn2(write_pair_to_bv, {pair bv mode pad}, 3, 0, { 7491 %stl(a0, pair) 7492 %stl(a1, bv) 7493 %stl(a2, mode) 7494 7495 %ldl(a0, bv) 7496 %li(a1, 40) 7497 %call(&str_putc) 7498 7499 :.loop 7500 %ldl(t0, pair) 7501 %car(a0, t0) 7502 %ldl(a1, bv) 7503 %ldl(a2, mode) 7504 %call(&write_to_bv) 7505 7506 %ldl(t0, pair) 7507 %cdr(t0, t0) 7508 %stl(t0, pair) 7509 7510 %if_nil(t1, t0, &.done) 7511 %tagof(t1, t0) 7512 %li(t2, %TAG.PAIR) 7513 %beq(t1, t2, &.cont) 7514 7515 # Dotted tail: emit ` . ` then write_to_bv(cdr). 7516 %ldl(a0, bv) 7517 %li(a1, 32) 7518 %call(&str_putc) 7519 %ldl(a0, bv) 7520 %li(a1, 46) 7521 %call(&str_putc) 7522 %ldl(a0, bv) 7523 %li(a1, 32) 7524 %call(&str_putc) 7525 %ldl(a0, pair) 7526 %ldl(a1, bv) 7527 %ldl(a2, mode) 7528 %call(&write_to_bv) 7529 %b(&.done) 7530 7531 :.cont 7532 %ldl(a0, bv) 7533 %li(a1, 32) 7534 %call(&str_putc) 7535 %b(&.loop) 7536 7537 :.done 7538 %ldl(a0, bv) 7539 %li(a1, 41) 7540 %gctail(&str_putc) 7541 }) 7542 7543 # value_to_bv(val=a0, mode=a1) -> bv (a0). Allocate an empty NUL- 7544 # terminated bv and delegate to write_to_bv; helper for display / write 7545 # / error / format. write_to_bv internally uses str_*, so the result 7546 # has cap > length and a trailing NUL -- safe to hand to syscalls or 7547 # runtime_error as a C string. 7548 7549 %gcfn2(value_to_bv, {val mode}, 1, 0, { 7550 %stl(a0, val) 7551 %stl(a1, mode) 7552 %li(a0, 0) 7553 %call(&str_alloc) 7554 %mov(a1, a0) 7555 %ldl(a0, val) 7556 %ldl(a2, mode) 7557 %gctail(&write_to_bv) 7558 }) 7559 7560 # (display val) and (write val): build the printed representation in a 7561 # fresh bv, sys_write the raw bytes to fd 1, return UNSPEC. Partial 7562 # writes are not retried -- libp1pp's wrapper streams its own buffer 7563 # but the kernel may chunk a giant single write; in practice 7564 # scheme1 outputs are short and we accept the simple path. 7565 %fn(prim_display_entry, 0, { 7566 %car(a0, a0) 7567 %li(a1, 0) 7568 %call(&value_to_bv) 7569 %heap_ld(a1, a0, %BV.data) 7570 %heap_ld(a2, a0, %BV.hdr) 7571 %shri(a2, a2, 8) 7572 %li(a0, 1) 7573 %call(&sys_write) 7574 %li(a0, %imm_val(%IMM.UNSPEC)) 7575 }) 7576 7577 %fn(prim_write_entry, 0, { 7578 %car(a0, a0) 7579 %li(a1, 1) 7580 %call(&value_to_bv) 7581 %heap_ld(a1, a0, %BV.data) 7582 %heap_ld(a2, a0, %BV.hdr) 7583 %shri(a2, a2, 8) 7584 %li(a0, 1) 7585 %call(&sys_write) 7586 %li(a0, %imm_val(%IMM.UNSPEC)) 7587 }) 7588 7589 # (error msg-bv irritant ...). Builds `scheme1: error: <msg> <irr> ...` 7590 # in a string-bv (irritants joined by single spaces, all rendered with 7591 # display semantics) and tails into runtime_error. str_alloc + str_* 7592 # guarantee cap > length and a trailing NUL, making the bv's data_ptr 7593 # a valid C string for panic's eprint_cstr. 7594 # 7595 # Locals: 7596 # walk (initially args; advances over irritants) 7597 # bv 7598 %gcfn2(prim_error_entry, {walk bv}, 3, 0, { 7599 %stl(a0, walk) 7600 7601 %li(a0, 0) 7602 %call(&str_alloc) 7603 %stl(a0, bv) 7604 7605 %la(a1, &str_error_prefix) 7606 %li(a2, 16) 7607 %ldl(a0, bv) 7608 %call(&str_putn) 7609 7610 # First arg (the message) goes through write_to_bv with display mode. 7611 %ldl(t0, walk) 7612 %car(a0, t0) 7613 %ldl(a1, bv) 7614 %li(a2, 0) 7615 %call(&write_to_bv) 7616 7617 %ldl(t0, walk) 7618 %cdr(t0, t0) 7619 %stl(t0, walk) 7620 7621 :.loop 7622 %ldl(t0, walk) 7623 %if_nil(t1, t0, &.done) 7624 7625 %ldl(a0, bv) 7626 %li(a1, 32) 7627 %call(&str_putc) 7628 7629 %ldl(t0, walk) 7630 %car(a0, t0) 7631 %ldl(a1, bv) 7632 %li(a2, 0) 7633 %call(&write_to_bv) 7634 7635 %ldl(t0, walk) 7636 %cdr(t0, t0) 7637 %stl(t0, walk) 7638 %b(&.loop) 7639 7640 :.done 7641 %ldl(t0, bv) 7642 %heap_ld(a0, t0, %BV.data) 7643 %gctail(&runtime_error) 7644 }) 7645 7646 # (format template-bv arg ...). Walks the template bv byte by byte; 7647 # `~X` consumes the next byte as a directive: a (display), s (write), 7648 # d (decimal fixnum), x (lowercase hex fixnum, signed), % (newline), 7649 # ~ (literal tilde). Unknown specs pass through verbatim. Returns the 7650 # assembled bv; the caller decides how to consume it (e.g. 7651 # (display (format ...))). 7652 # 7653 # Locals: 7654 # out bv 7655 # template bv 7656 # args walk 7657 # idx (current byte offset into template) 7658 %gcfn2(prim_format_entry, {out template args idx}, 7, 0, { 7659 %stl(a0, args) ; spill incoming args while we set up 7660 7661 %li(a0, 0) 7662 %call(&str_alloc) 7663 %stl(a0, out) 7664 7665 %ldl(t0, args) 7666 %car(t1, t0) 7667 %stl(t1, template) 7668 %cdr(t0, t0) 7669 %stl(t0, args) 7670 7671 %li(t0, 0) 7672 %stl(t0, idx) 7673 7674 :.loop 7675 %ldl(t1, template) 7676 %heap_ld(t2, t1, %BV.hdr) 7677 %shri(t2, t2, 8) ; template length 7678 %ldl(t0, idx) 7679 %beq(t0, t2, &.done) 7680 7681 %heap_ld(a3, t1, %BV.data) 7682 %add(a3, a3, t0) 7683 %lb(a3, a3, 0) ; byte = template.data[idx] 7684 7685 %addi(t1, a3, -126) ; '~' 7686 %beqz(t1, &.tilde) 7687 7688 # Plain byte: emit and advance. 7689 %ldl(a0, out) 7690 %mov(a1, a3) 7691 %call(&str_putc) 7692 %ldl(t0, idx) 7693 %addi(t0, t0, 1) 7694 %stl(t0, idx) 7695 %b(&.loop) 7696 7697 :.tilde 7698 %ldl(t0, idx) 7699 %addi(t0, t0, 1) 7700 %ldl(t1, template) 7701 %heap_ld(t2, t1, %BV.hdr) 7702 %shri(t2, t2, 8) 7703 %beq(t0, t2, &.tilde_lit) 7704 7705 %heap_ld(t1, t1, %BV.data) 7706 %add(t1, t1, t0) 7707 %lb(a3, t1, 0) ; spec 7708 7709 %addi(t0, t0, 1) ; advance past spec 7710 %stl(t0, idx) 7711 7712 %addi(t1, a3, -97) ; 'a' 7713 %beqz(t1, &.spec_a) 7714 %addi(t1, a3, -115) ; 's' 7715 %beqz(t1, &.spec_s) 7716 %addi(t1, a3, -100) ; 'd' 7717 %beqz(t1, &.spec_d) 7718 %addi(t1, a3, -120) ; 'x' 7719 %beqz(t1, &.spec_x) 7720 %addi(t1, a3, -37) ; '%' 7721 %beqz(t1, &.spec_pct) 7722 %addi(t1, a3, -126) ; '~' 7723 %beqz(t1, &.spec_tilde) 7724 7725 # Unknown directive: emit `~` then the spec byte verbatim. Re-read 7726 # the spec byte from the template since str_putc may clobber a3. 7727 %ldl(a0, out) 7728 %li(a1, 126) 7729 %call(&str_putc) 7730 %ldl(t0, template) 7731 %heap_ld(t1, t0, %BV.data) 7732 %ldl(t0, idx) 7733 %addi(t0, t0, -1) 7734 %add(t1, t1, t0) 7735 %lb(a1, t1, 0) 7736 %ldl(a0, out) 7737 %call(&str_putc) 7738 %b(&.loop) 7739 7740 :.tilde_lit 7741 # `~` at end of template: emit literal `~` and finish next iter. 7742 %ldl(a0, out) 7743 %li(a1, 126) 7744 %call(&str_putc) 7745 %ldl(t0, idx) 7746 %addi(t0, t0, 1) 7747 %stl(t0, idx) 7748 %b(&.loop) 7749 7750 :.spec_a 7751 %ldl(t0, args) 7752 %car(a0, t0) 7753 %cdr(t0, t0) 7754 %stl(t0, args) 7755 %ldl(a1, out) 7756 %li(a2, 0) 7757 %call(&write_to_bv) 7758 %b(&.loop) 7759 7760 :.spec_s 7761 %ldl(t0, args) 7762 %car(a0, t0) 7763 %cdr(t0, t0) 7764 %stl(t0, args) 7765 %ldl(a1, out) 7766 %li(a2, 1) 7767 %call(&write_to_bv) 7768 %b(&.loop) 7769 7770 :.spec_d 7771 %ldl(t0, args) 7772 %car(t1, t0) 7773 %cdr(t0, t0) 7774 %stl(t0, args) 7775 %sari(a1, t1, 3) 7776 %ldl(a0, out) 7777 %call(&str_putint) 7778 %b(&.loop) 7779 7780 :.spec_x 7781 %ldl(t0, args) 7782 %car(t1, t0) 7783 %cdr(t0, t0) 7784 %stl(t0, args) 7785 %sari(a1, t1, 3) 7786 %ldl(a0, out) 7787 %call(&str_puthex) 7788 %b(&.loop) 7789 7790 :.spec_pct 7791 %ldl(a0, out) 7792 %li(a1, 10) 7793 %call(&str_putc) 7794 %b(&.loop) 7795 7796 :.spec_tilde 7797 %ldl(a0, out) 7798 %li(a1, 126) 7799 %call(&str_putc) 7800 %b(&.loop) 7801 7802 :.done 7803 %ldl(a0, out) 7804 }) 7805 7806 # ========================================================================= 7807 # Syscall primitives 7808 # ========================================================================= 7809 # 7810 # Each syscall primitive untags the args list, calls a thin libp1pp- or 7811 # scheme1-local syscall wrapper, and routes the raw return through 7812 # wrap_syscall_result: r >= 0 -> (#t . r), r < 0 -> (#f . -r). 7813 # 7814 # Bytevector args (paths, buffers) are passed by their raw data_ptr (slot 7815 # +5 from the tagged wrapper). For syscalls that read data_ptr as a C 7816 # string (paths, argv elements), the caller must produce the bv via the 7817 # str_* family so cap > length and data[length] == 0. Callers that only 7818 # expose the bv as a (data_ptr, count) pair (sys-read, sys-write buffers) 7819 # can pass plain bytevectors -- no NUL needed. 7820 7821 # wrap_syscall_result(raw=a0) -> (#t . r) or (#f . errno). 7822 7823 %fn2(wrap_syscall_result, {raw pad}, { 7824 %stl(a0, raw) 7825 %bltz(a0, &.err) 7826 %mkfix(a1, a0) 7827 %li(a0, %imm_val(%IMM.TRUE)) 7828 %tail(&cons) 7829 7830 :.err 7831 %ldl(t0, raw) 7832 %li(t1, 0) 7833 %sub(t0, t1, t0) 7834 %mkfix(a1, t0) 7835 %li(a0, %imm_val(%IMM.FALSE)) 7836 %tail(&cons) 7837 }) 7838 7839 # sys_openat(dirfd=a0, path=a1, flags=a2, mode=a3) -> r (a0). Leaf. 7840 :sys_openat 7841 %mov(t0, a3) 7842 %mov(a3, a2) 7843 %mov(a2, a1) 7844 %mov(a1, a0) 7845 %li(a0, %p1_sys_openat) 7846 %syscall 7847 %ret 7848 7849 # sys_clone() -> r (a0). Linux clone(SIGCHLD, 0, 0, 0, 0) -- fork-style. 7850 # Saves and restores s0 around the syscall because %p1_syscall reads s0 7851 # as the 5th OS-syscall argument. 7852 7853 %fn2(sys_clone, {saved_s0 pad}, { 7854 %stl(s0, saved_s0) 7855 %li(s0, 0) 7856 7857 %li(a1, 17) 7858 %li(a2, 0) 7859 %li(a3, 0) 7860 %li(t0, 0) 7861 %li(a0, %p1_sys_clone) 7862 %syscall 7863 7864 %ldl(s0, saved_s0) 7865 }) 7866 7867 # sys_execve(path=a0, argv=a1, envp=a2) -> -errno (a0). Only returns on 7868 # failure; on success the new image takes over. 7869 :sys_execve 7870 %mov(a3, a2) 7871 %mov(a2, a1) 7872 %mov(a1, a0) 7873 %li(a0, %p1_sys_execve) 7874 %syscall 7875 %ret 7876 7877 # sys_spawn(path=a0, argv=a1) -> r (a0). Atomic clone+execve, single 7878 # syscall: kernel saves parent state, swaps user pool with no copy, 7879 # loads the ELF, builds the user stack, and erets into the child. The 7880 # parent's spawn() returns child_pid only after the child exit_groups. 7881 # Provided by the seed kernel (private syscall 1024). On Linux this 7882 # number is unmapped so the kernel returns -ENOSYS, which the prelude 7883 # uses to detect environment and fall back to sys_clone+sys_execve. 7884 :sys_spawn 7885 %mov(a2, a1) 7886 %mov(a1, a0) 7887 %li(a0, %p1_sys_spawn) 7888 %syscall 7889 %ret 7890 7891 # sys_waitid(idtype=a0, id=a1, infop=a2, options=a3) -> r (a0). Leaf. 7892 :sys_waitid 7893 %mov(t0, a3) 7894 %mov(a3, a2) 7895 %mov(a2, a1) 7896 %mov(a1, a0) 7897 %li(a0, %p1_sys_waitid) 7898 %syscall 7899 # Canonicalize RV32's 4-byte-aligned siginfo child-status field to the 7900 # offset used by the shared Scheme prelude (24 on the 64-bit ABIs). 7901 %lb(t0, a3, %p1_waitid_status_off) 7902 %sb(t0, a3, 24) 7903 %ret 7904 7905 # build_execve_argv(list=a0) -> raw NULL-terminated array (a0). 7906 # Walks `list` (cons-list of bytevectors), allocates a native pointer array, 7907 # writes each bv's data_ptr, terminates with NULL. 7908 # 7909 # Locals: 7910 # list 7911 # count 7912 # array ptr (raw) 7913 %gcfn2(build_execve_argv, {list count array}, 1, 0, { 7914 %stl(a0, list) 7915 %call(&list_length) ; clobbers a0 -> count 7916 %stl(a0, count) 7917 7918 %addi(a0, a0, 1) 7919 %shli(a0, a0, %p1_word_shift) 7920 %call(&alloc_bytes) 7921 %stl(a0, array) 7922 7923 %ldl(t0, list) 7924 %ldl(t1, array) 7925 7926 :.fill_loop 7927 %if_nil(t2, t0, &.fill_done) 7928 %car(a3, t0) 7929 %heap_ld(a2, a3, %BV.data) 7930 %st(a2, t1, 0) 7931 %addi(t1, t1, %p1_word_bytes) 7932 %cdr(t0, t0) 7933 %b(&.fill_loop) 7934 7935 :.fill_done 7936 %li(t2, 0) 7937 %st(t2, t1, 0) 7938 7939 %ldl(a0, array) 7940 }) 7941 7942 # (sys-read fd buf offset count). Passes (buf.data_ptr + offset) to the 7943 # kernel; offset lets callers read into the middle of a bv without first 7944 # slicing/copying. 7945 %fn(prim_sys_read_entry, 0, { 7946 %args4(t0, t1, t2, a3, a0) 7947 %sari(t0, t0, 3) ; fd 7948 %heap_ld(t1, t1, %BV.data) ; buf data ptr 7949 %sari(t2, t2, 3) ; offset 7950 %add(t1, t1, t2) ; data_ptr + offset 7951 %sari(t2, a3, 3) ; count 7952 %mov(a0, t0) 7953 %mov(a1, t1) 7954 %mov(a2, t2) 7955 %call(&sys_read) 7956 %tail(&wrap_syscall_result) 7957 }) 7958 7959 # (sys-write fd buf offset count). Passes (buf.data_ptr + offset) to the 7960 # kernel; offset lets callers retry the unwritten tail of a partial 7961 # write without bytevector-copy. 7962 %fn(prim_sys_write_entry, 0, { 7963 %args4(t0, t1, t2, a3, a0) 7964 %sari(t0, t0, 3) ; fd 7965 %heap_ld(t1, t1, %BV.data) ; buf data ptr 7966 %sari(t2, t2, 3) ; offset 7967 %add(t1, t1, t2) ; data_ptr + offset 7968 %sari(t2, a3, 3) ; count 7969 %mov(a0, t0) 7970 %mov(a1, t1) 7971 %mov(a2, t2) 7972 %call(&sys_write) 7973 %tail(&wrap_syscall_result) 7974 }) 7975 7976 # (sys-close fd) 7977 %fn(prim_sys_close_entry, 0, { 7978 %car_fix(a0, a0) 7979 %call(&sys_close) 7980 %tail(&wrap_syscall_result) 7981 }) 7982 7983 # (sys-openat dirfd path flags mode) 7984 %fn(prim_sys_openat_entry, 0, { 7985 %args4(t0, t1, t2, a3, a0) 7986 %sari(t0, t0, 3) ; dirfd 7987 %heap_ld(t1, t1, %BV.data) ; path data_ptr 7988 %sari(t2, t2, 3) ; flags 7989 %sari(a3, a3, 3) ; mode 7990 %mov(a0, t0) 7991 %mov(a1, t1) 7992 %mov(a2, t2) 7993 %call(&sys_openat) 7994 %tail(&wrap_syscall_result) 7995 }) 7996 7997 # (sys-clone). Linux POSIX-style fork; only used as a fallback path on 7998 # Linux since the seed kernel doesn't implement clone (it offers 7999 # sys-spawn instead). 8000 %fn(prim_sys_clone_entry, 0, { 8001 %call(&sys_clone) 8002 %tail(&wrap_syscall_result) 8003 }) 8004 8005 # (sys-execve path argv-list) 8006 8007 %gcfn2(prim_sys_execve_entry, {path pad}, 1, 0, { 8008 %args2(t0, a0, a0) ; t0 = path bv, a0 = argv-list 8009 %stl(t0, path) 8010 %call(&build_execve_argv) 8011 %mov(a1, a0) 8012 %ldl(a0, path) 8013 %heap_ld(a0, a0, %BV.data) ; path data ptr 8014 %li(a2, 0) 8015 %call(&sys_execve) 8016 %gctail(&wrap_syscall_result) 8017 }) 8018 8019 # (sys-spawn path argv-list). Same calling convention as sys-execve, but 8020 # wraps the seed kernel's atomic spawn syscall: returns (#t . child-pid) 8021 # after the child has exit_grouped (the kernel suspends the parent for 8022 # the lifetime of the child), or (#f . -errno) on failure (notably 8023 # -ENOSYS=38 on Linux, which the prelude probes for at init time). 8024 %gcfn2(prim_sys_spawn_entry, {path pad}, 1, 0, { 8025 %args2(t0, a0, a0) ; t0 = path bv, a0 = argv-list 8026 %stl(t0, path) 8027 %call(&build_execve_argv) 8028 %mov(a1, a0) 8029 %ldl(a0, path) 8030 %heap_ld(a0, a0, %BV.data) ; path data ptr 8031 %call(&sys_spawn) 8032 %gctail(&wrap_syscall_result) 8033 }) 8034 8035 # (sys-waitid idtype id infop options) 8036 %fn(prim_sys_waitid_entry, 0, { 8037 %args4(t0, t1, t2, a3, a0) 8038 %sari(t0, t0, 3) ; idtype 8039 %sari(t1, t1, 3) ; id 8040 %heap_ld(t2, t2, %BV.data) ; infop bv data ptr 8041 %sari(a3, a3, 3) ; options 8042 %mov(a0, t0) 8043 %mov(a1, t1) 8044 %mov(a2, t2) 8045 %call(&sys_waitid) 8046 %tail(&wrap_syscall_result) 8047 }) 8048 8049 # (sys-argv) -> list of bytevectors. Walks saved_argv, strlen-ing each 8050 # NUL-terminated entry into a fresh bytevector and consing them in order 8051 # via the head/tail trick. 8052 # 8053 # Locals: 8054 # argv ptr (advancing one native pointer per iteration) 8055 # count remaining (decrementing from saved_argc) 8056 # head 8057 # tail 8058 # bv 8059 %gcfn2(prim_sys_argv_entry, {argv count head tail bv}, 28, 0, { 8060 %ld_global(t0, &saved_argv) 8061 %stl(t0, argv) 8062 %ld_global(t0, &saved_argc) 8063 %stl(t0, count) 8064 %li(t0, %imm_val(%IMM.NIL)) 8065 %stl(t0, head) 8066 %stl(t0, tail) 8067 8068 :.loop 8069 %ldl(t0, count) 8070 %beqz(t0, &.done) 8071 8072 # len = strlen(*argv) 8073 %ldl(t0, argv) 8074 %ld(a0, t0, 0) 8075 %call(&libp1pp__strlen) 8076 8077 # bv = str_alloc(len). argv entries flow into syscalls (sys-openat, 8078 # sys-execve) that read data_ptr as a C string, so the trailing NUL 8079 # is required. 8080 %call(&str_alloc) 8081 %stl(a0, bv) 8082 8083 # memcpy(bv.data_ptr, *argv, len-from-bv-hdr). 8084 %ldl(t0, bv) 8085 %heap_ld(a0, t0, %BV.data) 8086 %ldl(t1, argv) 8087 %ld(a1, t1, 0) 8088 %heap_ld(t1, t0, %BV.hdr) 8089 %shri(a2, t1, 8) 8090 %call(&memcpy) 8091 8092 # cell = cons(bv, NIL); append to list head/tail. 8093 %ldl(a0, bv) 8094 %li(a1, %imm_val(%IMM.NIL)) 8095 %call(&cons) 8096 8097 %ldl(t0, head) 8098 %if_nil(t1, t0, &.first) 8099 %ldl(t0, tail) 8100 %set_cdr(a0, t0) 8101 %stl(a0, tail) 8102 %b(&.advance) 8103 8104 :.first 8105 %stl(a0, head) 8106 %stl(a0, tail) 8107 8108 :.advance 8109 %ldl(t0, argv) 8110 %addi(t0, t0, %p1_word_bytes) 8111 %stl(t0, argv) 8112 %ldl(t0, count) 8113 %addi(t0, t0, -1) 8114 %stl(t0, count) 8115 %b(&.loop) 8116 8117 :.done 8118 %ldl(a0, head) 8119 }) 8120 8121 # (eof? x). The `eof` value itself is bound at startup in p1_main as a 8122 # direct global -> IMM.EOF, not via a primitive thunk. 8123 :prim_eofq_entry 8124 .scope 8125 %car(t0, a0) 8126 %li(t1, %imm_val(%IMM.EOF)) 8127 %li(a0, %imm_val(%IMM.FALSE)) 8128 %bne(t0, t1, &.end) 8129 %li(a0, %imm_val(%IMM.TRUE)) 8130 :.end 8131 %ret 8132 .endscope 8133 8134 # (heap-usage) -> tagged fixnum: currently allocated managed bytes, 8135 # including the 16-byte header of every live allocation. 8136 :prim_heap_usage_entry 8137 %ld_global(a0, &heap_allocated) 8138 %mkfix(a0, a0) 8139 %ret 8140 8141 # (target-word-bytes) / (target-word-bits) expose the active P1 data model 8142 # to portable Scheme-hosted tools such as cc.scm. Arguments are ignored, 8143 # matching the other zero-argument introspection primitives. 8144 :prim_target_word_bytes_entry 8145 %li(a0, %p1_word_bytes) 8146 %mkfix(a0, a0) 8147 %ret 8148 8149 :prim_target_word_bits_entry 8150 %li(a0, %p1_word_bits) 8151 %mkfix(a0, a0) 8152 %ret 8153 8154 # (collect-garbage) -> unspecified. The primitive's argument list is 8155 # intentionally ignored, so it does not retain otherwise unreachable data. 8156 %fn(prim_collect_garbage_entry, 0, { 8157 %call(&gc_collect) 8158 %li(a0, %imm_val(%IMM.UNSPEC)) 8159 }) 8160 8161 # Record introspection. Surfaces the unsafe %record-* helpers (heap 8162 # layout: [HDR.REC][td][f0..fN-1], field i at tagged + 13 + 8*i; 8163 # nfields lives at TD's offset 13 raw). All primitives below trust 8164 # their inputs -- no bounds check, no kind check on record-ref / 8165 # record-set! / record-td. 8166 8167 # (record? obj) -> bool. True iff obj is HEAP-tagged with HDR.REC. 8168 :prim_recordq_entry 8169 .scope 8170 %car(t0, a0) 8171 %li(a0, %imm_val(%IMM.FALSE)) 8172 %tagof(t1, t0) 8173 %li(t2, %TAG.HEAP) 8174 %bne(t1, t2, &.end) 8175 %hdr_type(t1, t0) 8176 %li(t2, %HDR.REC) 8177 %bne(t1, t2, &.end) 8178 %li(a0, %imm_val(%IMM.TRUE)) 8179 :.end 8180 %ret 8181 .endscope 8182 8183 # (record-td rec) -> td. Reads the TD slot from the record header. No 8184 # kind check; caller is expected to gate with record? if needed. 8185 :prim_record_td_entry 8186 %car(t0, a0) 8187 %heap_ld(a0, t0, %REC.td) 8188 %ret 8189 8190 # (record-ref rec idx) -> field value. idx is a tagged fixnum; since 8191 # tagged_fixnum = raw_idx * 8 (fixnum tag bits are 0), the byte offset 8192 # is exactly idx + 13 from the tagged record pointer. No bounds check. 8193 :prim_record_ref_entry 8194 %args2(t0, t1, a0) ; t0=rec, t1=idx (tagged fixnum = raw*8) 8195 %addi(t0, t0, 13) 8196 %add(t0, t0, t1) 8197 %ld(a0, t0, 0) 8198 %ret 8199 8200 # (record-set! rec idx val) -> unspec. In-place store at slot idx. 8201 :prim_record_set_bang_entry 8202 .scope 8203 %car(t0, a0) ; rec 8204 %cdr(a0, a0) 8205 %car(t1, a0) ; idx (tagged fixnum) 8206 %cdr(a0, a0) 8207 %car(t2, a0) ; val 8208 %addi(t0, t0, 13) 8209 %add(t0, t0, t1) 8210 %st(t2, t0, 0) 8211 %li(a0, %imm_val(%IMM.UNSPEC)) 8212 %ret 8213 .endscope 8214 8215 # (make-record/td td) -> fresh record allocated in the current heap. 8216 # Reads td.nfields, allocates 16 + nfields*8 bytes with HDR.REC, sets 8217 # the td slot, and zero-fills field slots to IMM.UNSPEC. Mirrors 8218 # eval_define_record_type's ctor allocation but driven by the TD's 8219 # nfields rather than a runtime args list. Used by deep-copy as a 8220 # pre-fill stand-in before recursive slot promotion. 8221 # 8222 # Locals: 8223 # td (the TD pointer; saved across alloc_hdr) 8224 # record (the new record pointer) 8225 %gcfn2(prim_make_record_td_entry, {td record}, 3, 0, { 8226 %car(t0, a0) ; td 8227 %stl(t0, td) 8228 8229 %heap_ld(a0, t0, %TD.nfields) ; raw nfields 8230 %shli(a0, a0, 3) ; nfields * 8 8231 %addi(a0, a0, 16) ; + REC header (hdr + td slot) 8232 %li(a1, %HDR.REC) 8233 %call(&alloc_hdr) 8234 %stl(a0, record) 8235 8236 %ldl(t0, td) 8237 %heap_st(t0, a0, %REC.td) 8238 8239 # Zero-fill field slots to IMM.UNSPEC. Cursor starts at first slot 8240 # (tagged + 13); count = nfields read again from the TD. 8241 %heap_ld(t0, t0, %TD.nfields) 8242 %addi(t1, a0, 13) 8243 %li(t2, %imm_val(%IMM.UNSPEC)) 8244 8245 :.fill_loop 8246 %beqz(t0, &.fill_done) 8247 %st(t2, t1, 0) 8248 %addi(t1, t1, 8) 8249 %addi(t0, t0, -1) 8250 %b(&.fill_loop) 8251 8252 :.fill_done 8253 %ldl(a0, record) 8254 }) 8255 8256 # (td-nfields td) -> tagged fixnum count of fields. 8257 :prim_td_nfields_entry 8258 %car(t0, a0) 8259 %heap_ld(a0, t0, %TD.nfields) ; raw count 8260 %mkfix(a0, a0) 8261 %ret 8262 8263 # (td-name td) -> symbol bound at define-record-type time. 8264 :prim_td_name_entry 8265 %car(t0, a0) 8266 %heap_ld(a0, t0, %TD.name) 8267 %ret 8268 8269 # Debug primitives. UNSAFE: peek-memory-u8 dereferences arbitrary addresses. 8270 # Intended for diagnosing heap-layout bugs from scheme1 user code; not 8271 # part of the surface contract. 8272 8273 # (tagged-value obj) -> fixnum. Returns the raw byte address of obj 8274 # with tag bits masked off, encoded as a tagged fixnum so format / 8275 # display can print it. Pass the result back into peek-memory-u8 to read raw 8276 # bytes. For non-pointer values (fixnums, immediates, syms) the masked 8277 # value is small but still encodable; the result is meaningful only for 8278 # heap-tagged inputs. 8279 :prim_tagged_value_entry 8280 %car(t0, a0) 8281 %li(t1, -8) 8282 %and(t0, t0, t1) 8283 %mkfix(a0, t0) 8284 %ret 8285 8286 # (peek-memory-u8 addr) -> fixnum. Reads one byte at the given raw byte 8287 # address (tagged fixnum input, untagged inside). UNSAFE: no bounds 8288 # check; a wild address segfaults the process. 8289 :prim_peek_memory_u8_entry 8290 %car(t0, a0) 8291 %sari(t0, t0, 3) 8292 %lb(a0, t0, 0) 8293 %mkfix(a0, a0) 8294 %ret 8295 8296 # (values . xs) -- multiple-values producer. Single-arg case returns the 8297 # arg unchanged so (values x) is interchangeable with x in any 1-value 8298 # context; 0 or 2+ args materialize an MV-pack. 8299 :prim_values_entry 8300 .scope 8301 %if_nil(t0, a0, &.pack) 8302 %cdr(t0, a0) 8303 %if_nil(t1, t0, &.single) 8304 :.pack 8305 %b(&list_to_mv) 8306 :.single 8307 %car(a0, a0) 8308 %ret 8309 .endscope 8310 8311 # (call-with-values producer consumer) -- apply producer to no args, then 8312 # normalize its result (via mv_to_list) and tail-apply the consumer to the 8313 # resulting argument list. 8314 # 8315 # Locals: 8316 # consumer (saved across apply(producer) and mv_to_list) 8317 %gcfn2(prim_call_with_values_entry, {consumer pad}, 1, 0, { 8318 %args2(t0, t1, a0) ; t0 = producer, t1 = consumer 8319 %stl(t1, consumer) 8320 8321 %mov(a0, t0) 8322 %li(a1, %imm_val(%IMM.NIL)) 8323 %call(&apply) 8324 8325 %call(&mv_to_list) 8326 8327 %mov(a1, a0) 8328 %ldl(a0, consumer) 8329 %gctail(&apply) 8330 }) 8331 8332 # ========================================================================= 8333 # Startup -- heap_init 8334 # ========================================================================= 8335 8336 # heap_init() -> none. Initializes the physical heap chain, free list, 8337 # accounting, and bounded exact-root stack. Leaf. 8338 :heap_init 8339 %ld_global(t0, &heap_buf_ptr) 8340 %alignup(t0, t0, 8, t1) 8341 %st_global(t0, &heap_base, t1) 8342 %st_global(t0, &heap_tail, t1) 8343 8344 %ld_global(t0, &heap_buf_ptr) 8345 %li(t1, %HEAP_CAP_BYTES) 8346 %add(t0, t0, t1) 8347 %st_global(t0, &heap_end, t1) 8348 8349 %li(t0, 0) 8350 %st_global(t0, &gc_free_list, t1) 8351 %st_global(t0, &gc_mark_worklist, t1) 8352 %st_global(t0, &heap_allocated, t1) 8353 8354 %ld_global(t0, &gc_root_buf_ptr) 8355 %st_global(t0, &gc_root_next, t1) 8356 %li(t1, (* %GC_ROOT_CAP_FRAMES %GC_ROOT_FRAME_BYTES)) 8357 %add(t0, t0, t1) 8358 %st_global(t0, &gc_root_end, t1) 8359 8360 %ret 8361 8362 # Sentinel: marks the boundary between executable text and rodata. 8363 # Read by scripts/disasm-elf.sh (via scripts/m1-symbols.py) to bound 8364 # disassembly so trailing strings don't decode as bogus instructions. 8365 :_text_end 8366 8367 .align 8 8368 8369 # Primitive surface names. 8370 :name_sys_exit %cstr8("sys-exit") 8371 :name_cons %cstr8("cons") 8372 :name_car %cstr8("car") 8373 :name_cdr %cstr8("cdr") 8374 :name_nullq %cstr8("null?") 8375 :name_pairq %cstr8("pair?") 8376 :name_stringq %cstr8("string?") 8377 :name_bytevectorq %cstr8("bytevector?") 8378 :name_bytesq %cstr8("bytes?") 8379 :name_set_car %cstr8("set-car!") 8380 :name_set_cdr %cstr8("set-cdr!") 8381 :name_length %cstr8("length") 8382 :name_list_ref %cstr8("list-ref") 8383 :name_assq %cstr8("assq") 8384 :name_assoc %cstr8("assoc") 8385 :name_reverse %cstr8("reverse") 8386 :name_str_to_sym %cstr8("string->symbol") 8387 :name_sym_to_str %cstr8("symbol->string") 8388 :name_num_to_str %cstr8("number->string") 8389 :name_str_to_num %cstr8("string->number") 8390 :name_bv_append %cstr8("bytevector-append") 8391 :name_booleanq %cstr8("boolean?") 8392 :name_integerq %cstr8("integer?") 8393 :name_charq %cstr8("char?") 8394 :name_char_to_integer %cstr8("char->integer") 8395 :name_integer_to_char %cstr8("integer->char") 8396 :name_symbolq %cstr8("symbol?") 8397 :name_procedureq %cstr8("procedure?") 8398 :name_zeroq %cstr8("zero?") 8399 :name_not %cstr8("not") 8400 :name_eqq %cstr8("eq?") 8401 :name_equal %cstr8("equal?") 8402 :name_plus %cstr8("+") 8403 :name_minus %cstr8("-") 8404 :name_mult %cstr8("*") 8405 :name_eq %cstr8("=") 8406 :name_lt %cstr8("<") 8407 :name_gt %cstr8(">") 8408 :name_quotient %cstr8("quotient") 8409 :name_remainder %cstr8("remainder") 8410 :name_bit_and %cstr8("bit-and") 8411 :name_bit_or %cstr8("bit-or") 8412 :name_bit_xor %cstr8("bit-xor") 8413 :name_bit_not %cstr8("bit-not") 8414 :name_arith_shift %cstr8("arithmetic-shift") 8415 :name_apply %cstr8("apply") 8416 :name_make_bv %cstr8("make-bytevector") 8417 :name_make_string %cstr8("make-string") 8418 :name_bv_length %cstr8("bytevector-length") 8419 :name_string_length %cstr8("string-length") 8420 :name_string_ref %cstr8("string-ref") 8421 :name_string_set %cstr8("string-set!") 8422 :name_bv_u8_ref %cstr8("bytevector-u8-ref") 8423 :name_bv_u8_set %cstr8("bytevector-u8-set!") 8424 :name_bv_copy %cstr8("bytevector-copy") 8425 :name_bv_copy_b %cstr8("bytevector-copy!") 8426 :name_bv_eq %cstr8("bytevector=?") 8427 :name_bytes_eq %cstr8("bytes=?") 8428 :name_make_hash %cstr8("%make-hash-table") 8429 :name_hash_ref %cstr8("%hash-ref") 8430 :name_hash_set %cstr8("%hash-set!") 8431 :name_hash_delete %cstr8("%hash-delete!") 8432 :name_hash_size %cstr8("%hash-size") 8433 8434 :name_sys_read %cstr8("sys-read") 8435 :name_sys_write %cstr8("sys-write") 8436 :name_sys_close %cstr8("sys-close") 8437 :name_sys_openat %cstr8("sys-openat") 8438 :name_sys_clone %cstr8("sys-clone") 8439 :name_sys_execve %cstr8("sys-execve") 8440 :name_sys_spawn %cstr8("sys-spawn") 8441 :name_sys_waitid %cstr8("sys-waitid") 8442 :name_sys_argv %cstr8("sys-argv") 8443 :name_eof %cstr8("eof") 8444 :name_eofq %cstr8("eof?") 8445 :name_values %cstr8("values") 8446 :name_call_with_values %cstr8("call-with-values") 8447 :name_display %cstr8("display") 8448 :name_write %cstr8("write") 8449 :name_error %cstr8("error") 8450 :name_format %cstr8("format") 8451 :name_heap_usage %cstr8("heap-usage") 8452 :name_target_word_bytes %cstr8("target-word-bytes") 8453 :name_target_word_bits %cstr8("target-word-bits") 8454 :name_collect_garbage %cstr8("collect-garbage") 8455 :name_recordq %cstr8("record?") 8456 :name_record_td %cstr8("record-td") 8457 :name_record_ref %cstr8("record-ref") 8458 :name_record_set_bang %cstr8("record-set!") 8459 :name_make_record_td %cstr8("make-record/td") 8460 :name_td_nfields %cstr8("td-nfields") 8461 :name_td_name %cstr8("td-name") 8462 :name_tagged_value %cstr8("tagged-value") 8463 :name_peek_memory_u8 %cstr8("peek-memory-u8") 8464 8465 # Writer string constants. Lengths are hard-coded at the str_putn call 8466 # sites (write_to_bv branches). They are emitted through cstr8 so the 8467 # labels remain aligned and are also safe as C strings if reused later. 8468 :str_false %cstr8("#f") 8469 :str_true %cstr8("#t") 8470 :str_nil %cstr8("()") 8471 :str_unspec %cstr8("#!unspec") 8472 :str_unbound %cstr8("#!unbound") 8473 :str_eof %cstr8("#!eof") 8474 :str_closure %cstr8("#<closure>") 8475 :str_prim %cstr8("#<prim>") 8476 :str_td %cstr8("#<rec-type>") 8477 :str_rec %cstr8("#<record>") 8478 :str_unknown %cstr8("#<unknown>") 8479 :str_error_prefix %cstr8("scheme1: error: ") 8480 :str_u8_open %cstr8("#u8(") 8481 :str_char_open %cstr8("#\\") 8482 8483 # Primitive registration table. Each entry: 8-byte name_ptr (4-byte label 8484 # ref + 4 pad), 8-byte name_len, 8-byte entry_label (4 ref + 4 pad). 8485 :prim_table 8486 &name_sys_exit %(0) $(8) &prim_sys_exit_entry %(0) 8487 &name_cons %(0) $(4) &prim_cons_entry %(0) 8488 &name_car %(0) $(3) &prim_car_entry %(0) 8489 &name_cdr %(0) $(3) &prim_cdr_entry %(0) 8490 &name_nullq %(0) $(5) &prim_nullq_entry %(0) 8491 &name_pairq %(0) $(5) &prim_pairq_entry %(0) 8492 &name_stringq %(0) $(7) &prim_stringq_entry %(0) 8493 &name_bytevectorq %(0) $(11) &prim_bytevectorq_entry %(0) 8494 &name_bytesq %(0) $(6) &prim_bytesq_entry %(0) 8495 &name_set_car %(0) $(8) &prim_set_car_entry %(0) 8496 &name_set_cdr %(0) $(8) &prim_set_cdr_entry %(0) 8497 &name_length %(0) $(6) &prim_length_entry %(0) 8498 &name_list_ref %(0) $(8) &prim_list_ref_entry %(0) 8499 &name_assq %(0) $(4) &prim_assq_entry %(0) 8500 &name_assoc %(0) $(5) &prim_assoc_entry %(0) 8501 &name_reverse %(0) $(7) &prim_reverse_entry %(0) 8502 &name_str_to_sym %(0) $(14) &prim_string_to_symbol_entry %(0) 8503 &name_sym_to_str %(0) $(14) &prim_symbol_to_string_entry %(0) 8504 &name_num_to_str %(0) $(14) &prim_number_to_string_entry %(0) 8505 &name_str_to_num %(0) $(14) &prim_string_to_number_entry %(0) 8506 &name_bv_append %(0) $(17) &prim_bv_append_entry %(0) 8507 &name_booleanq %(0) $(8) &prim_booleanq_entry %(0) 8508 &name_integerq %(0) $(8) &prim_integerq_entry %(0) 8509 &name_charq %(0) $(5) &prim_charq_entry %(0) 8510 &name_char_to_integer %(0) $(13) &prim_char_to_integer_entry %(0) 8511 &name_integer_to_char %(0) $(13) &prim_integer_to_char_entry %(0) 8512 &name_symbolq %(0) $(7) &prim_symbolq_entry %(0) 8513 &name_procedureq %(0) $(10) &prim_procedureq_entry %(0) 8514 &name_zeroq %(0) $(5) &prim_zeroq_entry %(0) 8515 &name_not %(0) $(3) &prim_not_entry %(0) 8516 &name_eqq %(0) $(3) &prim_eqq_entry %(0) 8517 &name_equal %(0) $(6) &prim_equal_entry %(0) 8518 &name_plus %(0) $(1) &prim_plus_entry %(0) 8519 &name_minus %(0) $(1) &prim_minus_entry %(0) 8520 &name_mult %(0) $(1) &prim_mult_entry %(0) 8521 &name_eq %(0) $(1) &prim_eq_entry %(0) 8522 &name_lt %(0) $(1) &prim_lt_entry %(0) 8523 &name_gt %(0) $(1) &prim_gt_entry %(0) 8524 &name_quotient %(0) $(8) &prim_quotient_entry %(0) 8525 &name_remainder %(0) $(9) &prim_remainder_entry %(0) 8526 &name_bit_and %(0) $(7) &prim_bit_and_entry %(0) 8527 &name_bit_or %(0) $(6) &prim_bit_or_entry %(0) 8528 &name_bit_xor %(0) $(7) &prim_bit_xor_entry %(0) 8529 &name_bit_not %(0) $(7) &prim_bit_not_entry %(0) 8530 &name_arith_shift %(0) $(16) &prim_arith_shift_entry %(0) 8531 &name_apply %(0) $(5) &prim_apply_entry %(0) 8532 &name_make_bv %(0) $(15) &prim_make_bytevector_entry %(0) 8533 &name_make_string %(0) $(11) &prim_make_string_entry %(0) 8534 &name_bv_length %(0) $(17) &prim_bv_length_entry %(0) 8535 &name_string_length %(0) $(13) &prim_string_length_entry %(0) 8536 &name_string_ref %(0) $(10) &prim_string_ref_entry %(0) 8537 &name_string_set %(0) $(11) &prim_string_set_entry %(0) 8538 &name_bv_u8_ref %(0) $(17) &prim_bv_u8_ref_entry %(0) 8539 &name_bv_u8_set %(0) $(18) &prim_bv_u8_set_entry %(0) 8540 &name_bv_copy %(0) $(15) &prim_bv_copy_entry %(0) 8541 &name_bv_copy_b %(0) $(16) &prim_bv_copy_bang_entry %(0) 8542 &name_bv_eq %(0) $(12) &prim_bytevector_eq_entry %(0) 8543 &name_bytes_eq %(0) $(7) &prim_bytevector_eq_entry %(0) 8544 &name_make_hash %(0) $(16) &prim_make_hash_entry %(0) 8545 &name_hash_ref %(0) $(9) &prim_hash_ref_entry %(0) 8546 &name_hash_set %(0) $(10) &prim_hash_set_entry %(0) 8547 &name_hash_delete %(0) $(13) &prim_hash_delete_entry %(0) 8548 &name_hash_size %(0) $(10) &prim_hash_size_entry %(0) 8549 &name_sys_read %(0) $(8) &prim_sys_read_entry %(0) 8550 &name_sys_write %(0) $(9) &prim_sys_write_entry %(0) 8551 &name_sys_close %(0) $(9) &prim_sys_close_entry %(0) 8552 &name_sys_openat %(0) $(10) &prim_sys_openat_entry %(0) 8553 &name_sys_clone %(0) $(9) &prim_sys_clone_entry %(0) 8554 &name_sys_execve %(0) $(10) &prim_sys_execve_entry %(0) 8555 &name_sys_spawn %(0) $(9) &prim_sys_spawn_entry %(0) 8556 &name_sys_waitid %(0) $(10) &prim_sys_waitid_entry %(0) 8557 &name_sys_argv %(0) $(8) &prim_sys_argv_entry %(0) 8558 &name_eofq %(0) $(4) &prim_eofq_entry %(0) 8559 &name_display %(0) $(7) &prim_display_entry %(0) 8560 &name_write %(0) $(5) &prim_write_entry %(0) 8561 &name_error %(0) $(5) &prim_error_entry %(0) 8562 &name_format %(0) $(6) &prim_format_entry %(0) 8563 &name_heap_usage %(0) $(10) &prim_heap_usage_entry %(0) 8564 &name_target_word_bytes %(0) $(17) &prim_target_word_bytes_entry %(0) 8565 &name_target_word_bits %(0) $(16) &prim_target_word_bits_entry %(0) 8566 &name_collect_garbage %(0) $(15) &prim_collect_garbage_entry %(0) 8567 &name_recordq %(0) $(7) &prim_recordq_entry %(0) 8568 &name_record_td %(0) $(9) &prim_record_td_entry %(0) 8569 &name_record_ref %(0) $(10) &prim_record_ref_entry %(0) 8570 &name_record_set_bang %(0) $(11) &prim_record_set_bang_entry %(0) 8571 &name_make_record_td %(0) $(14) &prim_make_record_td_entry %(0) 8572 &name_td_nfields %(0) $(10) &prim_td_nfields_entry %(0) 8573 &name_td_name %(0) $(7) &prim_td_name_entry %(0) 8574 &name_tagged_value %(0) $(12) &prim_tagged_value_entry %(0) 8575 &name_peek_memory_u8 %(0) $(14) &prim_peek_memory_u8_entry %(0) 8576 &name_values %(0) $(6) &prim_values_entry %(0) 8577 &name_call_with_values %(0) $(16) &prim_call_with_values_entry %(0) 8578 :prim_table_end 8579 8580 ;; Error messages are NUL-terminated C strings. The embedded newline 8581 ;; keeps the old stderr formatting; runtime_error's panic path appends 8582 ;; another newline, which shell command substitution trims in tests. 8583 :msg_usage %cstr8("scheme1: usage: scheme1 SOURCE.scm\n") 8584 :msg_load_fail %cstr8("scheme1: failed to read source\n") 8585 :msg_symtab_full %cstr8("scheme1: symbol table full\n") 8586 :msg_unexp_rparen %cstr8("scheme1: unexpected ')'\n") 8587 :msg_bad_hash %cstr8("scheme1: bad #-syntax\n") 8588 :msg_unexp_eof %cstr8("scheme1: unexpected EOF in form\n") 8589 :msg_unterm_list %cstr8("scheme1: unterminated list\n") 8590 :msg_unbound %cstr8("scheme1: unbound variable\n") 8591 :msg_unbound_set %cstr8("scheme1: set!: unbound variable\n") 8592 :msg_not_proc %cstr8("scheme1: not a procedure\n") 8593 :msg_type %cstr8("scheme1: type error\n") 8594 :msg_arity %cstr8("scheme1: wrong number of arguments\n") 8595 :msg_heap_full %cstr8("scheme1: heap exhausted\n") 8596 :msg_heap_corrupt %cstr8("scheme1: corrupt managed heap\n") 8597 :msg_gc_roots_full %cstr8("scheme1: shadow root stack overflow\n") 8598 :msg_readbuf_full %cstr8("scheme1: source buffer overflow\n") 8599 :msg_bv_oob %cstr8("scheme1: bytevector index out of range\n") 8600 :msg_string_oob %cstr8("scheme1: string index out of range\n") 8601 :msg_bad_byte %cstr8("scheme1: byte value out of range\n") 8602 :msg_unterm_string %cstr8("scheme1: unterminated string literal\n") 8603 :msg_bad_escape %cstr8("scheme1: bad string escape\n") 8604 :msg_bad_char %cstr8("scheme1: bad #\\ character literal\n") 8605 :msg_bad_number %cstr8("scheme1: bad number literal\n") 8606 :msg_bad_radix %cstr8("scheme1: unsupported radix\n") 8607 :msg_integer_overflow %cstr8("scheme1: exact integer overflow\n") 8608 :msg_divide_zero %cstr8("scheme1: division by zero\n") 8609 :msg_bad_ident %cstr8("scheme1: bad identifier\n") 8610 :msg_unsupported_syntax %cstr8("scheme1: unsupported reader syntax\n") 8611 :msg_internal_define %cstr8("scheme1: internal define is not supported\n") 8612 :msg_cond_else_not_final %cstr8("scheme1: cond: else clause is not final\n") 8613 :msg_record_ctor_spec %cstr8("scheme1: unsupported record constructor specification\n") 8614 :msg_record_ctor_arity %cstr8("scheme1: record constructor arity mismatch\n") 8615 :msg_pmatch_no_match %cstr8("scheme1: pmatch: no clause matched\n") 8616 :msg_bad_unquote_pattern %cstr8("scheme1: pmatch: malformed ,-pattern\n") 8617 :msg_hash_full %cstr8("scheme1: hash table probe exhausted\n") 8618 8619 :name_ch_tab %cstr8("tab") 8620 :name_ch_null %cstr8("null") 8621 :name_ch_space %cstr8("space") 8622 :name_ch_return %cstr8("return") 8623 :name_ch_newline %cstr8("newline") 8624 8625 # ========================================================================= 8626 # BSS arena table 8627 # ========================================================================= 8628 # 8629 # (slot, size) rows for libp1pp's init_arenas, walked once at startup. 8630 # init_arenas threads a running offset, so each arena starts where the 8631 # previous one ended. 8632 :arena_table 8633 %arena_entry(&readbuf_buf_ptr, %READBUF_CAP_BYTES) 8634 %arena_entry(&symtab_buf_ptr, (* %SYMTAB_CAP_SLOTS %SYMENT.SIZE)) 8635 %arena_entry(&gc_root_buf_ptr, (* %GC_ROOT_CAP_FRAMES %GC_ROOT_FRAME_BYTES)) 8636 %arena_entry(&heap_buf_ptr, %HEAP_CAP_BYTES) 8637 :arena_table_end 8638 8639 # ========================================================================= 8640 # Scalar BSS (file-resident, zero-initialized) 8641 # ========================================================================= 8642 8643 # Managed-heap physical chain and accounting. 8644 :heap_base $(0) 8645 :heap_tail $(0) 8646 :heap_end $(0) 8647 :heap_allocated $(0) 8648 :gc_free_list $(0) 8649 :gc_mark_worklist $(0) 8650 8651 # Exact shadow-root frame stack. 8652 :gc_root_next $(0) 8653 :gc_root_end $(0) 8654 8655 # Source-buffer cursor and slurped length. 8656 :readbuf_pos $(0) 8657 :readbuf_len $(0) 8658 8659 # Symbol table count (number of entries used). 8660 :symtab_count $(0) 8661 8662 # Cached tagged-symbol values for special forms (filled by 8663 # intern_special_forms at startup). 8664 :sym_quote $(0) 8665 :sym_if $(0) 8666 :sym_lambda $(0) 8667 :sym_define $(0) 8668 :sym_begin $(0) 8669 :sym_cond $(0) 8670 :sym_else $(0) 8671 :sym_arrow $(0) 8672 :sym_let $(0) 8673 :sym_letstar $(0) 8674 :sym_let_values $(0) 8675 :sym_letstar_values $(0) 8676 :sym_and $(0) 8677 :sym_or $(0) 8678 :sym_when $(0) 8679 :sym_case $(0) 8680 :sym_setbang $(0) 8681 :sym_define_record_type $(0) 8682 :sym_pmatch $(0) 8683 :sym_do $(0) 8684 :sym_unquote $(0) 8685 :sym_guard $(0) 8686 :sym_underscore $(0) 8687 :sym_dollar $(0) 8688 8689 # Process startup state, captured by p1_main and read by sys-argv. 8690 :saved_argc $(0) 8691 :saved_argv $(0) 8692 8693 # Scratch buffer for bv_putint / str_putint -> fmt_dec. fmt_dec writes 8694 # at most 20 bytes for a 64-bit signed integer; 24 bytes (three words) 8695 # is comfortable room and keeps following slots word-aligned. 8696 :writer_num_buf $(0) $(0) $(0) 8697 8698 # Pointer slots for the past-:ELF_end arenas. 8699 :readbuf_buf_ptr $(0) 8700 :heap_buf_ptr $(0) 8701 :symtab_buf_ptr $(0) 8702 :gc_root_buf_ptr $(0) 8703 8704 :ELF_end