Skip to content

coreclr: implement Environment.CurrentManagedThreadId via ManagedThreadId.Current#129954

Merged
VSadov merged 4 commits into
mainfrom
copilot/coreclr-use-managedthreadid-current
Jun 29, 2026
Merged

coreclr: implement Environment.CurrentManagedThreadId via ManagedThreadId.Current#129954
VSadov merged 4 commits into
mainfrom
copilot/coreclr-use-managedthreadid-current

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

On CoreCLR, Environment.CurrentManagedThreadId was backed by an FCall (JIT_GetCurrentManagedThreadIdGetThread()->GetThreadId()). ManagedThreadId.Current reads t_currentManagedThreadId, a [ThreadStatic] wired by the type system to a special location that is populated with the thread's managed ID before any managed caller can observe it — the thin-lock implementation already depends on this guarantee.

This makes Environment.CurrentManagedThreadId a plain thread-static read on CoreCLR, matching what NativeAOT and Mono already do. The dedicated native JIT_GetCurrentManagedThreadId FCall is removed; the CORINFO_HELP_GETCURRENTMANAGEDTHREADID JIT helper is rebound to the managed property so both Environment.CurrentManagedThreadId and the Thread.CurrentThread.ManagedThreadId intrinsic resolve to the inlinable thread-static read.

Changes

  • Environment.CoreCLR.cs: replace [MethodImpl(MethodImplOptions.InternalCall)] extern property with => Threading.ManagedThreadId.Current, keeping AggressiveInlining on the getter so it folds into the thread-static read
  • ecalllist.h: remove FCFuncElement("get_CurrentManagedThreadId", JIT_GetCurrentManagedThreadId) from gEnvironmentFuncs
  • corelib.h: add METHOD__ENVIRONMENT__CURRENT_MANAGED_THREAD_ID for get_CurrentManagedThreadId
  • jithelpers.h: make CORINFO_HELP_GETCURRENTMANAGEDTHREADID an IL-based DYNAMICJITHELPER bound to the managed method (following the MEMSET precedent) instead of native code
  • JIT_GetCurrentManagedThreadId (jithelpers.cpp / jitinterface.h) is removed: the helper now resolves to the managed Environment.CurrentManagedThreadId property, so the native FCall is no longer needed
  • importercalls.cpp: the Thread.CurrentThread.ManagedThreadId intrinsic converts the CORINFO_HELP_GETCURRENTMANAGEDTHREADID helper into an inlinable user call (impConvertToUserCallAndMarkForInlining) and, since intrinsic-imported calls skip impImportCall's inline-candidate spill, manually appends it and hands back a GT_RET_EXPR. This lets the inliner fold the property into the same thread-static read as Environment.CurrentManagedThreadId and avoids the checked GTF_CALL_INLINE_CANDIDATE morph assert from the unspilled candidate

Verified clr+libs -rc release builds clean and Environment.CurrentManagedThreadId/Thread.CurrentThread.ManagedThreadId parity holds (with no checked assert under JitStress/TailcallStress) on the main thread, a new managed thread, and a thread-pool thread.

Copilot AI requested review from Copilot and removed request for Copilot June 29, 2026 00:58
@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.

….Current on CoreCLR

On CoreCLR, `t_currentManagedThreadId` is set up by the type system to
point to a special location populated with the thread's managed ID before
any managed caller can observe it (the thin-lock implementation already
relies on this guarantee).

This change makes `Environment.CurrentManagedThreadId` simply delegate to
`ManagedThreadId.Current`, turning an FCall into a cheap thread-static
read, aligning CoreCLR with NativeAOT/Mono which already do the same.

- Environment.CoreCLR.cs: replace InternalCall property with
  `=> Threading.ManagedThreadId.Current`
- ecalllist.h: remove the now-unused
  `FCFuncElement("get_CurrentManagedThreadId", JIT_GetCurrentManagedThreadId)`

