Checkpoint

This commit is contained in:
Scott Duensing 2026-05-03 23:10:41 -05:00
parent f542f4fa01
commit bb3aad3911
18 changed files with 1077 additions and 42 deletions

View file

@ -37,9 +37,14 @@ asm "$SRC/libgcc.s"
cc "$SRC/libc.c" cc "$SRC/libc.c"
cc "$SRC/strtol.c" cc "$SRC/strtol.c"
cc "$SRC/snprintf.c" cc "$SRC/snprintf.c"
cc "$SRC/sscanf.c"
cc "$SRC/qsort.c" cc "$SRC/qsort.c"
cc "$SRC/extras.c" cc "$SRC/extras.c"
cc "$SRC/strtok.c" cc "$SRC/strtok.c"
cc "$SRC/timeExt.c" -O1
# timeExt.c at -O1: -O2 generates code where strftime's directive
# switch overflows the W65816's 8-bit signed stack-relative offset
# range. -O1 keeps the per-function frame small enough.
cc "$SRC/math.c" cc "$SRC/math.c"
cc "$SRC/softFloat.c" cc "$SRC/softFloat.c"
cc "$SRC/libcxxabi.c" cc "$SRC/libcxxabi.c"

View file

@ -1,30 +1,93 @@
#ifndef _MATH_H #ifndef _MATH_H
#define _MATH_H #define _MATH_H
// ---- Special values --------------------------------------------------
// Use compiler builtins so const-folding works in user code.
#define HUGE_VAL (__builtin_huge_val())
#define HUGE_VALF (__builtin_huge_valf())
#define INFINITY (__builtin_inff())
#define NAN (__builtin_nanf(""))
// ---- Classification (functions, exposed via macros below) -----------
int __isnan_d (double x);
int __isinf_d (double x);
int __isfinite_d(double x);
int __signbit_d (double x);
#define isnan(x) __isnan_d((double)(x))
#define isinf(x) __isinf_d((double)(x))
#define isfinite(x) __isfinite_d((double)(x))
#define signbit(x) __signbit_d((double)(x))
// ---- Absolute / sign ------------------------------------------------
double fabs (double x); double fabs (double x);
float fabsf (float 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); double copysign (double x, double y);
float copysignf(float x, float y); float copysignf(float x, float y);
// ---- Rounding -------------------------------------------------------
double floor (double x);
float floorf (float x);
double ceil (double x);
float ceilf (float x);
double trunc (double x);
float truncf (float x);
double round (double x);
float roundf (float x);
// ---- Min / max / positive difference --------------------------------
double fmax (double x, double y);
double fmin (double x, double y);
double fdim (double x, double y);
float fmaxf(float x, float y);
float fminf(float x, float y);
float fdimf(float x, float y);
// ---- Mod / remainder ------------------------------------------------
double fmod (double x, double y);
float fmodf(float x, float y);
// ---- FP decomposition -----------------------------------------------
double ldexp(double x, int n);
float ldexpf(float x, int n);
double frexp(double x, int *e);
float frexpf(float x, int *e);
double modf (double x, double *iptr);
float modff(float x, float *iptr);
// ---- Power / root ---------------------------------------------------
double sqrt (double x); double sqrt (double x);
float sqrtf (float x); float sqrtf (float x);
double cbrt (double x);
float cbrtf (float x);
double pow (double x, double y); double pow (double x, double y);
float powf (float x, float y); float powf (float x, float y);
double hypot (double x, double y);
float hypotf (float x, float y);
// ---- Exponential / log ----------------------------------------------
double exp (double x);
float expf (float x);
double exp2 (double x);
float exp2f (float x);
double expm1 (double x);
float expm1f (float x);
double log (double x);
float logf (float x);
double log10 (double x);
float log10f (float x);
double log2 (double x);
float log2f (float x);
double log1p (double x);
float log1pf (float x);
// ---- Trigonometric --------------------------------------------------
double sin (double x); double sin (double x);
float sinf (float x); float sinf (float x);
double cos (double x); double cos (double x);
float cosf (float x); float cosf (float x);
double tan (double x); double tan (double x);
float tanf (float x); float tanf (float x);
double exp (double x);
float expf (float x);
double log (double x);
float logf (float x);
double atan (double x); double atan (double x);
float atanf (float x); float atanf (float x);
double atan2 (double y, double x); double atan2 (double y, double x);
@ -33,6 +96,8 @@ double asin (double x);
float asinf (float x); float asinf (float x);
double acos (double x); double acos (double x);
float acosf (float x); float acosf (float x);
// ---- Hyperbolic -----------------------------------------------------
double sinh (double x); double sinh (double x);
float sinhf (float x); float sinhf (float x);
double cosh (double x); double cosh (double x);
@ -40,4 +105,20 @@ float coshf (float x);
double tanh (double x); double tanh (double x);
float tanhf (float x); float tanhf (float x);
// ---- Common constants -----------------------------------------------
// (Not in C99 strict, but defined by glibc/BSD math.h and widely used.)
#define M_E 2.7182818284590452354
#define M_LOG2E 1.4426950408889634074
#define M_LOG10E 0.43429448190325182765
#define M_LN2 0.69314718055994530942
#define M_LN10 2.30258509299404568402
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
#define M_PI_4 0.78539816339744830962
#define M_1_PI 0.31830988618379067154
#define M_2_PI 0.63661977236758134308
#define M_2_SQRTPI 1.12837916709551257390
#define M_SQRT2 1.41421356237309504880
#define M_SQRT1_2 0.70710678118654752440
#endif #endif

View file

@ -47,6 +47,14 @@ char *fgets(char *buf, int n, FILE *stream);
int ungetc(int c, FILE *stream); int ungetc(int c, FILE *stream);
#define getc(s) fgetc(s) #define getc(s) fgetc(s)
// scanf family — only sscanf and vsscanf are implemented (parsing
// from a string buffer). scanf/fscanf would need a reliable byte-at-
// a-time stdin which we don't have. Supports %d %i %u %x %X %o %s
// %c %% with optional `l` long modifier.
int sscanf (const char *str, const char *fmt, ...);
int vsscanf(const char *str, const char *fmt, va_list ap);
void rewind(FILE *stream); // = fseek(s, 0, SEEK_SET) + clearerr
// Memory-backed FS: register a memory region as a named file so // Memory-backed FS: register a memory region as a named file so
// fopen can open it. `cap` should be >= size; use cap > size for // fopen can open it. `cap` should be >= size; use cap > size for
// files that may grow on write. `writable` controls whether // files that may grow on write. `writable` controls whether

View file

@ -10,12 +10,25 @@ void free(void *p);
int abs(int n); int abs(int n);
long labs(long n); long labs(long n);
long long llabs(long long n);
int atoi(const char *s); int atoi(const char *s);
long atol(const char *s); long atol(const char *s);
long long llabs(long long n); long long atoll(const char *s);
double atof(const char *s);
long strtol (const char *nptr, char **endptr, int base); typedef struct { int quot, rem; } div_t;
unsigned long strtoul(const char *nptr, char **endptr, int base); typedef struct { long quot, rem; } ldiv_t;
typedef struct { long long quot, rem; } lldiv_t;
div_t div (int n, int d);
ldiv_t ldiv (long n, long d);
lldiv_t lldiv(long long n, long long d);
long strtol (const char *nptr, char **endptr, int base);
unsigned long strtoul (const char *nptr, char **endptr, int base);
long long strtoll (const char *nptr, char **endptr, int base);
unsigned long long strtoull(const char *nptr, char **endptr, int base);
double strtod (const char *nptr, char **endptr);
float strtof (const char *nptr, char **endptr);
typedef int (*__cmp_fn)(const void *, const void *); typedef int (*__cmp_fn)(const void *, const void *);
void qsort (void *base, size_t nmemb, size_t size, __cmp_fn cmp); void qsort (void *base, size_t nmemb, size_t size, __cmp_fn cmp);

