Skip to content

Commit 9f98e49

Browse files
bioothodalexbrainman
authored andcommitted
runtime: make time correctly update on Wine
Implemented low-level time system for windows on hardware (software), which does not support memory mapped _KSYSTEM_TIME page update. In particular this problem exists on Wine where _KSYSTEM_TIME only contains time at the start, and is never modified. On start we try to detect Wine and if it's so we fallback to GetSystemTimeAsFileTime() for current time and a monotonic timer based on QueryPerformanceCounter family of syscalls: https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx Fixes #18537 Change-Id: I269d22467ed9b0afb62056974d23e731b80c83ed Reviewed-on: https://go-review.googlesource.com/35710 Reviewed-by: Alex Brainman <[email protected]> Run-TryBot: Alex Brainman <[email protected]> TryBot-Result: Gobot Gobot <[email protected]>
1 parent b0472e2 commit 9f98e49

File tree

3 files changed

+102
-3
lines changed

3 files changed

+102
-3
lines changed

src/runtime/os_windows.go

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ var (
7373
_GetQueuedCompletionStatus,
7474
_GetStdHandle,
7575
_GetSystemInfo,
76+
_GetSystemTimeAsFileTime,
7677
_GetThreadContext,
7778
_LoadLibraryW,
7879
_LoadLibraryA,
80+
_QueryPerformanceCounter,
81+
_QueryPerformanceFrequency,
7982
_ResumeThread,
8083
_SetConsoleCtrlHandler,
8184
_SetErrorMode,
@@ -188,6 +191,11 @@ func loadOptionalSyscalls() {
188191
throw("ntdll.dll not found")
189192
}
190193
_NtWaitForSingleObject = windowsFindfunc(n32, []byte("NtWaitForSingleObject\000"))
194+
195+
if windowsFindfunc(n32, []byte("wine_get_version\000")) != nil {
196+
// running on Wine
197+
initWine(k32)
198+
}
191199
}
192200

193201
//go:nosplit
@@ -292,6 +300,79 @@ func osinit() {
292300
stdcall2(_SetProcessPriorityBoost, currentProcess, 1)
293301
}
294302

