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//
5
6//
7
8/*============================================================
9**
10** File: COMUtilNative
11**
12**
13**
14** Purpose: A dumping ground for classes which aren't large
15** enough to get their own file in the EE.
16**
17**
18**
19===========================================================*/
20#include "common.h"
21#include "object.h"
22#include "excep.h"
23#include "vars.hpp"
24#include "comutilnative.h"
25
26#include "utilcode.h"
27#include "frames.h"
28#include "field.h"
29#include "winwrap.h"
30#include "gcheaputilities.h"
31#include "fcall.h"
32#include "invokeutil.h"
33#include "eeconfig.h"
34#include "typestring.h"
35#include "sha1.h"
36#include "finalizerthread.h"
37
38#ifdef FEATURE_COMINTEROP
39 #include "comcallablewrapper.h"
40 #include "comcache.h"
41#endif // FEATURE_COMINTEROP
42
43#include "arraynative.inl"
44
45#define STACK_OVERFLOW_MESSAGE W("StackOverflowException")
46
47/*===================================IsDigit====================================
48**Returns a bool indicating whether the character passed in represents a **
49**digit.
50==============================================================================*/
51bool IsDigit(WCHAR c, int radix, int *result)
52{
53 CONTRACTL
54 {
55 NOTHROW;
56 GC_NOTRIGGER;
57 MODE_ANY;
58 PRECONDITION(CheckPointer(result));
59 }
60 CONTRACTL_END;
61
62 if (IS_DIGIT(c)) {
63 *result = DIGIT_TO_INT(c);
64 }
65 else if (c>='A' && c<='Z') {
66 //+10 is necessary because A is actually 10, etc.
67 *result = c-'A'+10;
68 }
69 else if (c>='a' && c<='z') {
70 //+10 is necessary because a is actually 10, etc.
71 *result = c-'a'+10;
72 }
73 else {
74 *result = -1;
75 }
76
77 if ((*result >=0) && (*result < radix))
78 return true;
79
80 return false;
81}
82
83INT32 wtoi(__in_ecount(length) WCHAR* wstr, DWORD length)
84{
85 CONTRACTL
86 {
87 NOTHROW;
88 GC_NOTRIGGER;
89 MODE_ANY;
90 PRECONDITION(CheckPointer(wstr));
91 PRECONDITION(length >= 0);
92 }
93 CONTRACTL_END;
94
95 DWORD i = 0;
96 int value;
97 INT32 result = 0;
98
99 while ( (i < length) && (IsDigit(wstr[i], 10 ,&value)) ) {
100 //Read all of the digits and convert to a number
101 result = result*10 + value;
102 i++;
103 }
104
105 return result;
106}
107
108
109
110//
111//
112// EXCEPTION NATIVE
113//
114//
115FCIMPL1(FC_BOOL_RET, ExceptionNative::IsImmutableAgileException, Object* pExceptionUNSAFE)
116{
117 FCALL_CONTRACT;
118
119 ASSERT(pExceptionUNSAFE != NULL);
120
121 OBJECTREF pException = (OBJECTREF) pExceptionUNSAFE;
122
123 // The preallocated exception objects may be used from multiple AppDomains
124 // and therefore must remain immutable from the application's perspective.
125 FC_RETURN_BOOL(CLRException::IsPreallocatedExceptionObject(pException));
126}
127FCIMPLEND
128
129FCIMPL1(FC_BOOL_RET, ExceptionNative::IsTransient, INT32 hresult)
130{
131 FCALL_CONTRACT;
132
133 FC_RETURN_BOOL(Exception::IsTransient(hresult));
134}
135FCIMPLEND
136
137
138// This FCall sets a flag against the thread exception state to indicate to
139// IL_Throw and the StackTraceInfo implementation to account for the fact
140// that we have restored a foreign exception dispatch details.
141//
142// Refer to the respective methods for details on how they use this flag.
143FCIMPL0(VOID, ExceptionNative::PrepareForForeignExceptionRaise)
144{
145 FCALL_CONTRACT;
146
147 PTR_ThreadExceptionState pCurTES = GetThread()->GetExceptionState();
148
149 // Set a flag against the TES to indicate this is a foreign exception raise.
150 pCurTES->SetRaisingForeignException();
151}
152FCIMPLEND
153
154// Given an exception object, this method will extract the stacktrace and dynamic method array and set them up for return to the caller.
155FCIMPL3(VOID, ExceptionNative::GetStackTracesDeepCopy, Object* pExceptionObjectUnsafe, Object **pStackTraceUnsafe, Object **pDynamicMethodsUnsafe);
156{
157 CONTRACTL
158 {
159 FCALL_CHECK;
160 }
161 CONTRACTL_END;
162
163 ASSERT(pExceptionObjectUnsafe != NULL);
164 ASSERT(pStackTraceUnsafe != NULL);
165 ASSERT(pDynamicMethodsUnsafe != NULL);
166
167 struct _gc
168 {
169 StackTraceArray stackTrace;
170 StackTraceArray stackTraceCopy;
171 EXCEPTIONREF refException;
172 PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers
173 PTRARRAYREF dynamicMethodsArrayCopy; // Copy of the object array of Managed Resolvers
174 };
175 _gc gc;
176 ZeroMemory(&gc, sizeof(gc));
177
178 // GC protect the array reference
179 HELPER_METHOD_FRAME_BEGIN_PROTECT(gc);
180
181 // Get the exception object reference
182 gc.refException = (EXCEPTIONREF)(ObjectToOBJECTREF(pExceptionObjectUnsafe));
183
184 // Fetch the stacktrace details from the exception under a lock
185 gc.refException->GetStackTrace(gc.stackTrace, &gc.dynamicMethodsArray);
186
187 bool fHaveStackTrace = false;
188 bool fHaveDynamicMethodArray = false;
189
190 if ((unsigned)gc.stackTrace.Size() > 0)
191 {
192 // Deepcopy the array
193 gc.stackTraceCopy.CopyFrom(gc.stackTrace);
194 fHaveStackTrace = true;
195 }
196
197 if (gc.dynamicMethodsArray != NULL)
198 {
199 // Get the number of elements in the dynamic methods array
200 unsigned cOrigDynamic = gc.dynamicMethodsArray->GetNumComponents();
201
202 // ..and allocate a new array. This can trigger GC or throw under OOM.
203 gc.dynamicMethodsArrayCopy = (PTRARRAYREF)AllocateObjectArray(cOrigDynamic, g_pObjectClass);
204
205 // Deepcopy references to the new array we just allocated
206 memmoveGCRefs(gc.dynamicMethodsArrayCopy->GetDataPtr(), gc.dynamicMethodsArray->GetDataPtr(),
207 cOrigDynamic * sizeof(Object *));
208
209 fHaveDynamicMethodArray = true;
210 }
211
212 // Prep to return
213 *pStackTraceUnsafe = fHaveStackTrace?OBJECTREFToObject(gc.stackTraceCopy.Get()):NULL;
214 *pDynamicMethodsUnsafe = fHaveDynamicMethodArray?OBJECTREFToObject(gc.dynamicMethodsArrayCopy):NULL;
215
216 HELPER_METHOD_FRAME_END();
217}
218FCIMPLEND
219
220// Given an exception object and deep copied instances of a stacktrace and/or dynamic method array, this method will set the latter in the exception object instance.
221FCIMPL3(VOID, ExceptionNative::SaveStackTracesFromDeepCopy, Object* pExceptionObjectUnsafe, Object *pStackTraceUnsafe, Object *pDynamicMethodsUnsafe);
222{
223 CONTRACTL
224 {
225 FCALL_CHECK;
226 }
227 CONTRACTL_END;
228
229 ASSERT(pExceptionObjectUnsafe != NULL);
230
231 struct _gc
232 {
233 StackTraceArray stackTrace;
234 EXCEPTIONREF refException;
235 PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers
236 };
237 _gc gc;
238 ZeroMemory(&gc, sizeof(gc));
239
240 // GC protect the array reference
241 HELPER_METHOD_FRAME_BEGIN_PROTECT(gc);
242
243 // Get the exception object reference
244 gc.refException = (EXCEPTIONREF)(ObjectToOBJECTREF(pExceptionObjectUnsafe));
245
246 if (pStackTraceUnsafe != NULL)
247 {
248 // Copy the stacktrace
249 StackTraceArray stackTraceArray((I1ARRAYREF)ObjectToOBJECTREF(pStackTraceUnsafe));
250 gc.stackTrace.Swap(stackTraceArray);
251 }
252
253 gc.dynamicMethodsArray = NULL;
254 if (pDynamicMethodsUnsafe != NULL)
255 {
256 gc.dynamicMethodsArray = (PTRARRAYREF)ObjectToOBJECTREF(pDynamicMethodsUnsafe);
257 }
258
259 // If there is no stacktrace, then there cannot be any dynamic method array. Thus,
260 // save stacktrace only when we have it.
261 if (gc.stackTrace.Size() > 0)
262 {
263 // Save the stacktrace details in the exception under a lock
264 gc.refException->SetStackTrace(gc.stackTrace, gc.dynamicMethodsArray);
265 }
266 else
267 {
268 gc.refException->SetNullStackTrace();
269 }
270
271 HELPER_METHOD_FRAME_END();
272}
273FCIMPLEND
274
275// This method performs a deep copy of the stack trace array.
276FCIMPL1(Object*, ExceptionNative::CopyStackTrace, Object* pStackTraceUNSAFE)
277{
278 FCALL_CONTRACT;
279
280 ASSERT(pStackTraceUNSAFE != NULL);
281
282 struct _gc
283 {
284 StackTraceArray stackTrace;
285 StackTraceArray stackTraceCopy;
286 _gc(I1ARRAYREF refStackTrace)
287 : stackTrace(refStackTrace)
288 {}
289 };
290 _gc gc((I1ARRAYREF)(ObjectToOBJECTREF(pStackTraceUNSAFE)));
291
292 // GC protect the array reference
293 HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
294
295 // Deepcopy the array
296 gc.stackTraceCopy.CopyFrom(gc.stackTrace);
297
298 HELPER_METHOD_FRAME_END();
299
300 return OBJECTREFToObject(gc.stackTraceCopy.Get());
301}
302FCIMPLEND
303
304// This method performs a deep copy of the dynamic method array.
305FCIMPL1(Object*, ExceptionNative::CopyDynamicMethods, Object* pDynamicMethodsUNSAFE)
306{
307 FCALL_CONTRACT;
308
309 ASSERT(pDynamicMethodsUNSAFE != NULL);
310
311 struct _gc
312 {
313 PTRARRAYREF dynamicMethodsArray; // Object array of Managed Resolvers
314 PTRARRAYREF dynamicMethodsArrayCopy; // Copy of the object array of Managed Resolvers
315 _gc()
316 {}
317 };
318 _gc gc;
319 ZeroMemory(&gc, sizeof(gc));
320 HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
321
322 gc.dynamicMethodsArray = (PTRARRAYREF)(ObjectToOBJECTREF(pDynamicMethodsUNSAFE));
323
324 // Get the number of elements in the array
325 unsigned cOrigDynamic = gc.dynamicMethodsArray->GetNumComponents();
326 // ..and allocate a new array. This can trigger GC or throw under OOM.
327 gc.dynamicMethodsArrayCopy = (PTRARRAYREF)AllocateObjectArray(cOrigDynamic, g_pObjectClass);
328
329 // Copy references to the new array we just allocated
330 memmoveGCRefs(gc.dynamicMethodsArrayCopy->GetDataPtr(), gc.dynamicMethodsArray->GetDataPtr(),
331 cOrigDynamic * sizeof(Object *));
332 HELPER_METHOD_FRAME_END();
333
334 return OBJECTREFToObject(gc.dynamicMethodsArrayCopy);
335}
336FCIMPLEND
337
338
339BSTR BStrFromString(STRINGREF s)
340{
341 CONTRACTL
342 {
343 THROWS;
344 }
345 CONTRACTL_END;
346
347 WCHAR *wz;
348 int cch;
349 BSTR bstr;
350
351 if (s == NULL)
352 return NULL;
353
354 s->RefInterpretGetStringValuesDangerousForGC(&wz, &cch);
355
356 bstr = SysAllocString(wz);
357 if (bstr == NULL)
358 COMPlusThrowOM();
359
360 return bstr;
361}
362
363static BSTR GetExceptionDescription(OBJECTREF objException)
364{
365 CONTRACTL
366 {
367 THROWS;
368 GC_TRIGGERS;
369 MODE_COOPERATIVE;
370 PRECONDITION( IsException(objException->GetMethodTable()) );
371 }
372 CONTRACTL_END;
373
374 BSTR bstrDescription;
375
376 STRINGREF MessageString = NULL;
377 GCPROTECT_BEGIN(MessageString)
378 GCPROTECT_BEGIN(objException)
379 {
380#ifdef FEATURE_APPX
381 if (AppX::IsAppXProcess())
382 {
383 // In AppX, call Exception.ToString(false, false) which returns a string containing the exception class
384 // name and callstack without file paths/names. This is used for unhandled exception bucketing/analysis.
385 MethodDescCallSite getMessage(METHOD__EXCEPTION__TO_STRING, &objException);
386
387 ARG_SLOT GetMessageArgs[] =
388 {
389 ObjToArgSlot(objException),
390 BoolToArgSlot(false), // needFileLineInfo
391 BoolToArgSlot(false) // needMessage
392 };
393 MessageString = getMessage.Call_RetSTRINGREF(GetMessageArgs);
394 }
395 else
396#endif // FEATURE_APPX
397 {
398 // read Exception.Message property
399 MethodDescCallSite getMessage(METHOD__EXCEPTION__GET_MESSAGE, &objException);
400
401 ARG_SLOT GetMessageArgs[] = { ObjToArgSlot(objException)};
402 MessageString = getMessage.Call_RetSTRINGREF(GetMessageArgs);
403
404 // if the message string is empty then use the exception classname.
405 if (MessageString == NULL || MessageString->GetStringLength() == 0) {
406 // call GetClassName
407 MethodDescCallSite getClassName(METHOD__EXCEPTION__GET_CLASS_NAME, &objException);
408 ARG_SLOT GetClassNameArgs[] = { ObjToArgSlot(objException)};
409 MessageString = getClassName.Call_RetSTRINGREF(GetClassNameArgs);
410 _ASSERTE(MessageString != NULL && MessageString->GetStringLength() != 0);
411 }
412 }
413
414 // Allocate the description BSTR.
415 int DescriptionLen = MessageString->GetStringLength();
416 bstrDescription = SysAllocStringLen(MessageString->GetBuffer(), DescriptionLen);
417 }
418 GCPROTECT_END();
419 GCPROTECT_END();
420
421 return bstrDescription;
422}
423
424static BSTR GetExceptionSource(OBJECTREF objException)
425{
426 CONTRACTL
427 {
428 THROWS;
429 GC_TRIGGERS;
430 MODE_COOPERATIVE;
431 PRECONDITION( IsException(objException->GetMethodTable()) );
432 }
433 CONTRACTL_END;
434
435 STRINGREF refRetVal;
436 GCPROTECT_BEGIN(objException)
437
438 // read Exception.Source property
439 MethodDescCallSite getSource(METHOD__EXCEPTION__GET_SOURCE, &objException);
440
441 ARG_SLOT GetSourceArgs[] = { ObjToArgSlot(objException)};
442
443 refRetVal = getSource.Call_RetSTRINGREF(GetSourceArgs);
444
445 GCPROTECT_END();
446 return BStrFromString(refRetVal);
447}
448
449static void GetExceptionHelp(OBJECTREF objException, BSTR *pbstrHelpFile, DWORD *pdwHelpContext)
450{
451 CONTRACTL
452 {
453 THROWS;
454 GC_TRIGGERS;
455 MODE_COOPERATIVE;
456 INJECT_FAULT(COMPlusThrowOM());
457 PRECONDITION(IsException(objException->GetMethodTable()));
458 PRECONDITION(CheckPointer(pbstrHelpFile));
459 PRECONDITION(CheckPointer(pdwHelpContext));
460 }
461 CONTRACTL_END;
462
463 *pdwHelpContext = 0;
464
465 GCPROTECT_BEGIN(objException);
466
467 // read Exception.HelpLink property
468 MethodDescCallSite getHelpLink(METHOD__EXCEPTION__GET_HELP_LINK, &objException);
469
470 ARG_SLOT GetHelpLinkArgs[] = { ObjToArgSlot(objException)};
471 *pbstrHelpFile = BStrFromString(getHelpLink.Call_RetSTRINGREF(GetHelpLinkArgs));
472
473 GCPROTECT_END();
474
475 // parse the help file to check for the presence of helpcontext
476 int len = SysStringLen(*pbstrHelpFile);
477 int pos = len;
478 WCHAR *pwstr = *pbstrHelpFile;
479 if (pwstr) {
480 BOOL fFoundPound = FALSE;
481
482 for (pos = len - 1; pos >= 0; pos--) {
483 if (pwstr[pos] == W('#')) {
484 fFoundPound = TRUE;
485 break;
486 }
487 }
488
489 if (fFoundPound) {
490 int PoundPos = pos;
491 int NumberStartPos = -1;
492 BOOL bNumberStarted = FALSE;
493 BOOL bNumberFinished = FALSE;
494 BOOL bInvalidDigitsFound = FALSE;
495
496 _ASSERTE(pwstr[pos] == W('#'));
497
498 // Check to see if the string to the right of the pound a valid number.
499 for (pos++; pos < len; pos++) {
500 if (bNumberFinished) {
501 if (!COMCharacter::nativeIsWhiteSpace(pwstr[pos])) {
502 bInvalidDigitsFound = TRUE;
503 break;
504 }
505 }
506 else if (bNumberStarted) {
507 if (COMCharacter::nativeIsWhiteSpace(pwstr[pos])) {
508 bNumberFinished = TRUE;
509 }
510 else if (!COMCharacter::nativeIsDigit(pwstr[pos])) {
511 bInvalidDigitsFound = TRUE;
512 break;
513 }
514 }
515 else {
516 if (COMCharacter::nativeIsDigit(pwstr[pos])) {
517 NumberStartPos = pos;
518 bNumberStarted = TRUE;
519 }
520 else if (!COMCharacter::nativeIsWhiteSpace(pwstr[pos])) {
521 bInvalidDigitsFound = TRUE;
522 break;
523 }
524 }
525 }
526
527 if (bNumberStarted && !bInvalidDigitsFound) {
528 // Grab the help context and remove it from the help file.
529 *pdwHelpContext = (DWORD)wtoi(&pwstr[NumberStartPos], len - NumberStartPos);
530
531 // Allocate a new help file string of the right length.
532 BSTR strOld = *pbstrHelpFile;
533 *pbstrHelpFile = SysAllocStringLen(strOld, PoundPos);
534 SysFreeString(strOld);
535 if (!*pbstrHelpFile)
536 COMPlusThrowOM();
537 }
538 }
539 }
540}
541
542// NOTE: caller cleans up any partially initialized BSTRs in pED
543void ExceptionNative::GetExceptionData(OBJECTREF objException, ExceptionData *pED)
544{
545 CONTRACTL
546 {
547 THROWS;
548 GC_TRIGGERS;
549 MODE_COOPERATIVE;
550 PRECONDITION(IsException(objException->GetMethodTable()));
551 PRECONDITION(CheckPointer(pED));
552 }
553 CONTRACTL_END;
554
555 ZeroMemory(pED, sizeof(ExceptionData));
556
557 if (objException->GetMethodTable() == g_pStackOverflowExceptionClass) {
558 // In a low stack situation, most everything else in here will fail.
559 // <TODO>@TODO: We're not turning the guard page back on here, yet.</TODO>
560 pED->hr = COR_E_STACKOVERFLOW;
561 pED->bstrDescription = SysAllocString(STACK_OVERFLOW_MESSAGE);
562 return;
563 }
564
565 GCPROTECT_BEGIN(objException);
566 pED->hr = GetExceptionHResult(objException);
567 pED->bstrDescription = GetExceptionDescription(objException);
568 pED->bstrSource = GetExceptionSource(objException);
569 GetExceptionHelp(objException, &pED->bstrHelpFile, &pED->dwHelpContext);
570 GCPROTECT_END();
571 return;
572}
573
574#ifdef FEATURE_COMINTEROP
575
576HRESULT SimpleComCallWrapper::IErrorInfo_hr()
577{
578 WRAPPER_NO_CONTRACT;
579 return GetExceptionHResult(this->GetObjectRef());
580}
581
582BSTR SimpleComCallWrapper::IErrorInfo_bstrDescription()
583{
584 WRAPPER_NO_CONTRACT;
585 return GetExceptionDescription(this->GetObjectRef());
586}
587
588BSTR SimpleComCallWrapper::IErrorInfo_bstrSource()
589{
590 WRAPPER_NO_CONTRACT;
591 return GetExceptionSource(this->GetObjectRef());
592}
593
594BSTR SimpleComCallWrapper::IErrorInfo_bstrHelpFile()
595{
596 WRAPPER_NO_CONTRACT;
597 BSTR bstrHelpFile;
598 DWORD dwHelpContext;
599 GetExceptionHelp(this->GetObjectRef(), &bstrHelpFile, &dwHelpContext);
600 return bstrHelpFile;
601}
602
603DWORD SimpleComCallWrapper::IErrorInfo_dwHelpContext()
604{
605 WRAPPER_NO_CONTRACT;
606 BSTR bstrHelpFile;
607 DWORD dwHelpContext;
608 GetExceptionHelp(this->GetObjectRef(), &bstrHelpFile, &dwHelpContext);
609 SysFreeString(bstrHelpFile);
610 return dwHelpContext;
611}
612
613GUID SimpleComCallWrapper::IErrorInfo_guid()
614{
615 LIMITED_METHOD_CONTRACT;
616 return GUID_NULL;
617}
618
619#endif // FEATURE_COMINTEROP
620
621FCIMPL0(EXCEPTION_POINTERS*, ExceptionNative::GetExceptionPointers)
622{
623 FCALL_CONTRACT;
624
625 EXCEPTION_POINTERS* retVal = NULL;
626
627 Thread *pThread = GetThread();
628 _ASSERTE(pThread);
629
630 if (pThread->IsExceptionInProgress())
631 {
632 retVal = pThread->GetExceptionState()->GetExceptionPointers();
633 }
634
635 return retVal;
636}
637FCIMPLEND
638
639FCIMPL0(INT32, ExceptionNative::GetExceptionCode)
640{
641 FCALL_CONTRACT;
642
643 INT32 retVal = 0;
644
645 Thread *pThread = GetThread();
646 _ASSERTE(pThread);
647
648 if (pThread->IsExceptionInProgress())
649 {
650 retVal = pThread->GetExceptionState()->GetExceptionCode();
651 }
652
653 return retVal;
654}
655FCIMPLEND
656
657
658//
659// This must be implemented as an FCALL because managed code cannot
660// swallow a thread abort exception without resetting the abort,
661// which we don't want to do. Additionally, we can run into deadlocks
662// if we use the ResourceManager to do resource lookups - it requires
663// taking managed locks when initializing Globalization & Security,
664// but a thread abort on a separate thread initializing those same
665// systems would also do a resource lookup via the ResourceManager.
666// We've deadlocked in CompareInfo.GetCompareInfo &
667// Environment.GetResourceString. It's not practical to take all of
668// our locks within CER's to avoid this problem - just use the CLR's
669// unmanaged resources.
670//
671void QCALLTYPE ExceptionNative::GetMessageFromNativeResources(ExceptionMessageKind kind, QCall::StringHandleOnStack retMesg)
672{
673 QCALL_CONTRACT;
674
675 BEGIN_QCALL;
676
677 SString buffer;
678 HRESULT hr = S_OK;
679 const WCHAR * wszFallbackString = NULL;
680
681 switch(kind) {
682 case ThreadAbort:
683 hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_THREAD_ABORT);
684 if (FAILED(hr)) {
685 wszFallbackString = W("Thread was being aborted.");
686 }
687 break;
688
689 case ThreadInterrupted:
690 hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_THREAD_INTERRUPTED);
691 if (FAILED(hr)) {
692 wszFallbackString = W("Thread was interrupted from a waiting state.");
693 }
694 break;
695
696 case OutOfMemory:
697 hr = buffer.LoadResourceAndReturnHR(CCompRC::Error, IDS_EE_OUT_OF_MEMORY);
698 if (FAILED(hr)) {
699 wszFallbackString = W("Insufficient memory to continue the execution of the program.");
700 }
701 break;
702
703 default:
704 _ASSERTE(!"Unknown ExceptionMessageKind value!");
705 }
706 if (FAILED(hr)) {
707 STRESS_LOG1(LF_BCL, LL_ALWAYS, "LoadResource error: %x", hr);
708 _ASSERTE(wszFallbackString != NULL);
709 retMesg.Set(wszFallbackString);
710 }
711 else {
712 retMesg.Set(buffer);
713 }
714
715 END_QCALL;
716}
717
718// BlockCopy
719// This method from one primitive array to another based
720// upon an offset into each an a byte count.
721FCIMPL5(VOID, Buffer::BlockCopy, ArrayBase *src, int srcOffset, ArrayBase *dst, int dstOffset, int count)
722{
723 FCALL_CONTRACT;
724
725 // Verify that both the src and dst are Arrays of primitive
726 // types.
727 // <TODO>@TODO: We need to check for booleans</TODO>
728 if (src==NULL || dst==NULL)
729 FCThrowArgumentNullVoid((src==NULL) ? W("src") : W("dst"));
730
731 SIZE_T srcLen, dstLen;
732
733 //
734 // Use specialized fast path for byte arrays because of it is what Buffer::BlockCopy is
735 // typically used for.
736 //
737
738 MethodTable * pByteArrayMT = g_pByteArrayMT;
739 _ASSERTE(pByteArrayMT != NULL);
740
741 // Optimization: If src is a byte array, we can
742 // simply set srcLen to GetNumComponents, without having
743 // to call GetComponentSize or verifying GetArrayElementType
744 if (src->GetMethodTable() == pByteArrayMT)
745 {
746 srcLen = src->GetNumComponents();
747 }
748 else
749 {
750 srcLen = src->GetNumComponents() * src->GetComponentSize();
751
752 // We only want to allow arrays of primitives, no Objects.
753 const CorElementType srcET = src->GetArrayElementType();
754 if (!CorTypeInfo::IsPrimitiveType_NoThrow(srcET))
755 FCThrowArgumentVoid(W("src"), W("Arg_MustBePrimArray"));
756 }
757
758 // Optimization: If copying to/from the same array, then
759 // we know that dstLen and srcLen must be the same.
760 if (src == dst)
761 {
762 dstLen = srcLen;
763 }
764 else if (dst->GetMethodTable() == pByteArrayMT)
765 {
766 dstLen = dst->GetNumComponents();
767 }
768 else
769 {
770 dstLen = dst->GetNumComponents() * dst->GetComponentSize();
771 if (dst->GetMethodTable() != src->GetMethodTable())
772 {
773 const CorElementType dstET = dst->GetArrayElementType();
774 if (!CorTypeInfo::IsPrimitiveType_NoThrow(dstET))
775 FCThrowArgumentVoid(W("dest"), W("Arg_MustBePrimArray"));
776 }
777 }
778
779 if (srcOffset < 0 || dstOffset < 0 || count < 0) {
780 const wchar_t* str = W("srcOffset");
781 if (dstOffset < 0) str = W("dstOffset");
782 if (count < 0) str = W("count");
783 FCThrowArgumentOutOfRangeVoid(str, W("ArgumentOutOfRange_NeedNonNegNum"));
784 }
785
786 if (srcLen < (SIZE_T)srcOffset + (SIZE_T)count || dstLen < (SIZE_T)dstOffset + (SIZE_T)count) {
787 FCThrowArgumentVoid(NULL, W("Argument_InvalidOffLen"));
788 }
789
790 PTR_BYTE srcPtr = src->GetDataPtr() + srcOffset;
791 PTR_BYTE dstPtr = dst->GetDataPtr() + dstOffset;
792
793 if ((srcPtr != dstPtr) && (count > 0)) {
794#if defined(_AMD64_) && !defined(PLATFORM_UNIX)
795 JIT_MemCpy(dstPtr, srcPtr, count);
796#else
797 memmove(dstPtr, srcPtr, count);
798#endif
799 }
800
801 FC_GC_POLL();
802}
803FCIMPLEND
804
805
806void QCALLTYPE MemoryNative::Clear(void *dst, size_t length)
807{
808 QCALL_CONTRACT;
809
810 memset(dst, 0, length);
811}
812
813FCIMPL3(VOID, MemoryNative::BulkMoveWithWriteBarrier, void *dst, void *src, size_t byteCount)
814{
815 FCALL_CONTRACT;
816
817 InlinedMemmoveGCRefsHelper(dst, src, byteCount);
818
819 FC_GC_POLL();
820}
821FCIMPLEND
822
823void QCALLTYPE Buffer::MemMove(void *dst, void *src, size_t length)
824{
825 QCALL_CONTRACT;
826
827#if !defined(FEATURE_CORESYSTEM)
828 // Callers of memcpy do expect and handle access violations in some scenarios.
829 // Access violations in the runtime dll are turned into fail fast by the vector exception handler by default.
830 // We need to supress this behavior for CoreCLR using AVInRuntimeImplOkayHolder because of memcpy is statically linked in.
831 AVInRuntimeImplOkayHolder avOk;
832#endif
833
834 memmove(dst, src, length);
835}
836
837// Returns a bool to indicate if the array is of primitive types or not.
838FCIMPL1(FC_BOOL_RET, Buffer::IsPrimitiveTypeArray, ArrayBase *arrayUNSAFE)
839{
840 FCALL_CONTRACT;
841
842 _ASSERTE(arrayUNSAFE != NULL);
843
844 // Check the type from the contained element's handle
845 TypeHandle elementTH = arrayUNSAFE->GetArrayElementTypeHandle();
846 BOOL fIsPrimitiveTypeArray = CorTypeInfo::IsPrimitiveType_NoThrow(elementTH.GetVerifierCorElementType());
847
848 FC_RETURN_BOOL(fIsPrimitiveTypeArray);
849
850}
851FCIMPLEND
852
853// Gets a particular byte out of the array. The array can't be an array of Objects - it
854// must be a primitive array.
855FCIMPL2(FC_UINT8_RET, Buffer::GetByte, ArrayBase *arrayUNSAFE, INT32 index)
856{
857 FCALL_CONTRACT;
858
859 _ASSERTE(arrayUNSAFE != NULL);
860 _ASSERTE(index >=0 && index < ((INT32)(arrayUNSAFE->GetComponentSize() * arrayUNSAFE->GetNumComponents())));
861
862 UINT8 bData = *((BYTE*)arrayUNSAFE->GetDataPtr() + index);
863 return bData;
864}
865FCIMPLEND
866
867// Sets a particular byte in an array. The array can't be an array of Objects - it
868// must be a primitive array.
869//
870// Semantically the bData argment is of type BYTE but FCallCheckSignature expects the
871// type to be UINT8 and raises an error if this isn't this case when
872// COMPlus_ConsistencyCheck is set.
873FCIMPL3(VOID, Buffer::SetByte, ArrayBase *arrayUNSAFE, INT32 index, UINT8 bData)
874{
875 FCALL_CONTRACT;
876
877 _ASSERTE(arrayUNSAFE != NULL);
878 _ASSERTE(index >=0 && index < ((INT32)(arrayUNSAFE->GetComponentSize() * arrayUNSAFE->GetNumComponents())));
879
880 *((BYTE*)arrayUNSAFE->GetDataPtr() + index) = (BYTE) bData;
881}
882FCIMPLEND
883
884// Returns the length in bytes of an array containing
885// primitive type elements
886FCIMPL1(INT32, Buffer::ByteLength, ArrayBase* arrayUNSAFE)
887{
888 FCALL_CONTRACT;
889
890 _ASSERTE(arrayUNSAFE != NULL);
891
892 SIZE_T iRetVal = arrayUNSAFE->GetNumComponents() * arrayUNSAFE->GetComponentSize();
893
894 // This API is explosed both as Buffer.ByteLength and also used indirectly in argument
895 // checks for Buffer.GetByte/SetByte.
896 //
897 // If somebody called Get/SetByte on 2GB+ arrays, there is a decent chance that
898 // the computation of the index has overflowed. Thus we intentionally always
899 // throw on 2GB+ arrays in Get/SetByte argument checks (even for indicies <2GB)
900 // to prevent people from running into a trap silently.
901 if (iRetVal > INT32_MAX)
902 FCThrow(kOverflowException);
903
904 return (INT32)iRetVal;
905}
906FCIMPLEND
907
908//
909// GCInterface
910//
911MethodDesc *GCInterface::m_pCacheMethod=NULL;
912
913UINT64 GCInterface::m_ulMemPressure = 0;
914UINT64 GCInterface::m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD;
915INT32 GCInterface::m_gc_counts[3] = {0,0,0};
916CrstStatic GCInterface::m_MemoryPressureLock;
917
918UINT64 GCInterface::m_addPressure[NEW_PRESSURE_COUNT] = {0, 0, 0, 0}; // history of memory pressure additions
919UINT64 GCInterface::m_remPressure[NEW_PRESSURE_COUNT] = {0, 0, 0, 0}; // history of memory pressure removals
920
921// incremented after a gen2 GC has been detected,
922// (m_iteration % NEW_PRESSURE_COUNT) is used as an index into m_addPressure and m_remPressure
923UINT GCInterface::m_iteration = 0;
924
925FCIMPL5(void, GCInterface::GetMemoryInfo, UINT32* highMemLoadThreshold, UINT64* totalPhysicalMem, UINT32* lastRecordedMemLoad, size_t* lastRecordedHeapSize, size_t* lastRecordedFragmentation)
926{
927 FCALL_CONTRACT;
928
929 FC_GC_POLL_NOT_NEEDED();
930
931 return GCHeapUtilities::GetGCHeap()->GetMemoryInfo(highMemLoadThreshold, totalPhysicalMem,
932 lastRecordedMemLoad,
933 lastRecordedHeapSize, lastRecordedFragmentation);
934}
935FCIMPLEND
936
937FCIMPL0(int, GCInterface::GetGcLatencyMode)
938{
939 FCALL_CONTRACT;
940
941 FC_GC_POLL_NOT_NEEDED();
942
943 int result = (INT32)GCHeapUtilities::GetGCHeap()->GetGcLatencyMode();
944 return result;
945}
946FCIMPLEND
947
948FCIMPL1(int, GCInterface::SetGcLatencyMode, int newLatencyMode)
949{
950 FCALL_CONTRACT;
951
952 FC_GC_POLL_NOT_NEEDED();
953
954 return GCHeapUtilities::GetGCHeap()->SetGcLatencyMode(newLatencyMode);
955}
956FCIMPLEND
957
958FCIMPL0(int, GCInterface::GetLOHCompactionMode)
959{
960 FCALL_CONTRACT;
961
962 FC_GC_POLL_NOT_NEEDED();
963
964 int result = (INT32)GCHeapUtilities::GetGCHeap()->GetLOHCompactionMode();
965 return result;
966}
967FCIMPLEND
968
969FCIMPL1(void, GCInterface::SetLOHCompactionMode, int newLOHCompactionyMode)
970{
971 FCALL_CONTRACT;
972
973 FC_GC_POLL_NOT_NEEDED();
974
975 GCHeapUtilities::GetGCHeap()->SetLOHCompactionMode(newLOHCompactionyMode);
976}
977FCIMPLEND
978
979
980FCIMPL2(FC_BOOL_RET, GCInterface::RegisterForFullGCNotification, UINT32 gen2Percentage, UINT32 lohPercentage)
981{
982 FCALL_CONTRACT;
983
984 FC_GC_POLL_NOT_NEEDED();
985
986 FC_RETURN_BOOL(GCHeapUtilities::GetGCHeap()->RegisterForFullGCNotification(gen2Percentage, lohPercentage));
987}
988FCIMPLEND
989
990FCIMPL0(FC_BOOL_RET, GCInterface::CancelFullGCNotification)
991{
992 FCALL_CONTRACT;
993
994 FC_GC_POLL_NOT_NEEDED();
995 FC_RETURN_BOOL(GCHeapUtilities::GetGCHeap()->CancelFullGCNotification());
996}
997FCIMPLEND
998
999FCIMPL1(int, GCInterface::WaitForFullGCApproach, int millisecondsTimeout)
1000{
1001 CONTRACTL
1002 {
1003 THROWS;
1004 MODE_COOPERATIVE;
1005 DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
1006 SO_TOLERANT;
1007 }
1008 CONTRACTL_END;
1009
1010 int result = 0;
1011
1012 //We don't need to check the top end because the GC will take care of that.
1013 HELPER_METHOD_FRAME_BEGIN_RET_0();
1014
1015 DWORD dwMilliseconds = ((millisecondsTimeout == -1) ? INFINITE : millisecondsTimeout);
1016 result = GCHeapUtilities::GetGCHeap()->WaitForFullGCApproach(dwMilliseconds);
1017
1018 HELPER_METHOD_FRAME_END();
1019
1020 return result;
1021}
1022FCIMPLEND
1023
1024FCIMPL1(int, GCInterface::WaitForFullGCComplete, int millisecondsTimeout)
1025{
1026 CONTRACTL
1027 {
1028 THROWS;
1029 MODE_COOPERATIVE;
1030 DISABLED(GC_TRIGGERS); // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
1031 SO_TOLERANT;
1032 }
1033 CONTRACTL_END;
1034
1035 int result = 0;
1036
1037 //We don't need to check the top end because the GC will take care of that.
1038 HELPER_METHOD_FRAME_BEGIN_RET_0();
1039
1040 DWORD dwMilliseconds = ((millisecondsTimeout == -1) ? INFINITE : millisecondsTimeout);
1041 result = GCHeapUtilities::GetGCHeap()->WaitForFullGCComplete(dwMilliseconds);
1042
1043 HELPER_METHOD_FRAME_END();
1044
1045 return result;
1046}
1047FCIMPLEND
1048
1049/*================================GetGeneration=================================
1050**Action: Returns the generation in which args->obj is found.
1051**Returns: The generation in which args->obj is found.
1052**Arguments: args->obj -- The object to locate.
1053**Exceptions: ArgumentException if args->obj is null.
1054==============================================================================*/
1055FCIMPL1(int, GCInterface::GetGeneration, Object* objUNSAFE)
1056{
1057 FCALL_CONTRACT;
1058
1059 if (objUNSAFE == NULL)
1060 FCThrowArgumentNull(W("obj"));
1061
1062 int result = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(objUNSAFE);
1063 FC_GC_POLL_RET();
1064 return result;
1065}
1066FCIMPLEND
1067
1068/*================================CollectionCount=================================
1069**Action: Returns the number of collections for this generation since the begining of the life of the process
1070**Returns: The collection count.
1071**Arguments: args->generation -- The generation
1072**Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration();
1073==============================================================================*/
1074FCIMPL2(int, GCInterface::CollectionCount, INT32 generation, INT32 getSpecialGCCount)
1075{
1076 FCALL_CONTRACT;
1077
1078 //We've already checked this in GC.cs, so we'll just assert it here.
1079 _ASSERTE(generation >= 0);
1080
1081 //We don't need to check the top end because the GC will take care of that.
1082 int result = (INT32)GCHeapUtilities::GetGCHeap()->CollectionCount(generation, getSpecialGCCount);
1083 FC_GC_POLL_RET();
1084 return result;
1085}
1086FCIMPLEND
1087
1088int QCALLTYPE GCInterface::StartNoGCRegion(INT64 totalSize, BOOL lohSizeKnown, INT64 lohSize, BOOL disallowFullBlockingGC)
1089{
1090 QCALL_CONTRACT;
1091
1092 int retVal = 0;
1093
1094 BEGIN_QCALL;
1095
1096 GCX_COOP();
1097
1098 retVal = GCHeapUtilities::GetGCHeap()->StartNoGCRegion((ULONGLONG)totalSize,
1099 !!lohSizeKnown,
1100 (ULONGLONG)lohSize,
1101 !!disallowFullBlockingGC);
1102
1103 END_QCALL;
1104
1105 return retVal;
1106}
1107
1108int QCALLTYPE GCInterface::EndNoGCRegion()
1109{
1110 QCALL_CONTRACT;
1111
1112 int retVal = FALSE;
1113
1114 BEGIN_QCALL;
1115
1116 retVal = GCHeapUtilities::GetGCHeap()->EndNoGCRegion();
1117
1118 END_QCALL;
1119
1120 return retVal;
1121}
1122
1123/*===============================GetGenerationWR================================
1124**Action: Returns the generation in which the object pointed to by a WeakReference is found.
1125**Returns:
1126**Arguments: args->handle -- the OBJECTHANDLE to the object which we're locating.
1127**Exceptions: ArgumentException if handle points to an object which is not accessible.
1128==============================================================================*/
1129FCIMPL1(int, GCInterface::GetGenerationWR, LPVOID handle)
1130{
1131 FCALL_CONTRACT;
1132
1133 int iRetVal = 0;
1134
1135 HELPER_METHOD_FRAME_BEGIN_RET_0();
1136
1137 OBJECTREF temp;
1138 temp = ObjectFromHandle((OBJECTHANDLE) handle);
1139 if (temp == NULL)
1140 COMPlusThrowArgumentNull(W("weak handle"));
1141
1142 iRetVal = (INT32)GCHeapUtilities::GetGCHeap()->WhichGeneration(OBJECTREFToObject(temp));
1143
1144 HELPER_METHOD_FRAME_END();
1145
1146 return iRetVal;
1147}
1148FCIMPLEND
1149
1150/*================================GetTotalMemory================================
1151**Action: Returns the total number of bytes in use
1152**Returns: The total number of bytes in use
1153**Arguments: None
1154**Exceptions: None
1155==============================================================================*/
1156INT64 QCALLTYPE GCInterface::GetTotalMemory()
1157{
1158 QCALL_CONTRACT;
1159
1160 INT64 iRetVal = 0;
1161
1162 BEGIN_QCALL;
1163
1164 GCX_COOP();
1165 iRetVal = (INT64) GCHeapUtilities::GetGCHeap()->GetTotalBytesInUse();
1166
1167 END_QCALL;
1168
1169 return iRetVal;
1170}
1171
1172/*==============================Collect=========================================
1173**Action: Collects all generations <= args->generation
1174**Returns: void
1175**Arguments: args->generation: The maximum generation to collect
1176**Exceptions: Argument exception if args->generation is < 0 or > GetMaxGeneration();
1177==============================================================================*/
1178void QCALLTYPE GCInterface::Collect(INT32 generation, INT32 mode)
1179{
1180 QCALL_CONTRACT;
1181
1182 BEGIN_QCALL;
1183
1184 //We've already checked this in GC.cs, so we'll just assert it here.
1185 _ASSERTE(generation >= -1);
1186
1187 //We don't need to check the top end because the GC will take care of that.
1188
1189 GCX_COOP();
1190 GCHeapUtilities::GetGCHeap()->GarbageCollect(generation, false, mode);
1191
1192 END_QCALL;
1193}
1194
1195
1196/*==========================WaitForPendingFinalizers============================
1197**Action: Run all Finalizers that haven't been run.
1198**Arguments: None
1199**Exceptions: None
1200==============================================================================*/
1201void QCALLTYPE GCInterface::WaitForPendingFinalizers()
1202{
1203 QCALL_CONTRACT;
1204
1205 BEGIN_QCALL;
1206
1207 FinalizerThread::FinalizerThreadWait();
1208
1209 END_QCALL;
1210}
1211
1212
1213/*===============================GetMaxGeneration===============================
1214**Action: Returns the largest GC generation
1215**Returns: The largest GC Generation
1216**Arguments: None
1217**Exceptions: None
1218==============================================================================*/
1219FCIMPL0(int, GCInterface::GetMaxGeneration)
1220{
1221 FCALL_CONTRACT;
1222
1223 return(INT32)GCHeapUtilities::GetGCHeap()->GetMaxGeneration();
1224}
1225FCIMPLEND
1226
1227/*===============================GetAllocatedBytesForCurrentThread===============================
1228**Action: Computes the allocated bytes so far on the current thread
1229**Returns: The allocated bytes so far on the current thread
1230**Arguments: None
1231**Exceptions: None
1232==============================================================================*/
1233FCIMPL0(INT64, GCInterface::GetAllocatedBytesForCurrentThread)
1234{
1235 FCALL_CONTRACT;
1236
1237 INT64 currentAllocated = 0;
1238 Thread *pThread = GetThread();
1239 gc_alloc_context* ac = pThread->GetAllocContext();
1240 currentAllocated = ac->alloc_bytes + ac->alloc_bytes_loh - (ac->alloc_limit - ac->alloc_ptr);
1241
1242 return currentAllocated;
1243}
1244FCIMPLEND
1245
1246/*==============================SuppressFinalize================================
1247**Action: Indicate that an object's finalizer should not be run by the system
1248**Arguments: Object of interest
1249**Exceptions: None
1250==============================================================================*/
1251FCIMPL1(void, GCInterface::SuppressFinalize, Object *obj)
1252{
1253 FCALL_CONTRACT;
1254
1255 // Checked by the caller
1256 _ASSERTE(obj != NULL);
1257
1258 if (!obj->GetMethodTable ()->HasFinalizer())
1259 return;
1260
1261 GCHeapUtilities::GetGCHeap()->SetFinalizationRun(obj);
1262 FC_GC_POLL();
1263}
1264FCIMPLEND
1265
1266
1267/*============================ReRegisterForFinalize==============================
1268**Action: Indicate that an object's finalizer should be run by the system.
1269**Arguments: Object of interest
1270**Exceptions: None
1271==============================================================================*/
1272FCIMPL1(void, GCInterface::ReRegisterForFinalize, Object *obj)
1273{
1274 FCALL_CONTRACT;
1275
1276 // Checked by the caller
1277 _ASSERTE(obj != NULL);
1278
1279 if (obj->GetMethodTable()->HasFinalizer())
1280 {
1281 HELPER_METHOD_FRAME_BEGIN_1(obj);
1282 if (!GCHeapUtilities::GetGCHeap()->RegisterForFinalization(-1, obj))
1283 {
1284 ThrowOutOfMemory();
1285 }
1286 HELPER_METHOD_FRAME_END();
1287 }
1288}
1289FCIMPLEND
1290
1291FORCEINLINE UINT64 GCInterface::InterlockedAdd (UINT64 *pAugend, UINT64 addend) {
1292 WRAPPER_NO_CONTRACT;
1293 STATIC_CONTRACT_SO_TOLERANT;
1294
1295 UINT64 oldMemValue;
1296 UINT64 newMemValue;
1297
1298 do {
1299 oldMemValue = *pAugend;
1300 newMemValue = oldMemValue + addend;
1301
1302 // check for overflow
1303 if (newMemValue < oldMemValue)
1304 {
1305 newMemValue = UINT64_MAX;
1306 }
1307 } while (InterlockedCompareExchange64((LONGLONG*) pAugend, (LONGLONG) newMemValue, (LONGLONG) oldMemValue) != (LONGLONG) oldMemValue);
1308
1309 return newMemValue;
1310}
1311
1312FORCEINLINE UINT64 GCInterface::InterlockedSub(UINT64 *pMinuend, UINT64 subtrahend) {
1313 WRAPPER_NO_CONTRACT;
1314 STATIC_CONTRACT_SO_TOLERANT;
1315
1316 UINT64 oldMemValue;
1317 UINT64 newMemValue;
1318
1319 do {
1320 oldMemValue = *pMinuend;
1321 newMemValue = oldMemValue - subtrahend;
1322
1323 // check for underflow
1324 if (newMemValue > oldMemValue)
1325 newMemValue = 0;
1326
1327 } while (InterlockedCompareExchange64((LONGLONG*) pMinuend, (LONGLONG) newMemValue, (LONGLONG) oldMemValue) != (LONGLONG) oldMemValue);
1328
1329 return newMemValue;
1330}
1331
1332void QCALLTYPE GCInterface::_AddMemoryPressure(UINT64 bytesAllocated)
1333{
1334 QCALL_CONTRACT;
1335
1336 // AddMemoryPressure could cause a GC, so we need a frame
1337 BEGIN_QCALL;
1338 AddMemoryPressure(bytesAllocated);
1339 END_QCALL;
1340}
1341
1342void GCInterface::AddMemoryPressure(UINT64 bytesAllocated)
1343{
1344 CONTRACTL
1345 {
1346 THROWS;
1347 GC_TRIGGERS;
1348 MODE_ANY;
1349 }
1350 CONTRACTL_END;
1351
1352 SendEtwAddMemoryPressureEvent(bytesAllocated);
1353
1354 UINT64 newMemValue = InterlockedAdd(&m_ulMemPressure, bytesAllocated);
1355
1356 if (newMemValue > m_ulThreshold)
1357 {
1358 INT32 gen_collect = 0;
1359 {
1360 GCX_PREEMP();
1361 CrstHolder holder(&m_MemoryPressureLock);
1362
1363 // to avoid collecting too often, take the max threshold of the linear and geometric growth
1364 // heuristics.
1365 UINT64 addMethod;
1366 UINT64 multMethod;
1367 UINT64 bytesAllocatedMax = (UINT64_MAX - m_ulThreshold) / 8;
1368
1369 if (bytesAllocated >= bytesAllocatedMax) // overflow check
1370 {
1371 addMethod = UINT64_MAX;
1372 }
1373 else
1374 {
1375 addMethod = m_ulThreshold + bytesAllocated * 8;
1376 }
1377
1378 multMethod = newMemValue + newMemValue / 10;
1379 if (multMethod < newMemValue) // overflow check
1380 {
1381 multMethod = UINT64_MAX;
1382 }
1383
1384 m_ulThreshold = (addMethod > multMethod) ? addMethod : multMethod;
1385 for (int i = 0; i <= 1; i++)
1386 {
1387 if ((GCHeapUtilities::GetGCHeap()->CollectionCount(i) / RELATIVE_GC_RATIO) > GCHeapUtilities::GetGCHeap()->CollectionCount(i + 1))
1388 {
1389 gen_collect = i + 1;
1390 break;
1391 }
1392 }
1393 }
1394
1395 PREFIX_ASSUME(gen_collect <= 2);
1396
1397 if ((gen_collect == 0) || (m_gc_counts[gen_collect] == GCHeapUtilities::GetGCHeap()->CollectionCount(gen_collect)))
1398 {
1399 GarbageCollectModeAny(gen_collect);
1400 }
1401
1402 for (int i = 0; i < 3; i++)
1403 {
1404 m_gc_counts [i] = GCHeapUtilities::GetGCHeap()->CollectionCount(i);
1405 }
1406 }
1407}
1408
1409#ifdef _WIN64
1410const unsigned MIN_MEMORYPRESSURE_BUDGET = 4 * 1024 * 1024; // 4 MB
1411#else // _WIN64
1412const unsigned MIN_MEMORYPRESSURE_BUDGET = 3 * 1024 * 1024; // 3 MB
1413#endif // _WIN64
1414
1415const unsigned MAX_MEMORYPRESSURE_RATIO = 10; // 40 MB or 30 MB
1416
1417
1418// Resets pressure accounting after a gen2 GC has occurred.
1419void GCInterface::CheckCollectionCount()
1420{
1421 LIMITED_METHOD_CONTRACT;
1422
1423 IGCHeap * pHeap = GCHeapUtilities::GetGCHeap();
1424
1425 if (m_gc_counts[2] != pHeap->CollectionCount(2))
1426 {
1427 for (int i = 0; i < 3; i++)
1428 {
1429 m_gc_counts[i] = pHeap->CollectionCount(i);
1430 }
1431
1432 m_iteration++;
1433
1434 UINT p = m_iteration % NEW_PRESSURE_COUNT;
1435
1436 m_addPressure[p] = 0; // new pressure will be accumulated here
1437 m_remPressure[p] = 0;
1438 }
1439}
1440
1441
1442// New AddMemoryPressure implementation (used by RCW and the CLRServicesImpl class)
1443//
1444// 1. Less sensitive than the original implementation (start budget 3 MB)
1445// 2. Focuses more on newly added memory pressure
1446// 3. Budget adjusted by effectiveness of last 3 triggered GC (add / remove ratio, max 10x)
1447// 4. Budget maxed with 30% of current managed GC size
1448// 5. If Gen2 GC is happening naturally, ignore past pressure
1449//
1450// Here's a brief description of the ideal algorithm for Add/Remove memory pressure:
1451// Do a GC when (HeapStart < X * MemPressureGrowth) where
1452// - HeapStart is GC Heap size after doing the last GC
1453// - MemPressureGrowth is the net of Add and Remove since the last GC
1454// - X is proportional to our guess of the ummanaged memory death rate per GC interval,
1455// and would be calculated based on historic data using standard exponential approximation:
1456// Xnew = UMDeath/UMTotal * 0.5 + Xprev
1457//
1458void GCInterface::NewAddMemoryPressure(UINT64 bytesAllocated)
1459{
1460 CONTRACTL
1461 {
1462 THROWS;
1463 GC_TRIGGERS;
1464 MODE_ANY;
1465 }
1466 CONTRACTL_END;
1467
1468 CheckCollectionCount();
1469
1470 UINT p = m_iteration % NEW_PRESSURE_COUNT;
1471
1472 UINT64 newMemValue = InterlockedAdd(&m_addPressure[p], bytesAllocated);
1473
1474 static_assert(NEW_PRESSURE_COUNT == 4, "NewAddMemoryPressure contains unrolled loops which depend on NEW_PRESSURE_COUNT");
1475
1476 UINT64 add = m_addPressure[0] + m_addPressure[1] + m_addPressure[2] + m_addPressure[3] - m_addPressure[p];
1477 UINT64 rem = m_remPressure[0] + m_remPressure[1] + m_remPressure[2] + m_remPressure[3] - m_remPressure[p];
1478
1479 STRESS_LOG4(LF_GCINFO, LL_INFO10000, "AMP Add: %I64u => added=%I64u total_added=%I64u total_removed=%I64u",
1480 bytesAllocated, newMemValue, add, rem);
1481
1482 SendEtwAddMemoryPressureEvent(bytesAllocated);
1483
1484 if (newMemValue >= MIN_MEMORYPRESSURE_BUDGET)
1485 {
1486 UINT64 budget = MIN_MEMORYPRESSURE_BUDGET;
1487
1488 if (m_iteration >= NEW_PRESSURE_COUNT) // wait until we have enough data points
1489 {
1490 // Adjust according to effectiveness of GC
1491 // Scale budget according to past m_addPressure / m_remPressure ratio
1492 if (add >= rem * MAX_MEMORYPRESSURE_RATIO)
1493 {
1494 budget = MIN_MEMORYPRESSURE_BUDGET * MAX_MEMORYPRESSURE_RATIO;
1495 }
1496 else if (add > rem)
1497 {
1498 CONSISTENCY_CHECK(rem != 0);
1499
1500 // Avoid overflow by calculating addPressure / remPressure as fixed point (1 = 1024)
1501 budget = (add * 1024 / rem) * budget / 1024;
1502 }
1503 }
1504
1505 // If still over budget, check current managed heap size
1506 if (newMemValue >= budget)
1507 {
1508 IGCHeap *pGCHeap = GCHeapUtilities::GetGCHeap();
1509 UINT64 heapOver3 = pGCHeap->GetCurrentObjSize() / 3;
1510
1511 if (budget < heapOver3) // Max
1512 {
1513 budget = heapOver3;
1514 }
1515
1516 if (newMemValue >= budget)
1517 {
1518 // last check - if we would exceed 20% of GC "duty cycle", do not trigger GC at this time
1519 if ((pGCHeap->GetNow() - pGCHeap->GetLastGCStartTime(2)) > (pGCHeap->GetLastGCDuration(2) * 5))
1520 {
1521 STRESS_LOG6(LF_GCINFO, LL_INFO10000, "AMP Budget: pressure=%I64u ? budget=%I64u (total_added=%I64u, total_removed=%I64u, mng_heap=%I64u) pos=%d",
1522 newMemValue, budget, add, rem, heapOver3 * 3, m_iteration);
1523
1524 GarbageCollectModeAny(2);
1525
1526 CheckCollectionCount();
1527 }
1528 }
1529 }
1530 }
1531}
1532
1533void QCALLTYPE GCInterface::_RemoveMemoryPressure(UINT64 bytesAllocated)
1534{
1535 QCALL_CONTRACT;
1536
1537 BEGIN_QCALL;
1538 RemoveMemoryPressure(bytesAllocated);
1539 END_QCALL;
1540}
1541
1542void GCInterface::RemoveMemoryPressure(UINT64 bytesAllocated)
1543{
1544 CONTRACTL
1545 {
1546 NOTHROW;
1547 GC_TRIGGERS;
1548 MODE_ANY;
1549 }
1550 CONTRACTL_END;
1551
1552 SendEtwRemoveMemoryPressureEvent(bytesAllocated);
1553
1554 UINT64 newMemValue = InterlockedSub(&m_ulMemPressure, bytesAllocated);
1555 UINT64 new_th;
1556 UINT64 bytesAllocatedMax = (m_ulThreshold / 4);
1557 UINT64 addMethod;
1558 UINT64 multMethod = (m_ulThreshold - m_ulThreshold / 20); // can never underflow
1559 if (bytesAllocated >= bytesAllocatedMax) // protect against underflow
1560 {
1561 m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD;
1562 return;
1563 }
1564 else
1565 {
1566 addMethod = m_ulThreshold - bytesAllocated * 4;
1567 }
1568
1569 new_th = (addMethod < multMethod) ? addMethod : multMethod;
1570
1571 if (newMemValue <= new_th)
1572 {
1573 GCX_PREEMP();
1574 CrstHolder holder(&m_MemoryPressureLock);
1575 if (new_th > MIN_GC_MEMORYPRESSURE_THRESHOLD)
1576 m_ulThreshold = new_th;
1577 else
1578 m_ulThreshold = MIN_GC_MEMORYPRESSURE_THRESHOLD;
1579
1580 for (int i = 0; i < 3; i++)
1581 {
1582 m_gc_counts [i] = GCHeapUtilities::GetGCHeap()->CollectionCount(i);
1583 }
1584 }
1585}
1586
1587void GCInterface::NewRemoveMemoryPressure(UINT64 bytesAllocated)
1588{
1589 CONTRACTL
1590 {
1591 NOTHROW;
1592 GC_TRIGGERS;
1593 MODE_ANY;
1594 }
1595 CONTRACTL_END;
1596
1597 CheckCollectionCount();
1598
1599 UINT p = m_iteration % NEW_PRESSURE_COUNT;
1600
1601 SendEtwRemoveMemoryPressureEvent(bytesAllocated);
1602
1603 InterlockedAdd(&m_remPressure[p], bytesAllocated);
1604
1605 STRESS_LOG2(LF_GCINFO, LL_INFO10000, "AMP Remove: %I64u => removed=%I64u",
1606 bytesAllocated, m_remPressure[p]);
1607}
1608
1609inline void GCInterface::SendEtwAddMemoryPressureEvent(UINT64 bytesAllocated)
1610{
1611 CONTRACTL
1612 {
1613 THROWS;
1614 GC_TRIGGERS;
1615 MODE_ANY;
1616 }
1617 CONTRACTL_END;
1618
1619 FireEtwIncreaseMemoryPressure(bytesAllocated, GetClrInstanceId());
1620}
1621
1622// Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw.
1623NOINLINE void GCInterface::SendEtwRemoveMemoryPressureEvent(UINT64 bytesAllocated)
1624{
1625 CONTRACTL
1626 {
1627 NOTHROW;
1628 GC_TRIGGERS;
1629 MODE_ANY;
1630 }
1631 CONTRACTL_END;
1632
1633 EX_TRY
1634 {
1635 FireEtwDecreaseMemoryPressure(bytesAllocated, GetClrInstanceId());
1636 }
1637 EX_CATCH
1638 {
1639 // Ignore failures
1640 }
1641 EX_END_CATCH(SwallowAllExceptions)
1642}
1643
1644// Out-of-line helper to avoid EH prolog/epilog in functions that otherwise don't throw.
1645NOINLINE void GCInterface::GarbageCollectModeAny(int generation)
1646{
1647 CONTRACTL
1648 {
1649 THROWS;
1650 GC_TRIGGERS;
1651 MODE_ANY;
1652 }
1653 CONTRACTL_END;
1654
1655 GCX_COOP();
1656 GCHeapUtilities::GetGCHeap()->GarbageCollect(generation, false, collection_non_blocking);
1657}
1658
1659//
1660// COMInterlocked
1661//
1662
1663#include <optsmallperfcritical.h>
1664
1665FCIMPL2(INT32,COMInterlocked::Exchange, INT32 *location, INT32 value)
1666{
1667 FCALL_CONTRACT;
1668
1669 if( NULL == location) {
1670 FCThrow(kNullReferenceException);
1671 }
1672
1673 return FastInterlockExchange((LONG *) location, value);
1674}
1675FCIMPLEND
1676
1677FCIMPL2_IV(INT64,COMInterlocked::Exchange64, INT64 *location, INT64 value)
1678{
1679 FCALL_CONTRACT;
1680
1681 if( NULL == location) {
1682 FCThrow(kNullReferenceException);
1683 }
1684
1685 return FastInterlockExchangeLong((INT64 *) location, value);
1686}
1687FCIMPLEND
1688
1689FCIMPL2(LPVOID,COMInterlocked::ExchangePointer, LPVOID *location, LPVOID value)
1690{
1691 FCALL_CONTRACT;
1692
1693 if( NULL == location) {
1694 FCThrow(kNullReferenceException);
1695 }
1696
1697 FCUnique(0x15);
1698 return FastInterlockExchangePointer(location, value);
1699}
1700FCIMPLEND
1701
1702FCIMPL3(INT32, COMInterlocked::CompareExchange, INT32* location, INT32 value, INT32 comparand)
1703{
1704 FCALL_CONTRACT;
1705
1706 if( NULL == location) {
1707 FCThrow(kNullReferenceException);
1708 }
1709
1710 return FastInterlockCompareExchange((LONG*)location, value, comparand);
1711}
1712FCIMPLEND
1713
1714FCIMPL4(INT32, COMInterlocked::CompareExchangeReliableResult, INT32* location, INT32 value, INT32 comparand, CLR_BOOL* succeeded)
1715{
1716 FCALL_CONTRACT;
1717
1718 if( NULL == location) {
1719 FCThrow(kNullReferenceException);
1720 }
1721
1722 INT32 result = FastInterlockCompareExchange((LONG*)location, value, comparand);
1723 if (result == comparand)
1724 *succeeded = true;
1725
1726 return result;
1727}
1728FCIMPLEND
1729
1730FCIMPL3_IVV(INT64, COMInterlocked::CompareExchange64, INT64* location, INT64 value, INT64 comparand)
1731{
1732 FCALL_CONTRACT;
1733
1734 if( NULL == location) {
1735 FCThrow(kNullReferenceException);
1736 }
1737
1738 return FastInterlockCompareExchangeLong((INT64*)location, value, comparand);
1739}
1740FCIMPLEND
1741
1742FCIMPL3(LPVOID,COMInterlocked::CompareExchangePointer, LPVOID *location, LPVOID value, LPVOID comparand)
1743{
1744 FCALL_CONTRACT;
1745
1746 if( NULL == location) {
1747 FCThrow(kNullReferenceException);
1748 }
1749
1750 FCUnique(0x59);
1751 return FastInterlockCompareExchangePointer(location, value, comparand);
1752}
1753FCIMPLEND
1754
1755FCIMPL2_IV(float,COMInterlocked::ExchangeFloat, float *location, float value)
1756{
1757 FCALL_CONTRACT;
1758
1759 if( NULL == location) {
1760 FCThrow(kNullReferenceException);
1761 }
1762
1763 LONG ret = FastInterlockExchange((LONG *) location, *(LONG*)&value);
1764 return *(float*)&ret;
1765}
1766FCIMPLEND
1767
1768FCIMPL2_IV(double,COMInterlocked::ExchangeDouble, double *location, double value)
1769{
1770 FCALL_CONTRACT;
1771
1772 if( NULL == location) {
1773 FCThrow(kNullReferenceException);
1774 }
1775
1776
1777 INT64 ret = FastInterlockExchangeLong((INT64 *) location, *(INT64*)&value);
1778 return *(double*)&ret;
1779}
1780FCIMPLEND
1781
1782FCIMPL3_IVV(float,COMInterlocked::CompareExchangeFloat, float *location, float value, float comparand)
1783{
1784 FCALL_CONTRACT;
1785
1786 if( NULL == location) {
1787 FCThrow(kNullReferenceException);
1788 }
1789
1790 LONG ret = (LONG)FastInterlockCompareExchange((LONG*) location, *(LONG*)&value, *(LONG*)&comparand);
1791 return *(float*)&ret;
1792}
1793FCIMPLEND
1794
1795FCIMPL3_IVV(double,COMInterlocked::CompareExchangeDouble, double *location, double value, double comparand)
1796{
1797 FCALL_CONTRACT;
1798
1799 if( NULL == location) {
1800 FCThrow(kNullReferenceException);
1801 }
1802
1803 INT64 ret = (INT64)FastInterlockCompareExchangeLong((INT64*) location, *(INT64*)&value, *(INT64*)&comparand);
1804 return *(double*)&ret;
1805}
1806FCIMPLEND
1807
1808FCIMPL2(LPVOID,COMInterlocked::ExchangeObject, LPVOID*location, LPVOID value)
1809{
1810 FCALL_CONTRACT;
1811
1812 if( NULL == location) {
1813 FCThrow(kNullReferenceException);
1814 }
1815
1816 LPVOID ret = FastInterlockExchangePointer(location, value);
1817#ifdef _DEBUG
1818 Thread::ObjectRefAssign((OBJECTREF *)location);
1819#endif
1820 ErectWriteBarrier((OBJECTREF*) location, ObjectToOBJECTREF((Object*) value));
1821 return ret;
1822}
1823FCIMPLEND
1824
1825FCIMPL3(LPVOID,COMInterlocked::CompareExchangeObject, LPVOID *location, LPVOID value, LPVOID comparand)
1826{
1827 FCALL_CONTRACT;
1828
1829 if( NULL == location) {
1830 FCThrow(kNullReferenceException);
1831 }
1832
1833 // <TODO>@todo: only set ref if is updated</TODO>
1834 LPVOID ret = FastInterlockCompareExchangePointer(location, value, comparand);
1835 if (ret == comparand) {
1836#ifdef _DEBUG
1837 Thread::ObjectRefAssign((OBJECTREF *)location);
1838#endif
1839 ErectWriteBarrier((OBJECTREF*) location, ObjectToOBJECTREF((Object*) value));
1840 }
1841 return ret;
1842}
1843FCIMPLEND
1844
1845FCIMPL2(INT32,COMInterlocked::ExchangeAdd32, INT32 *location, INT32 value)
1846{
1847 FCALL_CONTRACT;
1848
1849 if( NULL == location) {
1850 FCThrow(kNullReferenceException);
1851 }
1852
1853 return FastInterlockExchangeAdd((LONG *) location, value);
1854}
1855FCIMPLEND
1856
1857FCIMPL2_IV(INT64,COMInterlocked::ExchangeAdd64, INT64 *location, INT64 value)
1858{
1859 FCALL_CONTRACT;
1860
1861 if( NULL == location) {
1862 FCThrow(kNullReferenceException);
1863 }
1864
1865 return FastInterlockExchangeAddLong((INT64 *) location, value);
1866}
1867FCIMPLEND
1868
1869FCIMPL0(void, COMInterlocked::FCMemoryBarrier)
1870{
1871 FCALL_CONTRACT;
1872
1873 MemoryBarrier();
1874 FC_GC_POLL();
1875}
1876FCIMPLEND
1877
1878#include <optdefault.h>
1879
1880void QCALLTYPE COMInterlocked::MemoryBarrierProcessWide()
1881{
1882 QCALL_CONTRACT;
1883
1884 FlushProcessWriteBuffers();
1885}
1886
1887static BOOL HasOverriddenMethod(MethodTable* mt, MethodTable* classMT, WORD methodSlot)
1888{
1889 CONTRACTL{
1890 NOTHROW;
1891 GC_NOTRIGGER;
1892 MODE_ANY;
1893 SO_TOLERANT;
1894 } CONTRACTL_END;
1895
1896 _ASSERTE(mt != NULL);
1897 _ASSERTE(classMT != NULL);
1898 _ASSERTE(methodSlot != 0);
1899
1900 PCODE actual = mt->GetRestoredSlot(methodSlot);
1901 PCODE base = classMT->GetRestoredSlot(methodSlot);
1902
1903 if (actual == base)
1904 {
1905 return FALSE;
1906 }
1907
1908 if (!classMT->IsZapped())
1909 {
1910 // If mscorlib is JITed, the slots can be patched and thus we need to compare the actual MethodDescs
1911 // to detect match reliably
1912 if (MethodTable::GetMethodDescForSlotAddress(actual) == MethodTable::GetMethodDescForSlotAddress(base))
1913 {
1914 return FALSE;
1915 }
1916 }
1917
1918 return TRUE;
1919}
1920
1921static BOOL CanCompareBitsOrUseFastGetHashCode(MethodTable* mt)
1922{
1923 CONTRACTL
1924 {
1925 THROWS;
1926 GC_TRIGGERS;
1927 MODE_COOPERATIVE;
1928 } CONTRACTL_END;
1929
1930 _ASSERTE(mt != NULL);
1931
1932 if (mt->HasCheckedCanCompareBitsOrUseFastGetHashCode())
1933 {
1934 return mt->CanCompareBitsOrUseFastGetHashCode();
1935 }
1936
1937 if (mt->ContainsPointers()
1938 || mt->IsNotTightlyPacked())
1939 {
1940 mt->SetHasCheckedCanCompareBitsOrUseFastGetHashCode();
1941 return FALSE;
1942 }
1943
1944 MethodTable* valueTypeMT = MscorlibBinder::GetClass(CLASS__VALUE_TYPE);
1945 WORD slotEquals = MscorlibBinder::GetMethod(METHOD__VALUE_TYPE__EQUALS)->GetSlot();
1946 WORD slotGetHashCode = MscorlibBinder::GetMethod(METHOD__VALUE_TYPE__GET_HASH_CODE)->GetSlot();
1947
1948 // Check the input type.
1949 if (HasOverriddenMethod(mt, valueTypeMT, slotEquals)
1950 || HasOverriddenMethod(mt, valueTypeMT, slotGetHashCode))
1951 {
1952 mt->SetHasCheckedCanCompareBitsOrUseFastGetHashCode();
1953
1954 // If overridden Equals or GetHashCode found, stop searching further.
1955 return FALSE;
1956 }
1957
1958 BOOL canCompareBitsOrUseFastGetHashCode = TRUE;
1959
1960 // The type itself did not override Equals or GetHashCode, go for its fields.
1961 ApproxFieldDescIterator iter = ApproxFieldDescIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS);
1962 for (FieldDesc* pField = iter.Next(); pField != NULL; pField = iter.Next())
1963 {
1964 if (pField->GetFieldType() == ELEMENT_TYPE_VALUETYPE)
1965 {
1966 // Check current field type.
1967 MethodTable* fieldMethodTable = pField->GetApproxFieldTypeHandleThrowing().GetMethodTable();
1968 if (!CanCompareBitsOrUseFastGetHashCode(fieldMethodTable))
1969 {
1970 canCompareBitsOrUseFastGetHashCode = FALSE;
1971 break;
1972 }
1973 }
1974 else if (pField->GetFieldType() == ELEMENT_TYPE_R8
1975 || pField->GetFieldType() == ELEMENT_TYPE_R4)
1976 {
1977 // We have double/single field, cannot compare in fast path.
1978 canCompareBitsOrUseFastGetHashCode = FALSE;
1979 break;
1980 }
1981 }
1982
1983 // We've gone through all instance fields. It's time to cache the result.
1984 // Note SetCanCompareBitsOrUseFastGetHashCode(BOOL) ensures the checked flag
1985 // and canCompare flag being set atomically to avoid race.
1986 mt->SetCanCompareBitsOrUseFastGetHashCode(canCompareBitsOrUseFastGetHashCode);
1987
1988 return canCompareBitsOrUseFastGetHashCode;
1989}
1990
1991NOINLINE static FC_BOOL_RET CanCompareBitsHelper(MethodTable* mt, OBJECTREF objRef)
1992{
1993 FC_INNER_PROLOG(ValueTypeHelper::CanCompareBits);
1994
1995 _ASSERTE(mt != NULL);
1996 _ASSERTE(objRef != NULL);
1997
1998 BOOL ret = FALSE;
1999
2000 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2, objRef);
2001
2002 ret = CanCompareBitsOrUseFastGetHashCode(mt);
2003
2004 HELPER_METHOD_FRAME_END();
2005 FC_INNER_EPILOG();
2006
2007 FC_RETURN_BOOL(ret);
2008}
2009
2010// Return true if the valuetype does not contain pointer, is tightly packed,
2011// does not have floating point number field and does not override Equals method.
2012FCIMPL1(FC_BOOL_RET, ValueTypeHelper::CanCompareBits, Object* obj)
2013{
2014 FCALL_CONTRACT;
2015
2016 _ASSERTE(obj != NULL);
2017 MethodTable* mt = obj->GetMethodTable();
2018
2019 if (mt->HasCheckedCanCompareBitsOrUseFastGetHashCode())
2020 {
2021 FC_RETURN_BOOL(mt->CanCompareBitsOrUseFastGetHashCode());
2022 }
2023
2024 OBJECTREF objRef(obj);
2025
2026 FC_INNER_RETURN(FC_BOOL_RET, CanCompareBitsHelper(mt, objRef));
2027}
2028FCIMPLEND
2029
2030FCIMPL2(FC_BOOL_RET, ValueTypeHelper::FastEqualsCheck, Object* obj1, Object* obj2)
2031{
2032 FCALL_CONTRACT;
2033
2034 _ASSERTE(obj1 != NULL);
2035 _ASSERTE(obj2 != NULL);
2036 _ASSERTE(!obj1->GetMethodTable()->ContainsPointers());
2037 _ASSERTE(obj1->GetSize() == obj2->GetSize());
2038
2039 TypeHandle pTh = obj1->GetTypeHandle();
2040
2041 FC_RETURN_BOOL(memcmp(obj1->GetData(),obj2->GetData(),pTh.GetSize()) == 0);
2042}
2043FCIMPLEND
2044
2045static INT32 FastGetValueTypeHashCodeHelper(MethodTable *mt, void *pObjRef)
2046{
2047 CONTRACTL
2048 {
2049 NOTHROW;
2050 GC_NOTRIGGER;
2051 MODE_COOPERATIVE;
2052 SO_TOLERANT;
2053 } CONTRACTL_END;
2054
2055 INT32 hashCode = 0;
2056 INT32 *pObj = (INT32*)pObjRef;
2057
2058 // this is a struct with no refs and no "strange" offsets, just go through the obj and xor the bits
2059 INT32 size = mt->GetNumInstanceFieldBytes();
2060 for (INT32 i = 0; i < (INT32)(size / sizeof(INT32)); i++)
2061 hashCode ^= *pObj++;
2062
2063 return hashCode;
2064}
2065
2066static INT32 RegularGetValueTypeHashCode(MethodTable *mt, void *pObjRef)
2067{
2068 CONTRACTL
2069 {
2070 THROWS;
2071 GC_TRIGGERS;
2072 MODE_COOPERATIVE;
2073 } CONTRACTL_END;
2074
2075 INT32 hashCode = 0;
2076
2077 GCPROTECT_BEGININTERIOR(pObjRef);
2078
2079 BOOL canUseFastGetHashCodeHelper = FALSE;
2080 if (mt->HasCheckedCanCompareBitsOrUseFastGetHashCode())
2081 {
2082 canUseFastGetHashCodeHelper = mt->CanCompareBitsOrUseFastGetHashCode();
2083 }
2084 else
2085 {
2086 canUseFastGetHashCodeHelper = CanCompareBitsOrUseFastGetHashCode(mt);
2087 }
2088
2089 // While we shouln't get here directly from ValueTypeHelper::GetHashCode, if we recurse we need to
2090 // be able to handle getting the hashcode for an embedded structure whose hashcode is computed by the fast path.
2091 if (canUseFastGetHashCodeHelper)
2092 {
2093 hashCode = FastGetValueTypeHashCodeHelper(mt, pObjRef);
2094 }
2095 else
2096 {
2097 // it's looking ugly so we'll use the old behavior in managed code. Grab the first non-null
2098 // field and return its hash code or 'it' as hash code
2099 // <TODO> Note that the old behavior has already been broken for value types
2100 // that is qualified for CanUseFastGetHashCodeHelper. So maybe we should
2101 // change the implementation here to use all fields instead of just the 1st one.
2102 // </TODO>
2103 //
2104 // <TODO> check this approximation - we may be losing exact type information </TODO>
2105 ApproxFieldDescIterator fdIterator(mt, ApproxFieldDescIterator::INSTANCE_FIELDS);
2106
2107 FieldDesc *field;
2108 while ((field = fdIterator.Next()) != NULL)
2109 {
2110 _ASSERTE(!field->IsRVA());
2111 if (field->IsObjRef())
2112 {
2113 // if we get an object reference we get the hash code out of that
2114 if (*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe()) != NULL)
2115 {
2116 PREPARE_SIMPLE_VIRTUAL_CALLSITE(METHOD__OBJECT__GET_HASH_CODE, (*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe())));
2117 DECLARE_ARGHOLDER_ARRAY(args, 1);
2118 args[ARGNUM_0] = PTR_TO_ARGHOLDER(*(Object**)((BYTE *)pObjRef + field->GetOffsetUnsafe()));
2119 CALL_MANAGED_METHOD(hashCode, INT32, args);
2120 }
2121 else
2122 {
2123 // null object reference, try next
2124 continue;
2125 }
2126 }
2127 else
2128 {
2129 CorElementType fieldType = field->GetFieldType();
2130 if (fieldType == ELEMENT_TYPE_R8)
2131 {
2132 PREPARE_NONVIRTUAL_CALLSITE(METHOD__DOUBLE__GET_HASH_CODE);
2133 DECLARE_ARGHOLDER_ARRAY(args, 1);
2134 args[ARGNUM_0] = PTR_TO_ARGHOLDER(((BYTE *)pObjRef + field->GetOffsetUnsafe()));
2135 CALL_MANAGED_METHOD(hashCode, INT32, args);
2136 }
2137 else if (fieldType == ELEMENT_TYPE_R4)
2138 {
2139 PREPARE_NONVIRTUAL_CALLSITE(METHOD__SINGLE__GET_HASH_CODE);
2140 DECLARE_ARGHOLDER_ARRAY(args, 1);
2141 args[ARGNUM_0] = PTR_TO_ARGHOLDER(((BYTE *)pObjRef + field->GetOffsetUnsafe()));
2142 CALL_MANAGED_METHOD(hashCode, INT32, args);
2143 }
2144 else if (fieldType != ELEMENT_TYPE_VALUETYPE)
2145 {
2146 UINT fieldSize = field->LoadSize();
2147 INT32 *pValue = (INT32*)((BYTE *)pObjRef + field->GetOffsetUnsafe());
2148 for (INT32 j = 0; j < (INT32)(fieldSize / sizeof(INT32)); j++)
2149 hashCode ^= *pValue++;
2150 }
2151 else
2152 {
2153 // got another value type. Get the type
2154 TypeHandle fieldTH = field->GetFieldTypeHandleThrowing();
2155 _ASSERTE(!fieldTH.IsNull());
2156 hashCode = RegularGetValueTypeHashCode(fieldTH.GetMethodTable(), (BYTE *)pObjRef + field->GetOffsetUnsafe());
2157 }
2158 }
2159 break;
2160 }
2161 }
2162
2163 GCPROTECT_END();
2164
2165 return hashCode;
2166}
2167
2168// The default implementation of GetHashCode() for all value types.
2169// Note that this implementation reveals the value of the fields.
2170// So if the value type contains any sensitive information it should
2171// implement its own GetHashCode().
2172FCIMPL1(INT32, ValueTypeHelper::GetHashCode, Object* objUNSAFE)
2173{
2174 FCALL_CONTRACT;
2175
2176 if (objUNSAFE == NULL)
2177 FCThrow(kNullReferenceException);
2178
2179 OBJECTREF obj = ObjectToOBJECTREF(objUNSAFE);
2180 VALIDATEOBJECTREF(obj);
2181
2182 INT32 hashCode = 0;
2183 MethodTable *pMT = objUNSAFE->GetMethodTable();
2184
2185 // We don't want to expose the method table pointer in the hash code
2186 // Let's use the typeID instead.
2187 UINT32 typeID = pMT->LookupTypeID();
2188 if (typeID == TypeIDProvider::INVALID_TYPE_ID)
2189 {
2190 // If the typeID has yet to be generated, fall back to GetTypeID
2191 // This only needs to be done once per MethodTable
2192 HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
2193 typeID = pMT->GetTypeID();
2194 HELPER_METHOD_FRAME_END();
2195 }
2196
2197 // To get less colliding and more evenly distributed hash codes,
2198 // we munge the class index with two big prime numbers
2199 hashCode = typeID * 711650207 + 2506965631U;
2200
2201 BOOL canUseFastGetHashCodeHelper = FALSE;
2202 if (pMT->HasCheckedCanCompareBitsOrUseFastGetHashCode())
2203 {
2204 canUseFastGetHashCodeHelper = pMT->CanCompareBitsOrUseFastGetHashCode();
2205 }
2206 else
2207 {
2208 HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
2209 canUseFastGetHashCodeHelper = CanCompareBitsOrUseFastGetHashCode(pMT);
2210 HELPER_METHOD_FRAME_END();
2211 }
2212
2213 if (canUseFastGetHashCodeHelper)
2214 {
2215 hashCode ^= FastGetValueTypeHashCodeHelper(pMT, obj->UnBox());
2216 }
2217 else
2218 {
2219 HELPER_METHOD_FRAME_BEGIN_RET_1(obj);
2220 hashCode ^= RegularGetValueTypeHashCode(pMT, obj->UnBox());
2221 HELPER_METHOD_FRAME_END();
2222 }
2223
2224 return hashCode;
2225}
2226FCIMPLEND
2227
2228static LONG s_dwSeed;
2229
2230FCIMPL1(INT32, ValueTypeHelper::GetHashCodeOfPtr, LPVOID ptr)
2231{
2232 FCALL_CONTRACT;
2233
2234 INT32 hashCode = (INT32)((INT64)(ptr));
2235
2236 if (hashCode == 0)
2237 {
2238 return 0;
2239 }
2240
2241 DWORD dwSeed = s_dwSeed;
2242
2243 // Initialize s_dwSeed lazily
2244 if (dwSeed == 0)
2245 {
2246 // We use the first non-0 pointer as the seed, all hashcodes will be based off that.
2247 // This is to make sure that we only reveal relative memory addresses and never absolute ones.
2248 dwSeed = hashCode;
2249 InterlockedCompareExchange(&s_dwSeed, dwSeed, 0);
2250 dwSeed = s_dwSeed;
2251 }
2252 _ASSERTE(dwSeed != 0);
2253
2254 return hashCode - dwSeed;
2255}
2256FCIMPLEND
2257
2258static MethodTable * g_pStreamMT;
2259static WORD g_slotBeginRead, g_slotEndRead;
2260static WORD g_slotBeginWrite, g_slotEndWrite;
2261
2262static bool HasOverriddenStreamMethod(MethodTable * pMT, WORD slot)
2263{
2264 CONTRACTL{
2265 NOTHROW;
2266 GC_NOTRIGGER;
2267 MODE_ANY;
2268 SO_TOLERANT;
2269 } CONTRACTL_END;
2270
2271 PCODE actual = pMT->GetRestoredSlot(slot);
2272 PCODE base = g_pStreamMT->GetRestoredSlot(slot);
2273 if (actual == base)
2274 return false;
2275
2276 if (!g_pStreamMT->IsZapped())
2277 {
2278 // If mscorlib is JITed, the slots can be patched and thus we need to compare the actual MethodDescs
2279 // to detect match reliably
2280 if (MethodTable::GetMethodDescForSlotAddress(actual) == MethodTable::GetMethodDescForSlotAddress(base))
2281 return false;
2282 }
2283
2284 return true;
2285}
2286
2287FCIMPL1(FC_BOOL_RET, StreamNative::HasOverriddenBeginEndRead, Object *stream)
2288{
2289 FCALL_CONTRACT;
2290
2291 if (stream == NULL)
2292 FC_RETURN_BOOL(TRUE);
2293
2294 if (g_pStreamMT == NULL || g_slotBeginRead == 0 || g_slotEndRead == 0)
2295 {
2296 HELPER_METHOD_FRAME_BEGIN_RET_1(stream);
2297 g_pStreamMT = MscorlibBinder::GetClass(CLASS__STREAM);
2298 g_slotBeginRead = MscorlibBinder::GetMethod(METHOD__STREAM__BEGIN_READ)->GetSlot();
2299 g_slotEndRead = MscorlibBinder::GetMethod(METHOD__STREAM__END_READ)->GetSlot();
2300 HELPER_METHOD_FRAME_END();
2301 }
2302
2303 MethodTable * pMT = stream->GetMethodTable();
2304
2305 FC_RETURN_BOOL(HasOverriddenStreamMethod(pMT, g_slotBeginRead) || HasOverriddenStreamMethod(pMT, g_slotEndRead));
2306}
2307FCIMPLEND
2308
2309FCIMPL1(FC_BOOL_RET, StreamNative::HasOverriddenBeginEndWrite, Object *stream)
2310{
2311 FCALL_CONTRACT;
2312
2313 if (stream == NULL)
2314 FC_RETURN_BOOL(TRUE);
2315
2316 if (g_pStreamMT == NULL || g_slotBeginWrite == 0 || g_slotEndWrite == 0)
2317 {
2318 HELPER_METHOD_FRAME_BEGIN_RET_1(stream);
2319 g_pStreamMT = MscorlibBinder::GetClass(CLASS__STREAM);
2320 g_slotBeginWrite = MscorlibBinder::GetMethod(METHOD__STREAM__BEGIN_WRITE)->GetSlot();
2321 g_slotEndWrite = MscorlibBinder::GetMethod(METHOD__STREAM__END_WRITE)->GetSlot();
2322 HELPER_METHOD_FRAME_END();
2323 }
2324
2325 MethodTable * pMT = stream->GetMethodTable();
2326
2327 FC_RETURN_BOOL(HasOverriddenStreamMethod(pMT, g_slotBeginWrite) || HasOverriddenStreamMethod(pMT, g_slotEndWrite));
2328}
2329FCIMPLEND
2330