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

JIT: Add basic support for Swift reverse pinvokes #100018

Merged
merged 16 commits into from
Mar 21, 2024
Merged
20 changes: 20 additions & 0 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2987,6 +2987,18 @@ void CodeGen::genFnPrologCalleeRegArgs(regNumber xtraReg, bool* pXtraRegClobbere
}
}

#ifdef SWIFT_SUPPORT
// The Swift self parameter is passed in a callee save register and is
// not part of the arg register order that this function relies on to
// handle conflicts. For this reason we always mark it as DNER and
// handle it outside the normal register arguments.
// TODO-CQ: Fix this.
if (varNum == compiler->lvaSwiftSelfArg)
{
continue;
}
#endif

var_types regType = compiler->mangleVarArgsType(varDsc->TypeGet());
// Change regType to the HFA type when we have a HFA argument
if (varDsc->lvIsHfaRegArg())
Expand Down Expand Up @@ -6131,6 +6143,14 @@ void CodeGen::genFnProlog()
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SECRET_STUB_PARAM;
}

#ifdef SWIFT_SUPPORT
if ((compiler->lvaSwiftSelfArg != BAD_VAR_NUM) && ((intRegState.rsCalleeRegArgMaskLiveIn & RBM_SWIFT_SELF) != 0))
Copy link
Member

Choose a reason for hiding this comment

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

Are there situations where lvaSwiftSelfArg is valid, but the reg mask doesn't include the Swift self reg? If not, maybe we should assert that the reg mask includes it if lvaSwiftSelfArg is set.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, if lvaSwiftSelfArg is dead on entry then it shouldn't be in the live-in set.

{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SWIFT_SELF, compiler->lvaSwiftSelfArg, 0);
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SWIFT_SELF;
}
#endif

//
// Zero out the frame as needed
//
Expand Down
5 changes: 5 additions & 0 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10825,6 +10825,10 @@ void Compiler::EnregisterStats::RecordLocal(const LclVarDsc* varDsc)
m_simdUserForcesDep++;
break;

case DoNotEnregisterReason::NonStandardParameter:
m_nonStandardParameter++;
break;

default:
unreached();
break;
Expand Down Expand Up @@ -10952,6 +10956,7 @@ void Compiler::EnregisterStats::Dump(FILE* fout) const
PRINT_STATS(m_returnSpCheck, notEnreg);
PRINT_STATS(m_callSpCheck, notEnreg);
PRINT_STATS(m_simdUserForcesDep, notEnreg);
PRINT_STATS(m_nonStandardParameter, notEnreg);

fprintf(fout, "\nAddr exposed details:\n");
if (m_addrExposed == 0)
Expand Down
20 changes: 12 additions & 8 deletions src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -456,13 +456,14 @@ enum class DoNotEnregisterReason
#endif
LclAddrNode, // the local is accessed with LCL_ADDR_VAR/FLD.
CastTakesAddr,
StoreBlkSrc, // the local is used as STORE_BLK source.
SwizzleArg, // the local is passed using LCL_FLD as another type.
BlockOpRet, // the struct is returned and it promoted or there is a cast.
ReturnSpCheck, // the local is used to do SP check on return from function
CallSpCheck, // the local is used to do SP check on every call
SimdUserForcesDep, // a promoted struct was used by a SIMD/HWI node; it must be dependently promoted
HiddenBufferStructArg // the argument is a hidden return buffer passed to a method.
StoreBlkSrc, // the local is used as STORE_BLK source.
SwizzleArg, // the local is passed using LCL_FLD as another type.
BlockOpRet, // the struct is returned and it promoted or there is a cast.
ReturnSpCheck, // the local is used to do SP check on return from function
CallSpCheck, // the local is used to do SP check on every call
SimdUserForcesDep, // a promoted struct was used by a SIMD/HWI node; it must be dependently promoted
HiddenBufferStructArg, // the argument is a hidden return buffer passed to a method.
NonStandardParameter, // local is a parameter that is passed in a register unhandled by genFnPrologCalleeRegArgs
};

