commit 1d5aeed1c3804b5b2140324a64c89dd437a4cc33
parent a27e22c08bc27e3140f0cdc334e852447ab5c92e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 1 May 2026 19:55:41 -0700
merge: cap variadic args at 16 + boundary tests
Diffstat:
5 files changed, 108 insertions(+), 7 deletions(-)
diff --git a/cc/cc.scm b/cc/cc.scm
@@ -2958,6 +2958,13 @@
(bv-cat (list "%st(a0, sp, "
(%cg-slot-expr cg ss) ")\n")))))
(else (%cg-fn-set! cg '%fn-sret-slot #f))))
+ ;; Variadic save area is capped at 16 incoming-arg slots; reject
+ ;; variadic definitions whose named-arg count would already fill or
+ ;; exceed it (no room left for variadic reads).
+ (cond
+ ((and variadic? (> (length params) 16))
+ (die #f "cg-fn-begin: variadic function exceeds 16-arg save-area cap"
+ name (length params))))
;; With sret, explicit arg i lives at ABI position (i+1): args 0..2
;; in a1..a3, args 3+ in slot (i-3).
(let* ((sret-shift (if (%cg-fn-get cg '%fn-sret?) 1 0))
@@ -3708,6 +3715,24 @@
(sret? (and has-result?
(or (eq? rk 'struct) (eq? rk 'union))
(> (ctype-size rty) 16)))
+ ;; If the callee is variadic, the callee's save area caps total
+ ;; incoming args at 16. Reject silent miscompiles up front.
+ (callee-fty (cond
+ ((eq? (ctype-kind fty) 'fn) fty)
+ ((and (eq? (ctype-kind fty) 'ptr)
+ (eq? (ctype-kind (ctype-ext fty)) 'fn))
+ (ctype-ext fty))
+ (else #f)))
+ (callee-variadic? (and callee-fty
+ (let ((ext (ctype-ext callee-fty)))
+ (and (pair? ext) (pair? (cdr ext))
+ (pair? (cddr ext))
+ (car (cddr ext))))))
+ (_cap-check (cond
+ ((and callee-variadic? (> arity 16))
+ (die #f "cg-call: variadic call exceeds 16-arg save-area cap"
+ arity))
+ (else 0)))
(sret-shift (if sret? 1 0))
(recv-slot (cond
(sret?
@@ -3858,19 +3883,22 @@
(%cg-emit-many cg (list "%continue(" tag ")\n")))
;; --------------------------------------------------------------------
-;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a 4-slot
-;; saved-register area at known frame offsets; va_start sets ap to the
-;; address of the first slot past the named-arg count; va_arg reads
-;; *ap, advances ap by 8, and pushes the value as the requested type.
+;; Variadic receive (§G.2). Layout: cg-fn-begin/v reserves a 16-slot
+;; (8 bytes each) save area at known frame offsets, populating each
+;; slot from the appropriate ABI source — a-register for indices 0..3,
+;; LDARG for indices 4..15. va_start sets ap to the address of the
+;; first slot past the named-arg count; va_arg reads *ap, advances ap
+;; by 8, and pushes the value as the requested type.
;;
;; ap is an lval (typically a `va_list` local). cg-va-start pops it,
;; computes the address, stores into *ap (or the slot directly), and
;; pushes nothing. cg-va-arg pops ap-lval, loads ap, dereferences for
;; the value, advances ap, stores back, pushes the loaded value.
;;
-;; Limitation: only first 4 incoming args (named + variadic) live in
-;; the save area; variadic args at index >= 4 need LDARG and are not
-;; yet supported.
+;; Cap: total incoming args (named + variadic) must fit in the 16-slot
+;; save area. Variadic call sites exceeding this die in cg-call;
+;; variadic definitions whose named-arg count exceeds it die in
+;; cg-fn-begin/v.
;; --------------------------------------------------------------------
(define (%cg-vararg-first-slot cg)
(let ((s (%cg-fn-get cg '%fn-vararg-first-slot)))
diff --git a/tests/cc/320-vararg-cap.c b/tests/cc/320-vararg-cap.c
@@ -0,0 +1,36 @@
+/* Variadic save area is capped at 16 incoming-arg slots (named +
+ * variadic). cg should reject calls that exceed this cap with a clear
+ * compile-time error rather than silently miscompile by writing past
+ * the save area in the callee's prologue.
+ *
+ * This test exercises the boundary: 1 named + 15 variadic = 16 args
+ * total, which is the maximum supported. Sum 1..15 = 120. */
+
+#ifndef CCSCM
+#include <stdarg.h>
+#else
+typedef char *va_list;
+#define va_start(ap, n) __builtin_va_start(ap, n)
+#define va_arg(ap, t) __builtin_va_arg(ap, t)
+#define va_end(ap) __builtin_va_end(ap)
+#endif
+
+int sum(int n, ...) {
+ va_list ap;
+ int total;
+ int i;
+ va_start(ap, n);
+ total = 0;
+ i = 0;
+ while (i < n) {
+ total = total + va_arg(ap, int);
+ i = i + 1;
+ }
+ va_end(ap);
+ return total;
+}
+
+int main(void) {
+ /* 15 variadic ints; n itself is named arg 0; total 16 args. */
+ return sum(15, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
+}
diff --git a/tests/cc/320-vararg-cap.expected-exit b/tests/cc/320-vararg-cap.expected-exit
@@ -0,0 +1 @@
+120
diff --git a/tests/cc/321-vararg-mixed-many.c b/tests/cc/321-vararg-mixed-many.c
@@ -0,0 +1,35 @@
+/* Mixed-boundary variadic: 2 named + several variadic spanning the
+ * a-register / LDARG boundary. Probes that:
+ * - named arg 0 (lives in a0/save-slot 0) is preserved
+ * - named arg 1 (lives in a1/save-slot 1) is preserved
+ * - variadic args at idx >= 4 (LDARG-loaded) are read correctly
+ *
+ * Sum = base + tag + 1+2+3+4+5+6 = 100 + 7 + 21 = 128. */
+
+#ifndef CCSCM
+#include <stdarg.h>
+#else
+typedef char *va_list;
+#define va_start(ap, n) __builtin_va_start(ap, n)
+#define va_arg(ap, t) __builtin_va_arg(ap, t)
+#define va_end(ap) __builtin_va_end(ap)
+#endif
+
+int sum_with(int base, int n, ...) {
+ va_list ap;
+ int total;
+ int i;
+ va_start(ap, n);
+ total = base;
+ i = 0;
+ while (i < n) {
+ total = total + va_arg(ap, int);
+ i = i + 1;
+ }
+ va_end(ap);
+ return total;
+}
+
+int main(void) {
+ return sum_with(100, 7, 1, 2, 3, 4, 5, 6, 7);
+}
diff --git a/tests/cc/321-vararg-mixed-many.expected-exit b/tests/cc/321-vararg-mixed-many.expected-exit
@@ -0,0 +1 @@
+128