Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 14 additions & 1 deletion src/coreclr/interpreter/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1865,6 +1865,13 @@ int32_t InterpCompiler::GetInterpTypeStackSize(CORINFO_CLASS_HANDLE clsHnd, Inte
// All vars are stored at 8 byte aligned offsets
if (align < INTERP_STACK_SLOT_SIZE)
align = INTERP_STACK_SLOT_SIZE;

// We do not align beyond the stack alignment
// (This is relevant for structs with very high alignment requirements,
// where we align within struct layout, but the structs are not actually
// aligned on the stack)
if (align > INTERP_STACK_ALIGNMENT)
align = INTERP_STACK_ALIGNMENT;
}
else
{
Expand Down Expand Up @@ -3487,7 +3494,7 @@ void InterpCompiler::EmitPushUnboxAny(const CORINFO_GENERICHANDLE_RESULT& arg1,
PushStackType(resultStackType, clsHndStack);
int resultVar = m_pStackPointer[-1].var;

PushStackType(StackTypeI, NULL);
PushStackType(StackTypeByRef, NULL);
int32_t intermediateVar = m_pStackPointer[-1].var;
m_pStackPointer--;

Expand Down Expand Up @@ -6375,6 +6382,12 @@ void InterpCompiler::GenerateCode(CORINFO_METHOD_INFO* methodInfo)
EmitUnaryArithmeticOp(INTOP_NOT_I4);
m_ip++;
break;
case CEE_JMP:
EmitCall(NULL /*pConstrainedToken*/, false /* readonly*/, true /* tailcall*/, false /*newObj*/, false /*isCalli*/);
pConstrainedToken = NULL;
readonly = false;
tailcall = false;
break;
case CEE_CALLVIRT:
case CEE_CALL:
EmitCall(pConstrainedToken, readonly, tailcall, false /*newObj*/, false /*isCalli*/);
Expand Down
14 changes: 14 additions & 0 deletions src/coreclr/interpreter/compileropt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,20 @@ int32_t InterpCompiler::AllocVarOffset(int var, int32_t *pPos)
offset = *pPos;
size = m_pVars[var].size;

size_t align = INTERP_STACK_SLOT_SIZE;

if (size > INTERP_STACK_SLOT_SIZE)
{
assert(m_pVars[var].interpType == InterpTypeVT);
align = m_compHnd->getClassAlignmentRequirement(m_pVars[var].clsHnd);
if (align < INTERP_STACK_SLOT_SIZE)
align = INTERP_STACK_SLOT_SIZE;
if (align > INTERP_STACK_ALIGNMENT)
align = INTERP_STACK_ALIGNMENT;
}

offset = (int32_t)ALIGN_UP_TO(offset, align);

m_pVars[var].offset = offset;

*pPos = ALIGN_UP_TO(offset + size, INTERP_STACK_SLOT_SIZE);
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/interpreter/eeinterp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ CorJitResult CILInterp::compileMethod(ICorJitInfo* compHnd,
break;
}

// 2: use interpreter for everything except intrinsics. All intrinsics fallback to JIT. Implies DOTNET_ReadyToRun=0.
// 2: use interpreter for everything except intrinsics. All intrinsics fallback to JIT. Implies DOTNET_ReadyToRun=0, DOTNET_MaxVectorTBitWidth=128, DOTNET_PreferredVectorBitWidth=128.
case 2:
doInterpret = !(compHnd->getMethodAttribs(methodInfo->ftn) & CORINFO_FLG_INTRINSIC);
break;

// 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0
// 3: use interpreter for everything, the full interpreter-only mode, no fallbacks to R2R or JIT whatsoever. Implies DOTNET_ReadyToRun=0, DOTNET_EnableHWIntrinsic=0, DOTNET_MaxVectorTBitWidth=128, DOTNET_PreferredVectorBitWidth=128
case 3:
doInterpret = true;
break;
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/amd64/AsmHelpers.asm
Original file line number Diff line number Diff line change
Expand Up @@ -879,6 +879,12 @@ LEAF_ENTRY Store_XMM3, _TEXT
jmp qword ptr [r11]
LEAF_END Store_XMM3, _TEXT

LEAF_ENTRY InjectInterpStackAlign, _TEXT
add r10, 8
add r11, 8
jmp qword ptr [r11]
LEAF_END InjectInterpStackAlign, _TEXT

; Copy arguments from the interpreter stack to the processor stack.
; The CPU stack slots are aligned to pointer size.
LEAF_ENTRY Load_Stack, _TEXT
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/amd64/asmhelpers.S
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,12 @@ LEAF_END Store_XMM7, _TEXT
// Routines for passing value type arguments by reference in general purpose registers RDI, RSI, RDX, RCX, R8, R9
// from the interpreter to native code

LEAF_ENTRY InjectInterpStackAlign, _TEXT
add r10, 8
add r11, 8
jmp qword ptr [r11]
LEAF_END InjectInterpStackAlign, _TEXT

// Copy arguments from the the interpreter stack to the CPU stack.
// The CPU stack slots are aligned to pointer size.
LEAF_ENTRY Load_Stack, _TEXT
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/arm64/asmhelpers.S
Original file line number Diff line number Diff line change
Expand Up @@ -1312,6 +1312,12 @@ ALTERNATE_ENTRY Store_D7
EPILOG_BRANCH_REG x11
LEAF_END Store_D1_D2_D3_D4_D5_D6_D7

LEAF_ENTRY InjectInterpStackAlign
add x9, x9, #8
ldr x11, [x10], #8
EPILOG_BRANCH_REG x11
LEAF_END InjectInterpStackAlign

// Copy arguments from the interpreter stack to the processor stack
// The CPU stack slots are aligned to pointer size.
LEAF_ENTRY Load_Stack
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/vm/arm64/asmhelpers.asm
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,12 @@ RefCopyDone$argReg
EPILOG_BRANCH_REG x11
LEAF_END Store_D1_D2_D3_D4_D5_D6_D7

LEAF_ENTRY InjectInterpStackAlign
add x9, x9, #8
ldr x11, [x10], #8
EPILOG_BRANCH_REG x11
LEAF_END InjectInterpStackAlign

; Routines for passing value type arguments by reference in general purpose registers X0..X7
; from the interpreter to native code
; Copy arguments from the interpreter stack to the processor stack
Expand Down
53 changes: 52 additions & 1 deletion src/coreclr/vm/callstubgenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "callstubgenerator.h"
#include "ecall.h"

extern "C" void InjectInterpStackAlign();
extern "C" void Load_Stack();
extern "C" void Store_Stack();

Expand Down Expand Up @@ -1350,7 +1351,7 @@ CallStubHeader *CallStubGenerator::GenerateCallStubForSig(MetaSig &sig)
{
STANDARD_VM_CONTRACT;

// Allocate space for the routines. The size of the array is conservatively set to twice the number of arguments
// Allocate space for the routines. The size of the array is conservatively set to three times the number of arguments
// plus one slot for the target pointer and reallocated to the real size at the end.
size_t tempStorageSize = ComputeTempStorageSize(sig);
PCODE *pRoutines = (PCODE*)alloca(tempStorageSize);
Expand Down Expand Up @@ -1408,6 +1409,7 @@ void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines)
{

ArgIterator argIt(&sig);
int32_t interpreterStackOffset = 0;

m_r1 = NoRange; // indicates that there is no active range of general purpose registers
m_r2 = 0;
Expand All @@ -1431,6 +1433,7 @@ void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines)
// we need to "inject" it here.
// CLR ABI specifies that unlike the native Windows x64 calling convention, it is passed in the first argument register.
m_r1 = 0;
interpreterStackOffset += INTERP_STACK_SLOT_SIZE;
}

if (argIt.HasParamType())
Expand All @@ -1442,6 +1445,7 @@ void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines)
ArgLocDesc paramArgLocDesc;
argIt.GetParamTypeLoc(&paramArgLocDesc);
ProcessArgument(NULL, paramArgLocDesc, pRoutines);
interpreterStackOffset += INTERP_STACK_SLOT_SIZE;
}

