// 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 long 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); } struct tm *gmtime_r(const time_t *t, struct tm *out); // gmtime / localtime: convert seconds-since-1970 to broken-down time. // "local" is identical to "gm" — no timezone support. // // Returns a pointer to a static global (`__gmtimeBuf`). Under GS/OS // Loader (DBR != 0) caller-side pointer-deref reads need to land in // the same bank where gmtime wrote; this requires the runtime build // to enable `-mllvm -w65816-loader-bank-deref`, which makes // LDAptr/STAptr load the bank byte from DP $BE (set by crt0 from // PHK / current PBR). Without the flag, gmtime still works under // MAME / non-Loader runs where DBR=0 throughout. struct tm *gmtime(const time_t *t) { return gmtime_r(t, &__gmtimeBuf); } struct tm *localtime(const time_t *t) { return gmtime(t); } // gmtime_r / localtime_r — POSIX reentrant variants. Take a caller- // supplied struct tm so the buffer lives on the user's stack (which // is bank-0 in 65816 native mode regardless of DBR). This avoids the // bank-mismatch issue that breaks plain gmtime under Loader. // // Full broken-down time computation. Marked optnone because at -O2 // LLVM's combined IR optimizations (loop rotation + reassociation + // induction-variable-simplify) mis-evaluate the year-increment loop's // `days >= 365L + (__isLeap(...) ? 1 : 0)` comparison, leaving the // loop body unexecuted and date fields stuck at the 1970 sentinel. // optnone preserves the per-statement structure and the loop runs // correctly. Verified end-to-end against 1710484245L → 2024-03-15 // 06:30:45 UTC (Friday, day-of-year 74). __attribute__((optnone)) struct tm *gmtime_r(const time_t *t, struct tm *out) { long secs = *t; int sec = (int)(secs % 60L); secs /= 60L; int min = (int)(secs % 60L); secs /= 60L; int hour = (int)(secs % 24L); secs /= 24L; long days = secs; int wday = (int)((days + 4L) % 7L); if (wday < 0) wday += 7; int year = 70; // years since 1900 while (days >= 365L + (__isLeap(1900 + year) ? 1 : 0)) { days -= 365L + (__isLeap(1900 + year) ? 1 : 0); year++; } int yday = (int)days; int leap = __isLeap(1900 + year); int mon = 11; while (mon > 0) { int firstDayOfMon = __monthDays[mon] + (leap && mon > 1 ? 1 : 0); if ((int)days >= firstDayOfMon) break; mon--; } int firstDay = __monthDays[mon] + (leap && mon > 1 ? 1 : 0); int mday = (int)days - firstDay + 1; out->tm_sec = sec; out->tm_min = min; out->tm_hour = hour; out->tm_mday = mday; out->tm_mon = mon; out->tm_year = year; out->tm_wday = wday; out->tm_yday = yday; out->tm_isdst = -1; return out; } struct tm *localtime_r(const time_t *t, struct tm *out) { return gmtime_r(t, out); } // 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); }