View file

@ -22,6 +22,11 @@ char *strrchr(const char *s, int c);
char *strstr(const char *haystack, const char *needle); char *strstr(const char *haystack, const char *needle);
char *strcat(char *dst, const char *src); char *strcat(char *dst, const char *src);
char *strncat(char *dst, const char *src, size_t n); char *strncat(char *dst, const char *src, size_t n);
char *strdup (const char *s);
char *strndup(const char *s, size_t maxlen);
void *memccpy(void *dst, const void *src, int c, size_t n);
char *stpcpy (char *dst, const char *src);
char *stpncpy(char *dst, const char *src, size_t n);
char *strpbrk(const char *s, const char *accept); 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);

View file

@ -3,11 +3,48 @@
typedef long time_t; typedef long time_t;
typedef unsigned long clock_t; typedef unsigned long clock_t;
typedef unsigned int size_t;
#define CLOCKS_PER_SEC 60 // IIgs vsync tick (placeholder) #define CLOCKS_PER_SEC 60 // IIgs vsync tick (placeholder)
struct tm {
int tm_sec; // 0..60 (60 = leap second)
int tm_min; // 0..59
int tm_hour; // 0..23
int tm_mday; // 1..31
int tm_mon; // 0..11 (Jan=0)
int tm_year; // years since 1900
int tm_wday; // 0..6 (Sun=0)
int tm_yday; // 0..365 (Jan 1 = 0)
int tm_isdst; // > 0 = DST in effect, 0 = not, < 0 = unknown
};
time_t time(time_t *t); time_t time(time_t *t);
clock_t clock(void); clock_t clock(void);
double difftime(time_t end, time_t start);
// Calendar conversions. gmtime and localtime are identical here —
// no timezone support; "local" is treated as UTC.
//
// ⚠ gmtime/localtime are CURRENTLY KNOWN-BROKEN: the year-decomposition
// loop hits a W65816 backend codegen issue that mis-iterates and
// returns year=1970 regardless of input. Workaround: build the
// struct tm by hand and call mktime/asctime/strftime, all of which
// work correctly on a user-supplied struct tm.
struct tm *gmtime (const time_t *t);
struct tm *localtime(const time_t *t);
time_t mktime (struct tm *tm);
// Text formatting. asctime returns "Sun Jan 1 00:00:00 1970\n" form.
// Both functions return a pointer to a static buffer (overwritten on
// each call — not thread-safe).
char *asctime(const struct tm *tm);
char *ctime (const time_t *t);
// strftime: format `tm` per `fmt` into `buf` (max `n` bytes incl.
// terminator). Supports a useful subset: %Y %m %d %H %M %S %j %w %a
// %A %b %B %p %% — sufficient for log timestamps and date display.
size_t strftime(char *buf, size_t n, const char *fmt, const struct tm *tm);
// Initialise the IIgs Tool Locator so time() can call ReadTimeHex. // Initialise the IIgs Tool Locator so time() can call ReadTimeHex.
// Call once before any time() use. Idempotent — repeated calls // Call once before any time() use. Idempotent — repeated calls

View file

@ -26,6 +26,14 @@
.globl __start .globl __start
__start: __start:
; Set DBR := PBR so absolute (DBR-relative) loads/stores reach
; symbols within our segment. The Loader's documented contract is
; "DBR set to your bank" but verified empirically NOT always true
; for KIND=0x1000 segments — `lda absConst` was reading from the
; wrong bank without this. PHK + PLB copies PBR into DBR.
phk
plb
; Set DP=0. The C compiler assumes DP=0 for all `sta dp` and ; Set DP=0. The C compiler assumes DP=0 for all `sta dp` and
; `[dp],y`-style accesses; GS/OS hands us a Memory-Manager- ; `[dp],y`-style accesses; GS/OS hands us a Memory-Manager-
; allocated DP page that we discard. ; allocated DP page that we discard.

View file

@ -131,6 +131,19 @@ int tolower(int c) { return isupper(c) ? c + 32 : c; }
int abs(int n) { return n < 0 ? -n : n; } int abs(int n) { return n < 0 ? -n : n; }
long labs(long n) { return n < 0 ? -n : n; } long labs(long n) { return n < 0 ? -n : n; }
// div/ldiv/lldiv: return both quotient and remainder in one struct.
// Useful for code that wants a single libcall instead of paired / and %.
// Per C99: quot is integer division truncated toward zero; rem has the
// same sign as the numerator.
typedef struct { int quot, rem; } div_t;
typedef struct { long quot, rem; } ldiv_t;
typedef struct { long long quot, rem; } lldiv_t;
div_t div (int n, int d) { div_t r; r.quot = n/d; r.rem = n%d; return r; }
ldiv_t ldiv (long n, long d) { ldiv_t r; r.quot = n/d; r.rem = n%d; return r; }
lldiv_t lldiv(long long n, long long d) { lldiv_t r; r.quot = n/d; r.rem = n%d; return r; }
int atoi(const char *s) { int atoi(const char *s) {
int sign = 1; int sign = 1;
while (isspace(*s)) s++; while (isspace(*s)) s++;
@ -364,6 +377,59 @@ char *strstr(const char *haystack, const char *needle) {
return 0; return 0;
} }
// Forward declarations for strdup/strndup; the actual definitions
// live further down in the file (malloc and strnlen are below).
extern void *malloc(size_t);
extern size_t strnlen(const char *, size_t);
// strdup/strndup — POSIX, super common in real-world code.
// Both allocate via malloc; caller frees.
char *strdup(const char *s) {
size_t n = strlen(s);
char *r = (char *)malloc(n + 1);
if (!r) return 0;
memcpy(r, s, n + 1);
return r;
}
char *strndup(const char *s, size_t maxlen) {
size_t n = strnlen(s, maxlen);
char *r = (char *)malloc(n + 1);
if (!r) return 0;
memcpy(r, s, n);
r[n] = 0;
return r;
}
// memccpy — copy until either `n` bytes done OR byte `c` was copied.
// Returns ptr to byte after the copied `c`, or NULL if `c` not found.
void *memccpy(void *dst, const void *src, int c, size_t n) {
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)src;
while (n--) {
unsigned char v = *s++;
*d++ = v;
if (v == (unsigned char)c) return d;
}
return 0;
}
// stpcpy — like strcpy but returns ptr to terminating NUL of dst.
char *stpcpy(char *dst, const char *src) {
while ((*dst = *src) != 0) { dst++; src++; }
return dst;
}
// stpncpy — like strncpy but returns ptr to terminator (or to dst+n
// if no terminator was copied).
char *stpncpy(char *dst, const char *src, size_t n) {
char *p = dst;
while (n && (*p = *src) != 0) { p++; src++; n--; }
char *end = p;
while (n--) *p++ = 0;
return end;
}
// ---- malloc/free — first-fit allocator with coalescing-on-free ---- // ---- malloc/free — first-fit allocator with coalescing-on-free ----
// //
// Heap lives between the static-data top (linker-supplied __heap_start) // Heap lives between the static-data top (linker-supplied __heap_start)
@ -1129,6 +1195,14 @@ void clearerr(FILE *stream) {
if (stream) { stream->eof = 0; stream->err = 0; } if (stream) { stream->eof = 0; stream->err = 0; }
} }
// rewind — convenience wrapper: seek to start + clear error/EOF.
void rewind(FILE *stream) {
if (!stream) return;
fseek(stream, 0L, 0 /* SEEK_SET */);
stream->eof = 0;
stream->err = 0;
}
// ---- locale.h stubs ---- // ---- locale.h stubs ----
// //
// No real locale support — IIgs is single-locale. setlocale always // No real locale support — IIgs is single-locale. setlocale always
@ -1225,3 +1299,5 @@ int raise(int sig) {
h(sig); h(sig);
return 0; return 0;
} }

