65816-llvm-mos/runtime/src/extras.c
Scott Duensing 3388f3c5a5 More updates
2026-06-03 20:46:31 -05:00

666 lines
18 KiB
C

// Tiny string.h / stdlib.h helpers — kept out of libc.c because
// adding to that translation unit shifts vprintf's internal branch
// distances and randomly breaks BranchExpand (same precedent as
// strtol.c and snprintf.c).
//
// Functions:
// string.h: strcat, strncat
// stdlib.h: atol, llabs
typedef unsigned long size_t;
char *strcat(char *dst, const char *src) {
char *d = dst;
while (*d) {
d++;
}
while ((*d = *src)) {
d++;
src++;
}
return dst;
}
char *strncat(char *dst, const char *src, size_t n) {
char *d = dst;
while (*d) {
d++;
}
while (n && *src) {
*d = *src;
d++;
src++;
n--;
}
*d = 0;
return dst;
}
extern int isspace(int);
long atol(const char *s) {
while (isspace(*s)) {
s++;
}
int sign = 1;
if (*s == '-') {
sign = -1;
s++;
} else if (*s == '+') {
s++;
}
// Parse magnitude as unsigned to avoid signed-overflow UB (e.g.
// "-2147483648" — the magnitude 2147483648 doesn't fit in long).
unsigned long u = 0;
while (*s >= '0' && *s <= '9') {
u = u * 10 + (unsigned long)(*s - '0');
s++;
}
return sign < 0 ? (long)(0ul - u) : (long)u;
}
long long llabs(long long n) {
return n < 0 ? -n : n;
}
// strnlen: like strlen but capped at maxlen. Useful for safely
// measuring strings that may not be NUL-terminated within a known
// buffer size.
size_t strnlen(const char *s, size_t maxlen) {
size_t n = 0;
while (n < maxlen && s[n]) {
n++;
}
return n;
}
static int toLowerByte(int c) {
if (c >= 'A' && c <= 'Z') {
return c - 'A' + 'a';
}
return c;
}
int strcasecmp(const char *a, const char *b) {
while (*a && *b) {
int da = toLowerByte((unsigned char)*a);
int db = toLowerByte((unsigned char)*b);
if (da != db) {
return da - db;
}
a++;
b++;
}
return toLowerByte((unsigned char)*a) - toLowerByte((unsigned char)*b);
}
int strncasecmp(const char *a, const char *b, size_t n) {
while (n && *a && *b) {
int da = toLowerByte((unsigned char)*a);
int db = toLowerByte((unsigned char)*b);
if (da != db) {
return da - db;
}
a++;
b++;
n--;
}
if (!n) return 0;
return toLowerByte((unsigned char)*a) - toLowerByte((unsigned char)*b);
}
// Linear congruential generator — Numerical Recipes constants.
// Returns 15-bit values (RAND_MAX = 0x7FFF) per C standard convention.
static unsigned long randSeed = 1;
void srand(unsigned int seed) {
randSeed = seed;
}
int rand(void) {
randSeed = randSeed * 1103515245UL + 12345UL;
return (int)((randSeed >> 16) & 0x7FFF);
}
// crt0 hook: seed rand() from the IIgs RTC via ReadTimeHex (Misc
// Tool $0D03). Called from crt0Gsos.s / crt0Gno.s after .init_array
// has run. The Tool Locator is already up at that point (the GS/OS
// Loader brings it up before transferring control to __start; GNO's
// kernel does likewise), so JSL $E10000 X=$0D03 is safe.
//
// Without this hook randSeed stays at 1 and every run produces an
// identical PRNG sequence -- a silent correctness bug for callers
// like mkstemp that rely on rand() for uniqueness across invocations.
//
// Mixing strategy: fold the 8 TimeRec bytes into the seed via a
// simple u16 rotate-XOR, then place into the high half of randSeed
// (the LCG output is `(seed >> 16) & 0x7FFF`, so the first rand()
// directly reflects the seed bits we just installed). u16 arithmetic
// keeps the helper small -- ~150 B vs ~860 B for the u32 form.
extern void iigsReadTimeHex(unsigned char *buf8);
void __srandInitFromTime(void) {
unsigned char b[8];
iigsReadTimeHex(b);
unsigned short s = 0;
for (int i = 0; i < 8; i++) {
s = (unsigned short)((s << 3) | (s >> 13));
s = (unsigned short)(s ^ (unsigned short)b[i]);
}
// Force non-zero (LCG with seed 0 still cycles, but at least one
// bit set keeps the early outputs out of the trivial-prefix range).
if (!s) {
s = 1;
}
// Place the time-derived bits in the high half so the first
// rand() output -- ((seed * K + C) >> 16) & 0x7FFF -- carries
// them. Low half stays 0; the LCG mixes it into the next call.
randSeed = ((unsigned long)s) << 16;
}
// ----- sys/time.h gettimeofday() ---------------------------------------
//
// Thin shim over libc.c's time() — same epoch-second source, packaged
// in the POSIX struct timeval shape. tv_usec is always 0 because the
// IIgs has no sub-second wall clock (the VBL counter at $E1:006B is
// monotonic but not aligned to wall-clock seconds). The tz argument
// is accepted for source compat and ignored; the IIgs has no
// timezone database.
//
// Declared in <sys/time.h>; the struct timeval layout matches that
// header byte-for-byte (time_t, then long).
extern long time(long *t); // matches signature in <time.h>
struct __ggGtodTimeval {
long tv_sec;
long tv_usec;
};
int gettimeofday(struct __ggGtodTimeval *tv, void *tz) {
(void)tz;
if (!tv) {
return 0;
}
long s = time((long *)0);
if (s == 0) {
// time() returns 0 either at Unix epoch midnight (impossible on
// a real IIgs RTC) or when the Tool Locator isn't up. Treat as
// failure -- matches the POSIX convention.
tv->tv_sec = 0;
tv->tv_usec = 0;
return -1;
}
tv->tv_sec = s;
tv->tv_usec = 0;
return 0;
}
// ----- additional string.h ----------------------------------------------
static int inSet(char c, const char *set) {
while (*set) {
if (c == *set) {
return 1;
}
set++;
}
return 0;
}
char *strpbrk(const char *s, const char *accept) {
while (*s) {
if (inSet(*s, accept)) {
return (char *)s;
}
s++;
}
return 0;
}
size_t strspn(const char *s, const char *accept) {
size_t n = 0;
while (s[n] && inSet(s[n], accept)) {
n++;
}
return n;
}
size_t strcspn(const char *s, const char *reject) {
size_t n = 0;
while (s[n] && !inSet(s[n], reject)) {
n++;
}
return n;
}
// strtok / strtok_r are in runtime/src/strtok.c.
// ---- wchar.h ----
// wchar_t is 16-bit on this target. The wcs* functions mirror the
// str* family. mbtowc / wctomb use the trivial 1:1 byte<->wide-char
// mapping (essentially Latin-1) — no real multi-byte / locale support.
// Now `int` to match the clang builtin signature for wcslen/wcscmp/
// wcscpy etc; was `unsigned short`. Latin-1 content (0..255) is
// representable in both.
typedef int wchar_t;
size_t wcslen(const wchar_t *s) {
size_t n = 0;
while (*s++) n++;
return n;
}
int wcscmp(const wchar_t *a, const wchar_t *b) {
while (*a && *a == *b) { a++; b++; }
return (int)((short)*a - (short)*b);
}
int wcsncmp(const wchar_t *a, const wchar_t *b, size_t n) {
while (n && *a && *a == *b) { a++; b++; n--; }
if (!n) return 0;
return (int)((short)*a - (short)*b);
}
wchar_t *wcscpy(wchar_t *dst, const wchar_t *src) {
wchar_t *d = dst;
while ((*d++ = *src++)) {}
return dst;
}
wchar_t *wcsncpy(wchar_t *dst, const wchar_t *src, size_t n) {
wchar_t *d = dst;
while (n && (*d = *src)) { d++; src++; n--; }
while (n--) *d++ = 0;
return dst;
}
wchar_t *wcscat(wchar_t *dst, const wchar_t *src) {
wchar_t *d = dst;
while (*d) d++;
while ((*d++ = *src++)) {}
return dst;
}
wchar_t *wcschr(const wchar_t *s, wchar_t c) {
while (*s) {
if (*s == c) return (wchar_t *)s;
s++;
}
return (c == 0) ? (wchar_t *)s : (wchar_t *)0;
}
wchar_t *wcsrchr(const wchar_t *s, wchar_t c) {
const wchar_t *last = (const wchar_t *)0;
while (*s) {
if (*s == c) last = s;
s++;
}
if (c == 0) return (wchar_t *)s;
return (wchar_t *)last;
}
int mbtowc(wchar_t *pwc, const char *s, size_t n) {
if (!s) return 0; // no shift state
if (n == 0) return -1;
unsigned char c = (unsigned char)*s;
if (pwc) *pwc = (wchar_t)c;
return c ? 1 : 0;
}
int wctomb(char *s, wchar_t wc) {
if (!s) return 0; // no shift state
if (wc > 0xFF) return -1;
*s = (char)wc;
return 1;
}
size_t mbstowcs(wchar_t *pwcs, const char *s, size_t n) {
size_t i = 0;
while (i < n && s[i]) {
if (pwcs) pwcs[i] = (wchar_t)(unsigned char)s[i];
i++;
}
if (pwcs && i < n) pwcs[i] = 0;
return i;
}
size_t wcstombs(char *s, const wchar_t *pwcs, size_t n) {
size_t i = 0;
while (i < n && pwcs[i]) {
if (pwcs[i] > 0xFF) return (size_t)-1;
if (s) s[i] = (char)pwcs[i];
i++;
}
if (s && i < n) s[i] = 0;
return i;
}
int mblen(const char *s, size_t n) {
if (!s) return 0;
if (n == 0) return -1;
return *s ? 1 : 0;
}
// ---- wide-char memory + scan/format ---------------------------------
// Operate on wchar_t arrays (wchar_t is `int` on this target = 2
// bytes). Under Latin-1 we delegate the actual work to the byte/str
// equivalents wherever the data fits in 8 bits.
#include <stdarg.h>
struct tm;
extern void *memcpy (void *dst, const void *src, size_t n);
extern void *memmove(void *dst, const void *src, size_t n);
extern long strtol (const char *nptr, char **endptr, int base);
extern unsigned long strtoul (const char *nptr, char **endptr, int base);
extern long long strtoll (const char *nptr, char **endptr, int base);
extern unsigned long long strtoull(const char *nptr, char **endptr, int base);
extern double strtod (const char *nptr, char **endptr);
extern float strtof (const char *nptr, char **endptr);
extern int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap);
extern size_t strftime (char *buf, size_t n, const char *fmt, const struct tm *tm);
wchar_t *wmemcpy(wchar_t *dst, const wchar_t *src, size_t n) {
memcpy(dst, src, n * sizeof(wchar_t));
return dst;
}
wchar_t *wmemmove(wchar_t *dst, const wchar_t *src, size_t n) {
memmove(dst, src, n * sizeof(wchar_t));
return dst;
}
wchar_t *wmemset(wchar_t *dst, wchar_t c, size_t n) {
wchar_t *p = dst;
while (n--) {
*p++ = c;
}
return dst;
}
int wmemcmp(const wchar_t *a, const wchar_t *b, size_t n) {
while (n--) {
if (*a != *b) {
return (int)(*a - *b);
}
a++;
b++;
}
return 0;
}
wchar_t *wmemchr(const wchar_t *s, wchar_t c, size_t n) {
while (n--) {
if (*s == c) {
return (wchar_t *)s;
}
s++;
}
return (wchar_t *)0;
}
// Helper: narrow a wide string of up to `lim` chars into a byte
// buffer. Stops at the first NUL or after `lim` chars. Returns
// the number of bytes written (excluding any trailing NUL).
static size_t __narrow(char *out, const wchar_t *in, size_t lim) {
size_t i = 0;
while (i < lim && in[i]) {
out[i] = (char)(in[i] & 0xFF);
i++;
}
if (i < lim) {
out[i] = 0;
}
return i;
}
long wcstol(const wchar_t *nptr, wchar_t **endptr, int base) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
long r = strtol(buf, &bend, base);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
unsigned long wcstoul(const wchar_t *nptr, wchar_t **endptr, int base) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
unsigned long r = strtoul(buf, &bend, base);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
long long wcstoll(const wchar_t *nptr, wchar_t **endptr, int base) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
long long r = strtoll(buf, &bend, base);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
unsigned long long wcstoull(const wchar_t *nptr, wchar_t **endptr, int base) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
unsigned long long r = strtoull(buf, &bend, base);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
double wcstod(const wchar_t *nptr, wchar_t **endptr) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
double r = strtod(buf, &bend);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
float wcstof(const wchar_t *nptr, wchar_t **endptr) {
char buf[40];
size_t k = __narrow(buf, nptr, sizeof(buf) - 1);
buf[k] = 0;
char *bend;
float r = strtof(buf, &bend);
if (endptr) {
*endptr = (wchar_t *)(nptr + (bend - buf));
}
return r;
}
// swprintf: narrow the format string, route through vsnprintf into a
// byte buffer, then widen the result back into `buf`. Limits the
// format-spec coverage to what vsnprintf supports; %ls / %lc are not
// honoured (caller must pass narrow-char args). Returns -1 on
// overflow per C11.
//
// Buffers kept small (64 bytes each) so the total frame stays under
// the W65816's 256-byte stack-rel addressing limit. Long format
// strings and long outputs are truncated.
int vswprintf(wchar_t *buf, size_t n, const wchar_t *fmt, va_list ap) {
if (n == 0) {
return -1;
}
char fmtBuf[64];
__narrow(fmtBuf, fmt, sizeof(fmtBuf) - 1);
fmtBuf[sizeof(fmtBuf) - 1] = 0;
char outBuf[64];
size_t cap = n - 1 < sizeof(outBuf) - 1 ? n - 1 : sizeof(outBuf) - 1;
int wrote = vsnprintf(outBuf, cap + 1, fmtBuf, ap);
if (wrote < 0 || (size_t)wrote >= n) {
buf[0] = 0;
return -1;
}
int i;
for (i = 0; i < wrote; i++) {
buf[i] = (wchar_t)(unsigned char)outBuf[i];
}
buf[wrote] = 0;
return wrote;
}
int swprintf(wchar_t *buf, size_t n, const wchar_t *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int r = vswprintf(buf, n, fmt, ap);
va_end(ap);
return r;
}
size_t wcsftime(wchar_t *buf, size_t n, const wchar_t *fmt, const struct tm *tm) {
if (n == 0) {
return 0;
}
char fmtBuf[64];
__narrow(fmtBuf, fmt, sizeof(fmtBuf) - 1);
fmtBuf[sizeof(fmtBuf) - 1] = 0;
char outBuf[128];
size_t cap = n - 1 < sizeof(outBuf) - 1 ? n - 1 : sizeof(outBuf) - 1;
size_t wrote = strftime(outBuf, cap + 1, fmtBuf, tm);
if (wrote == 0 || wrote >= n) {
buf[0] = 0;
return 0;
}
size_t i;
for (i = 0; i < wrote; i++) {
buf[i] = (wchar_t)(unsigned char)outBuf[i];
}
buf[wrote] = 0;
return wrote;
}
// ---- fenv.h ----------------------------------------------------------
//
// softFloat / softDouble are fixed at round-to-nearest-even and don't
// raise IEEE exceptions. We track the requested rounding mode and an
// exception-flag word but neither affects soft-float output.
static int __fenvRound = 0; /* FE_TONEAREST */
static unsigned short __fenvExcept = 0;
int feclearexcept(int excepts) { __fenvExcept &= (unsigned short)~excepts; return 0; }
int feraiseexcept(int excepts) { __fenvExcept |= (unsigned short)excepts; return 0; }
int fetestexcept(int excepts) { return __fenvExcept & excepts; }
int fegetexceptflag(unsigned short *flagp, int e) { (void)e; if (flagp) *flagp = __fenvExcept; return 0; }
int fesetexceptflag(const unsigned short *flagp, int e) {
if (!flagp) return -1;
__fenvExcept = (unsigned short)((__fenvExcept & ~e) | (*flagp & e));
return 0;
}
int fegetround(void) { return __fenvRound; }
int fesetround(int r) { __fenvRound = r; return 0; }
int fegetenv(unsigned short *envp) { if (envp) *envp = __fenvExcept; return 0; }
int feholdexcept(unsigned short *envp) { if (envp) *envp = __fenvExcept; __fenvExcept = 0; return 0; }
int fesetenv(const unsigned short *envp) { __fenvExcept = envp ? *envp : 0; return 0; }
int feupdateenv(const unsigned short *envp) { unsigned short e = envp ? *envp : 0; __fenvExcept |= e; return 0; }
// ---- threads.h backing storage ---------------------------------------
//
// All thread / mutex / cond ops are inline no-ops; only tss_* needs
// real per-key storage. 8 keys is enough for any single-core code.
void *__tss_slots[8];
int __tss_next = 0;
// ---- aligned_alloc / posix_memalign ---------------------------------
//
// Wraps malloc with an over-allocation + alignment-adjust trick: alloc
// (n + alignment + sizeof(void*)) bytes; align upward; stash the
// original pointer just before the returned address for free() to find.
// `aligned_alloc` requires `n` to be a multiple of `alignment` (C11).
extern void *malloc(unsigned long n);
extern void free (void *p);
void *aligned_alloc(unsigned long alignment, unsigned long size) {
if (alignment == 0 || (alignment & (alignment - 1))) return (void *)0;
if (size % alignment) return (void *)0;
unsigned long over = size + alignment + sizeof(void *);
char *raw = (char *)malloc(over);
if (!raw) return (void *)0;
unsigned long addr = (unsigned long)raw + sizeof(void *);
unsigned long aligned = (addr + alignment - 1) & ~(alignment - 1);
((void **)aligned)[-1] = raw;
return (void *)aligned;
}
// Wrappers that read the stashed raw pointer and free the underlying
// block. Callers should use these (not plain free) for aligned_alloc'd
// pointers. Single-source projects can `#define free aligned_free` if
// needed; the standard C11 contract is that `free` works on aligned
// pointers, so we also patch free below.
void aligned_free(void *p) {
if (!p) return;
void *raw = ((void **)p)[-1];
free(raw);
}
int posix_memalign(void **memptr, unsigned long alignment, unsigned long size) {
if (!memptr) return 22; /* EINVAL */
if (alignment < sizeof(void *) || (alignment & (alignment - 1))) {
*memptr = (void *)0;
return 22;
}
void *p = aligned_alloc(alignment, (size + alignment - 1) & ~(alignment - 1));
if (!p) { *memptr = (void *)0; return 12; /* ENOMEM */ }
*memptr = p;
return 0;
}