Skip to content

Deduplicate minipal thread ID TLS cache. - #131991

Merged
jkotas merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/fix-minipal-tls-inflation
Aug 11, 2026
Merged

Deduplicate minipal thread ID TLS cache.#131991
jkotas merged 3 commits into
dotnet:mainfrom
lateralusX:lateralusX/fix-minipal-tls-inflation

Conversation

@lateralusX

@lateralusX lateralusX commented Aug 7, 2026

Copy link
Copy Markdown
Member

Summary

Deduplicate the TLS cache used by minipal_get_current_thread_id.

Fixes #131954.

Root cause

minipal_get_current_thread_id previously declared its cached thread ID as a function-local static thread_local variable in thread.h.

Because the function has internal linkage, each translation unit using it could emit a separate 8-byte TLS slot. Enabling the in-process crash reporter added another consumer, increasing libcoreclr.so's TLS footprint enough to exceed glibc's optional static TLS allocation on Linux ARM64.

This caused glibc to resolve CoreCLR TLS accesses through _dl_tlsdesc_dynamic instead of _dl_tlsdesc_return, adding overhead to allocation, thread-static access, and other common runtime paths.

Changes

  • Move the cached thread ID into a single minipal compilation unit.
  • Externally declare the shared TLS variable from thread.h.
  • Use a common macro for the C and C++ TLS storage-class spellings.

Validation

A test with two independent C and C++ translation units showed:

  • Before: two local 8-byte TLS slots and a 16-byte TLS segment.
  • After: one shared 8-byte TLS slot.
  • GCC C++ emits no TLS dynamic-initialization relocation for the shared variable.

Move the cached thread ID into a single minipal compilation unit
instead of emitting one TLS slot per translation unit that
includes thread.h.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @VSadov
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Deduplicates the TLS-backed cache used by minipal_get_current_thread_id() by moving the cached thread ID out of a function-local TLS and into a single TLS variable defined in a dedicated minipal compilation unit.

Changes:

  • Introduces MINIPAL_THREAD_LOCAL and uses it to declare a shared TLS variable minipal_cached_thread_id in thread.h.
  • Adds thread.c to define minipal_cached_thread_id (under the same WASM/reentrancy guards).
  • Updates minipal’s CMake source list to compile thread.c on Unix hosts.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/native/minipal/thread.h Switches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.c Defines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txt Ensures thread.c is built into minipal on Unix.

Comment thread src/native/minipal/thread.h Outdated
Copilot AI review requested due to automatic review settings August 7, 2026 10:45
@EgorBo

EgorBo commented Aug 7, 2026

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64 --filter "System.Tests.Perf_UInt16.Parse*"

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/native/minipal/CMakeLists.txt
Copilot AI review requested due to automatic review settings August 7, 2026 11:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@AndyAyersMS

Copy link
Copy Markdown
Member

For some reason egorbot failed on main, but passed on the PR

Method Toolchain value Mean Error Ratio Allocated Alloc Ratio
Parse /main/corerun 0 NA NA ? NA ?
Parse /PR_131991/corerun 0 5.631 ns 0.0008 ns 1.00 - NA
Parse /main/corerun 12345 NA NA ? NA ?
Parse /PR_131991/corerun 12345 8.717 ns 0.0013 ns 1.00 - NA
Parse /main/corerun 65535 NA NA ? NA ?
Parse /PR_131991/corerun 65535 8.852 ns 0.0014 ns 1.00 - NA

PR results look similar to the fast "before" results we have from the lab

image image

Comment thread src/native/minipal/thread.h
@mdh1418

mdh1418 commented Aug 11, 2026

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

using BenchmarkDotNet.Attributes;
 
public class Perf_Enum
{
    [Benchmark]
    [Arguments(DayOfWeek.Wednesday, "x")]
    public string ToString_Format_NonFlags(DayOfWeek value, string format) =>
        value.ToString(format);
}

@jkotas
jkotas merged commit 0abc149 into dotnet:main Aug 11, 2026
172 of 174 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 12, 2026
mdh1418 added a commit that referenced this pull request Aug 13, 2026
Addresses
#131991 (comment)

Deduplicate the helper functions currently defined with internal linkage
in  src/native/minipal  headers.

The affected helpers remain defined as  inline  in their headers so
callers can inline them, but paired  .c  files now provide one external
fallback definition for cases where the compiler emits a call instead.
In C, each paired source file does this by including the inline
definition and then redeclaring the function with  extern.

## C inline linkage

A plain C  inline  definition does not necessarily emit an externally
linkable function. At higher optimization levels, the compiler may
substitute the header implementation directly at the call site, but at
lower optimization levels, or whenever it chooses not to inline, the
generated code may call an external symbol.

Each paired source file therefore follows this pattern:
```
#include "header.h"
 
extern return_type function(arguments);
```

The header supplies the function body, and the extern redeclaration
causes that translation unit to provide the external definition required
by non-inlined callers. This preserves access to the inline
implementation while avoiding a private `static`  copy in every
translation unit.

## Out-of-line helpers

Based on review feedback, the following helpers are not sufficiently
performance-sensitive to justify retaining their implementations in
headers:

 -  minipal_getexepath 
 -  minipal_get_current_thread_id_no_cache 
 -  minipal_set_thread_name 

Their implementations now live in  getexepath.c  and  thread.c , and
their headers contain declarations only.

 minipal_get_current_thread_id  remains inline because its common path
is a TLS lookup and branch. It calls the out-of-line uncached
implementation only when the TLS cache is empty.

Moving  minipal_set_thread_name  and the uncached thread-ID
implementation into  thread.c  also keeps  _GNU_SOURCE  source-local.
Arbitrary consumers of  thread.h  no longer compile code requiring
GNU-only declarations.

## Executable-path configuration

The executable-path implementation uses  getauxval(AT_EXECFN)  as a
Linux fallback when  /proc/self/exe  cannot be resolved. Availability
was previously determined by component-specific generated configuration
headers, which were not available to minipal’s source file.

Minipal now performs its own  getauxval  capability check and exposes
the result through  minipalconfig.h . Because  minipal_getexepath  has
one out-of-line implementation, all callers now use the same
capability-tested behavior regardless of optimization level or consumer
configuration.

## CPUID linker symbol names

The CPUID fallback helpers retain their source-level names,  `__cpuid` 
and `__cpuidex` , to match the corresponding compiler intrinsics. Those
names were harmless while the functions were  `static` , because each
definition had translation-unit-local linkage.

Providing external fallback definitions under those names would export
reserved double-underscore symbols and could collide with compiler
headers or compatibility shims. Assembler-name labels are therefore used
to assign minipal-owned linker names:

 `inline void __cpuid(...) __asm("minipal_cpuid");`
 `inline void __cpuidex(...) __asm("minipal_cpuidex");`

This preserves the existing source-level API while emitting the external
symbols as  minipal_cpuid  and  minipal_cpuidex . These labels are
separate from the inline assembly inside the function bodies that
executes the CPUID instruction.

Validation

- Built  clr+libs+host  for Linux x64 Debug.
- Verified GCC and Clang C consumers link at  -O0  using the external
definitions.
 - Verified optimized consumers can use the inline definitions.
 - Verified C++ consumers use compatible C-linkage symbols.
- Verified the minipal archive provides the expected external helper
symbols.
- Verified the shipped archives expose  minipal_cpuid  and
 minipal_cpuidex  rather than strong  __cpuid  and  __cpuidex  symbols.
- Verified no  _SOURCE  or  _INLINE  implementation-control macros
remain.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Perf] Linux/arm64: 624 Regressions on 7/29/2026 2:14:39 AM +00:00

6 participants