Checkpoint.

This commit is contained in:
Scott Duensing 2026-04-30 21:40:13 -05:00
parent 1622053eb2
commit e805911e18
8 changed files with 518 additions and 100 deletions

View file

@ -49,11 +49,14 @@ which runs correctly under MAME (apple2gs).
strcspn, atol, llabs (kept in their own translation unit so strcspn, atol, llabs (kept in their own translation unit so
vprintf's branch layout doesn't shift). vprintf's branch layout doesn't shift).
- `<math.h>`: fabs, floor, ceil, fmod, copysign, sqrt, pow, - `<math.h>`: fabs, floor, ceil, fmod, copysign, sqrt, pow,
sin, cos, exp, log (and float variants). Bit-twiddling for sin, cos, exp, log, atan, atan2, asin, acos, sinh, cosh, tanh
fabs/floor/ceil/copysign; Newton iteration for sqrt; (and float variants). Bit-twiddling for fabs/floor/ceil/copysign;
range-reduction + Taylor for the transcendentals. Accuracy Newton iteration for sqrt; range-reduction + Taylor for sin/cos/
is in the ~1e-6 range — good enough for typical numeric work, exp/log/atan; identities for asin/acos/atan2/sinh/cosh/tanh.
far short of glibc-quality. 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. - `setjmp` / `longjmp` from libgcc.s.
- Static constructors via crt0's init_array walk. - Static constructors via crt0's init_array walk.
@ -82,20 +85,25 @@ which runs correctly under MAME (apple2gs).
## In flight ## In flight
Only one tracked task is open: `strtok` continuation calls Nothing tracked is open. Runtime now exposes a ~complete C99
(`strtok(NULL, ...)`) return NULL even though the internal save subset: sprintf/snprintf with correct %.Nf precision, qsort/bsearch,
pointer is correctly populated by the first call. Three rewrites the full string.h family (strcat/strncat/strpbrk/strspn/strcspn/
chased the same backend mishap; the other string-extras helpers strtok/strtok_r), math.h with the eleven common transcendentals
(strpbrk, strspn, strcspn) all work, so users who need a tokenizer (sqrt/pow/sin/cos/exp/log/atan/atan2/asin/acos/sinh/cosh/tanh),
can roll one against those for now. Tracked but not blocking. 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 `strtok` / `strtok_r` live in their own TU built at `-O0` — the
including sqrt/pow/sin/cos/exp/log), and the small string/stdlib `-O2` codegen for the str==NULL continuation path miscompiles
gaps (strcat, strncat, strpbrk, strspn, strcspn, atol, llabs). (documented as the same backend-fragility class as #70 / qsort,
sprintf/snprintf was the most invasive — eight independent W65816 both mitigated by reaching for fast regalloc per-TU). Multi-call
backend workarounds documented in the file banner, including the strtok over "a,b,,c" works end-to-end in smoke.
%.Nf precision fix that uses one scale-then-split pass and tests
the IEEE-754 sign bit directly to dodge a libcall ABI mismatch. 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 The **DWARF sidecar** (`link816 --debug-out FILE`) now applies
text/rodata/bss/init_array relocations to every `.debug_*` section text/rodata/bss/init_array relocations to every `.debug_*` section
@ -150,9 +158,9 @@ sidecar bytes.
- **More of the C standard library**: real `<stdio.h>` file I/O - **More of the C standard library**: real `<stdio.h>` file I/O
(`fopen`, `fread`, `fwrite`, `fseek` are currently stubs (`fopen`, `fread`, `fwrite`, `fseek` are currently stubs
returning success/zero), additional `<math.h>` (atan, asin, returning success/zero) — would need a memory-backed FS or a
acos, hyperbolic forms), and `<locale.h>` / `<wchar.h>` if any MAME hook; `<locale.h>` / `<wchar.h>` if any real-world code
real-world code needs them. needs them.
- **C++ runtime support**: vtable layout for multiple inheritance, - **C++ runtime support**: vtable layout for multiple inheritance,
RTTI, exceptions (or a documented `-fno-exceptions` requirement). RTTI, exceptions (or a documented `-fno-exceptions` requirement).

View file

@ -38,6 +38,12 @@ cc "$SRC/strtol.c"
cc "$SRC/snprintf.c" cc "$SRC/snprintf.c"
cc "$SRC/qsort.c" cc "$SRC/qsort.c"
cc "$SRC/extras.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/math.c"
cc "$SRC/softFloat.c" cc "$SRC/softFloat.c"
# softDouble.c needs -regalloc=fast: __muldf3's 64x64 -> 128 mul + # softDouble.c needs -regalloc=fast: __muldf3's 64x64 -> 128 mul +

View file

@ -23,5 +23,19 @@ double exp (double x);
float expf (float x); float expf (float x);
double log (double x); double log (double x);
float logf (float 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 #endif

View file

@ -23,6 +23,7 @@ char *strpbrk(const char *s, const char *accept);
size_t strspn (const char *s, const char *accept); size_t strspn (const char *s, const char *accept);
size_t strcspn(const char *s, const char *reject); size_t strcspn(const char *s, const char *reject);
char *strtok (char *str, const char *delim); char *strtok (char *str, const char *delim);
char *strtok_r(char *str, const char *delim, char **saveptr);
char *strerror(int err); char *strerror(int err);

View file

@ -109,77 +109,5 @@ size_t strcspn(const char *s, const char *reject) {
} }
// strtok: stateful tokenizer. First call passes the string; subsequent // strtok / strtok_r are in runtime/src/strtok.c (built at -O0 to dodge
// calls pass NULL and resume from the saved cursor. Single-threaded // a backend miscompile of the str==NULL continuation path; #84).
// 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++;
}
}

