Skip to content

JIT: Remove PUTARG_SPLIT #116074

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 5 commits into from
Jun 4, 2025
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
9 changes: 6 additions & 3 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,12 @@ var_types ABIPassingSegment::GetRegisterType() const

//-----------------------------------------------------------------------------
// GetRegisterType:
// Return the smallest type larger or equal to Size that most naturally
// represents the register this segment is passed in, taking into account the
// GC info of the specified layout.
// Return the smallest type larger or equal to Size that most naturally
// represents the register this segment is passed in, taking into account the
// GC info of the specified layout.
//
// Parameters:
// layout - The layout of the class that this segment is part of
//
// Return Value:
// A type that matches ABIPassingSegment::Size and the register.
Expand Down
7 changes: 0 additions & 7 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -872,9 +872,6 @@ class CodeGen final : public CodeGenInterface
void genIntrinsic(GenTreeIntrinsic* treeNode);
void genPutArgStk(GenTreePutArgStk* treeNode);
void genPutArgReg(GenTreeOp* tree);
#if FEATURE_ARG_SPLIT
void genPutArgSplit(GenTreePutArgSplit* treeNode);
#endif // FEATURE_ARG_SPLIT

#if defined(TARGET_XARCH)
unsigned getBaseVarForPutArgStk(GenTree* treeNode);
Expand Down Expand Up @@ -1095,10 +1092,6 @@ class CodeGen final : public CodeGenInterface
regNumber dstReg,
regNumber srcReg,
regNumber sizeReg);
#if FEATURE_ARG_SPLIT
void genConsumeArgSplitStruct(GenTreePutArgSplit* putArgNode);
#endif // FEATURE_ARG_SPLIT

void genConsumeRegs(GenTree* tree);
void genConsumeOperands(GenTreeOp* tree);
#if defined(FEATURE_SIMD) || defined(FEATURE_HW_INTRINSICS)
Expand Down
235 changes: 0 additions & 235 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -428,10 +428,6 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode)
genPutArgReg(treeNode->AsOp());
break;

case GT_PUTARG_SPLIT:
genPutArgSplit(treeNode->AsPutArgSplit());
break;

case GT_CALL:
genCall(treeNode->AsCall());
break;
Expand Down Expand Up @@ -1161,237 +1157,6 @@ void CodeGen::genPutArgReg(GenTreeOp* tree)
genProduceReg(tree);
}

