Skip to content

Support frozen struct returns for Swift calls #99704

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 20 commits into from
Mar 14, 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
4 changes: 2 additions & 2 deletions src/coreclr/jit/codegenarmarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3410,7 +3410,7 @@ void CodeGen::genCall(GenTreeCall* call)
for (unsigned i = 0; i < regCount; ++i)
{
var_types regType = pRetTypeDesc->GetReturnRegType(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i, call->GetUnmanagedCallConv());
regNumber allocatedReg = call->GetRegNumByIdx(i);
inst_Mov(regType, allocatedReg, returnReg, /* canSkip */ true);
}
Expand Down Expand Up @@ -4828,7 +4828,7 @@ void CodeGen::genSIMDSplitReturn(GenTree* src, ReturnTypeDesc* retTypeDesc)
for (unsigned i = 0; i < regCount; ++i)
{
var_types type = retTypeDesc->GetReturnRegType(i);
regNumber reg = retTypeDesc->GetABIReturnReg(i);
regNumber reg = retTypeDesc->GetABIReturnReg(i, compiler->info.compCallConv);
if (varTypeIsFloating(type))
{
// If the register piece is to be passed in a floating point register
Expand Down
29 changes: 23 additions & 6 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7770,7 +7770,8 @@ void CodeGen::genReturn(GenTree* treeNode)
{
if (varTypeIsGC(retTypeDesc.GetReturnRegType(i)))
{
gcInfo.gcMarkRegPtrVal(retTypeDesc.GetABIReturnReg(i), retTypeDesc.GetReturnRegType(i));
gcInfo.gcMarkRegPtrVal(retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv),
retTypeDesc.GetReturnRegType(i));
}
}
}
Expand All @@ -7787,7 +7788,7 @@ void CodeGen::genReturn(GenTree* treeNode)
{
if (varTypeIsGC(retTypeDesc.GetReturnRegType(i)))
{
gcInfo.gcMarkRegSetNpt(genRegMask(retTypeDesc.GetABIReturnReg(i)));
gcInfo.gcMarkRegSetNpt(genRegMask(retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv)));
}
}
}
Expand Down Expand Up @@ -7894,23 +7895,23 @@ void CodeGen::genStructReturn(GenTree* treeNode)
// 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);
regNumber toReg = retTypeDesc.GetABIReturnReg(0, compiler->info.compCallConv);
GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), 0);
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);
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
int offset = 0;
for (unsigned i = 0; i < regCount; ++i)
{
var_types type = retTypeDesc.GetReturnRegType(i);
regNumber toReg = retTypeDesc.GetABIReturnReg(i);
regNumber toReg = retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv);
GetEmitter()->emitIns_R_S(ins_Load(type), emitTypeSize(type), toReg, lclNode->GetLclNum(), offset);
offset += genTypeSize(type);
}
Expand All @@ -7921,7 +7922,7 @@ void CodeGen::genStructReturn(GenTree* treeNode)
for (unsigned i = 0; i < regCount; ++i)
{
var_types type = retTypeDesc.GetReturnRegType(i);
regNumber toReg = retTypeDesc.GetABIReturnReg(i);
regNumber toReg = retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv);
regNumber fromReg = op1->GetRegByIndex(i);
if ((fromReg == REG_NA) && op1->OperIs(GT_COPY))
{
Expand Down Expand Up @@ -8045,6 +8046,16 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode)
assert(regCount == varDsc->lvFieldCnt);
}

#ifdef SWIFT_SUPPORT
const uint32_t* offsets = nullptr;
if (op1->IsCall() && (op1->AsCall()->GetUnmanagedCallConv() == CorInfoCallConvExtension::Swift))
{
const CORINFO_SWIFT_LOWERING* lowering = compiler->GetSwiftLowering(op1->AsCall()->gtRetClsHnd);
assert(!lowering->byReference && (regCount == lowering->numLoweredElements));
offsets = lowering->offsets;
}
#endif

