From 0271395e57a7d32a03d6f4f3290cc54d83a446c2 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Thu, 2 Jul 2026 16:42:28 +0800 Subject: [PATCH] runtime: nanosecond monotonic clock on darwin and linux The monotonic time source had two problems: - On Linux, runtimeNano passed clite's CLOCK_MONOTONIC, whose value is Darwin's clock id (6). Linux interprets 6 as CLOCK_MONOTONIC_COARSE, a millisecond-granularity clock: consecutive time.Now() readings were identical 100% of the time and the smallest nonzero delta was 1ms. - On Darwin, clock_gettime(CLOCK_MONOTONIC) itself only has microsecond granularity (96% identical consecutive readings, 1us minimum delta). Mirror Go's runtime structure with a per-OS nanotime1 in the runtime package itself, keeping the hot path free of clite indirection and clite unchanged: Darwin reads CLOCK_UPTIME_RAW through clock_gettime_nsec_np (the same clock Go's nanotime uses there), Linux uses clock_gettime with the OS-correct CLOCK_MONOTONIC id as a local constant, and remaining platforms keep the previous behavior. Measured with consecutive time.Now() deltas (min nonzero / zero-frac): - macOS arm64: 1us / 96.5% -> 41ns / 26% (Go 1.26: 41ns / 22%) - Linux arm64: 1ms / 100% -> 41ns / 21% time.Sleep, Timer and Ticker behave identically before and after. Co-Authored-By: Claude Fable 5 --- .../lib/runtime/nanotime_darwin_llgo.go | 36 +++++++++++++++++ .../lib/runtime/nanotime_linux_llgo.go | 40 +++++++++++++++++++ runtime/internal/lib/runtime/nanotime_llgo.go | 25 ++++++++++++ .../lib/runtime/nanotime_other_llgo.go | 33 +++++++++++++++ runtime/internal/lib/runtime/time_llgo.go | 6 --- .../internal/lib/runtime/time_llgo_go123.go | 6 --- 6 files changed, 134 insertions(+), 12 deletions(-) create mode 100644 runtime/internal/lib/runtime/nanotime_darwin_llgo.go create mode 100644 runtime/internal/lib/runtime/nanotime_linux_llgo.go create mode 100644 runtime/internal/lib/runtime/nanotime_llgo.go create mode 100644 runtime/internal/lib/runtime/nanotime_other_llgo.go diff --git a/runtime/internal/lib/runtime/nanotime_darwin_llgo.go b/runtime/internal/lib/runtime/nanotime_darwin_llgo.go new file mode 100644 index 0000000000..7d487edadf --- /dev/null +++ b/runtime/internal/lib/runtime/nanotime_darwin_llgo.go @@ -0,0 +1,36 @@ +//go:build darwin && !baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + _ "unsafe" +) + +// Mirrors Go's runtime.nanotime1 on Darwin (sys_darwin.go): read +// CLOCK_UPTIME_RAW through clock_gettime_nsec_np. Darwin serves +// clock_gettime(CLOCK_MONOTONIC) with only microsecond granularity, while +// CLOCK_UPTIME_RAW is mach_absolute_time with full nanosecond resolution. +const _CLOCK_UPTIME_RAW = 8 + +//go:linkname c_clock_gettime_nsec_np C.clock_gettime_nsec_np +func c_clock_gettime_nsec_np(clockID int32) uint64 + +func nanotime1() int64 { + return int64(c_clock_gettime_nsec_np(_CLOCK_UPTIME_RAW)) +} diff --git a/runtime/internal/lib/runtime/nanotime_linux_llgo.go b/runtime/internal/lib/runtime/nanotime_linux_llgo.go new file mode 100644 index 0000000000..a07def21eb --- /dev/null +++ b/runtime/internal/lib/runtime/nanotime_linux_llgo.go @@ -0,0 +1,40 @@ +//go:build linux && !baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + ct "github.com/goplus/llgo/runtime/internal/clite/time" +) + +// Linux CLOCK_MONOTONIC (see ), which has nanosecond +// resolution. Deliberately a local constant: ct.CLOCK_MONOTONIC carries +// Darwin's id (6), which Linux interprets as CLOCK_MONOTONIC_COARSE — a +// millisecond-granularity clock that quantized every monotonic timestamp +// the runtime produced. +const _CLOCK_MONOTONIC = 1 + +// nanotime1 mirrors Go's runtime.nanotime1 on Linux. +func nanotime1() int64 { + tv := (*ct.Timespec)(c.Alloca(unsafe.Sizeof(ct.Timespec{}))) + ct.ClockGettime(ct.ClockidT(_CLOCK_MONOTONIC), tv) + return int64(tv.Sec)*1e9 + int64(tv.Nsec) +} diff --git a/runtime/internal/lib/runtime/nanotime_llgo.go b/runtime/internal/lib/runtime/nanotime_llgo.go new file mode 100644 index 0000000000..d9644d6281 --- /dev/null +++ b/runtime/internal/lib/runtime/nanotime_llgo.go @@ -0,0 +1,25 @@ +//go:build !baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +// runtimeNano mirrors Go's runtime.nanotime: the monotonic clock in +// nanoseconds, implemented per OS by nanotime1. +func runtimeNano() int64 { + return nanotime1() +} diff --git a/runtime/internal/lib/runtime/nanotime_other_llgo.go b/runtime/internal/lib/runtime/nanotime_other_llgo.go new file mode 100644 index 0000000000..8478b3ec8f --- /dev/null +++ b/runtime/internal/lib/runtime/nanotime_other_llgo.go @@ -0,0 +1,33 @@ +//go:build !darwin && !linux && !baremetal + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package runtime + +import ( + "unsafe" + + c "github.com/goplus/llgo/runtime/internal/clite" + ct "github.com/goplus/llgo/runtime/internal/clite/time" +) + +// nanotime1 keeps the previous behavior on remaining platforms. +func nanotime1() int64 { + tv := (*ct.Timespec)(c.Alloca(unsafe.Sizeof(ct.Timespec{}))) + ct.ClockGettime(ct.CLOCK_MONOTONIC, tv) + return int64(tv.Sec)*1e9 + int64(tv.Nsec) +} diff --git a/runtime/internal/lib/runtime/time_llgo.go b/runtime/internal/lib/runtime/time_llgo.go index 0a09c98e07..bc4586dd77 100644 --- a/runtime/internal/lib/runtime/time_llgo.go +++ b/runtime/internal/lib/runtime/time_llgo.go @@ -545,9 +545,3 @@ func timeSleepWake(arg any, _ uintptr) { ch := arg.(chan struct{}) ch <- struct{}{} } - -func runtimeNano() int64 { - tv := (*ct.Timespec)(c.Alloca(unsafe.Sizeof(ct.Timespec{}))) - ct.ClockGettime(ct.CLOCK_MONOTONIC, tv) - return int64(tv.Sec)*1e9 + int64(tv.Nsec) -} diff --git a/runtime/internal/lib/runtime/time_llgo_go123.go b/runtime/internal/lib/runtime/time_llgo_go123.go index ceb2d73ed4..85661dd122 100644 --- a/runtime/internal/lib/runtime/time_llgo_go123.go +++ b/runtime/internal/lib/runtime/time_llgo_go123.go @@ -579,9 +579,3 @@ func resetTimer(t *timeTimer, when, period int64) bool { r := &t.r return resetRuntimeTimer(r, when, period, r.f, r.arg, r.seq) } - -func runtimeNano() int64 { - tv := (*ct.Timespec)(c.Alloca(unsafe.Sizeof(ct.Timespec{}))) - ct.ClockGettime(ct.CLOCK_MONOTONIC, tv) - return int64(tv.Sec)*1e9 + int64(tv.Nsec) -}