boot2

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

commit a835d6b3630add6a1700ddcb6103208e5bf9d9bf
parent c0af21b7d2e5511016c8989f404945b0d697b8ba
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 26 Apr 2026 22:34:12 -0700

cc/cg: add cg-push-field + array decay (§D.1)

cg-push-field cg fname pops a struct/union lval, looks up the field
in ext's (tag complete? fields) field list, and pushes a new lval
at the field's offset with its ctype. Three lval input cases:
direct frame (shift slot), indirect frame (addr+fo into new
indirect slot), global (la + addi via new indirect slot).

cg-decay-array converts an arr-typed lval at the top into a
ptr-rval to its first element. Wired into cg-load (arr lvals
decay) and cg-take-addr (&arr yields T*, not (T[N])*) so existing
pointer-arithmetic paths see plain pointers.

Locks the contract via cc-cg/36-struct-load.scm: 5 stored to s.a
and 7 to s.b through cg-push-field, then s.b - s.a == 2 returned
as exit 1. The pre-fix parser stub used offset 0 for both fields;
the cg path is independent so this fixture validates the cg API
directly per CC-INTERNALS §Feature workflow.

Diffstat:
Mcc/cg.scm | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Atests/cc-cg/36-struct-load.expected-exit | 1+
Atests/cc-cg/36-struct-load.scm | 45+++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 159 insertions(+), 1 deletion(-)

