[llvm] [X86] Add llvm.x86.movnt, an opaque non-temporal store intrinsic (PR #228875)

via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 4 05:24:04 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-x86

@llvm/pr-subscribers-llvm-transforms

Author: Jarrod Petz (jlpetz)

<details>
<summary>Changes</summary>

This adds `llvm.x86.movnt`, a non-temporal store that the optimizer treats as an opaque memory operation instead of an ordinary store. It is overloaded on the stored type and has no memory attributes, so to IR passes it is a call that may access any memory. The X86 backend lowers it to a volatile non-temporal store, which selects exactly the instructions a `store ... !nontemporal` selects today (`MOVNTI`, `MOVNTPS`, `MOVNTPD`, `MOVNTDQ` at every width; identical codegen with SSE2, AVX, AVX-512F and AVX-512VL in the new test) and keeps the backend from merging, eliminating or reordering it.

Nothing about existing `!nontemporal` stores changes, and Clang is unaffected: its `_mm_stream_*` intrinsics and `__builtin_nontemporal_store` keep producing `!nontemporal` stores.

## Why

How `!nontemporal` stores interact with LLVM's memory model and optimizations is not specified (#<!-- -->64521). x86 `MOVNT*` stores are weakly ordered, but the optimizer treats a `store !nontemporal` as an ordinary store. With `opt -O2`, compared with the same code using `llvm.x86.movnt`:

| | `store !nontemporal` | `llvm.x86.movnt` |
|---|---|---|
| store overwritten by a later store | deleted | kept |
| store followed by a load | value forwarded, load removed | load kept |
| loop of stores to one address | collapsed into one plain store after the loop; the non-temporal hint is gone | loop kept |

These are all legal under single-threaded semantics, so they aren't miscompiles. They show that `!nontemporal` is optimized as an ordinary store, which is exactly the part #<!-- -->178264 documents as "not yet fully explored and verified".

Frontends that need a defined operation therefore can't use `!nontemporal` today. Rust is one: since 2024, Rust's `std::arch` implements every `_mm*_stream_*` intrinsic with inline assembly (rust-lang/stdarch#<!-- -->1541), and `core::intrinsics::nontemporal_store` emits a plain store on x86 (rust-lang/rust#<!-- -->128149), following rust-lang/rust#<!-- -->114582.

Inline assembly is correct, but it costs code quality that a target intrinsic doesn't. LLVM won't runtime-unroll a loop around it, each store needs its own address register (no displacement folding), and the asm hard-codes one encoding, so `std::arch`'s `_mm_stream_si128` is the legacy-SSE `movntdq` even inside AVX code. An opaque intrinsic has the same "may access any memory" semantics as that inline asm, so it is exactly as conservative about ordering, while loops around it unroll, addresses fold, and the encoding follows the subtarget.

This is meant as the defined interim form, not a replacement for `!nontemporal`.

## Precedent: LLVM already exposes the other weakly ordered x86 operations this way

Each of these is an `Intrinsic<..., []>` with no memory attributes, with the ordering obligation (a fence before other threads rely on the effect) left to the caller:

- `MOVNTQ`, the MMX non-temporal store: `llvm.x86.mmx.movnt.dq`
- `MOVDIRI`, 4/8-byte direct stores: `llvm.x86.directstore32`, `llvm.x86.directstore64`
- `MOVDIR64B`, 64-byte direct store: `llvm.x86.movdir64b`
- `CLFLUSHOPT` and `CLWB`, weakly ordered cache-line flush and write-back: `llvm.x86.clflushopt`, `llvm.x86.clwb`

`llvm.x86.movnt` gives the SSE/AVX/AVX-512 non-temporal stores the same treatment.

## History

LLVM had opaque intrinsics for these stores, and removed them as redundant once `!nontemporal` existed, before the semantics problem was known:

- 2011-05: db0996c8221f "Replace the "movnt" intrinsics with a native store + nontemporal metadata bit" (SSE `movnt.ps/dq/pd/i`), one of a series moving SSE builtins to native IR for better code generation.
- 2012-05: 7daf8976785e "Remove 256-bit AVX non-temporal store intrinsics. Similar was previously done for 128-bit."
- 2016-06: f4b2af1b9fca "[X86][SSE4A] Autoupgrade and remove MOVNTSD/MOVNTSS intrinsics"
- 2016-07: 70610cf7b630 "[X86] Remove and autoupgrade 512-bit non-temporal store intrinsics."

The long-term fix, specifying `!nontemporal`'s semantics and adding a non-temporal fence to the IR, has been waiting for over ten years:

- 2016-01: [RFC: non-temporal fencing in LLVM IR](https://lists.llvm.org/pipermail/llvm-dev/2016-January/093912.html) (JF Bastien, with Philip Reames and Hans Boehm), five months before the last opaque intrinsics were removed.
- 2020-04: [Nontemporal memory accesses and fences](https://lists.llvm.org/pipermail/llvm-dev/2020-April/141278.html), noting no one had pushed the RFC further.
- 2023-08: #<!-- -->64521.
- 2026-01: #<!-- -->178264, documenting the current state; still open.

Defining those semantics needs real memory-model work, so this PR doesn't attempt it. It gives frontends a sound option in the meantime, and they can move back to `!nontemporal` once its semantics are specified.

## What this enables in Rust

With `llvm.x86.movnt`, Rust's `std::arch` could implement `_mm_stream_si32/si64/si128/ps/pd`, `_mm256_stream_*` and `_mm512_stream_*` without inline assembly, keeping their documented `_mm_sfence` requirement. Non-temporal stores in Rust would be ordinary loops again:

```rust
use core::arch::x86_64::*;

#[target_feature(enable = "avx2")]
pub unsafe fn fill(dst: &mut [__m256i], v: __m256i) {
    for d in dst {
        unsafe { _mm256_stream_si256(d, v) };
    }
    _mm_sfence();
}
```

Before, with today's `std::arch` (inline asm), one store per iteration:

```asm
.LBB0_2:
        vmovntdq ymmword ptr [rcx], ymm0
        add     rcx, 32
        add     rdx, -32
        jne     .LBB0_2
```

After, the same Rust IR with the asm replaced by `llvm.x86.movnt.v4i64` (what `std::arch` would emit), through this patch's `opt -O2` and `llc`: unrolled 8x with folded displacements:

```asm
.LBB0_6:
        vmovntps ymmword ptr [rax], ymm0
        vmovntps ymmword ptr [rax + 32], ymm0
        vmovntps ymmword ptr [rax + 64], ymm0
        vmovntps ymmword ptr [rax + 96], ymm0
        vmovntps ymmword ptr [rax + 128], ymm0
        vmovntps ymmword ptr [rax + 160], ymm0
        vmovntps ymmword ptr [rax + 192], ymm0
        vmovntps ymmword ptr [rax + 224], ymm0
        add     rax, 256
        cmp     rax, rcx
        jne     .LBB0_6
```

Today, getting the second loop from Rust means hand-unrolling and writing your own multi-store `asm!` block per register width.

That matters for [fearless_simd](https://github.com/linebender/fearless_simd), Rust's safe portable SIMD library. Its maintainers suggested prototyping non-temporal stores in an extension crate first. In [plumb](https://github.com/jlpetz/plumb) the store and its `SFENCE` obligation are wrapped in a closure scope, so they're safe to use:

```rust
plumb_lines::nontemporal(simd, dst, |w| w.fill_with(|_| v)); // SFENCE when the scope ends
```

Today that wrapper has to carry hand-written multi-store `asm!` blocks per register width to get good code. With `llvm.x86.movnt` under `std::arch`, it would be plain Rust over the standard intrinsics, at every vector width fearless_simd supports, and safe non-temporal stores could move into fearless_simd itself.

## Testing

- `CodeGen/X86/movnt-intrinsic.ll`: `i32`, `i64`, and the `f32`, `f64` and `i64` vectors at 128, 256 and 512 bits, with SSE2, AVX, AVX-512F and AVX-512VL. This includes wide vectors split on narrower targets and a folded-displacement case. Output is identical to the same functions written with `store !nontemporal`.
- `Transforms/DeadStoreElimination/x86-movnt-intrinsic.ll`: DSE and GVN don't treat the call as an ordinary store (an overwritten call stays, and its value isn't forwarded to a later load).
- The existing X86 non-temporal and direct-store tests and the DeadStoreElimination tests still pass.

cc @<!-- -->bwendling: the 2011 conversion (db0996c8221f) cites rdar://8460511. In case you recall, was there a correctness reason for replacing the intrinsics, or was it about code generation?

Assisted-by: Claude Opus 5.5


---
Full diff: https://github.com/llvm/llvm-project/pull/228875.diff


5 Files Affected:

- (modified) llvm/docs/ReleaseNotes.md (+3) 
- (modified) llvm/include/llvm/IR/IntrinsicsX86.td (+13) 
- (modified) llvm/lib/Target/X86/X86ISelLowering.cpp (+27) 
- (added) llvm/test/CodeGen/X86/movnt-intrinsic.ll (+220) 
- (added) llvm/test/Transforms/DeadStoreElimination/x86-movnt-intrinsic.ll (+27) 


``````````diff
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 9a5dbbcdb6609..53d53e72699fa 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -302,6 +302,9 @@ Makes programs 10x faster by doing Special New Thing.
 
 * Added assembler and code generation support for the `AVX10_V2_AUX`
   instruction set.
+* Added the `llvm.x86.movnt` intrinsic: a non-temporal store (`MOVNTI`,
+  `MOVNTPS`, `MOVNTPD` or `MOVNTDQ`) that is not optimized as an ordinary
+  store, like the existing `MOVNTQ`, `MOVDIRI` and `MOVDIR64B` intrinsics.
 
 ### Changes to the OCaml bindings
 
diff --git a/llvm/include/llvm/IR/IntrinsicsX86.td b/llvm/include/llvm/IR/IntrinsicsX86.td
index 3617008d2ee8e..ce4ebefc58222 100644
--- a/llvm/include/llvm/IR/IntrinsicsX86.td
+++ b/llvm/include/llvm/IR/IntrinsicsX86.td
@@ -235,6 +235,19 @@ let TargetPrefix = "x86" in {  // All intrinsics start with "llvm.x86.".
 let TargetPrefix = "x86" in {  // All intrinsics start with "llvm.x86.".
   def int_x86_sse_sfence : ClangBuiltin<"__builtin_ia32_sfence">,
               Intrinsic<[], [], []>;
+
+  // Non-temporal store of the second operand to the pointer, as an opaque
+  // operation. It selects the same instructions (MOVNTI, MOVNTPS, MOVNTPD,
+  // MOVNTDQ) as a store with !nontemporal metadata, but like the MOVNTQ,
+  // MOVDIRI and MOVDIR64B intrinsics it has no memory attributes: the
+  // optimizer treats it as a call that may access any memory, not as an
+  // ordinary store. Non-temporal stores are weakly ordered, and how
+  // !nontemporal stores interact with LLVM's memory model and optimizations
+  // is not specified (see #64521), so this gives frontends a defined
+  // alternative to inline assembly. Ordering it with other threads still
+  // needs an SFENCE. A vector store requires the pointer to be aligned to the
+  // vector's size.
+  def int_x86_movnt : Intrinsic<[], [llvm_ptr_ty, llvm_any_ty], []>;
 }
 
 // Control register.
diff --git a/llvm/lib/Target/X86/X86ISelLowering.cpp b/llvm/lib/Target/X86/X86ISelLowering.cpp
index c29b09ae846bb..4196f92db224f 100644
--- a/llvm/lib/Target/X86/X86ISelLowering.cpp
+++ b/llvm/lib/Target/X86/X86ISelLowering.cpp
@@ -28426,6 +28426,25 @@ bool X86::isExtendedSwiftAsyncFrameSupported(const X86Subtarget &Subtarget,
   return !MF.getTarget().getMCAsmInfo().usesWindowsCFI();
 }
 
+// Lower llvm.x86.movnt to a non-temporal store, which selects the same MOVNT
+// instructions as a !nontemporal store. The store is also volatile, so the
+// backend doesn't merge, eliminate or reorder it with other memory accesses,
+// matching the intrinsic's opaque IR semantics.
+static SDValue lowerX86MOVNT(SDNode *N, SelectionDAG &DAG) {
+  SDLoc DL(N);
+  SDValue Chain = N->getOperand(0);
+  SDValue Ptr = N->getOperand(2);
+  SDValue Val = N->getOperand(3);
+  EVT VT = Val.getValueType();
+  // MOVNTI has no alignment requirement; the vector forms require the pointer
+  // to be aligned to the vector's size, as documented on the intrinsic.
+  Align Alignment =
+      VT.isVector() ? Align(VT.getStoreSize().getFixedValue()) : Align(1);
+  return DAG.getStore(Chain, DL, Val, Ptr, MachinePointerInfo(), Alignment,
+                      MachineMemOperand::MOVolatile |
+                          MachineMemOperand::MONonTemporal);
+}
+
 static SDValue LowerINTRINSIC_W_CHAIN(SDValue Op, const X86Subtarget &Subtarget,
                                       SelectionDAG &DAG) {
   unsigned IntNo = Op.getConstantOperandVal(1);
@@ -28433,6 +28452,9 @@ static SDValue LowerINTRINSIC_W_CHAIN(SDValue Op, const X86Subtarget &Subtarget,
   if (!IntrData) {
     switch (IntNo) {
 
+    case Intrinsic::x86_movnt:
+      return lowerX86MOVNT(Op.getNode(), DAG);
+
     case Intrinsic::swift_async_context_addr: {
       SDLoc dl(Op);
       auto &MF = DAG.getMachineFunction();
@@ -64005,6 +64027,11 @@ static SDValue combineINTRINSIC_VOID(SDNode *N, SelectionDAG &DAG,
   if (IntrData && IntrData->Type == INTR_TYPE_CAST_MMX)
     return FixupMMXIntrinsicTypes(N, DAG);
 
+  // Before type legalization, so that a vector wider than the target's
+  // registers is split like any other store.
+  if (IntNo == Intrinsic::x86_movnt)
+    return lowerX86MOVNT(N, DAG);
+
   return SDValue();
 }
 
diff --git a/llvm/test/CodeGen/X86/movnt-intrinsic.ll b/llvm/test/CodeGen/X86/movnt-intrinsic.ll
new file mode 100644
index 0000000000000..ee86a0f66a789
--- /dev/null
+++ b/llvm/test/CodeGen/X86/movnt-intrinsic.ll
@@ -0,0 +1,220 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+sse2 | FileCheck %s --check-prefixes=SSE
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+avx | FileCheck %s --check-prefixes=AVX,AVX1
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+avx512f | FileCheck %s --check-prefixes=AVX,AVX512
+; RUN: llc < %s -mtriple=x86_64-unknown-unknown -mattr=+avx512vl | FileCheck %s --check-prefixes=AVX,AVX512
+
+; llvm.x86.movnt selects the same non-temporal stores as !nontemporal stores.
+
+define void @movnt_i32(ptr %p, i32 %v) {
+; SSE-LABEL: movnt_i32:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntil %esi, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_i32:
+; AVX:       # %bb.0:
+; AVX-NEXT:    movntil %esi, (%rdi)
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.i32(ptr %p, i32 %v)
+  ret void
+}
+
+define void @movnt_i64(ptr %p, i64 %v) {
+; SSE-LABEL: movnt_i64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntiq %rsi, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_i64:
+; AVX:       # %bb.0:
+; AVX-NEXT:    movntiq %rsi, (%rdi)
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.i64(ptr %p, i64 %v)
+  ret void
+}
+
+define void @movnt_v4f32(ptr %p, <4 x float> %v) {
+; SSE-LABEL: movnt_v4f32:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v4f32:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %xmm0, (%rdi)
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v4f32(ptr %p, <4 x float> %v)
+  ret void
+}
+
+define void @movnt_v2f64(ptr %p, <2 x double> %v) {
+; SSE-LABEL: movnt_v2f64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v2f64:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %xmm0, (%rdi)
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v2f64(ptr %p, <2 x double> %v)
+  ret void
+}
+
+define void @movnt_v2i64(ptr %p, <2 x i64> %v) {
+; SSE-LABEL: movnt_v2i64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v2i64:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %xmm0, (%rdi)
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v2i64(ptr %p, <2 x i64> %v)
+  ret void
+}
+
+define void @movnt_v8f32(ptr %p, <8 x float> %v) {
+; SSE-LABEL: movnt_v8f32:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v8f32:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX-NEXT:    vzeroupper
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v8f32(ptr %p, <8 x float> %v)
+  ret void
+}
+
+define void @movnt_v4f64(ptr %p, <4 x double> %v) {
+; SSE-LABEL: movnt_v4f64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v4f64:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX-NEXT:    vzeroupper
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v4f64(ptr %p, <4 x double> %v)
+  ret void
+}
+
+define void @movnt_v4i64(ptr %p, <4 x i64> %v) {
+; SSE-LABEL: movnt_v4i64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v4i64:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX-NEXT:    vzeroupper
+; AVX-NEXT:    retq
+  call void @llvm.x86.movnt.v4i64(ptr %p, <4 x i64> %v)
+  ret void
+}
+
+define void @movnt_v16f32(ptr %p, <16 x float> %v) {
+; SSE-LABEL: movnt_v16f32:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm3, 48(%rdi)
+; SSE-NEXT:    movntps %xmm2, 32(%rdi)
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX1-LABEL: movnt_v16f32:
+; AVX1:       # %bb.0:
+; AVX1-NEXT:    vmovntps %ymm1, 32(%rdi)
+; AVX1-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX1-NEXT:    vzeroupper
+; AVX1-NEXT:    retq
+;
+; AVX512-LABEL: movnt_v16f32:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vmovntps %zmm0, (%rdi)
+; AVX512-NEXT:    vzeroupper
+; AVX512-NEXT:    retq
+  call void @llvm.x86.movnt.v16f32(ptr %p, <16 x float> %v)
+  ret void
+}
+
+define void @movnt_v8f64(ptr %p, <8 x double> %v) {
+; SSE-LABEL: movnt_v8f64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm3, 48(%rdi)
+; SSE-NEXT:    movntps %xmm2, 32(%rdi)
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX1-LABEL: movnt_v8f64:
+; AVX1:       # %bb.0:
+; AVX1-NEXT:    vmovntps %ymm1, 32(%rdi)
+; AVX1-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX1-NEXT:    vzeroupper
+; AVX1-NEXT:    retq
+;
+; AVX512-LABEL: movnt_v8f64:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vmovntps %zmm0, (%rdi)
+; AVX512-NEXT:    vzeroupper
+; AVX512-NEXT:    retq
+  call void @llvm.x86.movnt.v8f64(ptr %p, <8 x double> %v)
+  ret void
+}
+
+define void @movnt_v8i64(ptr %p, <8 x i64> %v) {
+; SSE-LABEL: movnt_v8i64:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm3, 48(%rdi)
+; SSE-NEXT:    movntps %xmm2, 32(%rdi)
+; SSE-NEXT:    movntps %xmm1, 16(%rdi)
+; SSE-NEXT:    movntps %xmm0, (%rdi)
+; SSE-NEXT:    retq
+;
+; AVX1-LABEL: movnt_v8i64:
+; AVX1:       # %bb.0:
+; AVX1-NEXT:    vmovntps %ymm1, 32(%rdi)
+; AVX1-NEXT:    vmovntps %ymm0, (%rdi)
+; AVX1-NEXT:    vzeroupper
+; AVX1-NEXT:    retq
+;
+; AVX512-LABEL: movnt_v8i64:
+; AVX512:       # %bb.0:
+; AVX512-NEXT:    vmovntps %zmm0, (%rdi)
+; AVX512-NEXT:    vzeroupper
+; AVX512-NEXT:    retq
+  call void @llvm.x86.movnt.v8i64(ptr %p, <8 x i64> %v)
+  ret void
+}
+
+; The address is folded into the store, as for the MOVDIRI/MOVDIR64B intrinsics.
+define void @movnt_v2i64_offsets(ptr %p, <2 x i64> %a, <2 x i64> %b) {
+; SSE-LABEL: movnt_v2i64_offsets:
+; SSE:       # %bb.0:
+; SSE-NEXT:    movntps %xmm0, 16(%rdi)
+; SSE-NEXT:    movntps %xmm1, 48(%rdi)
+; SSE-NEXT:    retq
+;
+; AVX-LABEL: movnt_v2i64_offsets:
+; AVX:       # %bb.0:
+; AVX-NEXT:    vmovntps %xmm0, 16(%rdi)
+; AVX-NEXT:    vmovntps %xmm1, 48(%rdi)
+; AVX-NEXT:    retq
+  %p1 = getelementptr inbounds <2 x i64>, ptr %p, i64 1
+  %p3 = getelementptr inbounds <2 x i64>, ptr %p, i64 3
+  call void @llvm.x86.movnt.v2i64(ptr %p1, <2 x i64> %a)
+  call void @llvm.x86.movnt.v2i64(ptr %p3, <2 x i64> %b)
+  ret void
+}
diff --git a/llvm/test/Transforms/DeadStoreElimination/x86-movnt-intrinsic.ll b/llvm/test/Transforms/DeadStoreElimination/x86-movnt-intrinsic.ll
new file mode 100644
index 0000000000000..a036daf22ce5a
--- /dev/null
+++ b/llvm/test/Transforms/DeadStoreElimination/x86-movnt-intrinsic.ll
@@ -0,0 +1,27 @@
+; RUN: opt -S -passes=dse,gvn < %s | FileCheck %s
+
+; llvm.x86.movnt has no memory attributes, so it is not optimized as an
+; ordinary store: an overwritten call is not removed, and its value is not
+; forwarded to a later load.
+
+define void @overwritten(ptr %p, <2 x i64> %a, <2 x i64> %b) {
+; CHECK-LABEL: @overwritten(
+; CHECK-NEXT:    call void @llvm.x86.movnt.v2i64(ptr [[P:%.*]], <2 x i64> [[A:%.*]])
+; CHECK-NEXT:    store <2 x i64> [[B:%.*]], ptr [[P]], align 16
+; CHECK-NEXT:    ret void
+;
+  call void @llvm.x86.movnt.v2i64(ptr %p, <2 x i64> %a)
+  store <2 x i64> %b, ptr %p, align 16
+  ret void
+}
+
+define i64 @not_forwarded(ptr %p) {
+; CHECK-LABEL: @not_forwarded(
+; CHECK-NEXT:    call void @llvm.x86.movnt.i64(ptr [[P:%.*]], i64 5)
+; CHECK-NEXT:    [[V:%.*]] = load i64, ptr [[P]], align 8
+; CHECK-NEXT:    ret i64 [[V]]
+;
+  call void @llvm.x86.movnt.i64(ptr %p, i64 5)
+  %v = load i64, ptr %p, align 8
+  ret i64 %v
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/228875


More information about the llvm-commits mailing list