<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/94463>94463</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            generic `@llvm.ssub.sat` optimizes less well than target-specific `@llvm.aarch64.neon.sqsub`
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            new issue
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          folkertdev
      </td>
    </tr>
</table>

<pre>
    The generic `@llvm.ssub.sat.v2i64` intrinsic optimizes less well than the target-specific `@llvm.aarch64.neon.sqsub.v2i64` intrinsic. 

This godbolt shows the issue https://godbolt.org/z/4qEe3xM9v

We see two functions that use these two saturating subtractions, but are otherwise the same. they generate very similar initial LLVM IR, except that the generic function is called in a slightly different way (requiring some extra `alloca`s).

```llvm
define void @specific(ptr dead_on_unwind noalias nocapture noundef writable sret([16 x i8]) align 16 dereferenceable(16) %_0, ptr noalias nocapture noundef align 16 dereferenceable(16) %a, ptr noalias nocapture noundef align 16 dereferenceable(16) %b, ptr noalias nocapture noundef align 16 dereferenceable(16) %c) unnamed_addr {
start:
  %0 = alloca [16 x i8], align 16
  %1 = alloca [16 x i8], align 16
  %2 = alloca [16 x i8], align 16
  %3 = alloca [16 x i8], align 16
  %4 = alloca [16 x i8], align 16
  call void @llvm.lifetime.start.p0(i64 16, ptr %4)
  %5 = load <4 x i32>, ptr %b, align 16
  store <4 x i32> %5, ptr %3, align 16
  %6 = load <4 x i32>, ptr %c, align 16
  store <4 x i32> %6, ptr %2, align 16
  call void @core::core_arch::aarch64::neon::generated::vqdmull_high_laneq_s32::h66aa645ca0aefe90(ptr noalias nocapture noundef sret([16 x i8]) align 16 dereferenceable(16) %4, ptr noalias nocapture noundef align 16 dereferenceable(16) %3, ptr noalias nocapture noundef align 16 dereferenceable(16) %2)
  %_4 = load <2 x i64>, ptr %4, align 16
  call void @llvm.lifetime.end.p0(i64 16, ptr %4)
  %7 = load <2 x i64>, ptr %a, align 16
  store <2 x i64> %7, ptr %1, align 16
  store <2 x i64> %_4, ptr %0, align 16
  ; after InlinerPass this call becomes a call to `@llvm.aarch64.neon.sqsub.v2i64` 
  call void @core::core_arch::arm_shared::neon::generated::vqsubq_s64::h1887dd6c0650937c(ptr noalias nocapture noundef sret([16 x i8]) align 16 dereferenceable(16) %_0, ptr noalias nocapture noundef align 16 dereferenceable(16) %1, ptr noalias nocapture noundef align 16 dereferenceable(16) %0)
  ret void
}

define void @generic(ptr dead_on_unwind noalias nocapture noundef writable sret([16 x i8]) align 16 dereferenceable(16) %_0, ptr noalias nocapture noundef align 16 dereferenceable(16) %a, ptr noalias nocapture noundef align 16 dereferenceable(16) %b, ptr noalias nocapture noundef align 16 dereferenceable(16) %c) unnamed_addr {
start:
  %0 = alloca [16 x i8], align 16
  %1 = alloca [16 x i8], align 16
  %2 = alloca [16 x i8], align 16
  call void @llvm.lifetime.start.p0(i64 16, ptr %2)
  %3 = load <4 x i32>, ptr %b, align 16
  store <4 x i32> %3, ptr %1, align 16
  %4 = load <4 x i32>, ptr %c, align 16
  store <4 x i32> %4, ptr %0, align 16
  call void @core::core_arch::aarch64::neon::generated::vqdmull_high_laneq_s32::h66aa645ca0aefe90(ptr noalias nocapture noundef sret([16 x i8]) align 16 dereferenceable(16) %2, ptr noalias nocapture noundef align 16 dereferenceable(16) %1, ptr noalias nocapture noundef align 16 dereferenceable(16) %0)
  %_4 = load <2 x i64>, ptr %2, align 16
  call void @llvm.lifetime.end.p0(i64 16, ptr %2)
  %5 = load <2 x i64>, ptr %a, align 16
  %6 = call <2 x i64> @llvm.ssub.sat.v2i64(<2 x i64> %5, <2 x i64> %_4)
  store <2 x i64> %6, ptr %_0, align 16
  ret void
}
```

As a user, I expect the generic variant to eventually be lowered to the specific one.

When the intrinsics are used on their own, this is in fact the case: https://godbolt.org/z/ErjETo3bh. Both functions emit the `sqsub` instruction. This logic appears to be [implemented here](https://github.com/llvm/llvm-project/blob/806ed2625e9569bdb55a13a2b1f9c3e71293fda6/llvm/lib/Target/AArch64/AArch64ISelLowering.cpp#L20840-L20842).

But in my example, there is an optimization that `@llvm.aarch64.neon.sqsub.v2i64` participates in, but the generic `@llvm.ssub.sat.v2i64` does not. 

```asm
specific:
        ldr     q0, [x1]
        ldr q1, [x2]
        ldr     q2, [x0]
        sqdmlsl2        v2.2d, v0.4s, v1.s[1]
        str     q2, [x8]
        ret

generic:
 ldr     q0, [x1]
        ldr     q1, [x2]
        sqdmull2 v0.2d, v0.4s, v1.s[1]
        ldr     q1, [x0]
        sqsub   v0.2d, v1.2d, v0.2d
        str     q0, [x8]
        ret
```

This is unexpected, and seems to imply that many optimizations are missed when using the generic SIMD intrinsics (at least for Neon). My intuition is that the lowering of the generic to the specific instruction occurs too late. It should occur earlier so that it can participate in backend-specific optimizations. 

- [discussion in the rust zullip](https://rust-lang.zulipchat.com/#narrow/stream/187780-t-compiler.2Fwg-llvm/topic/simd.20fails.20to.20specialize.20for.20target)
- PR where this problem was spotted https://github.com/rust-lang/stdarch/pull/1575
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzsWV9v4zgO_zTqC1HDlv_EfshDu50ABWYOi93B7WMgW3SsXVlyJTlp59MfJCcZp38TbA84HLYIaisSSZEif6QYZq3YKMQlyW9JfnfFRtdps2y1_AuN47i9qjV_Wn7vEDao0IgGSBGTLJZy20fWjnVkmYu2VBQZKWIQyhmhrGhAD0704gdakGgt7FBKcB1T4DoEx8wG3bUdsBHtKU_GTNMVWaRQq8g-eAkvuUdA4jsS30z_v3fCwkbzWksHttM7G4QIa0eEzrnBkvSG0BWhq_2qSJsNoasfhK6yhy-YPn6rtnOOfyBYRHA7De2oGie08jyZg9GiZ26nScvcaJgTagN2rJ1h01JCf4F6dMAMgnYdmp2YyMCyHiP_9jTZkzmELZonsKIXkhkQSjjBJHz9-u9vcP-b54SPDQ5uEu9mB3HYGQgLDZMSOQgFDKwUm87JJ-CibdGgcrBjT0BoafBhFCbsVvcI-OgM87ZnUuqGkSK2hFbR3BD-YMLHn830FcdWKIStFhxIFh_OkNBycAY4Mr7Waj2qnVAclGZSMAtKN2xwo0FQelQcW9gZ4VgtEaxBR2hJ8tukgEcQJcnvCK2ASbFRkBTA0WDQo0FPQGiZFH4Bofk69gbyct8WdA4f9jls6s9h0_jnqBTrka8Z5wbI4nYyvnXMOO_NYQR-dQwkvYPpCOGZFX85ypsRJJcS0EsJ0ksJsosIvLMf_S-AhhQtOtFjFOwTDTGhpSgyT7M_ES-E0GomMw8ypWYcSPpL5gWmlKRfZhT1a9Kt0wZPSQK7GV36hprFxyKbC0TOlaMfGqrRBr3npDf-be1hdhruEXcaeNid3g74xKfh9oH3o5TrTmy6tWQKH9bW78TPdUXBWJHlDYsZtljFezB4Owz-TtBnnxNl6eewoadetc5Ozph65bxtv5w64kVOjYqf59KLj2Wzd_3rJ0lgN6NLLqBbZzPC-NVYSG-BtQ4N3CspFJpfmfUJdp_JoMZG92iBTUOnz60QLvR8069tx8zBx99xfjvWD2t7iJIuKcsF50UTF3lcpYvmv-rwn5Xlks9hE8-czqALZt5XC4u7efHwrFLY1y3_FAr_FAp_J40_A9z0c9N4-hHmHauVT0vjH6Ll_2cap_-zoHZmGv-45Do3jdP3KtPz0_ixwgybeJaWX7-y0_JF9g6F7Ks5vfog789VWr_qyW-li8Mtc549bnz6Hy0az-ge8HHA5vQCvGVGMOV8eYBbVG5kUj5BjSD1Dg1yPxEu3Yc2g1Z4crv9o8OpH3FsLNhwZx8tctBhShjQO-W3EKoT_1HQsv1OGmZ9MH7YZPhi_vzyXad1F8Gtdt2sqYC9mFj527evMqY-h3VmDCsiCN0NqTeiATYMyIz1etXoAVb0g8QelUMOHRoM4Vc-241w3VhHje4JXYVr_PS4Hoz-ExtH6KqWuiZ0VcYFclrQHKu8qGpe5zlLUkbrpK2aFBcJrdKWs2LGR3i676GbQ-jq5maCoOPb_e8ov_rDEGoTNcNAaPqVxmUWX4cHfd5uuB2dt2__BPjIvG6T4dGgtzxTh64SC32P0BE5szAcmHGiEQNz6I_w0KFx5za2uEYPLe6083R0XGb33ZFjO-Rn6p3-JDfh-RACg-S3j4k_rRdrHpLDPH11PvCghzXxizX2gffSSnoYb2lEuV--jaMsdKa2SWQ9Yr8kdc_Zly_WeMCfGeBQ0x21PVfNsOYdVe2Up6jf9rn7f8n3NfPYsfZmObJNfvKn_A2DxOcY5DUQ-74HjVFN-IVBFFMcLGIfItnH8NPkyz1TTycuPsFRL6wHpJ1Hq9EKtTlx29_vv93NAYzQkjmQyKyDVhv4ly8FaBXBtye_bBSHnuGxoSj3EQq6PeH8HD5nsAS6acaARBokcxjBfWi9jpJPU4DMSIEGrJ4ECQcNU_M49JFes-YvVPxnJ_hE-9Ngu_YnwIVtRmuDChN2m9E6-DFKKYbX4M9PX0umNtGPUYqh6ZjbYyGhqWLG6B2hK-sMMv9lUi4WZXztrhvdD0Kiiehqt7ne453Tg7_ArKzoeUTjlglpIxo7HdGpEcqk-IF-Rhv__R4Yq4MCv_7mT9HglEwGo2uJPeyYBTtoF1D8bew-ahL2y0PlR1fDKKXfd77Ir_gy5VVasStcJotkkZdVleZX3ZKVRVbUSdW0eZowXhYtZXVKyypfsKpdxFdiSWOaxUWcJzQp8yxq8wyxXGCbxjytm5pkMfZMyCjAozabq9BdX1ZZVqRXktUobfgNgVKFu6n1TqgP6yuzDNmmHjfWw6uwzv7k4oSTuHwHgT32vv1TwiU_I5AivhqNXF6cHYMyltDVpOx2Sf8TAAD__01Mmwg">