Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 30, 2026

Description

GetThreadStaticsBase was bypassing ReadyToRun compilation to avoid recursive initialization dependency. This caused all thread static field access on iOS (composite-r2r with interpreter fallback) to execute through the interpreter, resulting in 35x performance degradation.

Changes

Added CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE helper as a normal JIT helper to enable R2R compilation of GetThreadStaticsBase:

  • New JIT helper: JIT_GetDirectOnThreadLocalDataNonGCThreadStaticBase returns &((ThreadLocalData*)&t_ThreadStatics)->pThread - the address of the pThread field within thread-local storage, which serves as the storage location for DirectOnThreadLocalData.pNativeThread
  • Helper type: JITHELPER (not DYNAMICJITHELPER or ReadyToRun helper)
  • prestub.cpp: Modified getHelperForStaticBase to detect DirectOnThreadLocalData MethodTable and use the new helper for READYTORUN_FIXUP_ThreadStaticBaseNonGC
  • Thread.CoreCLR.cs: Removed BypassReadyToRunAttribute from GetThreadStaticsBase
  • JIT interface: Updated version GUID in jiteeversionguid.h
  • ReadyToRun compiler: Added the helper to RequiresRuntimeJitException list - no ReadyToRun constants were changed

The new helper provides direct access to the pThread field in thread local storage without triggering recursive initialization, allowing GetThreadStaticsBase to be compiled into R2R images.

Testing

  • Built and verified CoreCLR with no errors
  • Confirmed all helper enums are consistent across runtime and compiler
  • Verified no ReadyToRun constants were modified

Microsoft Reviewers

Original prompt

This section details on the original issue you should resolve

<issue_title>GetThreadStaticsBase method is not included in R2R image</issue_title>
<issue_description>Thread static helpers were moved to managed code in https://github.com/dotnet/runtime/pull/108167/files. It seems GetThreadStaticsBase has a BypassReadyToRunAttribute as a workaround for some recursive dependency during initialization. On iOS this is relevant because every single thread static field access will now go through interpreter, which is much slower.

On the following microbenchmark, running in composite-r2r configuration with interpreter fallback (jit disabled) is 35x slower than default jit configuration.

using System;
using System.Runtime.CompilerServices;
using System.Threading;

public class Program {
    [ThreadStatic]
    private static int a;

    [MethodImpl(MethodImplOptions.NoInlining)]
    public static int GetA()
    {
        return a;
    }
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static void SetA(int val)
    {
        a = val;
    }

