Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corert.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergiy Kuryata <sergeyk@microsoft.com>2016-12-09 06:52:13 +0300
committerSergiy Kuryata <sergeyk@microsoft.com>2016-12-09 07:26:15 +0300
commitc84bb4e535e8268bbeb14f256a8764df932bd48d (patch)
tree30a9c2f3f21e4a18357112a673bbde9574dcc569
parente80f0a3604a3073e896d19b76ca3f803307a687c (diff)
Implement basic support for thread static fields
This change implements basic support for thread static fields. Most things are already functional, performance could definitely be improved in the future but it should be sufficient to get things off the ground. This code passes CoreRT and Top200 CoreCLR tests on Windows. The BasicThreading test in this change verifies that thread static fields work for both non-generic and generic types in the single-threaded and multi-thread environment (using Tasks). One thing that does not work yet is multi-module compilation because the driver creates a separate ReadyToRun helper for a type in every module that accesses thread statics of the type. I am currently working on fixing this. The existing code has already implemented a good chuck of required functionality so this simply builds on top of that. Each module has a ThreadStatic region. Each entry in the region points to an EEType that represents a GC map for the thread static fields of a given type. The index of the entry in the region is the TLS index of the type. The TypeManager indirection node of the module has been extended to contain the index of the module in addition to a pointer to the type manager (which is also used for initialization on first access). The generated ReadyToRun helpers (that return thread static base for a type) look like this: __GetThreadStaticBase_System_Private_CoreLib_System_Threading_ManagedThreadId: 48 8D 0D BD E0 39 00 lea rcx,[__typemanager_indirection (07FF70D68AD58h)] <= module information (type manager, module index) BA 03 00 00 00 mov edx,3 <= TLS index of the type E9 33 FB E4 FF jmp System_Private_CoreLib_Internal_Runtime_ThreadStatics__GetThreadStaticBaseForType (07FF70D13C7D8h)
-rw-r--r--src/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs26
-rw-r--r--src/Common/src/TypeSystem/Ecma/EcmaField.cs3
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs4
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs6
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs6
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64Emitter.cs8
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs36
-rw-r--r--src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs1
-rw-r--r--src/Native/Runtime/thread.cpp97
-rw-r--r--src/Native/Runtime/thread.h10
-rw-r--r--src/System.Private.CoreLib/src/Internal/Runtime/ThreadStatics.cs88
-rw-r--r--src/System.Private.CoreLib/src/System.Private.CoreLib.csproj1
-rw-r--r--src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs10
-rw-r--r--src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs15
-rw-r--r--src/Test.CoreLib/src/System/Runtime/RuntimeImports.cs5
-rw-r--r--tests/src/Simple/BasicThreading/BasicThreading.cmd12
-rw-r--r--tests/src/Simple/BasicThreading/BasicThreading.cs139
-rw-r--r--tests/src/Simple/BasicThreading/BasicThreading.csproj7
-rw-r--r--tests/src/Simple/BasicThreading/BasicThreading.sh9
-rw-r--r--tests/src/Simple/BasicThreading/no_cpp1
-rw-r--r--tests/src/Simple/BasicThreading/no_unix1
21 files changed, 468 insertions, 17 deletions
diff --git a/src/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs b/src/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs
index ba8c0c2b1..b20ae0b01 100644
--- a/src/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs
+++ b/src/Common/src/Internal/Runtime/CompilerHelpers/StartupCodeHelpers.cs
@@ -26,7 +26,7 @@ namespace Internal.Runtime.CompilerHelpers
for (int i = 0; i < modules.Length; i++)
{
- InitializeGlobalTablesForModule(modules[i]);
+ InitializeGlobalTablesForModule(modules[i], i);
}
// We are now at a stage where we can use GC statics - publish the list of modules
@@ -71,16 +71,17 @@ namespace Internal.Runtime.CompilerHelpers
/// statics, etc that need initializing. InitializeGlobalTables walks through the modules
/// and offers each a chance to initialize its global tables.
/// </summary>
- private static unsafe void InitializeGlobalTablesForModule(IntPtr typeManager)
+ private static unsafe void InitializeGlobalTablesForModule(IntPtr typeManager, int moduleIndex)
{
// Configure the module indirection cell with the newly created TypeManager. This allows EETypes to find
// their interface dispatch map tables.
int length;
- IntPtr* section = (IntPtr*)GetModuleSection(typeManager, ReadyToRunSectionType.TypeManagerIndirection, out length);
- *section = typeManager;
+ TypeManagerSlot* section = (TypeManagerSlot*)RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.TypeManagerIndirection, out length);
+ section->TypeManager = typeManager;
+ section->ModuleIndex = moduleIndex;
// Initialize statics if any are present
- IntPtr staticsSection = GetModuleSection(typeManager, ReadyToRunSectionType.GCStaticRegion, out length);
+ IntPtr staticsSection = RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.GCStaticRegion, out length);
if (staticsSection != IntPtr.Zero)
{
Debug.Assert(length % IntPtr.Size == 0);
@@ -88,7 +89,7 @@ namespace Internal.Runtime.CompilerHelpers
}
// Initialize frozen object segment with GC present
- IntPtr frozenObjectSection = GetModuleSection(typeManager, ReadyToRunSectionType.FrozenObjectRegion, out length);
+ IntPtr frozenObjectSection = RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.FrozenObjectRegion, out length);
if (frozenObjectSection != IntPtr.Zero)
{
Debug.Assert(length % IntPtr.Size == 0);
@@ -110,7 +111,7 @@ namespace Internal.Runtime.CompilerHelpers
int length;
// Run eager class constructors if any are present
- IntPtr eagerClassConstructorSection = GetModuleSection(typeManager, ReadyToRunSectionType.EagerCctor, out length);
+ IntPtr eagerClassConstructorSection = RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.EagerCctor, out length);
if (eagerClassConstructorSection != IntPtr.Zero)
{
Debug.Assert(length % IntPtr.Size == 0);
@@ -158,12 +159,15 @@ namespace Internal.Runtime.CompilerHelpers
return len;
}
- [RuntimeImport(".", "RhpGetModuleSection")]
- [MethodImplAttribute(MethodImplOptions.InternalCall)]
- private static extern IntPtr GetModuleSection(IntPtr module, ReadyToRunSectionType section, out int length);
-
[RuntimeImport(".", "RhpCreateTypeManager")]
[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static unsafe extern IntPtr CreateTypeManager(IntPtr moduleHeader);
}
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct TypeManagerSlot
+ {
+ public IntPtr TypeManager;
+ public Int32 ModuleIndex;
+ }
}
diff --git a/src/Common/src/TypeSystem/Ecma/EcmaField.cs b/src/Common/src/TypeSystem/Ecma/EcmaField.cs
index 7055c66e3..430d9b328 100644
--- a/src/Common/src/TypeSystem/Ecma/EcmaField.cs
+++ b/src/Common/src/TypeSystem/Ecma/EcmaField.cs
@@ -155,8 +155,7 @@ namespace Internal.TypeSystem.Ecma
{
if (metadataReader.StringComparer.Equals(nameHandle, "ThreadStaticAttribute"))
{
- // TODO: Thread statics
- //flags |= FieldFlags.ThreadStatic;
+ flags |= FieldFlags.ThreadStatic;
}
}
}
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
index 88d287e88..1028e3d75 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/GCStaticEETypeNode.cs
@@ -91,8 +91,8 @@ namespace ILCompiler.DependencyAnalysis
totalSize = Math.Max(totalSize, _target.PointerSize * 3); // minimum GC eetype size is 3 pointers
dataBuilder.EmitInt(totalSize);
- // This is just so that EEType::Validate doesn't blow up at runtime
- dataBuilder.EmitPointerReloc(this); // Related type: itself
+ // Related type: System.Object. This allows storing an instance of this type in an array of objects.
+ dataBuilder.EmitPointerReloc(factory.NecessaryTypeSymbol(factory.TypeSystemContext.GetWellKnownType(WellKnownType.Object)));
return dataBuilder.ToObjectData();
}
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs
index 97be12a47..4906ef034 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/NodeFactory.cs
@@ -504,7 +504,9 @@ namespace ILCompiler.DependencyAnalysis
private static readonly string[][] s_helperEntrypointNames = new string[][] {
new string[] { "System.Runtime.CompilerServices", "ClassConstructorRunner", "CheckStaticClassConstructionReturnGCStaticBase" },
- new string[] { "System.Runtime.CompilerServices", "ClassConstructorRunner", "CheckStaticClassConstructionReturnNonGCStaticBase" }
+ new string[] { "System.Runtime.CompilerServices", "ClassConstructorRunner", "CheckStaticClassConstructionReturnNonGCStaticBase" },
+ new string[] { "System.Runtime.CompilerServices", "ClassConstructorRunner", "CheckStaticClassConstructionReturnThreadStaticBase" },
+ new string[] { "Internal.Runtime", "ThreadStatics", "GetThreadStaticBaseForType" }
};
private ISymbolNode[] _helperEntrypointSymbols;
@@ -716,5 +718,7 @@ namespace ILCompiler.DependencyAnalysis
{
EnsureClassConstructorRunAndReturnGCStaticBase,
EnsureClassConstructorRunAndReturnNonGCStaticBase,
+ EnsureClassConstructorRunAndReturnThreadStaticBase,
+ GetThreadStaticBaseForType,
}
}
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs
index 477eef6dd..249fc4114 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/ReadyToRunHelperNode.cs
@@ -156,6 +156,12 @@ namespace ILCompiler.DependencyAnalysis
dependencyList.Add(factory.VirtualMethodUse((MethodDesc)_target), "ReadyToRun Virtual Method Address Load");
return dependencyList;
}
+ else if (_id == ReadyToRunHelperId.GetThreadStaticBase)
+ {
+ DependencyList dependencyList = new DependencyList();
+ dependencyList.Add(factory.TypeThreadStaticsSymbol((MetadataType)_target), "ReadyToRun Thread Static Storage");
+ return dependencyList;
+ }
else
{
return null;
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64Emitter.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64Emitter.cs
index 7c8714876..82637edde 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64Emitter.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64Emitter.cs
@@ -32,6 +32,14 @@ namespace ILCompiler.DependencyAnalysis.X64
Builder.EmitByte((byte)(0xC0 | (((int)regSrc & 0x07) << 3) | (((int)regDst & 0x07))));
}
+ public void EmitMOV(Register regDst, int imm32)
+ {
+ AddrMode rexAddrMode = new AddrMode(regDst, null, 0, 0, AddrModeSize.Int32);
+ EmitRexPrefix(regDst, ref rexAddrMode);
+ Builder.EmitByte((byte)(0xB8 | ((int)regDst & 0x07)));
+ Builder.EmitInt(imm32);
+ }
+
public void EmitLEAQ(Register reg, ISymbolNode symbol, int delta = 0)
{
AddrMode rexAddrMode = new AddrMode(Register.RAX, null, 0, 0, AddrModeSize.Int64);
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs
index b782bd7b0..8a7bea4a1 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/Target_X64/X64ReadyToRunHelperNode.cs
@@ -108,7 +108,41 @@ namespace ILCompiler.DependencyAnalysis
break;
case ReadyToRunHelperId.GetThreadStaticBase:
- encoder.EmitINT3();
+ {
+ MetadataType target = (MetadataType)Target;
+ ThreadStaticsNode targetNode = factory.TypeThreadStaticsSymbol(target) as ThreadStaticsNode;
+ int typeTlsIndex = 0;
+
+ // The GetThreadStaticBase helper should be generated only in the compilation module group
+ // that contains the thread static field because the helper needs the index of the type
+ // in Thread Static section of the containing module.
+ // TODO: This needs to be fixed this for the multi-module compilation
+ Debug.Assert(targetNode != null);
+
+ if (!relocsOnly)
+ {
+ // Get index of the targetNode in the Thread Static region
+ typeTlsIndex = factory.ThreadStaticsRegion.IndexOfEmbeddedObject(targetNode);
+ }
+
+ // First arg: address of the TypeManager slot that provides the helper with
+ // information about module index and the type manager instance (which is used
+ // for initialization on first access).
+ encoder.EmitLEAQ(encoder.TargetRegister.Arg0, factory.TypeManagerIndirection);
+ // Second arg: index of the type in the ThreadStatic section of the modules
+ encoder.EmitMOV(encoder.TargetRegister.Arg1, typeTlsIndex);
+
+ if (!factory.TypeSystemContext.HasLazyStaticConstructor(target))
+ {
+ encoder.EmitJMP(factory.HelperEntrypoint(HelperEntrypoint.GetThreadStaticBaseForType));
+ }
+ else
+ {
+ encoder.EmitLEAQ(encoder.TargetRegister.Arg2, factory.TypeNonGCStaticsSymbol(target));
+ // TODO: performance optimization - inline the check verifying whether we need to trigger the cctor
+ encoder.EmitJMP(factory.HelperEntrypoint(HelperEntrypoint.EnsureClassConstructorRunAndReturnThreadStaticBase));
+ }
+ }
break;
case ReadyToRunHelperId.GetGCStaticBase:
diff --git a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs
index 7bd01fb1a..b71d4c103 100644
--- a/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs
+++ b/src/ILCompiler.Compiler/src/Compiler/DependencyAnalysis/TypeManagerIndirectionNode.cs
@@ -27,6 +27,7 @@ namespace ILCompiler.DependencyAnalysis
objData.DefinedSymbols.Add(this);
objData.RequirePointerAlignment();
objData.EmitZeroPointer();
+ objData.EmitZeroPointer();
return objData.ToObjectData();
}
}
diff --git a/src/Native/Runtime/thread.cpp b/src/Native/Runtime/thread.cpp
index 34e523f9e..b149b2553 100644
--- a/src/Native/Runtime/thread.cpp
+++ b/src/Native/Runtime/thread.cpp
@@ -30,6 +30,9 @@
#ifndef DACCESS_COMPILE
+EXTERN_C REDHAWK_API void* REDHAWK_CALLCONV RhpHandleAlloc(void* pObject, int type);
+EXTERN_C REDHAWK_API void REDHAWK_CALLCONV RhHandleFree(void*);
+
#ifdef _MSC_VER
extern "C" void _ReadWriteBarrier(void);
#pragma intrinsic(_ReadWriteBarrier)
@@ -286,6 +289,11 @@ void Thread::Construct()
m_numDynamicTypesTlsCells = 0;
m_pDynamicTypesTlsCells = NULL;
+#if CORERT
+ m_pThreadLocalModuleStatics = NULL;
+ m_numThreadLocalModuleStatics = 0;
+#endif // CORERT
+
// NOTE: We do not explicitly defer to the GC implementation to initialize the alloc_context. The
// alloc_context will be initialized to 0 via the static initialization of tls_CurrentThread. If the
// alloc_context ever needs different initialization, a matching change to the tls_CurrentThread
@@ -368,6 +376,20 @@ void Thread::Destroy()
delete[] m_pDynamicTypesTlsCells;
}
+#if CORERT
+ if (m_pThreadLocalModuleStatics != NULL)
+ {
+ for (UInt32 i = 0; i < m_numThreadLocalModuleStatics; i++)
+ {
+ if (m_pThreadLocalModuleStatics[i] != NULL)
+ {
+ RhHandleFree(m_pThreadLocalModuleStatics[i]);
+ }
+ }
+ delete[] m_pThreadLocalModuleStatics;
+ }
+#endif // CORERT
+
RedhawkGCInterface::ReleaseAllocContext(GetAllocContext());
// Thread::Destroy is called when the thread's "home" fiber dies. We mark the thread as "detached" here
@@ -1130,6 +1152,81 @@ FORCEINLINE void Thread::InlineReversePInvokeReturn(ReversePInvokeFrame * pFrame
}
}
+#if CORERT
+Object* Thread::GetThreadStaticStorageForModule(UInt32 moduleIndex)
+{
+ // Return a pointer to the TLS storage if it has already been
+ // allocated for the specified module.
+ if (moduleIndex < m_numThreadLocalModuleStatics)
+ {
+ Object** threadStaticsStorageHandle = (Object**)m_pThreadLocalModuleStatics[moduleIndex];
+ if (threadStaticsStorageHandle != NULL)
+ {
+ return *threadStaticsStorageHandle;
+ }
+ }
+
+ return NULL;
+}
+
+Boolean Thread::SetThreadStaticStorageForModule(Object * pStorage, UInt32 moduleIndex)
+{
+ // Grow thread local storage if needed.
+ if (m_numThreadLocalModuleStatics <= moduleIndex)
+ {
+ UInt32 newSize = moduleIndex + 1;
+ if (newSize < moduleIndex)
+ {
+ return FALSE;
+ }
+
+ PTR_PTR_VOID pThreadLocalModuleStatics = new (nothrow) PTR_VOID[newSize];
+ if (pThreadLocalModuleStatics == NULL)
+ {
+ return FALSE;
+ }
+
+ memset(&pThreadLocalModuleStatics[m_numThreadLocalModuleStatics], 0, sizeof(PTR_VOID) * (newSize - m_numThreadLocalModuleStatics));
+
+ if (m_pThreadLocalModuleStatics != NULL)
+ {
+ memcpy(pThreadLocalModuleStatics, m_pThreadLocalModuleStatics, sizeof(PTR_VOID) * m_numThreadLocalModuleStatics);
+ delete[] m_pThreadLocalModuleStatics;
+ }
+
+ m_pThreadLocalModuleStatics = pThreadLocalModuleStatics;
+ m_numThreadLocalModuleStatics = newSize;
+ }
+
+ void* threadStaticsStorageHandle = RhpHandleAlloc(pStorage, 2 /* Normal */);
+ if (threadStaticsStorageHandle == NULL)
+ {
+ return FALSE;
+ }
+
+ // Free the existing storage before assigning a new one
+ if (m_pThreadLocalModuleStatics[moduleIndex] != NULL)
+ {
+ RhHandleFree(m_pThreadLocalModuleStatics[moduleIndex]);
+ }
+
+ m_pThreadLocalModuleStatics[moduleIndex] = threadStaticsStorageHandle;
+ return TRUE;
+}
+
+COOP_PINVOKE_HELPER(Array*, RhGetThreadStaticStorageForModule, (UInt32 moduleIndex))
+{
+ Thread * pCurrentThread = ThreadStore::RawGetCurrentThread();
+ return (Array*)pCurrentThread->GetThreadStaticStorageForModule(moduleIndex);
+}
+
+COOP_PINVOKE_HELPER(Boolean, RhSetThreadStaticStorageForModule, (Array * pStorage, UInt32 moduleIndex))
+{
+ Thread * pCurrentThread = ThreadStore::RawGetCurrentThread();
+ return pCurrentThread->SetThreadStaticStorageForModule((Object*)pStorage, moduleIndex);
+}
+#endif // CORERT
+
// Standard calling convention variant and actual implementation for RhpReversePInvokeAttachOrTrapThread
EXTERN_C NOINLINE void FASTCALL RhpReversePInvokeAttachOrTrapThread2(ReversePInvokeFrame * pFrame)
{
diff --git a/src/Native/Runtime/thread.h b/src/Native/Runtime/thread.h
index 5d4d39fcf..5eada8318 100644
--- a/src/Native/Runtime/thread.h
+++ b/src/Native/Runtime/thread.h
@@ -91,6 +91,11 @@ struct ThreadBuffer
// Thread Statics Storage for dynamic types
UInt32 m_numDynamicTypesTlsCells;
PTR_PTR_UInt8 m_pDynamicTypesTlsCells;
+
+#if CORERT
+ PTR_PTR_VOID m_pThreadLocalModuleStatics;
+ UInt32 m_numThreadLocalModuleStatics;
+#endif // CORERT
};
struct ReversePInvokeFrame
@@ -248,6 +253,11 @@ public:
bool InlineTryFastReversePInvoke(ReversePInvokeFrame * pFrame);
void InlineReversePInvokeReturn(ReversePInvokeFrame * pFrame);
+
+#if CORERT
+ Object* GetThreadStaticStorageForModule(UInt32 moduleIndex);
+ Boolean SetThreadStaticStorageForModule(Object * pStorage, UInt32 moduleIndex);
+#endif // CORERT
};
#ifndef GCENV_INCLUDED
diff --git a/src/System.Private.CoreLib/src/Internal/Runtime/ThreadStatics.cs b/src/System.Private.CoreLib/src/Internal/Runtime/ThreadStatics.cs
new file mode 100644
index 000000000..ceff3a00b
--- /dev/null
+++ b/src/System.Private.CoreLib/src/Internal/Runtime/ThreadStatics.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime;
+using Internal.Runtime.CompilerHelpers;
+
+namespace Internal.Runtime
+{
+ /// <summary>
+ /// This class is used by ReadyToRun helpers to get access to thread static fields of a type
+ /// and to allocate required TLS memory.
+ /// </summary>
+ internal static class ThreadStatics
+ {
+ /// <summary>
+ /// This method is called from a ReadyToRun helper to get base address of thread
+ /// static storage for the given type.
+ /// </summary>
+ internal static unsafe object GetThreadStaticBaseForType(TypeManagerSlot* pModuleData, Int32 typeTlsIndex)
+ {
+ // Get the array that holds thread static memory blocks for each type in the given module
+ Int32 moduleIndex = pModuleData->ModuleIndex;
+ object[] storage = (object[])RuntimeImports.RhGetThreadStaticStorageForModule(moduleIndex);
+
+ // Check whether thread static storage has already been allocated for this module and type.
+ if ((storage != null) && (typeTlsIndex < storage.Length) && (storage[typeTlsIndex] != null))
+ {
+ return storage[typeTlsIndex];
+ }
+
+ // This the first access to the thread statics of the type corresponding to typeTlsIndex.
+ // Make sure there is enough storage allocated to hold it.
+ storage = EnsureThreadStaticStorage(moduleIndex, storage, requiredSize: typeTlsIndex + 1);
+
+ // Allocate an object that will represent a memory block for all thread static fields of the type
+ object threadStaticBase = AllocateThreadStaticStorageForType(pModuleData->TypeManager, typeTlsIndex);
+ storage[typeTlsIndex] = threadStaticBase;
+
+ return threadStaticBase;
+
+ }
+
+ /// <summary>
+ /// if it is required, this method extends thread static storage of the given module
+ /// to the specified size and then registers the memory with the runtime.
+ /// </summary>
+ private static object[] EnsureThreadStaticStorage(Int32 moduleIndex, object[] existingStorage, Int32 requiredSize)
+ {
+ if ((existingStorage != null) && (requiredSize < existingStorage.Length))
+ {
+ return existingStorage;
+ }
+
+ object[] newStorage = new object[requiredSize];
+ if (existingStorage != null)
+ {
+ Array.Copy(existingStorage, newStorage, existingStorage.Length);
+ }
+
+ // Install the newly created array as thread static storage for the given module
+ // on the current thread. This call can fail due to a failure to allocate/extend required
+ // internal thread specific resources.
+ if (!RuntimeImports.RhSetThreadStaticStorageForModule(newStorage, moduleIndex))
+ {
+ throw new OutOfMemoryException();
+ }
+
+ return newStorage;
+ }
+
+ /// <summary>
+ /// This method allocates an object that represents a memory block for all thread static fields of the type
+ /// that corresponds to the specified TLS index.
+ /// </summary>
+ private static unsafe object AllocateThreadStaticStorageForType(IntPtr typeManager, Int32 typeTlsIndex)
+ {
+ Int32 length;
+ IntPtr* threadStaticRegion;
+
+ // Get a pointer to the beginning of the module's Thread Static section. Then get a pointer
+ // to the EEType that represents a memory map for thread statics storage.
+ threadStaticRegion = (IntPtr*)RuntimeImports.RhGetModuleSection(typeManager, ReadyToRunSectionType.ThreadStaticRegion, out length);
+ return RuntimeImports.RhNewObject(new EETypePtr(threadStaticRegion[typeTlsIndex]));
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
index 472d017ee..f13af57fe 100644
--- a/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
+++ b/src/System.Private.CoreLib/src/System.Private.CoreLib.csproj
@@ -76,6 +76,7 @@
<Compile Include="..\..\Common\src\Internal\Runtime\CompilerHelpers\StartupDebug.cs">
<Link>Internal\Runtime\CompilerHelpers\StartupCode\StartupDebug.cs</Link>
</Compile>
+ <Compile Include="Internal\Runtime\ThreadStatics.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\StartupCode\StartupCodeHelpers.Extensions.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\ArrayHelpers.cs" />
<Compile Include="Internal\Runtime\CompilerHelpers\InteropHelpers.cs" />
diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs
index eb37ac750..9abd5023e 100644
--- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs
+++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/ClassConstructorRunner.cs
@@ -8,6 +8,9 @@ using System.Diagnostics;
using System.Collections.Generic;
using System.Runtime.InteropServices;
+using Internal.Runtime;
+using Internal.Runtime.CompilerHelpers;
+
namespace System.Runtime.CompilerServices
{
// Marked [EagerStaticClassConstruction] because Cctor.GetCctor
@@ -49,6 +52,13 @@ namespace System.Runtime.CompilerServices
EnsureClassConstructorRun(context);
return nonGcStaticBase;
}
+
+ private unsafe static object CheckStaticClassConstructionReturnThreadStaticBase(TypeManagerSlot* pModuleData, Int32 typeTlsIndex, StaticClassConstructionContext* context)
+ {
+ object threadStaticBase = ThreadStatics.GetThreadStaticBaseForType(pModuleData, typeTlsIndex);
+ EnsureClassConstructorRun(context);
+ return threadStaticBase;
+ }
#endif
public static unsafe void EnsureClassConstructorRun(StaticClassConstructionContext* pContext)
diff --git a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
index 7beebd523..36cd75b18 100644
--- a/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
+++ b/src/System.Private.CoreLib/src/System/Runtime/RuntimeImports.cs
@@ -6,6 +6,7 @@ using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
+using Internal.Runtime;
namespace System.Runtime
{
@@ -418,6 +419,10 @@ namespace System.Runtime
internal static unsafe extern bool RhFindBlob(IntPtr hOsModule, uint blobId, byte** ppbBlob, uint* pcbBlob);
#if CORERT
+ [RuntimeImport(RuntimeLibrary, "RhpGetModuleSection")]
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern IntPtr RhGetModuleSection(IntPtr module, ReadyToRunSectionType section, out int length);
+
internal static uint RhGetLoadedModules(IntPtr[] resultArray)
{
IntPtr[] loadedModules = Internal.Runtime.CompilerHelpers.StartupCodeHelpers.Modules;
@@ -445,6 +450,16 @@ namespace System.Runtime
[RuntimeImport(RuntimeLibrary, "RhGetThreadStaticFieldAddress")]
internal static unsafe extern byte* RhGetThreadStaticFieldAddress(EETypePtr pEEType, IntPtr fieldCookie);
+#if CORERT
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ [RuntimeImport(RuntimeLibrary, "RhGetThreadStaticStorageForModule")]
+ internal static unsafe extern Array RhGetThreadStaticStorageForModule(Int32 moduleIndex);
+
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ [RuntimeImport(RuntimeLibrary, "RhSetThreadStaticStorageForModule")]
+ internal static unsafe extern bool RhSetThreadStaticStorageForModule(Array storage, Int32 moduleIndex);
+#endif
+
[MethodImplAttribute(MethodImplOptions.InternalCall)]
[RuntimeImport(RuntimeLibrary, "RhGetCodeTarget")]
internal static extern IntPtr RhGetCodeTarget(IntPtr pCode);
diff --git a/src/Test.CoreLib/src/System/Runtime/RuntimeImports.cs b/src/Test.CoreLib/src/System/Runtime/RuntimeImports.cs
index e240db0a2..f32429fcc 100644
--- a/src/Test.CoreLib/src/System/Runtime/RuntimeImports.cs
+++ b/src/Test.CoreLib/src/System/Runtime/RuntimeImports.cs
@@ -6,6 +6,7 @@ using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
+using Internal.Runtime;
namespace System.Runtime
{
@@ -44,6 +45,10 @@ namespace System.Runtime
[RuntimeImport(RuntimeLibrary, "RhpRegisterFrozenSegment")]
internal static extern bool RhpRegisterFrozenSegment(IntPtr pSegmentStart, int length);
+ [RuntimeImport(RuntimeLibrary, "RhpGetModuleSection")]
+ [MethodImplAttribute(MethodImplOptions.InternalCall)]
+ internal static extern IntPtr RhGetModuleSection(IntPtr module, ReadyToRunSectionType section, out int length);
+
//
// calls to runtime for allocation
// These calls are needed in types which cannot use "new" to allocate and need to do it manually
diff --git a/tests/src/Simple/BasicThreading/BasicThreading.cmd b/tests/src/Simple/BasicThreading/BasicThreading.cmd
new file mode 100644
index 000000000..aa72a68bb
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/BasicThreading.cmd
@@ -0,0 +1,12 @@
+@echo off
+setlocal
+"%1\%2"
+set ErrorCode=%ERRORLEVEL%
+IF "%ErrorCode%"=="100" (
+ echo %~n0: pass
+ EXIT /b 0
+) ELSE (
+ echo %~n0: fail
+ EXIT /b 1
+)
+endlocal
diff --git a/tests/src/Simple/BasicThreading/BasicThreading.cs b/tests/src/Simple/BasicThreading/BasicThreading.cs
new file mode 100644
index 000000000..6d2f9c9d4
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/BasicThreading.cs
@@ -0,0 +1,139 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Threading.Tasks;
+
+class Program
+{
+ static int Main()
+ {
+ SimpleReadWriteThreadStaticTest.Run(42, "SimpleReadWriteThreadStatic");
+ ThreadStaticsTestWithTasks.Run();
+ return 100;
+ }
+}
+
+class SimpleReadWriteThreadStaticTest
+{
+ public static void Run(int intValue, string stringValue)
+ {
+ NonGenericReadWriteThreadStaticsTest(intValue, "NonGeneric" + stringValue);
+ GenericReadWriteThreadStaticsTest(intValue + 1, "Generic" + stringValue);
+ }
+
+ class NonGenericType
+ {
+ [ThreadStatic]
+ public static int IntValue;
+
+ [ThreadStatic]
+ public static string StringValue;
+ }
+
+ class GenericType<T, V>
+ {
+ [ThreadStatic]
+ public static T ValueT;
+
+ [ThreadStatic]
+ public static V ValueV;
+ }
+
+ static void NonGenericReadWriteThreadStaticsTest(int intValue, string stringValue)
+ {
+ NonGenericType.IntValue = intValue;
+ NonGenericType.StringValue = stringValue;
+
+ if (NonGenericType.IntValue != intValue)
+ {
+ throw new Exception("SimpleReadWriteThreadStaticsTest: wrong integer value: " + NonGenericType.IntValue.ToString());
+ }
+
+ if (NonGenericType.StringValue != stringValue)
+ {
+ throw new Exception("SimpleReadWriteThreadStaticsTest: wrong string value: " + NonGenericType.StringValue);
+ }
+ }
+
+ static void GenericReadWriteThreadStaticsTest(int intValue, string stringValue)
+ {
+ GenericType<int, string>.ValueT = intValue;
+ GenericType<int, string>.ValueV = stringValue;
+
+ if (GenericType<int, string>.ValueT != intValue)
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest1a: wrong integer value: " + GenericType<int, string>.ValueT.ToString());
+ }
+
+ if (GenericType<int, string>.ValueV != stringValue)
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest1b: wrong string value: " + GenericType<int, string>.ValueV);
+ }
+
+ intValue++;
+ GenericType<int, int>.ValueT = intValue;
+ GenericType<int, int>.ValueV = intValue + 1;
+
+ if (GenericType<int, int>.ValueT != intValue)
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest2a: wrong integer value: " + GenericType<int, string>.ValueT.ToString());
+ }
+
+ if (GenericType<int, int>.ValueV != (intValue + 1))
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest2b: wrong integer value: " + GenericType<int, string>.ValueV.ToString());
+ }
+
+ GenericType<string, string>.ValueT = stringValue + "a";
+ GenericType<string, string>.ValueV = stringValue + "b";
+
+ if (GenericType<string, string>.ValueT != (stringValue + "a"))
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest3a: wrong string value: " + GenericType<string, string>.ValueT);
+ }
+
+ if (GenericType<string, string>.ValueV != (stringValue + "b"))
+ {
+ throw new Exception("GenericReadWriteThreadStaticsTest3b: wrong string value: " + GenericType<string, string>.ValueV);
+ }
+ }
+}
+
+class ThreadStaticsTestWithTasks
+{
+ static object lockObject = new object();
+ const int TotalTaskCount = 32;
+
+ public static void Run()
+ {
+ Task[] tasks = new Task[TotalTaskCount];
+ for (int i = 0; i < tasks.Length; ++i)
+ {
+ tasks[i] = Task.Factory.StartNew((param) =>
+ {
+ int index = (int)param;
+ int intTestValue = index * 10;
+ string stringTestValue = "ThreadStaticsTestWithTasks" + index;
+
+ // Try to run the on every other task
+ if ((index % 2) == 0)
+ {
+ lock (lockObject)
+ {
+ SimpleReadWriteThreadStaticTest.Run(intTestValue, stringTestValue);
+ }
+ }
+ else
+ {
+ SimpleReadWriteThreadStaticTest.Run(intTestValue, stringTestValue);
+ }
+ }, i);
+ }
+ for (int i = 0; i < tasks.Length; ++i)
+ {
+ tasks[i].Wait();
+ }
+ }
+}
diff --git a/tests/src/Simple/BasicThreading/BasicThreading.csproj b/tests/src/Simple/BasicThreading/BasicThreading.csproj
new file mode 100644
index 000000000..0948ddf81
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/BasicThreading.csproj
@@ -0,0 +1,7 @@
+<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Compile Include="*.cs" />
+ </ItemGroup>
+
+ <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), SimpleTest.targets))\SimpleTest.targets" />
+</Project>
diff --git a/tests/src/Simple/BasicThreading/BasicThreading.sh b/tests/src/Simple/BasicThreading/BasicThreading.sh
new file mode 100644
index 000000000..6640f629f
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/BasicThreading.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+$1/$2
+if [ $? == 100 ]; then
+ echo pass
+ exit 0
+else
+ echo fail
+ exit 1
+fi
diff --git a/tests/src/Simple/BasicThreading/no_cpp b/tests/src/Simple/BasicThreading/no_cpp
new file mode 100644
index 000000000..639bcaf8b
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/no_cpp
@@ -0,0 +1 @@
+Skip this test for cpp codegen mode
diff --git a/tests/src/Simple/BasicThreading/no_unix b/tests/src/Simple/BasicThreading/no_unix
new file mode 100644
index 000000000..e6c22996c
--- /dev/null
+++ b/tests/src/Simple/BasicThreading/no_unix
@@ -0,0 +1 @@
+Doesn't work on OSX. \ No newline at end of file