boot2

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

commit 82d992202c949f29f917edf6bc55ef99a684a8f6
parent 5e3047ee5ff286a50094e68e176cda152288806d
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 26 Apr 2026 23:53:28 -0700

Merge Agent 3: §E + §I + §L.3 (initializers + storage + fn-ptr table)

# Conflicts:
#	cc/cg.scm

Diffstat:
Mcc/cg.scm | 50++++++++++++++++++++++++++++++++++++++++++++------
Mcc/parse.scm | 489+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdocs/CC-INTERNALS.md | 5++++-
Mdocs/CC-PUNCHLIST.md | 110++++++++++++++++++++++++++++++++++++++++++++-----------------------------------
Atests/cc-cg/49-init-scalar-global.expected-exit | 1+
Atests/cc-cg/49-init-scalar-global.scm | 19+++++++++++++++++++
Atests/cc-cg/50-init-addr.expected-exit | 1+
Atests/cc-cg/50-init-addr.scm | 27+++++++++++++++++++++++++++
Atests/cc-cg/51-init-array-list.expected-exit | 1+
Atests/cc-cg/51-init-array-list.scm | 32++++++++++++++++++++++++++++++++
Atests/cc-cg/52-init-array-str.expected-exit | 1+
Atests/cc-cg/52-init-array-str.scm | 24++++++++++++++++++++++++
Atests/cc-cg/53-init-struct-pos.expected-exit | 1+
Atests/cc-cg/53-init-struct-pos.scm | 36++++++++++++++++++++++++++++++++++++
Atests/cc-cg/54-init-struct-desig.expected-exit | 1+
Atests/cc-cg/54-init-struct-desig.scm | 26++++++++++++++++++++++++++
Atests/cc-cg/57-block-static.expected-exit | 1+
Atests/cc-cg/57-block-static.scm | 45+++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-cg/58-fnptr-tab.expected-exit | 1+
Atests/cc-cg/58-fnptr-tab.scm | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-parse/49-init-scalar-global.c | 4++++
Atests/cc-parse/49-init-scalar-global.expected-exit | 1+
Atests/cc-parse/50-init-addr.c | 4++++
Atests/cc-parse/50-init-addr.expected-exit | 1+
Atests/cc-parse/51-init-array-list.c | 8++++++++
Atests/cc-parse/51-init-array-list.expected-exit | 1+
Atests/cc-parse/52-init-array-str.c | 7+++++++
Atests/cc-parse/52-init-array-str.expected-exit | 1+
Atests/cc-parse/53-init-struct-pos.c | 11+++++++++++
Atests/cc-parse/53-init-struct-pos.expected-exit | 1+
Atests/cc-parse/54-init-struct-desig.c | 10++++++++++
Atests/cc-parse/54-init-struct-desig.expected-exit | 1+
Atests/cc-parse/55-init-local-array.c | 11+++++++++++
Atests/cc-parse/55-init-local-array.expected-exit | 1+
Atests/cc-parse/56-init-local-struct.c | 11+++++++++++
Atests/cc-parse/56-init-local-struct.expected-exit | 1+
Atests/cc-parse/57-block-static.c | 22++++++++++++++++++++++
Atests/cc-parse/57-block-static.expected-exit | 1+
Atests/cc-parse/58-fnptr-tab.c | 15+++++++++++++++
Atests/cc-parse/58-fnptr-tab.expected-exit | 1+
40 files changed, 958 insertions(+), 75 deletions(-)

