Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 5.6k
JIT: generalize cloning conditions slightly#128532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
509d8b1020e623b0afb8aab57dbeb4fd5f79900d9fa6d478f37f4466bd3adafae5b4fa0ab830dFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -5566,19 +5566,31 @@ GenTreeLclVarCommon* FlowGraphNaturalLoop::FindDef(unsigned lclNum) | ||
| // the loop. | ||
| // | ||
| // Parameters: | ||
| // info - [out] Loop information | ||
| // info - [out] Loop information | ||
| // allowMissingBaseCase - If true, succeed even when we cannot prove that the | ||
| // loop condition [IterVar TestOper Limit] holds on | ||
| // entry, provided the limit form is one we know how | ||
| // to materialize at runtime. The caller is then | ||
| // responsible for emitting a runtime entry guard | ||
| // equivalent to that condition when | ||
| // info->NeedsZeroTripGuard is set on return. | ||
| // Defaults to false; existing callers retain the | ||
| // stronger guarantees described below. | ||
| // | ||
| // Returns: | ||
| // True if the structure was analyzed and we can make guarantees about it; | ||
| // otherwise false. | ||
| // | ||
| // Remarks: | ||
| // On a true return, the function guarantees that the loop invariant is true | ||
| // and maintained at all points within the loop, except possibly right after | ||
| // the update of the iterator variable (NaturalLoopIterInfo::IterTree). The | ||
| // function guarantees that the test (NaturalLoopIterInfo::TestTree) occurs | ||
| // immediately after the update, so no IR in the loop is executed without the | ||
| // loop invariant being true, except for the test. | ||
| // On a true return with allowMissingBaseCase == false (or with the flag set | ||
| // but info->NeedsZeroTripGuard == false), the function guarantees that the | ||
| // loop invariant is true and maintained at all points within the loop, | ||
| // except possibly right after the update of the iterator variable | ||
| // (NaturalLoopIterInfo::IterTree). optExtractTestIncr permits the IV update | ||
| // to be separated from the loop test by other statements, but it rejects | ||
| // any candidate where an intervening statement references the iterator | ||
| // variable, so no IR in the loop is executed observing the post-update | ||
| // value of the iterator except the test itself. | ||
| // | ||
| // The loop invariant is defined as the expression obtained by | ||
| // [info->IterVar] [info->TestOper()] [info->Limit()]. Note that | ||
| @@ -5599,7 +5611,19 @@ GenTreeLclVarCommon* FlowGraphNaturalLoop::FindDef(unsigned lclNum) | ||
| // In some cases we also know the initial value on entry to the loop; see | ||
| // ::HasConstInit and ::ConstInitValue. | ||
| // | ||
| bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info) | ||
| // When allowMissingBaseCase is true and the function would otherwise fail | ||
| // because the loop condition [IterVar TestOper Limit] cannot be proven to | ||
| // hold on entry (no suitable preheader BBJ_COND guard and no constant | ||
| // init/limit pair that proves the base case), the function may instead | ||
| // succeed with info->NeedsZeroTripGuard set. In that mode the above | ||
| // invariant only holds conditionally: it is the caller's obligation to | ||
| // insert a runtime test equivalent to [IterVar TestOper() Limit] on the | ||
| // path that reaches the analyzed loop body. Loop cloning uses this to emit | ||
| // the guard as an extra cloning condition on the fast-path version. Note | ||
| // that this includes do/while-style loops that are guaranteed to execute | ||
| // at least once but whose loop condition may be false on entry. | ||
| // | ||
| bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info, bool allowMissingBaseCase) | ||
| { | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| JITDUMP("Analyzing iteration for " FMT_LP " with header " FMT_BB "\n", m_index, m_header->bbNum); | ||
| @@ -5613,7 +5637,8 @@ bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info) | ||
| GenTree* test = nullptr; | ||
| info->IterVar = BAD_VAR_NUM; | ||
| info->IterVar = BAD_VAR_NUM; | ||
| info->NeedsZeroTripGuard = false; | ||
| for (FlowEdge* exitEdge : ExitEdges()) | ||
| { | ||
| @@ -5700,8 +5725,21 @@ bool FlowGraphNaturalLoop::AnalyzeIteration(NaturalLoopIterInfo* info) | ||
| if (!CheckLoopConditionBaseCase(preheader, info)) | ||
| { | ||
| JITDUMP(" Loop condition may not be true on the first iteration\n"); | ||
| return false; | ||
| // If the caller can emit its own runtime guard for the case where the | ||
| // loop body might not execute on the first iteration, allow the loop | ||
| // through provided we have enough symbolic info about init and limit | ||
| // to express the guard. | ||
| if (allowMissingBaseCase && (info->HasConstLimit || info->HasInvariantLocalLimit || info->HasArrayLengthLimit)) | ||
| { | ||
| JITDUMP(" Loop condition may not be true on the first iteration; deferring to caller " | ||
| "(NeedsZeroTripGuard)\n"); | ||
| info->NeedsZeroTripGuard = true; | ||
| } | ||
| else | ||
| { | ||
| JITDUMP(" Loop condition may not be true on the first iteration\n"); | ||
| return false; | ||
| } | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| #ifdef DEBUG | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1227,6 +1227,95 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl | ||
| return false; | ||
| } | ||
| // If the loop limit is an array length, compute the underlying ArrIndex | ||
| // and queue the deref check once up front. Both the optional zero-trip | ||
| // guard below and the regular limit conditions further down reuse this | ||
| // single ArrIndex to avoid duplicating the deref entry and allocation. | ||
| // | ||
| ArrIndex* limitArrIndex = nullptr; | ||
| if (iterInfo->HasArrayLengthLimit) | ||
| { | ||
| limitArrIndex = new (getAllocator(CMK_LoopClone)) ArrIndex(getAllocator(CMK_LoopClone)); | ||
| if (!iterInfo->ArrLenLimit(this, limitArrIndex)) | ||
| { | ||
| JITDUMP("> ArrLen not matching\n"); | ||
| return false; | ||
| } | ||
| LC_Array array(LC_Array::Jagged, limitArrIndex, LC_Array::None); | ||
| context->EnsureArrayDerefs(loop->GetIndex())->Push(array); | ||
| } | ||
| // If AnalyzeIteration could not prove the loop condition holds on entry, | ||
| // emit an explicit runtime entry guard as one of the cloning conditions. | ||
| // The fast path is then only entered when "init TestOper limit" holds. | ||
| if (iterInfo->NeedsZeroTripGuard) | ||
| { | ||
| LC_Ident initIdent; | ||
| if (iterInfo->HasConstInit) | ||
| { | ||
| if (iterInfo->ConstInitValue < 0) | ||
| { | ||
| JITDUMP("> NeedsZeroTripGuard: init %d is invalid\n", iterInfo->ConstInitValue); | ||
| return false; | ||
| } | ||
| initIdent = LC_Ident::CreateConst(static_cast<unsigned>(iterInfo->ConstInitValue)); | ||
| } | ||
| else | ||
| { | ||
| // Init is unknown statically; use the IV local as it stands at the | ||
| // preheader (the analysis already verified the local is not | ||
| // address-exposed and has no extraneous defs inside the loop, so | ||
| // reading it in the preheader gives the entry value). | ||
| const unsigned initLcl = iterInfo->IterVar; | ||
| if (!genActualTypeIsInt(lvaGetDesc(initLcl))) | ||
| { | ||
| JITDUMP("> NeedsZeroTripGuard: iter var V%02u not compatible with TYP_INT\n", initLcl); | ||
| return false; | ||
| } | ||
| initIdent = LC_Ident::CreateVar(initLcl); | ||
| } | ||
| LC_Ident limitIdent; | ||
| if (iterInfo->HasConstLimit) | ||
| { | ||
| int limit = iterInfo->ConstLimit(); | ||
| if (limit < 0) | ||
| { | ||
| JITDUMP("> NeedsZeroTripGuard: limit %d is invalid\n", limit); | ||
| return false; | ||
| } | ||
| limitIdent = LC_Ident::CreateConst(static_cast<unsigned>(limit)); | ||
| } | ||
| else if (iterInfo->HasInvariantLocalLimit) | ||
| { | ||
| const unsigned limitLcl = iterInfo->VarLimit(); | ||
| if (!genActualTypeIsInt(lvaGetDesc(limitLcl))) | ||
| { | ||
| JITDUMP("> NeedsZeroTripGuard: limit var V%02u not compatible with TYP_INT\n", limitLcl); | ||
| return false; | ||
| } | ||
| limitIdent = LC_Ident::CreateVar(limitLcl); | ||
| } | ||
| else if (iterInfo->HasArrayLengthLimit) | ||
| { | ||
| assert(limitArrIndex != nullptr); | ||
| limitIdent = LC_Ident::CreateArrAccess(LC_Array(LC_Array::Jagged, limitArrIndex, LC_Array::ArrLen)); | ||
| } | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| else | ||
| { | ||
| JITDUMP("> NeedsZeroTripGuard: undetected limit\n"); | ||
| return false; | ||
| } | ||
| // TestOper() returns the stays-in-loop relop in IV-on-lhs form, already | ||
| // adjusted for IsReversed and ExitedOnTrue. | ||
| LC_Condition zeroTrip(iterInfo->TestOper(), LC_Expr(initIdent), LC_Expr(limitIdent), | ||
| iterInfo->TestTree->IsUnsigned()); | ||
| context->EnsureConditions(loop->GetIndex())->Push(zeroTrip); | ||
| JITDUMP("Added zero-trip guard cloning condition\n"); | ||
| } | ||
| LC_Ident ident; | ||
| // Init conditions | ||
| if (iterInfo->HasConstInit) | ||
| @@ -1311,17 +1400,8 @@ bool Compiler::optDeriveLoopCloningConditions(FlowGraphNaturalLoop* loop, LoopCl | ||
| } | ||
| else if (iterInfo->HasArrayLengthLimit) | ||
| { | ||
| ArrIndex* index = new (getAllocator(CMK_LoopClone)) ArrIndex(getAllocator(CMK_LoopClone)); | ||
| if (!iterInfo->ArrLenLimit(this, index)) | ||
| { | ||
| JITDUMP("> ArrLen not matching\n"); | ||
| return false; | ||
| } | ||
| ident = LC_Ident::CreateArrAccess(LC_Array(LC_Array::Jagged, index, LC_Array::ArrLen)); | ||
| // Ensure that this array must be dereference-able, before executing the actual condition. | ||
| LC_Array array(LC_Array::Jagged, index, LC_Array::None); | ||
| context->EnsureArrayDerefs(loop->GetIndex())->Push(array); | ||
| assert(limitArrIndex != nullptr); | ||
| ident = LC_Ident::CreateArrAccess(LC_Array(LC_Array::Jagged, limitArrIndex, LC_Array::ArrLen)); | ||
| } | ||
| else | ||
| { | ||
| @@ -2993,7 +3073,7 @@ bool Compiler::optObtainLoopCloningOpts(LoopCloneContext* context) | ||
| { | ||
| JITDUMP("Considering loop " FMT_LP " to clone for optimizations.\n", loop->GetIndex()); | ||
| NaturalLoopIterInfo iterInfo; | ||
| if (loop->AnalyzeIteration(&iterInfo)) | ||
| if (loop->AnalyzeIteration(&iterInfo, /* allowMissingBaseCase */ true)) | ||
| { | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| context->SetLoopIterInfo(loop->GetIndex(), new (this, CMK_LoopClone) NaturalLoopIterInfo(iterInfo)); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -356,8 +356,9 @@ bool Compiler::optExtractTestIncr(BasicBlock* cond, GenTree** ppTest, GenTree** | ||
| assert(ppTest != nullptr); | ||
| assert(ppIncr != nullptr); | ||
| // Check if last two statements in the loop body are the increment of the iterator | ||
| // and the loop termination test. | ||
| // The loop termination test is expected to be the last statement of the exiting | ||
| // block. The increment of the iterator is expected somewhere earlier in the | ||
| // same block; we scan backward from the test to find an IV-shaped update. | ||
| noway_assert(cond->firstStmt() != nullptr); | ||
| Statement* testStmt = cond->lastStmt(); | ||
| noway_assert(testStmt != nullptr && testStmt->GetNextStmt() == nullptr); | ||
| @@ -368,24 +369,87 @@ bool Compiler::optExtractTestIncr(BasicBlock* cond, GenTree** ppTest, GenTree** | ||
| testStmt = newTestStmt; | ||
| } | ||
| // Check if we have the incr stmt before the test stmt, if we don't, | ||
| // check if incr is part of the loop "header". | ||
| Statement* incrStmt = testStmt->GetPrevStmt(); | ||
| // If we've added profile instrumentation, we may need to skip past a BB counter update. | ||
| // Walk backward from the test statement looking for a candidate IV increment | ||
| // of the form 'v = v op c'. For each such candidate, verify it is suitable: | ||
| // - the iterator local is not address-exposed (our intervening-read check | ||
| // would not see indirect accesses, and AnalyzeIteration rejects | ||
| // address-exposed IVs anyway); | ||
| // - the test actually reads the iterator (otherwise this is an unrelated | ||
| // update that happens to be IV-shaped); | ||
| // - no statement strictly between the candidate and the test reads the | ||
| // iterator. Such statements would execute with the post-increment value, | ||
| // violating the AnalyzeIteration invariant that no loop body IR observes | ||
| // the post-increment value except the test. Stores are not checked here: | ||
| // AnalyzeIteration's VisitDefs already rejects any def of iterVar in the | ||
| // loop other than the picked increment. If the test block is in a try | ||
| // region, treat any intervening statement that may throw as if it were | ||
| // an intervening read, since an EH handler could observe the | ||
| // post-increment value. | ||
| // On any rejection, continue scanning backward; only fail when no candidate | ||
| // in the block satisfies all checks. A budget bounds the combined work of | ||
| // the outer scan and the inner intervening-read scan to avoid pathological | ||
| // O(N^2) behavior in blocks with many IV-shaped statements. | ||
| // | ||
| if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_BBINSTR) && (incrStmt != nullptr) && | ||
| incrStmt->GetRootNode()->IsBlockProfileUpdate()) | ||
| { | ||
| incrStmt = incrStmt->GetPrevStmt(); | ||
| Statement* incrStmt = nullptr; | ||
| unsigned iterVar = BAD_VAR_NUM; | ||
| Statement* firstStmt = cond->firstStmt(); | ||
| const bool condInTry = cond->hasTryIndex(); | ||
| unsigned int budget = 100; | ||
| if (testStmt != firstStmt) | ||
| { | ||
| for (Statement* s = testStmt->GetPrevStmt();; s = s->GetPrevStmt()) | ||
| { | ||
| if (budget-- == 0) | ||
| { | ||
| JITDUMP("optExtractTestIncr: budget exhausted in " FMT_BB "\n", cond->bbNum); | ||
| return false; | ||
| } | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| unsigned candVar = optIsLoopIncrTree(s->GetRootNode()); | ||
| if (candVar != BAD_VAR_NUM) | ||
| { | ||
| if (!lvaGetDesc(candVar)->IsAddressExposed() && gtTreeHasLocalRead(testStmt->GetRootNode(), candVar)) | ||
| { | ||
| bool intermediateUse = false; | ||
| for (Statement* t = s->GetNextStmt(); t != testStmt; t = t->GetNextStmt()) | ||
| { | ||
| assert(t != nullptr); | ||
| if (budget-- == 0) | ||
| { | ||
| JITDUMP("optExtractTestIncr: budget exhausted in " FMT_BB "\n", cond->bbNum); | ||
| return false; | ||
| } | ||
| GenTree* root = t->GetRootNode(); | ||
| if (gtTreeHasLocalRead(root, candVar) || (condInTry && ((root->gtFlags & GTF_EXCEPT) != 0))) | ||
| { | ||
| intermediateUse = true; | ||
| break; | ||
| } | ||
| } | ||
| if (!intermediateUse) | ||
| { | ||
| incrStmt = s; | ||
| iterVar = candVar; | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (s == firstStmt) | ||
| { | ||
| break; | ||
| } | ||
| } | ||
AndyAyersMS marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| } | ||
| if (incrStmt == nullptr || (optIsLoopIncrTree(incrStmt->GetRootNode()) == BAD_VAR_NUM)) | ||
| if (incrStmt == nullptr) | ||
| { | ||
| return false; | ||
| } | ||
| assert(testStmt != incrStmt); | ||
| assert(iterVar != BAD_VAR_NUM); | ||
| *ppTest = testStmt->GetRootNode(); | ||
| *ppIncr = incrStmt->GetRootNode(); | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.