From a6088891da73ef2bf813b6ef7c4587e1a30036b5 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 30 Oct 2019 09:58:06 -0700 Subject: [PATCH] JIT: add pass to merge common throw helper calls (dotnet/coreclr#27113) Look for blocks with single statement noreturn calls, and try to reroute flow so there's just one block call that all predecessors target. Resolves dotnet/coreclr#14770. Note this impairs debuggability of optimized code a bit, as it can change which line of code apparently invokes a throw helper in a backtrace. But since we're already commoning jit-inserted throw helpers (like array index OOB) this is not breaking any new ground. We could also handle commoning BBJ_THROW blocks, with some extra effort, but prototyping indicates duplicate throws are pretty rare. This phase runs just before `optOptimizeFlow`, so that we can leverage the ref counts and predecessor lists to ensure we make correct flow updates. It doesn't bother trying to clean out IR, that happens naturally as blocks become unreferenced. In some cases nothrow helpers end up being tail call candidates. We now suppress tail calling noreturn methods if there is more than one such call site in the method, hoping that instead we can merge the calls. Commit migrated from https://github.com/dotnet/coreclr/commit/b962c97257400bee07805ccee66cd85d97195b40 --- src/coreclr/src/jit/compiler.cpp | 6 +- src/coreclr/src/jit/compiler.h | 22 + src/coreclr/src/jit/compmemkind.h | 1 + src/coreclr/src/jit/compphases.h | 1 + src/coreclr/src/jit/flowgraph.cpp | 338 +++++++++++++ src/coreclr/src/jit/morph.cpp | 11 +- .../tests/src/JIT/opt/ThrowHelper/ThrowHelper.cs | 541 +++++++++++++++++++++ .../src/JIT/opt/ThrowHelper/ThrowHelper.csproj | 13 + 8 files changed, 930 insertions(+), 3 deletions(-) create mode 100644 src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.cs create mode 100644 src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.csproj diff --git a/src/coreclr/src/jit/compiler.cpp b/src/coreclr/src/jit/compiler.cpp index 37fd565..c835035 100644 --- a/src/coreclr/src/jit/compiler.cpp +++ b/src/coreclr/src/jit/compiler.cpp @@ -1835,7 +1835,8 @@ void Compiler::compInit(ArenaAllocator* pAlloc, InlineInfo* inlineInfo) opts.instrCount = 0; // Used to track when we should consider running EarlyProp - optMethodFlags = 0; + optMethodFlags = 0; + optNoReturnCallCount = 0; #ifdef DEBUG m_nodeTestData = nullptr; @@ -4655,6 +4656,9 @@ void Compiler::compCompile(void** methodCodePtr, ULONG* methodCodeSize, JitFlags if (opts.OptimizationEnabled()) { + fgTailMergeThrows(); + EndPhase(PHASE_MERGE_THROWS); + optOptimizeLayout(); EndPhase(PHASE_OPTIMIZE_LAYOUT); diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 2b9776b..0970639 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -4356,6 +4356,16 @@ public: void fgAddFinallyTargetFlags(); + void fgTailMergeThrows(); + void fgTailMergeThrowsFallThroughHelper(BasicBlock* predBlock, + BasicBlock* nonCanonicalBlock, + BasicBlock* canonicalBlock, + flowList* predEdge); + void fgTailMergeThrowsJumpToHelper(BasicBlock* predBlock, + BasicBlock* nonCanonicalBlock, + BasicBlock* canonicalBlock, + flowList* predEdge); + #if defined(FEATURE_EH_FUNCLETS) && defined(_TARGET_ARM_) // Sometimes we need to defer updating the BBF_FINALLY_TARGET bit. fgNeedToAddFinallyTargetBits signals // when this is necessary. @@ -6345,6 +6355,18 @@ public: unsigned optMethodFlags; + bool doesMethodHaveNoReturnCalls() + { + return optNoReturnCallCount > 0; + } + + void setMethodHasNoReturnCalls() + { + optNoReturnCallCount++; + } + + unsigned optNoReturnCallCount; + // Recursion bound controls how far we can go backwards tracking for a SSA value. // No throughput diff was found with backward walk bound between 3-8. static const int optEarlyPropRecurBound = 5; diff --git a/src/coreclr/src/jit/compmemkind.h b/src/coreclr/src/jit/compmemkind.h index 03b9d14..986c4e2 100644 --- a/src/coreclr/src/jit/compmemkind.h +++ b/src/coreclr/src/jit/compmemkind.h @@ -56,6 +56,7 @@ CompMemKindMacro(SideEffects) CompMemKindMacro(ObjectAllocator) CompMemKindMacro(VariableLiveRanges) CompMemKindMacro(ClassLayout) +CompMemKindMacro(TailMergeThrows) //clang-format on #undef CompMemKindMacro diff --git a/src/coreclr/src/jit/compphases.h b/src/coreclr/src/jit/compphases.h index 6ab8c85..ae78751 100644 --- a/src/coreclr/src/jit/compphases.h +++ b/src/coreclr/src/jit/compphases.h @@ -45,6 +45,7 @@ CompPhaseNameMacro(PHASE_COMPUTE_EDGE_WEIGHTS, "Compute edge weights (1, false #if defined(FEATURE_EH_FUNCLETS) CompPhaseNameMacro(PHASE_CREATE_FUNCLETS, "Create EH funclets", "EH-FUNC", false, -1, false) #endif // FEATURE_EH_FUNCLETS +CompPhaseNameMacro(PHASE_MERGE_THROWS, "Merge throw blocks", "MRGTHROW", false, -1, false) CompPhaseNameMacro(PHASE_OPTIMIZE_LAYOUT, "Optimize layout", "LAYOUT", false, -1, false) CompPhaseNameMacro(PHASE_COMPUTE_REACHABILITY, "Compute blocks reachability", "BL_REACH", false, -1, false) CompPhaseNameMacro(PHASE_OPTIMIZE_LOOPS, "Optimize loops", "LOOP-OPT", false, -1, false) diff --git a/src/coreclr/src/jit/flowgraph.cpp b/src/coreclr/src/jit/flowgraph.cpp index 86c630f..569e584 100644 --- a/src/coreclr/src/jit/flowgraph.cpp +++ b/src/coreclr/src/jit/flowgraph.cpp @@ -5043,6 +5043,8 @@ void Compiler::fgFindJumpTargets(const BYTE* codeAddr, IL_OFFSET codeSize, Fixed { // Mark the call node as "no return" as it can impact caller's code quality. impInlineInfo->iciCall->gtCallMoreFlags |= GTF_CALL_M_DOES_NOT_RETURN; + // Mark root method as containing a noreturn call. + impInlineRoot()->setMethodHasNoReturnCalls(); } // If the inline is viable and discretionary, do the @@ -25702,3 +25704,339 @@ bool Compiler::fgUseThrowHelperBlocks() { return !opts.compDbgCode; } + +//------------------------------------------------------------------------ +// fgTailMergeThrows: Tail merge throw blocks and blocks with no return calls. +// +// Notes: +// Scans the flow graph for throw blocks and blocks with no return calls +// that can be merged, and opportunistically merges them. +// +// Does not handle throws yet as the analysis is more costly and less +// likely to pay off. So analysis is restricted to blocks with just one +// statement. +// +// For throw helper call merging, we are looking for examples like +// the below. Here BB17 and BB21 have identical trees that call noreturn +// methods, so we can modify BB16 to branch to BB21 and delete BB17. +// +// Also note as a quirk of how we model flow that both BB17 and BB21 +// have successor blocks. We don't turn these into true throw blocks +// until morph. +// +// BB16 [005..006) -> BB18 (cond), preds={} succs={BB17,BB18} +// +// * JTRUE void +// \--* NE int +// ... +// +// BB17 [005..006), preds={} succs={BB19} +// +// * CALL void ThrowHelper.ThrowArgumentOutOfRangeException +// \--* CNS_INT int 33 +// +// BB20 [005..006) -> BB22 (cond), preds={} succs={BB21,BB22} +// +// * JTRUE void +// \--* LE int +// ... +// +// BB21 [005..006), preds={} succs={BB22} +// +// * CALL void ThrowHelper.ThrowArgumentOutOfRangeException +// \--* CNS_INT int 33 +// +void Compiler::fgTailMergeThrows() +{ + noway_assert(opts.OptimizationEnabled()); + + JITDUMP("\n*************** In fgTailMergeThrows\n"); + + // Early out case for most methods. Throw helpers are rare. + if (optNoReturnCallCount < 2) + { + JITDUMP("Method does not have multiple noreturn calls.\n"); + return; + } + + // This transformation requires block pred lists to be built + // so that flow can be safely updated. + assert(fgComputePredsDone); + + struct ThrowHelper + { + BasicBlock* m_block; + GenTreeCall* m_call; + + ThrowHelper() : m_block(nullptr), m_call(nullptr) + { + } + + ThrowHelper(BasicBlock* block, GenTreeCall* call) : m_block(block), m_call(call) + { + } + + static bool Equals(const ThrowHelper x, const ThrowHelper& y) + { + return BasicBlock::sameEHRegion(x.m_block, y.m_block) && GenTreeCall::Equals(x.m_call, y.m_call); + } + + static unsigned GetHashCode(const ThrowHelper& x) + { + return static_cast(reinterpret_cast(x.m_call->gtCallMethHnd)); + } + }; + + typedef JitHashTable CallToBlockMap; + + CompAllocator allocator(getAllocator(CMK_TailMergeThrows)); + CallToBlockMap callMap(allocator); + BlockToBlockMap blockMap(allocator); + + // We run two passes here. + // + // The first pass finds candidate blocks. The first candidate for + // each unique kind of throw is chosen as the canonical example of + // that kind of throw. Subsequent matching candidates are mapped + // to that throw. + // + // The second pass modifies flow so that predecessors of + // non-canonical throw blocks now transfer control to the + // appropriate canonical block. + int numCandidates = 0; + + // First pass + // + // Scan for THROW blocks. Note early on in compilation (before morph) + // noreturn blocks are not marked as BBJ_THROW. + // + // Walk blocks from last to first so that any branches we + // introduce to the canonical blocks end up lexically forward + // and there is less jumbled flow to sort out later. + for (BasicBlock* block = fgLastBB; block != nullptr; block = block->bbPrev) + { + // For throw helpers the block should have exactly one statement.... + // (this isn't guaranteed, but seems likely) + Statement* stmt = block->firstStmt(); + + if ((stmt == nullptr) || (stmt->GetNextStmt() != nullptr)) + { + continue; + } + + // ...that is a call + GenTree* const tree = stmt->GetRootNode(); + + if (!tree->IsCall()) + { + continue; + } + + // ...that does not return + GenTreeCall* const call = tree->AsCall(); + + if (!call->IsNoReturn()) + { + continue; + } + + // Sanity check -- only user funcs should be marked do not return + assert(call->gtCallType == CT_USER_FUNC); + + // Ok, we've found a suitable call. See if this is one we know + // about already, or something new. + BasicBlock* canonicalBlock = nullptr; + + JITDUMP("\n*** Does not return call\n"); + DISPTREE(call); + + // Have we found an equivalent call already? + ThrowHelper key(block, call); + if (callMap.Lookup(key, &canonicalBlock)) + { + // Yes, this one can be optimized away... + JITDUMP(" in " FMT_BB " can be dup'd to canonical " FMT_BB "\n", block->bbNum, canonicalBlock->bbNum); + blockMap.Set(block, canonicalBlock); + numCandidates++; + } + else + { + // No, add this as the canonical example + JITDUMP(" in " FMT_BB " is unique, marking it as canonical\n", block->bbNum); + callMap.Set(key, block); + } + } + + // Bail if no candidates were found + if (numCandidates == 0) + { + JITDUMP("\n*************** no throws can be tail merged, sorry\n"); + return; + } + + JITDUMP("\n*** found %d merge candidates, rewriting flow\n\n", numCandidates); + + // Second pass. + // + // We walk the map rather than the block list, to save a bit of time. + BlockToBlockMap::KeyIterator iter(blockMap.Begin()); + BlockToBlockMap::KeyIterator end(blockMap.End()); + int updateCount = 0; + + for (; !iter.Equal(end); iter++) + { + BasicBlock* const nonCanonicalBlock = iter.Get(); + BasicBlock* const canonicalBlock = iter.GetValue(); + flowList* nextPredEdge = nullptr; + + // Walk pred list of the non canonical block, updating flow to target + // the canonical block instead. + for (flowList* predEdge = nonCanonicalBlock->bbPreds; predEdge != nullptr; predEdge = nextPredEdge) + { + BasicBlock* const predBlock = predEdge->flBlock; + nextPredEdge = predEdge->flNext; + + switch (predBlock->bbJumpKind) + { + case BBJ_NONE: + { + fgTailMergeThrowsFallThroughHelper(predBlock, nonCanonicalBlock, canonicalBlock, predEdge); + updateCount++; + } + break; + + case BBJ_ALWAYS: + { + fgTailMergeThrowsJumpToHelper(predBlock, nonCanonicalBlock, canonicalBlock, predEdge); + updateCount++; + } + break; + + case BBJ_COND: + { + // Flow to non canonical block could be via fall through or jump or both. + if (predBlock->bbNext == nonCanonicalBlock) + { + fgTailMergeThrowsFallThroughHelper(predBlock, nonCanonicalBlock, canonicalBlock, predEdge); + } + + if (predBlock->bbJumpDest == nonCanonicalBlock) + { + fgTailMergeThrowsJumpToHelper(predBlock, nonCanonicalBlock, canonicalBlock, predEdge); + } + updateCount++; + } + break; + + case BBJ_SWITCH: + { + JITDUMP("*** " FMT_BB " now branching to " FMT_BB "\n", predBlock->bbNum, canonicalBlock->bbNum); + fgReplaceSwitchJumpTarget(predBlock, canonicalBlock, nonCanonicalBlock); + updateCount++; + } + break; + + default: + // We don't expect other kinds of preds, and it is safe to ignore them + // as flow is still correct, just not as optimized as it could be. + break; + } + } + } + + // If we altered flow, reset fgModified. Given where we sit in the + // phase list, flow-dependent side data hasn't been built yet, so + // nothing needs invalidation. + // + // Note we could invoke a cleanup pass here, but optOptimizeFlow + // seems to be missing some safety checks and doesn't expect to + // see an already cleaned-up flow graph. + if (updateCount > 0) + { + assert(fgModified); + fgModified = false; + } + +#if DEBUG + if (verbose) + { + printf("\n*************** After fgTailMergeThrows(%d updates)\n", updateCount); + fgDispBasicBlocks(); + fgVerifyHandlerTab(); + fgDebugCheckBBlist(); + } +#endif // DEBUG +} + +//------------------------------------------------------------------------ +// fgTailMergeThrowsFallThroughHelper: fixup flow for fall throughs to mergable throws +// +// Arguments: +// predBlock - block falling through to the throw helper +// nonCanonicalBlock - original fall through target +// canonicalBlock - new (jump) target +// predEdge - original flow edge +// +// Notes: +// Alters fall through flow of predBlock so it jumps to the +// canonicalBlock via a new basic block. Does not try and fix +// jump-around flow; we leave that to optOptimizeFlow which runs +// just afterwards. +// +void Compiler::fgTailMergeThrowsFallThroughHelper(BasicBlock* predBlock, + BasicBlock* nonCanonicalBlock, + BasicBlock* canonicalBlock, + flowList* predEdge) +{ + assert(predBlock->bbNext == nonCanonicalBlock); + + BasicBlock* const newBlock = fgNewBBafter(BBJ_ALWAYS, predBlock, true); + + JITDUMP("*** " FMT_BB " now falling through to empty " FMT_BB " and then to " FMT_BB "\n", predBlock->bbNum, + newBlock->bbNum, canonicalBlock->bbNum); + + // Remove the old flow + fgRemoveRefPred(nonCanonicalBlock, predBlock); + + // Wire up the new flow + predBlock->bbNext = newBlock; + fgAddRefPred(newBlock, predBlock, predEdge); + + newBlock->bbJumpDest = canonicalBlock; + fgAddRefPred(canonicalBlock, newBlock, predEdge); + + // Note there is now a jump to the canonical block + canonicalBlock->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL); +} + +//------------------------------------------------------------------------ +// fgTailMergeThrowsJumpToHelper: fixup flow for jumps to mergable throws +// +// Arguments: +// predBlock - block jumping to the throw helper +// nonCanonicalBlock - original jump target +// canonicalBlock - new jump target +// predEdge - original flow edge +// +// Notes: +// Alters jumpDest of predBlock so it jumps to the canonicalBlock. +// +void Compiler::fgTailMergeThrowsJumpToHelper(BasicBlock* predBlock, + BasicBlock* nonCanonicalBlock, + BasicBlock* canonicalBlock, + flowList* predEdge) +{ + assert(predBlock->bbJumpDest == nonCanonicalBlock); + + JITDUMP("*** " FMT_BB " now branching to " FMT_BB "\n", predBlock->bbNum, canonicalBlock->bbNum); + + // Remove the old flow + fgRemoveRefPred(nonCanonicalBlock, predBlock); + + // Wire up the new flow + predBlock->bbJumpDest = canonicalBlock; + fgAddRefPred(canonicalBlock, predBlock, predEdge); + + // Note there is now a jump to the canonical block + canonicalBlock->bbFlags |= (BBF_JMP_TARGET | BBF_HAS_LABEL); +} diff --git a/src/coreclr/src/jit/morph.cpp b/src/coreclr/src/jit/morph.cpp index da02e6b..72382a4 100644 --- a/src/coreclr/src/jit/morph.cpp +++ b/src/coreclr/src/jit/morph.cpp @@ -6957,6 +6957,14 @@ GenTree* Compiler::fgMorphPotentialTailCall(GenTreeCall* call) return nullptr; } + // Heuristic: regular calls to noreturn methods can sometimes be + // merged, so if we have multiple such calls, we defer tail calling. + if (call->IsNoReturn() && (optNoReturnCallCount > 1)) + { + failTailCall("Defer tail calling throw helper; anticipating merge"); + return nullptr; + } + #ifdef _TARGET_AMD64_ // Needed for Jit64 compat. // In future, enabling tail calls from methods that need GS cookie check @@ -16605,11 +16613,10 @@ void Compiler::fgMorph() EndPhase(PHASE_MERGE_FINALLY_CHAINS); fgCloneFinally(); + fgUpdateFinallyTargetFlags(); EndPhase(PHASE_CLONE_FINALLY); - fgUpdateFinallyTargetFlags(); - /* For x64 and ARM64 we need to mark irregular parameters */ lvaRefCountState = RCS_EARLY; fgResetImplicitByRefRefCount(); diff --git a/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.cs b/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.cs new file mode 100644 index 0000000..ec953b7 --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.cs @@ -0,0 +1,541 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +class TestException : Exception +{ + int x; + string y; + + public TestException() {} + public TestException(int _x) { x = _x; } + public TestException(string _y) { y = _y; } +} + +class TestCases +{ + static void Throw() => throw new TestException(); + static void Throw(int x) => throw new TestException(x); + static void Throw(string y) => throw new TestException(y); + + static int MayThrow(int x) + { + if (x > 0) + { + throw new TestException(x); + } + return x; + } + + static int ReturnThrow() => throw new TestException(); + + public static int OneThrowHelper(int x) + { + if (x > 0) + { + Throw(); + } + + return x; + } + + public static void OneThrowHelperTail(int x) + { + if (x > 0) + { + Throw(); + return; + } + } + + public static int OneMayThrowHelper(int x) + { + if (x > 0) + { + MayThrow(x); + } + + return x; + } + + public static int OneMayThrowHelperTail(int x) + { + if (x > 0) + { + MayThrow(x); + return 0; + } + + return x; + } + + public static int OneReturnThrowHelper(int x) + { + if (x > 0) + { + return ReturnThrow(); + } + + return x; + } + + public static int OneReturnThrowHelperTail(int x) + { + if (x > 0) + { + return ReturnThrow(); + } + + return x; + } + + public static int TwoIdenticalThrowHelpers_If(int x) + { + if (x == 0) + { + Throw(); + } + + if (x == 1) + { + Throw(); + } + + return x; + } + + public static void TwoIdenticalThrowHelpers_IfOneTail(int x) + { + if (x == 0) + { + Throw(); + return; + } + + if (x == 1) + { + Throw(); + } + } + + public static void TwoIdenticalThrowHelpers_IfTwoTail(int x) + { + if (x == 0) + { + Throw(); + return; + } + + if (x == 1) + { + Throw(); + return; + } + } + + public static int ThreeIdenticalThrowHelpers_If(int x) + { + if (x == 0) + { + Throw(); + } + + if (x == 1) + { + Throw(); + } + + if (x == 2) + { + Throw(); + } + + return x; + } + + public static void ThreeIdenticalThrowHelpers_IfOneTail(int x) + { + if (x == 0) + { + Throw(); + return; + } + + if (x == 1) + { + Throw(); + } + + if (x == 2) + { + Throw(); + } + } + + public static void ThreeIdenticalThrowHelpers_IfTwoTail(int x) + { + if (x == 0) + { + Throw(); + return; + } + + if (x == 1) + { + Throw(); + return; + } + + if (x == 2) + { + Throw(); + } + } + + public static void ThreeIdenticalThrowHelpers_IfThreeTail(int x) + { + if (x == 0) + { + Throw(); + return; + } + + if (x == 1) + { + Throw(); + return; + } + + if (x == 2) + { + Throw(); + return; + } + } + + public static int TwoIdenticalThrowHelpers_Goto(int x) + { + if (x == 0) + { + goto L1; + } + + if (x == 1) + { + goto L2; + } + + return x; + L1: + Throw(); + L2: + Throw(); + + return x; + } + + public static void TwoIdenticalThrowHelpers_GotoOneTail(int x) + { + if (x == 0) + { + goto L1; + } + + if (x == 1) + { + goto L2; + } + + return; + + L1: + Throw(); + L2: + Throw(); + } + + public static void TwoIdenticalThrowHelpers_GotoTwoTail(int x) + { + if (x == 0) + { + goto L1; + } + + if (x == 1) + { + goto L2; + } + + return; + L1: + Throw(); + return; + + L2: + Throw(); + } + + public static int TwoIdenticalThrowHelpers_Switch(int x) + { + switch (x) + { + case 0: + { + Throw(); + } + break; + + case 1: + { + Throw(); + } + break; + } + + return x; + } + + public static void TwoIdenticalThrowHelpers_SwitchOneTail(int x) + { + switch (x) + { + case 0: + { + Throw(); + return; + } + + case 1: + { + Throw(); + } + break; + } + } + + public static void TwoIdenticalThrowHelpers_SwitchTwoTail(int x) + { + switch (x) + { + case 0: + { + Throw(); + return; + } + + case 1: + { + Throw(); + return; + } + } + } + + public static int TwoIdenticalThrowHelpers_SwitchGoto(int x) + { + switch (x) + { + case 0: + { + goto L1; + } + + case 1: + { + goto L2; + } + } + + return x; + + L1: + Throw(); + L2: + Throw(); + + return x; + } + + public static void TwoIdenticalThrowHelpers_SwitchGotoOneTail(int x) + { + switch (x) + { + case 0: + { + goto L1; + } + + case 1: + { + goto L2; + } + } + + return; + + L1: + Throw(); + L2: + Throw(); + } + + public static void TwoIdenticalThrowHelpers_SwitchGotoTwoTail(int x) + { + switch (x) + { + case 0: + { + goto L1; + } + + case 1: + { + goto L2; + } + } + + return; + + L1: + Throw(); + return; + L2: + Throw(); + } + + public static int TwoDifferentThrowHelpers(int x) + { + if (x == 0) + { + Throw(); + } + + if (x == 1) + { + Throw(1); + } + + return x; + } + + public static int TwoIdenticalThrowHelpersDifferentArgs(int x) + { + if (x == 0) + { + Throw(0); + } + + if (x == 1) + { + Throw(1); + } + + return x; + } + + public static int TwoIdenticalThrowHelpersSameArgTrees(int x, int[] y) + { + if (x == 0) + { + Throw(y[0]); + } + else if (x == 1) + { + Throw(y[0]); + } + + return x; + } + + public static int TwoIdenticalThrowHelpersDifferentArgTrees(int x, int[] y) + { + if (x == 0) + { + Throw(y[0]); + } + else if (x == 1) + { + Throw(y[1]); + } + + return x; + } + + + static int testNumber = 0; + static bool failed = false; + + static void Try(Func f) + { + testNumber++; + + if (f(-1) != -1) + { + Console.WriteLine($"Test {testNumber} failed\n"); + failed = true; + } + } + + static void Try(Func f) + { + testNumber++; + + int[] y = new int[0]; + if (f(-1, y) != -1) + { + Console.WriteLine($"Test {testNumber} failed\n"); + failed = true; + } + } + + static void Try(Action f) + { + testNumber++; + + try + { + f(-1); + } + catch (TestException) + { + Console.WriteLine($"Test {testNumber} failed\n"); + failed = true; + } + } + + public static int Main() + { + Try(OneThrowHelper); + Try(OneThrowHelperTail); + Try(OneMayThrowHelper); + Try(OneMayThrowHelperTail); + Try(OneReturnThrowHelper); + Try(OneReturnThrowHelperTail); + Try(TwoIdenticalThrowHelpers_If); + Try(TwoIdenticalThrowHelpers_IfOneTail); + Try(TwoIdenticalThrowHelpers_IfTwoTail); + Try(TwoIdenticalThrowHelpers_Goto); + Try(TwoIdenticalThrowHelpers_GotoOneTail); + Try(TwoIdenticalThrowHelpers_GotoTwoTail); + Try(TwoIdenticalThrowHelpers_Switch); + Try(TwoIdenticalThrowHelpers_SwitchOneTail); + Try(TwoIdenticalThrowHelpers_SwitchTwoTail); + Try(TwoIdenticalThrowHelpers_SwitchGoto); + Try(TwoIdenticalThrowHelpers_SwitchGotoOneTail); + Try(TwoIdenticalThrowHelpers_SwitchGotoTwoTail); + Try(TwoDifferentThrowHelpers); + Try(TwoIdenticalThrowHelpersDifferentArgs); + Try(ThreeIdenticalThrowHelpers_If); + Try(ThreeIdenticalThrowHelpers_IfOneTail); + Try(ThreeIdenticalThrowHelpers_IfTwoTail); + Try(ThreeIdenticalThrowHelpers_IfThreeTail); + Try(TwoIdenticalThrowHelpersSameArgTrees); + Try(TwoIdenticalThrowHelpersDifferentArgTrees); + + Console.WriteLine(failed ? "" : $"All {testNumber} tests passed"); + return failed ? -1 : 100; + } +} diff --git a/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.csproj b/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.csproj new file mode 100644 index 0000000..eae6f85 --- /dev/null +++ b/src/coreclr/tests/src/JIT/opt/ThrowHelper/ThrowHelper.csproj @@ -0,0 +1,13 @@ + + + Exe + + + None + True + false + + + + + -- 2.7.4