-
Notifications
You must be signed in to change notification settings - Fork 5.2k
[clr-interp] Implement CEE_LOCALLOC
and frame data allocator for dynamic stack allocations
#114860
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
1fb49a3
e5fb653
6b81d9b
43f4481
f660365
e59cace
7c649a8
1d1e226
cb0b212
581e856
e72e0e3
50e94f6
dfb170b
c3bc007
c91b99d
2bfe64f
c08ab74
7f42176
df20036
8dc3223
cd3d5b7
bbb4345
4b8c03c
db6355f
0e97a9c
980eb78
d203e02
902e4d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -21,6 +21,7 @@ InterpThreadContext* InterpGetThreadContext() | |||||||||||
// 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; | ||||||||||||
threadContext->pFrameDataAllocator = new FrameDataAllocator(INTERP_STACK_FRAGMENT_SIZE); | ||||||||||||
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
jkotas marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||
|
||||||||||||
t_pThreadContext = threadContext; | ||||||||||||
return threadContext; | ||||||||||||
|
@@ -584,7 +585,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<int32_t>::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<int64_t>::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<uint32_t>::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<uint64_t>::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 +1152,33 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr | |||||||||||
memset(LOCAL_VAR(ip[1], void*), 0, ip[2]); | ||||||||||||
ip += 3; | ||||||||||||
break; | ||||||||||||
case INTOP_LOCALLOC: | ||||||||||||
{ | ||||||||||||
int32_t len = LOCAL_VAR(ip[2], int32_t); | ||||||||||||
void* mem; | ||||||||||||
|
||||||||||||
if (len > 0) | ||||||||||||
{ | ||||||||||||
len = ALIGN_UP(len, INTERP_STACK_ALIGNMENT); | ||||||||||||
mem = pThreadContext->pFrameDataAllocator->Alloc((InterpreterFrame*)pFrame, len); | ||||||||||||
if (!mem) | ||||||||||||
{ | ||||||||||||
// Interpreter-TODO: OutOfMemoryException | ||||||||||||
assert(0); | ||||||||||||
} | ||||||||||||
|
||||||||||||
if (pMethod->initLocals) | ||||||||||||
{ | ||||||||||||
memset(mem, 0, len); | ||||||||||||
} | ||||||||||||
} else | ||||||||||||
{ | ||||||||||||
mem = NULL; | ||||||||||||
|
// Put the size value in targetReg. If it is zero, bail out by returning null in targetReg. | |
genConsumeRegAndCopy(size, targetReg); | |
endLabel = genCreateTempLabel(); | |
GetEmitter()->emitIns_R_R(INS_test, easz, targetReg, targetReg); | |
inst_JMP(EJ_je, endLabel); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for pointing it out. Looks like there is a mismatch between alloca in C++ and the IL instruction.
I think it would be best to match what RyuJIT does and return zero as well like you have done originally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, reverted.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how can we delete the allocator here. If I understand it correctly, we allocate it in the InterpGetThreadContext
, but only if the t_pThreadContext
was not set yet. And we delete it here when we exit from a block of interpreted frames that can happen many times. E.g. with the EH, a catch funclet exits here and then we resume in the parent function after the catch. That means that after resuming from catch, the pFrameDataAllocator
would be invalid.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That workaround was only temporary, but you are right. I’ve now implemented a destructor for InterpThreadContext. Please let me know if there is a deterministic place to destroy the thread context.
If this approach is reasonable, I would move the initialization into the constructor instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer moving the InterpThreadContext
instance to the coreclr Thread
class instead and performing the cleanup in the Thread::~Thread
or Thread::OnThreadTerminate
. Having a thread local object with a destructor is a recipe for possible problems due to the fact that the order of their destruction is arbitrary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented the InterpThreadContext constructor and destructor, and moved its instance into the CoreCLR Thread class. It is now created lazily on first pThread->GetInterpThreadContext()
in prestub.cpp. This ensures that all allocated memory is properly released when the Thread destructor runs.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
#include "interpexec.h" | ||
#include "interpframeallocator.h" | ||
|
||
FrameDataFragment::FrameDataFragment(size_t size) | ||
{ | ||
if (size < INTERP_STACK_FRAGMENT_SIZE) | ||
janvorli marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
size = INTERP_STACK_FRAGMENT_SIZE; | ||
} | ||
|
||
start = (uint8_t*)malloc(size); | ||
assert(start && "Failed to allocate FrameDataFragment"); | ||
kotlarmilos marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
end = start + size; | ||
pos = start; | ||
pNext = nullptr; | ||
} | ||
|
||
FrameDataFragment::~FrameDataFragment() | ||
{ | ||
free(start); | ||
} | ||
|
||
FrameDataAllocator::FrameDataAllocator(size_t size) | ||
{ | ||
pFirst = new FrameDataFragment(size); | ||
assert(pFirst && "Failed to allocate initial fragment"); | ||
pCurrent = pFirst; | ||
pInfos = nullptr; | ||
infosLen = 0; | ||
infosCapacity = 0; | ||
} | ||
|
||
FrameDataAllocator::~FrameDataAllocator() | ||
{ | ||
assert(pCurrent == pFirst && pCurrent->pos == pCurrent->start); | ||
FreeFragments(pFirst); | ||
free(pInfos); | ||
} | ||
|
||
void FrameDataAllocator::FreeFragments(FrameDataFragment *pFrag) | ||
{ | ||
while (pFrag) | ||
{ | ||
FrameDataFragment *pNext = pFrag->pNext; | ||
delete pFrag; | ||
pFrag = pNext; | ||
} | ||
} | ||
|
||
void FrameDataAllocator::PushInfo(InterpreterFrame *pFrame) | ||
{ | ||
if (infosLen == infosCapacity) | ||
{ | ||
size_t newCapacity = infosCapacity == 0 ? 8 : infosCapacity * 2; | ||
pInfos = (FrameDataInfo*)realloc(pInfos, newCapacity * sizeof(FrameDataInfo)); | ||
assert(pInfos && "Failed to reallocate frame info"); | ||
infosCapacity = newCapacity; | ||
} | ||
|
||
FrameDataInfo *pInfo = &pInfos[infosLen++]; | ||
pInfo->pFrame = pFrame; | ||
pInfo->pFrag = pCurrent; | ||
pInfo->pos = pCurrent->pos; | ||
} | ||
|
||
void *FrameDataAllocator::Alloc(InterpreterFrame *pFrame, size_t size) | ||
{ | ||
kotlarmilos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!infosLen || (infosLen > 0 && pInfos[infosLen - 1].pFrame != pFrame)) | ||
{ | ||
PushInfo(pFrame); | ||
} | ||
|
||
uint8_t *pos = pCurrent->pos; | ||
|
||
if (pos + size > pCurrent->end) | ||
{ | ||
if (pCurrent->pNext && ((pCurrent->pNext->start + size) <= pCurrent->pNext->end)) | ||
{ | ||
pCurrent = pCurrent->pNext; | ||
pos = pCurrent->pos = pCurrent->start; | ||
} | ||
else | ||
{ | ||
FreeFragments(pCurrent->pNext); | ||
FrameDataFragment *pNewFrag = new FrameDataFragment(size); | ||
assert(pNewFrag && "Failed to allocate new fragment"); | ||
pCurrent->pNext = pNewFrag; | ||
pCurrent = pNewFrag; | ||
|
||
pos = pNewFrag->pos; | ||
} | ||
} | ||
|
||
void *result = (void*)pos; | ||
pCurrent->pos = (uint8_t*)(pos + size); | ||
return result; | ||
} | ||
|
||
void FrameDataAllocator::PopInfo(InterpreterFrame *pFrame) | ||
{ | ||
int top = infosLen - 1; | ||
if (top >= 0 && pInfos[top].pFrame == pFrame) | ||
{ | ||
FrameDataInfo *pInfo = &pInfos[--infosLen]; | ||
pCurrent = pInfo->pFrag; | ||
pCurrent->pos = pInfo->pos; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.