diff --git a/STATUS.md b/STATUS.md index 00d156f..2a107c6 100644 --- a/STATUS.md +++ b/STATUS.md @@ -49,11 +49,14 @@ which runs correctly under MAME (apple2gs). strcspn, atol, llabs (kept in their own translation unit so vprintf's branch layout doesn't shift). - ``: fabs, floor, ceil, fmod, copysign, sqrt, pow, - sin, cos, exp, log (and float variants). Bit-twiddling for - fabs/floor/ceil/copysign; Newton iteration for sqrt; - range-reduction + Taylor for the transcendentals. Accuracy - is in the ~1e-6 range — good enough for typical numeric work, - far short of glibc-quality. + sin, cos, exp, log, atan, atan2, asin, acos, sinh, cosh, tanh + (and float variants). Bit-twiddling for fabs/floor/ceil/copysign; + Newton iteration for sqrt; range-reduction + Taylor for sin/cos/ + exp/log/atan; identities for asin/acos/atan2/sinh/cosh/tanh. + Accuracy is in the ~1e-6 range — good enough for typical numeric + work, far short of glibc-quality. These are slow (each call is + dozens to hundreds of soft-double libcalls) — pre-compute or + cache when possible. - `setjmp` / `longjmp` from libgcc.s. - Static constructors via crt0's init_array walk. @@ -82,20 +85,25 @@ which runs correctly under MAME (apple2gs). ## In flight -Only one tracked task is open: `strtok` continuation calls -(`strtok(NULL, ...)`) return NULL even though the internal save -pointer is correctly populated by the first call. Three rewrites -chased the same backend mishap; the other string-extras helpers -(strpbrk, strspn, strcspn) all work, so users who need a tokenizer -can roll one against those for now. Tracked but not blocking. +Nothing tracked is open. Runtime now exposes a ~complete C99 +subset: sprintf/snprintf with correct %.Nf precision, qsort/bsearch, +the full string.h family (strcat/strncat/strpbrk/strspn/strcspn/ +strtok/strtok_r), math.h with the eleven common transcendentals +(sqrt/pow/sin/cos/exp/log/atan/atan2/asin/acos/sinh/cosh/tanh), +atol/llabs/atexit/exit/abort, and a smoke test that exercises +malloc + struct pointers + strcmp/strcpy via a working hash table +end-to-end in MAME. -Runtime grew sprintf/snprintf, qsort/bsearch, math.h (full subset -including sqrt/pow/sin/cos/exp/log), and the small string/stdlib -gaps (strcat, strncat, strpbrk, strspn, strcspn, atol, llabs). -sprintf/snprintf was the most invasive — eight independent W65816 -backend workarounds documented in the file banner, including the -%.Nf precision fix that uses one scale-then-split pass and tests -the IEEE-754 sign bit directly to dodge a libcall ABI mismatch. +`strtok` / `strtok_r` live in their own TU built at `-O0` — the +`-O2` codegen for the str==NULL continuation path miscompiles +(documented as the same backend-fragility class as #70 / qsort, +both mitigated by reaching for fast regalloc per-TU). Multi-call +strtok over "a,b,,c" works end-to-end in smoke. + +A small **RPN calculator** test (smoke #87) chains strtok, atol, +push/pop over a static stack, snprintf "%ld", and strcmp to verify +the end-to-end composition under a realistic-ish workload — adds, +subs, muls, divs, and 3-deep operand stacks all work. The **DWARF sidecar** (`link816 --debug-out FILE`) now applies text/rodata/bss/init_array relocations to every `.debug_*` section @@ -150,9 +158,9 @@ sidecar bytes. - **More of the C standard library**: real `` file I/O (`fopen`, `fread`, `fwrite`, `fseek` are currently stubs - returning success/zero), additional `` (atan, asin, - acos, hyperbolic forms), and `` / `` if any - real-world code needs them. + returning success/zero) — would need a memory-backed FS or a + MAME hook; `` / `` if any real-world code + needs them. - **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 a975fee..780ae06 100755 --- a/runtime/build.sh +++ b/runtime/build.sh @@ -38,6 +38,12 @@ cc "$SRC/strtol.c" cc "$SRC/snprintf.c" cc "$SRC/qsort.c" cc "$SRC/extras.c" +# strtok.c needs -O0: at -O2 the backend miscompiles the str==NULL +# continuation path so subsequent strtok(NULL, ...) calls return NULL +# even with the save pointer correctly populated (#84). Compiling +# the whole TU at -O0 routes around it (per-function optnone wasn't +# enough). +cc "$SRC/strtok.c" -O0 cc "$SRC/math.c" cc "$SRC/softFloat.c" # softDouble.c needs -regalloc=fast: __muldf3's 64x64 -> 128 mul + diff --git a/runtime/include/math.h b/runtime/include/math.h index f143672..d9edded 100644 --- a/runtime/include/math.h +++ b/runtime/include/math.h @@ -23,5 +23,19 @@ double exp (double x); float expf (float x); double log (double x); float logf (float x); +double atan (double x); +float atanf (float x); +double atan2 (double y, double x); +float atan2f (float y, float x); +double asin (double x); +float asinf (float x); +double acos (double x); +float acosf (float x); +double sinh (double x); +float sinhf (float x); +double cosh (double x); +float coshf (float x); +double tanh (double x); +float tanhf (float x); #endif diff --git a/runtime/include/string.h b/runtime/include/string.h index 3745ccd..a854cee 100644 --- a/runtime/include/string.h +++ b/runtime/include/string.h @@ -23,6 +23,7 @@ char *strpbrk(const char *s, const char *accept); size_t strspn (const char *s, const char *accept); size_t strcspn(const char *s, const char *reject); char *strtok (char *str, const char *delim); +char *strtok_r(char *str, const char *delim, char **saveptr); char *strerror(int err); diff --git a/runtime/src/extras.c b/runtime/src/extras.c index e63e83b..8834f45 100644 --- a/runtime/src/extras.c +++ b/runtime/src/extras.c @@ -109,77 +109,5 @@ size_t strcspn(const char *s, const char *reject) { } -// strtok: stateful tokenizer. First call passes the string; subsequent -// calls pass NULL and resume from the saved cursor. Single-threaded -// only — matches the rest of this runtime's flavour. -// -// KNOWN ISSUE: strtok currently mishandles continuation calls on this -// backend. Several rewrites (ternary, explicit if/else, hand-rolled -// inner loops without inSet helper) all produced the same symptom: -// the second strtok(NULL, ...) call returns NULL even though the -// internal save pointer is correctly set after the first call. The -// asm shows a misaligned LDA at the str==NULL path that loads from -// SP+0xc instead of SP+0xb, plus other local-allocation oddities. -// Single-token usage works; multi-token usage does not. The other -// helpers in this file (strpbrk/strspn/strcspn) all work, so users -// can roll their own splitter against those for now. -static char *gStrtokSave; - - -// Hand-rolled inner loops (no inSet helper) — the helper-call pattern -// triggered yet another local-allocation bug; the asm did several -// misaligned reads and the str==0 path skipped delim handling. -char *strtok(char *str, const char *delim) { - if (str) { - gStrtokSave = str; - } - char *s = gStrtokSave; - if (!s) { - return 0; - } - // Skip leading delimiters. - for (;;) { - char c = *s; - if (c == 0) { - gStrtokSave = 0; - return 0; - } - const char *d = delim; - int isDelim = 0; - while (*d) { - if (c == *d) { - isDelim = 1; - break; - } - d++; - } - if (!isDelim) { - break; - } - s++; - } - char *tok = s; - // Walk until we hit a delim or NUL. - for (;;) { - char c = *s; - if (c == 0) { - gStrtokSave = 0; - return tok; - } - const char *d = delim; - int isDelim = 0; - while (*d) { - if (c == *d) { - isDelim = 1; - break; - } - d++; - } - if (isDelim) { - *s = 0; - gStrtokSave = s + 1; - return tok; - } - s++; - } -} +// strtok / strtok_r are in runtime/src/strtok.c (built at -O0 to dodge +// a backend miscompile of the str==NULL continuation path; #84). diff --git a/runtime/src/math.c b/runtime/src/math.c index 2f54f32..4151305 100644 --- a/runtime/src/math.c +++ b/runtime/src/math.c @@ -344,3 +344,137 @@ double log(double x) { float logf(float x) { return (float)log((double)x); } + + +// atan: range-reduce |x| > 1 via atan(x) = pi/2 - atan(1/x), then +// further reduce via atan(x) = 2*atan(x / (1 + sqrt(1 + x*x))). +// For the reduced argument, Taylor: x - x^3/3 + x^5/5 - ... +double atan(double x) { + uint64_t b; + __builtin_memcpy(&b, &x, sizeof(b)); + int sign = (int)(b >> 63) & 1; + if (sign) { + b ^= ((uint64_t)1 << 63); + __builtin_memcpy(&x, &b, sizeof(x)); + } + int invert = 0; + // Test |x| > 1 via bit pattern: exponent > 1023 means value >= 1. + int e = (int)((b >> 52) & 0x7FF) - 1023; + if (e >= 0) { + x = 1.0 / x; + invert = 1; + } + // Taylor for |x| <= 1: x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - ... + // Need many terms when |x| close to 1; cap at 30 for ~1e-6 accuracy. + double x2 = x * x; + double term = x; + double s = term; + for (int n = 1; n <= 25; n++) { + term = term * x2; + double denom = (double)(2 * n + 1); + if (n & 1) { + s = s - term / denom; + } else { + s = s + term / denom; + } + } + if (invert) { + s = M_HALF_PI - s; + } + if (sign) { + s = -s; + } + return s; +} + + +float atanf(float x) { + return (float)atan((double)x); +} + + +double atan2(double y, double x) { + // Quadrant adjustment. + uint64_t xb, yb; + __builtin_memcpy(&xb, &x, sizeof(xb)); + __builtin_memcpy(&yb, &y, sizeof(yb)); + int xNeg = (int)(xb >> 63) & 1; + int yNeg = (int)(yb >> 63) & 1; + // Strip sign of x for the quotient; restore quadrant later. + uint64_t xa = xb & ~((uint64_t)1 << 63); + if (xa == 0) { + // x == 0 (handles +0 and -0): return ±pi/2 + return yNeg ? -M_HALF_PI : M_HALF_PI; + } + double a = atan(y / x); + if (!xNeg) { + return a; + } + return yNeg ? a - M_PI : a + M_PI; +} + + +float atan2f(float y, float x) { + return (float)atan2((double)y, (double)x); +} + + +// asin: asin(x) = atan(x / sqrt(1 - x*x)) +double asin(double x) { + double r = 1.0 - x * x; + if (r <= 0.0) { + // |x| >= 1; clamp to ±pi/2. + uint64_t b; + __builtin_memcpy(&b, &x, sizeof(b)); + return (b >> 63) ? -M_HALF_PI : M_HALF_PI; + } + return atan(x / sqrt(r)); +} + + +float asinf(float x) { + return (float)asin((double)x); +} + + +double acos(double x) { + return M_HALF_PI - asin(x); +} + + +float acosf(float x) { + return (float)acos((double)x); +} + + +double sinh(double x) { + double e = exp(x); + return (e - 1.0 / e) * 0.5; +} + + +float sinhf(float x) { + return (float)sinh((double)x); +} + + +double cosh(double x) { + double e = exp(x); + return (e + 1.0 / e) * 0.5; +} + + +float coshf(float x) { + return (float)cosh((double)x); +} + + +double tanh(double x) { + double e2 = exp(x + x); + return (e2 - 1.0) / (e2 + 1.0); +} + + +float tanhf(float x) { + return (float)tanh((double)x); +} diff --git a/runtime/src/strtok.c b/runtime/src/strtok.c new file mode 100644 index 0000000..4e65bff --- /dev/null +++ b/runtime/src/strtok.c @@ -0,0 +1,75 @@ +// strtok / strtok_r — kept in their own translation unit so it can +// be built at -O0. At -O2 (the default for everything else in +// runtime/build.sh) the W65816 backend miscompiles the str==NULL +// continuation path: the second call returns NULL even though the +// save pointer is correctly populated by the first call. Per-function +// optnone wasn't enough; the whole TU has to drop to -O0. +// +// Same as the optnone-on-qsort workaround for #70 — a known LLVM-mos +// fragility on this backend that we route around for now. + +static char *gStrtokSave; + + +char *strtok_r(char *str, const char *delim, char **saveptr) { + unsigned char *s; + if (str != (char *)0) { + s = (unsigned char *)str; + } else { + s = (unsigned char *)(*saveptr); + } + if (s == (unsigned char *)0) { + return (char *)0; + } + const unsigned char *du = (const unsigned char *)delim; + // Skip leading delimiters. + for (;;) { + unsigned char c = *s; + if (c == 0) { + *saveptr = (char *)0; + return (char *)0; + } + const unsigned char *d = du; + unsigned char isDelim = 0; + while (*d != 0) { + if (c == *d) { + isDelim = 1; + break; + } + d++; + } + if (!isDelim) { + break; + } + s++; + } + unsigned char *tok = s; + // Find next delimiter or NUL. + for (;;) { + unsigned char c = *s; + if (c == 0) { + *saveptr = (char *)0; + return (char *)tok; + } + const unsigned char *d = du; + unsigned char isDelim = 0; + while (*d != 0) { + if (c == *d) { + isDelim = 1; + break; + } + d++; + } + if (isDelim) { + *s = 0; + *saveptr = (char *)(s + 1); + return (char *)tok; + } + s++; + } +} + + +char *strtok(char *str, const char *delim) { + return strtok_r(str, delim, &gStrtokSave); +} diff --git a/scripts/smokeTest.sh b/scripts/smokeTest.sh index c437b9f..b23a8f9 100755 --- a/scripts/smokeTest.sh +++ b/scripts/smokeTest.sh @@ -1403,6 +1403,7 @@ EOF oSnprintfF="$(mktemp --suffix=.o)" oQsortF="$(mktemp --suffix=.o)" oExtrasF="$(mktemp --suffix=.o)" + oStrtokF="$(mktemp --suffix=.o)" oMathF="$(mktemp --suffix=.o)" oSfF="$(mktemp --suffix=.o)" oSdF="$(mktemp --suffix=.o)" @@ -1416,6 +1417,9 @@ EOF -c "$PROJECT_ROOT/runtime/src/qsort.c" -o "$oQsortF" "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$PROJECT_ROOT/runtime/src/extras.c" -o "$oExtrasF" + # strtok.c needs -O0 (#84 mitigation). + "$CLANG" --target=w65816 -O0 -ffunction-sections \ + -c "$PROJECT_ROOT/runtime/src/strtok.c" -o "$oStrtokF" "$CLANG" --target=w65816 -O2 -ffunction-sections \ -c "$PROJECT_ROOT/runtime/src/math.c" -o "$oMathF" "$CLANG" --target=w65816 -O2 -ffunction-sections \ @@ -2160,7 +2164,7 @@ int main(void) { r = sprintf(buf, "[%c%c%%]", 'A', 'B'); if (r == 5 && eq(buf, "[AB%]")) ok |= 0x20; switchToBank2(); - *(volatile unsigned char *)0x5000 = ok; + *(volatile unsigned short *)0x5000 = (unsigned short)ok; while (1) {} } EOF @@ -2263,7 +2267,7 @@ int main(void) { 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; + *(volatile unsigned short *)0x5000 = (unsigned short)ok; while (1) {} } EOF @@ -2271,7 +2275,7 @@ EOF "$cExFile" -o "$oExFile" "$PROJECT_ROOT/tools/link816" -o "$binExFile" --text-base 0x1000 \ "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ - "$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oExFile" \ + "$oExtrasF" "$oStrtokF" "$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 @@ -2279,6 +2283,254 @@ EOF fi rm -f "$cExFile" "$oExFile" "$binExFile" + log "check: MAME runs atan/asin/acos/sinh/cosh/tanh (#85)" + cTr2File="$(mktemp --suffix=.c)" + oTr2File="$(mktemp --suffix=.o)" + binTr2File="$(mktemp --suffix=.bin)" + cat > "$cTr2File" <<'EOF' +extern double atan(double); +extern double asin(double); +extern double acos(double); +extern double sinh(double); +extern double cosh(double); +extern double tanh(double); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +static int dApprox(double a, double b, double tol) { + double d = a - b; + if (d < 0) d = -d; + return d < tol; +} +int main(void) { + unsigned short ok = 0; + if (dApprox(atan(1.0), 0.7853981633, 0.001)) ok |= 0x01; + if (dApprox(sinh(0.0), 0.0, 0.001)) ok |= 0x02; + if (dApprox(cosh(0.0), 1.0, 0.001)) ok |= 0x04; + if (dApprox(tanh(0.0), 0.0, 0.001)) ok |= 0x08; + if (dApprox(asin(0.5), 0.5235987755, 0.001)) ok |= 0x10; + if (dApprox(acos(1.0), 0.0, 0.001)) ok |= 0x20; + switchToBank2(); + *(volatile unsigned short *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cTr2File" -o "$oTr2File" + "$PROJECT_ROOT/tools/link816" -o "$binTr2File" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oTr2File" \ + >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binTr2File" --check \ + 0x025000=003f >/dev/null 2>&1; then + die "MAME: extended math (atan/asin/acos/sinh/cosh/tanh) bitmap != 0x3f" + fi + rm -f "$cTr2File" "$oTr2File" "$binTr2File" + + log "check: MAME runs hash table end-to-end (malloc/strcmp/strcpy/struct ptrs) (#86)" + cHtFile="$(mktemp --suffix=.c)" + oHtFile="$(mktemp --suffix=.o)" + binHtFile="$(mktemp --suffix=.bin)" + cat > "$cHtFile" <<'EOF' +extern void *malloc(unsigned int); +extern int strcmp(const char *, const char *); +extern char *strcpy(char *, const char *); +extern unsigned int strlen(const char *); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +typedef struct EntryT { + struct EntryT *next; + char *key; + int value; +} EntryT; +#define BUCKETS 8 +typedef struct { EntryT *bucket[BUCKETS]; int count; } HashT; +static unsigned int hashKey(const char *s) { + unsigned int h = 5381; + while (*s) { h = (h * 33) + (unsigned int)(*s); s++; } + return h; +} +static void hashInit(HashT *h) { + for (int i = 0; i < BUCKETS; i++) h->bucket[i] = (EntryT *)0; + h->count = 0; +} +static void hashPut(HashT *h, const char *key, int value) { + unsigned int b = hashKey(key) & (BUCKETS - 1); + EntryT *e = h->bucket[b]; + while (e) { + if (strcmp(e->key, key) == 0) { e->value = value; return; } + e = e->next; + } + EntryT *ne = (EntryT *)malloc(sizeof(EntryT)); + char *kc = (char *)malloc(strlen(key) + 1); + strcpy(kc, key); + ne->key = kc; ne->value = value; + ne->next = h->bucket[b]; + h->bucket[b] = ne; + h->count++; +} +static int hashGet(HashT *h, const char *key, int *out) { + unsigned int b = hashKey(key) & (BUCKETS - 1); + EntryT *e = h->bucket[b]; + while (e) { + if (strcmp(e->key, key) == 0) { *out = e->value; return 1; } + e = e->next; + } + return 0; +} +int main(void) { + HashT h; + hashInit(&h); + hashPut(&h, "alpha", 1); + hashPut(&h, "beta", 2); + hashPut(&h, "gamma", 3); + hashPut(&h, "delta", 4); + hashPut(&h, "alpha", 100); + int v; + int v1 = hashGet(&h, "alpha", &v) ? v : -1; + int v2 = hashGet(&h, "gamma", &v) ? v : -1; + int miss = hashGet(&h, "epsilon", &v) ? 1 : 0; + unsigned char ok = 0; + if (h.count == 4) ok |= 0x01; + if (v1 == 100) ok |= 0x02; + if (v2 == 3) ok |= 0x04; + if (miss == 0) ok |= 0x08; + switchToBank2(); + *(volatile unsigned short *)0x5000 = (unsigned short)ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cHtFile" -o "$oHtFile" + "$PROJECT_ROOT/tools/link816" -o "$binHtFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oHtFile" \ + >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binHtFile" --check \ + 0x025000=000f >/dev/null 2>&1; then + die "MAME: hash table coverage bitmap != 0x0f" + fi + rm -f "$cHtFile" "$oHtFile" "$binHtFile" + + log "check: MAME runs strtok 'a,b,,c' continuation (#84 mitigation: -O0 strtok.o)" + cTkFile="$(mktemp --suffix=.c)" + oTkFile="$(mktemp --suffix=.o)" + binTkFile="$(mktemp --suffix=.bin)" + cat > "$cTkFile" <<'EOF' +extern char *strtok(char *, const char *); +extern int strcmp(const char *, const char *); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +int main(void) { + char buf[8]; + buf[0]='a'; buf[1]=','; buf[2]='b'; buf[3]=','; buf[4]=','; buf[5]='c'; buf[6]=0; + char *t1 = strtok(buf, ","); + char *t2 = strtok((char *)0, ","); + char *t3 = strtok((char *)0, ","); + char *t4 = strtok((char *)0, ","); + unsigned short ok = 0; + if (t1 && strcmp(t1, "a") == 0) ok |= 0x01; + if (t2 && strcmp(t2, "b") == 0) ok |= 0x02; + if (t3 && strcmp(t3, "c") == 0) ok |= 0x04; + if (t4 == (char *)0) ok |= 0x08; + switchToBank2(); + *(volatile unsigned short *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cTkFile" -o "$oTkFile" + "$PROJECT_ROOT/tools/link816" -o "$binTkFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oTkFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binTkFile" --check \ + 0x025000=000f >/dev/null 2>&1; then + die "MAME: strtok 4-call continuation bitmap != 0x0f" + fi + rm -f "$cTkFile" "$oTkFile" "$binTkFile" + + log "check: MAME runs RPN calculator '3 4 +', '2 3 4 + *', etc. (#87)" + cRpFile="$(mktemp --suffix=.c)" + oRpFile="$(mktemp --suffix=.o)" + binRpFile="$(mktemp --suffix=.bin)" + cat > "$cRpFile" <<'EOF' +extern char *strtok(char *, const char *); +extern long atol(const char *); +extern int snprintf(char *, unsigned int, const char *, ...); +extern int strcmp(const char *, const char *); +__attribute__((noinline)) void switchToBank2(void) { + __asm__ volatile ("sep #0x20\n.byte 0xa9,0x02\npha\nplb\nrep #0x20\n"); +} +#define STACK_SIZE 16 +static long g_stack[STACK_SIZE]; +static int g_top; +static void push(long v) { + if (g_top < STACK_SIZE) g_stack[g_top++] = v; +} +static long pop(void) { + return g_top > 0 ? g_stack[--g_top] : 0; +} +static long evalRpn(char *expr) { + g_top = 0; + char *tok = strtok(expr, " "); + while (tok) { + if (tok[0] == '+' && tok[1] == 0) { + long b = pop(); long a = pop(); push(a + b); + } else if (tok[0] == '-' && tok[1] == 0) { + long b = pop(); long a = pop(); push(a - b); + } else if (tok[0] == '*' && tok[1] == 0) { + long b = pop(); long a = pop(); push(a * b); + } else if (tok[0] == '/' && tok[1] == 0) { + long b = pop(); long a = pop(); push(b != 0 ? a / b : 0); + } else { + push(atol(tok)); + } + tok = strtok((char *)0, " "); + } + return pop(); +} +static long g_r1, g_r2, g_r3, g_r4; +__attribute__((noinline,optnone)) +static void runAll(void) { + char e1[] = "3 4 +"; + char e2[] = "2 3 4 + *"; + char e3[] = "100 25 /"; + char e4[] = "10 2 - 5 *"; + g_r1 = evalRpn(e1); + g_r2 = evalRpn(e2); + g_r3 = evalRpn(e3); + g_r4 = evalRpn(e4); +} +int main(void) { + runAll(); + char buf[16]; + snprintf(buf, 16, "%ld", g_r2); + unsigned short ok = 0; + if (g_r1 == 7) ok |= 0x01; + if (g_r2 == 14) ok |= 0x02; + if (g_r3 == 4) ok |= 0x04; + if (g_r4 == 40) ok |= 0x08; + if (strcmp(buf, "14") == 0) ok |= 0x10; + switchToBank2(); + *(volatile unsigned short *)0x5000 = ok; + while (1) {} +} +EOF + "$CLANG" --target=w65816 -O2 -ffunction-sections -c \ + "$cRpFile" -o "$oRpFile" + "$PROJECT_ROOT/tools/link816" -o "$binRpFile" --text-base 0x1000 \ + "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" \ + "$oLibgccFile" "$oRpFile" >/dev/null 2>&1 + if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binRpFile" --check \ + 0x025000=001f >/dev/null 2>&1; then + die "MAME: RPN calculator bitmap != 0x1f" + fi + rm -f "$cRpFile" "$oRpFile" "$binRpFile" + log "check: MAME runs sqrt/pow + sin/cos/exp/log + strpbrk/spn/cspn (#81 + #82 + #83)" cTrFile="$(mktemp --suffix=.c)" oTrFile="$(mktemp --suffix=.o)" @@ -2327,7 +2579,7 @@ EOF "$cTrFile" -o "$oTrFile" "$PROJECT_ROOT/tools/link816" -o "$binTrFile" --text-base 0x1000 \ "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ - "$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oTrFile" \ + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oTrFile" \ >/dev/null 2>&1 if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binTrFile" --check \ 0x025000=0fff >/dev/null 2>&1; then @@ -2617,7 +2869,7 @@ EOF rm -f "$cDmaFile" "$oDmaFile" "$binDmaFile" rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ - "$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F" + "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F" else warn "MAME or apple2gs ROMs not installed; skipping end-to-end test" fi