From d62f9d13d049bb622f9a9ec276aed1b2796b0cfa Mon Sep 17 00:00:00 2001 From: Joseph Tremoulet Date: Fri, 20 Mar 2015 13:32:10 -0400 Subject: [PATCH 1/2] Add Name parameter to createTemporary Callers can pass an explicit name to create more readable IR. --- include/Reader/readerir.h | 3 ++- lib/Reader/readerir.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/include/Reader/readerir.h b/include/Reader/readerir.h index 99a069326d8..9836da7ad8c 100644 --- a/include/Reader/readerir.h +++ b/include/Reader/readerir.h @@ -977,7 +977,8 @@ class GenIR : public ReaderBase { /// /// \param Ty Type for the new variable. /// \returns Instruction establishing the variable's location. - llvm::Instruction *createTemporary(llvm::Type *Ty); + llvm::Instruction *createTemporary(llvm::Type *Ty, + const llvm::Twine &Name = ""); IRNode * loadManagedAddress(const std::vector &UnmanagedAddresses, diff --git a/lib/Reader/readerir.cpp b/lib/Reader/readerir.cpp index 3e8bddb4ae4..547cd0670ba 100644 --- a/lib/Reader/readerir.cpp +++ b/lib/Reader/readerir.cpp @@ -496,7 +496,7 @@ Function *GenIR::getFunction(CORINFO_METHOD_HANDLE MethodHandle) { bool GenIR::objIsThis(IRNode *Obj) { return false; } // Create a new temporary with the indicated type. -Instruction *GenIR::createTemporary(Type *Ty) { +Instruction *GenIR::createTemporary(Type *Ty, const Twine &Name) { // Put the alloca for this temporary into the entry block so // the temporary uses can appear anywhere. IRBuilder<>::InsertPoint IP = LLVMBuilder->saveIP(); @@ -512,7 +512,7 @@ Instruction *GenIR::createTemporary(Type *Ty) { LLVMBuilder->SetInsertPoint(TempInsertionPoint->getNextNode()); } - AllocaInst *AllocaInst = LLVMBuilder->CreateAlloca(Ty); + AllocaInst *AllocaInst = LLVMBuilder->CreateAlloca(Ty, nullptr, Name); // Update the end of the alloca range. TempInsertionPoint = AllocaInst; LLVMBuilder->restoreIP(IP); From 5f6af8ff2a79e2ad1b67bd07f23fd45f8e7609fb Mon Sep 17 00:00:00 2001 From: Joseph Tremoulet Date: Fri, 20 Mar 2015 13:33:00 -0400 Subject: [PATCH 2/2] Implement finally continuation selection for leave For each finally, generate a continuation selector variable, and end the finally with a switch that jumps to a continuation based on the selector. For each leave instruction, set the selector variables appropriately for each finally handler being exited before jumping to the innermost finally. These explicit sequences match what Clang generates for gotos across cleanups and what the WinEHPrepare funclet extractor will expect to see. The 'default' case for each switch targets a block with just an Unreachable in it (again following Clang's example). The instructions to set the selector variables are generated and inserted in the IR during first-pass flow-graph building. I've added a map to the reader objects tracking where selector stores have been inserted at which MSIL offsets, updated fgNodeGetEndInsertIRNode to consult the map, and updated a few places that were manually grabbing a block's terminator to instead call fgNodeGetEndInsertIRNode, so that 2nd pass IR insertion can use the correct insertion point when it adds IR to these blocks. The switch instruction itself (and selector load feeding it) is generated when processing the first leave across a particular finally, and inserted when processing the first endfinally for that finally. Since a finally region can have an arbitrary number of endfinally instructions, endfinally becomes a branch that targets a block that holds the switch, so that all endfinally instructions for a given finally can branch to the shared switch. Resolves #260 --- Documentation/llilc-jit-eh.md | 20 ++- include/Reader/reader.h | 85 +++++++----- include/Reader/readerir.h | 23 +++- lib/Reader/GenIRStubs.cpp | 2 - lib/Reader/reader.cpp | 190 +++++++++----------------- lib/Reader/readerir.cpp | 245 +++++++++++++++++++++++++++++++--- 6 files changed, 371 insertions(+), 194 deletions(-) diff --git a/Documentation/llilc-jit-eh.md b/Documentation/llilc-jit-eh.md index 2948df30cc2..4fe65d56702 100644 --- a/Documentation/llilc-jit-eh.md +++ b/Documentation/llilc-jit-eh.md @@ -498,17 +498,12 @@ function correctly. Thus, in order to unblock progress in other areas of LLILC during bring-up, initially the EH support will be stubbed out, with just enough functionality for such test programs to pass. In particular, code with EH constructs is expected to compile cleanly, but it is only -expected to behave correctly if it does not attempt to raise exceptions or -make nontrivial finally clause invocations at runtime. To avoid silent bad -code generation, any nontrivial finally clause invocations (i.e. invocations -where control upon exit from the finally needs to be transferred anywhere -other than the MSIL immediately following the finally handler) will be -detected and rejected at compile-time. The throw operator and the explicit -test/throw sequences for implicit MSIL exceptions will be implemented on top -of the stub support (with throws using `call` rather than `invoke`), to -reflect correct program semantics and allow compilation of code with -conditional exceptions that will execute correctly if the exception -conditions don't arise at run-time. +expected to behave correctly if it does not attempt to raise exceptions at +runtime. The throw operator and the explicit test/throw sequences for +implicit MSIL exceptions will be implemented on top of the stub support +(with throws using `call` rather than `invoke`), to reflect correct program +semantics and allow compilation of code with conditional exceptions that +will execute correctly if the exception conditions don't arise at run-time. Once the stub support (with explicit and implicit exceptions) is in place, the next steps will be to translate handlers in the reader and generate EH @@ -529,8 +524,9 @@ for explicit throws and some but not all implicit exceptions. In summary, the plan/status is: 1. [ ] Stub EH support - - [x] Reader discards handlers + - [x] Reader discards catch/filter/fault handlers - [x] Explicit throw becomes helper call + - [x] Continuation passing for finally handlers invoked by `leave` - [ ] Implicit exceptions expanded to explicit test/throw sequences - [x] Null dereference - [ ] Divide by zero diff --git a/include/Reader/reader.h b/include/Reader/reader.h index 4e3a1d92feb..7b826f0fad5 100644 --- a/include/Reader/reader.h +++ b/include/Reader/reader.h @@ -583,6 +583,12 @@ void rgnSetCatchTryRegion(EHRegion *CatchRegion, EHRegion *TryRegion); mdToken rgnGetCatchClassToken(EHRegion *CatchRegion); void rgnSetCatchClassToken(EHRegion *CatchRegion, mdToken Token); +/// Get the finally region attached to the given \p TryRegion, if any. +/// +/// \param TryRegion Try region to check for finally handler +/// \returns The finally protecting this try if it exists; else nullptr +EHRegion *getFinallyRegion(EHRegion *TryRegion); + // Interface to GenIR defined Flow Graph structures. // Implementation Supplied by Jit Client EHRegion *fgNodeGetRegion(FlowGraphNode *FgNode); @@ -596,11 +602,6 @@ IRNode *fgNodeGetStartIRNode(FlowGraphNode *FgNode); // Get the first non-placekeeping node in block IRNode *fgNodeGetStartInsertIRNode(FlowGraphNode *FgNode); -// Get the last non-placekeeping node in block -IRNode *fgNodeGetEndInsertIRNode(FlowGraphNode *FgNode); - -IRNode *fgNodeGetEndIRInsertionPoint(FlowGraphNode *FgNode); - GlobalVerifyData *fgNodeGetGlobalVerifyData(FlowGraphNode *Fg); void fgNodeSetGlobalVerifyData(FlowGraphNode *Fg, GlobalVerifyData *GvData); @@ -855,11 +856,6 @@ class ReaderBase { // SEQUENCE POINT Info ReaderBitVector *CustomSequencePoints; - // EH Info - CORINFO_EH_CLAUSE *EhClauseInfo; // raw eh clause info - EHRegion *EhRegionTree; - EHRegionList *AllRegionList; - // Fg Info - unused after fg is built // NodeOffsetListArray is an ordered array of FlowGraphNodeOffsetList*. @@ -878,6 +874,11 @@ class ReaderBase { protected: uint32_t CurrentBranchDepth; + // EH Info + CORINFO_EH_CLAUSE *EhClauseInfo; // raw eh clause info + EHRegion *EhRegionTree; + EHRegionList *AllRegionList; + // \brief Indicates that null checks use explicit compare+branch IR sequences // // Compiling with this set to false isn't really supported (the generated IR @@ -1117,6 +1118,36 @@ class ReaderBase { void readBytesForFlowGraphNodeHelper(ReadBytesForFlowGraphNodeHelperParam *Param); +protected: + /// \brief Create or return a flow graph node for the indicated offset + /// + /// This method sees if there is an existing flow graph node that begins at + /// the indicated target. If so, \p Node is set to this block. If not, a + /// temporary block is allocated to use as a target, and an entry is added + /// to the \p NodeOffsetListArray so a subsequent pass can update the + /// temporary target blocks to real target blocks. + /// + /// \param Node [out] Node to use as the branch target + /// \param TargetOffset MSIL offset of the branch target + /// \returns List node of target in offset list + FlowGraphNodeOffsetList *fgAddNodeMSILOffset(FlowGraphNode **Node, + uint32_t TargetOffset); + + /// Get the innermost finally region enclosing the given \p Offset + /// + /// \param Offset MSIL offset of interest + /// \returns The innermost finally region enclosing \p Offset if one exists; + /// nullptr otherwise + EHRegion *getInnermostFinallyRegion(uint32_t Offset); + + /// Find the next-innermost region enclosing the given \p Offset + /// + /// \param OuterRegion Limit search to regions nested inside OuterRegion + /// \param Offset Limit search to regions enclosing Offset + /// \returns The outermost region that is nested inside \p OuterRegion and + /// that includes \p Offset, if such a region exists; else nullptr + EHRegion *getInnerEnclosingRegion(EHRegion *OuterRegion, uint32_t Offset); + private: /// \brief Perform special processing for blocks that start EH regions. /// @@ -1300,10 +1331,11 @@ class ReaderBase { /// there can be more than one of these in a finally region. /// /// \param BlockNode the block that is the end of the finally - /// \param CurentOffset msil offset for the endfinally instruction - /// \param IsLexicalEnd true if the endfinally is at the end of the finally - IRNode *fgMakeEndFinallyHelper(IRNode *BlockNode, uint32_t Offset, - bool IsLexicalEnd); + /// \param FinallyRegion the finally region being ended + /// \param Offset msil offset for the endfinally instruction + /// \returns the branch generated to terminate the block for this endfinally + IRNode *fgMakeEndFinallyHelper(IRNode *BlockNode, EHRegion *FinallyRegion, + uint32_t Offset); /// \brief Remove all unreachable blocks /// @@ -1325,20 +1357,6 @@ class ReaderBase { /// there are large amounts of MSIL for the method. int32_t *FgGetRegionCanonicalExitOffsetBuff; - /// \brief Create or return a flow graph node for the indicated offset - /// - /// This method sees if there is an existing flow graph node that begins at - /// the indicated target. If so, \p Node is set to this block. If not, a - /// temporary block is allocated to use as a target, and an entry is added - /// to the \p NodeOffsetListArray so a subsequent pass can update the - /// temporary target blocks to real target blocks. - /// - /// \param Node [out] Node to use as the branch target - /// \param TargetOffset MSIL offset of the branch target - /// \returns List node of target in offset list - FlowGraphNodeOffsetList *fgAddNodeMSILOffset(FlowGraphNode **Node, - uint32_t TargetOffset); - /// Determine if a leave exits the enclosing EH region in a non-local manner. /// /// \param Fg flow graph node containing the leave @@ -2116,6 +2134,9 @@ class ReaderBase { virtual void jmp(ReaderBaseNS::CallOpcode Opcode, mdToken Token, bool HasThis, bool HasVarArg) = 0; + virtual uint32_t updateLeaveOffset(uint32_t LeaveOffset, uint32_t NextOffset, + FlowGraphNode *LeaveBlock, + uint32_t TargetOffset) = 0; virtual void leave(uint32_t TargetOffset, bool IsNonLocal, bool EndsWithNonLocalGoto) = 0; virtual IRNode *loadArg(uint32_t ArgOrdinal, bool IsJmp) = 0; @@ -2380,6 +2401,7 @@ class ReaderBase { virtual void fgDeleteBlock(FlowGraphNode *Block) = 0; virtual void fgDeleteEdge(FlowGraphEdgeList *Arc) = 0; virtual void fgDeleteNodesFromBlock(FlowGraphNode *Block) = 0; + virtual IRNode *fgNodeGetEndInsertIRNode(FlowGraphNode *FgNode) = 0; // Returns true iff client considers the JMP recursive and wants a // loop back-edge rather than a forward edge to the exit label. @@ -2400,8 +2422,8 @@ class ReaderBase { virtual IRNode *fgMakeBranch(IRNode *LabelNode, IRNode *BlockNode, uint32_t CurrentOffset, bool IsConditional, bool IsNominal) = 0; - virtual IRNode *fgMakeEndFinally(IRNode *BlockNode, uint32_t CurrentOffset, - bool IsLexicalEnd) = 0; + virtual IRNode *fgMakeEndFinally(IRNode *BlockNode, EHRegion *FinallyRegion, + uint32_t CurrentOffset) = 0; // turns an unconditional branch to the entry label into a fall-through // or a branch to the exit label, depending on whether it was a recursive @@ -2562,9 +2584,6 @@ class ReaderBase { char DummyLastBaseField; // Fields after this one will not be initialized in the constructor. /////////////////////////////////////////////////////////////////////// - - // Deferred NYI map for Leave instructions (temporary) - std::map NyiLeaveMap; }; /// \brief The exception that is thrown when a particular operation is not yet diff --git a/include/Reader/readerir.h b/include/Reader/readerir.h index 9836da7ad8c..2f704f12de0 100644 --- a/include/Reader/readerir.h +++ b/include/Reader/readerir.h @@ -333,6 +333,12 @@ class GenIR : public ReaderBase { throw NotYetImplementedException("jmp"); }; + virtual uint32_t updateLeaveOffset(uint32_t LeaveOffset, uint32_t NextOffset, + FlowGraphNode *LeaveBlock, + uint32_t TargetOffset) override; + uint32_t updateLeaveOffset(EHRegion *Region, uint32_t LeaveOffset, + uint32_t NextOffset, FlowGraphNode *LeaveBlock, + uint32_t TargetOffset, bool &IsInHandler); void leave(uint32_t TargetOffset, bool IsNonLocal, bool EndsWithNonLocalGoto) override; IRNode *loadArg(uint32_t ArgOrdinal, bool IsJmp) override; @@ -581,6 +587,7 @@ class GenIR : public ReaderBase { throw NotYetImplementedException("fgDeleteEdge"); }; void fgDeleteNodesFromBlock(FlowGraphNode *Block) override; + IRNode *fgNodeGetEndInsertIRNode(FlowGraphNode *FgNode) override; bool commonTailCallChecks(CORINFO_METHOD_HANDLE DeclaredMethod, CORINFO_METHOD_HANDLE ExactMethod, @@ -611,8 +618,8 @@ class GenIR : public ReaderBase { IRNode *fgMakeBranch(IRNode *LabelNode, IRNode *InsertNode, uint32_t CurrentOffset, bool IsConditional, bool IsNominal) override; - IRNode *fgMakeEndFinally(IRNode *InsertNode, uint32_t CurrentOffset, - bool IsLexicalEnd) override; + IRNode *fgMakeEndFinally(IRNode *InsertNode, EHRegion *FinallyRegion, + uint32_t CurrentOffset) override; // turns an unconditional branch to the entry label into a fall-through // or a branch to the exit label, depending on whether it was a recursive @@ -976,6 +983,7 @@ class GenIR : public ReaderBase { /// used anywhere within the method. /// /// \param Ty Type for the new variable. + /// \param Name Optional name for the new variable. /// \returns Instruction establishing the variable's location. llvm::Instruction *createTemporary(llvm::Type *Ty, const llvm::Twine &Name = ""); @@ -1070,6 +1078,15 @@ class GenIR : public ReaderBase { private: LLILCJitContext *JitContext; llvm::Function *Function; + // The LLVMBuilder has a notion of a current insertion point. During the + // first-pass flow-graph construction, each method sets the insertion point + // explicitly before inserting IR (the fg- methods typically take an + // InsertNode parameter indicating where to set it). During the second pass + // translation of the non-flow instructions, the insertion point is + // explicitly set at the start of each block (in beginFlowGraphNode), and + // translation methods assume that the builder's current insertion point is + // where they should be inserted (the gen- methods do not take explicit + // insertion point parameters). llvm::IRBuilder<> *LLVMBuilder; std::map *ClassTypeMap; std::map, @@ -1080,6 +1097,8 @@ class GenIR : public ReaderBase { std::vector LocalVarCorTypes; std::vector Arguments; std::vector ArgumentCorTypes; + llvm::DenseMap ContinuationStoreMap; + llvm::BasicBlock *UnreachableContinuationBlock; CorInfoType ReturnCorType; bool HasThis; bool HasTypeParameter; diff --git a/lib/Reader/GenIRStubs.cpp b/lib/Reader/GenIRStubs.cpp index c043c89d8c3..aa7e932ed38 100644 --- a/lib/Reader/GenIRStubs.cpp +++ b/lib/Reader/GenIRStubs.cpp @@ -26,8 +26,6 @@ IRNode *fgNodeGetStartInsertIRNode(FlowGraphNode *FgNode) { return fgNodeGetStartIRNode(FgNode); } -IRNode *fgNodeGetEndIRInsertionPoint(FlowGraphNode *FgNode) { return nullptr; } - GlobalVerifyData *fgNodeGetGlobalVerifyData(FlowGraphNode *Fg) { throw NotYetImplementedException("fgNodeGetGlobalVerifyData"); } diff --git a/lib/Reader/reader.cpp b/lib/Reader/reader.cpp index 8f8f8e0535c..d4811fb6e1b 100644 --- a/lib/Reader/reader.cpp +++ b/lib/Reader/reader.cpp @@ -2635,6 +2635,58 @@ void ReaderBase::fgInsertEHAnnotations(EHRegion *Region) { } } +EHRegion *ReaderBase::getInnermostFinallyRegion(uint32_t Offset) { + EHRegion *FinallyRegion = nullptr; + // Walk from outer to inner regions + for (EHRegion *TestRegion = EhRegionTree; TestRegion != nullptr; + TestRegion = getInnerEnclosingRegion(TestRegion, Offset)) { + // TestRegion encloses Offset + if (rgnGetRegionType(TestRegion) == ReaderBaseNS::RGN_Finally) { + // Found a finally region nested inside our previous best + FinallyRegion = TestRegion; + } + } + + return FinallyRegion; +} + +EHRegion *ReaderBase::getInnerEnclosingRegion(EHRegion *OuterRegion, + uint32_t Offset) { + // Search the immediate children of the given region + for (EHRegionList *ChildNode = rgnGetChildList(OuterRegion); ChildNode; + ChildNode = rgnListGetNext(ChildNode)) { + + EHRegion *Child = rgnListGetRgn(ChildNode); + + if ((Offset < rgnGetEndMSILOffset(Child)) && + (Offset >= rgnGetStartMSILOffset(Child))) { + // This offset is in this child region. + return Child; + } + + if (rgnGetRegionType(Child) == ReaderBaseNS::RegionKind::RGN_Try) { + // A handler region for the try is a child of it in the tree but follows + // it in the IR, so we explicitly have to check for grandchildren in this + // case (the current offset falls in the grandchild's range but not the + // child's range). + for (EHRegionList *GrandchildNode = rgnGetChildList(Child); + GrandchildNode; GrandchildNode = rgnListGetNext(GrandchildNode)) { + + EHRegion *Grandchild = rgnListGetRgn(GrandchildNode); + + if ((Offset < rgnGetEndMSILOffset(Grandchild)) && + (Offset >= rgnGetStartMSILOffset(Grandchild))) { + // This offset is in this grandchild region + return Grandchild; + } + } + } + } + + // No inner region encloses this offset + return nullptr; +} + IRNode *ReaderBase::fgAddCaseToCaseListHelper(IRNode *SwitchNode, IRNode *LabelNode, uint32_t Element) { @@ -2658,11 +2710,11 @@ IRNode *ReaderBase::fgMakeBranchHelper(IRNode *LabelNode, IRNode *BlockNode, } IRNode *ReaderBase::fgMakeEndFinallyHelper(IRNode *BlockNode, - uint32_t CurrentOffset, - bool IsLexicalEnd) { + EHRegion *FinallyRegion, + uint32_t CurrentOffset) { IRNode *EndFinallyNode; - EndFinallyNode = fgMakeEndFinally(BlockNode, CurrentOffset, IsLexicalEnd); + EndFinallyNode = fgMakeEndFinally(BlockNode, FinallyRegion, CurrentOffset); irNodeSetRegion(EndFinallyNode, fgGetRegionFromMSILOffset(CurrentOffset)); return EndFinallyNode; } @@ -2800,66 +2852,6 @@ EHRegion *getFinallyRegion(EHRegion *TryRegion) { return nullptr; } -// If this is a trivial leave (i.e. if it can be implemented as a -// simple branch), return the offset that it should branch to. -// Otherwise, reject it. -// -// TODO: This method should go away when we have EH -// flow modeled properly; it's a placeholder to get clean -// compilation and correct execution for programs with EH constructs -// but that don't actually throw exceptions at runtime. -uint32_t updateLeaveOffset(EHRegion *Region, uint32_t LeaveOffset, - uint32_t TargetOffset, const char **FailReason) { - if (rgnGetRegionType(Region) == ReaderBaseNS::RegionKind::RGN_Try) { - // See if this is a try region we are leaving. - if ((TargetOffset < rgnGetStartMSILOffset(Region)) || - (TargetOffset >= rgnGetEndMSILOffset(Region))) { - // We are leaving this try. See if there is a finally to invoke. - EHRegion *FinallyRegion = getFinallyRegion(Region); - if (FinallyRegion) { - // There is a finally. - if (TargetOffset == rgnGetEndMSILOffset(FinallyRegion)) { - // The target of the leave is the code immediately after the - // finally. Redirect it to simply goto the finally code. - TargetOffset = rgnGetStartMSILOffset(FinallyRegion); - } else { - // Invoking this finally would require cloning it or passing - // it a continuation. This is NYI. - *FailReason = "Leave: nontrivial finally invocation"; - return TargetOffset; - } - } - } - } - - // Check if this leave exits any nested regions. - for (EHRegionList *ChildNode = rgnGetChildList(Region); ChildNode; - ChildNode = rgnListGetNext(ChildNode)) { - EHRegion *Child = rgnListGetRgn(ChildNode); - if ((LeaveOffset < rgnGetEndMSILOffset(Child)) && - (LeaveOffset >= rgnGetStartMSILOffset(Child))) { - return updateLeaveOffset(Child, LeaveOffset, TargetOffset, FailReason); - } - if (rgnGetRegionType(Child) == ReaderBaseNS::RegionKind::RGN_Try) { - // A handler region for the try is a child of it in the tree but follows - // it in the IR, so we explicitly have to check for grandchildren in this - // case (the current offset falls in the grandchild's range but not the - // child's range). - for (EHRegionList *GrandchildNode = rgnGetChildList(Child); - GrandchildNode; GrandchildNode = rgnListGetNext(GrandchildNode)) { - EHRegion *Grandchild = rgnListGetRgn(GrandchildNode); - if ((LeaveOffset < rgnGetEndMSILOffset(Grandchild)) && - (LeaveOffset >= rgnGetStartMSILOffset(Grandchild))) { - return updateLeaveOffset(Grandchild, LeaveOffset, TargetOffset, - FailReason); - } - } - } - } - - return TargetOffset; -} - #define CHECKTARGET(TargetOffset, BufSize) \ { \ if (TargetOffset < 0 || TargetOffset >= BufSize) \ @@ -2878,9 +2870,8 @@ void ReaderBase::fgBuildPhase1(FlowGraphNode *Block, uint8_t *ILInput, IRNode *BranchNode, *BlockNode, *TheExitLabel; FlowGraphNode *GraphNode; uint32_t CurrentOffset, BranchOffset, TargetOffset, NextOffset, NumCases; - EHRegion *Region; + EHRegion *Region, *FinallyRegion; bool IsShortInstr, IsConditional, IsTailCall, IsReadOnly, PreviousWasPrefix; - bool IsLexicalEnd; bool LoadFtnToken; mdToken TokenConstrained; uint32_t StackOffset = 0; @@ -2991,23 +2982,10 @@ void ReaderBase::fgBuildPhase1(FlowGraphNode *Block, uint8_t *ILInput, TargetOffset = NextOffset + BranchOffset; CHECKTARGET(TargetOffset, ILInputSize); - // A leave requires more processing if it's within - // a protected region. If it's outside of a region - // it acts like a branch. - if ((Opcode == ReaderBaseNS::CEE_LEAVE || - Opcode == ReaderBaseNS::CEE_LEAVE_S) && - (EhRegionTree != nullptr)) { - const char *FailReason = nullptr; - TargetOffset = updateLeaveOffset(EhRegionTree, CurrentOffset, - TargetOffset, &FailReason); - if (FailReason != nullptr) { - // Record that we can't handle this leave instruction. - // Don't abort now, beause it may prove unreachable - // (e.g. if it is in a catch, it will appear unreachable, - // because EH flow is not yet modeled); defer aborting until - // we see a reachable unhandled leave in Pass 2. - NyiLeaveMap[CurrentOffset] = FailReason; - } + if (Opcode == ReaderBaseNS::CEE_LEAVE || + Opcode == ReaderBaseNS::CEE_LEAVE_S) { + TargetOffset = + updateLeaveOffset(CurrentOffset, NextOffset, Block, TargetOffset); } GraphNode = nullptr; @@ -3098,51 +3076,23 @@ void ReaderBase::fgBuildPhase1(FlowGraphNode *Block, uint8_t *ILInput, // // if this endfinally is not in a finally don't do anything // verification will catch it later and insert throw - Region = fgGetRegionFromMSILOffset(CurrentOffset); + FinallyRegion = getInnermostFinallyRegion(CurrentOffset); // note endfinally is same instruction as endfault - if (Region == nullptr || - (rgnGetRegionType(Region) != ReaderBaseNS::RGN_Finally && - rgnGetRegionType(Region) != ReaderBaseNS::RGN_Fault)) { + if (FinallyRegion == nullptr || + (rgnGetRegionType(FinallyRegion) != ReaderBaseNS::RGN_Finally && + rgnGetRegionType(FinallyRegion) != ReaderBaseNS::RGN_Fault)) { BADCODE(MVER_E_ENDFINALLY); } - // We want to consider it equivalent to branching to the end of - // the FINALLY. - // - // It is *not* easy, however to simply insert a label at the end - // of the Region and branch to it in our reader framework. Why? - // Because our framework is based on offsets and what we want in - // this case is a label which is after every instruction in the - // region, but still in this region... We really need something - // like rgnGetEndMSILOffset(Region) - 0.5 ! A reworking of this - // aspect of the reader would be nice, but in the meantime we - // simply insert an end finally here, and let a post-pass insert - // the label in the precise location and convert the end finally - // to a goto. - // - // Also note that we don't *need* an end finally if it appears - // at the lexical end of the finally region (afterall, we're - // simply going to turn them into GOTOs to that point). - // However, we insert them there for 2 reasons: (1) ease of - // debugging; (2) our reader API's don't give us a good way to - // split blocks at the current block insertion point, w/o - // actually adding something at the insertion point. If our end - // finally is at the lexical end of the region, though, we pass - // that info. on to the client, so that they can cache it away - // for later (in our canon phase, for example, we don't insert - // the GOTO if this bit is set). - IsLexicalEnd = (NextOffset == rgnGetEndMSILOffset(Region)); - // Make/insert end finally BlockNode = fgNodeGetStartIRNode(Block); BranchNode = - fgMakeEndFinallyHelper(BlockNode, CurrentOffset, IsLexicalEnd); + fgMakeEndFinallyHelper(BlockNode, FinallyRegion, CurrentOffset); // And split the block fgNodeSetEndMSILOffset(Block, NextOffset); - Block = fgSplitBlock(Block, NextOffset, - findBlockSplitPointAfterNode(BranchNode)); + Block = fgSplitBlock(Block, NextOffset, nullptr); break; case ReaderBaseNS::CEE_JMP: @@ -7145,14 +7095,6 @@ void ReaderBase::readBytesForFlowGraphNodeHelper( { bool NonLocal, EndsWithNonLocalGoto; - std::map::iterator NyiIter = - NyiLeaveMap.find(CurrentOffset); - if (NyiIter != NyiLeaveMap.end()) { - // Support for this type of leave instruction is not yet implemented. - // Throw an appropriate exception. - throw NotYetImplementedException(NyiIter->second); - } - clearStack(); NonLocal = fgLeaveIsNonLocal(Fg, NextOffset, TargetOffset, &EndsWithNonLocalGoto); diff --git a/lib/Reader/readerir.cpp b/lib/Reader/readerir.cpp index 547cd0670ba..375a7720a05 100644 --- a/lib/Reader/readerir.cpp +++ b/lib/Reader/readerir.cpp @@ -103,6 +103,7 @@ struct EHRegion { uint32_t StartMsilOffset; uint32_t EndMsilOffset; ReaderBaseNS::RegionKind Kind; + llvm::SwitchInst *EndFinallySwitch; }; struct EHRegionList { @@ -297,6 +298,7 @@ void GenIR::readerPrePass(uint8_t *Buffer, uint32_t NumBytes) { HasTypeParameter = JitContext->MethodInfo->args.hasTypeArg(); HasVarargsToken = JitContext->MethodInfo->args.isVarArg(); KeepGenericContextAlive = false; + UnreachableContinuationBlock = nullptr; initParamsAndAutos(NumArgs, NumLocals); @@ -1775,20 +1777,47 @@ IRNode *GenIR::fgMakeThrow(IRNode *Insert) { return (IRNode *)Unreachable; } -IRNode *GenIR::fgMakeEndFinally(IRNode *InsertNode, uint32_t CurrentOffset, - bool IsLexicalEnd) { - // TODO: figure out what (if any) marker we need to generate here - return nullptr; +IRNode *GenIR::fgMakeEndFinally(IRNode *InsertNode, EHRegion *FinallyRegion, + uint32_t CurrentOffset) { + BasicBlock *Block = (BasicBlock *)InsertNode; + SwitchInst *Switch = FinallyRegion->EndFinallySwitch; + if (Switch == nullptr) { + // This finally is never invoked. + LLVMBuilder->SetInsertPoint(Block); + return (IRNode *)LLVMBuilder->CreateUnreachable(); + } + + BasicBlock *TargetBlock = Switch->getParent(); + if (TargetBlock == nullptr) { + // This is the first endfinally for this finally. Generate a block to + // hold the switch. + TargetBlock = BasicBlock::Create(*JitContext->LLVMContext, "endfinally", + Function, Block->getNextNode()); + LLVMBuilder->SetInsertPoint(TargetBlock); + + // Insert the load of the selector variable and the switch. + LLVMBuilder->Insert((LoadInst *)Switch->getCondition()); + LLVMBuilder->Insert(Switch); + + // Use the finally end offset as the switch block's begin/end. + FlowGraphNode *TargetNode = (FlowGraphNode *)TargetBlock; + uint32_t EndOffset = FinallyRegion->EndMsilOffset; + fgNodeSetStartMSILOffset(TargetNode, EndOffset); + fgNodeSetEndMSILOffset(TargetNode, EndOffset); + } + + // Generate and return branch to the block that holds the switch + LLVMBuilder->SetInsertPoint(Block); + return (IRNode *)LLVMBuilder->CreateBr(TargetBlock); } void GenIR::beginFlowGraphNode(FlowGraphNode *Fg, uint32_t CurrOffset, bool IsVerifyOnly) { - BasicBlock *Block = (BasicBlock *)Fg; - TerminatorInst *TermInst = Block->getTerminator(); - if (TermInst != nullptr) { - LLVMBuilder->SetInsertPoint(TermInst); + IRNode *InsertInst = fgNodeGetEndInsertIRNode(Fg); + if (InsertInst != nullptr) { + LLVMBuilder->SetInsertPoint((Instruction *)InsertInst); } else { - LLVMBuilder->SetInsertPoint(Block); + LLVMBuilder->SetInsertPoint(Fg); } } @@ -1802,13 +1831,14 @@ IRNode *GenIR::findBlockSplitPointAfterNode(IRNode *Node) { } // Get the last non-placekeeping node in block -IRNode *fgNodeGetEndInsertIRNode(FlowGraphNode *FgNode) { +IRNode *GenIR::fgNodeGetEndInsertIRNode(FlowGraphNode *FgNode) { BasicBlock *Block = (BasicBlock *)FgNode; - if (Block->empty()) { - return nullptr; - } else { - return (IRNode *)&(((BasicBlock *)FgNode)->back()); + uint32_t EndOffset = fgNodeGetEndMSILOffset(FgNode); + Instruction *InsertInst = ContinuationStoreMap.lookup(EndOffset); + if (InsertInst == nullptr) { + InsertInst = Block->getTerminator(); } + return (IRNode *)InsertInst; } void GenIR::replaceFlowGraphNodeUses(FlowGraphNode *OldNode, @@ -3933,11 +3963,175 @@ IRNode *GenIR::genBoundsCheck(IRNode *Array, IRNode *Index) { return Array; }; +/// \brief Get the immediate target (innermost exited finally) for this leave. +/// +/// Also create any IR and reader state needed to pass the appropriate +/// continuation for this leave to the finallies being exited, and for the +/// finallies to respect the passed continuations. +/// +/// \param LeaveOffset MSIL offset of the leave instruction +/// \param NextOffset MSIL offset immediately after the leave instruction +/// \param LeaveBlock Block containing the leave instruction +/// \param TargetOffset Ultimate target of the leave instruction +/// \returns Immediate target of the leave instruction: start of innermost +// exited finally if any exists, \p TargetOffset otherwise +uint32_t GenIR::updateLeaveOffset(uint32_t LeaveOffset, uint32_t NextOffset, + FlowGraphNode *LeaveBlock, + uint32_t TargetOffset) { + EHRegion *RootRegion = EhRegionTree; + if (RootRegion == nullptr) { + // Leave outside of a protected region is treated like a goto. + return TargetOffset; + } + bool IsInHandler = false; + return updateLeaveOffset(RootRegion, LeaveOffset, NextOffset, LeaveBlock, + TargetOffset, IsInHandler); +} + +/// \brief Get the immediate target (innermost exited finally) for this leave. +/// +/// Also create any necessary selector variables, finally-exiting switch +/// instructions, and selector variable stores. Selector variable stores are +/// inserted at the leave location (and \p ContinuationStoreMap is updated so +/// subsequent reader passes will know where to insert IR). Switch insertion +/// is deferred until the first endfinally for the affected finally is +/// processed. +/// +/// \param Region Current region to process, which contains the +/// leave instruction. Inner regions are processed +/// recursively. +/// \param LeaveOffset MSIL offset of the leave instruction +/// \param NextOffset MSIL offset immediately after the leave +/// instruction +/// \param LeaveBlock Block containing the leave instruction +/// \param TargetOffset Ultimate target of the leave instruction +/// \param IsInHandler [in/out] Support for dynamic exceptions is NYI; if this +/// method finds that the leave is in a handler +/// which can only be entered by an exception, +/// IsInHandler is set to true and processing is +/// aborted (no IR is inserted and the original +/// \p TargetOffset is returned). Initial caller +/// must pass false. +/// \returns Immediate target of the leave instruction: start of innermost +// exited finally if any exists, \p TargetOffset otherwise +uint32_t GenIR::updateLeaveOffset(EHRegion *Region, uint32_t LeaveOffset, + uint32_t NextOffset, + FlowGraphNode *LeaveBlock, + uint32_t TargetOffset, bool &IsInHandler) { + ReaderBaseNS::RegionKind RegionKind = rgnGetRegionType(Region); + + if ((RegionKind == ReaderBaseNS::RegionKind::RGN_MCatch) || + (RegionKind == ReaderBaseNS::RegionKind::RGN_Fault) || + (RegionKind == ReaderBaseNS::RegionKind::RGN_Filter) || + (RegionKind == ReaderBaseNS::RegionKind::RGN_MExcept)) { + // This leave is in an exception handler. Dynamic exceptions are not + // currently supported, so skip the update for this leave. + + IsInHandler = true; + return TargetOffset; + } + + EHRegion *FinallyRegion = nullptr; + uint32_t ChildTargetOffset = TargetOffset; + + if ((RegionKind == ReaderBaseNS::RegionKind::RGN_Try) && + ((TargetOffset < rgnGetStartMSILOffset(Region)) || + (TargetOffset >= rgnGetEndMSILOffset(Region)))) { + // We are leaving this try. See if there is a finally to invoke. + FinallyRegion = getFinallyRegion(Region); + if (FinallyRegion) { + // There is a finally. Update ChildTargetOffset so that recursive + // processing for inner regions will know to target this finally. + ChildTargetOffset = rgnGetStartMSILOffset(FinallyRegion); + } + } + + uint32_t InnermostTargetOffset = ChildTargetOffset; + + // Check if this leave exits any nested regions. + if (EHRegion *ChildRegion = getInnerEnclosingRegion(Region, LeaveOffset)) { + InnermostTargetOffset = + updateLeaveOffset(ChildRegion, LeaveOffset, NextOffset, LeaveBlock, + ChildTargetOffset, IsInHandler); + + if (IsInHandler) { + // Skip processing for this leave + return TargetOffset; + } + } + + if (FinallyRegion != nullptr) { + // Generate the code to set the continuation for the finally we are leaving + // First, get a pointer to the continuation block. + FlowGraphNode *TargetNode = nullptr; + fgAddNodeMSILOffset(&TargetNode, TargetOffset); + BasicBlock *TargetBlock = (BasicBlock *)TargetNode; + + // Get or create the switch that terminates the finally. + LLVMContext &Context = *JitContext->LLVMContext; + SwitchInst *Switch = FinallyRegion->EndFinallySwitch; + IntegerType *SelectorType; + Value *SelectorAddr; + ConstantInt *SelectorValue; + + if (Switch == nullptr) { + // First leave exiting this finally; generate a new switch. + SelectorType = IntegerType::getInt32Ty(Context); + SelectorAddr = createTemporary(SelectorType, "finally_cont"); + SelectorValue = nullptr; + + if (UnreachableContinuationBlock == nullptr) { + // First finally for this function; generate an unreachable block + // that can be used as the default switch target. + UnreachableContinuationBlock = + BasicBlock::Create(Context, "NullDefault", Function); + new UnreachableInst(Context, UnreachableContinuationBlock); + } + + LoadInst *Load = new LoadInst(SelectorAddr); + FinallyRegion->EndFinallySwitch = Switch = + SwitchInst::Create(Load, UnreachableContinuationBlock, 4); + } else { + // This finally already has a switch. See if it already has a case for + // this target continuation. + LoadInst *Load = (LoadInst *)Switch->getCondition(); + SelectorAddr = Load->getPointerOperand(); + SelectorType = (IntegerType *)Load->getType(); + SelectorValue = Switch->findCaseDest(TargetBlock); + } + + if (SelectorValue == nullptr) { + // The switch doesn't have a case for this target continuation yet; + // add one. + SelectorValue = + ConstantInt::get(SelectorType, Switch->getNumCases() + 1U); + Switch->addCase(SelectorValue, TargetBlock); + } + + // Create the store instruction to set this continuation selector for + // this leave across this finally. + LLVMBuilder->SetInsertPoint(LeaveBlock); + StoreInst *Store = LLVMBuilder->CreateStore(SelectorValue, SelectorAddr); + + if (InnermostTargetOffset == ChildTargetOffset) { + // This is the innermost finally being exited (no child region updated + // InnermostTargetOffset). + // Record the first continuation selector store in this block so that + // the 2nd pass will know to insert code before them rather than after + // them. + ContinuationStoreMap.insert(std::make_pair(NextOffset, Store)); + + // Update InnermostTargetOffset. + InnermostTargetOffset = FinallyRegion->StartMsilOffset; + } + } + + return InnermostTargetOffset; +} + void GenIR::leave(uint32_t TargetOffset, bool IsNonLocal, bool EndsWithNonLocalGoto) { - // TODO: handle exiting through nested finallies - // currently FG-building phase 1 generates an appropriate - // branch instruction for trivial leaves and rejects others + // TODO: handle leaves from handler regions return; } @@ -4635,12 +4829,21 @@ void GenIR::maintainOperandStack(FlowGraphNode *CurrentBlock) { PHINode *GenIR::createPHINode(BasicBlock *Block, Type *Ty, unsigned int NumReservedValues, const Twine &NameStr) { - TerminatorInst *TermInst = Block->getTerminator(); - if (TermInst != nullptr) { - return PHINode::Create(Ty, NumReservedValues, NameStr, TermInst); + // Put this new PHI after any existing PHIs but before anything else. + BasicBlock::iterator I = Block->begin(); + BasicBlock::iterator IE = Block->end(); + while ((I != IE) && isa(I)) { + ++I; + } + + PHINode *Result; + if (I == IE) { + Result = PHINode::Create(Ty, NumReservedValues, NameStr, Block); } else { - return PHINode::Create(Ty, NumReservedValues, NameStr, Block); + Result = PHINode::Create(Ty, NumReservedValues, NameStr, I); } + + return Result; } // Check whether the node is constant null.