enum class AddressExposedReason
Expand All @@ -489,7 +490,6 @@ class LclVarDsc
// The constructor. Most things can just be zero'ed.
//
// Initialize the ArgRegs to REG_STK.
// Morph will update if this local is passed in a register.
LclVarDsc()
: _lvArgReg(REG_STK)
#if FEATURE_MULTIREG_ARGS
Expand Down Expand Up @@ -3859,6 +3859,7 @@ class Compiler
// mechanism passes the address of the return address to a runtime helper
// where it is used to detect tail-call chains.
unsigned lvaRetAddrVar;
unsigned lvaSwiftSelfArg;
Copy link
Member

Choose a reason for hiding this comment

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

Since all the places where we check lvaSwiftSelfArg are under #ifdef SWIFT_SUPPORT, should we ifdef this, and where we initialize it in Compiler::lvaInit?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'll do that.


#if defined(DEBUG) && defined(TARGET_XARCH)

Expand Down Expand Up @@ -3982,6 +3983,8 @@ class Compiler
CORINFO_ARG_LIST_HANDLE varList,
CORINFO_SIG_INFO* varSig);

bool lvaInitSpecialSwiftParam(InitVarDscInfo* varDscInfo, CorInfoType type, CORINFO_CLASS_HANDLE typeHnd);
Copy link
Member

Choose a reason for hiding this comment

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

Based on the naming of this method, I guess we'll be using this to do the SwiftError* type lookup too...

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was thinking that same logic could live here.


var_types lvaGetActualType(unsigned lclNum);
var_types lvaGetRealType(unsigned lclNum);

Expand Down Expand Up @@ -10636,6 +10639,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
unsigned m_returnSpCheck;
unsigned m_callSpCheck;
unsigned m_simdUserForcesDep;
unsigned m_nonStandardParameter;
unsigned m_liveInOutHndlr;
unsigned m_depField;
unsigned m_noRegVars;
Expand Down
62 changes: 58 additions & 4 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ void Compiler::lvaInit()
lvaArg0Var = BAD_VAR_NUM;
lvaMonAcquired = BAD_VAR_NUM;
lvaRetAddrVar = BAD_VAR_NUM;
lvaSwiftSelfArg = BAD_VAR_NUM;

lvaInlineeReturnSpillTemp = BAD_VAR_NUM;

Expand Down Expand Up @@ -622,6 +623,17 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un
lvaSetClass(varDscInfo->varNum, clsHnd);
}

// The final home for this incoming parameter might be our local stack frame.
varDsc->lvOnFrame = true;

#ifdef SWIFT_SUPPORT
if ((info.compCallConv == CorInfoCallConvExtension::Swift) &&
lvaInitSpecialSwiftParam(varDscInfo, strip(corInfoType), typeHnd))
{
continue;
}
#endif

// For ARM, ARM64, LOONGARCH64, RISCV64 and AMD64 varargs, all arguments go in integer registers
var_types argType = mangleVarArgsType(varDsc->TypeGet());

Expand Down Expand Up @@ -821,10 +833,6 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un
}
#endif // UNIX_AMD64_ABI

// The final home for this incoming register might be our local stack frame.
// For System V platforms the final home will always be on the local stack frame.
varDsc->lvOnFrame = true;

bool canPassArgInRegisters = false;

#if defined(UNIX_AMD64_ABI)
Expand Down Expand Up @@ -1301,6 +1309,48 @@ void Compiler::lvaInitUserArgs(InitVarDscInfo* varDscInfo, unsigned skipArgs, un
#endif // TARGET_ARM
}

