Checkpoint.
This commit is contained in:
parent
1622053eb2
commit
e805911e18
8 changed files with 518 additions and 100 deletions
50
STATUS.md
50
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).
|
||||
- `<math.h>`: 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 `<stdio.h>` file I/O
|
||||
(`fopen`, `fread`, `fwrite`, `fseek` are currently stubs
|
||||
returning success/zero), additional `<math.h>` (atan, asin,
|
||||
acos, hyperbolic forms), and `<locale.h>` / `<wchar.h>` if any
|
||||
real-world code needs them.
|
||||
returning success/zero) — would need a memory-backed FS or a
|
||||
MAME hook; `<locale.h>` / `<wchar.h>` if any real-world code
|
||||
needs them.
|
||||
|
||||
- **C++ runtime support**: vtable layout for multiple inheritance,
|
||||
RTTI, exceptions (or a documented `-fno-exceptions` requirement).
|
||||
|
|
|
|||
|
|
@ -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 +
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
75
runtime/src/strtok.c
Normal file
75
runtime/src/strtok.c
Normal 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);
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue