boot2

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

commit 57ce58cc61e396c0f65b4ced0f9c501ecb1c5b26
parent 14a2a38c5247dd571ff4bb68491008f085cb86fe
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 27 Apr 2026 11:24:43 -0700

cc: add 35 e2e tests probing parse/codegen gaps from CC.md

Each test is a single .c file compiled by cc.scm, linked, run; exit code
is checked against .expected-exit (0 = pass, N = which sub-check failed).
All 35 verified against host cc -O0 before commit.

82-110 — features and corner cases under CC.md not yet exercised:
unions, struct pass-by-value, struct copy-assign, string-literal
concat, nested ternary, sizeof-no-eval, chained assign, comma-in-for,
inc/dec on struct members, multi-declarator decls, array of struct,
string table, narrow bitwise + sign-ext, forward fn decl, forward
struct decl, variadic with >4 named args (cg.scm:1027 TODO), >6 fn
args (stack-staged), zero-fill init tail, integer-literal suffixes,
char escapes, narrow compound assign, ptr subtraction, _Bool semantics,
multi-level deref, switch fall-through + non-tail default, file-scope
static, typedef'd fn-ptr, anon-typedef struct, fn-ptr cast through void*.

111-116 — struct return at 1-word, 2-word, >2-word sizes, plus
interactions with >4/>6 named args and varargs. cg.scm has only a
single 8-byte return slot (CC-CONTRACTS §3.2) with no hidden-pointer
convention, so 112+ are expected to expose codegen gaps.

Diffstat:
Atests/cc/100-int-suffixes.c | 21+++++++++++++++++++++
Atests/cc/100-int-suffixes.expected-exit | 1+
Atests/cc/101-char-escapes.c | 17+++++++++++++++++
Atests/cc/101-char-escapes.expected-exit | 1+
Atests/cc/102-cmpd-narrow.c | 22++++++++++++++++++++++
Atests/cc/102-cmpd-narrow.expected-exit | 1+
Atests/cc/103-ptr-arith-sub.c | 21+++++++++++++++++++++
Atests/cc/103-ptr-arith-sub.expected-exit | 1+
Atests/cc/104-bool-type.c | 18++++++++++++++++++
Atests/cc/104-bool-type.expected-exit | 1+
Atests/cc/105-deeper-deref.c | 26++++++++++++++++++++++++++
Atests/cc/105-deeper-deref.expected-exit | 1+
Atests/cc/106-switch-fallthrough.c | 22++++++++++++++++++++++
Atests/cc/106-switch-fallthrough.expected-exit | 1+
Atests/cc/107-static-file-scope.c | 12++++++++++++
Atests/cc/107-static-file-scope.expected-exit | 1+
Atests/cc/108-typedef-fnptr.c | 24++++++++++++++++++++++++
Atests/cc/108-typedef-fnptr.expected-exit | 1+
Atests/cc/109-typedef-anon.c | 25+++++++++++++++++++++++++
Atests/cc/109-typedef-anon.expected-exit | 1+
Atests/cc/110-cast-fnptr.c | 12++++++++++++
Atests/cc/110-cast-fnptr.expected-exit | 1+
Atests/cc/111-struct-ret-1word.c | 29+++++++++++++++++++++++++++++
Atests/cc/111-struct-ret-1word.expected-exit | 1+
Atests/cc/112-struct-ret-2word.c | 32++++++++++++++++++++++++++++++++
Atests/cc/112-struct-ret-2word.expected-exit | 1+
Atests/cc/113-struct-ret-3word.c | 36++++++++++++++++++++++++++++++++++++
Atests/cc/113-struct-ret-3word.expected-exit | 1+
Atests/cc/114-struct-ret-many-args.c | 30++++++++++++++++++++++++++++++
Atests/cc/114-struct-ret-many-args.expected-exit | 1+
Atests/cc/115-struct-ret-3word-many-args.c | 26++++++++++++++++++++++++++
Atests/cc/115-struct-ret-3word-many-args.expected-exit | 1+
Atests/cc/116-struct-ret-vararg.c | 45+++++++++++++++++++++++++++++++++++++++++++++
Atests/cc/116-struct-ret-vararg.expected-exit | 1+
Atests/cc/82-union-basic.c | 26++++++++++++++++++++++++++
Atests/cc/82-union-basic.expected-exit | 1+
Atests/cc/83-struct-byval-arg.c | 14++++++++++++++
Atests/cc/83-struct-byval-arg.expected-exit | 1+
Atests/cc/84-struct-assign.c | 15+++++++++++++++
Atests/cc/84-struct-assign.expected-exit | 1+
Atests/cc/85-string-concat.c | 9+++++++++
Atests/cc/85-string-concat.expected-exit | 1+
Atests/cc/86-nested-ternary.c | 17+++++++++++++++++
Atests/cc/86-nested-ternary.expected-exit | 1+
Atests/cc/87-sizeof-noeval.c | 9+++++++++
Atests/cc/87-sizeof-noeval.expected-exit | 1+
Atests/cc/88-chained-assign.c | 15+++++++++++++++
Atests/cc/88-chained-assign.expected-exit | 1+
Atests/cc/89-for-comma.c | 13+++++++++++++
Atests/cc/89-for-comma.expected-exit | 1+
Atests/cc/90-incdec-member.c | 18++++++++++++++++++
Atests/cc/90-incdec-member.expected-exit | 1+
Atests/cc/91-multi-decl.c | 16++++++++++++++++
Atests/cc/91-multi-decl.expected-exit | 1+
Atests/cc/92-array-of-struct.c | 16++++++++++++++++
Atests/cc/92-array-of-struct.expected-exit | 1+
Atests/cc/93-string-table.c | 19+++++++++++++++++++
Atests/cc/93-string-table.expected-exit | 1+
Atests/cc/94-narrow-bitwise.c | 19+++++++++++++++++++
Atests/cc/94-narrow-bitwise.expected-exit | 1+
Atests/cc/95-fwd-fn-decl.c | 10++++++++++
Atests/cc/95-fwd-fn-decl.expected-exit | 1+
Atests/cc/96-fwd-struct.c | 20++++++++++++++++++++
Atests/cc/96-fwd-struct.expected-exit | 1+
Atests/cc/97-vararg-many-named.c | 26++++++++++++++++++++++++++
Atests/cc/97-vararg-many-named.expected-exit | 1+
Atests/cc/98-call-7args.c | 17+++++++++++++++++
Atests/cc/98-call-7args.expected-exit | 1+
Atests/cc/99-init-zero-tail.c | 17+++++++++++++++++
Atests/cc/99-init-zero-tail.expected-exit | 1+
70 files changed, 749 insertions(+), 0 deletions(-)

