Skip to content
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

JIT: Generalize parameter register to local mapping in the backend #110795

Merged
merged 9 commits into from
Dec 20, 2024
Merged
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
25 changes: 17 additions & 8 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,15 +465,24 @@ void ABIPassingInformation::Dump() const
}

const ABIPassingSegment& seg = Segment(i);
seg.Dump();
printf("\n");
}
}

if (seg.IsPassedInRegister())
{
printf("[%02u..%02u) reg %s\n", seg.Offset, seg.Offset + seg.Size, getRegName(seg.GetRegister()));
}
else
{
printf("[%02u..%02u) stack @ +%02u\n", seg.Offset, seg.Offset + seg.Size, seg.GetStackOffset());
}
//-----------------------------------------------------------------------------
// Dump:
// Dump the ABIPassingSegment to stdout.
//
void ABIPassingSegment::Dump() const
{
if (IsPassedInRegister())
{
printf("[%02u..%02u) reg %s", Offset, Offset + Size, getRegName(GetRegister()));
}
else
{
printf("[%02u..%02u) stack @ +%02u", Offset, Offset + Size, GetStackOffset());
}
}
#endif
Expand Down
4 changes: 4 additions & 0 deletions src/coreclr/jit/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class ABIPassingSegment

static ABIPassingSegment InRegister(regNumber reg, unsigned offset, unsigned size);
static ABIPassingSegment OnStack(unsigned stackOffset, unsigned offset, unsigned size);

#ifdef DEBUG
void Dump() const;
#endif
};

class ABIPassingSegmentIterator
Expand Down
7 changes: 4 additions & 3 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,9 +260,10 @@ class CodeGen final : public CodeGenInterface
regMaskTP genGetParameterHomingTempRegisterCandidates();

var_types genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& seg);
void genSpillOrAddRegisterParam(unsigned lclNum, class RegGraph* graph);
void genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, class RegGraph* graph);
void genEnregisterIncomingStackArgs();
void genSpillOrAddRegisterParam(
unsigned lclNum, unsigned offset, unsigned paramLclNum, const ABIPassingSegment& seg, class RegGraph* graph);
void genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, class RegGraph* graph);
void genEnregisterIncomingStackArgs();
#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
void genEnregisterOSRArgsAndLocals(regNumber initReg, bool* pInitRegZeroed);
#else
Expand Down
146 changes: 72 additions & 74 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3239,83 +3239,70 @@ var_types CodeGen::genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& se
// to stack immediately, or by adding it to the register graph.
//
// Parameters:
// lclNum - Parameter local (or field of it)
// graph - The register graph to add to
//
void CodeGen::genSpillOrAddRegisterParam(unsigned lclNum, RegGraph* graph)
// lclNum - Target local
// offset - Offset into the target local
// paramLclNum - Local that is the actual parameter that has the incoming register
// segment - Register segment to either spill or put in the register graph
// graph - The register graph to add to
//
void CodeGen::genSpillOrAddRegisterParam(
unsigned lclNum, unsigned offset, unsigned paramLclNum, const ABIPassingSegment& segment, RegGraph* graph)
{
regMaskTP paramRegs = intRegState.rsCalleeRegArgMaskLiveIn | floatRegState.rsCalleeRegArgMaskLiveIn;
LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum);
regMaskTP paramRegs = intRegState.rsCalleeRegArgMaskLiveIn | floatRegState.rsCalleeRegArgMaskLiveIn;

unsigned baseOffset = varDsc->lvIsStructField ? varDsc->lvFldOffset : 0;
unsigned size = varDsc->lvExactSize();
if (!segment.IsPassedInRegister() || ((paramRegs & genRegMask(segment.GetRegister())) == 0))
{
return;
}

