Skip to content

Commit

Permalink
JIT: Add support for frozen structs in Swift reverse pinvokes (dotnet…
Browse files Browse the repository at this point in the history
…#100344)

This adds the final support for frozen structs in UCO methods.

This passes 2500 auto generated tests locally on both macOS x64 and arm64. This
PR includes only 100 tests.

Frozen struct parameters are handled via the new ABI representation added in
dotnet#100138. When such a parameter exists we always allocate space for it on the
local stack frame. The struct is then reassembled from its passed constituents
as the first thing in the codegen.

One complication is that there can be an arbitrary amount of codegen to handle
this reassembling. We cannot handle an arbitrary amount of codegen in the
prolog, so the reassembling is handled in two places. First, since the amount of
register passed data is limited, we handle those in the prolog (which frees them
up to be used for other things). If some pieces were passed on the stack the JIT
then ensures that there is a scratch BB and generates the code to reassemble the
remaining parts as the first thing in the scratch BB.

Since Swift structs can be passed by reference in certain cases this PR also
enables `FEATURE_IMPLICIT_BYREFS` for SysV x64 to handle those cases. Depending
on the TP impact we can refine some of the ifdefs around this.
  • Loading branch information
jakobbotsch committed Apr 5, 2024
1 parent 1a7904e commit a971763
Show file tree
Hide file tree
Showing 14 changed files with 12,637 additions and 435 deletions.
152 changes: 152 additions & 0 deletions src/coreclr/jit/abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,28 @@ regNumber ABIPassingSegment::GetRegister() const
return m_register;
}

//-----------------------------------------------------------------------------
// GetRegisterMask:
// Get the mask of registers that this segment is passed in.
//
// Return Value:
// The register mask.
//
regMaskTP ABIPassingSegment::GetRegisterMask() const
{
assert(IsPassedInRegister());
regMaskTP reg = genRegMask(m_register);

#ifdef TARGET_ARM
if (genIsValidFloatReg(m_register) && (Size == 8))
{
reg |= genRegMask(REG_NEXT(m_register));
}
#endif

return reg;
}

//-----------------------------------------------------------------------------
// GetStackOffset:
// Get the stack offset where this segment is passed.
Expand All @@ -54,6 +76,53 @@ unsigned ABIPassingSegment::GetStackOffset() const
return m_stackOffset;
}

//-----------------------------------------------------------------------------
// GetRegisterStoreType:
// Return a type that can be used to store from the register this segment is
// in, taking the segment's size into account.
//
// Return Value:
// A type that matches ABIPassingSegment::Size and the register type.
//
var_types ABIPassingSegment::GetRegisterStoreType() const
{
assert(IsPassedInRegister());
if (genIsValidFloatReg(m_register))
{
switch (Size)
{
case 4:
return TYP_FLOAT;
case 8:
return TYP_DOUBLE;
#ifdef FEATURE_SIMD
case 16:
return TYP_SIMD16;
#endif
default:
return TYP_UNDEF;
}
}
else
{
switch (Size)
{
case 1:
return TYP_UBYTE;
case 2:
return TYP_USHORT;
case 4:
return TYP_INT;
#ifdef TARGET_64BIT
case 8:
return TYP_LONG;
#endif
default:
return TYP_UNDEF;
}
}
}

//-----------------------------------------------------------------------------
// InRegister:
// Create an ABIPassingSegment representing that a segment is passed in a
Expand Down Expand Up @@ -101,6 +170,56 @@ ABIPassingSegment ABIPassingSegment::OnStack(unsigned stackOffset, unsigned offs
return segment;
}

//-----------------------------------------------------------------------------
// HasAnyRegisterSegment:
// Check if any part of this value is passed in a register.
//
// Return Value:
// True if so.
//
bool ABIPassingInformation::HasAnyRegisterSegment() const
{
for (unsigned i = 0; i < NumSegments; i++)
{
if (Segments[i].IsPassedInRegister())
{
return true;
}
}
return false;
}

//-----------------------------------------------------------------------------
// HasAnyStackSegment:
// Check if any part of this value is passed on the stack.
//
// Return Value:
// True if so.
//
bool ABIPassingInformation::HasAnyStackSegment() const
{
for (unsigned i = 0; i < NumSegments; i++)
{
if (Segments[i].IsPassedOnStack())
{
return true;
}
}
return false;
}

//-----------------------------------------------------------------------------
// HasExactlyOneStackSegment:
// Check if this value is passed as a single stack segment.
//
// Return Value:
// True if so.
//
bool ABIPassingInformation::HasExactlyOneStackSegment() const
{
return (NumSegments == 1) && Segments[0].IsPassedOnStack();
}