for (unsigned i = 0; i < regCount; ++i)
{
regNumber reg = genConsumeReg(op1, i);
Expand Down Expand Up @@ -8087,6 +8098,12 @@ void CodeGen::genMultiRegStoreToLocal(GenTreeLclVar* lclNode)
#if defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
// should consider the padding field within a struct.
offset = (offset % genTypeSize(srcType)) ? AlignUp(offset, genTypeSize(srcType)) : offset;
#endif
#ifdef SWIFT_SUPPORT
if (offsets != nullptr)
{
offset = offsets[i];
}
#endif
// Several fields could be passed in one register, copy using the register type.
// It could rewrite memory outside of the fields but local on the stack are rounded to POINTER_SIZE so
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/codegenloongarch64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6436,7 +6436,7 @@ void CodeGen::genCall(GenTreeCall* call)
for (unsigned i = 0; i < regCount; ++i)
{
var_types regType = pRetTypeDesc->GetReturnRegType(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i, call->GetUnmanagedCallConv());
regNumber allocatedReg = call->GetRegNumByIdx(i);
inst_Mov(regType, allocatedReg, returnReg, /* canSkip */ true);
}
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/codegenriscv64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6509,7 +6509,7 @@ void CodeGen::genCall(GenTreeCall* call)
for (unsigned i = 0; i < regCount; ++i)
{
var_types regType = pRetTypeDesc->GetReturnRegType(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i);
returnReg = pRetTypeDesc->GetABIReturnReg(i, call->GetUnmanagedCallConv());
regNumber allocatedReg = call->GetRegNumByIdx(i);
inst_Mov(regType, allocatedReg, returnReg, /* canSkip */ true);
}
Expand Down
12 changes: 6 additions & 6 deletions src/coreclr/jit/codegenxarch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ void CodeGen::genEmitGSCookieCheck(bool pushReg)

for (unsigned i = 0; i < regCount; ++i)
{
gcInfo.gcMarkRegPtrVal(retTypeDesc.GetABIReturnReg(i), retTypeDesc.GetReturnRegType(i));
gcInfo.gcMarkRegPtrVal(retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv),
retTypeDesc.GetReturnRegType(i));
}
}
}
Expand Down Expand Up @@ -1318,8 +1319,8 @@ void CodeGen::genSIMDSplitReturn(GenTree* src, ReturnTypeDesc* retTypeDesc)
// This is a case of operand is in a single reg and needs to be
// returned in multiple ABI return registers.
regNumber opReg = src->GetRegNum();
regNumber reg0 = retTypeDesc->GetABIReturnReg(0);
regNumber reg1 = retTypeDesc->GetABIReturnReg(1);
regNumber reg0 = retTypeDesc->GetABIReturnReg(0, compiler->info.compCallConv);
regNumber reg1 = retTypeDesc->GetABIReturnReg(1, compiler->info.compCallConv);

assert((reg0 != REG_NA) && (reg1 != REG_NA) && (opReg != REG_NA));

Expand Down Expand Up @@ -2247,7 +2248,6 @@ void CodeGen::genMultiRegStoreToSIMDLocal(GenTreeLclVar* lclNode)
// This case is always a call (AsCall() will assert if it is not).
GenTreeCall* call = actualOp1->AsCall();
const ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc();
assert(retTypeDesc->GetReturnRegCount() == MAX_RET_REG_COUNT);

assert(regCount == 2);
regNumber targetReg = lclNode->GetRegNum();
Expand Down Expand Up @@ -6063,7 +6063,7 @@ void CodeGen::genCall(GenTreeCall* call)
for (unsigned i = 0; i < regCount; ++i)
{
var_types regType = retTypeDesc->GetReturnRegType(i);
returnReg = retTypeDesc->GetABIReturnReg(i);
returnReg = retTypeDesc->GetABIReturnReg(i, call->GetUnmanagedCallConv());
regNumber allocatedReg = call->GetRegNumByIdx(i);
inst_Mov(regType, allocatedReg, returnReg, /* canSkip */ true);
}
Expand All @@ -6074,7 +6074,7 @@ void CodeGen::genCall(GenTreeCall* call)
// the native compiler doesn't guarantee it.
if (call->IsUnmanaged() && (returnType == TYP_SIMD12))
{
returnReg = retTypeDesc->GetABIReturnReg(1);
returnReg = retTypeDesc->GetABIReturnReg(1, call->GetUnmanagedCallConv());
genSimd12UpperClear(returnReg);
}
#endif // FEATURE_SIMD
Expand Down
40 changes: 40 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,42 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd,
}
assert(structSize > 0);