unsigned paramLclNum = varDsc->lvIsStructField ? varDsc->lvParentLcl : lclNum;
LclVarDsc* paramVarDsc = compiler->lvaGetDesc(paramLclNum);
const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(paramLclNum);
for (const ABIPassingSegment& seg : abiInfo.Segments())
LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum);
if (varDsc->lvOnFrame && (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr))
{
if (!seg.IsPassedInRegister() || ((paramRegs & genRegMask(seg.GetRegister())) == 0))
{
continue;
}
LclVarDsc* paramVarDsc = compiler->lvaGetDesc(paramLclNum);

if (seg.Offset + seg.Size <= baseOffset)
var_types storeType = genParamStackType(paramVarDsc, segment);
if ((varDsc->TypeGet() != TYP_STRUCT) && (genTypeSize(genActualType(varDsc)) < genTypeSize(storeType)))
{
continue;
// Can happen for struct fields due to padding.
storeType = genActualType(varDsc);
}

if (baseOffset + size <= seg.Offset)
{
continue;
}

if (varDsc->lvOnFrame && (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr))
{
var_types storeType = genParamStackType(paramVarDsc, seg);
if ((varDsc->TypeGet() != TYP_STRUCT) && (genTypeSize(genActualType(varDsc)) < genTypeSize(storeType)))
{
// Can happen for struct fields due to padding.
storeType = genActualType(varDsc);
}

GetEmitter()->emitIns_S_R(ins_Store(storeType), emitActualTypeSize(storeType), seg.GetRegister(), lclNum,
seg.Offset - baseOffset);
}
GetEmitter()->emitIns_S_R(ins_Store(storeType), emitActualTypeSize(storeType), segment.GetRegister(), lclNum,
offset);
}

if (!varDsc->lvIsInReg())
{
continue;
}
if (!varDsc->lvIsInReg())
{
return;
}

var_types edgeType = genActualType(varDsc->GetRegisterType());
// Some parameters can be passed in multiple registers but enregistered
// in a single one (e.g. SIMD types on arm64). In this case the edges
// we add here represent insertions of each element.
if (seg.Size < genTypeSize(edgeType))
{
edgeType = seg.GetRegisterType();
}
var_types edgeType = genActualType(varDsc->GetRegisterType());
// Some parameters can be passed in multiple registers but enregistered
// in a single one (e.g. SIMD types on arm64). In this case the edges
// we add here represent insertions of each element.
if (segment.Size < genTypeSize(edgeType))
{
edgeType = segment.GetRegisterType();
}

RegNode* sourceReg = graph->GetOrAdd(seg.GetRegister());
RegNode* destReg = graph->GetOrAdd(varDsc->GetRegNum());
RegNode* sourceReg = graph->GetOrAdd(segment.GetRegister());
RegNode* destReg = graph->GetOrAdd(varDsc->GetRegNum());

if ((sourceReg != destReg) || (baseOffset != seg.Offset))
{
if ((sourceReg != destReg) || (offset != 0))
{
#ifdef TARGET_ARM
if (edgeType == TYP_DOUBLE)
{
assert(seg.Offset == baseOffset);
graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0);
if (edgeType == TYP_DOUBLE)
{
assert(offset == 0);
graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0);

sourceReg = graph->GetOrAdd(REG_NEXT(sourceReg->reg));
destReg = graph->GetOrAdd(REG_NEXT(destReg->reg));
graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0);
continue;
}
#endif
graph->AddEdge(sourceReg, destReg, edgeType, seg.Offset - baseOffset);
sourceReg = graph->GetOrAdd(REG_NEXT(sourceReg->reg));
destReg = graph->GetOrAdd(REG_NEXT(destReg->reg));
graph->AddEdge(sourceReg, destReg, TYP_FLOAT, 0);
return;
}
#endif
graph->AddEdge(sourceReg, destReg, edgeType, offset);
}
Copy link
Member Author

Choose a reason for hiding this comment

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

No actual diffs here, just refactoring the function to take the segment directly so that we can get it from the new data structure in the caller.

}

Expand Down Expand Up @@ -3386,7 +3373,8 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed)
}
}

if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE))
if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE) &&
compiler->lvaGetDesc(compiler->lvaStubArgumentVar)->lvOnFrame)
Comment on lines +3376 to +3377
Copy link
Member Author

@jakobbotsch jakobbotsch Dec 18, 2024

Choose a reason for hiding this comment

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

This change was required because of the unification of the code in LSRA that figures out which registers are live-in. That code marks all registers assigned to untracked locals as live-in in regState.rsCalleeRegArgMaskLiveIn; however, even untracked locals may not have a stack home allocated if they have 0 ref count. This code would then try to store the register to its (non-existent) home.
We should update LSRA to avoid marking registers live-in in this case, but it comes with diffs (should be improvements), so I'd like to do it separately.

