scheme1.P1pp (19854B)
1 # scheme1.P1pp -- Phase 1 minimal Scheme interpreter on P1. 2 # 3 # The full target is described in docs/LISP-C.md and docs/LISP.md. This 4 # file is the spine: enough infrastructure to read a single source file, 5 # parse one s-expression, and evaluate it via tag-dispatched eval/apply 6 # with a single primitive (`sys-exit`). Every later piece (more 7 # primitives, special forms, closures, pmatch, records, prelude, repl 8 # loop) hooks onto these anchors without restructuring. 9 # 10 # What's wired up now: 11 # - Tag layout per LISP-C.md: fixnums, pairs, symbols, headered objects, 12 # immediate singletons. mkimm composes IMM constants at M1pp time. 13 # - Bump heap allocator (cons + alloc_hdr) over a BSS-past-ELF_end arena. 14 # - Linear-scan symbol intern table; entries store (name_ptr, name_len, 15 # global_val) borrowed directly from readbuf. 16 # - Reader: '(', ')', fixnums (decimal, optional leading `-`), bare 17 # symbols. No strings/hex/chars/quote/dotted yet, no `;` comments. 18 # - eval: tag dispatch -> self-eval / symbol lookup / pair application. 19 # - apply: HDR.PRIM dispatch only; closures/specials come later. 20 # - One primitive: sys-exit. Exits with the raw fixnum. 21 # 22 # Build chain (P1pp.P1pp = libp1pp must be in the catm sequence): 23 # catm P1-<arch>.M1pp P1.M1pp P1pp.P1pp scheme1/scheme1.P1pp \ 24 # | m1pp -> M0 -> hex2 -> ELF 25 # 26 # Memory model: the ELF's ph_memsz is 8 MB (boot2 default), so all 27 # zero-initialized arenas live past :ELF_end and cost zero file bytes. 28 # p1_main writes their absolute addresses into pointer slots once at 29 # startup; every later access goes through one extra load. 30 31 # ========================================================================= 32 # Constants 33 # ========================================================================= 34 35 %enum TAG { FIXNUM PAIR SYM HEAP IMM } 36 %enum IMM { FALSE TRUE NIL UNSPEC UNBOUND } 37 %enum HDR { BV CLOSURE PRIM TD REC } 38 39 # imm_val(idx) -> integer-expression for the tagged immediate at IMM index 40 # `idx`. Used both at %li sites (loaded into a register) and at $() emission 41 # sites (baked into a static word). 42 %macro imm_val(idx) 43 (| (<< idx 3) %TAG.IMM) 44 %endm 45 46 # Layout helpers. %struct stride is 8 bytes per field. 47 %struct PAIR { car cdr } # .SIZE = 16 48 %struct SYMENT { name_ptr name_len global_val pad } # .SIZE = 32 49 %struct PRIM { hdr entry_w } # .SIZE = 16 50 51 # BSS arena offsets from :ELF_end. Each arena is 64 KiB; the three are 52 # packed back-to-back below ELF_end + 192 KiB. p1_main's startup loop 53 # materializes &ELF_end + OFF_X into the matching pointer slot. The 54 # offsets are emitted directly in bss_init_tbl via $(). 55 56 %macro SYMTAB_CAP_SLOTS() 57 1024 58 %endm 59 60 %macro READBUF_CAP_BYTES() 61 65536 62 %endm 63 64 # ========================================================================= 65 # Tag idioms 66 # ========================================================================= 67 68 %macro tagof(rd, rs) 69 %andi(rd, rs, 7) 70 %endm 71 72 %macro mkfix(rd, rs) 73 %shli(rd, rs, 3) 74 %endm 75 76 %macro untag_fix(rd, rs) 77 %sari(rd, rs, 3) 78 %endm 79 80 %macro untag_sym(rd, rs) 81 %sari(rd, rs, 3) 82 %endm 83 84 %macro car(rd, rs) 85 %ld(rd, rs, -1) 86 %endm 87 88 %macro cdr(rd, rs) 89 %ld(rd, rs, 7) 90 %endm 91 92 %macro hdr_type(rd, rs) 93 %lb(rd, rs, -3) 94 %endm 95 96 # ========================================================================= 97 # Scheme1-local helpers 98 # ========================================================================= 99 100 # Load the byte at readbuf_buf[off_reg] into rd. Clobbers rd. `rd` must 101 # be the destination register; the macro reuses it as a scratch pointer 102 # during the la / ld / add chain before the final lb writes the byte. 103 %macro readbuf_byte(rd, off_reg) 104 %la(rd, &readbuf_buf_ptr) 105 %ld(rd, rd, 0) 106 %add(rd, rd, off_reg) 107 %lb(rd, rd, 0) 108 %endm 109 110 # Branch to `target` if `ch_reg` holds an ASCII whitespace byte (space, 111 # tab, LF, CR). `scratch` is clobbered. 112 %macro is_ws_branch(scratch, ch_reg, target) 113 %addi(scratch, ch_reg, -32) 114 %beqz(scratch, target) 115 %addi(scratch, ch_reg, -9) 116 %beqz(scratch, target) 117 %addi(scratch, ch_reg, -10) 118 %beqz(scratch, target) 119 %addi(scratch, ch_reg, -13) 120 %beqz(scratch, target) 121 %endm 122 123 # Compute &symtab_buf + idx_reg * SYMENT.SIZE into rd. `scratch` is 124 # clobbered. 125 %macro symtab_entry(rd, idx_reg, scratch) 126 %la(rd, &symtab_buf_ptr) 127 %ld(rd, rd, 0) 128 %shli(scratch, idx_reg, 5) 129 %add(rd, rd, scratch) 130 %endm 131 132 # Print msg_label, exit with `code`. Never returns. 133 %macro die(msg, code) 134 %la(a0, & ## msg) 135 %call(&print_cstr) 136 %li(a0, code) 137 %call(&sys_exit) 138 %endm 139 140 # ========================================================================= 141 # p1_main -- runtime spine 142 # ========================================================================= 143 # 144 # Frame layout (16 bytes): 145 # +0 saved argv 146 147 %fn(p1_main, 16, { 148 %st(a1, sp, 0) 149 150 %li(t0, 2) 151 %bltu(a0, t0, &::usage) 152 153 # Initialize BSS pointer slots from ELF_end + OFF_*. Same idiom as 154 # M1pp.P1: a tiny init table walked once. 155 %la(t0, &ELF_end) 156 %la(t1, &bss_init_tbl) 157 %la(t2, &bss_init_tbl_end) 158 ::bss_loop 159 %beq(t1, t2, &::bss_done) 160 %ld(a0, t1, 0) 161 %ld(a2, t1, 8) 162 %add(a2, a2, t0) 163 %st(a2, a0, 0) 164 %addi(t1, t1, 16) 165 %b(&::bss_loop) 166 ::bss_done 167 168 # heap_next = &heap_buf, rounded up to 8-byte alignment. The BSS arena 169 # starts at &ELF_end + OFF_heap, but &ELF_end's alignment depends on 170 # the data section above it; cons assumes 8-byte-aligned heap_next so 171 # every pair pointer's low 3 bits are exactly the PAIR tag. 172 %la(t0, &heap_buf_ptr) 173 %ld(t0, t0, 0) 174 %alignup(t0, t0, 8, t1) 175 %la(t1, &heap_next) 176 %st(t0, t1, 0) 177 178 # Bind built-in primitives. 179 %call(®ister_primitives) 180 181 # argv[1] is the source path (NUL-terminated cstr from the kernel). 182 %ld(a1, sp, 0) 183 %ld(a0, a1, 8) 184 %call(&load_source) 185 186 # parse_one returns one s-expression in a0. 187 %call(&parse_one) 188 189 # eval(expr, NIL). 190 %li(a1, %imm_val(%IMM.NIL)) 191 %call(&eval) 192 193 # If the form did not sys-exit (e.g. evaluated to a fixnum), drop 194 # cleanly with status 0. Useful while wiring up more of the runtime. 195 %li(a0, 0) 196 %eret 197 198 ::usage 199 %la(a0, &msg_usage) 200 %call(&print_cstr) 201 %li(a0, 2) 202 }) 203 204 # ========================================================================= 205 # Source loading -- argv[1] -> readbuf, length stored in readbuf_len 206 # ========================================================================= 207 208 %fn(load_source, 0, { 209 %la(t0, &readbuf_buf_ptr) 210 %ld(a1, t0, 0) 211 %li(a2, %READBUF_CAP_BYTES) 212 %call(&read_file) 213 %bltz(a0, &::fail) 214 215 %la(t0, &readbuf_len) 216 %st(a0, t0, 0) 217 %li(a0, 0) 218 %eret 219 220 ::fail 221 %die(msg_load_fail, 3) 222 }) 223 224 # ========================================================================= 225 # Heap: cons (leaf) and alloc_hdr (leaf) 226 # ========================================================================= 227 # 228 # Both are call-free leaves: bump heap_next, write fields, return tagged 229 # pointer. Heap exhaustion crashes via a deliberate read past the arena 230 # end -- runtime check arrives when error reporting does (LISP-C.md 231 # milestone 3). 232 233 # cons(car=a0, cdr=a1) -> tagged pair (a0) 234 :cons 235 %la(t2, &heap_next) 236 %ld(t0, t2, 0) 237 %st(a0, t0, 0) 238 %st(a1, t0, 8) 239 %addi(t1, t0, 16) 240 %st(t1, t2, 0) 241 %addi(a0, t0, 1) 242 %ret 243 244 # alloc_hdr(bytes=a0, hdr_word=a1) -> tagged heap obj (a0) 245 # Rounds bytes up to a multiple of 8 and writes hdr_word at offset 0. 246 :alloc_hdr 247 %alignup(a0, a0, 8, t0) 248 %la(t2, &heap_next) 249 %ld(t0, t2, 0) 250 %add(t1, t0, a0) 251 %st(t1, t2, 0) 252 %st(a1, t0, 0) 253 %addi(a0, t0, 3) 254 %ret 255 256 # ========================================================================= 257 # Symbol intern -- linear scan, append on miss 258 # ========================================================================= 259 # 260 # Frame: 32 bytes 261 # +0 name_ptr (input) 262 # +8 name_len (input) 263 # +16 idx (loop counter / found index) 264 # +24 entry_ptr (spilled across memcmp) 265 266 %fn(intern, 32, { 267 %st(a0, sp, 0) 268 %st(a1, sp, 8) 269 270 %li(t0, 0) 271 %st(t0, sp, 16) 272 273 ::scan 274 # idx >= count? -> append 275 %ld(t0, sp, 16) 276 %la(t1, &symtab_count) 277 %ld(t1, t1, 0) 278 %bltu(t0, t1, &::probe) 279 %b(&::append) 280 281 ::probe 282 %symtab_entry(t1, t0, t2) 283 %st(t1, sp, 24) 284 285 # entry.name_len == name_len ? 286 %ld(t2, t1, 8) 287 %ld(a2, sp, 8) 288 %bne(t2, a2, &::next) 289 290 # memcmp(entry.name_ptr, name_ptr, len) 291 %ld(a0, t1, 0) 292 %ld(a1, sp, 0) 293 %ld(a2, sp, 8) 294 %call(&memcmp) 295 %beqz(a0, &::found) 296 297 ::next 298 %ld(t0, sp, 16) 299 %addi(t0, t0, 1) 300 %st(t0, sp, 16) 301 %b(&::scan) 302 303 ::append 304 # Bounds check; on overflow exit 5 with a message. 305 %ld(t0, sp, 16) 306 %li(t1, %SYMTAB_CAP_SLOTS) 307 %bltu(t0, t1, &::append_ok) 308 %die(msg_symtab_full, 5) 309 310 ::append_ok 311 %symtab_entry(t1, t0, t2) 312 %ld(a0, sp, 0) 313 %st(a0, t1, 0) 314 %ld(a0, sp, 8) 315 %st(a0, t1, 8) 316 %li(a0, %imm_val(%IMM.UNBOUND)) 317 %st(a0, t1, 16) 318 %li(a0, 0) 319 %st(a0, t1, 24) 320 321 # symtab_count = idx + 1 322 %addi(a0, t0, 1) 323 %la(t2, &symtab_count) 324 %st(a0, t2, 0) 325 326 # fall through with idx in t0 = sp[16] 327 328 ::found 329 %ld(t0, sp, 16) 330 %shli(a0, t0, 3) 331 %ori(a0, a0, %TAG.SYM) 332 }) 333 334 # Lookup by sym_idx (untagged, in a0). Returns symtab[idx].global_val in a0. 335 # Leaf. 336 :sym_global 337 %la(t0, &symtab_buf_ptr) 338 %ld(t0, t0, 0) 339 %shli(t1, a0, 5) 340 %add(t0, t0, t1) 341 %ld(a0, t0, 16) 342 %ret 343 344 # sym_set_global(idx=a0, val=a1). Leaf. 345 :sym_set_global 346 %la(t0, &symtab_buf_ptr) 347 %ld(t0, t0, 0) 348 %shli(t1, a0, 5) 349 %add(t0, t0, t1) 350 %st(a1, t0, 16) 351 %ret 352 353 # ========================================================================= 354 # Reader -- parse_one over readbuf with a single byte cursor 355 # ========================================================================= 356 # 357 # Cursor lives in &readbuf_pos; readbuf_len holds the slurped byte count. 358 # The reader is called recursively from parse_list, so every state goes 359 # through frame slots, not s-registers. 360 361 # Skip whitespace (ASCII 32, 9, 10, 13). Leaf. 362 :skip_ws 363 %scope skip_ws 364 %la(t2, &readbuf_pos) 365 %ld(t0, t2, 0) 366 %la(t1, &readbuf_len) 367 %ld(t1, t1, 0) 368 ::loop 369 %beq(t0, t1, &::done) 370 %readbuf_byte(a0, t0) 371 %is_ws_branch(a1, a0, &::step) 372 %b(&::done) 373 ::step 374 %addi(t0, t0, 1) 375 %b(&::loop) 376 ::done 377 %st(t0, t2, 0) 378 %ret 379 %endscope 380 381 # parse_one() -> tagged value in a0 382 %fn(parse_one, 0, { 383 %call(&skip_ws) 384 385 %la(t0, &readbuf_pos) 386 %ld(t0, t0, 0) 387 %la(t1, &readbuf_len) 388 %ld(t1, t1, 0) 389 %beq(t0, t1, &::eof) 390 391 %readbuf_byte(a0, t0) 392 393 %addi(a1, a0, -40) 394 %beqz(a1, &::lparen) 395 %addi(a1, a0, -41) 396 %beqz(a1, &::rparen) 397 398 %call(&parse_atom) 399 %eret 400 401 ::lparen 402 # Consume '(' and read items until ')'. 403 %la(t0, &readbuf_pos) 404 %ld(t1, t0, 0) 405 %addi(t1, t1, 1) 406 %st(t1, t0, 0) 407 %call(&parse_list) 408 %eret 409 410 ::rparen 411 %die(msg_unexp_rparen, 6) 412 413 ::eof 414 %die(msg_unexp_eof, 6) 415 }) 416 417 # parse_list() -> tagged list value in a0. Cursor sits past '(' on entry; 418 # returns once ')' is consumed. 419 # 420 # Frame: 16 bytes 421 # +0 head (NIL until first item) 422 # +8 tail (most recent cons; set-cdr! target) 423 %fn(parse_list, 16, { 424 %li(t0, %imm_val(%IMM.NIL)) 425 %st(t0, sp, 0) 426 %st(t0, sp, 8) 427 428 ::loop 429 %call(&skip_ws) 430 %la(t0, &readbuf_pos) 431 %ld(t0, t0, 0) 432 %la(t1, &readbuf_len) 433 %ld(t1, t1, 0) 434 %beq(t0, t1, &::eof) 435 436 %readbuf_byte(a0, t0) 437 %addi(a1, a0, -41) 438 %beqz(a1, &::close) 439 440 # Not ')': parse one item, append. 441 %call(&parse_one) 442 %li(a1, %imm_val(%IMM.NIL)) 443 %call(&cons) 444 445 # If head is NIL, both head and tail = new cons; else set-cdr! tail = new. 446 %ld(t0, sp, 0) 447 %li(t1, %imm_val(%IMM.NIL)) 448 %bne(t0, t1, &::link) 449 %st(a0, sp, 0) 450 %st(a0, sp, 8) 451 %b(&::loop) 452 453 ::link 454 %ld(t0, sp, 8) 455 # set-cdr! tail = a0 -> store a0 at [tail + 7] (raw + 8) 456 %st(a0, t0, 7) 457 %st(a0, sp, 8) 458 %b(&::loop) 459 460 ::close 461 # Consume ')' and return head. 462 %la(t0, &readbuf_pos) 463 %ld(t1, t0, 0) 464 %addi(t1, t1, 1) 465 %st(t1, t0, 0) 466 %ld(a0, sp, 0) 467 %eret 468 469 ::eof 470 %die(msg_unterm_list, 6) 471 }) 472 473 # parse_atom() -> tagged value (fixnum or symbol) in a0. 474 # Reads until whitespace or paren or EOF, then dispatches by first byte. 475 # 476 # Frame: 16 bytes 477 # +0 start cursor (byte offset) 478 # +8 end cursor (byte offset) 479 %fn(parse_atom, 16, { 480 %la(t0, &readbuf_pos) 481 %ld(t1, t0, 0) 482 %st(t1, sp, 0) 483 484 %la(t2, &readbuf_len) 485 %ld(t2, t2, 0) 486 487 ::scan 488 %beq(t1, t2, &::end) 489 %readbuf_byte(a0, t1) 490 491 %is_ws_branch(a1, a0, &::end) 492 # paren? 493 %addi(a1, a0, -40) 494 %beqz(a1, &::end) 495 %addi(a1, a0, -41) 496 %beqz(a1, &::end) 497 498 %addi(t1, t1, 1) 499 %b(&::scan) 500 501 ::end 502 %st(t1, sp, 8) 503 %la(t0, &readbuf_pos) 504 %st(t1, t0, 0) 505 506 # Dispatch on the first byte. 507 %ld(t0, sp, 0) 508 %la(a0, &readbuf_buf_ptr) 509 %ld(a0, a0, 0) 510 %add(a0, a0, t0) 511 %lb(t1, a0, 0) 512 513 # '0'..'9' -> int 514 %addi(a1, t1, -48) 515 %li(a2, 10) 516 %bltu(a1, a2, &::is_int) 517 # '-' followed by digit -> int 518 %addi(a1, t1, -45) 519 %bnez(a1, &::is_sym) 520 # there must be at least one more byte for it to be a number 521 %ld(t2, sp, 8) 522 %addi(t0, t0, 1) 523 %beq(t0, t2, &::is_sym) 524 %readbuf_byte(a0, t0) 525 %addi(a1, a0, -48) 526 %bltu(a1, a2, &::is_int) 527 # fall through to is_sym 528 529 ::is_sym 530 %ld(a0, sp, 0) 531 %la(t0, &readbuf_buf_ptr) 532 %ld(t0, t0, 0) 533 %add(a0, t0, a0) 534 %ld(t1, sp, 8) 535 %ld(t2, sp, 0) 536 %sub(a1, t1, t2) 537 %call(&intern) 538 %eret 539 540 ::is_int 541 %ld(a0, sp, 0) 542 %ld(a1, sp, 8) 543 %call(&parse_int) 544 }) 545 546 # parse_int(start_off=a0, end_off=a1) -> tagged fixnum in a0. Leaf. 547 :parse_int 548 %scope parse_int 549 %la(t0, &readbuf_buf_ptr) 550 %ld(t0, t0, 0) 551 %add(t1, t0, a1) ; t1 = end pointer = base + end_off 552 %add(t0, t0, a0) ; t0 = start pointer = base + start_off 553 554 %li(a2, 0) ; a2 = "is negative" flag (0 = positive) 555 %lb(a3, t0, 0) 556 %addi(a3, a3, -45) 557 %bnez(a3, &::loop) 558 %li(a2, 1) 559 %addi(t0, t0, 1) 560 561 ::loop 562 %li(a0, 0) 563 %li(t2, 10) 564 ::step 565 %beq(t0, t1, &::done) 566 %lb(a3, t0, 0) 567 %addi(a3, a3, -48) 568 %mul(a0, a0, t2) 569 %add(a0, a0, a3) 570 %addi(t0, t0, 1) 571 %b(&::step) 572 573 ::done 574 %beqz(a2, &::tag) 575 %li(t2, 0) 576 %sub(a0, t2, a0) 577 ::tag 578 %mkfix(a0, a0) 579 %ret 580 %endscope 581 582 # ========================================================================= 583 # eval / apply 584 # ========================================================================= 585 # 586 # eval is the only place that touches tag bits at runtime; the table 587 # below is a flat compare cascade for now (5 tags). When special-form 588 # dispatch is wired up the SYM/PAIR paths split further per LISP-C.md 589 # §Eval. 590 591 # eval(expr=a0, env=a1) -> value (a0) 592 # 593 # Frame: 32 bytes 594 # +0 expr 595 # +8 env 596 # +16 fn (head value, while args are being evaluated) 597 %fn(eval, 32, { 598 %st(a0, sp, 0) 599 %st(a1, sp, 8) 600 601 %tagof(t0, a0) 602 %li(t1, %TAG.PAIR) 603 %beq(t0, t1, &::pair) 604 %li(t1, %TAG.SYM) 605 %beq(t0, t1, &::sym) 606 # FIXNUM, HEAP, IMM all self-evaluate. 607 %eret 608 609 ::sym 610 %untag_sym(a0, a0) 611 %call(&sym_global) 612 %li(t0, %imm_val(%IMM.UNBOUND)) 613 %beq(a0, t0, &::unbound) 614 %eret 615 616 ::unbound 617 %die(msg_unbound, 7) 618 619 ::pair 620 # head = eval(car(expr), env) 621 %ld(a0, sp, 0) 622 %car(a0, a0) 623 %ld(a1, sp, 8) 624 %call(&eval) 625 %st(a0, sp, 16) 626 627 # args = eval_args(cdr(expr), env) 628 %ld(a0, sp, 0) 629 %cdr(a0, a0) 630 %ld(a1, sp, 8) 631 %call(&eval_args) 632 633 # apply(fn, args) 634 %mov(a1, a0) 635 %ld(a0, sp, 16) 636 %call(&apply) 637 }) 638 639 # eval_args(args=a0, env=a1) -> evaluated args list (cons-built). 640 # Recursion depth = arg count, so very long arg lists could blow the 641 # stack. Iterative tail-build is a future tightening. 642 %fn(eval_args, 24, { 643 %li(t0, %imm_val(%IMM.NIL)) 644 %beq(a0, t0, &::nil) 645 646 %st(a0, sp, 0) 647 %st(a1, sp, 8) 648 649 # val = eval(car(args), env) 650 %car(a0, a0) 651 %call(&eval) 652 %st(a0, sp, 16) 653 654 # rest = eval_args(cdr(args), env) 655 %ld(a0, sp, 0) 656 %cdr(a0, a0) 657 %ld(a1, sp, 8) 658 %call(&eval_args) 659 660 # cons(val, rest) 661 %mov(a1, a0) 662 %ld(a0, sp, 16) 663 %call(&cons) 664 %eret 665 666 ::nil 667 %li(a0, %imm_val(%IMM.NIL)) 668 }) 669 670 # apply(fn=a0, args=a1) -> result (a0) 671 # 672 # Frame: 16 bytes 673 # +0 args 674 %fn(apply, 16, { 675 %st(a1, sp, 0) 676 677 # Only HEAP-tagged values can be applicable. PRIM is the only header 678 # type wired up here. 679 %tagof(t0, a0) 680 %li(t1, %TAG.HEAP) 681 %bne(t0, t1, &::not_proc) 682 683 %hdr_type(t0, a0) 684 %li(t1, %HDR.PRIM) 685 %beq(t0, t1, &::prim) 686 687 ::not_proc 688 %die(msg_not_proc, 8) 689 690 ::prim 691 %ld(t0, a0, 5) ; t0 = entry word (offset = -3 + 8) 692 %ld(a0, sp, 0) ; args list -> a0 693 %callr(t0) 694 }) 695 696 # ========================================================================= 697 # Primitives 698 # ========================================================================= 699 # 700 # Each primitive sits behind a 16-byte heap object literal in the data 701 # section: [hdr_word, entry_word]. The tagged value is &obj + 3. 702 # register_primitives interns the surface name and writes the tagged 703 # pointer into the symbol's global slot. 704 705 %fn(register_primitives, 0, { 706 %la(a0, &name_sys_exit) 707 %li(a1, 8) 708 %call(&intern) 709 %untag_sym(a0, a0) ; idx 710 %la(a1, &prim_sys_exit) 711 %addi(a1, a1, 3) ; tag HEAP 712 %call(&sym_set_global) 713 }) 714 715 # prim_sys_exit_entry(args=a0). Args is a one-element list whose car is 716 # the exit code as a tagged fixnum. Untag and tail-jump to libp1pp's 717 # sys_exit (a %b, not a %call -- this is a leaf with no frame, and 718 # sys_exit doesn't return anyway). 719 :prim_sys_exit_entry 720 %car(a0, a0) ; car = fixnum 721 %untag_fix(a0, a0) 722 %b(&sys_exit) 723 724 # ========================================================================= 725 # Read-only data 726 # ========================================================================= 727 728 # Primitive object literals (16 bytes each). 729 :prim_sys_exit 730 $(%HDR.PRIM) &prim_sys_exit_entry %(0) 731 732 # Surface names. Length is hard-coded at the call site; no NUL needed 733 # because intern takes (ptr, len). Aligned padding via "\0" bytes is 734 # fine -- M0 emits ASCII verbatim. 735 :name_sys_exit "sys-exit" 736 737 :msg_usage "scheme1: usage: scheme1 SOURCE.scm" '0a' '00' 738 :msg_load_fail "scheme1: failed to read source" '0a' '00' 739 :msg_symtab_full "scheme1: symbol table full" '0a' '00' 740 :msg_unexp_rparen "scheme1: unexpected ')'" '0a' '00' 741 :msg_unexp_eof "scheme1: unexpected EOF in form" '0a' '00' 742 :msg_unterm_list "scheme1: unterminated list" '0a' '00' 743 :msg_unbound "scheme1: unbound variable" '0a' '00' 744 :msg_not_proc "scheme1: not a procedure" '0a' '00' 745 746 # ========================================================================= 747 # BSS pointer-init table 748 # ========================================================================= 749 # 750 # Each entry: 8-byte slot pointer (4-byte label ref + 4 bytes pad) + 751 # 8-byte offset constant. p1_main walks this once at startup. 752 :bss_init_tbl 753 &readbuf_buf_ptr %(0) $(0) 754 &heap_buf_ptr %(0) $(0x10000) 755 &symtab_buf_ptr %(0) $(0x20000) 756 :bss_init_tbl_end 757 758 # ========================================================================= 759 # Scalar BSS (file-resident, zero-initialized) 760 # ========================================================================= 761 762 # heap_next: bump pointer; written once by p1_main, then by cons/alloc_hdr. 763 :heap_next $(0) 764 765 # Source-buffer cursor and slurped length. 766 :readbuf_pos $(0) 767 :readbuf_len $(0) 768 769 # Symbol table count (number of entries used). 770 :symtab_count $(0) 771 772 # Pointer slots for the past-:ELF_end arenas. 773 :readbuf_buf_ptr $(0) 774 :heap_buf_ptr $(0) 775 :symtab_buf_ptr $(0) 776 777 :ELF_end