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

[RISC-V][LoongArch64] JIT: pass structs according to floating-point calling convention properly #104237

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
dcf2edb
Replace StructFloatFieldInfoFlags with FpStructInRegistersInfo which …
tomeksowi Jun 25, 2024
48caa9c
Replace StructFloatFieldInfoFlags with FpStruct::Flags in profiler
tomeksowi Jun 25, 2024
853bd99
Remove FpStructInRegistersInfo::FromOldFlags()
tomeksowi Jun 25, 2024
0e80ccf
Fix duplicating types in HandleInlineArray
tomeksowi Jun 25, 2024
3d139ac
Remove signedness from FpStruct::IntKind because most probably we won…
tomeksowi Jun 25, 2024
89cc148
Remove old StructFloatFieldInfoFlags calculating routine
tomeksowi Jun 25, 2024
c2984a2
Typo in TARGET_LOONGARCH64
tomeksowi Jun 25, 2024
a229831
Remove m_returnedFpFieldOffsets from ArgIterator
tomeksowi Jun 25, 2024
f2b38f8
Add missing ENREGISTERED_PARAMTYPE_MAXSIZE condition to C# version of…
tomeksowi Jun 25, 2024
5481693
Rename RISCV64PassStructInRegister to match settled casing for RiscV …
tomeksowi Jun 26, 2024
4250bf4
Update hardcoded flags for float and double in ArgIteratorTemplate::C…
tomeksowi Jun 26, 2024
0f03a88
Fix build on other platforms
tomeksowi Jun 26, 2024
f485362
Update LoongArch to use FpStructInRegistersInfo
tomeksowi Jun 26, 2024
9967e2e
Remove unused old flag masks
tomeksowi Jun 26, 2024
6bc28d1
LoongArch64 typo
tomeksowi Jun 27, 2024
33a6aee
Missing FpStruct namespace
tomeksowi Jun 27, 2024
1e951bd
Missing FpStruct namespace
tomeksowi Jun 27, 2024
fd49a25
Missing FpStruct namespace
tomeksowi Jun 27, 2024
214b94f
Use FpStruct namespace everywhere in JIT
tomeksowi Jun 27, 2024
d7d2583
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jun 28, 2024
0e40a0c
Add tests
tomeksowi Jun 28, 2024
bf0fead
Fix passing FP structs in JIT
tomeksowi Jul 1, 2024
9c2bc9b
Remove CallArgABIInformation.StructFloatFieldType and Offset because …
tomeksowi Jul 1, 2024
3754ad2
Remove CallArgABIInformation.StructDesc on System V because it was wr…
tomeksowi Jul 1, 2024
1966b34
Remove unused local vars from ABIPassingInformation::Classify
tomeksowi Jul 1, 2024
5db3122
Pass variables after the tested struct to detect potential register/s…
tomeksowi Jul 1, 2024
531598b
Add test for two-field but one-slot struct. Fix assertion bug that it…
tomeksowi Jul 1, 2024
40c71bf
Add sanity check for empty struct passing, disable on RISC-V and Syst…
tomeksowi Jul 1, 2024
e34a68d
Merge branch 'main' into fp-struct-info
tomeksowi Jul 1, 2024
a029909
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jul 1, 2024
f8397b4
Format
tomeksowi Jul 2, 2024
08c6385
JIT review
tomeksowi Jul 2, 2024
f163a78
Merge branch 'main' into fp-struct-info
tomeksowi Jul 3, 2024
e23c0ab
Update StructFloatFieldInfoFlags description
tomeksowi Jul 3, 2024
ef5a6d4
Revert to hitherto instruction set order as it's not the point of thi…
tomeksowi Jul 3, 2024
624a427
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jul 3, 2024
92bf02a
Disable Test_Empty_Sanity on arm32 due to bug in Clang
tomeksowi Jul 3, 2024
902a73e
Add ActiveIssue on arm and arm64
tomeksowi Jul 3, 2024
c3eb32c
Exclude arm64 from Test_PackedEmptyFloatLong*_RiscV
tomeksowi Jul 4, 2024
100eced
Unify get{LoongArch,RiscV}64PassFpStructInRegistersInfo JIT interfaces
tomeksowi Jul 4, 2024
5c9e5ac
Use JIT_TO_EE_TRANSITION instead of _LEAF because MethodTable::GetFpS…
tomeksowi Jul 4, 2024
1d5ad91
Remove FpStruct::IntKind, we should have similar info in ClassLayout …
tomeksowi Jul 4, 2024
7b1587b
Change JIT interface to return a struct similar to CORINFO_SWIFT_LOWE…
tomeksowi Jul 8, 2024
e37e984
Change JIT to use new Swift-like getFpStructLowering
tomeksowi Jul 9, 2024
c7c88e0
Cache CORINFO_FPSTRUCT_LOWERING
tomeksowi Jul 9, 2024
5c478d7
Update LoongArch classifier to use CORINFO_FPSTRUCT_LOWERING
tomeksowi Jul 9, 2024
a5ce6fe
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jul 9, 2024
1f16783
Update StructFloatInfoFlags doc comment on C#
tomeksowi Jul 9, 2024
1dfcf3b
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jul 9, 2024
67bdf3e
Add arm32 clang issue
tomeksowi Jul 9, 2024
f064eb6
Move StructFloatFieldInfoFlags and FpStructInRegistersInfo out of the…
tomeksowi Jul 11, 2024
d4be92c
Merge LoongArch and RISC-V AOT calculation of FpStructInRegistersInfo…
tomeksowi Jul 11, 2024
823df8b
Don't zero-initialize CORINFO_FPSTRUCT_LOWERING
tomeksowi Jul 12, 2024
cebecf8
Merge branch 'fp-struct-info' into jit-empty-struct-passing
tomeksowi Jul 15, 2024
9fa2188
Merge branch 'main' into jit-empty-struct-passing
tomeksowi Jul 26, 2024
0112494
Update LoongArch classifier
tomeksowi Jul 26, 2024
e7b4199
Fix assert for two-field structs smaller than 8 bytes, add test
tomeksowi Jul 26, 2024
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
35 changes: 24 additions & 11 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3201,6 +3201,13 @@ var_types CodeGen::genParamStackType(LclVarDsc* dsc, const ABIPassingSegment& se
// can always use the full register size here. This allows us to
// use stp more often.
return TYP_I_IMPL;
#elif defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
// On RISC-V/LoongArch structs passed according to floating-point calling convention are enregistered one
// field per register regardless of the field layout in memory, so the small int load/store instructions
// must not be upsized to 4 bytes, otherwise for example:
// * struct { struct{} e1,e2,e3; byte b; float f; } -- 4-byte store for 'b' would trash 'f'
// * struct { float f; struct{} e1,e2,e3; byte b; } -- 4-byte store for 'b' would trash adjacent stack slot
return seg.GetRegisterType();
#else
return genActualType(seg.GetRegisterType());
#endif
Expand Down Expand Up @@ -7390,18 +7397,19 @@ void CodeGen::genStructReturn(GenTree* treeNode)
assert(varDsc->lvIsMultiRegRet);

#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// On LoongArch64, for a struct like "{ int, double }", "retTypeDesc" will be "{ TYP_INT, TYP_DOUBLE }",
// i. e. not include the padding for the first field, and so the general loop below won't work.
var_types type = retTypeDesc.GetReturnRegType(0);
regNumber toReg = retTypeDesc.GetABIReturnReg(0, compiler->info.compCallConv);
GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), 0);
var_types type = retTypeDesc.GetReturnRegType(0);
unsigned offset = retTypeDesc.GetReturnFieldOffset(0);
regNumber toReg = retTypeDesc.GetABIReturnReg(0, compiler->info.compCallConv);

GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), offset);
if (regCount > 1)
{
assert(regCount == 2);
int offset = genTypeSize(type);
type = retTypeDesc.GetReturnRegType(1);
offset = (int)((unsigned int)offset < genTypeSize(type) ? genTypeSize(type) : offset);
toReg = retTypeDesc.GetABIReturnReg(1, compiler->info.compCallConv);
assert(offset + genTypeSize(type) <= retTypeDesc.GetReturnFieldOffset(1));
type = retTypeDesc.GetReturnRegType(1);
offset = retTypeDesc.GetReturnFieldOffset(1);
toReg = retTypeDesc.GetABIReturnReg(1, compiler->info.compCallConv);

GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), offset);
}
#else // !TARGET_LOONGARCH64 && !TARGET_RISCV64
Expand Down Expand Up @@ -7779,6 +7787,11 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode)
assert(regCount == varDsc->lvFieldCnt);
}

#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
// genMultiRegStoreToLocal is only used for calls on RISC-V and LoongArch
const ReturnTypeDesc* returnTypeDesc = actualOp1->AsCall()->GetReturnTypeDesc();
#endif

#ifdef SWIFT_SUPPORT
const uint32_t* offsets = nullptr;
if (actualOp1->IsCall() && (actualOp1->AsCall()->GetUnmanagedCallConv() == CorInfoCallConvExtension::Swift))
Expand Down Expand Up @@ -7829,8 +7842,8 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode)
else
{
#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// should consider the padding field within a struct.
offset = (offset % genTypeSize(srcType)) ? AlignUp(offset, genTypeSize(srcType)) : offset;
// Should consider the padding, empty struct fields, etc within a struct.
offset = returnTypeDesc->GetReturnFieldOffset(i);
#endif
#ifdef SWIFT_SUPPORT
if (offsets != nullptr)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@ var_types Compiler::getArgTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,

// Otherwise we pass this struct by value on the stack
// setup wbPassType and useType indicate that this is passed by value according to the X86/ARM32 ABI
// On LOONGARCH64 struct that is 1-16 bytes is passed by value in one/two register(s)
// On LOONGARCH64 and RISCV64 struct that is 1-16 bytes is passed by value in one/two register(s)
howToPassStruct = SPK_ByValue;
useType = TYP_STRUCT;

Expand Down
32 changes: 20 additions & 12 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29478,6 +29478,15 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp,
assert(returnType != TYP_UNKNOWN);
assert(returnType != TYP_STRUCT);
m_regType[0] = returnType;

#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
const CORINFO_FPSTRUCT_LOWERING* lowering = comp->GetFpStructLowering(retClsHnd);
if (!lowering->byIntegerCallConv)
{
assert(lowering->numLoweredElements == 1);
m_fieldOffset[0] = lowering->offsets[0];
}
#endif // defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
break;
}