diff --git a/tests/cc/100-int-suffixes.c b/tests/cc/100-int-suffixes.c @@ -0,0 +1,21 @@ +/* Integer literal suffixes per CC.md §Lexical syntax. */ + +int main(int argc, char **argv) { + long a = 1L; + long long b = 2LL; + unsigned int c = 3U; + unsigned long d = 4UL; + unsigned long long e = 5ULL; + if (a + b + c + d + e != 15) return 1; + + /* Hex / octal */ + int h = 0x1F; /* 31 */ + int o = 017; /* 15 */ + if (h + o != 46) return 2; + + /* Mixed-case suffix */ + long long ll = 0x100ll; + unsigned long long ull = 0xFFull; + if (ll - ull != 1) return 3; + return 0; +} diff --git a/tests/cc/100-int-suffixes.expected-exit b/tests/cc/100-int-suffixes.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/101-char-escapes.c b/tests/cc/101-char-escapes.c @@ -0,0 +1,17 @@ +/* Character escape sequences per CC.md §Lexical syntax: + * \n \t \r \\ \' \" \0 \xNN \NNN */ + +int main(int argc, char **argv) { + if ('\n' != 10) return 1; + if ('\t' != 9) return 2; + if ('\r' != 13) return 3; + if ('\\' != 92) return 4; + if ('\'' != 39) return 5; + if ('\"' != 34) return 6; + if ('\0' != 0) return 7; + if ('\x41' != 65) return 8; + /* Octal up to 3 digits */ + if ('\101' != 65) return 9; + if ('\7' != 7) return 10; + return 0; +} diff --git a/tests/cc/101-char-escapes.expected-exit b/tests/cc/101-char-escapes.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/102-cmpd-narrow.c b/tests/cc/102-cmpd-narrow.c @@ -0,0 +1,22 @@ +/* Compound assignment on narrow targets: char/short. The result is + * truncated/sign-extended back into the lvalue's type. */ + +int main(int argc, char **argv) { + char c = 100; + c += 50; /* 150 -> truncated to (signed char) -106 */ + if ((int)c != -106) return 1; + + short s = 30000; + s += 10000; /* 40000 -> wraps in 16-bit signed: -25536 */ + if ((int)s != -25536) return 2; + + unsigned char uc = 250; + uc += 10; /* 260 -> wraps to 4 */ + if ((int)uc != 4) return 3; + + /* Shift compound */ + char b = 1; + b <<= 2; + if ((int)b != 4) return 4; + return 0; +} diff --git a/tests/cc/102-cmpd-narrow.expected-exit b/tests/cc/102-cmpd-narrow.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/103-ptr-arith-sub.c b/tests/cc/103-ptr-arith-sub.c @@ -0,0 +1,21 @@ +/* Pointer subtraction yielding ptrdiff_t element count, plus + * mixing with array indexing. */ + +int main(int argc, char **argv) { + int a[10]; + int *p = a; + int *q = a + 7; + if (q - p != 7) return 1; + + /* Pointer minus integer. */ + int *r = q - 3; + if (r - p != 4) return 2; + + /* Index a pointer the same as the array. */ + int i; + for (i = 0; i < 10; i = i + 1) a[i] = i * i; + if (p[5] != 25) return 3; + if (*(p + 5) != 25) return 4; + if ((q - 2)[0] != 25) return 5; + return 0; +} diff --git a/tests/cc/103-ptr-arith-sub.expected-exit b/tests/cc/103-ptr-arith-sub.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/104-bool-type.c b/tests/cc/104-bool-type.c @@ -0,0 +1,18 @@ +/* _Bool type: 1 byte, values 0 or 1. CC.md §Types lists _Bool. */ + +int main(int argc, char **argv) { + _Bool t = 1; + _Bool f = 0; + if (sizeof(_Bool) != 1) return 1; + if (t != 1) return 2; + if (f != 0) return 3; + + /* Assigning a non-zero scalar should produce 1, not the raw value. */ + _Bool b = 42; + if (b != 1) return 4; + + /* Negation. */ + if (!t != 0) return 5; + if (!f != 1) return 6; + return 0; +} diff --git a/tests/cc/104-bool-type.expected-exit b/tests/cc/104-bool-type.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/105-deeper-deref.c b/tests/cc/105-deeper-deref.c @@ -0,0 +1,26 @@ +/* Multi-level pointer indirection and address-of patterns + * frequently seen in tcc.c. */ + +int triple_deref(int ***p) { return ***p; } + +int main(int argc, char **argv) { + int x = 42; + int *p = &x; + int **pp = &p; + int ***ppp = &pp; + if (triple_deref(ppp) != 42) return 1; + + /* Modify through three levels. */ + ***ppp = 99; + if (x != 99) return 2; + + /* Pointer to array of pointers. */ + int a = 1, b = 2, c = 3; + int *arr[3] = { &a, &b, &c }; + int **q = arr; + if (*q[0] != 1) return 3; + if (*q[2] != 3) return 4; + *q[1] = 20; + if (b != 20) return 5; + return 0; +} diff --git a/tests/cc/105-deeper-deref.expected-exit b/tests/cc/105-deeper-deref.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/106-switch-fallthrough.c b/tests/cc/106-switch-fallthrough.c @@ -0,0 +1,22 @@ +/* Switch with explicit fallthrough (no break) and default not last. */ + +int classify(int x) { + int r = 0; + switch (x) { + case 1: r = r + 1; /* fallthrough */ + case 2: r = r + 10; /* fallthrough */ + case 3: r = r + 100; break; + default: r = -1; break; + case 4: r = r + 1000; break; + } + return r; +} + +int main(int argc, char **argv) { + if (classify(1) != 111) return 1; /* 1+10+100 */ + if (classify(2) != 110) return 2; /* 10+100 */ + if (classify(3) != 100) return 3; + if (classify(4) != 1000) return 4; + if (classify(5) != -1) return 5; + return 0; +} diff --git a/tests/cc/106-switch-fallthrough.expected-exit b/tests/cc/106-switch-fallthrough.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/107-static-file-scope.c b/tests/cc/107-static-file-scope.c @@ -0,0 +1,12 @@ +/* file-scope `static` variable: internal linkage but observable. + * Codegen should still allocate and initialize it. */ + +static int counter = 100; +static int bump(void) { counter = counter + 1; return counter; } + +int main(int argc, char **argv) { + if (bump() != 101) return 1; + if (bump() != 102) return 2; + if (counter != 102) return 3; + return 0; +} diff --git a/tests/cc/107-static-file-scope.expected-exit b/tests/cc/107-static-file-scope.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/108-typedef-fnptr.c b/tests/cc/108-typedef-fnptr.c @@ -0,0 +1,24 @@ +/* typedef of a function pointer type, used as a parameter, in a typedef + * struct. */ + +typedef int (*binop)(int, int); + +int do_add(int a, int b) { return a + b; } +int do_mul(int a, int b) { return a * b; } + +int apply(binop op, int x, int y) { return op(x, y); } + +typedef struct { + binop op; + int k; +} OP; + +int main(int argc, char **argv) { + if (apply(do_add, 3, 4) != 7) return 1; + if (apply(do_mul, 5, 6) != 30) return 2; + + OP rec = { do_add, 99 }; + if (rec.op(10, 20) != 30) return 3; + if (rec.k != 99) return 4; + return 0; +} diff --git a/tests/cc/108-typedef-fnptr.expected-exit b/tests/cc/108-typedef-fnptr.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/109-typedef-anon.c b/tests/cc/109-typedef-anon.c @@ -0,0 +1,25 @@ +/* Anonymous struct via typedef (the form tcc.c uses for many types). + * No struct tag, only a typedef name. */ + +typedef struct { + int x; + int y; +} Point; + +Point make(int a, int b) { + Point p; + p.x = a; + p.y = b; + return p; +} + +int main(int argc, char **argv) { + Point a; + a.x = 3; + a.y = 4; + if (a.x + a.y != 7) return 1; + + Point b = make(10, 20); + if (b.x != 10 || b.y != 20) return 2; + return 0; +} diff --git a/tests/cc/109-typedef-anon.expected-exit b/tests/cc/109-typedef-anon.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/110-cast-fnptr.c b/tests/cc/110-cast-fnptr.c @@ -0,0 +1,12 @@ +/* Cast of a function pointer through void* and back. tcc.c does this + * to generic-ize callbacks. */ + +int dbl(int x) { return x * 2; } + +int main(int argc, char **argv) { + int (*fp)(int) = dbl; + void *gp = (void *)fp; + int (*fp2)(int) = (int (*)(int))gp; + if (fp2(21) != 42) return 1; + return 0; +} diff --git a/tests/cc/110-cast-fnptr.expected-exit b/tests/cc/110-cast-fnptr.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/111-struct-ret-1word.c b/tests/cc/111-struct-ret-1word.c @@ -0,0 +1,29 @@ +/* Struct return — 1 word. + * + * Whole struct fits in a single 8-byte return slot. Per CC-CONTRACTS + * §3.2, the function exit emits `LD a0, [sp + <return-slot>]`; a + * single-int struct rides through that slot intact. */ + +struct OneInt { int x; }; /* 4 bytes */ +struct TwoInt { int x; int y; }; /* 8 bytes */ +struct OneLong { long v; }; /* 8 bytes */ + +struct OneInt ret1(int v) { struct OneInt s; s.x = v; return s; } +struct TwoInt ret2(int a, int b) { struct TwoInt s; s.x = a; s.y = b; return s; } +struct OneLong retL(long v) { struct OneLong s; s.v = v; return s; } + +int main(int argc, char **argv) { + struct OneInt a = ret1(42); + if (a.x != 42) return 1; + + struct TwoInt b = ret2(7, 9); + if (b.x != 7 || b.y != 9) return 2; + + struct OneLong c = retL(0x1122334455667788L); + if (c.v != 0x1122334455667788L) return 3; + + /* Call result used directly without intermediate. */ + if (ret1(99).x != 99) return 4; + if (ret2(11, 22).y != 22) return 5; + return 0; +} diff --git a/tests/cc/111-struct-ret-1word.expected-exit b/tests/cc/111-struct-ret-1word.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/112-struct-ret-2word.c b/tests/cc/112-struct-ret-2word.c @@ -0,0 +1,32 @@ +/* Struct return — 2 words (16 bytes). + * + * The cg return path uses one 8-byte return slot loaded into a0 at + * function exit (CC-CONTRACTS §3.2). A 2-word struct does not fit; on + * most ABIs the second word goes through a1 (or a hidden-pointer + * convention is used). This test surfaces whichever path is wired, + * or the absence of one. */ + +struct Pair { long a; long b; }; /* 16 bytes */ + +struct Pair make_pair(long a, long b) { + struct Pair p; + p.a = a; + p.b = b; + return p; +} + +int main(int argc, char **argv) { + struct Pair p = make_pair(0x1111111111111111L, 0x2222222222222222L); + if (p.a != 0x1111111111111111L) return 1; + if (p.b != 0x2222222222222222L) return 2; + + /* Direct field access on the call result. */ + if (make_pair(7, 8).a != 7) return 3; + if (make_pair(7, 8).b != 8) return 4; + + /* Several calls in succession — exercises spill/reload. */ + struct Pair q = make_pair(100, 200); + struct Pair r = make_pair(300, 400); + if (q.a + q.b + r.a + r.b != 1000) return 5; + return 0; +} diff --git a/tests/cc/112-struct-ret-2word.expected-exit b/tests/cc/112-struct-ret-2word.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/113-struct-ret-3word.c b/tests/cc/113-struct-ret-3word.c @@ -0,0 +1,36 @@ +/* Struct return — >2 words (24 bytes). + * + * Larger than any reasonable register-pair return. Most real ABIs + * would use a hidden first-arg pointer (sret/byval) so the callee + * stores the result through that pointer. CC-CONTRACTS §3.2 does + * not describe such a convention, so this exercises whatever the + * code does today — a loud fail is the expected outcome until a + * large-return ABI lands. */ + +struct Triple { long a; long b; long c; }; /* 24 bytes */ + +struct Triple make_triple(long a, long b, long c) { + struct Triple t; + t.a = a; + t.b = b; + t.c = c; + return t; +} + +int main(int argc, char **argv) { + struct Triple t = make_triple(10, 20, 30); + if (t.a != 10) return 1; + if (t.b != 20) return 2; + if (t.c != 30) return 3; + + /* Field of inline call result. */ + if (make_triple(1, 2, 3).c != 3) return 4; + + /* Two calls back-to-back: caller must allocate a fresh result + * area for each so they don't alias. */ + struct Triple u = make_triple(100, 200, 300); + struct Triple v = make_triple(400, 500, 600); + if (u.a + u.b + u.c != 600) return 5; + if (v.a + v.b + v.c != 1500) return 6; + return 0; +} diff --git a/tests/cc/113-struct-ret-3word.expected-exit b/tests/cc/113-struct-ret-3word.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/114-struct-ret-many-args.c b/tests/cc/114-struct-ret-many-args.c @@ -0,0 +1,30 @@ +/* Struct return interacting with many args. + * + * Two pressures at once: + * - >4 named args spill from arg regs into stack-staged slots + * (CC-CONTRACTS §3.4 outgoing-arg staging). + * - The return value is a struct, which on a hidden-pointer ABI + * would consume an *additional* arg slot (often arg0). + * If both paths exist they must compose; the offsets must not collide. */ + +struct Pair { long a; long b; }; /* 2 words */ + +struct Pair big_combine(int p0, int p1, int p2, int p3, + int p4, int p5, int p6, int p7) { + struct Pair r; + r.a = p0 + p1 + p2 + p3; /* 4 reg-args */ + r.b = p4 + p5 + p6 + p7; /* 4 stack-staged args */ + return r; +} + +int main(int argc, char **argv) { + struct Pair r = big_combine(1, 2, 3, 4, 5, 6, 7, 8); + if (r.a != 10) return 1; /* 1+2+3+4 */ + if (r.b != 26) return 2; /* 5+6+7+8 */ + + /* Same call, second time — staging area must be re-usable. */ + struct Pair s = big_combine(10, 20, 30, 40, 50, 60, 70, 80); + if (s.a != 100) return 3; + if (s.b != 260) return 4; + return 0; +} diff --git a/tests/cc/114-struct-ret-many-args.expected-exit b/tests/cc/114-struct-ret-many-args.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/115-struct-ret-3word-many-args.c b/tests/cc/115-struct-ret-3word-many-args.c @@ -0,0 +1,26 @@ +/* Struct return >2 words combined with many args. + * + * If a hidden result-pointer convention exists, it would land in arg0 + * and shift the visible args by one slot — easy to miscount when also + * spilling >4 args to the stage. This stresses both at once. */ + +struct Wide { long a; long b; long c; long d; }; /* 32 bytes */ + +struct Wide build(int p0, int p1, int p2, int p3, + int p4, int p5, int p6, int p7) { + struct Wide w; + w.a = p0 + p1; + w.b = p2 + p3; + w.c = p4 + p5; + w.d = p6 + p7; + return w; +} + +int main(int argc, char **argv) { + struct Wide w = build(1, 2, 3, 4, 5, 6, 7, 8); + if (w.a != 3) return 1; + if (w.b != 7) return 2; + if (w.c != 11) return 3; + if (w.d != 15) return 4; + return 0; +} diff --git a/tests/cc/115-struct-ret-3word-many-args.expected-exit b/tests/cc/115-struct-ret-3word-many-args.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/116-struct-ret-vararg.c b/tests/cc/116-struct-ret-vararg.c @@ -0,0 +1,45 @@ +/* Struct return from a variadic function. + * + * Combines two ABI-shaped concerns: the variadic save area (cg.scm + * around L1027 has a TODO for variadic args >= 4) and the struct + * return slot (single 8-byte slot per CC-CONTRACTS §3.2). + * + * If the hidden return pointer is wired through arg0, the variadic + * save area's indexing must skip it; if it isn't wired, a 2-word + * struct return will silently truncate. Either way this surfaces + * the interaction. */ + +typedef char *va_list; + +struct Pair { long a; long b; }; + +struct Pair sum_pair(int n, ...) { + va_list ap; + long total_a = 0; + long total_b = 0; + int i = 0; + __builtin_va_start(ap, n); + while (i < n) { + total_a = total_a + __builtin_va_arg(ap, int); + total_b = total_b + __builtin_va_arg(ap, int) * 10; + i = i + 1; + } + __builtin_va_end(ap); + struct Pair r; + r.a = total_a; + r.b = total_b; + return r; +} + +int main(int argc, char **argv) { + /* Two pairs of (int,int): (1,2) (3,4) -> a = 1+3 = 4, b = (2+4)*10 = 60 */ + struct Pair r = sum_pair(2, 1, 2, 3, 4); + if (r.a != 4) return 1; + if (r.b != 60) return 2; + + /* Three pairs: (10,1) (20,2) (30,3) -> a = 60, b = 60 */ + struct Pair s = sum_pair(3, 10, 1, 20, 2, 30, 3); + if (s.a != 60) return 3; + if (s.b != 60) return 4; + return 0; +} diff --git a/tests/cc/116-struct-ret-vararg.expected-exit b/tests/cc/116-struct-ret-vararg.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/82-union-basic.c b/tests/cc/82-union-basic.c @@ -0,0 +1,26 @@ +/* Union: shared storage, member access. CC.md §Types lists union. */ + +union Box { int i; char c[4]; }; + +int test_int_then_char(void) { + union Box u; + u.i = 0; + u.c[0] = 1; + u.c[1] = 2; + u.c[2] = 3; + u.c[3] = 4; + /* On LP64 little-endian: 0x04030201 */ + return u.i == 0x04030201 ? 0 : 1; +} + +int test_size(void) { + return sizeof(union Box) == 4 ? 0 : 2; +} + +int main(int argc, char **argv) { + int r = test_int_then_char(); + if (r) return r; + r = test_size(); + if (r) return r; + return 0; +} diff --git a/tests/cc/82-union-basic.expected-exit b/tests/cc/82-union-basic.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/83-struct-byval-arg.c b/tests/cc/83-struct-byval-arg.c @@ -0,0 +1,14 @@ +/* Pass struct by value as fn arg, return struct by value. + * CC.md §Declarations doesn't forbid; tcc.c uses it. */ + +struct Pair { int a; int b; }; + +int sum_pair(struct Pair p) { return p.a + p.b; } + +int main(int argc, char **argv) { + struct Pair x = { 3, 7 }; + if (sum_pair(x) != 10) return 1; + /* Caller's struct must be unmodified by callee scope */ + if (x.a != 3 || x.b != 7) return 2; + return 0; +} diff --git a/tests/cc/83-struct-byval-arg.expected-exit b/tests/cc/83-struct-byval-arg.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/84-struct-assign.c b/tests/cc/84-struct-assign.c @@ -0,0 +1,15 @@ +/* Struct copy via assignment operator. C99 6.5.16. */ + +struct Pair { int a; int b; }; + +int main(int argc, char **argv) { + struct Pair x = { 3, 7 }; + struct Pair y; + y = x; + if (y.a != 3) return 1; + if (y.b != 7) return 2; + /* Mutating x must not affect y. */ + x.a = 99; + if (y.a != 3) return 3; + return 0; +} diff --git a/tests/cc/84-struct-assign.expected-exit b/tests/cc/84-struct-assign.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/85-string-concat.c b/tests/cc/85-string-concat.c @@ -0,0 +1,9 @@ +/* Adjacent string-literal concatenation. CC.md §Lexical syntax: "a" "b" -> "ab". */ + +int main(int argc, char **argv) { + char *s = "hel" "lo"; + if (s[0] != 'h') return 1; + if (s[4] != 'o') return 2; + if (s[5] != 0) return 3; + return 0; +} diff --git a/tests/cc/85-string-concat.expected-exit b/tests/cc/85-string-concat.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/86-nested-ternary.c b/tests/cc/86-nested-ternary.c @@ -0,0 +1,17 @@ +/* Nested ternary across both arms. */ + +int classify(int x) { + return x < 0 ? -1 : x == 0 ? 0 : x < 10 ? 1 : 2; +} + +int main(int argc, char **argv) { + if (classify(-5) != -1) return 1; + if (classify(0) != 0) return 2; + if (classify(5) != 1) return 3; + if (classify(50) != 2) return 4; + /* Nested in both arms. */ + int a = 1, b = 1; + int r = a ? (b ? 10 : 20) : (b ? 30 : 40); + if (r != 10) return 5; + return 0; +} diff --git a/tests/cc/86-nested-ternary.expected-exit b/tests/cc/86-nested-ternary.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/87-sizeof-noeval.c b/tests/cc/87-sizeof-noeval.c @@ -0,0 +1,9 @@ +/* sizeof does NOT evaluate its operand expression. CC.md §Expressions. */ + +int main(int argc, char **argv) { + int x = 5; + int s = sizeof(x++); /* must not increment x */ + if (s != 4) return 1; + if (x != 5) return 2; + return 0; +} diff --git a/tests/cc/87-sizeof-noeval.expected-exit b/tests/cc/87-sizeof-noeval.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/88-chained-assign.c b/tests/cc/88-chained-assign.c @@ -0,0 +1,15 @@ +/* Chained assignment is right-associative. C99 6.5.16. */ + +int main(int argc, char **argv) { + int a, b, c; + a = b = c = 7; + if (a != 7) return 1; + if (b != 7) return 2; + if (c != 7) return 3; + /* Result of assignment is the lhs after assignment. */ + int x = (a = 3) + (b = 4); + if (x != 7) return 4; + if (a != 3) return 5; + if (b != 4) return 6; + return 0; +} diff --git a/tests/cc/88-chained-assign.expected-exit b/tests/cc/88-chained-assign.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/89-for-comma.c b/tests/cc/89-for-comma.c @@ -0,0 +1,13 @@ +/* Comma in for-loop init and step clauses. */ + +int main(int argc, char **argv) { + int i, j, sum = 0; + for (i = 0, j = 10; i < 5; i = i + 1, j = j - 1) { + sum = sum + i + j; + } + /* i: 0..4 sum=10; j: 10..6 sum=40; total=50 */ + if (sum != 50) return 1; + if (i != 5) return 2; + if (j != 5) return 3; + return 0; +} diff --git a/tests/cc/89-for-comma.expected-exit b/tests/cc/89-for-comma.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/90-incdec-member.c b/tests/cc/90-incdec-member.c @@ -0,0 +1,18 @@ +/* Pre/post increment on struct members and through pointers. */ + +struct S { int x; int y; }; + +int main(int argc, char **argv) { + struct S s = { 5, 10 }; + int a = s.x++; /* a=5, s.x=6 */ + int b = ++s.y; /* b=11, s.y=11 */ + if (a != 5 || s.x != 6) return 1; + if (b != 11 || s.y != 11) return 2; + + struct S *p = &s; + int c = p->x++; /* c=6, s.x=7 */ + int d = --p->y; /* d=10, s.y=10 */ + if (c != 6 || s.x != 7) return 3; + if (d != 10 || s.y != 10) return 4; + return 0; +} diff --git a/tests/cc/90-incdec-member.expected-exit b/tests/cc/90-incdec-member.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/91-multi-decl.c b/tests/cc/91-multi-decl.c @@ -0,0 +1,16 @@ +/* Multiple declarators in one declaration: int a=1, b=2, c=3; */ + +int g_a = 1, g_b = 2, g_c = 3; + +int main(int argc, char **argv) { + if (g_a + g_b + g_c != 6) return 1; + int a = 10, b = 20, c; + c = a + b; + if (c != 30) return 2; + /* Mixed types via pointers in same decl. */ + int x = 5; + int y = 7, *p = &x; + if (*p != 5) return 3; + if (y != 7) return 4; + return 0; +} diff --git a/tests/cc/91-multi-decl.expected-exit b/tests/cc/91-multi-decl.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/92-array-of-struct.c b/tests/cc/92-array-of-struct.c @@ -0,0 +1,16 @@ +/* Local array of structs with brace-nested initializer. */ + +struct Point { int x; int y; }; + +int main(int argc, char **argv) { + struct Point a[3] = { {1, 2}, {3, 4}, {5, 6} }; + int sum = 0; + int i; + for (i = 0; i < 3; i = i + 1) sum = sum + a[i].x + a[i].y; + if (sum != 21) return 1; + /* Address arithmetic on array of structs. */ + struct Point *p = a; + if ((p + 2)->x != 5) return 2; + if ((p + 2)->y != 6) return 3; + return 0; +} diff --git a/tests/cc/92-array-of-struct.expected-exit b/tests/cc/92-array-of-struct.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/93-string-table.c b/tests/cc/93-string-table.c @@ -0,0 +1,19 @@ +/* Array of pointers to string literals. tcc.c uses this pattern heavily + * (token names, error messages). */ + +char *names[] = { "alpha", "beta", "gamma" }; + +int strlen_local(char *s) { + int n = 0; + while (*s) { n = n + 1; s = s + 1; } + return n; +} + +int main(int argc, char **argv) { + if (strlen_local(names[0]) != 5) return 1; + if (strlen_local(names[1]) != 4) return 2; + if (strlen_local(names[2]) != 5) return 3; + if (names[0][0] != 'a') return 4; + if (names[2][4] != 'a') return 5; + return 0; +} diff --git a/tests/cc/93-string-table.expected-exit b/tests/cc/93-string-table.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/94-narrow-bitwise.c b/tests/cc/94-narrow-bitwise.c @@ -0,0 +1,19 @@ +/* Bitwise on narrow types: char/short are sign-extended to int before op. */ + +int main(int argc, char **argv) { + /* (char)0xFF == -1; sign-extended -> 0xFFFFFFFF; & 0xAA -> 0xAA */ + char c = (char)0xFF; + int r = c & 0xAA; + if (r != 0xAA) return 1; + + /* unsigned char: zero-extends. */ + unsigned char u = 0xFF; + int r2 = u & 0xAA; + if (r2 != 0xAA) return 2; + + /* Shift on short: width is int. */ + short s = 1; + int sh = s << 16; + if (sh != 0x10000) return 3; + return 0; +} diff --git a/tests/cc/94-narrow-bitwise.expected-exit b/tests/cc/94-narrow-bitwise.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/95-fwd-fn-decl.c b/tests/cc/95-fwd-fn-decl.c @@ -0,0 +1,10 @@ +/* Forward function declaration (prototype) before definition. */ + +int forward_then_call(int); + +int main(int argc, char **argv) { + if (forward_then_call(5) != 25) return 1; + return 0; +} + +int forward_then_call(int x) { return x * x; } diff --git a/tests/cc/95-fwd-fn-decl.expected-exit b/tests/cc/95-fwd-fn-decl.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/96-fwd-struct.c b/tests/cc/96-fwd-struct.c @@ -0,0 +1,20 @@ +/* Forward struct declaration: struct Node; then struct Node *next inside + * the actual definition. CC.md §Types: forward declarations supported. */ + +struct Node; + +struct Node { + int v; + struct Node *next; +}; + +int main(int argc, char **argv) { + struct Node tail = { 2, 0 }; + struct Node head; + head.v = 1; + head.next = &tail; + if (head.v != 1) return 1; + if (head.next->v != 2) return 2; + if (head.next->next != 0) return 3; + return 0; +} diff --git a/tests/cc/96-fwd-struct.expected-exit b/tests/cc/96-fwd-struct.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/97-vararg-many-named.c b/tests/cc/97-vararg-many-named.c @@ -0,0 +1,26 @@ +/* Variadic with >4 named args before the ellipsis. cg.scm comment notes + * the save area only holds the first 4 incoming args; this probes + * va_arg access of variadic slots when named args already fill it. */ + +typedef char *va_list; + +int summer(int a, int b, int c, int d, int e, ...) { + va_list ap; + int n = 3; + int total = a + b + c + d + e; + int i = 0; + __builtin_va_start(ap, e); + while (i < n) { + total = total + __builtin_va_arg(ap, int); + i = i + 1; + } + __builtin_va_end(ap); + return total; +} + +int main(int argc, char **argv) { + /* 1+2+3+4+5 + 6+7+8 = 36 */ + int r = summer(1, 2, 3, 4, 5, 6, 7, 8); + if (r != 36) return 1; + return 0; +} diff --git a/tests/cc/97-vararg-many-named.expected-exit b/tests/cc/97-vararg-many-named.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/98-call-7args.c b/tests/cc/98-call-7args.c @@ -0,0 +1,17 @@ +/* Function call with > 6 args. Many ABIs only pass 6 in registers; the + * rest go on the stack. This exercises caller stack-arg setup and + * callee parameter access at indices >= 6. */ + +int sum7(int a, int b, int c, int d, int e, int f, int g) { + return a + b + c + d + e + f + g; +} + +int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { + return a + b + c + d + e + f + g + h; +} + +int main(int argc, char **argv) { + if (sum7(1, 2, 3, 4, 5, 6, 7) != 28) return 1; + if (sum8(1, 2, 3, 4, 5, 6, 7, 8) != 36) return 2; + return 0; +} diff --git a/tests/cc/98-call-7args.expected-exit b/tests/cc/98-call-7args.expected-exit @@ -0,0 +1 @@ +0 diff --git a/tests/cc/99-init-zero-tail.c b/tests/cc/99-init-zero-tail.c @@ -0,0 +1,17 @@ +/* Partial initializer: missing trailing initializers are zero. C99 6.7.8 */ + +int main(int argc, char **argv) { + int a[5] = { 1, 2 }; /* a[2..4] = 0 */ + if (a[0] != 1) return 1; + if (a[1] != 2) return 2; + if (a[2] != 0) return 3; + if (a[3] != 0) return 4; + if (a[4] != 0) return 5; + + struct S { int x; int y; int z; }; + struct S s = { 7 }; + if (s.x != 7) return 6; + if (s.y != 0) return 7; + if (s.z != 0) return 8; + return 0; +} diff --git a/tests/cc/99-init-zero-tail.expected-exit b/tests/cc/99-init-zero-tail.expected-exit @@ -0,0 +1 @@ +0