-
Notifications
You must be signed in to change notification settings - Fork 49
runtime: stabilize goroutine hooks and GOROOT classifications #2421
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
6f1a070
8eb7c31
9d1b927
106df34
e0b723e
6d766f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| //go:build darwin || linux | ||
|
|
||
| package runtime | ||
|
|
||
| import ( | ||
| _ "unsafe" | ||
|
|
||
| psync "github.com/xgo-dev/llgo/runtime/internal/clite/pthread/sync" | ||
| ) | ||
|
|
||
| var procPinOnce psync.Once | ||
| var procPinMu psync.Mutex | ||
|
|
||
| func initProcPinMu() { | ||
| procPinMu.Init(nil) | ||
| } | ||
|
|
||
| // LLGo has no Go P to pin a goroutine to. Serialize procPin regions instead, | ||
| // preserving the exclusion that sync.Pool and sync/atomic.Value require. | ||
| func procPin() int { | ||
| procPinOnce.Do(initProcPinMu) | ||
| procPinMu.Lock() | ||
| return 0 | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Panic-safety of the global serialization (minor / latent).
Both hold under the current 1:1 goroutine↔OS-thread backend, but a future M:N scheduler change could silently break them. Consider a short comment recording these two invariants (and a "must not panic while pinned" note). |
||
|
|
||
| func procUnpin() { | ||
| procPinMu.Unlock() | ||
| } | ||
|
|
||
| //go:linkname syncRuntimeProcPin sync.runtime_procPin | ||
| func syncRuntimeProcPin() int { | ||
| return procPin() | ||
| } | ||
|
|
||
| //go:linkname syncRuntimeProcUnpin sync.runtime_procUnpin | ||
| func syncRuntimeProcUnpin() { | ||
| procUnpin() | ||
| } | ||
|
|
||
| //go:linkname atomicRuntimeProcPin sync/atomic.runtime_procPin | ||
| func atomicRuntimeProcPin() int { | ||
| return procPin() | ||
| } | ||
|
|
||
| //go:linkname atomicRuntimeProcUnpin sync/atomic.runtime_procUnpin | ||
| func atomicRuntimeProcUnpin() { | ||
| procUnpin() | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -357,7 +357,7 @@ func TestGoRootRunCases(t *testing.T) { | |
| } | ||
| switch { | ||
| case err == nil && notApply: | ||
| t.Fatalf("unexpected success for not-applicable case: %s", notApplyReason) | ||
| t.Logf("not-applicable case passed: %s", notApplyReason) | ||
| case err == nil && match: | ||
| t.Fatalf("unexpected success for xfail case: %s", reason) | ||
| case err == nil && flaky: | ||
|
|
@@ -378,7 +378,8 @@ func TestGoRootRunCases(t *testing.T) { | |
| func writeStdlibImportCfg(t *testing.T, goCmd string) string { | ||
| t.Helper() | ||
| cmd := exec.Command(goCmd, "list", "-export", "-f", "{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}", "std") | ||
| cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") | ||
| cmd.Dir = t.TempDir() | ||
| cmd.Env = baselineGoEnv() | ||
| output, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| t.Fatalf("list stdlib exports with %s: %v\n%s", goCmd, err, output) | ||
|
|
@@ -406,7 +407,7 @@ func repoRoot(t *testing.T) string { | |
| func loadToolchainEnv(t *testing.T, goCmd string) toolchainEnv { | ||
| t.Helper() | ||
| cmd := exec.Command(goCmd, "env", "-json", "GOOS", "GOARCH", "GOVERSION", "CGO_ENABLED") | ||
| cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") | ||
| cmd.Env = baselineGoEnv() | ||
| var stdout bytes.Buffer | ||
| var stderr bytes.Buffer | ||
| cmd.Stdout = &stdout | ||
|
|
@@ -786,6 +787,8 @@ func runnerEnv(repoRoot, goroot, gopath string, extra []string) []string { | |
| env[i] = "GOENV=off" | ||
| case strings.HasPrefix(item, "GOFLAGS="): | ||
| env[i] = "GOFLAGS=" | ||
| case strings.HasPrefix(item, "GOTOOLCHAIN="): | ||
| env[i] = "GOTOOLCHAIN=local" | ||
| case strings.HasPrefix(item, "LLGO_ROOT="): | ||
| env[i] = "LLGO_ROOT=" + repoRoot | ||
| case strings.HasPrefix(item, "GOPATH="): | ||
|
|
@@ -803,6 +806,7 @@ func runnerEnv(repoRoot, goroot, gopath string, extra []string) []string { | |
| env = appendIfMissing(env, "GOROOT="+goroot) | ||
| env = appendIfMissing(env, "GOENV=off") | ||
| env = appendIfMissing(env, "GOFLAGS=") | ||
| env = appendIfMissing(env, "GOTOOLCHAIN=local") | ||
| env = appendIfMissing(env, "LLGO_ROOT="+repoRoot) | ||
| env = appendIfMissing(env, "GOPATH="+gopath) | ||
| env = appendIfMissing(env, "GO111MODULE=off") | ||
|
|
@@ -812,6 +816,13 @@ func runnerEnv(repoRoot, goroot, gopath string, extra []string) []string { | |
| return env | ||
| } | ||
|
|
||
| func baselineGoEnv() []string { | ||
| env := append([]string{}, os.Environ()...) | ||
| env = upsertEnv(env, "GOENV=off") | ||
| env = upsertEnv(env, "GOFLAGS=") | ||
| return upsertEnv(env, "GOTOOLCHAIN=local") | ||
| } | ||
|
|
||
| func appendIfMissing(env []string, kv string) []string { | ||
| key := strings.SplitN(kv, "=", 2)[0] + "=" | ||
| for _, item := range env { | ||
|
|
@@ -2066,7 +2077,20 @@ func (cfg xfailConfig) Match(goVersion, platform string, tc testCase) (bool, str | |
| } | ||
|
|
||
| func (cfg notApplicableConfig) Match(goVersion, platform string, tc testCase) (bool, string) { | ||
| return matchEntries(cfg.Entries, goVersion, platform, tc) | ||
| for _, entry := range cfg.Entries { | ||
| // Applicability is an LLGo design property, not an observation tied to | ||
| // one hosted platform or Go release. Keep accepting legacy selectors in | ||
| // the YAML, but intentionally do not use them when classifying a case. | ||
| if !matchEntry("", "", entry.Directive, entry.Case, goVersion, platform, tc) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ignored |
||
| continue | ||
| } | ||
| reason := entry.Reason | ||
| if reason == "" { | ||
| reason = entry.Case | ||
| } | ||
| return true, reason | ||
| } | ||
| return false, "" | ||
| } | ||
|
|
||
| func (cfg xfailConfig) MatchFlaky(goVersion, platform string, tc testCase) (bool, string) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per-call
pthread_onceon a hot path (minor perf).psync.Once.Dolinks to libcpthread_once, so everysync.Pool/sync/atomic.Valueoperation that hitsruntime_procPinnow pays apthread_oncecall in addition to the mutex lock.pthread_onceis cheap after first completion, but it's avoidable:pthread_mutex_init(nil)is equivalent toPTHREAD_MUTEX_INITIALIZER, soprocPinMucould be eagerly/statically initialized (or initialized once in aninit/first-getg) and theOnceguard dropped entirely.Separately worth flagging in a comment or tracking issue: the single process-wide
procPinMuturns what is a contention-free per-P op in upstream Go into a globally serialized critical section, which becomes a scalability ceiling forsync.Pool-heavy concurrent workloads. This is the deliberate correctness tradeoff noted in the comment above — just calling out the ceiling explicitly.