diff --git a/cc/cg.scm b/cc/cg.scm @@ -398,12 +398,119 @@ (cg-push cg (%opnd 'frame pe off #t))))))) ;; -------------------------------------------------------------------- +;; Aggregate field access (§D.1–D.4) +;; -------------------------------------------------------------------- +;; cg-push-field cg fname: +;; pop a struct/union lval; look up `fname` in the struct's fields +;; list (data.scm: ext = (tag complete? fields), where each field +;; is (name-bv ctype offset)); push a new lval at the field's +;; offset with the field's ctype. +;; +;; Three input cases: +;; - direct frame lval at slot `off` -> frame lval at off+fo +;; - indirect frame lval (slot holds addr) -> new indirect slot for +;; addr+fo +;; - global lval at label L -> indirect slot for +;; la(L)+fo +;; In all cases the resulting lval has the field's ctype. + +(define (%cg-find-field fields fname) + (let loop ((xs fields)) + (cond + ((null? xs) #f) + ((bv= (car (car xs)) fname) (car xs)) + (else (loop (cdr xs)))))) + +(define (cg-push-field cg fname) + (let* ((s (cg-pop cg)) + (sty (opnd-type s)) + (k (ctype-kind sty))) + (cond + ((not (or (eq? k 'struct) (eq? k 'union))) + (die #f "cg-push-field: not a struct/union" k)) + ((not (opnd-lval? s)) + (die #f "cg-push-field: not an lvalue" k)) + (else + (let* ((fields (car (cddr (ctype-ext sty)))) + (f (%cg-find-field fields fname))) + (cond + ((not f) (die #f "cg-push-field: no such field" fname)) + (else + (let* ((fty (cadr f)) (fo (car (cddr f)))) + (cond + ;; direct frame lval: just shift the slot offset. + ((and (eq? (opnd-kind s) 'frame) + (not (%cg-indirect? cg (opnd-ext s)))) + (cg-push cg (%opnd 'frame fty (+ (opnd-ext s) fo) #t))) + ;; indirect frame lval: addr lives in the slot. Compute + ;; addr+fo into a new indirect slot. + ((eq? (opnd-kind s) 'frame) + (%cg-emit-ld-slot cg 't0 (opnd-ext s)) + (cond + ((> fo 0) + (%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n")))) + (let ((no (cg-alloc-slot cg 8 8))) + (%cg-emit-st-slot cg 't0 no) + (%cg-mark-indirect! cg no) + (cg-push cg (%opnd 'frame fty no #t)))) + ;; global lval: load addr, add offset, indirect slot. + ((eq? (opnd-kind s) 'global) + (%cg-emit-la cg 't0 (opnd-ext s)) + (cond + ((> fo 0) + (%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n")))) + (let ((no (cg-alloc-slot cg 8 8))) + (%cg-emit-st-slot cg 't0 no) + (%cg-mark-indirect! cg no) + (cg-push cg (%opnd 'frame fty no #t)))) + (else + (die #f "cg-push-field: unsupported lval kind" + (opnd-kind s)))))))))))) + +;; cg-decay-array: +;; if top of vstack is an arr-typed lval, replace it with a ptr-rval +;; to the first element. C arrays decay to T* in most contexts; +;; parse calls this before rval-style operations. No-op otherwise. +(define (cg-decay-array cg) + (let ((tp (cg-top cg))) + (cond + ((and (opnd-lval? tp) (eq? (ctype-kind (opnd-type tp)) 'arr)) + (let* ((p (cg-pop cg)) + (et (car (ctype-ext (opnd-type p)))) + (pty (%ctype 'ptr 8 8 et))) + (cond + ;; direct frame lval: address is sp+off. + ((and (eq? (opnd-kind p) 'frame) + (not (%cg-indirect? cg (opnd-ext p)))) + (%cg-emit-many cg (list "%mov(t0, sp)\n" + "%addi(t0, t0, " + (%cg-slot-expr cg (opnd-ext p)) ")\n")) + (%cg-spill-reg cg 't0 pty)) + ;; indirect frame lval (rare for arrays, but support it): + ;; the slot holds the address already. + ((eq? (opnd-kind p) 'frame) + (%cg-emit-ld-slot cg 't0 (opnd-ext p)) + (%cg-spill-reg cg 't0 pty)) + ;; global array: la(label) is the address. + ((eq? (opnd-kind p) 'global) + (%cg-emit-la cg 't0 (opnd-ext p)) + (%cg-spill-reg cg 't0 pty)) + (else (die #f "cg-decay-array: unsupported lval kind" + (opnd-kind p)))))) + (else tp)))) + +;; -------------------------------------------------------------------- ;; Address & deref ;; -------------------------------------------------------------------- (define (cg-take-addr cg) (let* ((p (cg-pop cg)) (ty (opnd-type p)) - (pty (%ctype 'ptr 8 8 ty))) + ;; &arr yields T*, not (T[N])*. Result type is ptr-to-elem + ;; for arrays so subsequent pointer arithmetic scales by + ;; element size, not by array size. + (pty (cond ((eq? (ctype-kind ty) 'arr) + (%ctype 'ptr 8 8 (car (ctype-ext ty)))) + (else (%ctype 'ptr 8 8 ty))))) (cond ((not (opnd-lval? p)) (die #f "cg-take-addr: not an lvalue")) @@ -429,6 +536,11 @@ (let* ((p (cg-pop cg)) (ty (opnd-type p))) (cond ((not (opnd-lval? p)) (die #f "cg-load: not an lvalue")) + ;; Array lvalues decay to a ptr-rval addressing the first + ;; element (C array-to-pointer decay). We push the lval back + ;; and route through cg-decay-array for a single source of truth. + ((eq? (ctype-kind ty) 'arr) + (cg-push cg p) (cg-decay-array cg)) ((and (eq? (opnd-kind p) 'frame) (%cg-indirect? cg (opnd-ext p))) ;; Indirect frame-lval: slot holds the address. Stage the diff --git a/tests/cc-cg/36-struct-load.expected-exit b/tests/cc-cg/36-struct-load.expected-exit @@ -0,0 +1 @@ +1 diff --git a/tests/cc-cg/36-struct-load.scm b/tests/cc-cg/36-struct-load.scm @@ -0,0 +1,45 @@ +;; tests/cc-cg/36-struct-load.scm — struct member load via cg-push-field +;; (§D.1 of docs/CC-PUNCHLIST.md). +;; +;; Models: +;; struct S { int a; int b; }; +;; struct S s; +;; s.a = 5; s.b = 7; <- using direct cg-push-field + cg-assign +;; return s.b - s.a; <- 2; assert (s.b - s.a == 2) -> exit 1 +;; +;; If cg-push-field is the broken stub it would access offset 0 for +;; both fields, so loading s.b would also yield 5 and the equality +;; check would fail (exit 0). With correct field-offset arithmetic +;; the result is 2 and exit is 1. + +(let* ((cg (cg-init)) + (st-ty (%ctype 'struct 8 4 + (list "S" #t + (list (list "a" %t-i32 0) + (list "b" %t-i32 4)))))) + (cg-fn-begin cg "main" '() %t-i32) + (let* ((off-s (cg-alloc-slot cg 8 4)) + (sym-s (%sym "s" 'var 'auto st-ty off-s))) + ;; s.a = 5 + (cg-push-sym cg sym-s) + (cg-push-field cg "a") + (cg-push-imm cg %t-i32 5) + (cg-assign cg) (cg-pop cg) + ;; s.b = 7 + (cg-push-sym cg sym-s) + (cg-push-field cg "b") + (cg-push-imm cg %t-i32 7) + (cg-assign cg) (cg-pop cg) + ;; return (s.b - s.a) == 2 + (cg-push-sym cg sym-s) + (cg-push-field cg "b") + (cg-load cg) + (cg-push-sym cg sym-s) + (cg-push-field cg "a") + (cg-load cg) + (cg-binop cg 'sub) + (cg-push-imm cg %t-i32 2) + (cg-binop cg 'eq) + (cg-return cg)) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg)))