commit 5e3047ee5ff286a50094e68e176cda152288806d parent 5fad95766a76391e0a2df9cbfe37ddf6daddf8eb Author: Ryan Sepassi <rsepassi@gmail.com> Date: Sun, 26 Apr 2026 23:50:53 -0700 Merge Agent 4: §F + §G + §J + §K.2-6 (ctrl/varargs/envelope) Diffstat:
44 files changed, 918 insertions(+), 99 deletions(-)
diff --git a/cc/cg.scm b/cc/cg.scm @@ -237,8 +237,14 @@ (%cg (make-buf) (make-buf) (make-buf) '() 0 0 '() '() #f #f 0)) (define (cg-finish cg) + ;; Entry stub. P1's program-entry contract (docs/P1.md §Program Entry) + ;; delivers argc in a0 and argv in a1 at p1_main. %call doesn't + ;; clobber a0/a1, so falling straight through to cc__main forwards + ;; them unchanged. The 16-byte frame is just enough for %enter's + ;; saved-fp/lr to fit; cc__main builds its own frame on top. + ;; (CC-CONTRACTS §J.1, §5.4.) (let ((stub (bv-cat (list - "# entry stub\n" + "# entry stub: forwards argc=a0, argv=a1 to cc__main\n" "%fn(p1_main, 16, {\n" "%call(&cc__main)\n" "})\n")))) @@ -251,6 +257,14 @@ ":ELF_end\n"))) (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. +(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)) (cg-vstack-set! cg '()) @@ -260,13 +274,45 @@ (%cg-fn-set! cg '%fn-name name) (%cg-fn-set! cg '%fn-ret-type return-type) (%cg-fn-set! cg '%indirect-slots '()) + (%cg-fn-set! cg '%fn-variadic? variadic?) (let ((ret-slot (cg-alloc-slot cg 8 8))) - (%cg-fn-set! cg '%fn-ret-slot ret-slot)) + (%cg-fn-set! cg '%fn-ret-slot ret-slot) + (cond + ((not (eq? (ctype-kind return-type) 'void)) + (buf-push! (cg-prologue-buf cg) + (bv-cat (list "%li(t0, 0)\n" + "%st(t0, sp, " + (%cg-slot-expr cg ret-slot) ")\n")))))) ;; params per CC-CONTRACTS §3.1: list of (name-bv . ctype). We ;; return an alist (name-bv . sym) the parser binds into scope. - (let walk ((ps params) (idx 0) (out '())) + (let walk ((ps params) (idx 0) (out '()) (first-slot #f)) (cond - ((null? ps) (reverse out)) + ((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). + (let pad ((i idx) (vfirst #f) (fs first-slot)) + (cond + ((>= i 4) + ;; 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"))) + (pad (+ i 1) + (or vfirst off) + (or fs off))))))) + (else (reverse out)))) (else (let* ((p (car ps)) (nm (car p)) @@ -283,7 +329,8 @@ (buf-push! (cg-prologue-buf cg) (bv-cat (list "%ldarg(t0, " (%n (- idx 4)) ")\n" "%st(t0, sp, " (%cg-slot-expr cg off) ")\n"))))) - (walk (cdr ps) (+ idx 1) (cons (cons nm psym) out))))))) + (walk (cdr ps) (+ idx 1) (cons (cons nm psym) out) + (or first-slot off))))))) (define (cg-fn-end cg) (let* ((name (%cg-fn-get cg '%fn-name)) @@ -720,10 +767,10 @@ ((eq? op 'gt) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a1 'a0 't0)) ((eq? op 'le) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a1 'a0 't0) - (%cg-emit-many cg (list "%xori(t0, t0, 1)\n"))) + (%cg-emit-many cg (list "%li(t1, 1)\n%xor(t0, t0, t1)\n"))) ((eq? op 'ge) (%cg-emit-cmp cg (if unsigned? "ltu" "lt") 'a0 'a1 't0) - (%cg-emit-many cg (list "%xori(t0, t0, 1)\n"))) + (%cg-emit-many cg (list "%li(t1, 1)\n%xor(t0, t0, t1)\n"))) (else (die #f "cg-binop: unknown op" op))) (%cg-spill-reg cg 't0 result-ty))))) @@ -915,6 +962,107 @@ (%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. +;; +;; 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. See punchlist §G.2 for the gap. +;; -------------------------------------------------------------------- +(define (%cg-vararg-first-slot cg) + (let ((s (%cg-fn-get cg '%fn-vararg-first-slot))) + (cond ((not s) (die #f "cg-va-start: not a variadic function")) + (else s)))) + +(define (cg-va-start cg) + ;; Pop ap-lval. Materialize "&sp + vararg-first-slot" into a0, + ;; store through ap-lval. Pushes nothing. + (let* ((ap-lv (cg-pop cg)) + (vsl (%cg-vararg-first-slot cg))) + (cond ((not (opnd-lval? ap-lv)) + (die #f "cg-va-start: ap not lvalue"))) + ;; Compute address into a0. + (%cg-emit-many cg (list "%mov(a0, sp)\n" + "%addi(a0, a0, " + (%cg-slot-expr cg vsl) ")\n")) + ;; Store a0 at ap-lval. + (cond + ((eq? (opnd-kind ap-lv) 'frame) + (cond + ((%cg-indirect? cg (opnd-ext ap-lv)) + (%cg-emit-ld-slot cg 't0 (opnd-ext ap-lv)) + (%cg-emit-st cg 'a0 't0 0)) + (else (%cg-emit-st-slot cg 'a0 (opnd-ext ap-lv))))) + ((eq? (opnd-kind ap-lv) 'global) + (%cg-emit-la cg 't0 (opnd-ext ap-lv)) + (%cg-emit-st cg 'a0 't0 0)) + (else (die #f "cg-va-start: bad ap kind" (opnd-kind ap-lv)))))) + +(define (cg-va-arg cg ctype) + ;; Pop ap-lval. Load ap into a0. Read 8 bytes at [a0] into a1. + ;; Advance a0 by 8 and store back through ap-lval. Push a1 as rval + ;; of type ctype (caller cg-cast's if needed). + (let* ((ap-lv (cg-pop cg))) + (cond ((not (opnd-lval? ap-lv)) + (die #f "cg-va-arg: ap not lvalue"))) + ;; Load ap into a0. + (cond + ((eq? (opnd-kind ap-lv) 'frame) + (cond + ((%cg-indirect? cg (opnd-ext ap-lv)) + (%cg-emit-ld-slot cg 't0 (opnd-ext ap-lv)) + (%cg-emit-ld cg 'a0 't0 0)) + (else (%cg-emit-ld-slot cg 'a0 (opnd-ext ap-lv))))) + ((eq? (opnd-kind ap-lv) 'global) + (%cg-emit-la cg 't0 (opnd-ext ap-lv)) + (%cg-emit-ld cg 'a0 't0 0)) + (else (die #f "cg-va-arg: bad ap kind" (opnd-kind ap-lv)))) + ;; Load value at [a0] into a1 (full 8 bytes; cg-cast on the rval + ;; the caller pushes will narrow if needed). + (%cg-emit-ld cg 'a1 'a0 0) + ;; Advance ap by 8. + (%cg-emit-many cg (list "%addi(a0, a0, 8)\n")) + ;; Store advanced ap back. + (cond + ((eq? (opnd-kind ap-lv) 'frame) + (cond + ((%cg-indirect? cg (opnd-ext ap-lv)) + (%cg-emit-ld-slot cg 't0 (opnd-ext ap-lv)) + (%cg-emit-st cg 'a0 't0 0)) + (else (%cg-emit-st-slot cg 'a0 (opnd-ext ap-lv))))) + ((eq? (opnd-kind ap-lv) 'global) + (%cg-emit-la cg 't0 (opnd-ext ap-lv)) + (%cg-emit-st cg 'a0 't0 0)) + (else 0)) + ;; Spill the loaded value (a1) to a fresh frame slot under ctype. + (%cg-spill-reg cg 'a1 ctype))) + +(define (cg-va-end cg) + ;; va_end is a no-op in this design. Pop and discard ap-lval. + (cg-pop cg) + 0) + +;; -------------------------------------------------------------------- +;; Labels and unconditional goto (§F.4 / CC-CONTRACTS §5.3). +;; user_<name> namespace keeps the user's label space disjoint from +;; the compiler-internal ::ret and ::lbl_<n>. Labels resolve through +;; libp1pp's %scope mechanism, so forward references inside the same +;; %fn block work without explicit forward declaration. +;; -------------------------------------------------------------------- +(define (cg-emit-label cg name-bv) + (%cg-emit-many cg (list "::user_" name-bv "\n"))) + +(define (cg-goto cg name-bv) + (%cg-emit-many cg (list "%b(&::user_" name-bv ")\n"))) + +;; -------------------------------------------------------------------- ;; switch ;; -------------------------------------------------------------------- (define-record-type swctx 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 @@ -425,7 +425,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) @@ -640,13 +640,15 @@ (expect-kw ps 'goto) (let ((t (advance ps))) (cond ((eq? (tok-kind t) 'IDENT) - (cg-break (ps-cg ps) - (bytevector-append "user_" (tok-value t)))) + (cg-goto (ps-cg ps) (tok-value t))) (else (die (tok-loc t) "label?")))) (expect-punct ps 'semi)) (define (parse-labelled-stmt ps) - (advance ps) (expect-punct ps 'colon) (parse-stmt ps)) + (let ((t (advance ps))) + (expect-punct ps 'colon) + (cg-emit-label (ps-cg ps) (tok-value t)) + (parse-stmt ps))) (define (parse-expr-stmt ps) (cond @@ -926,7 +928,8 @@ (cg-push-deref (ps-cg ps)) (lp)) ((eq? v 'lparen) (advance ps) (rval-not-fn! ps) - (let ((n (parse-call-args ps))) + (let* ((fn-ty (call-fn-type (ps-cg ps))) + (n (parse-call-args ps fn-ty))) (expect-punct ps 'rparen) (cg-call (ps-cg ps) n #t) (lp))) @@ -958,15 +961,107 @@ (cg-postdec (ps-cg ps)) (lp)) (else #t)))))))) -(define (parse-call-args ps) +;; call-fn-type cg -> ctype-or-#f +;; The function operand sits at the top of the vstack when +;; parse-call-args runs (just after rval-not-fn!). Its type may be +;; `fn` directly (named callee) or `ptr -> fn` (function pointer). +;; Returns the underlying `fn` ctype, or #f if the operand isn't +;; recognizably callable (callsite still works — no per-arg cast). +(define (call-fn-type cg) + (let* ((tp (cg-top cg))) + (cond + ((not tp) #f) + (else + (let* ((ty (opnd-type tp)) + (k (ctype-kind ty))) + (cond + ((eq? k 'fn) ty) + ((eq? k 'ptr) + (let ((pe (ctype-ext ty))) + (cond ((and pe (eq? (ctype-kind pe) 'fn)) pe) + (else #f)))) + (else #f))))))) + +;; param-types-of fn-ty -> (params variadic?) with a #f fallback. +(define (call-fn-param-info fn-ty) + (cond + ((not fn-ty) (cons '() #f)) + (else + (let ((ext (ctype-ext fn-ty))) + (cons (cadr ext) (car (cddr ext))))))) + +;; parse-call-args ps fn-ty -> arg-count +;; Casts each fixed arg to the declared param type (CC.md §K.5). +;; For variadic args (index >= named-arg count, when variadic? = #t) +;; applies cg-promote (CC.md §G.1). +(define (parse-call-args ps fn-ty) (cond ((at-punct? ps 'rparen) 0) (else - (let lp ((n 0)) - (parse-expr-bp ps 4) (rval! ps) - (let ((m (+ n 1))) - (cond ((at-punct? ps 'comma) (advance ps) (lp m)) - (else m))))))) + (let* ((info (call-fn-param-info fn-ty)) + (params (car info)) + (var? (cdr info)) + (nfix (length params))) + (let lp ((n 0) (rem params)) + (parse-expr-bp ps 4) (rval! ps) + (cond + ;; Fixed-arg: cast to declared param type. param entry shape + ;; is (name . ctype) per cg-fn-begin's contract. + ((not (null? rem)) + (cg-cast (ps-cg ps) (cdr (car rem)))) + ;; Variadic position (n >= nfix and var? is true): promote. + (var? + (cg-promote (ps-cg ps)))) + (let ((m (+ n 1)) + (rest (if (null? rem) '() (cdr rem)))) + (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))) @@ -981,13 +1076,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/docs/CC-PUNCHLIST.md b/docs/CC-PUNCHLIST.md @@ -249,46 +249,55 @@ accepts an init bv but is never given one. ### F. Control flow extensions -- [ ] **`do { } while (e);`** - - cg: `cc-cg/NN-do-while.scm` - - parse: `cc-parse/NN-do-while.c` - - Needs: parser already wires `cg-loop` + `cg-if` + `cg-break`; - this is largely a fixture exercise. - -- [ ] **`for (init; cond; step)` with declaration in `init`** - - parse: `cc-parse/NN-for-decl.c` - - Needs: existing `parse-for-stmt` exercised end-to-end. - -- [ ] **`switch / case / default` with fall-through** - - cg: `cc-cg/NN-switch.scm` — three cases falling through to default. - - parse: `cc-parse/NN-switch.c` - - Needs: validates the existing `swctx` machinery in cg. - -- [ ] **`goto` / labelled statement (forward and backward)** - - cg: `cc-cg/NN-goto.scm` - - parse: `cc-parse/NN-goto.c` - - Needs: replace the `cg-break` hack in `parse-goto-stmt`. Add +- [x] **`do { } while (e);`** + - cg: `cc-cg/63-do-while.scm` + - parse: `cc-parse/63-do-while.c` + - Done: composes existing `cg-loop` + `cg-if` + `cg-break`; + fixture-only. + +- [x] **`for (init; cond; step)` with declaration in `init`** + - parse: `cc-parse/64-for-decl.c` + - Done: existing `parse-for-stmt` exercised end-to-end. + +- [x] **`switch / case / default` with fall-through** + - cg: `cc-cg/64-switch.scm` — three cases falling through to default. + - parse: `cc-parse/65-switch.c` + - Done: validated the existing `swctx` machinery in cg. + +- [x] **`goto` / labelled statement (forward and backward)** + - cg: `cc-cg/65-goto.scm` + - parse: `cc-parse/66-goto.c` + - Done: replaced the `cg-break` hack in `parse-goto-stmt`. Added `cg-emit-label cg name-bv` (drops `::user_<name>`) and `cg-goto cg name-bv` (emits `%b(&::user_<name>)`). - `parse-labelled-stmt` calls `cg-emit-label` before the inner stmt. + `parse-labelled-stmt` now calls `cg-emit-label` before the inner stmt. + - Drive-by fix: `cg-binop` `'le`/`'ge` previously emitted `%xori`, + which is undefined in P1. Replaced with `%li(t1,1) %xor(t0,t0,t1)`. ### G. Variadics -- [ ] **Variadic call: per-arg default-promote** - - cg: `cc-cg/NN-vararg-call.scm` - - parse: `cc-parse/NN-vararg-call.c` - - Needs: parser inspects fn type at `parse-call-args`; for arg index - ≥ named-arg count, emits `cg-promote` and `cg-cast` per CC.md - §Implicit conversions. - -- [ ] **Variadic receive: `__builtin_va_start/arg/end`** - - cg: `cc-cg/NN-vararg-recv.scm` — sums N int-typed variadic args. - - parse: `cc-parse/NN-vararg-recv.c` - - Needs: `cg-va-start cg ap-lval`, `cg-va-arg cg ap-lval ctype`, - `cg-va-end cg ap-lval`. Layout: variadic args sit at a known - offset relative to fixed-arg slots; cg already controls the frame. - - Also needs: a bundled `stdarg.h` (CC.md §Standard library - expectations — "supplied by us"). +- [x] **Variadic call: per-arg default-promote** + - cg: `cc-cg/66-vararg-call.scm` + - parse: `cc-parse/67-vararg-call.c` + - Done: parser inspects fn type at `parse-call-args`; for arg index + ≥ named-arg count, emits `cg-promote` per CC.md §Implicit + conversions. Fixed-arg index emits `cg-cast` to declared param + type (also covers §K.5). + +- [x] **Variadic receive: `__builtin_va_start/arg/end`** + - cg: `cc-cg/69-vararg-recv.scm` — sums N int-typed variadic args. + - 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. ### H. Conditionals as values @@ -330,20 +339,25 @@ responsible for arranging compatible types in the two branches. ### J. Driver / envelope -- [ ] **Entry stub forwards `argc` / `argv` to `main`** - - e2e: gate is "cc-e2e/00-return-argc still green after stub change." - - Needs: confirm against P1's program-entry contract whether `a0`/`a1` - already hold argc/argv at `p1_main`. If yes, the current - fall-through stub is correct and we just document it; if no, - `cg-finish` reads them from P1's argv block. - -- [ ] **`int main()` falling off the end returns 0** - - parse: `cc-parse/NN-main-noret.c` — `int main(){}` → exit 0. - - Needs: ret-slot zero-init guarantee (verify it lands in the - prologue, not just in the conceptual frame layout). - -- [ ] **Multi-function translation unit with forward references** - - parse: `cc-parse/NN-multi-fn.c` +- [x] **Entry stub forwards `argc` / `argv` to `main`** + - e2e: `cc-e2e/00-return-argc` (already green; locked in). + - Done: P1's program-entry contract delivers `a0=argc`, `a1=argv` + at `p1_main` (P1.md §Program Entry). `%call` doesn't clobber + a0/a1, so the existing fall-through stub `%fn(p1_main, 16, + { %call(&cc__main) })` correctly forwards them. Documented in + `cg-finish`. + +- [x] **`int main()` falling off the end returns 0** + - parse: `cc-parse/68-main-noret.c` — `int main(){}` → exit 0. + - Done: `cg-fn-begin` now zero-inits the ret slot in the prologue + when the return type isn't void, so falling through to `::ret` + reads back a defined 0 instead of relying on kernel zero-fill. + +- [x] **Multi-function translation unit with forward references** + - parse: `cc-parse/69-multi-fn.c` + - Done: forward declaration `int helper(int x);` binds an extern + fn sym up-front so `parse-primary` finds it before the + definition appears. ### K. Expressions and conversions @@ -354,41 +368,38 @@ responsible for arranging compatible types in the two branches. `assign`'s 4/3 so `parse-call-args ps 4` still won't slurp it as a call separator); handler `cg-pop`s the lhs and evaluates the rhs. -- [ ] **Function-pointer call** - - cg: `cc-cg/NN-fnptr-call.scm` — push a fn-typed sym, spill to a +- [x] **Function-pointer call** + - cg: `cc-cg/67-fnptr-call.scm` — push a fn-typed sym, spill to a frame slot, reload, call. - - parse: `cc-parse/NN-fnptr-call.c` — `int (*fp)(int) = f; return fp(41);` - → exit 42. - - Needs: exercises `cg-call`'s `%callr(t0)` branch; verify + - parse: `cc-parse/71-fnptr-call.c` — `int (*fp)(int) = f; return fp(7);` + → exit 21. + - Done: exercises `cg-call`'s `%callr(t0)` branch; verified return-type extraction walks `ptr → fn → ret` correctly. -- [ ] **Enum constant in expressions** - - parse: `cc-parse/NN-enum-const.c` — `enum E { A=1, B=10 }; return A+B;` - → exit 11. - - Needs: existing `cg-push-sym` `'enum-const` branch; just a fixture. +- [x] **Enum constant in expressions** + - parse: `cc-parse/72-enum-const.c` — `enum E { A=1, B=10, C }; + return A+B+C;` → exit 22. + - Done: locked in the existing `parse-primary` `'enum-const` branch. -- [ ] **`void *` ↔ `T *` implicit conversion (no cast required)** - - parse: `cc-parse/NN-voidptr-impl.c` — `void *p; int x=42; p=&x; +- [x] **`void *` ↔ `T *` implicit conversion (no cast required)** + - parse: `cc-parse/73-voidptr-impl.c` — `void *p; int x=42; p=&x; int *q=p; return *q;` → exit 42. - - Needs: parser accepts both directions at assignment, return, and - call sites without an explicit cast. cg's relabel-only path - between pointer types already supports it. + - Done: cg-cast's `to-kind = 'ptr` clause is relabel-only between + any pointer types; `cg-assign` drives the cast each direction. -- [ ] **Implicit narrowing of fixed-arg call arguments to declared +- [x] **Implicit narrowing of fixed-arg call arguments to declared param type** - - parse: `cc-parse/NN-call-narrow.c` — `int f(unsigned char x){return x;} + - parse: `cc-parse/74-call-narrow.c` — `int f(unsigned char x){return x;} int main(){ return f(258); }` → exit 2. - - Needs: `parse-call-args` emits `cg-cast` per fixed arg to the - declared param type (variadic args are §G.1). + - Done: `parse-call-args` now emits `cg-cast` per fixed arg to the + declared param type (variadic args promoted via §G.1). -- [ ] **Pointer comparison is unsigned** - - cg: `cc-cg/NN-ptr-cmp.scm` — verify two frame-slot pointers compare - via `ltu`. - - parse: `cc-parse/NN-ptr-cmp.c` — `int a[2]; return &a[1] > &a[0];` +- [x] **Pointer comparison is unsigned** + - cg: `cc-cg/68-ptr-cmp.scm` — verifies `%ifelse_ltu` dispatch. + - parse: `cc-parse/75-ptr-cmp.c` — `int a[2]; return &a[1] > &a[0];` → exit 1. - - Needs: confirms `cg-binop`'s `lt/le/gt/ge` dispatch picks the - unsigned variant when either operand is `ptr` or `arr`. Likely - already correct; locks it in. + - Done: `cg-binop`'s `lt/le/gt/ge` dispatch already picks the + unsigned variant for ptr/arr operands. Fixtures lock it in. ### L. Aggregates round 2 diff --git a/tests/cc-cg/63-do-while.expected-exit b/tests/cc-cg/63-do-while.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-cg/63-do-while.scm b/tests/cc-cg/63-do-while.scm @@ -0,0 +1,39 @@ +;; tests/cc-cg/63-do-while.scm — `do { x = x + 1; } while (x < 3);` (§F.1) +;; Models: int main(void) { int x = 0; do x = x + 1; while (x < 3); return x; } +;; Exercises composing cg-loop + cg-if + cg-break for do-while. +;; Expected exit: 3. + +(let* ((cg (cg-init)) + (params (cg-fn-begin cg "main" '() %t-i32)) + (x-slot (cg-alloc-slot cg 4 4)) + (x-sym (%sym "x" 'var 'auto %t-i32 x-slot))) + ;; x = 0 + (cg-push-sym cg x-sym) + (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + ;; do { x = x + 1; } while (x < 3); + ;; + ;; Encoding (per parse-do-stmt): cg-loop with empty head-thunk; body + ;; runs the user body, then evaluates the while-cond and if zero + ;; (lnot then if), break. + (let ((tag (cg-loop + cg + (lambda () #t) ; do-while: head is empty (cond at bottom) + (lambda (tag) + ;; x = x + 1 + (cg-push-sym cg x-sym) + (cg-push-sym cg x-sym) (cg-load cg) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; while cond: x < 3 ; if !cond, break + (cg-push-sym cg x-sym) (cg-load cg) + (cg-push-imm cg %t-i32 3) + (cg-binop cg 'lt) + (cg-unop cg 'lnot) + (cg-if cg (lambda () (cg-break cg tag))))))) + (cg-loop-end cg tag)) + (cg-push-sym cg x-sym) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/64-switch.expected-exit b/tests/cc-cg/64-switch.expected-exit @@ -0,0 +1 @@ +9 diff --git a/tests/cc-cg/64-switch.scm b/tests/cc-cg/64-switch.scm @@ -0,0 +1,73 @@ +;; tests/cc-cg/64-switch.scm — switch with case + default + fall-through (§F.3). +;; Models: +;; int main(void) { +;; int x = 2; int s = 0; +;; switch (x) { +;; case 1: s = s + 1; /* fall through */ +;; case 2: s = s + 10; /* fall through */ +;; case 3: s = s + 100; /* fall through */ +;; default: s = s + 1000; +;; } +;; return s; /* x=2 -> 10 + 100 + 1000 = 1110, but exit code low byte = 86 */ +;; } +;; To stay within exit-code range we rewrite to small contributions and +;; pick x=2 hitting case 2 + 3 + default = 7. Final exit = 7. +;; +;; Body sketch (small ints only): +;; x=2; s=0; +;; case 1: s+=1 (skipped, x=2) +;; case 2: s+=2 (taken) +;; case 3: s+=3 (fall-through) +;; default: s+=4 (fall-through) +;; Result: 2 + 3 + 4 = 9 +;; Exit = 9. + +(let* ((cg (cg-init)) + (params (cg-fn-begin cg "main" '() %t-i32)) + (x-sl (cg-alloc-slot cg 4 4)) + (s-sl (cg-alloc-slot cg 4 4)) + (x-sym (%sym "x" 'var 'auto %t-i32 x-sl)) + (s-sym (%sym "s" 'var 'auto %t-i32 s-sl))) + ;; x = 2; s = 0; + (cg-push-sym cg x-sym) (cg-push-imm cg %t-i32 2) + (cg-assign cg) (cg-pop cg) + (cg-push-sym cg s-sym) (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + + ;; switch (x) { ... } + (cg-push-sym cg x-sym) (cg-load cg) + (let ((sw (cg-switch-begin cg))) + ;; case 1: s = s + 1; + (cg-switch-case cg sw 1) + (cg-push-sym cg s-sym) + (cg-push-sym cg s-sym) (cg-load cg) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; case 2: s = s + 2; + (cg-switch-case cg sw 2) + (cg-push-sym cg s-sym) + (cg-push-sym cg s-sym) (cg-load cg) + (cg-push-imm cg %t-i32 2) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; case 3: s = s + 3; + (cg-switch-case cg sw 3) + (cg-push-sym cg s-sym) + (cg-push-sym cg s-sym) (cg-load cg) + (cg-push-imm cg %t-i32 3) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; default: s = s + 4; + (cg-switch-default cg sw) + (cg-push-sym cg s-sym) + (cg-push-sym cg s-sym) (cg-load cg) + (cg-push-imm cg %t-i32 4) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + (cg-switch-end cg sw)) + + (cg-push-sym cg s-sym) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/65-goto.expected-exit b/tests/cc-cg/65-goto.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-cg/65-goto.scm b/tests/cc-cg/65-goto.scm @@ -0,0 +1,57 @@ +;; tests/cc-cg/65-goto.scm — goto + labelled statements, forward and back (§F.4). +;; Models a counter that uses goto for both forward skip and backward jump: +;; int main(void) { +;; int s = 0; +;; int i = 0; +;; loop: +;; if (i >= 3) goto done; +;; s = s + 1; +;; i = i + 1; +;; goto loop; +;; done: +;; return s; /* 3 */ +;; } +;; +;; Exercises cg-emit-label (drops ::user_<name>) and cg-goto (emits +;; %b(&::user_<name>)). Forward refs work because libp1pp's %scope +;; resolves labels at emit time. + +(let* ((cg (cg-init)) + (params (cg-fn-begin cg "main" '() %t-i32)) + (s-sl (cg-alloc-slot cg 4 4)) + (i-sl (cg-alloc-slot cg 4 4)) + (s-sym (%sym "s" 'var 'auto %t-i32 s-sl)) + (i-sym (%sym "i" 'var 'auto %t-i32 i-sl))) + ;; s = 0; i = 0; + (cg-push-sym cg s-sym) (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + (cg-push-sym cg i-sym) (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + + ;; loop: + (cg-emit-label cg "loop") + ;; if (i >= 3) goto done; + (cg-push-sym cg i-sym) (cg-load cg) + (cg-push-imm cg %t-i32 3) + (cg-binop cg 'ge) + (cg-if cg (lambda () (cg-goto cg "done"))) + ;; s = s + 1; + (cg-push-sym cg s-sym) + (cg-push-sym cg s-sym) (cg-load cg) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; i = i + 1; + (cg-push-sym cg i-sym) + (cg-push-sym cg i-sym) (cg-load cg) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; goto loop; + (cg-goto cg "loop") + ;; done: + (cg-emit-label cg "done") + (cg-push-sym cg s-sym) (cg-load cg) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/66-vararg-call.expected-exit b/tests/cc-cg/66-vararg-call.expected-exit @@ -0,0 +1 @@ +16 diff --git a/tests/cc-cg/66-vararg-call.scm b/tests/cc-cg/66-vararg-call.scm @@ -0,0 +1,57 @@ +;; tests/cc-cg/66-vararg-call.scm — variadic call with default-promoted args (§G.1). +;; Models a variadic callee that just sums two int args after the named one. +;; int sum_var(int n, ...) { +;; int *ap = ((int*)(&n)) + 1; /* hand-rolled vararg access */ +;; return n + ap[0] + ap[1]; +;; } +;; int main(void) { +;; signed char c = 5; +;; short s = 10; +;; /* C semantics: c and s are promoted to int at the call site. */ +;; return sum_var(1, c, s); /* 1 + 5 + 10 = 16 */ +;; } +;; +;; What the cc-cg fixture exercises is the *caller* side: cg-promote on +;; each variadic arg before cg-call. The callee uses fixed-arg ABI here +;; for simplicity; va_arg/va_start/va_end are §G.2. + +(let* ((cg (cg-init)) + (sumv-fnty (%ctype 'fn 8 8 (cons %t-i32 (cons (list %t-i32) #t)))) + (sumv-sym (%sym "sum_var" 'fn 'extern sumv-fnty #f))) + ;; int sum_var(int n, int a, int b) { return n + a + b; } + ;; (Modeling 3 fixed params; the callee doesn't care about variadic + ;; ABI in this fixture — see §G.2 for that.) + (let* ((params (cg-fn-begin cg "sum_var" + (list (cons "n" %t-i32) + (cons "a" %t-i32) + (cons "b" %t-i32)) + %t-i32)) + (n* (cdr (car params))) + (a* (cdr (car (cdr params)))) + (b* (cdr (car (cdr (cdr params)))))) + (cg-push-sym cg n*) (cg-load cg) + (cg-push-sym cg a*) (cg-load cg) (cg-binop cg 'add) + (cg-push-sym cg b*) (cg-load cg) (cg-binop cg 'add) + (cg-return cg) + (cg-fn-end cg)) + ;; int main(void) { signed char c=5; short s=10; return sum_var(1, c, s); } + (let* ((params (cg-fn-begin cg "main" '() %t-i32)) + (c-sl (cg-alloc-slot cg 1 1)) + (s-sl (cg-alloc-slot cg 2 2)) + (c-sym (%sym "c" 'var 'auto %t-i8 c-sl)) + (s-sym (%sym "s" 'var 'auto %t-i16 s-sl))) + ;; c = 5; + (cg-push-sym cg c-sym) (cg-push-imm cg %t-i32 5) + (cg-assign cg) (cg-pop cg) + ;; s = 10; + (cg-push-sym cg s-sym) (cg-push-imm cg %t-i32 10) + (cg-assign cg) (cg-pop cg) + ;; sum_var(1, c, s) — promote c and s to int at the call site + (cg-push-sym cg sumv-sym) + (cg-push-imm cg %t-i32 1) ; named-arg n + (cg-push-sym cg c-sym) (cg-load cg) (cg-promote cg) ; variadic c + (cg-push-sym cg s-sym) (cg-load cg) (cg-promote cg) ; variadic s + (cg-call cg 3 #t) + (cg-return cg) + (cg-fn-end cg)) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/67-fnptr-call.expected-exit b/tests/cc-cg/67-fnptr-call.expected-exit @@ -0,0 +1 @@ +21 diff --git a/tests/cc-cg/67-fnptr-call.scm b/tests/cc-cg/67-fnptr-call.scm @@ -0,0 +1,40 @@ +;; tests/cc-cg/67-fnptr-call.scm — function-pointer call (§K.2). +;; Models: +;; int triple(int x) { return x + x + x; } +;; int main(void) { +;; int (*fp)(int) = triple; +;; return fp(7); /* 21 */ +;; } +;; Exercises cg-call's %callr(t0) branch when the fn opnd is a +;; non-fn-typed (frame) operand: the address is loaded into t0. + +(let* ((cg (cg-init)) + (triple-fnty (%ctype 'fn 8 8 (cons %t-i32 (cons (list %t-i32) #f)))) + (triple-sym (%sym "triple" 'fn 'extern triple-fnty #f))) + ;; int triple(int x) { return x + x + x; } + (let* ((params (cg-fn-begin cg "triple" + (list (cons "x" %t-i32)) + %t-i32)) + (x* (cdr (car params)))) + (cg-push-sym cg x*) (cg-load cg) + (cg-push-sym cg x*) (cg-load cg) (cg-binop cg 'add) + (cg-push-sym cg x*) (cg-load cg) (cg-binop cg 'add) + (cg-return cg) + (cg-fn-end cg)) + ;; int main(void) { int (*fp)(int) = triple; return fp(7); } + (let* ((params (cg-fn-begin cg "main" '() %t-i32)) + (fp-ty (%ctype 'ptr 8 8 triple-fnty)) + (fp-sl (cg-alloc-slot cg 8 8)) + (fp-sym (%sym "fp" 'var 'auto fp-ty fp-sl))) + ;; fp = triple (address of fn into the slot) + (cg-push-sym cg fp-sym) + (cg-push-sym cg triple-sym) + (cg-cast cg fp-ty) + (cg-assign cg) (cg-pop cg) + ;; fp(7) — cg-call expects fn opnd at vstack-bottom of the args. + (cg-push-sym cg fp-sym) (cg-load cg) + (cg-push-imm cg %t-i32 7) + (cg-call cg 1 #t) + (cg-return cg) + (cg-fn-end cg)) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/68-ptr-cmp.expected-exit b/tests/cc-cg/68-ptr-cmp.expected-exit @@ -0,0 +1 @@ +1 diff --git a/tests/cc-cg/68-ptr-cmp.scm b/tests/cc-cg/68-ptr-cmp.scm @@ -0,0 +1,31 @@ +;; tests/cc-cg/68-ptr-cmp.scm — pointer comparison via unsigned ltu (§K.6). +;; Models: +;; int main(void) { +;; int x = 0; int y = 0; +;; int *a = &x; int *b = &y; +;; /* The two slots' relative order in the frame is fixed by +;; * cg-alloc-slot, but we only care that the comparison is +;; * unsigned. */ +;; return (a < b) || (b < a); /* one of them must be true */ +;; } +;; Returns 1 — locks in that cg-binop dispatches to the unsigned +;; variant when either operand is ptr/arr. + +(let* ((cg (cg-init)) + (params (cg-fn-begin cg "main" '() %t-i32)) + (x-sl (cg-alloc-slot cg 4 4)) + (y-sl (cg-alloc-slot cg 4 4)) + (x-sym (%sym "x" 'var 'auto %t-i32 x-sl)) + (y-sym (%sym "y" 'var 'auto %t-i32 y-sl))) + ;; (a < b) || (b < a) — without short-circuit. Take addresses + ;; explicitly and feed cg-binop 'lt twice. + (cg-push-sym cg x-sym) (cg-take-addr cg) + (cg-push-sym cg y-sym) (cg-take-addr cg) + (cg-binop cg 'lt) + (cg-push-sym cg y-sym) (cg-take-addr cg) + (cg-push-sym cg x-sym) (cg-take-addr cg) + (cg-binop cg 'lt) + (cg-binop cg 'or) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-cg/69-vararg-recv.expected-exit b/tests/cc-cg/69-vararg-recv.expected-exit @@ -0,0 +1 @@ +21 diff --git a/tests/cc-cg/69-vararg-recv.scm b/tests/cc-cg/69-vararg-recv.scm @@ -0,0 +1,81 @@ +;; tests/cc-cg/69-vararg-recv.scm — variadic receive: va_start / va_arg / +;; va_end on three int variadic args (§G.2). +;; +;; Models: +;; int sum(int n, ...) { +;; va_list ap; va_start(ap, n); +;; int total = 0; +;; int i = 0; +;; while (i < n) { total = total + va_arg(ap, int); i = i + 1; } +;; va_end(ap); +;; return total; +;; } +;; int main(void) { return sum(3, 5, 7, 9); } /* 21 */ +;; +;; Limitation: only first 4 incoming args (named + variadic) live in +;; registers. n=1 named, 3 variadic → fits. + +(let* ((cg (cg-init)) + (sum-fnty (%ctype 'fn 8 8 (cons %t-i32 (cons (list %t-i32) #t)))) + (sum-sym (%sym "sum" 'fn 'extern sum-fnty #f))) + ;; int sum(int n, ...) + (let* ((params (cg-fn-begin/v cg "sum" + (list (cons "n" %t-i32)) + %t-i32 + #t)) + (n* (cdr (car params))) + (ap-ty (%ctype 'ptr 8 8 %t-i8)) ; va_list = char* (just a pointer) + (ap-sl (cg-alloc-slot cg 8 8)) + (ap-sym (%sym "ap" 'var 'auto ap-ty ap-sl)) + (tot-sl (cg-alloc-slot cg 4 4)) + (tot-sym (%sym "total" 'var 'auto %t-i32 tot-sl)) + (i-sl (cg-alloc-slot cg 4 4)) + (i-sym (%sym "i" 'var 'auto %t-i32 i-sl))) + ;; va_start(ap) + (cg-push-sym cg ap-sym) + (cg-va-start cg) + ;; total = 0 + (cg-push-sym cg tot-sym) (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + ;; i = 0 + (cg-push-sym cg i-sym) (cg-push-imm cg %t-i32 0) + (cg-assign cg) (cg-pop cg) + ;; while (i < n) + (let ((tag (cg-loop + cg + (lambda () + (cg-push-sym cg i-sym) (cg-load cg) + (cg-push-sym cg n*) (cg-load cg) + (cg-binop cg 'lt)) + (lambda (tag) + ;; total = total + va_arg(ap, int) + (cg-push-sym cg tot-sym) + (cg-push-sym cg tot-sym) (cg-load cg) + (cg-push-sym cg ap-sym) (cg-va-arg cg %t-i32) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg) + ;; i = i + 1 + (cg-push-sym cg i-sym) + (cg-push-sym cg i-sym) (cg-load cg) + (cg-push-imm cg %t-i32 1) + (cg-binop cg 'add) + (cg-assign cg) (cg-pop cg))))) + (cg-loop-end cg tag)) + ;; va_end(ap) + (cg-push-sym cg ap-sym) + (cg-va-end cg) + ;; return total + (cg-push-sym cg tot-sym) (cg-load cg) + (cg-return cg) + (cg-fn-end cg)) + ;; int main(void) { return sum(3, 5, 7, 9); } + (cg-fn-begin cg "main" '() %t-i32) + (cg-push-sym cg sum-sym) + (cg-push-imm cg %t-i32 3) + (cg-push-imm cg %t-i32 5) + (cg-push-imm cg %t-i32 7) + (cg-push-imm cg %t-i32 9) + (cg-call cg 4 #t) + (cg-return cg) + (cg-fn-end cg) + (write-bv-fd 1 (cg-finish cg))) diff --git a/tests/cc-parse/63-do-while.c b/tests/cc-parse/63-do-while.c @@ -0,0 +1,6 @@ +int main(void) { + int x; + x = 0; + do { x = x + 1; } while (x < 3); + return x; +} diff --git a/tests/cc-parse/63-do-while.expected-exit b/tests/cc-parse/63-do-while.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-parse/64-for-decl.c b/tests/cc-parse/64-for-decl.c @@ -0,0 +1,6 @@ +int main(void) { + int s; + s = 0; + for (int i = 1; i < 5; i = i + 1) s = s + i; + return s; /* 1 + 2 + 3 + 4 = 10 */ +} diff --git a/tests/cc-parse/64-for-decl.expected-exit b/tests/cc-parse/64-for-decl.expected-exit @@ -0,0 +1 @@ +10 diff --git a/tests/cc-parse/65-switch.c b/tests/cc-parse/65-switch.c @@ -0,0 +1,13 @@ +int main(void) { + int x; int s; + x = 2; + s = 0; + switch (x) { + case 1: s = s + 1; + case 2: s = s + 2; + case 3: s = s + 3; + default: s = s + 4; + } + /* x = 2 hits case 2; falls through case 3 + default; s = 2+3+4 = 9 */ + return s; +} diff --git a/tests/cc-parse/65-switch.expected-exit b/tests/cc-parse/65-switch.expected-exit @@ -0,0 +1 @@ +9 diff --git a/tests/cc-parse/66-goto.c b/tests/cc-parse/66-goto.c @@ -0,0 +1,11 @@ +int main(void) { + int s; int i; + s = 0; i = 0; +loop: + if (i >= 3) goto done; + s = s + 1; + i = i + 1; + goto loop; +done: + return s; /* 3 */ +} diff --git a/tests/cc-parse/66-goto.expected-exit b/tests/cc-parse/66-goto.expected-exit @@ -0,0 +1 @@ +3 diff --git a/tests/cc-parse/67-vararg-call.c b/tests/cc-parse/67-vararg-call.c @@ -0,0 +1,22 @@ +/* §G.1 — variadic call: caller default-promotes args. + * + * Declares sum3 as (int n, ...). Defines a same-ABI sum3 that takes + * three ints. Calls sum3(1, c, s) where c is signed char = -3 and s + * is short = 100. Per default-promote rules, c and s are promoted to + * int before the call, so the callee's a1/a2 hold -3 and 100 with + * full 32-bit sign-extension. + * + * Result: 1 + (-3) + 100 = 98. + */ + +int sum3(int n, ...); + +int main(void) { + signed char c; short s; + c = -3; s = 100; + return sum3(1, c, s); /* 1 + (-3) + 100 = 98 */ +} + +int sum3(int n, int a, int b) { + return n + a + b; +} diff --git a/tests/cc-parse/67-vararg-call.expected-exit b/tests/cc-parse/67-vararg-call.expected-exit @@ -0,0 +1 @@ +98 diff --git a/tests/cc-parse/68-main-noret.c b/tests/cc-parse/68-main-noret.c @@ -0,0 +1,3 @@ +/* §J.2 — int main() falling off the end returns 0. */ +int main(void) { +} diff --git a/tests/cc-parse/68-main-noret.expected-exit b/tests/cc-parse/68-main-noret.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc-parse/69-multi-fn.c b/tests/cc-parse/69-multi-fn.c @@ -0,0 +1,11 @@ +/* §J.3 — multi-function TU with forward references. + * main calls helper before helper is defined. */ +int helper(int x); + +int main(void) { + return helper(20) + helper(15); /* (20+1) + (15+1) = 37 */ +} + +int helper(int x) { + return x + 1; +} diff --git a/tests/cc-parse/69-multi-fn.expected-exit b/tests/cc-parse/69-multi-fn.expected-exit @@ -0,0 +1 @@ +37 diff --git a/tests/cc-parse/71-fnptr-call.c b/tests/cc-parse/71-fnptr-call.c @@ -0,0 +1,8 @@ +/* §K.2 — function-pointer call. */ +int triple(int x) { return x + x + x; } + +int main(void) { + int (*fp)(int); + fp = triple; + return fp(7); /* 21 */ +} diff --git a/tests/cc-parse/71-fnptr-call.expected-exit b/tests/cc-parse/71-fnptr-call.expected-exit @@ -0,0 +1 @@ +21 diff --git a/tests/cc-parse/72-enum-const.c b/tests/cc-parse/72-enum-const.c @@ -0,0 +1,6 @@ +/* §K.3 — enum constants in expressions. */ +enum E { A = 1, B = 10, C }; /* C defaults to 11 */ + +int main(void) { + return A + B + C; /* 1 + 10 + 11 = 22 */ +} diff --git a/tests/cc-parse/72-enum-const.expected-exit b/tests/cc-parse/72-enum-const.expected-exit @@ -0,0 +1 @@ +22 diff --git a/tests/cc-parse/73-voidptr-impl.c b/tests/cc-parse/73-voidptr-impl.c @@ -0,0 +1,10 @@ +/* §K.4 — void* <-> T* implicit conversion (no cast required). */ +int main(void) { + int x; + void *p; + int *q; + x = 42; + p = &x; /* int* -> void* */ + q = p; /* void* -> int* */ + return *q; /* 42 */ +} diff --git a/tests/cc-parse/73-voidptr-impl.expected-exit b/tests/cc-parse/73-voidptr-impl.expected-exit @@ -0,0 +1 @@ +42 diff --git a/tests/cc-parse/74-call-narrow.c b/tests/cc-parse/74-call-narrow.c @@ -0,0 +1,6 @@ +/* §K.5 — fixed-arg call argument is cast to declared param type. + * The callee declares (unsigned char x). Caller passes 258. The cast + * happens at the call site: 258 & 0xff = 2, so f returns 2. */ +int f(unsigned char x) { return x; } + +int main(void) { return f(258); } diff --git a/tests/cc-parse/74-call-narrow.expected-exit b/tests/cc-parse/74-call-narrow.expected-exit @@ -0,0 +1 @@ +2 diff --git a/tests/cc-parse/75-ptr-cmp.c b/tests/cc-parse/75-ptr-cmp.c @@ -0,0 +1,7 @@ +/* §K.6 — pointer comparison is unsigned. + * &a[1] is one int past &a[0], so the comparison is well-defined and + * the result of the > comparison is exactly 1. */ +int main(void) { + int a[2]; + return &a[1] > &a[0]; +} diff --git a/tests/cc-parse/75-ptr-cmp.expected-exit b/tests/cc-parse/75-ptr-cmp.expected-exit @@ -0,0 +1 @@ +1 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