//---------------------------------------------------------------------
// genPutArgSplit - generate code for a GT_PUTARG_SPLIT node
//
// Arguments
// tree - the GT_PUTARG_SPLIT node
//
// Return value:
// None
//
void CodeGen::genPutArgSplit(GenTreePutArgSplit* treeNode)
{
assert(treeNode->OperIs(GT_PUTARG_SPLIT));

GenTree* source = treeNode->gtOp1;
emitter* emit = GetEmitter();
unsigned varNumOut = compiler->lvaOutgoingArgSpaceVar;
unsigned argOffsetMax = compiler->lvaOutgoingArgSpaceSize;

if (source->OperIs(GT_FIELD_LIST))
{
// Evaluate each of the GT_FIELD_LIST items into their register
// and store their register into the outgoing argument area
unsigned regIndex = 0;
unsigned firstOnStackOffs = UINT_MAX;

for (GenTreeFieldList::Use& use : source->AsFieldList()->Uses())
{
GenTree* nextArgNode = use.GetNode();
regNumber fieldReg = nextArgNode->GetRegNum();
genConsumeReg(nextArgNode);

if (regIndex >= treeNode->gtNumRegs)
{
if (firstOnStackOffs == UINT_MAX)
{
firstOnStackOffs = use.GetOffset();
}

var_types type = use.GetType();
unsigned offset = treeNode->getArgOffset() + use.GetOffset() - firstOnStackOffs;
// We can't write beyond the outgoing arg area
assert((offset + genTypeSize(type)) <= argOffsetMax);

// Emit store instructions to store the registers produced by the GT_FIELD_LIST into the outgoing
// argument area
emit->emitIns_S_R(ins_Store(type), emitActualTypeSize(type), fieldReg, varNumOut, offset);
}
else
{
var_types type = treeNode->GetRegType(regIndex);
regNumber argReg = treeNode->GetRegNumByIdx(regIndex);

// If child node is not already in the register we need, move it
inst_Mov(type, argReg, fieldReg, /* canSkip */ true);

regIndex++;
}
}
}
else
{
var_types targetType = source->TypeGet();
assert(source->isContained() && varTypeIsStruct(targetType));

// We need a register to store intermediate values that we are loading
// from the source into. We can usually use one of the target registers
// that will be overridden anyway. The exception is when the source is
// in a register and that register is the unique target register we are
// placing. LSRA will always allocate an internal register when there
// is just one target register to handle this situation.
//
int firstRegToPlace;
regNumber valueReg = REG_NA;
unsigned srcLclNum = BAD_VAR_NUM;
unsigned srcLclOffset = 0;
regNumber addrReg = REG_NA;
var_types addrType = TYP_UNDEF;
ClassLayout* layout = nullptr;

if (source->OperIsLocalRead())
{
srcLclNum = source->AsLclVarCommon()->GetLclNum();
srcLclOffset = source->AsLclVarCommon()->GetLclOffs();
layout = source->AsLclVarCommon()->GetLayout(compiler);
LclVarDsc* varDsc = compiler->lvaGetDesc(srcLclNum);

// This struct must live on the stack frame.
assert(varDsc->lvOnFrame && !varDsc->lvRegister);

// No possible conflicts, just use the first register as the value register.
firstRegToPlace = 0;
valueReg = treeNode->GetRegNumByIdx(0);
}
else // we must have a GT_BLK
{
layout = source->AsBlk()->GetLayout();
addrReg = genConsumeReg(source->AsBlk()->Addr());
addrType = source->AsBlk()->Addr()->TypeGet();

regNumber allocatedValueReg = REG_NA;
if (treeNode->gtNumRegs == 1)
{
allocatedValueReg = internalRegisters.Extract(treeNode);
}

// Pick a register to store intermediate values in for the to-stack
// copy. It must not conflict with addrReg. We try to prefer an
// argument register since those can always use thumb encoding.
valueReg = treeNode->GetRegNumByIdx(0);
if (valueReg == addrReg)
{
if (treeNode->gtNumRegs == 1)
{
valueReg = allocatedValueReg;
}
else
{
// Prefer argument register that can always use thumb encoding.
valueReg = treeNode->GetRegNumByIdx(1);
}
}

// Find first register to place. If we are placing addrReg, then
// make sure we place it last to avoid clobbering its value.
//
// The loop below will start at firstRegToPlace and place
// treeNode->gtNumRegs registers in order, with wraparound. For
// example, if the registers to place are r0, r1, r2=addrReg, r3
// then we will set firstRegToPlace = 3 (r3) and the loop below
// will place r3, r0, r1, r2. The last placement will clobber
// addrReg.
firstRegToPlace = 0;
for (unsigned i = 0; i < treeNode->gtNumRegs; i++)
{
if (treeNode->GetRegNumByIdx(i) == addrReg)
{
firstRegToPlace = i + 1;
break;
}
}
}

// Put on stack first
unsigned structOffset = treeNode->gtNumRegs * TARGET_POINTER_SIZE;
unsigned remainingSize = layout->GetSize() - structOffset;
unsigned argOffsetOut = treeNode->getArgOffset();

assert((remainingSize > 0) && (roundUp(remainingSize, TARGET_POINTER_SIZE) == treeNode->GetStackByteSize()));
while (remainingSize > 0)
{
var_types type;
if (remainingSize >= TARGET_POINTER_SIZE)
{
type = layout->GetGCPtrType(structOffset / TARGET_POINTER_SIZE);
}
else if (remainingSize >= 4)
{
type = TYP_INT;
}
else if (remainingSize >= 2)
{
type = TYP_USHORT;
}
else
{
assert(remainingSize == 1);
type = TYP_UBYTE;
}

emitAttr attr = emitActualTypeSize(type);
unsigned moveSize = genTypeSize(type);

instruction loadIns = ins_Load(type);
if (srcLclNum != BAD_VAR_NUM)
{
// Load from our local source
emit->emitIns_R_S(loadIns, attr, valueReg, srcLclNum, srcLclOffset + structOffset);
}
else
{
assert(valueReg != addrReg);

// Load from our address expression source
emit->emitIns_R_R_I(loadIns, attr, valueReg, addrReg, structOffset);
}

// Emit the instruction to store the register into the outgoing argument area
emit->emitIns_S_R(ins_Store(type), attr, valueReg, varNumOut, argOffsetOut);
argOffsetOut += moveSize;
assert(argOffsetOut <= argOffsetMax);

remainingSize -= moveSize;
structOffset += moveSize;
}

// Place registers starting from firstRegToPlace. It should ensure we
// place addrReg last (if we place it at all).
structOffset = static_cast<unsigned>(firstRegToPlace) * TARGET_POINTER_SIZE;
unsigned curRegIndex = firstRegToPlace;

for (unsigned regsPlaced = 0; regsPlaced < treeNode->gtNumRegs; regsPlaced++)
{
if (curRegIndex == treeNode->gtNumRegs)
{
curRegIndex = 0;
structOffset = 0;
}

regNumber targetReg = treeNode->GetRegNumByIdx(curRegIndex);
var_types type = treeNode->GetRegType(curRegIndex);

if (srcLclNum != BAD_VAR_NUM)
{
// Load from our local source
emit->emitIns_R_S(INS_ldr, emitTypeSize(type), targetReg, srcLclNum, srcLclOffset + structOffset);
}
else
{
assert((addrReg != targetReg) || (regsPlaced == treeNode->gtNumRegs - 1));

// Load from our address expression source
emit->emitIns_R_R_I(INS_ldr, emitTypeSize(type), targetReg, addrReg, structOffset);
}

curRegIndex++;
structOffset += TARGET_POINTER_SIZE;
}
}
genProduceReg(treeNode);
}