diff --git a/cc/cg.scm b/cc/cg.scm @@ -675,8 +675,12 @@ (sa (%ctype-size ta)) (sb (%ctype-size tb))) (cond + ;; Pointer/array arithmetic: leave types alone so cg-binop's + ;; ptr-aware add/sub branch fires with the correct pointee type + ;; (and doesn't see two pointers, which would skip scaling). ((or (%ctype-ptr? ta) (%ctype-ptr? tb)) - (cg-push cg a) (cg-push cg b)) + (cg-push cg a) + (cg-push cg b)) (else (let ((common (cond ((> sa sb) ta) @@ -1121,16 +1125,50 @@ ;; -------------------------------------------------------------------- ;; Globals and data ;; -------------------------------------------------------------------- -(define (cg-emit-global cg sym init-bv-or-false) +;; cg-emit-global: emit a global symbol into either .data (initialized) +;; or .bss (zero-init). +;; +;; init can be: +;; #f — zero-init in .bss (size from sym's ctype). +;; (piece ...) — initialized in .data; pieces concatenated. +;; +;; Each piece is either: +;; <bytevector> — raw bytes; emitted as N×!(byte) entries. +;; (label-ref . <label-bv>) — 8-byte pointer slot containing &label; +;; emitted as `&<label> %(0)` (4B label ref + +;; 4B zero pad). +(define (%cg-init-piece->bv piece) + (cond + ((bytevector? piece) + (let ((n (bytevector-length piece))) + (let loop ((i 0) (acc '())) + (cond + ((= i n) (bv-cat (reverse acc))) + (else + (loop (+ i 1) + (cons (bv-cat (list "!(" + (number->string + (bytevector-u8-ref piece i) 10) + ")\n")) + acc))))))) + ((and (pair? piece) (eq? (car piece) 'label-ref)) + (bv-cat (list "&" (cdr piece) " %(0)\n"))) + (else (die #f "cg-emit-global: bad init piece" piece)))) + +(define (cg-emit-global cg sym init) (let* ((nm (sym-name sym)) (lbl (%cg-mangle-global nm)) (sz (ctype-size (sym-type sym))) (size (if (< sz 0) 8 sz))) (cond - (init-bv-or-false - (buf-push! (cg-data cg) - (bv-cat (list "\n:" lbl "\n" - "\"" init-bv-or-false "\"\n")))) + (init + (buf-push! (cg-data cg) (bv-cat (list "\n:" lbl "\n"))) + (let walk ((ps init)) + (cond + ((null? ps) 0) + (else + (buf-push! (cg-data cg) (%cg-init-piece->bv (car ps))) + (walk (cdr ps)))))) (else (buf-push! (cg-bss cg) (bv-cat (list "\n:" lbl "\n" diff --git a/cc/parse.scm b/cc/parse.scm @@ -371,6 +371,23 @@ (scope-bind! ps n (%sym n 'fn (or sto 'extern) ty (bytevector-append "cc__" n)))) + ;; §I: block-scope `static` routes to a global with a name mangled + ;; on the enclosing function so two functions can each have their + ;; own `static int n;` without colliding. The sym's NAME holds the + ;; mangled form (cg-push-sym / cg-emit-global both prefix "cc__" + ;; onto sym-name to derive the emitted label); scope-bind!s key + ;; remains the original identifier for source-level lookup. + ((and (eq? sto 'static) (ps-fn-ctx ps)) + (let* ((fname (fn-ctx-name (ps-fn-ctx ps))) + (mangled (bytevector-append fname "__" n)) + (sm (%sym mangled 'var 'static ty + (bytevector-append "cc__" mangled)))) + (scope-bind! ps n sm) + (cond + ((at-punct? ps 'assign) + (advance ps) + (cg-emit-global (ps-cg ps) sm (parse-init-global ps ty))) + (else (cg-emit-global (ps-cg ps) sm #f))))) (else (cond ((not (ps-fn-ctx ps)) @@ -381,7 +398,7 @@ ((at-punct? ps 'assign) (advance ps) (cg-emit-global (ps-cg ps) sm - (parse-init-list ps ty))) + (parse-init-global ps ty))) ((eq? sto 'extern) (cg-emit-extern (ps-cg ps) sm)) (else (cg-emit-global (ps-cg ps) sm #f))))) (else @@ -393,30 +410,462 @@ (cond ((at-punct? ps 'assign) (advance ps) - (cg-push-sym (ps-cg ps) sm) - (parse-expr-bp ps 4) (rval! ps) - (cg-cast (ps-cg ps) ty) - (cg-assign (ps-cg ps)) - (cg-pop (ps-cg ps))) + (cond + ;; Aggregate locals get the per-element store treatment. + ((or (at-punct? ps 'lbrace) + (and (eq? (ctype-kind ty) 'arr) + (eq? (tok-kind (peek ps)) 'STR))) + (parse-init-local-aggregate ps sm ty)) + (else + (cg-push-sym (ps-cg ps) sm) + (parse-expr-bp ps 4) (rval! ps) + (cg-cast (ps-cg ps) ty) + (cg-assign (ps-cg ps)) + (cg-pop (ps-cg ps))))) (else #t)))))))) -(define (parse-init-list ps ty) +;; ==================================================================== +;; Initializers (CC.md §Variable initializers, §E of CC-PUNCHLIST). +;; +;; parse-init-global ps ty +;; Reads the initializer following `=` for a file-scope or block-scope +;; static var of static-storage type `ty` and returns a list of +;; pieces suitable for cg-emit-global. See cg.scm §cg-emit-global for +;; the piece grammar. +;; +;; parse-init-local ps sm ty +;; Reads the initializer for an auto-storage variable bound to slot +;; sym `sm` and emits per-element store cg ops. Returns unspecified. +;; ==================================================================== + +(define (%int->le-bv n nbytes) + ;; N-byte little-endian encoding of integer n into a fresh bv. Bytes + ;; >= sign-bit are filled by repeated >>8 (works for both signed and + ;; unsigned because we only keep the low N bytes). + (let ((out (make-bytevector nbytes 0))) + (let loop ((i 0) (v n)) + (cond + ((= i nbytes) out) + (else + (bytevector-u8-set! out i (bit-and v 255)) + (loop (+ i 1) (arithmetic-shift v -8))))))) + +(define (%const-init-piece ps ty) + ;; Parse a non-brace initializer expression for scalar type `ty` and + ;; return a single piece. Recognised forms: + ;; - INT (with optional unary +/-) -> N-byte LE bv + ;; - enum-const IDENT -> N-byte LE bv + ;; - &IDENT (address of a global var/fn) -> (label-ref . cc__name) + ;; - IDENT (function name; decays to fn ptr) -> (label-ref . cc__name) + ;; - STR (only for char* targets) -> (label-ref . string-pool-label) + (let ((t (peek ps))) + (cond + ;; Address initializer: &ident -> label-ref + ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'amp)) + (advance ps) + (let ((it (peek ps))) + (cond + ((eq? (tok-kind it) 'IDENT) + (advance ps) + (let ((sm (scope-lookup ps (tok-value it)))) + (cond + ((not sm) (die (tok-loc it) "init: undecl" (tok-value it))) + ((or (eq? (sym-kind sm) 'fn) + (and (eq? (sym-kind sm) 'var) + (or (eq? (sym-storage sm) 'static) + (eq? (sym-storage sm) 'extern)))) + (cons 'label-ref (sym-slot sm))) + (else + (die (tok-loc it) "init: &x must reference a global" + (tok-value it)))))) + (else (die (tok-loc it) "init: &?" (tok-value it)))))) + ;; Function name or array name as a label-ref initializer. + ;; (Both decay to a pointer when used as a value.) + ((and (eq? (tok-kind t) 'IDENT) + (let ((sm (scope-lookup ps (tok-value t)))) + (and sm + (or (eq? (sym-kind sm) 'fn) + (and (eq? (sym-kind sm) 'var) + (eq? (ctype-kind (sym-type sm)) 'arr) + (or (eq? (sym-storage sm) 'static) + (eq? (sym-storage sm) 'extern))))))) + (advance ps) + (let ((sm (scope-lookup ps (tok-value t)))) + (cons 'label-ref (sym-slot sm)))) + ;; Plain string literal as char* initializer. + ((eq? (tok-kind t) 'STR) + (advance ps) + (let ((lbl (cg-intern-string (ps-cg ps) (tok-value t)))) + (cons 'label-ref lbl))) + ;; Otherwise it's a const integer. + (else + (let ((v (parse-const-int ps))) + (%int->le-bv v (max (ctype-size ty) 1))))))) + +(define (%init-array-elem-type ty) + (cond ((eq? (ctype-kind ty) 'arr) (car (ctype-ext ty))) + (else (die #f "init: not an array" ty)))) + +(define (%init-array-decl-len ty) + ;; Declared array length (-1 = inferred). + (cond ((eq? (ctype-kind ty) 'arr) (cdr (ctype-ext ty))) (else -1))) + +(define (%init-fix-array-size! ty count) + ;; Patch an inferred-length array to `count`. + (let ((elem (car (ctype-ext ty)))) + (ctype-ext-set! ty (cons elem count)) + (ctype-size-set! ty (* count (ctype-size elem))))) + +(define (%init-struct-fields ty) + ;; Return ((name-bv ctype offset) ...) for a struct/union ctype. + (let ((ext (ctype-ext ty))) + (cond ((and (pair? ext) (pair? (cdr ext))) (car (cddr ext))) + (else (die #f "init: not a struct" ty))))) + +(define (%find-field fields nm) + (cond ((null? fields) #f) + ((equal? (car (car fields)) nm) (car fields)) + (else (%find-field (cdr fields) nm)))) + +(define (%pad-piece nbytes) + (make-bytevector nbytes 0)) + +;; ----- Global initializers --------------------------------------------- +(define (parse-init-global ps ty) (cond + ;; String literal initializer for char[] + ((and (eq? (ctype-kind ty) 'arr) + (eq? (tok-kind (peek ps)) 'STR) + (let ((et (car (ctype-ext ty)))) + (or (eq? et %t-i8) (eq? et %t-u8)))) + (let* ((t (advance ps)) + (s (tok-value t)) + (slen (bytevector-length s)) + (decl (cdr (ctype-ext ty))) + (final (cond ((< decl 0) (+ slen 1)) (else decl)))) + (cond ((< decl 0) (%init-fix-array-size! ty final))) + (let ((bv (make-bytevector final 0))) + (let loop ((i 0)) + (cond + ((or (= i slen) (>= i final)) (list bv)) + (else + (bytevector-u8-set! bv i (bytevector-u8-ref s i)) + (loop (+ i 1)))))))) + ;; Brace-form ((at-punct? ps 'lbrace) (advance ps) - (let lp ((d 1)) - (cond - ((<= d 0) #f) - (else - (let ((t (advance ps))) - (cond - ((eq? (tok-kind t) 'EOF) (die (tok-loc t) "EOF init")) - ((and (eq? (tok-kind t) 'PUNCT) - (eq? (tok-value t) 'lbrace)) (lp (+ d 1))) - ((and (eq? (tok-kind t) 'PUNCT) - (eq? (tok-value t) 'rbrace)) (lp (- d 1))) - (else (lp d)))))))) - (else (parse-const-int ps) #f))) + (cond + ((eq? (ctype-kind ty) 'arr) + (%parse-init-array-list ps ty)) + ((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union)) + (%parse-init-struct-list ps ty)) + (else + ;; Brace-wrapped scalar: { expr } + (let ((piece (%const-init-piece ps ty))) + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace) + (list piece))))) + ;; Bare scalar initializer + (else (list (%const-init-piece ps ty))))) + +(define (%parse-init-array-list ps ty) + ;; Element-list array initializer; assumes `{` already consumed. + (let* ((elem (%init-array-elem-type ty)) + (esize (ctype-size elem)) + (decl (%init-array-decl-len ty))) + (let lp ((acc '()) (count 0)) + (cond + ((at-punct? ps 'rbrace) + (advance ps) + (cond ((< decl 0) (%init-fix-array-size! ty count))) + ;; Pad to declared length if longer than count. + (let* ((final (cond ((< decl 0) count) (else decl))) + (pad (- final count))) + (cond + ((> pad 0) + (reverse (cons (%pad-piece (* pad esize)) acc))) + (else (reverse acc))))) + (else + (let ((piece + (cond + ((at-punct? ps 'lbrace) + ;; Nested aggregate: brace-flatten via recursion. + (advance ps) + ;; element is itself struct/array + (cond + ((eq? (ctype-kind elem) 'arr) + (%parse-init-array-list ps elem)) + ((or (eq? (ctype-kind elem) 'struct) + (eq? (ctype-kind elem) 'union)) + (%parse-init-struct-list ps elem)) + (else + (let ((p (%const-init-piece ps elem))) + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace) + (list p))))) + (else + (list (%const-init-piece ps elem)))))) + (cond ((at-punct? ps 'comma) (advance ps))) + (lp (append (reverse piece) acc) (+ count 1)))))))) + +(define (%parse-init-struct-list ps ty) + ;; Struct/union initializer; assumes `{` already consumed. + ;; Supports positional and `.field = expr` forms. + (let* ((fields (%init-struct-fields ty)) + (size (ctype-size ty))) + (let lp ((acc '()) (filled 0) (rest fields)) + (cond + ((at-punct? ps 'rbrace) + (advance ps) + (cond + ((< filled size) + (reverse (cons (%pad-piece (- size filled)) acc))) + (else (reverse acc)))) + (else + (let* ((designated? (at-punct? ps 'dot)) + (target + (cond + (designated? + (advance ps) + (let ((nt (advance ps))) + (cond + ((not (eq? (tok-kind nt) 'IDENT)) + (die (tok-loc nt) "init: .field expects ident"))) + (let ((f (%find-field fields (tok-value nt)))) + (cond + ((not f) (die (tok-loc nt) "init: no such field" + (tok-value nt)))) + (expect-punct ps 'assign) + f))) + ((null? rest) + (die (tok-loc (peek ps)) "init: too many fields")) + (else (car rest)))) + (fname (car target)) + (fty (car (cdr target))) + (foff (car (cddr target))) + (fsize (ctype-size fty)) + ;; Pad from `filled` to `foff` if needed. + (pad-bytes (- foff filled)) + (piece-list + (cond + ((at-punct? ps 'lbrace) + (advance ps) + (cond + ((eq? (ctype-kind fty) 'arr) + (%parse-init-array-list ps fty)) + ((or (eq? (ctype-kind fty) 'struct) + (eq? (ctype-kind fty) 'union)) + (%parse-init-struct-list ps fty)) + (else + (let ((p (%const-init-piece ps fty))) + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace) + (list p))))) + (else + (list (%const-init-piece ps fty))))) + (acc2 (cond ((> pad-bytes 0) + (cons (%pad-piece pad-bytes) acc)) + (else acc))) + (acc3 (append (reverse piece-list) acc2))) + (cond ((at-punct? ps 'comma) (advance ps))) + (lp acc3 (+ foff fsize) + (cond + (designated? + ;; designated init: drop fields up to and including target + (let drop ((xs fields)) + (cond + ((null? xs) '()) + ((equal? (car (car xs)) fname) (cdr xs)) + (else (drop (cdr xs)))))) + (else (cdr rest)))))))))) + +;; ----- Local aggregate initializers ------------------------------------ +;; Emits per-element store sequences via cg ops into the slot of `sm` +;; (a 'var sym whose slot is the frame offset). Assumes the assignment +;; `=` has already been consumed. +(define (parse-init-local-aggregate ps sm ty) + (cond + ;; Local char[] = "string" — fill from string bytes. + ((and (eq? (ctype-kind ty) 'arr) + (eq? (tok-kind (peek ps)) 'STR) + (let ((et (car (ctype-ext ty)))) + (or (eq? et %t-i8) (eq? et %t-u8)))) + (let* ((t (advance ps)) + (s (tok-value t)) + (slen (bytevector-length s)) + (decl (cdr (ctype-ext ty))) + (final (cond ((< decl 0) (+ slen 1)) (else decl)))) + (cond ((< decl 0) (%init-fix-array-size! ty final))) + ;; Emit byte stores for each char in s, plus NUL for the + ;; trailing slot if final > slen. + (let loop ((i 0)) + (cond + ((>= i final) #t) + (else + (let ((b (cond ((< i slen) (bytevector-u8-ref s i)) + (else 0))) + (off (+ (sym-slot sm) i))) + (%push-frame-elem-lval ps off %t-u8) + (cg-push-imm (ps-cg ps) %t-u8 b) + (cg-assign (ps-cg ps)) + (cg-pop (ps-cg ps)) + (loop (+ i 1)))))))) + ((at-punct? ps 'lbrace) + (advance ps) + (cond + ((eq? (ctype-kind ty) 'arr) + (%parse-init-local-array-list ps sm (sym-slot sm) ty)) + ((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union)) + (%parse-init-local-struct-list ps sm (sym-slot sm) ty)) + (else (die #f "init local: brace on scalar?")))) + (else (die (tok-loc (peek ps)) "init local aggregate?")))) + +(define (%emit-local-elem-store ps sm rel-off elem-ty piece-or-thunk) + ;; Emit a single scalar store at slot[base + rel-off]. piece is the + ;; raw initializer expression — but here we want to actually evaluate + ;; it via parse-expr to allow non-const expressions for autos. + ;; Caller handles this; this helper handles the store-into-frame ops. + 0) + +(define (%push-frame-elem-lval ps base-off ty) + (cg-push (ps-cg ps) (%opnd 'frame ty base-off #t))) + +(define (%parse-init-local-array-list ps sm base-off ty) + (let* ((elem (%init-array-elem-type ty)) + (esize (ctype-size elem)) + (decl (%init-array-decl-len ty))) + (let lp ((i 0)) + (cond + ((at-punct? ps 'rbrace) + (advance ps) + (cond ((< decl 0) (%init-fix-array-size! ty i))) + ;; Zero out remaining slots if any (declared length > i). + (let ((final (cond ((< decl 0) i) (else decl)))) + (let zlp ((k i)) + (cond + ((>= k final) #t) + (else + (let ((off (+ base-off (* k esize)))) + (cond + ((or (eq? (ctype-kind elem) 'arr) + (eq? (ctype-kind elem) 'struct) + (eq? (ctype-kind elem) 'union)) + ;; Zero each byte in this aggregate slot. + (let zb ((j 0)) + (cond + ((>= j esize) #t) + (else + (%push-frame-elem-lval ps (+ off j) %t-u8) + (cg-push-imm (ps-cg ps) %t-u8 0) + (cg-assign (ps-cg ps)) + (cg-pop (ps-cg ps)) + (zb (+ j 1)))))) + (else + (%push-frame-elem-lval ps off elem) + (cg-push-imm (ps-cg ps) elem 0) + (cg-assign (ps-cg ps)) + (cg-pop (ps-cg ps))))) + (zlp (+ k 1))))))) + (else + (let ((eoff (+ base-off (* i esize)))) + (cond + ((at-punct? ps 'lbrace) + (advance ps) + (cond + ((eq? (ctype-kind elem) 'arr) + (%parse-init-local-array-list ps sm eoff elem)) + ((or (eq? (ctype-kind elem) 'struct) + (eq? (ctype-kind elem) 'union)) + (%parse-init-local-struct-list ps sm eoff elem)) + (else + (%push-frame-elem-lval ps eoff elem) + (parse-expr-bp ps 4) (rval! ps) + (cg-cast (ps-cg ps) elem) + (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)) + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace)))) + (else + (%push-frame-elem-lval ps eoff elem) + (parse-expr-bp ps 4) (rval! ps) + (cg-cast (ps-cg ps) elem) + (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)))) + (cond ((at-punct? ps 'comma) (advance ps))) + (lp (+ i 1)))))))) + +(define (%parse-init-local-struct-list ps sm base-off ty) + (let ((fields (%init-struct-fields ty))) + (let lp ((rest fields)) + (cond + ((at-punct? ps 'rbrace) + (advance ps) + ;; Zero any remaining fields. + (let zlp ((xs rest)) + (cond + ((null? xs) #t) + (else + (let* ((f (car xs)) (fty (car (cdr f))) + (foff (car (cddr f))) (fsize (ctype-size fty))) + (let zb ((j 0)) + (cond + ((>= j fsize) #t) + (else + (%push-frame-elem-lval ps (+ base-off foff j) %t-u8) + (cg-push-imm (ps-cg ps) %t-u8 0) + (cg-assign (ps-cg ps)) + (cg-pop (ps-cg ps)) + (zb (+ j 1))))) + (zlp (cdr xs))))))) + (else + (let* ((designated? (at-punct? ps 'dot)) + (target + (cond + (designated? + (advance ps) + (let ((nt (advance ps))) + (let ((f (%find-field fields (tok-value nt)))) + (cond + ((not f) (die (tok-loc nt) "init: no such field" + (tok-value nt)))) + (expect-punct ps 'assign) + f))) + ((null? rest) + (die (tok-loc (peek ps)) "init: too many fields")) + (else (car rest)))) + (fname (car target)) + (fty (car (cdr target))) + (foff (car (cddr target))) + (eoff (+ base-off foff))) + (cond + ((at-punct? ps 'lbrace) + (advance ps) + (cond + ((eq? (ctype-kind fty) 'arr) + (%parse-init-local-array-list ps sm eoff fty)) + ((or (eq? (ctype-kind fty) 'struct) + (eq? (ctype-kind fty) 'union)) + (%parse-init-local-struct-list ps sm eoff fty)) + (else + (%push-frame-elem-lval ps eoff fty) + (parse-expr-bp ps 4) (rval! ps) + (cg-cast (ps-cg ps) fty) + (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)) + (cond ((at-punct? ps 'comma) (advance ps))) + (expect-punct ps 'rbrace)))) + (else + (%push-frame-elem-lval ps eoff fty) + (parse-expr-bp ps 4) (rval! ps) + (cg-cast (ps-cg ps) fty) + (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps)))) + (cond ((at-punct? ps 'comma) (advance ps))) + (lp (cond + (designated? + (let drop ((xs fields)) + (cond + ((null? xs) '()) + ((equal? (car (car xs)) fname) (cdr xs)) + (else (drop (cdr xs)))))) + (else (cdr rest)))))))))) + (define (parse-fn-body ps name dt) (let* ((e (ctype-ext dt)) (ret (car e)) diff --git a/docs/CC-INTERNALS.md b/docs/CC-INTERNALS.md @@ -555,7 +555,10 @@ beat seven.) ### Globals and data ```scheme -(cg-emit-global cg sym init-bv-or-#f) ; init-bv: bytes for .data, or #f for .bss +(cg-emit-global cg sym init) ; init = #f (zero-init in .bss) + ; | (piece ...) in .data +;; piece := <bytevector> — raw bytes +;; | (label-ref . <label-bv>) — 8-byte slot holding &label (cg-emit-extern cg sym) ; declare without defining (cg-intern-string cg bv-content) -> bv-label ; idempotent; used internally by cg-push-string ``` diff --git a/docs/CC-PUNCHLIST.md b/docs/CC-PUNCHLIST.md @@ -212,40 +212,55 @@ primitive to capture the old rval before the store. See braces and returns `#f`, dropping all initializer data. `cg-emit-global` accepts an init bv but is never given one. -- [ ] **Scalar global with constant initializer** - - cg: `cc-cg/NN-init-scalar-global.scm` — emit `int g = 42` via cg - API; in `main`, return g. - - parse: `cc-parse/NN-init-scalar-global.c` - - Needs: parser builds an N-byte LE bv from the const expression and - passes to `cg-emit-global`. - -- [ ] **Scalar global with address initializer (`int *p = &x;`)** - - cg: `cc-cg/NN-init-addr.scm` - - parse: `cc-parse/NN-init-addr.c` - - Needs: `cg-emit-global` accepts a structured init (bytes + - label-references) and emits `&label` form to `cg-data`. - -- [ ] **Array global from element list** - - cg: `cc-cg/NN-init-array-list.scm` — `int a[3] = {1,2,4};` - - parse: `cc-parse/NN-init-array-list.c` - -- [ ] **Array global from string literal** - - parse: `cc-parse/NN-init-array-str.c` — `char s[]="abc"; return s[1];` - → exit 98. - -- [ ] **Struct global, positional init** - - parse: `cc-parse/NN-init-struct-pos.c` - -- [ ] **Struct global, designated init (`.field = …`)** - - parse: `cc-parse/NN-init-struct-desig.c` - - Needs: required by tcc.c per CC.md §Variable initializers. - -- [ ] **Local array initializer** - - parse: `cc-parse/NN-init-local-array.c` - - Needs: parser emits per-element store sequence into the frame slot. - -- [ ] **Local struct initializer** - - parse: `cc-parse/NN-init-local-struct.c` +- [x] **Scalar global with constant initializer** + - cg: `cc-cg/49-init-scalar-global.scm` + - parse: `cc-parse/49-init-scalar-global.c` + - Done: cg-emit-global now consumes a list of pieces (bytevectors + or `(label-ref . label-bv)` pairs); parser's parse-init-global + builds N-byte LE bv via %int->le-bv from a const expression. + +- [x] **Scalar global with address initializer (`int *p = &x;`)** + - cg: `cc-cg/50-init-addr.scm` + - parse: `cc-parse/50-init-addr.c` + - Done: %const-init-piece recognises `&IDENT` and bare-IDENT for + fn / static / extern symbols, emitting `(label-ref . cc__name)`. + +- [x] **Array global from element list** + - cg: `cc-cg/51-init-array-list.scm` + - parse: `cc-parse/51-init-array-list.c` + - Done: %parse-init-array-list walks brace lists; element types + drive bv width; array-name → label-ref decay so `int *p = a;` + works as init too. + +- [x] **Array global from string literal** + - cg: `cc-cg/52-init-array-str.scm` + - parse: `cc-parse/52-init-array-str.c` + - Done: parse-init-global recognises STR for char[] target; + inferred-length arrays (`T a[]`) get patched with the literal + length. + +- [x] **Struct global, positional init** + - cg: `cc-cg/53-init-struct-pos.scm` + - parse: `cc-parse/53-init-struct-pos.c` + - Done: %parse-init-struct-list walks fields positionally; trailing + fields zero-padded. + +- [x] **Struct global, designated init (`.field = …`)** + - cg: `cc-cg/54-init-struct-desig.scm` + - parse: `cc-parse/54-init-struct-desig.c` + - Done: same %parse-init-struct-list handles `.name = …` form. + Also: cg-arith-conv now leaves pointer-typed operands alone so + `*(p + N)` scales correctly when one side is ptr. + +- [x] **Local array initializer** + - parse: `cc-parse/55-init-local-array.c` + - Done: parse-init-local-aggregate emits per-element store ops at + slot+(i*esize); zero-pads trailing slots when declared length + exceeds initializer count. + +- [x] **Local struct initializer** + - parse: `cc-parse/56-init-local-struct.c` + - Done: same per-field store sequence; designated form supported. ### F. Control flow extensions @@ -329,13 +344,12 @@ responsible for arranging compatible types in the two branches. ### I. Storage classes -- [ ] **Block-scope `static` lives in bss/data, not on the stack** - - cg: `cc-cg/NN-block-static.scm` — counter that survives across calls. - - parse: `cc-parse/NN-block-static.c` - - Needs: `parse.scm` `handle-decl` checks `sto = 'static'` *before* - branching on `(ps-fn-ctx ps)` and routes static block-scope to - `cg-emit-global`. Mangling adds the function name to avoid - cross-function collisions (e.g. `cc__<fn>__<var>`). +- [x] **Block-scope `static` lives in bss/data, not on the stack** + - cg: `cc-cg/57-block-static.scm` + - parse: `cc-parse/57-block-static.c` + - Done: handle-decl gates on `sto = 'static'` *before* the + file-vs-block branch and routes block-scope statics to + cg-emit-global with a `cc__<fn>__<var>` mangled label. ### J. Driver / envelope @@ -420,13 +434,13 @@ responsible for arranging compatible types in the two branches. `a[i]` decays the param's loaded ptr-rval and scales the index correctly. -- [ ] **Array of function pointers initialized with named functions** - - parse: `cc-parse/NN-fnptr-tab.c` — `int f1(){return 1;} - int f2(){return 2;} int (*tab[])() = {f1, f2}; - return tab[0]() + tab[1]()*10;` → exit 21. - - Needs: composes §E.4 (array list init) with §E.2 (address init); - parser admits a fn name as an initializer expression that - evaluates to a label reference. +- [x] **Array of function pointers initialized with named functions** + - cg: `cc-cg/58-fnptr-tab.scm` + - parse: `cc-parse/58-fnptr-tab.c` + - Done: composes §E.3's array list init with §E.2's label-ref; + %const-init-piece's bare-IDENT branch already covers `fn` syms. + Parse fixture uses an explicit typedef for the array element + because `int (*tab[])()` declarators are owned elsewhere. ## Phase milestones (CC.md §Validation) diff --git a/tests/cc-cg/49-init-scalar-global.expected-exit b/tests/cc-cg/49-init-scalar-global.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/cc-cg/49-init-scalar-global.scm b/tests/cc-cg/49-init-scalar-global.scm @@ -0,0 +1,19 @@ +;; tests/cc-cg/49-init-scalar-global.scm — emit a global int with a +;; constant initializer; main returns its value. §E.1 of CC-PUNCHLIST. +;; +;; Models: int g = 42; int main(void) { return g; } +;; Runtime: exits 42. + +(let* ((cg (cg-init)) + (g (%sym "g" 'var 'static %t-i32 #f)) + ;; structured init: a single 4-byte LE bytevector piece for 42. + (bv4 (make-bytevector 4 0)) + (_ (bytevector-u8-set! bv4 0 42)) + (init (list bv4))) + (cg-emit-global cg g init) + (cg-fn-begin cg "main" '() %t-i32) + (cg-push-sym cg g) + (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/50-init-addr.expected-exit b/tests/cc-cg/50-init-addr.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-cg/50-init-addr.scm b/tests/cc-cg/50-init-addr.scm @@ -0,0 +1,27 @@ +;; tests/cc-cg/50-init-addr.scm — global pointer initialized to address +;; of another global. §E.2. +;; +;; Models: +;; int x = 7; +;; int *p = &x; +;; int main(void) { return *p; } +;; Runtime: exits 7. + +(let* ((cg (cg-init)) + ;; int x = 7 + (x (%sym "x" 'var 'static %t-i32 #f)) + (bvx (make-bytevector 4 0)) + (_x (bytevector-u8-set! bvx 0 7)) + ;; int *p = &x -> structured init: a single label-ref to cc__x + (p (%sym "p" 'var 'static (%ctype 'ptr 8 8 %t-i32) #f))) + (cg-emit-global cg x (list bvx)) + (cg-emit-global cg p (list (cons 'label-ref "cc__x"))) + (cg-fn-begin cg "main" '() %t-i32) + ;; return *p + (cg-push-sym cg p) + (cg-load cg) ; rval ptr-to-int (the value of p) + (cg-push-deref cg) ; lval int through pointer + (cg-load cg) ; rval int + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/51-init-array-list.expected-exit b/tests/cc-cg/51-init-array-list.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-cg/51-init-array-list.scm b/tests/cc-cg/51-init-array-list.scm @@ -0,0 +1,32 @@ +;; tests/cc-cg/51-init-array-list.scm — global int[3] = {1,2,4}; +;; main returns a[0]+a[1]+a[2]. §E.3. +;; Runtime: exits 7. + +(let* ((cg (cg-init)) + (elem %t-i32) + (aty (%ctype 'arr 12 4 (cons elem 3))) + (a (%sym "a" 'var 'static aty #f)) + ;; Three 4-byte LE pieces. + (e1 (make-bytevector 4 0)) (_1 (bytevector-u8-set! e1 0 1)) + (e2 (make-bytevector 4 0)) (_2 (bytevector-u8-set! e2 0 2)) + (e3 (make-bytevector 4 0)) (_3 (bytevector-u8-set! e3 0 4))) + (cg-emit-global cg a (list e1 e2 e3)) + (cg-fn-begin cg "main" '() %t-i32) + ;; Sum a[0]+a[1]+a[2]. We avoid array→ptr decay (which is a + ;; parser-side concern) by taking &a explicitly, casting to int*, + ;; then doing ordinary pointer arithmetic. + (let ((emit-elem + (lambda (idx) + (cg-push-sym cg a) (cg-take-addr cg) ; rval ptr-to-arr + (cg-cast cg (%ctype 'ptr 8 8 elem)) ; relabel as int* + (cg-push-imm cg %t-i32 idx) + (cg-binop cg 'add) ; int* (scaling x 4) + (cg-push-deref cg) (cg-load cg)))) ; rval int + (emit-elem 0) + (emit-elem 1) + (cg-binop cg 'add) + (emit-elem 2) + (cg-binop cg 'add)) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/52-init-array-str.expected-exit b/tests/cc-cg/52-init-array-str.expected-exit @@ -0,0 +1 @@ +97 diff --git a/tests/cc-cg/52-init-array-str.scm b/tests/cc-cg/52-init-array-str.scm @@ -0,0 +1,24 @@ +;; tests/cc-cg/52-init-array-str.scm — global char[] from a string-literal +;; initializer. §E.4. +;; +;; Models: char s[] = "abc"; return *s; (== 'a' == 97) +;; The init is a single 4-byte bv {97,98,99,0}. + +(let* ((cg (cg-init)) + (elem %t-i8) + (aty (%ctype 'arr 4 1 (cons elem 4))) + (s (%sym "s" 'var 'static aty #f)) + (bv (make-bytevector 4 0)) + (_a (bytevector-u8-set! bv 0 97)) + (_b (bytevector-u8-set! bv 1 98)) + (_c (bytevector-u8-set! bv 2 99))) + (cg-emit-global cg s (list bv)) + (cg-fn-begin cg "main" '() %t-i32) + ;; Read the first byte from cc__s. Just take address, cast to char*, + ;; deref. + (cg-push-sym cg s) (cg-take-addr cg) + (cg-cast cg (%ctype 'ptr 8 8 elem)) + (cg-push-deref cg) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/53-init-struct-pos.expected-exit b/tests/cc-cg/53-init-struct-pos.expected-exit @@ -0,0 +1 @@ +43 diff --git a/tests/cc-cg/53-init-struct-pos.scm b/tests/cc-cg/53-init-struct-pos.scm @@ -0,0 +1,36 @@ +;; tests/cc-cg/53-init-struct-pos.scm — global struct {int a; int b;} +;; with positional initializer. §E.5. +;; +;; Models: struct S { int a; int b; }; struct S s = {3, 4}; return s.a + s.b*10; +;; main returns 43. +;; +;; cc-cg drives the cg API directly; we read a (offset 0) and b (offset 4) +;; via &s + offset and a cast to int*. + +(let* ((cg (cg-init)) + ;; Build struct ctype: { int a; int b; } + (sty (%ctype 'struct 8 4 + (list "S" #t + (list (list "a" %t-i32 0) + (list "b" %t-i32 4))))) + (s (%sym "s" 'var 'static sty #f)) + ;; Init: 3 (4 bytes LE) | 4 (4 bytes LE) + (bvA (make-bytevector 4 0)) (_a (bytevector-u8-set! bvA 0 3)) + (bvB (make-bytevector 4 0)) (_b (bytevector-u8-set! bvB 0 4))) + (cg-emit-global cg s (list bvA bvB)) + (cg-fn-begin cg "main" '() %t-i32) + (let ((load-field + (lambda (off) + (cg-push-sym cg s) (cg-take-addr cg) + (cg-cast cg (%ctype 'ptr 8 8 %t-i32)) + (cg-push-imm cg %t-i32 off) + (cg-binop cg 'add) + (cg-push-deref cg) (cg-load cg)))) + (load-field 0) ; s.a == 3 + (load-field 1) ; s.b == 4 (4 bytes / 4 = +1 in int*-units) + (cg-push-imm cg %t-i32 10) + (cg-binop cg 'mul) + (cg-binop cg 'add)) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/54-init-struct-desig.expected-exit b/tests/cc-cg/54-init-struct-desig.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-cg/54-init-struct-desig.scm b/tests/cc-cg/54-init-struct-desig.scm @@ -0,0 +1,26 @@ +;; tests/cc-cg/54-init-struct-desig.scm — global struct designated init. +;; §E.6. +;; +;; Models: struct S { int a; int b; }; struct S s = {.b = 7}; +;; Read b. With proper init, b == 7 and a == 0. +;; main returns *p where p = (int*)&s + 1 (== &s.b == 7). + +(let* ((cg (cg-init)) + (sty (%ctype 'struct 8 4 + (list "S" #t + (list (list "a" %t-i32 0) + (list "b" %t-i32 4))))) + (s (%sym "s" 'var 'static sty #f)) + ;; Designated init: zero pad for a, then 7 for b. + (pad (make-bytevector 4 0)) + (bvB (make-bytevector 4 0)) (_b (bytevector-u8-set! bvB 0 7))) + (cg-emit-global cg s (list pad bvB)) + (cg-fn-begin cg "main" '() %t-i32) + (cg-push-sym cg s) (cg-take-addr cg) + (cg-cast cg (%ctype 'ptr 8 8 %t-i32)) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-push-deref cg) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/57-block-static.expected-exit b/tests/cc-cg/57-block-static.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-cg/57-block-static.scm b/tests/cc-cg/57-block-static.scm @@ -0,0 +1,45 @@ +;; tests/cc-cg/57-block-static.scm — block-scope `static int n;` should +;; live in .bss/.data, not on the stack. §I. +;; +;; Models: +;; int incr(void) { static int n = 0; n = n + 1; return n; } +;; int main(void) { incr(); incr(); return incr(); } (== 3) +;; +;; Two functions could declare `static int n;` independently; mangling +;; with the function name avoids collisions. Here we lay down a +;; pre-mangled cc__incr__n directly via the cg API. + +(let* ((cg (cg-init)) + (n (%sym "n" 'var 'static %t-i32 (string->symbol "cc__incr__n")))) + ;; cg-emit-global expects the slot to be a bv (the emitted label name). + ;; Build n with a string slot directly. + (let* ((nsym (%sym "n" 'var 'static %t-i32 #f)) + (zero (make-bytevector 4 0))) + ;; n is sym-named "n"; cg-mangle-global prefixes with "cc__" → "cc__n". + ;; That collides if multiple fns name `n`; the parser side adds the + ;; fn-name segment via handle-decl. For the cg fixture we just use + ;; the unprefixed sym-name. + (cg-emit-global cg nsym (list zero)) + + ;; int incr(void) { n = n + 1; return n; } + (cg-fn-begin cg "incr" '() %t-i32) + (cg-push-sym cg nsym) ; lhs lval n + (cg-push-sym cg nsym) (cg-load cg) ; rhs n + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + (cg-push-sym cg nsym) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + + ;; int main(void) { incr(); incr(); return incr(); } + (cg-fn-begin cg "main" '() %t-i32) + (let ((incr-sym (%sym "incr" 'fn 'extern + (%ctype 'fn -1 -1 (list %t-i32 '() #f)) + "cc__incr"))) + (cg-push-sym cg incr-sym) (cg-call cg 0 #t) (cg-pop cg) + (cg-push-sym cg incr-sym) (cg-call cg 0 #t) (cg-pop cg) + (cg-push-sym cg incr-sym) (cg-call cg 0 #t) + (cg-return cg)) + (cg-fn-end cg)) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/58-fnptr-tab.expected-exit b/tests/cc-cg/58-fnptr-tab.expected-exit @@ -0,0 +1 @@ +21 diff --git a/tests/cc-cg/58-fnptr-tab.scm b/tests/cc-cg/58-fnptr-tab.scm @@ -0,0 +1,49 @@ +;; tests/cc-cg/58-fnptr-tab.scm — array of function pointers initialized +;; with named functions. §L.3. +;; +;; Models: +;; int f1(void) { return 1; } +;; int f2(void) { return 2; } +;; int (*tab[])(void) = { f1, f2 }; +;; int main(void) { return tab[0]() + tab[1]() * 10; } (== 21) +;; +;; tab[i] is read via &tab + i (in fn-ptr units == 8 bytes), then a +;; %callr indirect call. + +(let* ((cg (cg-init)) + (fnty (%ctype 'fn -1 -1 (list %t-i32 '() #f))) + (fnptr (%ctype 'ptr 8 8 fnty))) + ;; int f1(void) { return 1; } + (cg-fn-begin cg "f1" '() %t-i32) + (cg-push-imm cg %t-i32 1) + (cg-return cg) + (cg-fn-end cg) + ;; int f2(void) { return 2; } + (cg-fn-begin cg "f2" '() %t-i32) + (cg-push-imm cg %t-i32 2) + (cg-return cg) + (cg-fn-end cg) + ;; tab[2] = { &f1, &f2 } — two label-ref pieces. + (let* ((aty (%ctype 'arr 16 8 (cons fnptr 2))) + (tab (%sym "tab" 'var 'static aty #f))) + (cg-emit-global cg tab (list (cons 'label-ref "cc__f1") + (cons 'label-ref "cc__f2"))) + ;; int main(void) { return tab[0]() + tab[1]() * 10; } + (cg-fn-begin cg "main" '() %t-i32) + (let ((pp (%ctype 'ptr 8 8 fnptr)) ; ptr to fnptr = pointee size 8 + (emit-call + (lambda (idx ppt) + (cg-push-sym cg tab) (cg-take-addr cg) + (cg-cast cg ppt) ; relabel to ptr-to-fnptr + (cg-push-imm cg %t-i32 idx) + (cg-binop cg 'add) ; ptr arith scales by 8 + (cg-push-deref cg) (cg-load cg) + (cg-call cg 0 #t)))) + (emit-call 0 pp) + (emit-call 1 pp) + (cg-push-imm cg %t-i32 10) + (cg-binop cg 'mul) + (cg-binop cg 'add)) + (cg-return cg) + (cg-fn-end cg)) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-parse/49-init-scalar-global.c b/tests/cc-parse/49-init-scalar-global.c @@ -0,0 +1,4 @@ +// tests/cc-parse/49-init-scalar-global.c — scalar global with constant +// initializer. §E.1. +int g = 42; +int main(void) { return g; } diff --git a/tests/cc-parse/49-init-scalar-global.expected-exit b/tests/cc-parse/49-init-scalar-global.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/cc-parse/50-init-addr.c b/tests/cc-parse/50-init-addr.c @@ -0,0 +1,4 @@ +// tests/cc-parse/50-init-addr.c — global pointer initialized to &global. §E.2. +int x = 7; +int *p = &x; +int main(void) { return *p; } diff --git a/tests/cc-parse/50-init-addr.expected-exit b/tests/cc-parse/50-init-addr.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-parse/51-init-array-list.c b/tests/cc-parse/51-init-array-list.c @@ -0,0 +1,8 @@ +// tests/cc-parse/51-init-array-list.c — global int array initialised +// from element list. §E.3. +// +// Array indexing (a[i]) is owned by Agent 2 (§D). Read each element +// via a manually-decayed pointer and pointer arithmetic. +int a[3] = {1, 2, 4}; +int *p = a; +int main(void) { return *p + *(p + 1) + *(p + 2); } diff --git a/tests/cc-parse/51-init-array-list.expected-exit b/tests/cc-parse/51-init-array-list.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-parse/52-init-array-str.c b/tests/cc-parse/52-init-array-str.c @@ -0,0 +1,7 @@ +// tests/cc-parse/52-init-array-str.c — global char[] from a string +// literal initializer. §E.4. +// +// Returns the first byte of the array (97 = 'a'). +char s[] = "abc"; +char *p = s; +int main(void) { return *p; } diff --git a/tests/cc-parse/52-init-array-str.expected-exit b/tests/cc-parse/52-init-array-str.expected-exit @@ -0,0 +1 @@ +97 diff --git a/tests/cc-parse/53-init-struct-pos.c b/tests/cc-parse/53-init-struct-pos.c @@ -0,0 +1,11 @@ +// tests/cc-parse/53-init-struct-pos.c — global struct positional init. §E.5. +// +// Struct member access (.a/.b) is currently stubbed to offset 0 in +// parse-postfix-rest (Agent 2's §D). To stay inside the working surface, +// we treat &s as int* and read the first field by deref. +struct S { int a; int b; }; +struct S s = {3, 4}; +int main(void) { + int *p = (int *)&s; + return *p; +} diff --git a/tests/cc-parse/53-init-struct-pos.expected-exit b/tests/cc-parse/53-init-struct-pos.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-parse/54-init-struct-desig.c b/tests/cc-parse/54-init-struct-desig.c @@ -0,0 +1,10 @@ +// tests/cc-parse/54-init-struct-desig.c — designated struct init. §E.6. +// +// Read field b through pointer arithmetic; member-access codegen is +// owned elsewhere (§D). +struct S { int a; int b; }; +struct S s = { .b = 7 }; +int main(void) { + int *p = (int *)&s; + return *(p + 1); +} diff --git a/tests/cc-parse/54-init-struct-desig.expected-exit b/tests/cc-parse/54-init-struct-desig.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-parse/55-init-local-array.c b/tests/cc-parse/55-init-local-array.c @@ -0,0 +1,11 @@ +// tests/cc-parse/55-init-local-array.c — local array initializer. §E.7. +// +// Stack-allocated int[3] initialised inside main via a brace list; we +// read element 1 through a manually-decayed pointer. Whatever the +// parser does to lay down the initializer must hit the right offsets +// — here the test asserts that writes to a[0] don't bleed into a[1]. +int main(void) { + int a[3] = {10, 20, 30}; + int *p = (int *)&a; + return *(p + 1); +} diff --git a/tests/cc-parse/55-init-local-array.expected-exit b/tests/cc-parse/55-init-local-array.expected-exit @@ -0,0 +1 @@ +20 diff --git a/tests/cc-parse/56-init-local-struct.c b/tests/cc-parse/56-init-local-struct.c @@ -0,0 +1,11 @@ +// tests/cc-parse/56-init-local-struct.c — local struct initializer. §E.8. +// +// Stack-allocated struct S { int a; int b; } initialised via brace list. +// Reads field b via pointer arithmetic — member-access codegen is +// owned by Agent 2 (§D). +struct S { int a; int b; }; +int main(void) { + struct S s = {3, 9}; + int *p = (int *)&s; + return *(p + 1); +} diff --git a/tests/cc-parse/56-init-local-struct.expected-exit b/tests/cc-parse/56-init-local-struct.expected-exit @@ -0,0 +1 @@ +9 diff --git a/tests/cc-parse/57-block-static.c b/tests/cc-parse/57-block-static.c @@ -0,0 +1,22 @@ +// tests/cc-parse/57-block-static.c — block-scope static survives across +// calls and does not collide across functions. §I. +// +// Two distinct `static int n` instances must mangle to distinct labels. +int incr(void) { + static int n = 0; + n = n + 1; + return n; +} +int decr(void) { + static int n = 100; + n = n - 1; + return n; +} +int main(void) { + incr(); + decr(); + decr(); + // incr() returns 1; second decr returned 98. + // Result = incr() * 10 + decr() = 2 * 10 + 97 = 117. + return incr() * 10 + decr(); +} diff --git a/tests/cc-parse/57-block-static.expected-exit b/tests/cc-parse/57-block-static.expected-exit @@ -0,0 +1 @@ +117 diff --git a/tests/cc-parse/58-fnptr-tab.c b/tests/cc-parse/58-fnptr-tab.c @@ -0,0 +1,15 @@ +// tests/cc-parse/58-fnptr-tab.c — array of fn pointers initialised +// with named functions. §L.3. +// +// `int (*tab[])()` is currently outside parse-fn-params (Agent 2), +// so we use an explicit typedef. Member access via tab[i] is also +// pending; read elements via pointer arithmetic over &tab as +// fnptr*. +typedef int (*fnptr_t)(void); +int f1(void) { return 1; } +int f2(void) { return 2; } +fnptr_t tab[] = { f1, f2 }; +int main(void) { + fnptr_t *p = (fnptr_t *)&tab; + return (*(p + 0))() + (*(p + 1))() * 10; +} diff --git a/tests/cc-parse/58-fnptr-tab.expected-exit b/tests/cc-parse/58-fnptr-tab.expected-exit @@ -0,0 +1 @@ +21