runtime: use futexes with 64-bit time on Linux

Linux introduced new syscalls to fix the year 2038 issue.
To still be able to use the old ones, the Kconfig option
COMPAT_32BIT_TIME would be necessary.

Use the new syscall with 64-bit values for futexwakeup by default.
Define _ENOSYS for detecting if it's not available.
Add a fallback to use the older syscall in case the new one is
not available, since Go runs on Linux from 2.6.32 on, per
https://go.dev/wiki/MinimumRequirements.

Fixes #75133
This commit is contained in:
Daniel Maslowski 2025-09-07 00:12:28 +02:00
parent ef05b66d61
commit 96dd1bd84b
19 changed files with 186 additions and 22 deletions

View file

@ -48,6 +48,7 @@ const (
EINTR = C.EINTR EINTR = C.EINTR
EAGAIN = C.EAGAIN EAGAIN = C.EAGAIN
ENOMEM = C.ENOMEM ENOMEM = C.ENOMEM
ENOSYS = C.ENOSYS
PROT_NONE = C.PROT_NONE PROT_NONE = C.PROT_NONE
PROT_READ = C.PROT_READ PROT_READ = C.PROT_READ

View file

@ -37,6 +37,7 @@ const (
EINTR = C.EINTR EINTR = C.EINTR
EAGAIN = C.EAGAIN EAGAIN = C.EAGAIN
ENOMEM = C.ENOMEM ENOMEM = C.ENOMEM
ENOSYS = C.ENOSYS
PROT_NONE = C.PROT_NONE PROT_NONE = C.PROT_NONE
PROT_READ = C.PROT_READ PROT_READ = C.PROT_READ

View file

@ -9,6 +9,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1
@ -136,16 +137,30 @@ type fpstate struct {
anon0 [48]byte anon0 [48]byte
} }
type timespec struct { // The timespec structs and types are defined in Linux in
// include/uapi/linux/time_types.h and include/uapi/asm-generic/posix_types.h.
type timespec32 struct {
tv_sec int32 tv_sec int32
tv_nsec int32 tv_nsec int32
} }
//go:nosplit //go:nosplit
func (ts *timespec) setNsec(ns int64) { func (ts *timespec32) setNsec(ns int64) {
ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec) ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec)
} }
type timespec struct {
tv_sec int64
tv_nsec int64
}
//go:nosplit
func (ts *timespec) setNsec(ns int64) {
var newNS int32
ts.tv_sec = int64(timediv(ns, 1e9, &newNS))
ts.tv_nsec = int64(newNS)
}
type timeval struct { type timeval struct {
tv_sec int32 tv_sec int32
tv_usec int32 tv_usec int32
@ -223,8 +238,8 @@ type ucontext struct {
} }
type itimerspec struct { type itimerspec struct {
it_interval timespec it_interval timespec32
it_value timespec it_value timespec32
} }
type itimerval struct { type itimerval struct {

View file

@ -9,6 +9,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -11,6 +11,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_ENOMEM = 0xc _ENOMEM = 0xc
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOSYS = 0x26
_PROT_NONE = 0 _PROT_NONE = 0
_PROT_READ = 0x1 _PROT_READ = 0x1
@ -95,16 +96,30 @@ const (
_SOCK_DGRAM = 0x2 _SOCK_DGRAM = 0x2
) )
type timespec struct { // The timespec structs and types are defined in Linux in
// include/uapi/linux/time_types.h and include/uapi/asm-generic/posix_types.h.
type timespec32 struct {
tv_sec int32 tv_sec int32
tv_nsec int32 tv_nsec int32
} }
//go:nosplit //go:nosplit
func (ts *timespec) setNsec(ns int64) { func (ts *timespec32) setNsec(ns int64) {
ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec) ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec)
} }
type timespec struct {
tv_sec int64
tv_nsec int64
}
//go:nosplit
func (ts *timespec) setNsec(ns int64) {
var newNS int32
ts.tv_sec = int64(timediv(ns, 1e9, &newNS))
ts.tv_nsec = int64(newNS)
}
type stackt struct { type stackt struct {
ss_sp *byte ss_sp *byte
ss_flags int32 ss_flags int32
@ -155,8 +170,8 @@ func (tv *timeval) set_usec(x int32) {
} }
type itimerspec struct { type itimerspec struct {
it_interval timespec it_interval timespec32
it_value timespec it_value timespec32
} }
type itimerval struct { type itimerval struct {

View file

@ -9,6 +9,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -10,6 +10,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -12,6 +12,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -12,6 +12,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1
@ -93,16 +94,30 @@ const (
_SIGEV_THREAD_ID = 0x4 _SIGEV_THREAD_ID = 0x4
) )
type timespec struct { // The timespec structs and types are defined in Linux in
// include/uapi/linux/time_types.h and include/uapi/asm-generic/posix_types.h.
type timespec32 struct {
tv_sec int32 tv_sec int32
tv_nsec int32 tv_nsec int32
} }
//go:nosplit //go:nosplit
func (ts *timespec) setNsec(ns int64) { func (ts *timespec32) setNsec(ns int64) {
ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec) ts.tv_sec = timediv(ns, 1e9, &ts.tv_nsec)
} }
type timespec struct {
tv_sec int64
tv_nsec int64
}
//go:nosplit
func (ts *timespec) setNsec(ns int64) {
var newNS int32
ts.tv_sec = int64(timediv(ns, 1e9, &newNS))
ts.tv_nsec = int64(newNS)
}
type timeval struct { type timeval struct {
tv_sec int32 tv_sec int32
tv_usec int32 tv_usec int32
@ -138,8 +153,8 @@ type siginfo struct {
} }
type itimerspec struct { type itimerspec struct {
it_interval timespec it_interval timespec32
it_value timespec it_value timespec32
} }
type itimerval struct { type itimerval struct {

View file

@ -9,6 +9,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -9,6 +9,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -10,6 +10,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -10,6 +10,7 @@ const (
_EINTR = 0x4 _EINTR = 0x4
_EAGAIN = 0xb _EAGAIN = 0xb
_ENOMEM = 0xc _ENOMEM = 0xc
_ENOSYS = 0x26
_PROT_NONE = 0x0 _PROT_NONE = 0x0
_PROT_READ = 0x1 _PROT_READ = 0x1

View file

@ -40,9 +40,6 @@ type mOS struct {
waitsema uint32 // semaphore for parking on locks waitsema uint32 // semaphore for parking on locks
} }
//go:noescape
func futex(addr unsafe.Pointer, op int32, val uint32, ts, addr2 unsafe.Pointer, val3 uint32) int32
// Linux futex. // Linux futex.
// //
// futexsleep(uint32 *addr, uint32 val) // futexsleep(uint32 *addr, uint32 val)
@ -79,7 +76,7 @@ func futexsleep(addr *uint32, val uint32, ns int64) {
var ts timespec var ts timespec
ts.setNsec(ns) ts.setNsec(ns)
futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, unsafe.Pointer(&ts), nil, 0) futex(unsafe.Pointer(addr), _FUTEX_WAIT_PRIVATE, val, &ts, nil, 0)
} }
// If any procs are sleeping on addr, wake up at most cnt. // If any procs are sleeping on addr, wake up at most cnt.

View file

@ -0,0 +1,40 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && (386 || arm || mips || mipsle || ppc)
package runtime
import (
"internal/runtime/atomic"
"unsafe"
)
//go:noescape
func futex_time32(addr unsafe.Pointer, op int32, val uint32, ts *timespec32, addr2 unsafe.Pointer, val3 uint32) int32
//go:noescape
func futex_time64(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32
var is32bitOnly atomic.Bool
//go:nosplit
func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32 {
if !is32bitOnly.Load() {
ret := futex_time64(addr, op, val, ts, addr2, val3)
// futex_time64 is only supported on Linux 5.0+
if ret != -_ENOSYS {
return ret
}
is32bitOnly.Store(true)
}
// Downgrade ts.
var ts32 timespec32
var pts32 *timespec32
if ts != nil {
ts32.setNsec(ts.tv_sec*1e9 + ts.tv_nsec)
pts32 = &ts32
}
return futex_time32(addr, op, val, pts32, addr2, val3)
}

View file

@ -0,0 +1,14 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && !(386 || arm || mips || mipsle || ppc || s390)
package runtime
import (
"unsafe"
)
//go:noescape
func futex(addr unsafe.Pointer, op int32, val uint32, ts *timespec, addr2 unsafe.Pointer, val3 uint32) int32

View file

@ -48,6 +48,7 @@
#define SYS_madvise 219 #define SYS_madvise 219
#define SYS_gettid 224 #define SYS_gettid 224
#define SYS_futex 240 #define SYS_futex 240
#define SYS_futex_time64 422
#define SYS_sched_getaffinity 242 #define SYS_sched_getaffinity 242
#define SYS_set_thread_area 243 #define SYS_set_thread_area 243
#define SYS_exit_group 252 #define SYS_exit_group 252
@ -532,10 +533,26 @@ TEXT runtime·madvise(SB),NOSPLIT,$0
MOVL AX, ret+12(FP) MOVL AX, ret+12(FP)
RET RET
// Linux: kernel/futex/syscalls.c, requiring COMPAT_32BIT_TIME
// int32 futex(int32 *uaddr, int32 op, int32 val,
// struct old_timespec32 *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex_time32(SB),NOSPLIT,$0
MOVL $SYS_futex, AX
MOVL addr+0(FP), BX
MOVL op+4(FP), CX
MOVL val+8(FP), DX
MOVL ts+12(FP), SI
MOVL addr2+16(FP), DI
MOVL val3+20(FP), BP
INVOKE_SYSCALL
MOVL AX, ret+24(FP)
RET
// Linux: kernel/futex/syscalls.c
// int32 futex(int32 *uaddr, int32 op, int32 val, // int32 futex(int32 *uaddr, int32 op, int32 val,
// struct timespec *timeout, int32 *uaddr2, int32 val2); // struct timespec *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex(SB),NOSPLIT,$0 TEXT runtime·futex_time64(SB),NOSPLIT,$0
MOVL $SYS_futex, AX MOVL $SYS_futex_time64, AX
MOVL addr+0(FP), BX MOVL addr+0(FP), BX
MOVL op+4(FP), CX MOVL op+4(FP), CX
MOVL val+8(FP), DX MOVL val+8(FP), DX

View file

@ -30,6 +30,7 @@
#define SYS_sigaltstack (SYS_BASE + 186) #define SYS_sigaltstack (SYS_BASE + 186)
#define SYS_mmap2 (SYS_BASE + 192) #define SYS_mmap2 (SYS_BASE + 192)
#define SYS_futex (SYS_BASE + 240) #define SYS_futex (SYS_BASE + 240)
#define SYS_futex_time64 (SYS_BASE + 422)
#define SYS_exit_group (SYS_BASE + 248) #define SYS_exit_group (SYS_BASE + 248)
#define SYS_munmap (SYS_BASE + 91) #define SYS_munmap (SYS_BASE + 91)
#define SYS_madvise (SYS_BASE + 220) #define SYS_madvise (SYS_BASE + 220)
@ -403,9 +404,10 @@ finish:
RET RET
// Linux: kernel/futex/syscalls.c, requiring COMPAT_32BIT_TIME
// int32 futex(int32 *uaddr, int32 op, int32 val, // int32 futex(int32 *uaddr, int32 op, int32 val,
// struct timespec *timeout, int32 *uaddr2, int32 val2); // struct old_timespec32 *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex(SB),NOSPLIT,$0 TEXT runtime·futex_time32(SB),NOSPLIT,$0
MOVW addr+0(FP), R0 MOVW addr+0(FP), R0
MOVW op+4(FP), R1 MOVW op+4(FP), R1
MOVW val+8(FP), R2 MOVW val+8(FP), R2
@ -417,6 +419,21 @@ TEXT runtime·futex(SB),NOSPLIT,$0
MOVW R0, ret+24(FP) MOVW R0, ret+24(FP)
RET RET
// Linux: kernel/futex/syscalls.c
// int32 futex(int32 *uaddr, int32 op, int32 val,
// struct timespec *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex_time64(SB),NOSPLIT,$0
MOVW addr+0(FP), R0
MOVW op+4(FP), R1
MOVW val+8(FP), R2
MOVW ts+12(FP), R3
MOVW addr2+16(FP), R4
MOVW val3+20(FP), R5
MOVW $SYS_futex_time64, R7
SWI $0
MOVW R0, ret+24(FP)
RET
// int32 clone(int32 flags, void *stack, M *mp, G *gp, void (*fn)(void)); // int32 clone(int32 flags, void *stack, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB),NOSPLIT,$0 TEXT runtime·clone(SB),NOSPLIT,$0
MOVW flags+0(FP), R0 MOVW flags+0(FP), R0

View file

@ -34,6 +34,7 @@
#define SYS_mincore 4217 #define SYS_mincore 4217
#define SYS_gettid 4222 #define SYS_gettid 4222
#define SYS_futex 4238 #define SYS_futex 4238
#define SYS_futex_time64 4422
#define SYS_sched_getaffinity 4240 #define SYS_sched_getaffinity 4240
#define SYS_exit_group 4246 #define SYS_exit_group 4246
#define SYS_timer_create 4257 #define SYS_timer_create 4257
@ -362,8 +363,10 @@ TEXT runtime·madvise(SB),NOSPLIT,$0-16
MOVW R2, ret+12(FP) MOVW R2, ret+12(FP)
RET RET
// int32 futex(int32 *uaddr, int32 op, int32 val, struct timespec *timeout, int32 *uaddr2, int32 val2); // Linux: kernel/futex/syscalls.c, requiring COMPAT_32BIT_TIME
TEXT runtime·futex(SB),NOSPLIT,$20-28 // int32 futex(int32 *uaddr, int32 op, int32 val,
// struct old_timespec32 *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex_time32(SB),NOSPLIT,$20-28
MOVW addr+0(FP), R4 MOVW addr+0(FP), R4
MOVW op+4(FP), R5 MOVW op+4(FP), R5
MOVW val+8(FP), R6 MOVW val+8(FP), R6
@ -382,6 +385,27 @@ TEXT runtime·futex(SB),NOSPLIT,$20-28
MOVW R2, ret+24(FP) MOVW R2, ret+24(FP)
RET RET
// Linux: kernel/futex/syscalls.c
// int32 futex(int32 *uaddr, int32 op, int32 val,
// struct timespec *timeout, int32 *uaddr2, int32 val2);
TEXT runtime·futex_time64(SB),NOSPLIT,$20-28
MOVW addr+0(FP), R4
MOVW op+4(FP), R5
MOVW val+8(FP), R6
MOVW ts+12(FP), R7
MOVW addr2+16(FP), R8
MOVW val3+20(FP), R9
MOVW R8, 16(R29)
MOVW R9, 20(R29)
MOVW $SYS_futex_time64, R2
SYSCALL
BEQ R7, 2(PC)
SUBU R2, R0, R2 // caller expects negative errno
MOVW R2, ret+24(FP)
RET
// int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void)); // int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB),NOSPLIT|NOFRAME,$0-24 TEXT runtime·clone(SB),NOSPLIT|NOFRAME,$0-24