{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SECRET_STUB_PARAM,
compiler->lvaStubArgumentVar, 0);
Expand All @@ -3408,19 +3396,27 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed)

for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++)
{
LclVarDsc* lclDsc = compiler->lvaGetDesc(lclNum);
LclVarDsc* lclDsc = compiler->lvaGetDesc(lclNum);
const ABIPassingInformation& abiInfo = compiler->lvaGetParameterABIInfo(lclNum);

if (compiler->lvaGetPromotionType(lclNum) == Compiler::PROMOTION_TYPE_INDEPENDENT)
for (const ABIPassingSegment& segment : abiInfo.Segments())
{
for (unsigned fld = 0; fld < lclDsc->lvFieldCnt; fld++)
if (!segment.IsPassedInRegister())
{
unsigned fieldLclNum = lclDsc->lvFieldLclStart + fld;
genSpillOrAddRegisterParam(fieldLclNum, &graph);
continue;
}

const ParameterRegisterLocalMapping* mapping =
compiler->FindParameterRegisterLocalMappingByRegister(segment.GetRegister());

if (mapping != nullptr)
{
genSpillOrAddRegisterParam(mapping->LclNum, mapping->Offset, lclNum, segment, &graph);
}
else
{
genSpillOrAddRegisterParam(lclNum, segment.Offset, lclNum, segment, &graph);
}
}
else
{
genSpillOrAddRegisterParam(lclNum, &graph);
}
}

Expand Down Expand Up @@ -5711,14 +5707,16 @@ void CodeGen::genFnProlog()
if (compiler->info.compCallConv == CorInfoCallConvExtension::Swift)
{
if ((compiler->lvaSwiftSelfArg != BAD_VAR_NUM) &&
((intRegState.rsCalleeRegArgMaskLiveIn & RBM_SWIFT_SELF) != 0))
((intRegState.rsCalleeRegArgMaskLiveIn & RBM_SWIFT_SELF) != 0) &&
compiler->lvaGetDesc(compiler->lvaSwiftSelfArg)->lvOnFrame)
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SWIFT_SELF, compiler->lvaSwiftSelfArg, 0);
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_SELF;
}

if ((compiler->lvaSwiftIndirectResultArg != BAD_VAR_NUM) &&
((intRegState.rsCalleeRegArgMaskLiveIn & theFixedRetBuffMask(CorInfoCallConvExtension::Swift)) != 0))
((intRegState.rsCalleeRegArgMaskLiveIn & theFixedRetBuffMask(CorInfoCallConvExtension::Swift)) != 0) &&
compiler->lvaGetDesc(compiler->lvaSwiftIndirectResultArg)->lvOnFrame)
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE,
theFixedRetBuffReg(CorInfoCallConvExtension::Swift),
Expand Down
59 changes: 59 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4221,6 +4221,65 @@ bool Compiler::compRsvdRegCheck(FrameLayoutState curState)
}
#endif // TARGET_ARMARCH || TARGET_RISCV64

//------------------------------------------------------------------------
// FindParameterRegisterLocalMappingByRegister:
// Try to find a mapping that maps a particular parameter register to an
// incoming defined local.
//
// Returns:
// The mapping, or nullptr if no mapping was found for this register.
//
const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByRegister(regNumber reg)
{
if (m_paramRegLocalMappings == nullptr)
{
return nullptr;
}

for (int i = 0; i < m_paramRegLocalMappings->Height(); i++)
{
const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i);
Copy link
Member

Choose a reason for hiding this comment

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

i am assuming an ArrayStack is used instead of map to keep things simpler and there are not many entries in the ArrayStack that this will cause performance issues?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, the number of entries is inherently limited because of the small number of registers used for parameters, so not much searching is required here.

if (mapping.RegisterSegment->GetRegister() == reg)
{
return &mapping;
}
}

return nullptr;
}