Expand Down Expand Up @@ -29543,37 +29552,36 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp,
}

#elif defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
assert((structSize >= TARGET_POINTER_SIZE) && (structSize <= (2 * TARGET_POINTER_SIZE)));
assert(structSize > sizeof(float));
assert(structSize <= (2 * TARGET_POINTER_SIZE));
BYTE gcPtrs[2] = {TYPE_GC_NONE, TYPE_GC_NONE};
comp->info.compCompHnd->getClassGClayout(retClsHnd, &gcPtrs[0]);
const CORINFO_FPSTRUCT_LOWERING* lowering = comp->GetFpStructLowering(retClsHnd);
if (!lowering->byIntegerCallConv)
{
comp->compFloatingPointUsed = true;
assert(lowering->numLoweredElements == MAX_RET_REG_COUNT);
var_types types[MAX_RET_REG_COUNT] = {JITtype2varType(lowering->loweredElements[0]),
JITtype2varType(lowering->loweredElements[1])};
assert(varTypeIsFloating(types[0]) || varTypeIsFloating(types[1]));
assert((structSize > 8) == ((genTypeSize(types[0]) == 8) || (genTypeSize(types[1]) == 8)));
static_assert(MAX_RET_REG_COUNT == MAX_FPSTRUCT_LOWERED_ELEMENTS, "");
for (unsigned i = 0; i < MAX_RET_REG_COUNT; ++i)
{
if (varTypeIsFloating(types[i]))
{
m_regType[i] = types[i];
}
else
m_regType[i] = JITtype2varType(lowering->loweredElements[i]);
m_fieldOffset[i] = lowering->offsets[i];
if (m_regType[i] == TYP_LONG && ((m_fieldOffset[i] % TARGET_POINTER_SIZE) == 0))
{
assert(varTypeIsIntegralOrI(types[i]));
m_regType[i] = (genTypeSize(types[i]) == 8) ? comp->getJitGCType(gcPtrs[i]) : TYP_INT;
unsigned slot = m_fieldOffset[i] / TARGET_POINTER_SIZE;
m_regType[i] = comp->getJitGCType(gcPtrs[slot]);
}
}
assert(varTypeIsFloating(m_regType[0]) || varTypeIsFloating(m_regType[1]));
}
else
{
for (unsigned i = 0; i < 2; ++i)
{
m_regType[i] = comp->getJitGCType(gcPtrs[i]);
}
m_fieldOffset[0] = 0;
m_fieldOffset[1] = TARGET_POINTER_SIZE;
}