#ifdef SWIFT_SUPPORT
if (callConv == CorInfoCallConvExtension::Swift)
{
const CORINFO_SWIFT_LOWERING* lowering = GetSwiftLowering(clsHnd);
if (lowering->byReference)
{
howToReturnStruct = SPK_ByReference;
useType = TYP_UNKNOWN;
}
else if (lowering->numLoweredElements == 1)
{
useType = JITtype2varType(lowering->loweredElements[0]);
if (genTypeSize(useType) == structSize)
{
howToReturnStruct = SPK_PrimitiveType;
}
else
{
howToReturnStruct = SPK_EnclosingType;
}
}
else
{
howToReturnStruct = SPK_ByValue;
useType = TYP_STRUCT;
}

if (wbReturnStruct != nullptr)
{
*wbReturnStruct = howToReturnStruct;
}

return useType;
}
#endif

#ifdef UNIX_AMD64_ABI
// An 8-byte struct may need to be returned in a floating point register
// So we always consult the struct "Classifier" routine
Expand Down Expand Up @@ -1950,6 +1986,10 @@ void Compiler::compInit(ArenaAllocator* pAlloc,
fgSsaValid = false;
fgVNPassesCompleted = 0;

#ifdef SWIFT_SUPPORT
m_swiftLoweringCache = nullptr;
#endif

// check that HelperCallProperties are initialized

assert(s_helperCallProperties.IsPure(CORINFO_HELP_GETSHARED_GCSTATIC_BASE));
Expand Down
6 changes: 6 additions & 0 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -5504,6 +5504,12 @@ class Compiler
return m_signatureToLookupInfoMap;
}

#ifdef SWIFT_SUPPORT
typedef JitHashTable<CORINFO_CLASS_HANDLE, JitPtrKeyFuncs<struct CORINFO_CLASS_STRUCT_>, CORINFO_SWIFT_LOWERING*> SwiftLoweringMap;
SwiftLoweringMap* m_swiftLoweringCache;
const CORINFO_SWIFT_LOWERING* GetSwiftLowering(CORINFO_CLASS_HANDLE clsHnd);
#endif

void optRecordLoopMemoryDependence(GenTree* tree, BasicBlock* block, ValueNum memoryVN);
void optCopyLoopMemoryDependence(GenTree* fromTree, GenTree* toTree);

Expand Down
86 changes: 80 additions & 6 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,15 @@ regNumber CallArgs::GetCustomRegister(Compiler* comp, CorInfoCallConvExtension c
}
}

#if defined(TARGET_AMD64) && defined(SWIFT_SUPPORT)
// TODO-Cleanup: Unify this with hasFixedRetBuffReg. That will
// likely be necessary for the reverse pinvoke support regardless.
if (cc == CorInfoCallConvExtension::Swift)
{
return REG_SWIFT_ARG_RET_BUFF;
}
#endif

break;

case WellKnownArg::VirtualStubCell:
Expand Down Expand Up @@ -26841,6 +26850,14 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp,
{
assert(varTypeIsStruct(returnType));

#ifdef SWIFT_SUPPORT
if (callConv == CorInfoCallConvExtension::Swift)
{
InitializeSwiftReturnRegs(comp, retClsHnd);
break;
}
#endif

#ifdef UNIX_AMD64_ABI

SYSTEMV_AMD64_CORINFO_STRUCT_REG_PASSING_DESCRIPTOR structDesc;
Expand Down Expand Up @@ -26944,6 +26961,31 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp,
#endif
}

#ifdef SWIFT_SUPPORT
//---------------------------------------------------------------------------------------
// InitializeSwiftReturnRegs:
// Initialize the Return Type Descriptor for a method that returns with the
// Swift calling convention.
//
// Parameters:
// comp - Compiler instance
// clsHnd - Struct type being returned
//
void ReturnTypeDesc::InitializeSwiftReturnRegs(Compiler* comp, CORINFO_CLASS_HANDLE clsHnd)
{
const CORINFO_SWIFT_LOWERING* lowering = comp->GetSwiftLowering(clsHnd);
assert(!lowering->byReference);

static_assert_no_msg(MAX_SWIFT_LOWERED_ELEMENTS <= MAX_RET_REG_COUNT);
assert(lowering->numLoweredElements <= MAX_RET_REG_COUNT);
Copy link
Member

Choose a reason for hiding this comment

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

MAX_RET_REG_COUNT is already 4 on ARM64, but it might be helpful to the reader to static_assert that here.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a static assert.


for (size_t i = 0; i < lowering->numLoweredElements; i++)
{
m_regType[i] = JITtype2varType(lowering->loweredElements[i]);
}
}
#endif

//---------------------------------------------------------------------------------------
// InitializeLongReturnType:
// Initialize the Return Type Descriptor for a method that returns a TYP_LONG
Expand Down Expand Up @@ -27007,8 +27049,9 @@ void ReturnTypeDesc::InitializeReturnType(Compiler* comp,
// GetABIReturnReg: Return i'th return register as per target ABI
//
// Arguments:
// idx - Index of the return register.
// The first return register has an index of 0 and so on.
// idx - Index of the return register.
// The first return register has an index of 0 and so on.
// callConv - Associated calling convention
//
// Return Value:
// Returns i'th return register as per target ABI.
Expand All @@ -27017,13 +27060,44 @@ void ReturnTypeDesc::InitializeReturnType(Compiler* comp,
// x86 and ARM return long in multiple registers.
// ARM and ARM64 return HFA struct in multiple registers.
//
regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) const
regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx, CorInfoCallConvExtension callConv) const
{
unsigned count = GetReturnRegCount();
assert(idx < count);

regNumber resultReg = REG_NA;

#ifdef SWIFT_SUPPORT
if (callConv == CorInfoCallConvExtension::Swift)
{
static const regNumber swiftIntReturnRegs[] = {REG_SWIFT_INTRET_ORDER};
static const regNumber swiftFloatReturnRegs[] = {REG_SWIFT_FLOATRET_ORDER};
assert((idx < ArrLen(swiftIntReturnRegs)) && (idx < ArrLen(swiftFloatReturnRegs)));
unsigned intRegIdx = 0;
unsigned floatRegIdx = 0;
for (unsigned i = 0; i < idx; i++)
Copy link
Member

Choose a reason for hiding this comment

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

Since this isn't on a hot path, and we have at most 4 return registers for a Swift call, I'm guessing it's not worth caching the return registers used so we can skip this loop? Though we do call GetABIReturnReg while looping over all return register indices in a few places...

Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally we would keep all the registers and also the offsets as part of this type, from a simple code hygiene standpoint, but increasing its size increases the size of GenTreeCall which increases the size of all large nodes. I think it would be good to clean this up to make the additional data for ReturnTypeDesc allocated only when necessary, but that's a separate change.

I don't think there's anything to be concerned about wrt. throughput here.

{
if (varTypeUsesIntReg(GetReturnRegType(i)))
{
intRegIdx++;
}
else
{
floatRegIdx++;
}
}

if (varTypeUsesIntReg(GetReturnRegType(idx)))
{
return swiftIntReturnRegs[intRegIdx];
}
else
{
return swiftFloatReturnRegs[floatRegIdx];
}
}
#endif

#ifdef UNIX_AMD64_ABI
var_types regType0 = GetReturnRegType(0);

Expand Down Expand Up @@ -27171,7 +27245,7 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) const
// GetABIReturnRegs: get the mask of return registers as per target arch ABI.
//
// Arguments:
// None
// callConv - The calling convention
//
// Return Value:
// reg mask of return registers in which the return type is returned.
Expand All @@ -27181,14 +27255,14 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) const
// of return registers and wants to know the set of return registers.
//
// static
regMaskTP ReturnTypeDesc::GetABIReturnRegs() const
regMaskTP ReturnTypeDesc::GetABIReturnRegs(CorInfoCallConvExtension callConv) const
{
regMaskTP resultMask = RBM_NONE;

unsigned count = GetReturnRegCount();
for (unsigned i = 0; i < count; ++i)
{
resultMask |= genRegMask(GetABIReturnReg(i));
resultMask |= genRegMask(GetABIReturnReg(i, callConv));
}

return resultMask;
Expand Down
Loading