JIT: less loop inversion for bottom tested loops with no evident IV#130368
JIT: less loop inversion for bottom tested loops with no evident IV#130368AndyAyersMS wants to merge 6 commits into
Conversation
Under JitLoopInversionRequireBenefitForBottomTested (off by default), skip inverting an already bottom-tested loop with no recognized IV unless the duplicated condition holds a call or a loop-invariant, hoistable load. Targets the arm64 regressions in dotnet#130045 while keeping the Dictionary Enumerator.MoveNext inversion. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
|
@EgorBot -arm64 --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class Bench
{
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int Span_IndexOf_NotFound() => _a.AsSpan().IndexOf(-1);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
[Benchmark]
public bool BigInteger_Equals() => _x.Equals(_y);
}Base is Note Comment generated with GitHub Copilot CLI. |
There was a problem hiding this comment.
Pull request overview
This PR adds an (opt-in) heuristic gate to optTryInvertWhileLoop so that when a loop is already bottom-tested and AnalyzeIteration recognizes no IV, loop inversion is only performed if duplicating the condition block appears to have a “benefit” (call or potentially hoistable memory load). It also introduces a new JIT config knob to enable this gate.
Changes:
- Track “exiting cond latch seen” separately from “IV-test latch” and only apply the new gate in the bottom-tested + no-IV case.
- Classify
condBlockfor calls/indirections and use the existing loop size walk to conservatively detect stores to locals used in indirection address expressions. - Add
JitLoopInversionRequireBenefitForBottomTestedconfig (default off).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/coreclr/jit/optimizer.cpp |
Adds the benefit-based gate and condition classification for bottom-tested no-IV loops. |
src/coreclr/jit/jitconfigvalues.h |
Introduces the new release config switch controlling the gate. |
|
Re-running on server arm64 (Cobalt 100) — the earlier @EgorBot -linux_arm64 --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
public class Bench
{
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int Span_IndexOf_NotFound() => _a.AsSpan().IndexOf(-1);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
[Benchmark]
public bool BigInteger_Equals() => _x.Equals(_y);
}Base is Note Comment generated with GitHub Copilot CLI. |
|
Adding the two biggest #130045 regressions ( @EgorBot -linux_arm64 -ubuntu24_azure_genoa -windows_x64 --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SearchValues<char> _searchValues;
private char[] _textExcept;
private int[] _found;
private Dictionary<int, int> _dictionary;
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
_searchValues = SearchValues.Create("ßäöüÄÖÜ");
_textExcept = new string('ß', 256).ToCharArray();
_textExcept[128] = '\n';
_found = Enumerable.Range(0, 512).ToArray();
_dictionary = _found.ToDictionary(k => k, k => k);
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public int LastIndexOfAnyExcept() => _textExcept.AsSpan().LastIndexOfAnyExcept(_searchValues);
[Benchmark]
public bool ContainsKeyTrue_IDictionary() => ContainsKey(_dictionary);
[MethodImpl(MethodImplOptions.NoInlining)]
private bool ContainsKey(IDictionary<int, int> collection)
{
bool result = false;
var found = _found;
for (int i = 0; i < found.Length; i++)
result ^= collection.ContainsKey(found[i]);
return result;
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
}Base is Note Comment generated with GitHub Copilot CLI. |
|
Also on Ampere (Neoverse-N1) — the core class behind the @EgorBot -ubuntu24_azure_ampere --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SearchValues<char> _searchValues;
private char[] _textExcept;
private int[] _found;
private Dictionary<int, int> _dictionary;
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
_searchValues = SearchValues.Create("ßäöüÄÖÜ");
_textExcept = new string('ß', 256).ToCharArray();
_textExcept[128] = '\n';
_found = Enumerable.Range(0, 512).ToArray();
_dictionary = _found.ToDictionary(k => k, k => k);
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public int LastIndexOfAnyExcept() => _textExcept.AsSpan().LastIndexOfAnyExcept(_searchValues);
[Benchmark]
public bool ContainsKeyTrue_IDictionary() => ContainsKey(_dictionary);
[MethodImpl(MethodImplOptions.NoInlining)]
private bool ContainsKey(IDictionary<int, int> collection)
{
bool result = false;
var found = _found;
for (int i = 0; i < found.Length; i++)
result ^= collection.ContainsKey(found[i]);
return result;
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
}Base is Note Comment generated with GitHub Copilot CLI. |
|
@EgorBot -ubuntu24_azure_ampere --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 --filter "ContainsKeyTrueInt32IDictionary" Real perf-repo benchmark this time (random Note Comment generated with GitHub Copilot CLI. |
|
Retry of the @EgorBot -ubuntu24_azure_ampere --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 --filter "ContainsKeyTrueInt32IDictionary" Base is Note Comment generated with GitHub Copilot CLI. |
EgorBot validation summaryRan the #130045 cases across the hardware classes behind the perf lab, comparing Hardware → lab mapping: Ampere N1 = The two biggest regressions recover to their lab baselines
The PR numbers land right on the pre-#129868 lab baselines. Full matrix (fix effect on the benchmark's Mean; negative = faster)
Reading it
Notes: Note Comment generated with GitHub Copilot CLI. |
Defer the benefit-gate bitvec allocation to when the config is on, correct the "indirection address locals" wording, and soften the config comment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…bottom-tested-benefit
Remove the JitLoopInversionRequireBenefitForBottomTested config and always apply the gate: for an already bottom-tested loop with no recognized IV, only invert when the duplicated condition has a call or an indirection whose address has no loop-varying local. Also address review feedback: track GT_LCL_ADDR in indirection addresses via OperIsAnyLocal, and describe the check as an indirection rather than a load. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The message claimed a "hoistable" benefit, but the check only requires a call or an indirection whose address has no loop-varying local (which does not by itself prove the load is hoistable). Describe it accurately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@jakobbotsch PTAL |
|
Azure Pipelines: Successfully started running 5 pipeline(s). 10 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
| // If the loop is already bottom-tested (has an exiting BBJ_COND latch that is not the IV test) | ||
| // and no induction variable was recognized, only invert when the block that would be duplicated | ||
| // (condBlock) contains a call or an indirection whose address has no loop-varying local. Classify | ||
| // condBlock here and record the locals appearing in its indirection address expressions; the | ||
| // size-check walk below flags whether any of them is assigned in the loop (making the indirection | ||
| // loop-variant). |
There was a problem hiding this comment.
Seems very ad-hoc... Is this targeting array lengths specifically or what is the motivating case?
There was a problem hiding this comment.
This is mainly trying to claw back overly aggressive inversions from #129868. Without a bottom-tested IV, we need some evidence that inversion may help CQ.
The call check is less sensible, let me see if can refine that to just hoistable calls.
Screen a bottom-tested/no-IV loop condition's calls with optIsCSEcandidate (no persistent side effects, not an allocator) instead of accepting any call, and require the call's arguments to be loop-invariant, matching the treatment of indirections. optIsCSEcandidate/CanConsiderTree gain a skipCostChecks arg so the structural filter can run during loop inversion before tree costs are set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| if (n->OperIsIndir()) | ||
| { | ||
| *m_hasCandidate = true; | ||
| CollectLocals(n->AsIndir()->Addr()); | ||
| } |
| // Skip the inversion unless the duplicated test carries a benefit: a loop-invariant hoisting | ||
| // candidate (a CSE-able call or an indirection whose operands have no loop-varying local), which | ||
| // LICM can lift once the body dominates the back-edge. condCandidateStored is left conservatively | ||
| // false if the size walk above was skipped or aborted early, keeping the inversion. |
| JITDUMP("No loop-inversion for " FMT_LP "; already bottom-tested with no recognized IV and no " | ||
| "loop-invariant hoisting candidate in the duplicated condition\n", | ||
| loop->GetIndex()); |
|
Re-validating the key #130045 cases now that the gate is on by default — the @EgorBot -ubuntu24_azure_ampere -linux_arm64 -ubuntu24_azure_genoa using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SearchValues<char> _searchValues;
private char[] _textExcept;
private int[] _found;
private Dictionary<int, int> _dictionary;
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
_searchValues = SearchValues.Create("ßäöüÄÖÜ");
_textExcept = new string('ß', 256).ToCharArray();
_textExcept[128] = '\n';
_found = Enumerable.Range(0, 512).ToArray();
_dictionary = _found.ToDictionary(k => k, k => k);
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public int LastIndexOfAnyExcept() => _textExcept.AsSpan().LastIndexOfAnyExcept(_searchValues);
[Benchmark]
public bool ContainsKeyTrue_IDictionary() => ContainsKey(_dictionary);
[MethodImpl(MethodImplOptions.NoInlining)]
private bool ContainsKey(IDictionary<int, int> collection)
{
bool result = false;
var found = _found;
for (int i = 0; i < found.Length; i++)
result ^= collection.ContainsKey(found[i]);
return result;
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
}Hardware → lab: Ampere N1 = Note Comment generated with GitHub Copilot CLI. |
|
Workflow state for the Holistic Review Orchestrator. {
"version": 5,
"last_dispatched_commit": "14fabd8de5f4d59307a8eb606fc9edc40c8bbd13",
"last_dispatched_base_ref": "main",
"last_dispatched_base_sha": "6d47555c87f335445a547d00da9cd7a2034d94ce",
"last_reviewed_commit": "14fabd8de5f4d59307a8eb606fc9edc40c8bbd13",
"last_reviewed_base_ref": "main",
"last_reviewed_base_sha": "6d47555c87f335445a547d00da9cd7a2034d94ce",
"last_recorded_worker_run_id": "29685646334",
"review_attempt_commit": "",
"review_attempt_base_ref": "",
"review_attempt_count": 0,
"max_review_attempts": 5,
"review_history_format": "holistic-review-disclosure-v1",
"review_history": [
{
"commit": "14fabd8de5f4d59307a8eb606fc9edc40c8bbd13",
"review_id": 4730705724
}
]
} |
There was a problem hiding this comment.
Holistic Review
Motivation: Fixes #130045, where loop inversion of already bottom-tested loops with no recognized induction variable caused arm64 regressions. Inverting such loops duplicates the exit condition without a clear payoff, so the change gates that specific case on evidence that duplication will actually enable a downstream optimization.
Approach: In optTryInvertWhileLoop, the existing back-edge scan is refactored to track sawExitingCondLatch separately from the early-out IV-test check. When a loop is bottom-tested with no recognized IV (bottomTestedNoIV), a GenTreeVisitor classifies condBlock for a hoisting benefit — a CSE-able call (via optIsCSEcandidate) or an indirection — and records the operand locals. The existing loop-size complexity walk is reused to detect whether any of those locals is stored in the loop (making the candidate loop-variant). Inversion proceeds only when a benefit exists and none of its operands are stored. To support calling the CSE legality filter before tree costs are initialized, CanConsiderTree/optIsCSEcandidate gain a skipCostChecks parameter that bypasses only the cost-based MIN_CSE_COST gate while preserving all legality/structural checks.
Summary: The change is well-scoped, thoroughly commented, and correct. The gate applies only to the narrow bottomTestedNoIV case, and every conservative fallback (size walk skipped when JitLoopInversionSizeLimit < 0, or aborted early on complexity) is biased toward preserving the prior inverting behavior rather than newly suppressing it. The assert(analyzedIteration) holds because sawExitingCondLatch is only set inside branches that first call isIvTest, which forces AnalyzeIteration. The skipCostChecks refactor is a clean, side-effect-free restructuring of the cost block. Piggy-backing local-store detection on the existing complexity walk keeps the added cost negligible. I have only minor, non-blocking observations noted below. LGTM.
Detailed Findings
Non-blocking observations (not actionable, no changes required):
-
optcse.cpp— The indirection branch inCondClassifier::PreOrderVisitsets*m_hasCandidate = truefor anyOperIsIndirnode without excluding volatile indirections, which LICM cannot hoist. Because the address-local store check still runs, a genuinely loop-variant indirection is filtered out, but a volatile invariant load would be treated as a benefit. This only makes the gate slightly more permissive (retaining an inversion that may not pay off), so it does not regress correctness or the targeted #130045 scenario. Optional tightening if the heuristic proves too permissive in practice. -
optcse.cpp—optGetCSEheuristic()now constructs and caches the CSE heuristic during loop inversion (earlier than the CSE phase) for methods that reach the classifier with a call incondBlock. This is harmless — the object is cached inoptCSEheuristicand reused unchanged byoptOptimizeValnumCSEs— but it is a phase-ordering behavior change worth noting; the heuristic is now instantiated for some methods that previously deferred it until CSE. No functional impact.
Given the heuristic nature of the change, the arm64-focused CI/perf validation referenced in the PR is the right gate for confirming the regression fix and absence of broad CQ loss; the code itself is sound.
Note
This review was generated by this repository's Holistic Review agentic workflow to complement the built-in Copilot review.
Generated by Holistic Review · 118.6 AIC · ⌖ 14.9 AIC · ⊞ 10K
|
@EgorBot -ubuntu24_azure_ampere -linux_arm64 -ubuntu24_azure_genoa using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SearchValues<char> _searchValues;
private char[] _textExcept;
private int[] _found;
private Dictionary<int, int> _dictionary;
private readonly int[] _a = new int[512];
private readonly int[] _b = new int[512];
private BigInteger _x, _y;
[GlobalSetup]
public void Setup()
{
_searchValues = SearchValues.Create("ßäöüÄÖÜ");
_textExcept = new string('ß', 256).ToCharArray();
_textExcept[128] = '\n';
_found = Enumerable.Range(0, 512).ToArray();
_dictionary = _found.ToDictionary(k => k, k => k);
for (int i = 0; i < _a.Length; i++) { _a[i] = i; _b[i] = i; }
byte[] bytes = new byte[259];
new Random(42).NextBytes(bytes);
bytes[^1] &= 0x7f;
_x = new BigInteger(bytes);
_y = new BigInteger(bytes);
}
[Benchmark]
public int LastIndexOfAnyExcept() => _textExcept.AsSpan().LastIndexOfAnyExcept(_searchValues);
[Benchmark]
public bool ContainsKeyTrue_IDictionary() => ContainsKey(_dictionary);
[MethodImpl(MethodImplOptions.NoInlining)]
private bool ContainsKey(IDictionary<int, int> collection)
{
bool result = false;
var found = _found;
for (int i = 0; i < found.Length; i++)
result ^= collection.ContainsKey(found[i]);
return result;
}
[Benchmark]
public bool Span_SequenceEqual() => _a.AsSpan().SequenceEqual(_b);
[Benchmark]
public int BigInteger_CompareTo() => _x.CompareTo(_y);
} |
|
Checking whether this PR also addresses #130046 (Windows Zen4). The fleet has no Windows Zen4, so best-available Zen4 is Linux Genoa; Windows here is Turin (Zen5) as an anchor. @EgorBot -ubuntu24_azure_genoa -windows_x64 --envvars DOTNET_JitLoopInversionRequireBenefitForBottomTested:1 using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SortedDictionary<int, int> _sortedDict;
private Dictionary<string, string> _stringDict;
private CompareInfo _en;
private string _text;
[GlobalSetup]
public void Setup()
{
var d = Enumerable.Range(0, 512).ToDictionary(k => k, k => k);
_sortedDict = new SortedDictionary<int, int>(d);
var rnd = new Random(12345);
var keys = new HashSet<string>();
while (keys.Count < 512)
keys.Add(rnd.Next().ToString("X8") + rnd.Next().ToString("X8"));
_stringDict = keys.ToDictionary(k => k, k => k);
_en = CultureInfo.GetCultureInfo("en-US").CompareInfo;
_text = "NET Conf provides a wide selection of live sessions streaming here that feature speakers from the community and .NET product teams. It is a chance to learn, ask questions live, and get inspired for your next software project";
}
// System.Collections.CtorFromCollection<Int32>.SortedDictionaryDeepCopy(Size: 512)
[Benchmark]
public SortedDictionary<int, int> SortedDictionaryDeepCopy_Int()
=> new SortedDictionary<int, int>(_sortedDict);
// System.Collections.CtorFromCollection<String>.Dictionary(Size: 512)
[Benchmark]
public Dictionary<string, string> Dictionary_String()
=> new Dictionary<string, string>(_stringDict);
// System.Globalization.Tests.StringSearch.LastIndexOf_Word_NotFound(en-US, OrdinalIgnoreCase, false)
[Benchmark]
public int StringSearch_LastIndexOf_Word_NotFound()
=> _en.LastIndexOf(_text, "word", CompareOptions.OrdinalIgnoreCase);
}Base is Note Comment generated with GitHub Copilot CLI. |
|
Re-running (previous attempt hit an EgorBot arg-parsing glitch on @EgorBot -ubuntu24_azure_genoa using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SortedDictionary<int, int> _sortedDict;
private Dictionary<string, string> _stringDict;
private CompareInfo _en;
private string _text;
[GlobalSetup]
public void Setup()
{
var d = Enumerable.Range(0, 512).ToDictionary(k => k, k => k);
_sortedDict = new SortedDictionary<int, int>(d);
var rnd = new Random(12345);
var keys = new HashSet<string>();
while (keys.Count < 512)
keys.Add(rnd.Next().ToString("X8") + rnd.Next().ToString("X8"));
_stringDict = keys.ToDictionary(k => k, k => k);
_en = CultureInfo.GetCultureInfo("en-US").CompareInfo;
_text = "NET Conf provides a wide selection of live sessions streaming here that feature speakers from the community and .NET product teams. It is a chance to learn, ask questions live, and get inspired for your next software project";
}
[Benchmark]
public SortedDictionary<int, int> SortedDictionaryDeepCopy_Int()
=> new SortedDictionary<int, int>(_sortedDict);
[Benchmark]
public Dictionary<string, string> Dictionary_String()
=> new Dictionary<string, string>(_stringDict);
[Benchmark]
public int StringSearch_LastIndexOf_Word_NotFound()
=> _en.LastIndexOf(_text, "word", CompareOptions.OrdinalIgnoreCase);
}Genoa Zen4 is the closest available mapping for #130046's Windows Zen4. Note Comment generated with GitHub Copilot CLI. |
|
@EgorBot -ubuntu24_azure_genoa using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using BenchmarkDotNet.Attributes;
public class Bench
{
private SortedDictionary<int, int> _sortedDict;
private Dictionary<string, string> _stringDict;
private CompareInfo _en;
private string _text;
[GlobalSetup]
public void Setup()
{
var d = Enumerable.Range(0, 512).ToDictionary(k => k, k => k);
_sortedDict = new SortedDictionary<int, int>(d);
var rnd = new Random(12345);
var keys = new HashSet<string>();
while (keys.Count < 512)
keys.Add(rnd.Next().ToString("X8") + rnd.Next().ToString("X8"));
_stringDict = keys.ToDictionary(k => k, k => k);
_en = CultureInfo.GetCultureInfo("en-US").CompareInfo;
_text = "NET Conf provides a wide selection of live sessions streaming here that feature speakers from the community and .NET product teams. It is a chance to learn, ask questions live, and get inspired for your next software project";
}
[Benchmark]
public SortedDictionary<int, int> SortedDictionaryDeepCopy_Int()
=> new SortedDictionary<int, int>(_sortedDict);
[Benchmark]
public Dictionary<string, string> Dictionary_String()
=> new Dictionary<string, string>(_stringDict);
[Benchmark]
public int StringSearch_LastIndexOf_Word_NotFound()
=> _en.LastIndexOf(_text, "word", CompareOptions.OrdinalIgnoreCase);
}Third attempt — checking whether this PR also addresses the #130046 (Windows Zen4) regressions. Genoa Zen4 is the closest available mapping in the fleet. Gate is on by default on this branch, so no env var needed. (Previous two runs hit an EgorBot parser bug that injects a stray Note Comment generated with GitHub Copilot CLI. |
Make loop inversion a bit less aggressive.
If a loop is bottom-tested and has no evident IV, only invert if there is a hint that inversion might lead to a beneficial CSE (call or possibly invariant load). Analysis is a approximate and piggy backs on existing IR walks we already do.
Fixes #130045.
Note
This change and PR description were produced with GitHub Copilot CLI.