Skip to content

[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

Merged
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1fb49a3
Implement localloc in the interpreter
kotlarmilos Apr 15, 2025
e5fb653
Add comment
kotlarmilos Apr 15, 2025
6b81d9b
Implement FrameDataAllocator for dynamic stack allocations
kotlarmilos Apr 21, 2025
43f4481
Merge branch 'main' of github.com:kotlarmilos/runtime into feature/co…
kotlarmilos Apr 21, 2025
f660365
Fix merge conflicts
kotlarmilos Apr 21, 2025
e59cace
Initialize local variables to zero if the flag is set
kotlarmilos Apr 22, 2025
7c649a8
Initialize local variables based on the CORINFO_OPT_INIT_LOCALS. Move…
kotlarmilos Apr 22, 2025
1d1e226
Implement FrameDataFragment destructor
kotlarmilos Apr 22, 2025
cb0b212
Handle memory allocation failure in frame data allocation
kotlarmilos Apr 22, 2025
581e856
Fix typo
kotlarmilos Apr 22, 2025
e72e0e3
Add preprocessor directive FEATURE_INTERPRETER
kotlarmilos Apr 22, 2025
50e94f6
Fix windows build
kotlarmilos Apr 22, 2025
dfb170b
If size is 0 allocates a zero-length item and returns a valid pointer…
kotlarmilos Apr 22, 2025
c3bc007
Update frame allocator to use InterpMethodContextFrame
kotlarmilos Apr 23, 2025
c91b99d
Throw OutOfMemory exception if alloc fails
kotlarmilos Apr 23, 2025
2bfe64f
Revert assert changes
kotlarmilos Apr 23, 2025
c08ab74
Update FrameDataAllocator to return nullptr if alloc fails
kotlarmilos Apr 24, 2025
7f42176
Check if infosLen is >0
kotlarmilos Apr 24, 2025
df20036
Use consistent naming for frame pointers
kotlarmilos Apr 24, 2025
8dc3223
Test __SIZEOF_POINTER__ for pointer size checks
kotlarmilos Apr 24, 2025
cd3d5b7
Replace __SIZEOF_POINTER__ with TARGET_64BIT
kotlarmilos Apr 24, 2025
bbb4345
Encapsulate FrameDataAllocator structs into class and make them private
kotlarmilos Apr 24, 2025
4b8c03c
Implement destructor for InterpThreadContext to free FrameDataAllocator
kotlarmilos Apr 25, 2025
db6355f
Move InterpThreadContext instance to the CoreCLR Thread and implement…
kotlarmilos Apr 25, 2025
0e97a9c
Move InterpThreadContext destructor call to OnThreadTerminate
kotlarmilos Apr 26, 2025
980eb78
Refactor Alloc to accept size by value instead of pointer
kotlarmilos Apr 26, 2025
d203e02
Merge branch 'main' into feature/coreclr-interp-opcode-localloc
kotlarmilos Apr 27, 2025
902e4d0
Fix typo
kotlarmilos Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 3 additions & 1 deletion src/coreclr/interpreter/interpretershared.h
Original file line number Diff line number Diff line change
@@ -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;
}
};

7 changes: 7 additions & 0 deletions src/coreclr/interpreter/intops.def
Original file line number Diff line number Diff line change
@@ -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)
2 changes: 2 additions & 0 deletions src/coreclr/vm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
98 changes: 80 additions & 18 deletions src/coreclr/vm/interpexec.cpp
Original file line number Diff line number Diff line change
@@ -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<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 +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
8 changes: 7 additions & 1 deletion src/coreclr/vm/interpexec.h
Original file line number Diff line number Diff line change
@@ -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
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.