diff options
Diffstat (limited to 'src/runtime/runtime.go')
-rw-r--r-- | src/runtime/runtime.go | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/runtime/runtime.go b/src/runtime/runtime.go new file mode 100644 index 0000000..c70a76e --- /dev/null +++ b/src/runtime/runtime.go @@ -0,0 +1,233 @@ +// Copyright 2009 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. + +package runtime + +import ( + "runtime/internal/atomic" + "unsafe" +) + +//go:generate go run wincallback.go +//go:generate go run mkduff.go +//go:generate go run mkfastlog2table.go +//go:generate go run mklockrank.go -o lockrank.go + +var ticks ticksType + +type ticksType struct { + // lock protects access to start* and val. + lock mutex + startTicks int64 + startTime int64 + val atomic.Int64 +} + +// init initializes ticks to maximize the chance that we have a good ticksPerSecond reference. +// +// Must not run concurrently with ticksPerSecond. +func (t *ticksType) init() { + lock(&ticks.lock) + t.startTime = nanotime() + t.startTicks = cputicks() + unlock(&ticks.lock) +} + +// minTimeForTicksPerSecond is the minimum elapsed time we require to consider our ticksPerSecond +// measurement to be of decent enough quality for profiling. +// +// There's a linear relationship here between minimum time and error from the true value. +// The error from the true ticks-per-second in a linux/amd64 VM seems to be: +// - 1 ms -> ~0.02% error +// - 5 ms -> ~0.004% error +// - 10 ms -> ~0.002% error +// - 50 ms -> ~0.0003% error +// - 100 ms -> ~0.0001% error +// +// We're willing to take 0.004% error here, because ticksPerSecond is intended to be used for +// converting durations, not timestamps. Durations are usually going to be much larger, and so +// the tiny error doesn't matter. The error is definitely going to be a problem when trying to +// use this for timestamps, as it'll make those timestamps much less likely to line up. +const minTimeForTicksPerSecond = 5_000_000*(1-osHasLowResClockInt) + 100_000_000*osHasLowResClockInt + +// ticksPerSecond returns a conversion rate between the cputicks clock and the nanotime clock. +// +// Note: Clocks are hard. Using this as an actual conversion rate for timestamps is ill-advised +// and should be avoided when possible. Use only for durations, where a tiny error term isn't going +// to make a meaningful difference in even a 1ms duration. If an accurate timestamp is needed, +// use nanotime instead. (The entire Windows platform is a broad exception to this rule, where nanotime +// produces timestamps on such a coarse granularity that the error from this conversion is actually +// preferable.) +// +// The strategy for computing the conversion rate is to write down nanotime and cputicks as +// early in process startup as possible. From then, we just need to wait until we get values +// from nanotime that we can use (some platforms have a really coarse system time granularity). +// We require some amount of time to pass to ensure that the conversion rate is fairly accurate +// in aggregate. But because we compute this rate lazily, there's a pretty good chance a decent +// amount of time has passed by the time we get here. +// +// Must be called from a normal goroutine context (running regular goroutine with a P). +// +// Called by runtime/pprof in addition to runtime code. +// +// TODO(mknyszek): This doesn't account for things like CPU frequency scaling. Consider +// a more sophisticated and general approach in the future. +func ticksPerSecond() int64 { + // Get the conversion rate if we've already computed it. + r := ticks.val.Load() + if r != 0 { + return r + } + + // Compute the conversion rate. + for { + lock(&ticks.lock) + r = ticks.val.Load() + if r != 0 { + unlock(&ticks.lock) + return r + } + + // Grab the current time in both clocks. + nowTime := nanotime() + nowTicks := cputicks() + + // See if we can use these times. + if nowTicks > ticks.startTicks && nowTime-ticks.startTime > minTimeForTicksPerSecond { + // Perform the calculation with floats. We don't want to risk overflow. + r = int64(float64(nowTicks-ticks.startTicks) * 1e9 / float64(nowTime-ticks.startTime)) + if r == 0 { + // Zero is both a sentinel value and it would be bad if callers used this as + // a divisor. We tried out best, so just make it 1. + r++ + } + ticks.val.Store(r) + unlock(&ticks.lock) + break + } + unlock(&ticks.lock) + + // Sleep in one millisecond increments until we have a reliable time. + timeSleep(1_000_000) + } + return r +} + +var envs []string +var argslice []string + +//go:linkname syscall_runtime_envs syscall.runtime_envs +func syscall_runtime_envs() []string { return append([]string{}, envs...) } + +//go:linkname syscall_Getpagesize syscall.Getpagesize +func syscall_Getpagesize() int { return int(physPageSize) } + +//go:linkname os_runtime_args os.runtime_args +func os_runtime_args() []string { return append([]string{}, argslice...) } + +//go:linkname syscall_Exit syscall.Exit +//go:nosplit +func syscall_Exit(code int) { + exit(int32(code)) +} + +var godebugDefault string +var godebugUpdate atomic.Pointer[func(string, string)] +var godebugEnv atomic.Pointer[string] // set by parsedebugvars +var godebugNewIncNonDefault atomic.Pointer[func(string) func()] + +//go:linkname godebug_setUpdate internal/godebug.setUpdate +func godebug_setUpdate(update func(string, string)) { + p := new(func(string, string)) + *p = update + godebugUpdate.Store(p) + godebugNotify(false) +} + +//go:linkname godebug_setNewIncNonDefault internal/godebug.setNewIncNonDefault +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) { + p := new(func(string) func()) + *p = newIncNonDefault + godebugNewIncNonDefault.Store(p) +} + +// A godebugInc provides access to internal/godebug's IncNonDefault function +// for a given GODEBUG setting. +// Calls before internal/godebug registers itself are dropped on the floor. +type godebugInc struct { + name string + inc atomic.Pointer[func()] +} + +func (g *godebugInc) IncNonDefault() { + inc := g.inc.Load() + if inc == nil { + newInc := godebugNewIncNonDefault.Load() + if newInc == nil { + return + } + inc = new(func()) + *inc = (*newInc)(g.name) + if raceenabled { + racereleasemerge(unsafe.Pointer(&g.inc)) + } + if !g.inc.CompareAndSwap(nil, inc) { + inc = g.inc.Load() + } + } + if raceenabled { + raceacquire(unsafe.Pointer(&g.inc)) + } + (*inc)() +} + +func godebugNotify(envChanged bool) { + update := godebugUpdate.Load() + var env string + if p := godebugEnv.Load(); p != nil { + env = *p + } + if envChanged { + reparsedebugvars(env) + } + if update != nil { + (*update)(godebugDefault, env) + } +} + +//go:linkname syscall_runtimeSetenv syscall.runtimeSetenv +func syscall_runtimeSetenv(key, value string) { + setenv_c(key, value) + if key == "GODEBUG" { + p := new(string) + *p = value + godebugEnv.Store(p) + godebugNotify(true) + } +} + +//go:linkname syscall_runtimeUnsetenv syscall.runtimeUnsetenv +func syscall_runtimeUnsetenv(key string) { + unsetenv_c(key) + if key == "GODEBUG" { + godebugEnv.Store(nil) + godebugNotify(true) + } +} + +// writeErrStr writes a string to descriptor 2. +// +//go:nosplit +func writeErrStr(s string) { + write(2, unsafe.Pointer(unsafe.StringData(s)), int32(len(s))) +} + +// auxv is populated on relevant platforms but defined here for all platforms +// so x/sys/cpu can assume the getAuxv symbol exists without keeping its list +// of auxv-using GOOS build tags in sync. +// +// It contains an even number of elements, (tag, value) pairs. +var auxv []uintptr + +func getAuxv() []uintptr { return auxv } // accessed from x/sys/cpu; see issue 57336 |