// 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 ; the struct timeval layout matches that // header byte-for-byte (time_t, then long). extern long time(long *t); // matches signature in 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 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; }