`JIT_GetCurrentManagedThreadId` is kept in jithelpers.cpp and
jitinterface.h because it is still needed as the native entry point for
the JIT helper `CORINFO_HELP_GETCURRENTMANAGEDTHREADID` (used by the JIT
when optimizing `Thread.CurrentThread.ManagedThreadId`).

Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 29, 2026 01:24
Copilot AI changed the title [WIP] Update Environment.CurrentManagedThreadId to return ManagedThreadId.Current coreclr: implement Environment.CurrentManagedThreadId via ManagedThreadId.Current Jun 29, 2026
Copilot AI requested a review from VSadov June 29, 2026 01:24
@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

@copilot is it possible to interpret call to System.Threading.Thread.CurrentThread.ManagedThreadId the same as a call to Environment.CurrentManagedThreadId (or to ManagedThreadId.Current if simpler) instead of a call to a JIT helper?

@MichalPetryka

Copy link
Copy Markdown
Contributor

@copilot is it possible to interpret call to System.Threading.Thread.CurrentThread.ManagedThreadId the same as a call to Environment.CurrentManagedThreadId (or to ManagedThreadId.Current if simpler) instead of a call to a JIT helper?

JIT can only generate calls to helpers, but they can be managed methods instead.

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

@copilot is it possible to interpret call to System.Threading.Thread.CurrentThread.ManagedThreadId the same as a call to Environment.CurrentManagedThreadId (or to ManagedThreadId.Current if simpler) instead of a call to a JIT helper?

JIT can only generate calls to helpers, but they can be managed methods instead.

The idea is - if JIT sees System.Threading.Thread.CurrentThread.ManagedThreadId, can it pretend it has seen Environment.CurrentManagedThreadId instead and perform same actions?

If the substituted call can't be inlined, it is probably not much better than calling a native helper.

@MichalPetryka

Copy link
Copy Markdown
Contributor

If the substituted call can't be inlined, it is probably not much better than calling a native helper.

@EgorBo recently added a way to mark helpers calls introduced early as inlineable.

…anagedThreadId; remove native FCall

Make the CORINFO_HELP_GETCURRENTMANAGEDTHREADID JIT helper IL-based,
binding it to Environment.get_CurrentManagedThreadId (now a thread-static
read via ManagedThreadId.Current), following the MEMSET precedent. This
lets the helper resolve to the managed property instead of native code,
so the dedicated native JIT_GetCurrentManagedThreadId FCall is no longer
needed and is removed.

- corelib.h: add METHOD__ENVIRONMENT__CURRENT_MANAGED_THREAD_ID
- jithelpers.h: GETCURRENTMANAGEDTHREADID -> DYNAMICJITHELPER bound to the method
- jithelpers.cpp/jitinterface.h: delete JIT_GetCurrentManagedThreadId
- Environment.CoreCLR.cs: keep AggressiveInlining on get_CurrentManagedThreadId

