boot2

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

commit 5b5ed0309cc714b0e05fd444067adafc6265d892
parent 0cd6aa8cf9ff39a34a3e4de9f2508aaa7fb6f6ed
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 29 Apr 2026 13:05:47 -0700

tests/cc-libc: cover all P1pp syscalls + complex libc layers

Each P1pp syscall now has a fixture that drives it through the public
libc surface: sys_exit (exit), sys_open/close/write/read (fopen +
fputs + fread), sys_lseek (fseek), sys_unlink (unlink). Adds targeted
fixtures for printf %s / mixed va_args, strlen/strcmp/strchr,
malloc/free cycle, and atoi sign handling.

Diffstat:
Atests/cc-libc/09-exit-code.c | 13+++++++++++++
Atests/cc-libc/09-exit-code.expected-exit | 1+
Atests/cc-libc/10-file-roundtrip.c | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-libc/10-file-roundtrip.expected | 1+
Atests/cc-libc/11-fseek.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-libc/11-fseek.expected | 1+
Atests/cc-libc/12-unlink.c | 46++++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-libc/12-unlink.expected | 1+
Atests/cc-libc/13-printf-string.c | 12++++++++++++
Atests/cc-libc/13-printf-string.expected | 1+
Atests/cc-libc/14-printf-mixed.c | 12++++++++++++
Atests/cc-libc/14-printf-mixed.expected | 1+
Atests/cc-libc/15-strops.c | 40++++++++++++++++++++++++++++++++++++++++
Atests/cc-libc/15-strops.expected | 1+
Atests/cc-libc/16-malloc-free.c | 44++++++++++++++++++++++++++++++++++++++++++++
Atests/cc-libc/16-malloc-free.expected | 1+
Atests/cc-libc/17-atoi.c | 14++++++++++++++
Atests/cc-libc/17-atoi.expected | 2++
18 files changed, 303 insertions(+), 0 deletions(-)

diff --git a/tests/cc-libc/09-exit-code.c b/tests/cc-libc/09-exit-code.c @@ -0,0 +1,13 @@ +/* exit(N) -> stdlib/exit.c -> stdlib/__exit.c -> lispcc-syscall.c::_exit + * -> P1pp sys_exit. 00-exit covers the implicit return-from-main path; + * this fixture exercises the explicit terminator that abort() / errors + * out of main use. The literal `return 99;` after the exit call must + * never run — if it does, the harness sees exit code 99 and FAILs. */ +extern void exit (int code); + +int +main (void) +{ + exit (7); + return 99; +} diff --git a/tests/cc-libc/09-exit-code.expected-exit b/tests/cc-libc/09-exit-code.expected-exit @@ -0,0 +1 @@ +7 diff --git a/tests/cc-libc/10-file-roundtrip.c b/tests/cc-libc/10-file-roundtrip.c @@ -0,0 +1,55 @@ +/* fopen("w") + fputs + fclose, then fopen("r") + fread + fclose. + * + * Smoke-tests the full file I/O stack on top of P1pp sys_open / + * sys_close / sys_write / sys_read: + * - fopen -> stdio/fopen.c -> _open3 -> sys_open + * - fputs -> stdio/fputs.c -> fputc -> _write -> sys_write + * - fclose -> stdio/fclose.c -> close -> sys_close + * - fread -> stdio/fread.c -> read -> _read -> sys_read + * + * Mes's fopen returns the fd cast to FILE*, so a single declaration of + * FILE as `long` is enough to thread it through. Diagnostics use the + * already-proven write() path so a failure inside fopen / fread is + * easy to spot from stdout. */ +typedef long FILE; +typedef long ssize_t; +typedef unsigned long size_t; + +extern FILE *fopen (char const *path, char const *mode); +extern int fclose (FILE *stream); +extern int fputs (char const *s, FILE *stream); +extern size_t fread (void *ptr, size_t size, size_t count, FILE *stream); +extern ssize_t write (int fd, void const *buf, size_t n); + +int +main (void) +{ + char const *path = "/tmp/lispcc-cclibc-10"; + + FILE *w = fopen (path, "w"); + if (!w) + { + write (1, "fopen-w-fail\n", 13); + return 1; + } + fputs ("hello\n", w); + fclose (w); + + FILE *r = fopen (path, "r"); + if (!r) + { + write (1, "fopen-r-fail\n", 13); + return 2; + } + char buf[8]; + size_t n = fread (buf, 1, 6, r); + fclose (r); + + if (n != 6) + { + write (1, "short-read\n", 11); + return 3; + } + write (1, buf, 6); + return 0; +} diff --git a/tests/cc-libc/10-file-roundtrip.expected b/tests/cc-libc/10-file-roundtrip.expected @@ -0,0 +1 @@ +hello diff --git a/tests/cc-libc/11-fseek.c b/tests/cc-libc/11-fseek.c @@ -0,0 +1,57 @@ +/* fseek -> stdio/fseek.c -> lseek (posix) -> _lseek -> sys_lseek. + * + * Lays a known payload in a tmp file, opens it for reading, fseek's + * past the first 7 bytes, then fread's the next 5. Expected stdout is + * "world" — the substring after "hello, ". Anchors the lseek path with + * a non-zero, non-end offset so a SEEK_SET that silently no-ops would + * read "hello," instead of "world". */ +typedef long FILE; +typedef long ssize_t; +typedef unsigned long size_t; + +extern FILE *fopen (char const *path, char const *mode); +extern int fclose (FILE *stream); +extern int fputs (char const *s, FILE *stream); +extern int fseek (FILE *stream, long offset, int whence); +extern size_t fread (void *ptr, size_t size, size_t count, FILE *stream); +extern ssize_t write (int fd, void const *buf, size_t n); + +int +main (void) +{ + char const *path = "/tmp/lispcc-cclibc-11"; + + FILE *w = fopen (path, "w"); + if (!w) + { + write (1, "fopen-w-fail\n", 13); + return 1; + } + fputs ("hello, world\n", w); + fclose (w); + + FILE *r = fopen (path, "r"); + if (!r) + { + write (1, "fopen-r-fail\n", 13); + return 2; + } + if (fseek (r, 7, 0)) /* SEEK_SET = 0 */ + { + write (1, "fseek-fail\n", 11); + fclose (r); + return 3; + } + char buf[8]; + size_t n = fread (buf, 1, 5, r); + fclose (r); + + if (n != 5) + { + write (1, "short-read\n", 11); + return 4; + } + buf[5] = '\n'; + write (1, buf, 6); + return 0; +} diff --git a/tests/cc-libc/11-fseek.expected b/tests/cc-libc/11-fseek.expected @@ -0,0 +1 @@ +world diff --git a/tests/cc-libc/12-unlink.c b/tests/cc-libc/12-unlink.c @@ -0,0 +1,46 @@ +/* unlink -> lispcc-syscall.c::unlink -> P1pp sys_unlink (= unlinkat + * with AT_FDCWD). Drives the syscall by creating a file, removing it, + * then re-opening it for read and asserting fopen now returns NULL. + * The first fopen("w") must succeed, the second fopen("r") after + * unlink must fail — anything else means the path either never + * existed or the unlink no-oped. */ +typedef long FILE; +typedef long ssize_t; +typedef unsigned long size_t; + +extern FILE *fopen (char const *path, char const *mode); +extern int fclose (FILE *stream); +extern int fputs (char const *s, FILE *stream); +extern int unlink (char const *path); +extern ssize_t write (int fd, void const *buf, size_t n); + +int +main (void) +{ + char const *path = "/tmp/lispcc-cclibc-12"; + + FILE *w = fopen (path, "w"); + if (!w) + { + write (1, "fopen-w-fail\n", 13); + return 1; + } + fputs ("x\n", w); + fclose (w); + + if (unlink (path)) + { + write (1, "unlink-fail\n", 12); + return 2; + } + + FILE *r = fopen (path, "r"); + if (r) + { + write (1, "still-there\n", 12); + fclose (r); + return 3; + } + write (1, "gone\n", 5); + return 0; +} diff --git a/tests/cc-libc/12-unlink.expected b/tests/cc-libc/12-unlink.expected @@ -0,0 +1 @@ +gone diff --git a/tests/cc-libc/13-printf-string.c b/tests/cc-libc/13-printf-string.c @@ -0,0 +1,12 @@ +/* printf("%s") — exercises vfprintf's string-conversion branch on top + * of the va_arg pull. 04 covers the no-conversions path, 05 covers + * %d. This pins %s, which is the conversion the most code paths in + * tcc.c rely on (printf("%s\n", error_msg) etc.). */ +extern int printf (char const *fmt, ...); + +int +main (void) +{ + printf ("hello, %s\n", "world"); + return 0; +} diff --git a/tests/cc-libc/13-printf-string.expected b/tests/cc-libc/13-printf-string.expected @@ -0,0 +1 @@ +hello, world diff --git a/tests/cc-libc/14-printf-mixed.c b/tests/cc-libc/14-printf-mixed.c @@ -0,0 +1,12 @@ +/* printf with %s + %d + %x in one call — three va_arg pulls of two + * different widths/types, plus three vfprintf conversion branches. + * Catches bugs where one pull works in isolation (covered by 05/13) + * but the second/third pull walks the va_list off the rails. */ +extern int printf (char const *fmt, ...); + +int +main (void) +{ + printf ("%s=%d %x\n", "answer", 42, 255); + return 0; +} diff --git a/tests/cc-libc/14-printf-mixed.expected b/tests/cc-libc/14-printf-mixed.expected @@ -0,0 +1 @@ +answer=42 ff diff --git a/tests/cc-libc/15-strops.c b/tests/cc-libc/15-strops.c @@ -0,0 +1,40 @@ +/* Public string ops the libc surfaces from string/. tests/cc/129 + * exercises the libp1pp-provided memcpy/memset/strlen labels by way + * of bare-name extern decls; this fixture instead drives the calls + * through the libc.P1pp surface (where strcmp / strchr come from + * mes's string/strcmp.c and string/strchr.c). */ +typedef unsigned long size_t; +typedef long ssize_t; + +extern size_t strlen (char const *s); +extern int strcmp (char const *a, char const *b); +extern char *strchr (char const *s, int c); +extern ssize_t write (int fd, void const *buf, size_t n); + +int +main (void) +{ + if (strlen ("abc") != 3) + { + write (1, "len-fail\n", 9); + return 1; + } + if (strcmp ("foo", "foo") != 0) + { + write (1, "eq-fail\n", 8); + return 2; + } + if (strcmp ("foo", "bar") == 0) + { + write (1, "ne-fail\n", 8); + return 3; + } + char *p = strchr ("hello", 'l'); + if (!p || *p != 'l' || p[1] != 'l') + { + write (1, "chr-fail\n", 9); + return 4; + } + write (1, "ok\n", 3); + return 0; +} diff --git a/tests/cc-libc/15-strops.expected b/tests/cc-libc/15-strops.expected @@ -0,0 +1 @@ +ok diff --git a/tests/cc-libc/16-malloc-free.c b/tests/cc-libc/16-malloc-free.c @@ -0,0 +1,44 @@ +/* Two-cycle malloc/free smoke. mes's free is a no-op (linux/malloc.c + * is bump-only, by design — tcc-boot2 never reaches a real allocator), + * so this fixture just verifies that calling free between two malloc + * requests doesn't perturb the bump cursor: the second allocation + * must still hand back a usable pointer at strictly higher address. + * 07-malloc-roundtrip covers the single-malloc round-trip; this adds + * the free + second-malloc layer. */ +typedef unsigned long size_t; +typedef long ssize_t; + +extern void *malloc (size_t size); +extern void free (void *ptr); +extern ssize_t write (int fd, void const *buf, size_t n); + +int +main (void) +{ + char *p = (char *) malloc (8); + if (!p) + { + write (1, "m1-null\n", 8); + return 1; + } + p[0] = 'A'; + free (p); + + char *q = (char *) malloc (16); + if (!q) + { + write (1, "m2-null\n", 8); + return 2; + } + if (q <= p) + { + write (1, "no-advance\n", 11); + return 3; + } + q[0] = 'O'; + q[1] = 'K'; + q[2] = '\n'; + write (1, q, 3); + free (q); + return 0; +} diff --git a/tests/cc-libc/16-malloc-free.expected b/tests/cc-libc/16-malloc-free.expected @@ -0,0 +1 @@ +OK diff --git a/tests/cc-libc/17-atoi.c b/tests/cc-libc/17-atoi.c @@ -0,0 +1,14 @@ +/* atoi -> stdlib/atoi.c -> mes/abtol.c. Negative-sign and digit-loop + * paths in one fixture; printf is reused from 14-printf-mixed for the + * two %d round-trips, so a regression here that flips the sign bit + * surfaces immediately. */ +extern int atoi (char const *s); +extern int printf (char const *fmt, ...); + +int +main (void) +{ + printf ("%d\n", atoi ("123")); + printf ("%d\n", atoi ("-42")); + return 0; +} diff --git a/tests/cc-libc/17-atoi.expected b/tests/cc-libc/17-atoi.expected @@ -0,0 +1,2 @@ +123 +-42