View file

@ -344,3 +344,137 @@ double log(double x) {
float logf(float x) { float logf(float x) {
return (float)log((double)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);
}

75
runtime/src/strtok.c Normal file
View file

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

View file

@ -1403,6 +1403,7 @@ EOF
oSnprintfF="$(mktemp --suffix=.o)" oSnprintfF="$(mktemp --suffix=.o)"
oQsortF="$(mktemp --suffix=.o)" oQsortF="$(mktemp --suffix=.o)"
oExtrasF="$(mktemp --suffix=.o)" oExtrasF="$(mktemp --suffix=.o)"
oStrtokF="$(mktemp --suffix=.o)"
oMathF="$(mktemp --suffix=.o)" oMathF="$(mktemp --suffix=.o)"
oSfF="$(mktemp --suffix=.o)" oSfF="$(mktemp --suffix=.o)"
oSdF="$(mktemp --suffix=.o)" oSdF="$(mktemp --suffix=.o)"
@ -1416,6 +1417,9 @@ EOF
-c "$PROJECT_ROOT/runtime/src/qsort.c" -o "$oQsortF" -c "$PROJECT_ROOT/runtime/src/qsort.c" -o "$oQsortF"
"$CLANG" --target=w65816 -O2 -ffunction-sections \ "$CLANG" --target=w65816 -O2 -ffunction-sections \
-c "$PROJECT_ROOT/runtime/src/extras.c" -o "$oExtrasF" -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 \ "$CLANG" --target=w65816 -O2 -ffunction-sections \
-c "$PROJECT_ROOT/runtime/src/math.c" -o "$oMathF" -c "$PROJECT_ROOT/runtime/src/math.c" -o "$oMathF"
"$CLANG" --target=w65816 -O2 -ffunction-sections \ "$CLANG" --target=w65816 -O2 -ffunction-sections \
@ -2160,7 +2164,7 @@ int main(void) {
r = sprintf(buf, "[%c%c%%]", 'A', 'B'); r = sprintf(buf, "[%c%c%%]", 'A', 'B');
if (r == 5 && eq(buf, "[AB%]")) ok |= 0x20; if (r == 5 && eq(buf, "[AB%]")) ok |= 0x20;
switchToBank2(); switchToBank2();
*(volatile unsigned char *)0x5000 = ok; *(volatile unsigned short *)0x5000 = (unsigned short)ok;
while (1) {} while (1) {}
} }
EOF EOF
@ -2263,7 +2267,7 @@ int main(void) {
if (dEq(ceil(2.3), 3.0)) ok |= 0x40; if (dEq(ceil(2.3), 3.0)) ok |= 0x40;
if (dEq(copysign(1.0, -2.0), -1.0)) ok |= 0x80; if (dEq(copysign(1.0, -2.0), -1.0)) ok |= 0x80;
switchToBank2(); switchToBank2();
*(volatile unsigned char *)0x5000 = ok; *(volatile unsigned short *)0x5000 = (unsigned short)ok;
while (1) {} while (1) {}
} }
EOF EOF
@ -2271,7 +2275,7 @@ EOF
"$cExFile" -o "$oExFile" "$cExFile" -o "$oExFile"
"$PROJECT_ROOT/tools/link816" -o "$binExFile" --text-base 0x1000 \ "$PROJECT_ROOT/tools/link816" -o "$binExFile" --text-base 0x1000 \
"$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \
"$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oExFile" \ "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oExFile" \
>/dev/null 2>&1 >/dev/null 2>&1
if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binExFile" --check \ if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binExFile" --check \
0x025000=00ff >/dev/null 2>&1; then 0x025000=00ff >/dev/null 2>&1; then
@ -2279,6 +2283,254 @@ EOF
fi fi
rm -f "$cExFile" "$oExFile" "$binExFile" 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)" log "check: MAME runs sqrt/pow + sin/cos/exp/log + strpbrk/spn/cspn (#81 + #82 + #83)"
cTrFile="$(mktemp --suffix=.c)" cTrFile="$(mktemp --suffix=.c)"
oTrFile="$(mktemp --suffix=.o)" oTrFile="$(mktemp --suffix=.o)"
@ -2327,7 +2579,7 @@ EOF
"$cTrFile" -o "$oTrFile" "$cTrFile" -o "$oTrFile"
"$PROJECT_ROOT/tools/link816" -o "$binTrFile" --text-base 0x1000 \ "$PROJECT_ROOT/tools/link816" -o "$binTrFile" --text-base 0x1000 \
"$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ "$oCrt0F" "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \
"$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oTrFile" \ "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oLibgccFile" "$oTrFile" \
>/dev/null 2>&1 >/dev/null 2>&1
if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binTrFile" --check \ if ! bash "$PROJECT_ROOT/scripts/runInMame.sh" "$binTrFile" --check \
0x025000=0fff >/dev/null 2>&1; then 0x025000=0fff >/dev/null 2>&1; then
@ -2617,7 +2869,7 @@ EOF
rm -f "$cDmaFile" "$oDmaFile" "$binDmaFile" rm -f "$cDmaFile" "$oDmaFile" "$binDmaFile"
rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \ rm -f "$oLibcF" "$oStrtolF" "$oSnprintfF" "$oQsortF" \
"$oExtrasF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F" "$oExtrasF" "$oStrtokF" "$oMathF" "$oSfF" "$oSdF" "$oCrt0F"
else else
warn "MAME or apple2gs ROMs not installed; skipping end-to-end test" warn "MAME or apple2gs ROMs not installed; skipping end-to-end test"
fi fi