    public static void Main(string[] args)
    {
        a = 1;

        for (int i = 0; i < 500000000; i++)
            SetA(GetA() + 1);
    }
}
```</issue_description>

<agent_instructions>This needs to be fixed by changing the behavior of getHelperForStaticBase when called with a MethodTable that is which is the same as the DIRECTONTHREADLOCALDATA methodtable, and a kind of READYTORUN_FIXUP_ThreadStaticBaseNonGC to return a helper which returns the address of the pThread field that is on the t_ThreadStatics thread local variable. To do this, the change will need to add a new helper to the helpers defined in jithelpers.h, and in the enumeration CorInfoHelpFunc in corinfo.h. That helper needs to be implemented in jithelpers.cpp. In addition, since it is modifying corinfo.h in a way that changes the jit interface it will need to update the guid in jiteeversionguid.h. In addition, the change should update Thread.CoreCLR.cs to remove the BypassReadyToRun attribute which is on GetThreadStaticsBase.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
<comment_new><author>@BrzVlad</author><body>
@davidwrighton Could you please give this a thought ?</body></comment_new>
</comments>


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 4 commits January 30, 2026 20:05
…LocalData

This change adds a new JIT helper to handle thread static access for DirectOnThreadLocalData in ReadyToRun images. The new helper returns the address of the thread local data structure, which is used to access the pNativeThread field. This eliminates the need for the BypassReadyToRun attribute on GetThreadStaticsBase, allowing it to be included in R2R images and improving performance on platforms like iOS that rely on composite-r2r with interpreter fallback.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
…ALBASE

Update the CrossGen2 ReadyToRun compiler to handle the new CORINFO_HELP_READYTORUN_THREADLOCALBASE helper. This helper is used to get the address of the thread local data structure for DirectOnThreadLocalData.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Add the new GetThreadLocalBase helper to the ReadyToRunHelper enum and map it to the CORINFO_HELP_READYTORUN_THREADLOCALBASE helper in the ReadyToRun constants and helper mapping tables.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Corrected the comment to accurately state that the helper returns the address of the thread local data structure, not just the pThread field.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix GetThreadStaticsBase method inclusion in R2R image Fix GetThreadStaticsBase to be included in R2R images Jan 30, 2026
Copy link
Member

@davidwrighton davidwrighton left a comment

Choose a reason for hiding this comment

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

@copilot, please adjust the change to respond to this feedback.

Copilot AI and others added 4 commits January 30, 2026 20:55
…helper

- Changed CORINFO_HELP_READYTORUN_THREADLOCALBASE to CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE as a normal JIT helper
- Renamed JIT_GetThreadLocalBase to JIT_GetDirectOnThreadLocalDataNonGCThreadStaticBase
- Fixed implementation to return address of pThread field instead of entire ThreadLocalData structure
- Reverted all ReadyToRun constant changes (readytorun.h, readytorunhelpers.h, ReadyToRunConstants.cs)
- Removed ReadyToRun compiler awareness of this helper

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Added the CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE helper definition that was missing from jithelpers.h, causing enum mismatch errors.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
JIT_GetDirectOnThreadLocalDataNonGCThreadStaticBase should be a JITHELPER, not a DYNAMICJITHELPER.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
The CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE helper should not be used during R2R compilation and should throw RequiresRuntimeJitException.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Jan 30, 2026

@copilot, please adjust the change to respond to this feedback.

Addressed all feedback in commits 6388684, f699f05, 5964bd5, and c6772e4. Changes made:

  1. Changed to normal JIT helper (not ReadyToRun) with name specific to DirectOnThreadLocalData
  2. Reverted all ReadyToRun constants (readytorun.h, readytorunhelpers.h, ReadyToRunConstants.cs)
  3. Fixed implementation to return address of pThread field: &((ThreadLocalData*)&t_ThreadStatics)->pThread
  4. Changed from DYNAMICJITHELPER to JITHELPER
  5. Added exception handling in ReadyToRun compiler

Copilot AI and others added 2 commits January 30, 2026 21:11
Added CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE to the CorInfoHelpFunc enum in the tools directory.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
Made the comment more precise to clarify that this helper returns the address of the pThread field which serves as the storage location for DirectOnThreadLocalData.pNativeThread thread static field.

Co-authored-by: davidwrighton <10779849+davidwrighton@users.noreply.github.com>
@davidwrighton davidwrighton marked this pull request as ready for review January 30, 2026 23:08
Copilot AI review requested due to automatic review settings January 30, 2026 23:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a critical performance issue where GetThreadStaticsBase bypassed ReadyToRun compilation, causing 35x performance degradation on iOS with composite-r2r and interpreter fallback. The solution introduces a new JIT helper that provides direct access to thread-local storage for the DirectOnThreadLocalData type, enabling R2R compilation of GetThreadStaticsBase.

Changes:

  • Added CORINFO_HELP_GETDIRECTONTHREADLOCALDATA_NONGCTHREADSTATIC_BASE JIT helper with implementation JIT_GetDirectOnThreadLocalDataNonGCThreadStaticBase to return the address of the pThread field in thread-local storage
  • Modified getHelperForStaticBase in prestub.cpp to detect DirectOnThreadLocalData MethodTable and return the new helper for non-GC thread static base fixups
  • Removed BypassReadyToRunAttribute from GetThreadStaticsBase in Thread.CoreCLR.cs to enable R2R compilation
  • Updated JIT interface version GUID to reflect the interface change
  • Added the new helper to RequiresRuntimeJitException list in ReadyToRun compiler to ensure it's used at runtime, not during compilation

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/coreclr/vm/prestub.cpp Added special case detection for DirectOnThreadLocalData to use the new helper during R2R fixup resolution
src/coreclr/vm/jithelpers.cpp Implemented the new helper function that returns the address of pThread field in thread-local storage
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs Added new helper to RequiresRuntimeJitException list to prevent R2R compilation of the helper itself
src/coreclr/tools/Common/JitInterface/CorInfoHelpFunc.cs Added new helper enum value to match corinfo.h
src/coreclr/inc/jithelpers.h Defined the new helper as a JITHELPER mapped to the implementation function
src/coreclr/inc/jiteeversionguid.h Updated JIT interface version GUID to reflect the interface change
src/coreclr/inc/corinfo.h Added new CorInfoHelpFunc enum value for the DirectOnThreadLocalData helper
src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs Removed BypassReadyToRunAttribute and associated comment to enable R2R compilation of GetThreadStaticsBase

davidwrighton and others added 2 commits January 31, 2026 02:40
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…foImpl.ReadyToRun.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@davidwrighton davidwrighton enabled auto-merge (squash) January 31, 2026 02:40
@davidwrighton davidwrighton merged commit 1c57781 into main Jan 31, 2026
99 of 101 checks passed
@jkotas jkotas deleted the copilot/fix-getthreadstaticsbase-in-r2r-image branch February 2, 2026 23:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GetThreadStaticsBase method is not included in R2R image

3 participants