//-----------------------------------------------------------------------------
// IsSplitAcrossRegistersAndStack:
// Check if this ABIPassingInformation represents passing a value in both
Expand Down Expand Up @@ -253,6 +372,39 @@ ABIPassingInformation SwiftABIClassifier::Classify(Compiler* comp,
TARGET_POINTER_SIZE));
}

if (type == TYP_STRUCT)
{
const CORINFO_SWIFT_LOWERING* lowering = comp->GetSwiftLowering(structLayout->GetClassHandle());
if (lowering->byReference)
{
return m_classifier.Classify(comp, TYP_I_IMPL, nullptr, WellKnownArg::None);
}

ArrayStack<ABIPassingSegment> segments(comp->getAllocator(CMK_ABI));
for (unsigned i = 0; i < lowering->numLoweredElements; i++)
{
var_types elemType = JITtype2varType(lowering->loweredElements[i]);
ABIPassingInformation elemInfo = m_classifier.Classify(comp, elemType, nullptr, WellKnownArg::None);

for (unsigned j = 0; j < elemInfo.NumSegments; j++)
{
ABIPassingSegment newSegment = elemInfo.Segments[j];
newSegment.Offset += lowering->offsets[i];
segments.Push(newSegment);
}
}

ABIPassingInformation result;
result.NumSegments = static_cast<unsigned>(segments.Height());
result.Segments = new (comp, CMK_ABI) ABIPassingSegment[result.NumSegments];
for (int i = 0; i < segments.Height(); i++)
{
result.Segments[i] = segments.Bottom(i);
}

return result;
}

return m_classifier.Classify(comp, type, structLayout, wellKnownParam);
}
#endif
7 changes: 7 additions & 0 deletions src/coreclr/jit/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@ class ABIPassingSegment
// If this segment is passed in a register, return the particular register.
regNumber GetRegister() const;

regMaskTP GetRegisterMask() const;

// If this segment is passed on the stack then return the particular stack
// offset, relative to the first stack argument's offset.
unsigned GetStackOffset() const;

var_types GetRegisterStoreType() const;

static ABIPassingSegment InRegister(regNumber reg, unsigned offset, unsigned size);
static ABIPassingSegment OnStack(unsigned stackOffset, unsigned offset, unsigned size);
};
Expand All @@ -47,6 +51,9 @@ struct ABIPassingInformation
unsigned NumSegments = 0;
ABIPassingSegment* Segments = nullptr;

bool HasAnyRegisterSegment() const;
bool HasAnyStackSegment() const;
bool HasExactlyOneStackSegment() const;
bool IsSplitAcrossRegistersAndStack() const;

static ABIPassingInformation FromSegment(Compiler* comp, const ABIPassingSegment& segment);
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,9 @@ class CodeGen final : public CodeGenInterface
#else
void genEnregisterOSRArgsAndLocals();
#endif

void genHomeSwiftStructParameters(bool handleStack);

void genCheckUseBlockInit();
#if defined(UNIX_AMD64_ABI) && defined(FEATURE_SIMD)
void genClearStackVec3ArgUpperBits();
Expand Down
135 changes: 123 additions & 12 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4963,6 +4963,110 @@ void CodeGen::genEnregisterOSRArgsAndLocals()
}
}

#ifdef SWIFT_SUPPORT