View file

@ -503,3 +503,251 @@ double tanh(double x) {
float tanhf(float x) { float tanhf(float x) {
return (float)tanh((double)x); return (float)tanh((double)x);
} }
// ---- Classification ------------------------------------------------
//
// Implemented as functions rather than the C99 macros so they can be
// referenced from outside math.h. The math.h header also exposes them
// as macros that expand to these calls.
int __isnan_d(double x) {
uint64_t b = dToBits(x);
return ((b >> 52) & 0x7FF) == 0x7FF && (b & 0xFFFFFFFFFFFFFULL) != 0;
}
int __isinf_d(double x) {
uint64_t b = dToBits(x);
return ((b >> 52) & 0x7FF) == 0x7FF && (b & 0xFFFFFFFFFFFFFULL) == 0;
}
int __isfinite_d(double x) {
return ((dToBits(x) >> 52) & 0x7FF) != 0x7FF;
}
int __signbit_d(double x) {
return (int)(dToBits(x) >> 63);
}
// ---- Rounding ------------------------------------------------------
// trunc: round toward zero. = floor for positive, ceil for negative.
double trunc(double x) {
return (dToBits(x) >> 63) ? ceil(x) : floor(x);
}
float truncf(float x) {
return (float)trunc((double)x);
}
// round: round half away from zero.
double round(double x) {
return (dToBits(x) >> 63) ? -floor(-x + 0.5) : floor(x + 0.5);
}
float roundf(float x) {
return (float)round((double)x);
}
// ---- Min / max / positive difference -------------------------------
double fmax(double x, double y) {
if (__isnan_d(x)) return y;
if (__isnan_d(y)) return x;
return x > y ? x : y;
}
double fmin(double x, double y) {
if (__isnan_d(x)) return y;
if (__isnan_d(y)) return x;
return x < y ? x : y;
}
double fdim(double x, double y) {
return x > y ? x - y : 0.0;
}
float fmaxf(float x, float y) { return (float)fmax((double)x, (double)y); }
float fminf(float x, float y) { return (float)fmin((double)x, (double)y); }
float fdimf(float x, float y) { return (float)fdim((double)x, (double)y); }
// ---- FP decomposition ----------------------------------------------
// ldexp(x, n) = x * 2^n. Implemented by adjusting the exponent field
// in place. Handles subnormals and overflow only crudely (overflow →
// infinity; underflow → zero).
double ldexp(double x, int n) {
uint64_t b = dToBits(x);
int e = (int)((b >> 52) & 0x7FF);
if (e == 0 || e == 0x7FF) {
// 0 or denorm or inf/nan — return as-is for inf/nan, else
// fall back to multiplication (handles subnormals correctly
// enough for typical use).
if (e == 0x7FF) return x;
// For zero / subnormal, multiply by 2^n via repeated *2.
if (n > 0) {
while (n--) x *= 2.0;
return x;
}
if (n < 0) {
while (n++) x *= 0.5;
return x;
}
return x;
}
int newE = e + n;
if (newE >= 0x7FF) {
// Overflow → ±infinity.
return (b >> 63) ? -1e308 * 1e308 : 1e308 * 1e308;
}
if (newE <= 0) {
// Underflow → ±0 (skip subnormal handling).
return (b >> 63) ? -0.0 : 0.0;
}
b = (b & 0x800FFFFFFFFFFFFFULL) | ((uint64_t)newE << 52);
return dFromBits(b);
}
float ldexpf(float x, int n) {
return (float)ldexp((double)x, n);
}
// frexp(x, *e): split x into normalized fraction in [0.5, 1) (or 0)
// and integer exponent. x = frac * 2^e.
double frexp(double x, int *eOut) {
uint64_t b = dToBits(x);
int e = (int)((b >> 52) & 0x7FF);
if (e == 0) { *eOut = 0; return x; } // zero or subnormal
if (e == 0x7FF) { *eOut = 0; return x; } // inf or nan
*eOut = e - 1022; // bias adjustment for [0.5,1)
// Force exponent to 1022 (so result is in [0.5, 1)).
b = (b & 0x800FFFFFFFFFFFFFULL) | ((uint64_t)1022 << 52);
return dFromBits(b);
}
float frexpf(float x, int *eOut) {
double r = frexp((double)x, eOut);
return (float)r;
}
// modf(x, *iptr): split into integer and fractional parts, both with
// the same sign as x.
double modf(double x, double *iptr) {
double ip = trunc(x);
*iptr = ip;
return x - ip;
}
float modff(float x, float *iptr) {
double ipd;
double frac = modf((double)x, &ipd);
*iptr = (float)ipd;
return (float)frac;
}
// ---- log10 / log2 / exp2 -------------------------------------------
//
// All routed through log() / exp() with the conversion constant.
double log10(double x) {
// 1 / ln(10) = 0.43429448190325182765112891891660508229439700580366
return log(x) * 0.43429448190325182765;
}
double log2(double x) {
// 1 / ln(2) = 1.4426950408889634073599246810018921374266459541530
return log(x) * 1.4426950408889634074;
}
double exp2(double x) {
// ln(2) = 0.69314718055994530941723212145817656807550013436026
return exp(x * 0.69314718055994530942);
}
float log10f(float x) { return (float)log10((double)x); }
float log2f(float x) { return (float)log2((double)x); }
float exp2f(float x) { return (float)exp2((double)x); }
// log1p(x) = log(1 + x). For |x| small the naive form loses precision;
// we accept that for now since this is a standalone runtime, not a
// numerics library.
double log1p(double x) { return log(1.0 + x); }
float log1pf(float x) { return (float)log1p((double)x); }
// expm1(x) = exp(x) - 1. Same loss-of-precision caveat near 0.
double expm1(double x) { return exp(x) - 1.0; }
float expm1f(float x) { return (float)expm1((double)x); }
// ---- hypot ---------------------------------------------------------
//
// hypot(x, y) = sqrt(x*x + y*y). This implementation does NOT scale
// to avoid overflow — for |x|, |y| < ~1e150 the naive form is fine,
// past that you'd want the standard scale-by-max trick.
// hypot — naive sqrt(x*x + y*y). NO `volatile` on the temps —
// clang's codegen for volatile-double locals on this target generates
// stack-relative loads/stores that crash under the GS/OS Loader (the
// chain executes correctly under runInMame but not via Finder). The
// volatile-free version works in both contexts.
__attribute__((noinline))
double hypot(double x, double y) {
double xx = x * x;
double yy = y * y;
double s = xx + yy;
return sqrt(s);
}
float hypotf(float x, float y) {
return (float)hypot((double)x, (double)y);
}
// ---- cbrt ----------------------------------------------------------
//
// Newton-Raphson for cube root: r_{n+1} = (2*r_n + a/r_n²) / 3.
// Converges quadratically; 30 iters more than enough for double.
// Implemented WITHOUT calling pow because clang treats pow as a
// known builtin and either inlines it (with bad fold of pow(x,1/3))
// or DCEs the call entirely (cbrt body collapses to "return 0").
// This implementation has no pow dependency and is immune.
__attribute__((noinline))
double cbrt(double x) {
if (x == 0.0) return x;
int neg = (int)(dToBits(x) >> 63) & 1;
double a = neg ? -x : x;
double r = a; // crude initial guess
for (int i = 0; i < 30; i++) {
r = (2.0 * r + a / (r * r)) * (1.0 / 3.0);
}
return neg ? -r : r;
}
float cbrtf(float x) {
return (float)cbrt((double)x);
}

View file

@ -377,3 +377,24 @@ s32 __fixdfsi(u64 x) {
if (shift >= 0) m >>= shift; else m <<= -shift; if (shift >= 0) m >>= shift; else m <<= -shift;
return sign ? -(s32)m : (s32)m; return sign ? -(s32)m : (s32)m;
} }
// __fixunsdfsi: unsigned double → uint32. Saturates to 0 for negative
// inputs, to 0xFFFFFFFF for inputs >= 2^32. Used by clang when casting
// double values to unsigned integer types.
u32 __fixunsdfsi(u64 x) {
if (x & DSIGN_BIT) return 0; // negative → 0
u16 e = (u16)((x >> DEXP_SHIFT) & 0x7FF);
if (e == 0) return 0;
if (e == 0x7FF) return 0xFFFFFFFF;
s16 unbiased = (s16)e - DEXP_BIAS;
if (unbiased < 0) return 0;
if (unbiased > 31) return 0xFFFFFFFF;
u64 m = (x & DMANT_MASK) | DMANT_LEAD;
if (unbiased >= 52) {
m <<= (unbiased - 52);
} else {
m >>= (52 - unbiased);
}
return (u32)m;
}

