Skip to content

Thread-Local Storage

Thread-local storage is addressed through the GS0 segment register. TLS references that remain inside the program and have a link-time constant offset use the local-exec model. TLS references that cross dynamic object boundaries, may be interposed, or otherwise require runtime resolution use the TLSDESC model.

TLS Base Model

GS0 is the TLS base register. A TLS offset is translated by GS0 segment pre-translation before page-table translation. The local-exec model encodes a link-time constant byte offset from the GS0 base; TLSDESC supplies runtime resolution when the reference crosses a dynamic-object boundary or remains interposable.

TLS Relocation Families

Model Relocations
local-exec R_BEDROCK_TLS_OFFSET32S; R_BEDROCK_TLS_OFFSET64
TLSDESC R_BEDROCK_TLSDESC_GOTPCREL32S; R_BEDROCK_TLSDESC_GOTPCREL64; R_BEDROCK_TLSDESC_CALL; R_BEDROCK_TLSDESC

Taking the address of a TLS object returns an ordinary pre-segment pointer to that thread instance. Every ABI-permitted GS0 and ordinary-data path consuming that coordinate resolves it to the same byte of that TLS object. Copying a TLS pointer to another thread retains its originating thread instance; the pointer becomes invalid when its originating thread terminates or the defining shared object is unloaded.

Model Selection

  • A TLS symbol defined in the same program image may use local-exec addressing when its byte offset from GS0 is link-time constant.

  • A TLS reference from a shared object or to an interposable TLS symbol uses TLSDESC.

  • The linker may relax a TLSDESC sequence to local-exec when symbol binding and final placement make the GS0-relative offset constant.

  • A source expression S+k uses the descriptor for S; after resolution, the caller adds k to the returned offset. The descriptor relocations encode the base symbol S.

TLSDESC Entry

TLSDESC Entry Layout

Offset Field Type Meaning
+0x00 function u64 near-call address of the resolver or fast function
+0x08 argument u64 signed direct GS0-relative offset or resolver-specific opaque token

Each entry occupies 16 bytes in .got and has 16-byte alignment. The loader resolves the symbol binding, writes both words, and completes any memory ordering required to publish the pair before application code can observe it. Both words are immutable after publication and may subsequently be protected by PT_GNU_RELRO. The resolver and fast function may use loader-private near-call addresses.

The argument word belongs to the implementation of the selected function. A fast function may load [R0+8] and return it directly. An opaque token is loader-owned metadata and remains valid while the object that defines the TLS symbol is loaded. A resolver may perform dynamic per-thread lookup or allocation. The published descriptor remains immutable for both resolver and fast-function paths.

TLSDESC Call ABI

The canonical sequence is exactly:

LEA.Q [PC + descriptor@TLSDESC_GOTPCREL], R0
CALL  [R0]

The LEA.Q places the descriptor address in R0. CALL [R0] reads the 64-bit near-call target from descriptor offset zero and preserves R0, so the called function receives the descriptor address in R0. The canonical sequence places the LEA.Q and CALL adjacently.

The function returns a valid signed byte offset from the GS0 TLS base in R0. It preserves GS0, R8R15, F8F15, V16V31, and P8P15. The linkage protocol catalog identifies the resolver scratch set. Zero is a valid TLS offset. Resolution failure terminates the resolver path.

TLSDESC Relocations

Descriptor address.

R_BEDROCK_TLSDESC_GOTPCREL32S and R_BEDROCK_TLSDESC_GOTPCREL64 apply to the PC-relative displacement field of the canonical LEA.Q and compute the address of the descriptor for STT_TLS symbol S. The source expression has semantic TLS addend zero. Under the PC-relative field-bias rule in Section 6.1, the RELA addend is exactly \(A=\delta\) and contains solely the instruction-field offset.

Call marker.

R_BEDROCK_TLSDESC_CALL has size zero and names the same TLS symbol as the paired TLSDESC GOTPCREL relocation on the immediately preceding LEA.Q. Its addend is zero and its place is byte zero of the exact immediately following CALL [R0]. It is a static-link relaxation marker consumed exclusively by the static linker. A producer places labels and control-flow targets outside both instructions of a relaxable sequence.

Descriptor initialization.

R_BEDROCK_TLSDESC applies only to STT_TLS symbols, has addend zero, and is placed at offset zero of an aligned descriptor. It directs the loader to initialize two adjacent 64-bit words with the selected {function, argument} pair for S; this operation initializes the pair directly. A link or load rejects an unresolved weak TLS symbol because GS0-relative offset zero represents a valid resolved offset. A weak symbol that resolves to a definition is handled normally.

The linker may merge descriptors only when they name the same resolved binding S and have the required zero semantic addend. If any unrelaxed use remains, a final dynamic link emits one aligned pair in .got and the corresponding dynamic R_BEDROCK_TLSDESC. The loader initializes the pair eagerly before publication. A fully static executable relaxes every TLSDESC use; any remaining use makes the static link fail. If all uses are relaxed, the final link discards the descriptor and its initialization relocation.

TLSDESC Local-Exec Relaxation

Relaxation is permitted only after final binding proves that S is non-preemptible and TLS(S) is a link-time constant. It replaces the entire canonical pair while preserving the section size and every following instruction address:

  • With R_BEDROCK_TLSDESC_GOTPCREL32S, the original 7-byte LEA.Q and 4-byte CALL [R0] occupy 11 bytes. If TLS(S) fits signed 32 bits, the linker emits LEN 11, MOV.Q TLS(S), R0, applies R_BEDROCK_TLS_OFFSET32S with addend zero to its 32-bit immediate payload, and writes four zero padding bytes after the natural 7-byte encoding.

  • With R_BEDROCK_TLSDESC_GOTPCREL64, the original 11-byte LEA.Q and 4-byte CALL [R0] occupy 15 bytes. The linker emits LEN 15, MOV.Q TLS(S), R0, applies R_BEDROCK_TLS_OFFSET64 with addend zero to its 64-bit immediate payload, and writes four zero padding bytes after the natural 11-byte encoding.

The linker consumes both code relocations. The removed call may eliminate normal caller-saved clobbers and unobservable resolver stack activity. Code relies only on the returned offset and defined preserved state. A descriptor is allocated exactly when an unrelaxed reference remains.