int ofs;
Expand All @@ -1453,6 +1457,53 @@ void CallStubGenerator::ComputeCallStub(MetaSig &sig, PCODE *pRoutines)
ArgLocDesc argLocDesc;
argIt.GetArgLoc(ofs, &argLocDesc);

// Each argument takes at least one slot on the interpreter stack
int interpStackSlotSize = INTERP_STACK_SLOT_SIZE;

// Each entry on the interpreter stack is always aligned to at least 8 bytes, but some arguments are 16 byte aligned
TypeHandle thArgTypeHandle;
if (argIt.GetArgType(&thArgTypeHandle) == ELEMENT_TYPE_VALUETYPE)
{
unsigned align = CEEInfo::getClassAlignmentRequirementStatic(thArgTypeHandle);
if (align < INTERP_STACK_SLOT_SIZE)
{
align = INTERP_STACK_SLOT_SIZE;
}
else if (align > INTERP_STACK_ALIGNMENT)
{
align = INTERP_STACK_ALIGNMENT;
}
assert(align == 8 || align == 16); // At the moment, we can only have an 8 or 16 byte alignment requirement here
while (interpreterStackOffset != ALIGN_UP(interpreterStackOffset, align))
{
if (m_r1 != NoRange)
{
pRoutines[m_routineIndex++] = GetGPRegRangeRoutine(m_r1, m_r2);
m_r1 = NoRange;
}
else if (m_x1 != NoRange)
{
pRoutines[m_routineIndex++] = GetFPRegRangeRoutine(m_x1, m_x2);
m_x1 = NoRange;
}
else if (m_s1 != NoRange)
{
pRoutines[m_routineIndex++] = GetStackRoutine();
pRoutines[m_routineIndex++] = ((int64_t)(m_s2 - m_s1 + 1) << 32) | m_s1;
m_s1 = NoRange;
}

interpreterStackOffset += INTERP_STACK_SLOT_SIZE;
pRoutines[m_routineIndex++] = (PCODE)InjectInterpStackAlign;
#if LOG_COMPUTE_CALL_STUB
printf("Inject stack align argument\n");
#endif
}

interpStackSlotSize = ALIGN_UP(thArgTypeHandle.GetSize(), align);
}
interpreterStackOffset += interpStackSlotSize;

