From 91b57e6b826382b78c25af340cf72a75133c6a2d Mon Sep 17 00:00:00 2001 From: Sergey Andreenko Date: Sat, 28 Jan 2017 15:28:05 -0800 Subject: [PATCH] CoreRT calli transformation Transform managed calli when compiling for CoreRT. Commit migrated from https://github.com/dotnet/coreclr/commit/654fed28001085adc162de1ea3207e9a22230235 --- src/coreclr/src/jit/compiler.cpp | 10 +- src/coreclr/src/jit/compiler.h | 31 ++- src/coreclr/src/jit/flowgraph.cpp | 385 +++++++++++++++++++++++++++++++++++++- src/coreclr/src/jit/gentree.h | 84 ++++++--- src/coreclr/src/jit/importer.cpp | 42 ++++- 5 files changed, 508 insertions(+), 44 deletions(-) diff --git a/src/coreclr/src/jit/compiler.cpp b/src/coreclr/src/jit/compiler.cpp index bdf527f..8f0b224 100644 --- a/src/coreclr/src/jit/compiler.cpp +++ b/src/coreclr/src/jit/compiler.cpp @@ -4194,11 +4194,17 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags assert(!fgComputePredsDone); if (fgCheapPredsValid) { - // Remove cheap predecessors before inlining; allowing the cheap predecessor lists to be inserted - // with inlined blocks causes problems. + // Remove cheap predecessors before inlining and fat call transformation; + // allowing the cheap predecessor lists to be inserted causes problems + // with splitting existing blocks. fgRemovePreds(); } + if (IsTargetAbi(CORINFO_CORERT_ABI) && doesMethodHaveFatPointer()) + { + fgTransformFatCalli(); + } + EndPhase(PHASE_IMPORTATION); if (compIsForInlining()) diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 82228b6..a148d7b 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -3494,6 +3494,8 @@ public: void fgImport(); + void fgTransformFatCalli(); + void fgInline(); void fgRemoveEmptyTry(); @@ -4692,6 +4694,9 @@ private: #ifdef DEBUG static fgWalkPreFn fgDebugCheckInlineCandidates; + + void CheckNoFatPointerCandidatesLeft(); + static fgWalkPreFn fgDebugCheckFatPointerCandidates; #endif void fgPromoteStructs(); @@ -5468,11 +5473,27 @@ public: } }; -#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an array -#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type. -#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores. -#define OMF_HAS_VTABLEREF 0x00000008 // Method contains method table reference. -#define OMF_HAS_NULLCHECK 0x00000010 // Method contains null check. +#define OMF_HAS_NEWARRAY 0x00000001 // Method contains 'new' of an array +#define OMF_HAS_NEWOBJ 0x00000002 // Method contains 'new' of an object type. +#define OMF_HAS_ARRAYREF 0x00000004 // Method contains array element loads or stores. +#define OMF_HAS_VTABLEREF 0x00000008 // Method contains method table reference. +#define OMF_HAS_NULLCHECK 0x00000010 // Method contains null check. +#define OMF_HAS_FATPOINTER 0x00000020 // Method contains call, that needs fat pointer transformation. + + bool doesMethodHaveFatPointer() + { + return (optMethodFlags & OMF_HAS_FATPOINTER) != 0; + } + + void setMethodHasFatPointer() + { + optMethodFlags |= OMF_HAS_FATPOINTER; + } + + void clearMethodHasFatPointer() + { + optMethodFlags &= ~OMF_HAS_FATPOINTER; + } unsigned optMethodFlags; diff --git a/src/coreclr/src/jit/flowgraph.cpp b/src/coreclr/src/jit/flowgraph.cpp index d265bde..8ef6b23 100644 --- a/src/coreclr/src/jit/flowgraph.cpp +++ b/src/coreclr/src/jit/flowgraph.cpp @@ -23870,6 +23870,389 @@ void Compiler::fgUpdateFinallyTargetFlags() } } } - #endif // FEATURE_EH_FUNCLETS && defined(_TARGET_ARM_) } + +// FatCalliTransformer transforms calli that can use fat function pointer. +// Fat function pointer is pointer with the second least significant bit set, +// if the bit is set, the pointer (after clearing the bit) actually points to +// a tuple where +// instantiationArgument is a hidden first argument required by method pointer. +// +// Fat pointers are used in CoreRT as a replacement for instantiating stubs, +// because CoreRT can't generate stubs in runtime. +// +// Jit is responsible for the checking the bit, do the regular call if it is not set +// or load hidden argument, fix the pointer and make a call with the fixed pointer and +// the instantiation argument. +// +// before: +// current block +// { +// previous statements +// transforming statement +// { +// call with GTF_CALL_M_FAT_POINTER_CHECK flag set in function ptr +// } +// subsequent statements +// } +// +// after: +// current block +// { +// previous statements +// } BBJ_NONE check block +// check block +// { +// jump to else if function ptr has GTF_CALL_M_FAT_POINTER_CHECK set. +// } BBJ_COND then block, else block +// then block +// { +// original statement +// } BBJ_ALWAYS remainder block +// else block +// { +// unset GTF_CALL_M_FAT_POINTER_CHECK +// load actual function pointer +// load instantiation argument +// create newArgList = (instantiation argument, original argList) +// call (actual function pointer, newArgList) +// } BBJ_NONE remainder block +// remainder block +// { +// subsequent statements +// } +// +class FatCalliTransformer +{ +public: + FatCalliTransformer(Compiler* compiler) : compiler(compiler) + { + } + + //------------------------------------------------------------------------ + // Run: run transformation for each block. + // + void Run() + { + for (BasicBlock* block = compiler->fgFirstBB; block != nullptr; block = block->bbNext) + { + TransformBlock(block); + } + } + +private: + //------------------------------------------------------------------------ + // TransformBlock: look through statements and transform statements with fat pointer calls. + // + void TransformBlock(BasicBlock* block) + { + for (GenTreeStmt* stmt = block->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + if (ContainsFatCalli(stmt)) + { + StatementTransformer stmtTransformer(compiler, block, stmt); + stmtTransformer.Run(); + } + } + } + + //------------------------------------------------------------------------ + // ContainsFatCalli: check does this statement contain fat pointer call. + // + // Checks fatPointerCandidate in form of call() or lclVar = call(). + // + // Return Value: + // true if contains, false otherwise. + // + bool ContainsFatCalli(GenTreeStmt* stmt) + { + GenTreePtr fatPointerCandidate = stmt->gtStmtExpr; + if (fatPointerCandidate->OperIsAssignment()) + { + fatPointerCandidate = fatPointerCandidate->gtGetOp2(); + } + return fatPointerCandidate->IsCall() && fatPointerCandidate->AsCall()->IsFatPointerCandidate(); + } + + class StatementTransformer + { + public: + StatementTransformer(Compiler* compiler, BasicBlock* block, GenTreeStmt* stmt) + : compiler(compiler), currBlock(block), stmt(stmt) + { + remainderBlock = nullptr; + checkBlock = nullptr; + thenBlock = nullptr; + elseBlock = nullptr; + doesReturnValue = stmt->gtStmtExpr->OperIsAssignment(); + origCall = GetCall(stmt); + fptrAddress = origCall->gtCallAddr; + pointerType = fptrAddress->TypeGet(); + } + + //------------------------------------------------------------------------ + // Run: transform the statement as described above. + // + void Run() + { + ClearFatFlag(); + CreateRemainder(); + CreateCheck(); + CreateThen(); + CreateElse(); + + RemoveOldStatement(); + SetWeights(); + ChainFlow(); + } + + private: + //------------------------------------------------------------------------ + // GetCall: find a call in a statement. + // + // Arguments: + // callStmt - the statement with the call inside. + // + // Return Value: + // call tree node pointer. + GenTreeCall* GetCall(GenTreeStmt* callStmt) + { + GenTreePtr tree = callStmt->gtStmtExpr; + GenTreeCall* call = nullptr; + if (doesReturnValue) + { + assert(tree->OperIsAssignment()); + call = tree->gtGetOp2()->AsCall(); + } + else + { + call = tree->AsCall(); // call with void return type. + } + return call; + } + + //------------------------------------------------------------------------ + // ClearFatFlag: clear fat pointer candidate flag from the original call. + // + void ClearFatFlag() + { + origCall->ClearFatPointerCandidate(); + } + + //------------------------------------------------------------------------ + // CreateRemainder: split current block at the fat call stmt and + // insert statements after the call into remainderBlock. + // + void CreateRemainder() + { + remainderBlock = compiler->fgSplitBlockAfterStatement(currBlock, stmt); + unsigned propagateFlags = currBlock->bbFlags & BBF_GC_SAFE_POINT; + remainderBlock->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL | propagateFlags; + } + + //------------------------------------------------------------------------ + // CreateCheck: create check block, that checks fat pointer bit set. + // + void CreateCheck() + { + checkBlock = CreateAndInsertBasicBlock(BBJ_COND, currBlock); + GenTreePtr fatPointerMask = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, FAT_POINTER_MASK); + GenTreePtr fptrAddressCopy = compiler->gtCloneExpr(fptrAddress); + GenTreePtr fatPointerAnd = compiler->gtNewOperNode(GT_AND, TYP_I_IMPL, fptrAddressCopy, fatPointerMask); + GenTreePtr zero = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, 0); + GenTreePtr fatPointerCmp = compiler->gtNewOperNode(GT_NE, TYP_INT, fatPointerAnd, zero); + GenTreePtr jmpTree = compiler->gtNewOperNode(GT_JTRUE, TYP_VOID, fatPointerCmp); + GenTreePtr jmpStmt = compiler->fgNewStmtFromTree(jmpTree, stmt->gtStmt.gtStmtILoffsx); + compiler->fgInsertStmtAtEnd(checkBlock, jmpStmt); + } + + //------------------------------------------------------------------------ + // CreateCheck: create then block, that is executed if call address is not fat pointer. + // + void CreateThen() + { + thenBlock = CreateAndInsertBasicBlock(BBJ_ALWAYS, checkBlock); + GenTreePtr nonFatCallStmt = compiler->gtCloneExpr(stmt)->AsStmt(); + compiler->fgInsertStmtAtEnd(thenBlock, nonFatCallStmt); + } + + //------------------------------------------------------------------------ + // CreateCheck: create else block, that is executed if call address is fat pointer. + // + void CreateElse() + { + elseBlock = CreateAndInsertBasicBlock(BBJ_NONE, thenBlock); + + GenTreePtr fixedFptrAddress = GetFixedFptrAddress(); + GenTreePtr actualCallAddress = compiler->gtNewOperNode(GT_IND, pointerType, fixedFptrAddress); + GenTreePtr hiddenArgument = GetHiddenArgument(fixedFptrAddress); + + GenTreeStmt* fatStmt = CreateFatCallStmt(actualCallAddress, hiddenArgument); + compiler->fgInsertStmtAtEnd(elseBlock, fatStmt); + } + + //------------------------------------------------------------------------ + // CreateAndInsertBasicBlock: ask compiler to create new basic block. + // and insert in into the basic block list. + // + // Arguments: + // jumpKind - jump kind for the new basic block + // insertAfter - basic block, after which compiler has to insert the new one. + // + // Return Value: + // new basic block. + BasicBlock* CreateAndInsertBasicBlock(BBjumpKinds jumpKind, BasicBlock* insertAfter) + { + BasicBlock* block = compiler->fgNewBBafter(jumpKind, insertAfter, true); + if ((insertAfter->bbFlags & BBF_INTERNAL) == 0) + { + block->bbFlags &= ~BBF_INTERNAL; + block->bbFlags |= BBF_IMPORTED; + } + return block; + } + + //------------------------------------------------------------------------ + // GetFixedFptrAddress: clear fat pointer bit from fat pointer address. + // + // Return Value: + // address without fat pointer bit set. + GenTreePtr GetFixedFptrAddress() + { + GenTreePtr fptrAddressCopy = compiler->gtCloneExpr(fptrAddress); + GenTreePtr fatPointerMask = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, FAT_POINTER_MASK); + return compiler->gtNewOperNode(GT_XOR, pointerType, fptrAddressCopy, fatPointerMask); + } + + //------------------------------------------------------------------------ + // GetHiddenArgument: load hidden argument. + // + // Arguments: + // fixedFptrAddress - pointer to the tuple + // + // Return Value: + // loaded hidden argument. + GenTreePtr GetHiddenArgument(GenTreePtr fixedFptrAddress) + { + GenTreePtr fixedFptrAddressCopy = compiler->gtCloneExpr(fixedFptrAddress); + GenTreePtr wordSize = new (compiler, GT_CNS_INT) GenTreeIntCon(TYP_I_IMPL, genTypeSize(TYP_I_IMPL)); + GenTreePtr hiddenArgumentPtrPtr = + compiler->gtNewOperNode(GT_ADD, pointerType, fixedFptrAddressCopy, wordSize); + GenTreePtr hiddenArgumentPtr = compiler->gtNewOperNode(GT_IND, pointerType, hiddenArgumentPtrPtr); + return compiler->gtNewOperNode(GT_IND, fixedFptrAddressCopy->TypeGet(), hiddenArgumentPtr); + } + + //------------------------------------------------------------------------ + // CreateFatCallStmt: create call with fixed call address and hidden argument in the args list. + // + // Arguments: + // actualCallAddress - fixed call address + // hiddenArgument - loaded hidden argument + // + // Return Value: + // created call node. + GenTreeStmt* CreateFatCallStmt(GenTreePtr actualCallAddress, GenTreePtr hiddenArgument) + { + GenTreeStmt* fatStmt = compiler->gtCloneExpr(stmt)->AsStmt(); + GenTreePtr fatTree = fatStmt->gtStmtExpr; + GenTreeCall* fatCall = GetCall(fatStmt); + fatCall->gtCallAddr = actualCallAddress; + GenTreeArgList* args = fatCall->gtCallArgs; + args = compiler->gtNewListNode(hiddenArgument, args); + fatCall->gtCallArgs = args; + return fatStmt; + } + + //------------------------------------------------------------------------ + // RemoveOldStatement: remove original stmt from current block. + // + void RemoveOldStatement() + { + compiler->fgRemoveStmt(currBlock, stmt); + } + + //------------------------------------------------------------------------ + // SetWeights: set weights for new blocks. + // + void SetWeights() + { + remainderBlock->inheritWeight(currBlock); + checkBlock->inheritWeight(currBlock); + thenBlock->inheritWeightPercentage(currBlock, HIGH_PROBABILITY); + elseBlock->inheritWeightPercentage(currBlock, 100 - HIGH_PROBABILITY); + } + + //------------------------------------------------------------------------ + // ChainFlow: link new blocks into correct cfg. + // + void ChainFlow() + { + assert(!compiler->fgComputePredsDone); + checkBlock->bbJumpDest = elseBlock; + thenBlock->bbJumpDest = remainderBlock; + } + + Compiler* compiler; + BasicBlock* currBlock; + BasicBlock* remainderBlock; + BasicBlock* checkBlock; + BasicBlock* thenBlock; + BasicBlock* elseBlock; + GenTreeStmt* stmt; + GenTreeCall* origCall; + GenTreePtr fptrAddress; + var_types pointerType; + bool doesReturnValue; + + const int FAT_POINTER_MASK = 0b00000010; + const int HIGH_PROBABILITY = 80; + }; + + Compiler* compiler; +}; + +#ifdef DEBUG + +//------------------------------------------------------------------------ +// fgDebugCheckFatPointerCandidates: callback to make sure there are no more GTF_CALL_M_FAT_POINTER_CHECK calls. +// +Compiler::fgWalkResult Compiler::fgDebugCheckFatPointerCandidates(GenTreePtr* pTree, fgWalkData* data) +{ + GenTreePtr tree = *pTree; + if (tree->IsCall()) + { + assert(!tree->AsCall()->IsFatPointerCandidate()); + } + return WALK_CONTINUE; +} + +//------------------------------------------------------------------------ +// CheckNoFatPointerCandidatesLeft: walk through blocks and check that there are no fat pointer candidates left. +// +void Compiler::CheckNoFatPointerCandidatesLeft() +{ + assert(!doesMethodHaveFatPointer()); + for (BasicBlock* block = fgFirstBB; block != nullptr; block = block->bbNext) + { + for (GenTreeStmt* stmt = fgFirstBB->firstStmt(); stmt != nullptr; stmt = stmt->gtNextStmt) + { + fgWalkTreePre(&stmt->gtStmtExpr, fgDebugCheckFatPointerCandidates); + } + } +} +#endif + +//------------------------------------------------------------------------ +// fgTransformFatCalli: find and transform fat calls. +// +void Compiler::fgTransformFatCalli() +{ + assert(IsTargetAbi(CORINFO_CORERT_ABI)); + FatCalliTransformer fatCalliTransformer(this); + fatCalliTransformer.Run(); + clearMethodHasFatPointer(); +#ifdef DEBUG + CheckNoFatPointerCandidatesLeft(); +#endif +} diff --git a/src/coreclr/src/jit/gentree.h b/src/coreclr/src/jit/gentree.h index 10ba4b0..b589023 100644 --- a/src/coreclr/src/jit/gentree.h +++ b/src/coreclr/src/jit/gentree.h @@ -3263,43 +3263,52 @@ struct GenTreeCall final : public GenTree #endif } -#define GTF_CALL_M_EXPLICIT_TAILCALL \ - 0x00000001 // GT_CALL -- the call is "tail" prefixed and importer has performed tail call checks -#define GTF_CALL_M_TAILCALL 0x00000002 // GT_CALL -- the call is a tailcall -#define GTF_CALL_M_VARARGS 0x00000004 // GT_CALL -- the call uses varargs ABI -#define GTF_CALL_M_RETBUFFARG 0x00000008 // GT_CALL -- first parameter is the return buffer argument -#define GTF_CALL_M_DELEGATE_INV 0x00000010 // GT_CALL -- call to Delegate.Invoke -#define GTF_CALL_M_NOGCCHECK 0x00000020 // GT_CALL -- not a call for computing full interruptability -#define GTF_CALL_M_SPECIAL_INTRINSIC 0x00000040 // GT_CALL -- function that could be optimized as an intrinsic - // in special cases. Used to optimize fast way out in morphing -#define GTF_CALL_M_UNMGD_THISCALL \ - 0x00000080 // "this" pointer (first argument) should be enregistered (only for GTF_CALL_UNMANAGED) -#define GTF_CALL_M_VIRTSTUB_REL_INDIRECT \ - 0x00000080 // the virtstub is indirected through a relative address (only for GTF_CALL_VIRT_STUB) -#define GTF_CALL_M_NONVIRT_SAME_THIS \ - 0x00000080 // callee "this" pointer is equal to caller this pointer (only for GTF_CALL_NONVIRT) -#define GTF_CALL_M_FRAME_VAR_DEATH 0x00000100 // GT_CALL -- the compLvFrameListRoot variable dies here (last use) +// clang-format off + +#define GTF_CALL_M_EXPLICIT_TAILCALL 0x00000001 // GT_CALL -- the call is "tail" prefixed and + // importer has performed tail call checks +#define GTF_CALL_M_TAILCALL 0x00000002 // GT_CALL -- the call is a tailcall +#define GTF_CALL_M_VARARGS 0x00000004 // GT_CALL -- the call uses varargs ABI +#define GTF_CALL_M_RETBUFFARG 0x00000008 // GT_CALL -- first parameter is the return buffer argument +#define GTF_CALL_M_DELEGATE_INV 0x00000010 // GT_CALL -- call to Delegate.Invoke +#define GTF_CALL_M_NOGCCHECK 0x00000020 // GT_CALL -- not a call for computing full interruptability +#define GTF_CALL_M_SPECIAL_INTRINSIC 0x00000040 // GT_CALL -- function that could be optimized as an intrinsic + // in special cases. Used to optimize fast way out in morphing +#define GTF_CALL_M_UNMGD_THISCALL 0x00000080 // GT_CALL -- "this" pointer (first argument) + // should be enregistered (only for GTF_CALL_UNMANAGED) +#define GTF_CALL_M_VIRTSTUB_REL_INDIRECT 0x00000080 // the virtstub is indirected through + // a relative address (only for GTF_CALL_VIRT_STUB) +#define GTF_CALL_M_NONVIRT_SAME_THIS 0x00000080 // GT_CALL -- callee "this" pointer is + // equal to caller this pointer (only for GTF_CALL_NONVIRT) +#define GTF_CALL_M_FRAME_VAR_DEATH 0x00000100 // GT_CALL -- the compLvFrameListRoot variable dies here (last use) #ifndef LEGACY_BACKEND -#define GTF_CALL_M_TAILCALL_VIA_HELPER 0x00000200 // GT_CALL -- call is a tail call dispatched via tail call JIT helper. -#endif // !LEGACY_BACKEND +#define GTF_CALL_M_TAILCALL_VIA_HELPER 0x00000200 // GT_CALL -- call is a tail call dispatched via tail call JIT helper. +#endif #if FEATURE_TAILCALL_OPT -#define GTF_CALL_M_IMPLICIT_TAILCALL \ - 0x00000400 // GT_CALL -- call is an opportunistic tail call and importer has performed tail call checks -#define GTF_CALL_M_TAILCALL_TO_LOOP \ - 0x00000800 // GT_CALL -- call is a fast recursive tail call that can be converted into a loop +#define GTF_CALL_M_IMPLICIT_TAILCALL 0x00000400 // GT_CALL -- call is an opportunistic + // tail call and importer has performed tail call checks +#define GTF_CALL_M_TAILCALL_TO_LOOP 0x00000800 // GT_CALL -- call is a fast recursive tail call + // that can be converted into a loop #endif -#define GTF_CALL_M_PINVOKE 0x00001000 // GT_CALL -- call is a pinvoke. This mirrors VM flag CORINFO_FLG_PINVOKE. - // A call marked as Pinvoke is not necessarily a GT_CALL_UNMANAGED. For e.g. - // an IL Stub dynamically generated for a PInvoke declaration is flagged as - // a Pinvoke but not as an unmanaged call. See impCheckForPInvokeCall() to - // know when these flags are set. +#define GTF_CALL_M_PINVOKE 0x00001000 // GT_CALL -- call is a pinvoke. This mirrors VM flag CORINFO_FLG_PINVOKE. + // A call marked as Pinvoke is not necessarily a GT_CALL_UNMANAGED. For e.g. + // an IL Stub dynamically generated for a PInvoke declaration is flagged as + // a Pinvoke but not as an unmanaged call. See impCheckForPInvokeCall() to + // know when these flags are set. + +#define GTF_CALL_M_R2R_REL_INDIRECT 0x00002000 // GT_CALL -- ready to run call is indirected through a relative address +#define GTF_CALL_M_DOES_NOT_RETURN 0x00004000 // GT_CALL -- call does not return +#define GTF_CALL_M_SECURE_DELEGATE_INV 0x00008000 // GT_CALL -- call is in secure delegate +#define GTF_CALL_M_FAT_POINTER_CHECK 0x00010000 // GT_CALL -- CoreRT managed calli needs transformation, that checks + // special bit in calli address. If it is set, then it is necessary + // to restore real function address and load hidden argument + // as the first argument for calli. It is CoreRT replacement for instantiating + // stubs, because executable code cannot be generated at runtime. -#define GTF_CALL_M_R2R_REL_INDIRECT 0x00002000 // GT_CALL -- ready to run call is indirected through a relative address -#define GTF_CALL_M_DOES_NOT_RETURN 0x00004000 // GT_CALL -- call does not return -#define GTF_CALL_M_SECURE_DELEGATE_INV 0x00008000 // GT_CALL -- call is in secure delegate + // clang-format on bool IsUnmanaged() const { @@ -3497,8 +3506,23 @@ struct GenTreeCall final : public GenTree return (gtCallMoreFlags & GTF_CALL_M_DOES_NOT_RETURN) != 0; } + bool IsFatPointerCandidate() const + { + return (gtCallMoreFlags & GTF_CALL_M_FAT_POINTER_CHECK) != 0; + } + bool IsPure(Compiler* compiler) const; + void ClearFatPointerCandidate() + { + gtCallMoreFlags &= ~GTF_CALL_M_FAT_POINTER_CHECK; + } + + void SetFatPointerCandidate() + { + gtCallMoreFlags |= GTF_CALL_M_FAT_POINTER_CHECK; + } + unsigned gtCallMoreFlags; // in addition to gtFlags unsigned char gtCallType : 3; // value from the gtCallTypes enumeration diff --git a/src/coreclr/src/jit/importer.cpp b/src/coreclr/src/jit/importer.cpp index 026628d..206693c 100644 --- a/src/coreclr/src/jit/importer.cpp +++ b/src/coreclr/src/jit/importer.cpp @@ -6434,6 +6434,7 @@ var_types Compiler::impImportCall(OPCODE opcode, eeGetSig(pResolvedToken->token, info.compScopeHnd, impTokenLookupContextHandle, &calliSig); callRetTyp = JITtype2varType(calliSig.retType); + clsHnd = calliSig.retTypeClass; call = impImportIndirectCall(&calliSig, ilOffset); @@ -6462,6 +6463,16 @@ var_types Compiler::impImportCall(OPCODE opcode, call->gtCall.callSig = new (this, CMK_CorSig) CORINFO_SIG_INFO; *call->gtCall.callSig = calliSig; #endif // DEBUG + + if (IsTargetAbi(CORINFO_CORERT_ABI)) + { + bool managedCall = (calliSig.callConv & GTF_CALL_UNMANAGED) == 0; + if (managedCall) + { + call->AsCall()->SetFatPointerCandidate(); + setMethodHasFatPointer(); + } + } } else // (opcode != CEE_CALLI) { @@ -6608,7 +6619,6 @@ var_types Compiler::impImportCall(OPCODE opcode, if ((mflags & CORINFO_FLG_VIRTUAL) && (mflags & CORINFO_FLG_EnC) && (opcode == CEE_CALLVIRT)) { NO_WAY("Virtual call to a function added via EnC is not supported"); - goto DONE_CALL; } if ((sig->callConv & CORINFO_CALLCONV_MASK) != CORINFO_CALLCONV_DEFAULT && @@ -7544,10 +7554,8 @@ DONE: } } -// Note: we assume that small return types are already normalized by the managed callee -// or by the pinvoke stub for calls to unmanaged code. - -DONE_CALL: + // Note: we assume that small return types are already normalized by the managed callee + // or by the pinvoke stub for calls to unmanaged code. if (!bIntrinsicImported) { @@ -7592,6 +7600,7 @@ DONE_CALL: impMarkInlineCandidate(call, exactContextHnd, callInfo); } +DONE_CALL: // Push or append the result of the call if (callRetTyp == TYP_VOID) { @@ -7644,9 +7653,11 @@ DONE_CALL: } } - if (call->gtOper == GT_CALL) + if (call->IsCall()) { // Sometimes "call" is not a GT_CALL (if we imported an intrinsic that didn't turn into a call) + + bool fatPointerCandidate = call->AsCall()->IsFatPointerCandidate(); if (varTypeIsStruct(callRetTyp)) { call = impFixupCallStructReturn(call, sig->retTypeClass); @@ -7655,6 +7666,7 @@ DONE_CALL: if ((call->gtFlags & GTF_CALL_INLINE_CANDIDATE) != 0) { assert(opts.OptEnabled(CLFLG_INLINING)); + assert(!fatPointerCandidate); // We should not try to inline calli. // Make the call its own tree (spill the stack if needed). impAppendTree(call, (unsigned)CHECK_SPILL_ALL, impCurStmtOffs); @@ -7664,6 +7676,24 @@ DONE_CALL: } else { + if (fatPointerCandidate) + { + // fatPointer candidates should be in statements of the form call() or var = call(). + // Such form allows to find statements with fat calls without walking through whole trees + // and removes problems with cutting trees. + assert(!bIntrinsicImported); + assert(IsTargetAbi(CORINFO_CORERT_ABI)); + if (call->OperGet() != GT_LCL_VAR) // can be already converted by impFixupCallStructReturn. + { + unsigned calliSlot = lvaGrabTemp(true DEBUGARG("calli")); + LclVarDsc* varDsc = &lvaTable[calliSlot]; + varDsc->lvVerTypeInfo = tiRetVal; + impAssignTempGen(calliSlot, call, clsHnd, (unsigned)CHECK_SPILL_NONE); + // impAssignTempGen can change src arg list and return type for call that returns struct. + var_types type = genActualType(lvaTable[calliSlot].TypeGet()); + call = gtNewLclvNode(calliSlot, type); + } + } // For non-candidates we must also spill, since we // might have locals live on the eval stack that this // call can modify. -- 2.7.4