Skip to content

compiler/runtime: add llgo:tls and llgo:gls variable storage#2076

Closed
cpunion wants to merge 2 commits into
codex/runtime-g-statefrom
codex/threadlocal-getg
Closed

compiler/runtime: add llgo:tls and llgo:gls variable storage#2076
cpunion wants to merge 2 commits into
codex/runtime-g-statefrom
codex/threadlocal-getg

Conversation

@cpunion

@cpunion cpunion commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Problem

PR #2074 consolidates per-goroutine runtime state behind getg, but getg still uses the generic pthread-key TLS handle. That path performs pthread_getspecific on access and allocates a separate slot on first use. LLGo also has no first-class package-variable model for thread-owned versus goroutine-owned state.

Without this PR, runtime code must keep hand-written TLS handles, dynamic local initializers cannot follow package initialization semantics, pointer-bearing native TLS has no compiler-managed GC lifetime, and cross-package/cache builds cannot preserve locality metadata.

Proposal

The proposal is tracked in #2077. It adds:

Directive Source owner Current lowering Future M:N lowering
//llgo:tls OS thread LLVM native TLS LLVM native TLS
//llgo:gls Go goroutine LLVM native TLS storage owned by g

TLS and GLS intentionally remain distinct in compiler and cache metadata even though the current one-pthread-per-goroutine runtime lowers both to native TLS. currentG is TLS because it bootstraps future GLS lookup.

Implementation

  • Unifies go:linkname, LLGo type/locality, and export comment parsing behind one directive parser. The normal build scans each package object once during preload; the direct compiler entry point only performs the fallback scan when needed.
  • Stores linkname, type background, locality, initializer, and ensure symbols in one mutex-protected declaration record.
  • Emits native LLVM TLS globals for both directives, including distinct storage for zero-sized variables and correct external declarations for cross-package access.
  • Synthesizes per-owner initializer helpers and three-state TLS guards. Recursive access sees partial state; a panicking initializer is not retried. The initial thread uses the ordinary package initializer exactly once.
  • Pre-registers every local global before compiling package init, including variables whose names sort after init, so initial-thread side effects cannot run twice.
  • Registers pointer-bearing TLS ranges as BDWGC roots. One pthread-owned list removes all ranges on thread exit; nogc builds use a no-op registration function.
  • Serializes declaration records in stable order in package cache manifests. Cache hits validate package ownership and enum values, reject duplicates/conflicts, and atomically merge all records before dependent SSA compilation.
  • Migrates runtime getg from tls.Alloc[g] to //llgo:tls var currentG g. Unsupported cross/embedded targets report a compile-time diagnostic.

Verification

All local commands used GOMAXPROCS=2, GOMEMLIMIT=2GiB, and -p=1.

  • Go 1.24.11: go test -timeout=45m ./... passed, including cl, internal/build, internal/cabi, ssa, test/go, GOROOT, and std wrapper tests.
  • Full package coverage passed: cl 95.0%, internal/build 71.0%, ssa 87.9%.
  • Local changed-line estimate: 683 executable lines, 632 fully covered, 18 partially covered, 33 uncovered; 95.17% covered-or-partial and 92.53% fully-covered-only.
  • The tlsgls acceptance test covers cross-package TLS/GLS access, independent main/two-goroutine values, dynamic and recursive initialization, panic poisoning, a late-sorted initializer, GC pointer survival, and thread-exit cleanup.
  • The same acceptance program passes with GC and -tags=nogc.
  • A cold build reports a cache miss for tlsgls/state; the second build reports CACHE HIT and produces identical output. The main package remains a cache miss by existing policy.
  • Optimized Darwin arm64 getg resolves native TLV addresses and checks the local guard; its hot path has no pthread_getspecific and no heap-backed TLS slot.

Stack

This draft is stacked on #2074 (codex/runtime-g-state). The base branch in xgo-dev mirrors the current #2074 head and will be replaced with main after #2074 merges. Darwin and Linux CI must both pass before it is ready for review.

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.63415% with 52 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/build/collect.go 67.74% 25 Missing and 5 partials ⚠️
internal/build/build.go 48.00% 8 Missing and 5 partials ⚠️
ssa/package.go 91.42% 5 Missing and 1 partial ⚠️
ssa/decl.go 70.00% 2 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion

cpunion commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

Closing this draft because the implementation branch must live in the contributor fork. The local work is retained and a replacement draft PR will be opened from cpunion/llgo after local CI and coverage verification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant