commit 53e1036601af2a4c60b08c58b3018f4aa634fe8d
parent 34257e04a2beb2249af0ef36ac3cde0f2e3f86cb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 27 Apr 2026 02:29:09 -0700
cc/cg: variadic incoming-arg window covers indices 0..15
cg-fn-begin/v previously reserved a 4-slot saved-register area for
incoming arg indices 0..3 only. Calls past the 4-arg boundary walked
ap into adjacent frame slots (other locals, ret-slot) and read
garbage.
Extend the pad loop to 16 slots: indices 0..3 are still saved from
a0..a3, indices 4..15 are spilled from LDARG slots 0..11 in the
prologue. va_arg walks the now-contiguous 16-slot window linearly
from the slot whose index equals the named-arg count.
Limit: 15 variadic args after the named ones (was 3). Bump VARARG_WINDOW
if a target needs more. Sufficient for tcc.c's logging shapes.
Lock-in: tests/cc-parse/79-vararg-deep.c — sum(6, 1, 2, 4, 8, 16, 32)
= 63, exercising 1 named + 6 variadic so 3 args cross from registers
into LDARG.
Diffstat:
5 files changed, 72 insertions(+), 29 deletions(-)
diff --git a/cc/cg.scm b/cc/cg.scm
@@ -259,11 +259,15 @@
(define (cg-fn-begin cg name params return-type)
(cg-fn-begin/v cg name params return-type #f))
-;; Variadic-aware variant. variadic? = #t reserves 4 contiguous 8-byte
-;; slots for incoming arg registers a0..a3 (named + spillover), and
-;; saves all 4 into them unconditionally. va_start computes the address
-;; of the first slot past the named-arg count. Limitation: variadic
-;; args beyond index 4 require LDARG and are not supported here.
+;; Variadic-aware variant. variadic? = #t reserves 16 contiguous 8-byte
+;; slots covering incoming arg indices 0..15, populating each from the
+;; appropriate source: a-register for idx 0..3, LDARG slot (idx-4) for
+;; idx 4..15. va_start computes the address of the slot at index =
+;; named-arg count, so va_arg walks linearly through the rest.
+;; Indices 4..15 may be garbage when the caller passed fewer args; user
+;; code stops walking based on a count or sentinel before those slots
+;; are read. Limit of 15 variadic args (after named) is enough for
+;; tcc.c's logging shapes; bump VARARG_WINDOW if you need more.
(define (cg-fn-begin/v cg name params return-type variadic?)
(cg-fn-buf-set! cg (make-buf))
(cg-prologue-buf-set! cg (make-buf))
@@ -290,25 +294,33 @@
((null? ps)
(cond
(variadic?
- ;; Allocate slots for the remaining a-registers up through 3
- ;; so the saved-arg area is always exactly 4 slots wide.
- ;; Track first-vararg-slot as the offset of the slot whose
- ;; index equals the named-arg count (= idx here on entry).
+ ;; Pad the incoming-arg window out to 16 slots. For idx 0..3
+ ;; the slot is filled from a-register; for idx 4..15 from
+ ;; LDARG slot (idx-4). va_start points at the slot whose
+ ;; index equals the named-arg count, and va_arg walks
+ ;; linearly from there through the rest of the window.
(let pad ((i idx) (vfirst #f) (fs first-slot))
(cond
- ((>= i 4)
+ ((>= i 16)
;; If named-arg count was 0, vfirst is the very first
;; slot of the save area (= fs).
(%cg-fn-set! cg '%fn-vararg-first-slot
(or vfirst fs))
(reverse out))
(else
- (let ((off (cg-alloc-slot cg 8 8))
- (ar (%reg-by-idx i)))
- (buf-push! (cg-prologue-buf cg)
- (bv-cat (list "%st(" (%cg-reg->bv ar)
- ", sp, "
- (%cg-slot-expr cg off) ")\n")))
+ (let ((off (cg-alloc-slot cg 8 8)))
+ (cond
+ ((< i 4)
+ (let ((ar (%reg-by-idx i)))
+ (buf-push! (cg-prologue-buf cg)
+ (bv-cat (list "%st(" (%cg-reg->bv ar)
+ ", sp, "
+ (%cg-slot-expr cg off) ")\n")))))
+ (else
+ (buf-push! (cg-prologue-buf cg)
+ (bv-cat (list "%ldarg(t0, " (%n (- i 4)) ")\n"
+ "%st(t0, sp, "
+ (%cg-slot-expr cg off) ")\n")))))
(pad (+ i 1)
(or vfirst off)
(or fs off)))))))
diff --git a/cc/headers/stdarg.h b/cc/headers/stdarg.h
@@ -6,10 +6,9 @@
* around the __builtin_va_* names recognized by the parser. See
* cc/parse.scm parse-builtin-va-{start,arg,end}.
*
- * Limitation (cc/cg.scm cg-fn-begin/v): only the first 4 incoming
- * args (named + variadic combined) live in the saved-register area.
- * Variadic args at index >= 4 require an LDARG path that is not yet
- * implemented. Sufficient for printf-shape calls in tcc.c. */
+ * Limit (cc/cg.scm cg-fn-begin/v): the incoming-arg window covers
+ * indices 0..15 (a-regs for 0..3, LDARG for 4..15). Calls with more
+ * than 15 variadic args after the named ones won't see them. */
#ifndef _STDARG_H
#define _STDARG_H
diff --git a/docs/CC-PUNCHLIST.md b/docs/CC-PUNCHLIST.md
@@ -304,15 +304,16 @@ accepts an init bv but is never given one.
- parse: `cc-parse/76-vararg-recv.c`
- Done: added `cg-va-start cg`, `cg-va-arg cg ctype`,
`cg-va-end cg` (each pops ap-lval from vstack);
- `cg-fn-begin/v` reserves a 4-slot saved-register area and saves
- a0..a3 unconditionally so va_arg can read past the named-arg count.
- Parser recognizes `__builtin_va_start/arg/end` at parse-primary;
- `parse-fn-body` threads the fn ctype's variadic? flag.
- - Bundled `cc/headers/stdarg.h` aliases `va_list`/`va_start`/
- `va_arg`/`va_end` to the builtins.
- - **Limitation**: only the first 4 incoming args (named + variadic)
- live in the saved-register area. Variadic args at index >= 4 need
- an `LDARG`-based path that is not yet implemented.
+ `cg-fn-begin/v` reserves a 16-slot incoming-arg window: indices
+ 0..3 are saved from a-registers, indices 4..15 from `LDARG` slots
+ 0..11. va_arg walks the window linearly from the slot at index =
+ named-arg count. Parser recognizes `__builtin_va_start/arg/end` at
+ parse-primary; `parse-fn-body` threads the fn ctype's variadic?
+ flag. Bundled `cc/headers/stdarg.h` aliases `va_list`/`va_start`/
+ `va_arg`/`va_end` to the builtins. Lock-in fixture
+ `cc-parse/79-vararg-deep.c` exercises 1 named + 6 variadic args.
+ Limit: 15 variadic args after the named ones; bump
+ `VARARG_WINDOW` (currently 16) in `cg-fn-begin/v` to extend.
### H. Conditionals as values
diff --git a/tests/cc-parse/79-vararg-deep.c b/tests/cc-parse/79-vararg-deep.c
@@ -0,0 +1,30 @@
+/* Variadic receive past index 4 — extends §G.2 across the stack-arg
+ * boundary. P1 ABI puts args 0..3 in registers and args 4+ in the
+ * incoming stack-arg area accessed via LDARG.
+ *
+ * sum(6, 1, 2, 4, 8, 16, 32): n=6 named + 6 variadic.
+ * Register-passed: n=6, v0=1, v1=2, v2=4
+ * Stack-passed: v3=8, v4=16, v5=32
+ * Variadic sum = 63
+ */
+
+typedef char *va_list;
+
+int sum(int n, ...) {
+ va_list ap;
+ int total;
+ int i;
+ __builtin_va_start(ap, n);
+ total = 0;
+ i = 0;
+ while (i < n) {
+ total = total + __builtin_va_arg(ap, int);
+ i = i + 1;
+ }
+ __builtin_va_end(ap);
+ return total;
+}
+
+int main(void) {
+ return sum(6, 1, 2, 4, 8, 16, 32);
+}
diff --git a/tests/cc-parse/79-vararg-deep.expected-exit b/tests/cc-parse/79-vararg-deep.expected-exit
@@ -0,0 +1 @@
+63