#ifdef SWIFT_SUPPORT
//-----------------------------------------------------------------------------
// lvaInitSpecialSwiftParam:
// If the parameter is a special Swift parameter then initialize it and return true.
//
// Parameters:
// varDsc - LclVarDsc* for the parameter
// type - Type of the parameter
// typeHnd - Class handle for the type of the parameter
//
// Remarks:
// Handles SwiftSelf.
//
bool Compiler::lvaInitSpecialSwiftParam(InitVarDscInfo* varDscInfo, CorInfoType type, CORINFO_CLASS_HANDLE typeHnd)
{
if (type != CORINFO_TYPE_VALUECLASS)
{
return false;
}

if (!info.compCompHnd->isIntrinsicType(typeHnd))
{
return false;
}

const char* namespaceName;
const char* className = info.compCompHnd->getClassNameFromMetadata(typeHnd, &namespaceName);
if ((strcmp(className, "SwiftSelf") == 0) && (strcmp(namespaceName, "System.Runtime.InteropServices.Swift") == 0))
{
LclVarDsc* varDsc = varDscInfo->varDsc;
varDsc->SetArgReg(REG_SWIFT_SELF);
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean that the reg value is loaded into the SwiftSelf argument?

Copy link
Member Author

Choose a reason for hiding this comment

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

The JIT can directly reference and use IL locals in its internal IR, and each of the parameter locals are marked with information about how they're passed (in a register or on stack). That's what the code here is doing.

It's ultimately up to the register allocator to decide what register every local is allocated into. For parameter locals LSRA will prefer to keep it in the same register that it was passed in, but it might also be allocated to another register (in which case a move from the initial register is needed in the prolog) or to stack (in which case a store to the stack frame is needed).

The PR here doesn't allow enregistering SwiftSelf so we will always end up keeping it on the stack frame. That's because the case where there are conflicts in the move graph is not yet handled for this register (e.g. we can have cases where the SwiftSelf register x20 needs to go to x0 and x0 needs to go to x20). It needs a bit of work but I was expecting to work on the relevant logic due to another issue, so I will handle both of these when I do that work.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, thanks for info.

varDsc->SetOtherArgReg(REG_NA);
varDsc->lvIsRegArg = true;
lvaSwiftSelfArg = varDscInfo->varNum;
lvaSetVarDoNotEnregister(lvaSwiftSelfArg DEBUGARG(DoNotEnregisterReason::NonStandardParameter));
return true;
}

return false;
}
#endif