Co-authored-by: VSadov <8218165+VSadov@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 29, 2026 04:03

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot is it possible to interpret call to System.Threading.Thread.CurrentThread.ManagedThreadId the same as a call to Environment.CurrentManagedThreadId (or to `ManagedThreadId....

Yes — done in 3c9cb11. The CORINFO_HELP_GETCURRENTMANAGEDTHREADID helper is now IL-based: it binds to Environment.get_CurrentManagedThreadId (which is ManagedThreadId.Current, a thread-static read) instead of the native JIT_GetCurrentManagedThreadId FCall, following the same DYNAMICJITHELPER(... METHOD__SPAN_HELPERS__MEMSET) precedent. So both Environment.CurrentManagedThreadId and the Thread.CurrentThread.ManagedThreadId intrinsic resolve to the managed property; the property keeps AggressiveInlining so it folds into the thread-static read. The native FCall is removed entirely.

Verified parity (Env vs Thread.ManagedThreadId) on main, a new managed thread, and a thread-pool thread.

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

@copilot is it possible to interpret call to System.Threading.Thread.CurrentThread.ManagedThreadId the same as a call to Environment.CurrentManagedThreadId (or to ManagedThreadId.Current if ...

Right — and that's the catch with the current state of this PR. Both Environment.CurrentManagedThreadId and the Thread.CurrentThread.ManagedThreadId intrinsic resolve to CORINFO_HELP_GETCURRENTMANAGEDTHREADID, which is now a managed method (Environment.get_CurrentManagedThreadIdManagedThreadId.Current). But a DYNAMICJITHELPER call site is emitted as a direct call to that managed method; the JIT doesn't treat helper calls as inline candidates, so the thread-static read does not fold in — it stays a call, same cost profile as the old native FCall. My earlier "folds into the thread-static read" only holds when someone calls Environment.CurrentManagedThreadId directly (a normal managed call with AggressiveInlining).

To get true inlining, the intrinsic path for get_ManagedThreadId should import an actual inlinable managed call to Environment.CurrentManagedThreadId/ManagedThreadId.Current (your "pretend it saw Environment.CurrentManagedThreadId" idea) rather than emitting the helper. The blocker is that the JIT importer has no method handle for an arbitrary CoreLib method without a CORINFO mechanism, so it'd need a way to materialize that call as an inline candidate. Want me to pursue that substitution so it inlines, or keep the helper binding as-is?

Note

AI-generated content by copilot may be incorrect.

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

If the substituted call can't be inlined, it is probably not much better than calling a native helper.

@EgorBo recently added a way to mark helpers calls introduced early as inlineable.

@copilot can you check if that can help?

@VSadov VSadov marked this pull request as ready for review June 29, 2026 13:03
Copilot AI review requested due to automatic review settings June 29, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR changes CoreCLR’s implementation of Environment.CurrentManagedThreadId from a native FCall to a managed, inlinable path backed by System.Threading.ManagedThreadId.Current, and rebinds the JIT helper CORINFO_HELP_GETCURRENTMANAGEDTHREADID to the managed accessor.

Changes:

  • Replace the CoreCLR Environment.CurrentManagedThreadId InternalCall with an aggressively-inlined managed getter (=> Threading.ManagedThreadId.Current).
  • Remove the native JIT_GetCurrentManagedThreadId FCall surface (declaration, implementation, and ecall table entry).
  • Rebind CORINFO_HELP_GETCURRENTMANAGEDTHREADID as a DYNAMICJITHELPER targeting Environment.get_CurrentManagedThreadId, and adjust the JIT Thread.ManagedThreadId intrinsic import to convert/spill inline candidates similarly to impImportCall.

Reviewed changes

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

Show a summary per file
File Description
src/coreclr/vm/jitinterface.h Removes the JIT_GetCurrentManagedThreadId FCDECL export.
src/coreclr/vm/jithelpers.cpp Deletes the FCIMPL0 implementation of JIT_GetCurrentManagedThreadId.
src/coreclr/vm/ecalllist.h Removes the Environment.get_CurrentManagedThreadId FCall mapping.
src/coreclr/vm/corelib.h Adds a CoreLib binder entry for Environment.get_CurrentManagedThreadId for helper rebinding.
src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs Implements CurrentManagedThreadId as a managed, aggressively-inlined getter reading ManagedThreadId.Current.
src/coreclr/jit/importercalls.cpp Updates the Thread.get_ManagedThreadId intrinsic path to convert the helper to an inlinable user call and spill/return a GT_RET_EXPR when appropriate.
src/coreclr/inc/jithelpers.h Rebinds CORINFO_HELP_GETCURRENTMANAGEDTHREADID to a managed method via DYNAMICJITHELPER.

Comment thread src/coreclr/jit/importercalls.cpp
@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

@EgorBot -linux_x64 -windows_x64 -arm64 -linux_arm64

using BenchmarkDotNet.Attributes;
using System.Threading;

public class ManagedThreadIdBench
{
    [Benchmark]
    public int Environment_CurrentManagedThreadId()
        => Environment.CurrentManagedThreadId;

    [Benchmark]
    public int Thread_CurrentThread_ManagedThreadId()
        => Thread.CurrentThread.ManagedThreadId;

    // Summing in a tight loop makes the per-call cost easier to see and
    // prevents the JIT from eliminating the read entirely.
    [Benchmark]
    public int Environment_CurrentManagedThreadId_Loop()
    {
        int sum = 0;
        for (int i = 0; i < 1000; i++)
            sum += Environment.CurrentManagedThreadId;
        return sum;
    }

    [Benchmark]
    public int Thread_CurrentThread_ManagedThreadId_Loop()
    {
        int sum = 0;
        for (int i = 0; i < 1000; i++)
            sum += Thread.CurrentThread.ManagedThreadId;
        return sum;
    }
}

@EgorBo

EgorBo commented Jun 29, 2026

Copy link
Copy Markdown
Member

Probably not for this PR, but I wonder if Thread.CurrentThread.ManagedThreadId is optimized efficiently as is (without that JIT opt that replaces it with a helper call) since JIT inlines TLS so we could just remove all vm/jit bits

@EgorBot -arm -amd -commits main

using BenchmarkDotNet.Attributes;
using System.Threading;

public class ManagedThreadIdBench
{
    [Benchmark]
    public int Environment_CurrentManagedThreadId()
        => Environment.CurrentManagedThreadId;

    [Benchmark]
    public int Thread_CurrentThread_ManagedThreadId()
        => Thread.CurrentThread.ManagedThreadId;

    [Benchmark]
    public int Thread_CurrentThread_ManagedThreadId_not_expanded()
    {
        var tmp = Thread.CurrentThread;
        // tmp breaks the `Thread.CurrentThread.ManagedThreadId` pattern match.
        return tmp .ManagedThreadId;
    }
}

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

Probably not for this PR, but I wonder if Thread.CurrentThread.ManagedThreadId is optimized efficiently as is (without that JIT opt that replaces it with a helper call) since JIT inlines TLS so we could just remove all vm/jit bits

As I understand the purpose for the Thread.CurrentThread.ManagedThreadId optimization is not as much about avoiding TLS access, as it would happen anyways, but for avoiding materialization of the managed Thread object, when only Id is needed.

I am not sure how far real programs may get without allocating Thread these days. Perhaps not far and we should just allocate Thread eagerly?

This is a relatively mechanical PR though that keeps overall strategy the same, modulo inlining.

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

The benchmarks show considerable gains from this change:
(smaller number is better)

Results for macos15_helix_arm64

BenchmarkDotNet v0.15.8, macOS Sequoia 15.4.1 (24E263) [Darwin 24.4.0]
Apple M4, 1 CPU, 10 logical and 10 physical cores
.NET SDK 11.0.100-preview.7.26329.101
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), Arm64 RyuJIT armv8.0-a
Method Toolchain Mean Error Ratio
Environment_CurrentManagedThreadId PR #129954 0.0276 ns 0.0009 ns 1.00
Environment_CurrentManagedThreadId main 1.2827 ns 0.0131 ns 46.54
Thread_CurrentThread_ManagedThreadId PR #129954 0.0415 ns 0.0015 ns 1.00
Thread_CurrentThread_ManagedThreadId main 0.5297 ns 0.0052 ns 12.77
Environment_CurrentManagedThreadId_Loop PR #129954 230.7498 ns 0.1642 ns 1.00
Environment_CurrentManagedThreadId_Loop main 1,192.4792 ns 5.4503 ns 5.17
Thread_CurrentThread_ManagedThreadId_Loop PR #129954 230.5095 ns 0.1828 ns 1.00
Thread_CurrentThread_ManagedThreadId_Loop main 231.2885 ns 0.1146 ns 1.00

Full logs

Results for ubuntu24_azure_turin

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V45 2.60GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 11.0.100-preview.7.26329.101
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4
Method Toolchain Mean Error Ratio
Environment_CurrentManagedThreadId PR #129954 0.9188 ns 0.0201 ns 1.00
Environment_CurrentManagedThreadId main 1.3938 ns 0.0364 ns 1.52
Thread_CurrentThread_ManagedThreadId PR #129954 0.9012 ns 0.0203 ns 1.00
Thread_CurrentThread_ManagedThreadId main 2.0580 ns 0.0274 ns 2.28
Environment_CurrentManagedThreadId_Loop PR #129954 234.8007 ns 3.6196 ns 1.00
Environment_CurrentManagedThreadId_Loop main 2,269.4807 ns 25.7584 ns 9.67
Thread_CurrentThread_ManagedThreadId_Loop PR #129954 234.3946 ns 2.3461 ns 1.00
Thread_CurrentThread_ManagedThreadId_Loop main 235.7425 ns 3.0677 ns 1.01

Full logs

Results for ubuntu24_azure_cobalt100

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
Neoverse-N2, 8 physical cores
.NET SDK 11.0.100-preview.7.26329.101
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), Arm64 RyuJIT armv8.0-a
Method Toolchain Mean Error Ratio
Environment_CurrentManagedThreadId PR #129954 0.0321 ns 0.0075 ns 1.05
Environment_CurrentManagedThreadId main 0.8895 ns 0.0010 ns 28.99
Thread_CurrentThread_ManagedThreadId PR #129954 0.0342 ns 0.0052 ns 1.019
Thread_CurrentThread_ManagedThreadId main 0.0000 ns 0.0000 ns 0.000
Environment_CurrentManagedThreadId_Loop PR #129954 298.0678 ns 0.0399 ns 1.00
Environment_CurrentManagedThreadId_Loop main 1,507.0731 ns 1.9008 ns 5.06
Thread_CurrentThread_ManagedThreadId_Loop PR #129954 297.9481 ns 0.0500 ns 1.00
Thread_CurrentThread_ManagedThreadId_Loop main 300.7611 ns 0.0598 ns 1.01

Full logs

Results for windows_azure_turin


BenchmarkDotNet v0.15.8, Windows 11 (10.0.26100.32995/24H2/2024Update/HudsonValley) (Hyper-V)
AMD EPYC 9V45 2.60GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK 11.0.100-preview.7.26329.101
  [Host]     : .NET 10.0.9 (10.0.9, 10.0.926.27113), X64 RyuJIT x86-64-v4


Method Toolchain Mean Error Ratio
Environment_CurrentManagedThreadId PR #129954 0.0159 ns 0.0094 ns 1.25
Environment_CurrentManagedThreadId main 0.2192 ns 0.0055 ns 17.25
Thread_CurrentThread_ManagedThreadId PR #129954 0.0089 ns 0.0161 ns ?
Thread_CurrentThread_ManagedThreadId main 0.8976 ns 0.0174 ns ?
Environment_CurrentManagedThreadId_Loop PR #129954 231.9105 ns 3.0379 ns 1.00
Environment_CurrentManagedThreadId_Loop main 1,121.5578 ns 9.6127 ns 4.84
Thread_CurrentThread_ManagedThreadId_Loop PR #129954 228.3862 ns 1.4556 ns 1.00
Thread_CurrentThread_ManagedThreadId_Loop main 231.0352 ns 2.0251 ns 1.01

Full logs

@EgorBo

EgorBo commented Jun 29, 2026

Copy link
Copy Markdown
Member

but for avoiding materialization of the managed Thread object,

I am also not sure it's a big problem either since it's a one-time job per thread.

@EgorBo

EgorBo commented Jun 29, 2026

Copy link
Copy Markdown
Member

The benchmarks show considerable gains from this change:

Yeah, so Thread.CurrentThread.ManagedThreadId used to be replaced with a helper call in JIT and JIT knew it's side-effect free and can be hoisted from loops, while Environment.CurrentManagedThreadId was just an unkown fcall inside the loop body.

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

The benchmarks show considerable gains from this change:

Yeah, so Thread.CurrentThread.ManagedThreadId used to be replaced with a helper call in JIT and JIT knew it's side-effect free and can be hoisted from loops, while Environment.CurrentManagedThreadId was just an unkown fcall inside the loop body.

Also that call could be costly next to just accessing TLS.
See: Environment_CurrentManagedThreadId benchmark on ARM64 (both M4 and cobalt)

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

@MichalPetryka - thanks for the hint about inlining!
I actually did not know that some JIT helpers can be inlined now.

@VSadov VSadov merged commit 761041f into main Jun 29, 2026
139 checks passed
@VSadov VSadov deleted the copilot/coreclr-use-managedthreadid-current branch June 29, 2026 21:11
@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

Yeah, so Thread.CurrentThread.ManagedThreadId used to be replaced with a helper call in JIT and JIT knew it's side-effect free and can be hoisted from loops.

The helper is not side-effect free if the loop contains a runtime async await.
Not sure if we handled that correctly, but it is not a problem now either way. (or is it?)

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

@EgorBo @jakobbotsch - I see that we still have

        // A constant within any method.
        case CORINFO_HELP_GETCURRENTMANAGEDTHREADID:
            vnf = VNF_ManagedThreadId;
            break;

Can that somehow cause issues in an async methods?
The thread ID is not a constant across suspend points.

It is not generally a good idea to mix TLS accesses and async, but I am sure there are cases, for whatever reason...

@EgorBo

EgorBo commented Jun 29, 2026

Copy link
Copy Markdown
Member

@EgorBo @jakobbotsch - I see that we still have

        // A constant within any method.
        case CORINFO_HELP_GETCURRENTMANAGEDTHREADID:
            vnf = VNF_ManagedThreadId;
            break;

Can that somehow cause issues in an async methods? The thread ID is not a constant across suspend points.

It is not generally a good idea to mix TLS accesses and async, but I am sure there are cases, for whatever reason...

I think that can be deleted now that you marked it as inline candidate (and I expect it to be always inlined) so that helper should almost never make it to the VN phase. if it doesn't inline for whatever reason it's not a big deal if it doesn't get that VN func.

@VSadov

VSadov commented Jun 29, 2026

Copy link
Copy Markdown
Member

What about the actual TLS access - is nothing hoisted there, at least across suspend points? (like the address of the variable storage,...)
I am not super familiar with details or granularity of what can/cannot be hoisted.

@jakobbotsch

Copy link
Copy Markdown
Member

What about the actual TLS access - is nothing hoisted there, at least across suspend points? (like the address of the variable storage,...) I am not super familiar with details or granularity of what can/cannot be hoisted.

No, TLS accesses are done through a byref typed base value, so those cannot be hoisted across awaits.
This thread ID one may be a problem though.

@VSadov

VSadov commented Jun 30, 2026

Copy link
Copy Markdown
Member

What about the actual TLS access - is nothing hoisted there, at least across suspend points? (like the address of the variable storage,...) I am not super familiar with details or granularity of what can/cannot be hoisted.

No, TLS accesses are done through a byref typed base value, so those cannot be hoisted across awaits. This thread ID one may be a problem though.

I suspected that some detail like clearing byrefs saves us, or we'd have issues with restoring contexts to wrong threads.

I think treating CORINFO_HELP_GETCURRENTMANAGEDTHREADID as sideefeecting (not pure) could be sufficient. It would generally inline in cases where we care, and then it will be accessed via TLS/byref. If it does not inline, in rare cases like if optimizations are turned off, then it'll be an opaque method call.
It still can be no-throw.

PR: #130006

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants