Skip to content

JIT: Fold insertps chains that source a scalar extracted from another vector#130422

Merged
tannergooding merged 2 commits into
dotnet:mainfrom
tannergooding:tannergooding-cuddly-fishstick
Jul 13, 2026
Merged

JIT: Fold insertps chains that source a scalar extracted from another vector#130422
tannergooding merged 2 commits into
dotnet:mainfrom
tannergooding:tannergooding-cuddly-fishstick

Conversation

@tannergooding

Copy link
Copy Markdown
Member

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.

… 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>
Copilot AI review requested due to automatic review settings July 9, 2026 17:47
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 9, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 collapse Insert(..., CreateScalarUnsafe(GetElement(...)), ...) into a single insertps with updated count_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

Comment thread src/coreclr/jit/lowerxarch.cpp
Comment thread src/tests/JIT/opt/Vectorization/InsertScalarFromVector.cs
Copilot AI review requested due to automatic review settings July 9, 2026 18:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new

@tannergooding

Copy link
Copy Markdown
Member Author

@MihuBot -nuget

@tannergooding

Copy link
Copy Markdown
Member Author

CC. @EgorBo, @dotnet/jit-contrib.

Simple improvement to the insertps folding logic in lowering that removes a known pessimization. Gets -1218 bytes of codegen, namely in ImageSharp and BepuPhysics.

@tannergooding
tannergooding requested a review from EgorBo July 10, 2026 14:55
@tannergooding
tannergooding merged commit b40c320 into dotnet:main Jul 13, 2026
143 checks passed
@tannergooding
tannergooding deleted the tannergooding-cuddly-fishstick branch July 13, 2026 13:23
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 14, 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants