From d6c9fc8252df6ae08f1fb58999e143275bd0c37b Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 30 Apr 2026 20:48:41 -0500 Subject: [PATCH] Checkpoint. --- STATUS.md | 31 +++- runtime/build.sh | 4 + runtime/include/math.h | 15 ++ runtime/include/stdio.h | 4 + runtime/include/stdlib.h | 10 ++ runtime/include/string.h | 2 + runtime/src/extras.c | 67 ++++++++ runtime/src/math.c | 132 ++++++++++++++++ runtime/src/qsort.c | 67 ++++++++ runtime/src/snprintf.c | 331 +++++++++++++++++++++++++++++++++++++++ scripts/smokeTest.sh | 162 ++++++++++++++++++- 11 files changed, 820 insertions(+), 5 deletions(-) create mode 100644 runtime/include/math.h create mode 100644 runtime/src/extras.c create mode 100644 runtime/src/math.c create mode 100644 runtime/src/qsort.c create mode 100644 runtime/src/snprintf.c diff --git a/STATUS.md b/STATUS.md index 1f10fbb..91b864e 100644 --- a/STATUS.md +++ b/STATUS.md @@ -38,6 +38,18 @@ which runs correctly under MAME (apple2gs). - C++ minimal: clang++ compiles a class with virtual + non-trivial ctor (vtable + RTTI omitted; no exceptions). - printf with `%d %x %s %c %p` and width/precision specifiers. +- sprintf / snprintf / vsprintf / vsnprintf with the same format + coverage as printf (`%d %u %x %ld %lu %s %c %f %p %%` + width). + C99 truncation semantics for snprintf. +- qsort + bsearch over arbitrary element size with a user `cmp` + callback (insertion-sort variant — sidesteps the greedy regalloc + bug in the recursive iterative-qsort form). +- Standard string/stdlib glue: strcat, strncat, atol, llabs (added + in their own translation unit so vprintf's branch layout doesn't + shift). +- `` basics: fabs, floor, ceil, fmod, copysign (and the + float variants). All implemented via direct IEEE-754 bit + manipulation, no transcendentals. - `setjmp` / `longjmp` from libgcc.s. - Static constructors via crt0's init_array walk. @@ -69,6 +81,14 @@ which runs correctly under MAME (apple2gs). Nothing currently in flight. All tracked tasks are closed; remaining items are listed under "What's still needed" below. +Runtime grew sprintf/snprintf, qsort/bsearch, math.h basics, and +the small string/stdlib gaps (strcat, strncat, atol, llabs). +sprintf/snprintf was the most invasive — it tripped three independent +W65816 backend miscompiles (struct-pointer mis-addressing, fmt-as-arg1 +loop-local uninit, `buf+0xFFFE` lowered to `dec a`) plus a fourth +codegen bug in countdown loops; each workaround is documented in the +file banner so a future cleanup pass doesn't undo them. + The **DWARF sidecar** (`link816 --debug-out FILE`) now applies text/rodata/bss/init_array relocations to every `.debug_*` section before writing it. PC values in `.debug_addr` and `.debug_line` end @@ -120,10 +140,13 @@ sidecar bytes. `softDouble.c` and unblocks pattern-rich code that currently must be compiled at `-O0` for correctness. -- **More of the C standard library**: `` transcendental - functions (sin, cos, exp, log, pow), `` beyond what's - hand-coded, `` file I/O (`fopen`, `fread`, `fwrite`, - `fseek`). +- **More of the C standard library**: `` transcendentals + (sin, cos, exp, log, pow), real `` file I/O (`fopen`, + `fread`, `fwrite`, `fseek` are currently stubs returning + success/zero), and full snprintf %f fractional precision (today + only the integer part of `%f` is reliable — same caveat as + libc's writeDouble, the soft-double `(long)(frac * mul)` step + loses precision). - **C++ runtime support**: vtable layout for multiple inheritance, RTTI, exceptions (or a documented `-fno-exceptions` requirement). diff --git a/runtime/build.sh b/runtime/build.sh index 4548296..a975fee 100755 --- a/runtime/build.sh +++ b/runtime/build.sh @@ -35,6 +35,10 @@ asm "$SRC/crt0.s" asm "$SRC/libgcc.s" cc "$SRC/libc.c" cc "$SRC/strtol.c" +cc "$SRC/snprintf.c" +cc "$SRC/qsort.c" +cc "$SRC/extras.c" +cc "$SRC/math.c" cc "$SRC/softFloat.c" # softDouble.c needs -regalloc=fast: __muldf3's 64x64 -> 128 mul + # inlined alignment shifts overflows the greedy allocator on the diff --git a/runtime/include/math.h b/runtime/include/math.h new file mode 100644 index 0000000..088572c --- /dev/null +++ b/runtime/include/math.h @@ -0,0 +1,15 @@ +#ifndef _MATH_H +#define _MATH_H + +double fabs (double x); +float fabsf (float x); +double floor (double x); +float floorf (float x); +double ceil (double x); +float ceilf (float x); +double fmod (double x, double y); +float fmodf (float x, float y); +double copysign (double x, double y); +float copysignf(float x, float y); + +#endif diff --git a/runtime/include/stdio.h b/runtime/include/stdio.h index d39fcce..f8966e6 100644 --- a/runtime/include/stdio.h +++ b/runtime/include/stdio.h @@ -14,6 +14,10 @@ int putchar(int c); int puts(const char *s); int printf(const char *fmt, ...); int vprintf(const char *fmt, va_list ap); +int sprintf(char *buf, const char *fmt, ...); +int snprintf(char *buf, size_t n, const char *fmt, ...); +int vsprintf(char *buf, const char *fmt, va_list ap); +int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap); int fprintf(FILE *stream, const char *fmt, ...); int fputc(int c, FILE *stream); int fputs(const char *s, FILE *stream); diff --git a/runtime/include/stdlib.h b/runtime/include/stdlib.h index 34533ad..53c9dd8 100644 --- a/runtime/include/stdlib.h +++ b/runtime/include/stdlib.h @@ -11,6 +11,16 @@ void free(void *p); int abs(int n); long labs(long n); int atoi(const char *s); +long atol(const char *s); +long long llabs(long long n); + +long strtol (const char *nptr, char **endptr, int base); +unsigned long strtoul(const char *nptr, char **endptr, int base); + +typedef int (*__cmp_fn)(const void *, const void *); +void qsort (void *base, size_t nmemb, size_t size, __cmp_fn cmp); +void *bsearch(const void *key, const void *base, size_t nmemb, + size_t size, __cmp_fn cmp); void exit(int code) __attribute__((noreturn)); void abort(void) __attribute__((noreturn)); diff --git a/runtime/include/string.h b/runtime/include/string.h index 12002ca..7f0d11e 100644 --- a/runtime/include/string.h +++ b/runtime/include/string.h @@ -17,6 +17,8 @@ int strncmp(const char *a, const char *b, size_t n); char *strchr(const char *s, int c); char *strrchr(const char *s, int c); char *strstr(const char *haystack, const char *needle); +char *strcat(char *dst, const char *src); +char *strncat(char *dst, const char *src, size_t n); char *strerror(int err); diff --git a/runtime/src/extras.c b/runtime/src/extras.c new file mode 100644 index 0000000..16daf3a --- /dev/null +++ b/runtime/src/extras.c @@ -0,0 +1,67 @@ +// Tiny string.h / stdlib.h helpers — kept out of libc.c because +// adding to that translation unit shifts vprintf's internal branch +// distances and randomly breaks BranchExpand (same precedent as +// strtol.c and snprintf.c). +// +// Functions: +// string.h: strcat, strncat +// stdlib.h: atol, llabs + +typedef unsigned int size_t; + + +char *strcat(char *dst, const char *src) { + char *d = dst; + while (*d) { + d++; + } + while ((*d = *src)) { + d++; + src++; + } + return dst; +} + + +char *strncat(char *dst, const char *src, size_t n) { + char *d = dst; + while (*d) { + d++; + } + while (n && *src) { + *d = *src; + d++; + src++; + n--; + } + *d = 0; + return dst; +} + + +extern int isspace(int); + + +long atol(const char *s) { + while (isspace(*s)) { + s++; + } + int sign = 1; + if (*s == '-') { + sign = -1; + s++; + } else if (*s == '+') { + s++; + } + long n = 0; + while (*s >= '0' && *s <= '9') { + n = n * 10 + (*s - '0'); + s++; + } + return sign < 0 ? -n : n; +} + + +long long llabs(long long n) { + return n < 0 ? -n : n; +} diff --git a/runtime/src/math.c b/runtime/src/math.c new file mode 100644 index 0000000..ac6d3e8 --- /dev/null +++ b/runtime/src/math.c @@ -0,0 +1,132 @@ +// Minimal math.h subset for the W65816 runtime. +// +// All operations work directly on the IEEE-754 bit representation +// rather than going through long sequences of soft-float libcalls. +// fabs/copysign/floor/ceil/fmod cover the common needs of routine +// numeric code; transcendentals are intentionally out of scope. +// +// Layout assumed: +// double = sign(1) | exp(11) | frac(52) stored little-endian +// (high word at +6, low word at +0) +// float = sign(1) | exp(8) | frac(23) +// +// We type-pun via __builtin_memcpy + uint64_t / uint32_t to stay +// strict-aliasing-clean. + +typedef unsigned long uint32_t; +typedef unsigned long long uint64_t; +typedef long long int64_t; + + +static uint64_t dToBits(double v) { + uint64_t b; + __builtin_memcpy(&b, &v, sizeof(b)); + return b; +} + + +static double dFromBits(uint64_t b) { + double v; + __builtin_memcpy(&v, &b, sizeof(v)); + return v; +} + + +static uint32_t fToBits(float v) { + uint32_t b; + __builtin_memcpy(&b, &v, sizeof(b)); + return b; +} + + +static float fFromBits(uint32_t b) { + float v; + __builtin_memcpy(&v, &b, sizeof(v)); + return v; +} + + +double fabs(double x) { + return dFromBits(dToBits(x) & ~((uint64_t)1 << 63)); +} + + +float fabsf(float x) { + return fFromBits(fToBits(x) & ~((uint32_t)1 << 31)); +} + + +double copysign(double x, double y) { + uint64_t mask = (uint64_t)1 << 63; + return dFromBits((dToBits(x) & ~mask) | (dToBits(y) & mask)); +} + + +float copysignf(float x, float y) { + uint32_t mask = (uint32_t)1 << 31; + return fFromBits((fToBits(x) & ~mask) | (fToBits(y) & mask)); +} + + +// floor(x) = round toward -inf. Truncate the fraction bits below +// the integer position; if any non-zero bit was discarded for a +// negative value, subtract one (round more negative). +double floor(double x) { + uint64_t b = dToBits(x); + int e = (int)((b >> 52) & 0x7FF) - 1023; + if (e < 0) { + // |x| < 1: floor(positive) = 0, floor(negative) = -1. + return (b >> 63) ? -1.0 : 0.0; + } + if (e >= 52) { + return x; // already integer (or NaN/Inf) + } + uint64_t fracMask = ((uint64_t)1 << (52 - e)) - 1; + if ((b & fracMask) == 0) { + return x; // already integer + } + uint64_t truncBits = b & ~fracMask; + double truncVal = dFromBits(truncBits); + // For negatives with a non-zero discarded fraction, subtract 1. + if (b >> 63) { + truncVal = truncVal - 1.0; + } + return truncVal; +} + + +double ceil(double x) { + return -floor(-x); +} + + +float floorf(float x) { + return (float)floor((double)x); +} + + +float ceilf(float x) { + return (float)ceil((double)x); +} + + +// fmod(x, y) = x - trunc(x / y) * y. trunc(q) = round-toward-zero. +double fmod(double x, double y) { + if (y == 0.0) { + return 0.0; // implementation-defined; we return 0 + } + double q = x / y; + // trunc: floor for positives, ceil for negatives. + double t; + if (q < 0.0) { + t = -floor(-q); + } else { + t = floor(q); + } + return x - t * y; +} + + +float fmodf(float x, float y) { + return (float)fmod((double)x, (double)y); +} diff --git a/runtime/src/qsort.c b/runtime/src/qsort.c new file mode 100644 index 0000000..22f4a7d --- /dev/null +++ b/runtime/src/qsort.c @@ -0,0 +1,67 @@ +// qsort + bsearch — own translation unit so the indirect cmp call +// and the byte-swap inner loop don't perturb other libc code. +// +// qsort uses insertion sort (O(n^2)) rather than recursion-driven +// quicksort; the W65816 backend's greedy regalloc still mis-orders +// spills in iterative quicksort with if/else recursion (#70), and +// for the small arrays this runtime targets (typical IIgs C +// program: dozens of items, not thousands) the constant-factor win +// of insertion sort over recursive quicksort is meaningful. + +typedef unsigned int size_t; +typedef int (*CmpFnT)(const void *, const void *); + + +// Swap `size` bytes between a and b using a 1-byte scratch. Tight +// forward loop is what the backend handles best on this target. +static void byteSwap(unsigned char *a, unsigned char *b, size_t size) { + for (size_t i = 0; i < size; i++) { + unsigned char t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + + +void *bsearch(const void *key, const void *base, size_t nmemb, + size_t size, CmpFnT cmp) { + const unsigned char *baseP = (const unsigned char *)base; + size_t lo = 0; + size_t hi = nmemb; + while (lo < hi) { + size_t mid = lo + (hi - lo) / 2; + const unsigned char *p = baseP + mid * size; + int r = cmp(key, p); + if (r == 0) { + return (void *)p; + } + if (r < 0) { + hi = mid; + } else { + lo = mid + 1; + } + } + return (void *)0; +} + + +void qsort(void *base, size_t nmemb, size_t size, CmpFnT cmp) { + if (nmemb < 2 || size == 0) { + return; + } + unsigned char *baseP = (unsigned char *)base; + // Insertion sort: for each i in 1..n-1, walk left swapping until + // the slot at j-1 is <= the slot at j. Stable, in-place. + for (size_t i = 1; i < nmemb; i++) { + size_t j = i; + while (j > 0) { + unsigned char *cur = baseP + j * size; + unsigned char *prev = cur - size; + if (cmp(prev, cur) <= 0) { + break; + } + byteSwap(prev, cur, size); + j--; + } + } +} diff --git a/runtime/src/snprintf.c b/runtime/src/snprintf.c new file mode 100644 index 0000000..89c6d06 --- /dev/null +++ b/runtime/src/snprintf.c @@ -0,0 +1,331 @@ +// Buffer-formatting siblings of printf — kept in their own translation +// unit so the shared writeXxx helpers don't have to take a function- +// pointer sink (indirect call cost on this target) and so adding the +// formatter to libc.c can't shift vprintf's branch distances out of +// range (per the strtol.c precedent). +// +// Functions: +// int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap); +// int snprintf (char *buf, size_t n, const char *fmt, ...); +// int vsprintf (char *buf, const char *fmt, va_list ap); +// int sprintf (char *buf, const char *fmt, ...); +// +// Format support matches vprintf: %d %i %u %x %X %c %s %p %f and the +// `l` length modifier (%ld %lu). Width is honoured for %x. %f +// precision is capped at 9 fractional digits. +// +// Return value: number of characters that would have been written had +// the buffer been unbounded (C99 vsnprintf semantics), not just the +// number actually written. This lets callers detect truncation. +// +// **Sink state lives in file-static globals** instead of an explicit +// struct passed by pointer. Two W65816 backend bugs forced this: +// (1) The address of a stack-resident struct is computed wrong +// (&s came out as SP+5 = address of s.end instead of SP+3). +// emit() then read garbage cur/end values, the cur >= end branch +// skipped every write, and snprintf returned the right length +// with an empty buffer. +// (2) Functions taking fmt as arg1 (stack) didn't initialize the +// fmt local before the loop body — first char came from the +// arg slot but the loop's fmt++ ran on uninitialized memory. +// Fixed by making fmt always arg0 (A reg). +// Single-threaded use only, but that matches the rest of this runtime. + +typedef unsigned int size_t; +typedef __builtin_va_list va_list; +#define va_start(ap, last) __builtin_va_start(ap, last) +#define va_arg(ap, ty) __builtin_va_arg(ap, ty) +#define va_end(ap) __builtin_va_end(ap) + + +static char *gCur; +static char *gEnd; +static size_t gTotal; + + +__attribute__((noinline)) +static void emit(char c) { + if (gCur < gEnd) { + *gCur++ = c; + } + gTotal++; +} + + +__attribute__((noinline)) +static void emitStr(const char *p) { + if (!p) { + p = "(null)"; + } + while (*p) { + emit(*p++); + } +} + + +__attribute__((noinline)) +static void emitUDec(unsigned int n) { + char buf[6]; + int i = 0; + if (n == 0) { + emit('0'); + return; + } + while (n > 0) { + buf[i++] = '0' + (n % 10); + n /= 10; + } + // Reverse-emit using forward index arithmetic. The natural + // countdown forms (`while (i > 0) emit(buf[--i])`, + // `while (i > 0) { i--; emit(buf[i]); }`, + // `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a + // do-while whose `dec a; bpl` exit condition runs the loop one + // extra time on this backend, leaking a buf[-1] read. The forward + // count + index-arithmetic form below avoids the bad lowering. + int top = i; + for (int j = 0; j < top; j++) { + emit(buf[top - 1 - j]); + } +} + + +__attribute__((noinline)) +static void emitDec(int n) { + if (n < 0) { + emit('-'); + emitUDec((unsigned int)(-n)); + } else { + emitUDec((unsigned int)n); + } +} + + +__attribute__((noinline)) +static void emitULong(unsigned long n) { + char buf[11]; + int i = 0; + if (n == 0) { + emit('0'); + return; + } + while (n > 0) { + buf[i++] = '0' + (n % 10); + n /= 10; + } + // Reverse-emit using forward index arithmetic. The natural + // countdown forms (`while (i > 0) emit(buf[--i])`, + // `while (i > 0) { i--; emit(buf[i]); }`, + // `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a + // do-while whose `dec a; bpl` exit condition runs the loop one + // extra time on this backend, leaking a buf[-1] read. The forward + // count + index-arithmetic form below avoids the bad lowering. + int top = i; + for (int j = 0; j < top; j++) { + emit(buf[top - 1 - j]); + } +} + + +__attribute__((noinline)) +static void emitSignedLong(long n) { + if (n < 0) { + emit('-'); + emitULong((unsigned long)(-n)); + } else { + emitULong((unsigned long)n); + } +} + + +__attribute__((noinline)) +static void emitHex(unsigned int n, int width) { + static const char digits[] = "0123456789abcdef"; + char buf[5]; + int i = 0; + if (n == 0) { + buf[i++] = '0'; + } + while (n > 0) { + buf[i++] = digits[n & 0xF]; + n >>= 4; + } + while (i < width) { + buf[i++] = '0'; + } + // Reverse-emit using forward index arithmetic. The natural + // countdown forms (`while (i > 0) emit(buf[--i])`, + // `while (i > 0) { i--; emit(buf[i]); }`, + // `for (j = i - 1; j >= 0; j--) emit(buf[j])`) all lower to a + // do-while whose `dec a; bpl` exit condition runs the loop one + // extra time on this backend, leaking a buf[-1] read. The forward + // count + index-arithmetic form below avoids the bad lowering. + int top = i; + for (int j = 0; j < top; j++) { + emit(buf[top - 1 - j]); + } +} + + +__attribute__((noinline)) +static void emitDouble(double v, int prec) { + if (prec < 0) { + prec = 6; + } + if (prec > 9) { + prec = 9; + } + if (v < 0) { + emit('-'); + v = -v; + } + long ipart = (long)v; + emitULong((unsigned long)ipart); + if (prec == 0) { + return; + } + emit('.'); + double frac = v - (double)ipart; + long mul = 1; + for (int i = 0; i < prec; i++) { + mul *= 10; + } + long fdigits = (long)(frac * (double)mul); + if (fdigits < 0) { + fdigits = -fdigits; + } + char buf[10]; + int n = 0; + long scale = mul / 10; + while (n < prec) { + if (scale == 0) { + scale = 1; + } + long d = fdigits / scale; + buf[n++] = '0' + (char)(d % 10); + scale /= 10; + if (scale == 0) { + break; + } + } + while (n < prec) { + buf[n++] = '0'; + } + for (int i = 0; i < n; i++) { + emit(buf[i]); + } +} + + +// fmt is arg0 (A register); see banner comment for why the order matters. +static int format(const char *fmt, va_list ap) { + while (*fmt) { + char c = *fmt++; + if (c != '%') { + emit(c); + continue; + } + int width = 0; + while (*fmt >= '0' && *fmt <= '9') { + width = width * 10 + (*fmt - '0'); + fmt++; + } + int prec = -1; + if (*fmt == '.') { + fmt++; + prec = 0; + while (*fmt >= '0' && *fmt <= '9') { + prec = prec * 10 + (*fmt - '0'); + fmt++; + } + } + int isLong = 0; + if (*fmt == 'l') { + isLong = 1; + fmt++; + } + char spec = *fmt++; + if (spec == 'd' || spec == 'i') { + if (isLong) { + emitSignedLong(va_arg(ap, long)); + } else { + emitDec(va_arg(ap, int)); + } + } else if (spec == 'u') { + if (isLong) { + emitULong(va_arg(ap, unsigned long)); + } else { + emitUDec(va_arg(ap, unsigned int)); + } + } else if (spec == 'x' || spec == 'X') { + emitHex(va_arg(ap, unsigned int), width); + } else if (spec == 'c') { + emit((char)va_arg(ap, int)); + } else if (spec == 's') { + emitStr(va_arg(ap, const char *)); + } else if (spec == 'f' || spec == 'F' || + spec == 'g' || spec == 'G' || + spec == 'e' || spec == 'E') { + emitDouble(va_arg(ap, double), prec); + } else if (spec == 'p') { + emit('0'); + emit('x'); + emitHex(va_arg(ap, unsigned int), 4); + } else if (spec == '%') { + emit('%'); + } else { + emit('%'); + emit(spec); + } + } + if (gCur < gEnd) { + *gCur = '\0'; + } else if (gEnd > (char *)0) { + gEnd[-1] = '\0'; + } + return (int)gTotal; +} + + +int snprintf(char *buf, size_t n, const char *fmt, ...) { + gCur = buf; + gEnd = buf + (n ? n : 0); + gTotal = 0; + va_list ap; + va_start(ap, fmt); + int r = format(fmt, ap); + va_end(ap); + return r; +} + + +int sprintf(char *buf, const char *fmt, ...) { + gCur = buf; + // sprintf is unbounded. Setting gEnd = buf + 0xFFFE looks innocuous + // but clang lowers the +0xFFFE to a `dec a; dec a` peephole (since + // 0xFFFE is -2 in 16-bit), giving gEnd = buf - 2 — and then the + // emit() bounds test `cur < end` is always false, so nothing gets + // written. Use the absolute top-of-bank sentinel instead. + gEnd = (char *)0xFFFF; + gTotal = 0; + va_list ap; + va_start(ap, fmt); + int r = format(fmt, ap); + va_end(ap); + return r; +} + + +int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap) { + gCur = buf; + gEnd = buf + (n ? n : 0); + gTotal = 0; + return format(fmt, ap); +} + + +int vsprintf(char *buf, const char *fmt, va_list ap) { + gCur = buf; + gEnd = (char *)0xFFFF; + gTotal = 0; + return format(fmt, ap); +} diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index 07122ec..a1b5e21 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -1400,12 +1400,24 @@ EOF "$cFactFile" -o "$oFactFile" oLibcF="$(mktemp --suffix=.o)" oStrtolF="$(mktemp --suffix=.o)" + oSnprintfF="$(mktemp --suffix=.o)" + oQsortF="$(mktemp --suffix=.o)" + oExtrasF="$(mktemp --suffix=.o)" + oMathF="$(mktemp --suffix=.o)" oSfF="$(mktemp --suffix=.o)" oSdF="$(mktemp --suffix=.o)" "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$PROJECT_ROOT/runtime/src/libc.c" -o "$oLibcF" "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$PROJECT_ROOT/runtime/src/strtol.c" -o "$oStrtolF" + "$CLANG" --target=w65816 -O2 -ffunction-sections \ + -c "$PROJECT_ROOT/runtime/src/snprintf.c" -o "$oSnprintfF" + "$CLANG" --target=w65816 -O2 -ffunction-sections \ + -c "$PROJECT_ROOT/runtime/src/qsort.c" -o "$oQsortF" + "$CLANG" --target=w65816 -O2 -ffunction-sections \ + -c "$PROJECT_ROOT/runtime/src/extras.c" -o "$oExtrasF" + "$CLANG" --target=w65816 -O2 -ffunction-sections \ + -c "$PROJECT_ROOT/runtime/src/math.c" -o "$oMathF" "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$PROJECT_ROOT/runtime/src/softFloat.c" -o "$oSfF" "$CLANG" --target=w65816 -O2 -ffunction-sections -mllvm -regalloc=fast \ @@ -2120,6 +2132,153 @@ EOF fi rm -f "$cStFile" "$oStFile" "$binStFile" + log "check: MAME runs sprintf/snprintf format coverage (#76)" + cSpFile="$(mktemp --suffix=.c)" + oSpFile="$(mktemp --suffix=.o)" + binSpFile="$(mktemp --suffix=.bin)" + cat > "$cSpFile" <<'EOF' +extern int sprintf(char *buf, const char *fmt, ...); +extern int snprintf(char *buf, unsigned int n, const char *fmt, ...); +extern int strcmp(const char *a, const char *b); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +static int eq(const char *a, const char *b) { return strcmp(a, b) == 0; } +int main(void) { + char buf[32]; + unsigned char ok = 0; + int r = sprintf(buf, "ok-%d", 7); + if (r == 4 && eq(buf, "ok-7")) ok |= 0x01; + r = sprintf(buf, "n=%d s=%s", -42, "hi"); + if (r == 10 && eq(buf, "n=-42 s=hi")) ok |= 0x02; + r = sprintf(buf, "%04x %lu", 0xC, (unsigned long)123456); + if (r == 11 && eq(buf, "000c 123456")) ok |= 0x04; + r = snprintf(buf, 6, "abcdefghij"); + if (r == 10 && eq(buf, "abcde")) ok |= 0x08; + r = sprintf(buf, "%.0f", 42.0); + if (r == 2 && eq(buf, "42")) ok |= 0x10; + r = sprintf(buf, "[%c%c%%]", 'A', 'B'); + if (r == 5 && eq(buf, "[AB%]")) ok |= 0x20; + switchToBank2(); + *(volatile unsigned char *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cSpFile" -o "$oSpFile" + "$PROJECT_ROOT/tools/link816" -o "$binSpFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oSpFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binSpFile" --check \ + 0x025000=003f >/dev/null 2>&1; then + die "MAME: sprintf/snprintf format-coverage bitmap != 0x3f" + fi + rm -f "$cSpFile" "$oSpFile" "$binSpFile" + + log "check: MAME runs qsort([3,1,4,1,5]) + bsearch (#77)" + cQbFile="$(mktemp --suffix=.c)" + oQbFile="$(mktemp --suffix=.o)" + binQbFile="$(mktemp --suffix=.bin)" + cat > "$cQbFile" <<'EOF' +extern void qsort(void *, unsigned int, unsigned int, + int (*)(const void *, const void *)); +extern void *bsearch(const void *, const void *, unsigned int, unsigned int, + int (*)(const void *, const void *)); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +static int cmpInt(const void *a, const void *b) { + int x = *(const int *)a, y = *(const int *)b; + return x < y ? -1 : (x > y ? 1 : 0); +} +int main(void) { + int arr[5] = {3, 1, 4, 1, 5}; + qsort(arr, 5, sizeof(int), cmpInt); + int key = 4; + int *hit = (int *)bsearch(&key, arr, 5, sizeof(int), cmpInt); + int idx = hit ? (int)(hit - arr) : -1; + int miss = 99; + int *m = (int *)bsearch(&miss, arr, 5, sizeof(int), cmpInt); + short missIdx = m ? 0 : -1; + switchToBank2(); + *(volatile unsigned short *)0x5000 = (unsigned short)arr[0]; + *(volatile unsigned short *)0x5002 = (unsigned short)arr[1]; + *(volatile unsigned short *)0x5004 = (unsigned short)arr[2]; + *(volatile unsigned short *)0x5006 = (unsigned short)arr[3]; + *(volatile unsigned short *)0x5008 = (unsigned short)arr[4]; + *(volatile unsigned short *)0x500a = (unsigned short)idx; + *(volatile unsigned short *)0x500c = (unsigned short)missIdx; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cQbFile" -o "$oQbFile" + "$PROJECT_ROOT/tools/link816" -o "$binQbFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oSfF" "$oSdF" "$oLibgccFile" "$oQbFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binQbFile" --check \ + 0x025000=0001 0x025002=0001 0x025004=0003 \ + 0x025006=0004 0x025008=0005 \ + 0x02500a=0003 0x02500c=ffff >/dev/null 2>&1; then + die "MAME: qsort/bsearch wrong" + fi + rm -f "$cQbFile" "$oQbFile" "$binQbFile" + + log "check: MAME runs strcat/atol/llabs + math fabs/floor/ceil/copysign (#78 + #79)" + cExFile="$(mktemp --suffix=.c)" + oExFile="$(mktemp --suffix=.o)" + binExFile="$(mktemp --suffix=.bin)" + cat > "$cExFile" <<'EOF' +extern char *strcat(char *, const char *); +extern char *strncat(char *, const char *, unsigned int); +extern int strcmp(const char *, const char *); +extern long atol(const char *); +extern long long llabs(long long); +extern double fabs(double); +extern double floor(double); +extern double ceil(double); +extern double copysign(double, double); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +static int eq(const char *a, const char *b) { return strcmp(a, b) == 0; } +static int dEq(double a, double b) { + unsigned long long x, y; + __builtin_memcpy(&x, &a, sizeof(x)); + __builtin_memcpy(&y, &b, sizeof(y)); + return x == y; +} +int main(void) { + char buf[16]; + unsigned char ok = 0; + buf[0] = 0; strcat(buf, "ab"); strcat(buf, "cd"); + if (eq(buf, "abcd")) ok |= 0x01; + buf[0]='h'; buf[1]='i'; buf[2]=0; + strncat(buf, "world", 3); + if (eq(buf, "hiwor")) ok |= 0x02; + if (atol(" -12345 garbage") == -12345) ok |= 0x04; + if (llabs(-(long long)1000) == 1000) ok |= 0x08; + if (dEq(fabs(-1.5), 1.5)) ok |= 0x10; + if (dEq(floor(2.7), 2.0)) ok |= 0x20; + if (dEq(ceil(2.3), 3.0)) ok |= 0x40; + if (dEq(copysign(1.0, -2.0), -1.0)) ok |= 0x80; + switchToBank2(); + *(volatile unsigned char *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cExFile" -o "$oExFile" + "$PROJECT_ROOT/tools/link816" -o "$binExFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oExFile" \ + >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binExFile" --check \ + 0x025000=00ff >/dev/null 2>&1; then + die "MAME: extras + math basics bitmap != 0xff" + fi + rm -f "$cExFile" "$oExFile" "$binExFile" + log "check: MAME runs udivmod(0x123...DEF, 0x10000, &m) → q=0x12345_6789AB m=0xCDEF (#69)" cUdmFile="$(mktemp --suffix=.c)" oUdmFile="$(mktemp --suffix=.o)" @@ -2401,7 +2560,8 @@ EOF fi rm -f "$cDmaFile" "$oDmaFile" "$binDmaFile" - rm -f "$oLibcF" "$oStrtolF" "$oSfF" "$oSdF" "$oCrt0F" + rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F" else warn "MAME or apple2gs ROMs not installed; skipping end-to-end test" fi