Skip to content

Add pico_tls library providing thread local variable support#2910

Open
kilograham wants to merge 1 commit intodevelopfrom
tls-support
Open

Add pico_tls library providing thread local variable support#2910
kilograham wants to merge 1 commit intodevelopfrom
tls-support

Conversation

@kilograham
Copy link
Copy Markdown
Contributor

  • Works on GCC/llvm, newlib/picolibc, Arm/RISC-V
  • Provides picolibc style functions for FreeRTOS (and perhaps others to use)
  • Attempts to minimize runtime overhead when not used:
    • note linker scripts are not competent enough to allow us to elide code based on the emptiness of sections
    • on Arm we have a signal of whether __aeabi_read_tp or __emutls_get_address are called (we dont use this to change the link, we just do lazy initializtion from these methods which removes most oof the code if there are no thread local variables (since the methods aren't ever called and we use gc-sections)
    • on RISC-V theres not much we can do since TP reg is used
    • support for resetting thread locals on core 1 re-init is optional
    • thread local support can be dialed back if not needed (see below)
  • Three types of TLS support are provided:
    • per_thread (default) - proper per core (and potentially RTOS task) values for __thread variables
    • global - __thread is supported but only a single shared value is used
    • none - programs using __thread may fail to link and runtime results are undefined
  • Note that tdata/tbss source data sections can be used if present
  • Note that emutls source data sections can be used if present
  • Code will still compile with older linker scripts missing the tls sections
  • In either case two linear RAM sections .tls0 and .tls1 are used, and _init_tls is used to copy the relevant source data from there
  • Note .tdata/.tbss sit between .data/.bss so that they are copied/cleared correctly by crt0. This was the case before, but I've recombined the separate linker script fragments to make this more concrete
  • The CMake build has code to detect emutls vs __aeabi_read_tp on Arm which allows it to pass defines indicating support is only needed for one type. Of course, other build systems or pico_clib_interfaces are free to define these variables too, but they are not required for correct behavior (they may just save space)
  • Note that the code was basde somewhat on earlier separate PRs. Despite being somewhat related to the c library being used, it is orthogonal in the pico-sdk code since it really is a compiler decision. If a particular pico_clib_interface wants to influence the behavior, it can do so via #defines

PER_THREAD tls mode

  • picolibc style tls funcs (_tls_size(), _init_tls()) work based on either tdata/tbss or emutls source data for the latter the emutls_objects are walked to figure out a linear size
  • two sections .tls0 and .tls1 are added to RAM to hold tdata and tbss for the two cores
  • .tls1 is re-initialized (or rather re-initialization is marked but deferred via per core runtime_init by default so values are reset on core 1 reset). There is a define to turn this off to save a tiny amount of space if not needed
  • note that .tbss actually currently wasted space in RAM in this scenario which is annoying but I think common to most runtimes
  • note similarly .tdata need not be in RAM here either as it is not directly
  • note w.r.t. to the last two points I don't want to rock the boar in terms of other code that may use these sections (e.g. other RTOS)
  • on RISC-V TP is initialed per core to .tls0 and .tls1

GLOBAL tls mode

  • the __aeabi_read_tp() value returned always points at .tdata/.tbss
  • RISC-V TP reg is initialized to .tdata./.tbss on core init
  • emutls falls back to the c library
  • the .tls0 and .tls1 sections are elided from the output via some sorcery
  • _tls_size() returns 0 to prevent anyone else allocating space

NONE tls mode

  • the .tls0 and .tls1 sections are elided from the output via some sorcery
  • no other code is included, so you are left with whatever c library support there is, but without crt having initialized it correctly. in other words, it ain't gonna work right!

* Works on GCC/llvm, newlib/picolibc, Arm/RISC-V
* Provides picolibc style functions for FreeRTOS (and perhaps others to use)
* Attempts to minimize runtime overhead when not used:
   - note linker scripts are not competent enough to allow us to elide code
     based on the emptiness of sections
   - on Arm we have a signal of whether __aeabi_read_tp or __emutls_get_address
     are called (we dont use this to change the link, we just do lazy
     initializtion from these methods which removes most oof the code if there
     are no thread local variables (since the methods aren't ever called and
     we use gc-sections)
   - on RISC-V theres not much we can do since TP reg is used
   - support for resetting thread locals on core 1 re-init is optional
   - thread local support can be dialed back if not needed (see below)
* Three types of TLS support are provided:
   - per_thread (default) - proper per core (and potentially RTOS task) values for
                  __thread variables
   - global - __thread is supported but only a single shared value is used
   - none - programs using __thread may fail to link and runtime results are
            undefined
* Note that tdata/tbss source data sections can be used if present
* Note that emutls source data sections can be used if present
* Code will still compile with older linker scripts missing the tls sections
* In either case two linear RAM sections .tls0 and .tls1 are used, and _init_tls
  is used to copy the relevant source data from there
* Note .tdata/.tbss sit between .data/.bss so that they are copied/cleared
  correctly by crt0. This was the case before, but I've recombined the
  separate linker script fragments to make this more concrete
* The CMake build has code to detect emutls vs __aeabi_read_tp on Arm which
  allows it to pass defines indicating support is only needed for one type.
  Of course, other build systems or pico_clib_interfaces are free to define
  these variables too, but they are not required for correct behavior (they
  may just save space)
* Note that the code was basde somewhat on earlier separate PRs. Despite
  being somewhat related to the c library being used, it is orthogonal
  in the pico-sdk code since it really is a compiler decision. If a particular
  pico_clib_interface wants to influence the behavior, it can do so via
  #defines

PER_THREAD tls mode
   - picolibc style tls funcs (_tls_size(), _init_tls()) work based on either
     tdata/tbss or emutls source data for the latter the emutls_objects are
     walked to figure out a linear size
   - two sections .tls0 and .tls1 are added to RAM to hold tdata and tbss
     for the two cores
   - .tls1 is re-initialized (or rather re-initialization is marked but deferred
     via per core runtime_init by default so values are reset on core 1 reset).
     There is a define to turn this off to save a tiny amount of space if
     not needed
   - note that .tbss actually currently wasted space in RAM in this scenario
     which is annoying but I think common to most runtimes
   - note similarly .tdata need not be in RAM here either as it is not directly
   - note w.r.t. to the last two points I don't want to rock the boar
     in terms of other code that may use these sections (e.g. other RTOS)
   - on RISC-V TP is initialed per core to .tls0 and .tls1

GLOBAL tls mode
   - the __aeabi_read_tp() value returned always points at .tdata/.tbss
   - RISC-V TP reg is initialized to .tdata./.tbss on core init
   - emutls falls back to the c library
   - the .tls0 and .tls1 sections are elided from the output via some sorcery
   - _tls_size() returns 0 to prevent anyone else allocating space

NONE tls mode
   - the .tls0 and .tls1 sections are elided from the output via some sorcery
   - no other code is included, so you are left with whatever c library
     support there is, but without crt having initialized it correctly. in
     other words, it ain't gonna work right!

Co-authored-by: Keith Packard <keithp@keithp.com>
Co-authored-by: Al <alastairpatrick@gmail.com>
@kilograham kilograham added this to the 2.2.1 milestone Apr 25, 2026
@kilograham kilograham requested a review from will-v-pi April 25, 2026 01:38
@kilograham
Copy link
Copy Markdown
Contributor Author

cc @alastairpatrick @keith-packard

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