boot2

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

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:
Acc/headers/stdarg.h | 23+++++++++++++++++++++++
Mcc/parse.scm | 70++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Atests/cc-parse/76-vararg-recv.c | 27+++++++++++++++++++++++++++
Atests/cc-parse/76-vararg-recv.expected-exit | 1+
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