#ifdef FEATURE_SIMD
//----------------------------------------------------------------------------------
// genMultiRegStoreToSIMDLocal: store multi-reg value to a single-reg SIMD local
Expand Down
30 changes: 0 additions & 30 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7218,36 +7218,6 @@ void CodeGen::genCallPlaceRegArgs(GenTreeCall* call)
}
#endif

#if FEATURE_ARG_SPLIT
if (argNode->OperIs(GT_PUTARG_SPLIT))
{
assert(compFeatureArgSplit());
genConsumeArgSplitStruct(argNode->AsPutArgSplit());
unsigned regIndex = 0;
for (const ABIPassingSegment& seg : abiInfo.Segments())
{
if (!seg.IsPassedInRegister())
{
continue;
}

regNumber allocReg = argNode->AsPutArgSplit()->GetRegNumByIdx(regIndex);
var_types type = argNode->AsPutArgSplit()->GetRegType(regIndex);
inst_Mov(genActualType(type), seg.GetRegister(), allocReg, /* canSkip */ true);

if (call->IsFastTailCall())
{
// We won't actually consume the register here -- keep it alive into the epilog.
gcInfo.gcMarkRegPtrVal(seg.GetRegister(), type);
}

regIndex++;
}

continue;
}
#endif

if (abiInfo.HasExactlyOneRegisterSegment())
{
regNumber argReg = abiInfo.Segment(0).GetRegister();
Expand Down
23 changes: 0 additions & 23 deletions src/coreclr/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1826,29 +1826,6 @@ void CodeGen::genConsumePutStructArgStk(GenTreePutArgStk* putArgNode,
}
}

#if FEATURE_ARG_SPLIT
//------------------------------------------------------------------------
// genConsumeArgRegSplit: Consume register(s) in Call node to set split struct argument.
//
// Arguments:
// putArgNode - the PUTARG_STK tree.
//
// Return Value:
// None.
//
void CodeGen::genConsumeArgSplitStruct(GenTreePutArgSplit* putArgNode)
{
assert(putArgNode->OperIs(GT_PUTARG_SPLIT));
assert(putArgNode->gtHasReg(compiler));

genUnspillRegIfNeeded(putArgNode);

gcInfo.gcMarkRegSetNpt(putArgNode->gtGetRegMask());

genCheckConsumeNode(putArgNode);
}
#endif // FEATURE_ARG_SPLIT

//------------------------------------------------------------------------
// genPutArgStkFieldList: Generate code for a putArgStk whose source is a GT_FIELD_LIST
//
Expand Down
Loading
Loading