From 54280788590d6012758302d4056aa45720133be2 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 6 Sep 2024 14:11:33 -0700 Subject: [PATCH] Remove Helper Method Frames for Exception, GC and Thread methods (#107218) * Convert Exception.GetFrozenStackTrace() * Convert GC.AllocateNewArray() Removed use of Unsafe.As(). * Convert Thread.GetApartmentStateNative() and Thread.SetApartmentStateNative() * Convert Thread.Join() * Convert Thread.Priority property --- .../src/System/Exception.CoreCLR.cs | 10 +- .../src/System/GC.CoreCLR.cs | 29 +- .../src/System/Threading/Thread.CoreCLR.cs | 79 +++-- src/coreclr/vm/comsynchronizable.cpp | 270 +++++++----------- src/coreclr/vm/comsynchronizable.h | 18 +- src/coreclr/vm/comutilnative.cpp | 65 ++--- src/coreclr/vm/comutilnative.h | 7 +- src/coreclr/vm/corelib.h | 1 + src/coreclr/vm/ecalllist.h | 10 - src/coreclr/vm/object.h | 9 + src/coreclr/vm/qcallentrypoints.cpp | 8 + src/coreclr/vm/threads.cpp | 18 +- src/coreclr/vm/threads.h | 4 - .../src/linker/Linker.Steps/MarkStep.cs | 1 + 14 files changed, 250 insertions(+), 279 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs index 79944b1cca8e6..1029f3e570e33 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs @@ -117,9 +117,6 @@ internal void InternalPreserveStackTrace() [MethodImpl(MethodImplOptions.InternalCall)] private static extern void PrepareForForeignExceptionRaise(); - [MethodImpl(MethodImplOptions.InternalCall)] - private static extern object? GetFrozenStackTrace(Exception exception); - [MethodImpl(MethodImplOptions.InternalCall)] internal static extern uint GetExceptionCount(); @@ -226,9 +223,14 @@ public DispatchState( } } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ExceptionNative_GetFrozenStackTrace")] + private static partial void GetFrozenStackTrace(ObjectHandleOnStack exception, ObjectHandleOnStack stackTrace); + internal DispatchState CaptureDispatchState() { - object? stackTrace = GetFrozenStackTrace(this); + Exception _this = this; + object? stackTrace = null; + GetFrozenStackTrace(ObjectHandleOnStack.Create(ref _this), ObjectHandleOnStack.Create(ref stackTrace)); return new DispatchState(stackTrace, _remoteStackTraceString, _ipForWatsonBuckets, _watsonBuckets); diff --git a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs index 1bd94635a26c7..f8246642b9e61 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs @@ -103,8 +103,8 @@ internal enum GC_ALLOC_FLAGS GC_ALLOC_PINNED_OBJECT_HEAP = 64, }; - [MethodImpl(MethodImplOptions.InternalCall)] - internal static extern Array AllocateNewArray(IntPtr typeHandle, int length, GC_ALLOC_FLAGS flags); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_AllocateNewArray")] + private static partial void AllocateNewArray(IntPtr typeHandlePtr, int length, GC_ALLOC_FLAGS flags, ObjectHandleOnStack ret); [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCInterface_GetTotalMemory")] private static partial long GetTotalMemory(); @@ -791,16 +791,25 @@ public static unsafe T[] AllocateUninitializedArray(int length, bool pinned = { return new T[length]; } - #endif } - // Runtime overrides GC_ALLOC_ZEROING_OPTIONAL if the type contains references, so we don't need to worry about that. - GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_ZEROING_OPTIONAL; - if (pinned) - flags |= GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; + return AllocateNewArrayWorker(length, pinned); + + [MethodImpl(MethodImplOptions.NoInlining)] + static T[] AllocateNewArrayWorker(int length, bool pinned) + { + // Runtime overrides GC_ALLOC_ZEROING_OPTIONAL if the type contains references, so we don't need to worry about that. + GC_ALLOC_FLAGS flags = GC_ALLOC_FLAGS.GC_ALLOC_ZEROING_OPTIONAL; + if (pinned) + { + flags |= GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; + } - return Unsafe.As(AllocateNewArray(RuntimeTypeHandle.ToIntPtr(typeof(T[]).TypeHandle), length, flags)); + T[]? result = null; + AllocateNewArray(RuntimeTypeHandle.ToIntPtr(typeof(T[]).TypeHandle), length, flags, ObjectHandleOnStack.Create(ref result)); + return result!; + } } /// @@ -818,7 +827,9 @@ public static T[] AllocateArray(int length, bool pinned = false) // T[] rathe flags = GC_ALLOC_FLAGS.GC_ALLOC_PINNED_OBJECT_HEAP; } - return Unsafe.As(AllocateNewArray(RuntimeTypeHandle.ToIntPtr(typeof(T[]).TypeHandle), length, flags)); + T[]? result = null; + AllocateNewArray(RuntimeTypeHandle.ToIntPtr(typeof(T[]).TypeHandle), length, flags, ObjectHandleOnStack.Create(ref result)); + return result!; } [MethodImpl(MethodImplOptions.InternalCall)] diff --git a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs index d7d83ba51ac0c..486e5a505abeb 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Threading/Thread.CoreCLR.cs @@ -58,6 +58,9 @@ public sealed partial class Thread // but those types of changes may race with the reset anyway, so this field doesn't need to be synchronized. private bool _mayNeedResetForThreadPool; + // Set in unmanaged and read in managed code. + private bool _isDead; + private Thread() { } public int ManagedThreadId @@ -74,7 +77,7 @@ internal ThreadHandle GetNativeHandle() // This should never happen under normal circumstances. if (thread == IntPtr.Zero) { - throw new ArgumentException(null, SR.Argument_InvalidHandle); + throw new ThreadStateException(SR.Argument_InvalidHandle); } return new ThreadHandle(thread); @@ -211,13 +214,25 @@ public extern bool IsThreadPoolThread internal set; } + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SetPriority")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial void SetPriority(ObjectHandleOnStack thread, int priority); + /// Returns the priority of the thread. public ThreadPriority Priority { - get => (ThreadPriority)GetPriorityNative(); + get + { + if (_isDead) + { + throw new ThreadStateException(SR.ThreadState_Dead_Priority); + } + return (ThreadPriority)_priority; + } set { - SetPriorityNative((int)value); + Thread _this = this; + SetPriority(ObjectHandleOnStack.Create(ref _this), (int)value); if (value != ThreadPriority.Normal) { _mayNeedResetForThreadPool = true; @@ -225,12 +240,6 @@ public ThreadPriority Priority } } - [MethodImpl(MethodImplOptions.InternalCall)] - private extern int GetPriorityNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - private extern void SetPriorityNative(int priority); - [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_GetCurrentOSThreadId")] private static partial ulong GetCurrentOSThreadId(); @@ -243,21 +252,31 @@ public ThreadPriority Priority [MethodImpl(MethodImplOptions.InternalCall)] private extern int GetThreadStateNative(); - public ApartmentState GetApartmentState() => -#if FEATURE_COMINTEROP_APARTMENT_SUPPORT - (ApartmentState)GetApartmentStateNative(); -#else // !FEATURE_COMINTEROP_APARTMENT_SUPPORT - ApartmentState.Unknown; -#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT - /// /// An unstarted thread can be marked to indicate that it will host a /// single-threaded or multi-threaded apartment. /// #if FEATURE_COMINTEROP_APARTMENT_SUPPORT + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_GetApartmentState")] + private static partial int GetApartmentState(ObjectHandleOnStack t); + + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_SetApartmentState")] + private static partial int SetApartmentState(ObjectHandleOnStack t, int state); + + public ApartmentState GetApartmentState() + { + Thread _this = this; + return (ApartmentState)GetApartmentState(ObjectHandleOnStack.Create(ref _this)); + } + private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) { - ApartmentState retState = (ApartmentState)SetApartmentStateNative((int)state); + ApartmentState retState; + lock (this) // This lock is only needed when the this is not the current thread. + { + Thread _this = this; + retState = (ApartmentState)SetApartmentState(ObjectHandleOnStack.Create(ref _this), (int)state); + } // Special case where we pass in Unknown and get back MTA. // Once we CoUninitialize the thread, the OS will still @@ -282,12 +301,9 @@ private bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) return true; } - [MethodImpl(MethodImplOptions.InternalCall)] - internal extern int GetApartmentStateNative(); - - [MethodImpl(MethodImplOptions.InternalCall)] - internal extern int SetApartmentStateNative(int state); #else // FEATURE_COMINTEROP_APARTMENT_SUPPORT + public ApartmentState GetApartmentState() => ApartmentState.Unknown; + private static bool SetApartmentStateUnchecked(ApartmentState state, bool throwOnError) { if (state != ApartmentState.Unknown) @@ -331,6 +347,10 @@ public void Interrupt() [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_Interrupt")] private static partial void Interrupt(ThreadHandle t); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ThreadNative_Join")] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool Join(ObjectHandleOnStack thread, int millisecondsTimeout); + /// /// Waits for the thread to die or for timeout milliseconds to elapse. /// @@ -338,11 +358,20 @@ public void Interrupt() /// Returns true if the thread died, or false if the wait timed out. If /// -1 is given as the parameter, no timeout will occur. /// - /// if timeout < -1 (Timeout.Infinite) + /// if timeout < -1 (Timeout.Infinite) /// if the thread is interrupted while waiting /// if the thread has not been started yet - [MethodImpl(MethodImplOptions.InternalCall)] - public extern bool Join(int millisecondsTimeout); + public bool Join(int millisecondsTimeout) + { + // Validate the timeout + if (millisecondsTimeout < 0 && millisecondsTimeout != Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), SR.ArgumentOutOfRange_NeedNonNegOrNegative1); + } + + Thread _this = this; + return Join(ObjectHandleOnStack.Create(ref _this), millisecondsTimeout); + } /// /// Max value to be passed into for optimal delaying. This value is normalized to be diff --git a/src/coreclr/vm/comsynchronizable.cpp b/src/coreclr/vm/comsynchronizable.cpp index 23dfb4d238803..73cc949ec66d5 100644 --- a/src/coreclr/vm/comsynchronizable.cpp +++ b/src/coreclr/vm/comsynchronizable.cpp @@ -69,37 +69,28 @@ static INT32 MapToNTPriority(INT32 ours) } CONTRACTL_END; - INT32 NTPriority = 0; - switch (ours) { case ThreadNative::PRIORITY_LOWEST: - NTPriority = THREAD_PRIORITY_LOWEST; - break; + return THREAD_PRIORITY_LOWEST; case ThreadNative::PRIORITY_BELOW_NORMAL: - NTPriority = THREAD_PRIORITY_BELOW_NORMAL; - break; + return THREAD_PRIORITY_BELOW_NORMAL; case ThreadNative::PRIORITY_NORMAL: - NTPriority = THREAD_PRIORITY_NORMAL; - break; + return THREAD_PRIORITY_NORMAL; case ThreadNative::PRIORITY_ABOVE_NORMAL: - NTPriority = THREAD_PRIORITY_ABOVE_NORMAL; - break; + return THREAD_PRIORITY_ABOVE_NORMAL; case ThreadNative::PRIORITY_HIGHEST: - NTPriority = THREAD_PRIORITY_HIGHEST; - break; + return THREAD_PRIORITY_HIGHEST; default: COMPlusThrow(kArgumentOutOfRangeException, W("Argument_InvalidFlag")); } - return NTPriority; } - // Map to our exposed notion of thread priorities from the enumeration that NT uses. INT32 MapFromNTPriority(INT32 NTPriority) { @@ -252,6 +243,8 @@ extern "C" void QCALLTYPE ThreadNative_Start(QCall::ThreadHandle thread, int thr void ThreadNative::Start(Thread* pNewThread, int threadStackSize, int priority, PCWSTR pThreadName) { + STANDARD_VM_CONTRACT; + _ASSERTE(pNewThread != NULL); // Is the thread already started? You can't restart a thread. @@ -292,7 +285,9 @@ void ThreadNative::Start(Thread* pNewThread, int threadStackSize, int priority, // After we have established the thread handle, we can check m_Priority. // This ordering is required to eliminate the race condition on setting the // priority of a thread just as it starts up. - pNewThread->SetThreadPriority(MapToNTPriority(priority)); + INT32 NTPriority = MapToNTPriority(priority); + + pNewThread->SetThreadPriority(NTPriority); pNewThread->ChooseThreadCPUGroupAffinity(); pNewThread->SetThreadState(Thread::TS_LegalToJoin); @@ -326,65 +321,46 @@ void ThreadNative::Start(Thread* pNewThread, int threadStackSize, int priority, } } -// Note that you can manipulate the priority of a thread that hasn't started yet, -// or one that is running. But you get an exception if you manipulate the priority -// of a thread that has died. -FCIMPL1(INT32, ThreadNative::GetPriority, ThreadBaseObject* pThisUNSAFE) +extern "C" void QCALLTYPE ThreadNative_SetPriority(QCall::ObjectHandleOnStack thread, INT32 iPriority) { - FCALL_CONTRACT; - - if (pThisUNSAFE==NULL) - FCThrowRes(kNullReferenceException, W("NullReference_This")); - - // validate the handle - if (ThreadIsDead(pThisUNSAFE->GetInternal())) - FCThrowRes(kThreadStateException, W("ThreadState_Dead_Priority")); - - return pThisUNSAFE->m_Priority; -} -FCIMPLEND + QCALL_CONTRACT; -FCIMPL2(void, ThreadNative::SetPriority, ThreadBaseObject* pThisUNSAFE, INT32 iPriority) -{ - FCALL_CONTRACT; + BEGIN_QCALL; - int priority; - Thread *thread; + GCX_COOP(); - THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; - HELPER_METHOD_FRAME_BEGIN_1(pThis); + THREADBASEREF threadRef = NULL; + GCPROTECT_BEGIN(threadRef) + threadRef = (THREADBASEREF)thread.Get(); - if (pThis==NULL) - { + if (threadRef == NULL) COMPlusThrow(kNullReferenceException, W("NullReference_This")); - } - - // translate the priority (validating as well) - priority = MapToNTPriority(iPriority); // can throw; needs a frame - // validate the thread - thread = pThis->GetInternal(); - - if (ThreadIsDead(thread)) - { + // Note that you can manipulate the priority of a thread that hasn't started yet, + // or one that is running. But you get an exception if you manipulate the priority + // of a thread that has died. + Thread* th = threadRef->GetInternal(); + if (ThreadIsDead(th)) COMPlusThrow(kThreadStateException, W("ThreadState_Dead_Priority")); - } - INT32 oldPriority = pThis->m_Priority; + // translate the priority (validating as well) + INT32 priority = MapToNTPriority(iPriority); + + INT32 oldPriority = threadRef->GetPriority(); - // Eliminate the race condition by establishing m_Priority before we check for if - // the thread is running. See ThreadNative::Start() for the other half. - pThis->m_Priority = iPriority; + // Eliminate the race condition by setting priority field before we check for if + // the thread is running. See ThreadNative::Start() for the other half. + threadRef->SetPriority(iPriority); - if (!thread->SetThreadPriority(priority)) + if (!th->SetThreadPriority(priority)) { - pThis->m_Priority = oldPriority; + threadRef->SetPriority(oldPriority); COMPlusThrow(kThreadStateException, W("ThreadState_SetPriorityFailed")); } - HELPER_METHOD_FRAME_END(); + GCPROTECT_END(); + END_QCALL; } -FCIMPLEND FCIMPL1(FC_BOOL_RET, ThreadNative::IsAlive, ThreadBaseObject* pThisUNSAFE) { @@ -417,30 +393,6 @@ FCIMPL1(FC_BOOL_RET, ThreadNative::IsAlive, ThreadBaseObject* pThisUNSAFE) } FCIMPLEND -FCIMPL2(FC_BOOL_RET, ThreadNative::Join, ThreadBaseObject* pThisUNSAFE, INT32 Timeout) -{ - FCALL_CONTRACT; - - BOOL retVal = FALSE; - THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; - - HELPER_METHOD_FRAME_BEGIN_RET_1(pThis); - - if (pThis==NULL) - COMPlusThrow(kNullReferenceException, W("NullReference_This")); - - // validate the timeout - if ((Timeout < 0) && (Timeout != INFINITE_TIMEOUT)) - COMPlusThrowArgumentOutOfRange(W("millisecondsTimeout"), W("ArgumentOutOfRange_NeedNonNegOrNegative1")); - - retVal = DoJoin(pThis, Timeout); - - HELPER_METHOD_FRAME_END(); - - FC_RETURN_BOOL(retVal); -} -FCIMPLEND - NOINLINE static Object* GetCurrentThreadHelper() { FCALL_CONTRACT; @@ -591,83 +543,27 @@ FCIMPLEND #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT -// Indicate whether the thread will host an STA (this may fail if the thread has -// already been made part of the MTA, use GetApartmentState or the return state -// from this routine to check for this). -FCIMPL2(INT32, ThreadNative::SetApartmentState, ThreadBaseObject* pThisUNSAFE, INT32 iState) -{ - FCALL_CONTRACT; - - if (pThisUNSAFE==NULL) - FCThrowRes(kNullReferenceException, W("NullReference_This")); - - BOOL ok = TRUE; - THREADBASEREF pThis = (THREADBASEREF) pThisUNSAFE; - - HELPER_METHOD_FRAME_BEGIN_RET_1(pThis); - - Thread *thread = pThis->GetInternal(); - if (!thread) - COMPlusThrow(kThreadStateException, IDS_EE_THREAD_CANNOT_GET); - - { - pThis->EnterObjMonitor(); - - // We can only change the apartment if the thread is unstarted or - // running, and if it's running we have to be in the thread's - // context. - if ((!ThreadNotStarted(thread) && !ThreadIsRunning(thread)) || - (!ThreadNotStarted(thread) && (GetThread() != thread))) - ok = FALSE; - else - { - EX_TRY - { - iState = thread->SetApartment((Thread::ApartmentState)iState); - } - EX_CATCH - { - pThis->LeaveObjMonitor(); - EX_RETHROW; - } - EX_END_CATCH_UNREACHABLE; - } - - pThis->LeaveObjMonitor(); - } - - // Now it's safe to throw exceptions again. - if (!ok) - COMPlusThrow(kThreadStateException); - - HELPER_METHOD_FRAME_END(); - - return iState; -} -FCIMPLEND - // Return whether the thread hosts an STA, is a member of the MTA or is not // currently initialized for COM. -FCIMPL1(INT32, ThreadNative::GetApartmentState, ThreadBaseObject* pThisUNSAFE) +extern "C" INT32 QCALLTYPE ThreadNative_GetApartmentState(QCall::ObjectHandleOnStack t) { - FCALL_CONTRACT; + QCALL_CONTRACT; INT32 retVal = 0; - THREADBASEREF refThis = (THREADBASEREF) ObjectToOBJECTREF(pThisUNSAFE); - - HELPER_METHOD_FRAME_BEGIN_RET_1(refThis); + BEGIN_QCALL; - if (refThis == NULL) + Thread* thread = NULL; { - COMPlusThrow(kNullReferenceException, W("NullReference_This")); - } + GCX_COOP(); + THREADBASEREF threadRef = (THREADBASEREF)t.Get(); + if (threadRef == NULL) + COMPlusThrow(kNullReferenceException, W("NullReference_This")); - Thread* thread = refThis->GetInternal(); + thread = threadRef->GetInternal(); - if (ThreadIsDead(thread)) - { - COMPlusThrow(kThreadStateException, W("ThreadState_Dead_State")); + if (ThreadIsDead(thread)) + COMPlusThrow(kThreadStateException, W("ThreadState_Dead_State")); } retVal = thread->GetApartment(); @@ -686,12 +582,45 @@ FCIMPL1(INT32, ThreadNative::GetApartmentState, ThreadBaseObject* pThisUNSAFE) } #endif // FEATURE_COMINTEROP - HELPER_METHOD_FRAME_END(); - + END_QCALL; return retVal; } -FCIMPLEND +// Indicate whether the thread will host an STA (this may fail if the thread has +// already been made part of the MTA, use GetApartmentState or the return state +// from this routine to check for this). +extern "C" INT32 QCALLTYPE ThreadNative_SetApartmentState(QCall::ObjectHandleOnStack t, INT32 iState) +{ + QCALL_CONTRACT; + + INT32 retVal = 0; + + BEGIN_QCALL; + + Thread* thread = NULL; + { + GCX_COOP(); + THREADBASEREF threadRef = (THREADBASEREF)t.Get(); + if (threadRef == NULL) + COMPlusThrow(kNullReferenceException, W("NullReference_This")); + + thread = threadRef->GetInternal(); + } + + // We can only change the apartment if the thread is unstarted or + // running, and if it's running we have to be in the thread's + // context. + if (!ThreadNotStarted(thread) + && (!ThreadIsRunning(thread) || (GetThread() != thread))) + { + COMPlusThrow(kThreadStateException); + } + + retVal = thread->SetApartment((Thread::ApartmentState)iState); + + END_QCALL; + return retVal; +} #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT void ReleaseThreadExternalCount(Thread * pThread) @@ -703,23 +632,23 @@ void ReleaseThreadExternalCount(Thread * pThread) typedef Holder ThreadExternalCountHolder; // Wait for the thread to die -BOOL ThreadNative::DoJoin(THREADBASEREF DyingThread, INT32 timeout) +static BOOL DoJoin(THREADBASEREF dyingThread, INT32 timeout) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; - PRECONDITION(DyingThread != NULL); + PRECONDITION(dyingThread != NULL); PRECONDITION((timeout >= 0) || (timeout == INFINITE_TIMEOUT)); } CONTRACTL_END; - Thread * DyingInternal = DyingThread->GetInternal(); + Thread* DyingInternal = dyingThread->GetInternal(); // Validate the handle. It's valid to Join a thread that's not running -- so // long as it was once started. - if (DyingInternal == 0 || + if (DyingInternal == NULL || !(DyingInternal->m_State & Thread::TS_LegalToJoin)) { COMPlusThrow(kThreadStateException, W("ThreadState_NotStarted")); @@ -730,12 +659,8 @@ BOOL ThreadNative::DoJoin(THREADBASEREF DyingThread, INT32 timeout) if (ThreadIsDead(DyingInternal) || !DyingInternal->HasValidThreadHandle()) return TRUE; - DWORD dwTimeOut32 = (timeout == INFINITE_TIMEOUT - ? INFINITE - : (DWORD) timeout); - - // There is a race here. DyingThread is going to close its thread handle. - // If we grab the handle and then DyingThread closes it, we will wait forever + // There is a race here. The Thread is going to close its thread handle. + // If we grab the handle and then the Thread closes it, we will wait forever // in DoAppropriateWait. int RefCount = DyingInternal->IncExternalCount(); if (RefCount == 1) @@ -756,8 +681,11 @@ BOOL ThreadNative::DoJoin(THREADBASEREF DyingThread, INT32 timeout) } GCX_PREEMP(); - DWORD rv = DyingInternal->JoinEx(dwTimeOut32, (WaitMode)(WaitMode_Alertable/*alertable*/|WaitMode_InDeadlock)); + DWORD dwTimeOut32 = (timeout == INFINITE_TIMEOUT + ? INFINITE + : (DWORD) timeout); + DWORD rv = DyingInternal->JoinEx(dwTimeOut32, (WaitMode)(WaitMode_Alertable/*alertable*/|WaitMode_InDeadlock)); switch(rv) { case WAIT_OBJECT_0: @@ -779,6 +707,22 @@ BOOL ThreadNative::DoJoin(THREADBASEREF DyingThread, INT32 timeout) return FALSE; } +extern "C" BOOL QCALLTYPE ThreadNative_Join(QCall::ObjectHandleOnStack thread, INT32 Timeout) +{ + QCALL_CONTRACT; + + BOOL retVal = FALSE; + + BEGIN_QCALL; + + GCX_COOP(); + retVal = DoJoin((THREADBASEREF)thread.Get(), Timeout); + + END_QCALL; + + return retVal; +} + // If the exposed object is created after-the-fact, for an existing thread, we call // InitExisting on it. This is the other "construction", as opposed to SetDelegate. void ThreadBaseObject::InitExisting() diff --git a/src/coreclr/vm/comsynchronizable.h b/src/coreclr/vm/comsynchronizable.h index b0f5b72295a15..9180216deeea9 100644 --- a/src/coreclr/vm/comsynchronizable.h +++ b/src/coreclr/vm/comsynchronizable.h @@ -52,20 +52,11 @@ friend class ThreadBaseObject; ThreadAbortRequested = 128, }; - static FCDECL1(INT32, GetPriority, ThreadBaseObject* pThisUNSAFE); - static FCDECL2(void, SetPriority, ThreadBaseObject* pThisUNSAFE, INT32 iPriority); static FCDECL1(FC_BOOL_RET, IsAlive, ThreadBaseObject* pThisUNSAFE); - static FCDECL2(FC_BOOL_RET, Join, ThreadBaseObject* pThisUNSAFE, INT32 Timeout); static FCDECL1(void, Initialize, ThreadBaseObject* pThisUNSAFE); static FCDECL1(FC_BOOL_RET, GetIsBackground, ThreadBaseObject* pThisUNSAFE); static FCDECL1(INT32, GetThreadState, ThreadBaseObject* pThisUNSAFE); -#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT - static FCDECL1(INT32, GetApartmentState, ThreadBaseObject* pThis); - static FCDECL2(INT32, SetApartmentState, ThreadBaseObject* pThisUNSAFE, INT32 iState); -#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT - - static FCDECL0(INT32, GetOptimalMaxSpinWaitsPerSpinIteration); static FCDECL0(Object*, GetCurrentThread); static FCDECL1(void, Finalize, ThreadBaseObject* pThis); @@ -84,14 +75,21 @@ friend class ThreadBaseObject; static void KickOffThread_Worker(LPVOID /* KickOffThread_Args* */); static ULONG WINAPI KickOffThread(void *pass); - static BOOL DoJoin(THREADBASEREF DyingThread, INT32 timeout); }; extern "C" void QCALLTYPE ThreadNative_Start(QCall::ThreadHandle thread, int threadStackSize, int priority, PCWSTR pThreadName); +extern "C" void QCALLTYPE ThreadNative_SetPriority(QCall::ObjectHandleOnStack thread, INT32 iPriority); extern "C" void QCALLTYPE ThreadNative_SetIsBackground(QCall::ThreadHandle thread, BOOL value); extern "C" void QCALLTYPE ThreadNative_InformThreadNameChange(QCall::ThreadHandle thread, LPCWSTR name, INT32 len); extern "C" BOOL QCALLTYPE ThreadNative_YieldThread(); extern "C" UINT64 QCALLTYPE ThreadNative_GetCurrentOSThreadId(); + +#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT +extern "C" INT32 QCALLTYPE ThreadNative_GetApartmentState(QCall::ObjectHandleOnStack t); +extern "C" INT32 QCALLTYPE ThreadNative_SetApartmentState(QCall::ObjectHandleOnStack t, INT32 iState); +#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + +extern "C" BOOL QCALLTYPE ThreadNative_Join(QCall::ObjectHandleOnStack thread, INT32 Timeout); extern "C" void QCALLTYPE ThreadNative_Abort(QCall::ThreadHandle thread); extern "C" void QCALLTYPE ThreadNative_ResetAbort(); extern "C" void QCALLTYPE ThreadNative_SpinWait(INT32 iterations); diff --git a/src/coreclr/vm/comutilnative.cpp b/src/coreclr/vm/comutilnative.cpp index a4f0eca49a330..f15b1085ebab4 100644 --- a/src/coreclr/vm/comutilnative.cpp +++ b/src/coreclr/vm/comutilnative.cpp @@ -78,29 +78,26 @@ FCIMPLEND // Given an exception object, this method will mark its stack trace as frozen and return it to the caller. // Frozen stack traces are immutable, when a thread attempts to add a frame to it, the stack trace is cloned first. -FCIMPL1(Object *, ExceptionNative::GetFrozenStackTrace, Object* pExceptionObjectUnsafe); +extern "C" void QCALLTYPE ExceptionNative_GetFrozenStackTrace(QCall::ObjectHandleOnStack exception, QCall::ObjectHandleOnStack ret) { - CONTRACTL - { - FCALL_CHECK; - } - CONTRACTL_END; + QCALL_CONTRACT; + + BEGIN_QCALL; + + GCX_COOP(); - ASSERT(pExceptionObjectUnsafe != NULL); + _ASSERTE(exception.Get() != NULL); struct { StackTraceArray stackTrace; EXCEPTIONREF refException = NULL; PTRARRAYREF keepAliveArray = NULL; // Object array of Managed Resolvers / AssemblyLoadContexts - OBJECTREF result = NULL; } gc; - - // GC protect the array reference - HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + GCPROTECT_BEGIN(gc); // Get the exception object reference - gc.refException = (EXCEPTIONREF)(ObjectToOBJECTREF(pExceptionObjectUnsafe)); + gc.refException = (EXCEPTIONREF)exception.Get(); gc.refException->GetStackTrace(gc.stackTrace, &gc.keepAliveArray); @@ -108,22 +105,20 @@ FCIMPL1(Object *, ExceptionNative::GetFrozenStackTrace, Object* pExceptionObject if (gc.keepAliveArray != NULL) { - gc.result = gc.keepAliveArray; + ret.Set(gc.keepAliveArray); } else { - gc.result = gc.stackTrace.Get(); + ret.Set(gc.stackTrace.Get()); } + GCPROTECT_END(); - HELPER_METHOD_FRAME_END(); - - return OBJECTREFToObject(gc.result); + END_QCALL; } -FCIMPLEND #ifdef FEATURE_COMINTEROP -BSTR BStrFromString(STRINGREF s) +static BSTR BStrFromString(STRINGREF s) { CONTRACTL { @@ -926,34 +921,30 @@ FCIMPLEND /*===============================AllocateNewArray=============================== **Action: Allocates a new array object. Allows passing extra flags -**Returns: The allocated array. -**Arguments: elementTypeHandle -> type of the element, -** length -> number of elements, -** zeroingOptional -> whether caller prefers to skip clearing the content of the array, if possible. +**Arguments: typeHandlePtr -> TypeHandle pointer of array, +** length -> Number of elements, +** flags -> Flags that impact allocated memory, +** ret -> The allocated array. **Exceptions: IDS_EE_ARRAY_DIMENSIONS_EXCEEDED when size is too large. OOM if can't allocate. ==============================================================================*/ -FCIMPL3(Object*, GCInterface::AllocateNewArray, void* arrayTypeHandle, INT32 length, INT32 flags) +extern "C" void QCALLTYPE GCInterface_AllocateNewArray(void* typeHandlePtr, INT32 length, INT32 flags, QCall::ObjectHandleOnStack ret) { - CONTRACTL { - FCALL_CHECK; - } CONTRACTL_END; + QCALL_CONTRACT; + _ASSERTE(typeHandlePtr != NULL); + + BEGIN_QCALL; - OBJECTREF pRet = NULL; - TypeHandle arrayType = TypeHandle::FromPtr(arrayTypeHandle); + GCX_COOP(); - HELPER_METHOD_FRAME_BEGIN_RET_0(); + TypeHandle typeHandle = TypeHandle::FromPtr(typeHandlePtr); + _ASSERTE(typeHandle.IsArray()); //Only the following flags are used by GC.cs, so we'll just assert it here. _ASSERTE((flags & ~(GC_ALLOC_ZEROING_OPTIONAL | GC_ALLOC_PINNED_OBJECT_HEAP)) == 0); + ret.Set(AllocateSzArray(typeHandle, length, (GC_ALLOC_FLAGS)flags)); - pRet = AllocateSzArray(arrayType, length, (GC_ALLOC_FLAGS)flags); - - HELPER_METHOD_FRAME_END(); - - return OBJECTREFToObject(pRet); + END_QCALL; } -FCIMPLEND - FCIMPL0(INT64, GCInterface::GetTotalAllocatedBytesApproximate) { diff --git a/src/coreclr/vm/comutilnative.h b/src/coreclr/vm/comutilnative.h index a0cea8d190d59..ef41239a6bb0f 100644 --- a/src/coreclr/vm/comutilnative.h +++ b/src/coreclr/vm/comutilnative.h @@ -41,7 +41,6 @@ class ExceptionNative static FCDECL1(FC_BOOL_RET, IsImmutableAgileException, Object* pExceptionUNSAFE); static FCDECL1(FC_BOOL_RET, IsTransient, INT32 hresult); static FCDECL0(VOID, PrepareForForeignExceptionRaise); - static FCDECL1(Object *, GetFrozenStackTrace, Object* pExceptionObjectUnsafe); #ifdef FEATURE_COMINTEROP // NOTE: caller cleans up any partially initialized BSTRs in pED @@ -54,6 +53,8 @@ class ExceptionNative static FCDECL0(UINT32, GetExceptionCount); }; +extern "C" void QCALLTYPE ExceptionNative_GetFrozenStackTrace(QCall::ObjectHandleOnStack exception, QCall::ObjectHandleOnStack ret); + enum class ExceptionMessageKind { ThreadAbort = 1, ThreadInterrupted = 2, @@ -182,8 +183,6 @@ class GCInterface { static FCDECL0(INT64, GetAllocatedBytesForCurrentThread); static FCDECL0(INT64, GetTotalAllocatedBytesApproximate); - static FCDECL3(Object*, AllocateNewArray, void* elementTypeHandle, INT32 length, INT32 flags); - NOINLINE static void SendEtwRemoveMemoryPressureEvent(UINT64 bytesAllocated); static void SendEtwAddMemoryPressureEvent(UINT64 bytesAllocated); @@ -203,6 +202,8 @@ class GCInterface { extern "C" INT64 QCALLTYPE GCInterface_GetTotalAllocatedBytesPrecise(); +extern "C" void QCALLTYPE GCInterface_AllocateNewArray(void* typeHandlePtr, INT32 length, INT32 flags, QCall::ObjectHandleOnStack ret); + extern "C" INT64 QCALLTYPE GCInterface_GetTotalMemory(); extern "C" void QCALLTYPE GCInterface_Collect(INT32 generation, INT32 mode); diff --git a/src/coreclr/vm/corelib.h b/src/coreclr/vm/corelib.h index 25d0163e4f172..ec5ee0795a504 100644 --- a/src/coreclr/vm/corelib.h +++ b/src/coreclr/vm/corelib.h @@ -835,6 +835,7 @@ DEFINE_FIELD_U(_name, ThreadBaseObject, m_Name) DEFINE_FIELD_U(_startHelper, ThreadBaseObject, m_StartHelper) DEFINE_FIELD_U(_DONT_USE_InternalThread, ThreadBaseObject, m_InternalThread) DEFINE_FIELD_U(_priority, ThreadBaseObject, m_Priority) +DEFINE_FIELD_U(_isDead, ThreadBaseObject, m_IsDead) DEFINE_CLASS(THREAD, Threading, Thread) DEFINE_METHOD(THREAD, START_CALLBACK, StartCallback, IM_RetVoid) #ifdef FEATURE_OBJCMARSHAL diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 69f9747a97cd1..2b065fd338034 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -83,7 +83,6 @@ FCFuncEnd() FCFuncStart(gExceptionFuncs) FCFuncElement("IsImmutableAgileException", ExceptionNative::IsImmutableAgileException) FCFuncElement("PrepareForForeignExceptionRaise", ExceptionNative::PrepareForForeignExceptionRaise) - FCFuncElement("GetFrozenStackTrace", ExceptionNative::GetFrozenStackTrace) FCFuncElement("GetExceptionCount", ExceptionNative::GetExceptionCount) FCFuncEnd() @@ -306,14 +305,7 @@ FCFuncStart(gThreadFuncs) FCFuncElement("GetIsBackground", ThreadNative::GetIsBackground) FCFuncElement("get_IsThreadPoolThread", ThreadNative::IsThreadpoolThread) FCFuncElement("set_IsThreadPoolThread", ThreadNative::SetIsThreadpoolThread) - FCFuncElement("GetPriorityNative", ThreadNative::GetPriority) - FCFuncElement("SetPriorityNative", ThreadNative::SetPriority) FCFuncElement("GetThreadStateNative", ThreadNative::GetThreadState) -#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT - FCFuncElement("GetApartmentStateNative", ThreadNative::GetApartmentState) - FCFuncElement("SetApartmentStateNative", ThreadNative::SetApartmentState) -#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT - FCFuncElement("Join", ThreadNative::Join) FCFuncElement("get_OptimalMaxSpinWaitsPerSpinIteration", ThreadNative::GetOptimalMaxSpinWaitsPerSpinIteration) FCFuncEnd() @@ -364,8 +356,6 @@ FCFuncStart(gGCInterfaceFuncs) FCFuncElement("GetAllocatedBytesForCurrentThread", GCInterface::GetAllocatedBytesForCurrentThread) FCFuncElement("GetTotalAllocatedBytesApproximate", GCInterface::GetTotalAllocatedBytesApproximate) - - FCFuncElement("AllocateNewArray", GCInterface::AllocateNewArray) FCFuncEnd() FCFuncStart(gGCSettingsFuncs) diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h index 7fa562e509a39..1afd632dea09e 100644 --- a/src/coreclr/vm/object.h +++ b/src/coreclr/vm/object.h @@ -1333,6 +1333,9 @@ class ThreadBaseObject : public Object // Only used by managed code, see comment there bool m_MayNeedResetForThreadPool; + // Set in unmanaged code and read in managed code. + bool m_IsDead; + protected: // the ctor and dtor can do no useful work. ThreadBaseObject() {LIMITED_METHOD_CONTRACT;}; @@ -1384,6 +1387,12 @@ class ThreadBaseObject : public Object LIMITED_METHOD_CONTRACT; return m_Priority; } + + void SetIsDead() + { + LIMITED_METHOD_CONTRACT; + m_IsDead = true; + } }; // MarshalByRefObjectBaseObject diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 9eb0584379516..2d2345ca214c1 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -99,6 +99,7 @@ static const Entry s_QCall[] = DllImportEntry(Environment_Exit) DllImportEntry(Environment_FailFast) DllImportEntry(Environment_GetProcessorCount) + DllImportEntry(ExceptionNative_GetFrozenStackTrace) DllImportEntry(ExceptionNative_GetMessageFromNativeResources) DllImportEntry(ExceptionNative_GetMethodFromStackTrace) DllImportEntry(ExceptionNative_ThrowAmbiguousResolutionException) @@ -233,10 +234,16 @@ static const Entry s_QCall[] = DllImportEntry(String_IsInterned) DllImportEntry(AppDomain_CreateDynamicAssembly) DllImportEntry(ThreadNative_Start) + DllImportEntry(ThreadNative_SetPriority) DllImportEntry(ThreadNative_SetIsBackground) DllImportEntry(ThreadNative_InformThreadNameChange) DllImportEntry(ThreadNative_YieldThread) DllImportEntry(ThreadNative_GetCurrentOSThreadId) +#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT + DllImportEntry(ThreadNative_GetApartmentState) + DllImportEntry(ThreadNative_SetApartmentState) +#endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT + DllImportEntry(ThreadNative_Join) DllImportEntry(ThreadNative_Abort) DllImportEntry(ThreadNative_ResetAbort) DllImportEntry(ThreadNative_SpinWait) @@ -258,6 +265,7 @@ static const Entry s_QCall[] = DllImportEntry(GCInterface_WaitForFullGCComplete) DllImportEntry(GCInterface_StartNoGCRegion) DllImportEntry(GCInterface_EndNoGCRegion) + DllImportEntry(GCInterface_AllocateNewArray) DllImportEntry(GCInterface_GetTotalMemory) DllImportEntry(GCInterface_Collect) DllImportEntry(GCInterface_ReRegisterForFinalize) diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 8c60b2b5a7982..f9fbfec9bc661 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -2765,6 +2765,10 @@ void Thread::CooperativeCleanup() // Clear out the alloc context pointer for this thread. When TLS is gone, this pointer will point into freed memory. m_pRuntimeThreadLocals = nullptr; } + + OBJECTREF threadObjMaybe = GetExposedObjectRaw(); + if (threadObjMaybe != NULL) + ((THREADBASEREF)threadObjMaybe)->SetIsDead(); } // See general comments on thread destruction (code:#threadDestruction) above. @@ -4781,20 +4785,6 @@ Thread::ApartmentState Thread::GetFinalApartment() return as; } -// when we get apartment tear-down notification, -// we want reset the apartment state we cache on the thread -VOID Thread::ResetApartment() -{ - CONTRACTL { - NOTHROW; - GC_NOTRIGGER; - } - CONTRACTL_END; - - // reset the TS_InSTA bit and TS_InMTA bit - ResetThreadState((Thread::ThreadState)(TS_InSTA | TS_InMTA)); -} - // Attempt to set current thread's apartment state. The actual apartment state // achieved is returned and may differ from the input state if someone managed // to call CoInitializeEx on this thread first (note that calls to SetApartment diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index 6aa3e04b00465..f3b7894c3dea6 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -2168,10 +2168,6 @@ class Thread // call CoInitializeEx on this thread first (note that calls to SetApartment made // before the thread has started are guaranteed to succeed). ApartmentState SetApartment(ApartmentState state); - - // when we get apartment tear-down notification, - // we want reset the apartment state we cache on the thread - VOID ResetApartment(); #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT // Either perform WaitForSingleObject or MsgWaitForSingleObject as appropriate. diff --git a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs index 738e45ab72508..27859afd6df75 100644 --- a/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs +++ b/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs @@ -2597,6 +2597,7 @@ protected virtual bool AlwaysMarkTypeAsInstantiated (TypeDefinition td) case "MulticastDelegate": case "ValueType": case "Enum": + case "Array": return td.Namespace == "System"; }