#ifdef UNIX_AMD64_ABI
if (argIt.GetArgLocDescForStructInRegs() != NULL)
{
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/callstubgenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ class CallStubGenerator
int numArgs = sig.NumFixedArgs() + (sig.HasThis() ? 1 : 0);

// The size of the temporary storage is the size of the CallStubHeader plus the size of the routines array.
// The size of the routines array is twice the number of arguments plus one slot for the target method pointer.
return sizeof(CallStubHeader) + ((numArgs + 1) * 2 + 1) * sizeof(PCODE);
// The size of the routines array is three the number of arguments plus one slot for the target method pointer.
return sizeof(CallStubHeader) + ((numArgs + 1) * 3 + 1) * sizeof(PCODE);
}
void ComputeCallStub(MetaSig &sig, PCODE *pRoutines);
};
Expand Down
23 changes: 20 additions & 3 deletions src/coreclr/vm/codeman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,15 @@ void EEJitManager::SetCpuInfo()

// Get the maximum bitwidth of Vector<T>, rounding down to the nearest multiple of 128-bits
uint32_t maxVectorTBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_MaxVectorTBitWidth) / 128) * 128;
bool allowHWIntrinsic = true;

#if defined(FEATURE_INTERPRETER)
if (maxVectorTBitWidth != 128 && CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) >= 2)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be GetConfigValue(CLRConfig::EXTERNAL_InterpMode) == 3?

{
// Disable larger Vector<T> sizes when interpreter is enabled
maxVectorTBitWidth = 128;
}
#endif

#if defined(TARGET_X86) || defined(TARGET_AMD64)
CPUCompileFlags.Set(InstructionSet_VectorT128);
Expand All @@ -1213,7 +1222,7 @@ void EEJitManager::SetCpuInfo()

// x86-64-v2

if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableHWIntrinsic))
if (allowHWIntrinsic && g_pConfig->EnableHWIntrinsic())
{
CPUCompileFlags.Set(InstructionSet_X86Base);
}
Expand Down Expand Up @@ -1324,7 +1333,7 @@ void EEJitManager::SetCpuInfo()
#elif defined(TARGET_ARM64)
CPUCompileFlags.Set(InstructionSet_VectorT128);

if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableHWIntrinsic))
if (allowHWIntrinsic && g_pConfig->EnableHWIntrinsic())
{
CPUCompileFlags.Set(InstructionSet_ArmBase);
CPUCompileFlags.Set(InstructionSet_AdvSimd);
Expand Down Expand Up @@ -1408,7 +1417,7 @@ void EEJitManager::SetCpuInfo()
g_arm64_atomics_present = true;
}
#elif defined(TARGET_RISCV64)
if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableHWIntrinsic))
if (allowHWIntrinsic && g_pConfig->EnableHWIntrinsic())
{
CPUCompileFlags.Set(InstructionSet_RiscV64Base);
}
Expand Down Expand Up @@ -1509,6 +1518,14 @@ void EEJitManager::SetCpuInfo()

uint32_t preferredVectorBitWidth = (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_PreferredVectorBitWidth) / 128) * 128;

#ifdef FEATURE_INTERPRETER
if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) >= 2)
{
// Disable larger Vector<T> sizes when interpreter is enabled, and not compiling S.P.Corelib
preferredVectorBitWidth = 128;
}
#endif

