Skip to content

Commit

Permalink
Support frozen struct returns for Swift calls (#99704)
Browse files Browse the repository at this point in the history
Adds support for pinvokes to Swift functions that return frozen structs in
multiple registers. This turned out to be simpler than I expected on the JIT
side; there is a small change necessary in `genMultiRegStoreToLocal` to take into
account that the Swift fields are going into offsets that don't necessarily
correspond to the register sizes (we already DNER the cases where things don't
work out, it seems).

Also adds 100 tests.

The support is complicated by the fact that Swift calls take the ret buffer in
rax on x64. This requires some VM side changes to avoid using rax in the interop
thunks.
  • Loading branch information
jakobbotsch committed Mar 14, 2024
1 parent 172ecdf commit 0ea9b8f
Show file tree
Hide file tree
Showing 24 changed files with 5,826 additions and 69 deletions.
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 @@ -26856,6 +26865,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 @@ -26959,6 +26976,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);

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 @@ -27022,8 +27064,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 @@ -27032,13 +27075,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++)
{
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 @@ -27186,7 +27260,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 @@ -27196,14 +27270,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

0 comments on commit 0ea9b8f

Please sign in to comment.