Prevously:
The Zig standard library is not time64 clean, but since most of the development/deployment is done on 64-bit targets it hasn't become an issue. For the standard library to be time64 clean, two things need to be done for each target:
- Use the most up-to-date types.
- Pass them to the right functions.
Sounds simple, but after looking into this I've come up with a list of issues:
- Some targets have no concept of "time" - think of the freestanding/real-time OSs where time-keeping functionality can be stripped out.
- Retrieving time differs between OSs: C/POSIX-compatibles use
time_t, timespec and clock_gettime whilst Windows and Plan 9 use completely different APIs.
- The POSIX-compatibles may have multiple versions of their types/APIs thanks to backwards-compatibility.
Since most of the issues stem from the Nixen, where time_t/timespec are set in the ABI, this issue is focused on them. Reformulating the tasks then:
- If the target requires using the C standard library and/or is POSIX-compatible:
- Use the largest
time_t/timespec possible for the cpu-os-abi combo.
- Pass these types to the proper time64-compatible APIs.
Since time_t, timespec and friends show up in many places (stating, sleeping, signaling etc.), any existing structures that use these types will need to be audited and modified if necessary.
The following sections describe how each Nixen the standard library supports handles 64-bit time.
- macOS/Darwin:
time_t -> __darwin_time_t -> long for x86 and arm; no time64 support for the ILP32 targets, which AFAIK is only an issue on the Apple Watch (it uses a ILP32 userspace).
- OpenBSD: Broke ABI to use 64-bit time in 2013. No special types/APIs needed.
- FreeBSD: Currently,
time_t is 32-bit on x86_32 and 64-bit on all other targets. FreeBSD 15 dropped support for 32-bit targets along with MIPS64 and big endian PowerPC, so this only matters for x86-freebsd in user-space.
- DragonFlyBSD:
time_t is 64-bit since x86_64 is the only supported arch.
- NetBSD: Migrated targets to use 64-bit time in NetBSD 6. The time64 symbols to use are of the form
__{function}50.
- Illumos: Solaris (and thus Illumos) never added time64 support for the 32-bit targets. None of this matters though since Illumos only supports x86_64.
- Linux: Oh boy...
Linux - Kernel
The kernel is generally considered time64 ready since 5.6. It achieves this by adding new types and syscalls (__kernel_time64_t, __kernel_timespec). The catch is that while older 32-bit targets have two versions of a syscall, newer ones like riscv32/loongarch32 only use the newer ones. The process then for std.os.linux is:
- Only define the modern types (e.g.
__kernel_timespec).
- Pass them to the proper syscalls per-target:
- If it's a 64-bit target, do nothing.
- If it's a 32-bit target with time32 and time64 syscalls, use the time64 ones.
- If it's a 32-bit target with only time64 syscalls, use the time64 ones.
- Implement older syscalls via newer ones if a target doesn't include them (
utimes -> utimensat).
Linux - Musl
Musl added complete time64 in Musl 1.2.0 without changing the ABI. It does this by using ASM Labels to alter symbol names, but only on 32-bit targets that support the time32 syscalls (grep for _REDIR_TIME64 in arch/{arch}/bits/alltypes.h.in). On these targets:
- The
time32 compat library under compat/time32 is compiled in.
- The symbols for the compat functions are redirected to their normal form; e.g.
__nanosleep_time32 -> nanosleep.
- Inversely, the symbols for the normal functions are redirected to have a
time64 suffix; e.g. nanosleep -> __nanosleep_time64.
This was done to maintain compatibility with programs dynamically linking to musl before the 1.2.0 release.
The solution is to perform the same redirection in std.c.
Linux - glibc
Glibc uses the same ASM label trick based on the macros _TIME_BITS, __TIMESIZE and __USE_TIME_BITS64:
_TIME_BITS overrides the time abi in use. As of 2.35 this is time64 by default.
__TIMESIZE is the bit-size of __kernel_old_time_t if the target needs it (i.e. old 32-bit ones).
__USE_TIME_BITS64 is set to 0 or 1 depending if _TIME_BITS==64 or __TIMESIZE==64.
- 32bit compat functions are compiled in using the normal name if
__TIMESIZE==32.
Unfortunately, musl and glibc don't use the same naming scheme for their symbols: glibc uses __nanosleep64 whilst musl uses __nanosleep_time64.
Prevously:
- [#4726][4726].
- [#21440](https://github.com/ziglang/zig/pull/21440) (symbol shenanigans for glibc and musl).
[4726]: https://github.com/ziglang/zig/issues/4726
---
The Zig standard library is not [time64 clean][4726], but since most of the development/deployment is done on 64-bit targets it hasn't become an issue. For the standard library to be time64 clean, two things need to be done for each target:
1. Use the most up-to-date types.
2. Pass them to the right functions.
Sounds simple, but after looking into this I've come up with a list of issues:
- Some targets have no concept of "time" - think of the freestanding/real-time OSs where time-keeping functionality can be stripped out.
- Retrieving time differs between OSs: C/POSIX-compatibles use `time_t`, `timespec` and `clock_gettime` whilst Windows and Plan 9 use completely different APIs.
- The POSIX-compatibles may have multiple versions of their types/APIs thanks to backwards-compatibility.
Since most of the issues stem from the Nixen, where `time_t`/`timespec` are set in the ABI, this issue is focused on them. Reformulating the tasks then:
- *If* the target requires using the C standard library and/or is POSIX-compatible:
1. Use the largest `time_t`/`timespec` possible for the cpu-os-abi combo.
2. Pass these types to the proper time64-compatible APIs.
Since `time_t`, `timespec` and friends show up in many places (`stat`ing, `sleep`ing, `signal`ing etc.), any existing structures that use these types will need to be audited and modified if necessary.
The following sections describe how each Nixen the standard library supports handles 64-bit time.
- **macOS/Darwin**: `time_t -> __darwin_time_t -> long` for x86 and arm; no time64 support for the ILP32 targets, which AFAIK is only an issue on the Apple Watch (it uses a ILP32 userspace).
- **OpenBSD**: [Broke ABI][openbsd] to use 64-bit time in 2013. No special types/APIs needed.
- **FreeBSD**: Currently, `time_t` is [32-bit on `x86_32`][freebsd] and 64-bit on all other targets. FreeBSD 15 dropped support for 32-bit targets along with MIPS64 and big endian PowerPC, so this only matters for `x86-freebsd` in user-space.
- **DragonFlyBSD**: `time_t` is [64-bit][dragonfly] since `x86_64` is the only supported arch.
- **NetBSD**: [Migrated targets][netbsd] to use 64-bit time in NetBSD 6. The time64 symbols to use are of the form `__{function}50`.
- **Illumos**: Solaris (and thus Illumos) [never added time64 support][solaris-blog] for the 32-bit targets. None of this matters though since Illumos only supports x86_64.
- **Linux**: Oh boy...
[openbsd]: https://undeadly.org/cgi?action=article;sid=20130813072244
[freebsd]: https://github.com/freebsd/freebsd-src/blob/178d0b5b8da7480f455273aedf40dd8f1e785d3f/sys/x86/include/_types.h#L70-L78
[dragonfly]: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/065436f551fb2a156f3f7a6b58f96b074662c835/sys/cpu/x86_64/include/stdint.h#L122
[netbsd]: https://wiki.netbsd.org/symbol_versions/
[solaris-blog]: https://blogs.oracle.com/solaris/moving-oracle-solaris-to-lp64-bit-by-bit#lp64-abi-changes
## Linux - Kernel
The kernel is [generally considered][musl-time64] time64 ready since 5.6. It achieves this by adding new types and syscalls (`__kernel_time64_t`, `__kernel_timespec`). The catch is that while older 32-bit targets have two versions of a syscall, newer ones like `riscv32`/`loongarch32` only use the newer ones. The process then for `std.os.linux` is:
[musl-time64]: https://musl.libc.org/time64.html
- Only define the modern types (e.g. `__kernel_timespec`).
- Pass them to the proper syscalls per-target:
- If it's a 64-bit target, do nothing.
- If it's a 32-bit target with time32 and time64 syscalls, use the time64 ones.
- If it's a 32-bit target with only time64 syscalls, use the time64 ones.
- Implement older syscalls via newer ones if a target doesn't include them (`utimes -> utimensat`).
## Linux - Musl
Musl added complete time64 in [Musl 1.2.0][musl-time64] without changing the ABI. It does this by using [ASM Labels][asm-label] to alter symbol names, but only on 32-bit targets that support the time32 syscalls (grep for `_REDIR_TIME64` in `arch/{arch}/bits/alltypes.h.in`). On these targets:
[asm-label]: https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html
- The `time32` compat library under `compat/time32` is compiled in.
- The symbols for the compat functions are redirected to their normal form; e.g. `__nanosleep_time32 -> nanosleep`.
- Inversely, the symbols for the normal functions are redirected to have a `time64` suffix; e.g. `nanosleep -> __nanosleep_time64`.
This was done to maintain compatibility with programs dynamically linking to musl before the 1.2.0 release.
The solution is to perform the same redirection in `std.c`.
## Linux - glibc
Glibc uses the same ASM label trick based on the macros `_TIME_BITS`, `__TIMESIZE` and `__USE_TIME_BITS64`:
- `_TIME_BITS` overrides the time abi in use. As of 2.35 this is time64 by default.
- `__TIMESIZE` is the bit-size of `__kernel_old_time_t` if the target needs it (i.e. old 32-bit ones).
- `__USE_TIME_BITS64` is set to `0` or `1` depending if `_TIME_BITS==64` or `__TIMESIZE==64`.
- 32bit compat functions are compiled in using the normal name if `__TIMESIZE==32`.
Unfortunately, musl and glibc don't use the same naming scheme for their symbols: glibc uses `__nanosleep64` whilst musl uses `__nanosleep_time64`.