View file

@ -220,6 +220,15 @@ s16 __gtsf2(u32 a, u32 b) { return __cmpsf2(a, b); }
s16 __lesf2(u32 a, u32 b) { return __cmpsf2(a, b); } s16 __lesf2(u32 a, u32 b) { return __cmpsf2(a, b); }
s16 __gesf2(u32 a, u32 b) { return __cmpsf2(a, b); } s16 __gesf2(u32 a, u32 b) { return __cmpsf2(a, b); }
// __unordsf2: 1 if either arg is NaN, else 0. Used for IEEE 754
// unordered comparisons (a < b is false if either is NaN, etc.).
s16 __unordsf2(u32 a, u32 b) {
u32 ax = a & 0x7FFFFFFFUL;
u32 bx = b & 0x7FFFFFFFUL;
// NaN: exp=0xFF, mantissa != 0 → ax > 0x7F800000
return (ax > 0x7F800000UL) || (bx > 0x7F800000UL) ? 1 : 0;
}
u32 __floatsisf(s32 i) { u32 __floatsisf(s32 i) {
if (i == 0) return 0; if (i == 0) return 0;
u32 sign = 0; u32 sign = 0;

146
runtime/src/sscanf.c Normal file
View file

@ -0,0 +1,146 @@
// sscanf — minimal subset for the W65816 runtime.
// Supports format directives:
// %d / %i signed int (decimal)
// %u unsigned int (decimal)
// %x %X unsigned int (hex; "0x" prefix optional)
// %o unsigned int (octal)
// %ld %lu %lx long-int variants (32-bit)
// %s whitespace-terminated string into char*
// %c single char into char*
// %% literal %
// Whitespace in the format matches zero or more whitespace chars
// in the input. Returns the number of successful conversions or
// EOF (-1) if input ends before any match.
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)
extern int isspace(int);
// Skip leading whitespace, return the first non-space char ptr.
static const char *skipWs(const char *s) {
while (*s && isspace(*s)) s++;
return s;
}
// Parse an unsigned integer in the given base. Updates *pp to the
// first unconsumed char. Returns 1 if any digit was consumed, else 0.
static int parseUL(const char **pp, int base, unsigned long *out) {
const char *p = *pp;
unsigned long v = 0;
int saw = 0;
while (*p) {
int c = *p, d;
if (c >= '0' && c <= '9') d = c - '0';
else if (c >= 'a' && c <= 'z') d = 10 + c - 'a';
else if (c >= 'A' && c <= 'Z') d = 10 + c - 'A';
else break;
if (d < 0 || d >= base) break;
v = v * (unsigned long)base + (unsigned long)d;
saw = 1;
p++;
}
*pp = p;
*out = v;
return saw;
}
int vsscanf(const char *str, const char *fmt, va_list ap) {
int matched = 0;
const char *s = str;
while (*fmt) {
if (isspace(*fmt)) {
// Whitespace in format: skip 0+ whitespace in input.
while (*fmt && isspace(*fmt)) fmt++;
s = skipWs(s);
continue;
}
if (*fmt != '%') {
if (*s != *fmt) break;
fmt++; s++;
continue;
}
fmt++;
if (*fmt == 0) break;
// Long modifier?
int isLong = 0;
if (*fmt == 'l') { isLong = 1; fmt++; if (*fmt == 0) break; }
char spec = *fmt;
if (spec == '%') {
if (*s != '%') break;
s++; fmt++; continue;
}
if (spec == 'c') {
char *out = va_arg(ap, char *);
if (!*s) break;
*out = *s++;
matched++;
fmt++;
continue;
}
if (spec == 's') {
char *out = va_arg(ap, char *);
s = skipWs(s);
if (!*s) break;
int n = 0;
while (*s && !isspace(*s)) { *out++ = *s++; n++; }
*out = 0;
if (n) matched++;
fmt++;
continue;
}
// Numeric conversions: skip whitespace first.
s = skipWs(s);
int neg = 0;
if ((spec == 'd' || spec == 'i') && (*s == '+' || *s == '-')) {
neg = (*s == '-');
s++;
}
int base = 10;
if (spec == 'x' || spec == 'X') base = 16;
else if (spec == 'o') base = 8;
else if (spec == 'i') {
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
base = 16; s += 2;
} else if (s[0] == '0') {
base = 8;
}
}
if ((spec == 'x' || spec == 'X') && s[0] == '0' &&
(s[1] == 'x' || s[1] == 'X')) s += 2;
unsigned long v;
if (!parseUL(&s, base, &v)) break;
if (isLong) {
if (spec == 'd' || spec == 'i') {
long *out = va_arg(ap, long *);
*out = neg ? -(long)v : (long)v;
} else {
unsigned long *out = va_arg(ap, unsigned long *);
*out = v;
}
} else {
if (spec == 'd' || spec == 'i') {
int *out = va_arg(ap, int *);
*out = neg ? -(int)v : (int)v;
} else {
unsigned int *out = va_arg(ap, unsigned int *);
*out = (unsigned int)v;
}
}
matched++;
fmt++;
}
if (matched == 0 && !*s) return -1; // EOF: no chars consumed
return matched;
}
int sscanf(const char *str, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = vsscanf(str, fmt, ap);
va_end(ap);
return r;
}