if ((preferredVectorBitWidth == 0) && throttleVector512)
{
preferredVectorBitWidth = 256;
Expand Down
37 changes: 37 additions & 0 deletions src/coreclr/vm/eeconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,8 +454,45 @@ HRESULT EEConfig::sync()

pReadyToRunExcludeList = NULL;

#ifdef FEATURE_INTERPRETER
#ifdef FEATURE_JIT
LPWSTR interpreterConfig;
IfFailThrow(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Interpreter, &interpreterConfig));
if (interpreterConfig == NULL)
{
if ((CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) != 0))
{
enableInterpreter = true;
}
}
else
{
enableInterpreter = true;
}
#else
enableInterpreter = true;
#endif // FEATURE_JIT
#endif // FEATURE_INTERPRETER

enableHWIntrinsic = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_EnableHWIntrinsic);
#ifdef FEATURE_INTERPRETER
if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) >= 3)
{
// R2R mode 3 disables all hw intrinsics
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment mentions 'R2R mode 3' but should refer to 'InterpMode 3' for clarity, as this is about interpreter mode, not ReadyToRun mode.

Suggested change
// R2R mode 3 disables all hw intrinsics
// InterpMode 3 disables all hw intrinsics

Copilot uses AI. Check for mistakes.

enableHWIntrinsic = 0;
}
#endif // FEATURE_INTERPRETER

#if defined(FEATURE_READYTORUN)
fReadyToRun = CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_ReadyToRun);
#if defined(FEATURE_INTERPRETER)
if (fReadyToRun && CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_InterpMode) >= 2)
{
// ReadyToRun and Interpreter modes 2 and 3 are mutually exclusive.
// If both are set, Interpreter wins.
fReadyToRun = false;
}
#endif // defined(FEATURE_INTERPRETER)

if (fReadyToRun)
{
Expand Down
11 changes: 11 additions & 0 deletions src/coreclr/vm/eeconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,11 @@ class EEConfig

bool RuntimeAsync() const { LIMITED_METHOD_CONTRACT; return runtimeAsync; }

#ifdef FEATURE_INTERPRETER
bool EnableInterpreter() const { LIMITED_METHOD_CONTRACT; return enableInterpreter; }
#endif
bool EnableHWIntrinsic() const { LIMITED_METHOD_CONTRACT; return enableHWIntrinsic; }

private: //----------------------------------------------------------------

bool fInited; // have we synced to the registry at least once?
Expand Down Expand Up @@ -612,6 +617,12 @@ class EEConfig
bool fReadyToRun;
#endif

bool enableHWIntrinsic;

#ifdef FEATURE_INTERPRETER
bool enableInterpreter;
#endif

#if defined(FEATURE_ON_STACK_REPLACEMENT)
DWORD dwOSR_HitLimit;
DWORD dwOSR_CounterBump;
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/vm/jithost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ int JitHost::getIntConfigValue(const char* name, int defaultValue)
{
WRAPPER_NO_CONTRACT;

if (!strcmp(name, "EnableHWIntrinsic"))
{
return g_pConfig->EnableHWIntrinsic() ? 1 : 0;
}

StackSString str;
SString(SString::Utf8Literal, name).ConvertToUnicode(str);

Expand Down
8 changes: 1 addition & 7 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13375,15 +13375,9 @@ PCODE UnsafeJitFunction(PrepareCodeConfig* config,

#ifdef FEATURE_INTERPRETER
InterpreterJitManager* interpreterMgr = ExecutionManager::GetInterpreterJitManager();
if (!interpreterMgr->IsInterpreterLoaded())
if (!interpreterMgr->IsInterpreterLoaded() && g_pConfig->EnableInterpreter())
{
LPWSTR interpreterConfig;
IfFailThrow(CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_Interpreter, &interpreterConfig));
if (
#ifdef FEATURE_JIT
// If both JIT and interpret are available, load the interpreter for testing purposes only if the config switch is set
(interpreterConfig != NULL) &&
#endif
!interpreterMgr->LoadInterpreter())
{
EEPOLICY_HANDLE_FATAL_ERROR_WITH_MESSAGE(COR_E_EXECUTIONENGINE, W("Failed to load interpreter"));
Expand Down
2 changes: 1 addition & 1 deletion src/tests/Common/CLRTest.Execute.Bash.targets
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ fi

if [ ! -z "$RunInterpreter" ]; then
# $(InputAssemblyName)
export DOTNET_Interpreter='$(AssemblyName)!*'
export DOTNET_InterpMode=3
fi

echo $LAUNCHER $ExePath %24(printf "'%s' " "${CLRTestExecutionArguments[@]}")
Expand Down
Loading
Loading