Checkpoint.

This commit is contained in:
Scott Duensing 2026-04-30 20:48:41 -05:00
parent a702f4a970
commit d6c9fc8252
11 changed files with 820 additions and 5 deletions

View file

@ -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).
- `<math.h>` 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**: `<math.h>` transcendental
functions (sin, cos, exp, log, pow), `<string.h>` beyond what's
hand-coded, `<stdio.h>` file I/O (`fopen`, `fread`, `fwrite`,
`fseek`).
- **More of the C standard library**: `<math.h>` transcendentals
(sin, cos, exp, log, pow), real `<stdio.h>` 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).

View file

@ -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

15
runtime/include/math.h Normal file
View file

@ -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

View file

@ -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);

View file

@ -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));

View file

@ -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);

67
runtime/src/extras.c Normal file
View file

@ -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;
}

132
runtime/src/math.c Normal file
View file

@ -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);
}

67
runtime/src/qsort.c Normal file
View file

@ -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--;
}
}
}

331
runtime/src/snprintf.c Normal file
View file

@ -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);
}

View file

@ -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