303+
func nanotime() int64
304+
305+
// useQPCTime controls whether time.now and nanotime use QueryPerformanceCounter.
306+
// This is only set to 1 when running under Wine.
307+
var useQPCTime uint8
308+
309+
var qpcStartCounter int64
310+
var qpcMultiplier int64
311+
312+
//go:nosplit
313+
func nanotimeQPC() int64 {
314+
var counter int64 = 0
315+
stdcall1(_QueryPerformanceCounter, uintptr(unsafe.Pointer(&counter)))
316+
317+
// returns number of nanoseconds
318+
return (counter - qpcStartCounter) * qpcMultiplier
319+
}
320+
321+
//go:nosplit
322+
func nowQPC() (sec int64, nsec int32, mono int64) {
323+
var ft int64
324+
stdcall1(_GetSystemTimeAsFileTime, uintptr(unsafe.Pointer(&ft)))
325+
326+
t := (ft - 116444736000000000) * 100
327+
328+
sec = t / 1000000000
329+
nsec = int32(t - sec*1000000000)
330+
331+
mono = nanotimeQPC()
332+
return
333+
}
334+
335+
func initWine(k32 uintptr) {
336+
_GetSystemTimeAsFileTime = windowsFindfunc(k32, []byte("GetSystemTimeAsFileTime\000"))
337+
if _GetSystemTimeAsFileTime == nil {
338+
throw("could not find GetSystemTimeAsFileTime() syscall")
339+
}
340+
341+
_QueryPerformanceCounter = windowsFindfunc(k32, []byte("QueryPerformanceCounter\000"))
342+
_QueryPerformanceFrequency = windowsFindfunc(k32, []byte("QueryPerformanceFrequency\000"))
343+
if _QueryPerformanceCounter == nil || _QueryPerformanceFrequency == nil {
344+
throw("could not find QPC syscalls")
345+
}
346+
347+
// We can not simply fallback to GetSystemTimeAsFileTime() syscall, since its time is not monotonic,
348+
// instead we use QueryPerformanceCounter family of syscalls to implement monotonic timer
349+
// https://msdn.microsoft.com/en-us/library/windows/desktop/dn553408(v=vs.85).aspx
350+
351+
var tmp int64
352+
stdcall1(_QueryPerformanceFrequency, uintptr(unsafe.Pointer(&tmp)))
353+
if tmp == 0 {
354+
throw("QueryPerformanceFrequency syscall returned zero, running on unsupported hardware")
355+
}
356+
357+
// This should not overflow, it is a number of ticks of the performance counter per second,
358+
// its resolution is at most 10 per usecond (on Wine, even smaller on real hardware), so it will be at most 10 millions here,
359+
// panic if overflows.
360+
if tmp > (1<<31 - 1) {
361+
throw("QueryPerformanceFrequency overflow 32 bit divider, check nosplit discussion to proceed")
362+
}
363+
qpcFrequency := int32(tmp)
364+
stdcall1(_QueryPerformanceCounter, uintptr(unsafe.Pointer(&qpcStartCounter)))
365+
366+
// Since we are supposed to run this time calls only on Wine, it does not lose precision,
367+
// since Wine's timer is kind of emulated at 10 Mhz, so it will be a nice round multiplier of 100
368+
// but for general purpose system (like 3.3 Mhz timer on i7) it will not be very precise.
369+
// We have to do it this way (or similar), since multiplying QPC counter by 100 millions overflows
370+
// int64 and resulted time will always be invalid.
371+
qpcMultiplier = int64(timediv(1000000000, qpcFrequency, nil))
372+
373+
useQPCTime = 1
374+
}
375+
295376
//go:nosplit
296377
func getRandomData(r []byte) {
297378
n := 0
@@ -578,8 +659,6 @@ func unminit() {
578659
*tp = 0
579660
}
580661

581-
func nanotime() int64
582-
583662
// Calling stdcall on os stack.
584663
// May run during STW, so write barriers are not allowed.
585664
//go:nowritebarrier

src/runtime/sys_windows_386.s

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,8 @@ TEXT runtime·switchtothread(SB),NOSPLIT,$0
441441
#define time_hi2 8
442442

443443
TEXT runtime·nanotime(SB),NOSPLIT,$0-8
444+
CMPB runtime·useQPCTime(SB), $0
445+
JNE useQPC
444446
loop:
445447
MOVL (_INTERRUPT_TIME+time_hi1), AX
446448
MOVL (_INTERRUPT_TIME+time_lo), CX
@@ -459,8 +461,13 @@ loop:
459461
MOVL AX, ret_lo+0(FP)
460462
MOVL DX, ret_hi+4(FP)
461463
RET
464+
useQPC:
465+
JMP runtime·nanotimeQPC(SB)
466+
RET
462467

463468
TEXT time·now(SB),NOSPLIT,$0-20
469+
CMPB runtime·useQPCTime(SB), $0
470+
JNE useQPC
464471
loop:
465472
MOVL (_INTERRUPT_TIME+time_hi1), AX
466473
MOVL (_INTERRUPT_TIME+time_lo), CX
@@ -477,7 +484,7 @@ loop:
477484
// w*100 = DX:AX
478485
// subtract startNano and save for return
479486
SUBL runtime·startNano+0(SB), AX
480-
SBBL runtime·startNano+4(SB), DX
487+
SBBL runtime·startNano+4(SB), DX
481488
MOVL AX, mono+12(FP)
482489
MOVL DX, mono+16(FP)
483490

@@ -532,3 +539,6 @@ wall:
532539
MOVL AX, sec+0(FP)
533540
MOVL DX, sec+4(FP)
534541
RET
542+
useQPC:
543+
JMP runtime·nowQPC(SB)
544+
RET

src/runtime/sys_windows_amd64.s

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ TEXT runtime·switchtothread(SB),NOSPLIT|NOFRAME,$0
474474
#define time_hi2 8
475475

476476
TEXT runtime·nanotime(SB),NOSPLIT,$0-8
477+
CMPB runtime·useQPCTime(SB), $0
478+
JNE useQPC
477479
MOVQ $_INTERRUPT_TIME, DI
478480
loop:
479481
MOVL time_hi1(DI), AX
@@ -487,8 +489,13 @@ loop:
487489
SUBQ runtime·startNano(SB), CX
488490
MOVQ CX, ret+0(FP)
489491
RET
492+
useQPC:
493+
JMP runtime·nanotimeQPC(SB)
494+
RET
490495

491496
TEXT time·now(SB),NOSPLIT,$0-24
497+
CMPB runtime·useQPCTime(SB), $0
498+
JNE useQPC
492499
MOVQ $_INTERRUPT_TIME, DI
493500
loop:
494501
MOVL time_hi1(DI), AX
@@ -529,3 +536,6 @@ wall:
529536
SUBQ DX, CX
530537
MOVL CX, nsec+8(FP)
531538
RET
539+
useQPC:
540+
JMP runtime·nowQPC(SB)
541+
RET

0 commit comments

Comments
 (0)