//------------------------------------------------------------------------
// FindParameterRegisterLocalMappingByLocal:
// Try to find a mapping that maps a particular local from an incoming
// parameter register.
//
// Parameters:
// lclNum - The local to find a mapping for
// offset - The offset that the mapping maps to in the local
//
// Returns:
// The mapping, or nullptr if no mapping was found for this local.
//
const ParameterRegisterLocalMapping* Compiler::FindParameterRegisterLocalMappingByLocal(unsigned lclNum,
unsigned offset)
{
if (m_paramRegLocalMappings == nullptr)
{
return nullptr;
}

for (int i = 0; i < m_paramRegLocalMappings->Height(); i++)
{
const ParameterRegisterLocalMapping& mapping = m_paramRegLocalMappings->BottomRef(i);
if ((mapping.LclNum == lclNum) && (mapping.Offset == offset))
{
return &mapping;
}
}

return nullptr;
}

//------------------------------------------------------------------------
// compGetTieringName: get a string describing tiered compilation settings
// for this method
Expand Down
40 changes: 35 additions & 5 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ class LclVarDsc

unsigned char lvIsParam : 1; // is this a parameter?
unsigned char lvIsRegArg : 1; // is this an argument that was passed by register?
unsigned char lvIsParamRegTarget : 1; // is this the target of a param reg to local mapping?
unsigned char lvFramePointerBased : 1; // 0 = off of REG_SPBASE (e.g., ESP), 1 = off of REG_FPBASE (e.g., EBP)

unsigned char lvOnFrame : 1; // (part of) the variable lives on the frame
Expand Down Expand Up @@ -2580,6 +2581,32 @@ struct CloneTryInfo
bool ScaleOriginalBlockProfile = false;
};

//------------------------------------------------------------------------
// ParameterRegisterLocalMapping:
// Contains mappings between a parameter register segment and a corresponding
// local. Used by the backend to know which locals are expected to contain
// which register parameters after the prolog.
//
struct ParameterRegisterLocalMapping
{
const ABIPassingSegment* RegisterSegment;
unsigned LclNum;
// Offset at which the register is inserted into the local. Used e.g. for
// HFAs on arm64 that might have been promoted as a single local (e.g.
// System.Numerics.Plane is passed in 3 float regs but enregistered as
// TYP_SIMD12).
// SysV 64 also see similar situations e.g. Vector3 being passed in
// xmm0[0..8), xmm1[8..12), but enregistered as one register.
unsigned Offset;
Copy link
Member Author

Choose a reason for hiding this comment

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

Eventually I'd like to get rid of this field entirely and require that all mappings are cleanly mapped. However, the cases described in the comment above will need to be handled in a different way than they do today then. One way would be to create new locals in lowering and insert IR in the init BB to reconstruct the destination local from the multiple locals when we detect these cases.


ParameterRegisterLocalMapping(const ABIPassingSegment* segment, unsigned lclNum, unsigned offset)
: RegisterSegment(segment)
, LclNum(lclNum)
, Offset(offset)
{
}
};

/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Expand Down Expand Up @@ -8258,8 +8285,6 @@ class Compiler
*/

public:
regNumber raUpdateRegStateForArg(RegState* regState, LclVarDsc* argDsc);

void raMarkStkVars();

#if FEATURE_PARTIAL_SIMD_CALLEE_SAVE
Expand Down Expand Up @@ -8291,8 +8316,14 @@ class Compiler
bool rpMustCreateEBPFrame(INDEBUG(const char** wbReason));

private:
Lowering* m_pLowering; // Lowering; needed to Lower IR that's added or modified after Lowering.
LinearScanInterface* m_pLinearScan; // Linear Scan allocator
Lowering* m_pLowering = nullptr; // Lowering; needed to Lower IR that's added or modified after Lowering.
LinearScanInterface* m_pLinearScan = nullptr; // Linear Scan allocator

public:
ArrayStack<ParameterRegisterLocalMapping>* m_paramRegLocalMappings = nullptr;

const ParameterRegisterLocalMapping* FindParameterRegisterLocalMappingByRegister(regNumber reg);
const ParameterRegisterLocalMapping* FindParameterRegisterLocalMappingByLocal(unsigned lclNum, unsigned offset);

/*
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Expand All @@ -8307,7 +8338,6 @@ class Compiler
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
*/

public:
// Get handles

void eeGetCallInfo(CORINFO_RESOLVED_TOKEN* pResolvedToken,
Expand Down
Loading
Loading