| 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 |  | 
|---|
| 19 | NOINLINE 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 |  | 
|---|
| 63 | NOINLINE 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 |  | 
|---|
| 115 | NOINLINE 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 |  | 
|---|
| 156 | unsigned FcallTimeHist[11]; | 
|---|
| 157 |  | 
|---|
| 158 | #endif | 
|---|
| 159 |  | 
|---|
| 160 | #ifdef ENABLE_CONTRACTS | 
|---|
| 161 |  | 
|---|
| 162 | /**************************************************************************************/ | 
|---|
| 163 | #if defined(_TARGET_X86_) && defined(ENABLE_PERF_COUNTERS) | 
|---|
| 164 | static __int64 getCycleCount() { | 
|---|
| 165 |  | 
|---|
| 166 | LIMITED_METHOD_CONTRACT; | 
|---|
| 167 | return GET_CYCLE_COUNT(); | 
|---|
| 168 | } | 
|---|
| 169 | #else | 
|---|
| 170 | static __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. | 
|---|
| 177 | DEBUG_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. | 
|---|
| 191 | DEBUG_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 | /**************************************************************************************/ | 
|---|
| 203 | DEBUG_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 | /**************************************************************************************/ | 
|---|
| 218 | DEBUG_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 |  | 
|---|
| 265 | FCallTransitionState::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 |  | 
|---|
| 278 | FCallTransitionState::~FCallTransitionState () | 
|---|
| 279 | { | 
|---|
| 280 | WRAPPER_NO_CONTRACT; | 
|---|
| 281 |  | 
|---|
| 282 | m_pThread->m_pHelperMethodFrameCallerList = m_pPreviousHelperMethodFrameCallerList; | 
|---|
| 283 | } | 
|---|
| 284 |  | 
|---|
| 285 |  | 
|---|
| 286 | PermitHelperMethodFrameState::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 |  | 
|---|
| 301 | PermitHelperMethodFrameState::~PermitHelperMethodFrameState () | 
|---|
| 302 | { | 
|---|
| 303 | WRAPPER_NO_CONTRACT; | 
|---|
| 304 |  | 
|---|
| 305 | m_pThread->m_pHelperMethodFrameCallerList = m_ListEntry.pCaller; | 
|---|
| 306 | } | 
|---|
| 307 |  | 
|---|
| 308 |  | 
|---|
| 309 | VOID 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 |  | 
|---|
| 386 | CompletedFCallTransitionState::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 |  | 
|---|
| 399 | CompletedFCallTransitionState::~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 |  | 
|---|