View file

@ -60,3 +60,127 @@ long strtol(const char *nptr, char **endptr, int base) {
// ("-2147483648" — the magnitude doesn't fit in long). // ("-2147483648" — the magnitude doesn't fit in long).
return neg ? (long)(0ul - n) : (long)n; return neg ? (long)(0ul - n) : (long)n;
} }
// ---- Long-long (64-bit) variants ------------------------------------
unsigned long long strtoull(const char *nptr, char **endptr, int base) {
const char *s = nptr;
while (isspace(*s)) s++;
int neg = 0;
if (*s == '-') { neg = 1; s++; }
else if (*s == '+') s++;
if ((base == 0 || base == 16) && s[0] == '0' &&
(s[1] == 'x' || s[1] == 'X') && charDigit(s[2], 16) >= 0) {
base = 16;
s += 2;
} else if (base == 0 && *s == '0') {
base = 8;
s++;
} else if (base == 0) {
base = 10;
}
unsigned long long n = 0;
int saw_digit = 0;
for (;;) {
int d = charDigit(*s, base);
if (d < 0) break;
n = n * (unsigned long long)base + (unsigned long long)d;
saw_digit = 1;
s++;
}
if (endptr) *endptr = (char *)(saw_digit ? s : nptr);
return neg ? (0ull - n) : n;
}
long long strtoll(const char *nptr, char **endptr, int base) {
const char *s = nptr;
while (isspace(*s)) s++;
int neg = (*s == '-');
if (*s == '+' || *s == '-') s++;
char *ep = 0;
unsigned long long n = strtoull(s, &ep, base);
if (ep == s) {
if (endptr) *endptr = (char *)nptr;
return 0;
}
if (endptr) *endptr = ep;
return neg ? (long long)(0ull - n) : (long long)n;
}
long long atoll(const char *s) {
return strtoll(s, 0, 10);
}
// ---- Float parsing (atof / strtod / strtof) -------------------------
extern double pow(double, double); // for 10^N scaling
// strtod parses [whitespace][+|-]<digits>[.digits][e|E[+|-]<digits>].
// Pure-double accumulation; precision degrades for very long inputs but
// sufficient for typical config / data parsing.
double strtod(const char *nptr, char **endptr) {
const char *s = nptr;
while (isspace(*s)) s++;
int neg = 0;
if (*s == '-') { neg = 1; s++; }
else if (*s == '+') s++;
double whole = 0.0;
int saw_digit = 0;
while (*s >= '0' && *s <= '9') {
whole = whole * 10.0 + (double)(*s - '0');
s++;
saw_digit = 1;
}
double frac = 0.0;
double scale = 1.0;
if (*s == '.') {
s++;
while (*s >= '0' && *s <= '9') {
frac = frac * 10.0 + (double)(*s - '0');
scale *= 10.0;
s++;
saw_digit = 1;
}
}
if (!saw_digit) {
if (endptr) *endptr = (char *)nptr;
return 0.0;
}
double v = whole + frac / scale;
if (*s == 'e' || *s == 'E') {
const char *eMark = s;
s++;
int eNeg = 0;
if (*s == '-') { eNeg = 1; s++; }
else if (*s == '+') s++;
if (*s < '0' || *s > '9') {
// No exponent digits — leave 'e' unconsumed.
s = eMark;
} else {
int e = 0;
while (*s >= '0' && *s <= '9') {
e = e * 10 + (*s - '0');
s++;
}
v *= pow(10.0, eNeg ? -e : e);
}
}
if (endptr) *endptr = (char *)s;
return neg ? -v : v;
}
float strtof(const char *nptr, char **endptr) {
return (float)strtod(nptr, endptr);
}
double atof(const char *s) {
return strtod(s, 0);
}

207
runtime/src/timeExt.c Normal file
View file

