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
1 change: 1 addition & 0 deletions src/coreclr/jit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ set( JIT_SOURCES
optimizer.cpp
patchpoint.cpp
phase.cpp
promotefrozenstaticalloc.cpp
promotion.cpp
promotiondecomposition.cpp
promotionliveness.cpp
Expand Down
16 changes: 16 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4819,6 +4819,14 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
DoPhase(this, PHASE_OPTIMIZE_INDEX_CHECKS, &Compiler::rangeCheckPhase);
}

// For class constructors, promote stsfld allocations to the frozen-heap
// allocator helpers. Requires SSA + a settled IR.
if (((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FROZEN_ALLOC_ALLOWED))
{
DoPhase(this, PHASE_PROMOTE_CCTOR_ALLOCS, &Compiler::fgPromoteCctorAllocsToFrozenHeap);
}

if (doOptimizeIVs)
{
// Simplify and optimize induction variables used in natural loops
Expand Down Expand Up @@ -6932,6 +6940,14 @@ int Compiler::compCompileHelper(CORINFO_MODULE_HANDLE classPtr,
{
reason = "loop";
}
else if (((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FROZEN_ALLOC_ALLOWED) &&
(JitConfig.JitOptimizeCctors() != 0))
{
// Cctors run at most once, and we want SSA / VN available so the frozen-heap
// promotion phase can analyze the stsfld -> alloc patterns.
reason = "cctor frozen-heap promotion";
}

if (compHasBackwardJump && (reason == nullptr) && (JitConfig.TC_OnStackReplacement() > 0))
{
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -2551,6 +2551,8 @@ enum class FieldKindForVN

typedef JitHashTable<CORINFO_FIELD_HANDLE, JitPtrKeyFuncs<struct CORINFO_FIELD_STRUCT_>, FieldKindForVN> FieldHandleSet;

typedef JitHashTable<CORINFO_FIELD_HANDLE, JitPtrKeyFuncs<struct CORINFO_FIELD_STRUCT_>, bool> CctorFinalStaticFieldSet;

typedef JitHashTable<CORINFO_CLASS_HANDLE, JitPtrKeyFuncs<struct CORINFO_CLASS_STRUCT_>, bool> ClassHandleSet;

// Represents a distillation of the useful side effects that occur inside a loop.
Expand Down Expand Up @@ -6267,6 +6269,8 @@ class Compiler
bool fgVNBasedIntrinsicExpansionForCall(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call);
bool fgVNBasedIntrinsicExpansionForCall_ReadUtf8(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call);

PhaseStatus fgPromoteCctorAllocsToFrozenHeap();

PhaseStatus fgLateCastExpansion();
bool fgLateCastExpansionForCall(BasicBlock** pBlock, Statement* stmt, GenTreeCall* call);

Expand Down Expand Up @@ -7814,6 +7818,10 @@ class Compiler

unsigned optMethodFlags = 0;

// Static-readonly fields written from this method's stsfld; used by the
// cctor frozen-heap promotion phase. nullptr unless populated.
CctorFinalStaticFieldSet* m_cctorFinalStaticFields = nullptr;

bool doesMethodHaveNoReturnCalls()
{
return optNoReturnCallCount > 0;
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/compphases.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ CompPhaseNameMacro(PHASE_EARLY_PROP, "Early Value Propagation",
CompPhaseNameMacro(PHASE_OPTIMIZE_INDUCTION_VARIABLES, "Optimize Induction Variables", false, -1, false)
CompPhaseNameMacro(PHASE_VALUE_NUMBER, "Do value numbering", false, -1, false)
CompPhaseNameMacro(PHASE_OPTIMIZE_INDEX_CHECKS, "Optimize index checks", false, -1, false)
CompPhaseNameMacro(PHASE_PROMOTE_CCTOR_ALLOCS, "Promote cctor allocations to frozen heap", false, -1, false)
CompPhaseNameMacro(PHASE_OPTIMIZE_VALNUM_CSES, "Optimize Valnum CSEs", false, -1, false)
CompPhaseNameMacro(PHASE_VN_COPY_PROP, "VN based copy prop", false, -1, false)
CompPhaseNameMacro(PHASE_VN_BASED_INTRINSIC_EXPAND, "VN based intrinsic expansion", false, -1, false)
Expand Down
73 changes: 25 additions & 48 deletions src/coreclr/jit/importer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9688,6 +9688,29 @@ void Compiler::impImportBlockCode(BasicBlock* block)
BADCODE("static access on an instance field");
}

// Remember static-readonly fields written from a cctor; the late
// `fgPromoteCctorAllocsToFrozenHeap` phase uses this set to identify
// candidates without needing to re-resolve the field token.
//
// Restrict to fields whose owning class is the cctor's class. Verifiable
// IL forbids cross-class stsfld of initonly fields, but unverifiable IL
// can do it -- promoting such allocations could leave a frozen object
// orphaned if some other writer overwrites the field later.
if (isStoreStatic && ((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FROZEN_ALLOC_ALLOWED) &&
((fieldInfo.fieldFlags & (CORINFO_FLG_FIELD_STATIC | CORINFO_FLG_FIELD_FINAL)) ==
(CORINFO_FLG_FIELD_STATIC | CORINFO_FLG_FIELD_FINAL)) &&
!eeIsSharedInst(info.compClassHnd) &&
(info.compCompHnd->getFieldClass(resolvedToken.hField) == info.compClassHnd))
{
if (m_cctorFinalStaticFields == nullptr)
{
m_cctorFinalStaticFields =
new (this, CMK_Generic) CctorFinalStaticFieldSet(getAllocator(CMK_Generic));
}
m_cctorFinalStaticFields->Set(resolvedToken.hField, true, CctorFinalStaticFieldSet::Overwrite);
}

// We are using stfld on a static field.
// We allow it, but need to eval any side-effects for obj
if ((fieldInfo.fieldFlags & CORINFO_FLG_FIELD_STATIC) && obj != nullptr)
Expand Down Expand Up @@ -9910,54 +9933,8 @@ void Compiler::impImportBlockCode(BasicBlock* block)
// So if we have an int, explicitly extend it to be a native int.
op2 = impImplicitIorI4Cast(op2, TYP_I_IMPL);

bool isFrozenAllocator = false;
// If we're jitting a static constructor and detect the following code pattern:
//
// newarr
// stsfld
// ret
//
// we emit a "frozen" allocator for newarr to, hopefully, allocate that array on a frozen segment.
// This is a very simple and conservative implementation targeting Array.Empty<T>()'s shape
// Ideally, we want to be able to use frozen allocators more broadly, but such an analysis is
// not trivial.
//
if (((info.compFlags & FLG_CCTOR) == FLG_CCTOR) &&
// Does VM allow us to use frozen allocators?
opts.jitFlags->IsSet(JitFlags::JIT_FLAG_FROZEN_ALLOC_ALLOWED))
{
// Check next two opcodes (have to be STSFLD and RET)
const BYTE* nextOpcode1 = codeAddr + sizeof(mdToken);
const BYTE* nextOpcode2 = nextOpcode1 + sizeof(mdToken) + 1;
if ((nextOpcode2 < codeEndp) && (getU1LittleEndian(nextOpcode1) == CEE_STSFLD))
{
if (getU1LittleEndian(nextOpcode2) == CEE_RET)
{
// Check that the field is "static readonly", we don't want to waste memory
// for potentially mutable fields.
CORINFO_RESOLVED_TOKEN fldToken;
impResolveToken(nextOpcode1 + 1, &fldToken, CORINFO_TOKENKIND_Field);
CORINFO_FIELD_INFO fi;
eeGetFieldInfo(&fldToken, CORINFO_ACCESS_SET, &fi);
unsigned flagsToCheck = CORINFO_FLG_FIELD_STATIC | CORINFO_FLG_FIELD_FINAL;
if (((fi.fieldFlags & flagsToCheck) == flagsToCheck) && !eeIsSharedInst(info.compClassHnd))
{
#ifdef FEATURE_READYTORUN
if (IsAot())
{
// Need to restore array classes before creating array objects on the heap
op1 = impTokenToHandle(&resolvedToken, nullptr, true /*mustRestoreHandle*/);
}
#endif
op1 = gtNewHelperCallNode(CORINFO_HELP_NEWARR_1_MAYBEFROZEN, TYP_REF, op1, op2);
isFrozenAllocator = true;
}
}
}
}

#ifdef FEATURE_READYTORUN
if (IsAot() && !isFrozenAllocator)
if (IsAot())
{
helper = CORINFO_HELP_READYTORUN_NEWARR_1;
op1 = impReadyToRunHelperToTree(&resolvedToken, helper, TYP_REF, op2);
Expand All @@ -9980,7 +9957,7 @@ void Compiler::impImportBlockCode(BasicBlock* block)
}
}

if (!usingReadyToRunHelper && !isFrozenAllocator)
if (!usingReadyToRunHelper)
#endif
{
/* Create a call to 'new' */
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/jitconfigvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,12 @@ RELEASE_CONFIG_INTEGER(JitObjectStackAllocationTrackFields, "JitObjectStackAlloc
CONFIG_STRING(JitObjectStackAllocationTrackFieldsRange, "JitObjectStackAllocationTrackFieldsRange")
CONFIG_INTEGER(JitObjectStackAllocationDumpConnGraph, "JitObjectStackAllocationDumpConnGraph", 0)

// Force class constructors (.cctor) to be JITed at the FullOpts optimization level so that
// the frozen-heap-promotion phase (which relies on SSA / VN) can run against them. Has no
// effect when the VM has not set CORJIT_FLAG_FROZEN_ALLOC_ALLOWED (e.g. collectible loader
// contexts).
RELEASE_CONFIG_INTEGER(JitOptimizeCctors, "JitOptimizeCctors", 1) // enabled for CI testing
Comment on lines +709 to +713

RELEASE_CONFIG_INTEGER(JitEECallTimingInfo, "JitEECallTimingInfo", 0)

CONFIG_INTEGER(JitEnableFinallyCloning, "JitEnableFinallyCloning", 1)
Expand Down
17 changes: 15 additions & 2 deletions src/coreclr/jit/objectalloc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1634,13 +1634,26 @@ GenTree* ObjectAllocator::MorphAllocObjNodeIntoHelperCall(GenTreeAllocObj* alloc
}
#endif

const bool morphArgs = false;
GenTree* helperCall = m_compiler->fgMorphIntoHelperCall(allocObj, allocObj->gtNewHelper, morphArgs, arg);
const bool morphArgs = false;
CORINFO_CLASS_HANDLE allocObjClsHnd = allocObj->gtAllocObjClsHnd;
GenTree* helperCall =
m_compiler->fgMorphIntoHelperCall(allocObj, allocObj->gtNewHelper, morphArgs, arg);
if (helperHasSideEffects)
{
helperCall->AsCall()->gtCallMoreFlags |= GTF_CALL_M_ALLOC_SIDE_EFFECTS;
}

#ifdef FEATURE_READYTORUN
// For READYTORUN_NEW the type handle isn't a user arg (it lives in the R2R
// indirection cell), so preserve it on the call so later phases (e.g. cctor
// frozen-heap promotion) can recover it. For other helpers the type handle
// is in arg 0 and can be read from there directly.
if (helper == CORINFO_HELP_READYTORUN_NEW)
{
helperCall->AsCall()->compileTimeHelperArgumentHandle = (CORINFO_GENERIC_HANDLE)allocObjClsHnd;
}
#endif

#ifdef FEATURE_READYTORUN
if (entryPoint.addr != nullptr)
{
Expand Down
Loading
Loading