Skip to content

Commit

Permalink
Move static helpers to managed (#108167)
Browse files Browse the repository at this point in the history
Move the statics helpers from native code, occasionally using a HELPER_METHOD_FRAME, to managed code using QCalls for P/Invoke transition

Particularly notable parts of the change:

- Thread static lookup relies on the jit thread static optimization pathway. I needed to build a parallel helper call enum value for the case where we need to use the optimized helper path, but didn't actually have the ability to optimize in the JIT.
- This change maintains the volatile read of the normal static values, through a custom JIT intrinsic used only in this codepath.
  • Loading branch information
davidwrighton authored Oct 30, 2024
1 parent 5ca9043 commit 26612c8
Show file tree
Hide file tree
Showing 39 changed files with 590 additions and 377 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,12 @@
<Compile Include="$(BclSourcesRoot)\System\Reflection\RuntimePropertyInfo.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\TypeNameResolver.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Reflection\Metadata\RuntimeTypeMetadataUpdateHandler.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\BypassReadyToRunAttribute.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\GenericsHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\InitHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\StaticsHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\VirtualDispatchHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\DependentHandle.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime
{
// Use to disable ReadyToRun compilation for a method.
internal sealed class BypassReadyToRunAttribute : Attribute
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal static unsafe partial class InitHelpers

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static void InitClassSlow(MethodTable* mt)
internal static void InitClassSlow(MethodTable* mt)
{
InitClassHelper(mt);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,45 @@ public TypeHandle GetArrayElementTypeHandle()
public extern MethodTable* GetMethodTableMatchingParentClass(MethodTable* parent);
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe ref struct DynamicStaticsInfo
{
internal const int ISCLASSNOTINITED = 1;
internal IntPtr _pGCStatics; // The ISCLASSNOTINITED bit is set when the class is NOT initialized
internal IntPtr _pNonGCStatics; // The ISCLASSNOTINITED bit is set when the class is NOT initialized

/// <summary>
/// Given a statics pointer in the DynamicStaticsInfo, get the actual statics pointer.
/// If the class it initialized, this mask is not necessary
/// </summary>
internal static ref byte MaskStaticsPointer(ref byte staticsPtr)
{
fixed (byte* p = &staticsPtr)
{
return ref Unsafe.AsRef<byte>((byte*)((nuint)p & ~(nuint)DynamicStaticsInfo.ISCLASSNOTINITED));
}
}

internal unsafe MethodTable* _methodTable;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe ref struct GenericsStaticsInfo
{
// Pointer to field descs for statics
internal IntPtr _pFieldDescs;
internal DynamicStaticsInfo _dynamicStatics;
}

[StructLayout(LayoutKind.Sequential)]
internal unsafe ref struct ThreadStaticsInfo
{
internal int _nonGCTlsIndex;
internal int _gcTlsIndex;
internal GenericsStaticsInfo _genericStatics;
}


// Subset of src\vm\methodtable.h
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct MethodTableAuxiliaryData
Expand Down Expand Up @@ -939,6 +978,18 @@ public RuntimeType? ExposedClassObject
public bool IsClassInited => (Volatile.Read(ref Flags) & enum_flag_Initialized) != 0;

public bool IsClassInitedAndActive => (Volatile.Read(ref Flags) & (enum_flag_Initialized | enum_flag_EnsuredInstanceActive)) == (enum_flag_Initialized | enum_flag_EnsuredInstanceActive);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref DynamicStaticsInfo GetDynamicStaticsInfo()
{
return ref Unsafe.Subtract(ref Unsafe.As<MethodTableAuxiliaryData, DynamicStaticsInfo>(ref this), 1);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref ThreadStaticsInfo GetThreadStaticsInfo()
{
return ref Unsafe.Subtract(ref Unsafe.As<MethodTableAuxiliaryData, ThreadStaticsInfo>(ref this), 1);
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace System.Runtime.CompilerServices
{
[StackTraceHidden]
[DebuggerStepThrough]
internal static unsafe partial class StaticsHelpers
{
[LibraryImport(RuntimeHelpers.QCall)]
private static partial void GetThreadStaticsByIndex(ByteRefOnStack result, int index, [MarshalAs(UnmanagedType.Bool)] bool gcStatics);

[LibraryImport(RuntimeHelpers.QCall)]
private static partial void GetThreadStaticsByMethodTable(ByteRefOnStack result, MethodTable* pMT, [MarshalAs(UnmanagedType.Bool)] bool gcStatics);

[Intrinsic]
private static ref byte VolatileReadAsByref(ref IntPtr address) => ref VolatileReadAsByref(ref address);

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetNonGCStaticBaseSlow(MethodTable* mt)
{
InitHelpers.InitClassSlow(mt);
return ref DynamicStaticsInfo.MaskStaticsPointer(ref VolatileReadAsByref(ref mt->AuxiliaryData->GetDynamicStaticsInfo()._pNonGCStatics));
}

[DebuggerHidden]
private static ref byte GetNonGCStaticBase(MethodTable* mt)
{
ref byte nonGCStaticBase = ref VolatileReadAsByref(ref mt->AuxiliaryData->GetDynamicStaticsInfo()._pNonGCStatics);

if ((((nuint)Unsafe.AsPointer(ref nonGCStaticBase)) & DynamicStaticsInfo.ISCLASSNOTINITED) != 0)
return ref GetNonGCStaticBaseSlow(mt);
else
return ref nonGCStaticBase;
}

[DebuggerHidden]
private static ref byte GetDynamicNonGCStaticBase(DynamicStaticsInfo *dynamicStaticsInfo)
{
ref byte nonGCStaticBase = ref VolatileReadAsByref(ref dynamicStaticsInfo->_pNonGCStatics);

if ((((nuint)Unsafe.AsPointer(ref nonGCStaticBase)) & DynamicStaticsInfo.ISCLASSNOTINITED) != 0)
return ref GetNonGCStaticBaseSlow(dynamicStaticsInfo->_methodTable);
else
return ref nonGCStaticBase;
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetGCStaticBaseSlow(MethodTable* mt)
{
InitHelpers.InitClassSlow(mt);
return ref DynamicStaticsInfo.MaskStaticsPointer(ref VolatileReadAsByref(ref mt->AuxiliaryData->GetDynamicStaticsInfo()._pGCStatics));
}

[DebuggerHidden]
private static ref byte GetGCStaticBase(MethodTable* mt)
{
ref byte gcStaticBase = ref VolatileReadAsByref(ref mt->AuxiliaryData->GetDynamicStaticsInfo()._pGCStatics);

if ((((nuint)Unsafe.AsPointer(ref gcStaticBase)) & DynamicStaticsInfo.ISCLASSNOTINITED) != 0)
return ref GetGCStaticBaseSlow(mt);
else
return ref gcStaticBase;
}

[DebuggerHidden]
private static ref byte GetDynamicGCStaticBase(DynamicStaticsInfo *dynamicStaticsInfo)
{
ref byte gcStaticBase = ref VolatileReadAsByref(ref dynamicStaticsInfo->_pGCStatics);

if ((((nuint)Unsafe.AsPointer(ref gcStaticBase)) & DynamicStaticsInfo.ISCLASSNOTINITED) != 0)
return ref GetGCStaticBaseSlow(dynamicStaticsInfo->_methodTable);
else
return ref gcStaticBase;
}

// Thread static helpers

/// <summary>
/// Return beginning of the object as a reference to byte
/// </summary>
[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref byte GetObjectAsRefByte(object obj)
{
return ref Unsafe.Subtract(ref RuntimeHelpers.GetRawData(obj), sizeof(MethodTable*));
}

[StructLayout(LayoutKind.Sequential)]
internal struct ThreadLocalData
{
internal const int NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY = 2;
internal int _cNonCollectibleTlsData; // Size of offset into the non-collectible TLS array which is valid, NOTE: this is relative to the start of the nonCollectibleTlsArrayData object, not the start of the data in the array
internal int _cCollectibleTlsData; // Size of offset into the TLS array which is valid
private IntPtr _nonCollectibleTlsArrayData_private; // This is object[], but using object[] directly causes the structure to be laid out via auto-layout, which is not what we want.
internal IntPtr* _collectibleTlsArrayData; // Points at the Thread local array data.

internal object[] NonCollectibleTlsArrayData
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return Unsafe.As<IntPtr, object[]>(ref _nonCollectibleTlsArrayData_private);
}
}
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetIndexOffset(int index)
{
return index & 0xFFFFFF;
}

private const int NonCollectibleTLSIndexType = 0;
private const int DirectOnThreadLocalDataTLSIndexType = 2;

[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetIndexType(int index)
{
return index >> 24;
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsIndexAllocated(int index)
{
return index != -1;
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetNonGCThreadStaticsByIndexSlow(int index)
{
ByteRef result = default;
GetThreadStaticsByIndex(ByteRefOnStack.Create(ref result), index, false);
return ref result.Get();
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetGCThreadStaticsByIndexSlow(int index)
{
ByteRef result = default;
GetThreadStaticsByIndex(ByteRefOnStack.Create(ref result), index, true);
return ref result.Get();
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetNonGCThreadStaticBaseSlow(MethodTable* mt)
{
ByteRef result = default;
GetThreadStaticsByMethodTable(ByteRefOnStack.Create(ref result), mt, false);
return ref result.Get();
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte GetGCThreadStaticBaseSlow(MethodTable* mt)
{
ByteRef result = default;
GetThreadStaticsByMethodTable(ByteRefOnStack.Create(ref result), mt, true);
return ref result.Get();
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ref byte GetThreadLocalStaticBaseByIndex(int index, bool gcStatics)
{
ThreadLocalData *t_ThreadStatics = System.Threading.Thread.GetThreadStaticsBase();
int indexOffset = GetIndexOffset(index);
if (GetIndexType(index) == NonCollectibleTLSIndexType)
{
if (t_ThreadStatics->_cNonCollectibleTlsData > GetIndexOffset(index))
{
object? threadStaticObjectNonCollectible = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(t_ThreadStatics->NonCollectibleTlsArrayData), indexOffset - ThreadLocalData.NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY);
if (threadStaticObjectNonCollectible != null)
{
return ref GetObjectAsRefByte(threadStaticObjectNonCollectible);
}
}
}
else if (GetIndexType(index) == DirectOnThreadLocalDataTLSIndexType)
{
return ref Unsafe.Add(ref Unsafe.AsRef<byte>(t_ThreadStatics), indexOffset);
}
else
{
int cCollectibleTlsData = t_ThreadStatics->_cCollectibleTlsData;
if (cCollectibleTlsData > indexOffset)
{
IntPtr* pCollectibleTlsArrayData = t_ThreadStatics->_collectibleTlsArrayData;

pCollectibleTlsArrayData += indexOffset;
IntPtr objHandle = *pCollectibleTlsArrayData;
if (objHandle != IntPtr.Zero)
{
object? threadStaticObject = GCHandle.InternalGet(objHandle);
if (threadStaticObject != null)
{
return ref GetObjectAsRefByte(threadStaticObject);
}
}
}
}

if (gcStatics)
return ref GetGCThreadStaticsByIndexSlow(index);
else
return ref GetNonGCThreadStaticsByIndexSlow(index);
}

[DebuggerHidden]
private static ref byte GetNonGCThreadStaticBase(MethodTable* mt)
{
int index = mt->AuxiliaryData->GetThreadStaticsInfo()._nonGCTlsIndex;
if (IsIndexAllocated(index))
return ref GetThreadLocalStaticBaseByIndex(index, false);
else
return ref GetNonGCThreadStaticBaseSlow(mt);
}

[DebuggerHidden]
private static ref byte GetGCThreadStaticBase(MethodTable* mt)
{
int index = mt->AuxiliaryData->GetThreadStaticsInfo()._gcTlsIndex;
if (IsIndexAllocated(index))
return ref GetThreadLocalStaticBaseByIndex(index, true);
else
return ref GetGCThreadStaticBaseSlow(mt);
}

[DebuggerHidden]
private static ref byte GetDynamicNonGCThreadStaticBase(ThreadStaticsInfo *threadStaticsInfo)
{
int index = threadStaticsInfo->_nonGCTlsIndex;
if (IsIndexAllocated(index))
return ref GetThreadLocalStaticBaseByIndex(index, false);
else
return ref GetNonGCThreadStaticBaseSlow(threadStaticsInfo->_genericStatics._dynamicStatics._methodTable);
}

[DebuggerHidden]
private static ref byte GetDynamicGCThreadStaticBase(ThreadStaticsInfo *threadStaticsInfo)
{
int index = threadStaticsInfo->_gcTlsIndex;
if (IsIndexAllocated(index))
return ref GetThreadLocalStaticBaseByIndex(index, true);
else
return ref GetGCThreadStaticBaseSlow(threadStaticsInfo->_genericStatics._dynamicStatics._methodTable);
}

[DebuggerHidden]
private static ref byte GetOptimizedNonGCThreadStaticBase(int index)
{
return ref GetThreadLocalStaticBaseByIndex(index, false);
}

[DebuggerHidden]
private static ref byte GetOptimizedGCThreadStaticBase(int index)
{
return ref GetThreadLocalStaticBaseByIndex(index, true);
}
}
}
Loading

0 comments on commit 26612c8

Please sign in to comment.