From b80e73d579454bdbb37c2a22222e3f9642ffb8c8 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 9 Nov 2023 23:26:21 -0800 Subject: [PATCH 01/12] avoid searching entire assertion table where possible --- src/coreclr/jit/assertionprop.cpp | 67 ++++++++++++++----------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 86acf430075011..e0a3102d6304a8 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1617,7 +1617,7 @@ bool Compiler::optAssertionVnInvolvesNan(AssertionDsc* assertion) * * If it is already in the assertion table return the assertionIndex that * we use to refer to this element. - * Otherwise add it to the assertion table ad return the assertionIndex that + * Otherwise add it to the assertion table and return the assertionIndex that * we use to refer to this element. * If we need to add to the table and the table is full return the value zero */ @@ -2490,15 +2490,19 @@ AssertionIndex Compiler::optFindComplementary(AssertionIndex assertIndex) // AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange range, ASSERT_VALARG_TP assertions) { - if ((!optLocalAssertionProp && BitVecOps::IsEmpty(apTraits, assertions)) || !optCanPropSubRange) + if (!optCanPropSubRange) { + // (don't early out in checked, verify above) return NO_ASSERTION_INDEX; } - for (AssertionIndex index = 1; index <= optAssertionCount; index++) + BitVecOps::Iter iter(apTraits, assertions); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) { - AssertionDsc* curAssertion = optGetAssertion(index); - if (BitVecOps::IsMember(apTraits, assertions, index - 1) && curAssertion->CanPropSubRange()) + AssertionIndex const index = GetAssertionIndex(bvIndex); + AssertionDsc* const curAssertion = optGetAssertion(index); + if (curAssertion->CanPropSubRange()) { // For local assertion prop use comparison on locals, and use comparison on vns for global prop. bool isEqual = optLocalAssertionProp @@ -2530,18 +2534,12 @@ AssertionIndex Compiler::optAssertionIsSubrange(GenTree* tree, IntegralRange ran */ AssertionIndex Compiler::optAssertionIsSubtype(GenTree* tree, GenTree* methodTableArg, ASSERT_VALARG_TP assertions) { - if (BitVecOps::IsEmpty(apTraits, assertions)) - { - return NO_ASSERTION_INDEX; - } - for (AssertionIndex index = 1; index <= optAssertionCount; index++) + BitVecOps::Iter iter(apTraits, assertions); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) { - if (!BitVecOps::IsMember(apTraits, assertions, index - 1)) - { - continue; - } - - AssertionDsc* curAssertion = optGetAssertion(index); + AssertionIndex const index = GetAssertionIndex(bvIndex); + AssertionDsc* curAssertion = optGetAssertion(index); if (curAssertion->assertionKind != OAK_EQUAL || (curAssertion->op1.kind != O1K_SUBTYPE && curAssertion->op1.kind != O1K_EXACT_TYPE)) { @@ -3709,31 +3707,28 @@ AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual( { noway_assert((op1Kind == O1K_LCLVAR) || (op1Kind == O1K_EXACT_TYPE) || (op1Kind == O1K_SUBTYPE)); noway_assert((op2Kind == O2K_CONST_INT) || (op2Kind == O2K_IND_CNS_INT) || (op2Kind == O2K_ZEROOBJ)); - if (BitVecOps::IsEmpty(apTraits, assertions)) - { - return NO_ASSERTION_INDEX; - } - for (AssertionIndex index = 1; index <= optAssertionCount; ++index) + BitVecOps::Iter iter(apTraits, assertions); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) { - AssertionDsc* curAssertion = optGetAssertion(index); - if (BitVecOps::IsMember(apTraits, assertions, index - 1)) + AssertionIndex const index = GetAssertionIndex(bvIndex); + AssertionDsc* curAssertion = optGetAssertion(index); + + if ((curAssertion->assertionKind != OAK_EQUAL) && (curAssertion->assertionKind != OAK_NOT_EQUAL)) { - if ((curAssertion->assertionKind != OAK_EQUAL) && (curAssertion->assertionKind != OAK_NOT_EQUAL)) - { - continue; - } + continue; + } - if ((curAssertion->op1.kind == op1Kind) && (curAssertion->op1.lcl.lclNum == lclNum) && - (curAssertion->op2.kind == op2Kind)) - { - bool constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal); - bool assertionIsEqual = (curAssertion->assertionKind == OAK_EQUAL); + if ((curAssertion->op1.kind == op1Kind) && (curAssertion->op1.lcl.lclNum == lclNum) && + (curAssertion->op2.kind == op2Kind)) + { + bool constantIsEqual = (curAssertion->op2.u1.iconVal == cnsVal); + bool assertionIsEqual = (curAssertion->assertionKind == OAK_EQUAL); - if (constantIsEqual || assertionIsEqual) - { - return index; - } + if (constantIsEqual || assertionIsEqual) + { + return index; } } } From d4fb339690e65aab8a6cb618d4f87db94ce93704 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 9 Nov 2023 23:26:45 -0800 Subject: [PATCH 02/12] enable --- src/coreclr/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 65afd348819e11..7627e26716befb 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -654,7 +654,7 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) // Enable cross-block local assertion prop -CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0) +CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 1) #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the From fdfc1ee4e2d360e4d8b3035b27bd1e8b220703fc Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 01:06:35 -0800 Subject: [PATCH 03/12] Revert "enable" This reverts commit 062552152a1d209f6d9dbd89da8dd7f14b1a56fd. --- src/coreclr/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 7627e26716befb..65afd348819e11 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -654,7 +654,7 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) // Enable cross-block local assertion prop -CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 1) +CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0) #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the From 548be190d2be5a7539c2fcdb1323e1692ac9a700 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 08:03:28 -0800 Subject: [PATCH 04/12] re-enable --- src/coreclr/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 65afd348819e11..7627e26716befb 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -654,7 +654,7 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) // Enable cross-block local assertion prop -CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0) +CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 1) #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the From 6a07b54984d6f5197e8a71c6ec7ddb1d137f2e6b Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 11:20:09 -0800 Subject: [PATCH 05/12] handle local prop add via dep vectors --- src/coreclr/jit/assertionprop.cpp | 54 ++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index e0a3102d6304a8..c2b282dbec7361 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1633,13 +1633,57 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) return NO_ASSERTION_INDEX; } - // Check if exists already, so we can skip adding new one. Search backwards. - for (AssertionIndex index = optAssertionCount; index >= 1; index--) + // See if we already have this assertion in the table. + // + // For local assertion prop we can speed things up by checking the dep vectors. + // + if (optLocalAssertionProp) { - AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + assert(newAssertion->op1.kind == O1K_LCLVAR); + + unsigned lclNum = newAssertion->op1.lcl.lclNum; + BitVecOps::Iter iter(apTraits, GetAssertionDep(lclNum)); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) { - return index; + AssertionIndex const index = GetAssertionIndex(bvIndex); + AssertionDsc* const curAssertion = optGetAssertion(index); + + if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + { + return index; + } + } + + if (newAssertion->op2.kind == O2K_LCLVAR_COPY) + { + lclNum = newAssertion->op2.lcl.lclNum; + BitVecOps::Iter iter(apTraits, GetAssertionDep(lclNum)); + unsigned bvIndex = 0; + while (iter.NextElem(&bvIndex)) + { + AssertionIndex const index = GetAssertionIndex(bvIndex); + AssertionDsc* const curAssertion = optGetAssertion(index); + + if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + { + return index; + } + } + } + } + else + { + // For global prop we search the entire table. + // + // Check if exists already, so we can skip adding new one. Search backwards. + for (AssertionIndex index = optAssertionCount; index >= 1; index--) + { + AssertionDsc* curAssertion = optGetAssertion(index); + if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + { + return index; + } } } From e98dca24dff436744c615178bb419d7589dcc8ee Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 11:20:33 -0800 Subject: [PATCH 06/12] don't use pred assertions from unreachables --- src/coreclr/jit/compiler.h | 2 +- src/coreclr/jit/morph.cpp | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 25c82920978863..6c87bba0271e41 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -4799,7 +4799,7 @@ class Compiler FoldResult fgFoldConditional(BasicBlock* block); PhaseStatus fgMorphBlocks(); - void fgMorphBlock(BasicBlock* block); + void fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorder = 0); void fgMorphStmts(BasicBlock* block); void fgMergeBlockReturn(BasicBlock* block); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 6a9dd7539ee049..4a5c85abd89d3a 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13772,8 +13772,10 @@ void Compiler::fgMorphStmts(BasicBlock* block) // // Arguments: // block - block in question +// highestReachablePostorder - maximum postorder number for a +// reachable block. // -void Compiler::fgMorphBlock(BasicBlock* block) +void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorder) { JITDUMP("\nMorphing " FMT_BB "\n", block->bbNum); @@ -13788,6 +13790,8 @@ void Compiler::fgMorphBlock(BasicBlock* block) } else { + assert(highestReachablePostorder > 0); + // Determine if this block can leverage assertions from its pred blocks. // // Some blocks are ineligible. @@ -13818,6 +13822,14 @@ void Compiler::fgMorphBlock(BasicBlock* block) break; } + if (pred->bbPostorderNum > highestReachablePostorder) + { + // This pred was not reachable from the original DFS root set, so + // we can ignore its assertion information. + // + continue; + } + // Yes, pred assertions are available. If this is the first pred, copy. // If this is a subsequent pred, intersect. // @@ -13939,7 +13951,7 @@ PhaseStatus Compiler::fgMorphBlocks() // We are optimizing. Process in RPO. // fgRenumberBlocks(); - fgDfsReversePostorder(); + const unsigned highestReachablePostorder = fgDfsReversePostorder(); // Disallow general creation of new blocks or edges as it // would invalidate RPO. @@ -13971,7 +13983,7 @@ PhaseStatus Compiler::fgMorphBlocks() for (unsigned i = 1; i <= bbNumMax; i++) { BasicBlock* const block = fgBBReversePostorder[i]; - fgMorphBlock(block); + fgMorphBlock(block, highestReachablePostorder); } assert(bbNumMax == fgBBNumMax); From 631a42faefaaa590b1d740cf56665700b2fce6db Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 13:59:56 -0800 Subject: [PATCH 07/12] one more place we can trim --- src/coreclr/jit/assertionprop.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index c2b282dbec7361..07a3b2bc63b873 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -3752,7 +3752,10 @@ AssertionIndex Compiler::optLocalAssertionIsEqualOrNotEqual( noway_assert((op1Kind == O1K_LCLVAR) || (op1Kind == O1K_EXACT_TYPE) || (op1Kind == O1K_SUBTYPE)); noway_assert((op2Kind == O2K_CONST_INT) || (op2Kind == O2K_IND_CNS_INT) || (op2Kind == O2K_ZEROOBJ)); - BitVecOps::Iter iter(apTraits, assertions); + assert(optLocalAssertionProp); + ASSERT_TP apDependent = BitVecOps::Intersection(apTraits, GetAssertionDep(lclNum), assertions); + + BitVecOps::Iter iter(apTraits, apDependent); unsigned bvIndex = 0; while (iter.NextElem(&bvIndex)) { From 6d4960a5866e92d18a54a549b4885b28fc1e470d Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 10 Nov 2023 17:58:11 -0800 Subject: [PATCH 08/12] disable --- src/coreclr/jit/jitconfigvalues.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/jitconfigvalues.h b/src/coreclr/jit/jitconfigvalues.h index 7627e26716befb..65afd348819e11 100644 --- a/src/coreclr/jit/jitconfigvalues.h +++ b/src/coreclr/jit/jitconfigvalues.h @@ -654,7 +654,7 @@ CONFIG_INTEGER(JitEnableHeadTailMerge, W("JitEnableHeadTailMerge"), 1) CONFIG_INTEGER(JitEnablePhysicalPromotion, W("JitEnablePhysicalPromotion"), 1) // Enable cross-block local assertion prop -CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 1) +CONFIG_INTEGER(JitEnableCrossBlockLocalAssertionProp, W("JitEnableCrossBlockLocalAssertionProp"), 0) #if defined(DEBUG) // JitFunctionFile: Name of a file that contains a list of functions. If the currently compiled function is in the From b1981f17dc86ef85fa0e3e942d3641d2defd1e36 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sat, 11 Nov 2023 08:19:46 -0800 Subject: [PATCH 09/12] review feedback --- src/coreclr/jit/assertionprop.cpp | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 07a3b2bc63b873..3a586fd246c035 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1635,7 +1635,9 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) // See if we already have this assertion in the table. // - // For local assertion prop we can speed things up by checking the dep vectors. + // For local assertion prop we can speed things up by checking the dep vector. + // Note we only need check the op1 vector; copies get indexed on both op1 + // and op2, so searching the first will find any existing match. // if (optLocalAssertionProp) { @@ -1649,28 +1651,11 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) AssertionIndex const index = GetAssertionIndex(bvIndex); AssertionDsc* const curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + if (curAssertion->Equals(newAssertion, /* vnBased */ false) { return index; } } - - if (newAssertion->op2.kind == O2K_LCLVAR_COPY) - { - lclNum = newAssertion->op2.lcl.lclNum; - BitVecOps::Iter iter(apTraits, GetAssertionDep(lclNum)); - unsigned bvIndex = 0; - while (iter.NextElem(&bvIndex)) - { - AssertionIndex const index = GetAssertionIndex(bvIndex); - AssertionDsc* const curAssertion = optGetAssertion(index); - - if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) - { - return index; - } - } - } } else { @@ -1680,7 +1665,7 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) for (AssertionIndex index = optAssertionCount; index >= 1; index--) { AssertionDsc* curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, !optLocalAssertionProp)) + if (curAssertion->Equals(newAssertion, /* vnBased */ true)) { return index; } From 9ccd6acbc53803763ee4c7f8bfc11f4786f8ae12 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sat, 11 Nov 2023 13:20:51 -0800 Subject: [PATCH 10/12] fix test setting --- src/tests/Common/testenvironment.proj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/Common/testenvironment.proj b/src/tests/Common/testenvironment.proj index 1613582faf6ee7..759462d3c925a7 100644 --- a/src/tests/Common/testenvironment.proj +++ b/src/tests/Common/testenvironment.proj @@ -80,7 +80,7 @@ RunningIlasmRoundTrip; DOTNET_JitSynthesizeCounts; DOTNET_JitCheckSynthesizedCounts - DOTNET_JitDoCrossBlockLocalAssertionProp + DOTNET_JitEnableCrossBlockLocalAssertionProp From eb544c15b7a594a0cf2989657efb3077b052cbd2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sat, 11 Nov 2023 13:24:33 -0800 Subject: [PATCH 11/12] fix typo --- src/coreclr/jit/assertionprop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/assertionprop.cpp b/src/coreclr/jit/assertionprop.cpp index 3a586fd246c035..30e2db23ecd063 100644 --- a/src/coreclr/jit/assertionprop.cpp +++ b/src/coreclr/jit/assertionprop.cpp @@ -1651,7 +1651,7 @@ AssertionIndex Compiler::optAddAssertion(AssertionDsc* newAssertion) AssertionIndex const index = GetAssertionIndex(bvIndex); AssertionDsc* const curAssertion = optGetAssertion(index); - if (curAssertion->Equals(newAssertion, /* vnBased */ false) + if (curAssertion->Equals(newAssertion, /* vnBased */ false)) { return index; } From 17eb3b6b2c6c6d2e887f2e433216a88a2f2b8ebb Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sat, 11 Nov 2023 14:25:27 -0800 Subject: [PATCH 12/12] fix reachability check for OSR --- src/coreclr/jit/fgopt.cpp | 10 ++++++++++ src/coreclr/jit/morph.cpp | 3 +++ 2 files changed, 13 insertions(+) diff --git a/src/coreclr/jit/fgopt.cpp b/src/coreclr/jit/fgopt.cpp index 665aeeee217dfa..d144eb68b9e41b 100644 --- a/src/coreclr/jit/fgopt.cpp +++ b/src/coreclr/jit/fgopt.cpp @@ -788,6 +788,16 @@ unsigned Compiler::fgDfsReversePostorder() // fgDfsReversePostorderHelper(fgFirstBB, visited, preorderIndex, postorderIndex); + // For OSR, walk from the original method entry too. + // + if (opts.IsOSR() && (fgEntryBB != nullptr)) + { + if (!BlockSetOps::IsMember(this, visited, fgEntryBB->bbNum)) + { + fgDfsReversePostorderHelper(fgEntryBB, visited, preorderIndex, postorderIndex); + } + } + // If we didn't end up visiting everything, try the EH roots. // if (preorderIndex != fgBBcount + 1) diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 4a5c85abd89d3a..8460ef144f2abd 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -13827,6 +13827,9 @@ void Compiler::fgMorphBlock(BasicBlock* block, unsigned highestReachablePostorde // This pred was not reachable from the original DFS root set, so // we can ignore its assertion information. // + JITDUMP(FMT_BB " ignoring assertions from unreachable pred " FMT_BB + " [pred postorder num %u, highest reachable %u]\n", + block->bbNum, pred->bbNum, pred->bbPostorderNum, highestReachablePostorder); continue; }