@ -0,0 +1,207 @@
// time.h additions (struct tm, gmtime, localtime, mktime, asctime,
// ctime, difftime, strftime). Split out from libc.c to keep that
// translation unit's frame size below the W65816 stack-relative range.
typedef long time_t;
typedef unsigned long clock_t;
typedef unsigned int size_t;
extern size_t strlen(const char *);
static const unsigned short __monthDays[12] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
static int __isLeap(int y) {
return (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0);
}
// ---- time.h: struct tm + calendar conversions -----------------------
struct tm {
int tm_sec, tm_min, tm_hour;
int tm_mday, tm_mon, tm_year;
int tm_wday, tm_yday, tm_isdst;
};
// Internal mutable buffer for gmtime/localtime/asctime/ctime.
// Not thread-safe but matches the C standard's spec.
static struct tm __gmtimeBuf;
static char __asctimeBuf[32];
double difftime(time_t end, time_t start) {
return (double)(end - start);
}
// gmtime / localtime: convert seconds-since-1970 to broken-down time.
// "local" is identical to "gm" — no timezone support.
// Convert days-since-1970 to (year, days-into-year). Uses 4-year
// cycles where possible to keep the loop short and to avoid clang
// generating code that misbehaves on this target.
// gmtime: KNOWN BROKEN at all -O levels. The year-decomposition loop
// (subtract years from `days` until what's left fits in one year)
// triggers a W65816 backend codegen issue — the loop doesn't iterate
// correctly under either -O2 (frame overflow) or -O1/-O0 (wrong
// values returned). For now, gmtime fills in fields with zeros and
// just stashes the input epoch in tm_sec/tm_min as low/mid 16-bit.
// asctime/strftime/mktime work correctly on a user-supplied struct
// tm. Workaround for callers that need decomposition: build the
// struct tm manually.
struct tm *gmtime(const time_t *t) {
long secs = *t;
__gmtimeBuf.tm_sec = (int)(secs & 0xFFFF);
__gmtimeBuf.tm_min = (int)((secs >> 16) & 0xFFFF);
__gmtimeBuf.tm_hour = 0;
__gmtimeBuf.tm_mday = 1;
__gmtimeBuf.tm_mon = 0;
__gmtimeBuf.tm_year = 70; // 1970, sentinel "not decomposed"
__gmtimeBuf.tm_wday = 4; // Jan 1 1970 was Thursday
__gmtimeBuf.tm_yday = 0;
__gmtimeBuf.tm_isdst = -1;
return &__gmtimeBuf;
}
struct tm *localtime(const time_t *t) {
return gmtime(t);
}
// mktime: convert broken-down time → seconds-since-1970. Also fills
// in tm_wday and tm_yday if the caller didn't bother.
time_t mktime(struct tm *tm) {
int year = tm->tm_year + 1900;
int mon = tm->tm_mon;
long days = 0;
for (int y = 1970; y < year; y++) {
days += __isLeap(y) ? 366 : 365;
}
for (int m = 0; m < mon; m++) {
days += __monthDays[m + 1] - __monthDays[m];
// (the table starts at 0 for Jan, so each month is the diff)
}
if (mon > 1 && __isLeap(year)) days++;
days += tm->tm_mday - 1;
long secs = days * 86400L
+ (long)tm->tm_hour * 3600L
+ (long)tm->tm_min * 60L
+ tm->tm_sec;
tm->tm_wday = (int)((days + 4) % 7);
if (tm->tm_wday < 0) tm->tm_wday += 7;
tm->tm_yday = (int)(__monthDays[mon]
+ ((mon > 1 && __isLeap(year)) ? 1 : 0)
+ tm->tm_mday - 1);
return secs;
}
static const char *const __wdayShort[7] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char *const __wdayLong[7] = {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
};
static const char *const __monShort[12] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char *const __monLong[12] = {
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"
};
// Format N-digit zero-padded. Forces u32 (long) arithmetic — clang's
// strength reducer otherwise lowers /10 and %10 on small types into
// i8 mulhu by 0xCD (magic constant for div-by-10), which the W65816
// backend has no select pattern for.
static char *fmtN(char *p, unsigned long v, int n) {
p += n;
char *end = p;
while (n--) {
unsigned long q = v / 10ul;
unsigned long r = v - q * 10ul;
p--;
*p = (char)('0' + (int)r);
v = q;
}
return end;
}
static char *fmt02(char *p, int v) { return fmtN(p, (unsigned long)v, 2); }
static char *fmt04(char *p, int v) { return fmtN(p, (unsigned long)v, 4); }
char *asctime(const struct tm *tm) {
// "Sun Jan 1 00:00:00 1970\n\0" = 26 bytes
char *p = __asctimeBuf;
int wday = tm->tm_wday & 7; if (wday > 6) wday = 0;
int mon = tm->tm_mon & 15; if (mon > 11) mon = 0;
*p++ = __wdayShort[wday][0];
*p++ = __wdayShort[wday][1];
*p++ = __wdayShort[wday][2];
*p++ = ' ';
*p++ = __monShort[mon][0];
*p++ = __monShort[mon][1];
*p++ = __monShort[mon][2];
*p++ = ' ';
int mday = tm->tm_mday;
*p++ = (mday >= 10) ? ('0' + mday/10) : ' ';
*p++ = '0' + mday % 10;
*p++ = ' ';
p = fmt02(p, tm->tm_hour); *p++ = ':';
p = fmt02(p, tm->tm_min); *p++ = ':';
p = fmt02(p, tm->tm_sec); *p++ = ' ';
p = fmt04(p, tm->tm_year + 1900);
*p++ = '\n';
*p = 0;
return __asctimeBuf;
}
char *ctime(const time_t *t) {
return asctime(gmtime(t));
}
// strftime — directive expansion is split into a helper so the main
// loop's frame stays small (W65816 stack-relative offsets are 8-bit
// signed).
__attribute__((noinline))
static int strftimeOne(char dst[8], char spec, const struct tm *tm,
const char **strOut) {
*strOut = 0;
switch (spec) {
case 'Y': fmt04(dst, tm->tm_year + 1900); return 4;
case 'm': fmt02(dst, tm->tm_mon + 1); return 2;
case 'd': fmt02(dst, tm->tm_mday); return 2;
case 'H': fmt02(dst, tm->tm_hour); return 2;
case 'M': fmt02(dst, tm->tm_min); return 2;
case 'S': fmt02(dst, tm->tm_sec); return 2;
case 'j': fmtN(dst, (unsigned long)(tm->tm_yday + 1), 3); return 3;
case 'w': dst[0] = (char)('0' + (tm->tm_wday & 7)); return 1;
case 'a': *strOut = __wdayShort[tm->tm_wday & 7]; return 3;
case 'A': *strOut = __wdayLong [tm->tm_wday & 7];
return (int)strlen(*strOut);
case 'b':
case 'h': *strOut = __monShort[tm->tm_mon & 15]; return 3;
case 'B': *strOut = __monLong [tm->tm_mon & 15];
return (int)strlen(*strOut);
case 'p': *strOut = (tm->tm_hour < 12) ? "AM" : "PM"; return 2;
case '%': dst[0] = '%'; return 1;
default: dst[0] = '%'; dst[1] = spec; return 2;
}
}
size_t strftime(char *buf, size_t n, const char *fmt, const struct tm *tm) {
char *p = buf;
char *end = buf + n;
char tmp[8];
while (*fmt && p + 1 < end) {
if (*fmt != '%') { *p++ = *fmt++; continue; }
fmt++;
if (!*fmt) break;
const char *ins;
int len = strftimeOne(tmp, *fmt, tm, &ins);
const char *src = ins ? ins : tmp;
for (int i = 0; i < len && p + 1 < end; i++) *p++ = src[i];
fmt++;
}
if (p < end) *p = 0;
else if (n > 0) buf[n - 1] = 0;
return (size_t)(p - buf);
}

View file

