diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 546d9a0a2d9835..2212dba9936495 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -893,7 +893,9 @@ InterpMethod* InterpCompiler::CreateInterpMethod() for (int i = 0; i < numDataItems; i++) pDataItems[i] = m_dataItems.Get(i); - InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_totalVarsStackSize, pDataItems); + bool initLocals = (m_methodInfo->options & CORINFO_OPT_INIT_LOCALS) != 0; + + InterpMethod *pMethod = new InterpMethod(m_methodHnd, m_totalVarsStackSize, pDataItems, initLocals); return pMethod; } @@ -1443,7 +1445,7 @@ void InterpCompiler::EmitBinaryArithmeticOp(int32_t opBase) } else { -#if SIZEOF_VOID_P == 8 +#if TARGET_64BIT if (type1 == StackTypeI8 && type2 == StackTypeI4) { EmitConv(m_pStackPointer - 1, NULL, StackTypeI8, INTOP_CONV_I8_I4); @@ -2686,6 +2688,14 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) EmitBinaryArithmeticOp(INTOP_MUL_I4); m_ip++; break; + case CEE_MUL_OVF: + EmitBinaryArithmeticOp(INTOP_MUL_OVF_I4); + m_ip++; + break; + case CEE_MUL_OVF_UN: + EmitBinaryArithmeticOp(INTOP_MUL_OVF_UN_I4); + m_ip++; + break; case CEE_DIV: EmitBinaryArithmeticOp(INTOP_DIV_I4); m_ip++; @@ -3224,6 +3234,29 @@ int InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo) m_ip += 5; break; } + case CEE_LOCALLOC: + CHECK_STACK(1); +#if TARGET_64BIT + // Length is natural unsigned int + if (m_pStackPointer[-1].type == StackTypeI4) + { + EmitConv(m_pStackPointer - 1, NULL, StackTypeI8, INTOP_MOV_8); + m_pStackPointer[-1].type = StackTypeI8; + } +#endif + AddIns(INTOP_LOCALLOC); + m_pStackPointer--; + if (m_pStackPointer != m_pStackBase) + { + m_hasInvalidCode = true; + goto exit_bad_code; + } + + m_pLastNewIns->SetSVar(m_pStackPointer[0].var); + PushStackType(StackTypeByRef, NULL); + m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); + m_ip++; + break; default: assert(0); break; diff --git a/src/coreclr/interpreter/interpretershared.h b/src/coreclr/interpreter/interpretershared.h index 5e8928b840bafd..e778d541b0b5d6 100644 --- a/src/coreclr/interpreter/interpretershared.h +++ b/src/coreclr/interpreter/interpretershared.h @@ -18,12 +18,14 @@ struct InterpMethod CORINFO_METHOD_HANDLE methodHnd; int32_t allocaSize; void** pDataItems; + bool initLocals; - InterpMethod(CORINFO_METHOD_HANDLE methodHnd, int32_t allocaSize, void** pDataItems) + InterpMethod(CORINFO_METHOD_HANDLE methodHnd, int32_t allocaSize, void** pDataItems, bool initLocals) { this->methodHnd = methodHnd; this->allocaSize = allocaSize; this->pDataItems = pDataItems; + this->initLocals = initLocals; } }; diff --git a/src/coreclr/interpreter/intops.def b/src/coreclr/interpreter/intops.def index 84e429ff72f9ef..10fdb834c4bb37 100644 --- a/src/coreclr/interpreter/intops.def +++ b/src/coreclr/interpreter/intops.def @@ -160,6 +160,12 @@ OPDEF(INTOP_MUL_I8, "mul.i8", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_MUL_R4, "mul.r4", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_MUL_R8, "mul.r8", 4, 1, 2, InterpOpNoArgs) +OPDEF(INTOP_MUL_OVF_I4, "mul.ovf.i4", 4, 1, 2, InterpOpNoArgs) +OPDEF(INTOP_MUL_OVF_I8, "mul.ovf.i8", 4, 1, 2, InterpOpNoArgs) + +OPDEF(INTOP_MUL_OVF_UN_I4, "mul.ovf.un.i4", 4, 1, 2, InterpOpNoArgs) +OPDEF(INTOP_MUL_OVF_UN_I8, "mul.ovf.un.i8", 4, 1, 2, InterpOpNoArgs) + OPDEF(INTOP_DIV_I4, "div.i4", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_DIV_I8, "div.i8", 4, 1, 2, InterpOpNoArgs) OPDEF(INTOP_DIV_R4, "div.r4", 4, 1, 2, InterpOpNoArgs) @@ -253,6 +259,7 @@ OPDEF(INTOP_NEWOBJ_VT, "newobj.vt", 5, 1, 1, InterpOpMethodToken) OPDEF(INTOP_CALL_HELPER_PP, "call.helper.pp", 5, 1, 0, InterpOpThreeInts) OPDEF(INTOP_ZEROBLK_IMM, "zeroblk.imm", 3, 0, 1, InterpOpInt) +OPDEF(INTOP_LOCALLOC, "localloc", 3, 1, 1, InterpOpNoArgs) OPDEF(INTOP_BREAKPOINT, "breakpoint", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_FAILFAST, "failfast", 1, 0, 0, InterpOpNoArgs) OPDEF(INTOP_GC_COLLECT, "gc.collect", 1, 0, 0, InterpOpNoArgs) diff --git a/src/coreclr/vm/CMakeLists.txt b/src/coreclr/vm/CMakeLists.txt index 7fbeffa86df59d..d16c32d384be44 100644 --- a/src/coreclr/vm/CMakeLists.txt +++ b/src/coreclr/vm/CMakeLists.txt @@ -344,6 +344,7 @@ set(VM_SOURCES_WKS interopconverter.cpp interoputil.cpp interpexec.cpp + interpframeallocator.cpp invokeutil.cpp jithelpers.cpp managedmdimport.cpp @@ -444,6 +445,7 @@ set(VM_HEADERS_WKS interoputil.h interoputil.inl interpexec.h + interpframeallocator.h invokeutil.h managedmdimport.hpp marshalnative.h diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index cc46fd606f12df..78e0e1fe667612 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -9,26 +9,16 @@ typedef void* (*HELPER_FTN_PP)(void*); -thread_local InterpThreadContext *t_pThreadContext = NULL; - -InterpThreadContext* InterpGetThreadContext() +InterpThreadContext::InterpThreadContext() { - InterpThreadContext *threadContext = t_pThreadContext; - - if (!threadContext) - { - threadContext = new InterpThreadContext; - // FIXME VirtualAlloc/mmap with INTERP_STACK_ALIGNMENT alignment - threadContext->pStackStart = threadContext->pStackPointer = (int8_t*)malloc(INTERP_STACK_SIZE); - threadContext->pStackEnd = threadContext->pStackStart + INTERP_STACK_SIZE; + // FIXME VirtualAlloc/mmap with INTERP_STACK_ALIGNMENT alignment + pStackStart = pStackPointer = (int8_t*)malloc(INTERP_STACK_SIZE); + pStackEnd = pStackStart + INTERP_STACK_SIZE; +} - t_pThreadContext = threadContext; - return threadContext; - } - else - { - return threadContext; - } +InterpThreadContext::~InterpThreadContext() +{ + free(pStackStart); } #ifdef DEBUG @@ -584,7 +574,53 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr LOCAL_VAR(ip[1], double) = LOCAL_VAR(ip[2], double) * LOCAL_VAR(ip[3], double); ip += 4; break; + case INTOP_MUL_OVF_I4: + { + int32_t i1 = LOCAL_VAR(ip[2], int32_t); + int32_t i2 = LOCAL_VAR(ip[3], int32_t); + int32_t i3; + if (!ClrSafeInt::multiply(i1, i2, i3)) + assert(0); // Interpreter-TODO: OverflowException + LOCAL_VAR(ip[1], int32_t) = i3; + ip += 4; + break; + } + + case INTOP_MUL_OVF_I8: + { + int64_t i1 = LOCAL_VAR(ip[2], int64_t); + int64_t i2 = LOCAL_VAR(ip[3], int64_t); + int64_t i3; + if (!ClrSafeInt::multiply(i1, i2, i3)) + assert(0); // Interpreter-TODO: OverflowException + LOCAL_VAR(ip[1], int64_t) = i3; + ip += 4; + break; + } + + case INTOP_MUL_OVF_UN_I4: + { + uint32_t i1 = LOCAL_VAR(ip[2], uint32_t); + uint32_t i2 = LOCAL_VAR(ip[3], uint32_t); + uint32_t i3; + if (!ClrSafeInt::multiply(i1, i2, i3)) + assert(0); // Interpreter-TODO: OverflowException + LOCAL_VAR(ip[1], uint32_t) = i3; + ip += 4; + break; + } + case INTOP_MUL_OVF_UN_I8: + { + uint64_t i1 = LOCAL_VAR(ip[2], uint64_t); + uint64_t i2 = LOCAL_VAR(ip[3], uint64_t); + uint64_t i3; + if (!ClrSafeInt::multiply(i1, i2, i3)) + assert(0); // Interpreter-TODO: OverflowException + LOCAL_VAR(ip[1], uint64_t) = i3; + ip += 4; + break; + } case INTOP_DIV_I4: { int32_t i1 = LOCAL_VAR(ip[2], int32_t); @@ -1105,6 +1141,29 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr memset(LOCAL_VAR(ip[1], void*), 0, ip[2]); ip += 3; break; + case INTOP_LOCALLOC: + { + size_t len = LOCAL_VAR(ip[2], size_t); + void* pMemory = NULL; + + if (len > 0) + { + pMemory = pThreadContext->frameDataAllocator.Alloc(pFrame, len); + if (pMemory == NULL) + { + // Interpreter-TODO: OutOfMemoryException + assert(0); + } + if (pMethod->initLocals) + { + memset(pMemory, 0, len); + } + } + + LOCAL_VAR(ip[1], void*) = pMemory; + ip += 3; + break; + } case INTOP_GC_COLLECT: { // HACK: blocking gc of all generations to enable early stackwalk testing // Interpreter-TODO: Remove this @@ -1126,6 +1185,9 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } EXIT_FRAME: + + // Interpreter-TODO: Don't run PopInfo on the main return path, Add RET_LOCALLOC instead + pThreadContext->frameDataAllocator.PopInfo(pFrame); if (pFrame->pParent && pFrame->pParent->ip) { // Return to the main loop after a non-recursive interpreter call diff --git a/src/coreclr/vm/interpexec.h b/src/coreclr/vm/interpexec.h index f153006df658c5..7306f9796c137c 100644 --- a/src/coreclr/vm/interpexec.h +++ b/src/coreclr/vm/interpexec.h @@ -5,8 +5,10 @@ #define _INTERPEXEC_H_ #include "../interpreter/interpretershared.h" +#include "interpframeallocator.h" #define INTERP_STACK_SIZE 1024*1024 +#define INTERP_STACK_FRAGMENT_SIZE 4096 struct StackVal { @@ -52,9 +54,13 @@ struct InterpThreadContext // stack pointer. It is needed when re-entering interp, to know from which address we can start using // stack, and also needed for the GC to be able to scan the stack. int8_t *pStackPointer; + + FrameDataAllocator frameDataAllocator; + + InterpThreadContext(); + ~InterpThreadContext(); }; -InterpThreadContext* InterpGetThreadContext(); void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext); #endif diff --git a/src/coreclr/vm/interpframeallocator.cpp b/src/coreclr/vm/interpframeallocator.cpp new file mode 100644 index 00000000000000..cc9513f9b09b78 --- /dev/null +++ b/src/coreclr/vm/interpframeallocator.cpp @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifdef FEATURE_INTERPRETER + +#include "interpexec.h" +#include "interpframeallocator.h" + +FrameDataAllocator::FrameDataFragment::FrameDataFragment(size_t size) +{ + // Amortize allocation cost by allocating a larger chunk of memory + if (size < INTERP_STACK_FRAGMENT_SIZE) + { + size = INTERP_STACK_FRAGMENT_SIZE; + } + + pFrameStart = (uint8_t*)malloc(size); + if (pFrameStart != nullptr) + { + pFrameEnd = pFrameStart + size; + pFramePos = pFrameStart; + } + pNext = nullptr; +} + +FrameDataAllocator::FrameDataFragment::~FrameDataFragment() +{ + free(pFrameStart); +} + +FrameDataAllocator::FrameDataAllocator() +{ + pFirst = pCurrent = nullptr; + pInfos = nullptr; + infosLen = 0; + infosCapacity = 0; +} + +FrameDataAllocator::~FrameDataAllocator() +{ + if (pFirst != nullptr) + { + assert(pCurrent == pFirst && pCurrent->pFramePos == pCurrent->pFrameStart); + FreeFragments(pFirst); + free(pInfos); + } +} + +void FrameDataAllocator::FreeFragments(FrameDataFragment *pFrag) +{ + while (pFrag) + { + FrameDataFragment *pNext = pFrag->pNext; + delete pFrag; + pFrag = pNext; + } +} + +bool FrameDataAllocator::PushInfo(InterpMethodContextFrame *pFrame) +{ + if (infosLen == infosCapacity) + { + size_t newCapacity = infosCapacity == 0 ? 8 : infosCapacity * 2; + if (newCapacity > SIZE_MAX / sizeof(FrameDataInfo)) + { + return false; + } + FrameDataInfo* newInfos = (FrameDataInfo*)realloc(pInfos, newCapacity * sizeof(FrameDataInfo)); + if (newInfos == nullptr) + { + return false; + } + pInfos = newInfos; + infosCapacity = newCapacity; + } + + FrameDataInfo *pInfo = &pInfos[infosLen++]; + pInfo->pFrame = pFrame; + pInfo->pFrag = pCurrent; + pInfo->pFramePos = pCurrent->pFramePos; + return true; +} + +void *FrameDataAllocator::Alloc(InterpMethodContextFrame *pFrame, size_t size) +{ + size = ALIGN_UP(size, sizeof(void*)); + + if (pFirst == nullptr) + { + pFirst = new (nothrow) FrameDataFragment(size); + if (pFirst == nullptr || pFirst->pFrameStart == nullptr) + { + return nullptr; + } + pCurrent = pFirst; + } + + if (!infosLen || pInfos[infosLen - 1].pFrame != pFrame) + { + if (!PushInfo(pFrame)) + { + return nullptr; + } + } + + uint8_t *pFramePos = pCurrent->pFramePos; + + if (pFramePos + size > pCurrent->pFrameEnd) + { + // Move to the next fragment or create a new one if necessary + if (pCurrent->pNext && ((pCurrent->pNext->pFrameStart + size) <= pCurrent->pNext->pFrameEnd)) + { + pCurrent = pCurrent->pNext; + pFramePos = pCurrent->pFramePos = pCurrent->pFrameStart; + } + else + { + FreeFragments(pCurrent->pNext); + pCurrent->pNext = nullptr; + + FrameDataFragment *pNewFrag = new (nothrow) FrameDataFragment(size); + if (pNewFrag == nullptr || pNewFrag->pFrameStart == nullptr) + { + return nullptr; + } + + pCurrent->pNext = pNewFrag; + pCurrent = pNewFrag; + pFramePos = pNewFrag->pFramePos; + } + } + + void *pMemory = (void*)pFramePos; + pCurrent->pFramePos = (uint8_t*)(pCurrent->pFramePos + size); + return pMemory; +} + +void FrameDataAllocator::PopInfo(InterpMethodContextFrame *pFrame) +{ + if (infosLen > 0 && pInfos[infosLen - 1].pFrame == pFrame) + { + FrameDataInfo *pInfo = &pInfos[--infosLen]; + pCurrent = pInfo->pFrag; + pCurrent->pFramePos = pInfo->pFramePos; + } +} + +#endif // FEATURE_INTERPRETER diff --git a/src/coreclr/vm/interpframeallocator.h b/src/coreclr/vm/interpframeallocator.h new file mode 100644 index 00000000000000..3069dd6c36a5fa --- /dev/null +++ b/src/coreclr/vm/interpframeallocator.h @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#ifndef _INTERPFRAMEALLOCATOR_H_ +#define _INTERPFRAMEALLOCATOR_H_ + +class FrameDataAllocator +{ +private: + struct FrameDataFragment + { + // The start of the fragment + uint8_t *pFrameStart; + // The end of the fragment + uint8_t *pFrameEnd; + // The current position in the fragment + uint8_t *pFramePos; + // The next fragment in the list + FrameDataFragment *pNext; + + FrameDataFragment(size_t size); + ~FrameDataFragment(); + }; + + struct FrameDataInfo + { + // The frame that this data belongs to + InterpMethodContextFrame *pFrame; + // Pointers for restoring the localloc memory: + // pFrag - the current allocation fragment at frame entry + // pos - the fragment pointer at frame entry + // When the frame returns, we use these to roll back any local allocations + FrameDataFragment *pFrag; + uint8_t *pFramePos; + + FrameDataInfo(InterpMethodContextFrame *pFrame, FrameDataFragment *pFrag, uint8_t *pFramePos); + }; + + FrameDataFragment *pFirst; + FrameDataFragment *pCurrent; + FrameDataInfo *pInfos; + size_t infosLen; + size_t infosCapacity; + + bool PushInfo(InterpMethodContextFrame *pFrame); + void FreeFragments(FrameDataFragment *pFrag); +public: + FrameDataAllocator(); + ~FrameDataAllocator(); + + void *Alloc(InterpMethodContextFrame *pFrame, size_t size); + void PopInfo(InterpMethodContextFrame *pFrame); +}; + +#endif diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 50a8ce012b5f60..a0e9883ebb7b04 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -1943,7 +1943,12 @@ extern "C" void STDCALL ExecuteInterpretedMethod(TransitionBlock* pTransitionBlo { // Argument registers are in the TransitionBlock // The stack arguments are right after the pTransitionBlock - InterpThreadContext *threadContext = InterpGetThreadContext(); + Thread *pThread = GetThread(); + InterpThreadContext *threadContext = pThread->GetInterpThreadContext(); + if (threadContext == nullptr || threadContext->pStackStart == nullptr) + { + COMPlusThrow(kOutOfMemoryException); + } int8_t *sp = threadContext->pStackPointer; // This construct ensures that the InterpreterFrame is always stored at a higher address than the diff --git a/src/coreclr/vm/threads.cpp b/src/coreclr/vm/threads.cpp index 8706d4ccf76dd2..62e86fd7cd900d 100644 --- a/src/coreclr/vm/threads.cpp +++ b/src/coreclr/vm/threads.cpp @@ -56,6 +56,10 @@ #include "exinfo.h" #endif +#ifdef FEATURE_INTERPRETER +#include "interpexec.h" +#endif // FEATURE_INTERPRETER + static const PortableTailCallFrame g_sentinelTailCallFrame = { NULL, NULL }; TailCallTls::TailCallTls() @@ -1549,6 +1553,10 @@ Thread::Thread() #ifdef _DEBUG memset(dangerousObjRefs, 0, sizeof(dangerousObjRefs)); #endif // _DEBUG + +#ifdef FEATURE_INTERPRETER + m_pInterpThreadContext = nullptr; +#endif // FEATURE_INTERPRETER } //-------------------------------------------------------------------- @@ -2802,6 +2810,13 @@ void Thread::OnThreadTerminate(BOOL holdingLock) DWORD CurrentThreadID = pCurrentThread?pCurrentThread->GetThreadId():0; DWORD ThisThreadID = GetThreadId(); +#ifdef FEATURE_INTERPRETER + if (m_pInterpThreadContext != nullptr) + { + delete m_pInterpThreadContext; + } +#endif // FEATURE_INTERPRETER + #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT // If the currently running thread is the thread that died and it is an STA thread, then we // need to release all the RCW's in the current context. However, we cannot do this if we @@ -7794,6 +7809,20 @@ void ClrRestoreNonvolatileContext(PCONTEXT ContextRecord, size_t targetSSP) #endif } +#ifdef FEATURE_INTERPRETER +InterpThreadContext* Thread::GetInterpThreadContext() +{ + WRAPPER_NO_CONTRACT; + + if (m_pInterpThreadContext == nullptr) + { + m_pInterpThreadContext = new (nothrow) InterpThreadContext(); + } + + return m_pInterpThreadContext; +} +#endif // FEATURE_INTERPRETER + #endif // #ifndef DACCESS_COMPILE #ifdef DACCESS_COMPILE diff --git a/src/coreclr/vm/threads.h b/src/coreclr/vm/threads.h index a1ca4943a6be8c..93b77963aa0cd4 100644 --- a/src/coreclr/vm/threads.h +++ b/src/coreclr/vm/threads.h @@ -142,6 +142,7 @@ class FaultingExceptionFrame; enum BinderMethodID : int; class PrepareCodeConfig; class NativeCodeVersion; +struct InterpThreadContext; typedef void(*ADCallBackFcnType)(LPVOID); @@ -3979,6 +3980,14 @@ friend class DebuggerController; bool m_hasPendingActivation; friend struct ::cdac_data; + +#ifdef FEATURE_INTERPRETER +private: + InterpThreadContext *m_pInterpThreadContext; + +public: + InterpThreadContext* GetInterpThreadContext(); +#endif // FEATURE_INTERPRETER }; template<> diff --git a/src/tests/JIT/interpreter/Interpreter.cs b/src/tests/JIT/interpreter/Interpreter.cs index 3d61a3a782c383..daec2582cc8fe5 100644 --- a/src/tests/JIT/interpreter/Interpreter.cs +++ b/src/tests/JIT/interpreter/Interpreter.cs @@ -99,6 +99,10 @@ public static void RunInterpreterTests() // Environment.FailFast(null); if (!TestFloat()) Environment.FailFast(null); + + if (!TestLocalloc()) + Environment.FailFast(null); + // if (!TestVirtual()) // Environment.FailFast(null); @@ -243,6 +247,85 @@ public static bool TestFloat() return true; } + public static bool TestLocalloc() + { + // Default fragment size is 4096 bytes + + // Small tests + if (0 != LocallocIntTests(0)) return false; + if (0 != LocallocIntTests(1)) return false; + if (2 != LocallocIntTests(2)) return false; + + // Smoke tests + if (32 != LocallocByteTests(32)) return false; + if (32 != LocallocIntTests(32)) return false; + if (32 != LocallocLongTests(32)) return false; + + // Single frame tests + if (1024 != LocallocIntTests(1024)) return false; + if (512 != LocallocLongTests(512)) return false; + + // New fragment tests + if (1025 != LocallocIntTests(1025)) return false; + if (513 != LocallocLongTests(513)) return false; + + // Multi-fragment tests + if (10240 != LocallocIntTests(10240)) return false; + if (5120 != LocallocLongTests(5120)) return false; + + // Consecutive allocations tests + if ((256 + 512) != LocallocConsecutiveTests(256, 512)) return false; + + // Nested frames tests + if (1024 != LocallocNestedTests(256, 256, 256, 256)) return false; + if (2560 != LocallocNestedTests(1024, 256, 256, 1024)) return false; + + // Reuse fragment tests + if (3072 != LocallocNestedTests(1024, 512, 512, 1024)) return false; + + return true; + } + + public static unsafe int LocallocIntTests(int n) + { + int* a = stackalloc int[n]; + for (int i = 0; i < n; i++) a[i] = i; + return n < 2 ? 0 : a[0] + a[1] + a[n - 1]; + } + + public static unsafe long LocallocLongTests(int n) + { + long* a = stackalloc long[n]; + for (int i = 0; i < n; i++) a[i] = i; + return n < 2 ? 0 : a[0] + a[1] + a[n - 1]; + } + + public static unsafe int LocallocByteTests(int n) + { + byte* a = stackalloc byte[n]; + for (int i = 0; i < n; i++) a[i] = (byte)(i); + return n < 2 ? 0 : a[0] + a[1] + a[n - 1]; + } + + public static unsafe int LocallocConsecutiveTests(int n, int m) + { + int* a = stackalloc int[n]; + int* b = stackalloc int[m]; + for (int i = 0; i < n; i++) a[i] = i; + for (int i = 0; i < m; i++) b[i] = i; + return a[0] + a[1] + a[n - 1] + b[0] + b[1] + b[m - 1]; + } + + public static unsafe int LocallocNestedTests(int n, int m, int p, int k) + { + int* a1 = stackalloc int[n]; + for (int i = 0; i < n; i++) a1[i] = i; + int inner = LocallocConsecutiveTests(m, p); + int* a2 = stackalloc int[k]; + for (int i = 0; i < k; i++) a2[i] = i; + return a1[0] + a1[1] + a1[n - 1] + inner + a2[0] + a2[1] + a2[k - 1]; + } + public static bool TestVirtual() { BaseClass bc = new DerivedClass();