commit 790ab01a4a5ee273f357f12e3b872736371e3879
parent 4ff7a113f3caa6ba18e1dbf87be4adbfb515168e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sun, 26 Apr 2026 23:16:23 -0700
cc/parse: __builtin_va_{start,arg,end} + bundled stdarg.h (§G.2)
Recognizes __builtin_va_start(ap, last), __builtin_va_arg(ap, T),
__builtin_va_end(ap) at parse-primary as call-shape special forms.
Routes to cg-va-{start,arg,end}. parse-fn-body now threads the fn
ctype's variadic? flag into cg-fn-begin/v so the prologue saves all
4 reg args.
cc/headers/stdarg.h is the bundled header for the pre-flatten step
to splice in. va_list is `char*`; the public macros alias the
__builtin_va_* names.
Diffstat:
4 files changed, 113 insertions(+), 8 deletions(-)
diff --git a/cc/headers/stdarg.h b/cc/headers/stdarg.h
@@ -0,0 +1,23 @@
+/* lispcc-bundled <stdarg.h>. Per CC.md §Standard library expectations,
+ * the pre-flatten step splices this in for any source that
+ * `#include`s <stdarg.h>; the compiler proper rejects #include.
+ *
+ * va_list is a single pointer. va_start, va_arg, va_end are macros
+ * 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. */
+
+#ifndef _STDARG_H
+#define _STDARG_H
+
+typedef char *va_list;
+
+#define va_start(ap, last) __builtin_va_start((ap), (last))
+#define va_arg(ap, T) __builtin_va_arg((ap), T)
+#define va_end(ap) __builtin_va_end((ap))
+
+#endif
diff --git a/cc/parse.scm b/cc/parse.scm
@@ -420,7 +420,7 @@
(scope-bind! ps name
(%sym name 'fn 'extern dt
(bytevector-append "cc__" name)))))
- (let ((psyms (cg-fn-begin (ps-cg ps) name par ret)))
+ (let ((psyms (cg-fn-begin/v (ps-cg ps) name par ret var)))
(ps-fn-ctx-set! ps
(%fn-ctx name ret (map cdr psyms) var '()))
(scope-enter! ps)
@@ -993,6 +993,52 @@
(cond ((at-punct? ps 'comma) (advance ps) (lp m rest))
(else m))))))))
+;; --------------------------------------------------------------------
+;; __builtin_va_* (§G.2). va_list / va_start / va_arg / va_end in
+;; <stdarg.h> alias these. Each is parsed as: name '(' args ')'.
+;; va_start(ap, last) — last is parsed and discarded; cg only needs
+;; the variadic-first-slot offset, which it already tracks.
+;; va_arg(ap, T) — T is a type-name; result rval has that type.
+;; va_end(ap) — no-op codegen; just consumes ap.
+;;
+;; Pushes a single imm 0 for va_start / va_end so they fit as
+;; expression statements; va_arg pushes the rval.
+;; --------------------------------------------------------------------
+(define (parse-builtin-va-start ps)
+ (advance ps) ; IDENT
+ (expect-punct ps 'lparen)
+ (parse-expr-bp ps 4) ; ap (must be lval)
+ (expect-punct ps 'comma)
+ ;; "last" is parsed for syntactic completeness then dropped — cg
+ ;; doesn't need it; the variadic-first-slot was determined at
+ ;; cg-fn-begin/v time.
+ (parse-expr-bp ps 4) (cg-pop (ps-cg ps))
+ (expect-punct ps 'rparen)
+ (cg-va-start (ps-cg ps))
+ ;; Push a placeholder rval so the call expression has a value
+ ;; (matches va_start's "void" but our parser expects all
+ ;; expressions to leave one rval).
+ (cg-push-imm (ps-cg ps) %t-i32 0))
+
+(define (parse-builtin-va-arg ps)
+ (advance ps) ; IDENT
+ (expect-punct ps 'lparen)
+ (parse-expr-bp ps 4) ; ap (lval)
+ (expect-punct ps 'comma)
+ (let* ((sp (parse-decl-spec ps))
+ (p (parse-declarator ps (cdr sp)))
+ (ty (cdr p)))
+ (expect-punct ps 'rparen)
+ (cg-va-arg (ps-cg ps) ty)))
+
+(define (parse-builtin-va-end ps)
+ (advance ps) ; IDENT
+ (expect-punct ps 'lparen)
+ (parse-expr-bp ps 4) ; ap
+ (expect-punct ps 'rparen)
+ (cg-va-end (ps-cg ps))
+ (cg-push-imm (ps-cg ps) %t-i32 0))
+
(define (parse-primary ps)
(let ((t (peek ps)))
(cond
@@ -1006,13 +1052,21 @@
(advance ps)
(cg-push-string (ps-cg ps) (tok-value t)))
((eq? (tok-kind t) 'IDENT)
- (let ((sm (scope-lookup ps (tok-value t))))
- (advance ps)
- (cond
- ((not sm) (die (tok-loc t) "undecl" (tok-value t)))
- ((eq? (sym-kind sm) 'enum-const)
- (cg-push-imm (ps-cg ps) %t-i32 (sym-slot sm)))
- (else (cg-push-sym (ps-cg ps) sm)))))
+ (cond
+ ((bv= (tok-value t) "__builtin_va_start")
+ (parse-builtin-va-start ps))
+ ((bv= (tok-value t) "__builtin_va_arg")
+ (parse-builtin-va-arg ps))
+ ((bv= (tok-value t) "__builtin_va_end")
+ (parse-builtin-va-end ps))
+ (else
+ (let ((sm (scope-lookup ps (tok-value t))))
+ (advance ps)
+ (cond
+ ((not sm) (die (tok-loc t) "undecl" (tok-value t)))
+ ((eq? (sym-kind sm) 'enum-const)
+ (cg-push-imm (ps-cg ps) %t-i32 (sym-slot sm)))
+ (else (cg-push-sym (ps-cg ps) sm)))))))
((eq? (tok-kind t) 'PUNCT)
(cond
((eq? (tok-value t) 'lparen)
diff --git a/tests/cc-parse/76-vararg-recv.c b/tests/cc-parse/76-vararg-recv.c
@@ -0,0 +1,27 @@
+/* §G.2 — variadic receive: __builtin_va_start / arg / end.
+ *
+ * Mirrors what <stdarg.h> would ship: va_list = char*; va_start /
+ * va_arg / va_end are macro-aliases for the __builtin_* names. Here
+ * the test uses the builtins directly, since the cc-parse pipeline
+ * doesn't fold #include. */
+
+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(3, 5, 7, 9); /* 5 + 7 + 9 = 21 */
+}
diff --git a/tests/cc-parse/76-vararg-recv.expected-exit b/tests/cc-parse/76-vararg-recv.expected-exit
@@ -0,0 +1 @@
+21