diff --git a/src/runtime/os_windows.go b/src/runtime/os_windows.go index 75b8acd..15506da 100644 --- a/src/runtime/os_windows.go +++ b/src/runtime/os_windows.go @@ -33,6 +33,8 @@ const ( //go:cgo_import_dynamic runtime._GetThreadContext GetThreadContext%2 "kernel32.dll" //go:cgo_import_dynamic runtime._LoadLibraryW LoadLibraryW%1 "kernel32.dll" //go:cgo_import_dynamic runtime._LoadLibraryA LoadLibraryA%1 "kernel32.dll" +//go:cgo_import_dynamic runtime._QueryPerformanceCounter QueryPerformanceCounter%1 "kernel32.dll" +//go:cgo_import_dynamic runtime._QueryPerformanceFrequency QueryPerformanceFrequency%1 "kernel32.dll" //go:cgo_import_dynamic runtime._ResumeThread ResumeThread%1 "kernel32.dll" //go:cgo_import_dynamic runtime._SetConsoleCtrlHandler SetConsoleCtrlHandler%2 "kernel32.dll" //go:cgo_import_dynamic runtime._SetErrorMode SetErrorMode%1 "kernel32.dll" @@ -76,6 +78,8 @@ var ( _GetThreadContext, _LoadLibraryW, _LoadLibraryA, + _QueryPerformanceCounter, + _QueryPerformanceFrequency, _ResumeThread, _SetConsoleCtrlHandler, _SetErrorMode, @@ -290,6 +294,45 @@ func osinit() { // equivalent threads that all do a mix of GUI, IO, computations, etc. // In such context dynamic priority boosting does nothing but harm, so we turn it off. stdcall2(_SetProcessPriorityBoost, currentProcess, 1) + + check_timers() +} + +//go:nosplit +var systime func(addr uintptr) int64 +var systime_qpc_frequency int32 +var systime_qpc_counter_start int64 + +//go:nosplit +func check_timers() { + systime = systime_memory_mapping + + var tmp int64 + stdcall1(_QueryPerformanceFrequency, uintptr(unsafe.Pointer(&tmp))) + // this should not overflow, it is a number of ticks of the performance counter per second + // panic if overflow + if tmp == 0 { + panic("QueryPerformanceFrequency syscall returned zero, running on unsupported hardware") + } + if tmp > (1<<31 - 1) { + panic("QueryPerformanceFrequency overflow 32 bit divider, check nosplit discussion to proceed") + } + systime_qpc_frequency = int32(tmp) + stdcall1(_QueryPerformanceCounter, uintptr(unsafe.Pointer(&systime_qpc_counter_start))) + + // wine users will wait for at most 10 usecs at start to detect that wine + // doesn't update page containg INTERRUPT_TIME, which means fast time is not supported on wine + // we can not simply fallback to Sleep() syscall, since its time is not monotonic, + // instead we use QueryPerformanceCounter family of syscalls to implement monotonic timer + // https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx + t1 := nanotime() + for i := 0; i < 100; i++ { + if nanotime() != t1 { + return + } + } + + systime = systime_qpc_syscall } //go:nosplit @@ -591,7 +634,7 @@ const ( ) //go:nosplit -func systime(addr uintptr) int64 { +func systime_memory_mapping(addr uintptr) int64 { timeaddr := (*_KSYSTEM_TIME)(unsafe.Pointer(addr)) var t _KSYSTEM_TIME @@ -607,6 +650,8 @@ func systime(addr uintptr) int64 { osyield() } } + + // NB this recursively enters systime() call systemstack(func() { throw("interrupt/system time is changing too fast") }) @@ -614,6 +659,27 @@ func systime(addr uintptr) int64 { } //go:nosplit +func systime_qpc_syscall(addr uintptr) int64 { + // QPC timing is called when memory mapping is not updated, but it still contains the correct initial time + // we use this as a base and apply QPC bias + + var counter int64 = 0 + stdcall1(_QueryPerformanceCounter, uintptr(unsafe.Pointer(&counter))) + + return systime_memory_mapping(addr) + int64(timediv((counter - systime_qpc_counter_start) * 10000000, systime_qpc_frequency, nil)) +} +func SystimeQPCTest() bool { + t1 := systime_qpc_syscall(_INTERRUPT_TIME) + for i := 0; i < 1000; i++ { + t2 := systime_qpc_syscall(_INTERRUPT_TIME) + if t2 != t1 { + return true + } + } + return false +} + +//go:nosplit func unixnano() int64 { return (systime(_SYSTEM_TIME) - 116444736000000000) * 100 } diff --git a/src/runtime/syscall_windows_test.go b/src/runtime/syscall_windows_test.go index 11e67df..82dabea 100644 --- a/src/runtime/syscall_windows_test.go +++ b/src/runtime/syscall_windows_test.go @@ -933,6 +933,12 @@ func TestLoadLibraryEx(t *testing.T) { have, flags) } +func TimeSystimeTest(t *testing.T) { + if !runtime.SystimeQPCTest() { + t.Fatal("runtime.systime_qpc_syscall() has stalled") + } +} + var ( modwinmm = syscall.NewLazyDLL("winmm.dll") modkernel32 = syscall.NewLazyDLL("kernel32.dll")