summaryrefslogtreecommitdiff
path: root/src/vm/fcall.cpp
blob: 14da2e0f80fc9635215021fb2789f4b2602320d8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license 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