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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@lateralusXlateralusX 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.

CopilotAI 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.

FileDescription
src/native/minipal/thread.hSwitches the cache from a function-local TLS static to an extern TLS variable and adds a TLS macro.
src/native/minipal/thread.cDefines the shared TLS variable so all translation units refer to the same TLS slot.
src/native/minipal/CMakeLists.txtEnsures thread.c is built into minipal on Unix.

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

Copy link
Copy Markdown
Member

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

CopilotAI 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 threadsrc/native/minipal/CMakeLists.txt
CopilotAI review requested due to automatic review settings August 7, 2026 11:04

CopilotAI 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

MethodToolchainvalueMeanErrorRatioAllocatedAlloc Ratio
Parse/main/corerun0NANA?NA?
Parse/PR_131991/corerun05.631 ns0.0008 ns1.00-NA
Parse/main/corerun12345NANA?NA?
Parse/PR_131991/corerun123458.717 ns0.0013 ns1.00-NA
Parse/main/corerun65535NANA?NA?
Parse/PR_131991/corerun655358.852 ns0.0014 ns1.00-NA

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

imageimage

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

Copy link
Copy Markdown
Member

@EgorBot -linux_arm64

usingBenchmarkDotNet.Attributes;publicclassPerf_Enum{[Benchmark][Arguments(DayOfWeek.Wednesday,"x")]publicstringToString_Format_NonFlags(DayOfWeekvalue,stringformat)=>value.ToString(format);}

@jkotas
jkotas merged commit 0abc149 into dotnet:mainAug 11, 2026
172 of 174 checks passed
@dotnet-milestone-botdotnet-milestone-botBot 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 freeto 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

@lateralusX@EgorBo@AndyAyersMS@mdh1418@jkotas