1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4// FCALL.CPP
5//
6
7//
8
9
10#include "common.h"
11#include "vars.hpp"
12#include "fcall.h"
13#include "excep.h"
14#include "frames.h"
15#include "gms.h"
16#include "ecall.h"
17#include "eeconfig.h"
18
19NOINLINE LPVOID __FCThrow(LPVOID __me, RuntimeExceptionKind reKind, UINT resID, LPCWSTR arg1, LPCWSTR arg2, LPCWSTR arg3)
20{
21 STATIC_CONTRACT_THROWS;
22 // This isn't strictly true... But the guarentee that we make here is
23 // that we won't trigger without having setup a frame.
24 // STATIC_CONTRACT_TRIGGER
25 STATIC_CONTRACT_GC_NOTRIGGER;
26 STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work
27
28 // side effect the compiler can't remove
29 if (FC_NO_TAILCALL != 1)
30 return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1);
31
32 FC_CAN_TRIGGER_GC();
33 INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
34 FC_GC_POLL_NOT_NEEDED();
35
36 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
37 // Now, we can construct & throw.
38
39 // In V1, throwing an ExecutionEngineException actually never really threw anything... its was the same as a
40 // fatal error in the runtime, and we will most probably would have ripped the process down. Starting in
41 // Whidbey, this behavior has changed a lot. Its not really legal to try to throw an
42 // ExecutionEngineExcpetion with this function.
43 _ASSERTE((reKind != kExecutionEngineException) ||
44 !"Don't throw kExecutionEngineException from here. Go to EEPolicy directly, or throw something better.");
45
46 if (resID == 0)
47 {
48 // If we have an string to add use NonLocalized otherwise just throw the exception.
49 if (arg1)
50 COMPlusThrowNonLocalized(reKind, arg1); //COMPlusThrow(reKind,arg1);
51 else
52 COMPlusThrow(reKind);
53 }
54 else
55 COMPlusThrow(reKind, resID, arg1, arg2, arg3);
56
57 HELPER_METHOD_FRAME_END();
58 FC_CAN_TRIGGER_GC_END();
59 _ASSERTE(!"Throw returned");
60 return NULL;
61}
62
63NOINLINE LPVOID __FCThrowArgument(LPVOID __me, RuntimeExceptionKind reKind, LPCWSTR argName, LPCWSTR resourceName)
64{
65 STATIC_CONTRACT_THROWS;
66 // This isn't strictly true... But the guarentee that we make here is
67 // that we won't trigger without having setup a frame.
68 // STATIC_CONTRACT_TRIGGER
69 STATIC_CONTRACT_GC_NOTRIGGER;
70 STATIC_CONTRACT_SO_TOLERANT; // function probes before it does any work
71
72 // side effect the compiler can't remove
73 if (FC_NO_TAILCALL != 1)
74 return (LPVOID)(SIZE_T)(FC_NO_TAILCALL + 1);
75
76 FC_CAN_TRIGGER_GC();
77 INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
78 FC_GC_POLL_NOT_NEEDED(); // throws always open up for GC
79
80 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_NOPOLL(Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
81
82 switch (reKind) {
83 case kArgumentNullException:
84 if (resourceName) {
85 COMPlusThrowArgumentNull(argName, resourceName);
86 } else {
87 COMPlusThrowArgumentNull(argName);
88 }
89 break;
90
91 case kArgumentOutOfRangeException:
92 COMPlusThrowArgumentOutOfRange(argName, resourceName);
93 break;
94
95 case kArgumentException:
96 COMPlusThrowArgumentException(argName, resourceName);
97 break;
98
99 default:
100 // If you see this assert, add a case for your exception kind above.
101 _ASSERTE(argName == NULL);
102 COMPlusThrow(reKind, resourceName);
103 }
104
105 HELPER_METHOD_FRAME_END();
106 FC_CAN_TRIGGER_GC_END();
107 _ASSERTE(!"Throw returned");
108 return NULL;
109}
110
111/**************************************************************************************/
112/* erect a frame in the FCALL and then poll the GC, objToProtect will be protected
113 during the poll and the updated object returned. */
114
115NOINLINE Object* FC_GCPoll(void* __me, Object* objToProtect)
116{
117 CONTRACTL {
118 THROWS;
119 // This isn't strictly true... But the guarentee that we make here is
120 // that we won't trigger without having setup a frame.
121 UNCHECKED(GC_NOTRIGGER);
122 SO_TOLERANT; // function probes before it does any work
123 } CONTRACTL_END;
124
125 FC_CAN_TRIGGER_GC();
126 INCONTRACT(FCallCheck __fCallCheck(__FILE__, __LINE__));
127
128 Thread *thread = GetThread();
129 if (thread->CatchAtSafePointOpportunistic()) // Does someone want this thread stopped?
130 {
131 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objToProtect);
132
133#ifdef _DEBUG
134 BOOL GCOnTransition = FALSE;
135 if (g_pConfig->FastGCStressLevel()) {
136 GCOnTransition = GC_ON_TRANSITIONS (FALSE);
137 }
138#endif
139 CommonTripThread();
140#ifdef _DEBUG
141 if (g_pConfig->FastGCStressLevel()) {
142 GC_ON_TRANSITIONS (GCOnTransition);
143 }
144#endif
145
146 HELPER_METHOD_FRAME_END();
147 }
148
149 FC_CAN_TRIGGER_GC_END();
150
151 return objToProtect;
152}
153
154#ifdef _DEBUG
155
156unsigned FcallTimeHist[11];
157
158#endif
159
160#ifdef ENABLE_CONTRACTS
161
162/**************************************************************************************/
163#if defined(_TARGET_X86_) && defined(ENABLE_PERF_COUNTERS)
164static __int64 getCycleCount() {
165
166 LIMITED_METHOD_CONTRACT;
167 return GET_CYCLE_COUNT();
168}
169#else
170static __int64 getCycleCount() { LIMITED_METHOD_CONTRACT; return(0); }
171#endif
172
173/**************************************************************************************/
174// No contract here: The contract destructor restores the thread contract state to what it was
175// soon after constructing the contract. This would have the effect of reverting the contract
176// state change made by the call to BeginForbidGC.
177DEBUG_NOINLINE ForbidGC::ForbidGC(const char *szFile, int lineNum)
178{
179 SCAN_SCOPE_BEGIN;
180 STATIC_CONTRACT_GC_NOTRIGGER;
181 STATIC_CONTRACT_MODE_COOPERATIVE;
182
183 m_pThread = GetThread();
184 m_pThread->BeginForbidGC(szFile, lineNum);
185}
186
187/**************************************************************************************/
188// No contract here: The contract destructor restores the thread contract state to what it was
189// soon after constructing the contract. This would have the effect of reverting the contract
190// state change made by the call to BeginForbidGC.
191DEBUG_NOINLINE ForbidGC::~ForbidGC()
192{
193 SCAN_SCOPE_END;
194
195 // IF EH happens, this is still called, in which case
196 // we should not bother
197
198 if (m_pThread->RawGCNoTrigger())
199 m_pThread->EndNoTriggerGC();
200}
201
202/**************************************************************************************/
203DEBUG_NOINLINE FCallCheck::FCallCheck(const char *szFile, int lineNum) : ForbidGC(szFile, lineNum)
204{
205 SCAN_SCOPE_BEGIN;
206 STATIC_CONTRACT_GC_NOTRIGGER;
207 STATIC_CONTRACT_MODE_COOPERATIVE;
208
209#ifdef _DEBUG
210 unbreakableLockCount = m_pThread->GetUnbreakableLockCount();
211#endif
212 didGCPoll = false;
213 notNeeded = false;
214 startTicks = getCycleCount();
215}
216
217/**************************************************************************************/
218DEBUG_NOINLINE FCallCheck::~FCallCheck()
219{
220 SCAN_SCOPE_END;
221
222 // Confirm that we don't starve the GC or thread-abort.
223 // Basically every control flow path through an FCALL must
224 // to a poll. If you hit the assert below, you can fix it by
225 //
226 // If you erect a HELPER_METHOD_FRAME, you can
227 //
228 // Call HELPER_METHOD_POLL()
229 // or use HELPER_METHOD_FRAME_END_POLL
230 //
231 // If you don't have a helper frame you can used
232 //
233 // FC_GC_POLL_AND_RETURN_OBJREF or
234 // FC_GC_POLL or
235 // FC_GC_POLL_RET
236 //
237 // Note that these must be at GC safe points. In particular
238 // all object references that are NOT protected will be trashed.
239
240
241 // There is a special poll called FC_GC_POLL_NOT_NEEDED
242 // which says the code path is short enough that a GC poll is not need
243 // you should not use this in most cases.
244
245 _ASSERTE(unbreakableLockCount == m_pThread->GetUnbreakableLockCount() ||
246 (!m_pThread->HasUnbreakableLock() && !m_pThread->HasThreadStateNC(Thread::TSNC_OwnsSpinLock)));
247
248 if (notNeeded) {
249
250 /*<TODO> TODO, we want to actually measure the time to make certain we are not too far off
251
252 unsigned delta = unsigned(getCycleCount() - startTicks);
253 </TODO>*/
254 }
255 else if (!didGCPoll) {
256 // <TODO>TODO turn this on!!! _ASSERTE(!"FCALL without a GC poll in it somewhere!");</TODO>
257 }
258
259}
260
261
262#if defined(_TARGET_AMD64_)
263
264
265FCallTransitionState::FCallTransitionState ()
266{
267 WRAPPER_NO_CONTRACT;
268
269 m_pThread = GetThread();
270 _ASSERTE(m_pThread);
271
272 m_pPreviousHelperMethodFrameCallerList = m_pThread->m_pHelperMethodFrameCallerList;
273
274 m_pThread->m_pHelperMethodFrameCallerList = NULL;
275}
276
277
278FCallTransitionState::~FCallTransitionState ()
279{
280 WRAPPER_NO_CONTRACT;
281
282 m_pThread->m_pHelperMethodFrameCallerList = m_pPreviousHelperMethodFrameCallerList;
283}
284
285
286PermitHelperMethodFrameState::PermitHelperMethodFrameState ()
287{
288 WRAPPER_NO_CONTRACT;
289
290 m_pThread = GetThread();
291 _ASSERTE(m_pThread);
292
293 CONSISTENCY_CHECK_MSG((HelperMethodFrameCallerList*)-1 != m_pThread->m_pHelperMethodFrameCallerList,
294 "fcall entry point is missing a FCALL_TRANSITION_BEGIN or a FCIMPL\n");
295
296 m_ListEntry.pCaller = m_pThread->m_pHelperMethodFrameCallerList;
297 m_pThread->m_pHelperMethodFrameCallerList = &m_ListEntry;
298}
299
300
301PermitHelperMethodFrameState::~PermitHelperMethodFrameState ()
302{
303 WRAPPER_NO_CONTRACT;
304
305 m_pThread->m_pHelperMethodFrameCallerList = m_ListEntry.pCaller;
306}
307
308
309VOID PermitHelperMethodFrameState::CheckHelperMethodFramePermitted ()
310{
311 CONTRACTL {
312 NOTHROW;
313 GC_NOTRIGGER;
314 DEBUG_ONLY;
315 } CONTRACTL_END;
316
317 //
318 // Get current context and unwind to caller
319 //
320
321 CONTEXT ctx;
322
323 ClrCaptureContext(&ctx);
324 Thread::VirtualUnwindCallFrame(&ctx);
325
326 //
327 // Make sure each unmanaged frame used PERMIT_HELPER_METHOD_FRAME_BEGIN.
328 // If we hit NULL before we reach managed code, then the caller of the
329 // fcall was not managed.
330 //
331
332 Thread *pThread = GetThread();
333 _ASSERTE(pThread);
334
335 HelperMethodFrameCallerList *pList = pThread->m_pHelperMethodFrameCallerList;
336 PCODE CurrentIP;
337 TADDR CurrentSP;
338
339 do
340 {
341 CurrentSP = GetSP(&ctx);
342 CurrentIP = GetIP(&ctx);
343
344 Thread::VirtualUnwindCallFrame(&ctx);
345
346 TADDR CallerSP = GetSP(&ctx);
347
348 unsigned nAssociatedListEntries = 0;
349
350 while ( (SIZE_T)pList >= (SIZE_T)CurrentSP
351 && (SIZE_T)pList < (SIZE_T)CallerSP)
352 {
353 nAssociatedListEntries++;
354 pList = pList->pCaller;
355 }
356
357 if (!nAssociatedListEntries)
358 {
359 char szFunction[cchMaxAssertStackLevelStringLen];
360 GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction);
361
362 CONSISTENCY_CHECK_MSGF(false, ("Unmanaged caller %s at sp %p/ip %p is missing a "
363 "PERMIT_HELPER_METHOD_FRAME_BEGIN, or this function "
364 "is calling an fcall entry point that is missing a "
365 "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP));
366 }
367 }
368 while (pList && !ExecutionManager::IsManagedCode(GetIP(&ctx)));
369
370 //
371 // We should have exhausted the list. If not, the list was not reset at
372 // the transition from managed code.
373 //
374
375 if (pList)
376 {
377 char szFunction[cchMaxAssertStackLevelStringLen];
378 GetStringFromAddr((DWORD_PTR)CurrentIP, szFunction);
379
380 CONSISTENCY_CHECK_MSGF(false, ("fcall entry point %s at sp %p/ip %p is missing a "
381 "FCALL_TRANSITION_BEGIN or a FCIMPL\n", szFunction, CurrentSP, CurrentIP));
382 }
383}
384
385
386CompletedFCallTransitionState::CompletedFCallTransitionState ()
387{
388 WRAPPER_NO_CONTRACT;
389
390 Thread *pThread = GetThread();
391 _ASSERTE(pThread);
392
393 m_pLastHelperMethodFrameCallerList = pThread->m_pHelperMethodFrameCallerList;
394
395 pThread->m_pHelperMethodFrameCallerList = (HelperMethodFrameCallerList*)-1;
396}
397
398
399CompletedFCallTransitionState::~CompletedFCallTransitionState ()
400{
401 WRAPPER_NO_CONTRACT;
402
403 Thread *pThread = GetThread();
404 _ASSERTE(pThread);
405
406 pThread->m_pHelperMethodFrameCallerList = m_pLastHelperMethodFrameCallerList;
407}
408
409
410#endif // _TARGET_AMD64_
411
412#endif // ENABLE_CONTRACTS
413