diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index ebcb101663a308..0fa846632a63cd 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -4109,7 +4109,7 @@ GenTree* Compiler::optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeO } //------------------------------------------------------------------------ -// optAssertionProp_Return: Try and optimize a GT_RETURN via assertions. +// optAssertionProp_Return: Try and optimize a GT_RETURN/GT_SWIFT_ERROR_RET via assertions. // // Propagates ZEROOBJ for the return value. // @@ -4124,9 +4124,9 @@ GenTree* Compiler::optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeO // Notes: // stmt may be nullptr during local assertion prop // -GenTree* Compiler::optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeUnOp* ret, Statement* stmt) +GenTree* Compiler::optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeOp* ret, Statement* stmt) { - GenTree* retValue = ret->gtGetOp1(); + GenTree* retValue = ret->GetReturnValue(); // Only propagate zeroes that lowering can deal with. if (!ret->TypeIs(TYP_VOID) && varTypeIsStruct(retValue) && !varTypeIsStruct(info.compRetNativeType)) @@ -5512,7 +5512,8 @@ GenTree* Compiler::optAssertionProp(ASSERT_VALARG_TP assertions, GenTree* tree, return optAssertionProp_BlockStore(assertions, tree->AsBlk(), stmt); case GT_RETURN: - return optAssertionProp_Return(assertions, tree->AsUnOp(), stmt); + case GT_SWIFT_ERROR_RET: + return optAssertionProp_Return(assertions, tree->AsOp(), stmt); case GT_MOD: case GT_DIV: diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 410bc440874e32..ed5d9032b8535a 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1309,6 +1309,10 @@ class CodeGen final : public CodeGenInterface void genReturn(GenTree* treeNode); +#ifdef SWIFT_SUPPORT + void genSwiftErrorReturn(GenTree* treeNode); +#endif // SWIFT_SUPPORT + #ifdef TARGET_XARCH void genStackPointerConstantAdjustment(ssize_t spDelta, bool trackSpAdjustments); void genStackPointerConstantAdjustmentWithProbe(ssize_t spDelta, bool trackSpAdjustments); diff --git a/src/coreclr/jit/codegenarm64.cpp b/src/coreclr/jit/codegenarm64.cpp index cd1b1558d93e64..d4e8d58db741b7 100644 --- a/src/coreclr/jit/codegenarm64.cpp +++ b/src/coreclr/jit/codegenarm64.cpp @@ -2990,15 +2990,15 @@ void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* lclNode) // Note: treeNode's and op1's registers are already consumed. // // Arguments: -// treeNode - The GT_RETURN or GT_RETFILT tree node with non-struct and non-void type +// treeNode - The GT_RETURN/GT_RETFILT/GT_SWIFT_ERROR_RET tree node with non-struct and non-void type // // Return Value: // None // void CodeGen::genSimpleReturn(GenTree* treeNode) { - assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); - GenTree* op1 = treeNode->gtGetOp1(); + assert(treeNode->OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); + GenTree* op1 = treeNode->AsOp()->GetReturnValue(); var_types targetType = treeNode->TypeGet(); assert(targetType != TYP_STRUCT); diff --git a/src/coreclr/jit/codegenarmarch.cpp b/src/coreclr/jit/codegenarmarch.cpp index a9e2a41f73f945..b53ddba5e192fd 100644 --- a/src/coreclr/jit/codegenarmarch.cpp +++ b/src/coreclr/jit/codegenarmarch.cpp @@ -282,6 +282,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genReturn(treeNode); break; +#ifdef SWIFT_SUPPORT + case GT_SWIFT_ERROR_RET: + genSwiftErrorReturn(treeNode); + break; +#endif // SWIFT_SUPPORT + case GT_LEA: // If we are here, it is the case where there is an LEA that cannot be folded into a parent instruction. genLeaInstruction(treeNode->AsAddrMode()); diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index a2a4322148f742..d2709fc4c66144 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -7733,8 +7733,8 @@ GenTreeIntCon CodeGen::intForm(var_types type, ssize_t value) // void CodeGen::genLongReturn(GenTree* treeNode) { - assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); - assert(treeNode->TypeGet() == TYP_LONG); + assert(treeNode->OperIs(GT_RETURN, GT_RETFILT)); + assert(treeNode->TypeIs(TYP_LONG)); GenTree* op1 = treeNode->gtGetOp1(); var_types targetType = treeNode->TypeGet(); @@ -7758,16 +7758,16 @@ void CodeGen::genLongReturn(GenTree* treeNode) // In case of LONG return on 32-bit, delegates to the genLongReturn method. // // Arguments: -// treeNode - The GT_RETURN or GT_RETFILT tree node. +// treeNode - The GT_RETURN/GT_RETFILT/GT_SWIFT_ERROR_RET tree node. // // Return Value: // None // void CodeGen::genReturn(GenTree* treeNode) { - assert(treeNode->OperIs(GT_RETURN, GT_RETFILT)); + assert(treeNode->OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); - GenTree* op1 = treeNode->gtGetOp1(); + GenTree* op1 = treeNode->AsOp()->GetReturnValue(); var_types targetType = treeNode->TypeGet(); // A void GT_RETFILT is the end of a finally. For non-void filter returns we need to load the result in the return @@ -7869,7 +7869,7 @@ void CodeGen::genReturn(GenTree* treeNode) // // There should be a single GT_RETURN while generating profiler ELT callbacks. // - if (treeNode->OperIs(GT_RETURN) && compiler->compIsProfilerHookNeeded()) + if (treeNode->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET) && compiler->compIsProfilerHookNeeded()) { // !! NOTE !! // Since we are invalidating the assumption that we would slip into the epilog @@ -7939,18 +7939,28 @@ void CodeGen::genReturn(GenTree* treeNode) genStackPointerCheck(doStackPointerCheck, compiler->lvaReturnSpCheck); #endif // defined(DEBUG) && defined(TARGET_XARCH) +} #ifdef SWIFT_SUPPORT - // If this method has a SwiftError* out parameter, load the SwiftError pseudolocal value into the error register. - // TODO-CQ: Introduce GenTree node that models returning a normal and Swift error value. - if (compiler->lvaSwiftErrorArg != BAD_VAR_NUM) - { - assert(compiler->info.compCallConv == CorInfoCallConvExtension::Swift); - assert(compiler->lvaSwiftErrorLocal != BAD_VAR_NUM); - GetEmitter()->emitIns_R_S(ins_Load(TYP_I_IMPL), EA_PTRSIZE, REG_SWIFT_ERROR, compiler->lvaSwiftErrorLocal, 0); - } -#endif // SWIFT_SUPPORT +//------------------------------------------------------------------------ +// genSwiftErrorReturn: Generates code for returning the normal return value, +// and loading the SwiftError pseudolocal value in the error register. +// +// Arguments: +// treeNode - The GT_SWIFT_ERROR_RET tree node. +// +// Return Value: +// None +// +void CodeGen::genSwiftErrorReturn(GenTree* treeNode) +{ + assert(treeNode->OperIs(GT_SWIFT_ERROR_RET)); + GenTree* swiftErrorNode = treeNode->gtGetOp1(); + const regNumber errorSrcReg = genConsumeReg(swiftErrorNode); + inst_Mov(swiftErrorNode->TypeGet(), REG_SWIFT_ERROR, errorSrcReg, true, EA_PTRSIZE); + genReturn(treeNode); } +#endif // SWIFT_SUPPORT //------------------------------------------------------------------------ // isStructReturn: Returns whether the 'treeNode' is returning a struct. @@ -7959,15 +7969,15 @@ void CodeGen::genReturn(GenTree* treeNode) // treeNode - The tree node to evaluate whether is a struct return. // // Return Value: -// Returns true if the 'treeNode" is a GT_RETURN node of type struct. +// Returns true if the 'treeNode' is a GT_RETURN/GT_SWIFT_ERROR_RET node of type struct. // Otherwise returns false. // bool CodeGen::isStructReturn(GenTree* treeNode) { - // This method could be called for 'treeNode' of GT_RET_FILT or GT_RETURN. + // This method could be called for 'treeNode' of GT_RET_FILT/GT_RETURN/GT_SWIFT_ERROR_RET. // For the GT_RET_FILT, the return is always a bool or a void, for the end of a finally block. - noway_assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); - if (treeNode->OperGet() != GT_RETURN) + noway_assert(treeNode->OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); + if (!treeNode->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { return false; } @@ -7994,13 +8004,13 @@ bool CodeGen::isStructReturn(GenTree* treeNode) // void CodeGen::genStructReturn(GenTree* treeNode) { - assert(treeNode->OperGet() == GT_RETURN); - - genConsumeRegs(treeNode->gtGetOp1()); + assert(treeNode->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); - GenTree* op1 = treeNode->gtGetOp1(); + GenTree* op1 = treeNode->AsOp()->GetReturnValue(); GenTree* actualOp1 = op1->gtSkipReloadOrCopy(); + genConsumeRegs(op1); + ReturnTypeDesc retTypeDesc = compiler->compRetTypeDesc; const unsigned regCount = retTypeDesc.GetReturnRegCount(); assert(regCount <= MAX_RET_REG_COUNT); diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index 351ca14942838b..97b6ce2d582aad 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -498,9 +498,10 @@ void CodeGen::genCodeForBBlist() // as the determiner because something we are tracking as a byref // might be used as a return value of a int function (which is legal) GenTree* blockLastNode = block->lastNode(); - if ((blockLastNode != nullptr) && (blockLastNode->gtOper == GT_RETURN) && + if ((blockLastNode != nullptr) && (blockLastNode->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) && (varTypeIsGC(compiler->info.compRetType) || - (blockLastNode->AsOp()->gtOp1 != nullptr && varTypeIsGC(blockLastNode->AsOp()->gtOp1->TypeGet())))) + (blockLastNode->AsOp()->GetReturnValue() != nullptr && + varTypeIsGC(blockLastNode->AsOp()->GetReturnValue()->TypeGet())))) { nonVarPtrRegs &= ~RBM_INTRET; } diff --git a/src/coreclr/jit/codegenxarch.cpp b/src/coreclr/jit/codegenxarch.cpp index 3e5f1a4b38a691..79b76a6c8cc25b 100644 --- a/src/coreclr/jit/codegenxarch.cpp +++ b/src/coreclr/jit/codegenxarch.cpp @@ -1402,13 +1402,15 @@ void CodeGen::genSIMDSplitReturn(GenTree* src, ReturnTypeDesc* retTypeDesc) // // Arguments: // treeNode - The GT_RETURN or GT_RETFILT tree node with float type. +// (We don't expect treeNode to be a GT_SWIFT_ERROR_RET node, +// as Swift interop isn't supported on x86.) // // Return Value: // None // void CodeGen::genFloatReturn(GenTree* treeNode) { - assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); + assert(treeNode->OperIs(GT_RETURN, GT_RETFILT)); assert(varTypeIsFloating(treeNode)); GenTree* op1 = treeNode->gtGetOp1(); @@ -1966,6 +1968,12 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genReturn(treeNode); break; +#ifdef SWIFT_SUPPORT + case GT_SWIFT_ERROR_RET: + genSwiftErrorReturn(treeNode); + break; +#endif // SWIFT_SUPPORT + case GT_LEA: // If we are here, it is the case where there is an LEA that cannot be folded into a parent instruction. genLeaInstruction(treeNode->AsAddrMode()); diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 1edc4294140189..e602a40ae05b44 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -4717,6 +4717,12 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl // DoPhase(this, PHASE_MORPH_ADD_INTERNAL, &Compiler::fgAddInternal); +#ifdef SWIFT_SUPPORT + // Transform GT_RETURN nodes into GT_SWIFT_ERROR_RET nodes if this method has Swift error handling + // + DoPhase(this, PHASE_SWIFT_ERROR_RET, &Compiler::fgAddSwiftErrorReturns); +#endif // SWIFT_SUPPORT + // Remove empty try regions // DoPhase(this, PHASE_EMPTY_TRY, &Compiler::fgRemoveEmptyTry); diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index a9ec6f595ab5a7..175912a8760312 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -5279,6 +5279,10 @@ class Compiler PhaseStatus fgAddInternal(); +#ifdef SWIFT_SUPPORT + PhaseStatus fgAddSwiftErrorReturns(); +#endif // SWIFT_SUPPORT + enum class FoldResult { FOLD_DID_NOTHING, @@ -6523,7 +6527,7 @@ class Compiler GenTree* fgOptimizeBitwiseAnd(GenTreeOp* andOp); GenTree* fgOptimizeBitwiseXor(GenTreeOp* xorOp); GenTree* fgPropagateCommaThrow(GenTree* parent, GenTreeOp* commaThrow, GenTreeFlags precedingSideEffects); - GenTree* fgMorphRetInd(GenTreeUnOp* tree); + GenTree* fgMorphRetInd(GenTreeOp* tree); GenTree* fgMorphModToZero(GenTreeOp* tree); GenTree* fgMorphModToSubMulDiv(GenTreeOp* tree); GenTree* fgMorphUModToAndSub(GenTreeOp* tree); @@ -7878,7 +7882,7 @@ class Compiler GenTree* optAssertionProp_LocalStore(ASSERT_VALARG_TP assertions, GenTreeLclVarCommon* store, Statement* stmt); GenTree* optAssertionProp_BlockStore(ASSERT_VALARG_TP assertions, GenTreeBlk* store, Statement* stmt); GenTree* optAssertionProp_ModDiv(ASSERT_VALARG_TP assertions, GenTreeOp* tree, Statement* stmt); - GenTree* optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeUnOp* ret, Statement* stmt); + GenTree* optAssertionProp_Return(ASSERT_VALARG_TP assertions, GenTreeOp* ret, Statement* stmt); GenTree* optAssertionProp_Ind(ASSERT_VALARG_TP assertions, GenTree* tree, Statement* stmt); GenTree* optAssertionProp_Cast(ASSERT_VALARG_TP assertions, GenTreeCast* cast, Statement* stmt); GenTree* optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCall* call, Statement* stmt); @@ -8406,6 +8410,10 @@ class Compiler unsigned genReturnLocal; // Local number for the return value when applicable. BasicBlock* genReturnBB; // jumped to when not optimizing for speed. +#ifdef SWIFT_SUPPORT + unsigned genReturnErrorLocal; // Local number for the Swift error value when applicable. +#endif // SWIFT_SUPPORT + // The following properties are part of CodeGenContext. Getters are provided here for // convenience and backward compatibility, but the properties can only be set by invoking // the setter on CodeGenContext directly. diff --git a/src/coreclr/jit/compphases.h b/src/coreclr/jit/compphases.h index 4bd236ad7f1962..950313286d26fe 100644 --- a/src/coreclr/jit/compphases.h +++ b/src/coreclr/jit/compphases.h @@ -34,6 +34,7 @@ CompPhaseNameMacro(PHASE_INCPROFILE, "Profile incorporation", CompPhaseNameMacro(PHASE_MORPH_INIT, "Morph - Init", false, -1, false) CompPhaseNameMacro(PHASE_MORPH_INLINE, "Morph - Inlining", false, -1, true) CompPhaseNameMacro(PHASE_MORPH_ADD_INTERNAL, "Morph - Add internal blocks", false, -1, true) +CompPhaseNameMacro(PHASE_SWIFT_ERROR_RET, "Add Swift error returns", false, -1, true) CompPhaseNameMacro(PHASE_ALLOCATE_OBJECTS, "Allocate Objects", false, -1, false) CompPhaseNameMacro(PHASE_EMPTY_TRY, "Remove empty try", false, -1, false) CompPhaseNameMacro(PHASE_EMPTY_FINALLY, "Remove empty finally", false, -1, false) diff --git a/src/coreclr/jit/decomposelongs.cpp b/src/coreclr/jit/decomposelongs.cpp index ea87a996dbb1aa..84802400feeb0a 100644 --- a/src/coreclr/jit/decomposelongs.cpp +++ b/src/coreclr/jit/decomposelongs.cpp @@ -188,7 +188,8 @@ GenTree* DecomposeLongs::DecomposeNode(GenTree* tree) break; case GT_RETURN: - assert(tree->AsOp()->gtOp1->OperGet() == GT_LONG); + case GT_SWIFT_ERROR_RET: + assert(tree->AsOp()->GetReturnValue()->OperIs(GT_LONG)); break; case GT_STOREIND: diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 37683b188c303b..7c2ad719fb0296 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -70,6 +70,10 @@ void Compiler::fgInit() genReturnBB = nullptr; genReturnLocal = BAD_VAR_NUM; +#ifdef SWIFT_SUPPORT + genReturnErrorLocal = BAD_VAR_NUM; +#endif // SWIFT_SUPPORT + /* We haven't reached the global morphing phase */ fgGlobalMorph = false; fgGlobalMorphDone = false; diff --git a/src/coreclr/jit/fgstmt.cpp b/src/coreclr/jit/fgstmt.cpp index 2189955a6f9785..946d3818d1c729 100644 --- a/src/coreclr/jit/fgstmt.cpp +++ b/src/coreclr/jit/fgstmt.cpp @@ -197,7 +197,7 @@ void Compiler::fgInsertStmtNearEnd(BasicBlock* block, Statement* stmt) } else if (block->KindIs(BBJ_RETURN)) { - assert((lastStmt->GetRootNode()->OperIs(GT_RETURN, GT_JMP)) || + assert((lastStmt->GetRootNode()->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET, GT_JMP)) || // BBJ_RETURN blocks in functions returning void do not get a GT_RETURN node if they // have a .tail prefix (even if canTailCall returns false for these calls) // code:Compiler::impImportBlockCode (search for the RET: label) @@ -538,6 +538,7 @@ inline bool OperIsControlFlow(genTreeOps oper) case GT_RETURN: case GT_RETFILT: + case GT_SWIFT_ERROR_RET: #if defined(FEATURE_EH_WINDOWS_X86) case GT_END_LFIN: #endif // FEATURE_EH_WINDOWS_X86 diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 6c4416c4ce32e3..c9beffdb6575a6 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -1586,7 +1586,7 @@ GenTree* Compiler::fgCreateMonitorTree(unsigned lvaMonAcquired, unsigned lvaThis } #endif - if (block->KindIs(BBJ_RETURN) && block->lastStmt()->GetRootNode()->gtOper == GT_RETURN) + if (block->KindIs(BBJ_RETURN) && block->lastStmt()->GetRootNode()->OperIs(GT_RETURN)) { GenTreeUnOp* retNode = block->lastStmt()->GetRootNode()->AsUnOp(); GenTree* retExpr = retNode->gtOp1; @@ -2559,6 +2559,93 @@ PhaseStatus Compiler::fgAddInternal() return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } +#ifdef SWIFT_SUPPORT +//------------------------------------------------------------------------ +// fgAddSwiftErrorReturns: If this method uses Swift error handling, +// transform all GT_RETURN nodes into GT_SWIFT_ERROR_RET nodes +// to handle returning the error value alongside the normal return value. +// Also transform any GT_LCL_VAR uses of lvaSwiftErrorArg (the SwiftError* parameter) +// into GT_LCL_ADDR uses of lvaSwiftErrorLocal (the SwiftError pseudolocal). +// +// Returns: +// Suitable phase status. +// +PhaseStatus Compiler::fgAddSwiftErrorReturns() +{ + if (lvaSwiftErrorArg == BAD_VAR_NUM) + { + // No Swift error handling in this method + return PhaseStatus::MODIFIED_NOTHING; + } + + assert(lvaSwiftErrorLocal != BAD_VAR_NUM); + assert(info.compCallConv == CorInfoCallConvExtension::Swift); + + struct ReplaceSwiftErrorVisitor final : public GenTreeVisitor + { + enum + { + DoPreOrder = true, + DoLclVarsOnly = true, + }; + + ReplaceSwiftErrorVisitor(Compiler* comp) + : GenTreeVisitor(comp) + { + } + + fgWalkResult PreOrderVisit(GenTree** use, GenTree* user) + { + if ((*use)->AsLclVarCommon()->GetLclNum() == m_compiler->lvaSwiftErrorArg) + { + if (!(*use)->OperIs(GT_LCL_VAR)) + { + BADCODE("Found invalid use of SwiftError* parameter"); + } + + *use = m_compiler->gtNewLclVarAddrNode(m_compiler->lvaSwiftErrorLocal, genActualType(*use)); + } + + return fgWalkResult::WALK_CONTINUE; + } + }; + + ReplaceSwiftErrorVisitor visitor(this); + + for (BasicBlock* block : Blocks()) + { + for (Statement* const stmt : block->Statements()) + { + visitor.WalkTree(stmt->GetRootNodePointer(), nullptr); + } + + if (block->KindIs(BBJ_RETURN)) + { + GenTree* const ret = block->lastNode(); + assert(ret->OperIs(GT_RETURN)); + ret->SetOperRaw(GT_SWIFT_ERROR_RET); + ret->AsOp()->gtOp2 = ret->AsOp()->gtOp1; + + // If this is the merged return block, use the merged return error local as the error operand. + // Else, load the error value from the SwiftError pseudolocal (this will probably get promoted, anyway). + if (block == genReturnBB) + { + assert(genReturnErrorLocal == BAD_VAR_NUM); + genReturnErrorLocal = lvaGrabTemp(true DEBUGARG("Single return block SwiftError value")); + lvaGetDesc(genReturnErrorLocal)->lvType = TYP_I_IMPL; + ret->AsOp()->gtOp1 = gtNewLclvNode(genReturnErrorLocal, TYP_I_IMPL); + } + else + { + ret->AsOp()->gtOp1 = gtNewLclFldNode(lvaSwiftErrorLocal, TYP_I_IMPL, 0); + } + } + } + + return PhaseStatus::MODIFIED_EVERYTHING; +} +#endif // SWIFT_SUPPORT + /*****************************************************************************/ /*****************************************************************************/ diff --git a/src/coreclr/jit/forwardsub.cpp b/src/coreclr/jit/forwardsub.cpp index de4ac5fe8a4758..9d57fa3a4a6dff 100644 --- a/src/coreclr/jit/forwardsub.cpp +++ b/src/coreclr/jit/forwardsub.cpp @@ -798,7 +798,7 @@ bool Compiler::fgForwardSubStatement(Statement* stmt) // interaction between decomposition and RA. // if (compMethodReturnsMultiRegRetType() && (fsv.GetParentNode() != nullptr) && - fsv.GetParentNode()->OperIs(GT_RETURN)) + fsv.GetParentNode()->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { #if defined(TARGET_X86) if (fwdSubNode->TypeGet() == TYP_LONG) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 7e90d26a3d6820..791444aa1fb6e0 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -9650,6 +9650,10 @@ GenTree* Compiler::gtCloneExpr(GenTree* tree) } break; + case GT_SWIFT_ERROR_RET: + copy = new (this, oper) GenTreeOp(oper, tree->TypeGet(), tree->gtGetOp1(), tree->gtGetOp2()); + break; + default: assert(!GenTree::IsExOp(tree->OperKind()) && tree->OperIsSimple()); // We're in the SimpleOp case, so it's always unary or binary. diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index d5dbad500c16d2..152499a032b272 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1738,6 +1738,9 @@ struct GenTree #endif // defined(TARGET_ARM64) return true; + + case GT_SWIFT_ERROR_RET: + return (gtType == TYP_VOID); default: return false; } @@ -3037,6 +3040,34 @@ struct GenTreeOp : public GenTreeUnOp // then sets the flag GTF_DIV_BY_CNS_OPT and GTF_DONT_CSE on the constant void CheckDivideByConstOptimized(Compiler* comp); + GenTree* GetReturnValue() const + { + assert(OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); +#ifdef SWIFT_SUPPORT + if (OperIs(GT_SWIFT_ERROR_RET)) + { + return gtOp2; + } +#endif // SWIFT_SUPPORT + + return gtOp1; + } + + void SetReturnValue(GenTree* const retVal) + { + assert(OperIs(GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET)); +#ifdef SWIFT_SUPPORT + if (OperIs(GT_SWIFT_ERROR_RET)) + { + gtOp2 = retVal; + } + else +#endif // SWIFT_SUPPORT + { + gtOp1 = retVal; + } + } + #if !defined(TARGET_64BIT) || defined(TARGET_ARM64) bool IsValidLongMul(); #endif diff --git a/src/coreclr/jit/gtlist.h b/src/coreclr/jit/gtlist.h index 1d442f27673725..2554bb22ef7040 100644 --- a/src/coreclr/jit/gtlist.h +++ b/src/coreclr/jit/gtlist.h @@ -290,7 +290,9 @@ GTNODE(END_LFIN , GenTreeVal ,0,0,GTK_LEAF|GTK_NOVALUE) // End l // Swift interop-specific nodes: //----------------------------------------------------------------------------- -GTNODE(SWIFT_ERROR , GenTree ,0,0,GTK_LEAF) // Error register value post-Swift call +GTNODE(SWIFT_ERROR , GenTree ,0,0,GTK_LEAF) // Error register value post-Swift call +GTNODE(SWIFT_ERROR_RET , GenTreeOp ,0,1,GTK_BINOP|GTK_NOVALUE) // Returns normal return value, and SwiftError pseudolocal's value in REG_SWIFT_ERROR. + // op1 is the error value, and op2 is the return value (or null if the method returns void). //----------------------------------------------------------------------------- // Nodes used by Lower to generate a closer CPU representation of other nodes diff --git a/src/coreclr/jit/ifconversion.cpp b/src/coreclr/jit/ifconversion.cpp index 6d7ead881a91c5..6008bb432550b6 100644 --- a/src/coreclr/jit/ifconversion.cpp +++ b/src/coreclr/jit/ifconversion.cpp @@ -231,7 +231,7 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe for (Statement* const stmt : block->Statements()) { GenTree* tree = stmt->GetRootNode(); - switch (tree->gtOper) + switch (tree->OperGet()) { case GT_STORE_LCL_VAR: { @@ -287,7 +287,8 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe case GT_RETURN: { - GenTree* op1 = tree->gtGetOp1(); + // GT_SWIFT_ERROR_RET not supported + GenTree* const retVal = tree->gtGetOp1(); // Only allow RETURNs if else conversion is being used. if (!m_doElseConversion) @@ -296,7 +297,7 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe } // Only one per operation per block can be conditionally executed. - if (found || op1 == nullptr) + if (found || retVal == nullptr) { return false; } @@ -317,7 +318,7 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe #endif // Ensure it won't cause any additional side effects. - if ((op1->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0) + if ((retVal->gtFlags & (GTF_SIDE_EFFECT | GTF_ORDER_SIDEEFF)) != 0) { return false; } @@ -326,7 +327,8 @@ bool OptIfConversionDsc::IfConvertCheckStmts(BasicBlock* fromBlock, IfConvertOpe // with the condition (for example, the condition could be an explicit bounds // check and the operand could read an array element). Disallow this except // for some common cases that we know are always side effect free. - if (((m_cond->gtFlags & GTF_ORDER_SIDEEFF) != 0) && !op1->IsInvariant() && !op1->OperIsLocal()) + if (((m_cond->gtFlags & GTF_ORDER_SIDEEFF) != 0) && !retVal->IsInvariant() && + !retVal->OperIsLocal()) { return false; } @@ -634,10 +636,10 @@ bool OptIfConversionDsc::optIfConvert() else { assert(m_mainOper == GT_RETURN); - thenCost = m_thenOperation.node->gtGetOp1()->GetCostEx(); + thenCost = m_thenOperation.node->AsOp()->GetReturnValue()->GetCostEx(); if (m_doElseConversion) { - elseCost = m_elseOperation.node->gtGetOp1()->GetCostEx(); + elseCost = m_elseOperation.node->AsOp()->GetReturnValue()->GetCostEx(); } } @@ -697,8 +699,8 @@ bool OptIfConversionDsc::optIfConvert() assert(m_doElseConversion); assert(m_thenOperation.node->TypeGet() == m_elseOperation.node->TypeGet()); - selectTrueInput = m_elseOperation.node->gtGetOp1(); - selectFalseInput = m_thenOperation.node->gtGetOp1(); + selectTrueInput = m_elseOperation.node->AsOp()->GetReturnValue(); + selectFalseInput = m_thenOperation.node->AsOp()->GetReturnValue(); selectType = genActualType(m_thenOperation.node); } @@ -714,7 +716,7 @@ bool OptIfConversionDsc::optIfConvert() } else { - m_thenOperation.node->AsOp()->gtOp1 = select; + m_thenOperation.node->AsOp()->SetReturnValue(select); } m_comp->gtSetEvalOrder(m_thenOperation.node); m_comp->fgSetStmtSeq(m_thenOperation.stmt); diff --git a/src/coreclr/jit/importer.cpp b/src/coreclr/jit/importer.cpp index 12a612fde1a66a..242162e77e50cb 100644 --- a/src/coreclr/jit/importer.cpp +++ b/src/coreclr/jit/importer.cpp @@ -10464,21 +10464,6 @@ void Compiler::impLoadArg(unsigned ilArgNum, IL_OFFSET offset) { lclNum = lvaArg0Var; } -#ifdef SWIFT_SUPPORT - else if (lclNum == lvaSwiftErrorArg) - { - // Convert any usages of the SwiftError pointer/ref parameter to pointers/refs to the SwiftError pseudolocal - // (set side effect flags so usages of references to pseudolocal aren't removed) - assert(info.compCallConv == CorInfoCallConvExtension::Swift); - assert(lvaSwiftErrorArg != BAD_VAR_NUM); - assert(lvaSwiftErrorLocal != BAD_VAR_NUM); - const var_types type = lvaGetDesc(lvaSwiftErrorArg)->TypeGet(); - GenTree* const swiftErrorLocalRef = gtNewLclVarAddrNode(lvaSwiftErrorLocal, type); - impPushOnStack(swiftErrorLocalRef, typeInfo(type)); - JITDUMP("\nCreated GT_LCL_ADDR of SwiftError pseudolocal\n"); - return; - } -#endif // SWIFT_SUPPORT impLoadVar(lclNum, offset); } diff --git a/src/coreclr/jit/lclvars.cpp b/src/coreclr/jit/lclvars.cpp index 14016e710b6be0..a493ec0bb29f93 100644 --- a/src/coreclr/jit/lclvars.cpp +++ b/src/coreclr/jit/lclvars.cpp @@ -1467,7 +1467,6 @@ bool Compiler::lvaInitSpecialSwiftParam(CORINFO_ARG_LIST_HANDLE argHnd, // Instead, all usages of the SwiftError* parameter will be redirected to this pseudolocal. lvaSwiftErrorLocal = lvaGrabTempWithImplicitUse(false DEBUGARG("SwiftError pseudolocal")); lvaSetStruct(lvaSwiftErrorLocal, typeHnd, false); - lvaSetVarAddrExposed(lvaSwiftErrorLocal DEBUGARG(AddressExposedReason::ESCAPE_ADDRESS)); return true; } diff --git a/src/coreclr/jit/lir.cpp b/src/coreclr/jit/lir.cpp index d172cea22d369a..feabec03d02cad 100644 --- a/src/coreclr/jit/lir.cpp +++ b/src/coreclr/jit/lir.cpp @@ -1792,12 +1792,11 @@ void LIR::InsertBeforeTerminator(BasicBlock* block, LIR::Range&& range) break; case BBJ_SWITCH: - assert((insertionPoint->OperGet() == GT_SWITCH) || (insertionPoint->OperGet() == GT_SWITCH_TABLE)); + assert(insertionPoint->OperIs(GT_SWITCH, GT_SWITCH_TABLE)); break; case BBJ_RETURN: - assert((insertionPoint->OperGet() == GT_RETURN) || (insertionPoint->OperGet() == GT_JMP) || - (insertionPoint->OperGet() == GT_CALL)); + assert(insertionPoint->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET, GT_JMP, GT_CALL)); break; default: diff --git a/src/coreclr/jit/liveness.cpp b/src/coreclr/jit/liveness.cpp index 2f3f23826f8568..4f614aa71b8cae 100644 --- a/src/coreclr/jit/liveness.cpp +++ b/src/coreclr/jit/liveness.cpp @@ -1939,6 +1939,7 @@ void Compiler::fgComputeLifeLIR(VARSET_TP& life, BasicBlock* block, VARSET_VALAR case GT_PUTARG_STK: case GT_IL_OFFSET: case GT_KEEPALIVE: + case GT_SWIFT_ERROR_RET: // Never remove these nodes, as they are always side-effecting. // // NOTE: the only side-effect of some of these nodes (GT_CMP, GT_SUB_HI) is a write to the flags diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 0427cf666a3bcb..2257fca33d4e4a 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -520,8 +520,9 @@ GenTree* Lowering::LowerNode(GenTree* node) LowerJmpMethod(node); break; + case GT_SWIFT_ERROR_RET: case GT_RETURN: - LowerRet(node->AsUnOp()); + LowerRet(node->AsOp()); break; case GT_RETURNTRAP: @@ -4541,22 +4542,22 @@ void Lowering::LowerJmpMethod(GenTree* jmp) } } -// Lower GT_RETURN node to insert PInvoke method epilog if required. -void Lowering::LowerRet(GenTreeUnOp* ret) +// Lower GT_RETURN/GT_SWIFT_ERROR_RET node to insert PInvoke method epilog if required. +void Lowering::LowerRet(GenTreeOp* ret) { - assert(ret->OperGet() == GT_RETURN); + assert(ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); - JITDUMP("lowering GT_RETURN\n"); + JITDUMP("lowering return node\n"); DISPNODE(ret); JITDUMP("============\n"); - GenTree* retVal = ret->gtGetOp1(); + GenTree* retVal = ret->GetReturnValue(); // There are two kinds of retyping: // - A simple bitcast can be inserted when: // - We're returning a floating type as an integral type or vice-versa, or // - If we're returning a struct as a primitive type, we change the type of // 'retval' in 'LowerRetStructLclVar()' - bool needBitcast = (ret->TypeGet() != TYP_VOID) && !varTypeUsesSameRegType(ret, ret->gtGetOp1()); + bool needBitcast = (ret->TypeGet() != TYP_VOID) && !varTypeUsesSameRegType(ret, retVal); bool doPrimitiveBitcast = false; if (needBitcast) { @@ -4572,7 +4573,7 @@ void Lowering::LowerRet(GenTreeUnOp* ret) #endif GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), retVal); - ret->gtOp1 = bitcast; + ret->SetReturnValue(bitcast); BlockRange().InsertBefore(ret, bitcast); ContainCheckBitCast(bitcast); } @@ -4871,7 +4872,7 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) return; } - assert(ret->OperIs(GT_RETURN)); + assert(ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); assert(varTypeIsStruct(ret)); GenTree* retVal = ret->gtGetOp1(); @@ -4963,8 +4964,8 @@ void Lowering::LowerRetStruct(GenTreeUnOp* ret) void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret) { assert(!comp->compMethodReturnsMultiRegRetType()); - assert(ret->OperIs(GT_RETURN)); - GenTreeLclVarCommon* lclVar = ret->gtGetOp1()->AsLclVar(); + assert(ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); + GenTreeLclVarCommon* lclVar = ret->AsOp()->GetReturnValue()->AsLclVar(); assert(lclVar->OperIs(GT_LCL_VAR)); unsigned lclNum = lclVar->GetLclNum(); LclVarDsc* varDsc = comp->lvaGetDesc(lclNum); @@ -5012,7 +5013,7 @@ void Lowering::LowerRetSingleRegStructLclVar(GenTreeUnOp* ret) if (!varTypeUsesSameRegType(ret, lclVarType)) { GenTree* bitcast = comp->gtNewBitCastNode(ret->TypeGet(), ret->gtOp1); - ret->gtOp1 = bitcast; + ret->AsOp()->SetReturnValue(bitcast); BlockRange().InsertBefore(ret, bitcast); ContainCheckBitCast(bitcast); } @@ -8147,12 +8148,12 @@ void Lowering::ContainCheckLclHeap(GenTreeOp* node) // void Lowering::ContainCheckRet(GenTreeUnOp* ret) { - assert(ret->OperIs(GT_RETURN)); + assert(ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); #if !defined(TARGET_64BIT) if (ret->TypeGet() == TYP_LONG) { - GenTree* op1 = ret->gtGetOp1(); + GenTree* op1 = ret->AsOp()->GetReturnValue(); noway_assert(op1->OperGet() == GT_LONG); MakeSrcContained(ret, op1); } @@ -8160,7 +8161,7 @@ void Lowering::ContainCheckRet(GenTreeUnOp* ret) #if FEATURE_MULTIREG_RET if (ret->TypeIs(TYP_STRUCT)) { - GenTree* op1 = ret->gtGetOp1(); + GenTree* op1 = ret->AsOp()->GetReturnValue(); // op1 must be either a lclvar or a multi-reg returning call if (op1->OperGet() == GT_LCL_VAR) { diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 0538c661e16260..055f425b664566 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -154,7 +154,7 @@ class Lowering final : public Phase bool TryLowerConditionToFlagsNode(GenTree* parent, GenTree* condition, GenCondition* code); GenTreeCC* LowerNodeCC(GenTree* node, GenCondition condition); void LowerJmpMethod(GenTree* jmp); - void LowerRet(GenTreeUnOp* ret); + void LowerRet(GenTreeOp* ret); void LowerStoreLocCommon(GenTreeLclVarCommon* lclVar); void LowerRetStruct(GenTreeUnOp* ret); void LowerRetSingleRegStructLclVar(GenTreeUnOp* ret); diff --git a/src/coreclr/jit/lsra.cpp b/src/coreclr/jit/lsra.cpp index 831f6c7ac194cf..af29eef1eb22a5 100644 --- a/src/coreclr/jit/lsra.cpp +++ b/src/coreclr/jit/lsra.cpp @@ -8515,16 +8515,16 @@ void LinearScan::insertMove( noway_assert(!blockRange.IsEmpty()); GenTree* branch = lastNode; - assert(branch->OperIsConditionalJump() || branch->OperGet() == GT_SWITCH_TABLE || - branch->OperGet() == GT_SWITCH); + assert(branch->OperIsConditionalJump() || branch->OperIs(GT_SWITCH_TABLE, GT_SWITCH)); blockRange.InsertBefore(branch, std::move(treeRange)); } else { // These block kinds don't have a branch at the end. - assert((lastNode == nullptr) || (!lastNode->OperIsConditionalJump() && - !lastNode->OperIs(GT_SWITCH_TABLE, GT_SWITCH, GT_RETURN, GT_RETFILT))); + assert((lastNode == nullptr) || + (!lastNode->OperIsConditionalJump() && + !lastNode->OperIs(GT_SWITCH_TABLE, GT_SWITCH, GT_RETURN, GT_RETFILT, GT_SWIFT_ERROR_RET))); blockRange.InsertAtEnd(std::move(treeRange)); } } diff --git a/src/coreclr/jit/lsraarm64.cpp b/src/coreclr/jit/lsraarm64.cpp index 1096d7f11701c5..dfcebf4392c53c 100644 --- a/src/coreclr/jit/lsraarm64.cpp +++ b/src/coreclr/jit/lsraarm64.cpp @@ -582,7 +582,7 @@ int LinearScan::BuildNode(GenTree* tree) { assert(!tree->isContained()); int srcCount; - int dstCount = 0; + int dstCount; regMaskTP killMask = RBM_NONE; bool isLocalDefUse = false; @@ -741,6 +741,16 @@ int LinearScan::BuildNode(GenTree* tree) BuildDefsWithKills(tree, 0, RBM_NONE, killMask); break; +#ifdef SWIFT_SUPPORT + case GT_SWIFT_ERROR_RET: + BuildUse(tree->gtGetOp1(), RBM_SWIFT_ERROR); + // Plus one for error register + srcCount = BuildReturn(tree) + 1; + killMask = getKillSetForReturn(); + BuildDefsWithKills(tree, 0, RBM_NONE, killMask); + break; +#endif // SWIFT_SUPPORT + case GT_RETFILT: assert(dstCount == 0); if (tree->TypeGet() == TYP_VOID) diff --git a/src/coreclr/jit/lsrabuild.cpp b/src/coreclr/jit/lsrabuild.cpp index 6c11e00aa57cb6..dff38bc06f1c5f 100644 --- a/src/coreclr/jit/lsrabuild.cpp +++ b/src/coreclr/jit/lsrabuild.cpp @@ -3989,7 +3989,7 @@ int LinearScan::BuildSimple(GenTree* tree) // int LinearScan::BuildReturn(GenTree* tree) { - GenTree* op1 = tree->gtGetOp1(); + GenTree* op1 = tree->AsOp()->GetReturnValue(); #if !defined(TARGET_64BIT) if (tree->TypeGet() == TYP_LONG) diff --git a/src/coreclr/jit/lsraxarch.cpp b/src/coreclr/jit/lsraxarch.cpp index 7fe119ccfd165c..6abc86aab0ed5e 100644 --- a/src/coreclr/jit/lsraxarch.cpp +++ b/src/coreclr/jit/lsraxarch.cpp @@ -46,7 +46,7 @@ int LinearScan::BuildNode(GenTree* tree) { assert(!tree->isContained()); int srcCount; - int dstCount = 0; + int dstCount; regMaskTP killMask = RBM_NONE; bool isLocalDefUse = false; @@ -193,6 +193,16 @@ int LinearScan::BuildNode(GenTree* tree) BuildDefsWithKills(tree, 0, RBM_NONE, killMask); break; +#ifdef SWIFT_SUPPORT + case GT_SWIFT_ERROR_RET: + BuildUse(tree->gtGetOp1(), RBM_SWIFT_ERROR); + // Plus one for error register + srcCount = BuildReturn(tree) + 1; + killMask = getKillSetForReturn(); + BuildDefsWithKills(tree, 0, RBM_NONE, killMask); + break; +#endif // SWIFT_SUPPORT + case GT_RETFILT: assert(dstCount == 0); if (tree->TypeGet() == TYP_VOID) @@ -733,6 +743,7 @@ bool LinearScan::isRMWRegOper(GenTree* tree) #ifdef TARGET_X86 case GT_LONG: #endif + case GT_SWIFT_ERROR_RET: return false; case GT_ADD: diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7ba66dca69e879..56f5d89372050a 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -8737,34 +8737,61 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA return fgMorphIntoHelperCall(tree, helper, true /* morphArgs */, op1, op2); case GT_RETURN: + case GT_SWIFT_ERROR_RET: + { + GenTree* retVal = tree->AsOp()->GetReturnValue(); + if (!tree->TypeIs(TYP_VOID)) { - if (op1->OperIs(GT_LCL_FLD)) + if (retVal->OperIs(GT_LCL_FLD)) { - op1 = fgMorphRetInd(tree->AsUnOp()); + retVal = fgMorphRetInd(tree->AsOp()); } - fgTryReplaceStructLocalWithField(op1); + fgTryReplaceStructLocalWithField(retVal); } // normalize small integer return values - if (fgGlobalMorph && varTypeIsSmall(info.compRetType) && (op1 != nullptr) && !op1->TypeIs(TYP_VOID) && - fgCastNeeded(op1, info.compRetType)) + if (fgGlobalMorph && varTypeIsSmall(info.compRetType) && (retVal != nullptr) && !retVal->TypeIs(TYP_VOID) && + fgCastNeeded(retVal, info.compRetType)) { +#ifdef SWIFT_SUPPORT + // Morph error operand if tree is a GT_SWIFT_ERROR_RET node + if (tree->OperIs(GT_SWIFT_ERROR_RET)) + { + GenTree* const errorVal = fgMorphTree(tree->gtGetOp1()); + tree->AsOp()->gtOp1 = errorVal; + + // Propagate side effect flags + tree->SetAllEffectsFlags(errorVal); + } +#endif // SWIFT_SUPPORT + // Small-typed return values are normalized by the callee - op1 = gtNewCastNode(TYP_INT, op1, false, info.compRetType); + retVal = gtNewCastNode(TYP_INT, retVal, false, info.compRetType); // Propagate GTF_COLON_COND - op1->gtFlags |= (tree->gtFlags & GTF_COLON_COND); + retVal->gtFlags |= (tree->gtFlags & GTF_COLON_COND); - tree->AsOp()->gtOp1 = fgMorphTree(op1); + retVal = fgMorphTree(retVal); + tree->AsOp()->SetReturnValue(retVal); // Propagate side effect flags - tree->SetAllEffectsFlags(tree->AsOp()->gtGetOp1()); + tree->SetAllEffectsFlags(retVal); return tree; } + + if (tree->OperIs(GT_RETURN)) + { + op1 = retVal; + } + else + { + op2 = retVal; + } break; + } case GT_EQ: case GT_NE: @@ -9631,15 +9658,18 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA break; case GT_RETURN: - - // Retry updating op1 to a field -- assertion - // prop done when morphing op1 changed the local. + case GT_SWIFT_ERROR_RET: + { + // Retry updating return operand to a field -- assertion + // prop done when morphing this operand changed the local. // - if (op1 != nullptr) + GenTree* const retVal = tree->AsOp()->GetReturnValue(); + if (retVal != nullptr) { - fgTryReplaceStructLocalWithField(op1); + fgTryReplaceStructLocalWithField(retVal); } break; + } default: break; @@ -9694,7 +9724,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA // tree - tree to examine and possibly modify // // Notes: -// Currently only called when the tree parent is a GT_RETURN. +// Currently only called when the tree parent is a GT_RETURN/GT_SWIFT_ERROR_RET. // void Compiler::fgTryReplaceStructLocalWithField(GenTree* tree) { @@ -11538,16 +11568,16 @@ GenTree* Compiler::fgPropagateCommaThrow(GenTree* parent, GenTreeOp* commaThrow, // fgMorphRetInd: Try to get rid of extra local indirections in a return tree. // // Arguments: -// node - The return node that uses an local field. +// node - The return node that uses a local field. // // Return Value: -// the original op1 of the ret if there was no optimization or an optimized new op1. +// the original return operand if there was no optimization, or an optimized new return operand. // -GenTree* Compiler::fgMorphRetInd(GenTreeUnOp* ret) +GenTree* Compiler::fgMorphRetInd(GenTreeOp* ret) { - assert(ret->OperIs(GT_RETURN)); - assert(ret->gtGetOp1()->OperIs(GT_LCL_FLD)); - GenTreeLclFld* lclFld = ret->gtGetOp1()->AsLclFld(); + assert(ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); + assert(ret->GetReturnValue()->OperIs(GT_LCL_FLD)); + GenTreeLclFld* lclFld = ret->GetReturnValue()->AsLclFld(); unsigned lclNum = lclFld->GetLclNum(); if (fgGlobalMorph && varTypeIsStruct(lclFld) && !lvaIsImplicitByRefLocal(lclNum)) @@ -14071,7 +14101,7 @@ void Compiler::fgMergeBlockReturn(BasicBlock* block) Statement* lastStmt = block->lastStmt(); GenTree* ret = (lastStmt != nullptr) ? lastStmt->GetRootNode() : nullptr; - if ((ret != nullptr) && (ret->OperGet() == GT_RETURN) && ((ret->gtFlags & GTF_RET_MERGED) != 0)) + if ((ret != nullptr) && ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET) && ((ret->gtFlags & GTF_RET_MERGED) != 0)) { // This return was generated during epilog merging, so leave it alone } @@ -14092,27 +14122,40 @@ void Compiler::fgMergeBlockReturn(BasicBlock* block) fgReturnCount--; } +#ifdef SWIFT_SUPPORT + // If merging GT_SWIFT_ERROR_RET nodes, ensure the error operand is stored to the merged return error local, + // so the correct error value is retrieved in the merged return block. + if ((ret != nullptr) && ret->OperIs(GT_SWIFT_ERROR_RET)) + { + assert(genReturnErrorLocal != BAD_VAR_NUM); + const DebugInfo& di = lastStmt->GetDebugInfo(); + GenTree* swiftErrorStore = gtNewTempStore(genReturnErrorLocal, ret->gtGetOp1()); + Statement* const newStmt = gtNewStmt(swiftErrorStore, di); + fgInsertStmtBefore(block, lastStmt, newStmt); + } +#endif // SWIFT_SUPPORT + if (genReturnLocal != BAD_VAR_NUM) { - // replace the GT_RETURN node to be a STORE_LCL_VAR that stores the return value into genReturnLocal. + // replace the GT_RETURN/GT_SWIFT_ERROR_RET node to be a STORE_LCL_VAR that stores the return value into + // genReturnLocal. // Method must be returning a value other than TYP_VOID. noway_assert(compMethodHasRetVal()); - // This block must be ending with a GT_RETURN + // This block must be ending with a GT_RETURN/GT_SWIFT_ERROR_RET noway_assert(lastStmt != nullptr); noway_assert(lastStmt->GetNextStmt() == nullptr); noway_assert(ret != nullptr); - // GT_RETURN must have non-null operand as the method is returning the value assigned to + // Return node must have non-null operand as the method is returning the value assigned to // genReturnLocal - noway_assert(ret->OperGet() == GT_RETURN); - noway_assert(ret->gtGetOp1() != nullptr); + GenTree* const retVal = ret->AsOp()->GetReturnValue(); + noway_assert(retVal != nullptr); Statement* pAfterStatement = lastStmt; const DebugInfo& di = lastStmt->GetDebugInfo(); - GenTree* tree = - gtNewTempStore(genReturnLocal, ret->gtGetOp1(), CHECK_SPILL_NONE, &pAfterStatement, di, block); + GenTree* tree = gtNewTempStore(genReturnLocal, retVal, CHECK_SPILL_NONE, &pAfterStatement, di, block); if (tree->OperIsCopyBlkOp()) { tree = fgMorphCopyBlock(tree); @@ -14135,16 +14178,17 @@ void Compiler::fgMergeBlockReturn(BasicBlock* block) lastStmt = newStmt; } } - else if (ret != nullptr && ret->OperGet() == GT_RETURN) + else if ((ret != nullptr) && ret->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { - // This block ends with a GT_RETURN + // This block ends with a GT_RETURN/GT_SWIFT_ERROR_RET noway_assert(lastStmt != nullptr); noway_assert(lastStmt->GetNextStmt() == nullptr); - // Must be a void GT_RETURN with null operand; delete it as this block branches to oneReturn - // block + // Must be a void return node with null operand; delete it as this block branches to + // oneReturn block + GenTree* const retVal = ret->AsOp()->GetReturnValue(); noway_assert(ret->TypeGet() == TYP_VOID); - noway_assert(ret->gtGetOp1() == nullptr); + noway_assert(retVal == nullptr); if (opts.compDbgCode && lastStmt->GetDebugInfo().IsValid()) { diff --git a/src/coreclr/jit/optimizebools.cpp b/src/coreclr/jit/optimizebools.cpp index 1e5d5a00b107c0..07da4731252ff6 100644 --- a/src/coreclr/jit/optimizebools.cpp +++ b/src/coreclr/jit/optimizebools.cpp @@ -18,15 +18,43 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX /*****************************************************************************/ //----------------------------------------------------------------------------- -// OptTestInfo: Member of OptBoolsDsc struct used to test if a GT_JTRUE or GT_RETURN node +// OptTestInfo: Member of OptBoolsDsc struct used to test if a GT_JTRUE or return node // is a boolean comparison // struct OptTestInfo { Statement* testStmt; // Last statement of the basic block - GenTree* testTree; // The root node of the testStmt (GT_JTRUE or GT_RETURN). + GenTree* testTree; // The root node of the testStmt (GT_JTRUE or GT_RETURN/GT_SWIFT_ERROR_RET). GenTree* compTree; // The compare node (i.e. GT_EQ or GT_NE node) of the testTree bool isBool; // If the compTree is boolean expression + + GenTree* GetTestOp() const + { + assert(testTree != nullptr); + + if (testTree->OperIs(GT_JTRUE)) + { + return testTree->gtGetOp1(); + } + + assert(testTree->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); + return testTree->AsOp()->GetReturnValue(); + } + + void SetTestOp(GenTree* const op) + { + assert(testTree != nullptr); + + if (testTree->OperIs(GT_JTRUE)) + { + testTree->AsOp()->gtOp1 = op; + } + else + { + assert(testTree->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)); + testTree->AsOp()->SetReturnValue(op); + } + } }; //----------------------------------------------------------------------------- @@ -100,12 +128,12 @@ class OptBoolsDsc // For example, (x == 0 && y == 0 && z == 0) generates // B1: GT_JTRUE (BBJ_COND), jump to B4 // B2: GT_JTRUE (BBJ_COND), jump to B4 -// B3: GT_RETURN (BBJ_RETURN) -// B4: GT_RETURN (BBJ_RETURN) +// B3: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) +// B4: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) // and B1 and B2 are folded into B1: // B1: GT_JTRUE (BBJ_COND), jump to B4 -// B3: GT_RETURN (BBJ_RETURN) -// B4: GT_RETURN (BBJ_RETURN) +// B3: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) +// B4: GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) // // Case 2: if B2->FalseTargetIs(B1->GetTarget()), it transforms // B1 : brtrue(t1, B3) @@ -234,8 +262,7 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() cmpOp = GT_EQ; } else if (m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_LT && - (!m_testInfo1.testTree->AsOp()->gtOp1->IsUnsigned() && - !m_testInfo2.testTree->AsOp()->gtOp1->IsUnsigned())) + (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) { // t1:c1<0 t2:c2<0 ==> Branch to BX if either value < 0 // So we will branch to BX if (c1|c2)<0 @@ -297,8 +324,7 @@ bool OptBoolsDsc::optOptimizeBoolsCondBlock() cmpOp = GT_NE; } else if (m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_GE && - (!m_testInfo1.testTree->AsOp()->gtOp1->IsUnsigned() && - !m_testInfo2.testTree->AsOp()->gtOp1->IsUnsigned())) + (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) { // t1:c1<0 t2:c2>=0 ==> Branch to BX if both values >= 0 // So we will branch to BX if (c1|c2)>=0 @@ -1023,7 +1049,7 @@ bool OptBoolsDsc::optOptimizeCompareChainCondBlock() m_comp->gtNewOperNode(GT_NE, TYP_INT, chainedConditions, m_comp->gtNewZeroConNode(TYP_INT)); // Wire the chain into the second block - m_testInfo2.testTree->AsOp()->gtOp1 = testcondition; + m_testInfo2.SetTestOp(testcondition); m_testInfo2.testTree->AsOp()->gtFlags |= (testcondition->gtFlags & GTF_ALL_EFFECT); m_comp->gtSetEvalOrder(m_testInfo2.testTree); m_comp->fgSetStmtSeq(s2); @@ -1062,7 +1088,7 @@ bool OptBoolsDsc::optOptimizeCompareChainCondBlock() // // Notes: // This method checks if the second (and third block for cond/return/return case) contains only one statement, -// and checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN. +// and checks if tree operators are of the right type, e.g, GT_JTRUE, GT_RETURN, GT_SWIFT_ERROR_RET. // // On entry, m_b1, m_b2 are set and m_b3 is set for cond/return/return case. // If it passes all the conditions, m_testInfo1.testTree, m_testInfo2.testTree and m_t3 are set @@ -1091,7 +1117,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() Statement* s1 = m_b1->lastStmt(); GenTree* testTree1 = s1->GetRootNode(); - assert(testTree1->gtOper == GT_JTRUE); + assert(testTree1->OperIs(GT_JTRUE)); // The second and the third block must contain a single statement @@ -1105,11 +1131,11 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() if (!optReturnBlock) { - assert(testTree2->gtOper == GT_JTRUE); + assert(testTree2->OperIs(GT_JTRUE)); } else { - if (testTree2->gtOper != GT_RETURN) + if (!testTree2->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { return nullptr; } @@ -1121,7 +1147,7 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() } GenTree* testTree3 = s3->GetRootNode(); - if (testTree3->gtOper != GT_RETURN) + if (!testTree3->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { return nullptr; } @@ -1132,12 +1158,13 @@ Statement* OptBoolsDsc::optOptimizeBoolsChkBlkCond() } // The third block is Return with "CNS_INT int 0/1" - if (testTree3->AsOp()->gtOp1->gtOper != GT_CNS_INT) + GenTree* const retVal = testTree3->AsOp()->GetReturnValue(); + if (!retVal->OperIs(GT_CNS_INT)) { return nullptr; } - if (testTree3->AsOp()->gtOp1->gtType != TYP_INT) + if (!retVal->TypeIs(TYP_INT)) { return nullptr; } @@ -1236,10 +1263,10 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() t1Comp->AsOp()->gtOp2->gtType = m_foldType; // Could have been varTypeIsGC() if (optReturnBlock) { - // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN (BBJ_RETURN) + // Update tree when m_b1 is BBJ_COND and m_b2 and m_b3 are GT_RETURN/GT_SWIFT_ERROR_RET (BBJ_RETURN) t1Comp->AsOp()->gtOp2->AsIntCon()->gtIconVal = 0; - m_testInfo1.testTree->gtOper = GT_RETURN; - m_testInfo1.testTree->gtType = m_testInfo2.testTree->gtType; + m_testInfo1.testTree->gtOper = m_testInfo2.testTree->OperGet(); + m_testInfo1.testTree->gtType = m_testInfo2.testTree->TypeGet(); // Update the return count of flow graph assert(m_comp->fgReturnCount >= 2); @@ -1388,10 +1415,10 @@ void OptBoolsDsc::optOptimizeBoolsUpdateTrees() // // For example, (x==0 && y==0) generates: // B1: GT_JTRUE (BBJ_COND), jumps to B3 -// B2: GT_RETURN (BBJ_RETURN) -// B3: GT_RETURN (BBJ_RETURN), +// B2: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN) +// B3: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN), // and it is folded into -// B1: GT_RETURN (BBJ_RETURN) +// B1: GT_RETURN/GT_SWIFT_ERROR (BBJ_RETURN) // bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3) { @@ -1530,7 +1557,7 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3) } else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_GE) && (it1val == 0 && it2val == 0 && it3val == 0) && - (!m_testInfo1.testTree->AsOp()->gtOp1->IsUnsigned() && !m_testInfo2.testTree->AsOp()->gtOp1->IsUnsigned())) + (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) { // Case: x >= 0 && y >= 0 // t1:c1<0 t2:c2>=0 t3:c3==0 @@ -1559,7 +1586,7 @@ bool OptBoolsDsc::optOptimizeBoolsReturnBlock(BasicBlock* b3) } else if ((m_testInfo1.compTree->gtOper == GT_LT && m_testInfo2.compTree->gtOper == GT_LT) && (it1val == 0 && it2val == 0 && it3val == 1) && - (!m_testInfo1.testTree->AsOp()->gtOp1->IsUnsigned() && !m_testInfo2.testTree->AsOp()->gtOp1->IsUnsigned())) + (!m_testInfo1.GetTestOp()->IsUnsigned() && !m_testInfo2.GetTestOp()->IsUnsigned())) { // Case: x < 0 || y < 0 // t1:c1<0 t2:c2<0 t3:c3==1 @@ -1676,7 +1703,7 @@ void OptBoolsDsc::optOptimizeBoolsGcStress() // On success, compTree is set to the compare node (i.e. GT_EQ or GT_NE or GT_LT or GT_GE) of the testTree. // isBool is set to true if the comparand (i.e., operand 1 of compTree is boolean. Otherwise, false. // -// Given a GT_JTRUE or GT_RETURN node, this method checks if it is a boolean comparison +// Given a GT_JTRUE or GT_RETURN/GT_SWIFT_ERROR_RET node, this method checks if it is a boolean comparison // of the form "if (boolVal ==/!=/>=/< 0/1)".This is translated into // a GT_EQ/GT_NE/GT_GE/GT_LT node with "opr1" being a boolean lclVar and "opr2" the const 0/1. // @@ -1687,8 +1714,8 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) { pOptTest->isBool = false; - assert(pOptTest->testTree->gtOper == GT_JTRUE || pOptTest->testTree->gtOper == GT_RETURN); - GenTree* cond = pOptTest->testTree->AsOp()->gtOp1; + assert(pOptTest->testTree->OperIs(GT_JTRUE, GT_RETURN, GT_SWIFT_ERROR_RET)); + GenTree* cond = pOptTest->GetTestOp(); // The condition must be "!= 0" or "== 0" or >=0 or <= 0 or > 0 or < 0 if (!cond->OperIs(GT_EQ, GT_NE, GT_LT, GT_GT, GT_GE, GT_LE)) @@ -1749,13 +1776,13 @@ GenTree* OptBoolsDsc::optIsBoolComp(OptTestInfo* pOptTest) } //----------------------------------------------------------------------------- -// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GT_RETURN nodes +// optOptimizeBools: Folds boolean conditionals for GT_JTRUE/GT_RETURN/GT_SWIFT_ERROR_RET nodes // // Returns: // suitable phase status // // Notes: -// If the operand of GT_JTRUE/GT_RETURN node is GT_EQ/GT_NE/GT_GE/GT_LE/GT_GT/GT_LT of the form +// If the operand of GT_JTRUE/GT_RETURN/GT_SWIFT_ERROR_RET node is GT_EQ/GT_NE/GT_GE/GT_LE/GT_GT/GT_LT of the form // "if (boolVal ==/!=/>=/< 0/1)", the GT_EQ/GT_NE/GT_GE/GT_LE/GT_GT/GT_LT nodes are translated into a // GT_EQ/GT_NE/GT_GE/GT_LE/GT_GT/GT_LT node with // "op1" being a boolean GT_OR/GT_AND lclVar and diff --git a/src/coreclr/jit/promotion.cpp b/src/coreclr/jit/promotion.cpp index e02a5f0e06bab3..c22bf74e8309f6 100644 --- a/src/coreclr/jit/promotion.cpp +++ b/src/coreclr/jit/promotion.cpp @@ -1446,9 +1446,8 @@ class LocalsUseVisitor : public GenTreeVisitor flags |= AccessKindFlags::IsStoreSource; } - if (user->OperIs(GT_RETURN)) + if (user->OperIs(GT_RETURN, GT_SWIFT_ERROR_RET)) { - assert(user->gtGetOp1()->gtEffectiveVal() == lcl); flags |= AccessKindFlags::IsReturned; } #endif @@ -2546,7 +2545,7 @@ void ReplaceVisitor::ReplaceLocal(GenTree** use, GenTree* user) JITDUMP("Processing struct use [%06u] of V%02u.[%03u..%03u)\n", Compiler::dspTreeID(lcl), lclNum, offs, offs + lcl->GetLayout(m_compiler)->GetSize()); - assert(effectiveUser->OperIs(GT_CALL, GT_RETURN)); + assert(effectiveUser->OperIs(GT_CALL, GT_RETURN, GT_SWIFT_ERROR_RET)); unsigned size = lcl->GetLayout(m_compiler)->GetSize(); WriteBackBeforeUse(use, lclNum, lcl->GetLclOffs(), size); diff --git a/src/coreclr/jit/valuenum.cpp b/src/coreclr/jit/valuenum.cpp index 7a6136577ddba3..d80a747e974d91 100644 --- a/src/coreclr/jit/valuenum.cpp +++ b/src/coreclr/jit/valuenum.cpp @@ -9569,7 +9569,8 @@ static genTreeOps genTreeOpsIllegalAsVNFunc[] = {GT_IND, // When we do heap memo GT_NOP, // These control-flow operations need no values. - GT_JTRUE, GT_RETURN, GT_SWITCH, GT_RETFILT, GT_CKFINITE}; + GT_JTRUE, GT_RETURN, GT_SWITCH, GT_RETFILT, GT_CKFINITE, + GT_SWIFT_ERROR_RET}; void ValueNumStore::ValidateValueNumStoreStatics() { @@ -11696,6 +11697,28 @@ void Compiler::fgValueNumberTree(GenTree* tree) } break; + // GT_SWIFT_ERROR_RET is similar to GT_RETURN, but it's a binary node, and its return value is op2. + case GT_SWIFT_ERROR_RET: + if (tree->gtGetOp2() != nullptr) + { + // We have a return value and an error value. + ValueNumPair vnp; + ValueNumPair op1Xvnp; + ValueNumPair op2Xvnp; + vnStore->VNPUnpackExc(tree->gtGetOp1()->gtVNPair, &vnp, &op1Xvnp); + vnStore->VNPUnpackExc(tree->gtGetOp2()->gtVNPair, &vnp, &op2Xvnp); + + const ValueNumPair excSetPair = vnStore->VNPExcSetUnion(op1Xvnp, op2Xvnp); + tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(), excSetPair); + } + else + { + // We only have the error value. + tree->gtVNPair = vnStore->VNPWithExc(vnStore->VNPForVoid(), + vnStore->VNPExceptionSet(tree->gtGetOp1()->gtVNPair)); + } + break; + // BOX and CKFINITE are passthrough nodes (like NOP). We'll add the exception for the latter later. case GT_BOX: case GT_CKFINITE: