summaryrefslogtreecommitdiff
path: root/src/vm/fcall.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/fcall.cpp')
-rw-r--r--src/vm/fcall.cpp412
1 files changed, 412 insertions, 0 deletions
diff --git a/src/vm/fcall.cpp b/src/vm/fcall.cpp
new file mode 100644
index 0000000000..68fc271936
--- /dev/null
+++ b/src/vm/fcall.cpp
@@ -0,0 +1,412 @@
+// 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.
+// FCALL.CPP
+//
+
+//
+
+
+#include "common.h"
+#include "vars.hpp"
+#include "fcall.h"
+#include "excep.h"
+#include "frames.h"
+#include "gms.h"
+#include "ecall.h"
+#include "eeconfig.h"
+
+NOINLINE LPVOID __FCThrow(LPVOID __me, RuntimeExceptionKind reKind, UINT resID, LPCWSTR arg1, LPCWSTR arg2, LPCWSTR arg3)
+{
+ STATIC_CONTRACT_THROWS;
+ // This isn't strictly true... But the guarentee that we make here is
+ // that we won't trigger without having setup a frame.
+ // STATIC_CONTRACT_TRIGGER
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work
+
+ // side effect the compiler can't remove
+ if (FC_NO_TAILCALL != 1)
+ return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1);
+
+ FC_CAN_TRIGGER_GC();
+ INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
+ FC_GC_POLL_NOT_NEEDED();
+
+ HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
+ // Now, we can construct & throw.
+
+ // In V1, throwing an ExecutionEngineException actually never really threw anything... its was the same as a
+ // fatal error in the runtime, and we will most probably would have ripped the process down. Starting in
+ // Whidbey, this behavior has changed a lot. Its not really legal to try to throw an
+ // ExecutionEngineExcpetion with this function.
+ _ASSERTE((reKind != kExecutionEngineException) ||
+ !"Don't throw kExecutionEngineException from here. Go to EEPolicy directly, or throw something better.");
+
+ if (resID == 0)
+ {
+ // If we have an string to add use NonLocalized otherwise just throw the exception.
+ if (arg1)
+ COMPlusThrowNonLocalized(reKind, arg1); //COMPlusThrow(reKind,arg1);
+ else
+ COMPlusThrow(reKind);
+ }
+ else
+ COMPlusThrow(reKind, resID, arg1, arg2, arg3);
+
+ HELPER_METHOD_FRAME_END();
+ FC_CAN_TRIGGER_GC_END();
+ _ASSERTE(!"Throw returned");
+ return NULL;
+}
+
+NOINLINE LPVOID __FCThrowArgument(LPVOID __me, RuntimeExceptionKind reKind, LPCWSTR argName, LPCWSTR resourceName)
+{
+ STATIC_CONTRACT_THROWS;
+ // This isn't strictly true... But the guarentee that we make here is
+ // that we won't trigger without having setup a frame.
+ // STATIC_CONTRACT_TRIGGER
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work
+
+ // side effect the compiler can't remove
+ if (FC_NO_TAILCALL != 1)
+ return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1);
+
+ FC_CAN_TRIGGER_GC();
+ INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
+ FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC
+
+ HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
+
+ switch (reKind) {
+ case kArgumentNullException:
+ if (resourceName) {
+ COMPlusThrowArgumentNull(argName, resourceName);
+ } else {
+ COMPlusThrowArgumentNull(argName);
+ }
+ break;
+
+ case kArgumentOutOfRangeException:
+ COMPlusThrowArgumentOutOfRange(argName, resourceName);
+ break;
+
+ case kArgumentException:
+ COMPlusThrowArgumentException(argName, resourceName);
+ break;
+
+ default:
+ // If you see this assert, add a case for your exception kind above.
+ _ASSERTE(argName == NULL);
+ COMPlusThrow(reKind, resourceName);
+ }
+
+ HELPER_METHOD_FRAME_END();
+ FC_CAN_TRIGGER_GC_END();
+ _ASSERTE(!"Throw returned");
+ return NULL;
+}
+
+/**************************************************************************************/
+/* erect a frame in the FCALL and then poll the GC, objToProtect will be protected
+ during the poll and the updated object returned. */
+
+NOINLINE Object* FC_GCPoll(void* __me, Object* objToProtect)
+{
+ CONTRACTL {
+ THROWS;
+ // This isn't strictly true... But the guarentee that we make here is
+ // that we won't trigger without having setup a frame.
+ UNCHECKED(GC_NOTRIGGER);
+ SO_TOLERANT; // function probes before it does any work
+ } CONTRACTL_END;
+
+ FC_CAN_TRIGGER_GC();
+ INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
+
+ Thread *thread = GetThread();
+ if (thread->CatchAtSafePointOpportunistic()) // Does someone want this thread stopped?
+ {
+ HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objToProtect);
+
+#ifdef _DEBUG
+ BOOL GCOnTransition = FALSE;
+ if (g_pConfig->FastGCStressLevel()) {
+ GCOnTransition = GC_ON_TRANSITIONS (FALSE);
+ }
+#endif
+ CommonTripThread();
+#ifdef _DEBUG
+ if (g_pConfig->FastGCStressLevel()) {
+ GC_ON_TRANSITIONS (GCOnTransition);
+ }
+#endif
+
+ HELPER_METHOD_FRAME_END();
+ }
+
+ FC_CAN_TRIGGER_GC_END();
+
+ return objToProtect;
+}
+
+#ifdef _DEBUG
+
+unsigned FcallTimeHist[11];
+
+#endif
+
+#ifdef ENABLE_CONTRACTS
+
+/**************************************************************************************/
+#if defined(_TARGET_X86_) && defined(ENABLE_PERF_COUNTERS)
+static __int64 getCycleCount() {
+
+ LIMITED_METHOD_CONTRACT;
+ return GET_CYCLE_COUNT();
+}
+#else
+static __int64 getCycleCount() { LIMITED_METHOD_CONTRACT; return(0); }
+#endif
+
+/**************************************************************************************/
+// No contract here: The contract destructor restores the thread contract state to what it was
+// soon after constructing the contract. This would have the effect of reverting the contract
+// state change made by the call to BeginForbidGC.
+DEBUG_NOINLINE ForbidGC::ForbidGC(const char *szFile, int lineNum)
+{
+ SCAN_SCOPE_BEGIN;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+
+ m_pThread = GetThread();
+ m_pThread->BeginForbidGC(szFile, lineNum);
+}
+
+/**************************************************************************************/
+// No contract here: The contract destructor restores the thread contract state to what it was
+// soon after constructing the contract. This would have the effect of reverting the contract
+// state change made by the call to BeginForbidGC.
+DEBUG_NOINLINE ForbidGC::~ForbidGC()
+{
+ SCAN_SCOPE_END;
+
+ // IF EH happens, this is still called, in which case
+ // we should not bother
+
+ if (m_pThread->RawGCNoTrigger())
+ m_pThread->EndNoTriggerGC();
+}
+
+/**************************************************************************************/
+DEBUG_NOINLINE FCallCheck::FCallCheck(const char *szFile, int lineNum) : ForbidGC(szFile, lineNum)
+{
+ SCAN_SCOPE_BEGIN;
+ STATIC_CONTRACT_GC_NOTRIGGER;
+ STATIC_CONTRACT_MODE_COOPERATIVE;
+
+#ifdef _DEBUG
+ unbreakableLockCount = m_pThread->GetUnbreakableLockCount();
+#endif
+ didGCPoll = false;
+ notNeeded = false;
+ startTicks = getCycleCount();
+}
+
+/**************************************************************************************/
+DEBUG_NOINLINE FCallCheck::~FCallCheck()
+{
+ SCAN_SCOPE_END;
+
+ // Confirm that we don't starve the GC or thread-abort.
+ // Basically every control flow path through an FCALL must
+ // to a poll. If you hit the assert below, you can fix it by
+ //
+ // If you erect a HELPER_METHOD_FRAME, you can
+ //
+ // Call HELPER_METHOD_POLL()
+ // or use HELPER_METHOD_FRAME_END_POLL
+ //
+ // If you don't have a helper frame you can used
+ //
+ // FC_GC_POLL_AND_RETURN_OBJREF or
+ // FC_GC_POLL or
+ // FC_GC_POLL_RET
+ //
+ // Note that these must be at GC safe points. In particular
+ // all object references that are NOT protected will be trashed.
+
+
+ // There is a special poll called FC_GC_POLL_NOT_NEEDED
+ // which says the code path is short enough that a GC poll is not need
+ // you should not use this in most cases.
+
+ _ASSERTE(unbreakableLockCount == m_pThread->GetUnbreakableLockCount() ||
+ (!m_pThread->HasUnbreakableLock() && !m_pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock)));
+
+ if (notNeeded) {
+
+ /*<TODO> TODO, we want to actually measure the time to make certain we are not too far off
+
+ unsigned delta = unsigned(getCycleCount() - startTicks);
+ </TODO>*/
+ }
+ else if (!didGCPoll) {
+ // <TODO>TODO turn this on!!! _ASSERTE(!"FCALL without a GC poll in it somewhere!");</TODO>
+ }
+
+}
+
+
+#if defined(_TARGET_AMD64_)
+
+
+FCallTransitionState::FCallTransitionState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_pThread = GetThread();
+ _ASSERTE(m_pThread);
+
+ m_pPreviousHelperMethodFrameCallerList = m_pThread->m_pHelperMethodFrameCallerList;
+
+ m_pThread->m_pHelperMethodFrameCallerList = NULL;
+}
+
+
+FCallTransitionState::~FCallTransitionState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_pThread->m_pHelperMethodFrameCallerList = m_pPreviousHelperMethodFrameCallerList;
+}
+
+
+PermitHelperMethodFrameState::PermitHelperMethodFrameState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_pThread = GetThread();
+ _ASSERTE(m_pThread);
+
+ CONSISTENCY_CHECK_MSG((HelperMethodFrameCallerList*)-1 != m_pThread->m_pHelperMethodFrameCallerList,
+ "fcall entry point is missing a FCALL_TRANSITION_BEGIN or a FCIMPL\n");
+
+ m_ListEntry.pCaller = m_pThread->m_pHelperMethodFrameCallerList;
+ m_pThread->m_pHelperMethodFrameCallerList = &m_ListEntry;
+}
+
+
+PermitHelperMethodFrameState::~PermitHelperMethodFrameState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ m_pThread->m_pHelperMethodFrameCallerList = m_ListEntry.pCaller;
+}
+
+
+VOID PermitHelperMethodFrameState::CheckHelperMethodFramePermitted ()
+{
+ CONTRACTL {
+ NOTHROW;
+ GC_NOTRIGGER;
+ DEBUG_ONLY;
+ } CONTRACTL_END;
+
+ //
+ // Get current context and unwind to caller
+ //
+
+ CONTEXT ctx;
+
+ ClrCaptureContext(&ctx);
+ Thread::VirtualUnwindCallFrame(&ctx);
+
+ //
+ // Make sure each unmanaged frame used PERMIT_HELPER_METHOD_FRAME_BEGIN.
+ // If we hit NULL before we reach managed code, then the caller of the
+ // fcall was not managed.
+ //
+
+ Thread *pThread = GetThread();
+ _ASSERTE(pThread);
+
+ HelperMethodFrameCallerList *pList = pThread->m_pHelperMethodFrameCallerList;
+ PCODE CurrentIP;
+ TADDR CurrentSP;
+
+ do
+ {
+ CurrentSP = GetSP(&ctx);
+ CurrentIP = GetIP(&ctx);
+
+ Thread::VirtualUnwindCallFrame(&ctx);
+
+ TADDR CallerSP = GetSP(&ctx);
+
+ unsigned nAssociatedListEntries = 0;
+
+ while ( (SIZE_T)pList >= (SIZE_T)CurrentSP
+ && (SIZE_T)pList < (SIZE_T)CallerSP)
+ {
+ nAssociatedListEntries++;
+ pList = pList->pCaller;
+ }
+
+ if (!nAssociatedListEntries)
+ {
+ char szFunction[cchMaxAssertStackLevelStringLen];
+ GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction);
+
+ CONSISTENCY_CHECK_MSGF(false, ("Unmanaged caller %s at sp %p/ip %p is missing a "
+ "PERMIT_HELPER_METHOD_FRAME_BEGIN, or this function "
+ "is calling an fcall entry point that is missing a "
+ "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP));
+ }
+ }
+ while (pList && !ExecutionManager::IsManagedCode(GetIP(&ctx)));
+
+ //
+ // We should have exhausted the list. If not, the list was not reset at
+ // the transition from managed code.
+ //
+
+ if (pList)
+ {
+ char szFunction[cchMaxAssertStackLevelStringLen];
+ GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction);
+
+ CONSISTENCY_CHECK_MSGF(false, ("fcall entry point %s at sp %p/ip %p is missing a "
+ "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP));
+ }
+}
+
+
+CompletedFCallTransitionState::CompletedFCallTransitionState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ Thread *pThread = GetThread();
+ _ASSERTE(pThread);
+
+ m_pLastHelperMethodFrameCallerList = pThread->m_pHelperMethodFrameCallerList;
+
+ pThread->m_pHelperMethodFrameCallerList = (HelperMethodFrameCallerList*)-1;
+}
+
+
+CompletedFCallTransitionState::~CompletedFCallTransitionState ()
+{
+ WRAPPER_NO_CONTRACT;
+
+ Thread *pThread = GetThread();
+ _ASSERTE(pThread);
+
+ pThread->m_pHelperMethodFrameCallerList = m_pLastHelperMethodFrameCallerList;
+}
+
+
+#endif // _TARGET_AMD64_
+
+#endif // ENABLE_CONTRACTS