boot2

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

cc.scm (363129B)


      1 ;; cc/util.scm — leaf helpers. Depends only on the scheme1 prelude.
      2 
      3 ;; --------------------------------------------------------------------
      4 ;; byte-oriented helpers. R7RS strings and bytevectors are disjoint;
      5 ;; micro+boot2's bytes? / bytes=? and bytevector operations provide the
      6 ;; explicit bridge used by this byte-oriented compiler.
      7 ;; --------------------------------------------------------------------
      8 (define (bv= a b) (bytes=? a b))
      9 
     10 (define (bv-prefix? p s)
     11   ;; Is s a bv that starts with the bytes of p?
     12   (let ((plen (bytevector-length p))
     13         (slen (bytevector-length s)))
     14     (if (< slen plen)
     15         #f
     16         (let loop ((i 0))
     17           (cond ((= i plen) #t)
     18                 ((= (bytevector-u8-ref p i) (bytevector-u8-ref s i))
     19                  (loop (+ i 1)))
     20                 (else #f))))))
     21 
     22 (define (bv-find bv byte from)
     23   ;; Linear scan for the first byte == `byte` at index >= from.
     24   ;; Returns the index, or #f if not found.
     25   (let ((n (bytevector-length bv)))
     26     (let loop ((i from))
     27       (cond ((>= i n) #f)
     28             ((= (bytevector-u8-ref bv i) byte) i)
     29             (else (loop (+ i 1)))))))
     30 
     31 (define (bv-slice bv start end)
     32   ;; Fresh copy of bytes in [start, end). bytevector-copy already does
     33   ;; this in scheme1 (3-arg form returns a fresh bv).
     34   (bytevector-copy bv start end))
     35 
     36 (define (bv-of-byte b) (make-bytevector 1 b))
     37 
     38 (define (bv-cat lst-of-bv)
     39   ;; Concat a list of bytevectors with one allocation. bytevector-append
     40   ;; is variadic, so apply does this in a single linear pass.
     41   (apply bytevector-append lst-of-bv))
     42 
     43 (define (bv->fixnum bv radix)
     44   ;; (values ok? val) — #t/n on parse, #f/#f on fail.
     45   ;; string->number is pure and returns #f on parse failure.
     46   (let ((n (string->number bv radix)))
     47     (if n (values #t n) (values #f #f))))
     48 
     49 (define (fixnum->bv n radix)
     50   (bytevector-append (number->string n radix)))
     51 
     52 ;; --------------------------------------------------------------------
     53 ;; lists / alists
     54 ;; --------------------------------------------------------------------
     55 (define (key=? a b)
     56   (if (and (bytes? a) (bytes? b)) (bytes=? a b) (equal? a b)))
     57 
     58 (define (assoc/key key al)
     59   (cond ((null? al) #f)
     60         ((key=? key (car (car al))) (car al))
     61         (else (assoc/key key (cdr al)))))
     62 
     63 (define (alist-ref    key al) (let ((p (assoc/key key al))) (and p (cdr p))))
     64 (define (alist-ref/eq key al) (let ((p (assq  key al))) (and p (cdr p))))
     65 
     66 (define (alist-set key val al) (cons (cons key val) al))
     67 
     68 ;; Compiler-wide maps use scheme1's private GC-aware open-addressed table.
     69 ;; Bytevector/string keys compare by bytes, matching key=? above; symbol and
     70 ;; integer keys compare by identity. Small per-call environments remain alists
     71 ;; because constructing a table for a handful of macro parameters would cost
     72 ;; more than scanning them.
     73 (define (alist->hash al)
     74   (let ((h (%make-hash-table 16)))
     75     (let loop ((xs al))
     76       (cond ((null? xs) h)
     77             (else
     78              (%hash-set! h (car (car xs)) (cdr (car xs)))
     79              (loop (cdr xs)))))))
     80 
     81 (define (alist-update key f al)
     82   ;; Functional update by byte-aware key equality. If found, replace its value with
     83   ;; (f old-val). If not found, prepend (cons key (f #f)) so callers
     84   ;; can use this as upsert-with-default.
     85   (let loop ((xs al) (acc '()))
     86     (cond ((null? xs)
     87            (cons (cons key (f #f)) (reverse acc)))
     88           ((key=? (car (car xs)) key)
     89            (append (reverse acc)
     90                    (cons (cons key (f (cdr (car xs))))
     91                          (cdr xs))))
     92           (else (loop (cdr xs) (cons (car xs) acc))))))
     93 
     94 (define (any p xs)
     95   (cond ((null? xs) #f)
     96         ((p (car xs)) #t)
     97         (else (any p (cdr xs)))))
     98 
     99 (define (every p xs)
    100   (cond ((null? xs) #t)
    101         ((p (car xs)) (every p (cdr xs)))
    102         (else #f)))
    103 
    104 (define (count p xs)
    105   (let loop ((xs xs) (n 0))
    106     (cond ((null? xs) n)
    107           ((p (car xs)) (loop (cdr xs) (+ n 1)))
    108           (else (loop (cdr xs) n)))))
    109 
    110 ;; --------------------------------------------------------------------
    111 ;; ints
    112 ;; --------------------------------------------------------------------
    113 (define (min3 a b c)                 (min a (min b c)))
    114 (define (align-up n k)
    115   ;; round n up to the nearest multiple of k (k must be a power of 2)
    116   (let ((mask (- k 1)))
    117     (bit-and (+ n mask) (bit-not mask))))
    118 
    119 ;; --------------------------------------------------------------------
    120 ;; Fixed-width C integer carrier
    121 ;; --------------------------------------------------------------------
    122 ;;
    123 ;; scheme1 fixnums have three tag bits, so a P1-32 host cannot carry every
    124 ;; 32-bit C value directly (and a P1-64 host cannot carry every u64 either).
    125 ;; Keep out-of-range C values as an exact, little-endian 64-bit bytevector.
    126 ;; Small results normalize back to fixnums so sizes, offsets, and loop counts
    127 ;; keep using the cheap representation. All helpers accept either shape.
    128 
    129 (define-record-type c-value
    130   (%c-value bytes)
    131   c-value?
    132   (bytes c-value-bytes))
    133 
    134 (define-record-type c-int-lit
    135   (%c-int-lit value unsigned? long-count decimal?)
    136   c-int-lit?
    137   (value      c-int-lit-value)
    138   (unsigned?  c-int-lit-unsigned?)
    139   (long-count c-int-lit-long-count)
    140   (decimal?   c-int-lit-decimal?))
    141 
    142 (define %C-VALUE-BYTES 8)
    143 
    144 (define (%c-value-coerce v)
    145   (cond
    146     ((c-int-lit? v) (%c-value-coerce (c-int-lit-value v)))
    147     ((c-value? v) v)
    148     (else
    149      (let ((bv (make-bytevector %C-VALUE-BYTES 0)))
    150        (let loop ((i 0) (n v))
    151          (cond
    152            ((= i %C-VALUE-BYTES) (%c-value bv))
    153            (else
    154             (bytevector-u8-set! bv i (bit-and n 255))
    155             (loop (+ i 1) (arithmetic-shift n -8)))))))))
    156 
    157 (define (%c-value-copy v)
    158   (let ((bv (c-value-bytes (%c-value-coerce v))))
    159     (%c-value (bytevector-copy bv 0 (bytevector-length bv)))))
    160 
    161 (define (%c-value-zero) (%c-value (make-bytevector %C-VALUE-BYTES 0)))
    162 
    163 (define (%c-value-zero? v)
    164   (cond
    165     ((not (c-value? v)) (= v 0))
    166     (else
    167      (let ((bv (c-value-bytes v)))
    168        (let loop ((i 0))
    169          (cond ((= i %C-VALUE-BYTES) #t)
    170                ((not (= (bytevector-u8-ref bv i) 0)) #f)
    171                (else (loop (+ i 1)))))))))
    172 
    173 (define (%c-value-positive-small cv)
    174   ;; Conservative common subset of both Scheme fixnum ranges: [0,2^27).
    175   (let ((bv (c-value-bytes cv)))
    176     (cond
    177       ((or (not (= (bytevector-u8-ref bv 7) 0))
    178            (not (= (bytevector-u8-ref bv 6) 0))
    179            (not (= (bytevector-u8-ref bv 5) 0))
    180            (not (= (bytevector-u8-ref bv 4) 0))
    181            (>= (bytevector-u8-ref bv 3) 8)) #f)
    182       (else
    183        (let loop ((i 3) (n 0))
    184          (cond ((< i 0) n)
    185                (else
    186                 (loop (- i 1)
    187                       (+ (* n 256) (bytevector-u8-ref bv i))))))))))
    188 
    189 (define (%c-value-negate/raw v)
    190   (let* ((src (c-value-bytes (%c-value-coerce v)))
    191          (out (make-bytevector %C-VALUE-BYTES 0)))
    192     (let loop ((i 0) (carry 1))
    193       (cond
    194         ((= i %C-VALUE-BYTES) (%c-value out))
    195         (else
    196          (let ((z (+ (- 255 (bytevector-u8-ref src i)) carry)))
    197            (bytevector-u8-set! out i (bit-and z 255))
    198            (loop (+ i 1) (if (> z 255) 1 0))))))))
    199 
    200 (define (%c-value-normalize v)
    201   (cond
    202     ((not (c-value? v)) v)
    203     (else
    204      (let ((p (%c-value-positive-small v)))
    205        (cond
    206          (p p)
    207          ((>= (bytevector-u8-ref (c-value-bytes v) 7) 128)
    208           (let* ((mag (%c-value-negate/raw v))
    209                  (m (%c-value-positive-small mag)))
    210             (if m (- 0 m) v)))
    211          (else v))))))
    212 
    213 (define (%c-value-normalize-unsigned v)
    214   ;; Lexer accumulation starts from an unsigned source spelling. Preserve
    215   ;; values with bit 63 set as c-values rather than collapsing (for example)
    216   ;; 18446744073709551615 to the signed fixnum -1.
    217   (cond
    218     ((not (c-value? v)) v)
    219     (else
    220      (let ((p (%c-value-positive-small v)))
    221        (if p p v)))))
    222 
    223 (define (%c-value->fixnum v context)
    224   (let ((n (%c-value-normalize v)))
    225     (cond ((c-value? n) (die #f context "value does not fit host fixnum"))
    226           (else n))))
    227 
    228 (define (%c-value-mul-small-add v scale addend)
    229   ;; Used by the lexer. scale <= 16 and addend <= 15, so every step is
    230   ;; far below even RV32's fixnum ceiling.
    231   (let* ((src (c-value-bytes (%c-value-coerce v)))
    232          (out (make-bytevector %C-VALUE-BYTES 0)))
    233     (let loop ((i 0) (carry addend))
    234       (cond
    235         ((= i %C-VALUE-BYTES) (%c-value-normalize-unsigned (%c-value out)))
    236         (else
    237          (let ((z (+ (* (bytevector-u8-ref src i) scale) carry)))
    238            (bytevector-u8-set! out i (remainder z 256))
    239            (loop (+ i 1) (quotient z 256))))))))
    240 
    241 (define (%c-value-add a b)
    242   (let* ((av (c-value-bytes (%c-value-coerce a)))
    243          (bv (c-value-bytes (%c-value-coerce b)))
    244          (out (make-bytevector %C-VALUE-BYTES 0)))
    245     (let loop ((i 0) (carry 0))
    246       (cond
    247         ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
    248         (else
    249          (let ((z (+ (bytevector-u8-ref av i)
    250                      (bytevector-u8-ref bv i) carry)))
    251            (bytevector-u8-set! out i (remainder z 256))
    252            (loop (+ i 1) (quotient z 256))))))))
    253 
    254 (define (%c-value-negate v)
    255   (%c-value-normalize (%c-value-negate/raw v)))
    256 
    257 (define (%c-value-sub a b)
    258   (%c-value-add a (%c-value-negate/raw b)))
    259 
    260 (define (%c-value-mul a b)
    261   (let* ((av (c-value-bytes (%c-value-coerce a)))
    262          (bv (c-value-bytes (%c-value-coerce b)))
    263          (out (make-bytevector %C-VALUE-BYTES 0)))
    264     (let outer ((i 0))
    265       (cond
    266         ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
    267         (else
    268          (let inner ((j 0) (carry 0))
    269            (cond
    270              ((= (+ i j) %C-VALUE-BYTES) (outer (+ i 1)))
    271              (else
    272               (let* ((k (+ i j))
    273                      (z (+ (bytevector-u8-ref out k)
    274                            (* (bytevector-u8-ref av i)
    275                               (bytevector-u8-ref bv j))
    276                            carry)))
    277                 (bytevector-u8-set! out k (remainder z 256))
    278                 (inner (+ j 1) (quotient z 256)))))))))))
    279 
    280 (define (%c-value-bitop op a b)
    281   (let* ((av (c-value-bytes (%c-value-coerce a)))
    282          (bv (c-value-bytes (%c-value-coerce b)))
    283          (out (make-bytevector %C-VALUE-BYTES 0)))
    284     (let loop ((i 0))
    285       (cond
    286         ((= i %C-VALUE-BYTES) (%c-value-normalize (%c-value out)))
    287         (else
    288          (bytevector-u8-set! out i
    289            (op (bytevector-u8-ref av i) (bytevector-u8-ref bv i)))
    290          (loop (+ i 1)))))))
    291 
    292 (define (%c-value-and a b) (%c-value-bitop bit-and a b))
    293 (define (%c-value-or  a b) (%c-value-bitop bit-or  a b))
    294 (define (%c-value-xor a b) (%c-value-bitop bit-xor a b))
    295 (define (%c-value-not a)   (%c-value-xor a -1))
    296 
    297 (define (%c-value-ucmp a b)
    298   ;; -1 / 0 / 1 under unsigned 64-bit ordering.
    299   (let ((av (c-value-bytes (%c-value-coerce a)))
    300         (bv (c-value-bytes (%c-value-coerce b))))
    301     (let loop ((i (- %C-VALUE-BYTES 1)))
    302       (cond ((< i 0) 0)
    303             ((< (bytevector-u8-ref av i) (bytevector-u8-ref bv i)) -1)
    304             ((> (bytevector-u8-ref av i) (bytevector-u8-ref bv i)) 1)
    305             (else (loop (- i 1)))))))
    306 
    307 (define (%c-value-eq? a b) (= (%c-value-ucmp a b) 0))
    308 
    309 (define (%c-value-trunc v nbytes signed?)
    310   (let* ((src (c-value-bytes (%c-value-coerce v)))
    311          (out (bytevector-copy src 0 (bytevector-length src)))
    312          (fill (if (and signed? (> nbytes 0)
    313                         (>= (bytevector-u8-ref src (- nbytes 1)) 128))
    314                    255 0)))
    315     (let loop ((i nbytes))
    316       (cond ((= i %C-VALUE-BYTES)
    317              (%c-value-normalize (%c-value out)))
    318             (else
    319              (bytevector-u8-set! out i fill)
    320              (loop (+ i 1)))))))
    321 
    322 (define (%c-value-signed-negative? v nbytes)
    323   (and (> nbytes 0)
    324        (>= (bytevector-u8-ref
    325              (c-value-bytes (%c-value-coerce v)) (- nbytes 1))
    326            128)))
    327 
    328 (define (%c-value-cmp a b nbytes unsigned?)
    329   (let ((aa (%c-value-trunc a nbytes (not unsigned?)))
    330         (bb (%c-value-trunc b nbytes (not unsigned?))))
    331     (cond
    332       (unsigned? (%c-value-ucmp aa bb))
    333       ((and (%c-value-signed-negative? aa %C-VALUE-BYTES)
    334             (not (%c-value-signed-negative? bb %C-VALUE-BYTES))) -1)
    335       ((and (not (%c-value-signed-negative? aa %C-VALUE-BYTES))
    336             (%c-value-signed-negative? bb %C-VALUE-BYTES)) 1)
    337       (else (%c-value-ucmp aa bb)))))
    338 
    339 (define (%c-value-shift-one-left/raw cv)
    340   (let* ((src (c-value-bytes cv))
    341          (out (make-bytevector %C-VALUE-BYTES 0)))
    342     (let loop ((i 0) (carry 0))
    343       (cond ((= i %C-VALUE-BYTES) (%c-value out))
    344             (else
    345              (let ((z (+ (* (bytevector-u8-ref src i) 2) carry)))
    346                (bytevector-u8-set! out i (remainder z 256))
    347                (loop (+ i 1) (quotient z 256))))))))
    348 
    349 (define (%c-value-shift-one-right/raw cv arithmetic?)
    350   (let* ((src (c-value-bytes cv))
    351          (out (make-bytevector %C-VALUE-BYTES 0))
    352          (initial (if (and arithmetic?
    353                            (>= (bytevector-u8-ref src 7) 128)) 1 0)))
    354     (let loop ((i 7) (carry initial))
    355       (cond ((< i 0) (%c-value out))
    356             (else
    357              (let ((byte (bytevector-u8-ref src i)))
    358                (bytevector-u8-set! out i
    359                  (+ (quotient byte 2) (* carry 128)))
    360                (loop (- i 1) (remainder byte 2))))))))
    361 
    362 (define (%c-value-shift v count arithmetic-right?)
    363   (let ((start (%c-value-coerce v)))
    364     (cond
    365       ((>= count 64)
    366        (if (and arithmetic-right?
    367                 (%c-value-signed-negative? start %C-VALUE-BYTES))
    368            -1 0))
    369       ((<= count -64) 0)
    370       ((>= count 0)
    371        (let loop ((n count) (x start))
    372          (cond ((= n 0) (%c-value-normalize x))
    373                (else
    374                 (loop (- n 1)
    375                       (%c-value-shift-one-right/raw x arithmetic-right?))))))
    376       (else
    377        (let loop ((n (- 0 count)) (x start))
    378          (cond ((= n 0) (%c-value-normalize x))
    379                (else
    380                 (loop (- n 1) (%c-value-shift-one-left/raw x)))))))))
    381 
    382 (define (%c-value-bv-sub! dst rhs)
    383   (let loop ((i 0) (borrow 0))
    384     (cond
    385       ((= i %C-VALUE-BYTES) dst)
    386       (else
    387        (let ((z (- (bytevector-u8-ref dst i)
    388                    (bytevector-u8-ref rhs i) borrow)))
    389          (cond ((< z 0)
    390                 (bytevector-u8-set! dst i (+ z 256))
    391                 (loop (+ i 1) 1))
    392                (else
    393                 (bytevector-u8-set! dst i z)
    394                 (loop (+ i 1) 0))))))))
    395 
    396 (define (%c-value-bv-shl1! bv incoming)
    397   (let loop ((i 0) (carry incoming))
    398     (cond ((= i %C-VALUE-BYTES) bv)
    399           (else
    400            (let ((z (+ (* (bytevector-u8-ref bv i) 2) carry)))
    401              (bytevector-u8-set! bv i (remainder z 256))
    402              (loop (+ i 1) (quotient z 256)))))))
    403 
    404 (define (%c-value-bv-ucmp a b)
    405   (let loop ((i 7))
    406     (cond ((< i 0) 0)
    407           ((< (bytevector-u8-ref a i) (bytevector-u8-ref b i)) -1)
    408           ((> (bytevector-u8-ref a i) (bytevector-u8-ref b i)) 1)
    409           (else (loop (- i 1))))))
    410 
    411 (define (%c-value-udivmod a b)
    412   ;; Returns (quotient . remainder), both c-values. Binary long division
    413   ;; keeps every Scheme temporary byte-sized.
    414   (let* ((num (c-value-bytes (%c-value-coerce a)))
    415          (den (c-value-bytes (%c-value-coerce b)))
    416          (q (make-bytevector %C-VALUE-BYTES 0))
    417          (r (make-bytevector %C-VALUE-BYTES 0)))
    418     (cond ((%c-value-zero? (%c-value den))
    419            (die #f "C constant division by zero")))
    420     (let loop ((bit 63))
    421       (cond
    422         ((< bit 0)
    423          (cons (%c-value-normalize (%c-value q))
    424                (%c-value-normalize (%c-value r))))
    425         (else
    426          (let* ((byte-index (quotient bit 8))
    427                 (bit-index (remainder bit 8))
    428                 (incoming
    429                  (bit-and (arithmetic-shift
    430                             (bytevector-u8-ref num byte-index)
    431                             (- 0 bit-index))
    432                           1)))
    433            (%c-value-bv-shl1! r incoming)
    434            (cond
    435              ((>= (%c-value-bv-ucmp r den) 0)
    436               (%c-value-bv-sub! r den)
    437               (bytevector-u8-set! q byte-index
    438                 (bit-or (bytevector-u8-ref q byte-index)
    439                         (arithmetic-shift 1 bit-index)))))
    440            (loop (- bit 1))))))))
    441 
    442 (define (%c-value-divmod a b nbytes unsigned?)
    443   (let* ((aa (%c-value-trunc a nbytes (not unsigned?)))
    444          (bb (%c-value-trunc b nbytes (not unsigned?)))
    445          (aneg (and (not unsigned?)
    446                     (%c-value-signed-negative? aa %C-VALUE-BYTES)))
    447          (bneg (and (not unsigned?)
    448                     (%c-value-signed-negative? bb %C-VALUE-BYTES)))
    449          (amag (if aneg (%c-value-negate/raw aa) aa))
    450          (bmag (if bneg (%c-value-negate/raw bb) bb))
    451          (qr (%c-value-udivmod amag bmag))
    452          (q (if (if aneg (not bneg) bneg)
    453                 (%c-value-negate (car qr)) (car qr)))
    454          (r (if aneg (%c-value-negate (cdr qr)) (cdr qr))))
    455     (cons (%c-value-trunc q nbytes (not unsigned?))
    456           (%c-value-trunc r nbytes (not unsigned?)))))
    457 
    458 (define (%c-value->decimal-bv v)
    459   (cond
    460     ((not (c-value? v)) (fixnum->bv v 10))
    461     ((%c-value-zero? v) "0")
    462     (else
    463      (let* ((src (c-value-bytes v))
    464             (work (bytevector-copy src 0 (bytevector-length src))))
    465        (let digits ((acc '()))
    466          (let divide ((i 7) (rem 0))
    467            (cond
    468              ((< i 0)
    469               (let ((acc2 (cons (bv-of-byte (+ 48 rem)) acc)))
    470                 (if (= (%c-value-bv-ucmp work
    471                          (make-bytevector %C-VALUE-BYTES 0)) 0)
    472                     (bv-cat acc2)
    473                     (digits acc2))))
    474              (else
    475               (let ((z (+ (* rem 256) (bytevector-u8-ref work i))))
    476                 (bytevector-u8-set! work i (quotient z 10))
    477                 (divide (- i 1) (remainder z 10)))))))))))
    478 
    479 (define (%c-value->hex-bv v)
    480   (let* ((src (c-value-bytes (%c-value-coerce v)))
    481          (hex "0123456789abcdef")
    482          (out (make-bytevector 18 48)))
    483     (bytevector-u8-set! out 0 48)
    484     (bytevector-u8-set! out 1 120)
    485     (let loop ((i 7) (j 2))
    486       (cond ((< i 0) out)
    487             (else
    488              (let ((b (bytevector-u8-ref src i)))
    489                (bytevector-u8-set! out j
    490                  (bytevector-u8-ref hex (quotient b 16)))
    491                (bytevector-u8-set! out (+ j 1)
    492                  (bytevector-u8-ref hex (remainder b 16)))
    493                (loop (- i 1) (+ j 2))))))))
    494 
    495 (define (%c-value-source-bv v)
    496   (%c-value->decimal-bv (if (c-int-lit? v) (c-int-lit-value v) v)))
    497 (define (%c-value-literal-bv v)
    498   (let ((n (if (c-int-lit? v) (c-int-lit-value v) v)))
    499     (if (c-value? n) (%c-value->hex-bv n) (fixnum->bv n 10))))
    500 
    501 (define (%c-value-u32-literal-bv v word-index)
    502   ;; Render one little-endian 32-bit limb without ever converting it to a
    503   ;; Scheme integer. This is used when an RV32 %li materializes one half of
    504   ;; an i64/u64 C constant.
    505   (let ((full (%c-value->hex-bv v)))
    506     (cond
    507       ((= word-index 0) (bv-cat (list "0x" (bv-slice full 10 18))))
    508       ((= word-index 1) (bv-cat (list "0x" (bv-slice full 2 10))))
    509       (else (die #f "C constant: bad u32 limb index" word-index)))))
    510 
    511 (define (%c-value-parse-decimal-bv bv)
    512   (let ((n (bytevector-length bv)))
    513     (let loop ((i 0) (v 0))
    514       (cond
    515         ((= i n) (cons #t v))
    516         (else
    517          (let ((b (bytevector-u8-ref bv i)))
    518            (cond ((or (< b 48) (> b 57)) (cons #f #f))
    519                  (else
    520                   (loop (+ i 1)
    521                         (%c-value-mul-small-add v 10 (- b 48)))))))))))
    522 
    523 (define (%c-value-fits-unsigned-bits? v bits)
    524   (let* ((cv (%c-value-coerce v))
    525          (bv (c-value-bytes cv))
    526          (whole (quotient bits 8))
    527          (part (remainder bits 8)))
    528     (let loop ((i (+ whole (if (= part 0) 0 1))))
    529       (cond
    530         ((= i %C-VALUE-BYTES)
    531          (if (= part 0) #t
    532              (< (bytevector-u8-ref bv whole)
    533                 (arithmetic-shift 1 part))))
    534         ((not (= (bytevector-u8-ref bv i) 0)) #f)
    535         (else (loop (+ i 1)))))))
    536 
    537 (define (%c-int-raw v)
    538   (if (c-int-lit? v) (c-int-lit-value v) v))
    539 
    540 ;; --------------------------------------------------------------------
    541 ;; output buffer (fixed-size pre-allocated byte storage)
    542 ;;
    543 ;; Every buf owns one bytevector of `cap` bytes, plus a write `offset`.
    544 ;; buf-push! is bytevector-copy! into storage — zero allocation per
    545 ;; push, no chunks list to chase. The destination storage has stable
    546 ;; identity, so byte-level mutations survive collection.
    547 ;;
    548 ;; Sizing knobs live in one place so they're easy to tune as inputs
    549 ;; grow. cg-init picks per-buf caps; the per-fn bufs are reused
    550 ;; across functions (reset, not re-allocated).
    551 ;; --------------------------------------------------------------------
    552 
    553 ;; Tuning constants — total fixed pre-allocation is about 12.58 MiB on
    554 ;; 64-bit targets and 12.33 MiB on RV32. Bump these when a workload overflows; the buf-overflow
    555 ;; die() reports off/len/cap so misses are easy to diagnose.
    556 ;;
    557 ;; Each cap is a power of two. scheme1's bv_capacity_for rounds the
    558 ;; requested length up to the smallest power of two ≥ n, so asking for
    559 ;; 2^k bytes consumes exactly 2^k of heap.
    560 (define %BUF-CAP-TEXT     8388608)   ; 8 MiB:   .text + entry stub
    561 (define %BUF-CAP-DATA     2097152)   ; 2 MiB:   .data (strings, globals)
    562 (define %BUF-CAP-BSS      2097152)   ; 2 MiB:   .bss
    563 (define %BUF-CAP-FN       (if (= (target-word-bytes) 4) 262144 524288))
    564                                         ; 256/512 KiB: per-fn body asm
    565 (define %BUF-CAP-PROLOGUE 16384)     ; 16 KiB:  per-fn prologue
    566 (define %BUF-CAP-DEFAULT  65536)     ; 64 KiB:  make-buf fallback
    567 
    568 (define-record-type buf
    569   (%buf storage offset cap)
    570   buf?
    571   (storage buf-storage)                     ; bv: pre-allocated, never resized
    572   (offset  buf-offset  buf-offset-set!)     ; fixnum: bytes written so far
    573   (cap     buf-cap))                        ; fixnum: storage capacity
    574 
    575 (define (make-buf/cap cap)
    576   (%buf (make-bytevector cap 0) 0 cap))
    577 
    578 (define (make-buf) (make-buf/cap %BUF-CAP-DEFAULT))
    579 
    580 (define (buf-push! b bv)
    581   (let* ((n      (bytevector-length bv))
    582          (off    (buf-offset b))
    583          (newoff (+ off n)))
    584     (cond
    585       ((> newoff (buf-cap b))
    586        (die #f "buf overflow" off n (buf-cap b))))
    587     (bytevector-copy! (buf-storage b) off bv 0 n)
    588     (buf-offset-set! b newoff)))
    589 
    590 (define (buf-flush b)
    591   ;; Snapshot the used prefix as a fresh bv. One allocation; the
    592   ;; underlying storage is unchanged.
    593   (bytevector-copy (buf-storage b) 0 (buf-offset b)))
    594 
    595 (define (buf-reset! b) (buf-offset-set! b 0))
    596 
    597 (define (buf-drain! dst src)
    598   ;; Copy src's used bytes into dst at dst's current write head; reset
    599   ;; src to empty. dst and src must be distinct bufs.
    600   (let* ((slen   (buf-offset src))
    601          (doff   (buf-offset dst))
    602          (newoff (+ doff slen)))
    603     (cond
    604       ((> newoff (buf-cap dst))
    605        (die #f "buf-drain overflow" doff slen (buf-cap dst))))
    606     (bytevector-copy! (buf-storage dst) doff (buf-storage src) 0 slen)
    607     (buf-offset-set! dst newoff)
    608     (buf-offset-set! src 0)))
    609 
    610 ;; --------------------------------------------------------------------
    611 ;; diagnostics + I/O
    612 ;; --------------------------------------------------------------------
    613 (define (die loc msg . irritants)
    614   ;; Format:
    615   ;;   <file>:<line>:<col>: error: <msg>: <irritant> <irritant> ...
    616   ;; When loc is #f, the "<file>:<line>:<col>: " prefix is omitted.
    617   ;; irritants are written via display semantics (no quoting); format's
    618   ;; ~a handles bv/fixnum/pair/symbol the same way display does.
    619   ;;
    620   ;; All output is built into a single bv and sent to fd 2 with one
    621   ;; sys-write loop, so a partial write doesn't interleave fragments
    622   ;; from a concurrent process.
    623   (let* ((prefix (if loc
    624                      (format "~a:~d:~d: error: "
    625                              (loc-file loc) (loc-line loc) (loc-col loc))
    626                      "error: "))
    627          (head (bytevector-append prefix (format "~a" msg)))
    628          ;; Irritants get ": " before the first and " " between the rest.
    629          (tail (if (null? irritants)
    630                    (list NL-BV)
    631                    (let walk ((xs irritants) (sep ": ") (acc '()))
    632                      (if (null? xs)
    633                          (reverse (cons NL-BV acc))
    634                          (walk (cdr xs)
    635                                " "
    636                                (cons (format "~a" (car xs))
    637                                      (cons sep acc)))))))
    638          (out (bv-cat (cons head tail))))
    639     (write-bv-fd 2 out)
    640     (sys-exit 1)))
    641 
    642 (define (slurp-fd fd)
    643   ;; Read fd to EOF. Uses BUFSIZE chunks (same constant the prelude's
    644   ;; port layer uses); bv-concat-reverse builds the result in one
    645   ;; allocation so a multi-MB tcc.c stays linear.
    646   (let ((buf (make-bytevector BUFSIZE)))
    647     (let loop ((acc '()))
    648       (let ((r (sys-read fd buf 0 BUFSIZE)))
    649         (cond ((not (car r))
    650                (die #f "slurp-fd: sys-read failed" (cdr r)))
    651               ((zero? (cdr r))
    652                (bv-concat-reverse acc))
    653               (else
    654                (loop (cons (bytevector-copy buf 0 (cdr r)) acc))))))))
    655 
    656 (define (write-bv-fd fd bv)
    657   ;; Full write or die. sys-write may write fewer bytes than requested;
    658   ;; advance the offset and retry the unwritten tail.
    659   ;;
    660   ;; On failure we sys-exit directly instead of routing through `die`
    661   ;; — `die` itself uses write-bv-fd, so a write failure to fd 2 must
    662   ;; not recurse infinitely. Status 1 matches the contract for `die`.
    663   (let ((len (bytevector-length bv)))
    664     (let loop ((off 0))
    665       (if (= off len)
    666           #t
    667           (let ((r (sys-write fd bv off (- len off))))
    668             (cond ((not (car r))      (sys-exit 1))
    669                   ((zero? (cdr r))    (sys-exit 1))
    670                   (else (loop (+ off (cdr r))))))))))
    671 
    672 (define (write-bv-range-fd fd bv start len)
    673   ;; Write exactly LEN bytes starting at START without first copying the
    674   ;; range into a right-sized bytevector. This is important for the large,
    675   ;; fixed-capacity codegen buffers: their used prefixes can be streamed
    676   ;; without requiring another contiguous managed allocation.
    677   (let loop ((off start) (left len))
    678     (if (= left 0)
    679         #t
    680         (let ((r (sys-write fd bv off left)))
    681           (cond ((not (car r))      (sys-exit 1))
    682                 ((zero? (cdr r))    (sys-exit 1))
    683                 (else (loop (+ off (cdr r)) (- left (cdr r)))))))))
    684 
    685 ;; --------------------------------------------------------------------
    686 ;; debug logging
    687 ;;
    688 ;; Cheap sticky on/off: the cc compiler is single-threaded and short-
    689 ;; lived, so a top-level mutable flag is fine. Toggle via
    690 ;; (debug-log-on!) / (debug-log-off!). When on, (debug-log msg . irr)
    691 ;; writes one line to fd 2 in the same display-style format as `die`,
    692 ;; but doesn't abort. The intent is to trace heap usage between cc
    693 ;; phases (lex/pp/parse/cg-finish) without compile-time conditionals.
    694 ;; --------------------------------------------------------------------
    695 (define %debug-log-enabled #f)
    696 (define (debug-log-on!)  (set! %debug-log-enabled #t))
    697 (define (debug-log-off!) (set! %debug-log-enabled #f))
    698 (define (debug-log? )    %debug-log-enabled)
    699 
    700 ;; --cc-trace-emit: if on, cg-fn-end injects a `%trace(MANGLED)` line
    701 ;; at the top of each emitted function body (right after the prologue's
    702 ;; argument-spill, so the macro is free to clobber a0..a3). Pairs with
    703 ;; libp1pp's %trace macro + libp1pp__trace runtime helper to produce a
    704 ;; stderr line per function entry, with the runtime address of the
    705 ;; first body instruction. See P1/P1pp.P1pp's "Tracepoint" section.
    706 (define %trace-emit-enabled #f)
    707 (define (trace-emit-on!)  (set! %trace-emit-enabled #t))
    708 (define (trace-emit-off!) (set! %trace-emit-enabled #f))
    709 (define (trace-emit?)     %trace-emit-enabled)
    710 
    711 (define (debug-log msg . irritants)
    712   (cond
    713     (%debug-log-enabled
    714      (let* ((head (bytevector-append "[cc] " (format "~a" msg)))
    715             (tail (if (null? irritants)
    716                       (list NL-BV)
    717                       (let walk ((xs irritants) (sep ": ") (acc '()))
    718                         (if (null? xs)
    719                             (reverse (cons NL-BV acc))
    720                             (walk (cdr xs)
    721                                   " "
    722                                   (cons (format "~a" (car xs))
    723                                         (cons sep acc)))))))
    724             (out (bv-cat (cons head tail))))
    725        (write-bv-fd 2 out)))
    726     (else #t)))
    727 
    728 ;; --------------------------------------------------------------------
    729 ;; fresh-name generator (used for cg label counters, etc.)
    730 ;; --------------------------------------------------------------------
    731 (define (make-namer prefix)
    732   ;; Returns a thunk; each call yields prefix0, prefix1, ... as a fresh
    733   ;; bv. The counter lives in the closure's lexical environment; scheme1
    734   ;; closures heap-capture by reference, so set! on ctr is sticky.
    735   (let ((ctr 0))
    736     (lambda ()
    737       (let ((s (bytevector-append prefix (number->string ctr 10))))
    738         (set! ctr (+ ctr 1))
    739         s))))
    740 ;; cc/data.scm — record types and symbol alphabets shared across modules.
    741 
    742 ;; --------------------------------------------------------------------
    743 ;; loc — source location for diagnostics
    744 ;; --------------------------------------------------------------------
    745 (define-record-type loc
    746   (%loc file line col)
    747   loc?
    748   (file loc-file)            ; bv
    749   (line loc-line)            ; fixnum
    750   (col  loc-col))            ; fixnum
    751 
    752 ;; --------------------------------------------------------------------
    753 ;; tok — lexer token.
    754 ;; --------------------------------------------------------------------
    755 (define-record-type tok
    756   (%tok kind value loc hide)
    757   tok?
    758   (kind  tok-kind)           ; IDENT | INT | STR | CHAR | KW | PUNCT
    759                              ; | NL | HASH | EOF
    760   (value tok-value)          ; bv | fixnum/c-value/c-int-lit | symbol | #f
    761   (loc   tok-loc)            ; loc
    762   (hide  tok-hide))          ; list of bv (macro names already expanded)
    763 
    764 (define (make-tok kind value loc)
    765   (%tok kind value loc '()))
    766 
    767 ;; --------------------------------------------------------------------
    768 ;; macro — preprocessor macro definition
    769 ;; --------------------------------------------------------------------
    770 (define-record-type macro
    771   (%macro kind params body)
    772   macro?
    773   (kind   macro-kind)        ; 'obj | 'fn | 'fn-vararg
    774   (params macro-params)      ; list of bv
    775   (body   macro-body))       ; list of tok
    776 
    777 ;; --------------------------------------------------------------------
    778 ;; ctype — C type.
    779 ;;
    780 ;; size/align/ext mutate only on forward struct/union completion (see
    781 ;; complete-agg!). Every other ctype is constructed in its final shape
    782 ;; and treated as immutable thereafter.
    783 ;; --------------------------------------------------------------------
    784 (define-record-type ctype
    785   (%ctype kind size align ext)
    786   ctype?
    787   (kind  ctype-kind)
    788   (size  ctype-size  ctype-size-set!)
    789   (align ctype-align ctype-align-set!)
    790   (ext   ctype-ext   ctype-ext-set!))
    791 
    792 ;; Active P1 data model. scheme1 supplies these from the selected backend,
    793 ;; so cc.scm emits ILP32 for RV32 and LP64 for the existing targets.
    794 (define %CC-WORD-BYTES (target-word-bytes))
    795 (define %CC-WORD-BITS  (target-word-bits))
    796 (define %CC-PAIR-BYTES (* 2 %CC-WORD-BYTES))
    797 
    798 ;; Interned primitive ctypes. Equality is eq?.
    799 (define %t-void  (%ctype 'void  -1 -1 #f))
    800 (define %t-i8    (%ctype 'i8     1  1 #f))
    801 (define %t-u8    (%ctype 'u8     1  1 #f))
    802 (define %t-i16   (%ctype 'i16    2  2 #f))
    803 (define %t-u16   (%ctype 'u16    2  2 #f))
    804 (define %t-i32   (%ctype 'i32    4  4 #f))
    805 (define %t-u32   (%ctype 'u32    4  4 #f))
    806 (define %t-i64   (%ctype 'i64    8  8 #f))
    807 (define %t-u64   (%ctype 'u64    8  8 #f))
    808 (define %t-word-i (if (= %CC-WORD-BYTES 4) %t-i32 %t-i64))
    809 (define %t-word-u (if (= %CC-WORD-BYTES 4) %t-u32 %t-u64))
    810 (define %t-bool  (%ctype 'bool   1  1 #f))
    811 ;; Floating-point ctypes are parsed but never codegen'd; see CC.md §Cut.
    812 ;; Sizes/aligns match the SysV ABI so struct layout containing fp fields
    813 ;; works even when the cg refuses to emit fp ops.
    814 (define %t-flt   (%ctype 'flt    4  4 #f))
    815 (define %t-dbl   (%ctype 'dbl    8  8 #f))
    816 (define %t-ldbl  (%ctype 'ldbl   8  8 #f))
    817 
    818 ;; Select the first type in C11 6.4.4.1's candidate list that can represent
    819 ;; an integer constant. CType intentionally interns same-width C types into
    820 ;; one representation (for example, int and long are both i32 on ILP32), so
    821 ;; this returns the correct width and signedness even where their ranks differ.
    822 (define (%c-int-type lit)
    823   (let* ((v (%c-int-raw lit))
    824          (u? (and (c-int-lit? lit) (c-int-lit-unsigned? lit)))
    825          (lc (if (c-int-lit? lit) (c-int-lit-long-count lit) 0))
    826          (decimal? (if (c-int-lit? lit) (c-int-lit-decimal? lit) #t))
    827          (fits-i32? (%c-value-fits-unsigned-bits? v 31))
    828          (fits-u32? (%c-value-fits-unsigned-bits? v 32))
    829          (fits-i64? (%c-value-fits-unsigned-bits? v 63)))
    830     (cond
    831       ;; LL / ULL have only a 64-bit candidate in the supported data models.
    832       ((>= lc 2)
    833        (cond (u? %t-u64) (fits-i64? %t-i64) (else %t-u64)))
    834       ;; L / UL begin at the target's C long width.
    835       ((= lc 1)
    836        (cond
    837          (u?
    838           (if (= %CC-WORD-BITS 32)
    839               (if fits-u32? %t-u32 %t-u64)
    840               %t-u64))
    841          (decimal?
    842           (if (= %CC-WORD-BITS 32)
    843               (cond (fits-i32? %t-i32) (fits-i64? %t-i64) (else %t-u64))
    844               (if fits-i64? %t-i64 %t-u64)))
    845          ((= %CC-WORD-BITS 32)
    846           (cond (fits-i32? %t-i32) (fits-u32? %t-u32)
    847                 (fits-i64? %t-i64) (else %t-u64)))
    848          (else (if fits-i64? %t-i64 %t-u64))))
    849       ;; A bare U suffix starts at unsigned int on both targets.
    850       (u? (if fits-u32? %t-u32 %t-u64))
    851       ;; Unsuffixed decimal has no unsigned candidates before the extension
    852       ;; fallback; octal/hex may select unsigned int or unsigned long.
    853       (decimal?
    854        (cond (fits-i32? %t-i32) (fits-i64? %t-i64) (else %t-u64)))
    855       (else
    856        (cond (fits-i32? %t-i32) (fits-u32? %t-u32)
    857              (fits-i64? %t-i64) (else %t-u64))))))
    858 
    859 ;; --------------------------------------------------------------------
    860 ;; sym — declared identifier (function, variable, typedef, …)
    861 ;; defined? distinguishes a forward declaration (extern fn proto, extern
    862 ;; var) from a definition (fn body, var with initializer, tentative def
    863 ;; without `extern`). scope-bind! merges compatible decls; only two
    864 ;; defined? syms with the same name fire a redefinition error.
    865 ;;
    866 ;; sym is immutable — no `sym-*-set!` accessor exists. scope-bind!'s
    867 ;; merge logic constructs a fresh sym rather than mutating in place.
    868 ;; Keeping it immutable also makes shared world bindings straightforward.
    869 ;; --------------------------------------------------------------------
    870 (define-record-type sym
    871   (%sym name kind storage type slot defined?)
    872   sym?
    873   (name     sym-name)         ; bv
    874   (kind     sym-kind)         ; symbol from §1.7
    875   (storage  sym-storage)      ; symbol from §1.8 or #f
    876   (type     sym-type)         ; ctype
    877   (slot     sym-slot)         ; fixnum (auto local / param / enum-const value)
    878                               ; | #f (fn / global var / typedef)
    879   (defined? sym-defined?))    ; #t = definition, #f = decl-only
    880 
    881 ;; --------------------------------------------------------------------
    882 ;; opnd — operand on cg's vstack.
    883 ;; --------------------------------------------------------------------
    884 (define-record-type opnd
    885   (%opnd kind type ext lval?)
    886   opnd?
    887   (kind  opnd-kind)
    888   (type  opnd-type)
    889   (ext   opnd-ext)
    890   (lval? opnd-lval?))
    891 
    892 ;; --------------------------------------------------------------------
    893 ;; loop-ctx — entry on parser's loop/switch context stack.
    894 ;; --------------------------------------------------------------------
    895 (define-record-type loop-ctx
    896   (%loop-ctx kind tag has-continue?)
    897   loop-ctx?
    898   (kind          loop-ctx-kind)
    899   (tag           loop-ctx-tag)
    900   (has-continue? loop-ctx-has-continue?))
    901 
    902 ;; --------------------------------------------------------------------
    903 ;; fn-ctx — current-function context inside the parser.
    904 ;; --------------------------------------------------------------------
    905 (define-record-type fn-ctx
    906   (%fn-ctx name return-type params variadic? labels static-counter)
    907   fn-ctx?
    908   (name        fn-ctx-name)
    909   (return-type fn-ctx-return-type)
    910   (params      fn-ctx-params)
    911   (variadic?   fn-ctx-variadic?)
    912   (labels      fn-ctx-labels      fn-ctx-labels-set!)
    913   ;; Monotonic declaration identity for block-scope static objects.  The C
    914   ;; identifier alone is not unique: separate lexical scopes in one function
    915   ;; may each declare `static ... order[]` and must emit distinct labels.
    916   (static-counter fn-ctx-static-counter fn-ctx-static-counter-set!))
    917 
    918 ;; --------------------------------------------------------------------
    919 ;; world — cross-decl persistent parser/cg state. The same world record
    920 ;; is shared by pstate and cg so its slots — scope/tag hash-frame stacks,
    921 ;; the interned-string hash, and tentative-definition list + membership
    922 ;; hash — can be reasoned about as one persistent root graph.
    923 ;; --------------------------------------------------------------------
    924 (define-record-type world
    925   (%world scope tags str-pool tentatives)
    926   world?
    927   (scope      world-scope      world-scope-set!)
    928   (tags       world-tags       world-tags-set!)
    929   (str-pool   world-str-pool   world-str-pool-set!)
    930   (tentatives world-tentatives world-tentatives-set!))
    931 
    932 (define (make-world)
    933   (%world (list (%make-hash-table 64))
    934           (list (%make-hash-table 16))
    935           (%make-hash-table 16)
    936           (cons '() (%make-hash-table 16))))
    937 
    938 ;; --------------------------------------------------------------------
    939 ;; pstate — parser state. Owned by parse.scm; read-only to cg.
    940 ;; --------------------------------------------------------------------
    941 ;; iter holds a tok-iter (typically a pp-iter chained over a lex-iter).
    942 ;; peek / peek2 / advance go through iter-peek / iter-peek2 / iter-next
    943 ;; so the parser pulls one token at a time, with no full materialized
    944 ;; token list.
    945 (define-record-type pstate
    946   (%pstate iter world loops fn-ctx cg)
    947   pstate?
    948   (iter   ps-iter   ps-iter-set!)
    949   (world  ps-world)
    950   (loops  ps-loops  ps-loops-set!)
    951   (fn-ctx ps-fn-ctx ps-fn-ctx-set!)
    952   (cg     ps-cg))
    953 
    954 (define (ps-scope ps)        (world-scope (ps-world ps)))
    955 (define (ps-scope-set! ps v) (world-scope-set! (ps-world ps) v))
    956 (define (ps-tags ps)         (world-tags (ps-world ps)))
    957 (define (ps-tags-set! ps v)  (world-tags-set! (ps-world ps) v))
    958 
    959 ;; --------------------------------------------------------------------
    960 ;; cg — codegen state. Owned by cg.scm.
    961 ;; --------------------------------------------------------------------
    962 ;; fn-buf and prologue-buf are pre-allocated (cg-init) and reused across
    963 ;; functions — cg-fn-begin/v calls buf-reset! on them, cg-fn-end drains
    964 ;; them into cg-text via buf-drain!. Fixed-storage byte writes remain
    965 ;; stable while ordinary transient parser data is reclaimed by the GC.
    966 ;;
    967 ;; in-fn? discriminates "currently inside a function body" so
    968 ;; %cg-emit-buf can route emits to fn-buf during the body and cg-text
    969 ;; outside it (entry stub, etc.).
    970 ;;
    971 ;; cg-fn-meta: transient per-function state (fn-name, ret-slot, ret-type,
    972 ;; vararg-first-slot, indirect-slots, switch-case lists, ...). Reset on
    973 ;; cg-fn-begin/v; reads via %cg-fn-get / writes via %cg-fn-set!.
    974 ;; lib? / str-prefix encode the --lib=PFX flag from cc-main:
    975 ;;   #f / ""        — exec mode (default): cg-finish emits the
    976 ;;                    p1_main entry stub and trailing :ELF_end, and
    977 ;;                    cg-intern-string labels strings cc__str_N.
    978 ;;   #t / "<pfx>"   — library mode: skip the stub and :ELF_end so the
    979 ;;                    output catm's into a larger TU, and label strings
    980 ;;                    <pfx>cc__str_N so two cc.scm outputs in the same
    981 ;;                    link don't collide on cc__str_0..N.
    982 (define-record-type cg
    983   (%cg text data bss vstack frame-hi label-ctr world fn-meta fn-buf prologue-buf max-outgoing in-fn? lib? str-prefix)
    984   cg?
    985   (text         cg-text)
    986   (data         cg-data)
    987   (bss          cg-bss)
    988   (vstack       cg-vstack       cg-vstack-set!)
    989   (frame-hi     cg-frame-hi     cg-frame-hi-set!)
    990   (label-ctr    cg-label-ctr    cg-label-ctr-set!)
    991   (world        cg-world)
    992   (fn-meta      cg-fn-meta      cg-fn-meta-set!)
    993   (fn-buf       cg-fn-buf)
    994   (prologue-buf cg-prologue-buf)
    995   (max-outgoing cg-max-outgoing cg-max-outgoing-set!)
    996   (in-fn?       cg-in-fn?       cg-in-fn?-set!)
    997   (lib?         cg-lib?)
    998   (str-prefix   cg-str-prefix))
    999 
   1000 (define (cg-str-pool cg)        (world-str-pool (cg-world cg)))
   1001 (define (cg-str-pool-set! cg v) (world-str-pool-set! (cg-world cg) v))
   1002 
   1003 ;; ctype predicates used by both cg and parser.
   1004 (define (%ctype-ptr? t)
   1005   (let ((k (ctype-kind t)))
   1006     (if (eq? k 'ptr) #t (eq? k 'arr))))
   1007 
   1008 (define (%ctype-pointee t)
   1009   (cond ((eq? (ctype-kind t) 'ptr) (ctype-ext t))
   1010         ((eq? (ctype-kind t) 'arr) (car (ctype-ext t)))
   1011         (else #f)))
   1012 
   1013 (define (%ctype-unsigned? t)
   1014   (let ((k (ctype-kind t)))
   1015     (cond ((eq? k 'u8) #t) ((eq? k 'u16) #t) ((eq? k 'u32) #t)
   1016           ((eq? k 'u64) #t) ((eq? k 'bool) #t)
   1017           ((eq? k 'ptr) #t) ((eq? k 'arr) #t) ((eq? k 'fn) #t)
   1018           (else #f))))
   1019 
   1020 (define (%ctype-arith? t)
   1021   (let ((k (ctype-kind t)))
   1022     (cond ((eq? k 'i8) #t) ((eq? k 'i16) #t) ((eq? k 'i32) #t)
   1023           ((eq? k 'i64) #t) ((eq? k 'u8) #t) ((eq? k 'u16) #t)
   1024           ((eq? k 'u32) #t) ((eq? k 'u64) #t) ((eq? k 'bool) #t)
   1025           (else #f))))
   1026 
   1027 (define (%ctype-fp? t)
   1028   (let ((k (ctype-kind t)))
   1029     (cond ((eq? k 'flt) #t) ((eq? k 'dbl) #t) ((eq? k 'ldbl) #t)
   1030           (else #f))))
   1031 
   1032 (define (%ctype-wide-int? t)
   1033   ;; P1-32 lowers C's 64-bit integer types through a two-word pair. P1-64
   1034   ;; keeps the historical one-register representation.
   1035   (and (= %CC-WORD-BYTES 4)
   1036        (let ((k (ctype-kind t)))
   1037          (or (eq? k 'i64) (eq? k 'u64)))))
   1038 
   1039 ;; --------------------------------------------------------------------
   1040 ;; Symbol alphabets — canonical alists.
   1041 ;; --------------------------------------------------------------------
   1042 
   1043 ;; Keyword bytevector → keyword symbol.
   1044 (define %keyword-alist
   1045   '(;; storage
   1046     ("auto" . auto) ("register" . register) ("static" . static)
   1047     ("extern" . extern) ("typedef" . typedef)
   1048     ;; qualifiers (parsed and discarded by parse)
   1049     ("const" . const) ("volatile" . volatile) ("restrict" . restrict)
   1050     ("inline" . inline) ("_Noreturn" . _Noreturn)
   1051     ;; GNU attribute spec — parsed and discarded; see skip-gnu-attribute!
   1052     ("__attribute__" . __attribute__)
   1053     ;; type specifiers
   1054     ("void" . void) ("char" . char) ("short" . short)
   1055     ("int" . int) ("long" . long)
   1056     ("signed" . signed) ("unsigned" . unsigned) ("_Bool" . _Bool)
   1057     ;; rejected type specifiers (KW so diagnostics are crisp)
   1058     ("float" . float) ("double" . double)
   1059     ;; aggregates
   1060     ("struct" . struct) ("union" . union) ("enum" . enum)
   1061     ;; statements
   1062     ("if" . if) ("else" . else)
   1063     ("while" . while) ("do" . do) ("for" . for)
   1064     ("switch" . switch) ("case" . case) ("default" . default)
   1065     ("break" . break) ("continue" . continue)
   1066     ("return" . return) ("goto" . goto)
   1067     ;; operators
   1068     ("sizeof" . sizeof)
   1069     ;; reserved-and-rejected (KW so diagnostics are crisp)
   1070     ("_Generic" . _Generic) ("_Atomic" . _Atomic)
   1071     ("_Thread_local" . _Thread_local)
   1072     ("_Alignof" . _Alignof) ("__alignof" . _Alignof)
   1073     ("__alignof__" . _Alignof) ("_Alignas" . _Alignas)
   1074     ("_Static_assert" . _Static_assert)
   1075     ("_Complex" . _Complex) ("_Imaginary" . _Imaginary)))
   1076 
   1077 (define %keyword-map (alist->hash %keyword-alist))
   1078 
   1079 ;; Punctuator bytevector → punct symbol.
   1080 ;; Listed longest-match-first; the lexer scans this list in order.
   1081 ;; Digraphs (<: :> <% %> %: %:%:) lex to their standard equivalents.
   1082 (define %punct-alist
   1083   '(;; 4-byte
   1084     ("%:%:" . paste)
   1085     ;; 3-byte
   1086     ("..." . ellipsis) ("<<=" . shl-eq) (">>=" . shr-eq)
   1087     ;; 2-byte
   1088     ("##" . paste) ("->" . arrow)
   1089     ("++" . inc) ("--" . dec)
   1090     ("<<" . shl) (">>" . shr)
   1091     ("<=" . le) (">=" . ge) ("==" . eq2) ("!=" . ne)
   1092     ("&&" . land) ("||" . lor)
   1093     ("+=" . plus-eq) ("-=" . minus-eq) ("*=" . star-eq)
   1094     ("/=" . slash-eq) ("%=" . pct-eq)
   1095     ("&=" . amp-eq) ("^=" . caret-eq) ("|=" . bar-eq)
   1096     ;; digraphs (mapped to the standard equivalent symbol)
   1097     ("<:" . lbrack) (":>" . rbrack)
   1098     ("<%" . lbrace) ("%>" . rbrace) ("%:" . hash)
   1099     ;; 1-byte
   1100     ("[" . lbrack) ("]" . rbrack)
   1101     ("(" . lparen) (")" . rparen)
   1102     ("{" . lbrace) ("}" . rbrace)
   1103     ("." . dot) ("," . comma) (";" . semi) (":" . colon) ("?" . qmark)
   1104     ("+" . plus) ("-" . minus) ("*" . star) ("/" . slash) ("%" . pct)
   1105     ("&" . amp) ("|" . bar) ("^" . caret) ("~" . tilde) ("!" . bang)
   1106     ("<" . lt) (">" . gt) ("=" . assign)
   1107     ("#" . hash)))
   1108 ;; cc/lex.scm — bytestream → token list. Pure function; no I/O,
   1109 ;; no macro awareness.
   1110 ;;
   1111 ;; The lexer walks `src` byte-by-byte, threading (pos, line, col)
   1112 ;; explicitly through every helper (no mutable state). Each token
   1113 ;; captures its starting loc; helpers return (tok npos nline ncol).
   1114 ;; Trigraphs and `\<newline>` line splicing are handled via a single
   1115 ;; logical-byte primitive `%lex-peek`: it advances over splices and
   1116 ;; translates trigraphs in-place, so downstream code only ever sees
   1117 ;; the "translation phase 2" stream. Comments are stripped at the
   1118 ;; same level as whitespace. NL tokens are emitted at every physical
   1119 ;; newline so pp can use them to terminate directives.
   1120 ;;
   1121 (define (%lex-init!) #t)
   1122 
   1123 ;; --------------------------------------------------------------------
   1124 ;; Byte-class predicates (raw u8 values, not chars).
   1125 ;; --------------------------------------------------------------------
   1126 (define (%digit? b)        (if (< b 48) #f (if (< 57 b) #f #t)))     ; '0'..'9'
   1127 (define (%hex? b)
   1128   (cond ((%digit? b) #t)
   1129         ((if (< b 65) #f (if (< 70 b) #f #t)) #t)                    ; 'A'..'F'
   1130         ((if (< b 97) #f (if (< 102 b) #f #t)) #t)                   ; 'a'..'f'
   1131         (else #f)))
   1132 (define (%octal? b)        (if (< b 48) #f (if (< 55 b) #f #t)))     ; '0'..'7'
   1133 (define (%alpha? b)
   1134   (cond ((if (< b 65) #f (if (< 90 b) #f #t)) #t)                    ; 'A'..'Z'
   1135         ((if (< b 97) #f (if (< 122 b) #f #t)) #t)                   ; 'a'..'z'
   1136         (else #f)))
   1137 (define (%ident-start? b)  (or (%alpha? b) (= b 95)))                ; '_'
   1138 (define (%ident-cont?  b)  (or (%ident-start? b) (%digit? b)))
   1139 (define (%hspace? b)       (or (= b 32) (= b 9) (= b 11) (= b 12)))  ; SP TAB VT FF
   1140 (define (%newline? b)      (= b 10))                                 ; '\n'
   1141 
   1142 ;; --------------------------------------------------------------------
   1143 ;; Logical byte access. %lex-peek returns
   1144 ;;   (byte npos nline ncol)
   1145 ;; where (npos, nline, ncol) points *just past* the consumed physical
   1146 ;; bytes. On EOF it returns (#f pos line col).
   1147 ;;
   1148 ;; Two transformations folded in here:
   1149 ;;
   1150 ;;   - Trigraphs:  ??=  ??(  ??/  ??)  ??'  ??<  ??!  ??>  ??-
   1151 ;;                  #    [    \    ]    ^    {    |    }    ~
   1152 ;;     The pair `??` followed by one of the nine trigraph completers
   1153 ;;     produces the translated byte and advances 3 source bytes.
   1154 ;;   - Line splice: a backslash immediately followed by `\n` is removed
   1155 ;;     as a unit (incrementing line, resetting col to 1) and we recurse
   1156 ;;     to fetch the next logical byte.
   1157 ;;
   1158 ;; Other escapes (e.g. `\<not-newline>`) are returned as-is — string and
   1159 ;; char literals do their own escape-handling.
   1160 ;; --------------------------------------------------------------------
   1161 (define (%trigraph-byte b)
   1162   ;; Map the third trigraph byte to its replacement, or #f.
   1163   (cond ((= b 61) 35)   ; '=' -> '#'
   1164         ((= b 40) 91)   ; '(' -> '['
   1165         ((= b 47) 92)   ; '/' -> '\\'
   1166         ((= b 41) 93)   ; ')' -> ']'
   1167         ((= b 39) 94)   ; '\'' -> '^'
   1168         ((= b 60) 123)  ; '<' -> '{'
   1169         ((= b 33) 124)  ; '!' -> '|'
   1170         ((= b 62) 125)  ; '>' -> '}'
   1171         ((= b 45) 126)  ; '-' -> '~'
   1172         (else #f)))
   1173 
   1174 (define (%lex-peek src pos line col)
   1175   (let ((n (bytevector-length src)))
   1176     (cond
   1177       ((>= pos n) (list #f pos line col))
   1178       (else
   1179        (let ((b (bytevector-u8-ref src pos)))
   1180          (cond
   1181            ;; Trigraph: ?? + completer
   1182            ((and (= b 63)
   1183                  (< (+ pos 2) n)
   1184                  (= (bytevector-u8-ref src (+ pos 1)) 63))
   1185             (let ((tr (%trigraph-byte (bytevector-u8-ref src (+ pos 2)))))
   1186               (if tr
   1187                   (list tr (+ pos 3) line (+ col 3))
   1188                   (list b (+ pos 1) line (+ col 1)))))
   1189            ;; Line splice: backslash + newline (consume both, no token)
   1190            ((and (= b 92)
   1191                  (< (+ pos 1) n)
   1192                  (= (bytevector-u8-ref src (+ pos 1)) 10))
   1193             (%lex-peek src (+ pos 2) (+ line 1) 1))
   1194            ;; Newline: pass through but caller decides line/col bump
   1195            ((%newline? b)
   1196             (list b (+ pos 1) (+ line 1) 1))
   1197            (else
   1198             (list b (+ pos 1) line (+ col 1)))))))))
   1199 
   1200 ;; Convenience accessors over the 4-list.
   1201 (define (%pk-byte p)  (car p))
   1202 (define (%pk-pos  p)  (car (cdr p)))
   1203 (define (%pk-line p)  (car (cdr (cdr p))))
   1204 (define (%pk-col  p)  (car (cdr (cdr (cdr p)))))
   1205 
   1206 ;; Fast-byte test. When (%fast-byte? b) is #t, reading b directly with
   1207 ;; bytevector-u8-ref is exactly equivalent to %lex-peek's result: the
   1208 ;; logical byte is b, npos = pos+1, nline unchanged, ncol = col+1, and
   1209 ;; no list allocation is needed. Excludes the three bytes that %lex-peek
   1210 ;; can transform: '?' (trigraph), '\\' (line splice), '\n' (line bump).
   1211 (define (%fast-byte? b)
   1212   (cond ((= b 63) #f)
   1213         ((= b 92) #f)
   1214         ((= b 10) #f)
   1215         (else #t)))
   1216 
   1217 ;; --------------------------------------------------------------------
   1218 ;; Whitespace + comment skipper.  Returns (pos line col).
   1219 ;; Handles spaces/tabs, // line comments, /* block */ comments. Does
   1220 ;; *not* consume `\n` — newlines are tokens.
   1221 ;; --------------------------------------------------------------------
   1222 (define (%skip-ws-and-comments src pos line col file)
   1223   (let ((n (bytevector-length src)))
   1224     (cond
   1225       ((>= pos n) (list pos line col))
   1226       (else
   1227        (let ((b (bytevector-u8-ref src pos)))
   1228          (cond
   1229            ((and (%fast-byte? b) (%hspace? b))
   1230             (%skip-ws-and-comments src (+ pos 1) line (+ col 1) file))
   1231            ((%fast-byte? b)
   1232             ;; Fast-byte that isn't hspace. Only '/' is interesting;
   1233             ;; everything else terminates the skip.
   1234             (cond
   1235               ((= b 47) (%maybe-comment src pos line col file))
   1236               (else (list pos line col))))
   1237            (else
   1238             ;; Slow path: trigraph / splice / newline.
   1239             (let* ((p (%lex-peek src pos line col))
   1240                    (b2 (%pk-byte p)))
   1241               (cond
   1242                 ((not b2) (list pos line col))
   1243                 ((%hspace? b2)
   1244                  (%skip-ws-and-comments src (%pk-pos p) (%pk-line p) (%pk-col p)
   1245                                         file))
   1246                 ((= b2 47) (%maybe-comment src pos line col file))
   1247                 (else (list pos line col)))))))))))
   1248 
   1249 (define (%maybe-comment src pos line col file)
   1250   ;; Source byte at pos resolves to '/'. Decide between // line comment,
   1251   ;; /* block comment, or "leave the slash alone" (it's a punctuator).
   1252   (let* ((p (%lex-peek src pos line col))
   1253          (q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1254          (b2 (%pk-byte q)))
   1255     (cond
   1256       ((and b2 (= b2 47))
   1257        (%skip-line-comment src (%pk-pos q) (%pk-line q) (%pk-col q) file))
   1258       ((and b2 (= b2 42))
   1259        (%skip-block-comment src (%pk-pos q) (%pk-line q) (%pk-col q)
   1260                             file line col))
   1261       (else (list pos line col)))))
   1262 
   1263 (define (%skip-line-comment src pos line col file)
   1264   ;; Consume bytes until end-of-stream or until we *see* '\n' (do not
   1265   ;; consume the newline itself; outer loop emits the NL).
   1266   (let ((n (bytevector-length src)))
   1267     (cond
   1268       ((>= pos n) (%skip-ws-and-comments src pos line col file))
   1269       (else
   1270        (let ((b (bytevector-u8-ref src pos)))
   1271          (cond
   1272            ;; '\n' terminates without consuming.
   1273            ((= b 10) (%skip-ws-and-comments src pos line col file))
   1274            ((%fast-byte? b)
   1275             (%skip-line-comment src (+ pos 1) line (+ col 1) file))
   1276            (else
   1277             ;; Slow path: ?/\ — let %lex-peek handle trigraph/splice.
   1278             (let* ((p (%lex-peek src pos line col))
   1279                    (b2 (%pk-byte p)))
   1280               (cond
   1281                 ((not b2) (%skip-ws-and-comments src pos line col file))
   1282                 ((%newline? b2) (%skip-ws-and-comments src pos line col file))
   1283                 (else
   1284                  (%skip-line-comment src (%pk-pos p) (%pk-line p) (%pk-col p)
   1285                                      file)))))))))))
   1286 
   1287 (define (%skip-block-comment src pos line col file start-line start-col)
   1288   (let ((n (bytevector-length src)))
   1289     (cond
   1290       ((>= pos n)
   1291        (die (%loc file start-line start-col)
   1292             "unterminated /* block comment"))
   1293       (else
   1294        (let ((b (bytevector-u8-ref src pos)))
   1295          (cond
   1296            ;; Fast path for plain content bytes that aren't '*'.
   1297            ((and (%fast-byte? b) (not (= b 42)))
   1298             (%skip-block-comment src (+ pos 1) line (+ col 1)
   1299                                  file start-line start-col))
   1300            (else
   1301             ;; Slow path: '*', '\n', '?' (trigraph), '\\' (splice).
   1302             (let* ((p (%lex-peek src pos line col))
   1303                    (b1 (%pk-byte p)))
   1304               (cond
   1305                 ((not b1)
   1306                  (die (%loc file start-line start-col)
   1307                       "unterminated /* block comment"))
   1308                 ((= b1 42)
   1309                  (let* ((q  (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1310                         (b2 (%pk-byte q)))
   1311                    (cond
   1312                      ((not b2)
   1313                       (die (%loc file start-line start-col)
   1314                            "unterminated /* block comment"))
   1315                      ((= b2 47)
   1316                       (%skip-ws-and-comments src (%pk-pos q) (%pk-line q) (%pk-col q)
   1317                                              file))
   1318                      (else
   1319                       ;; Re-scan starting at the byte after '*'; the '*' was
   1320                       ;; not the closer, but the next byte might itself be '*'.
   1321                       (%skip-block-comment src (%pk-pos p) (%pk-line p) (%pk-col p)
   1322                                            file start-line start-col)))))
   1323                 (else
   1324                  (%skip-block-comment src (%pk-pos p) (%pk-line p) (%pk-col p)
   1325                                       file start-line start-col)))))))))))
   1326 
   1327 ;; --------------------------------------------------------------------
   1328 ;; Byte-run scanners.
   1329 ;;
   1330 ;; Tail-recursive walkers used by ident/number/string readers. None
   1331 ;; allocate per scanned byte on the fast path (only %lex-peek 4-lists
   1332 ;; on trigraph/splice/newline); tail recursion keeps the native call path
   1333 ;; bounded and unreachable Scheme environments are collected normally.
   1334 ;;
   1335 ;; - %scan-while:    count bytes that satisfy pred. (count npos nline ncol)
   1336 ;; - %fill-while-bv: write matching bytes into a pre-sized bv.
   1337 ;; - %accum-int-while: accumulate a base-N integer over digit bytes.
   1338 ;;     (val count npos nline ncol)
   1339 ;; - %accum-octal-bounded: same, but stops after k digits.
   1340 ;; --------------------------------------------------------------------
   1341 (define (%scan-while pred src pos line col)
   1342   (let ((n (bytevector-length src)))
   1343     (let loop ((pos pos) (line line) (col col) (cnt 0))
   1344       (cond
   1345         ((>= pos n) (list cnt pos line col))
   1346         (else
   1347          (let ((b (bytevector-u8-ref src pos)))
   1348            (cond
   1349              ((%fast-byte? b)
   1350               (if (pred b)
   1351                   (loop (+ pos 1) line (+ col 1) (+ cnt 1))
   1352                   (list cnt pos line col)))
   1353              (else
   1354               (let* ((p (%lex-peek src pos line col))
   1355                      (b2 (%pk-byte p)))
   1356                 (if (and b2 (pred b2))
   1357                     (loop (%pk-pos p) (%pk-line p) (%pk-col p) (+ cnt 1))
   1358                     (list cnt pos line col)))))))))))
   1359 
   1360 (define (%fill-while-bv pred src pos line col bv idx)
   1361   (let ((n (bytevector-length src)))
   1362     (let loop ((pos pos) (line line) (col col) (idx idx))
   1363       (cond
   1364         ((>= pos n) idx)
   1365         (else
   1366          (let ((b (bytevector-u8-ref src pos)))
   1367            (cond
   1368              ((%fast-byte? b)
   1369               (cond
   1370                 ((pred b)
   1371                  (bytevector-u8-set! bv idx b)
   1372                  (loop (+ pos 1) line (+ col 1) (+ idx 1)))
   1373                 (else idx)))
   1374              (else
   1375               (let* ((p (%lex-peek src pos line col))
   1376                      (b2 (%pk-byte p)))
   1377                 (cond
   1378                   ((and b2 (pred b2))
   1379                    (bytevector-u8-set! bv idx b2)
   1380                    (loop (%pk-pos p) (%pk-line p) (%pk-col p) (+ idx 1)))
   1381                   (else idx)))))))))))
   1382 
   1383 (define (%digit-val-byte b)
   1384   ;; ASCII digit byte → integer value. Caller guarantees b is a valid
   1385   ;; digit in the relevant base (0-9 / 0-7 / 0-9a-fA-F).
   1386   (cond ((%digit? b) (- b 48))
   1387         ((if (< b 65) #f (if (< 70 b) #f #t)) (+ (- b 65) 10))
   1388         ((if (< b 97) #f (if (< 102 b) #f #t)) (+ (- b 97) 10))
   1389         (else 0)))
   1390 
   1391 (define (%accum-int-while pred src pos line col base)
   1392   (let ((n (bytevector-length src)))
   1393     (let loop ((pos pos) (line line) (col col) (val 0) (cnt 0))
   1394       (cond
   1395         ((>= pos n) (list val cnt pos line col))
   1396         (else
   1397          (let ((b (bytevector-u8-ref src pos)))
   1398            (cond
   1399              ((%fast-byte? b)
   1400               (if (pred b)
   1401                   (loop (+ pos 1) line (+ col 1)
   1402                         (%c-value-mul-small-add
   1403                           val base (%digit-val-byte b))
   1404                         (+ cnt 1))
   1405                   (list val cnt pos line col)))
   1406              (else
   1407               (let* ((p (%lex-peek src pos line col))
   1408                      (b2 (%pk-byte p)))
   1409                 (if (and b2 (pred b2))
   1410                     (loop (%pk-pos p) (%pk-line p) (%pk-col p)
   1411                           (%c-value-mul-small-add
   1412                             val base (%digit-val-byte b2))
   1413                           (+ cnt 1))
   1414                     (list val cnt pos line col)))))))))))
   1415 
   1416 (define (%accum-octal-bounded src pos line col k)
   1417   ;; Up to k octal digits. Returns (val count npos nline ncol).
   1418   (let ((n (bytevector-length src)))
   1419     (let loop ((pos pos) (line line) (col col) (k k) (val 0) (cnt 0))
   1420       (cond
   1421         ((zero? k) (list val cnt pos line col))
   1422         ((>= pos n) (list val cnt pos line col))
   1423         (else
   1424          (let ((b (bytevector-u8-ref src pos)))
   1425            (cond
   1426              ((%fast-byte? b)
   1427               (if (%octal? b)
   1428                   (loop (+ pos 1) line (+ col 1) (- k 1)
   1429                         (+ (* val 8) (- b 48)) (+ cnt 1))
   1430                   (list val cnt pos line col)))
   1431              (else
   1432               (let* ((p (%lex-peek src pos line col))
   1433                      (b2 (%pk-byte p)))
   1434                 (if (and b2 (%octal? b2))
   1435                     (loop (%pk-pos p) (%pk-line p) (%pk-col p) (- k 1)
   1436                           (+ (* val 8) (- b2 48)) (+ cnt 1))
   1437                     (list val cnt pos line col)))))))))))
   1438 
   1439 ;; --------------------------------------------------------------------
   1440 ;; Identifier / keyword reader.
   1441 ;;
   1442 ;; Returns (tok npos nline ncol). Caller has already verified that the
   1443 ;; first byte at `pos` satisfies %ident-start?.
   1444 ;;
   1445 ;; Two-pass: pass 1 (%scan-while) sizes the run, then pass 2
   1446 ;; (%fill-while-bv) writes directly into the exact-size bytevector.
   1447 ;; --------------------------------------------------------------------
   1448 (define (lex-read-ident src pos file)
   1449   ;; Public for tests. Threads line/col from a fresh start.
   1450   (%lex-read-ident src pos 1 (+ pos 1) file))
   1451 
   1452 (define (%lex-read-ident src pos line col file)
   1453   (let ((start-loc (%loc file line col))
   1454         (count 0) (npos 0) (nline 0) (ncol 0))
   1455     (let ((sres (%scan-while %ident-cont? src pos line col)))
   1456       (set! count (car sres))
   1457       (set! npos  (car (cdr sres)))
   1458       (set! nline (car (cdr (cdr sres))))
   1459       (set! ncol  (car (cdr (cdr (cdr sres))))))
   1460     (let ((name (make-bytevector count 0)))
   1461       (%fill-while-bv %ident-cont? src pos line col name 0)
   1462       (let ((kw (%hash-ref %keyword-map name)))
   1463         (cons (if kw
   1464                   (make-tok 'KW kw start-loc)
   1465                   (make-tok 'IDENT name start-loc))
   1466               (list npos nline ncol))))))
   1467 
   1468 ;; --------------------------------------------------------------------
   1469 ;; Number reader.
   1470 ;;
   1471 ;; Decimal: [1-9][0-9]*  (suffix: u U l L ll LL combinations)
   1472 ;; Hex:     0x[0-9a-fA-F]+ | 0X...
   1473 ;; Octal:   0[0-7]*
   1474 ;; Float:   anything looking like 1.0, 1e3, .5 → die crisply.
   1475 ;;
   1476 ;; Returns (tok npos nline ncol) on success. Aborts via `die` on float.
   1477 ;;
   1478 ;; %accum-int-while folds digit collection and value computation into
   1479 ;; one walk — no per-byte cons cells, no separate digits-list pass.
   1480 ;; --------------------------------------------------------------------
   1481 (define (lex-read-number src pos file)
   1482   (%lex-read-number src pos 1 (+ pos 1) file))
   1483 
   1484 (define (%lex-finish-int val start-loc decimal? after)
   1485   ;; AFTER carries the scanner position followed by the parsed suffix flags.
   1486   ;; Keep only the position in the public lexer result; retain the flags on
   1487   ;; the INT token so the parser can apply the target C data model.
   1488   (let ((npos       (car after))
   1489         (nline      (car (cdr after)))
   1490         (ncol       (car (cdr (cdr after))))
   1491         (unsigned?  (car (cdr (cdr (cdr after)))))
   1492         (long-count (car (cdr (cdr (cdr (cdr after)))))))
   1493     (cons (make-tok 'INT
   1494                     (%c-int-lit val unsigned? long-count decimal?)
   1495                     start-loc)
   1496           (list npos nline ncol))))
   1497 
   1498 (define (%lex-read-number src pos line col file)
   1499   (let* ((start-loc (%loc file line col))
   1500          (p (%lex-peek src pos line col))
   1501          (b (%pk-byte p)))
   1502     (cond
   1503       ;; '0x' / '0X' hex prefix
   1504       ((and (= b 48)
   1505             (let* ((q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1506                    (b2 (%pk-byte q)))
   1507               (and b2 (or (= b2 120) (= b2 88)))))   ; 'x' or 'X'
   1508        (let* ((q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1509               (r (%accum-int-while %hex? src
   1510                                     (%pk-pos q) (%pk-line q) (%pk-col q) 16))
   1511               (val   (car r))
   1512               (cnt   (car (cdr r)))
   1513               (pos2  (car (cdr (cdr r))))
   1514               (line2 (car (cdr (cdr (cdr r)))))
   1515               (col2  (car (cdr (cdr (cdr (cdr r)))))))
   1516          (if (zero? cnt)
   1517              (die start-loc "expected hex digits after 0x")
   1518              (let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
   1519                (%lex-finish-int val start-loc #f after)))))
   1520       ;; '0' alone → octal sequence (could be just zero)
   1521       ((= b 48)
   1522        (let* ((r (%accum-int-while %octal? src
   1523                                     (%pk-pos p) (%pk-line p) (%pk-col p) 8))
   1524               (val   (car r))
   1525               (pos2  (car (cdr (cdr r))))
   1526               (line2 (car (cdr (cdr (cdr r)))))
   1527               (col2  (car (cdr (cdr (cdr (cdr r)))))))
   1528          ;; Reject '.' / 'e' / 'E' immediately after the octal run — float.
   1529          (%check-no-float src pos2 line2 col2 file start-loc)
   1530          ;; Reject stray digits 8/9 in an octal context (e.g. 089).
   1531          (let* ((p3 (%lex-peek src pos2 line2 col2))
   1532                 (b3 (%pk-byte p3)))
   1533            (if (and b3 (%digit? b3))
   1534                (die start-loc "invalid octal digit" (bv-of-byte b3))
   1535                (let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
   1536                  (%lex-finish-int val start-loc #f after))))))
   1537       ;; '1'-'9' → decimal
   1538       ((%digit? b)
   1539        (let* ((r (%accum-int-while %digit? src pos line col 10))
   1540               (val   (car r))
   1541               (pos2  (car (cdr (cdr r))))
   1542               (line2 (car (cdr (cdr (cdr r)))))
   1543               (col2  (car (cdr (cdr (cdr (cdr r)))))))
   1544          (%check-no-float src pos2 line2 col2 file start-loc)
   1545          (let ((after (%lex-strip-int-suffix src pos2 line2 col2 file)))
   1546            (%lex-finish-int val start-loc #t after))))
   1547       ;; '.' followed by a digit = float-style literal — reject.
   1548       ((= b 46)
   1549        (let* ((q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1550               (b2 (%pk-byte q)))
   1551          (if (and b2 (%digit? b2))
   1552              (die start-loc "floating-point literal not supported")
   1553              ;; Otherwise '.' was a punctuator — caller wouldn't have
   1554              ;; routed here unless it was a digit-led prefix.
   1555              (die start-loc "internal: number reader on non-number"))))
   1556       (else
   1557        (die start-loc "internal: number reader on non-number")))))
   1558 
   1559 (define (%check-no-float src pos line col file start-loc)
   1560   ;; If the byte at pos starts a fractional/exponent part, abort.
   1561   (let* ((p (%lex-peek src pos line col))
   1562          (b (%pk-byte p)))
   1563     (cond
   1564       ((not b) #t)
   1565       ((= b 46)  ; '.'
   1566        (die start-loc "floating-point literal not supported"))
   1567       ((or (= b 101) (= b 69))  ; 'e' / 'E'
   1568        ;; Only a float exponent if followed by [+-]?digit.
   1569        (let* ((q (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   1570               (b2 (%pk-byte q)))
   1571          (cond
   1572            ((and b2 (%digit? b2))
   1573             (die start-loc "floating-point literal not supported"))
   1574            ((and b2 (or (= b2 43) (= b2 45)))
   1575             (let* ((r (%lex-peek src (%pk-pos q) (%pk-line q) (%pk-col q)))
   1576                    (b3 (%pk-byte r)))
   1577               (if (and b3 (%digit? b3))
   1578                   (die start-loc "floating-point literal not supported")
   1579                   #t)))
   1580            (else #t))))
   1581       (else #t))))
   1582 
   1583 (define (%lex-strip-int-suffix src pos line col file)
   1584   ;; Consume any combination of u U l L (the long can be doubled). We
   1585   ;; don't validate orderings strictly; tcc.c uses the canonical forms.
   1586   ;; Returns (npos nline ncol unsigned? long-count).
   1587   (let loop ((pos pos) (line line) (col col) (unsigned? #f) (long-count 0))
   1588     (let* ((p (%lex-peek src pos line col))
   1589            (b (%pk-byte p)))
   1590       (cond
   1591         ((not b) (list pos line col unsigned? long-count))
   1592         ((or (= b 117) (= b 85))    ; u U
   1593          (loop (%pk-pos p) (%pk-line p) (%pk-col p) #t long-count))
   1594         ((or (= b 108) (= b 76))    ; l L
   1595          (loop (%pk-pos p) (%pk-line p) (%pk-col p)
   1596                unsigned? (+ long-count 1)))
   1597         (else (list pos line col unsigned? long-count))))))
   1598 
   1599 ;; --------------------------------------------------------------------
   1600 ;; Escape sequence reader.
   1601 ;;
   1602 ;; %scan-or-fill-escape decodes one escape sequence starting at `pos`
   1603 ;; (which points one past the leading `\\`). When `bv` is a bytevector,
   1604 ;; the resulting byte is written to (bv idx); when it is #f, no write
   1605 ;; occurs (used during the string-pass scan phase). Returns the 4-list
   1606 ;; (val npos nline ncol).
   1607 ;; --------------------------------------------------------------------
   1608 (define (%scan-or-fill-escape src pos line col file start-loc bv idx)
   1609   (let* ((p (%lex-peek src pos line col))
   1610          (b (%pk-byte p)))
   1611     (cond
   1612       ((not b) (die start-loc "unterminated escape sequence"))
   1613       ;; \xNN — 1+ hex digits (tcc.c uses 1- and 2-digit forms).
   1614       ((or (= b 120) (= b 88))   ; 'x' / 'X'
   1615        (let* ((r (%accum-int-while %hex? src
   1616                                     (%pk-pos p) (%pk-line p) (%pk-col p) 16))
   1617               (val0  (car r))
   1618               (cnt   (car (cdr r)))
   1619               (pos2  (car (cdr (cdr r))))
   1620               (line2 (car (cdr (cdr (cdr r)))))
   1621               (col2  (car (cdr (cdr (cdr (cdr r)))))))
   1622          (cond
   1623            ((zero? cnt) (die start-loc "expected hex digits after \\x"))
   1624            (else
   1625             (let ((val (bit-and val0 255)))
   1626               (cond (bv (bytevector-u8-set! bv idx val))
   1627                     (else #f))
   1628               (list val pos2 line2 col2))))))
   1629       ;; \NNN — 1..3 octal digits.
   1630       ((%octal? b)
   1631        (let* ((r (%accum-octal-bounded src pos line col 3))
   1632               (val0  (car r))
   1633               (pos2  (car (cdr (cdr r))))
   1634               (line2 (car (cdr (cdr (cdr r)))))
   1635               (col2  (car (cdr (cdr (cdr (cdr r))))))
   1636               (val   (bit-and val0 255)))
   1637          (cond (bv (bytevector-u8-set! bv idx val))
   1638                (else #f))
   1639          (list val pos2 line2 col2)))
   1640       (else
   1641        (let ((val (cond ((= b 110) 10)        ; n
   1642                         ((= b 116) 9)         ; t
   1643                         ((= b 114) 13)        ; r
   1644                         ((= b 92)  92)        ; \\
   1645                         ((= b 39)  39)        ; '
   1646                         ((= b 34)  34)        ; "
   1647                         ((= b 48)  0)         ; 0 (already handled by octal but be safe)
   1648                         ((= b 97)  7)         ; \a -> BEL
   1649                         ((= b 98)  8)         ; \b
   1650                         ((= b 102) 12)        ; \f
   1651                         ((= b 118) 11)        ; \v
   1652                         ((= b 63)  63)        ; \?
   1653                         (else
   1654                          (die start-loc "unknown escape" (bv-of-byte b))))))
   1655          (cond (bv (bytevector-u8-set! bv idx val))
   1656                (else #f))
   1657          (list val (%pk-pos p) (%pk-line p) (%pk-col p)))))))
   1658 
   1659 ;; --------------------------------------------------------------------
   1660 ;; String reader.
   1661 ;;
   1662 ;; Caller has verified src[pos] == '"' (raw byte 34). Returns
   1663 ;; (tok npos nline ncol) with the raw decoded bytes (no NUL appended).
   1664 ;;
   1665 ;; Two-pass: %string-pass with bv=#f counts effective bytes (escapes
   1666 ;; collapse to 1 byte each); then allocate the final bv and rerun with
   1667 ;; bv set so the bytes are written directly into it.
   1668 ;; --------------------------------------------------------------------
   1669 (define (lex-read-string src pos file)
   1670   (%lex-read-string src pos 1 (+ pos 1) file))
   1671 
   1672 (define (%lex-read-string src pos line col file)
   1673   (let ((start-loc (%loc file line col))
   1674         (cnt 0) (npos 0) (nline 0) (ncol 0))
   1675     ;; '"' (34) is a fast-byte and never a trigraph result, so the
   1676     ;; physical byte at `pos` is exactly the opening quote.
   1677     (cond
   1678       ((or (>= pos (bytevector-length src))
   1679            (not (= (bytevector-u8-ref src pos) 34)))
   1680        (die start-loc "internal: string reader on non-quote"))
   1681       (else
   1682        (let ((sres (%string-pass src (+ pos 1) line (+ col 1)
   1683                                   file start-loc #f)))
   1684          (set! cnt   (car sres))
   1685          (set! npos  (car (cdr sres)))
   1686          (set! nline (car (cdr (cdr sres))))
   1687          (set! ncol  (car (cdr (cdr (cdr sres))))))
   1688        (let ((bv (make-bytevector cnt 0)))
   1689          (%string-pass src (+ pos 1) line (+ col 1) file start-loc bv)
   1690          (cons (make-tok 'STR bv start-loc)
   1691                (list npos nline ncol)))))))
   1692 
   1693 (define (%string-pass src pos line col file start-loc bv)
   1694   ;; Walk the string body (after opening "). When `bv` is #f, count
   1695   ;; effective bytes; when it is a bytevector, write bytes into it at
   1696   ;; index 0..count-1. Returns (count npos nline ncol).
   1697   (let ((n (bytevector-length src)))
   1698     (let loop ((pos pos) (line line) (col col) (idx 0))
   1699       (cond
   1700         ((>= pos n) (die start-loc "unterminated string literal"))
   1701         (else
   1702          (let ((b (bytevector-u8-ref src pos)))
   1703            (cond
   1704              ;; Closing quote — fast byte but special.
   1705              ((= b 34)
   1706               (list idx (+ pos 1) line (+ col 1)))
   1707              ((%fast-byte? b)
   1708               (cond (bv (bytevector-u8-set! bv idx b))
   1709                     (else #f))
   1710               (loop (+ pos 1) line (+ col 1) (+ idx 1)))
   1711              (else
   1712               ;; Slow path: ?/\ (trigraph/splice/escape) or '\n'.
   1713               (let* ((p (%lex-peek src pos line col))
   1714                      (b2 (%pk-byte p)))
   1715                 (cond
   1716                   ((not b2)
   1717                    (die start-loc "unterminated string literal"))
   1718                   ((= b2 34)
   1719                    (list idx (%pk-pos p) (%pk-line p) (%pk-col p)))
   1720                   ((%newline? b2)
   1721                    (die start-loc "newline in string literal"))
   1722                   ((= b2 92)
   1723                    (let* ((er    (%scan-or-fill-escape
   1724                                    src (%pk-pos p) (%pk-line p) (%pk-col p)
   1725                                    file start-loc bv idx))
   1726                           (epos  (car (cdr er)))
   1727                           (eline (car (cdr (cdr er))))
   1728                           (ecol  (car (cdr (cdr (cdr er))))))
   1729                      (loop epos eline ecol (+ idx 1))))
   1730                   (else
   1731                    (cond (bv (bytevector-u8-set! bv idx b2))
   1732                          (else #f))
   1733                    (loop (%pk-pos p) (%pk-line p) (%pk-col p) (+ idx 1)))))))))))))
   1734 
   1735 ;; --------------------------------------------------------------------
   1736 ;; Char reader.
   1737 ;;
   1738 ;; Caller has verified src[pos] == '\''. Multi-character constants
   1739 ;; ('AB') are rejected via die.
   1740 ;; --------------------------------------------------------------------
   1741 (define (lex-read-char src pos file)
   1742   (%lex-read-char src pos 1 (+ pos 1) file))
   1743 
   1744 (define (%lex-read-char src pos line col file)
   1745   (let* ((start-loc (%loc file line col))
   1746          (p0 (%lex-peek src pos line col))
   1747          (b0 (%pk-byte p0)))
   1748     (if (not (and b0 (= b0 39)))
   1749         (die start-loc "internal: char reader on non-quote")
   1750         (%collect-char src (%pk-pos p0) (%pk-line p0) (%pk-col p0)
   1751                        file start-loc))))
   1752 
   1753 (define (%collect-char src pos line col file start-loc)
   1754   ;; Read exactly one byte (handling escapes), then expect closing '\''.
   1755   (let* ((p (%lex-peek src pos line col))
   1756          (b (%pk-byte p)))
   1757     (cond
   1758       ((not b) (die start-loc "unterminated char literal"))
   1759       ((= b 39) (die start-loc "empty char literal"))
   1760       ((%newline? b) (die start-loc "newline in char literal"))
   1761       ((= b 92)   ; escape
   1762        (let* ((r     (%scan-or-fill-escape src
   1763                                             (%pk-pos p) (%pk-line p) (%pk-col p)
   1764                                             file start-loc #f 0))
   1765               (val   (car r))
   1766               (pos2  (car (cdr r)))
   1767               (line2 (car (cdr (cdr r))))
   1768               (col2  (car (cdr (cdr (cdr r))))))
   1769          (%expect-char-close src pos2 line2 col2 file start-loc val)))
   1770       (else
   1771        (%expect-char-close src (%pk-pos p) (%pk-line p) (%pk-col p)
   1772                            file start-loc b)))))
   1773 
   1774 (define (%expect-char-close src pos line col file start-loc val)
   1775   (let* ((p (%lex-peek src pos line col))
   1776          (b (%pk-byte p)))
   1777     (cond
   1778       ((not b) (die start-loc "unterminated char literal"))
   1779       ((= b 39)
   1780        (cons (make-tok 'CHAR val start-loc)
   1781              (list (%pk-pos p) (%pk-line p) (%pk-col p))))
   1782       (else
   1783        (die start-loc "multi-character char constant not supported")))))
   1784 
   1785 ;; --------------------------------------------------------------------
   1786 ;; Punctuator reader.
   1787 ;;
   1788 ;; Greedy longest-match against %punct-alist. The alist
   1789 ;; is already ordered longest-first. We additionally bucket entries by
   1790 ;; their first byte so %lex-read-punct only loops over the small set of
   1791 ;; patterns that can start at the current source byte.
   1792 ;; --------------------------------------------------------------------
   1793 
   1794 (define (%alist-ref-int k al)
   1795   ;; Lookup in an int-keyed alist (linear scan, '= compare).
   1796   (cond ((null? al) #f)
   1797         ((= (car (car al)) k) (cdr (car al)))
   1798         (else (%alist-ref-int k (cdr al)))))
   1799 
   1800 (define (%mem-int? k xs)
   1801   (cond ((null? xs) #f)
   1802         ((= (car xs) k) #t)
   1803         (else (%mem-int? k (cdr xs)))))
   1804 
   1805 (define (%filter-by-first-byte b al)
   1806   ;; Subset of `al` whose pattern starts with byte b, preserving order.
   1807   (cond
   1808     ((null? al) '())
   1809     ((= (bytevector-u8-ref (car (car al)) 0) b)
   1810      (cons (car al) (%filter-by-first-byte b (cdr al))))
   1811     (else (%filter-by-first-byte b (cdr al)))))
   1812 
   1813 (define (%group-by-first-byte al)
   1814   ;; Build ((first-byte . sub-alist) ...) over `al`, one bucket per
   1815   ;; distinct first byte; sub-alist preserves longest-match-first
   1816   ;; order from the source list.
   1817   (let loop ((xs al) (seen '()) (out '()))
   1818     (cond
   1819       ((null? xs) (reverse out))
   1820       (else
   1821        (let* ((entry (car xs))
   1822               (pat   (car entry))
   1823               (b     (bytevector-u8-ref pat 0)))
   1824          (cond
   1825            ((%mem-int? b seen) (loop (cdr xs) seen out))
   1826            (else
   1827             (loop (cdr xs)
   1828                   (cons b seen)
   1829                   (cons (cons b (%filter-by-first-byte b al)) out)))))))))
   1830 
   1831 (define %punct-buckets (%group-by-first-byte %punct-alist))
   1832 
   1833 (define (lex-read-punct src pos file)
   1834   (%lex-read-punct src pos 1 (+ pos 1) file))
   1835 
   1836 (define (%lex-read-punct src pos line col file)
   1837   (let* ((start-loc (%loc file line col))
   1838          (p (%lex-peek src pos line col))
   1839          (b (%pk-byte p)))
   1840     (cond
   1841       ((not b) (die start-loc "unrecognized byte" "EOF"))
   1842       (else
   1843        (let ((bucket (%alist-ref-int b %punct-buckets)))
   1844          (cond
   1845            ((not bucket) (die start-loc "unrecognized byte" (bv-of-byte b)))
   1846            (else (%punct-loop src pos line col file start-loc bucket))))))))
   1847 
   1848 (define (%punct-loop src pos line col file start-loc al)
   1849   (cond
   1850     ((null? al)
   1851      (let* ((p (%lex-peek src pos line col)))
   1852        (die start-loc "unrecognized byte"
   1853             (if (%pk-byte p) (bv-of-byte (%pk-byte p)) "EOF"))))
   1854     (else
   1855      (let* ((entry (car al))
   1856             (pat   (car entry))
   1857             (sym   (cdr entry))
   1858             (m     (%match-bytes src pos line col pat 0)))
   1859        (if m
   1860            (cons (make-tok 'PUNCT sym start-loc) m)
   1861            (%punct-loop src pos line col file start-loc (cdr al)))))))
   1862 
   1863 (define (%match-bytes src pos line col pat i)
   1864   ;; If the next bytes from (pos line col), in logical-byte stream
   1865   ;; order, equal `pat[i..]`, return (npos nline ncol) after the
   1866   ;; match. Otherwise #f.
   1867   (cond
   1868     ((= i (bytevector-length pat)) (list pos line col))
   1869     (else
   1870      (let ((n (bytevector-length src)))
   1871        (cond
   1872          ((>= pos n) #f)
   1873          (else
   1874           (let ((b  (bytevector-u8-ref src pos))
   1875                 (pb (bytevector-u8-ref pat i)))
   1876             (cond
   1877               ((%fast-byte? b)
   1878                (if (= b pb)
   1879                    (%match-bytes src (+ pos 1) line (+ col 1) pat (+ i 1))
   1880                    #f))
   1881               (else
   1882                (let* ((p (%lex-peek src pos line col))
   1883                       (b2 (%pk-byte p)))
   1884                  (cond
   1885                    ((not b2) #f)
   1886                    ((= b2 pb)
   1887                     (%match-bytes src (%pk-pos p) (%pk-line p) (%pk-col p)
   1888                                   pat (+ i 1)))
   1889                    (else #f))))))))))))
   1890 
   1891 ;; --------------------------------------------------------------------
   1892 ;; tok-iter — streaming token source.
   1893 ;; --------------------------------------------------------------------
   1894 ;; Each pipeline layer (lex, pp, parser) wraps the layer below as a
   1895 ;; tok-iter. iter-next pulls one token at a time. iter-peek/iter-peek2
   1896 ;; cache lookahead in `buf`. iter-unget! pushes back. Live-data bound is
   1897 ;; lookahead (≤2) + per-layer state, not source length.
   1898 ;;
   1899 ;; Pull-fns must keep yielding EOF after the first EOF (idempotent).
   1900 (define-record-type tok-iter
   1901   (%tok-iter pull-fn state buf)
   1902   tok-iter?
   1903   (pull-fn tok-iter-pull-fn)
   1904   (state   tok-iter-state)
   1905   (buf     tok-iter-buf  tok-iter-buf-set!))
   1906 
   1907 (define (iter-next it)
   1908   (let ((b (tok-iter-buf it)))
   1909     (cond
   1910       ((null? b) ((tok-iter-pull-fn it) (tok-iter-state it)))
   1911       (else
   1912        (tok-iter-buf-set! it (cdr b))
   1913        (car b)))))
   1914 
   1915 (define (iter-peek it)
   1916   (let ((b (tok-iter-buf it)))
   1917     (cond
   1918       ((null? b)
   1919        (let ((t ((tok-iter-pull-fn it) (tok-iter-state it))))
   1920          (tok-iter-buf-set! it (list t))
   1921          t))
   1922       (else (car b)))))
   1923 
   1924 (define (iter-peek2 it)
   1925   (let ((b (tok-iter-buf it)))
   1926     (cond
   1927       ((null? b)
   1928        (let* ((t1 ((tok-iter-pull-fn it) (tok-iter-state it)))
   1929               (t2 ((tok-iter-pull-fn it) (tok-iter-state it))))
   1930          (tok-iter-buf-set! it (list t1 t2))
   1931          t2))
   1932       ((null? (cdr b))
   1933        (let ((t2 ((tok-iter-pull-fn it) (tok-iter-state it))))
   1934          (tok-iter-buf-set! it (cons (car b) (list t2)))
   1935          t2))
   1936       (else (car (cdr b))))))
   1937 
   1938 (define (iter-unget! it t)
   1939   (tok-iter-buf-set! it (cons t (tok-iter-buf it))))
   1940 
   1941 ;; Drain an iter to a list ending in EOF. Used by lex-tokenize /
   1942 ;; pp-expand so the cc-lex / cc-pp test runners can inspect the
   1943 ;; materialized stream.
   1944 (define (iter->list it)
   1945   (let loop ((acc '()))
   1946     (let ((t (iter-next it)))
   1947       (cond
   1948         ((eq? (tok-kind t) 'EOF) (reverse (cons t acc)))
   1949         (else (loop (cons t acc)))))))
   1950 
   1951 ;; --------------------------------------------------------------------
   1952 ;; list-iter — wrap an existing token list as a tok-iter. Yields each
   1953 ;; tok in turn; once exhausted, keeps yielding EOF (idempotent). The
   1954 ;; wrapped list typically already ends in EOF.
   1955 ;; --------------------------------------------------------------------
   1956 (define-record-type list-iter-state
   1957   (%list-iter-state toks)
   1958   list-iter-state?
   1959   (toks lis-toks lis-toks-set!))
   1960 
   1961 (define (make-list-iter toks)
   1962   (%tok-iter %list-iter-pull (%list-iter-state toks) '()))
   1963 
   1964 (define (%list-iter-pull st)
   1965   (let ((toks (lis-toks st)))
   1966     (cond
   1967       ((null? toks) (make-tok 'EOF #f #f))
   1968       (else
   1969        (lis-toks-set! st (cdr toks))
   1970        (car toks)))))
   1971 
   1972 ;; --------------------------------------------------------------------
   1973 ;; lex-iter — streaming lexer. Steady state: pos/line/col + bol? in
   1974 ;; lex-state; discarded per-token allocation is reclaimed by the GC.
   1975 ;; --------------------------------------------------------------------
   1976 ;; bol? — `#t` when no token has been emitted on the current physical
   1977 ;; line yet (start of file, or only NL + whitespace seen since the last
   1978 ;; line break). pp recognizes a directive only when its leading `#` is
   1979 ;; at line-start; we forward that decision into the token stream by
   1980 ;; emitting `HASH` instead of `(PUNCT hash …)` for a line-leading `#`.
   1981 (define-record-type lex-state
   1982   (%lex-state src file pos line col bol? done?)
   1983   lex-state?
   1984   (src   lex-state-src)
   1985   (file  lex-state-file)
   1986   (pos   lex-state-pos    lex-state-pos-set!)
   1987   (line  lex-state-line   lex-state-line-set!)
   1988   (col   lex-state-col    lex-state-col-set!)
   1989   (bol?  lex-state-bol?   lex-state-bol?-set!)
   1990   (done? lex-state-done?  lex-state-done?-set!))
   1991 
   1992 (define (make-lex-iter src file)
   1993   (%lex-init!)
   1994   (%tok-iter %lex-iter-pull
   1995              (%lex-state src file 0 1 1 #t #f)
   1996              '()))
   1997 
   1998 (define (%lex-iter-pull st)
   1999   (cond
   2000     ((lex-state-done? st)
   2001      ;; Idempotent EOF: keep yielding EOF after the first one.
   2002      (make-tok 'EOF #f (%loc (lex-state-file st)
   2003                               (lex-state-line st)
   2004                               (lex-state-col st))))
   2005     (else (%lex-iter-step st))))
   2006 
   2007 (define (%lex-iter-step st)
   2008   (let ((src  (lex-state-src st))
   2009         (file (lex-state-file st))
   2010         (pos  (lex-state-pos st))
   2011         (line (lex-state-line st))
   2012         (col  (lex-state-col st))
   2013         (bol? (lex-state-bol? st))
   2014         (kind #f) (val #f)
   2015         (loc-line 1) (loc-col 1)
   2016         (npos 0) (nline 1) (ncol 1) (nbol? #f))
   2017     (let* ((sw (%skip-ws-and-comments src pos line col file))
   2018            (pos1  (car sw))
   2019            (line1 (car (cdr sw)))
   2020            (col1  (car (cdr (cdr sw))))
   2021            (p     (%lex-peek src pos1 line1 col1))
   2022            (b     (%pk-byte p)))
   2023       (set! loc-line line1)
   2024       (set! loc-col col1)
   2025       (set! val #f) (set! nbol? #f)
   2026       (cond
   2027         ;; EOF
   2028         ((not b)
   2029          (set! kind 'EOF)
   2030          (set! npos pos1) (set! nline line1) (set! ncol col1))
   2031         ;; Newline → NL token; next call starts at bol.
   2032         ((%newline? b)
   2033          (set! kind 'NL)
   2034          (set! npos (%pk-pos p))
   2035          (set! nline (%pk-line p))
   2036          (set! ncol  (%pk-col  p))
   2037          (set! nbol? #t))
   2038         ;; Line-leading `#`: bare `#` becomes HASH; `##` falls
   2039         ;; through to punctuator (lexes as `paste`).
   2040         ((and bol? (= b 35))
   2041          (let* ((q  (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   2042                 (b2 (%pk-byte q)))
   2043            (cond
   2044              ((and b2 (= b2 35))
   2045               (let* ((r (%lex-read-punct src pos1 line1 col1 file))
   2046                      (tok (car r)) (rest (cdr r)))
   2047                 (set! kind 'PUNCT) (set! val (tok-value tok))
   2048                 (set! npos  (car rest))
   2049                 (set! nline (car (cdr rest)))
   2050                 (set! ncol  (car (cdr (cdr rest))))))
   2051              (else
   2052               (set! kind 'HASH)
   2053               (set! npos  (%pk-pos p))
   2054               (set! nline (%pk-line p))
   2055               (set! ncol  (%pk-col  p))))))
   2056         ;; Identifier / keyword
   2057         ((%ident-start? b)
   2058          (let* ((r (%lex-read-ident src pos1 line1 col1 file))
   2059                 (tok (car r)) (rest (cdr r)))
   2060            (set! kind (tok-kind tok))
   2061            (cond ((eq? (tok-kind tok) 'KW)
   2062                   (set! val (tok-value tok)))
   2063                  (else (set! val (tok-value tok))))
   2064            (set! npos  (car rest))
   2065            (set! nline (car (cdr rest)))
   2066            (set! ncol  (car (cdr (cdr rest))))))
   2067         ;; Number (digit start)
   2068         ((%digit? b)
   2069          (let* ((r (%lex-read-number src pos1 line1 col1 file))
   2070                 (tok (car r)) (rest (cdr r)))
   2071            (set! kind 'INT) (set! val (tok-value tok))
   2072            (set! npos  (car rest))
   2073            (set! nline (car (cdr rest)))
   2074            (set! ncol  (car (cdr (cdr rest))))))
   2075         ;; '.' is a punctuator unless followed by a digit (float).
   2076         ((= b 46)
   2077          (let* ((q  (%lex-peek src (%pk-pos p) (%pk-line p) (%pk-col p)))
   2078                 (b2 (%pk-byte q)))
   2079            (cond
   2080              ((and b2 (%digit? b2))
   2081               (die (%loc file line1 col1)
   2082                    "floating-point literal not supported"))
   2083              (else
   2084               (let* ((r (%lex-read-punct src pos1 line1 col1 file))
   2085                      (tok (car r)) (rest (cdr r)))
   2086                 (set! kind 'PUNCT) (set! val (tok-value tok))
   2087                 (set! npos  (car rest))
   2088                 (set! nline (car (cdr rest)))
   2089                 (set! ncol  (car (cdr (cdr rest)))))))))
   2090         ;; String
   2091         ((= b 34)
   2092          (let* ((r (%lex-read-string src pos1 line1 col1 file))
   2093                 (tok (car r)) (rest (cdr r))
   2094                 (bv  (tok-value tok)))
   2095            (set! kind 'STR)
   2096            (set! val bv)
   2097            (set! npos  (car rest))
   2098            (set! nline (car (cdr rest)))
   2099            (set! ncol  (car (cdr (cdr rest))))))
   2100         ;; Char
   2101         ((= b 39)
   2102          (let* ((r (%lex-read-char src pos1 line1 col1 file))
   2103                 (tok (car r)) (rest (cdr r)))
   2104            (set! kind 'CHAR) (set! val (tok-value tok))
   2105            (set! npos  (car rest))
   2106            (set! nline (car (cdr rest)))
   2107            (set! ncol  (car (cdr (cdr rest))))))
   2108         ;; Punctuator (default)
   2109         (else
   2110          (let* ((r (%lex-read-punct src pos1 line1 col1 file))
   2111                 (tok (car r)) (rest (cdr r)))
   2112            ;; Line-leading `%:` digraph also acts as HASH for directives.
   2113            (cond
   2114              ((and bol? (eq? (tok-value tok) 'hash))
   2115               (set! kind 'HASH))
   2116              (else
   2117               (set! kind 'PUNCT) (set! val (tok-value tok))))
   2118            (set! npos  (car rest))
   2119            (set! nline (car (cdr rest)))
   2120            (set! ncol  (car (cdr (cdr rest))))))))
   2121     ;; Advance the iterator state and return the token.
   2122     (cond
   2123       ((eq? kind 'EOF)
   2124        (lex-state-done?-set! st #t)
   2125        (make-tok 'EOF #f (%loc file loc-line loc-col)))
   2126       (else
   2127        (lex-state-pos-set! st npos)
   2128        (lex-state-line-set! st nline)
   2129        (lex-state-col-set! st ncol)
   2130        (lex-state-bol?-set! st nbol?)
   2131        (make-tok kind val (%loc file loc-line loc-col))))))
   2132 
   2133 ;; Drain a lex-iter into a list ending in EOF, for the cc-lex test
   2134 ;; runner. Production callers chain make-lex-iter directly.
   2135 (define (lex-tokenize src file)
   2136   (iter->list (make-lex-iter src file)))
   2137 ;; cc/pp.scm — preprocessor. Hide-set per C11 6.10.3.4.
   2138 ;; #include rejected (CC.md §Toolchain envelope).
   2139 
   2140 ;; --- helpers ---
   2141 (define (%pp-bv-mem? x xs)
   2142   (cond ((null? xs) #f)
   2143         ((bv= x (car xs)) #t)
   2144         (else (%pp-bv-mem? x (cdr xs)))))
   2145 
   2146 (define (%pp-bv-union a b)
   2147   (cond ((null? a) b)
   2148         ((%pp-bv-mem? (car a) b) (%pp-bv-union (cdr a) b))
   2149         (else (cons (car a) (%pp-bv-union (cdr a) b)))))
   2150 
   2151 (define (%pp-with-hide t hide)
   2152   (%tok (tok-kind t) (tok-value t) (tok-loc t) hide))
   2153 (define (%pp-with-loc t loc)
   2154   (%tok (tok-kind t) (tok-value t) loc (tok-hide t)))
   2155 
   2156 ;; --- pp-state (private record) ---
   2157 ;; cond-stack: list of (active? . has-taken?). Outer-active gating is
   2158 ;; computed by walking the stack rather than encoding it in frames.
   2159 ;;
   2160 ;; Streaming fields drive make-pp-iter; the bounded-buffer path used
   2161 ;; by pp-eval-cexpr leaves them at #f / '().
   2162 ;;   lex-iter   — upstream tok-iter, or #f
   2163 ;;   up-pending — toks unshifted upstream (macro-expansion bodies that
   2164 ;;                must be re-scanned for further expansion)
   2165 ;;   out-buf    — toks already dispatched but stashed for the next pull
   2166 ;;                (peek-and-fuse for adjacent STRs lookahead overshoots
   2167 ;;                by one tok, which lands here)
   2168 (define-record-type pp-state
   2169   (%pp-state macros cond-stack cur-file line-delta lex-iter up-pending out-buf)
   2170   pp-state?
   2171   (macros     pps-macros     pps-macros-set!)
   2172   (cond-stack pps-cond-stack pps-cond-stack-set!)
   2173   (cur-file   pps-cur-file   pps-cur-file-set!)
   2174   (line-delta pps-line-delta pps-line-delta-set!)
   2175   (lex-iter   pps-lex-iter)
   2176   (up-pending pps-up-pending pps-up-pending-set!)
   2177   (out-buf    pps-out-buf    pps-out-buf-set!))
   2178 
   2179 (define (%pp-make-state defs) (%pp-state defs '() #f 0 #f '() '()))
   2180 
   2181 (define (%pp-active? state)
   2182   (let loop ((xs (pps-cond-stack state)))
   2183     (cond ((null? xs) #t)
   2184           ((not (car (car xs))) #f)
   2185           (else (loop (cdr xs))))))
   2186 
   2187 ;; Active for the *parent* of the top frame (used by elif/else).
   2188 (define (%pp-parent-active? state)
   2189   (let ((cs (pps-cond-stack state)))
   2190     (cond ((null? cs) #t)
   2191           (else
   2192            (let loop ((xs (cdr cs)))
   2193              (cond ((null? xs) #t)
   2194                    ((not (car (car xs))) #f)
   2195                    (else (loop (cdr xs)))))))))
   2196 
   2197 ;; --- token classification ---
   2198 (define (%pp-eof? t)   (eq? (tok-kind t) 'EOF))
   2199 (define (%pp-nl? t)    (eq? (tok-kind t) 'NL))
   2200 (define (%pp-hash? t)  (eq? (tok-kind t) 'HASH))
   2201 (define (%pp-ident? t) (eq? (tok-kind t) 'IDENT))
   2202 (define (%pp-int? t)   (eq? (tok-kind t) 'INT))
   2203 (define (%pp-punct? t pname)
   2204   (and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) pname)))
   2205 (define (%pp-ident-name? t name-bv)
   2206   (and (%pp-ident? t) (bv= (tok-value t) name-bv)))
   2207 (define (%pp-skip-ws toks) toks)
   2208 
   2209 ;; --- built-in macro names ---
   2210 (define %pp-bv-FILE   "__FILE__")
   2211 (define %pp-bv-LINE   "__LINE__")
   2212 (define %pp-bv-STDC   "__STDC__")
   2213 (define %pp-bv-LISPCC "__LISPCC__")
   2214 (define %pp-bv-DATE   "__DATE__")
   2215 (define %pp-bv-TIME   "__TIME__")
   2216 (define %pp-bv-STDC-VERSION "__STDC_VERSION__")
   2217 (define %pp-bv-STDC-HOSTED  "__STDC_HOSTED__")
   2218 (define %pp-bv-VA-ARGS "__VA_ARGS__")
   2219 (define %pp-bv-defined "defined")
   2220 
   2221 ;; Fixed values for reproducibility — we don't read the wall clock.
   2222 (define %pp-bv-DATE-VALUE "Jan  1 1970")
   2223 (define %pp-bv-TIME-VALUE "00:00:00")
   2224 
   2225 (define (%pp-builtin? name)
   2226   (or (bv= name %pp-bv-FILE) (bv= name %pp-bv-LINE)
   2227       (bv= name %pp-bv-STDC) (bv= name %pp-bv-LISPCC)
   2228       (bv= name %pp-bv-DATE) (bv= name %pp-bv-TIME)
   2229       (bv= name %pp-bv-STDC-VERSION) (bv= name %pp-bv-STDC-HOSTED)))
   2230 
   2231 (define (%pp-expand-builtin name loc state)
   2232   ;; Emit the token at the ORIGINAL loc; %pp-relocate downstream will
   2233   ;; apply pps-cur-file / pps-line-delta. Doing the rewrite here too
   2234   ;; (then letting relocate re-apply it) double-shifts __LINE__'s loc.
   2235   ;; The VALUE of __LINE__ / __FILE__ already reflects the post-#line
   2236   ;; mapping because we compute `file`/`line` from cur-file/line-delta.
   2237   (let* ((file (or (pps-cur-file state) (loc-file loc)))
   2238          (line (+ (loc-line loc) (pps-line-delta state))))
   2239     (cond
   2240       ((bv= name %pp-bv-FILE)         (list (%tok 'STR file loc '())))
   2241       ((bv= name %pp-bv-LINE)         (list (%tok 'INT line loc '())))
   2242       ((bv= name %pp-bv-STDC)         (list (%tok 'INT 1 loc '())))
   2243       ((bv= name %pp-bv-LISPCC)       (list (%tok 'INT 1 loc '())))
   2244       ((bv= name %pp-bv-DATE)         (list (%tok 'STR %pp-bv-DATE-VALUE loc '())))
   2245       ((bv= name %pp-bv-TIME)         (list (%tok 'STR %pp-bv-TIME-VALUE loc '())))
   2246       ((bv= name %pp-bv-STDC-VERSION) (list (%tok 'INT 199901 loc '())))
   2247       ((bv= name %pp-bv-STDC-HOSTED)  (list (%tok 'INT 1 loc '())))
   2248       (else (die loc "internal: not a builtin" name)))))
   2249 
   2250 ;; --- buf-list: simple reversed-list buffer of toks ---
   2251 (define-record-type buf-list
   2252   (%buf-list xs)
   2253   buf-list?
   2254   (xs buf-list-xs buf-list-xs-set!))
   2255 (define (make-buf-list) (%buf-list '()))
   2256 (define (buf-list-push! b t) (buf-list-xs-set! b (cons t (buf-list-xs b))))
   2257 (define (buf-list-push-many! b ts)
   2258   (let loop ((ts ts))
   2259     (cond ((null? ts) #t)
   2260           (else (buf-list-push! b (car ts)) (loop (cdr ts))))))
   2261 (define (buf-list-flush b) (reverse (buf-list-xs b)))
   2262 
   2263 ;; --- make-pp-iter: streaming preprocessor ---
   2264 ;; Wraps a lex-iter (or any tok-iter). Returns a tok-iter. Live data
   2265 ;; bounded by parser state + lookahead, not source length. Adjacent-STR
   2266 ;; fusion happens inline via peek-and-stash.
   2267 (define (make-pp-iter src-iter initial-defines)
   2268   (let ((st (%pp-state (alist->hash initial-defines)
   2269                        '() #f 0 src-iter '() '())))
   2270     (%tok-iter %pp-iter-pull st '())))
   2271 
   2272 (define (%pp-iter-pull st)
   2273   (let ((ob (pps-out-buf st)))
   2274     (cond
   2275       ((not (null? ob))
   2276        (pps-out-buf-set! st (cdr ob))
   2277        (car ob))
   2278       (else (%pp-maybe-fuse-str st (%pp-dispatch-step st))))))
   2279 
   2280 ;; --- upstream helpers ---
   2281 ;; Upstream tokens come either from up-pending (macro-expansion bodies
   2282 ;; that need re-scanning) or from the wrapped lex-iter.
   2283 (define (%pp-pull-upstream st)
   2284   (let ((up (pps-up-pending st)))
   2285     (cond
   2286       ((not (null? up))
   2287        (pps-up-pending-set! st (cdr up))
   2288        (car up))
   2289       (else (iter-next (pps-lex-iter st))))))
   2290 
   2291 (define (%pp-peek-upstream st)
   2292   (let ((up (pps-up-pending st)))
   2293     (cond
   2294       ((not (null? up)) (car up))
   2295       (else (iter-peek (pps-lex-iter st))))))
   2296 
   2297 ;; Push toks to the front of upstream so (car toks) is yielded next.
   2298 (define (%pp-unshift-upstream! st toks)
   2299   (pps-up-pending-set! st (append toks (pps-up-pending st))))
   2300 
   2301 ;; Collect tokens up to (not including) NL or EOF. NL is consumed; EOF
   2302 ;; is unshifted back so the main loop sees it.
   2303 (define (%pp-collect-line-stream st)
   2304   (let loop ((acc '()))
   2305     (let ((t (%pp-pull-upstream st)))
   2306       (cond
   2307         ((%pp-eof? t)
   2308          (%pp-unshift-upstream! st (list t))
   2309          (reverse acc))
   2310         ((%pp-nl? t) (reverse acc))
   2311         (else (loop (cons t acc)))))))
   2312 
   2313 ;; Streaming arg collection for fn-like macro calls. Position is just
   2314 ;; AFTER the opening `(`. Returns the list of arg-tokenlists.
   2315 (define (%pp-collect-args-stream st call-loc)
   2316   (let loop ((depth 0) (cur '()) (args '()))
   2317     (let ((t (%pp-pull-upstream st)))
   2318       (cond
   2319         ((%pp-eof? t)
   2320          (die call-loc "macro call: unterminated argument list"))
   2321         ((and (= depth 0) (%pp-punct? t 'rparen))
   2322          (cond
   2323            ;; Empty parens count as one empty argument; bind-args
   2324            ;; degenerates this back to "no args" for 0-param macros.
   2325            ((and (null? args) (null? cur)) (list '()))
   2326            (else (reverse (cons (reverse cur) args)))))
   2327         ((and (= depth 0) (%pp-punct? t 'comma))
   2328          (loop 0 '() (cons (reverse cur) args)))
   2329         ((%pp-punct? t 'lparen)
   2330          (loop (+ depth 1) (cons t cur) args))
   2331         ((%pp-punct? t 'rparen)
   2332          (loop (- depth 1) (cons t cur) args))
   2333         (else (loop depth (cons t cur) args))))))
   2334 
   2335 ;; Single dispatch step. Returns one post-pp tok (skipping NLs,
   2336 ;; processing directives, expanding macros). Does NOT apply STR-fusion
   2337 ;; — that happens one layer up in %pp-iter-pull, otherwise the
   2338 ;; recursive lookahead during fusion would itself fuse further STRs
   2339 ;; and drag tokens past the run into out-buf.
   2340 (define (%pp-dispatch-step st)
   2341   (let ((t (%pp-pull-upstream st)))
   2342     (cond
   2343       ((%pp-eof? t)
   2344        (cond ((not (null? (pps-cond-stack st)))
   2345               (die (tok-loc t) "unterminated #if/#ifdef/#ifndef"))
   2346              (else t)))
   2347       ((%pp-nl? t) (%pp-dispatch-step st))
   2348       ((%pp-hash? t)
   2349        (let ((line (%pp-collect-line-stream st)))
   2350          (%pp-dispatch-directive t line st #f)
   2351          (%pp-dispatch-step st)))
   2352       ((not (%pp-active? st))
   2353        (%pp-dispatch-step st))
   2354       ((%pp-ident? t)
   2355        (let ((name (tok-value t)))
   2356          (cond
   2357            ((%pp-bv-mem? name (tok-hide t))
   2358             (%pp-relocate t st))
   2359            ((%pp-builtin? name)
   2360             (let ((toks (%pp-expand-builtin name (tok-loc t) st)))
   2361               (%pp-unshift-upstream! st toks)
   2362               (%pp-dispatch-step st)))
   2363            (else
   2364             (let ((m (%hash-ref (pps-macros st) name)))
   2365               (cond
   2366                 ((not m) (%pp-relocate t st))
   2367                 ((eq? (macro-kind m) 'obj)
   2368                  (let ((body (%pp-prepare-body (macro-body m)
   2369                                (cons name (tok-hide t))
   2370                                (tok-loc t))))
   2371                    (%pp-unshift-upstream! st body)
   2372                    (%pp-dispatch-step st)))
   2373                 (else
   2374                  ;; fn-like or fn-vararg: peek upstream for `(`. If
   2375                  ;; not present, pass IDENT through unchanged (no
   2376                  ;; consumption); the next iter call will process the
   2377                  ;; following tok normally.
   2378                  (let ((next (%pp-peek-upstream st)))
   2379                    (cond
   2380                      ((not (%pp-punct? next 'lparen))
   2381                       (%pp-relocate t st))
   2382                      (else
   2383                       (%pp-pull-upstream st)        ; consume `(`
   2384                       (let* ((args (%pp-collect-args-stream st (tok-loc t)))
   2385                              (params (macro-params m))
   2386                              (variadic? (eq? (macro-kind m) 'fn-vararg))
   2387                              (env (%pp-bind-args params args variadic? (tok-loc t)))
   2388                              (sub (%pp-substitute (macro-body m) env (tok-loc t) st))
   2389                              (body (%pp-prepare-body sub
   2390                                      (cons name (tok-hide t))
   2391                                      (tok-loc t))))
   2392                         (%pp-unshift-upstream! st body)
   2393                         (%pp-dispatch-step st))))))))))))
   2394       (else (%pp-relocate t st)))))
   2395 
   2396 ;; Translation phase 6 (peek-and-fuse). If `cur` is STR, look at the
   2397 ;; next post-pp tok; if it's STR, fuse and repeat. Anything else gets
   2398 ;; stashed in out-buf for the next iter-next call. Lookahead goes
   2399 ;; through %pp-dispatch-step (no fusion), so a non-STR neighbor
   2400 ;; correctly terminates the run.
   2401 (define (%pp-maybe-fuse-str st cur)
   2402   (cond
   2403     ((not (eq? (tok-kind cur) 'STR)) cur)
   2404     (else
   2405      (let loop ((cur cur))
   2406        (let ((next (%pp-dispatch-step st)))
   2407          (cond
   2408            ((eq? (tok-kind next) 'STR)
   2409             (loop (%tok 'STR
   2410                         (bytevector-append (tok-value cur) (tok-value next))
   2411                         (tok-loc cur)
   2412                         (tok-hide cur))))
   2413            (else
   2414             (pps-out-buf-set! st (cons next (pps-out-buf st)))
   2415             cur)))))))
   2416 
   2417 ;; Drain a pp-iter into a list ending in EOF, for the cc-pp test
   2418 ;; runner. The input token list becomes the upstream via make-list-iter.
   2419 ;; Production callers chain make-pp-iter directly over a make-lex-iter.
   2420 (define (pp-expand toks initial-defines)
   2421   (iter->list (make-pp-iter (make-list-iter toks) initial-defines)))
   2422 
   2423 ;; --- directive dispatch ---
   2424 ;; pmatch-based on the directive name bytes. Byte literals use the explicit
   2425 ;; mixed string/bytevector rule from micro+boot2.
   2426 ;; Directive name can arrive as IDENT (most cases) or KW (`if` and `else`
   2427 ;; are C keywords promoted by lex; their KW symbol values map back to bv
   2428 ;; via symbol->string).
   2429 (define (%pp-directive-name t)
   2430   (cond ((eq? (tok-kind t) 'IDENT) (tok-value t))
   2431         ((eq? (tok-kind t) 'KW)    (symbol->string (tok-value t)))
   2432         (else #f)))
   2433 
   2434 (define (%pp-dispatch-directive hash-tok line state out)
   2435   (let ((line (%pp-skip-ws line)))
   2436     (cond
   2437       ((null? line) #t)            ; bare `#` line — null directive
   2438       ((%pp-directive-name (car line))
   2439        (let ((name (%pp-directive-name (car line)))
   2440              (rest (cdr line))
   2441              (loc  (tok-loc (car line))))
   2442          (pmatch name
   2443            ("define"  (cond ((%pp-active? state) (%pp-do-define rest state)) (else #t)))
   2444            ("undef"   (cond ((%pp-active? state) (%pp-do-undef rest state))  (else #t)))
   2445            ("if"      (%pp-do-if rest state))
   2446            ("ifdef"   (%pp-do-ifdef rest state))
   2447            ("ifndef"  (%pp-do-ifndef rest state))
   2448            ("elif"    (%pp-do-elif rest state))
   2449            ("else"    (%pp-do-else rest state))
   2450            ("endif"   (%pp-do-endif rest state))
   2451            ("error"   (cond ((%pp-active? state)
   2452                              (%pp-do-error (cons (car line) rest) state))
   2453                             (else #t)))
   2454            ("line"    (cond ((%pp-active? state)
   2455                              ;; Macro-expand the operands BEFORE
   2456                              ;; processing (`#line MACRO`). Pre-expansion
   2457                              ;; we capture the directive's source line so
   2458                              ;; the line-delta math doesn't anchor on a
   2459                              ;; macro definition site.
   2460                              (let ((here (cond
   2461                                            ((null? rest)
   2462                                             (loc-line (tok-loc hash-tok)))
   2463                                            (else
   2464                                             (loc-line (tok-loc (car rest)))))))
   2465                                (%pp-do-line (%pp-expand-line rest state)
   2466                                             state here)))
   2467                             (else #t)))
   2468            ("pragma"  (cond ((%pp-active? state) (%pp-do-pragma rest state)) (else #t)))
   2469            ("include" (cond ((%pp-active? state) (%pp-do-include rest state)) (else #t)))
   2470            (else (die loc "unknown preprocessor directive" name)))))
   2471       (else
   2472        (die (tok-loc (car line)) "expected directive name after '#'"
   2473             (tok-kind (car line)))))))
   2474 
   2475 ;; --- #define ---
   2476 ;; function-like vs object-like is decided by an immediately-adjacent `(`.
   2477 ;; "Adjacent" = column of `(` equals column of name + length of name.
   2478 (define (%pp-do-define line state)
   2479   (cond
   2480     ((null? line) (die #f "#define requires a macro name"))
   2481     ((not (%pp-ident? (car line)))
   2482      (die (tok-loc (car line)) "#define: expected identifier"))
   2483     (else
   2484      (let* ((nt (car line)) (name (tok-value nt)) (rest (cdr line)))
   2485        (cond
   2486          ((and (not (null? rest))
   2487                (%pp-punct? (car rest) 'lparen)
   2488                (= (loc-col (tok-loc (car rest)))
   2489                   (+ (loc-col (tok-loc nt))
   2490                      (bytevector-length name))))
   2491           (%pp-define-fn name (cdr rest) (tok-loc nt) state))
   2492          (else
   2493           (let ((m (%macro 'obj '() rest)))
   2494             (%hash-set! (pps-macros state) name m))))))))
   2495 
   2496 (define (%pp-define-fn name post-lparen nloc state)
   2497   (let loop ((toks post-lparen) (params '()) (variadic? #f))
   2498     (cond
   2499       ((null? toks) (die nloc "#define: unterminated parameter list"))
   2500       ((%pp-punct? (car toks) 'rparen)
   2501        (let* ((body (cdr toks))
   2502               (kind (if variadic? 'fn-vararg 'fn))
   2503               (m    (%macro kind (reverse params) body)))
   2504          (%hash-set! (pps-macros state) name m)))
   2505       ((%pp-punct? (car toks) 'ellipsis)
   2506        (let ((rest (cdr toks)))
   2507          (cond
   2508            ((null? rest) (die (tok-loc (car toks)) "#define: '...' must precede ')'"))
   2509            ((%pp-punct? (car rest) 'rparen) (loop rest params #t))
   2510            (else (die (tok-loc (car rest)) "#define: garbage after '...'")))))
   2511       ((null? params)
   2512        (cond
   2513          ((%pp-ident? (car toks))
   2514           (loop (cdr toks) (cons (tok-value (car toks)) params) #f))
   2515          (else (die (tok-loc (car toks)) "#define: expected parameter name"))))
   2516       (else
   2517        (cond
   2518          ((%pp-punct? (car toks) 'comma)
   2519           (let ((after (cdr toks)))
   2520             (cond
   2521               ((null? after) (die (tok-loc (car toks)) "#define: trailing ','"))
   2522               ((%pp-punct? (car after) 'ellipsis)
   2523                (let ((aa (cdr after)))
   2524                  (cond
   2525                    ((and (not (null? aa)) (%pp-punct? (car aa) 'rparen))
   2526                     (loop aa params #t))
   2527                    (else (die (tok-loc (car after))
   2528                               "#define: '...' must precede ')'")))))
   2529               ((%pp-ident? (car after))
   2530                (loop (cdr after) (cons (tok-value (car after)) params) #f))
   2531               (else
   2532                (die (tok-loc (car after))
   2533                     "#define: expected parameter name after ','")))))
   2534          (else (die (tok-loc (car toks))
   2535                     "#define: expected ',' or ')' in parameter list")))))))
   2536 
   2537 ;; --- #undef ---
   2538 (define (%pp-do-undef line state)
   2539   (cond
   2540     ((null? line) (die #f "#undef requires a macro name"))
   2541     ((not (%pp-ident? (car line)))
   2542      (die (tok-loc (car line)) "#undef: expected identifier"))
   2543     (else
   2544      (%hash-delete! (pps-macros state) (tok-value (car line))))))
   2545 
   2546 ;; --- #if / #ifdef / #ifndef / #elif / #else / #endif ---
   2547 ;; cond-stack frame: (active? taken? else?). active? gates the body
   2548 ;; until the next #elif/#else/#endif; taken? records whether ANY arm
   2549 ;; (the original #if branch or any #elif) has matched, so later arms
   2550 ;; stay inactive; else? records that we have already passed an #else
   2551 ;; in this frame, so a subsequent #elif/#else is rejected.
   2552 (define (%pp-frame a? t? e?) (list a? t? e?))
   2553 (define (%pp-frame-active? f) (car f))
   2554 (define (%pp-frame-taken?  f) (car (cdr f)))
   2555 (define (%pp-frame-else?   f) (car (cdr (cdr f))))
   2556 
   2557 (define (%pp-do-if line state)
   2558   (cond
   2559     ((not (%pp-active? state))
   2560      (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
   2561     (else
   2562      (let* ((v (pp-eval-cexpr line state))
   2563             (a? (not (%c-value-zero? v))))
   2564        (pps-cond-stack-set! state (cons (%pp-frame a? a? #f) (pps-cond-stack state)))))))
   2565 
   2566 (define (%pp-do-ifdef line state)
   2567   (cond
   2568     ((not (%pp-active? state))
   2569      (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
   2570     (else
   2571      (let ((d? (%pp-defined? (%pp-name-of-single line) state)))
   2572        (pps-cond-stack-set! state
   2573          (cons (%pp-frame d? d? #f) (pps-cond-stack state)))))))
   2574 
   2575 (define (%pp-do-ifndef line state)
   2576   (cond
   2577     ((not (%pp-active? state))
   2578      (pps-cond-stack-set! state (cons (%pp-frame #f #f #f) (pps-cond-stack state))))
   2579     (else
   2580      (let ((a? (not (%pp-defined? (%pp-name-of-single line) state))))
   2581        (pps-cond-stack-set! state
   2582          (cons (%pp-frame a? a? #f) (pps-cond-stack state)))))))
   2583 
   2584 (define (%pp-name-of-single line)
   2585   (cond
   2586     ((null? line) (die #f "#ifdef/#ifndef: missing identifier"))
   2587     ((not (%pp-ident? (car line)))
   2588      (die (tok-loc (car line)) "#ifdef/#ifndef: expected identifier"))
   2589     (else (tok-value (car line)))))
   2590 
   2591 (define (%pp-defined? name state)
   2592   (or (%hash-ref (pps-macros state) name)
   2593       (%pp-builtin? name)
   2594       #f))
   2595 
   2596 (define (%pp-do-elif line state)
   2597   (let ((cs (pps-cond-stack state)))
   2598     (cond
   2599       ((null? cs) (die #f "#elif outside #if"))
   2600       (else
   2601        (let* ((top (car cs)) (rest (cdr cs))
   2602               (taken? (%pp-frame-taken? top))
   2603               (else?  (%pp-frame-else? top))
   2604               (par? (%pp-parent-active? state)))
   2605          (cond
   2606            (else? (die #f "#elif after #else"))
   2607            ((or (not par?) taken?)
   2608             (pps-cond-stack-set! state (cons (%pp-frame #f taken? #f) rest)))
   2609            (else
   2610             (let* ((v (pp-eval-cexpr line state))
   2611                    (a? (not (%c-value-zero? v))))
   2612               (pps-cond-stack-set! state
   2613                 (cons (%pp-frame a? (or a? taken?) #f) rest))))))))))
   2614 
   2615 (define (%pp-do-else line state)
   2616   (let ((cs (pps-cond-stack state)))
   2617     (cond
   2618       ((null? cs) (die #f "#else outside #if"))
   2619       (else
   2620        (let* ((top (car cs)) (rest (cdr cs))
   2621               (taken? (%pp-frame-taken? top))
   2622               (else?  (%pp-frame-else? top))
   2623               (par? (%pp-parent-active? state)))
   2624          (cond
   2625            (else? (die #f "#else after #else"))
   2626            ((not par?)
   2627             (pps-cond-stack-set! state (cons (%pp-frame #f taken? #t) rest)))
   2628            (taken?
   2629             (pps-cond-stack-set! state (cons (%pp-frame #f #t #t) rest)))
   2630            (else
   2631             (pps-cond-stack-set! state (cons (%pp-frame #t #t #t) rest)))))))))
   2632 
   2633 (define (%pp-do-endif line state)
   2634   (let ((cs (pps-cond-stack state)))
   2635     (cond ((null? cs) (die #f "#endif outside #if"))
   2636           (else (pps-cond-stack-set! state (cdr cs))))))
   2637 
   2638 ;; --- #error ---
   2639 ;; line[0] is the directive name "error"; the rest is the user message.
   2640 (define (%pp-do-error line state)
   2641   (let* ((msg-toks (if (null? line) '() (cdr line)))
   2642          (loc (if (null? line) #f (tok-loc (car line))))
   2643          (msg (%pp-toks->display msg-toks)))
   2644     (die loc "#error" msg)))
   2645 
   2646 ;; Per C11 §6.10.3.2 ¶2: whitespace between argument tokens becomes a
   2647 ;; single space; absence of whitespace must NOT introduce one. We
   2648 ;; approximate "had whitespace" by comparing locations: a space goes
   2649 ;; in iff the next token does not abut the previous one (different
   2650 ;; line, or column gap larger than the prev spelling length).
   2651 (define (%pp-toks->display toks)
   2652   (let loop ((toks toks) (prev #f) (prev-bv #f) (acc '()))
   2653     (cond
   2654       ((null? toks) (bv-cat (reverse acc)))
   2655       (else
   2656        (let* ((t (car toks)) (p (%pp-tok->bv t))
   2657               (sep? (cond
   2658                       ((not prev) #f)
   2659                       ((or (not (tok-loc prev)) (not (tok-loc t))) #t)
   2660                       ((not (= (loc-line (tok-loc prev))
   2661                                (loc-line (tok-loc t)))) #t)
   2662                       (else
   2663                        (not (= (loc-col (tok-loc t))
   2664                                (+ (loc-col (tok-loc prev))
   2665                                   (bytevector-length prev-bv))))))))
   2666          (loop (cdr toks) t p
   2667                (if sep? (cons p (cons " " acc)) (cons p acc))))))))
   2668 
   2669 ;; Reverse-map punctuator symbol -> source spelling. %punct-alist may
   2670 ;; map several spellings to the same symbol (e.g. both "[" and "<:"
   2671 ;; resolve to 'lbrack); the 1-byte canonical forms appear last in the
   2672 ;; source list, so a last-wins fold yields "[" rather than the digraph.
   2673 (define %pp-punct-spell
   2674   (let loop ((al %punct-alist) (acc '()))
   2675     (cond ((null? al) acc)
   2676           (else (loop (cdr al)
   2677                       (alist-set (cdr (car al)) (car (car al)) acc))))))
   2678 
   2679 (define (%pp-punct-spelling sym)
   2680   (or (alist-ref/eq sym %pp-punct-spell) (symbol->string sym)))
   2681 
   2682 (define (%pp-tok->bv t)
   2683   (let ((k (tok-kind t)) (v (tok-value t)))
   2684     (cond
   2685       ((eq? k 'IDENT) v)
   2686       ((eq? k 'INT)   (%c-value-source-bv v))
   2687       ((eq? k 'STR)   (%pp-quote-bytes v 34))
   2688       ((eq? k 'CHAR)  (%pp-quote-bytes (bv-of-byte v) 39))
   2689       ((eq? k 'KW)    (symbol->string v))
   2690       ((eq? k 'PUNCT) (%pp-punct-spelling v))
   2691       (else "?"))))
   2692 
   2693 ;; Reconstruct a string/char literal source spelling from cooked content.
   2694 ;; Per C11 6.10.3.2: stringize must reproduce the source spelling of
   2695 ;; STR/CHAR constants — every `"` and `\` is prefixed with `\`, and
   2696 ;; the common control-character escapes are restored from their cooked
   2697 ;; bytes. `delim` is 34 for STR, 39 for CHAR.
   2698 (define (%pp-quote-bytes bv delim)
   2699   (let* ((n (bytevector-length bv))
   2700          (delim-bv (bv-of-byte delim)))
   2701     (let loop ((i 0) (acc (list delim-bv)))
   2702       (cond
   2703         ((= i n) (bv-cat (reverse (cons delim-bv acc))))
   2704         (else
   2705          (let ((b (bytevector-u8-ref bv i)))
   2706            (cond
   2707              ((or (= b delim) (= b 92))
   2708               (loop (+ i 1) (cons (bv-of-byte b) (cons "\\" acc))))
   2709              ((= b 10) (loop (+ i 1) (cons "\\n" acc)))
   2710              ((= b 9)  (loop (+ i 1) (cons "\\t" acc)))
   2711              ((= b 13) (loop (+ i 1) (cons "\\r" acc)))
   2712              (else
   2713               (loop (+ i 1) (cons (bv-of-byte b) acc))))))))))
   2714 
   2715 ;; --- #line / #pragma / #include ---
   2716 ;; Approximate #line: subsequent toks have line = (orig-line + delta),
   2717 ;; where delta = (N - here-line - 1). Good enough for most cases.
   2718 (define (%pp-do-line line state here)
   2719   (cond
   2720     ((null? line) (die #f "#line requires a line number"))
   2721     ((not (%pp-int? (car line)))
   2722      (die (tok-loc (car line)) "#line: expected integer"))
   2723     (else
   2724      (let* ((nt (car line)) (n (%c-int-raw (tok-value nt)))
   2725             (rest (cdr line)))
   2726        (pps-line-delta-set! state (- n here 1))
   2727        (cond
   2728          ((null? rest) #t)
   2729          ((eq? (tok-kind (car rest)) 'STR)
   2730           (pps-cur-file-set! state (tok-value (car rest))))
   2731          (else (die (tok-loc (car rest))
   2732                     "#line: expected string after number")))))))
   2733 
   2734 (define (%pp-do-pragma line state) #t)
   2735 
   2736 (define (%pp-do-include line state)
   2737   (die (if (null? line) #f (tok-loc (car line)))
   2738        "#include: file inclusion is handled upstream by pre-flatten"))
   2739 
   2740 ;; --- macro expansion engine ---
   2741 ;; Walk toks; for each IDENT, look up in macros / builtins. Hide-set:
   2742 ;; if the name is in t.hide, don't expand. Otherwise expand and rescan
   2743 ;; the produced body, with hide += {name}.
   2744 (define (%pp-emit-expanded toks state out)
   2745   (let loop ((toks toks))
   2746     (cond
   2747       ((null? toks) #t)
   2748       (else
   2749        (let* ((t (car toks)) (rest (cdr toks)))
   2750          (cond
   2751            ((not (%pp-ident? t))
   2752             (buf-list-push! out (%pp-relocate t state))
   2753             (loop rest))
   2754            (else
   2755             (let ((name (tok-value t)))
   2756               (cond
   2757                 ((%pp-bv-mem? name (tok-hide t))
   2758                  (buf-list-push! out (%pp-relocate t state))
   2759                  (loop rest))
   2760                 ((%pp-builtin? name)
   2761                  (buf-list-push-many! out
   2762                    (%pp-expand-builtin name (tok-loc t) state))
   2763                  (loop rest))
   2764                 (else
   2765                  (let ((m (%hash-ref (pps-macros state) name)))
   2766                    (cond
   2767                      ((not m)
   2768                       (buf-list-push! out (%pp-relocate t state))
   2769                       (loop rest))
   2770                      (else
   2771                       (%pp-apply-macro t m rest state out loop))))))))))))))
   2772 
   2773 (define (%pp-apply-macro t m rest state out cont)
   2774   (let ((kind (macro-kind m)) (name (tok-value t)))
   2775     (cond
   2776       ((eq? kind 'obj)
   2777        (let ((bodies (%pp-prepare-body (macro-body m)
   2778                        (cons name (tok-hide t))
   2779                        (tok-loc t))))
   2780          (%pp-emit-expanded bodies state out)
   2781          (cont rest)))
   2782       (else
   2783        (let ((after (%pp-skip-ws rest)))
   2784          (cond
   2785            ((or (null? after) (not (%pp-punct? (car after) 'lparen)))
   2786             (buf-list-push! out (%pp-relocate t state))
   2787             (cont rest))
   2788            (else
   2789             (let* ((ar (%pp-collect-args (cdr after) (tok-loc t)))
   2790                    (args (car ar)) (rest2 (cdr ar))
   2791                    (params (macro-params m))
   2792                    (variadic? (eq? kind 'fn-vararg))
   2793                    (env (%pp-bind-args params args variadic? (tok-loc t)))
   2794                    (sub (%pp-substitute (macro-body m) env (tok-loc t) state))
   2795                    (bodies (%pp-prepare-body sub
   2796                              (cons name (tok-hide t))
   2797                              (tok-loc t))))
   2798               (%pp-emit-expanded bodies state out)
   2799               (cont rest2)))))))))
   2800 
   2801 ;; Stamp built-in marker tokens (__LINE__ / __FILE__) inside the body
   2802 ;; with the macro-invocation location, so they report the call site
   2803 ;; per C11 §6.10.8. Other body tokens keep their #define-time loc so
   2804 ;; diagnostics still point at the macro body. Hide-set is updated
   2805 ;; with the macro name on every token.
   2806 (define (%pp-prepare-body body extra-hide . call-loc-opt)
   2807   (let ((call-loc (cond ((null? call-loc-opt) #f)
   2808                         (else (car call-loc-opt)))))
   2809     (map (lambda (t)
   2810            (let ((hidden (%pp-with-hide t (%pp-bv-union extra-hide
   2811                                                         (tok-hide t)))))
   2812              (cond
   2813                ((and call-loc (%pp-ident? hidden)
   2814                      (or (bv= (tok-value hidden) %pp-bv-LINE)
   2815                          (bv= (tok-value hidden) %pp-bv-FILE)))
   2816                 (%pp-with-loc hidden call-loc))
   2817                (else hidden))))
   2818          body)))
   2819 
   2820 ;; Collect comma-separated args. `toks` starts AFTER `(`. Returns
   2821 ;; (args . rest), where args is a list of token-lists.
   2822 (define (%pp-collect-args toks call-loc)
   2823   (let loop ((toks toks) (depth 0) (cur '()) (args '()))
   2824     (cond
   2825       ((null? toks) (die call-loc "macro call: unterminated argument list"))
   2826       ((%pp-eof? (car toks))
   2827        (die call-loc "macro call: unterminated argument list"))
   2828       ((and (= depth 0) (%pp-punct? (car toks) 'rparen))
   2829        (let ((args*
   2830               (cond
   2831                 ;; Empty parens count as one empty argument; bind-args
   2832                 ;; degenerates this back to "no args" for 0-param macros.
   2833                 ((and (null? args) (null? cur)) (list '()))
   2834                 (else (reverse (cons (reverse cur) args))))))
   2835          (cons args* (cdr toks))))
   2836       ((and (= depth 0) (%pp-punct? (car toks) 'comma))
   2837        (loop (cdr toks) 0 '() (cons (reverse cur) args)))
   2838       ((%pp-punct? (car toks) 'lparen)
   2839        (loop (cdr toks) (+ depth 1) (cons (car toks) cur) args))
   2840       ((%pp-punct? (car toks) 'rparen)
   2841        (loop (cdr toks) (- depth 1) (cons (car toks) cur) args))
   2842       (else
   2843        (loop (cdr toks) depth (cons (car toks) cur) args)))))
   2844 
   2845 ;; Bind formals → token-lists (alist by bv key). Variadic gathers
   2846 ;; trailing actuals into __VA_ARGS__, joined with synthetic commas.
   2847 (define (%pp-bind-args params args variadic? call-loc)
   2848   (let* ((np (length params)) (na (length args)))
   2849     (cond
   2850       (variadic?
   2851        (cond
   2852          ((< na np) (die call-loc "macro call: too few arguments"))
   2853          (else
   2854           (let loop ((ps params) (as args) (acc '()))
   2855             (cond
   2856               ((null? ps)
   2857                (alist-set %pp-bv-VA-ARGS (%pp-join-comma as) acc))
   2858               (else
   2859                (loop (cdr ps) (cdr as)
   2860                      (alist-set (car ps) (car as) acc))))))))
   2861       (else
   2862        (cond
   2863          ((and (= np 0) (= na 1) (null? (car args))) '())
   2864          ((not (= np na)) (die call-loc "macro call: argument count mismatch"))
   2865          (else
   2866           (let loop ((ps params) (as args) (acc '()))
   2867             (cond
   2868               ((null? ps) acc)
   2869               (else (loop (cdr ps) (cdr as)
   2870                           (alist-set (car ps) (car as) acc)))))))))))
   2871 
   2872 (define (%pp-join-comma argss)
   2873   (cond
   2874     ((null? argss) '())
   2875     ((null? (cdr argss)) (car argss))
   2876     (else
   2877      (append (car argss)
   2878              (cons (%pp-synth-comma) (%pp-join-comma (cdr argss)))))))
   2879 
   2880 (define (%pp-synth-comma)
   2881   (%tok 'PUNCT 'comma (%loc "<expand>" 0 0) '()))
   2882 
   2883 ;; Body substitution: walk body; replace param IDENTs with arg toks,
   2884 ;; handle `#param` (stringize) and `a##b` (paste). Per C11 §6.10.3.1,
   2885 ;; arguments are macro-expanded BEFORE substitution into the body
   2886 ;; EXCEPT when the parameter is the operand of `#` or `##` (in which
   2887 ;; case the raw token list is used). Without prescan, recursive uses
   2888 ;; like M(M(1)) for `#define M(x) ...x...` fail to expand the inner
   2889 ;; M during rescan because the outer M is in every substituted
   2890 ;; token's hide-set.
   2891 (define (%pp-substitute body env call-loc state)
   2892   (let loop ((body body) (out '()))
   2893     (cond
   2894       ((null? body) (reverse out))
   2895       (else
   2896        (let ((t (car body)) (rest (cdr body)))
   2897          (cond
   2898            ((%pp-punct? t 'hash)
   2899             (cond
   2900               ((or (null? rest) (not (%pp-ident? (car rest))))
   2901                (die (tok-loc t) "stringize: '#' must precede a parameter name"))
   2902               (else
   2903                (let* ((id (car rest)) (pn (tok-value id))
   2904                       (pt (alist-ref pn env)))
   2905                  (cond
   2906                    ((not pt)
   2907                     (die (tok-loc id) "stringize: '#' operand must be a parameter" pn))
   2908                    (else
   2909                     (let ((s (%tok 'STR (%pp-toks->display pt) (tok-loc t) '())))
   2910                       (loop (cdr rest) (cons s out)))))))))
   2911            ((%pp-punct? t 'paste)
   2912             (cond
   2913               ((null? out) (die (tok-loc t) "paste: '##' cannot start a body"))
   2914               ((null? rest) (die (tok-loc t) "paste: '##' cannot end a body"))
   2915               (else
   2916                (let* ((lhs (car out))
   2917                       (rt (car rest))
   2918                       (rhs-list
   2919                        (cond
   2920                          ((and (%pp-ident? rt) (alist-ref (tok-value rt) env))
   2921                           (alist-ref (tok-value rt) env))
   2922                          (else (list rt)))))
   2923                  (cond
   2924                    ((null? rhs-list) (loop (cdr rest) out))
   2925                    (else
   2926                     (let* ((p (%pp-paste-tokens lhs (car rhs-list)))
   2927                            (after (append (cdr rhs-list) (cdr rest))))
   2928                       (loop after (cons p (cdr out))))))))))
   2929            ((%pp-ident? t)
   2930             (let* ((pn (tok-value t)) (pt (alist-ref pn env)))
   2931               (cond
   2932                 ((not pt) (loop rest (cons t out)))
   2933                 ((and (not (null? rest)) (%pp-punct? (car rest) 'paste))
   2934                  ;; Operand of ##: use raw arg tokens (no prescan).
   2935                  (cond
   2936                    ((null? pt) (loop (cdr rest) out))
   2937                    (else (loop rest (append (reverse pt) out)))))
   2938                 (else
   2939                  ;; Normal use: prescan (fully macro-expand the arg)
   2940                  ;; before substitution, per C11 §6.10.3.1.
   2941                  (let ((exp (%pp-expand-line pt state)))
   2942                    (loop rest (append (reverse exp) out)))))))
   2943            (else (loop rest (cons t out)))))))))
   2944 
   2945 ;; Paste two tokens textually; reparse the result.
   2946 (define (%pp-paste-tokens lhs rhs)
   2947   (let ((lk (tok-kind lhs)) (rk (tok-kind rhs)))
   2948     (cond
   2949       ((and (eq? lk 'IDENT) (eq? rk 'IDENT))
   2950        (%tok 'IDENT (bytevector-append (tok-value lhs) (tok-value rhs))
   2951              (tok-loc lhs) (%pp-bv-union (tok-hide lhs) (tok-hide rhs))))
   2952       ((and (eq? lk 'IDENT) (eq? rk 'INT))
   2953        (%tok 'IDENT (bytevector-append (tok-value lhs)
   2954                                       (%c-value-source-bv (tok-value rhs)))
   2955              (tok-loc lhs) (%pp-bv-union (tok-hide lhs) (tok-hide rhs))))
   2956       ((and (eq? lk 'INT) (eq? rk 'INT))
   2957        (let* ((s (bytevector-append (%c-value-source-bv (tok-value lhs))
   2958                                     (%c-value-source-bv (tok-value rhs))))
   2959               (r (%c-value-parse-decimal-bv s)))
   2960          (cond
   2961            ((not (car r)) (die (tok-loc lhs) "paste: cannot reparse as integer" s))
   2962            (else (%tok 'INT (%c-int-lit (cdr r) #f 0 #t) (tok-loc lhs)
   2963                        (%pp-bv-union (tok-hide lhs) (tok-hide rhs)))))))
   2964       (else (die (tok-loc lhs) "paste: unsupported token kinds" lk rk)))))
   2965 
   2966 (define (%pp-relocate t state)
   2967   (cond
   2968     ((and (= (pps-line-delta state) 0) (not (pps-cur-file state))) t)
   2969     (else
   2970      (let* ((l (tok-loc t))
   2971             (f (or (pps-cur-file state) (loc-file l)))
   2972             (ln (+ (loc-line l) (pps-line-delta state)))
   2973             (c (loc-col l)))
   2974        (%pp-with-loc t (%loc f ln c))))))
   2975 
   2976 ;; --- pp-eval-cexpr: #if expression evaluator ---
   2977 ;; Steps: resolve `defined NAME`, macro-expand the rest, treat any
   2978 ;; remaining IDENT as 0, then delegate to parse-const-int via a minimal
   2979 ;; pstate (empty scope, no cg). sizeof(type) works as an extension;
   2980 ;; sizeof(expr) dies with a clear message.
   2981 ;;
   2982 (define (%pp-make-const-ps toks)
   2983   (%pstate (make-list-iter toks)
   2984            (%world (list (%make-hash-table 8))
   2985                    (list (%make-hash-table 8))
   2986                    (%make-hash-table 8)
   2987                    (cons '() (%make-hash-table 8)))
   2988            '() #f #f))
   2989 
   2990 (define (pp-eval-cexpr toks outer)
   2991   ;; `outer` is the live %pp-state. We mint a fresh state for #if
   2992   ;; evaluation but inherit cur-file and line-delta so __FILE__ /
   2993   ;; __LINE__ inside the expression reflect any preceding #line.
   2994   (let* ((state (%pp-state (pps-macros outer) '()
   2995                            (pps-cur-file outer)
   2996                            (pps-line-delta outer)
   2997                            #f '() '()))
   2998          (s1 (%pp-resolve-defined toks state))
   2999          (s2 (%pp-expand-line s1 state))
   3000          (s3 (%pp-idents-as-zero s2))
   3001          (ps (%pp-make-const-ps s3))
   3002          (val (parse-const-int ps))
   3003          (t   (peek ps)))
   3004     (cond
   3005       ((eq? (tok-kind t) 'EOF) val)
   3006       (else (die (tok-loc t) "#if: garbage at end of expression"
   3007                  (tok-kind t))))))
   3008 
   3009 (define (%pp-expand-line toks state)
   3010   (let ((out (make-buf-list)))
   3011     (%pp-emit-expanded toks state out)
   3012     (buf-list-flush out)))
   3013 
   3014 (define (%pp-resolve-defined toks state)
   3015   (let loop ((toks toks) (acc '()))
   3016     (cond
   3017       ((null? toks) (reverse acc))
   3018       ((%pp-ident-name? (car toks) %pp-bv-defined)
   3019        (let ((rest (cdr toks)))
   3020          (cond
   3021            ((null? rest) (die (tok-loc (car toks)) "defined: missing operand"))
   3022            ((%pp-ident? (car rest))
   3023             (let ((v (if (%pp-defined? (tok-value (car rest)) state) 1 0)))
   3024               (loop (cdr rest)
   3025                     (cons (%tok 'INT v (tok-loc (car toks)) '()) acc))))
   3026            ((%pp-punct? (car rest) 'lparen)
   3027             (let ((after (cdr rest)))
   3028               (cond
   3029                 ((or (null? after) (not (%pp-ident? (car after))))
   3030                  (die (tok-loc (car toks)) "defined: expected identifier"))
   3031                 (else
   3032                  (let ((aa (cdr after)))
   3033                    (cond
   3034                      ((or (null? aa) (not (%pp-punct? (car aa) 'rparen)))
   3035                       (die (tok-loc (car toks)) "defined: expected ')'"))
   3036                      (else
   3037                       (let ((v (if (%pp-defined? (tok-value (car after)) state) 1 0)))
   3038                         (loop (cdr aa)
   3039                               (cons (%tok 'INT v (tok-loc (car toks)) '()) acc)))))))) ))
   3040            (else (die (tok-loc (car rest)) "defined: expected identifier or '('")))))
   3041       (else (loop (cdr toks) (cons (car toks) acc))))))
   3042 
   3043 (define (%pp-idents-as-zero toks)
   3044   (map (lambda (t)
   3045          (cond ((%pp-ident? t) (%tok 'INT 0 (tok-loc t) '()))
   3046                (else t)))
   3047        toks))
   3048 
   3049 ;; cc/cg.scm — codegen state and emission API.
   3050 ;; Conversion split: parse owns promotion etc; cg owns sign extension,
   3051 ;; signed/unsigned dispatch, pointer scaling.
   3052 ;;
   3053 ;; Output uses libp1pp's structured macros (%fn, %ifelse_nez,
   3054 ;; %break, %continue) per docs/LIBP1PP.md. Function-local control-flow
   3055 ;; labels are hex2++ dotted labels inside %fn's .scope.
   3056 ;;
   3057 ;; Frame layout:
   3058 ;;   [sp + 0 .. staging*8)        outgoing-arg staging
   3059 ;;   [sp + staging*8 ..)          locals + spilled vstack values
   3060 ;; Slot offsets are emitted symbolically as `(+ %<fn>__SO N)` so the
   3061 ;; staging size, only known at fn-end, can be filled in via a 0-arg
   3062 ;; M1pp macro `<fn>__SO` defined just before the `%fn(...)` block.
   3063 
   3064 (define (%cg-emit-buf cg)
   3065   (cond ((cg-in-fn? cg) (cg-fn-buf cg)) (else (cg-text cg))))
   3066 
   3067 (define (%cg-emit cg bv)
   3068   (buf-push! (%cg-emit-buf cg) bv))
   3069 
   3070 (define (%cg-emit-many cg bvs)
   3071   (for-each (lambda (b) (%cg-emit cg b)) bvs))
   3072 
   3073 (define (%n n) (%c-value-literal-bv n))
   3074 
   3075 ;; Per-fn metadata (name, ret-slot, ret-type, switch-case lists, ...)
   3076 ;; lives on cg-fn-meta, reset at every cg-fn-begin/v.
   3077 ;;
   3078 ;; Update is destructive: assq for the key, set-cdr! if found, else
   3079 ;; prepend. The functional alist-update path was O(n) per write *with*
   3080 ;; an append+reverse rebuild — and cg-fn-begin/v plus every emit in a
   3081 ;; function body hammers this. Mutation here is safe: the meta alist
   3082 ;; is private to one cg, scratch-only, and discarded at fn-end.
   3083 (define (%cg-fn-set! cg key val)
   3084   (let* ((meta (cg-fn-meta cg))
   3085          (p    (assq key meta)))
   3086     (cond (p (set-cdr! p val))
   3087           (else (cg-fn-meta-set! cg (cons (cons key val) meta))))))
   3088 
   3089 (define (%cg-fn-get cg key) (alist-ref/eq key (cg-fn-meta cg)))
   3090 
   3091 (define (%cg-fresh-label cg prefix)
   3092   (let* ((n (cg-label-ctr cg))
   3093          (bv (bytevector-append prefix (%n n))))
   3094     (cg-label-ctr-set! cg (+ n 1))
   3095     bv))
   3096 
   3097 (define (%cg-fresh-loop-tag cg) (%cg-fresh-label cg "L"))
   3098 (define (%cg-fresh-lbl cg)      (%cg-fresh-label cg "lbl_"))
   3099 
   3100 (define (%cg-bump-outgoing! cg n)
   3101   (if (< (cg-max-outgoing cg) n) (cg-max-outgoing-set! cg n) 0))
   3102 
   3103 (define (%cg-slot-expr cg logical-off)
   3104   (let ((nm (%cg-fn-get cg '%fn-label)))
   3105     (bv-cat (list "(+ %" nm "__SO " (%n logical-off) ")"))))
   3106 
   3107 (define (%cg-mangle-global cg name-bv)
   3108   (bytevector-append (cg-str-prefix cg) "cc__" name-bv))
   3109 
   3110 ;; Label for a sym at the M1 layer.
   3111 ;;
   3112 ;; C linkage rules drive this directly:
   3113 ;;   - external linkage (the default at file scope, plus any `extern`
   3114 ;;     decl): bare ident. Same label name shared between every decl
   3115 ;;     and the eventual definition, in any order. `extern T memcpy()`
   3116 ;;     links to libp1pp's `:memcpy`; `int g_acc;` and refs to it
   3117 ;;     share `:g_acc`.
   3118 ;;   - internal linkage (`static`): cc__-prefixed. Free to mangle
   3119 ;;     since `static` is invisible across TUs, and the prefix keeps
   3120 ;;     it out of the external/runtime namespace.
   3121 ;; Block-scope statics already mangle their sym-name to
   3122 ;; `<fnname>__<n>` at parse time (see line ~5125); the cc__ prefix
   3123 ;; here just nests another layer of namespacing on top of that.
   3124 (define (%cg-sym-label cg sm)
   3125   (cond
   3126     ((eq? (sym-storage sm) 'static) (%cg-mangle-global cg (sym-name sm)))
   3127     (else                            (sym-name sm))))
   3128 
   3129 (define (%cg-reg->bv r) (bytevector-append (symbol->string r)))
   3130 
   3131 (define (%cg-emit-li cg reg n)
   3132   (%cg-emit-many cg (list "%li(" (%cg-reg->bv reg) ", " (%n n) ")\n")))
   3133 
   3134 (define (%cg-emit-la cg reg label-bv)
   3135   (%cg-emit-many cg (list "%la(" (%cg-reg->bv reg) ", &" label-bv ")\n")))
   3136 
   3137 (define (%cg-emit-ld-slot cg reg logical-off)
   3138   (%cg-emit-many cg (list "%ld(" (%cg-reg->bv reg) ", sp, "
   3139                           (%cg-slot-expr cg logical-off) ")\n")))
   3140 
   3141 (define (%cg-emit-st-slot cg reg logical-off)
   3142   (%cg-emit-many cg (list "%st(" (%cg-reg->bv reg) ", sp, "
   3143                           (%cg-slot-expr cg logical-off) ")\n")))
   3144 
   3145 (define (%cg-emit-ld cg reg base off)
   3146   (%cg-emit-many cg (list "%ld(" (%cg-reg->bv reg) ", "
   3147                           (%cg-reg->bv base) ", " (%n off) ")\n")))
   3148 
   3149 (define (%cg-emit-st cg reg base off)
   3150   (%cg-emit-many cg (list "%st(" (%cg-reg->bv reg) ", "
   3151                           (%cg-reg->bv base) ", " (%n off) ")\n")))
   3152 
   3153 ;; Width-aware load/store. Dispatches on ctype-size:
   3154 ;;   1: %lb / %sb (LB zero-extends; for signed i8 we sign-extend by
   3155 ;;      target-word-relative shli/sari to materialize canonical form).
   3156 ;;   2/4: handled by libp1pp helpers (P1 has only 1-byte and target-word ops,
   3157 ;;      and word ops require natural alignment which we can't promise
   3158 ;;      for struct fields or non-word-aligned local slots). Loads
   3159 ;;      gather bytes via %lb + shli/or; stores scatter via shri/%sb.
   3160 ;;      Signed loads (i16/i32) sign-extend via shli/sari to canonical
   3161 ;;      target-word form.
   3162 ;;   target-word-sized fallback: %ld / %st. RV32 i64/u64 accesses use
   3163 ;;      the dedicated adjacent-pair path before reaching these helpers.
   3164 ;; Scratch convention: helpers may clobber t1; callers never pass
   3165 ;; reg=t1.
   3166 
   3167 ;; Sub-word loads/stores defer byte-decomposition to libp1pp's
   3168 ;; %ld_h / %ld_w / %ld_sh / %ld_sw / %st_h / %st_w macros (see
   3169 ;; P1/P1pp.P1pp). cc.scm just emits one macro call per access; the
   3170 ;; macro arranges the byte gather/scatter and (for signed loads) folds
   3171 ;; in the sign-extend. t1 is the conventional scratch.
   3172 (define (%cg-emit-ld-sub cg reg base-bv off-bv signed? n-bytes)
   3173   (let ((mname (cond ((= n-bytes 2) (if signed? "%ld_sh(" "%ld_h("))
   3174                      ((= n-bytes 4) (if signed? "%ld_sw(" "%ld_w("))
   3175                      (else (die #f "cg-emit-ld-sub: bad width" n-bytes)))))
   3176     (%cg-emit-many cg (list mname (%cg-reg->bv reg) ", "
   3177                             base-bv ", " off-bv ", t1)\n"))))
   3178 
   3179 (define (%cg-emit-st-sub cg reg base-bv off-bv n-bytes)
   3180   (let ((mname (cond ((= n-bytes 2) "%st_h(")
   3181                      ((= n-bytes 4) "%st_w(")
   3182                      (else (die #f "cg-emit-st-sub: bad width" n-bytes)))))
   3183     (%cg-emit-many cg (list mname (%cg-reg->bv reg) ", "
   3184                             base-bv ", " off-bv ", t1)\n"))))
   3185 
   3186 ;; "address of frame slot" — defers to libp1pp's %lea_slot, which hides
   3187 ;; the backend frame-header offset that %mov(rd, sp) folds in.
   3188 (define (%cg-emit-lea-slot cg reg-bv slot-bv)
   3189   (%cg-emit-many cg (list "%lea_slot(" reg-bv ", " slot-bv ")\n")))
   3190 
   3191 ;; sext8/16/32 emitted via libp1pp's %sext<N>(rd, ra). shift-amount is
   3192 ;; kept as the parameter for call-site clarity (callers think in bit
   3193 ;; widths via the same 56/48/32 amounts they always have).
   3194 (define (%cg-emit-sext cg reg shift-amount)
   3195   (let ((width (cond ((= shift-amount 56) "8")
   3196                      ((= shift-amount 48) "16")
   3197                      ((= shift-amount 32) "32")
   3198                      (else (die #f "cg-emit-sext: bad shift" shift-amount))))
   3199         (rb (%cg-reg->bv reg)))
   3200     (%cg-emit-many cg (list "%sext" width "(" rb ", " rb ")\n"))))
   3201 
   3202 ;; Canonicalize REG against CTYPE's kind: signed narrow types sign-extend,
   3203 ;; unsigned narrow types zero-extend, anything else is left alone (the
   3204 ;; full target-word value is already canonical). Used after operations that
   3205 ;; may have left a non-canonical bit pattern in reg — frame-rval load,
   3206 ;; narrowing cast, narrow-typed binop result.
   3207 (define (%cg-canonicalize cg reg ctype)
   3208   (let* ((rb (%cg-reg->bv reg))
   3209          (k  (ctype-kind ctype)))
   3210     (cond
   3211       ((eq? k 'i8)  (%cg-emit-sext cg reg 56))
   3212       ((eq? k 'i16) (%cg-emit-sext cg reg 48))
   3213       ((eq? k 'i32) (%cg-emit-sext cg reg 32))
   3214       ((or (eq? k 'u8) (eq? k 'bool))
   3215        (%cg-emit-many cg (list "%zext8(" rb ", " rb ")\n")))
   3216       ((eq? k 'u16)
   3217        (%cg-emit-many cg (list "%zext16(" rb ", " rb ")\n")))
   3218       ((eq? k 'u32)
   3219        (%cg-emit-many cg (list "%zext32(" rb ", " rb ", t1)\n")))
   3220       (else 0))))
   3221 
   3222 ;; Width-aware load/store core. BASE-BV / OFF-BV are pre-built (so the
   3223 ;; same body serves both the slot variants — base = "sp", off rendered
   3224 ;; through %cg-slot-expr — and the typed variants, where base is a
   3225 ;; register and off is a raw integer rendered via %n). 1-byte uses
   3226 ;; %lb/%sb (with i8 sext); 2- and 4-byte use the sub-word helpers; the
   3227 ;; target-word fallback emits a plain %ld/%st against the same base/off.
   3228 (define (%cg-emit-ld-bv cg reg ctype base-bv off-bv)
   3229   (%cg-fp-reject! 'ld ctype)
   3230   (let* ((sz (ctype-size ctype)) (kind (ctype-kind ctype))
   3231          (rb (%cg-reg->bv reg)))
   3232     (cond
   3233       ((= sz 1)
   3234        (%cg-emit-many cg (list "%lb(" rb ", " base-bv ", " off-bv ")\n"))
   3235        (cond ((eq? kind 'i8) (%cg-emit-sext cg reg 56))))
   3236       ((= sz 2) (%cg-emit-ld-sub cg reg base-bv off-bv (eq? kind 'i16) 2))
   3237       ((= sz 4) (%cg-emit-ld-sub cg reg base-bv off-bv (eq? kind 'i32) 4))
   3238       (else
   3239        (%cg-emit-many cg (list "%ld(" rb ", " base-bv ", " off-bv ")\n"))))))
   3240 
   3241 (define (%cg-emit-st-bv cg reg ctype base-bv off-bv)
   3242   (%cg-fp-reject! 'st ctype)
   3243   (let ((sz (ctype-size ctype))
   3244         (rb (%cg-reg->bv reg)))
   3245     (cond
   3246       ((= sz 1)
   3247        (%cg-emit-many cg (list "%sb(" rb ", " base-bv ", " off-bv ")\n")))
   3248       ((= sz 2) (%cg-emit-st-sub cg reg base-bv off-bv 2))
   3249       ((= sz 4) (%cg-emit-st-sub cg reg base-bv off-bv 4))
   3250       (else
   3251        (%cg-emit-many cg (list "%st(" rb ", " base-bv ", " off-bv ")\n"))))))
   3252 
   3253 (define (%cg-emit-ld-slot-typed cg reg ctype logical-off)
   3254   (%cg-emit-ld-bv cg reg ctype "sp" (%cg-slot-expr cg logical-off)))
   3255 (define (%cg-emit-st-slot-typed cg reg ctype logical-off)
   3256   (%cg-emit-st-bv cg reg ctype "sp" (%cg-slot-expr cg logical-off)))
   3257 
   3258 (define (%cg-emit-ld-typed cg reg ctype base off)
   3259   (%cg-emit-ld-bv cg reg ctype (%cg-reg->bv base) (%n off)))
   3260 (define (%cg-emit-st-typed cg reg ctype base off)
   3261   (%cg-emit-st-bv cg reg ctype (%cg-reg->bv base) (%n off)))
   3262 
   3263 (define (%cg-emit-li-wide cg lo hi value)
   3264   (%cg-emit-many cg
   3265                  (list "%li(" (%cg-reg->bv lo) ", "
   3266                        (%c-value-u32-literal-bv value 0) ")\n"
   3267                        "%li(" (%cg-reg->bv hi) ", "
   3268                        (%c-value-u32-literal-bv value 1) ")\n")))
   3269 
   3270 (define (%cg-load-wide-opnd-into cg op lo hi)
   3271   ;; Load an RV32 i64/u64 as (low-word, high-word). Wide frame rvalues own
   3272   ;; two adjacent target-word slots; wide lvalues read two adjacent words
   3273   ;; from their object storage. t2 is the address scratch for indirect and
   3274   ;; global lvalues, so callers must keep it distinct from LO/HI.
   3275   (cond
   3276     ((not (%ctype-wide-int? (opnd-type op)))
   3277      (die #f "cg internal: pair load of non-wide operand" (ctype-kind (opnd-type op))))
   3278     ((or (eq? lo 't2) (eq? hi 't2))
   3279      (die #f "cg internal: t2 cannot hold a wide operand limb"))
   3280     (else
   3281      (pmatch op
   3282        (($ opnd? (kind imm) (ext ,n))
   3283         (%cg-emit-li-wide cg lo hi n))
   3284        (($ opnd? (kind frame) (lval? #t) (ext ,off))
   3285         (guard (%cg-indirect? cg off))
   3286         (%cg-emit-ld-slot cg 't2 off)
   3287         (%cg-emit-ld cg lo 't2 0)
   3288         (%cg-emit-ld cg hi 't2 %CC-WORD-BYTES))
   3289        (($ opnd? (kind frame) (ext ,off))
   3290         (%cg-emit-ld-slot cg lo off)
   3291         (%cg-emit-ld-slot cg hi (+ off %CC-WORD-BYTES)))
   3292        (($ opnd? (kind global) (lval? #t) (ext ,lbl))
   3293         (%cg-emit-la cg 't2 lbl)
   3294         (%cg-emit-ld cg lo 't2 0)
   3295         (%cg-emit-ld cg hi 't2 %CC-WORD-BYTES))
   3296        (else
   3297         (die #f "cg internal: unsupported wide operand" (opnd-kind op)))))))
   3298 
   3299 (define (%cg-spill-pair cg lo hi ty)
   3300   (cond ((not (%ctype-wide-int? ty))
   3301          (die #f "cg internal: pair spill of non-wide type" (ctype-kind ty))))
   3302   (let* ((off (cg-alloc-slot cg (ctype-size ty)
   3303                              (max %CC-WORD-BYTES (ctype-align ty))))
   3304          (op (%opnd 'frame ty off #f)))
   3305     (%cg-emit-st-slot cg lo off)
   3306     (%cg-emit-st-slot cg hi (+ off %CC-WORD-BYTES))
   3307     (cg-vstack-set! cg (cons op (cg-vstack cg)))
   3308     op))
   3309 
   3310 (define (%cg-store-pair-to-lval cg lo hi lhs)
   3311   (cond ((not (%ctype-wide-int? (opnd-type lhs)))
   3312          (die #f "cg internal: pair store to non-wide lvalue"
   3313               (ctype-kind (opnd-type lhs)))))
   3314   (pmatch lhs
   3315     (($ opnd? (kind frame) (ext ,off))
   3316      (guard (%cg-indirect? cg off))
   3317      (%cg-emit-ld-slot cg 't2 off)
   3318      (%cg-emit-st cg lo 't2 0)
   3319      (%cg-emit-st cg hi 't2 %CC-WORD-BYTES))
   3320     (($ opnd? (kind frame) (ext ,off))
   3321      (%cg-emit-st-slot cg lo off)
   3322      (%cg-emit-st-slot cg hi (+ off %CC-WORD-BYTES)))
   3323     (($ opnd? (kind global) (ext ,lbl))
   3324      (%cg-emit-la cg 't2 lbl)
   3325      (%cg-emit-st cg lo 't2 0)
   3326      (%cg-emit-st cg hi 't2 %CC-WORD-BYTES))
   3327     (else (die #f "cg-assign: unsupported wide lhs kind" (opnd-kind lhs)))))
   3328 
   3329 (define (%cg-load-truth-into cg op reg)
   3330   (cond
   3331     ((%ctype-wide-int? (opnd-type op))
   3332      (%cg-load-wide-opnd-into cg op 'a0 'a1)
   3333      (%cg-emit-rrr cg "or" reg 'a0 'a1))
   3334     (else (%cg-load-opnd-into cg op reg))))
   3335 
   3336 (define (%cg-load-opnd-into cg op reg)
   3337   ;; frame lval: load at type width. frame rval is a spilled target word
   3338   ;; (allocated by %cg-spill-reg) — always a target-word load.
   3339   ;; global lval width > 1 byte-gathers must not alias dest with base —
   3340   ;; the first %lb would otherwise clobber the address before subsequent
   3341   ;; byte loads. Stage the address in t2.
   3342   (%cg-fp-reject! 'load (opnd-type op))
   3343   (cond ((%ctype-wide-int? (opnd-type op))
   3344          (die #f "cg internal: wide operand used as one word"
   3345               (ctype-kind (opnd-type op)))))
   3346   (pmatch op
   3347     (($ opnd? (kind imm)    (ext ,n))                (%cg-emit-li cg reg n))
   3348     (($ opnd? (kind frame)  (lval? #t) (type ,ty) (ext ,off))
   3349      (%cg-emit-ld-slot-typed cg reg ty off))
   3350     (($ opnd? (kind frame)  (lval? #f) (type ,ty) (ext ,off))
   3351      ;; Frame rval: spilled as one target word, but the slot's bit-pattern may
   3352      ;; not be canonical for the opnd's CURRENT type (e.g.
   3353      ;; cg-arith-conv relabeled a signed slot as unsigned). Canonicalize
   3354      ;; on load so downstream 64-bit ALU/compare ops see the C-semantic
   3355      ;; value.
   3356      (%cg-emit-ld-slot cg reg off)
   3357      (%cg-canonicalize cg reg ty))
   3358     (($ opnd? (kind frame)  (ext ,off))              (%cg-emit-ld-slot cg reg off))
   3359     (($ opnd? (kind global) (lval? #f) (ext ,lbl))   (%cg-emit-la cg reg lbl))
   3360     (($ opnd? (kind global) (type ,ty)  (ext ,lbl))
   3361      (%cg-emit-la cg 't2 lbl)
   3362      (%cg-emit-ld-typed cg reg ty 't2 0))
   3363     (else (die #f "cg internal: unknown opnd-kind" (opnd-kind op)))))
   3364 
   3365 (define (%cg-spill-reg cg reg ty)
   3366   (cond ((%ctype-wide-int? ty)
   3367          (die #f "cg internal: wide value spilled from one register"
   3368               (ctype-kind ty))))
   3369   (let* ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES))
   3370          (op  (%opnd 'frame ty off #f)))
   3371     (%cg-emit-st-slot cg reg off)
   3372     (cg-vstack-set! cg (cons op (cg-vstack cg)))
   3373     op))
   3374 
   3375 ;; Floating-point softening. Real FP arithmetic is not implemented;
   3376 ;; instead the cg silently treats fp ctypes as same-sized integer
   3377 ;; bit patterns (flt as 4-byte, dbl/ldbl as 8-byte). Loads, stores,
   3378 ;; and same-size casts round-trip the bytes; widening int→fp casts
   3379 ;; leave the int bit-pattern in the wider slot; binops use integer
   3380 ;; ALU ops. tcc.flat.c contains real fp code paths (parse_number,
   3381 ;; ieee_finite, …) that the bootstrap tcc-boot2 never executes when
   3382 ;; compiling float-free programs, so producing valid-but-semantically-
   3383 ;; wrong P1pp here is sufficient. Kept as a named no-op so the call
   3384 ;; sites stay grep-able if a future bootstrap target needs real FP.
   3385 (define (%cg-fp-reject! op-name ty) #t)
   3386 
   3387 (define (%reg-by-idx i)
   3388   (cond ((= i 0) 'a0) ((= i 1) 'a1) ((= i 2) 'a2) ((= i 3) 'a3)
   3389         (else (die #f "cg: param idx > 3 needs ldarg path" i))))
   3390 
   3391 ;; --------------------------------------------------------------------
   3392 ;; Lifecycle
   3393 ;; --------------------------------------------------------------------
   3394 
   3395 ;; cc-cg fixtures construct a cg directly via (cg-init) — they don't
   3396 ;; emit ELF and don't link against another TU, so library knobs are
   3397 ;; irrelevant. cc-main routes through cg-init/v with the parsed flag.
   3398 (define (cg-init) (cg-init/v #f ""))
   3399 
   3400 (define (cg-init/v lib? str-prefix)
   3401   (%cg (make-buf/cap %BUF-CAP-TEXT)        ; text
   3402        (make-buf/cap %BUF-CAP-DATA)        ; data
   3403        (make-buf/cap %BUF-CAP-BSS)         ; bss
   3404        '()                                  ; vstack
   3405        0                                    ; frame-hi
   3406        0                                    ; label-ctr
   3407        (make-world)                         ; world (shared with pstate)
   3408        '()                                  ; fn-meta
   3409        (make-buf/cap %BUF-CAP-FN)          ; fn-buf (reused per fn)
   3410        (make-buf/cap %BUF-CAP-PROLOGUE)    ; prologue-buf (reused per fn)
   3411        0                                    ; max-outgoing
   3412        #f                                   ; in-fn?
   3413        lib?                                 ; lib? (skip entry stub + :ELF_end)
   3414        str-prefix))                         ; str-prefix (cc__str_N namespacing)
   3415 
   3416 (define (cg-finalize! cg)
   3417   ;; Tentative file-scope defs (`int x;` / `static int x;` with no
   3418   ;; initializer and not later defined with `=`) get their .bss slot
   3419   ;; here at end of TU. C 6.9.2 — see cg-flush-tentatives!.
   3420   (cg-flush-tentatives! cg)
   3421   ;; Entry stub. P1's program-entry contract (docs/P1.md §Program Entry)
   3422   ;; delivers argc in a0 and argv in a1 at p1_main. %call doesn't
   3423   ;; clobber a0/a1, so falling straight through to main forwards
   3424   ;; them unchanged. The 16-byte frame is just enough for %enter's
   3425   ;; saved-fp/lr to fit; main builds its own frame on top.
   3426   ;;
   3427   ;; In lib mode the stub and :ELF_end are suppressed: the catm chain
   3428   ;; supplies them once, from P1/entry-*.P1pp and P1/elf-end.P1pp, so
   3429   ;; library TUs don't fight the executable TU for ownership of
   3430   ;; :p1_main and don't truncate ELF p_filesz at the first inner
   3431   ;; :ELF_end (hex2 sizes off the first one it sees).
   3432   (cond
   3433     ((not (cg-lib? cg))
   3434      (let ((tb (cg-text cg)))
   3435        (buf-push! tb "# entry stub: forwards argc=a0, argv=a1 to main\n")
   3436        (buf-push! tb "%fn(p1_main, 16, {\n")
   3437        (buf-push! tb "%call(&main)\n")
   3438        (buf-push! tb "})\n"))))
   3439   #t)
   3440 
   3441 (define (cg-output-size cg)
   3442   (+ (buf-offset (cg-text cg))
   3443      (buf-offset (cg-data cg))
   3444      (buf-offset (cg-bss cg))
   3445      (cond ((cg-lib? cg) 0)
   3446            (else (bytevector-length ":ELF_end\n")))))
   3447 
   3448 (define (cg-finish cg)
   3449   ;; In-memory form retained for tests and small callers. Production uses
   3450   ;; %cg-write-finalized-fd below so a fragmented non-moving heap need not
   3451   ;; provide several multi-megabyte snapshot allocations at once.
   3452   (cg-finalize! cg)
   3453   (bv-cat (list (buf-flush (cg-text cg))
   3454                 (buf-flush (cg-data cg))
   3455                 (buf-flush (cg-bss  cg))
   3456                 (cond ((cg-lib? cg) "")
   3457                       (else ":ELF_end\n")))))
   3458 
   3459 (define (%cg-write-finalized-fd cg fd)
   3460   (let ((text (cg-text cg))
   3461         (data (cg-data cg))
   3462         (bss  (cg-bss cg)))
   3463     (write-bv-range-fd fd (buf-storage text) 0 (buf-offset text))
   3464     (write-bv-range-fd fd (buf-storage data) 0 (buf-offset data))
   3465     (write-bv-range-fd fd (buf-storage bss)  0 (buf-offset bss))
   3466     (cond ((not (cg-lib? cg)) (write-bv-fd fd ":ELF_end\n")))
   3467     #t))
   3468 
   3469 (define (cg-fn-begin cg name params return-type)
   3470   (cg-fn-begin/v cg name params return-type #f))
   3471 
   3472 ;; Variadic-aware variant. variadic? = #t reserves a fixed contiguous
   3473 ;; target-word window, populating each slot from the
   3474 ;; appropriate source: a-register for idx 0..3, LDARG slot (idx-4) for
   3475 ;; later indices. va_start computes the address of the slot at index =
   3476 ;; named-arg count, so va_arg walks linearly through the rest.
   3477 ;; Indices beyond the actual arguments may be garbage; user
   3478 ;; code stops walking based on a count or sentinel before those slots
   3479 ;; are read. Kit's widest diagnostic calls require 20 total arguments.
   3480 (define %CG-VARARG-WINDOW 32)
   3481 
   3482 (define (cg-fn-begin/v cg name params return-type variadic?)
   3483   (buf-reset!           (cg-fn-buf       cg))
   3484   (buf-reset!           (cg-prologue-buf cg))
   3485   (cg-in-fn?-set!       cg #t)
   3486   (cg-vstack-set!       cg '())
   3487   (cg-frame-hi-set!     cg 0)
   3488   ;; cg-label-ctr is NOT reset per-fn. Compiler-internal labels are
   3489   ;; emitted as dotted hex2++ locals inside %fn's .scope (and sometimes
   3490   ;; nested .scope blocks), so within-TU collisions are already prevented
   3491   ;; by local lookup. Keeping the counter monotonic across functions is
   3492   ;; no longer required for correctness, just for stable, readable label
   3493   ;; names in expanded.M1 traces.
   3494   (cg-max-outgoing-set! cg 0)
   3495   (cg-fn-meta-set!      cg '())
   3496   (%cg-fn-set! cg '%fn-name        name)
   3497   (let ((sm (%hash-ref (car (world-scope (cg-world cg))) name)))
   3498     (%cg-fn-set! cg '%fn-label
   3499                  (cond (sm (%cg-sym-label cg sm)) (else name))))
   3500   (%cg-fn-set! cg '%fn-ret-type    return-type)
   3501   (%cg-fn-set! cg '%indirect-slots '())
   3502   (%cg-fn-set! cg '%fn-variadic?   variadic?)
   3503   ;; Return slot per P1.md §Arguments. One word → a0; two words → a0+a1;
   3504   ;; wider aggregates
   3505   ;; struct/union → indirect-result (A2): caller passes sret ptr in
   3506   ;; a0; cg-return writes through it; sret-slot saves a0 for cg-fn-end.
   3507   (let* ((rsz (cond ((eq? (ctype-kind return-type) 'void) %CC-WORD-BYTES)
   3508                     (else (align-up (max %CC-WORD-BYTES
   3509                                          (ctype-size return-type))
   3510                                     %CC-WORD-BYTES))))
   3511          (ret-slot (cg-alloc-slot cg rsz %CC-WORD-BYTES)))
   3512     (%cg-fn-set! cg '%fn-ret-slot ret-slot)
   3513     (cond
   3514       ((not (eq? (ctype-kind return-type) 'void))
   3515        (let zinit ((k 0))
   3516          (cond
   3517            ((>= k rsz) #t)
   3518            (else
   3519             (buf-push! (cg-prologue-buf cg)
   3520                        (bv-cat (list "%li(t0, 0)\n"
   3521                                      "%st(t0, sp, "
   3522                                      (%cg-slot-expr cg (+ ret-slot k))
   3523                                      ")\n")))
   3524             (zinit (+ k %CC-WORD-BYTES))))))))
   3525   (let* ((rk    (ctype-kind return-type))
   3526          (sret? (and (or (eq? rk 'struct) (eq? rk 'union))
   3527                      (> (ctype-size return-type) %CC-PAIR-BYTES))))
   3528     (%cg-fn-set! cg '%fn-sret? sret?)
   3529     (cond
   3530       (sret?
   3531        (let ((ss (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
   3532          (%cg-fn-set! cg '%fn-sret-slot ss)
   3533          (buf-push! (cg-prologue-buf cg)
   3534                     (bv-cat (list "%st(a0, sp, "
   3535                                   (%cg-slot-expr cg ss) ")\n")))))
   3536       (else (%cg-fn-set! cg '%fn-sret-slot #f))))
   3537   ;; Reject definitions whose named parameters leave no variadic slot.
   3538   ;; variadic definitions whose named-arg count would already fill or
   3539   ;; exceed it (no room left for variadic reads).
   3540   (let ((named-slots
   3541          (let count ((xs params) (n 0))
   3542            (cond ((null? xs) n)
   3543                  (else (count (cdr xs)
   3544                               (+ n (%cg-param-reg-count (cdar xs)))))))))
   3545     (cond
   3546       ((and variadic? (>= named-slots %CG-VARARG-WINDOW))
   3547        (die #f "cg-fn-begin: variadic function fills save-area"
   3548             name named-slots %CG-VARARG-WINDOW))))
   3549   ;; With sret, explicit arg i lives at ABI position (i+1): args 0..2
   3550   ;; in a1..a3, args 3+ in slot (i-3).
   3551   (let* ((sret-shift (if (%cg-fn-get cg '%fn-sret?) 1 0))
   3552          (spill (lambda (abi off)
   3553                   (cond
   3554                     ((< abi 4)
   3555                      (buf-push! (cg-prologue-buf cg)
   3556                                 (bv-cat (list "%st(" (%cg-reg->bv (%reg-by-idx abi))
   3557                                               ", sp, "
   3558                                               (%cg-slot-expr cg off) ")\n"))))
   3559                     (else
   3560                      (buf-push! (cg-prologue-buf cg)
   3561                                 (bv-cat (list "%ldarg(t0, " (%n (- abi 4)) ")\n"
   3562                                               "%st(t0, sp, "
   3563                                               (%cg-slot-expr cg off) ")\n"))))))))
   3564     (let walk ((ps params) (idx 0) (out '()) (first-slot #f))
   3565       (cond
   3566         ((null? ps)
   3567          (cond
   3568            (variadic?
   3569             (let pad ((i idx) (vfirst #f) (fs first-slot))
   3570               (cond
   3571                 ((>= i %CG-VARARG-WINDOW)
   3572                  (%cg-fn-set! cg '%fn-vararg-first-slot (or vfirst fs))
   3573                  (reverse out))
   3574                 (else
   3575                  (let ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
   3576                    (spill (+ i sret-shift) off)
   3577                    (pad (+ i 1) (or vfirst off) (or fs off)))))))
   3578            (else (reverse out))))
   3579         (else
   3580          (let* ((p    (car ps))
   3581                 (nm   (car p))
   3582                 (ty   (cdr p))
   3583                 ;; Aggregates wider than two words ride one ABI slot containing
   3584                 ;; a pointer to a caller-owned copy (AAPCS64 / SysV class
   3585                 ;; MEMORY). Smaller aggregates ride one or two words inline.
   3586                 (n    (%cg-param-reg-count ty))
   3587                 (sz   (cond ((%cg-param-indirect? ty) %CC-WORD-BYTES)
   3588                             ((or (%cg-param-aggregate? ty)
   3589                                   (%ctype-wide-int? ty))
   3590                              (align-up (ctype-size ty) %CC-WORD-BYTES))
   3591                             (else %CC-WORD-BYTES)))
   3592                 (al   (cond ((%cg-param-indirect? ty) %CC-WORD-BYTES)
   3593                             ((or (%cg-param-aggregate? ty)
   3594                                   (%ctype-wide-int? ty))
   3595                              (max %CC-WORD-BYTES (ctype-align ty)))
   3596                             (else %CC-WORD-BYTES)))
   3597                 (off  (cg-alloc-slot cg sz al))
   3598                 (psym (%sym nm 'param #f ty off #t)))
   3599            (let chunk ((i 0))
   3600              (cond ((>= i n) 0)
   3601                    (else
   3602                     (spill (+ idx sret-shift i)
   3603                            (+ off (* i %CC-WORD-BYTES)))
   3604                     (chunk (+ i 1)))))
   3605            (cond ((%cg-param-indirect? ty) (%cg-mark-indirect! cg off)))
   3606            (walk (cdr ps) (+ idx n) (cons (cons nm psym) out)
   3607                  (or first-slot off))))))))
   3608 
   3609 ;; Number of consecutive ABI slots (regs or stack words) consumed by a
   3610 ;; parameter of TY. Aggregates up to two words take ceil(size/word).
   3611 (define (%cg-param-reg-count ty)
   3612   (cond
   3613     ((%ctype-wide-int? ty) 2)
   3614     ((%cg-param-aggregate? ty)
   3615      (let ((sz (ctype-size ty)))
   3616        (cond
   3617          ((> sz %CC-PAIR-BYTES) 1)
   3618          ((> sz %CC-WORD-BYTES) 2)
   3619          (else 1))))
   3620     (else 1)))
   3621 
   3622 (define (%cg-param-aggregate? ty)
   3623   (let ((k (ctype-kind ty)))
   3624     (or (eq? k 'struct) (eq? k 'union))))
   3625 
   3626 (define (%cg-param-indirect? ty)
   3627   (and (%cg-param-aggregate? ty)
   3628        (> (ctype-size ty) %CC-PAIR-BYTES)))
   3629 
   3630 (define (cg-fn-end cg)
   3631   ;; Drain prologue-buf and fn-buf directly into cg-text via buf-drain!
   3632   ;; (memcpy, no allocation). Header/footer pieces go through buf-push!
   3633   ;; on cg-text — also memcpy. The only fresh objects here are the small
   3634   ;; (%n N) bytevectors for staging-bytes / frame-size.
   3635   (let* ((name          (%cg-fn-get cg '%fn-name))
   3636          (ret-slot      (%cg-fn-get cg '%fn-ret-slot))
   3637          (ret-type      (%cg-fn-get cg '%fn-ret-type))
   3638          (locals-hi     (cg-frame-hi cg))
   3639          (staging-bytes (* %CC-WORD-BYTES (cg-max-outgoing cg)))
   3640          (raw-size      (+ staging-bytes locals-hi))
   3641          (frame-size    (align-up raw-size 16))
   3642          ;; Look up the bound sym for this fn so `static void foo(){...}`
   3643          ;; emits the same cc__-mangled label that callers reference.
   3644          ;; The sym was bound by parse-fn-body before the body parse,
   3645          ;; so it's in the top scope frame at this point.
   3646          (mangled       (%cg-fn-get cg '%fn-label))
   3647          (tb            (cg-text cg)))
   3648     ;; Now that the body is fully emitted, leave fn dispatch so any
   3649     ;; trailing emits in this function (including the ret-block below)
   3650     ;; route to cg-text directly.
   3651     (cg-in-fn?-set! cg #f)
   3652     ;; staging-size macro
   3653     (buf-push! tb "%macro ")
   3654     (buf-push! tb mangled)
   3655     (buf-push! tb "__SO()\n")
   3656     (buf-push! tb (%n staging-bytes))
   3657     (buf-push! tb "\n%endm\n")
   3658     ;; %fn header
   3659     (buf-push! tb "%fn(")
   3660     (buf-push! tb mangled)
   3661     (buf-push! tb ", ")
   3662     (buf-push! tb (%n frame-size))
   3663     (buf-push! tb ", {\n")
   3664     ;; prologue + body, drained byte-for-byte
   3665     (buf-drain! tb (cg-prologue-buf cg))
   3666     ;; --cc-trace-emit: emit `%trace(&LBL, LEN)` between prologue (which
   3667     ;; spilled live argument regs to slots) and body, so the macro can
   3668     ;; freely clobber a0..a2. The mangled name rides through the
   3669     ;; regular string pool — cg-intern-string emits it with a trailing
   3670     ;; NUL and pads to 8-byte alignment, so the next data label stays
   3671     ;; aligned. We pass the *logical* byte length (no NUL) so the
   3672     ;; runtime print stops at the actual end of the name.
   3673     (cond
   3674       ((trace-emit?)
   3675        (let ((tag-lbl (cg-intern-string cg mangled)))
   3676          (buf-push! tb "%trace(&")
   3677          (buf-push! tb tag-lbl)
   3678          (buf-push! tb ", ")
   3679          (buf-push! tb (%n (bytevector-length mangled)))
   3680          (buf-push! tb ")\n"))))
   3681     (buf-drain! tb (cg-fn-buf cg))
   3682     ;; ret block: one word → a0; two words → a0+a1; wider sret → a0.
   3683     (buf-push! tb ":.ret\n")
   3684     (let ((rk (ctype-kind ret-type))
   3685           (sret? (%cg-fn-get cg '%fn-sret?)))
   3686       (cond
   3687         ((eq? rk 'void)
   3688          (buf-push! tb "%li(a0, 0)\n"))
   3689         (sret?
   3690          (buf-push! tb "%ld(a0, sp, ")
   3691          (buf-push! tb (%cg-slot-expr cg (%cg-fn-get cg '%fn-sret-slot)))
   3692          (buf-push! tb ")\n"))
   3693         (else
   3694          (buf-push! tb "%ld(a0, sp, ")
   3695          (buf-push! tb (%cg-slot-expr cg ret-slot))
   3696          (buf-push! tb ")\n")
   3697          (cond
   3698            ((> (ctype-size ret-type) %CC-WORD-BYTES)
   3699             (buf-push! tb "%ld(a1, sp, ")
   3700             (buf-push! tb (%cg-slot-expr cg
   3701                                          (+ ret-slot %CC-WORD-BYTES)))
   3702             (buf-push! tb ")\n"))))))
   3703     (buf-push! tb "})\n")
   3704     (cg-vstack-set!       cg '())
   3705     (cg-frame-hi-set!     cg 0)
   3706     (cg-max-outgoing-set! cg 0)
   3707     0))
   3708 
   3709 ;; --------------------------------------------------------------------
   3710 ;; Vstack
   3711 ;; --------------------------------------------------------------------
   3712 (define (cg-push cg op)
   3713   (cg-vstack-set! cg (cons op (cg-vstack cg)))
   3714   op)
   3715 
   3716 (define (cg-pop cg)
   3717   (let ((s (cg-vstack cg)))
   3718     (cond ((null? s) (die #f "cg-pop: empty vstack"))
   3719           (else (cg-vstack-set! cg (cdr s)) (car s)))))
   3720 
   3721 (define (cg-top cg)
   3722   (let ((s (cg-vstack cg)))
   3723     (cond ((null? s) (die #f "cg-top: empty vstack")) (else (car s)))))
   3724 
   3725 (define (cg-depth cg) (length (cg-vstack cg)))
   3726 
   3727 ;; --------------------------------------------------------------------
   3728 ;; Snapshot / rewind — discard any vstack pushes and fn-buf bytes
   3729 ;; emitted between snapshot and rewind. Used by sizeof to parse its
   3730 ;; operand for type information without retaining its side effects
   3731 ;; (CC.md §Expressions: sizeof's operand is not evaluated). Internal-
   3732 ;; only; the parser is the sole expected caller.
   3733 ;;
   3734 ;; vstack captures the head of the cons-list (immutable structurally).
   3735 ;; fn-buf is restored by resetting buf-offset; the underlying storage
   3736 ;; bytes past the new offset become garbage that the next buf-push!
   3737 ;; will overwrite (buf-push! always copies into [offset, offset+len)).
   3738 ;; frame-hi and max-outgoing are also restored so cg-alloc-slot calls
   3739 ;; inside the rewound region don't leak frame bytes.  The label counter and
   3740 ;; indirect-slot set are part of the same transaction: parsing `sizeof *p`
   3741 ;; mints an indirect temporary, and leaving that tag behind after frame-hi is
   3742 ;; rewound would make the next ordinary local at the reused offset behave as a
   3743 ;; pointer-to-storage slot.
   3744 ;; --------------------------------------------------------------------
   3745 (define (cg-snapshot cg)
   3746   (cond
   3747     ((not (cg-in-fn? cg))
   3748      (die #f "cg-snapshot: not in fn")))
   3749   (list (cg-vstack cg)
   3750         (buf-offset (cg-fn-buf cg))
   3751         (cg-frame-hi cg)
   3752         (cg-max-outgoing cg)
   3753         (cg-label-ctr cg)
   3754         (or (%cg-fn-get cg '%indirect-slots) '())))
   3755 
   3756 (define (cg-rewind cg tag)
   3757   (cg-vstack-set!       cg (car tag))
   3758   (buf-offset-set!      (cg-fn-buf cg) (cadr tag))
   3759   (cg-frame-hi-set!     cg (caddr tag))
   3760   (cg-max-outgoing-set! cg (cadddr tag))
   3761   (cg-label-ctr-set!    cg (car (cddddr tag)))
   3762   (%cg-fn-set! cg '%indirect-slots (cadr (cddddr tag))))
   3763 
   3764 ;; Duplicate the top vstack entry. For lvals this is safe — the slot
   3765 ;; (or label, or indirect-marked frame) backing the lval keeps existing
   3766 ;; until the function ends. For rvals it duplicates the descriptor of
   3767 ;; the spilled value; both copies refer to the same already-emitted
   3768 ;; storage. Used for `lhs += rhs` and `++lhs` to preserve the lhs
   3769 ;; across a `cg-load` so the subsequent `cg-assign` still has its
   3770 ;; address.
   3771 (define (cg-dup cg)
   3772   (let ((p (cg-top cg))) (cg-push cg p) p))
   3773 
   3774 ;; --------------------------------------------------------------------
   3775 ;; Materialize
   3776 ;; --------------------------------------------------------------------
   3777 (define (cg-push-imm cg ctype value)
   3778   (cg-push cg (%opnd 'imm ctype value #f)))
   3779 
   3780 (define (cg-push-string cg bv-content)
   3781   (let* ((label (cg-intern-string cg bv-content))
   3782          (cp-ty (%mk-ptr %t-i8)))
   3783     (cg-push cg (%opnd 'global cp-ty label #f))))
   3784 
   3785 (define (cg-push-sym cg sm)
   3786   (pmatch sm
   3787     (($ sym? (kind fn) (type ,ty))
   3788      (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #f)))
   3789     (($ sym? (kind enum-const) (type ,ty) (slot ,v))
   3790      (cg-push cg (%opnd 'imm ty v #f)))
   3791     (($ sym? (kind var) (storage extern) (type ,ty))
   3792      (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #t)))
   3793     (($ sym? (kind var) (storage static) (type ,ty))
   3794      (cg-push cg (%opnd 'global ty (%cg-sym-label cg sm) #t)))
   3795     (($ sym? (kind var) (type ,ty) (slot ,off))
   3796      (cg-push cg (%opnd 'frame ty off #t)))
   3797     (($ sym? (kind param) (type ,ty) (slot ,off))
   3798      (cg-push cg (%opnd 'frame ty off #t)))
   3799     (else (die #f "cg-push-sym: unsupported sym-kind" (sym-kind sm)))))
   3800 
   3801 ;; A cg-push-deref result is a frame-lval whose slot HOLDS THE ADDRESS
   3802 ;; (not the value). To distinguish from ordinary frame-lvals (whose
   3803 ;; slot holds the value directly), we tag indirect slots in
   3804 ;; %indirect-slots so cg-load and cg-assign can do the extra
   3805 ;; indirection.
   3806 (define (%cg-mark-indirect! cg off)
   3807   (let ((cur (or (%cg-fn-get cg '%indirect-slots) '())))
   3808     (%cg-fn-set! cg '%indirect-slots (cons off cur))))
   3809 
   3810 (define (%cg-indirect? cg off)
   3811   (let ((cur (or (%cg-fn-get cg '%indirect-slots) '())))
   3812     (let loop ((xs cur))
   3813       (cond ((null? xs) #f) ((= (car xs) off) #t) (else (loop (cdr xs)))))))
   3814 
   3815 (define (cg-push-deref cg)
   3816   (let* ((p  (cg-pop cg))
   3817          (pt (opnd-type p))
   3818          (pe (cond ((eq? (ctype-kind pt) 'ptr) (ctype-ext pt))
   3819                    ((eq? (ctype-kind pt) 'arr) (car (ctype-ext pt)))
   3820                    (else #f))))
   3821     (cond
   3822       ((not pe) (die #f "cg-push-deref: not a pointer" pt))
   3823       (else
   3824        (%cg-load-opnd-into cg p 't0)
   3825        (let ((off (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
   3826          (%cg-emit-st-slot cg 't0 off)
   3827          (%cg-mark-indirect! cg off)
   3828          (cg-push cg (%opnd 'frame pe off #t)))))))
   3829 
   3830 ;; --------------------------------------------------------------------
   3831 ;; Aggregate field access (§D.1–D.4)
   3832 ;; --------------------------------------------------------------------
   3833 ;; cg-push-field cg fname:
   3834 ;;   pop a struct/union lval; look up `fname` in the struct's fields
   3835 ;;   list (data.scm: ext = (tag complete? fields), where each field
   3836 ;;   is (name-bv ctype offset)); push a new lval at the field's
   3837 ;;   offset with the field's ctype.
   3838 ;;
   3839 ;; Three input cases:
   3840 ;;   - direct frame lval at slot `off`        -> frame lval at off+fo
   3841 ;;   - indirect frame lval (slot holds addr)  -> new indirect slot for
   3842 ;;                                                addr+fo
   3843 ;;   - global lval at label L                 -> indirect slot for
   3844 ;;                                                la(L)+fo
   3845 ;; In all cases the resulting lval has the field's ctype.
   3846 
   3847 ;; Look up FNAME in FIELDS. C11 §6.7.2.1: a struct/union member with no
   3848 ;; declarator (e.g. `union { int a; int b; };` inside another struct) is
   3849 ;; an "anonymous member" — its members are addressed as if they belonged
   3850 ;; directly to the enclosing aggregate. We recurse into any name=#f
   3851 ;; member of struct/union kind, composing the outer member's offset with
   3852 ;; the inner field's offset, and return a synthetic (name ctype off)
   3853 ;; triple so callers can stay agnostic about anonymity.
   3854 (define (%cg-find-field fields fname)
   3855   (let loop ((xs fields))
   3856     (cond
   3857       ((null? xs) #f)
   3858       (else
   3859        (let* ((f (car xs))
   3860               (fn (car f)))
   3861          (cond
   3862            ((and fn (bv= fn fname)) f)
   3863            ((and (not fn)
   3864                  (let ((k (ctype-kind (cadr f))))
   3865                    (or (eq? k 'struct) (eq? k 'union))))
   3866             (let* ((sub-ext (ctype-ext (cadr f)))
   3867                    (sub-fields (car (cddr sub-ext)))
   3868                    (hit (%cg-find-field sub-fields fname)))
   3869               (cond
   3870                 (hit (list (car hit)
   3871                            (cadr hit)
   3872                            (+ (car (cddr f)) (car (cddr hit)))))
   3873                 (else (loop (cdr xs))))))
   3874            (else (loop (cdr xs)))))))))
   3875 
   3876 (define (cg-push-field cg fname)
   3877   (let* ((s   (cg-pop cg))
   3878          (sty (opnd-type s))
   3879          (k   (ctype-kind sty)))
   3880     (cond
   3881       ((not (or (eq? k 'struct) (eq? k 'union)))
   3882        (die #f "cg-push-field: not a struct/union" k))
   3883       ((not (opnd-lval? s))
   3884        (die #f "cg-push-field: not an lvalue" k))
   3885       (else
   3886        (let* ((fields (car (cddr (ctype-ext sty))))
   3887               (f (%cg-find-field fields fname)))
   3888          (cond
   3889            ((not f) (die #f "cg-push-field: no such field" fname))
   3890            (else
   3891             (let* ((fty (cadr f)) (fo (car (cddr f))))
   3892               (pmatch s
   3893                 ;; direct frame lval: just shift the slot offset.
   3894                 (($ opnd? (kind frame) (ext ,off))
   3895                  (guard (not (%cg-indirect? cg off)))
   3896                  (cg-push cg (%opnd 'frame fty (+ off fo) #t)))
   3897                 ;; indirect frame lval: addr lives in the slot. Compute
   3898                 ;; addr+fo into a new indirect slot.
   3899                 (($ opnd? (kind frame) (ext ,off))
   3900                  (%cg-emit-ld-slot cg 't0 off)
   3901                  (cond
   3902                    ((> fo 0)
   3903                     (%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n"))))
   3904                  (let ((no (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
   3905                    (%cg-emit-st-slot cg 't0 no)
   3906                    (%cg-mark-indirect! cg no)
   3907                    (cg-push cg (%opnd 'frame fty no #t))))
   3908                 ;; global lval: load addr, add offset, indirect slot.
   3909                 (($ opnd? (kind global) (ext ,lbl))
   3910                  (%cg-emit-la cg 't0 lbl)
   3911                  (cond
   3912                    ((> fo 0)
   3913                     (%cg-emit-many cg (list "%addi(t0, t0, " (%n fo) ")\n"))))
   3914                  (let ((no (cg-alloc-slot cg %CC-WORD-BYTES %CC-WORD-BYTES)))
   3915                    (%cg-emit-st-slot cg 't0 no)
   3916                    (%cg-mark-indirect! cg no)
   3917                    (cg-push cg (%opnd 'frame fty no #t))))
   3918                 (else
   3919                  (die #f "cg-push-field: unsupported lval kind"
   3920                       (opnd-kind s))))))))))))
   3921 
   3922 ;; cg-decay-array:
   3923 ;;   if top of vstack is an arr-typed lval, replace it with a ptr-rval
   3924 ;;   to the first element. C arrays decay to T* in most contexts;
   3925 ;;   parse calls this before rval-style operations. No-op otherwise.
   3926 (define (cg-decay-array cg)
   3927   (let ((tp (cg-top cg)))
   3928     (cond
   3929       ((and (opnd-lval? tp) (eq? (ctype-kind (opnd-type tp)) 'arr))
   3930        (let* ((p   (cg-pop cg))
   3931               (et  (car (ctype-ext (opnd-type p))))
   3932               (pty (%mk-ptr et)))
   3933          (pmatch p
   3934            ;; direct frame lval: address is sp+off.
   3935            (($ opnd? (kind frame) (ext ,off))
   3936             (guard (not (%cg-indirect? cg off)))
   3937             (%cg-emit-lea-slot cg "t0" (%cg-slot-expr cg off))
   3938             (%cg-spill-reg cg 't0 pty))
   3939            ;; indirect frame lval (rare for arrays, but support it):
   3940            ;; the slot holds the address already.
   3941            (($ opnd? (kind frame) (ext ,off))
   3942             (%cg-emit-ld-slot cg 't0 off)
   3943             (%cg-spill-reg cg 't0 pty))
   3944            ;; global array: la(label) is the address.
   3945            (($ opnd? (kind global) (ext ,lbl))
   3946             (%cg-emit-la cg 't0 lbl)
   3947             (%cg-spill-reg cg 't0 pty))
   3948            (else (die #f "cg-decay-array: unsupported lval kind"
   3949                       (opnd-kind p))))))
   3950       (else tp))))
   3951 
   3952 ;; --------------------------------------------------------------------
   3953 ;; Address & deref
   3954 ;; --------------------------------------------------------------------
   3955 
   3956 ;; Materialize the address of an lval `op` directly into `reg`.
   3957 ;; Variant of cg-take-addr that doesn't spill — used by struct copy
   3958 ;; primitives (cg-return on struct, cg-copy-struct, cg-assign-struct,
   3959 ;; cg-call's struct receive). Caller owns the opnd (already popped).
   3960 ;;
   3961 ;; A frame opnd is treated as a slot whose address we want: if it's a
   3962 ;; flagged-indirect lval (slot holds a pointer to the real storage),
   3963 ;; load the pointer; otherwise the slot itself IS the storage and we
   3964 ;; lea its address. Frame rvals are temp spills — address = &slot. A
   3965 ;; global opnd's label is the address. Callers that require an lval
   3966 ;; check it before calling.
   3967 (define (%cg-emit-addr-of cg op reg)
   3968   (let ((reg-bv (%cg-reg->bv reg)))
   3969     (pmatch op
   3970       (($ opnd? (kind frame) (lval? #t) (ext ,off))
   3971        (guard (%cg-indirect? cg off))
   3972        (%cg-emit-ld-slot cg reg off))
   3973       (($ opnd? (kind frame) (ext ,off))
   3974        (%cg-emit-lea-slot cg reg-bv (%cg-slot-expr cg off)))
   3975       (($ opnd? (kind global) (ext ,lbl))
   3976        (%cg-emit-la cg reg lbl))
   3977       (else (die #f "cg-emit-addr-of: unsupported opnd"
   3978                  (opnd-kind op) (opnd-lval? op))))))
   3979 
   3980 ;; cg-copy-struct: pop src lval, pop dst lval, emit per-byte copy
   3981 ;; from src to dst (both must be lvals of the same struct/union type).
   3982 ;; Used by parser for struct-typed assignment / initializer-from-call
   3983 ;; targets. Pushes nothing.
   3984 (define (cg-copy-struct cg)
   3985   (let* ((src (cg-pop cg))
   3986          (dst (cg-pop cg))
   3987          (sty (opnd-type dst))
   3988          (sz  (ctype-size sty)))
   3989     (cond
   3990       ((not (opnd-lval? src)) (die #f "cg-copy-struct: src not lvalue"))
   3991       ((not (opnd-lval? dst)) (die #f "cg-copy-struct: dst not lvalue")))
   3992     (%cg-emit-addr-of cg src 't0)
   3993     (%cg-emit-addr-of cg dst 't2)
   3994     (%cg-emit-byte-copy cg 't2 't0 't1 sz)))
   3995 
   3996 ;; Struct/union `=` assignment: pop src lval, pop dst lval, memcpy,
   3997 ;; then push dst back so the assignment expression has a result for
   3998 ;; the surrounding parser to consume (parse-expr-stmt's trailing
   3999 ;; cg-pop, etc.). Distinct from cg-copy-struct because the
   4000 ;; initializer caller needs no result on the vstack.
   4001 ;;
   4002 ;; The src may be either a frame lvalue (named local slot, *p deref,
   4003 ;; callee return-slot) or a frame rvalue (anonymous slot from a temp
   4004 ;; spill); %cg-emit-addr-of handles both shapes by treating the slot
   4005 ;; itself as the address whenever the lval indirection flag isn't set.
   4006 (define (cg-assign-struct cg)
   4007   (let* ((src (cg-pop cg))
   4008          (dst (cg-pop cg))
   4009          (sty (opnd-type dst))
   4010          (sz  (ctype-size sty)))
   4011     (cond ((not (opnd-lval? dst)) (die #f "cg-assign-struct: dst not lvalue")))
   4012     (%cg-emit-addr-of cg src 't0)
   4013     (%cg-emit-addr-of cg dst 't2)
   4014     (%cg-emit-byte-copy cg 't2 't0 't1 sz)
   4015     (cg-push cg dst)))
   4016 
   4017 ;; Struct copy: defer to libp1pp memcpy via %memcpy_call. dst-reg and
   4018 ;; src-reg hold the addresses; size is the byte count. tmp-reg is no
   4019 ;; longer needed by this helper (kept in the signature so existing
   4020 ;; callers don't have to thread their scratch allocation differently),
   4021 ;; but the macro itself uses a0/a1/a2 around the call. dst-reg and
   4022 ;; src-reg must not be a0 (the dst move would clobber a different live
   4023 ;; input register); both current callers use t-regs.
   4024 (define (%cg-emit-byte-copy cg dst-reg src-reg tmp-reg size)
   4025   (%cg-emit-many cg (list "%memcpy_call("
   4026                           (%cg-reg->bv dst-reg) ", "
   4027                           (%cg-reg->bv src-reg) ", "
   4028                           (%n size) ")\n")))
   4029 
   4030 (define (cg-take-addr cg)
   4031   (let* ((p   (cg-pop cg))
   4032          (ty  (opnd-type p))
   4033          ;; &arr yields T(*)[N] per strict C. Pointer arithmetic on
   4034          ;; the result scales by sizeof(T[N]) (the whole array), so
   4035          ;; &arr + 1 is one-past-end. Array-to-pointer decay happens
   4036          ;; on use via cg-decay-array, not at the & operator.
   4037          (pty (%mk-ptr ty)))
   4038     (pmatch p
   4039       ;; &function: a function designator (rval of fn type pushed by
   4040       ;; cg-push-sym) already evaluates to its entry-point address. The
   4041       ;; `&` is a no-op semantically — re-tag the operand as ptr-to-fn.
   4042       (($ opnd? (kind global) (type ,t) (ext ,lbl) (lval? #f))
   4043        (guard (eq? (ctype-kind t) 'fn))
   4044        (cg-push cg (%opnd 'global pty lbl #f)))
   4045       (($ opnd? (lval? #f)) (die #f "cg-take-addr: not an lvalue"))
   4046       ;; The address itself lives at sp+slot — &*p degenerates to p.
   4047       (($ opnd? (kind frame) (ext ,off))
   4048        (guard (%cg-indirect? cg off))
   4049        (%cg-emit-ld-slot cg 't0 off)
   4050        (%cg-spill-reg cg 't0 pty))
   4051       ;; %lea_slot wraps the "%mov(rd, sp); %addi(rd, rd, slot)" idiom;
   4052       ;; the backend hides any frame-header offset inside %mov(rd, sp).
   4053       (($ opnd? (kind frame) (ext ,off))
   4054        (%cg-emit-lea-slot cg "t0" (%cg-slot-expr cg off))
   4055        (%cg-spill-reg cg 't0 pty))
   4056       (($ opnd? (kind global) (ext ,lbl))
   4057        (%cg-emit-la cg 't0 lbl)
   4058        (%cg-spill-reg cg 't0 pty))
   4059       (else (die #f "cg-take-addr: non-addressable" (opnd-kind p))))))
   4060 
   4061 (define (cg-load cg)
   4062   (let* ((p (cg-pop cg)) (ty (opnd-type p)))
   4063     (cond
   4064       ((not (opnd-lval? p)) (die #f "cg-load: not an lvalue"))
   4065       ;; Array lvalues decay to a ptr-rval addressing the first
   4066       ;; element (C array-to-pointer decay). We push the lval back
   4067       ;; and route through cg-decay-array for a single source of truth.
   4068       ((eq? (ctype-kind ty) 'arr)
   4069        (cg-push cg p) (cg-decay-array cg))
   4070       ;; Struct/union lvalues stay as lvalues — there is no
   4071       ;; register-sized rvalue form for an aggregate, and the
   4072       ;; historical one-word spill path silently truncated wider aggregates
   4073       ;; (the bug that broke `c = cond ? a : b`). Surrounding machinery
   4074       ;; (cg-ifelse-merge / cg-assign-struct / cg-call) consumes
   4075       ;; aggregate operands as lvalues already.
   4076       ((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union))
   4077        (cg-push cg p))
   4078       ((%ctype-wide-int? ty)
   4079        (%cg-load-wide-opnd-into cg p 't0 't1)
   4080        (%cg-spill-pair cg 't0 't1 ty))
   4081       ((and (eq? (opnd-kind p) 'frame)
   4082             (%cg-indirect? cg (opnd-ext p)))
   4083        ;; Indirect frame-lval: slot holds the address. Stage the
   4084        ;; address in t2 so multi-byte gathers don't alias dest with
   4085        ;; base.
   4086        (%cg-emit-ld-slot cg 't2 (opnd-ext p))
   4087        (%cg-emit-ld-typed cg 't0 ty 't2 0)
   4088        (%cg-spill-reg cg 't0 ty))
   4089       (else (%cg-load-opnd-into cg p 't0) (%cg-spill-reg cg 't0 ty)))))
   4090 
   4091 ;; --------------------------------------------------------------------
   4092 ;; Type conversions
   4093 ;; --------------------------------------------------------------------
   4094 (define (cg-cast cg to-type)
   4095   (let* ((p       (cg-pop cg))
   4096          (from-ty (opnd-type p))
   4097          (from-sz (ctype-size from-ty))
   4098          (to-sz   (ctype-size to-type))
   4099          (to-kind (ctype-kind to-type)))
   4100     (%cg-fp-reject! 'cast-to to-type)
   4101     (%cg-fp-reject! 'cast-from from-ty)
   4102     (cond
   4103       ((eq? to-kind 'bool)
   4104        (cond
   4105          ((%ctype-wide-int? from-ty)
   4106           (%cg-load-wide-opnd-into cg p 't0 't1)
   4107           (%cg-emit-rrr cg "or" 't0 't0 't1))
   4108          (else (%cg-load-opnd-into cg p 't0)))
   4109        (%cg-emit-many cg (list "%bool(t0, t0)\n"))
   4110        (%cg-spill-reg cg 't0 to-type))
   4111       ;; Pointer-to-pointer casts preserve the one-word representation.
   4112       ((and (eq? to-kind 'ptr)
   4113             (or (eq? (ctype-kind from-ty) 'ptr)
   4114                 (eq? (ctype-kind from-ty) 'arr)
   4115                 (eq? (ctype-kind from-ty) 'fn)))
   4116        (cg-push cg (%opnd (opnd-kind p) to-type (opnd-ext p) (opnd-lval? p))))
   4117       ;; Any RV32 narrowing conversion consumes only the low limb, then
   4118       ;; canonicalizes it for the destination type.
   4119       ((and (%ctype-wide-int? from-ty) (not (%ctype-wide-int? to-type)))
   4120        (%cg-load-wide-opnd-into cg p 't0 't1)
   4121        (%cg-canonicalize cg 't0 to-type)
   4122        (%cg-spill-reg cg 't0 to-type))
   4123       ;; Same-width signedness changes do not alter an i64 bit pattern.
   4124       ((and (%ctype-wide-int? from-ty) (%ctype-wide-int? to-type))
   4125        (cg-push cg (%opnd (opnd-kind p) to-type (opnd-ext p) (opnd-lval? p))))
   4126       ;; Widen a one-word RV32 value into a real two-word integer. The low
   4127       ;; limb is already canonical; signed sources replicate their sign bit.
   4128       ((%ctype-wide-int? to-type)
   4129        (%cg-load-opnd-into cg p 't0)
   4130        (cond
   4131          ((%ctype-unsigned? from-ty)
   4132           (%cg-emit-many cg (list "%li(t1, 0)\n")))
   4133          (else
   4134           (%cg-emit-many cg
   4135                          (list "%sari(t1, t0, "
   4136                                (%n (- %CC-WORD-BITS 1)) ")\n"))))
   4137        (%cg-spill-pair cg 't0 't1 to-type))
   4138       ;; Same-size or widening cast — retag only when the canonical
   4139       ;; 64-bit slot form for FROM-TY is also canonical for TO-TYPE.
   4140       ;; That holds unless we're crossing from a signed type into an
   4141       ;; unsigned one of the same or wider width: the source's
   4142       ;; sign-extended high bits would leak past the unsigned width
   4143       ;; and corrupt later 64-bit operands (compares, wider casts).
   4144       ;; Same applies to same-size unsigned→signed at narrow widths
   4145       ;; (the narrow branch's sign-extend turns 0xCA back into the
   4146       ;; canonical i8 slot 0xFF…FFCA).
   4147       ((and (>= to-sz from-sz)
   4148             (not (and (not (%ctype-unsigned? from-ty))
   4149                       (%ctype-unsigned? to-type)))
   4150             (not (and (= to-sz from-sz)
   4151                       (%ctype-unsigned? from-ty)
   4152                       (not (%ctype-unsigned? to-type)))))
   4153        (cg-push cg (%opnd (opnd-kind p) to-type (opnd-ext p) (opnd-lval? p))))
   4154       (else
   4155        ;; Narrowing cast OR same/widening with signedness flip.
   4156        ;; Signed targets (i8/i16/i32) shli/sari to truncate-and-
   4157        ;; sign-extend in one step, so the slot holds the canonical
   4158        ;; 64-bit form and a subsequent widening cast (which is
   4159        ;; relabel-only) restores the value. Unsigned targets mask
   4160        ;; off high bits to zero-extend.
   4161        (%cg-load-opnd-into cg p 't0)
   4162        (%cg-canonicalize cg 't0 to-type)
   4163        (%cg-spill-reg cg 't0 to-type)))))
   4164 
   4165 (define (cg-promote cg)
   4166   (let* ((p  (cg-pop cg))
   4167          (ty (opnd-type p))
   4168          (sz (ctype-size ty)))
   4169     (cond
   4170       ;; C 6.3.1.1: _Bool, char, short, and any narrower int type
   4171       ;; promote to (signed) int — every representable value fits
   4172       ;; in i32. Treating narrow unsigned types as u32 here would
   4173       ;; drag the subsequent arith-conv into picking the unsigned
   4174       ;; common type, flipping signedness of `>>`, comparisons,
   4175       ;; division, etc. against the C rule. Canonical form for any
   4176       ;; in-range narrow value already matches i32, so the cast is
   4177       ;; relabel-only.
   4178       ((< sz 4)
   4179        (cg-push cg (%opnd (opnd-kind p) %t-i32 (opnd-ext p) (opnd-lval? p))))
   4180       (else (cg-push cg p)))))
   4181 
   4182 (define (cg-arith-conv cg)
   4183   ;; Usual arithmetic conversions on arithmetic operands. When either
   4184   ;; operand is a pointer (or array,
   4185   ;; which behaves as a pointer in arithmetic), the pair is a
   4186   ;; pointer-arith case — leave the types alone so cg-binop can detect
   4187   ;; the ptr operand and apply the right scaling.
   4188   (let* ((b  (cg-pop cg))
   4189          (a  (cg-pop cg))
   4190          (ta (opnd-type a))
   4191          (tb (opnd-type b))
   4192          (sa (ctype-size ta))
   4193          (sb (ctype-size tb)))
   4194     (cond
   4195       ;; Pointer/array arithmetic: leave types alone so cg-binop's
   4196       ;; ptr-aware add/sub branch fires with the correct pointee type
   4197       ;; (and doesn't see two pointers, which would skip scaling).
   4198       ((or (%ctype-ptr? ta) (%ctype-ptr? tb))
   4199        (cg-push cg a)
   4200        (cg-push cg b))
   4201       (else
   4202        (let ((common (cond
   4203                        ((> sa sb) ta)
   4204                        ((> sb sa) tb)
   4205                        ((%ctype-unsigned? ta) ta)
   4206                        ((%ctype-unsigned? tb) tb)
   4207                        (else ta))))
   4208          ;; Route through cg-cast (rather than relabel only) so the
   4209          ;; canonical target-word slot form lines up with COMMON. Same-size
   4210          ;; cross-signedness conversions (i32→u32, u32→i32, …) need an
   4211          ;; actual zext/sext to canonicalize; otherwise an i32 -3
   4212          ;; relabeled to u32 keeps its sign-extended slot bits and
   4213          ;; compares unequal to a u32 imm with the same C value.
   4214          (cg-push cg a) (cg-cast cg common)
   4215          (let ((a* (cg-pop cg)))
   4216            (cg-push cg b) (cg-cast cg common)
   4217            (let ((b* (cg-pop cg)))
   4218              (cg-push cg a*)
   4219              (cg-push cg b*))))))))
   4220 
   4221 ;; --------------------------------------------------------------------
   4222 ;; Operators
   4223 ;; --------------------------------------------------------------------
   4224 (define (%cg-emit-rrr cg op rd ra rb)
   4225   (%cg-emit-many cg (list "%" op "(" (%cg-reg->bv rd) ", "
   4226                           (%cg-reg->bv ra) ", " (%cg-reg->bv rb) ")\n")))
   4227 
   4228 (define (%cg-emit-cmp cg cc ra rb rd)
   4229   (%cg-emit-many cg (list "%cmpset_" cc "("
   4230                           (%cg-reg->bv rd) ", "
   4231                           (%cg-reg->bv ra) ", " (%cg-reg->bv rb)
   4232                           ")\n")))
   4233 
   4234 (define (cg-binop cg op)
   4235   (let* ((b  (cg-pop cg))
   4236          (a  (cg-pop cg))
   4237          (ta (opnd-type a))
   4238          (tb (opnd-type b))
   4239          (unsigned? (or (%ctype-unsigned? ta) (%ctype-unsigned? tb)))
   4240          (a-ptr? (%ctype-ptr? ta))
   4241          (b-ptr? (%ctype-ptr? tb))
   4242          (result-ty
   4243           (cond
   4244             ((or (eq? op 'eq) (eq? op 'ne)
   4245                  (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
   4246              %t-i32)
   4247             ((and a-ptr? b-ptr? (eq? op 'sub)) %t-word-i)
   4248             (a-ptr? ta)
   4249             (b-ptr? tb)
   4250             (else ta))))
   4251     (cond
   4252       ((%ctype-wide-int? ta)
   4253        (%cg-load-wide-opnd-into cg a 'a0 'a1)
   4254        (cond
   4255          ((or (eq? op 'shl) (eq? op 'shr))
   4256           (cond
   4257             ((%ctype-wide-int? tb)
   4258              (%cg-load-wide-opnd-into cg b 'a2 'a3))
   4259             (else (%cg-load-opnd-into cg b 'a2)))
   4260           (%cg-emit-many
   4261            cg
   4262            (list (cond ((eq? op 'shl) "%i64_shl(")
   4263                        ((%ctype-unsigned? ta) "%i64_shr(")
   4264                        (else "%i64_sar("))
   4265                  "t0, t1, a0, a1, a2, t2)\n"))
   4266           (%cg-spill-pair cg 't0 't1 result-ty))
   4267          (else
   4268           (%cg-load-wide-opnd-into cg b 'a2 'a3)
   4269           (cond
   4270             ((eq? op 'add)
   4271              (%cg-emit-many cg
   4272                             (list "%i64_add(t0, t1, a0, a1, a2, a3, t2)\n")))
   4273             ((eq? op 'sub)
   4274              (%cg-emit-many cg
   4275                             (list "%i64_sub(t0, t1, a0, a1, a2, a3, t2)\n")))
   4276             ((eq? op 'mul)
   4277              (%cg-emit-many cg
   4278                             (list "%i64_mul(t0, t1, a0, a1, a2, a3, t2)\n")))
   4279             ((eq? op 'and)
   4280              (%cg-emit-rrr cg "and" 't0 'a0 'a2)
   4281              (%cg-emit-rrr cg "and" 't1 'a1 'a3))
   4282             ((eq? op 'or)
   4283              (%cg-emit-rrr cg "or" 't0 'a0 'a2)
   4284              (%cg-emit-rrr cg "or" 't1 'a1 'a3))
   4285             ((eq? op 'xor)
   4286              (%cg-emit-rrr cg "xor" 't0 'a0 'a2)
   4287              (%cg-emit-rrr cg "xor" 't1 'a1 'a3))
   4288             ((or (eq? op 'div) (eq? op 'rem))
   4289              (%cg-emit-many
   4290               cg
   4291               (list "%call(&"
   4292                     (if (%ctype-unsigned? ta)
   4293                         "p1_i64_udivmod" "p1_i64_divmod")
   4294                     ")\n")))
   4295             ((or (eq? op 'eq) (eq? op 'ne)
   4296                  (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
   4297              (%cg-emit-many
   4298               cg
   4299               (list "%i64_cmpset_"
   4300                     (cond ((eq? op 'eq) "eq")
   4301                           ((eq? op 'ne) "ne")
   4302                           ((eq? op 'lt) (if unsigned? "ltu" "lt"))
   4303                           ((eq? op 'le) (if unsigned? "leu" "le"))
   4304                           ((eq? op 'gt) (if unsigned? "gtu" "gt"))
   4305                           (else          (if unsigned? "geu" "ge")))
   4306                     "(t0, a0, a1, a2, a3, t1)\n")))
   4307             (else (die #f "cg-binop: unknown wide op" op)))
   4308           (cond
   4309             ((or (eq? op 'eq) (eq? op 'ne)
   4310                  (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge))
   4311              (%cg-spill-reg cg 't0 %t-i32))
   4312             ((eq? op 'div)
   4313              (%cg-spill-pair cg 'a0 'a1 result-ty))
   4314             ((eq? op 'rem)
   4315              (%cg-spill-pair cg 'a2 'a3 result-ty))
   4316             (else (%cg-spill-pair cg 't0 't1 result-ty))))))
   4317       ((and a-ptr? (or (eq? op 'add) (eq? op 'sub)) (not b-ptr?))
   4318        (%cg-load-opnd-into cg a 'a0)
   4319        (%cg-load-opnd-into cg b 'a1)
   4320        (let ((sz   (ctype-size (%ctype-pointee ta)))
   4321              (mac (if (eq? op 'add) "%ptr_add(" "%ptr_sub(")))
   4322          (%cg-emit-many cg (list mac "t0, a0, a1, " (%n sz) ", t1)\n")))
   4323        (%cg-spill-reg cg 't0 result-ty))
   4324       ((and b-ptr? (eq? op 'add) (not a-ptr?))
   4325        (%cg-load-opnd-into cg a 'a0)
   4326        (%cg-load-opnd-into cg b 'a1)
   4327        (let ((sz (ctype-size (%ctype-pointee tb))))
   4328          (%cg-emit-many cg (list "%ptr_add(t0, a1, a0, " (%n sz) ", t1)\n")))
   4329        (%cg-spill-reg cg 't0 result-ty))
   4330       ((and a-ptr? b-ptr? (eq? op 'sub))
   4331        (%cg-load-opnd-into cg a 'a0)
   4332        (%cg-load-opnd-into cg b 'a1)
   4333        (let ((sz (ctype-size (%ctype-pointee ta))))
   4334          (%cg-emit-many cg (list "%ptr_diff(t0, a0, a1, " (%n sz) ", t1)\n")))
   4335        (%cg-spill-reg cg 't0 result-ty))
   4336       (else
   4337        (%cg-load-opnd-into cg a 'a0)
   4338        (%cg-load-opnd-into cg b 'a1)
   4339        (cond
   4340          ((eq? op 'add) (%cg-emit-rrr cg "add" 't0 'a0 'a1))
   4341          ((eq? op 'sub) (%cg-emit-rrr cg "sub" 't0 'a0 'a1))
   4342          ((eq? op 'mul) (%cg-emit-rrr cg "mul" 't0 'a0 'a1))
   4343          ((eq? op 'and) (%cg-emit-rrr cg "and" 't0 'a0 'a1))
   4344          ((eq? op 'or)  (%cg-emit-rrr cg "or"  't0 'a0 'a1))
   4345          ((eq? op 'xor) (%cg-emit-rrr cg "xor" 't0 'a0 'a1))
   4346          ((eq? op 'shl) (%cg-emit-rrr cg "shl" 't0 'a0 'a1))
   4347          ((eq? op 'shr)
   4348           ;; Shift result type is the promoted LEFT operand's type
   4349           ;; (C 6.5.7); arithmetic vs logical shift must follow that
   4350           ;; signedness alone, not the rhs's. cg-arith-conv may have
   4351           ;; relabeled ta to match an unsigned rhs — guard against
   4352           ;; that by checking the original `a` opnd's signedness.
   4353           (if (%ctype-unsigned? ta)
   4354               (%cg-emit-rrr cg "shr" 't0 'a0 'a1)
   4355               (%cg-emit-rrr cg "sar" 't0 'a0 'a1)))
   4356          ((eq? op 'div)
   4357           (%cg-emit-rrr cg (if unsigned? "udiv" "div") 't0 'a0 'a1))
   4358          ((eq? op 'rem)
   4359           (%cg-emit-rrr cg (if unsigned? "urem" "rem") 't0 'a0 'a1))
   4360          ((eq? op 'eq) (%cg-emit-cmp cg "eq"  'a0 'a1 't0))
   4361          ((eq? op 'ne) (%cg-emit-cmp cg "ne"  'a0 'a1 't0))
   4362          ((eq? op 'lt) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a0 'a1 't0))
   4363          ((eq? op 'gt) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a1 'a0 't0))
   4364          ((eq? op 'le) (%cg-emit-cmp cg (if unsigned? "leu" "le") 'a0 'a1 't0))
   4365          ((eq? op 'ge) (%cg-emit-cmp cg (if unsigned? "geu" "ge") 'a0 'a1 't0))
   4366          (else (die #f "cg-binop: unknown op" op)))
   4367        ;; Canonicalize narrow integer results to their type's bit width
   4368        ;; before spilling, so the slot's bit-pattern matches result-ty.
   4369        ;; Compare ops already yield 0/1; skip them. Pointer-arith branches
   4370        ;; above don't reach here.
   4371        (cond
   4372          ((or (eq? op 'eq) (eq? op 'ne)
   4373               (eq? op 'lt) (eq? op 'le) (eq? op 'gt) (eq? op 'ge)) 0)
   4374          (else (%cg-canonicalize cg 't0 result-ty)))
   4375        (%cg-spill-reg cg 't0 result-ty)))))
   4376 
   4377 ;; Post-increment / post-decrement on the top-of-vstack lval.
   4378 ;; Pushes the OLD value (per C semantics) and emits the +1 / -1 store.
   4379 ;; Uses cg-dup + cg-load to capture the old rval (which is then in a
   4380 ;; never-reused spill slot), then runs the regular dup+load+add+assign
   4381 ;; pattern for the store. Pointer scaling falls out of cg-binop add.
   4382 (define (%cg-post-inc-dec cg op)
   4383   (cg-dup cg)
   4384   (cg-load cg)
   4385   (let ((old (cg-pop cg)))
   4386     (cg-dup cg)
   4387     (cg-load cg)
   4388     (cg-push-imm cg %t-i32 1)
   4389     (cg-binop cg op)
   4390     (cg-assign cg)
   4391     (cg-pop cg)
   4392     (cg-push cg old)))
   4393 
   4394 (define (cg-postinc cg) (%cg-post-inc-dec cg 'add))
   4395 (define (cg-postdec cg) (%cg-post-inc-dec cg 'sub))
   4396 
   4397 (define (cg-unop cg op)
   4398   (let* ((p  (cg-pop cg)) (ty (opnd-type p)))
   4399     (cond
   4400       ((%ctype-wide-int? ty)
   4401        (%cg-load-wide-opnd-into cg p 'a0 'a1)
   4402        (cond
   4403          ((eq? op 'neg)
   4404           (%cg-emit-many cg (list "%i64_neg(t0, t1, a0, a1, t2)\n"))
   4405           (%cg-spill-pair cg 't0 't1 ty))
   4406          ((eq? op 'bnot)
   4407           (%cg-emit-many cg (list "%bnot(t0, a0, t2)\n"
   4408                                   "%bnot(t1, a1, t2)\n"))
   4409           (%cg-spill-pair cg 't0 't1 ty))
   4410          ((eq? op 'lnot)
   4411           (%cg-emit-rrr cg "or" 't0 'a0 'a1)
   4412           (%cg-emit-many cg (list "%cmpset_eqz(t0, t0)\n"))
   4413           (%cg-spill-reg cg 't0 %t-i32))
   4414          (else (die #f "cg-unop: unknown wide op" op))))
   4415       (else
   4416        (%cg-load-opnd-into cg p 't0)
   4417        (cond
   4418       ((eq? op 'neg)
   4419        (%cg-emit-many cg (list "%neg(t0, t0, t1)\n"))
   4420        (%cg-spill-reg cg 't0 ty))
   4421       ((eq? op 'bnot)
   4422        (%cg-emit-many cg (list "%bnot(t0, t0, t1)\n"))
   4423        (%cg-spill-reg cg 't0 ty))
   4424       ((eq? op 'lnot)
   4425        (%cg-emit-many cg (list "%cmpset_eqz(t0, t0)\n"))
   4426        (%cg-spill-reg cg 't0 %t-i32))
   4427       (else (die #f "cg-unop: unknown op" op)))))))
   4428 
   4429 (define (cg-assign cg)
   4430   ;; Pops rhs, pops lhs, casts rhs to lhs's type (parser cannot peek
   4431   ;; deeper than vstack top to do this itself), emits the store, pushes
   4432   ;; the assigned value as the result rval.
   4433   (let* ((rhs0 (cg-pop cg))
   4434          (lhs  (cg-pop cg))
   4435          (ty   (opnd-type lhs)))
   4436     (cond ((not (opnd-lval? lhs)) (die #f "cg-assign: lhs not lvalue")))
   4437     ;; Cast rhs to lhs's type (no-op when the types already match).
   4438     (cg-push cg rhs0)
   4439     (cg-cast cg ty)
   4440     (let ((rhs (cg-pop cg)))
   4441       (cond
   4442         ((%ctype-wide-int? ty)
   4443          (%cg-load-wide-opnd-into cg rhs 'a0 'a1)
   4444          (%cg-store-pair-to-lval cg 'a0 'a1 lhs)
   4445          (%cg-spill-pair cg 'a0 'a1 ty))
   4446         (else
   4447          (%cg-load-opnd-into cg rhs 'a0)
   4448          (pmatch lhs
   4449            (($ opnd? (kind frame) (ext ,off))
   4450             (guard (%cg-indirect? cg off))
   4451             (%cg-emit-ld-slot cg 't0 off)
   4452             (%cg-emit-st-typed cg 'a0 ty 't0 0))
   4453            (($ opnd? (kind frame) (ext ,off))
   4454             (%cg-emit-st-slot-typed cg 'a0 ty off))
   4455            (($ opnd? (kind global) (ext ,lbl))
   4456             (%cg-emit-la cg 't0 lbl)
   4457             (%cg-emit-st-typed cg 'a0 ty 't0 0))
   4458            (else (die #f "cg-assign: unsupported lhs kind" (opnd-kind lhs))))
   4459          (%cg-spill-reg cg 'a0 ty))))))
   4460 
   4461 ;; --------------------------------------------------------------------
   4462 ;; Calls
   4463 ;; --------------------------------------------------------------------
   4464 (define (cg-call cg arity has-result?)
   4465   (let* ((args (let loop ((i 0) (acc '()))
   4466                  (cond ((= i arity) acc)
   4467                        (else (loop (+ i 1) (cons (cg-pop cg) acc))))))
   4468          (fn-op (cg-pop cg))
   4469          ;; sret = struct/union wider than two target words; shift args by one reg
   4470          ;; and place a0 last so it's not clobbered by arg loads.
   4471          (fty (opnd-type fn-op))
   4472          (rty (cond
   4473                 ((eq? (ctype-kind fty) 'fn) (car (ctype-ext fty)))
   4474                 ((eq? (ctype-kind fty) 'ptr)
   4475                  (let ((p (ctype-ext fty)))
   4476                    (if (eq? (ctype-kind p) 'fn) (car (ctype-ext p)) %t-word-i)))
   4477                 (else %t-word-i)))
   4478          (rk  (ctype-kind rty))
   4479          (sret? (and has-result?
   4480                      (or (eq? rk 'struct) (eq? rk 'union))
   4481                      (> (ctype-size rty) %CC-PAIR-BYTES)))
   4482          ;; If the callee is variadic, the callee's save area caps total
   4483          ;; incoming ABI words. Reject silent miscompiles up front.
   4484          (callee-fty (cond
   4485                        ((eq? (ctype-kind fty) 'fn) fty)
   4486                        ((and (eq? (ctype-kind fty) 'ptr)
   4487                              (eq? (ctype-kind (ctype-ext fty)) 'fn))
   4488                         (ctype-ext fty))
   4489                        (else #f)))
   4490          (callee-variadic? (and callee-fty
   4491                                 (let ((ext (ctype-ext callee-fty)))
   4492                                   (and (pair? ext) (pair? (cdr ext))
   4493                                        (pair? (cddr ext))
   4494                                        (car (cddr ext))))))
   4495          (arg-slots
   4496           (let count ((xs args) (n 0))
   4497             (cond ((null? xs) n)
   4498                   (else (count (cdr xs)
   4499                                (+ n (%cg-param-reg-count
   4500                                        (opnd-type (car xs)))))))))
   4501          (_cap-check (cond
   4502                        ((and callee-variadic?
   4503                              (> arg-slots %CG-VARARG-WINDOW))
   4504                         (die #f "cg-call: variadic call exceeds save-area"
   4505                              arg-slots %CG-VARARG-WINDOW))
   4506                        (else 0)))
   4507          (sret-shift (if sret? 1 0))
   4508          (recv-slot (cond
   4509                       (sret?
   4510                        (cg-alloc-slot cg
   4511                                       (align-up (ctype-size rty) %CC-WORD-BYTES)
   4512                                       (max %CC-WORD-BYTES (ctype-align rty))))
   4513                       (else #f))))
   4514     ;; Copy every indirect aggregate before loading any ABI argument register:
   4515     ;; %memcpy_call uses a0-a2, so interleaving a later copy with register
   4516     ;; staging would clobber earlier arguments.  The parallel list records the
   4517     ;; private frame slot for each large aggregate and #f for direct args.
   4518     (let ((indirect-copies
   4519            (let materialize ((xs args) (out '()))
   4520              (cond
   4521                ((null? xs) (reverse out))
   4522                (else
   4523                 (let* ((arg (car xs))
   4524                        (aty (opnd-type arg)))
   4525                   (cond
   4526                     ((%cg-param-indirect? aty)
   4527                      (let* ((sz (ctype-size aty))
   4528                             (al (max %CC-WORD-BYTES (ctype-align aty)))
   4529                             (slot (cg-alloc-slot
   4530                                    cg (align-up sz %CC-WORD-BYTES) al)))
   4531                        (%cg-emit-addr-of cg arg 't0)
   4532                        (%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg slot))
   4533                        (%cg-emit-byte-copy cg 't2 't0 't1 sz)
   4534                        (materialize (cdr xs) (cons slot out))))
   4535                     (else
   4536                      (materialize (cdr xs) (cons #f out))))))))))
   4537       (let stage ((xs args) (copies indirect-copies) (idx 0))
   4538         (cond
   4539           ((null? xs) 0)
   4540           (else
   4541            (let* ((arg (car xs))
   4542                   (aty (opnd-type arg))
   4543                   (n   (%cg-param-reg-count aty)))
   4544              (cond
   4545              ;; Large aggregate: pass the address of the private copy through
   4546              ;; one pointer ABI slot. The callee marks its pointer slot
   4547              ;; indirect, so ordinary struct lvalue operations address it.
   4548              ((%cg-param-indirect? aty)
   4549               (let ((slot (car copies))
   4550                     (abi (+ idx sret-shift)))
   4551                 (cond
   4552                   ((< abi 4)
   4553                    (%cg-emit-lea-slot cg
   4554                                       (%cg-reg->bv (%reg-by-idx abi))
   4555                                       (%cg-slot-expr cg slot)))
   4556                   (else
   4557                    (%cg-emit-lea-slot cg "t0" (%cg-slot-expr cg slot))
   4558                    (%cg-emit-st cg 't0 'sp
   4559                                 (* %CC-WORD-BYTES (- abi 4)))))
   4560                 (stage (cdr xs) (cdr copies) (+ idx 1))))
   4561              ;; RV32 i64/u64 values consume two consecutive ABI words.
   4562              ((%ctype-wide-int? aty)
   4563               (%cg-load-wide-opnd-into cg arg 't0 't1)
   4564               (let chunk ((i 0))
   4565                 (cond
   4566                   ((= i 2) 0)
   4567                   (else
   4568                    (let ((tabi (+ idx sret-shift i))
   4569                          (src (if (= i 0) 't0 't1)))
   4570                      (cond
   4571                        ((< tabi 4)
   4572                         (%cg-emit-many
   4573                          cg
   4574                          (list "%mov("
   4575                                (%cg-reg->bv (%reg-by-idx tabi)) ", "
   4576                                (%cg-reg->bv src) ")\n")))
   4577                        (else
   4578                         (%cg-emit-st cg src 'sp
   4579                                      (* %CC-WORD-BYTES (- tabi 4)))))
   4580                      (chunk (+ i 1))))))
   4581               (stage (cdr xs) (cdr copies) (+ idx 2)))
   4582              ;; Direct aggregate: load its target-word chunks into successive
   4583              ;; arg regs / stack slots.  This path also handles a one-word
   4584              ;; aggregate: unlike the generic scalar load below, addr-of knows
   4585              ;; when an aggregate lvalue is represented by an indirect frame
   4586              ;; slot (for example `p->loc`) and follows that slot before the
   4587              ;; chunk load.
   4588              ((%cg-param-aggregate? aty)
   4589               (%cg-emit-addr-of cg arg 't0)
   4590               (let chunk ((i 0))
   4591                 (cond
   4592                   ((>= i n) 0)
   4593                   (else
   4594                    (let ((tabi (+ idx sret-shift i)))
   4595                      (cond
   4596                        ((< tabi 4)
   4597                         (%cg-emit-many cg
   4598                                        (list "%ld("
   4599                                              (%cg-reg->bv (%reg-by-idx tabi))
   4600                                              ", t0, "
   4601                                              (%n (* i %CC-WORD-BYTES)) ")\n")))
   4602                        (else
   4603                         (%cg-emit-many cg
   4604                                        (list "%ld(t1, t0, "
   4605                                              (%n (* i %CC-WORD-BYTES)) ")\n"))
   4606                         (%cg-emit-st cg 't1 'sp
   4607                                      (* %CC-WORD-BYTES (- tabi 4))))))
   4608                    (chunk (+ i 1)))))
   4609               (stage (cdr xs) (cdr copies) (+ idx n)))
   4610              (else
   4611               (let ((abi (+ idx sret-shift)))
   4612                 (cond
   4613                   ((< abi 4)
   4614                    (%cg-load-opnd-into cg arg (%reg-by-idx abi))
   4615                    (stage (cdr xs) (cdr copies) (+ idx 1)))
   4616                   (else
   4617                    (%cg-load-opnd-into cg arg 't0)
   4618                    (%cg-emit-st cg 't0 'sp
   4619                                 (* %CC-WORD-BYTES (- abi 4)))
   4620                    (stage (cdr xs) (cdr copies) (+ idx 1))))))))))))
   4621     ;; Stack-arg footprint accounts for the extra ABI slot any
   4622     ;; >8B-aggregate arg consumed beyond its single-position cousin.
   4623     (let* ((nabi (let count ((xs args) (n sret-shift))
   4624                    (cond ((null? xs) n)
   4625                          (else (count (cdr xs)
   4626                                       (+ n (%cg-param-reg-count
   4627                                               (opnd-type (car xs)))))))))
   4628            (sa  (max 0 (- nabi 4))))
   4629       (cond ((> sa 0) (%cg-bump-outgoing! cg sa)) (else 0)))
   4630     (cond
   4631       (sret?
   4632        (%cg-emit-lea-slot cg "a0" (%cg-slot-expr cg recv-slot))))
   4633     (cond
   4634       ((and (eq? (opnd-kind fn-op) 'global) (not (opnd-lval? fn-op)))
   4635        (%cg-emit-many cg (list "%call(&" (opnd-ext fn-op) ")\n")))
   4636       (else
   4637        (%cg-load-opnd-into cg fn-op 't0)
   4638        (%cg-emit-many cg (list "%callr(t0)\n"))))
   4639     (cond
   4640       (has-result?
   4641        (cond
   4642          ((%ctype-wide-int? rty)
   4643           (%cg-spill-pair cg 'a0 'a1 rty))
   4644          ;; Wider-than-two-word sret (A2): a0 holds recv-slot; push as struct lval.
   4645          (sret? (cg-push cg (%opnd 'frame rty recv-slot #t)))
   4646          ;; At-most-two-word struct/union (A1): fresh slot, spill from a0/a1.
   4647          ((and (or (eq? rk 'struct) (eq? rk 'union))
   4648                (<= (ctype-size rty) %CC-PAIR-BYTES))
   4649           (let* ((sz   (ctype-size rty))
   4650                  (al   (max %CC-WORD-BYTES (ctype-align rty)))
   4651                  (slot (cg-alloc-slot cg
   4652                                       (align-up sz %CC-WORD-BYTES) al)))
   4653             (%cg-emit-st-slot cg 'a0 slot)
   4654             (cond ((> sz %CC-WORD-BYTES)
   4655                    (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES))))
   4656             (cg-push cg (%opnd 'frame rty slot #t))))
   4657          (else
   4658           (%cg-spill-reg cg 'a0 rty))))
   4659       (else #f))))
   4660 
   4661 ;; --------------------------------------------------------------------
   4662 ;; Return
   4663 ;; --------------------------------------------------------------------
   4664 (define (cg-return cg)
   4665   (let* ((ret-slot (%cg-fn-get cg '%fn-ret-slot))
   4666          (ret-type (%cg-fn-get cg '%fn-ret-type))
   4667          (rk       (ctype-kind ret-type))
   4668          (sret?    (%cg-fn-get cg '%fn-sret?)))
   4669     (cond
   4670       ((eq? rk 'void)
   4671        (%cg-emit-many cg (list "%b(&.ret)\n")))
   4672       ((or (eq? rk 'struct) (eq? rk 'union))
   4673        ;; struct-by-value: at most two words (A1) → ret-slot; wider
   4674        ;; (A2 sret) → *sret-slot.
   4675        (let* ((p (cg-pop cg)) (sz (ctype-size ret-type)))
   4676          (cond ((not (opnd-lval? p))
   4677                 (die #f "cg-return: struct value must be an lvalue")))
   4678          (%cg-emit-addr-of cg p 't0)
   4679          (cond
   4680            (sret?
   4681             (%cg-emit-ld-slot cg 't2 (%cg-fn-get cg '%fn-sret-slot)))
   4682          (else
   4683             (%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg ret-slot))))
   4684          (%cg-emit-byte-copy cg 't2 't0 't1 sz)
   4685          (%cg-emit-many cg (list "%b(&.ret)\n"))))
   4686       ((%ctype-wide-int? ret-type)
   4687        (let ((p (cg-pop cg)))
   4688          (%cg-load-wide-opnd-into cg p 'a0 'a1)
   4689          (%cg-emit-st-slot cg 'a0 ret-slot)
   4690          (%cg-emit-st-slot cg 'a1 (+ ret-slot %CC-WORD-BYTES))
   4691          (%cg-emit-many cg (list "%b(&.ret)\n"))))
   4692       (else
   4693        (let ((p (cg-pop cg)))
   4694          (%cg-load-opnd-into cg p 'a0)
   4695          (%cg-emit-st-slot cg 'a0 ret-slot)
   4696          (%cg-emit-many cg (list "%b(&.ret)\n")))))))
   4697 
   4698 ;; --------------------------------------------------------------------
   4699 ;; Structured control flow
   4700 ;; --------------------------------------------------------------------
   4701 (define (cg-if cg then-thunk)
   4702   (let ((p (cg-pop cg)))
   4703     (%cg-load-truth-into cg p 't0)
   4704     (%cg-emit-many cg (list "%if_nez(t0, {\n"))
   4705     (then-thunk)
   4706     (%cg-emit-many cg (list "})\n"))))
   4707 
   4708 (define (cg-ifelse cg then-thunk else-thunk)
   4709   (let ((p (cg-pop cg)))
   4710     (%cg-load-truth-into cg p 't0)
   4711     (%cg-emit-many cg (list "%ifelse_nez(t0, {\n"))
   4712     (then-thunk)
   4713     (%cg-emit-many cg (list "}, {\n"))
   4714     (else-thunk)
   4715     (%cg-emit-many cg (list "})\n"))))
   4716 
   4717 ;; Conditionals-as-values: `cg-ifelse` is correct for if-statements
   4718 ;; (thunks push nothing) but each thunk for ternary / `&&` / `||` ends
   4719 ;; with one rval on top of the vstack — and after both branches run,
   4720 ;; we'd be left with TWO opnds, which breaks the type contract for
   4721 ;; the surrounding expression. `cg-ifelse-merge` solves that: pop the
   4722 ;; cond, allocate one result slot, and after each thunk runs, pop its
   4723 ;; rval and store into the slot. Push the slot as one frame rval.
   4724 ;;
   4725 ;; Result type follows C11 §6.5.15 ¶5 for ternary: the usual arithmetic
   4726 ;; conversions over the two arms' types. The slot stores the raw target-word
   4727 ;; payload (per cc.scm's canonical-form discipline); %cg-load-opnd-into
   4728 ;; then re-canonicalizes on read against whatever common type we picked.
   4729 ;; For `&&` / `||` callers both arms are pre-cast to %t-i32 by the
   4730 ;; parser, so the merge is a no-op on type.
   4731 (define (cg-ifelse-merge cg then-thunk else-thunk)
   4732   (let* ((cond-op (cg-pop cg)))
   4733     (%cg-load-truth-into cg cond-op 't0)
   4734     (%cg-emit-many cg (list "%ifelse_nez(t0, {\n"))
   4735     (then-thunk)
   4736     (let* ((p     (cg-pop cg))
   4737            (rty1  (opnd-type p))
   4738            (rk1   (ctype-kind rty1))
   4739            ;; Struct/union arms can't ride the canonical target-word
   4740            ;; slot — the arm's bytes have to land in a slot sized to
   4741            ;; the struct, and each arm memcpys its lvalue in. tcc's
   4742            ;; expr_cond does this exact `type = bt1 == 6 ? type1 : type2`
   4743            ;; pattern across CType structs, so without this case
   4744            ;; cc.scm-compiled tcc-boot2 self-corrupts.
   4745            (aggr? (or (eq? rk1 'struct) (eq? rk1 'union)))
   4746            ;; On RV32 reserve both words for scalar merges. The second arm
   4747            ;; can widen the common type to i64/u64 after the first arm has
   4748            ;; already been emitted.
   4749            (pair-slot? (and (not aggr?) (= %CC-WORD-BYTES 4)))
   4750            (slot  (cond (aggr?
   4751                          (cg-alloc-slot cg
   4752                                         (align-up (ctype-size rty1)
   4753                                                   %CC-WORD-BYTES)
   4754                                         (max %CC-WORD-BYTES
   4755                                              (ctype-align rty1))))
   4756                         (pair-slot?
   4757                          (cg-alloc-slot cg
   4758                                         %CC-PAIR-BYTES %CC-WORD-BYTES))
   4759                         (else
   4760                          (cg-alloc-slot cg
   4761                                         %CC-WORD-BYTES %CC-WORD-BYTES)))))
   4762       (%cg-merge-write-arm cg p slot aggr? pair-slot?)
   4763       (%cg-emit-many cg (list "}, {\n"))
   4764       (else-thunk)
   4765       (let* ((q    (cg-pop cg))
   4766              (rty2 (opnd-type q)))
   4767         (%cg-merge-write-arm cg q slot aggr? pair-slot?)
   4768         (%cg-emit-many cg (list "})\n"))
   4769         ;; Aggregate result is pushed as a frame lval so cg-copy-struct
   4770         ;; (which asserts src must be lval) accepts it; %cg-emit-addr-of
   4771         ;; falls through the `lval? #t` guard (slot is direct, not
   4772         ;; indirect) and returns the slot's address either way.
   4773         (cg-push cg (%opnd 'frame
   4774                            (%cg-merge-arith-type rty1 rty2)
   4775                            slot
   4776                            aggr?))))))
   4777 
   4778 (define (%cg-merge-write-arm cg op slot aggr? pair-slot?)
   4779   (cond
   4780     (aggr?
   4781      (%cg-emit-addr-of cg op 't0)
   4782      (%cg-emit-lea-slot cg "t2" (%cg-slot-expr cg slot))
   4783      (%cg-emit-byte-copy cg 't2 't0 't1 (ctype-size (opnd-type op))))
   4784     ((and pair-slot? (%ctype-wide-int? (opnd-type op)))
   4785      (%cg-load-wide-opnd-into cg op 'a0 'a1)
   4786      (%cg-emit-st-slot cg 'a0 slot)
   4787      (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES)))
   4788     (pair-slot?
   4789      (%cg-load-opnd-into cg op 'a0)
   4790      (cond
   4791        ((%ctype-unsigned? (opnd-type op))
   4792         (%cg-emit-many cg (list "%li(a1, 0)\n")))
   4793        (else
   4794         (%cg-emit-many cg
   4795                        (list "%sari(a1, a0, "
   4796                              (%n (- %CC-WORD-BITS 1)) ")\n"))))
   4797      (%cg-emit-st-slot cg 'a0 slot)
   4798      (%cg-emit-st-slot cg 'a1 (+ slot %CC-WORD-BYTES)))
   4799     (else
   4800      (%cg-load-opnd-into cg op 'a0)
   4801      (%cg-emit-st-slot cg 'a0 slot))))
   4802 
   4803 ;; Conditional-expression common type (C11 §6.5.15): preserve a pointer
   4804 ;; operand regardless of arm order (the other valid mixed operand is a null
   4805 ;; pointer constant), otherwise apply the usual arithmetic conversions.
   4806 ;; Aggregate conditionals retain the first arm's type as before.
   4807 (define (%cg-merge-arith-type t1 t2)
   4808   (cond
   4809     ((%ctype-ptr? t1) t1)
   4810     ((%ctype-ptr? t2) t2)
   4811     ((and (%ctype-arith? t1) (%ctype-arith? t2))
   4812      (let ((p1 (cond ((< (ctype-size t1) 4) %t-i32) (else t1)))
   4813            (p2 (cond ((< (ctype-size t2) 4) %t-i32) (else t2))))
   4814        (cond
   4815          ((> (ctype-size p1) (ctype-size p2)) p1)
   4816          ((> (ctype-size p2) (ctype-size p1)) p2)
   4817          ((%ctype-unsigned? p1) p1)
   4818          ((%ctype-unsigned? p2) p2)
   4819          (else p1))))
   4820     (else t1)))
   4821 
   4822 (define (cg-loop cg head-thunk body-thunk)
   4823   ;; body-thunk receives the loop tag as its argument; parser uses
   4824   ;; that tag for cg-break / cg-continue inside the body.
   4825   (let ((tag (%cg-fresh-loop-tag cg)))
   4826     (%cg-emit-many cg (list ".scope\n"
   4827                             ":.top\n"))
   4828     (head-thunk)
   4829     (cond
   4830       ((zero? (cg-depth cg)) 0)
   4831       (else
   4832        (let ((c (cg-pop cg)))
   4833          (%cg-load-truth-into cg c 't0)
   4834          (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))))
   4835     (body-thunk tag)
   4836     (%cg-emit-many cg (list "%b(&.top)\n"
   4837                             ":.end\n"
   4838                             ".endscope\n"))
   4839     tag))
   4840 
   4841 (define (cg-break cg tag)
   4842   (%cg-emit-many cg (list "%break\n")))
   4843 
   4844 (define (cg-continue cg tag)
   4845   (%cg-emit-many cg (list "%continue\n")))
   4846 
   4847 ;; --------------------------------------------------------------------
   4848 ;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a fixed
   4849 ;; target-word save area at known frame offsets, populating each
   4850 ;; slot from the appropriate ABI source — a-register for indices 0..3,
   4851 ;; LDARG for later indices. va_start sets ap to the address of the
   4852 ;; first slot past the named-arg count; va_arg reads *ap, advances ap
   4853 ;; by one target word, and pushes the value as the requested type.
   4854 ;;
   4855 ;; ap is an lval (typically a `va_list` local). cg-va-start pops it,
   4856 ;; computes the address, stores into *ap (or the slot directly), and
   4857 ;; pushes nothing. cg-va-arg pops ap-lval, loads ap, dereferences for
   4858 ;; the value, advances ap, stores back, pushes the loaded value.
   4859 ;;
   4860 ;; Cap: total incoming ABI words (named + variadic) must fit in the
   4861 ;; %CG-VARARG-WINDOW-slot save area. Variadic call sites exceeding this die in cg-call;
   4862 ;; variadic definitions whose named-arg count exceeds it die in
   4863 ;; cg-fn-begin/v.
   4864 ;; --------------------------------------------------------------------
   4865 (define (%cg-vararg-first-slot cg)
   4866   (let ((s (%cg-fn-get cg '%fn-vararg-first-slot)))
   4867     (cond ((not s) (die #f "cg-va-start: not a variadic function"))
   4868           (else s))))
   4869 
   4870 (define (cg-va-start cg)
   4871   ;; Pop ap-lval. Materialize "&sp + vararg-first-slot" into a0,
   4872   ;; store through ap-lval. Pushes nothing.
   4873   (let* ((ap-lv (cg-pop cg))
   4874          (vsl   (%cg-vararg-first-slot cg)))
   4875     (cond ((not (opnd-lval? ap-lv))
   4876            (die #f "cg-va-start: ap not lvalue")))
   4877     (%cg-emit-lea-slot cg "a0" (%cg-slot-expr cg vsl))
   4878     (%cg-emit-addr-of cg ap-lv 't0)
   4879     (%cg-emit-st cg 'a0 't0 0)))
   4880 
   4881 (define (cg-va-arg cg ctype)
   4882   ;; Pop ap-lval. Load ap into a0. Read one word at [a0] into a1.
   4883   ;; Advance a0 by one word and store back through ap-lval. Push a1 as rval
   4884   ;; of type ctype (caller cg-cast's if needed).
   4885   (let ((ap-lv (cg-pop cg)))
   4886     (cond ((not (opnd-lval? ap-lv))
   4887            (die #f "cg-va-arg: ap not lvalue")))
   4888     ;; Address of the storage that holds ap → t0; ap value → a0.
   4889     (%cg-emit-addr-of cg ap-lv 't0)
   4890     (%cg-emit-ld cg 'a0 't0 0)
   4891     ;; Read one or two ABI words, advance ap by the consumed width, and
   4892     ;; store the updated cursor back through the va_list lvalue.
   4893     (%cg-emit-ld cg 'a1 'a0 0)
   4894     (cond
   4895       ((%ctype-wide-int? ctype)
   4896        (%cg-emit-ld cg 'a2 'a0 %CC-WORD-BYTES)
   4897        (%cg-emit-many cg
   4898                       (list "%addi(a0, a0, "
   4899                             (%n %CC-PAIR-BYTES) ")\n"))
   4900        (%cg-emit-st cg 'a0 't0 0)
   4901        (%cg-spill-pair cg 'a1 'a2 ctype))
   4902       (else
   4903        (%cg-emit-many cg
   4904                       (list "%addi(a0, a0, "
   4905                             (%n %CC-WORD-BYTES) ")\n"))
   4906        (%cg-emit-st cg 'a0 't0 0)
   4907        ;; Spill the loaded value (a1) to a fresh frame slot under ctype.
   4908        (%cg-spill-reg cg 'a1 ctype)))))
   4909 
   4910 (define (cg-va-end cg)
   4911   ;; va_end is a no-op in this design. Pop and discard ap-lval.
   4912   (cg-pop cg)
   4913   0)
   4914 
   4915 ;; --------------------------------------------------------------------
   4916 ;; Labels and unconditional goto.
   4917 ;; C labels have function scope, even when the labelled statement appears
   4918 ;; inside a nested block/loop. Emit them as function-qualified global
   4919 ;; labels rather than dotted hex2++ locals, because dotted definitions
   4920 ;; inside a nested `.scope` would be invisible to gotos outside it.
   4921 ;; --------------------------------------------------------------------
   4922 (define (%cg-user-label cg name-bv)
   4923   (let ((fn (%cg-fn-get cg '%fn-label)))
   4924     (bv-cat (list fn "__user_" name-bv))))
   4925 
   4926 (define (cg-emit-label cg name-bv)
   4927   (%cg-emit-many cg (list ":" (%cg-user-label cg name-bv) "\n")))
   4928 
   4929 (define (cg-goto cg name-bv)
   4930   (%cg-emit-many cg (list "%b(&" (%cg-user-label cg name-bv) ")\n")))
   4931 
   4932 ;; --------------------------------------------------------------------
   4933 ;; switch
   4934 ;; --------------------------------------------------------------------
   4935 (define-record-type swctx
   4936   (%swctx ctrl-slot ctrl-type end-tag default-lbl)
   4937   swctx?
   4938   (ctrl-slot   swctx-ctrl-slot)
   4939   (ctrl-type   swctx-ctrl-type)
   4940   (end-tag     swctx-end-tag)
   4941   (default-lbl swctx-default-lbl swctx-default-lbl-set!))
   4942 
   4943 (define (cg-switch-begin cg)
   4944   (let* ((p   (cg-pop cg))
   4945          (ty  (opnd-type p))
   4946          (wide? (%ctype-wide-int? ty))
   4947          (off (cg-alloc-slot cg
   4948                              (if wide? %CC-PAIR-BYTES %CC-WORD-BYTES)
   4949                              %CC-WORD-BYTES))
   4950          (tag (%cg-fresh-loop-tag cg))
   4951          (disp-lbl (bytevector-append "sw_disp_" tag)))
   4952     (cond
   4953       (wide?
   4954        (%cg-load-wide-opnd-into cg p 't0 't1)
   4955        (%cg-emit-st-slot cg 't0 off)
   4956        (%cg-emit-st-slot cg 't1 (+ off %CC-WORD-BYTES)))
   4957       (else
   4958        (%cg-load-opnd-into cg p 't0)
   4959        (%cg-emit-st-slot cg 't0 off)))
   4960     (%cg-emit-many cg (list ".scope\n"
   4961                             "%b(&." disp-lbl ")\n"))
   4962     (%swctx off ty tag #f)))
   4963 
   4964 (define (cg-switch-case cg sw const-int)
   4965   (let* ((lbl (%cg-fresh-lbl cg))
   4966          (key (string->symbol
   4967                (bytevector-append "%sw_cases__" (swctx-end-tag sw))))
   4968          (cur (or (%cg-fn-get cg key) '()))
   4969          (entry (cons const-int lbl)))
   4970     (%cg-fn-set! cg key (cons entry cur))
   4971     (%cg-emit-many cg (list ":." lbl "\n"))))
   4972 
   4973 (define (cg-switch-default cg sw)
   4974   (let ((lbl (%cg-fresh-lbl cg)))
   4975     (swctx-default-lbl-set! sw lbl)
   4976     (%cg-emit-many cg (list ":." lbl "\n"))))
   4977 
   4978 (define (cg-switch-end cg sw)
   4979   (let* ((tag (swctx-end-tag sw))
   4980          (key (string->symbol (bytevector-append "%sw_cases__" tag)))
   4981          (cases (reverse (or (%cg-fn-get cg key) '())))
   4982          (default-lbl (swctx-default-lbl sw))
   4983          (disp-lbl (bytevector-append "sw_disp_" tag)))
   4984     (%cg-emit-many cg (list "%break\n"
   4985                             ":." disp-lbl "\n"))
   4986     (cond
   4987       ((%ctype-wide-int? (swctx-ctrl-type sw))
   4988        (%cg-emit-ld-slot cg 't0 (swctx-ctrl-slot sw))
   4989        (%cg-emit-ld-slot cg 't1
   4990                          (+ (swctx-ctrl-slot sw) %CC-WORD-BYTES))
   4991        (for-each
   4992         (lambda (c)
   4993           (%cg-emit-li-wide cg 'a0 'a1 (car c))
   4994           (%cg-emit-many
   4995            cg
   4996            (list "%i64_cmpset_eq(t2, t0, t1, a0, a1, a2)\n"
   4997                  "%bnez(t2, &." (cdr c) ")\n")))
   4998         cases))
   4999       (else
   5000        (%cg-emit-many cg (list "%ld(t0, sp, "
   5001                                (%cg-slot-expr cg (swctx-ctrl-slot sw)) ")\n"))
   5002        (for-each
   5003         (lambda (c)
   5004           (%cg-emit-many cg (list "%switch_case(t0, t1, "
   5005                                   (%n (car c)) ", &." (cdr c) ")\n")))
   5006         cases)))
   5007     (cond
   5008       (default-lbl (%cg-emit-many cg (list "%b(&." default-lbl ")\n")))
   5009       (else 0))
   5010     (%cg-emit-many cg (list "%break\n"
   5011                             ":.end\n"
   5012                             ".endscope\n"))))
   5013 
   5014 ;; --------------------------------------------------------------------
   5015 ;; Globals and data
   5016 ;; --------------------------------------------------------------------
   5017 ;; cg-emit-global: emit a global symbol into either .data (initialized)
   5018 ;; or .bss (zero-init).
   5019 ;;
   5020 ;; init can be:
   5021 ;;   #f                       — zero-init in .bss (size from sym's ctype).
   5022 ;;   (piece ...)              — initialized in .data; pieces concatenated.
   5023 ;;
   5024 ;; Each piece is either:
   5025 ;;   <bytevector>             — raw bytes; emitted as bare hex chunks
   5026 ;;                              (64 bytes / 128 hex chars per line).
   5027 ;;   (label-ref . <label-bv>) — target-word pointer slot containing &label;
   5028 ;;                              (`&label` on RV32; `&label %(0)` on LP64).
   5029 (define (%cg-init-piece->bv piece)
   5030   (cond
   5031     ((bytes? piece)
   5032      (bv-cat (%cg-bv->hex-lines piece #f)))
   5033     ((and (pair? piece) (eq? (car piece) 'label-ref))
   5034      (bv-cat (list "&" (cdr piece)
   5035                    (if (= %CC-WORD-BYTES 4) "\n" " %(0)\n"))))
   5036     (else (die #f "cg-emit-global: bad init piece" piece))))
   5037 
   5038 (define (cg-emit-global cg sym init)
   5039   (let* ((lbl (%cg-sym-label cg sym))
   5040          (sz  (ctype-size (sym-type sym)))
   5041          (size (if (< sz 0) %CC-WORD-BYTES sz))
   5042          (al  (max 1 (ctype-align (sym-type sym)))))
   5043     (cond
   5044       (init
   5045        (buf-push! (cg-data cg) (bv-cat (list "\n.align " (%n al) "\n:"
   5046                                              lbl "\n")))
   5047        (let walk ((ps init))
   5048          (cond
   5049            ((null? ps) 0)
   5050            (else
   5051             (buf-push! (cg-data cg) (%cg-init-piece->bv (car ps)))
   5052             (walk (cdr ps))))))
   5053       (else
   5054        (buf-push! (cg-bss cg)
   5055                   (bv-cat (list "\n.align " (%n al) "\n:" lbl "\n"
   5056                                 (let zero-loop ((rem size) (acc '()))
   5057                                   (cond
   5058                                     ((<= rem 0) (bv-cat (reverse acc)))
   5059                                     ((>= rem 8)
   5060                                      (zero-loop (- rem 8) (cons "$(0)\n" acc)))
   5061                                     (else
   5062                                      (zero-loop (- rem 1) (cons "!(0)\n" acc))))))))))
   5063   0))
   5064 
   5065 (define (cg-emit-extern cg sym) 0)
   5066 
   5067 ;; Record `n` as a tentative file-scope definition: don't emit BSS yet,
   5068 ;; but if no full definition appears by end of TU, cg-finish will emit
   5069 ;; zero-init storage for it. The pair in world-tentatives holds the names in
   5070 ;; its car and a membership hash in its cdr, avoiding a quadratic `member`
   5071 ;; scan across translation units with many tentative declarations.
   5072 (define (cg-add-tentative! cg n)
   5073   (let* ((w (cg-world cg))
   5074          (pending (world-tentatives w))
   5075          (seen (cdr pending)))
   5076     (cond
   5077       ((%hash-ref seen n) #t)
   5078       (else
   5079        (%hash-set! seen n #t)
   5080        (set-car! pending (cons n (car pending)))))))
   5081 
   5082 ;; End-of-TU pass: for each pending tentative, look up the latest sym
   5083 ;; binding. If it's still `defined?=#f`, no real definition replaced it,
   5084 ;; so emit zero-init storage now. Otherwise the .data emission already
   5085 ;; covered it.
   5086 (define (cg-flush-tentatives! cg)
   5087   (let* ((w (cg-world cg))
   5088          (top (car (world-scope w))))
   5089     (for-each
   5090       (lambda (n)
   5091         (let ((sm (%hash-ref top n)))
   5092           (cond
   5093             ((and sm
   5094                   (eq? (sym-kind sm) 'var)
   5095                   (not (sym-defined? sm)))
   5096              (cg-emit-global cg sm #f)))))
   5097       (car (world-tentatives w)))))
   5098 
   5099 (define (cg-intern-string cg bv-content)
   5100   (let ((p (%hash-ref (cg-str-pool cg) bv-content)))
   5101     (cond
   5102       (p p)
   5103       (else
   5104        (let* ((n   (%hash-size (cg-str-pool cg)))
   5105               (lbl (bytevector-append
   5106                     (cg-str-prefix cg) "cc__str_" (%n n))))
   5107          (%hash-set! (cg-str-pool cg) bv-content lbl)
   5108          (buf-push! (cg-data cg)
   5109                     (bv-cat (append (list "\n.align " (%n %CG-STR-ALIGN)
   5110                                           "\n:" lbl "\n")
   5111                                     (%cg-bv->hex-lines bv-content #t)
   5112                                     (list ".align " (%n %CG-STR-ALIGN) "\n"))))
   5113          lbl)))))
   5114 
   5115 ;; Mint a fresh, never-recurring label for an unnamed file-scope
   5116 ;; compound literal. Mirrors cg-intern-string's namer pattern (prefix +
   5117 ;; "cc__cl_" + N), with N drawn from cg-label-ctr — the same monotonic
   5118 ;; counter the per-fn label minters use. Different prefix → no collision
   5119 ;; with `Lcc__N` / `lbl_N`.
   5120 (define (%cg-fresh-cl-label cg)
   5121   (let* ((n   (cg-label-ctr cg))
   5122          (lbl (bytevector-append (cg-str-prefix cg) "cc__cl_" (%n n))))
   5123     (cg-label-ctr-set! cg (+ n 1))
   5124     lbl))
   5125 
   5126 ;; Render BV's bytes as bare hex accepted directly by hex2++. Lines are
   5127 ;; chunked to ≤128 hex chars (= 64 bytes) to keep generated P1pp readable.
   5128 ;;
   5129 ;; If TRAILING-NUL? is #t, an extra 0x00 byte is appended to terminate
   5130 ;; a C string. Alignment is emitted explicitly by callers with .align
   5131 ;; so hex2++ owns padding instead of cc.scm manufacturing zero bytes.
   5132 ;; The other caller (%cg-init-piece->bv) emits arbitrary initializer
   5133 ;; bytes whose length is sized exactly to the C-visible field; padding a
   5134 ;; 4-byte int slot to 8 would shift every following struct field.
   5135 ;; Returns a list of bytevectors ready for bv-cat.
   5136 (define %CG-HEX-CHUNK-BYTES 64)
   5137 (define %CG-STR-ALIGN       8)
   5138 
   5139 (define (%cg-bv->hex-lines bv trailing-nul?)
   5140   (let* ((len     (bytevector-length bv))
   5141          (logical (cond (trailing-nul? (+ len 1)) (else len)))
   5142          (total   logical))
   5143     (cond
   5144       ((= total 0) '())
   5145       (else
   5146        (let loop ((i 0) (acc '()))
   5147          (cond
   5148            ((>= i total) (reverse acc))
   5149            (else
   5150             (let ((end (cond ((< (+ i %CG-HEX-CHUNK-BYTES) total)
   5151                               (+ i %CG-HEX-CHUNK-BYTES))
   5152                              (else total))))
   5153               (loop end (cons (%cg-hex-line bv i end len) acc))))))))))
   5154 
   5155 ;; One `XXXX...XX\n` line covering BV bytes [START, END). Indices
   5156 ;; >= LEN render as 0x00 (used for the trailing NUL terminator).
   5157 (define (%cg-hex-line bv start end len)
   5158   (let* ((nbytes (- end start))
   5159          (out    (make-bytevector (+ (* 2 nbytes) 1))))
   5160     (let loop ((j start) (k 0))
   5161       (cond
   5162         ((= j end)
   5163          (bytevector-u8-set! out k (char->integer #\newline))
   5164          out)
   5165         (else
   5166          (let ((b (cond ((< j len) (bytevector-u8-ref bv j))
   5167                         (else 0))))
   5168            (bytevector-u8-set! out k       (%cg-hex-digit
   5169                                             (arithmetic-shift b -4)))
   5170            (bytevector-u8-set! out (+ k 1) (%cg-hex-digit (bit-and b 15)))
   5171            (loop (+ j 1) (+ k 2))))))))
   5172 
   5173 (define (%cg-hex-digit n)
   5174   (cond ((< n 10) (+ n (char->integer #\0)))
   5175         (else    (+ (- n 10) (char->integer #\A)))))
   5176 
   5177 ;; --------------------------------------------------------------------
   5178 ;; Frame
   5179 ;; --------------------------------------------------------------------
   5180 (define (cg-alloc-slot cg bytes align)
   5181   (let* ((aligned (align-up (cg-frame-hi cg) align))
   5182          (new-hi  (+ aligned bytes)))
   5183     (cg-frame-hi-set! cg new-hi)
   5184     aligned))
   5185 ;; cc/parse.scm — recursive-descent + Pratt parser. Minimal scheme1.
   5186 
   5187 (define (make-pstate iter cg)
   5188   (%pstate iter (cg-world cg) '() #f cg))
   5189 
   5190 (define (peek ps)    (iter-peek  (ps-iter ps)))
   5191 (define (peek2 ps)   (iter-peek2 (ps-iter ps)))
   5192 (define (advance ps) (iter-next  (ps-iter ps)))
   5193 (define (at-kw? ps s)
   5194   (pmatch (peek ps)
   5195     (($ tok? (kind KW) (value ,v)) (eq? v s))
   5196     (else #f)))
   5197 (define (at-punct? ps s)
   5198   (pmatch (peek ps)
   5199     (($ tok? (kind PUNCT) (value ,v)) (eq? v s))
   5200     (else #f)))
   5201 (define (expect-kw ps s)
   5202   (let ((t (peek ps)))
   5203     (pmatch t
   5204       (($ tok? (kind KW) (value ,v)) (guard (eq? v s)) (advance ps))
   5205       (else (die (tok-loc t) "expected kw" s)))))
   5206 (define (expect-punct ps s)
   5207   (let ((t (peek ps)))
   5208     (pmatch t
   5209       (($ tok? (kind PUNCT) (value ,v)) (guard (eq? v s)) (advance ps))
   5210       (else (die (tok-loc t) "expected punct" s)))))
   5211 
   5212 (define (scope-enter! ps)
   5213   (ps-scope-set! ps (cons (%make-hash-table 16) (ps-scope ps)))
   5214   (ps-tags-set!  ps (cons (%make-hash-table 8) (ps-tags ps))))
   5215 (define (scope-leave! ps)
   5216   (ps-scope-set! ps (cdr (ps-scope ps)))
   5217   (ps-tags-set!  ps (cdr (ps-tags ps))))
   5218 (define (ctype-compat? a b)
   5219   (cond
   5220     ((eq? a b) #t)
   5221     ((not (eq? (ctype-kind a) (ctype-kind b))) #f)
   5222     (else
   5223      (let ((k (ctype-kind a)))
   5224        (cond
   5225          ((eq? k 'ptr) (ctype-compat? (ctype-ext a) (ctype-ext b)))
   5226          ((eq? k 'arr)
   5227           (let ((ea (ctype-ext a)) (eb (ctype-ext b)))
   5228             (and (ctype-compat? (car ea) (car eb))
   5229                  (or (= (cdr ea) (cdr eb))
   5230                      (< (cdr ea) 0) (< (cdr eb) 0)))))
   5231          ((eq? k 'fn) (%fn-ctype-compat? (ctype-ext a) (ctype-ext b)))
   5232          ((or (eq? k 'struct) (eq? k 'union) (eq? k 'enum)) #f)
   5233          (else #t))))))
   5234 
   5235 (define (%fn-ctype-compat? a b)
   5236   (and (ctype-compat? (car a) (car b))
   5237        (eq? (car (cddr a)) (car (cddr b)))
   5238        (%fn-params-compat? (cadr a) (cadr b))))
   5239 
   5240 (define (%fn-params-compat? pa pb)
   5241   (cond
   5242     ((and (null? pa) (null? pb)) #t)
   5243     ((or (null? pa) (null? pb)) #f)
   5244     ((ctype-compat? (cdar pa) (cdar pb))
   5245      (%fn-params-compat? (cdr pa) (cdr pb)))
   5246     (else #f)))
   5247 
   5248 (define (sym-merge old new)
   5249   (cond
   5250     ((not (eq? (sym-kind old) (sym-kind new)))
   5251      (die #f "redecl: kind mismatch" (sym-name old)))
   5252     ((not (ctype-compat? (sym-type old) (sym-type new)))
   5253      (die #f "redecl: type mismatch" (sym-name old)))
   5254     ((eq? (sym-kind old) 'typedef) old)
   5255     ((eq? (sym-kind old) 'enum-const)
   5256      (cond ((equal? (sym-slot old) (sym-slot new)) old)
   5257            (else (die #f "enum-const redecl" (sym-name old)))))
   5258     ((and (sym-defined? old) (sym-defined? new))
   5259      (die #f "redefinition" (sym-name old)))
   5260     ;; Linkage inherits from the first declaration (C 6.2.2 ¶4): if a
   5261     ;; later decl/def of the same identifier doesn't carry a storage
   5262     ;; class, it picks up the prior one. tcc.c relies on this with
   5263     ;; `static T f(); ... T f() {…}` — the prior `static` makes both
   5264     ;; the decl and the def internal-linkage. Without this carry-
   5265     ;; through cc.scm split them across two label namespaces.
   5266     ((sym-defined? new)
   5267      (cond
   5268        ((eq? (sym-storage old) 'static)
   5269         (%sym (sym-name new) (sym-kind new) 'static
   5270               (sym-type new) (sym-slot new) #t))
   5271        (else new)))
   5272     (else old)))
   5273 
   5274 (define (scope-bind! ps n s)
   5275   (let* ((top (car (ps-scope ps)))
   5276          (old (%hash-ref top n)))
   5277     (cond
   5278       ((not old)
   5279        (%hash-set! top n s))
   5280       (else
   5281        (let ((merged (sym-merge old s)))
   5282          (cond
   5283            ((eq? merged old) #t)
   5284            (else (%hash-set! top n merged))))))))
   5285 (define (scope-lookup ps n)
   5286   (let loop ((f (ps-scope ps)))
   5287     (cond ((null? f) #f)
   5288           (else
   5289            (let ((v (%hash-ref (car f) n)))
   5290              (if v v (loop (cdr f))))))))
   5291 (define (scope-lookup-current ps n)
   5292   (%hash-ref (car (ps-scope ps)) n))
   5293 (define (tag-bind! ps n c)
   5294   (%hash-set! (car (ps-tags ps)) n c))
   5295 (define (tag-lookup ps n)
   5296   (let loop ((f (ps-tags ps)))
   5297     (cond ((null? f) #f)
   5298           (else (let ((v (%hash-ref (car f) n)))
   5299                   (if v v (loop (cdr f))))))))
   5300 (define (typedef? ps n)
   5301   (let ((sm (scope-lookup ps n)))
   5302     (and sm (eq? (sym-kind sm) 'typedef))))
   5303 
   5304 (define (%mk-ptr p) (%ctype 'ptr %CC-WORD-BYTES %CC-WORD-BYTES p))
   5305 (define (%mk-arr e n)
   5306   (%ctype 'arr (if (< n 0) -1 (* n (ctype-size e)))
   5307           (ctype-align e) (cons e n)))
   5308 (define (%mk-fn r p v) (%ctype 'fn -1 -1 (list r p v)))
   5309 (define (ctype-is-ptr? t) (eq? (ctype-kind t) 'ptr))
   5310 (define (ctype-is-fn?  t) (eq? (ctype-kind t) 'fn))
   5311 (define (ctype-is-arr? t) (eq? (ctype-kind t) 'arr))
   5312 
   5313 (define (eat-cv-quals! ps)
   5314   (cond ((at-kw? ps '__attribute__)
   5315          (skip-gnu-attribute! ps) (eat-cv-quals! ps))
   5316         ((or (at-kw? ps 'const) (at-kw? ps 'volatile)
   5317              (at-kw? ps 'restrict))
   5318          (advance ps) (eat-cv-quals! ps))
   5319         (else #t)))
   5320 
   5321 ;; Consume a GNU `__attribute__ (( ... ))` spec and discard. The keyword
   5322 ;; has been peeked but not yet consumed. tcc.c's prototypes use these
   5323 ;; for noreturn / format / aligned annotations that the bootstrap doesn't
   5324 ;; need to honour semantically — same softening pattern as floats and
   5325 ;; rejected-but-accepted type specifiers.
   5326 (define (skip-gnu-attribute! ps)
   5327   (advance ps)
   5328   (expect-punct ps 'lparen)
   5329   (let loop ((depth 1))
   5330     (let ((t (peek ps)))
   5331       (cond
   5332         ((eq? (tok-kind t) 'EOF)
   5333          (die (tok-loc t) "EOF in __attribute__"))
   5334         ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'lparen))
   5335          (advance ps) (loop (+ depth 1)))
   5336         ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'rparen))
   5337          (advance ps)
   5338          (cond ((= depth 1) #t)
   5339                (else (loop (- depth 1)))))
   5340         (else (advance ps) (loop depth))))))
   5341 
   5342 (define (eat-gnu-attributes! ps)
   5343   (cond ((at-kw? ps '__attribute__)
   5344          (skip-gnu-attribute! ps) (eat-gnu-attributes! ps))
   5345         (else #t)))
   5346 
   5347 ;; Parse the universally layout-neutral C11 alignment requests: zero (the
   5348 ;; standard's no-effect spelling) and one/char alignment (which cannot weaken
   5349 ;; any object's natural alignment).  cc.scm does not carry a pending alignment
   5350 ;; specifier into its declarator layout, so accepting larger values could
   5351 ;; silently under-align an object; reject them instead.
   5352 (define (skip-c11-alignas! ps)
   5353   (expect-kw ps '_Alignas)
   5354   (expect-punct ps 'lparen)
   5355   (cond
   5356     ((%const-tok-is-decl? ps)
   5357      (let*-values (((_sto bty) (parse-decl-spec ps))
   5358                    ((_n ty) (parse-declarator ps bty)))
   5359        (let ((al (ctype-align ty)))
   5360          (cond ((<= al 0)
   5361                 (die (tok-loc (peek ps)) "_Alignas of incomplete type"))
   5362                ((> al 1)
   5363                 (die (tok-loc (peek ps))
   5364                      "_Alignas over-alignment unsupported" al))))))
   5365     (else
   5366      (let ((n (parse-const-int ps)))
   5367        (cond ((or (< n 0) (> n 1))
   5368               (die (tok-loc (peek ps))
   5369                    "_Alignas over-alignment unsupported" n))))))
   5370   (expect-punct ps 'rparen))
   5371 
   5372 (define (parse-decl-spec ps)
   5373   (let loop ((sto #f) (sn #f) (lg 0) (b #f) (saw #f))
   5374     (let ((t (peek ps)))
   5375       (cond
   5376         ((at-kw? ps '__attribute__)
   5377          (skip-gnu-attribute! ps) (loop sto sn lg b saw))
   5378         ((or (at-kw? ps 'auto) (at-kw? ps 'register))
   5379          (advance ps) (loop sto sn lg b #t))
   5380         ((at-kw? ps 'static)  (advance ps) (loop 'static sn lg b #t))
   5381         ((at-kw? ps 'extern)  (advance ps) (loop 'extern sn lg b #t))
   5382         ((at-kw? ps 'typedef) (advance ps) (loop 'typedef sn lg b #t))
   5383         ((or (at-kw? ps 'const) (at-kw? ps 'volatile)
   5384              (at-kw? ps 'restrict) (at-kw? ps 'inline)
   5385              (at-kw? ps '_Noreturn))
   5386          (advance ps) (loop sto sn lg b #t))
   5387         ((at-kw? ps '_Alignas)
   5388          (skip-c11-alignas! ps) (loop sto sn lg b #t))
   5389         ((at-kw? ps 'signed)   (advance ps) (loop sto 'signed lg b #t))
   5390         ((at-kw? ps 'unsigned) (advance ps) (loop sto 'unsigned lg b #t))
   5391         ((at-kw? ps 'short) (advance ps) (loop sto sn -1 b #t))
   5392         ((at-kw? ps 'long)  (advance ps) (loop sto sn (+ lg 1) b #t))
   5393         ((at-kw? ps 'void) (advance ps) (loop sto sn lg 'void #t))
   5394         ((at-kw? ps 'char) (advance ps) (loop sto sn lg 'char #t))
   5395         ((at-kw? ps 'int)  (advance ps) (loop sto sn lg 'int #t))
   5396         ((at-kw? ps '_Bool) (advance ps) (loop sto sn lg 'bool #t))
   5397         ;; Floats: parsed as type specifiers so prototypes and struct
   5398         ;; layouts in the flattened TU don't trip the parser. The cg
   5399         ;; rejects fp loads/arith/casts at use, see %cg-fp-reject!.
   5400         ;; _Complex / _Imaginary are eaten silently — tcc.c only mentions
   5401         ;; them inside HAVE_FLOAT-gated paths.
   5402         ((at-kw? ps 'float)  (advance ps) (loop sto sn lg 'float #t))
   5403         ((at-kw? ps 'double) (advance ps) (loop sto sn lg 'double #t))
   5404         ((or (at-kw? ps '_Complex) (at-kw? ps '_Imaginary))
   5405          (advance ps) (loop sto sn lg b #t))
   5406         ((or (at-kw? ps '_Atomic) (at-kw? ps '_Thread_local)
   5407              (at-kw? ps '_Generic))
   5408          (die (tok-loc t) "rejected" (tok-value t)))
   5409         ((at-kw? ps 'struct)
   5410          (loop sto sn lg (parse-aggregate-spec ps 'struct) #t))
   5411         ((at-kw? ps 'union)
   5412          (loop sto sn lg (parse-aggregate-spec ps 'union) #t))
   5413         ((at-kw? ps 'enum)
   5414          (loop sto sn lg (parse-enum-spec ps) #t))
   5415         ;; __builtin_va_list — gcc/clang builtin type. We don't model
   5416         ;; it as a struct; for our P1 ABI a va_list is just a char*
   5417         ;; into the stack save area (cg-va-start/arg/end work over an
   5418         ;; target-word slot). Letting __builtin_va_list mean `char *` here
   5419         ;; lets a single header source — `typedef __builtin_va_list
   5420         ;; va_list;` — compile cleanly under both cc.scm and stock
   5421         ;; gcc/clang (where it's their native struct).
   5422         ((and (not b) (eq? (tok-kind t) 'IDENT)
   5423               (bv= (tok-value t) "__builtin_va_list"))
   5424          (advance ps)
   5425          (loop sto sn lg (%mk-ptr %t-i8) #t))
   5426         ((and (not b) (eq? (tok-kind t) 'IDENT)
   5427               (let ((sm (scope-lookup ps (tok-value t))))
   5428                 (and sm (eq? (sym-kind sm) 'typedef))))
   5429          (let* ((tk (advance ps)) (sm (scope-lookup ps (tok-value tk))))
   5430            (loop sto sn lg (sym-type sm) #t)))
   5431         (else
   5432          (cond ((not saw) (die (tok-loc t) "expected decl-spec"
   5433                                (tok-value t)))
   5434                (else (values sto (resolve-base t sn lg b)))))))))
   5435 
   5436 (define (resolve-base loc sn lg b)
   5437   (cond
   5438     ((eq? b 'void)
   5439      (if (or sn (not (zero? lg))) (die loc "void+qual") %t-void))
   5440     ((eq? b 'bool)
   5441      (if (or sn (not (zero? lg))) (die loc "bool+qual") %t-bool))
   5442     ((eq? b 'char)
   5443      (cond ((eq? sn 'unsigned) %t-u8) (else %t-i8)))
   5444     ((or (eq? b 'int) (and (not b) (or sn (not (zero? lg)))))
   5445      (cond ((= lg -1) (if (eq? sn 'unsigned) %t-u16 %t-i16))
   5446            ((= lg 0)  (if (eq? sn 'unsigned) %t-u32 %t-i32))
   5447            ((= lg 1)  (if (eq? sn 'unsigned) %t-word-u %t-word-i))
   5448            (else      (if (eq? sn 'unsigned) %t-u64 %t-i64))))
   5449     ((eq? b 'float)
   5450      (if (or sn (not (zero? lg))) (die loc "float+qual") %t-flt))
   5451     ((eq? b 'double)
   5452      (cond (sn        (die loc "double+sign"))
   5453            ((= lg 0)  %t-dbl)
   5454            ((= lg 1)  %t-ldbl)
   5455            (else      (die loc "double+long*" lg))))
   5456     ((ctype? b)
   5457      (if (or sn (not (zero? lg))) (die loc "type+qual") b))
   5458     (else (die loc "unknown decl-spec"))))
   5459 
   5460 (define (parse-aggregate-spec ps kind)
   5461   (advance ps)
   5462   ;; GCC `__attribute__((...))` may sit between `struct/union` and
   5463   ;; the tag/`{`. Eat and discard.
   5464   (eat-gnu-attributes! ps)
   5465   (let ((tag (pmatch (peek ps)
   5466                (($ tok? (kind IDENT)) (tok-value (advance ps)))
   5467                (else #f))))
   5468     (eat-gnu-attributes! ps)
   5469     (cond
   5470       ((at-punct? ps 'lbrace)
   5471        (advance ps)
   5472        ;; A `struct/union TAG { ... }` declaration introduces (or
   5473        ;; completes) the tag in the *current* scope. Looking up in
   5474        ;; outer scopes via tag-lookup would let an inner-scope
   5475        ;; definition mutate an outer-scope same-tag ctype via
   5476        ;; complete-agg!. Restrict the reuse to the top frame, and
   5477        ;; only when the existing tag is still incomplete (size < 0);
   5478        ;; otherwise this is an attempted redefinition.
   5479        (let* ((ex (and tag (%hash-ref (car (ps-tags ps)) tag)))
   5480               (ct (cond ((and ex (eq? (ctype-kind ex) kind)
   5481                                (< (ctype-size ex) 0)) ex)
   5482                         ((and ex (eq? (ctype-kind ex) kind))
   5483                          (die (tok-loc (peek ps)) "agg redefinition" tag))
   5484                         (else (let ((c (%ctype kind -1 -1
   5485                                               (list (or tag #f) #f '()))))
   5486                                 (if tag (tag-bind! ps tag c)) c))))
   5487               (fields (parse-struct-fields ps kind)))
   5488          (expect-punct ps 'rbrace)
   5489          (complete-agg! ct kind tag fields) ct))
   5490       (tag (let ((ex (tag-lookup ps tag)))
   5491              (cond (ex ex)
   5492                    (else (let ((c (%ctype kind -1 -1
   5493                                          (list tag #f '()))))
   5494                            (tag-bind! ps tag c) c)))))
   5495       (else (die (tok-loc (peek ps)) "anon agg")))))
   5496 
   5497 (define (parse-struct-fields ps kind)
   5498   ;; For unions, every field stays at offset 0; complete-agg! takes the
   5499   ;; max of field sizes for the union's overall size.
   5500   (let ((struct? (eq? kind 'struct)))
   5501     (let loop ((acc '()) (off 0))
   5502       (cond
   5503         ((at-punct? ps 'rbrace) (reverse acc))
   5504         ((at-kw? ps '_Static_assert)
   5505          (parse-static-assert! ps) (loop acc off))
   5506         (else
   5507          (let-values (((_sto bty) (parse-decl-spec ps)))
   5508            (let dl ((acc2 acc) (o2 off))
   5509              (let*-values (((nm ty) (parse-declarator ps bty)))
   5510                (let* ((al (max (ctype-align ty) 1))
   5511                       (sz (ctype-size ty))
   5512                       (oa (if struct? (align-up o2 al) 0))
   5513                       (next (if struct? (+ oa (max sz 0)) 0)))
   5514                  (cond
   5515                    ((at-punct? ps 'comma)
   5516                     (advance ps)
   5517                     (dl (cons (list nm ty oa) acc2) next))
   5518                    ((at-punct? ps 'semi)
   5519                     (advance ps)
   5520                     (loop (cons (list nm ty oa) acc2) next))
   5521                    (else (die (tok-loc (peek ps)) "field"))))))))))))
   5522 
   5523 (define (complete-agg! ct k tag fs)
   5524   (let* ((ma (let m ((xs fs) (a 1))
   5525                (if (null? xs) a
   5526                    (m (cdr xs) (max a (ctype-align (cadr (car xs))))))))
   5527          (last (let l ((xs fs) (e 0))
   5528                  (if (null? xs) e
   5529                      (let* ((f (car xs)) (off (car (cddr f)))
   5530                             (sz (ctype-size (cadr f))))
   5531                        (l (cdr xs) (max e (+ off (max sz 0))))))))
   5532          (sz (cond ((eq? k 'union)
   5533                     (let u ((xs fs) (s 0))
   5534                       (if (null? xs) s
   5535                           (u (cdr xs)
   5536                              (max s (ctype-size (cadr (car xs))))))))
   5537                    (else (align-up last ma)))))
   5538     (ctype-size-set! ct sz)
   5539     (ctype-align-set! ct ma)
   5540     (ctype-ext-set! ct (list tag #t fs))))
   5541 
   5542 (define (parse-enum-spec ps)
   5543   (advance ps)
   5544   (let ((tag (pmatch (peek ps)
   5545                (($ tok? (kind IDENT)) (tok-value (advance ps)))
   5546                (else #f))))
   5547     (cond
   5548       ((at-punct? ps 'lbrace)
   5549        (advance ps)
   5550        ;; Parse all members first, then construct the enum ctype with
   5551        ;; the final members list and tag-bind it. Members reference
   5552        ;; earlier enum-consts via scope-lookup (not via the enum tag),
   5553        ;; so deferring tag-bind! is safe.
   5554        (let loop ((vs '()) (nv 0))
   5555          (cond
   5556            ((at-punct? ps 'rbrace)
   5557             (advance ps)
   5558             (let ((ct (%ctype 'enum 4 4 (list tag (reverse vs)))))
   5559               (if tag (tag-bind! ps tag ct))
   5560               ct))
   5561            (else
   5562             (let* ((nt (advance ps)) (nm (tok-value nt))
   5563                    (val (cond ((at-punct? ps 'assign)
   5564                                (advance ps) (parse-const-int ps))
   5565                               (else nv))))
   5566               (scope-bind! ps nm
   5567                            (%sym nm 'enum-const #f %t-i32 val #t))
   5568               (cond ((at-punct? ps 'comma) (advance ps))
   5569                     ((at-punct? ps 'rbrace) #t)
   5570                     (else (die (tok-loc (peek ps)) "enum")))
   5571               (loop (cons (cons nm val) vs) (%c-value-add val 1)))))))
   5572       (tag (let ((e (tag-lookup ps tag)))
   5573              (cond (e e)
   5574                    (else (let ((c (%ctype 'enum 4 4 (list tag '()))))
   5575                            (tag-bind! ps tag c) c)))))
   5576       (else (die (tok-loc (peek ps)) "enum")))))
   5577 
   5578 ;; ====================================================================
   5579 ;; Integer constant expressions (C99 §6.6).
   5580 ;;
   5581 ;; parse-const-expr ps -> (value . ctype)
   5582 ;;   A self-contained walker that never touches cg. The four call sites
   5583 ;;   that demand an integer constant expression — array bounds, enum
   5584 ;;   initializers, case labels, file-scope/static initializers — all go
   5585 ;;   through here. Returns a (value . ctype) pair so a final cast can
   5586 ;;   truncate at the target type's width (e.g. `(int)(unsigned char)257`
   5587 ;;   needs the inner cast to mask off to u8 before the outer relabel).
   5588 ;;
   5589 ;; Operand surface: integer / character literals, enum constants,
   5590 ;; sizeof(TYPENAME), unary + - ~ !, binary + - * / % << >> & | ^,
   5591 ;; compare < <= > >= == !=, logical && || (short-circuit at the value
   5592 ;; layer; both sides are still parsed so the token stream advances),
   5593 ;; ternary ?:, cast to integer type, parenthesization. Anything else
   5594 ;; dies. Floats / function calls / address-of / non-const idents / VLAs
   5595 ;; are out of scope.
   5596 ;; ====================================================================
   5597 
   5598 ;; Truncate VALUE to the width and signedness of CT. Integer types only
   5599 ;; — pointer/array/etc. operands abort upstream.
   5600 (define (%const-trunc value ct)
   5601   (let* ((sz (ctype-size ct))
   5602          (k  (ctype-kind ct)))
   5603     (cond
   5604       ;; bool: 0 or 1.
   5605       ((eq? k 'bool) (if (%c-value-zero? value) 0 1))
   5606       ((<= sz 0) 0)
   5607       (else (%c-value-trunc value sz (not (%ctype-unsigned? ct)))))))
   5608 
   5609 ;; Usual arithmetic conversions on (value . ctype) pairs. Both operands
   5610 ;; have already been integer-promoted (≤ int → int) by the caller.
   5611 ;; Returns three values: truncated a, truncated b, and the shared result
   5612 ;; ctype.
   5613 (define (%const-arith-conv ap bp)
   5614   (let* ((av (car ap)) (at (cdr ap))
   5615          (bv (car bp)) (bt (cdr bp))
   5616          (rt (%const-arith-conv-type at bt)))
   5617     (values (%const-trunc av rt) (%const-trunc bv rt) rt)))
   5618 
   5619 (define (%const-arith-conv-type at bt)
   5620   ;; Pick the wider type; tie-break on unsigned. Caller has already
   5621   ;; promoted both to >= int width.
   5622   (let ((sa (ctype-size at)) (sb (ctype-size bt)))
   5623     (cond
   5624       ((> sa sb) at)
   5625       ((> sb sa) bt)
   5626       ((%ctype-unsigned? at) at)
   5627       ((%ctype-unsigned? bt) bt)
   5628       (else at))))
   5629 
   5630 (define (%const-promote vp)
   5631   ;; Integer promotion (C11 §6.3.1.1): types narrower than int
   5632   ;; (i8/u8/i16/u16/bool) widen to (signed) int — every value of an
   5633   ;; unsigned sub-int type fits in int on this target, so the promotion
   5634   ;; rank picks signed int, not unsigned int. This matters for the
   5635   ;; usual arithmetic conversions in cross-signedness comparisons,
   5636   ;; e.g. ((unsigned char)-1 < (int)-1) must promote LHS to int 255
   5637   ;; (not u32 0xff) so the result is 0, not 1.
   5638   (let* ((v (car vp)) (ct (cdr vp))
   5639          (sz (ctype-size ct)))
   5640     (cond
   5641       ((< sz 4) (cons (%const-trunc v %t-i32) %t-i32))
   5642       (else vp))))
   5643 
   5644 (define (%const-bool? vp) (not (%c-value-zero? (car vp))))
   5645 
   5646 (define (parse-const-expr ps) (parse-const-cond ps))
   5647 
   5648 ;; Ternary (right-associative). Per C11 §6.6 ¶3 + §6.5.15/4 only the
   5649 ;; chosen branch is evaluated; the other need not be a valid constant
   5650 ;; expression (e.g. `1 ? 2 : 1/0` must yield 2, not abort). The dead
   5651 ;; arm is skipped via %const-skip-cond-{mid,rhs}, like the &&/||
   5652 ;; short-circuit paths above.
   5653 (define (parse-const-cond ps)
   5654   (let ((c (parse-const-lor ps)))
   5655     (cond
   5656       ((at-punct? ps 'qmark)
   5657        (advance ps)
   5658        (cond
   5659          ((%const-bool? c)
   5660           (let* ((t (parse-const-expr ps))
   5661                  (_ (expect-punct ps 'colon)))
   5662             (%const-skip-dead-arm ps)
   5663             t))
   5664          (else
   5665           (%const-skip-dead-arm ps)
   5666           (expect-punct ps 'colon)
   5667           (parse-const-cond ps))))
   5668       (else c))))
   5669 
   5670 ;; Generic top-level punct scanner used by skip-rhs / skip-cond helpers.
   5671 ;; Walks paren/bracket depth (a closing bracket at d=0 always stops) and
   5672 ;; optionally tracks ternary `?` depth. STOP? receives the punct value
   5673 ;; v at top-level (d=0, q=0 when q-aware?) and returns #t to stop. With
   5674 ;; Q-AWARE? = #t, a `?` at top-level opens a nested ternary and a
   5675 ;; matching `:` (q>0) closes it; the scanner stops on `:` only when q=0.
   5676 (define (%punct-scan ps stop? q-aware?)
   5677   (let lp ((d 0) (q 0))
   5678     (let ((t (peek ps)))
   5679       (cond
   5680         ((eq? (tok-kind t) 'EOF) #t)
   5681         ((not (eq? (tok-kind t) 'PUNCT))
   5682          (advance ps) (lp d q))
   5683         (else
   5684          (let ((v (tok-value t)))
   5685            (cond
   5686              ((or (eq? v 'lparen) (eq? v 'lbrack))
   5687               (advance ps) (lp (+ d 1) q))
   5688              ((or (eq? v 'rparen) (eq? v 'rbrack))
   5689               (cond ((zero? d) #t)
   5690                     (else (advance ps) (lp (- d 1) q))))
   5691              ((and q-aware? (zero? d) (eq? v 'qmark))
   5692               (advance ps) (lp d (+ q 1)))
   5693              ((and q-aware? (zero? d) (> q 0) (eq? v 'colon))
   5694               (advance ps) (lp d (- q 1)))
   5695              ((and (zero? d) (or (not q-aware?) (zero? q)) (stop? v))
   5696               #t)
   5697              (else (advance ps) (lp d q)))))))))
   5698 
   5699 ;; Skip the dead arm of a ternary. Same scanner whether we're skipping
   5700 ;; the middle (cond was false; will then expect-punct `:` and parse arm
   5701 ;; 3) or the third (cond was true; arm 2 already parsed and `:` already
   5702 ;; consumed). Both stop at top-level `:` / `,` / `;` / `}` with no
   5703 ;; open inner `?`; nested `?:` pairs are absorbed.
   5704 (define (%const-skip-dead-arm ps)
   5705   (%punct-scan ps
   5706     (lambda (v)
   5707       (or (eq? v 'colon) (eq? v 'comma) (eq? v 'semi) (eq? v 'rbrace)))
   5708     #t))
   5709 
   5710 ;; Generic left-associative binary level.
   5711 ;; ops: alist of punct-sym → (vp vp → vp).
   5712 (define (%const-binl ps next ops)
   5713   (let lp ((a (next ps)))
   5714     (let* ((t (peek ps))
   5715            (hit (and (eq? (tok-kind t) 'PUNCT)
   5716                      (alist-ref/eq (tok-value t) ops))))
   5717       (cond ((not hit) a)
   5718             (else (advance ps) (lp (hit a (next ps))))))))
   5719 
   5720 ;; Arithmetic combiner: promote both, arith-conv, apply op, truncate.
   5721 (define (%const-arith-op op a b)
   5722   (let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
   5723     (cons (%const-trunc
   5724             (cond ((eq? op 'add) (%c-value-add av bv))
   5725                   ((eq? op 'sub) (%c-value-sub av bv))
   5726                   ((eq? op 'mul) (%c-value-mul av bv))
   5727                   ((eq? op 'and) (%c-value-and av bv))
   5728                   ((eq? op 'or)  (%c-value-or av bv))
   5729                   ((eq? op 'xor) (%c-value-xor av bv))
   5730                   (else (die #f "const-expr: bad arithmetic op" op)))
   5731             rt)
   5732           rt)))
   5733 
   5734 ;; Like %const-arith-op but rejects a zero divisor.
   5735 (define (%const-div-op want-rem? a b)
   5736   (let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
   5737     (cond ((%c-value-zero? bv) (die #f "const-expr: divide by zero")))
   5738     (let ((qr (%c-value-divmod av bv (ctype-size rt) (%ctype-unsigned? rt))))
   5739       (cons (%const-trunc (if want-rem? (cdr qr) (car qr)) rt) rt))))
   5740 
   5741 ;; Comparison combiner: result is always (0-or-1 . %t-i32).
   5742 (define (%const-cmp-op op a b)
   5743   (let-values (((av bv rt) (%const-arith-conv (%const-promote a) (%const-promote b))))
   5744     (let ((c (%c-value-cmp av bv (ctype-size rt) (%ctype-unsigned? rt))))
   5745       (cons
   5746         (if (cond ((eq? op 'eq) (= c 0))
   5747                   ((eq? op 'ne) (not (= c 0)))
   5748                   ((eq? op 'lt) (< c 0))
   5749                   ((eq? op 'le) (<= c 0))
   5750                   ((eq? op 'gt) (> c 0))
   5751                   ((eq? op 'ge) (>= c 0))
   5752                   (else (die #f "const-expr: bad comparison op" op)))
   5753             1 0)
   5754         %t-i32))))
   5755 
   5756 ;; Short-circuit per C11 §6.5.13/14 ¶4: rhs is not evaluated when the
   5757 ;; lhs determines the result. Required so `1 || (1/0)` and
   5758 ;; `0 && (1/0)` yield 1/0 rather than aborting on divide-by-zero.
   5759 (define (parse-const-lor ps)
   5760   (let lp ((a (parse-const-land ps)))
   5761     (cond
   5762       ((at-punct? ps 'lor)
   5763        (advance ps)
   5764        (cond
   5765          ((%const-bool? a)
   5766           (%const-skip-lor-rhs ps)
   5767           (lp (cons 1 %t-i32)))
   5768          (else
   5769           (let ((b (parse-const-land ps)))
   5770             (lp (cons (if (%const-bool? b) 1 0) %t-i32))))))
   5771       (else a))))
   5772 
   5773 (define (parse-const-land ps)
   5774   (let lp ((a (parse-const-bor ps)))
   5775     (cond
   5776       ((at-punct? ps 'land)
   5777        (advance ps)
   5778        (cond
   5779          ((not (%const-bool? a))
   5780           (%const-skip-land-rhs ps)
   5781           (lp (cons 0 %t-i32)))
   5782          (else
   5783           (let ((b (parse-const-bor ps)))
   5784             (lp (cons (if (%const-bool? b) 1 0) %t-i32))))))
   5785       (else a))))
   5786 
   5787 ;; Skip the rhs of a short-circuited && / ||. The rhs grammar is
   5788 ;; the operand level of the operator: parse-const-bor for &&,
   5789 ;; parse-const-land for ||. We can't just call those parsers because
   5790 ;; the rhs may itself be invalid as a constant expression (e.g.
   5791 ;; `1/0`); instead, scan tokens at paren/brack depth 0 until we hit
   5792 ;; another operator at the same-or-lower binding level, comma,
   5793 ;; semicolon, colon, qmark, rbrace, rbrack, rparen, or EOF.
   5794 (define (%const-skip-land-rhs ps)
   5795   ;; rhs of && is a parse-const-bor — stop on `&&`, `||`, `?`, `:`,
   5796   ;; `,`, `;`, `}`, and any closing/separator at depth 0.
   5797   (%punct-scan ps
   5798     (lambda (v)
   5799       (or (eq? v 'land) (eq? v 'lor) (eq? v 'qmark) (eq? v 'colon)
   5800           (eq? v 'comma) (eq? v 'semi) (eq? v 'rbrace)))
   5801     #f))
   5802 (define (%const-skip-lor-rhs ps)
   5803   ;; rhs of || is a parse-const-land — stop on `||` (left-assoc),
   5804   ;; `?`, `:`, `,`, `;`, `}`. `&&` binds TIGHTER than `||`, so it is
   5805   ;; absorbed into the rhs and we do NOT stop on it.
   5806   (%punct-scan ps
   5807     (lambda (v)
   5808       (or (eq? v 'lor) (eq? v 'qmark) (eq? v 'colon)
   5809           (eq? v 'comma) (eq? v 'semi) (eq? v 'rbrace)))
   5810     #f))
   5811 
   5812 (define (parse-const-bor ps)
   5813   (%const-binl ps parse-const-bxor (list (cons 'bar   (lambda (a b) (%const-arith-op 'or  a b))))))
   5814 (define (parse-const-bxor ps)
   5815   (%const-binl ps parse-const-band (list (cons 'caret (lambda (a b) (%const-arith-op 'xor a b))))))
   5816 (define (parse-const-band ps)
   5817   (%const-binl ps parse-const-eq   (list (cons 'amp   (lambda (a b) (%const-arith-op 'and a b))))))
   5818 
   5819 (define (parse-const-eq ps)
   5820   (%const-binl ps parse-const-rel
   5821     (list (cons 'eq2 (lambda (a b) (%const-cmp-op 'eq a b)))
   5822           (cons 'ne  (lambda (a b) (%const-cmp-op 'ne a b))))))
   5823 
   5824 (define (parse-const-rel ps)
   5825   (%const-binl ps parse-const-shift
   5826     (list (cons 'lt (lambda (a b) (%const-cmp-op 'lt a b)))
   5827           (cons 'le (lambda (a b) (%const-cmp-op 'le a b)))
   5828           (cons 'gt (lambda (a b) (%const-cmp-op 'gt a b)))
   5829           (cons 'ge (lambda (a b) (%const-cmp-op 'ge a b))))))
   5830 
   5831 ;; Shift combiner: result type is the (promoted) lhs type — rhs is
   5832 ;; just a count, promoted independently. SIGN selects shl (+1) or shr (-1).
   5833 (define (%const-shift-op sign a b)
   5834   (let* ((ap (%const-promote a))
   5835          (bp (%const-promote b))
   5836          (rt (cdr ap))
   5837          (count (%c-value->fixnum (car bp) "const-expr shift")))
   5838     (cons (%const-trunc
   5839             (%c-value-shift (car ap) (* (- 0 sign) count)
   5840                             (and (= sign -1) (not (%ctype-unsigned? rt))))
   5841             rt)
   5842           rt)))
   5843 
   5844 (define (parse-const-shift ps)
   5845   (%const-binl ps parse-const-add
   5846     (list (cons 'shl (lambda (a b) (%const-shift-op  1 a b)))
   5847           (cons 'shr (lambda (a b) (%const-shift-op -1 a b))))))
   5848 
   5849 (define (parse-const-add ps)
   5850   (%const-binl ps parse-const-mul
   5851     (list (cons 'plus  (lambda (a b) (%const-arith-op 'add a b)))
   5852           (cons 'minus (lambda (a b) (%const-arith-op 'sub a b))))))
   5853 
   5854 (define (parse-const-mul ps)
   5855   (%const-binl ps parse-const-cast
   5856     (list (cons 'star  (lambda (a b) (%const-arith-op 'mul a b)))
   5857           (cons 'slash (lambda (a b) (%const-div-op #f a b)))
   5858           (cons 'pct   (lambda (a b) (%const-div-op #t a b))))))
   5859 
   5860 (define (parse-const-cast ps)
   5861   ;; (typename) operand — distinguished from ( expr ) by paren-is-group?.
   5862   ;; Pointer casts are accepted only as a type re-tag — the integer
   5863   ;; offset rides through unchanged. This is what the offsetof idiom
   5864   ;; `(T *)0` and the outer `(size_t) <ptr-const>` need; we do not
   5865   ;; admit general pointer arithmetic in const-expr.
   5866   (cond
   5867     ((at-punct? ps 'lparen)
   5868      (cond
   5869        ((%const-paren-is-cast? ps)
   5870         (advance ps)
   5871         (let*-values (((_sto bty) (parse-decl-spec ps))
   5872                       ((_n   ty)  (parse-declarator ps bty)))
   5873           (expect-punct ps 'rparen)
   5874           (cond
   5875             ((%ctype-int? ty)
   5876              (let ((v (parse-const-cast ps)))
   5877                (cons (%const-trunc (car v) ty) ty)))
   5878             ((eq? (ctype-kind ty) 'ptr)
   5879              (let ((v (parse-const-cast ps)))
   5880                (cons (car v) ty)))
   5881             (else
   5882              (die (tok-loc (peek ps))
   5883                   "const-expr: cast must be integer or pointer"
   5884                   (ctype-kind ty))))))
   5885        (else (parse-const-unary ps))))
   5886     (else (parse-const-unary ps))))
   5887 
   5888 (define (%const-paren-is-cast? ps)
   5889   ;; A '(' starts a cast iff the following token kicks off a type-name.
   5890   (%tok-decl-start? ps (peek2 ps)))
   5891 
   5892 (define (%ctype-int? ty)
   5893   (let ((k (ctype-kind ty)))
   5894     (or (eq? k 'i8) (eq? k 'u8) (eq? k 'i16) (eq? k 'u16)
   5895         (eq? k 'i32) (eq? k 'u32) (eq? k 'i64) (eq? k 'u64)
   5896         (eq? k 'bool) (eq? k 'enum))))
   5897 
   5898 (define (parse-const-unary ps)
   5899   (let ((t (peek ps)))
   5900     (pmatch t
   5901       (($ tok? (kind PUNCT) (value plus))
   5902        (advance ps) (%const-promote (parse-const-cast ps)))
   5903       (($ tok? (kind PUNCT) (value minus))
   5904        (advance ps)
   5905        (let* ((vp (%const-promote (parse-const-cast ps)))
   5906               (rt (cdr vp)))
   5907          (cons (%const-trunc (%c-value-negate (car vp)) rt) rt)))
   5908       (($ tok? (kind PUNCT) (value tilde))
   5909        (advance ps)
   5910        (let* ((vp (%const-promote (parse-const-cast ps)))
   5911               (rt (cdr vp)))
   5912          (cons (%const-trunc (%c-value-not (car vp)) rt) rt)))
   5913       (($ tok? (kind PUNCT) (value bang))
   5914        (advance ps)
   5915        (let ((vp (parse-const-cast ps)))
   5916          (cons (if (%const-bool? vp) 0 1) %t-i32)))
   5917       (($ tok? (kind PUNCT) (value amp))
   5918        ;; Address-of in const-expr context. Restricted to the offsetof
   5919        ;; idiom: a null-pointer-typed base reached via (T *)0 (with
   5920        ;; optional grouping/deref) followed by ->/. field selectors.
   5921        ;; The integer value is the running byte offset; '&' wraps the
   5922        ;; designator's type in a pointer for any outer integer cast to
   5923        ;; consume.
   5924        (advance ps)
   5925        (let* ((dp (%const-parse-addrof-postfix ps)))
   5926          (cons (car dp) (%mk-ptr (cdr dp)))))
   5927       (($ tok? (kind KW) (value sizeof))
   5928        (advance ps)
   5929        (cond
   5930          ((at-punct? ps 'lparen)
   5931           (advance ps)
   5932           (cond
   5933             ((%const-tok-is-decl? ps)
   5934              (let*-values (((_sto bty) (parse-decl-spec ps))
   5935                            ((_n   ty)  (parse-declarator ps bty)))
   5936                (expect-punct ps 'rparen)
   5937                (cons (max (ctype-size ty) 0) %t-word-u)))
   5938             (else
   5939              ;; sizeof(EXPR) in const-expr context. Operand is not
   5940              ;; evaluated (C11 §6.5.3.4) — snapshot the cg, parse the
   5941              ;; expr through the regular parser to recover its ctype,
   5942              ;; then rewind to discard any emission/vstack pushes.
   5943              (cons (%const-sizeof-expr ps #t) %t-word-u))))
   5944          (else
   5945           ;; `sizeof EXPR` (no parens). Same no-eval rule.
   5946           (cons (%const-sizeof-expr ps #f) %t-word-u))))
   5947       (($ tok? (kind KW) (value _Alignof))
   5948        (advance ps)
   5949        (expect-punct ps 'lparen)
   5950        (cond
   5951          ((%const-tok-is-decl? ps)
   5952           (let*-values (((_sto bty) (parse-decl-spec ps))
   5953                         ((_n ty) (parse-declarator ps bty)))
   5954             (expect-punct ps 'rparen)
   5955             (cons (max (ctype-align ty) 1) %t-word-u)))
   5956          (else
   5957           (cons (%const-alignof-expr ps) %t-word-u))))
   5958       (else (parse-const-primary ps)))))
   5959 
   5960 ;; Does TOK begin a type-name? Type specifiers, qualifiers,
   5961 ;; struct/union/enum tags, and typedef-name idents. Storage classes
   5962 ;; (auto/register/static/extern/typedef) are NOT included — those
   5963 ;; appear only at declaration position; callers that need them
   5964 ;; (e.g. stmt-starts-decl?) check separately.
   5965 (define (%tok-decl-start? ps t)
   5966   (pmatch t
   5967     (($ tok? (kind KW) (value ,v))
   5968      (or (eq? v 'void) (eq? v 'char) (eq? v 'short) (eq? v 'int)
   5969          (eq? v 'long) (eq? v 'signed) (eq? v 'unsigned)
   5970          (eq? v '_Bool) (eq? v 'float) (eq? v 'double)
   5971          (eq? v '_Complex) (eq? v '_Imaginary)
   5972          (eq? v 'struct) (eq? v 'union) (eq? v 'enum)
   5973          (eq? v 'const) (eq? v 'volatile) (eq? v 'restrict)
   5974          (eq? v 'inline) (eq? v '_Noreturn)))
   5975     (($ tok? (kind IDENT) (value ,n)) (typedef? ps n))
   5976     (else #f)))
   5977 
   5978 (define (%const-tok-is-decl? ps) (%tok-decl-start? ps (peek ps)))
   5979 
   5980 (define (%const-offsetof-field ps ty offset)
   5981   (let ((nt (advance ps)))
   5982     (cond ((not (eq? (tok-kind nt) 'IDENT))
   5983            (die (tok-loc nt) "__builtin_offsetof expects a member")))
   5984     (let ((f (%cg-find-field (%init-struct-fields ty) (tok-value nt))))
   5985       (cond ((not f)
   5986              (die (tok-loc nt) "__builtin_offsetof: no such member"
   5987                   (tok-value nt))))
   5988       (cons (+ offset (car (cddr f))) (car (cdr f))))))
   5989 
   5990 (define (%const-builtin-offsetof ps)
   5991   ;; `__builtin_offsetof(T, member[const].nested)` is an integer constant.
   5992   ;; Reuse the aggregate field metadata and array element sizes already used by
   5993   ;; normal designators; no pointer expression or code generation is needed.
   5994   (expect-punct ps 'lparen)
   5995   (let*-values (((_sto bty) (parse-decl-spec ps))
   5996                 ((_n ty) (parse-declarator ps bty)))
   5997     (expect-punct ps 'comma)
   5998     (let lp ((p (%const-offsetof-field ps ty 0)))
   5999       (pmatch (peek ps)
   6000         (($ tok? (kind PUNCT) (value dot))
   6001          (advance ps) (lp (%const-offsetof-field ps (cdr p) (car p))))
   6002         (($ tok? (kind PUNCT) (value lbrack))
   6003          (advance ps)
   6004          (let* ((index (parse-const-int ps))
   6005                 (_ (expect-punct ps 'rbrack))
   6006                 (aty (cdr p)))
   6007            (cond ((not (eq? (ctype-kind aty) 'arr))
   6008                   (die (tok-loc (peek ps))
   6009                        "__builtin_offsetof: subscript on non-array")))
   6010            (let ((elem (car (ctype-ext aty))))
   6011              (lp (cons (+ (car p) (* index (ctype-size elem))) elem)))))
   6012         (else
   6013          (expect-punct ps 'rparen)
   6014          (cons (car p) %t-word-u))))))
   6015 
   6016 (define (parse-const-primary ps)
   6017   (let ((t (peek ps)))
   6018     (pmatch t
   6019       (($ tok? (kind INT) (value ,v))
   6020        (advance ps)
   6021        (cons (%c-int-raw v) (%c-int-type v)))
   6022       (($ tok? (kind CHAR) (value ,v))
   6023        (advance ps)
   6024        ;; Character constants have type int in C.
   6025        (cons v %t-i32))
   6026       (($ tok? (kind PUNCT) (value lparen))
   6027        (advance ps)
   6028        (let ((v (parse-const-expr ps)))
   6029          (expect-punct ps 'rparen) v))
   6030       (($ tok? (kind IDENT) (value ,n))
   6031        (cond
   6032          ((bv= n "__builtin_offsetof")
   6033           (advance ps) (%const-builtin-offsetof ps))
   6034          (else
   6035           (let ((sm (scope-lookup ps n)))
   6036             (cond ((and sm (eq? (sym-kind sm) 'enum-const))
   6037                    (advance ps) (cons (sym-slot sm) %t-i32))
   6038                   (else
   6039                    (die (tok-loc t) "const-expr: not a constant" n)))))))
   6040       (else (die (tok-loc t) "const-expr: bad operand"
   6041                  (tok-value t))))))
   6042 
   6043 ;; ====================================================================
   6044 ;; offsetof support inside const-expr.
   6045 ;;
   6046 ;; Recognises `&((T *)0)->FIELD`, `&(*(T *)0).FIELD`, and chains thereof
   6047 ;; — the only address-of idioms that show up in static initializers
   6048 ;; (tcc.c options_W[] / options_f[] / options_m[] tables, and any
   6049 ;; offsetof macro expansion of the same shape). Each helper threads a
   6050 ;; (offset . ctype) pair: integer byte offset of the running designator
   6051 ;; from the null base, plus the lvalue's ctype. Field lookup reuses
   6052 ;; %cg-find-field, so anonymous union/struct members work the same way
   6053 ;; as in regular field access.
   6054 ;; ====================================================================
   6055 
   6056 (define (%const-parse-addrof-postfix ps)
   6057   ;; postfix: primary ( -> FIELD | . FIELD )*
   6058   (let lp ((p (%const-parse-addrof-primary ps)))
   6059     (pmatch (peek ps)
   6060       (($ tok? (kind PUNCT) (value arrow))
   6061        (advance ps) (lp (%const-addrof-arrow ps p)))
   6062       (($ tok? (kind PUNCT) (value dot))
   6063        (advance ps) (lp (%const-addrof-dot ps p)))
   6064       (else p))))
   6065 
   6066 (define (%const-parse-addrof-primary ps)
   6067   ;; primary: ( T )expr   ; pointer cast — the offsetof base
   6068   ;;        | ( postfix ) ; grouping
   6069   ;;        | * primary   ; deref
   6070   (cond
   6071     ((at-punct? ps 'lparen)
   6072      (cond
   6073        ((%const-paren-is-cast? ps)
   6074         (let ((cv (parse-const-cast ps)))
   6075           (cond
   6076             ((not (eq? (ctype-kind (cdr cv)) 'ptr))
   6077              (die #f "const-expr: addr-of: head must be a pointer cast"
   6078                   (ctype-kind (cdr cv)))))
   6079           cv))
   6080        (else
   6081         (advance ps)
   6082         (let ((r (%const-parse-addrof-postfix ps)))
   6083           (expect-punct ps 'rparen) r))))
   6084     ((at-punct? ps 'star)
   6085      (advance ps)
   6086      (let ((h (%const-parse-addrof-primary ps)))
   6087        (cond
   6088          ((not (eq? (ctype-kind (cdr h)) 'ptr))
   6089           (die #f "const-expr: addr-of: '*' on non-pointer"
   6090                (ctype-kind (cdr h)))))
   6091        (cons (car h) (ctype-ext (cdr h)))))
   6092     (else
   6093      (die (tok-loc (peek ps)) "const-expr: addr-of: unexpected token"
   6094           (tok-value (peek ps))))))
   6095 
   6096 (define (%const-addrof-arrow ps p)
   6097   (let* ((off (car p)) (ty (cdr p)))
   6098     (cond ((not (eq? (ctype-kind ty) 'ptr))
   6099            (die (tok-loc (peek ps)) "const-expr: -> on non-pointer"
   6100                 (ctype-kind ty))))
   6101     (let* ((sty (ctype-ext ty)) (sk (ctype-kind sty)))
   6102       (cond ((not (or (eq? sk 'struct) (eq? sk 'union)))
   6103              (die (tok-loc (peek ps))
   6104                   "const-expr: -> target not aggregate" sk)))
   6105       (%const-addrof-field ps sty off))))
   6106 
   6107 (define (%const-addrof-dot ps p)
   6108   (let* ((off (car p)) (ty (cdr p)) (k (ctype-kind ty)))
   6109     (cond ((not (or (eq? k 'struct) (eq? k 'union)))
   6110            (die (tok-loc (peek ps))
   6111                 "const-expr: . on non-aggregate" k)))
   6112     (%const-addrof-field ps ty off)))
   6113 
   6114 (define (%const-addrof-field ps sty base-off)
   6115   (let ((nt (peek ps)))
   6116     (cond ((not (eq? (tok-kind nt) 'IDENT))
   6117            (die (tok-loc nt)
   6118                 "const-expr: field selector needs an identifier"
   6119                 (tok-value nt))))
   6120     (advance ps)
   6121     (let* ((fields (car (cddr (ctype-ext sty))))
   6122            (f (%cg-find-field fields (tok-value nt))))
   6123       (cond ((not f) (die (tok-loc nt)
   6124                           "const-expr: no such field"
   6125                           (tok-value nt))))
   6126       (cons (+ base-off (car (cddr f))) (cadr f)))))
   6127 
   6128 ;; sizeof EXPR / sizeof(EXPR) in const-expr context. Delegates to the
   6129 ;; regular expression parser under a cg snapshot/rewind — same contract
   6130 ;; as parse-unary's sizeof: the operand is parsed to learn its type but
   6131 ;; not evaluated, so any emission or vstack push from the parse is
   6132 ;; discarded. Returns the operand's byte size as a non-negative int.
   6133 ;; If `paren?`, consumes the closing `)` after parsing.
   6134 (define (%const-unevaluated-primary-type ps)
   6135   ;; File-scope sizeof/_Alignof has no active function buffer to snapshot.
   6136   ;; Cover the ordinary unevaluated-designator grammar directly so static
   6137   ;; initializers such as `sizeof table / sizeof table[0]` remain constant
   6138   ;; expressions without inventing a synthetic function.
   6139   (let ((t (peek ps)))
   6140     (pmatch t
   6141       (($ tok? (kind IDENT) (value ,n))
   6142        (let ((sm (scope-lookup ps n)))
   6143          (cond ((not sm) (die (tok-loc t) "unevaluated: undecl" n)))
   6144          (advance ps)
   6145          (sym-type sm)))
   6146       (($ tok? (kind INT) (value ,v))
   6147        (advance ps) (%c-int-type v))
   6148       (($ tok? (kind CHAR)) (advance ps) %t-i32)
   6149       (($ tok? (kind STR) (value ,v))
   6150        (advance ps) (%mk-arr %t-i8 (+ (bytevector-length v) 1)))
   6151       (($ tok? (kind PUNCT) (value lparen))
   6152        (cond
   6153          ((%const-paren-is-cast? ps)
   6154           ;; In an unevaluated operand the cast expression still has to be
   6155           ;; consumed, but only its result type matters.  This covers the
   6156           ;; standard `sizeof(((T *)0)->field)` spelling without asking for an
   6157           ;; active function/codegen buffer at file scope.
   6158           (advance ps)
   6159           (let*-values (((_sto bty) (parse-decl-spec ps))
   6160                         ((_n ty) (parse-declarator ps bty)))
   6161             (expect-punct ps 'rparen)
   6162             (%const-unevaluated-unary-type ps)
   6163             ty))
   6164          (else
   6165           (advance ps)
   6166           (let ((ty (%const-unevaluated-unary-type ps)))
   6167             (expect-punct ps 'rparen)
   6168             ty))))
   6169       (else (die (tok-loc t) "unevaluated: unsupported operand"
   6170                  (tok-value t))))))
   6171 
   6172 (define (%const-unevaluated-postfix-type ps)
   6173   (let lp ((ty (%const-unevaluated-primary-type ps)))
   6174     (pmatch (peek ps)
   6175       (($ tok? (kind PUNCT) (value lbrack))
   6176        (advance ps)
   6177        ;; The index is itself unevaluated by sizeof, but parsing it as a
   6178        ;; constant keeps token consumption deterministic for the bootstrap
   6179        ;; subset (array tables conventionally use [0]).
   6180        (parse-const-int ps)
   6181        (expect-punct ps 'rbrack)
   6182        (let ((k (ctype-kind ty)))
   6183          (cond ((eq? k 'arr) (lp (car (ctype-ext ty))))
   6184                ((eq? k 'ptr) (lp (ctype-ext ty)))
   6185                (else (die #f "unevaluated: subscript on non-array" k)))))
   6186       (($ tok? (kind PUNCT) (value dot))
   6187        (advance ps)
   6188        (let ((nt (peek ps)))
   6189          (cond ((not (eq? (tok-kind nt) 'IDENT))
   6190                 (die (tok-loc nt) "unevaluated: field")))
   6191          (advance ps)
   6192          (let ((f (%cg-find-field (car (cddr (ctype-ext ty)))
   6193                                   (tok-value nt))))
   6194            (cond ((not f) (die (tok-loc nt) "unevaluated: no field"
   6195                                (tok-value nt))))
   6196            (lp (cadr f)))))
   6197       (($ tok? (kind PUNCT) (value arrow))
   6198        (advance ps)
   6199        (cond ((not (eq? (ctype-kind ty) 'ptr))
   6200               (die #f "unevaluated: arrow on non-pointer")))
   6201        (let* ((sty (ctype-ext ty)) (nt (peek ps)))
   6202          (cond ((not (eq? (tok-kind nt) 'IDENT))
   6203                 (die (tok-loc nt) "unevaluated: field")))
   6204          (advance ps)
   6205          (let ((f (%cg-find-field (car (cddr (ctype-ext sty)))
   6206                                   (tok-value nt))))
   6207            (cond ((not f) (die (tok-loc nt) "unevaluated: no field"
   6208                                (tok-value nt))))
   6209            (lp (cadr f)))))
   6210       (else ty))))
   6211 
   6212 (define (%const-unevaluated-unary-type ps)
   6213   (pmatch (peek ps)
   6214     (($ tok? (kind PUNCT) (value amp))
   6215      (advance ps) (%mk-ptr (%const-unevaluated-unary-type ps)))
   6216     (($ tok? (kind PUNCT) (value star))
   6217      (advance ps)
   6218      (let ((ty (%const-unevaluated-unary-type ps)))
   6219        (cond ((not (eq? (ctype-kind ty) 'ptr))
   6220               (die #f "unevaluated: dereference of non-pointer")))
   6221        (ctype-ext ty)))
   6222     (($ tok? (kind PUNCT) (value plus))
   6223      (advance ps)
   6224      (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
   6225     (($ tok? (kind PUNCT) (value minus))
   6226      (advance ps)
   6227      (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
   6228     (($ tok? (kind PUNCT) (value tilde))
   6229      (advance ps)
   6230      (cdr (%const-promote (cons 0 (%const-unevaluated-unary-type ps)))))
   6231     (($ tok? (kind PUNCT) (value bang))
   6232      (advance ps) (%const-unevaluated-unary-type ps) %t-i32)
   6233     (else (%const-unevaluated-postfix-type ps))))
   6234 
   6235 (define (%const-sizeof-expr ps paren?)
   6236   (cond
   6237     ((not (ps-cg ps))
   6238      (die #f "#if: sizeof of expression not valid in preprocessor context"))
   6239     ((cg-in-fn? (ps-cg ps))
   6240      (let ((tag (cg-snapshot (ps-cg ps))))
   6241        (cond (paren? (parse-expr ps) (expect-punct ps 'rparen))
   6242              (else  (parse-unary ps)))
   6243        (let* ((tp (cg-top (ps-cg ps)))
   6244               (sz (max (ctype-size (opnd-type tp)) 0)))
   6245          (cg-rewind (ps-cg ps) tag)
   6246          sz)))
   6247     (else
   6248      (let ((ty (%const-unevaluated-unary-type ps)))
   6249        (cond (paren? (expect-punct ps 'rparen)))
   6250        (max (ctype-size ty) 0)))))
   6251 
   6252 (define (%const-alignof-expr ps)
   6253   (cond
   6254     ((not (ps-cg ps))
   6255      (die #f "#if: _Alignof of expression not valid in preprocessor context"))
   6256     ((not (cg-in-fn? (ps-cg ps)))
   6257      (let ((ty (%const-unevaluated-unary-type ps)))
   6258        (expect-punct ps 'rparen)
   6259        (max (ctype-align ty) 1)))
   6260     (else
   6261      (let ((tag (cg-snapshot (ps-cg ps))))
   6262        (parse-expr ps)
   6263        (expect-punct ps 'rparen)
   6264        (let* ((tp (cg-top (ps-cg ps)))
   6265               (al (max (ctype-align (opnd-type tp)) 1)))
   6266          (cg-rewind (ps-cg ps) tag)
   6267          al)))))
   6268 
   6269 ;; Convenience: returns the integer value alone (callers that don't
   6270 ;; need the type half of parse-const-expr's (value . ctype) result).
   6271 (define (parse-const-int ps) (car (parse-const-expr ps)))
   6272 
   6273 (define (parse-declarator ps base)
   6274   ;; Returns (values name type).
   6275   ((cdr (parse-decl-cont ps)) base
   6276    (lambda (n t) (values n t))))
   6277 
   6278 (define (parse-decl-cont ps)
   6279   (pmatch (peek ps)
   6280     (($ tok? (kind KW) (value __attribute__))
   6281      (skip-gnu-attribute! ps) (parse-decl-cont ps))
   6282     (($ tok? (kind PUNCT) (value star))
   6283      (advance ps) (eat-cv-quals! ps)
   6284      (let* ((r (parse-decl-cont ps)) (rf (cdr r)))
   6285        (cons (car r) (lambda (b k) (rf (%mk-ptr b) k)))))
   6286     (($ tok? (kind PUNCT) (value lparen))
   6287      (guard (paren-is-group? ps))
   6288      (advance ps)
   6289      (let* ((i (parse-decl-cont ps)) (if- (cdr i)))
   6290        (expect-punct ps 'rparen)
   6291        (let ((s (parse-decl-suf-cont ps)))
   6292          (cons (car i) (lambda (b k) (if- (s b) k))))))
   6293     (($ tok? (kind IDENT) (value ,n))
   6294      (advance ps)
   6295      (let ((s (parse-decl-suf-cont ps)))
   6296        (cons n (lambda (b k) (k n (s b))))))
   6297     (else
   6298      (let ((s (parse-decl-suf-cont ps)))
   6299        (cons #f (lambda (b k) (k #f (s b))))))))
   6300 
   6301 (define (parse-decl-suf-cont ps)
   6302   ;; C declarator suffixes apply RIGHT-TO-LEFT (innermost first):
   6303   ;;   int a[2][3]  ⇒  arr (arr int 3) 2     (outer dim 2)
   6304   ;; not arr (arr int 2) 3 (which would treat the leftmost suffix as
   6305   ;; outermost). The recursive structure builds the inner suffix's
   6306   ;; result first, then this level wraps.
   6307   (pmatch (peek ps)
   6308     (($ tok? (kind PUNCT) (value lbrack))
   6309      (advance ps)
   6310      ;; C99 §6.7.5.2 allows `static`, type qualifiers (const /
   6311      ;; volatile / restrict), and `*` (variable length array
   6312      ;; placeholder) inside array-of-T brackets in function
   6313      ;; parameter declarators. We don't honour the qualifier
   6314      ;; semantics — just consume them so the dimension expression
   6315      ;; that follows parses.
   6316      (let lp ()
   6317        (cond
   6318          ((or (at-kw? ps 'const) (at-kw? ps 'volatile)
   6319               (at-kw? ps 'restrict) (at-kw? ps 'static))
   6320           (advance ps) (lp))
   6321          (else #t)))
   6322      (let* ((ln (cond ((at-punct? ps 'rbrack) -1)
   6323                       ((at-punct? ps 'star) (advance ps) -1)
   6324                       (else (parse-const-int ps))))
   6325             (_ (expect-punct ps 'rbrack))
   6326             (r (parse-decl-suf-cont ps)))
   6327        (lambda (b) (%mk-arr (r b) ln))))
   6328     (($ tok? (kind PUNCT) (value lparen))
   6329      (advance ps)
   6330      (let-values (((p v) (parse-fn-params ps)))
   6331        (expect-punct ps 'rparen)
   6332        (let ((r (parse-decl-suf-cont ps)))
   6333          (lambda (b) (%mk-fn (r b) p v)))))
   6334     (($ tok? (kind KW) (value __attribute__))
   6335      (skip-gnu-attribute! ps) (parse-decl-suf-cont ps))
   6336     (else (lambda (b) b))))
   6337 
   6338 (define (paren-is-group? ps)
   6339   (pmatch (peek2 ps)
   6340     (($ tok? (kind KW) (value ,v))
   6341      (cond ((or (eq? v 'void) (eq? v 'char) (eq? v 'short)
   6342                 (eq? v 'int) (eq? v 'long) (eq? v 'signed)
   6343                 (eq? v 'unsigned) (eq? v '_Bool)
   6344                 (eq? v 'float) (eq? v 'double)
   6345                 (eq? v '_Complex) (eq? v '_Imaginary)
   6346                 (eq? v 'struct) (eq? v 'union) (eq? v 'enum)
   6347                 (eq? v 'const) (eq? v 'volatile)
   6348                 (eq? v 'restrict) (eq? v 'static)
   6349                 (eq? v 'extern) (eq? v 'register)) #f)
   6350            (else #t)))
   6351     (($ tok? (kind IDENT) (value ,n))
   6352      (cond ((typedef? ps n) #f) (else #t)))
   6353     (($ tok? (kind PUNCT) (value rparen)) #f)
   6354     (($ tok? (kind PUNCT) (value star))   #t)
   6355     (($ tok? (kind PUNCT) (value lparen)) #t)
   6356     (($ tok? (kind PUNCT) (value lbrack)) #t)
   6357     (else #f)))
   6358 
   6359 (define (parse-fn-params ps)
   6360   ;; Returns (values params variadic?).
   6361   (cond
   6362     ((at-punct? ps 'rparen) (values '() #f))
   6363     ((and (at-kw? ps 'void)
   6364           (eq? (tok-kind (peek2 ps)) 'PUNCT)
   6365           (eq? (tok-value (peek2 ps)) 'rparen))
   6366      (advance ps) (values '() #f))
   6367     (else
   6368      (let loop ((acc '()))
   6369        (cond
   6370          ((at-punct? ps 'ellipsis)
   6371           (advance ps) (values (reverse acc) #t))
   6372          (else
   6373           (let*-values (((_sto bty) (parse-decl-spec ps))
   6374                         ((nm   ty)  (parse-declarator ps bty)))
   6375             (let ((ty2 (cond ((ctype-is-arr? ty)
   6376                               (%mk-ptr (car (ctype-ext ty))))
   6377                              ((ctype-is-fn? ty) (%mk-ptr ty))
   6378                              (else ty))))
   6379               (cond
   6380                 ((at-punct? ps 'comma)
   6381                  (advance ps) (loop (cons (cons nm ty2) acc)))
   6382                 ((at-punct? ps 'rparen)
   6383                  (values (reverse (cons (cons nm ty2) acc)) #f))
   6384                 (else (die (tok-loc (peek ps)) "param")))))))))))
   6385 
   6386 (define (parse-translation-unit ps)
   6387   (let loop ()
   6388     (cond
   6389       ((eq? (tok-kind (peek ps)) 'EOF) #t)
   6390       (else
   6391        (cond
   6392          ((debug-log?)
   6393           (let ((loc (tok-loc (peek ps))))
   6394             (debug-log "decl" "line" (loc-line loc)
   6395                        "heap" (heap-usage)))))
   6396        (cond ((at-kw? ps '_Static_assert) (parse-static-assert! ps))
   6397              (else (parse-decl-or-fn ps)))
   6398        ;; Function-local metadata is not part of the persistent world.
   6399        (cg-fn-meta-set! (ps-cg ps) '())
   6400        (loop)))))
   6401 
   6402 (define (parse-static-assert! ps)
   6403   (let ((loc (tok-loc (peek ps))))
   6404     (expect-kw ps '_Static_assert)
   6405     (expect-punct ps 'lparen)
   6406     (let ((v (parse-const-int ps)))
   6407       (expect-punct ps 'comma)
   6408       (cond ((not (eq? (tok-kind (peek ps)) 'STR))
   6409              (die (tok-loc (peek ps)) "_Static_assert needs a string")))
   6410       (let strings ()
   6411         (cond ((eq? (tok-kind (peek ps)) 'STR)
   6412                (advance ps) (strings))))
   6413       (expect-punct ps 'rparen)
   6414       (expect-punct ps 'semi)
   6415       (cond ((%c-value-zero? v) (die loc "static assertion failed"))
   6416             (else #t)))))
   6417 
   6418 (define (parse-decl-or-fn ps)
   6419   (let-values (((sto b) (parse-decl-spec ps)))
   6420     (cond
   6421       ((at-punct? ps 'semi) (advance ps) 'decl)
   6422       (else
   6423        (let-values (((n t) (parse-declarator ps b)))
   6424          (cond
   6425            ((and (ctype-is-fn? t) (at-punct? ps 'lbrace))
   6426             (parse-fn-body ps sto n t) 'fn)
   6427            (else
   6428             (handle-decl ps sto n t)
   6429             (let lp ()
   6430               (cond
   6431                 ((at-punct? ps 'comma)
   6432                  (advance ps)
   6433                  (let-values (((n2 t2) (parse-declarator ps b)))
   6434                    (handle-decl ps sto n2 t2) (lp)))
   6435                 (else (expect-punct ps 'semi) 'decl))))))))))
   6436 
   6437 ;; ---- Block-scope inferred-length array length resolution -------------
   6438 ;; The token iterator buffers lookahead in a list (see tok-iter); we
   6439 ;; can pull arbitrarily many tokens, then push them all back via
   6440 ;; iter-unget!. We use that to peek the initializer that follows `=`
   6441 ;; (without consuming it) and count its elements so cg-alloc-slot can
   6442 ;; reserve the right number of bytes BEFORE the initializer-emission
   6443 ;; loop runs (and starts spilling intermediate values into newly-
   6444 ;; allocated frame slots).
   6445 ;;
   6446 ;; Only the OUTERMOST length is inferred per C99 6.7.8/22, so for
   6447 ;; `int x[][3] = {{1,2,3},{4,5,6}};` we just count top-level
   6448 ;; brace-or-comma groups; the inner brace groups don't matter.
   6449 
   6450 (define (%peek-inferred-arr-init? ps)
   6451   ;; Check whether the next-after-`=` token starts a brace-init or a
   6452   ;; string-literal — the only initializer shapes that can resolve a
   6453   ;; block-scope inferred-length array. We do NOT consume `=`; we
   6454   ;; peek2 instead.
   6455   (let ((t2 (peek2 ps)))
   6456     (or (and (eq? (tok-kind t2) 'PUNCT) (eq? (tok-value t2) 'lbrace))
   6457         (eq? (tok-kind t2) 'STR))))
   6458 
   6459 (define (%resolve-inferred-arr-len ps ty)
   6460   ;; Returns a fresh array ctype with the resolved length. Does NOT
   6461   ;; consume the `=` or any of the initializer tokens — every token
   6462   ;; pulled is unget back in original order.
   6463   (let* ((eq-tok (iter-next (ps-iter ps)))    ; consume `=` (will unget)
   6464          (first  (iter-next (ps-iter ps)))     ; consume `{` or STR
   6465          (collected (list first eq-tok))       ; head order: revs at end
   6466          (count
   6467           (cond
   6468             ((eq? (tok-kind first) 'STR)
   6469              ;; String length + NUL.
   6470              (+ (bytevector-length (tok-value first)) 1))
   6471             (else
   6472              ;; first is `{`. Count top-level commas + 1, ignoring a
   6473              ;; trailing comma before `}`. Track brace depth so nested
   6474              ;; `{` for sub-aggregates are skipped.
   6475              (let lp ((depth 1) (n 0) (saw-elem? #f) (last-was-comma? #f)
   6476                       (acc collected))
   6477                (let ((t (iter-next (ps-iter ps))))
   6478                  (let ((acc2 (cons t acc)))
   6479                    (cond
   6480                      ((eq? (tok-kind t) 'EOF)
   6481                       ;; Bail; let the real parser report the error
   6482                       ;; after we restore tokens.
   6483                       (%inferred-arr-restore! ps acc2)
   6484                       (die #f "init: unterminated brace"))
   6485                      ((and (eq? (tok-kind t) 'PUNCT)
   6486                            (eq? (tok-value t) 'lbrace))
   6487                       (lp (+ depth 1) n #t #f acc2))
   6488                      ((and (eq? (tok-kind t) 'PUNCT)
   6489                            (eq? (tok-value t) 'rbrace))
   6490                       (cond
   6491                         ((= depth 1)
   6492                          ;; Done. Restore tokens (acc2 includes the
   6493                          ;; closing `}`).
   6494                          (%inferred-arr-restore! ps acc2)
   6495                          (cond ((not saw-elem?) 0)
   6496                                (last-was-comma? n)
   6497                                (else (+ n 1))))
   6498                         (else (lp (- depth 1) n saw-elem? #f acc2))))
   6499                      ((and (eq? (tok-kind t) 'PUNCT)
   6500                            (eq? (tok-value t) 'comma)
   6501                            (= depth 1))
   6502                       (lp depth (+ n 1) saw-elem? #t acc2))
   6503                      (else
   6504                       (lp depth n #t #f acc2)))))))))
   6505          )
   6506     (cond
   6507       ((eq? (tok-kind first) 'STR)
   6508        (%inferred-arr-restore! ps collected)))
   6509     (%init-fixed-arr-type ty count)))
   6510 
   6511 (define (%inferred-arr-restore! ps acc)
   6512   ;; acc is a stack of tokens in REVERSE consume order (most-recent
   6513   ;; first). iter-unget! prepends one at a time, so iterating acc in
   6514   ;; its current order pushes them back in the right sequence —
   6515   ;; i.e. the oldest-consumed token ends up at the front of the
   6516   ;; lookahead buffer.
   6517   (let lp ((xs acc))
   6518     (cond
   6519       ((null? xs) #t)
   6520       (else (iter-unget! (ps-iter ps) (car xs)) (lp (cdr xs))))))
   6521 
   6522 (define (handle-decl ps sto n ty)
   6523   (cond
   6524     ((not n) (die #f "no name"))
   6525     ((eq? sto 'typedef)
   6526      (scope-bind! ps n (%sym n 'typedef #f ty #f #t)))
   6527     ((ctype-is-fn? ty)
   6528      (scope-bind! ps n
   6529                   (%sym n 'fn (or sto 'extern) ty #f #f)))
   6530     ;; §I: block-scope `static` routes to a global with a declaration-id
   6531     ;; name under the enclosing function.  Function + identifier is not
   6532     ;; enough: separate nested scopes may legally reuse the same identifier.
   6533     ;; A same-scope redeclaration reuses its existing mangled name; a new
   6534     ;; declaration consumes the function's monotonic static counter.  The
   6535     ;; scope-bind! key remains the original identifier for source lookup.
   6536     ((and (eq? sto 'static) (ps-fn-ctx ps))
   6537      (let* ((fc (ps-fn-ctx ps))
   6538             (old (scope-lookup-current ps n))
   6539             (mangled
   6540              (cond
   6541                ((and old (eq? (sym-kind old) 'var)
   6542                      (eq? (sym-storage old) 'static))
   6543                 (sym-name old))
   6544                (else
   6545                 (let ((id (fn-ctx-static-counter fc)))
   6546                   (fn-ctx-static-counter-set! fc (+ id 1))
   6547                   (bytevector-append (fn-ctx-name fc) "__static_"
   6548                                      (number->string id 10) "__" n))))))
   6549        (cond
   6550          ((at-punct? ps 'assign)
   6551           (advance ps)
   6552           ;; Parse init first so an inferred-length array picks up its
   6553           ;; resolved type before sm is constructed (sym is immutable).
   6554           (let-values (((pieces ty2) (parse-init-global ps ty)))
   6555             (let ((sm (%sym mangled 'var 'static ty2 #f #t)))
   6556               (scope-bind! ps n sm)
   6557               (cg-emit-global (ps-cg ps) sm pieces))))
   6558          (else
   6559           (let ((sm (%sym mangled 'var 'static ty #f #t)))
   6560             (scope-bind! ps n sm)
   6561             (cg-emit-global (ps-cg ps) sm #f))))))
   6562     (else
   6563      (cond
   6564        ((not (ps-fn-ctx ps))
   6565         ;; File-scope decls. Three cases:
   6566         ;;   (a) initializer present  -> full external definition.
   6567         ;;   (b) `extern` no init     -> declaration only.
   6568         ;;   (c) no init, no `extern` -> tentative definition.
   6569         ;; (a) emits to .data immediately. (b) is recorded but emits
   6570         ;; nothing. (c) is recorded as `defined?=#f` and added to
   6571         ;; world-tentatives; cg-finish emits .bss at end of TU only if
   6572         ;; no full definition appeared. This lets two `static int x;`
   6573         ;; or a `static int x;` followed by `static int x = 1;`
   6574         ;; coexist (C 6.9.2 tentative-def merge).
   6575         (cond
   6576           ((at-punct? ps 'assign)
   6577            (advance ps)
   6578            (let-values (((pieces ty2) (parse-init-global ps ty)))
   6579              (let ((sm (%sym n 'var (or sto 'extern) ty2 #f #t)))
   6580                (scope-bind! ps n sm)
   6581                (cg-emit-global (ps-cg ps) sm pieces))))
   6582           ((eq? sto 'extern)
   6583            (let ((sm (%sym n 'var 'extern ty #f #f)))
   6584              (scope-bind! ps n sm)
   6585              (cg-emit-extern (ps-cg ps) sm)))
   6586           (else
   6587            (let ((sm (%sym n 'var (or sto 'extern) ty #f #f)))
   6588              (scope-bind! ps n sm)
   6589              (cg-add-tentative! (ps-cg ps) n)))))
   6590        (else
   6591         ;; Block-scope inferred-length array (`int a[] = {…};` or
   6592         ;; `char s[] = "…";`): peek the initializer past `=` to count
   6593         ;; elements / measure the string and rebuild `ty` with the
   6594         ;; resolved length BEFORE cg-alloc-slot. Otherwise the slot
   6595         ;; is sized off a -1 / 0 ctype-size (capped to 1 byte) and
   6596         ;; the per-element stores in parse-init-local-aggregate write
   6597         ;; past frame-hi — the next %cg-spill-reg then allocates
   6598         ;; right inside the array, clobbering elements.
   6599         (let* ((ty (cond
   6600                      ((and (eq? (ctype-kind ty) 'arr)
   6601                            (< (cdr (ctype-ext ty)) 0)
   6602                            (at-punct? ps 'assign)
   6603                            (%peek-inferred-arr-init? ps))
   6604                       (%resolve-inferred-arr-len ps ty))
   6605                      (else ty)))
   6606                (sz (max (ctype-size ty) 1))
   6607                (al (max (ctype-align ty) 1))
   6608                (sl (cg-alloc-slot (ps-cg ps) sz al))
   6609                (sm (%sym n 'var (or sto 'auto) ty sl #t)))
   6610           (scope-bind! ps n sm)
   6611           (cond
   6612             ((at-punct? ps 'assign)
   6613              (advance ps)
   6614              (cond
   6615                ;; Aggregate locals get the per-element store treatment.
   6616                ((or (at-punct? ps 'lbrace)
   6617                     (and (eq? (ctype-kind ty) 'arr)
   6618                          (eq? (tok-kind (peek ps)) 'STR)))
   6619                 (parse-init-local-aggregate ps sm ty))
   6620                ;; Struct/union initializer from a non-brace expression
   6621                ;; (typically a function call returning by-value). The
   6622                ;; expr produces a struct lval; we copy bytes into the
   6623                ;; destination slot.
   6624                ((or (eq? (ctype-kind ty) 'struct)
   6625                     (eq? (ctype-kind ty) 'union))
   6626                 (cg-push-sym (ps-cg ps) sm)
   6627                 (parse-expr-bp ps 4)
   6628                 (cg-copy-struct (ps-cg ps)))
   6629                (else
   6630                 (cg-push-sym (ps-cg ps) sm)
   6631                 (parse-expr-bp ps 4) (rval! ps)
   6632                 (cg-cast (ps-cg ps) ty)
   6633                 (cg-assign (ps-cg ps))
   6634                 (cg-pop (ps-cg ps)))))
   6635             (else #t))))))))
   6636 
   6637 ;; ====================================================================
   6638 ;; Initializers (see CC.md §Variable initializers).
   6639 ;;
   6640 ;; parse-init-global ps ty
   6641 ;;   Reads the initializer following `=` for a file-scope or block-scope
   6642 ;;   static var of static-storage type `ty` and returns a list of
   6643 ;;   pieces suitable for cg-emit-global. See cg.scm §cg-emit-global for
   6644 ;;   the piece grammar.
   6645 ;;
   6646 ;; parse-init-local ps sm ty
   6647 ;;   Reads the initializer for an auto-storage variable bound to slot
   6648 ;;   sym `sm` and emits per-element store cg ops. Returns unspecified.
   6649 ;; ====================================================================
   6650 
   6651 (define (%int->le-bv n nbytes)
   6652   ;; N-byte little-endian encoding of integer n into a fresh bv. Bytes
   6653   ;; >= sign-bit are filled by repeated >>8 (works for both signed and
   6654   ;; unsigned because we only keep the low N bytes).
   6655   (let* ((out (make-bytevector nbytes 0))
   6656          (src (c-value-bytes (%c-value-coerce n))))
   6657     (let loop ((i 0))
   6658       (cond
   6659         ((= i nbytes) out)
   6660         (else
   6661          (bytevector-u8-set! out i
   6662            (if (< i %C-VALUE-BYTES) (bytevector-u8-ref src i) 0))
   6663          (loop (+ i 1)))))))
   6664 
   6665 ;; File-scope compound literal (C99 §6.5.2.5). The bracketed initializer
   6666 ;; following a typename in a static-storage initializer (or behind `&`
   6667 ;; in same) is an unnamed object with static storage duration. Drive
   6668 ;; the existing parse-init-global → cg-emit-global pipeline against a
   6669 ;; synthetic sym whose label is freshly minted via %cg-fresh-cl-label.
   6670 ;; Returns the emitted label; the caller wraps it in a (label-ref . LBL)
   6671 ;; piece. The leading `(T)` and the storage-class disambiguation belong
   6672 ;; to the caller — this entry point assumes peek = `{`.
   6673 (define (%emit-fs-compound-literal ps ty)
   6674   (let-values (((pieces ty2) (parse-init-global ps ty)))
   6675     (let* ((lbl (%cg-fresh-cl-label (ps-cg ps)))
   6676            ;; storage 'extern → %cg-sym-label returns the bare name
   6677            ;; unchanged (no extra "cc__" prefix), so the emitted label
   6678            ;; matches what we hand back to the caller.
   6679            (sm  (%sym lbl 'var 'extern ty2 #f #t)))
   6680       (cg-emit-global (ps-cg ps) sm pieces)
   6681       lbl)))
   6682 
   6683 (define (%const-init-piece ps ty)
   6684   ;; Parse a non-brace initializer expression for scalar type `ty` and
   6685   ;; return a single piece. Recognised forms:
   6686   ;;   - INT (with optional unary +/-)               -> N-byte LE bv
   6687   ;;   - enum-const IDENT                            -> N-byte LE bv
   6688   ;;   - &IDENT (address of a global var/fn)         -> (label-ref . cc__name)
   6689   ;;   - &(T){...} (address of file-scope literal)   -> (label-ref . cc__cl_N)
   6690   ;;   - IDENT  (function name; decays to fn ptr)    -> (label-ref . cc__name)
   6691   ;;   - STR    (only for char* targets)             -> (label-ref . string-pool-label)
   6692   ;;   - (T){...} (file-scope compound literal)      -> (label-ref . cc__cl_N)
   6693   (let ((t (peek ps)))
   6694     (cond
   6695       ;; Redundant expression grouping around a static initializer. Macro
   6696       ;; APIs commonly spell literals as `((T){...})` and strings as
   6697       ;; `("...")`; peel one non-cast pair and let the regular initializer
   6698       ;; paths consume the enclosed value.
   6699       ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'lparen)
   6700             (eq? (tok-kind (peek2 ps)) 'STR))
   6701        (advance ps)
   6702        (let ((p (%const-init-piece ps ty)))
   6703          (expect-punct ps 'rparen)
   6704          p))
   6705       ;; Address initializer: &ident -> label-ref
   6706       ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'amp))
   6707        (advance ps)
   6708        (let ((it (peek ps)))
   6709          (cond
   6710            ((eq? (tok-kind it) 'IDENT)
   6711             (advance ps)
   6712             (let ((sm (scope-lookup ps (tok-value it))))
   6713               (cond
   6714                 ((not sm) (die (tok-loc it) "init: undecl" (tok-value it)))
   6715                 ((or (eq? (sym-kind sm) 'fn)
   6716                      (and (eq? (sym-kind sm) 'var)
   6717                           (or (eq? (sym-storage sm) 'static)
   6718                               (eq? (sym-storage sm) 'extern))))
   6719                  (cons 'label-ref (%cg-sym-label (ps-cg ps) sm)))
   6720                 (else
   6721                  (die (tok-loc it) "init: &x must reference a global"
   6722                       (tok-value it))))))
   6723            ;; &(T){...} — address of an unnamed file-scope compound
   6724            ;; literal. Parse the typename, expect `{`, drive the
   6725            ;; literal into .data, and yield its label.
   6726            ((and (eq? (tok-kind it) 'PUNCT) (eq? (tok-value it) 'lparen)
   6727                  (%const-paren-is-cast? ps))
   6728             (advance ps)
   6729             (let*-values (((_sto bty) (parse-decl-spec ps))
   6730                           ((_n   ty2) (parse-declarator ps bty)))
   6731               (expect-punct ps 'rparen)
   6732               (cond
   6733                 ((not (at-punct? ps 'lbrace))
   6734                  (die (tok-loc (peek ps))
   6735                       "init: &(T) must be followed by { ... }"
   6736                       (tok-value (peek ps)))))
   6737               (cons 'label-ref (%emit-fs-compound-literal ps ty2))))
   6738            (else (die (tok-loc it) "init: &?" (tok-value it))))))
   6739       ;; (T){...} — file-scope compound literal. The literal is an
   6740       ;; lvalue of array/struct/union type; assignment to a pointer
   6741       ;; target decays it via its label address (label = first byte).
   6742       ((and (eq? (tok-kind t) 'PUNCT) (eq? (tok-value t) 'lparen)
   6743             (%const-paren-is-cast? ps)
   6744             ;; Speculatively look past `(T)` for `{`. Since we have no
   6745             ;; 3-token peek, we have to commit to the (T) parse; if the
   6746             ;; following token isn't `{` it's a plain cast, so we fall
   6747             ;; back to the const-int path with the type already consumed.
   6748             #t)
   6749        ;; Take the (T) ourselves so we can dispatch on the next token.
   6750        (advance ps)
   6751        (let*-values (((_sto bty) (parse-decl-spec ps))
   6752                      ((_n   ty2) (parse-declarator ps bty)))
   6753          (expect-punct ps 'rparen)
   6754          (cond
   6755            ((at-punct? ps 'lbrace)
   6756             (cons 'label-ref (%emit-fs-compound-literal ps ty2)))
   6757            (else
   6758             ;; Not a compound literal — it's a constant cast, e.g.
   6759             ;; `(int)(unsigned char)257`. Mirror parse-const-cast's
   6760             ;; cast arm with the already-parsed type.
   6761             (cond
   6762               ((%ctype-int? ty2)
   6763                (let ((v (parse-const-cast ps)))
   6764                  (%int->le-bv (%const-trunc (car v) ty2)
   6765                               (max (ctype-size ty) 1))))
   6766               ((eq? (ctype-kind ty2) 'ptr)
   6767                ;; Pointer cast in const-expr: type-retag only. We expect
   6768                ;; the operand to be an integer-shaped const (e.g. 0) and
   6769                ;; emit it as the target's byte width.
   6770                (let ((v (parse-const-cast ps)))
   6771                  (%int->le-bv (car v) (max (ctype-size ty) 1))))
   6772               (else
   6773                (die (tok-loc (peek ps))
   6774                     "init: cast to non-scalar non-compound-literal"
   6775                     (ctype-kind ty2))))))))
   6776       ;; Function name or array name as a label-ref initializer.
   6777       ;; (Both decay to a pointer when used as a value.)
   6778       ((and (eq? (tok-kind t) 'IDENT)
   6779             (let ((sm (scope-lookup ps (tok-value t))))
   6780               (and sm
   6781                    (or (eq? (sym-kind sm) 'fn)
   6782                        (and (eq? (sym-kind sm) 'var)
   6783                             (eq? (ctype-kind (sym-type sm)) 'arr)
   6784                             (or (eq? (sym-storage sm) 'static)
   6785                                 (eq? (sym-storage sm) 'extern)))))))
   6786        (advance ps)
   6787        (let ((sm (scope-lookup ps (tok-value t))))
   6788          (cons 'label-ref (%cg-sym-label (ps-cg ps) sm))))
   6789       ;; Plain string literal as char* initializer.
   6790       ((eq? (tok-kind t) 'STR)
   6791        (advance ps)
   6792        (let ((lbl (cg-intern-string (ps-cg ps) (tok-value t))))
   6793          (cons 'label-ref lbl)))
   6794       ;; Otherwise it's a const integer.
   6795       (else
   6796        (let ((v (parse-const-int ps)))
   6797          (%int->le-bv v (max (ctype-size ty) 1)))))))
   6798 
   6799 ;; Parse a file-scope aggregate compound literal, accepting redundant outer
   6800 ;; grouping: `(T){...}` and `((T){...})`. The caller already knows an
   6801 ;; aggregate is required, so a leading parenthesis is unambiguous here.
   6802 (define (%global-init-compound ps expected-ty)
   6803   (expect-punct ps 'lparen)
   6804   (cond
   6805     ((at-punct? ps 'lparen)
   6806      (let ((pieces (%global-init-compound ps expected-ty)))
   6807        (expect-punct ps 'rparen)
   6808        pieces))
   6809     (else
   6810      (let*-values (((_sto bty) (parse-decl-spec ps))
   6811                    ((_n literal-ty) (parse-declarator ps bty)))
   6812        (expect-punct ps 'rparen)
   6813        (cond ((not (or (ctype-compat? literal-ty expected-ty)
   6814                        (%ctype-same-aggregate-tag? literal-ty expected-ty)))
   6815               (die (tok-loc (peek ps))
   6816                    "init: incompatible aggregate compound literal")))
   6817        (expect-punct ps 'lbrace)
   6818        (let ((k (ctype-kind literal-ty)))
   6819          (cond
   6820            ((eq? k 'arr)
   6821             (let-values (((pieces _count)
   6822                           (%parse-init-array-list ps literal-ty)))
   6823               pieces))
   6824            ((or (eq? k 'struct) (eq? k 'union))
   6825             (%parse-init-struct-list ps literal-ty))
   6826             (else (die #f "init: compound literal is not aggregate" k))))))))
   6827 
   6828 (define (%ctype-same-aggregate-tag? a b)
   6829   ;; Qualifier propagation can clone a tagged aggregate ctype, so pointer
   6830   ;; identity alone is too strict for `(T){...}` assigned to a const-qualified
   6831   ;; T field.  A matching non-anonymous tag is the seed compiler's nominal
   6832   ;; identity for structs/unions.
   6833   (let ((ka (ctype-kind a)) (kb (ctype-kind b)))
   6834     (and (eq? ka kb)
   6835          (or (eq? ka 'struct) (eq? ka 'union))
   6836          (let ((ta (car (ctype-ext a))) (tb (car (ctype-ext b))))
   6837            (and ta tb (bytes=? ta tb))))))
   6838 
   6839 (define (%init-grouped-compound-start? ps)
   6840   (or (%const-paren-is-cast? ps)
   6841       (pmatch (peek2 ps)
   6842         (($ tok? (kind PUNCT) (value lparen)) #t)
   6843         (else #f))))
   6844 
   6845 (define (%init-array-elem-type ty)
   6846   (cond ((eq? (ctype-kind ty) 'arr) (car (ctype-ext ty)))
   6847         (else (die #f "init: not an array" ty))))
   6848 
   6849 (define (%init-array-decl-len ty)
   6850   ;; Declared array length (-1 = inferred).
   6851   (cond ((eq? (ctype-kind ty) 'arr) (cdr (ctype-ext ty))) (else -1)))
   6852 
   6853 (define (%init-fixed-arr-type ty count)
   6854   ;; Construct a fresh array ctype with the inferred length resolved
   6855   ;; to `count`. Pure — does not mutate `ty`. For non-inferred or
   6856   ;; non-array `ty`, callers should detect this themselves and just
   6857   ;; pass `ty` through.
   6858   (%mk-arr (car (ctype-ext ty)) count))
   6859 
   6860 (define (%init-struct-fields ty)
   6861   ;; Return ((name-bv ctype offset) ...) for a struct/union ctype.
   6862   (let ((ext (ctype-ext ty)))
   6863     (cond ((and (pair? ext) (pair? (cdr ext))) (car (cddr ext)))
   6864           (else (die #f "init: not a struct" ty)))))
   6865 
   6866 ;; After processing a designated initializer for FNAME, return the
   6867 ;; field list with FNAME and all preceding (already-overwritten or
   6868 ;; skipped) fields removed. Empty list if FNAME isn't found (caller
   6869 ;; should already have validated the field exists).
   6870 (define (%init-drop-thru-field fields fname)
   6871   (cond ((null? fields) '())
   6872         ((bytes=? (car (car fields)) fname) (cdr fields))
   6873         (else (%init-drop-thru-field (cdr fields) fname))))
   6874 
   6875 ;; Consume a C99 field designator, including a chain such as `.v.stack`.
   6876 ;; Return the top-level field name (for positional-cursor advancement), the
   6877 ;; final selected type, and its aggregate-relative byte offset.
   6878 (define (%init-field-designator ps fields)
   6879   (expect-punct ps 'dot)
   6880   (let walk ((cur-fields fields) (top-name #f) (offset 0))
   6881     (let ((nt (advance ps)))
   6882       (cond ((not (eq? (tok-kind nt) 'IDENT))
   6883              (die (tok-loc nt) "init: .field expects ident")))
   6884       (let ((f (%cg-find-field cur-fields (tok-value nt))))
   6885         (cond ((not f) (die (tok-loc nt) "init: no such field"
   6886                             (tok-value nt))))
   6887         (let ((name (car f))
   6888               (fty (car (cdr f)))
   6889               (foff (car (cddr f))))
   6890           (cond
   6891             ((at-punct? ps 'dot)
   6892              (advance ps)
   6893              (walk (%init-struct-fields fty)
   6894                    (or top-name name) (+ offset foff)))
   6895             (else
   6896              (expect-punct ps 'assign)
   6897              (list (or top-name name) fty (+ offset foff)))))))))
   6898 
   6899 ;; #t when TY is an array of i8/u8 — a char[] a string literal may
   6900 ;; initialize directly (C11 §6.7.9 ¶14).
   6901 (define (%char-arr-type? t)
   6902   (and (eq? (ctype-kind t) 'arr)
   6903        (let ((et (car (ctype-ext t))))
   6904          (or (eq? et %t-i8) (eq? et %t-u8)))))
   6905 
   6906 ;; A char[] field/element written as `= "..."`: consume the STR token
   6907 ;; and return a byte-vector piece of the array's declared size, copying
   6908 ;; the string bytes (truncating an over-long string, zero-padding a
   6909 ;; short one). The nested-aggregate analogue of parse-init-global's
   6910 ;; top-level STR arm; `t` always has a declared length here (only a
   6911 ;; top-level array may infer its length from the string).
   6912 (define (%str-arr-piece ps t)
   6913   (let* ((s     (tok-value (peek ps)))
   6914          (slen  (bytevector-length s))
   6915          (final (ctype-size t))
   6916          (bv    (make-bytevector final 0)))
   6917     (advance ps)
   6918     (let loop ((i 0))
   6919       (cond
   6920         ((or (= i slen) (>= i final)) bv)
   6921         (else
   6922          (bytevector-u8-set! bv i (bytevector-u8-ref s i))
   6923          (loop (+ i 1)))))))
   6924 
   6925 ;; Element/field dispatch for global aggregate initializers. ELIDE? = #f
   6926 ;; means caller has just consumed `{` for this element and we own the
   6927 ;; matching `}`; ELIDE? = #t is C99 §6.7.8 ¶22 brace elision (the
   6928 ;; sub-aggregate draws items from the parent stream, no inner braces).
   6929 ;; Returns the piece-list contributing this element to the encoding.
   6930 (define (%global-init-elem ps t elide?)
   6931   (let ((k (ctype-kind t)))
   6932     (cond
   6933       ((and elide?
   6934             (or (eq? k 'arr) (eq? k 'struct) (eq? k 'union))
   6935             (at-punct? ps 'lparen)
   6936             (%init-grouped-compound-start? ps))
   6937        (let ((pieces (%global-init-compound ps t)))
   6938          (cond
   6939            (elide? pieces)
   6940            (else
   6941             (cond ((at-punct? ps 'comma) (advance ps)))
   6942             (expect-punct ps 'rbrace)
   6943             pieces))))
   6944       ;; char[] initialized by a string literal (`{"..."}` or, under
   6945       ;; brace elision, a bare `"..."`). Without this, the `arr` arm
   6946       ;; below would treat the string as an element-list and encode it
   6947       ;; as a pointer to the string pool instead of the bytes.
   6948       ((and (eq? k 'arr)
   6949             (eq? (tok-kind (peek ps)) 'STR)
   6950             (%char-arr-type? t))
   6951        (let ((p (%str-arr-piece ps t)))
   6952          (cond
   6953            (elide? (list p))
   6954            (else
   6955             (cond ((at-punct? ps 'comma) (advance ps)))
   6956             (expect-punct ps 'rbrace)
   6957             (list p)))))
   6958       ((eq? k 'arr)
   6959        (let-values (((p _c) (cond
   6960                               (elide? (%parse-init-array-list/mode ps t #f))
   6961                               (else   (%parse-init-array-list ps t)))))
   6962          p))
   6963       ((or (eq? k 'struct) (eq? k 'union))
   6964        (cond
   6965          (elide? (%parse-init-struct-list/mode ps t #f))
   6966          (else   (%parse-init-struct-list ps t))))
   6967       (else
   6968        (let ((p (%const-init-piece ps t)))
   6969          (cond
   6970            (elide? (list p))
   6971            (else
   6972             (cond ((at-punct? ps 'comma) (advance ps)))
   6973             (expect-punct ps 'rbrace)
   6974             (list p))))))))
   6975 
   6976 ;; Element/field dispatch for local aggregate initializers. Mirrors
   6977 ;; %global-init-elem but emits per-element store ops via cg-assign for
   6978 ;; scalar leaves, and recurses into the local-list walkers for
   6979 ;; aggregates. Returns 0; the side effect is the emitted code.
   6980 (define (%local-init-expr-tokens ps)
   6981   ;; Capture one assignment-expression without consuming the comma or `}`
   6982   ;; which terminates it in the surrounding initializer list.  Braces must
   6983   ;; be tracked separately from parentheses: a compound literal such as
   6984   ;; `(struct S){ .x = 1, .y = 2 }` is one expression even though it contains
   6985   ;; both commas and a closing brace.
   6986   (let loop ((acc '()) (paren 0) (brack 0) (brace 0))
   6987     (let ((t (peek ps)))
   6988       (cond
   6989         ((eq? (tok-kind t) 'EOF)
   6990          (die (tok-loc t) "EOF in local initializer expression"))
   6991         ((and (= paren 0) (= brack 0) (= brace 0)
   6992               (eq? (tok-kind t) 'PUNCT)
   6993               (or (eq? (tok-value t) 'comma)
   6994                   (eq? (tok-value t) 'rbrace)))
   6995          (reverse acc))
   6996         (else
   6997          (let ((nt (advance ps)))
   6998            (cond
   6999              ((not (eq? (tok-kind nt) 'PUNCT))
   7000               (loop (cons nt acc) paren brack brace))
   7001              ((eq? (tok-value nt) 'lparen)
   7002               (loop (cons nt acc) (+ paren 1) brack brace))
   7003              ((eq? (tok-value nt) 'rparen)
   7004               (loop (cons nt acc) (- paren 1) brack brace))
   7005              ((eq? (tok-value nt) 'lbrack)
   7006               (loop (cons nt acc) paren (+ brack 1) brace))
   7007              ((eq? (tok-value nt) 'rbrack)
   7008               (loop (cons nt acc) paren (- brack 1) brace))
   7009              ((eq? (tok-value nt) 'lbrace)
   7010               (loop (cons nt acc) paren brack (+ brace 1)))
   7011              ((eq? (tok-value nt) 'rbrace)
   7012               (loop (cons nt acc) paren brack (- brace 1)))
   7013              (else
   7014               (loop (cons nt acc) paren brack brace)))))))))
   7015 
   7016 (define (%local-init-aggregate-expr? ps expected)
   7017   ;; A brace-less aggregate initializer is ambiguous until its first
   7018   ;; assignment-expression has been typed: it can be a whole-aggregate copy
   7019   ;; (`.member = value`) or brace elision (`.member = 1, 2`).  Parse that
   7020   ;; expression once under a codegen snapshot, then put its tokens back.  The
   7021   ;; real parse below either copies the aggregate or descends into its first
   7022   ;; scalar member.  This is the same no-side-effect mechanism used by sizeof.
   7023   (let* ((it (ps-iter ps))
   7024          (toks (%local-init-expr-tokens ps)))
   7025     (for-each (lambda (t) (iter-unget! it t)) (reverse toks))
   7026     (cond
   7027       ((null? toks) #f)
   7028       (else
   7029        (let ((tag (cg-snapshot (ps-cg ps))))
   7030          (parse-saved-expr ps toks)
   7031          (let* ((top (cg-top (ps-cg ps)))
   7032                 (actual (opnd-type top))
   7033                 (aggregate?
   7034                  (and (or (eq? (ctype-kind actual) 'struct)
   7035                           (eq? (ctype-kind actual) 'union))
   7036                       (or (ctype-compat? actual expected)
   7037                           (%ctype-same-aggregate-tag? actual expected)))))
   7038            (cg-rewind (ps-cg ps) tag)
   7039            aggregate?))))))
   7040 
   7041 (define (%local-init-elem ps sm eoff t elide?)
   7042   (let ((k (ctype-kind t)))
   7043     (cond
   7044       ;; char[] field/element initialized by a string literal — emit the
   7045       ;; bytes into the frame slot. Mirrors %global-init-elem's STR arm;
   7046       ;; without it the `arr` arm would parse the string as a scalar
   7047       ;; expression and store one byte of its decayed pointer.
   7048       ((and (eq? k 'arr)
   7049             (eq? (tok-kind (peek ps)) 'STR)
   7050             (%char-arr-type? t))
   7051        (let* ((s     (tok-value (peek ps)))
   7052               (slen  (bytevector-length s))
   7053               (final (ctype-size t)))
   7054          (advance ps)
   7055          (let loop ((i 0))
   7056            (cond
   7057              ((>= i final) 0)
   7058              (else
   7059               (let ((b (cond ((< i slen) (bytevector-u8-ref s i)) (else 0))))
   7060                 (%push-frame-elem-lval ps (+ eoff i) %t-u8)
   7061                 (cg-push-imm (ps-cg ps) %t-u8 b)
   7062                 (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps))
   7063                 (loop (+ i 1))))))
   7064          (cond
   7065            (elide? 0)
   7066            (else
   7067             (cond ((at-punct? ps 'comma) (advance ps)))
   7068             (expect-punct ps 'rbrace)))))
   7069       ((eq? k 'arr)
   7070        (cond
   7071          (elide? (%parse-init-local-array-list/mode ps sm eoff t #f))
   7072          (else   (%parse-init-local-array-list ps sm eoff t))))
   7073       ((or (eq? k 'struct) (eq? k 'union))
   7074        (cond
   7075          ((and elide? (%local-init-aggregate-expr? ps t))
   7076           (%push-frame-elem-lval ps eoff t)
   7077           (parse-expr-bp ps 4)
   7078           (cg-copy-struct (ps-cg ps)))
   7079          (elide? (%parse-init-local-struct-list/mode ps sm eoff t #f))
   7080          (else   (%parse-init-local-struct-list ps sm eoff t))))
   7081       (else
   7082        (%push-frame-elem-lval ps eoff t)
   7083        (parse-expr-bp ps 4) (rval! ps)
   7084        (cg-cast (ps-cg ps) t)
   7085        (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps))
   7086        (cond
   7087          (elide? 0)
   7088          (else
   7089           (cond ((at-punct? ps 'comma) (advance ps)))
   7090           (expect-punct ps 'rbrace)))))))
   7091 
   7092 (define (%pad-piece nbytes)
   7093   (make-bytevector nbytes 0))
   7094 
   7095 ;; Prepend XS in reverse order without allocating an intermediate list.
   7096 (define (%init-prepend-reversed xs acc)
   7097   (let loop ((ys xs) (out acc))
   7098     (cond
   7099       ((null? ys) out)
   7100       (else
   7101        (loop (cdr ys) (cons (car ys) out))))))
   7102 
   7103 ;; ----- Global initializers ---------------------------------------------
   7104 ;; Returns (values pieces final-ty). For inferred-length array `ty`,
   7105 ;; final-ty is a freshly-built array ctype with the resolved length;
   7106 ;; otherwise final-ty is `ty` unchanged.
   7107 (define (parse-init-global ps ty)
   7108   (pmatch (peek ps)
   7109     ;; String literal initializer for char[]
   7110     (($ tok? (kind STR) (value ,s))
   7111      (guard (and (eq? (ctype-kind ty) 'arr)
   7112                  (let ((et (car (ctype-ext ty))))
   7113                    (or (eq? et %t-i8) (eq? et %t-u8)))))
   7114      (advance ps)
   7115      (let* ((slen (bytevector-length s))
   7116             (decl (cdr (ctype-ext ty)))
   7117             (final (cond ((< decl 0) (+ slen 1)) (else decl)))
   7118             (final-ty (cond ((< decl 0) (%init-fixed-arr-type ty final))
   7119                             (else ty))))
   7120        (let ((bv (make-bytevector final 0)))
   7121          (let loop ((i 0))
   7122            (cond
   7123              ((or (= i slen) (>= i final))
   7124               (values (list bv) final-ty))
   7125              (else
   7126               (bytevector-u8-set! bv i (bytevector-u8-ref s i))
   7127               (loop (+ i 1))))))))
   7128     ;; Brace-form
   7129     (($ tok? (kind PUNCT) (value lbrace))
   7130      (advance ps)
   7131      (cond
   7132        ((eq? (ctype-kind ty) 'arr)
   7133         (let-values (((pieces count) (%parse-init-array-list ps ty)))
   7134           (let* ((decl (%init-array-decl-len ty))
   7135                  (final-ty (cond ((< decl 0)
   7136                                   (%init-fixed-arr-type ty count))
   7137                                  (else ty))))
   7138             (values pieces final-ty))))
   7139        ((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union))
   7140         (values (%parse-init-struct-list ps ty) ty))
   7141        (else
   7142         ;; Brace-wrapped scalar: { expr }
   7143         (let ((piece (%const-init-piece ps ty)))
   7144           (cond ((at-punct? ps 'comma) (advance ps)))
   7145           (expect-punct ps 'rbrace)
   7146           (values (list piece) ty)))))
   7147     ;; Bare scalar initializer
   7148     (else (values (list (%const-init-piece ps ty)) ty))))
   7149 
   7150 ;; Returns (values pieces count). `count` is the number of element
   7151 ;; initializers actually consumed (used by parse-init-global to resolve
   7152 ;; an inferred top-level length). C99 forbids inferred length in
   7153 ;; nested array elements, so recursive callers ignore `count`.
   7154 ;;
   7155 ;; `brace?` controls termination: when #t (the normal case), the loop
   7156 ;; consumes elements until `}` is seen. When #f (brace-elision recursion
   7157 ;; from C99 §6.7.8 ¶22), the loop consumes exactly `decl` elements from
   7158 ;; the parent stream and returns without expecting `}`. In no-brace
   7159 ;; mode, a leading `.` or `[` designator targets the enclosing aggregate
   7160 ;; — the recursion terminates immediately, padding the unfilled tail.
   7161 (define (%parse-init-array-list ps ty)
   7162   (%parse-init-array-list/mode ps ty #t))
   7163 
   7164 (define (%parse-init-array-list/mode ps ty brace?)
   7165   ;; Element-list array initializer; assumes `{` already consumed when
   7166   ;; brace? is #t.
   7167   (let* ((elem  (%init-array-elem-type ty))
   7168          (esize (ctype-size elem))
   7169          (decl  (%init-array-decl-len ty)))
   7170     (let lp ((entries '()) (cursor 0) (count 0))
   7171       (cond
   7172         ((cond (brace? (at-punct? ps 'rbrace))
   7173                (else (or (>= cursor (cond ((< decl 0) 0) (else decl)))
   7174                          (at-punct? ps 'rbrace)
   7175                          (at-punct? ps 'dot)
   7176                          (at-punct? ps 'lbrack))))
   7177          (cond (brace? (advance ps)))
   7178          (let ((final (cond ((< decl 0) count) (else decl))))
   7179            (values (%merge-init-entries (reverse entries) (* final esize))
   7180                    count)))
   7181         (else
   7182          (let* ((designated? (and brace? (at-punct? ps 'lbrack)))
   7183                 (index
   7184                  (cond
   7185                    (designated?
   7186                     (advance ps)
   7187                     (let ((n (parse-const-int ps)))
   7188                       (expect-punct ps 'rbrack)
   7189                       (expect-punct ps 'assign)
   7190                       n))
   7191                    (else cursor)))
   7192                 (_range
   7193                  (cond
   7194                    ((or (< index 0) (and (>= decl 0) (>= index decl)))
   7195                     (die (tok-loc (peek ps))
   7196                          "init: array designator out of range" index))
   7197                    (else 0)))
   7198                 (piece
   7199                 (let ((p
   7200                        (cond
   7201                          ((at-punct? ps 'lbrace)
   7202                           (advance ps)
   7203                           (%global-init-elem ps elem #f))
   7204                          (else
   7205                           (%global-init-elem ps elem #t)))))
   7206                   ;; Inter-item comma: consume except for the comma
   7207                   ;; following our LAST item in no-brace mode — that
   7208                   ;; one belongs to the enclosing parent.
   7209                   (cond
   7210                     (brace?
   7211                      (cond ((at-punct? ps 'comma) (advance ps))))
   7212                     (else
   7213                      ;; no-brace: consume comma only if more items
   7214                      ;; remain in our quota.
   7215                      (cond
   7216                        ((and (< (+ index 1)
   7217                                 (cond ((< decl 0) 0) (else decl)))
   7218                              (at-punct? ps 'comma))
   7219                         (advance ps)))))
   7220                   p))
   7221                 (next (+ index 1)))
   7222            (lp (cons (cons (* index esize) piece) entries)
   7223                next (max count next))))))))
   7224 
   7225 (define (%piece-bytesize p)
   7226   ;; Output width of one piece (cf. %cg-init-piece->bv): a bv emits
   7227   ;; one byte per element; a (label-ref . _) emits a target-word slot.
   7228   (cond
   7229     ((bytes? p) (bytevector-length p))
   7230     ((and (pair? p) (eq? (car p) 'label-ref)) %CC-WORD-BYTES)
   7231     (else (die #f "init: unknown piece" p))))
   7232 
   7233 (define (%pieces-bytesize ps-list)
   7234   (let loop ((xs ps-list) (n 0))
   7235     (cond ((null? xs) n)
   7236           (else (loop (cdr xs) (+ n (%piece-bytesize (car xs))))))))
   7237 
   7238 (define (%merge-init-entries entries total-size)
   7239   ;; entries: list of (abs-offset . piece-list), in source order.
   7240   ;; Sort stably by offset (later writes to the same offset win, per C
   7241   ;; designated-init semantics) and emit pad pieces in any gaps and at
   7242   ;; the tail. Preserves label-ref pieces — we never merge them into a
   7243   ;; flat bv.
   7244   (let* ((sorted (%init-stable-sort-by-offset entries))
   7245          (out
   7246           (let walk ((xs sorted) (cursor 0) (acc '()))
   7247             (cond
   7248               ((null? xs)
   7249                (cond
   7250                  ((< cursor total-size)
   7251                   (reverse (cons (%pad-piece (- total-size cursor)) acc)))
   7252                  (else (reverse acc))))
   7253               (else
   7254                (let* ((e        (car xs))
   7255                       (eoff     (car e))
   7256                       (epieces  (cdr e))
   7257                       (esize    (%pieces-bytesize epieces))
   7258                       (acc1     (cond
   7259                                   ((> eoff cursor)
   7260                                    (cons (%pad-piece (- eoff cursor)) acc))
   7261                                   (else acc)))
   7262                       (acc2     (append (reverse epieces) acc1)))
   7263                  (walk (cdr xs) (+ eoff esize) acc2)))))))
   7264     out))
   7265 
   7266 (define (%init-stable-sort-by-offset entries)
   7267   ;; Insertion sort, stable by source order for ties. n is small (one
   7268   ;; entry per initialized field) so O(n^2) is fine.
   7269   (let lp ((xs entries) (acc '()))
   7270     (cond
   7271       ((null? xs) acc)
   7272       (else
   7273        (let ((e (car xs)))
   7274          (lp (cdr xs)
   7275              (let ins ((ys acc) (head '()))
   7276                (cond
   7277                  ((null? ys)
   7278                   (append (reverse head) (list e)))
   7279                  ((<= (car e) (car (car ys)))
   7280                   (append (reverse head) (cons e ys)))
   7281                  (else
   7282                   (ins (cdr ys) (cons (car ys) head)))))))))))
   7283 
   7284 (define (%parse-init-struct-list ps ty)
   7285   (%parse-init-struct-list/mode ps ty #t))
   7286 
   7287 (define (%parse-init-struct-list/mode ps ty brace?)
   7288   ;; Struct/union initializer; assumes `{` already consumed when brace?.
   7289   ;; In no-brace mode (brace elision, C99 §6.7.8 ¶22), terminate when
   7290   ;; positional fields are exhausted, when a `}` is seen (belongs to
   7291   ;; the enclosing aggregate), or when a designator (`.`) appears (it
   7292   ;; targets the enclosing aggregate). Doesn't consume the trailing
   7293   ;; comma after the last field — that belongs to the parent list.
   7294   (let* ((fields (%init-struct-fields ty))
   7295          (size   (ctype-size ty))
   7296          (union? (eq? (ctype-kind ty) 'union)))
   7297     (let lp ((entries '()) (rest fields))
   7298       (cond
   7299         ((cond (brace? (at-punct? ps 'rbrace))
   7300                (else (or (null? rest)
   7301                          (at-punct? ps 'rbrace)
   7302                          (at-punct? ps 'dot)
   7303                          ;; Union in brace-elision mode: take one
   7304                          ;; member then return — the next sibling
   7305                          ;; initializer belongs to the parent
   7306                          ;; (C99 §6.7.8 ¶22 + union has one active
   7307                          ;; member at a time).
   7308                          (and union? (pair? entries)))))
   7309          (cond (brace? (advance ps)))
   7310          (%merge-init-entries (reverse entries) size))
   7311         (else
   7312          (let* ((designated? (at-punct? ps 'dot))
   7313                 (target
   7314                 (cond
   7315                   (designated?
   7316                    (%init-field-designator ps fields))
   7317                    ((null? rest)
   7318                     (die (tok-loc (peek ps)) "init: too many fields"))
   7319                    (else (car rest))))
   7320                 (fname  (car target))
   7321                 (fty    (car (cdr target)))
   7322                 (foff   (car (cddr target)))
   7323                 (piece-list
   7324                  (cond
   7325                    ((at-punct? ps 'lbrace)
   7326                     (advance ps)
   7327                     (%global-init-elem ps fty #f))
   7328                    (else
   7329                     (%global-init-elem ps fty #t))))
   7330                 (rest1
   7331                  (cond
   7332                    ;; designated init: drop fields up to and including target
   7333                    (designated? (%init-drop-thru-field fields fname))
   7334                    (else (cdr rest)))))
   7335            ;; Inter-item comma: consume except for the comma after our
   7336            ;; LAST field in no-brace mode (belongs to enclosing list).
   7337            (cond
   7338              (brace?
   7339               (cond ((at-punct? ps 'comma) (advance ps))))
   7340              (else
   7341               (cond ((and (not (null? rest1))
   7342                           ;; Union in brace-elision mode terminates
   7343                           ;; after one element regardless of rest1;
   7344                           ;; that means the comma belongs to the parent.
   7345                           (not union?)
   7346                           (at-punct? ps 'comma))
   7347                      (advance ps)))))
   7348            (lp (cons (cons foff piece-list) entries) rest1)))))))
   7349 
   7350 ;; ----- Local aggregate initializers ------------------------------------
   7351 ;; Emits per-element store sequences via cg ops into the slot of `sm`
   7352 ;; (a 'var sym whose slot is the frame offset). Assumes the assignment
   7353 ;; `=` has already been consumed.
   7354 (define (parse-init-local-aggregate ps sm ty)
   7355   (pmatch (peek ps)
   7356     ;; Local char[] = "string" — fill from string bytes.
   7357     (($ tok? (kind STR) (value ,s))
   7358      (guard (and (eq? (ctype-kind ty) 'arr)
   7359                  (let ((et (car (ctype-ext ty))))
   7360                    (or (eq? et %t-i8) (eq? et %t-u8)))))
   7361      (advance ps)
   7362      ;; Note: for inferred-length (`int x[] = "..."`) auto arrays the
   7363      ;; sm-type still records the original (size=-1) ctype — `sizeof(x)`
   7364      ;; in the body would not see the resolved length. The slot is also
   7365      ;; sized off the original (= 1 byte), so the path is pre-existing
   7366      ;; broken; we don't paper over it here. Real C bootstrap code uses
   7367      ;; statics/globals for inferred-length arrays.
   7368      (let* ((slen (bytevector-length s))
   7369             (decl (cdr (ctype-ext ty)))
   7370             (final (cond ((< decl 0) (+ slen 1)) (else decl))))
   7371        ;; Emit byte stores for each char in s, plus NUL for the
   7372        ;; trailing slot if final > slen.
   7373        (let loop ((i 0))
   7374          (cond
   7375            ((>= i final) #t)
   7376            (else
   7377             (let ((b (cond ((< i slen) (bytevector-u8-ref s i))
   7378                            (else 0)))
   7379                   (off (+ (sym-slot sm) i)))
   7380               (%push-frame-elem-lval ps off %t-u8)
   7381               (cg-push-imm (ps-cg ps) %t-u8 b)
   7382               (cg-assign (ps-cg ps))
   7383               (cg-pop (ps-cg ps))
   7384               (loop (+ i 1))))))))
   7385     (($ tok? (kind PUNCT) (value lbrace))
   7386      (advance ps)
   7387      (cond
   7388        ((eq? (ctype-kind ty) 'arr)
   7389         (%parse-init-local-array-list ps sm (sym-slot sm) ty))
   7390        ((or (eq? (ctype-kind ty) 'struct) (eq? (ctype-kind ty) 'union))
   7391         (%parse-init-local-struct-list ps sm (sym-slot sm) ty))
   7392        (else (die #f "init local: brace on scalar?"))))
   7393     (else (die (tok-loc (peek ps)) "init local aggregate?"))))
   7394 
   7395 (define (%push-frame-elem-lval ps base-off ty)
   7396   (cg-push (ps-cg ps) (%opnd 'frame ty base-off #t)))
   7397 
   7398 (define (%parse-init-local-array-list ps sm base-off ty)
   7399   (%parse-init-local-array-list/mode ps sm base-off ty #t))
   7400 
   7401 (define (%parse-init-local-array-list/mode ps sm base-off ty brace?)
   7402   (let* ((elem (%init-array-elem-type ty))
   7403          (esize (ctype-size elem))
   7404          (decl  (%init-array-decl-len ty)))
   7405     (let lp ((i 0))
   7406       (cond
   7407         ((cond (brace? (at-punct? ps 'rbrace))
   7408                (else (or (>= i (cond ((< decl 0) 0) (else decl)))
   7409                          (at-punct? ps 'rbrace)
   7410                          (at-punct? ps 'dot)
   7411                          (at-punct? ps 'lbrack))))
   7412          (cond (brace? (advance ps)))
   7413          ;; Inferred-length auto path is pre-existing broken (slot
   7414          ;; allocated off size=-1, sm-type unfixed). See note in
   7415          ;; parse-init-local-aggregate STR branch.
   7416          ;; Zero out remaining slots if any (declared length > i).
   7417          (let ((final (cond ((< decl 0) i) (else decl))))
   7418            (let zlp ((k i))
   7419              (cond
   7420                ((>= k final) #t)
   7421                (else
   7422                 (let ((off (+ base-off (* k esize))))
   7423                   (cond
   7424                     ((or (eq? (ctype-kind elem) 'arr)
   7425                          (eq? (ctype-kind elem) 'struct)
   7426                          (eq? (ctype-kind elem) 'union))
   7427                      ;; Zero each byte in this aggregate slot.
   7428                      (let zb ((j 0))
   7429                        (cond
   7430                          ((>= j esize) #t)
   7431                          (else
   7432                           (%push-frame-elem-lval ps (+ off j) %t-u8)
   7433                           (cg-push-imm (ps-cg ps) %t-u8 0)
   7434                           (cg-assign (ps-cg ps))
   7435                           (cg-pop (ps-cg ps))
   7436                           (zb (+ j 1))))))
   7437                     (else
   7438                      (%push-frame-elem-lval ps off elem)
   7439                      (cg-push-imm (ps-cg ps) elem 0)
   7440                      (cg-assign (ps-cg ps))
   7441                      (cg-pop (ps-cg ps)))))
   7442                 (zlp (+ k 1)))))))
   7443         (else
   7444          (let ((eoff (+ base-off (* i esize))))
   7445            (cond
   7446              ((at-punct? ps 'lbrace)
   7447               (advance ps)
   7448               (%local-init-elem ps sm eoff elem #f))
   7449              (else
   7450               (%local-init-elem ps sm eoff elem #t)))
   7451            ;; Inter-item comma: in no-brace mode, don't eat the comma
   7452            ;; that follows our LAST item (it belongs to the parent).
   7453            (cond
   7454              (brace?
   7455               (cond ((at-punct? ps 'comma) (advance ps))))
   7456              (else
   7457               (cond ((and (< (+ i 1)
   7458                              (cond ((< decl 0) 0) (else decl)))
   7459                           (at-punct? ps 'comma))
   7460                      (advance ps)))))
   7461            (lp (+ i 1))))))))
   7462 
   7463 (define (%bv-in-list? bv xs)
   7464   (cond ((null? xs) #f)
   7465         ((bytes=? bv (car xs)) #t)
   7466         (else (%bv-in-list? bv (cdr xs)))))
   7467 
   7468 ;; Does any leaf-name of `f` (a struct/union field tuple, possibly with
   7469 ;; a nameless anon-aggregate type) appear in `seen`? Used by the
   7470 ;; local-struct zero-pass to skip an anonymous member whose sub-field
   7471 ;; was already written through a designator like `.a` (C11 §6.7.2.1).
   7472 (define (%anon-touched? f seen)
   7473   (let ((fn (car f)))
   7474     (cond
   7475       (fn (%bv-in-list? fn seen))
   7476       (else
   7477        (let ((k (ctype-kind (cadr f))))
   7478          (cond
   7479            ((or (eq? k 'struct) (eq? k 'union))
   7480             (let lp ((xs (car (cddr (ctype-ext (cadr f))))))
   7481               (cond
   7482                 ((null? xs) #f)
   7483                 ((%anon-touched? (car xs) seen) #t)
   7484                 (else (lp (cdr xs))))))
   7485            (else #f)))))))
   7486 
   7487 (define (%emit-zero-field ps base-off f)
   7488   ;; Keep the absolute byte-offset expression explicit: base + field offset,
   7489   ;; then add the byte index used by the zero-fill loop.
   7490   (let* ((fty       (car (cdr f)))
   7491          (foff      (car (cddr f)))
   7492          (fsize     (ctype-size fty))
   7493          (start-off (+ base-off foff)))
   7494     (let zb ((j 0))
   7495       (cond
   7496         ((>= j fsize) #t)
   7497         (else
   7498          (%push-frame-elem-lval ps (+ start-off j) %t-u8)
   7499          (cg-push-imm (ps-cg ps) %t-u8 0)
   7500          (cg-assign (ps-cg ps))
   7501          (cg-pop (ps-cg ps))
   7502          (zb (+ j 1)))))))
   7503 
   7504 (define (%parse-init-local-struct-list ps sm base-off ty)
   7505   (%parse-init-local-struct-list/mode ps sm base-off ty #t))
   7506 
   7507 (define (%parse-init-local-struct-list/mode ps sm base-off ty brace?)
   7508   ;; Track each initialized field by name in `seen`; at the closing brace
   7509   ;; zero every field NOT in `seen`. Tracking by name (rather than
   7510   ;; positional "remaining" fields) handles a designator jumping
   7511   ;; backwards correctly — e.g. `{.y = 5}` must still zero `x`.
   7512   ;; C requires every unmentioned member of an aggregate with at least
   7513   ;; one designator/initializer to be zeroed (C11 §6.7.9 ¶21).
   7514   ;;
   7515   ;; In no-brace mode (brace elision, C99 §6.7.8 ¶22): terminate when
   7516   ;; positional fields exhausted, on `}` (parent's), or on `.` designator
   7517   ;; (targets parent). Don't consume trailing comma after our last field.
   7518   (let ((fields (%init-struct-fields ty)))
   7519     (let lp ((rest fields) (seen '()))
   7520       (cond
   7521         ((cond (brace? (at-punct? ps 'rbrace))
   7522                (else (or (null? rest)
   7523                          (at-punct? ps 'rbrace)
   7524                          (at-punct? ps 'dot))))
   7525          (cond (brace? (advance ps)))
   7526          (for-each
   7527            (lambda (f)
   7528              (cond ((not (%anon-touched? f seen))
   7529                     (%emit-zero-field ps base-off f))))
   7530            fields))
   7531         (else
   7532          (let* ((designated? (at-punct? ps 'dot))
   7533                 (target
   7534                 (cond
   7535                   (designated?
   7536                    (%init-field-designator ps fields))
   7537                    ((null? rest)
   7538                     (die (tok-loc (peek ps)) "init: too many fields"))
   7539                    (else (car rest))))
   7540                 (fname (car target))
   7541                 (fty   (car (cdr target)))
   7542                 (foff  (car (cddr target)))
   7543                 (eoff  (+ base-off foff)))
   7544            (cond
   7545              ((at-punct? ps 'lbrace)
   7546               (advance ps)
   7547               (%local-init-elem ps sm eoff fty #f))
   7548              (else
   7549               (%local-init-elem ps sm eoff fty #t)))
   7550            (let ((rest1
   7551                   (cond
   7552                     (designated? (%init-drop-thru-field fields fname))
   7553                     (else (cdr rest)))))
   7554              ;; Inter-item comma: in no-brace mode, don't eat the comma
   7555              ;; that follows our LAST field (belongs to enclosing list).
   7556              (cond
   7557                (brace?
   7558                 (cond ((at-punct? ps 'comma) (advance ps))))
   7559                (else
   7560                 (cond ((and (not (null? rest1))
   7561                             (at-punct? ps 'comma))
   7562                        (advance ps)))))
   7563              (lp rest1 (cons fname seen)))))))))
   7564 
   7565 
   7566 ;; parse-fn-body: bind the fn-sym for recursive lookup, then parse the
   7567 ;; body. Persistent entries remain reachable through the world graph;
   7568 ;; function-local parser state becomes collectible after the body.
   7569 (define (parse-fn-body ps sto name dt)
   7570   (scope-bind! ps name (%sym name 'fn (or sto 'extern) dt #f #t))
   7571   (%parse-fn-body-inner ps name dt))
   7572 
   7573 (define (%parse-fn-body-inner ps name dt)
   7574   (let* ((e (ctype-ext dt)) (ret (car e))
   7575          (par (cadr e)) (var (car (cddr e))))
   7576     (let ((psyms (cg-fn-begin/v (ps-cg ps) name par ret var)))
   7577       (ps-fn-ctx-set! ps
   7578         (%fn-ctx name ret (map cdr psyms) var '() 0))
   7579       (scope-enter! ps)
   7580       (for-each (lambda (p) (scope-bind! ps (car p) (cdr p)))
   7581                 psyms)
   7582       (expect-punct ps 'lbrace)
   7583       (parse-cstmt-body ps)
   7584       (expect-punct ps 'rbrace)
   7585       (scope-leave! ps)
   7586       (ps-fn-ctx-set! ps #f)
   7587       (cg-fn-end (ps-cg ps)))))
   7588 
   7589 (define (parse-stmt ps)
   7590   (pmatch (peek ps)
   7591     (($ tok? (kind PUNCT) (value lbrace)) (parse-cstmt ps))
   7592     (($ tok? (kind KW) (value if))        (parse-if-stmt ps))
   7593     (($ tok? (kind KW) (value while))     (parse-while-stmt ps))
   7594     (($ tok? (kind KW) (value do))        (parse-do-stmt ps))
   7595     (($ tok? (kind KW) (value for))       (parse-for-stmt ps))
   7596     (($ tok? (kind KW) (value switch))    (parse-switch-stmt ps))
   7597     (($ tok? (kind KW) (value return))    (parse-return-stmt ps))
   7598     (($ tok? (kind KW) (value goto))      (parse-goto-stmt ps))
   7599     (($ tok? (kind KW) (value break))
   7600      (advance ps) (expect-punct ps 'semi) (do-break ps))
   7601     (($ tok? (kind KW) (value continue))
   7602      (advance ps) (expect-punct ps 'semi) (do-continue ps))
   7603     (($ tok? (kind KW) (value case))      (parse-case-stmt ps))
   7604     (($ tok? (kind KW) (value default))   (parse-default-stmt ps))
   7605     (($ tok? (kind KW) (value _Static_assert)) (parse-static-assert! ps))
   7606     (($ tok? (kind IDENT))
   7607      (guard (and (eq? (tok-kind (peek2 ps)) 'PUNCT)
   7608                  (eq? (tok-value (peek2 ps)) 'colon)))
   7609      (parse-labelled-stmt ps))
   7610     (else
   7611      (cond ((stmt-starts-decl? ps) (parse-local-decl ps))
   7612            (else (parse-expr-stmt ps))))))
   7613 
   7614 (define (stmt-starts-decl? ps)
   7615   (let ((t (peek ps)))
   7616     (or (%tok-decl-start? ps t)
   7617         ;; Storage classes only appear at declaration position; check here
   7618         ;; rather than fold them into %tok-decl-start? (which is also
   7619         ;; used for cast typenames where storage classes are illegal).
   7620         (pmatch t
   7621           (($ tok? (kind KW) (value ,v))
   7622            (or (eq? v 'auto) (eq? v 'register) (eq? v 'static)
   7623                (eq? v 'extern) (eq? v 'typedef)))
   7624           (else #f)))))
   7625 
   7626 (define (parse-local-decl ps)
   7627   (let-values (((sto b) (parse-decl-spec ps)))
   7628     (cond
   7629       ((at-punct? ps 'semi) (advance ps) #t)
   7630       (else
   7631        (let lp ()
   7632          (let-values (((n t) (parse-declarator ps b)))
   7633            (handle-decl ps sto n t)
   7634            (cond ((at-punct? ps 'comma) (advance ps) (lp))
   7635                  (else (expect-punct ps 'semi) #t))))))))
   7636 
   7637 (define (parse-cstmt ps)
   7638   (expect-punct ps 'lbrace)
   7639   (scope-enter! ps)
   7640   (parse-cstmt-body ps)
   7641   (scope-leave! ps)
   7642   (expect-punct ps 'rbrace) #t)
   7643 
   7644 (define (parse-cstmt-body ps)
   7645   (cond
   7646     ((at-punct? ps 'rbrace) #t)
   7647     ((eq? (tok-kind (peek ps)) 'EOF)
   7648      (die (tok-loc (peek ps)) "EOF in cstmt"))
   7649     (else (parse-stmt ps) (parse-cstmt-body ps))))
   7650 
   7651 (define (parse-compound-stmt ps) (parse-cstmt ps))
   7652 
   7653 (define (parse-if-stmt ps)
   7654   (expect-kw ps 'if)
   7655   (expect-punct ps 'lparen)
   7656   (parse-expr ps) (rval! ps)
   7657   (expect-punct ps 'rparen)
   7658   (cg-ifelse (ps-cg ps)
   7659              (lambda () (parse-stmt ps))
   7660              (lambda ()
   7661                (cond ((at-kw? ps 'else)
   7662                       (advance ps) (parse-stmt ps))
   7663                      (else #t)))))
   7664 
   7665 ;; cg-loop's body-thunk receives the tag from cg; the parser threads
   7666 ;; it into break/continue via loop-ctx.
   7667 
   7668 (define (parse-while-stmt ps)
   7669   (expect-kw ps 'while)
   7670   (expect-punct ps 'lparen)
   7671   (cg-loop (ps-cg ps)
   7672            (lambda () (parse-expr ps) (rval! ps))
   7673            (lambda (tag)
   7674              (expect-punct ps 'rparen)
   7675              (push-loop-ctx! ps 'while tag #t)
   7676              (parse-stmt ps)
   7677              (pop-loop-ctx! ps))) #t)
   7678 
   7679 (define (parse-do-stmt ps)
   7680   (expect-kw ps 'do)
   7681   ;; `continue` in a do-while must jump to the *cond test* (C11
   7682   ;; §6.8.6.2 ¶2), not to the top of the body. The scoped loop labels
   7683   ;; `.top` at the condition test and `.end` after the loop, so bare
   7684   ;; %continue / %break bind through hex2++ local lookup.
   7685   ;;
   7686   ;; Layout:
   7687   ;;   .scope
   7688   ;;   :.body
   7689   ;;     <body>
   7690   ;;   :.top                 ; %continue jumps here
   7691   ;;     <cond>
   7692   ;;     %if_eqz(c, %break)
   7693   ;;   %b(&.body)
   7694   ;;   :.end
   7695   ;;   .endscope
   7696   (let* ((cg (ps-cg ps))
   7697          (tag (%cg-fresh-loop-tag cg)))
   7698     (%cg-emit-many cg (list ".scope\n"
   7699                             ":.body\n"))
   7700     (push-loop-ctx! ps 'do tag #t)
   7701     (parse-stmt ps)
   7702     (pop-loop-ctx! ps)
   7703     (expect-kw ps 'while) (expect-punct ps 'lparen)
   7704     (%cg-emit-many cg (list ":.top\n"))
   7705     (parse-expr ps) (rval! ps)
   7706     (expect-punct ps 'rparen) (expect-punct ps 'semi)
   7707     (let ((c (cg-pop cg)))
   7708       (%cg-load-truth-into cg c 't0)
   7709       (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
   7710     (%cg-emit-many cg (list "%b(&.body)\n"
   7711                             ":.end\n"
   7712                             ".endscope\n")))
   7713   #t)
   7714 
   7715 (define (parse-for-stmt ps)
   7716   (expect-kw ps 'for) (expect-punct ps 'lparen)
   7717   (scope-enter! ps)
   7718   (cond
   7719     ((at-punct? ps 'semi) (advance ps))
   7720     ((stmt-starts-decl? ps) (parse-local-decl ps))
   7721     (else (parse-expr ps) (cg-pop (ps-cg ps))
   7722           (expect-punct ps 'semi)))
   7723   (let* ((cg (ps-cg ps))
   7724          (cond-toks (cond
   7725                       ((at-punct? ps 'semi) '())
   7726                       (else (collect-til-top-punct ps 'semi "EOF in for-cond"))))
   7727          (_ (expect-punct ps 'semi))
   7728          (step-toks (collect-til-rparen ps))
   7729          (_ (expect-punct ps 'rparen))
   7730          (tag (%cg-fresh-loop-tag cg)))
   7731     ;; A C `continue` in a for-loop must run the step expression before
   7732     ;; retesting the condition. Arrange the loop as:
   7733     ;;   jump test; top: step; test: condition; body; jump top
   7734     (%cg-emit-many cg (list ".scope\n"
   7735                             "%b(&.test)\n"
   7736                             ":.top\n"))
   7737     (parse-saved-expr-stmt ps step-toks)
   7738     (%cg-emit-many cg (list ":.test\n"))
   7739     (cond
   7740       ((null? cond-toks) (cg-push-imm cg %t-i32 1))
   7741       (else (parse-saved-expr ps cond-toks) (rval! ps)))
   7742     (let ((c (cg-pop cg)))
   7743       (%cg-load-truth-into cg c 't0)
   7744       (%cg-emit-many cg (list "%if_eqz(t0, { %break })\n")))
   7745     (push-loop-ctx! ps 'for tag #t)
   7746     (parse-stmt ps)
   7747     (pop-loop-ctx! ps)
   7748     (%cg-emit-many cg (list "%b(&.top)\n"
   7749                             ":.end\n"
   7750                             ".endscope\n")))
   7751   (scope-leave! ps) #t)
   7752 
   7753 (define (parse-saved-expr ps toks)
   7754   (let ((sv (ps-iter ps)))
   7755     (ps-iter-set! ps (make-list-iter (append toks (list (make-tok 'EOF #f #f)))))
   7756     (parse-expr ps)
   7757     (ps-iter-set! ps sv)))
   7758 
   7759 (define (parse-saved-expr-stmt ps toks)
   7760   (cond
   7761     ((null? toks) #t)
   7762     (else (parse-saved-expr ps toks) (cg-pop (ps-cg ps)))))
   7763 
   7764 (define (collect-til-top-punct ps punct err)
   7765   (let loop ((acc '()) (d 0))
   7766     (let ((t (peek ps)))
   7767       (cond
   7768         ((eq? (tok-kind t) 'EOF)
   7769          (die (tok-loc t) err))
   7770         ((and (zero? d) (eq? (tok-kind t) 'PUNCT)
   7771               (eq? (tok-value t) punct)) (reverse acc))
   7772         (else
   7773          (let ((nt (advance ps)))
   7774            (loop (cons nt acc)
   7775                  (cond ((not (eq? (tok-kind nt) 'PUNCT)) d)
   7776                        ((or (eq? (tok-value nt) 'lparen)
   7777                             (eq? (tok-value nt) 'lbrack)) (+ d 1))
   7778                        ((or (eq? (tok-value nt) 'rparen)
   7779                             (eq? (tok-value nt) 'rbrack)) (- d 1))
   7780                        (else d)))))))))
   7781 
   7782 (define (collect-til-rparen ps)
   7783   (collect-til-top-punct ps 'rparen "EOF in for-step"))
   7784 
   7785 (define (parse-switch-stmt ps)
   7786   (expect-kw ps 'switch) (expect-punct ps 'lparen)
   7787   (parse-expr ps) (rval! ps)
   7788   (expect-punct ps 'rparen)
   7789   ;; Switch's break-target tag is the swctx's end-tag — cg owns it,
   7790   ;; and we read it back so cg-break inside the switch body emits a
   7791   ;; tag cg actually labels.
   7792   (let* ((sw (cg-switch-begin (ps-cg ps)))
   7793          (tg (swctx-end-tag sw)))
   7794     (push-loop-ctx-sw! ps 'switch tg sw)
   7795     (parse-stmt ps)
   7796     (pop-loop-ctx! ps)
   7797     (cg-switch-end (ps-cg ps) sw)))
   7798 
   7799 (define (parse-case-stmt ps)
   7800   (expect-kw ps 'case)
   7801   (let ((v (parse-const-int ps)))
   7802     (expect-punct ps 'colon)
   7803     (cg-switch-case (ps-cg ps) (innermost-sw ps) v)
   7804     (parse-stmt ps)))
   7805 
   7806 (define (parse-default-stmt ps)
   7807   (expect-kw ps 'default) (expect-punct ps 'colon)
   7808   (cg-switch-default (ps-cg ps) (innermost-sw ps))
   7809   (parse-stmt ps))
   7810 
   7811 (define (parse-return-stmt ps)
   7812   (expect-kw ps 'return)
   7813   (cond
   7814     ((at-punct? ps 'semi) (advance ps) (cg-return (ps-cg ps)))
   7815     (else
   7816      (let* ((fc  (ps-fn-ctx ps))
   7817             (rty (and fc (fn-ctx-return-type fc)))
   7818             (rk  (and rty (ctype-kind rty))))
   7819        (cond
   7820          ;; Struct/union return — leave the source as a struct lval;
   7821          ;; cg-return copies bytes into the function's return slot.
   7822          ;; (P1.md §Arguments and return values.)
   7823          ((or (eq? rk 'struct) (eq? rk 'union))
   7824           (parse-expr ps)
   7825           (cg-return (ps-cg ps)))
   7826          (else
   7827           (parse-expr ps) (rval! ps)
   7828           (cond
   7829             ((and fc (not (eq? rk 'void)))
   7830              (cg-cast (ps-cg ps) rty))
   7831             (else #t))
   7832           (cg-return (ps-cg ps)))))
   7833      (expect-punct ps 'semi))))
   7834 
   7835 (define (parse-goto-stmt ps)
   7836   (expect-kw ps 'goto)
   7837   (let ((t (advance ps)))
   7838     (cond ((eq? (tok-kind t) 'IDENT)
   7839            (cg-goto (ps-cg ps) (tok-value t)))
   7840           (else (die (tok-loc t) "label?"))))
   7841   (expect-punct ps 'semi))
   7842 
   7843 (define (parse-labelled-stmt ps)
   7844   (let ((t (advance ps)))
   7845     (expect-punct ps 'colon)
   7846     (cg-emit-label (ps-cg ps) (tok-value t))
   7847     (parse-stmt ps)))
   7848 
   7849 (define (parse-expr-stmt ps)
   7850   (cond
   7851     ((at-punct? ps 'semi) (advance ps) #t)
   7852     (else (parse-expr ps) (cg-pop (ps-cg ps))
   7853           (expect-punct ps 'semi))))
   7854 
   7855 (define (push-loop-ctx! ps k tg hc)
   7856   (ps-loops-set! ps (cons (%loop-ctx k tg hc) (ps-loops ps))))
   7857 (define (push-loop-ctx-sw! ps k tg sw)
   7858   (ps-loops-set! ps
   7859     (cons (%loop-ctx k (cons tg sw) #f) (ps-loops ps))))
   7860 (define (pop-loop-ctx! ps)
   7861   (ps-loops-set! ps (cdr (ps-loops ps))))
   7862 (define (do-break ps)
   7863   (let ((c (innermost-loop ps)))
   7864     (cond
   7865       ((not c) (die #f "break outside"))
   7866       ((eq? (loop-ctx-kind c) 'switch)
   7867        (cg-break (ps-cg ps) (car (loop-ctx-tag c))))
   7868       (else (cg-break (ps-cg ps) (loop-ctx-tag c))))))
   7869 (define (do-continue ps)
   7870   (let ((c (innermost-cont ps)))
   7871     (cond ((not c) (die #f "cont outside"))
   7872           (else (cg-continue (ps-cg ps) (loop-ctx-tag c))))))
   7873 (define (innermost-loop ps)
   7874   (cond ((null? (ps-loops ps)) #f) (else (car (ps-loops ps)))))
   7875 (define (innermost-cont ps)
   7876   (let lp ((xs (ps-loops ps)))
   7877     (cond ((null? xs) #f)
   7878           ((eq? (loop-ctx-kind (car xs)) 'switch) (lp (cdr xs)))
   7879           (else (car xs)))))
   7880 (define (innermost-sw ps)
   7881   (let lp ((xs (ps-loops ps)))
   7882     (cond ((null? xs) (die #f "case outside switch"))
   7883           ((eq? (loop-ctx-kind (car xs)) 'switch)
   7884            (cdr (loop-ctx-tag (car xs))))
   7885           (else (lp (cdr xs))))))
   7886 
   7887 (define %binop-bp
   7888   (list
   7889     (cons 'comma      (cons 1 2))
   7890     (cons 'assign     (cons 4 3)) (cons 'plus-eq (cons 4 3))
   7891     (cons 'minus-eq   (cons 4 3)) (cons 'star-eq (cons 4 3))
   7892     (cons 'slash-eq   (cons 4 3)) (cons 'pct-eq  (cons 4 3))
   7893     (cons 'shl-eq     (cons 4 3)) (cons 'shr-eq  (cons 4 3))
   7894     (cons 'amp-eq     (cons 4 3)) (cons 'caret-eq (cons 4 3))
   7895     (cons 'bar-eq     (cons 4 3)) (cons 'qmark   (cons 6 5))
   7896     (cons 'lor (cons 10 11)) (cons 'land (cons 20 21))
   7897     (cons 'bar (cons 30 31)) (cons 'caret (cons 40 41))
   7898     (cons 'amp (cons 50 51))
   7899     (cons 'eq2 (cons 60 61)) (cons 'ne (cons 60 61))
   7900     (cons 'lt (cons 70 71)) (cons 'le (cons 70 71))
   7901     (cons 'gt (cons 70 71)) (cons 'ge (cons 70 71))
   7902     (cons 'shl (cons 80 81)) (cons 'shr (cons 80 81))
   7903     (cons 'plus (cons 90 91)) (cons 'minus (cons 90 91))
   7904     (cons 'star (cons 100 101)) (cons 'slash (cons 100 101))
   7905     (cons 'pct (cons 100 101))))
   7906 
   7907 (define (binop-bp-of s) (alist-ref/eq s %binop-bp))
   7908 
   7909 (define (punct-to-cgop s)
   7910   (cond ((eq? s 'plus)  'add) ((eq? s 'minus) 'sub)
   7911         ((eq? s 'star)  'mul) ((eq? s 'slash) 'div)
   7912         ((eq? s 'pct)   'rem) ((eq? s 'amp)   'and)
   7913         ((eq? s 'bar)   'or)  ((eq? s 'caret) 'xor)
   7914         ((eq? s 'shl)   'shl) ((eq? s 'shr)   'shr)
   7915         ((eq? s 'eq2)   'eq)  ((eq? s 'ne)    'ne)
   7916         ((eq? s 'lt)    'lt)  ((eq? s 'le)    'le)
   7917         ((eq? s 'gt)    'gt)  ((eq? s 'ge)    'ge)
   7918         (else (die #f "binop" s))))
   7919 
   7920 (define (compound-op s)
   7921   (cond ((eq? s 'plus-eq)  'add) ((eq? s 'minus-eq) 'sub)
   7922         ((eq? s 'star-eq)  'mul) ((eq? s 'slash-eq) 'div)
   7923         ((eq? s 'pct-eq)   'rem) ((eq? s 'shl-eq)   'shl)
   7924         ((eq? s 'shr-eq)   'shr) ((eq? s 'amp-eq)   'and)
   7925         ((eq? s 'caret-eq) 'xor) ((eq? s 'bar-eq)   'or)
   7926         (else #f)))
   7927 
   7928 (define (parse-expr ps) (parse-expr-bp ps 0))
   7929 
   7930 (define (parse-expr-bp ps mn)
   7931   (parse-unary ps) (parse-binary-rhs ps mn))
   7932 
   7933 (define (parse-binary-rhs ps mn)
   7934   (let ((t (peek ps)))
   7935     (cond
   7936       ((not (eq? (tok-kind t) 'PUNCT)) #t)
   7937       (else
   7938        (let ((bp (binop-bp-of (tok-value t))))
   7939          (cond
   7940            ((not bp) #t)
   7941            ((< (car bp) mn) #t)
   7942            (else
   7943             (let ((op (tok-value t)) (rb (cdr bp)))
   7944               (advance ps)
   7945               (cond
   7946                 ((eq? op 'comma)
   7947                  ;; lhs has been parsed; discard it and evaluate rhs.
   7948                  ;; Result of the comma expr is the rhs's rval.
   7949                  (cg-pop (ps-cg ps))
   7950                  (parse-expr-bp ps rb) (rval! ps))
   7951                 ((eq? op 'assign)
   7952                  ;; Struct/union assignment must memcpy the whole
   7953                  ;; aggregate. The scalar cg-assign path loads/stores
   7954                  ;; via a single 8-byte register, dropping any field at
   7955                  ;; offset >= 8. Detect via the lhs (already on the
   7956                  ;; vstack) and route to cg-assign-struct, which keeps
   7957                  ;; rhs as an lvalue and emits a memcpy.
   7958                  (let* ((lhs-top (cg-top (ps-cg ps)))
   7959                         (lk (cond ((and (opnd? lhs-top) (opnd-lval? lhs-top))
   7960                                    (ctype-kind (opnd-type lhs-top)))
   7961                                   (else #f))))
   7962                    (cond
   7963                      ((or (eq? lk 'struct) (eq? lk 'union))
   7964                       (parse-expr-bp ps rb)
   7965                       (cg-assign-struct (ps-cg ps)))
   7966                      (else
   7967                       (parse-expr-bp ps rb) (rval! ps)
   7968                       (cg-assign (ps-cg ps))))))
   7969                 ((compound-op op)
   7970                  (let ((b (compound-op op)))
   7971                    (cg-dup (ps-cg ps))
   7972                    (cg-load (ps-cg ps))
   7973                    (parse-expr-bp ps rb) (rval! ps)
   7974                    ;; Skip the usual arithmetic conversion for shift
   7975                    ;; compounds (`<<=` / `>>=`) so the lhs's signedness
   7976                    ;; survives; cg-binop's shr branch then picks the
   7977                    ;; right arithmetic-vs-logical opcode.
   7978                    (cond ((or (eq? b 'shl) (eq? b 'shr)) #t)
   7979                          (else (cg-arith-conv (ps-cg ps))))
   7980                    (cg-binop (ps-cg ps) b)
   7981                    (cg-assign (ps-cg ps))))
   7982                 ((eq? op 'qmark)
   7983                  (rval! ps)
   7984                  (cg-ifelse-merge (ps-cg ps)
   7985                             (lambda ()
   7986                               (parse-expr-bp ps 0) (rval! ps))
   7987                             (lambda ()
   7988                               (expect-punct ps 'colon)
   7989                               (parse-expr-bp ps rb) (rval! ps))))
   7990                 ((eq? op 'land)
   7991                  (rval! ps)
   7992                  ;; Both branches must push i32 0/1. Right side is
   7993                  ;; coerced via `cg-cast bool` so the merge slot
   7994                  ;; carries i32 (per §H.2).
   7995                  (cg-ifelse-merge (ps-cg ps)
   7996                             (lambda ()
   7997                               (parse-expr-bp ps rb) (rval! ps)
   7998                               (cg-cast (ps-cg ps) %t-bool)
   7999                               (cg-cast (ps-cg ps) %t-i32))
   8000                             (lambda ()
   8001                               (cg-push-imm (ps-cg ps) %t-i32 0))))
   8002                 ((eq? op 'lor)
   8003                  (rval! ps)
   8004                  (cg-ifelse-merge (ps-cg ps)
   8005                             (lambda ()
   8006                               (cg-push-imm (ps-cg ps) %t-i32 1))
   8007                             (lambda ()
   8008                               (parse-expr-bp ps rb) (rval! ps)
   8009                               (cg-cast (ps-cg ps) %t-bool)
   8010                               (cg-cast (ps-cg ps) %t-i32))))
   8011                 (else
   8012                  (rval! ps) (cg-promote (ps-cg ps))
   8013                  (parse-expr-bp ps rb) (rval! ps)
   8014                  (cg-promote (ps-cg ps))
   8015                  ;; Shifts (C 6.5.7) only require integer promotion of
   8016                  ;; each operand individually; the usual arithmetic
   8017                  ;; conversion would force the lhs into an unsigned
   8018                  ;; common type when the rhs is unsigned, breaking
   8019                  ;; arithmetic-shift semantics for `signed >> unsigned`.
   8020                  (cond ((or (eq? op 'shl) (eq? op 'shr)) #t)
   8021                        (else (cg-arith-conv (ps-cg ps))))
   8022                  (cg-binop (ps-cg ps) (punct-to-cgop op))))
   8023               (parse-binary-rhs ps mn)))))))))
   8024 
   8025 (define (parse-unary ps)
   8026   (pmatch (peek ps)
   8027     (($ tok? (kind PUNCT) (value amp))
   8028      (advance ps) (parse-unary ps)
   8029      (cg-take-addr (ps-cg ps)))
   8030     (($ tok? (kind PUNCT) (value star))
   8031      (advance ps) (parse-unary ps) (rval! ps)
   8032      (cg-push-deref (ps-cg ps)))
   8033     (($ tok? (kind PUNCT) (value plus))
   8034      (advance ps) (parse-unary ps)
   8035      (rval! ps) (cg-promote (ps-cg ps)))
   8036     (($ tok? (kind PUNCT) (value minus))
   8037      (advance ps) (parse-unary ps)
   8038      (rval! ps) (cg-promote (ps-cg ps))
   8039      (cg-unop (ps-cg ps) 'neg))
   8040     (($ tok? (kind PUNCT) (value tilde))
   8041      (advance ps) (parse-unary ps)
   8042      (rval! ps) (cg-promote (ps-cg ps))
   8043      (cg-unop (ps-cg ps) 'bnot))
   8044     (($ tok? (kind PUNCT) (value bang))
   8045      (advance ps) (parse-unary ps) (rval! ps)
   8046      (cg-unop (ps-cg ps) 'lnot))
   8047     (($ tok? (kind PUNCT) (value inc))
   8048      (advance ps) (parse-unary ps)
   8049      (cg-dup (ps-cg ps))
   8050      (cg-load (ps-cg ps))
   8051      (cg-push-imm (ps-cg ps) %t-i32 1)
   8052      (cg-binop (ps-cg ps) 'add) (cg-assign (ps-cg ps)))
   8053     (($ tok? (kind PUNCT) (value dec))
   8054      (advance ps) (parse-unary ps)
   8055      (cg-dup (ps-cg ps))
   8056      (cg-load (ps-cg ps))
   8057      (cg-push-imm (ps-cg ps) %t-i32 1)
   8058      (cg-binop (ps-cg ps) 'sub) (cg-assign (ps-cg ps)))
   8059     (($ tok? (kind PUNCT) (value lparen)) (parse-cast-or-unary ps))
   8060     (($ tok? (kind KW) (value sizeof))
   8061      (advance ps)
   8062      (cond
   8063        ((at-punct? ps 'lparen)
   8064         (advance ps)
   8065         (cond
   8066           ((token-is-decl? ps)
   8067            (let*-values (((_sto bty) (parse-decl-spec ps))
   8068                          ((_n   ty)  (parse-declarator ps bty)))
   8069              (expect-punct ps 'rparen)
   8070              (cg-push-imm (ps-cg ps) %t-word-u
   8071                           (max (ctype-size ty) 0))))
   8072           ;; A string literal has type char[N] until ordinary expression
   8073           ;; conversion.  parse-primary deliberately lowers strings as
   8074           ;; pointers for runtime expressions, so preserve the array bound
   8075           ;; here before entering that path.  Kit's KIT_SLICE_LIT relies on
   8076           ;; exactly this `sizeof("...") - 1` shape during parser setup.
   8077           ((and (eq? (tok-kind (peek ps)) 'STR)
   8078                 (eq? (tok-kind (peek2 ps)) 'PUNCT)
   8079                 (eq? (tok-value (peek2 ps)) 'rparen))
   8080            (let ((n (+ (bytevector-length (tok-value (peek ps))) 1)))
   8081              (advance ps)
   8082              (expect-punct ps 'rparen)
   8083              (cg-push-imm (ps-cg ps) %t-word-u n)))
   8084           (else
   8085            ;; sizeof(EXPR): C semantics — operand is NOT evaluated.
   8086            ;; Snapshot cg state, parse the expr to learn its type,
   8087            ;; then rewind to discard any code emission and vstack
   8088            ;; pushes the parse incurred (e.g. `sizeof(x++)` must not
   8089            ;; increment x). cf. CC.md §Expressions.
   8090            (let ((tag (cg-snapshot (ps-cg ps))))
   8091              (parse-expr ps) (expect-punct ps 'rparen)
   8092              (let* ((tp (cg-top (ps-cg ps)))
   8093                     (sz (max (ctype-size (opnd-type tp)) 0)))
   8094                (cg-rewind (ps-cg ps) tag)
   8095                (cg-push-imm (ps-cg ps) %t-word-u sz))))))
   8096        (else
   8097         ;; sizeof EXPR (no parens) — same no-eval rule.
   8098         (cond
   8099           ((eq? (tok-kind (peek ps)) 'STR)
   8100            (let ((n (+ (bytevector-length (tok-value (peek ps))) 1)))
   8101              (advance ps)
   8102              (cg-push-imm (ps-cg ps) %t-word-u n)))
   8103           (else
   8104            (let ((tag (cg-snapshot (ps-cg ps))))
   8105              (parse-unary ps)
   8106              (let* ((tp (cg-top (ps-cg ps)))
   8107                     (sz (max (ctype-size (opnd-type tp)) 0)))
   8108                (cg-rewind (ps-cg ps) tag)
   8109                (cg-push-imm (ps-cg ps) %t-word-u sz))))))))
   8110     (($ tok? (kind KW) (value _Alignof))
   8111      (advance ps)
   8112      (expect-punct ps 'lparen)
   8113      (cond
   8114        ((token-is-decl? ps)
   8115         (let*-values (((_sto bty) (parse-decl-spec ps))
   8116                       ((_n ty) (parse-declarator ps bty)))
   8117           (expect-punct ps 'rparen)
   8118           (cg-push-imm (ps-cg ps) %t-word-u
   8119                        (max (ctype-align ty) 1))))
   8120        (else
   8121         (let ((tag (cg-snapshot (ps-cg ps))))
   8122           (parse-expr ps)
   8123           (expect-punct ps 'rparen)
   8124           (let* ((tp (cg-top (ps-cg ps)))
   8125                  (al (max (ctype-align (opnd-type tp)) 1)))
   8126             (cg-rewind (ps-cg ps) tag)
   8127             (cg-push-imm (ps-cg ps) %t-word-u al))))))
   8128     (else (parse-postfix ps))))
   8129 
   8130 (define (token-is-decl? ps) (%tok-decl-start? ps (peek ps)))
   8131 
   8132 (define (parse-cast-or-unary ps)
   8133   (cond
   8134     ((or (%tok-decl-start? ps (peek2 ps))
   8135          ;; A leading GNU attribute on the cast typename
   8136          ;; (e.g. `((__attribute__((...)) int(*)(void))ptr)()`) — eaten
   8137          ;; by parse-decl-spec along with the rest of the decl-spec.
   8138          (let ((t (peek2 ps)))
   8139            (and (eq? (tok-kind t) 'KW) (eq? (tok-value t) '__attribute__))))
   8140      (advance ps)
   8141      (let*-values (((_sto bty) (parse-decl-spec ps))
   8142                    ((_n   ty)  (parse-declarator ps bty)))
   8143        (expect-punct ps 'rparen)
   8144        (cond
   8145          ;; (T){ ... } — compound literal (C99 §6.5.2.5). Looks like a
   8146          ;; cast at the typename level but disambiguates on the
   8147          ;; following `{` and is a postfix lvalue, not a cast operator.
   8148          ((at-punct? ps 'lbrace) (parse-compound-literal ps ty))
   8149          (else
   8150           (parse-unary ps)
   8151           ;; Cast operand undergoes lvalue conversion first (C semantics):
   8152           ;; arrays decay to pointers, lvals become rvals. cg-cast then
   8153           ;; bit-casts the resulting rval to the target type.
   8154           (rval! ps)
   8155           (cg-cast (ps-cg ps) ty)))))
   8156     (else (advance ps) (parse-expr ps)
   8157           (expect-punct ps 'rparen)
   8158           (parse-postfix-rest ps))))
   8159 
   8160 ;; --------------------------------------------------------------------
   8161 ;; Compound literals (C99 §6.5.2.5):  (T){ init-list }
   8162 ;;
   8163 ;; Block scope — allocate a fresh frame slot sized for T, drive the
   8164 ;; existing local-aggregate initializer path against it, then push a
   8165 ;; frame lval typed as T. The literal is an lvalue with automatic
   8166 ;; storage tied to the enclosing block, so &literal, literal.field,
   8167 ;; literal[i], byval pass, and array decay all chain through the
   8168 ;; existing primitives (cg-take-addr / cg-push-field / cg-decay-array
   8169 ;; via rval!).
   8170 ;;
   8171 ;; File scope — handled out-of-band in %const-init-piece (incl. its `&`
   8172 ;; arm) via %emit-fs-compound-literal: pieces go to .data under a fresh
   8173 ;; cc__cl_N label and the enclosing initializer takes a (label-ref . LBL)
   8174 ;; piece. Reaching parse-compound-literal at file scope would mean an
   8175 ;; expression context outside an initializer (which file scope doesn't
   8176 ;; have), so this entry point still rejects it.
   8177 ;; --------------------------------------------------------------------
   8178 (define (parse-compound-literal ps ty)
   8179   (cond
   8180     ((not (ps-fn-ctx ps))
   8181      (die (tok-loc (peek ps)) "compound literal at file scope: unsupported")))
   8182   (let* ((sz (max (ctype-size ty) 1))
   8183          (al (max (ctype-align ty) 1))
   8184          (sl (cg-alloc-slot (ps-cg ps) sz al))
   8185          ;; Synthetic sym: parse-init-local-aggregate only reads
   8186          ;; sym-slot at its top-level entry to seed base-off; the
   8187          ;; recursive helpers thread `sm` along but never read other
   8188          ;; fields. The name is unbound and never enters scope.
   8189          (sm (%sym "__cl" 'var 'auto ty sl #t)))
   8190     (cond
   8191       ((or (eq? (ctype-kind ty) 'arr)
   8192            (eq? (ctype-kind ty) 'struct)
   8193            (eq? (ctype-kind ty) 'union))
   8194        (parse-init-local-aggregate ps sm ty))
   8195       (else
   8196        ;; Scalar (T){expr [,]} — parse-init-local-aggregate's brace arm
   8197        ;; only handles aggregates, so emit the single-element store
   8198        ;; here directly.
   8199        (expect-punct ps 'lbrace)
   8200        (cg-push (ps-cg ps) (%opnd 'frame ty sl #t))
   8201        (parse-expr-bp ps 4) (rval! ps)
   8202        (cg-cast (ps-cg ps) ty)
   8203        (cg-assign (ps-cg ps)) (cg-pop (ps-cg ps))
   8204        (cond ((at-punct? ps 'comma) (advance ps)))
   8205        (expect-punct ps 'rbrace)))
   8206     ;; The literal is an lvalue with automatic storage. ctype-size may
   8207     ;; have been resolved by parse-init-local-aggregate (e.g. (int[])
   8208     ;; gets its bound fixed in-place); we re-fetch via the slot's type
   8209     ;; pointer (ty) which the init code mutated.
   8210     (cg-push (ps-cg ps) (%opnd 'frame ty sl #t))
   8211     (parse-postfix-rest ps)))
   8212 
   8213 (define (parse-postfix ps)
   8214   (parse-primary ps) (parse-postfix-rest ps))
   8215 
   8216 (define (parse-postfix-rest ps)
   8217   (let lp ()
   8218     (pmatch (peek ps)
   8219       (($ tok? (kind PUNCT) (value lbrack))
   8220        (advance ps) (rval! ps)
   8221        (parse-expr ps) (rval! ps)
   8222        (expect-punct ps 'rbrack)
   8223        (cg-binop (ps-cg ps) 'add)
   8224        (cg-push-deref (ps-cg ps)) (lp))
   8225       (($ tok? (kind PUNCT) (value lparen))
   8226        (advance ps) (rval-not-fn! ps)
   8227        (let* ((fn-ty   (call-fn-type (ps-cg ps)))
   8228               (n (parse-call-args ps fn-ty))
   8229               ;; has-result? = #f for known void returns. Skips the
   8230               ;; wasted ST a0 → frame-slot spill that cg-call would
   8231               ;; otherwise emit for void calls.
   8232               (has-result?
   8233                (cond
   8234                  ((not fn-ty) #t)
   8235                  ((eq? (ctype-kind (car (ctype-ext fn-ty))) 'void) #f)
   8236                  (else #t))))
   8237          (expect-punct ps 'rparen)
   8238          (cg-call (ps-cg ps) n has-result?)
   8239          ;; Maintain parse's "one rval per expression" invariant so
   8240          ;; comma / parse-expr-stmt / for-init/step pop sites stay
   8241          ;; simple. The placeholder is vstack-only and never
   8242          ;; materialized (cg-pop is a vstack op, no emit).
   8243          (cond ((not has-result?)
   8244                 (cg-push-imm (ps-cg ps) %t-i32 0)))
   8245          (lp)))
   8246       (($ tok? (kind PUNCT) (value dot))
   8247        (advance ps)
   8248        (pmatch (advance ps)
   8249          (($ tok? (kind IDENT) (value ,n))
   8250           (cg-push-field (ps-cg ps) n) (lp))
   8251          (($ tok? (loc ,l)) (die l "expected field name"))))
   8252       (($ tok? (kind PUNCT) (value arrow))
   8253        (advance ps)
   8254        (pmatch (advance ps)
   8255          (($ tok? (kind IDENT) (value ,n))
   8256           ;; ptr -> field: load the pointer to rval, deref to reach
   8257           ;; the struct lval, then push the field.
   8258           (rval! ps)
   8259           (cg-push-deref (ps-cg ps))
   8260           (cg-push-field (ps-cg ps) n) (lp))
   8261          (($ tok? (loc ,l)) (die l "expected field name"))))
   8262       (($ tok? (kind PUNCT) (value inc))
   8263        (advance ps)
   8264        (cg-postinc (ps-cg ps)) (lp))
   8265       (($ tok? (kind PUNCT) (value dec))
   8266        (advance ps)
   8267        (cg-postdec (ps-cg ps)) (lp))
   8268       (else #t))))
   8269 
   8270 ;; call-fn-type cg -> ctype-or-#f
   8271 ;;   The function operand sits at the top of the vstack when
   8272 ;;   parse-call-args runs (just after rval-not-fn!). Its type may be
   8273 ;;   `fn` directly (named callee) or `ptr -> fn` (function pointer).
   8274 ;;   Returns the underlying `fn` ctype, or #f if the operand isn't
   8275 ;;   recognizably callable (callsite still works — no per-arg cast).
   8276 (define (call-fn-type cg)
   8277   (let* ((tp (cg-top cg)))
   8278     (cond
   8279       ((not tp) #f)
   8280       (else
   8281        (let* ((ty (opnd-type tp))
   8282               (k  (ctype-kind ty)))
   8283          (cond
   8284            ((eq? k 'fn) ty)
   8285            ((eq? k 'ptr)
   8286             (let ((pe (ctype-ext ty)))
   8287               (cond ((and pe (eq? (ctype-kind pe) 'fn)) pe)
   8288                     (else #f))))
   8289            (else #f)))))))
   8290 
   8291 ;; param-types-of fn-ty -> (params variadic?)  with a #f fallback.
   8292 (define (call-fn-param-info fn-ty)
   8293   (cond
   8294     ((not fn-ty) (cons '() #f))
   8295     (else
   8296      (let ((ext (ctype-ext fn-ty)))
   8297        (cons (cadr ext) (car (cddr ext)))))))
   8298 
   8299 ;; parse-call-args ps fn-ty -> arg-count
   8300 ;;   Casts each fixed arg to the declared param type (CC.md §K.5).
   8301 ;;   For variadic args (index >= named-arg count, when variadic? = #t)
   8302 ;;   applies cg-promote (CC.md §G.1).
   8303 (define (parse-call-args ps fn-ty)
   8304   (cond
   8305     ((at-punct? ps 'rparen) 0)
   8306     (else
   8307      (let* ((info  (call-fn-param-info fn-ty))
   8308             (params (car info))
   8309             (var?  (cdr info))
   8310             (nfix  (length params)))
   8311        (let lp ((n 0) (rem params))
   8312          (parse-expr-bp ps 4) (rval! ps)
   8313          (cond
   8314            ;; Fixed-arg: cast to declared param type. param entry shape
   8315            ;; is (name . ctype) per cg-fn-begin's contract.
   8316            ((not (null? rem))
   8317             (cg-cast (ps-cg ps) (cdr (car rem))))
   8318            ;; Variadic position (n >= nfix and var? is true): promote.
   8319            (var?
   8320             (cg-promote (ps-cg ps))))
   8321          (let ((m (+ n 1))
   8322                (rest (if (null? rem) '() (cdr rem))))
   8323            (cond ((at-punct? ps 'comma) (advance ps) (lp m rest))
   8324                  (else m))))))))
   8325 
   8326 ;; --------------------------------------------------------------------
   8327 ;; __builtin_va_* (§G.2). va_list / va_start / va_arg / va_end in
   8328 ;; <stdarg.h> alias these. Each is parsed as: name '(' args ')'.
   8329 ;; va_start(ap, last)  — last is parsed and discarded; cg only needs
   8330 ;;   the variadic-first-slot offset, which it already tracks.
   8331 ;; va_arg(ap, T)       — T is a type-name; result rval has that type.
   8332 ;; va_end(ap)          — no-op codegen; just consumes ap.
   8333 ;;
   8334 ;; Pushes a single imm 0 for va_start / va_end so they fit as
   8335 ;; expression statements; va_arg pushes the rval.
   8336 ;; --------------------------------------------------------------------
   8337 (define (parse-builtin-va-start ps)
   8338   (advance ps)                                 ; IDENT
   8339   (expect-punct ps 'lparen)
   8340   (parse-expr-bp ps 4)                         ; ap (must be lval)
   8341   (expect-punct ps 'comma)
   8342   ;; "last" is parsed for syntactic completeness then dropped — cg
   8343   ;; doesn't need it; the variadic-first-slot was determined at
   8344   ;; cg-fn-begin/v time.
   8345   (parse-expr-bp ps 4) (cg-pop (ps-cg ps))
   8346   (expect-punct ps 'rparen)
   8347   (cg-va-start (ps-cg ps))
   8348   ;; Push a placeholder rval so the call expression has a value
   8349   ;; (matches va_start's "void" but our parser expects all
   8350   ;; expressions to leave one rval).
   8351   (cg-push-imm (ps-cg ps) %t-i32 0))
   8352 
   8353 (define (parse-builtin-va-arg ps)
   8354   (advance ps)                                 ; IDENT
   8355   (expect-punct ps 'lparen)
   8356   (parse-expr-bp ps 4)                         ; ap (lval)
   8357   (expect-punct ps 'comma)
   8358   (let*-values (((_sto bty) (parse-decl-spec ps))
   8359                 ((_n   ty)  (parse-declarator ps bty)))
   8360     (expect-punct ps 'rparen)
   8361     (cg-va-arg (ps-cg ps) ty)))
   8362 
   8363 (define (parse-builtin-expect ps)
   8364   ;; GCC `__builtin_expect(EXPR, EXPECTED)` — branch-prediction hint.
   8365   ;; We ignore the hint and emit just the value of EXPR.
   8366   (advance ps)                                  ; IDENT
   8367   (expect-punct ps 'lparen)
   8368   (parse-expr-bp ps 4) (rval! ps)               ; result
   8369   (expect-punct ps 'comma)
   8370   (parse-expr-bp ps 4) (cg-pop (ps-cg ps))      ; expected (drop)
   8371   (expect-punct ps 'rparen))
   8372 
   8373 (define (parse-builtin-va-end ps)
   8374   (advance ps)                                 ; IDENT
   8375   (expect-punct ps 'lparen)
   8376   (parse-expr-bp ps 4)                         ; ap
   8377   (expect-punct ps 'rparen)
   8378   (cg-va-end (ps-cg ps))
   8379   (cg-push-imm (ps-cg ps) %t-i32 0))
   8380 
   8381 (define (parse-builtin-offsetof ps)
   8382   ;; Reuse the constant aggregate-designator parser in ordinary expression
   8383   ;; context; offsetof emits no code beyond materializing its integer value.
   8384   (advance ps)                                 ; IDENT
   8385   (let ((v (%const-builtin-offsetof ps)))
   8386     (cg-push-imm (ps-cg ps) (cdr v) (car v))))
   8387 
   8388 (define (parse-primary ps)
   8389   (let ((t (peek ps)))
   8390     (pmatch t
   8391       (($ tok? (kind INT) (value ,n))
   8392        (advance ps)
   8393        (cg-push-imm (ps-cg ps) (%c-int-type n) (%c-int-raw n)))
   8394       (($ tok? (kind CHAR) (value ,c))
   8395        (advance ps)
   8396        ;; C99 §6.4.4.4: an integer character constant has type int.
   8397        (cg-push-imm (ps-cg ps) %t-i32 c))
   8398       (($ tok? (kind STR) (value ,s))
   8399        (advance ps)
   8400        (cg-push-string (ps-cg ps) s))
   8401       (($ tok? (kind IDENT) (value ,n))
   8402        (cond
   8403          ((bv= n "__builtin_va_start") (parse-builtin-va-start ps))
   8404          ((bv= n "__builtin_va_arg")   (parse-builtin-va-arg ps))
   8405          ((bv= n "__builtin_va_end")   (parse-builtin-va-end ps))
   8406          ((bv= n "__builtin_expect")   (parse-builtin-expect ps))
   8407          ((bv= n "__builtin_offsetof") (parse-builtin-offsetof ps))
   8408          (else
   8409           (let ((sm (scope-lookup ps n)))
   8410             (advance ps)
   8411             (cond
   8412               ((not sm) (die (tok-loc t) "undecl" n))
   8413               ((eq? (sym-kind sm) 'enum-const)
   8414                (cg-push-imm (ps-cg ps) %t-i32 (sym-slot sm)))
   8415               (else (cg-push-sym (ps-cg ps) sm)))))))
   8416       (($ tok? (kind PUNCT) (value lparen))
   8417        (advance ps) (parse-expr ps) (expect-punct ps 'rparen))
   8418       (else (die (tok-loc t) "unexp" (tok-value t))))))
   8419 
   8420 (define (rval! ps)
   8421   (let ((tp (cg-top (ps-cg ps))))
   8422     (cond ((and tp (opnd? tp) (opnd-lval? tp))
   8423            (cg-load (ps-cg ps)))
   8424           (else #t))))
   8425 
   8426 (define (rval-not-fn! ps)
   8427   (let ((tp (cg-top (ps-cg ps))))
   8428     (cond ((and tp (opnd? tp) (opnd-lval? tp)
   8429                 (not (ctype-is-fn? (opnd-type tp))))
   8430            (cg-load (ps-cg ps)))
   8431           (else #t))))
   8432 ;; cc/main.scm — driver. Argv, file I/O, ties phases together.
   8433 
   8434 ;; --------------------------------------------------------------------
   8435 ;; CLI:   cc [--cc-debug] [--cc-trace-emit] [--lib=PFX]
   8436 ;;           <input.c> <output.P1pp>
   8437 ;;
   8438 ;; scheme1 passes (argv) as a list of bvs; argv[0] is "scheme1", argv[1]
   8439 ;; is the catm'd compiler source path, argv[2..] are the user-facing
   8440 ;; positional args. cc-main strips the first two.
   8441 ;; --------------------------------------------------------------------
   8442 
   8443 (define (%cc-slurp path)
   8444   (let ((r (open-input path)))
   8445     (cond ((not (car r))
   8446            (die #f "cannot open input" path)))
   8447     (let* ((p (cdr r))
   8448            (rd (read-all p)))
   8449       (close p)
   8450       (cond ((not (car rd)) (die #f "read failed" path)))
   8451       (cdr rd))))
   8452 
   8453 (define (%cc-write path bv)
   8454   (let ((r (open-output path)))
   8455     (cond ((not (car r))
   8456            (die #f "cannot open output" path)))
   8457     (let ((p (cdr r)))
   8458       (write-bv-fd (port-fd p) bv)
   8459       (close p)
   8460       0)))
   8461 
   8462 (define (%cc-write-cg path cg)
   8463   (let ((r (open-output path)))
   8464     (cond ((not (car r))
   8465            (die #f "cannot open output" path)))
   8466     (let ((p (cdr r)))
   8467       (%cg-write-finalized-fd cg (port-fd p))
   8468       (close p)
   8469       0)))
   8470 
   8471 ;; CC_DEBUG=1 in the env doesn't fly here (no getenv); instead, scan
   8472 ;; argv for a sentinel "--cc-debug" flag. When present, debug-log
   8473 ;; prints heap usage between phases to fd 2.
   8474 (define (%cc-flag? args flag)
   8475   (cond ((null? args) #f)
   8476         ((bv= (car args) flag) #t)
   8477         (else (%cc-flag? (cdr args) flag))))
   8478 
   8479 (define (%cc-strip-flag args flag)
   8480   (cond ((null? args) '())
   8481         ((bv= (car args) flag) (cdr args))
   8482         (else (cons (car args) (%cc-strip-flag (cdr args) flag)))))
   8483 
   8484 ;; --lib=PFX selects library-mode codegen: cc.scm skips the p1_main
   8485 ;; entry stub and trailing :ELF_end (the catm chain supplies them
   8486 ;; from P1/entry-*.P1pp + P1/elf-end.P1pp once), and uses PFX as the
   8487 ;; translation-unit namespace for internal-linkage symbols, strings,
   8488 ;; compound literals, frame macros, and generated labels. Returns
   8489 ;; (values prefix-bv rest-args). PREFIX = "" means exec mode (flag
   8490 ;; absent). PREFIX = "" with the flag present is rejected — silently
   8491 ;; falling back to exec mode would mask a typo'd Makefile rule.
   8492 (define (%cc-take-lib args)
   8493   (let loop ((acc '()) (rest args) (pfx #f))
   8494     (cond
   8495       ((null? rest)
   8496        (values (cond (pfx pfx) (else "")) (reverse acc)))
   8497       ((bv-prefix? "--lib=" (car rest))
   8498        (cond (pfx (die #f "cc: --lib= specified twice")))
   8499        (let* ((arg (car rest))
   8500               (p   (bv-slice arg 6 (bytevector-length arg))))
   8501          (cond ((= 0 (bytevector-length p))
   8502                 (die #f "cc: --lib= requires a non-empty PREFIX")))
   8503          (loop acc (cdr rest) p)))
   8504       (else
   8505        (loop (cons (car rest) acc) (cdr rest) pfx)))))
   8506 
   8507 ;; Predefined macros visible to every translation unit. CCSCM lets
   8508 ;; tests/headers branch on "compiled by cc.scm" — e.g. skip <stdarg.h>
   8509 ;; and use the __builtin_va_* primitives directly.
   8510 (define %cc-initial-defines
   8511   (list (cons "CCSCM" (%macro 'obj '() '()))))
   8512 
   8513 (define (cc-main av)
   8514   (let* ((raw  (cdr (cdr av)))
   8515          (dbg  (%cc-flag? raw "--cc-debug"))
   8516          (a1   (%cc-strip-flag raw "--cc-debug"))
   8517          (tr   (%cc-flag? a1 "--cc-trace-emit"))
   8518          (a2   (%cc-strip-flag a1 "--cc-trace-emit")))
   8519     (cond (dbg (debug-log-on!)))
   8520     (cond (tr  (trace-emit-on!)))
   8521     (let-values (((lib-prefix args) (%cc-take-lib a2)))
   8522       (cond
   8523         ((or (null? args) (null? (cdr args)))
   8524          (die #f "usage: cc [--cc-debug] [--cc-trace-emit] [--lib=PFX] <input.c> <output.P1pp>")))
   8525       (let* ((in-path  (car args))
   8526              (out-path (car (cdr args)))
   8527              (lib?     (cond ((= 0 (bytevector-length lib-prefix)) #f)
   8528                              (else #t))))
   8529         (debug-log "phase=start" "heap" (heap-usage))
   8530         ;; Streaming pipeline: lex → pp → parser → cg, all concurrent.
   8531         ;; Each stage pulls one tok at a time from upstream. Steady-state
   8532         ;; live data is bounded by parser/pp state, not source length.
   8533         (let* ((src      (%cc-slurp in-path))
   8534                (_1       (debug-log "phase=slurp" "heap" (heap-usage)
   8535                                     "src-bytes" (bytevector-length src)))
   8536                (lex-iter (make-lex-iter src in-path))
   8537                (pp-iter  (make-pp-iter lex-iter %cc-initial-defines))
   8538                (cg       (cg-init/v lib? lib-prefix))
   8539                (ps       (make-pstate pp-iter cg)))
   8540           (parse-translation-unit ps)
   8541           (debug-log "phase=parse" "heap" (heap-usage))
   8542           (cg-finalize! cg)
   8543           (debug-log "phase=cg-finish" "heap" (heap-usage)
   8544                      "out-bytes" (cg-output-size cg))
   8545           (%cc-write-cg out-path cg)
   8546           0)))))