Proposal Details

Proposal: Support General Dynamic TLS Model

Summary

This proposal aims to add support for the general dynamic (GD) Thread Local Storage (TLS) model to the Go runtime, to resolve issue 54805. The current implementation only supports the initial exec (IE) and local exec (LE) TLS models, which restricts interoperability with certain C libraries (particularly Musl) and prevents loading Go shared libraries without LD_PRELOAD.

A more detailed design document is available at: Proposal: Go general dynamic TLS

There is a prototype implementation for Arm64 Linux in review 644975.

Problem

The Go runtime uses Thread Local Storage to preserve goroutine state (g) when interacting with C code, but lacks support for the general dynamic TLS model. This limitation:

  1. Hinders interoperability with C libraries that have strict TLS allocation schemes (e.g., Musl)
  2. Restricts loading of Go shared libraries without using LD_PRELOAD

Proposed Changes

Assembler and Linker Changes

  • Add a new flag -tls=[IE,LE,GD] to the assembler to explicitly select the TLS model
  • Default to -tls=IE for shared mode, while cmd/go specifies -tls=GD explicitly when needed
  • Add target-specific relocations in the linker depending on target architecture

Runtime Changes

  • Update references to thread-local variables in runtime assembly code

Command Changes

  • Modify cmd/go to automatically enable the general dynamic TLS model when required, based on the combination of GOOS/GOARCH and buildmode
  • Pass -D=TLS_GD and -tls=GD option to assembler to enable the GD model

Initial Implementation Focus

  • Arm64 architecture on Linux systems
  • buildmode=c-shared and buildmode=c-archive

Please refer to the full design document for more detailed information on the implementation approach and technical considerations.

Comment From: ianlancetaylor

CC @cherrymui @golang/compiler

Comment From: gabyhelp

Related Issues

Related Code Changes

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

Comment From: gopherbot

Change https://go.dev/cl/644615 mentions this issue: design: add 71953-go-dynamic-tls.md

Comment From: tudor-timcu

Hello! Is there a plan to support this for x86_64? I hit this problem recently on Alpine Linux and was wondering if there's a fix in sight in the near future? Thank you!

Comment From: amusman

Hello, While there isn't a concrete timeline for x86_64 support yet, the proposed design will accommodate it. Specifically, we can pass the same -D=TLS_GD flag and -tls=GD option on x86_64, and then implement the architecture-specific parts under these conditional flags. The initial implementation focuses on Arm64 and allows to extend support to new architectures incrementally.

Comment From: aclements

This proposal has been added to the active column of the proposals project and will now be reviewed at the weekly proposal review meetings. — aclements for the proposal review group

Comment From: cherrymui

Thanks for the proposal. As you mainly focus on the c-shared and c-archive build modes, I wonder if we can just switch to the General Dynamic TLS model by default for these build modes. The only downside I can think of is a slightly longer code sequence for the TLS access, which I think is fine, as we don't do it often. And I believe some C toolchain, at least for some platforms, can optimize the GD TLS access to IE or LE when appropriate (e.g. linking an archive to a static executable). This way we can do it transparently without any flags. (We could have a debugging flag for the compiler and assembler, to help identify issues, especially for the transition period, but general users are not expected to set.)

Thanks.

Comment From: amusman

I looked at cmd/go, it appears already passing -buildmode= to linker, and adding the same flag for assembler (instead of specific -tls=) would work fine. Some targets might in addition pass some preprocessor flag like -D=GOBUILDMODE_cshared=1 to customize the assembly implementing tls variable access (e.g. temporarily save link register across tls-stub call, leaving the function itself "leaf"). We could pass such flag as well for those targets from cmd/go. Or, alternately, assembler would have to treat functions with access to tls variables like non-leaf in corresponding build mode. By the way - please correct me if I'm wrong - it seems currently compiler does not need to know about which tls mode used, the only supported use cases of tls variables are in assembly code. Thanks!

Comment From: amusman

On the other hand, If we replace -tls= with -buildmode= in this design, we'd need to consider how that interacts with assembler's flags -shared and -dynlink - would we occasionally remove them from the assembler but keep them for the compiler? This would create an asymmetry where the compiler and assembler accept different flags for the same package, and might duplicate some of cmd/go's flag generation. Just adding -tls= to the assembler seems to fit cleaner into the current scheme where cmd/go handles GOOS/GOARCH-specific logic and translates buildmode into corresponding compiler/assembler flags like -shared/-dynlink (and possibly -tls=).

Comment From: cherrymui

We can arrange the go command to pass whatever necessary flags to the compiler/assembler/linker. They are considered implementation details, and don't necessarily require a proposal.

A -buildmode flag sounds reasonable. We can adjust (or remove) the exisiting -shared and -dynlink flags. If we do any change, we can do the same for the compiler and assembler. Redundant flags are also okay, e.g. -buildmode=c-shared implies -shared, but we can also allow passing both. (There are some build systems that directly invoke the compiler/asssembler/linker. That doesn't block us from changing the compiler/assembler/linker flags. If possible, we can support both old and new flags, to make the transition easier.)

Assembler macros like -D=GOBUILDMODE_cshared=1 is also okay. If possible, I'd hope to treat it transparently and let the assembler take care of it. On a number of architectures we have a dedicated temporary register for the assembler to use. It perhaps also okay to treat TLS access containing functions non-leaf in some build modes.

By the way - please correct me if I'm wrong - it seems currently compiler does not need to know about which tls mode used, the only supported use cases of tls variables are in assembly code.

The code dealing with TLS storage is in cmd/internal/obj/..., which is used in both the compiler (cmd/compile) and the assembler (cmd/asm) (as the two executables). They share some flags. For this, the go command could pass the same flag to both the assembler and the compiler.

Comment From: gopherbot

Change https://go.dev/cl/698796 mentions this issue: cmd/asm,cmd/compile,cmd/go: pass buildmode to assembler and compiler