//-----------------------------------------------------------------------------
// genHomeSwiftStructParameters:
// Reassemble Swift struct parameters if necessary.
//
// Parameters:
// handleStack - If true, reassemble the segments that were passed on the stack.
// If false, reassemble the segments that were passed in registers.
//
void CodeGen::genHomeSwiftStructParameters(bool handleStack)
{
for (unsigned lclNum = 0; lclNum < compiler->info.compArgsCount; lclNum++)
{
if (lclNum == compiler->lvaSwiftSelfArg)
{
continue;
}

LclVarDsc* dsc = compiler->lvaGetDesc(lclNum);
if ((dsc->TypeGet() != TYP_STRUCT) || compiler->lvaIsImplicitByRefLocal(lclNum) || !dsc->lvOnFrame)
{
continue;
}

JITDUMP("Homing Swift parameter V%02u: ", lclNum);
const ABIPassingInformation& abiInfo = compiler->lvaParameterPassingInfo[lclNum];
DBEXEC(VERBOSE, abiInfo.Dump());

for (unsigned i = 0; i < abiInfo.NumSegments; i++)
{
const ABIPassingSegment& seg = abiInfo.Segments[i];
if (seg.IsPassedOnStack() != handleStack)
{
continue;
}

if (seg.IsPassedInRegister())
{
RegState* regState = genIsValidFloatReg(seg.GetRegister()) ? &floatRegState : &intRegState;
regMaskTP regs = seg.GetRegisterMask();

if ((regState->rsCalleeRegArgMaskLiveIn & regs) != RBM_NONE)
{
var_types storeType = seg.GetRegisterStoreType();
assert(storeType != TYP_UNDEF);
GetEmitter()->emitIns_S_R(ins_Store(storeType), emitTypeSize(storeType), seg.GetRegister(), lclNum,
seg.Offset);

regState->rsCalleeRegArgMaskLiveIn &= ~regs;
}
}
else
{
var_types loadType = TYP_UNDEF;
switch (seg.Size)
{
case 1:
loadType = TYP_UBYTE;
break;
case 2:
loadType = TYP_USHORT;
break;
case 4:
loadType = TYP_INT;
break;
case 8:
loadType = TYP_LONG;
break;
default:
assert(!"Unexpected segment size for struct parameter not passed implicitly by ref");
continue;
}

int offset;
if (isFramePointerUsed())
{
offset = -genCallerSPtoFPdelta();
}
else
{
offset = -genCallerSPtoInitialSPdelta();
}

offset += (int)seg.GetStackOffset();

// Move the incoming segment to the local stack frame. We can
// use REG_SCRATCH as a temporary register here as we ensured
// that during LSRA build.
#ifdef TARGET_XARCH
GetEmitter()->emitIns_R_AR(ins_Load(loadType), emitTypeSize(loadType), REG_SCRATCH,
genFramePointerReg(), offset);
#else
genInstrWithConstant(ins_Load(loadType), emitTypeSize(loadType), REG_SCRATCH, genFramePointerReg(),
offset, REG_SCRATCH);
#endif

GetEmitter()->emitIns_S_R(ins_Store(loadType), emitTypeSize(loadType), REG_SCRATCH, lclNum, seg.Offset);
}
}
}
}
#endif

/*-----------------------------------------------------------------------------
*
* Save the generic context argument.
Expand Down Expand Up @@ -6133,18 +6237,6 @@ void CodeGen::genFnProlog()
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SECRET_STUB_PARAM;
}

#ifdef SWIFT_SUPPORT
if ((compiler->lvaSwiftSelfArg != BAD_VAR_NUM) && ((intRegState.rsCalleeRegArgMaskLiveIn & RBM_SWIFT_SELF) != 0))
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SWIFT_SELF, compiler->lvaSwiftSelfArg, 0);
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_SELF;
}
else if (compiler->lvaSwiftErrorArg != BAD_VAR_NUM)
{
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_ERROR;
}
#endif

//
// Zero out the frame as needed
//
Expand Down Expand Up @@ -6236,6 +6328,25 @@ void CodeGen::genFnProlog()
* Take care of register arguments first
*/

#ifdef SWIFT_SUPPORT
if (compiler->info.compCallConv == CorInfoCallConvExtension::Swift)
{
if ((compiler->lvaSwiftSelfArg != BAD_VAR_NUM) &&
((intRegState.rsCalleeRegArgMaskLiveIn & RBM_SWIFT_SELF) != 0))
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SWIFT_SELF, compiler->lvaSwiftSelfArg, 0);
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_SELF;
}

if (compiler->lvaSwiftErrorArg != BAD_VAR_NUM)
{
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_ERROR;
}

genHomeSwiftStructParameters(/* handleStack */ false);
}
#endif

// Home incoming arguments and generate any required inits.
// OSR handles this by moving the values from the original frame.
//
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/codegenlinear.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,16 @@ void CodeGen::genCodeForBBlist()
compiler->compCurStmt = nullptr;
compiler->compCurLifeTree = nullptr;

#ifdef SWIFT_SUPPORT
// Reassemble Swift struct parameters on the local stack frame in the
// scratch BB right after the prolog. There can be arbitrary amounts of
// codegen related to doing this, so it cannot be done in the prolog.
if (compiler->fgBBisScratch(block) && compiler->lvaHasAnySwiftStackParamToReassemble())
{
genHomeSwiftStructParameters(/* handleStack */ true);
}
#endif

// Emit poisoning into scratch BB that comes right after prolog.
// We cannot emit this code in the prolog as it might make the prolog too large.
if (compiler->compShouldPoisonFrame() && compiler->fgBBisScratch(block))
Expand Down
Loading

0 comments on commit a971763

Please sign in to comment.