#elif defined(TARGET_X86)
Expand Down
49 changes: 33 additions & 16 deletions src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -4275,6 +4275,13 @@ struct ReturnTypeDesc
private:
var_types m_regType[MAX_RET_REG_COUNT];

#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
// Structs according to hardware floating-point calling convention are passed as two logical fields, each in
// separate register, disregarding struct layout such as packing, custom alignment, padding with empty structs, etc.
// We need size (can be derived from m_regType) & offset of each field for memory load/stores
unsigned m_fieldOffset[MAX_RET_REG_COUNT];
#endif

#ifdef DEBUG
bool m_inited;
#endif
Expand Down Expand Up @@ -4306,6 +4313,9 @@ struct ReturnTypeDesc
for (unsigned i = 0; i < MAX_RET_REG_COUNT; ++i)
{
m_regType[i] = TYP_UNKNOWN;
#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
m_fieldOffset[i] = 0;
#endif
}
#ifdef DEBUG
m_inited = false;
Expand Down Expand Up @@ -4351,8 +4361,11 @@ struct ReturnTypeDesc
for (unsigned i = regCount + 1; i < MAX_RET_REG_COUNT; ++i)
{
assert(m_regType[i] == TYP_UNKNOWN);
}
#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
assert(m_fieldOffset[i] == 0);
#endif
}
#endif // DEBUG

return regCount;
}
Expand Down Expand Up @@ -4401,6 +4414,25 @@ struct ReturnTypeDesc
return result;
}

unsigned GetSingleReturnFieldOffset() const
{
assert(!IsMultiRegRetType());
assert(m_regType[0] != TYP_UNKNOWN);
#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
return m_fieldOffset[0];
#else
return 0;
#endif
}

#if defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64)
unsigned GetReturnFieldOffset(unsigned index) const
{
assert(m_regType[index] != TYP_UNKNOWN);
return m_fieldOffset[index];
}
#endif

// Get i'th ABI return register
regNumber GetABIReturnReg(unsigned idx, CorInfoCallConvExtension callConv) const;

Expand Down Expand Up @@ -4508,9 +4540,6 @@ struct CallArgABIInformation
: NumRegs(0)
, ByteOffset(0)
, ByteSize(0)
#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
, StructFloatFieldType()
#endif
, ArgType(TYP_UNDEF)
, PassedByRef(false)
#if FEATURE_ARG_SPLIT
Expand All @@ -4537,18 +4566,6 @@ struct CallArgABIInformation
unsigned NumRegs;
unsigned ByteOffset;
unsigned ByteSize;
#if defined(UNIX_AMD64_ABI)
// Unix amd64 will split floating point types and integer types in structs
// between floating point and general purpose registers. Keep track of that
// information so we do not need to recompute it later.
SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR StructDesc;
#endif // UNIX_AMD64_ABI
#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// For LoongArch64's and RISC-V 64's ABI, the struct which has float field(s) and no more than two fields
// may be passed by float register(s).
// e.g `struct {int a; float b;}` passed by an integer register and a float register.
var_types StructFloatFieldType[2];
#endif
// The type used to pass this argument. This is generally the original
// argument type, but when a struct is passed as a scalar type, this is
// that type. Note that if a struct is passed by reference, this will still
Expand Down
41 changes: 19 additions & 22 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -914,40 +914,37 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un
if ((lowering != nullptr) && !lowering->byIntegerCallConv)
{
assert(varTypeIsStruct(argType));
int floatNum = 0;
assert((lowering->numLoweredElements == 1) || (lowering->numLoweredElements == 2));
if (lowering->numLoweredElements == 1)
{
assert(argSize <= 8);
assert(varDsc->lvExactSize() <= argSize);

floatNum = 1;
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, 1);
cSlotsToEnregister = lowering->numLoweredElements;
argRegTypeInStruct1 = JITtype2varType(lowering->loweredElements[0]);
if (lowering->numLoweredElements == 2)
argRegTypeInStruct2 = JITtype2varType(lowering->loweredElements[1]);

argRegTypeInStruct1 = JITtype2varType(lowering->loweredElements[0]);
assert(varTypeIsFloating(argRegTypeInStruct1));
}
else
int floatNum = (int)varTypeIsFloating(argRegTypeInStruct1) + (int)varTypeIsFloating(argRegTypeInStruct2);
assert(floatNum > 0);

canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, floatNum);
if (canPassArgInRegisters && (floatNum < lowering->numLoweredElements))
{
assert(floatNum == 1);
assert(lowering->numLoweredElements == 2);
argRegTypeInStruct1 = genActualType(JITtype2varType(lowering->loweredElements[0]));
argRegTypeInStruct2 = genActualType(JITtype2varType(lowering->loweredElements[1]));
floatNum = (int)varTypeIsFloating(argRegTypeInStruct1) + (int)varTypeIsFloating(argRegTypeInStruct2);
canPassArgInRegisters = varDscInfo->canEnreg(TYP_DOUBLE, floatNum);
if (floatNum == 1)
canPassArgInRegisters = canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1);
assert(varTypeIsIntegralOrI(argRegTypeInStruct1) || varTypeIsIntegralOrI(argRegTypeInStruct2));
canPassArgInRegisters = varDscInfo->canEnreg(TYP_I_IMPL, 1);
}

assert((floatNum == 1) || (floatNum == 2));

if (!canPassArgInRegisters)
{
// On LoongArch64 and RISCV64, if there aren't any remaining floating-point registers to pass the
// argument, integer registers (if any) are used instead.
canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister);

// If a struct eligible for passing according to floating-point calling convention cannot be fully
// enregistered, it is passed according to integer calling convention -- in up to two integer registers
// and/or stack slots, as a lump of bits laid out like in memory.
cSlotsToEnregister = cSlots;
argRegTypeInStruct1 = TYP_UNKNOWN;
argRegTypeInStruct2 = TYP_UNKNOWN;

canPassArgInRegisters = varDscInfo->canEnreg(argType, cSlotsToEnregister);
if (cSlotsToEnregister == 2)
{
if (!canPassArgInRegisters && varDscInfo->canEnreg(TYP_I_IMPL, 1))
Expand Down Expand Up @@ -1082,7 +1079,7 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un
varDsc->SetOtherArgReg(
genMapRegArgNumToRegNum(secondAllocatedRegArgNum, argRegTypeInStruct2, info.compCallConv));
}
else if (cSlots > 1)
else if (cSlotsToEnregister > 1)
{
// Here a struct-arg which needs two registers but only one integer register available,
// it has to be split. But we reserved extra 8-bytes for the whole struct.
Expand Down
8 changes: 6 additions & 2 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4847,12 +4847,14 @@ GenTree* Lowering::LowerStoreLocCommon(GenTreeLclVarCommon* lclStore)
// x86 uses it only for long return type, not for structs.
assert(slotCount == 1);
assert(lclRegType != TYP_UNDEF);
#else // !TARGET_XARCH || UNIX_AMD64_ABI
#else // !TARGET_XARCH || UNIX_AMD64_ABI
if (!comp->IsHfa(layout->GetClassHandle()))
{
if (slotCount > 1)
{
#if !defined(TARGET_RISCV64) && !defined(TARGET_LOONGARCH64)
assert(call->HasMultiRegRetVal());
#endif
}
else
{
Expand Down Expand Up @@ -5158,6 +5160,7 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret)
// Otherwise we don't mind that we leave the upper bits undefined.
lclVar->ChangeType(ret->TypeGet());
}
lclVar->AsLclFld()->SetLclOffs(comp->compRetTypeDesc.GetSingleReturnFieldOffset());
}
else
{
Expand Down Expand Up @@ -5346,7 +5349,8 @@ GenTreeLclVar* Lowering::SpillStructCallResult(GenTreeCall* call) const
comp->lvaSetVarDoNotEnregister(spillNum DEBUGARG(DoNotEnregisterReason::LocalField));
CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd;
comp->lvaSetStruct(spillNum, retClsHnd, false);
GenTreeLclFld* spill = comp->gtNewStoreLclFldNode(spillNum, call->TypeGet(), 0, call);
unsigned offset = call->GetReturnTypeDesc()->GetSingleReturnFieldOffset();
GenTreeLclFld* spill = comp->gtNewStoreLclFldNode(spillNum, call->TypeGet(), offset, call);

BlockRange().InsertAfter(call, spill);
ContainCheckStoreLoc(spill);
Expand Down
Loading
Loading