@ -36,8 +36,12 @@ WORK=$(mktemp -d -t finderlaunch.XXXXXX)
trap 'rm -rf "$WORK"' EXIT trap 'rm -rf "$WORK"' EXIT
cp "$SYSDISK" "$WORK/disk.po" cp "$SYSDISK" "$WORK/disk.po"
cp "$OMF" "$WORK/HELLO#B30000" # Create a separate 800K data disk and put HELLO on it. Keeps the
"$CADIUS" ADDFILE "$WORK/disk.po" /SYSTEM.DISK "$WORK/HELLO#B30000" >/dev/null # boot disk untouched (and avoids the "20K free" limit on sys602.po
# that fails for OMFs > ~15K).
"$CADIUS" CREATEVOLUME "$WORK/data.po" DATA 800KB >/dev/null
cp "$OMF" "$WORK/HELLO#B30000"
"$CADIUS" ADDFILE "$WORK/data.po" /DATA "$WORK/HELLO#B30000" >/dev/null
LUA_CHECKS="" LUA_CHECKS=""
EXPECTS=() EXPECTS=()
@ -65,9 +69,9 @@ local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
local function press(f) if f then f:set_value(1) end end local function press(f) if f then f:set_value(1) end end
local function release(f) if f then f:set_value(0) end end local function release(f) if f then f:set_value(0) end end
-- Keystroke timeline: open System.Disk, then launch HELLO. -- Keystroke timeline: open DATA volume (the second disk), then launch HELLO.
local steps = { local steps = {
{3300, function() nat:post("S") end}, -- select System.Disk {3300, function() nat:post("D") end}, -- select Data
{3540, function() press(key_cmd) end}, {3540, function() press(key_cmd) end},
{3546, function() nat:post("o") end}, -- Cmd-O opens volume {3546, function() nat:post("o") end}, -- Cmd-O opens volume
{3600, function() release(key_cmd) end}, {3600, function() release(key_cmd) end},
@ -75,7 +79,7 @@ local steps = {
{4500, function() press(key_cmd) end}, {4500, function() press(key_cmd) end},
{4506, function() nat:post("o") end}, -- Cmd-O launches {4506, function() nat:post("o") end}, -- Cmd-O launches
{4560, function() release(key_cmd) end}, {4560, function() release(key_cmd) end},
{5400, function() {6000, function()
$LUA_CHECKS $LUA_CHECKS
manager.machine:exit() manager.machine:exit()
end}, end},
@ -89,9 +93,9 @@ emu.register_frame_done(function()
end) end)
LUA LUA
OUT=$(timeout 130 mame apple2gs -rompath "$PROJECT_ROOT/tools/mame/roms" \ OUT=$(timeout 150 mame apple2gs -rompath "$PROJECT_ROOT/tools/mame/roms" \
-window -nothrottle -sound none \ -window -nothrottle -sound none \
-seconds_to_run 110 -flop3 "$WORK/disk.po" \ -seconds_to_run 130 -flop3 "$WORK/disk.po" -flop4 "$WORK/data.po" \
-autoboot_script "$WORK/finder.lua" </dev/null 2>&1) -autoboot_script "$WORK/finder.lua" </dev/null 2>&1)
# Verify each expected value. # Verify each expected value.

View file

@ -4610,8 +4610,10 @@ EOF
oGsCrt0="$(mktemp --suffix=.o)" oGsCrt0="$(mktemp --suffix=.o)"
"$PROJECT_ROOT/tools/llvm-mos-build/bin/llvm-mc" -arch=w65816 -filetype=obj \ "$PROJECT_ROOT/tools/llvm-mos-build/bin/llvm-mc" -arch=w65816 -filetype=obj \
"$PROJECT_ROOT/runtime/src/crt0.s" -o "$oGsCrt0" "$PROJECT_ROOT/runtime/src/crt0.s" -o "$oGsCrt0"
# extras.o supplies strnlen (used by libc.c's strdup wrapper).
if ! "$PROJECT_ROOT/tools/link816" -o "$binGs" --text-base 0x1000 \ if ! "$PROJECT_ROOT/tools/link816" -o "$binGs" --text-base 0x1000 \
"$oGsCrt0" "$oGsLibc" "$oGsSnp" "$oGsSf" "$oGsSd" \ "$oGsCrt0" "$oGsLibc" "$oGsSnp" "$oGsSf" "$oGsSd" \
"$PROJECT_ROOT/runtime/extras.o" \
"$oGsFile" "$oGsAsm" "$oLibgccFile" \ "$oGsFile" "$oGsAsm" "$oLibgccFile" \
--no-gc-sections 2>&1; then --no-gc-sections 2>&1; then
die "iigs/gsos.h + iigsGsos.s failed to link" die "iigs/gsos.h + iigsGsos.s failed to link"
@ -5148,7 +5150,8 @@ while post < bytecnt:
op = b[post] op = b[post]
if op == 0xF5: if op == 0xF5:
# 1 + 1 (ByteCnt) + 1 (BitShift) + 2 (OffsetPatch) + 2 (OffsetReference) = 7 bytes # 1 + 1 (ByteCnt) + 1 (BitShift) + 2 (OffsetPatch) + 2 (OffsetReference) = 7 bytes
if b[post+1] != 3: print(f'FAIL: cRELOC ByteCnt {b[post+1]} != 3'); sys.exit(1) # ByteCnt 2 = IMM16 (data refs), 3 = IMM24 (JSL/JML/STAlong).
if b[post+1] not in (2, 3): print(f'FAIL: cRELOC ByteCnt {b[post+1]} not in {{2,3}}'); sys.exit(1)
nCreloc += 1 nCreloc += 1
post += 7 post += 7
elif op == 0x00: elif op == 0x00:

View file

@ -313,14 +313,15 @@ struct Layout {
std::vector<TextSeg> segments; std::vector<TextSeg> segments;
}; };
// One IMM24 (3-byte absolute) relocation site, recorded for OMF // One IMM-N relocation site (N=2 or 3), recorded for OMF cRELOC
// cRELOC emission. The Loader will rewrite the 3 bytes at `patchOff` // emission. The Loader rewrites the N bytes at `patchOff` to be
// to be (segPlacedBase + offsetRef) when the segment is placed at // (segPlacedBase + offsetRef) when the segment is placed at runtime
// runtime — this is what makes our compiled C runnable from Finder // — this fixes both 24-bit JSL/JML/STAlong AND 16-bit absolute data
// when the segment lands at e.g. bank $1F instead of bank 0. // references when the segment doesn't land at link-time-base.
struct Imm24Site { struct Imm24Site {
uint32_t patchOff; // offset within text image (== patchAddr - textBase) uint32_t patchOff; // offset within text image (== patchAddr - textBase)
uint32_t offsetRef; // offset within text image of target symbol uint32_t offsetRef; // offset within text image of target symbol
uint8_t byteCnt; // 2 = IMM16, 3 = IMM24
}; };
static std::vector<Imm24Site> gImm24Sites; static std::vector<Imm24Site> gImm24Sites;
static uint32_t gTextBaseForSites = 0; static uint32_t gTextBaseForSites = 0;
@ -346,6 +347,25 @@ static void applyReloc(std::vector<uint8_t> &buf, uint32_t off,
// caller's DBR points at the target's bank. // caller's DBR points at the target's bank.
buf[off] = static_cast<uint8_t>(target & 0xFF); buf[off] = static_cast<uint8_t>(target & 0xFF);
buf[off + 1] = static_cast<uint8_t>((target >> 8) & 0xFF); buf[off + 1] = static_cast<uint8_t>((target >> 8) & 0xFF);
// Record for cRELOC emission so the Loader patches this
// 2-byte operand to (segPlacedBase + offsetRef) at load
// time. Without this, `lda absConst` reads from the wrong
// address when the segment doesn't land at link-time-base
// (e.g., link-time-base=0x1000 but Loader places at bank:0).
if (gRecordSites) {
uint32_t targetBank = target & 0xFF0000;
uint32_t baseBank = gTextBaseForSites & 0xFF0000;
if (targetBank == baseBank) {
Imm24Site s;
s.patchOff = patchAddr - gTextBaseForSites;
s.offsetRef = target - gTextBaseForSites;
// Use type field width = 2 to distinguish from IMM24
// (3). Imm24Site struct is reused — emitOmf will
// emit cRELOC ByteCnt=2 for this.
s.byteCnt = 2;
gImm24Sites.push_back(s);
}
}
break; break;
case R_W65816_IMM24: case R_W65816_IMM24:
if (target > 0xFFFFFF) if (target > 0xFFFFFF)
@ -368,6 +388,7 @@ static void applyReloc(std::vector<uint8_t> &buf, uint32_t off,
Imm24Site s; Imm24Site s;
s.patchOff = patchAddr - gTextBaseForSites; s.patchOff = patchAddr - gTextBaseForSites;
s.offsetRef = target - gTextBaseForSites; s.offsetRef = target - gTextBaseForSites;
s.byteCnt = 3;
gImm24Sites.push_back(s); gImm24Sites.push_back(s);
} }
} }
@ -1361,20 +1382,23 @@ int main(int argc, char **argv) {
if (!mapPath.empty()) linker.writeMap(mapPath); if (!mapPath.empty()) linker.writeMap(mapPath);
if (!debugOutPath.empty()) linker.writeDebugSidecar(debugOutPath); if (!debugOutPath.empty()) linker.writeDebugSidecar(debugOutPath);
if (!relocOutPath.empty()) { if (!relocOutPath.empty()) {
// Sidecar binary format: // Sidecar binary format (v2):
// u32 count // u32 count
// { u32 patchOff; u32 offsetRef; } × count // { u32 patchOff; u32 offsetRef; u8 byteCnt; u8 pad[3]; } × count
// Both offsets are within the segment image (== link-time addr // Both offsets are within the segment image (== link-time addr
// minus textBase). Consumed by omfEmit --relocs to emit cRELOC // minus textBase). byteCnt = 2 for IMM16, 3 for IMM24.
// opcodes after the LCONST data. // Consumed by omfEmit --relocs to emit cRELOC opcodes.
std::ofstream rf(relocOutPath, std::ios::binary); std::ofstream rf(relocOutPath, std::ios::binary);
if (!rf) die("cannot open '" + relocOutPath + "' for writing"); if (!rf) die("cannot open '" + relocOutPath + "' for writing");
uint32_t count = (uint32_t)gImm24Sites.size(); uint32_t count = (uint32_t)gImm24Sites.size();
rf.write(reinterpret_cast<const char *>(&count), 4); rf.write(reinterpret_cast<const char *>(&count), 4);
for (const auto &s : gImm24Sites) { for (const auto &s : gImm24Sites) {
uint32_t po = s.patchOff, off = s.offsetRef; uint32_t po = s.patchOff, off = s.offsetRef;
uint8_t bc = s.byteCnt, pad[3] = {0, 0, 0};
rf.write(reinterpret_cast<const char *>(&po), 4); rf.write(reinterpret_cast<const char *>(&po), 4);
rf.write(reinterpret_cast<const char *>(&off), 4); rf.write(reinterpret_cast<const char *>(&off), 4);
rf.write(reinterpret_cast<const char *>(&bc), 1);
rf.write(reinterpret_cast<const char *>(pad), 3);
} }
} }
// Multi-segment: write per-segment images + manifest if there's // Multi-segment: write per-segment images + manifest if there's

View file

@ -38,11 +38,17 @@ namespace {
} }
// Populated by --relocs from a link816 sidecar. Each entry is // Populated by --relocs from a link816 sidecar. Each entry is
// (OffsetPatch, OffsetReference) — the in-segment offset to patch // (OffsetPatch, OffsetReference, ByteCnt) — the in-segment offset
// (3 bytes wide) and the in-segment offset of the target. Consumed // to patch, the in-segment offset of the target, and the byte width
// by emitOneSeg to write cRELOC opcodes between LCONST and END. // of the patch (2 for IMM16, 3 for IMM24). Consumed by emitOneSeg
// to write cRELOC opcodes between LCONST and END.
struct RelocSite {
uint16_t patchOff;
uint16_t offsetRef;
uint8_t byteCnt;
};
} // close namespace } // close namespace
std::vector<std::pair<uint16_t, uint16_t>> gReloc24Sites; std::vector<RelocSite> gReloc24Sites;
namespace { namespace {
static std::vector<uint8_t> readFile(const std::string &path) { static std::vector<uint8_t> readFile(const std::string &path) {
@ -128,10 +134,10 @@ static std::vector<uint8_t> emitOneSeg(const std::vector<uint8_t> &image,
// the Loader places us at non-zero bank. // the Loader places us at non-zero bank.
for (const auto &s : ::gReloc24Sites) { for (const auto &s : ::gReloc24Sites) {
body.push_back(0xF5); body.push_back(0xF5);
body.push_back(3); // ByteCnt body.push_back(s.byteCnt); // ByteCnt (2 or 3)
body.push_back(0); // BitShift body.push_back(0); // BitShift
put16(body, s.first); // OffsetPatch put16(body, s.patchOff); // OffsetPatch
put16(body, s.second); // OffsetReference put16(body, s.offsetRef); // OffsetReference
} }
body.push_back(0x00); // END opcode body.push_back(0x00); // END opcode
@ -550,26 +556,36 @@ int main(int argc, char **argv) {
} }
if (output.empty()) usage(argv[0]); if (output.empty()) usage(argv[0]);
// Load IMM24 reloc list, if provided. // Load reloc list, if provided.
// Sidecar v2 layout: u32 count + 12 bytes per entry
// { u32 patchOff; u32 offsetRef; u8 byteCnt; u8 pad[3]; }
if (!relocFile.empty()) { if (!relocFile.empty()) {
auto raw = readFile(relocFile); auto raw = readFile(relocFile);
if (raw.size() < 4) die("--relocs file too small"); if (raw.size() < 4) die("--relocs file too small");
uint32_t cnt = (uint32_t)raw[0] | ((uint32_t)raw[1] << 8) uint32_t cnt = (uint32_t)raw[0] | ((uint32_t)raw[1] << 8)
| ((uint32_t)raw[2] << 16) | ((uint32_t)raw[3] << 24); | ((uint32_t)raw[2] << 16) | ((uint32_t)raw[3] << 24);
if (raw.size() != 4 + 8 * cnt) const size_t entrySize = 12;
if (raw.size() != 4 + entrySize * cnt)
die("--relocs file size mismatch: count=" + std::to_string(cnt) die("--relocs file size mismatch: count=" + std::to_string(cnt)
+ " expected " + std::to_string(4 + 8*cnt) + " bytes, got " + " expected " + std::to_string(4 + entrySize*cnt) + " bytes, got "
+ std::to_string(raw.size())); + std::to_string(raw.size()));
gReloc24Sites.reserve(cnt); gReloc24Sites.reserve(cnt);
for (uint32_t k = 0; k < cnt; k++) { for (uint32_t k = 0; k < cnt; k++) {
size_t off = 4 + k * 8; size_t off = 4 + k * entrySize;
uint32_t patchOff = (uint32_t)raw[off] | ((uint32_t)raw[off+1] << 8) uint32_t patchOff = (uint32_t)raw[off] | ((uint32_t)raw[off+1] << 8)
| ((uint32_t)raw[off+2] << 16) | ((uint32_t)raw[off+3] << 24); | ((uint32_t)raw[off+2] << 16) | ((uint32_t)raw[off+3] << 24);
uint32_t offRef = (uint32_t)raw[off+4] | ((uint32_t)raw[off+5] << 8) uint32_t offRef = (uint32_t)raw[off+4] | ((uint32_t)raw[off+5] << 8)
| ((uint32_t)raw[off+6] << 16) | ((uint32_t)raw[off+7] << 24); | ((uint32_t)raw[off+6] << 16) | ((uint32_t)raw[off+7] << 24);
uint8_t byteCnt = raw[off+8];
if (patchOff > 0xFFFF || offRef > 0xFFFF) if (patchOff > 0xFFFF || offRef > 0xFFFF)
die("reloc site out of 16-bit range — segment too large?"); die("reloc site out of 16-bit range — segment too large?");
gReloc24Sites.emplace_back((uint16_t)patchOff, (uint16_t)offRef); if (byteCnt != 2 && byteCnt != 3)
die("reloc site byteCnt=" + std::to_string(byteCnt) + " (must be 2 or 3)");
RelocSite s;
s.patchOff = (uint16_t)patchOff;
s.offsetRef = (uint16_t)offRef;
s.byteCnt = byteCnt;
gReloc24Sites.push_back(s);
} }
} }