JIT: Fold insertps chains that source a scalar extracted from another vector#130422
Merged
tannergooding merged 2 commits intoJul 13, 2026
Merged
Conversation
… vector Recognize Insert(vector, CreateScalarUnsafe(vector2.GetElement(idx)), idx2) and rewrite it into a single insertps that selects the source lane directly via count_s, folding away the separate extraction. This is valid for any 4-byte element type (float/int/uint) and any source vector width, provided the extracted element is one of the first four lanes (all that count_s can encode and all that resides in the low 128 bits that insertps reads). Both NI_Vector_ToScalar (index 0) and constant-index NI_Vector_GetElement feeders are handled, keeping the register form only when the source is used from a register so the existing insertps xmm, m32 memory form is preserved. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR enhances x64/x86 JIT lowering for Sse41.Insert/insertps by folding a pattern where the inserted scalar is immediately sourced from another vector element, allowing the lowering to select the source lane directly via the count_s imm8 field and eliminate the separate extract+scalar-materialization sequence.
Changes:
- Add a lowering-time fold in
LowerHWIntrinsic(xarch) to collapseInsert(..., CreateScalarUnsafe(GetElement(...)), ...)into a singleinsertpswith updatedcount_s. - Add a new JIT optimization test project validating result correctness across index combinations, chained inserts, and a
Vector256<float>-sourced scenario.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/lowerxarch.cpp | Adds the CreateScalarUnsafe(GetElement(...))-source folding logic in the NI_X86Base_Insert lowering path. |
| src/tests/JIT/opt/Vectorization/InsertScalarFromVector.cs | New xUnit test covering combinations, chaining, and wide-source scenarios for the new fold. |
| src/tests/JIT/opt/Vectorization/InsertScalarFromVector.csproj | New test project file to build the added test. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 2
This was referenced Jul 9, 2026
Member
Author
|
@MihuBot -nuget |
Member
Author
|
CC. @EgorBo, @dotnet/jit-contrib. Simple improvement to the |
EgorBo
approved these changes
Jul 13, 2026
eiriktsarpalis
pushed a commit
that referenced
this pull request
Jul 15, 2026
… vector (#130422) Recognizes `insertps` chains where the value being inserted is itself a scalar extracted from another vector and folds them into a single `insertps` that selects the source element directly via `count_s` (bits 6-7 of the imm8). That is, it rewrites: ``` Insert(vector, CreateScalarUnsafe(vector2.GetElement(idx1)), idx2) ``` into a single `insertps` reading lane `idx1` of `vector2`, folding away the separate extraction (which otherwise requires its own `movshdup`/`unpckhps`/`shufps`). ## Details `insertps` operates purely on the 32-bit lanes of the low 128 bits of its source register, so this fold is valid for: - **Any 4-byte element type** (`float`, `int`, `uint`) — `count_s` selects a 32-bit dword, which maps to the element index only when the element size is 4 bytes. - **A source vector of any width** (128/256/512) — provided the extracted element is one of the first four lanes. That is all `count_s` can encode and all that resides in the low 128 bits, which is the only part `insertps` reads. Feeding a wider (`ymm`/`zmm`) source register to the 128-bit `insertps` is physically correct since the VEX.128 form reads the `xmm` view. Both `NI_Vector_ToScalar` (source index 0) and constant-index `NI_Vector_GetElement` feeders are handled. When the extraction reads from a contained memory operand, the existing lowering already produces an optimal `insertps xmm, m32` (which encodes the element offset in the address), so the fold keeps the register form only when the source is used from a register, avoiding a forced full-vector load. Because the fold keys off the (already-narrowed, SIMD16) `GetElement` node rather than the vector feeding it, it naturally handles wide sources whose element access has been lowered through `GetLower`/`GetLower128`/`GetUpper`/`ExtractVector128` — the correct 128-bit chunk is materialized and `count_s` selects the right lane within it. ## Diffs SuperPMI asmdiffs, windows x64, all collections: **-141 bytes, 18 methods improved, 0 regressed** (no PerfScore regressions). | Collection | Δ bytes | Methods | PerfScore (in diffs) | |---|--:|--:|--:| | libraries.crossgen2 | -29 | 5 improved | -8.31% | | libraries.pmi | -8 | 2 improved | -7.68% | | libraries_tests_no_tiered | -104 | 11 improved | -0.88% | Representative improvements: `Matrix4x4.CreateScale`, `Matrix3x2.CreateScale`, `Vector3.Reflect`, `Vector2.Reflect`, `Quaternion.Lerp`, and various `Matrix4x4` billboard/shadow/decompose helpers. A regression test covering register/memory sources, chained inserts, and wide (`Vector256`) sources across all element indices is included. > [!NOTE] > This PR was authored with the assistance of GitHub Copilot. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Recognizes
insertpschains where the value being inserted is itself a scalar extracted from another vector and folds them into a singleinsertpsthat selects the source element directly viacount_s(bits 6-7 of the imm8).That is, it rewrites:
into a single
insertpsreading laneidx1ofvector2, folding away the separate extraction (which otherwise requires its ownmovshdup/unpckhps/shufps).Details
insertpsoperates purely on the 32-bit lanes of the low 128 bits of its source register, so this fold is valid for:float,int,uint) —count_sselects a 32-bit dword, which maps to the element index only when the element size is 4 bytes.count_scan encode and all that resides in the low 128 bits, which is the only partinsertpsreads. Feeding a wider (ymm/zmm) source register to the 128-bitinsertpsis physically correct since the VEX.128 form reads thexmmview.Both
NI_Vector_ToScalar(source index 0) and constant-indexNI_Vector_GetElementfeeders are handled. When the extraction reads from a contained memory operand, the existing lowering already produces an optimalinsertps xmm, m32(which encodes the element offset in the address), so the fold keeps the register form only when the source is used from a register, avoiding a forced full-vector load.Because the fold keys off the (already-narrowed, SIMD16)
GetElementnode rather than the vector feeding it, it naturally handles wide sources whose element access has been lowered throughGetLower/GetLower128/GetUpper/ExtractVector128— the correct 128-bit chunk is materialized andcount_sselects the right lane within it.Diffs
SuperPMI asmdiffs, windows x64, all collections: -141 bytes, 18 methods improved, 0 regressed (no PerfScore regressions).
Representative improvements:
Matrix4x4.CreateScale,Matrix3x2.CreateScale,Vector3.Reflect,Vector2.Reflect,Quaternion.Lerp, and variousMatrix4x4billboard/shadow/decompose helpers.A regression test covering register/memory sources, chained inserts, and wide (
Vector256) sources across all element indices is included.Note
This PR was authored with the assistance of GitHub Copilot.