Repository navigation
JIT: Remove redundant Span.Slice/Vector128.Create checks in vectorized loops - #135739
Conversation
…d loops Improve range-based relop folding so that the checks in typical vectorized span loops can be removed: * Fold "(uint)X > (uint)Bnd" (Span.Slice) via symbolic ranges relative to Bnd. * Fold "Bnd - X >= cns" (Vector128.Create length check) by computing the range of X relative to Bnd. * Use "Bnd1 == Bnd2" assertions to express limits of Bnd1 via the preferred Bnd2. * EvalRelop: compare "Bnd + cns" limits with the same Bnd. * Widen: retry monotonic analysis for negative constant lower bounds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 23323467-e54f-4591-bf7e-c52042612588
|
Azure Pipelines: Successfully started running 5 pipeline(s). 11 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
🟡 Changes recommended
The new range derivation can negate INT32_MIN and lacks targeted codegen regression coverage.
2 open findings
What changed in this PR
Enhances JIT range analysis to eliminate redundant checks in vectorized span loops.
Changes:
- Compares symbolic limits sharing a checked bound.
- Derives ranges for
Bnd - Xand equivalent bounds. - Improves loop widening for negative lower bounds.
| File | Description |
|---|---|
rangecheck.h |
Extends symbolic relational folding. |
rangecheck.cpp |
Improves widening and equal-bound handling. |
compiler.h |
Declares new range-analysis helpers. |
assertionprop.cpp |
Adds bound selection and subtraction-range analysis. |
🧠 Review effort: Balanced
Widen now also tightens negative constant lower limits via monotonicity, but DoesVarDefOverflow skips the def-chain overflow check for non-dependent limits, so a wrapping IV (e.g. i += int.MaxValue) could get a bogus non-negative lower bound. Re-run the overflow check with a dependent lower limit first. Also avoid negating INT32_MIN in GetBoundMinusXRange. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2c55bab0-dbf9-4ac2-aa19-38b89f405ecc
|
PTAL @tannergooding @dotnet/jit-contrib I did some fuzzy testing + AI validation and found no issues. Since it's early in the cycle, I think it's safe to merge. |
|
@EgorBo I think the practical is we have up to 4 spans involved, will this work for that too? In particular we have cases like
and the current guard we do is: if (left.Length != right.Length || left.Length != addend.Length)
{
ThrowHelper.ThrowArgument_SpansMustHaveSameLength();
}
if (left.Length > destination.Length)
{
ThrowHelper.ThrowArgument_DestinationTooShort();
}So we essentially ensure:
We could do something like The loop we're doing is then essentially (if we converted it from unsafe to span): while (left.Length > (Vector<T>.Count * UnrollFactor))
{
tmp1 = Operator(Vector.Create(left[(Vector<T>.Count * 0)..]), Vector.Create(right[(Vector<T>.Count * 0)..]), Vector.Create(addend[(Vector<T>.Count * 0)..]));
tmp2 = Operator(Vector.Create(left[(Vector<T>.Count * 1)..]), Vector.Create(right[(Vector<T>.Count * 1)..]), Vector.Create(addend[(Vector<T>.Count * 1)..]));
// ... UnrollFactor times
tmp1.CopyTo(destination[(Vector<T>.Count * 0)..]);
tmp2.CopyTo(destination[(Vector<T>.Count * 1)..]);
// ... UnrollFactor times
left = left[(Vector<T>.Count * UnrollFactor)..];
right = right[(Vector<T>.Count * UnrollFactor)..];
addend = addend[(Vector<T>.Count * UnrollFactor)..];
destination = destination[(Vector<T>.Count * UnrollFactor)..];
}So the ideal from the span work is this scenario works, so we're covering The efficient trailing element handling is then the other relevant part, where we have some |
tannergooding
left a comment
There was a problem hiding this comment.
Changes LGTM. There's one small ordering pessimization (I think we should fix this) and potentially a missed scenario (I don't think this is super relevant)
I also gave an example of the "worst case" I know we actually want to support, which I think this will handle but it might be good to validate.
Check the "Bnd == PreferredBnd" case first, so the limit is expressed via the preferred bound even when Bnd is never negative (e.g. lengths of spans created from arrays). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2942b3d9-86e0-4bf3-8e10-5e401a447573
Replace AreVNsEqual with a short chain search over "==", "<" and "<=" assertions, so "X <= Bnd + cns" can be expressed via the preferred bound when "Bnd == PreferredBnd" holds transitively (e.g. "a == b && b == c"), or when "Bnd <= PreferredBnd" holds for an upper limit with cns <= 0 and a never negative Bnd. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2942b3d9-86e0-4bf3-8e10-5e401a447573
Follow SSA copies through gtEffectiveVal so "Bnd - X" is found behind CSE uses ("tmp = cse") and defs ("tmp = COMMA(STORE(cse, Bnd - X), cse)").
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2942b3d9-86e0-4bf3-8e10-5e401a447573
|
@tannergooding thanks, addressed your feedback, it increased the diffs even more (up to -840k) Unfortunately, we still can't handle the shape used in TensorPrimitives (but this PR makes a step towards that). |
|
nice! and sounds good on TensorPrimitives. We're getting closer at least |


Improve range-based relop folding so that the checks in typical vectorized span loops can be removed:
As the result, we fold all bound checks for:
Before:
After:
Quite big Diffs