/*****************************************************************************/
void Compiler::lvaInitGenericsCtxt(InitVarDscInfo* varDscInfo)
{
Expand Down Expand Up @@ -2752,6 +2802,10 @@ void Compiler::lvaSetVarDoNotEnregister(unsigned varNum DEBUGARG(DoNotEnregister
JITDUMP("Promoted struct used by a SIMD/HWI node\n");
break;

case DoNotEnregisterReason::NonStandardParameter:
JITDUMP("Non-standard parameter\n");
break;

default:
unreached();
break;
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/jit/regalloc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ regNumber Compiler::raUpdateRegStateForArg(RegState* regState, LclVarDsc* argDsc
// We should have recorded the variable number for the return buffer arg
noway_assert(info.compRetBuffArg != BAD_VAR_NUM);
}
#ifdef SWIFT_SUPPORT
else if ((info.compCallConv == CorInfoCallConvExtension::Swift) && (inArgReg == REG_SWIFT_SELF))
{
noway_assert((lvaSwiftSelfArg != BAD_VAR_NUM) &&
((argDsc == lvaGetDesc(lvaSwiftSelfArg)) ||
(argDsc->lvIsStructField && argDsc->lvParentLcl == lvaSwiftSelfArg)));
}
#endif
else // we have a regular arg
{
noway_assert(inArgMask & RBM_ARG_REGS);
Expand Down
9 changes: 0 additions & 9 deletions src/coreclr/jit/scopeinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1721,8 +1721,6 @@ void CodeGen::psiBegProlog()
regNumber otherRegNum = REG_NA;
for (unsigned nCnt = 0; nCnt < structDesc.eightByteCount; nCnt++)
{
var_types regType = TYP_UNDEF;

if (nCnt == 0)
{
regNum = lclVarDsc->GetArgReg();
Expand All @@ -1735,12 +1733,6 @@ void CodeGen::psiBegProlog()
{
assert(false && "Invalid eightbyte number.");
}

regType = compiler->GetEightByteType(structDesc, nCnt);
#ifdef DEBUG
regType = compiler->mangleVarArgsType(regType);
assert(genMapRegNumToRegArgNum((nCnt == 0 ? regNum : otherRegNum), regType) != (unsigned)-1);
#endif // DEBUG
Comment on lines -1738 to -1743
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 have removed this assert in a couple of places because I don't think it is asserting anything meaningful. The existence and mapping of genMapRegNumToRegArgNum is really an internal detail of how genFnPrologCalleeRegArgs works, and I expect to be able to remove it once that function is rewritten.

}

varLocation.storeVariableInRegisters(regNum, otherRegNum);
Expand Down Expand Up @@ -1789,7 +1781,6 @@ void CodeGen::psiBegProlog()
regType = lclVarDsc->GetHfaType();
}
#endif // defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
assert(genMapRegNumToRegArgNum(lclVarDsc->GetArgReg(), regType) != (unsigned)-1);
#endif // DEBUG
varLocation.storeVariableInRegisters(lclVarDsc->GetArgReg(), REG_NA);
}
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/targetamd64.h
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@
#define REG_SWIFT_ERROR REG_R12
#define RBM_SWIFT_ERROR RBM_R12
#define REG_SWIFT_SELF REG_R13
#define RBM_SWIFT_SELF RBM_R13

#define REG_SWIFT_INTRET_ORDER REG_RAX,REG_RDX,REG_RCX,REG_R8
#define REG_SWIFT_FLOATRET_ORDER REG_XMM0,REG_XMM1,REG_XMM2,REG_XMM3
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/targetarm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@
#define REG_SWIFT_ERROR REG_R21
#define RBM_SWIFT_ERROR RBM_R21
#define REG_SWIFT_SELF REG_R20
#define RBM_SWIFT_SELF RBM_R20
#define REG_SWIFT_INTRET_ORDER REG_R0,REG_R1,REG_R2,REG_R3
#define REG_SWIFT_FLOATRET_ORDER REG_V0,REG_V1,REG_V2,REG_V3

Expand Down
1 change: 1 addition & 0 deletions src/tests/Interop/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,5 @@ if(CLR_CMAKE_TARGET_APPLE)
add_subdirectory(Swift/SwiftInvalidCallConv)
add_subdirectory(Swift/SwiftAbiStress)
add_subdirectory(Swift/SwiftRetAbiStress)
add_subdirectory(Swift/SwiftCallbackAbiStress)
endif()
21 changes: 21 additions & 0 deletions src/tests/Interop/Swift/SwiftCallbackAbiStress/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
project(SwiftCallbackAbiStress)
include ("${CLR_INTEROP_TEST_ROOT}/Interop.cmake")

set(SOURCE SwiftCallbackAbiStress)

if (NOT SWIFT_COMPILER_TARGET AND CLR_CMAKE_TARGET_OSX)
set(SWIFT_PLATFORM "macosx")
set(SWIFT_PLATFORM_SUFFIX "")
set(SWIFT_DEPLOYMENT_TARGET ${CMAKE_OSX_DEPLOYMENT_TARGET})
set(SWIFT_COMPILER_TARGET "${CMAKE_OSX_ARCHITECTURES}-apple-${SWIFT_PLATFORM}${SWIFT_DEPLOYMENT_TARGET}${SWIFT_PLATFORM_SUFFIX}")
endif()

add_custom_target(${SOURCE} ALL
COMMAND xcrun swiftc -target ${SWIFT_COMPILER_TARGET} -emit-library ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift -o ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE}.swift
COMMENT "Generating ${SOURCE} library"
)

install(FILES ${CMAKE_CURRENT_BINARY_DIR}/lib${SOURCE}.dylib
DESTINATION bin
)
Loading
Loading