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

    <tr>
        <th>Summary</th>
        <td>
            [RISCV] Difference in splat handling between intrinsics and generic IR
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            backend:RISC-V,
            performance
      </td>
    </tr>

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

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

<pre>
    This is inspired by looking into the test changes in https://reviews.llvm.org/D130895.  On that review, I had raised a concern about the interaction of fixed length vectors and scalable vectors.  I sat down today to investigate alternate approaches to that patch, and came across some surprising results.  I decided to file this as a bug as the phabricator submission form doesn't encourage long detailed writing.  :)

First, when I wrote the analogous tests from the review using plain IR, I did not get the result I expected.  (See ec35a2f)  Instead of replicating the reported issue, we instead seem to consistently pick the narrower VL for the splat - at least on many common idiomatic cases.

The following reduced test shows the difference between plain IR and intrinsics:

```
 NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc -O3 -mtriple=riscv64 -mattr=+v < %s | FileCheck %s

define void @fixed_length_extract_vector(ptr %p, i32 %v) {
; CHECK-LABEL: fixed_length_extract_vector:
; CHECK:       # %bb.0:
; CHECK-NEXT:    vsetivli zero, 4, e32, m1, ta, mu
; CHECK-NEXT:    vmv.v.x v8, a1
; CHECK-NEXT:    vse32.v v8, (a0)
; CHECK-NEXT:    ret
  %elt.head = insertelement <vscale x 2 x i32> poison, i32 %v, i32 0
  %splat = shufflevector <vscale x 2 x i32> %elt.head, <vscale x 2 x i32> poison, <vscale x 2 x i32> zeroinitializer

  %fv = call <4 x i32> @llvm.vector.extract.v4i32.nxv2i32(<vscale x 2 x i32> %splat, i64 0)
  store <4 x i32> %fv, ptr %p
  ret void
}

declare <4 x i32> @llvm.vector.extract.v4i32.nxv2i32(<vscale x 2 x i32> %vec, i64 %idx)

declare <vscale x 2 x i32> @llvm.vector.insert.nxv2i32.v4i32(<vscale x 2 x i32>, <4 x i32> %vec, i64 %idx)

define void @vse_niave(ptr %p, i32 %v) {
; CHECK-LABEL: vse_niave:
; CHECK:       # %bb.0:
; CHECK-NEXT:    vsetvli a2, zero, e32, m1, ta, mu
; CHECK-NEXT:    vmv.v.x v8, a1
; CHECK-NEXT:    vsetivli zero, 4, e32, m1, ta, mu
; CHECK-NEXT:    vse32.v v8, (a0)
; CHECK-NEXT:    ret
  %elt.head = insertelement <vscale x 2 x i32> poison, i32 %v, i32 0
  %splat = shufflevector <vscale x 2 x i32> %elt.head, <vscale x 2 x i32> poison, <vscale x 2 x i32> zeroinitializer

  tail call void @llvm.riscv.vse.nxv2i32.i64(<vscale x 2 x i32> %splat, ptr %p, i64 4)
  ret void
}
```

The key reason for the difference can be seen in the following debug output:
```

Initial selection DAG: %bb.0 'fixed_length_extract_vector:'
SelectionDAG has 15 nodes:
  t0: ch = EntryToken
    t4: i64,ch = CopyFromReg t0, Register:i64 %1
  t5: i32 = truncate t4
  t7: i32 = Constant<0>
  t9: nxv2i32 = insert_vector_elt undef:nxv2i32, t5, Constant:i64<0>
        t10: nxv2i32 = splat_vector t5
      t11: v4i32 = extract_subvector t10, Constant:i64<0>
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t13: ch = store<(store (s128) into %ir.p)> t0, t11, t2, undef:i64
  t14: ch = RISCVISD::RET_FLAG t13


Optimized lowered selection DAG: %bb.0 'fixed_length_extract_vector:'
SelectionDAG has 10 nodes:
  t0: ch = EntryToken
          t4: i64,ch = CopyFromReg t0, Register:i64 %1
        t5: i32 = truncate t4
      t15: v4i32 = splat_vector t5
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t13: ch = store<(store (s128) into %ir.p)> t0, t15, t2, undef:i64
  t14: ch = RISCVISD::RET_FLAG t13
```

vs

```
Initial selection DAG: %bb.0 'vse_niave:'
SelectionDAG has 15 nodes:
  t0: ch = EntryToken
    t4: i64,ch = CopyFromReg t0, Register:i64 %1
  t5: i32 = truncate t4
  t7: i32 = Constant<0>
  t9: nxv2i32 = insert_vector_elt undef:nxv2i32, t5, Constant:i64<0>
      t10: nxv2i32 = splat_vector t5
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t13: ch = llvm.riscv.vse t0, TargetConstant:i64<7945>, t10, t2, Constant:i64<4>
  t14: ch = RISCVISD::RET_FLAG t13


Optimized lowered selection DAG: %bb.0 'vse_niave:'
SelectionDAG has 11 nodes:
  t0: ch = EntryToken
          t4: i64,ch = CopyFromReg t0, Register:i64 %1
        t5: i32 = truncate t4
      t10: nxv2i32 = splat_vector t5
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t13: ch = llvm.riscv.vse t0, TargetConstant:i64<7945>, t10, t2, Constant:i64<4>
  t14: ch = RISCVISD::RET_FLAG t13
```

The key thing to note is that we originally generate a nvx2i32 splat in both cases.  The difference is that the plain IR also gets a vector extract before a store of the fixed length type; the intrinsic case instead leaves the value typed as nxv2i32 all the way through.

This made me question what would happen if we added an extract equivalent pattern to the intrinsic.  Using an extract with a fixed length vse revealed we haven't implemented that intrinsic, but inverting it by using a fixed length splat and an insert does work.

```
define void @vse_insert(ptr %p, i32 %v) {
; CHECK-LABEL: vse_insert:
; CHECK:       # %bb.0:
; CHECK-NEXT:    vsetivli zero, 4, e32, m1, ta, mu
; CHECK-NEXT:    vmv.v.x v8, a1
; CHECK-NEXT:    vse32.v v8, (a0)
; CHECK-NEXT:    ret
  %elt.head = insertelement <4 x i32> poison, i32 %v, i32 0
  %splat = shufflevector <4 x i32> %elt.head, <4 x i32> poison, <4 x i32> zeroinitializer

  %scalable = call <vscale x 2 x i32> @llvm.vector.insert.nxv2i32.v4i32(<vscale x 2 x i32> undef, <4 x i32> %splat, i64 0)

  tail call void @llvm.riscv.vse.nxv2i32.i64(<vscale x 2 x i32> %scalable, ptr %p, i64 4)
  ret void
}
```

The patch which started my investigation works by rewriting the vmv.v.x after it has been inserted via legalization.  The insert variant works via generic combine before legalization.

```
Initial selection DAG: %bb.0 'vse_insert:'
SelectionDAG has 17 nodes:
  t0: ch = EntryToken
  t7: i32 = Constant<0>
              t4: i64,ch = CopyFromReg t0, Register:i64 %1
            t5: i32 = truncate t4
          t9: v4i32 = insert_vector_elt undef:v4i32, t5, Constant:i64<0>
        t10: v4i32 = vector_shuffle<0,0,0,0> t9, undef:v4i32
      t12: nxv2i32 = insert_subvector undef:nxv2i32, t10, Constant:i64<0>
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t15: ch = llvm.riscv.vse t0, TargetConstant:i64<7945>, t12, t2, Constant:i64<4>
  t16: ch = RISCVISD::RET_FLAG t15

Optimized lowered selection DAG: %bb.0 'vse_insert:'
SelectionDAG has 14 nodes:
  t0: ch = EntryToken
    t4: i64,ch = CopyFromReg t0, Register:i64 %1
  t5: i32 = truncate t4
        t17: v4i32 = BUILD_VECTOR t5, t5, t5, t5
      t12: nxv2i32 = insert_subvector undef:nxv2i32, t17, Constant:i64<0>
      t2: i64,ch = CopyFromReg t0, Register:i64 %0
    t15: ch = llvm.riscv.vse t0, TargetConstant:i64<7945>, t12, t2, Constant:i64<4>
  t16: ch = RISCVISD::RET_FLAG t15
```

As an aside, the fact our terminal node for the splat is a build vector here and not a splat is a symptom of another difference we should fix, but is - I think - irrelevant here. 

I think this difference is interesting.  One is effectively a demanded lane analysis (i.e. value based), the other is a peephole combine of a type conversion. 

I find myself wondering - if without a firm conclusion just yet - if we should be adding some form of subregister copy before the intrinsic, and then having generic code know how to fold that back into defining instructions (under normal one use restrictions, etc..).

More to follow as I think through this further.  



</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJztWt9v4zgO_mvSF6GB4yRN89CHNu3cFTc3BTqdwb0Viq3E2tqWV5Kd5v76-yjJid1f0-5kF3vAFKnjOBRJUR8pktFSpduzu0waRq_SVFKLlC23LFfqQZZrPLOK2UwwK4xlScbLtSBKlllbmcH4fBB_wkuLRoqNGeZ5UwyVXuPR5Wgcnc6nQ8ZuSnDglnmiQbxg1yzjKdNcGkjjLFFlInTJ-FLV1kmDWKF5YqUqmVqxlXwEYS7Ktc1YIxKrtGG8TJlJeM6XuWgfQto1M5CVqg2kqpRvcQW7BurLNbeC8RysS3dXVVrxJMOE3CQxrOI2yUhDYp7wAkSJVsYwo3Bval1pacguWpg6t15eKhKZQj8wWUnoYsmeHC-2rNd0QzOqMr7UMuHQEnyWhTSGJrdSuoCywpSDeGaZKBNVa74WWABISYXl4JiyjZYWYiHOmXw-iC4H0bm_fpLaWNJ5k4kS6my0ssLJ5CXP1VrVxq2eYSutCveFXwlWu6lUOcd6Xt_6hUllykpl2VrYQEoTxRfisYKNRUo6xKdfhWAiGU95vII6sEJprMCaYrG0qHKaKPH2HCqlMRAQM7VwitICe3ojREGGAwSMxKPS5ltWyeTBDS251mojNPv-mSzlnhnoa9kxwyUXHKCEFQtebsGiKHAvU6kKSE-wfkaYYddUdxi_UnmuNn4N0zqhdSNom0xt_EqlcrUSGish2FLYjYBRWxM5WACbGurLxMG_w31wEoWX-8i-3NxdgYSdGyM0QdkA9g1xBUteW7UWJVBuvcfVVuYGflNXKR7d53lyT4rdA5_JgxlW2yBkfMFuv30hviBhxzdjdlxAoSoXg_El0Jk0JxM84tZqPBjEFw0ws8CaTQ0bzBbsEwC1IJ7uUVf_VKxkCVdSgMBgEjmnu_dOdy8eLfnjvfczAKCymhhUtJ5yHNN9Q0gYzC72ii7-ebX41_Hn84urz6TwWxx3pmyH0QD_N4jHxH65HEbPyY6_XP3nLtA2RljZ5JL9V2hFik3oIsYxvRUjulru7uu3uBTNsBk-subUBYLR2wLH8bAJpLAKj_bO-dIALWwAB81I5HaYkRdgocgjgBKRiwJOQEvWUHAT7JHF-IeJB-MrVilpVNm3ub-POny9ixBTk9WrVS68jV9j2tHETeNHol8hIKPLEnGK5xL3XWQ5rVaNUwkDc2Ix6SgwidzG4dUcBmQMmwm-H5aPTUx08enr6rsJO0sA-_slYMyAn3gqjVQh4h2EAzEWx4E_aD677DtHkvPnvH5Wcwxs9cYnmT4-Ce4dsS9y6Mv3GGoFezVeFR-WcvJBfXoxAg5wX0oEtT8YEfbjD-X_5P7cOXwbBP5c_z9EwPkVRd4ZRSgb8gGkBaBDv9v1hjDjDvnA7_sCRg-0AP2kEzxeiwf9bX6fWTyILcZw49O6p7lEwkvs_JTwlJQ_214qkgpKFZH9VrXdY_wlQdfeNuCTC58fX57_g3ARPATvsx_ss_HMs_rasgAHZCaGjaZI_VKxz2tgcXI55P0OB1fIfLZ36kGU7dcgmBCBs_cikC1Utf2EVPNWrGk8LIs7Su5Ieogso52AqRtPOMRQq-syodwcbFuCWZdggSzK8hJGWkQUw1qiORGF1e84Qpj1PZDJ6hLBC2S7sLwg4bjueZJ2Tzj7PzuKngpwEAr8iVGH3o5GLrhNWtp2DZD4twNG0btE2_gPmTfqLNBo3FlCtyFCDrwj7I24GcFVEKhdrUdRXw8rcgO4iudP86E3Z7PWjKRTa_3RpCPi9vrr4vv110tC0fj89uru_tNnAIz06CbL7npTWVnAz1HcUZ4v0j8H19HHcR3M_5PoDlx-hHG_TtM-aN4C2N8DFdNDouKlYNf06pMnJO-JhL384lfc-2Dc-2DUOzAo-1t7GH_H9VrYZ5rP5pNpSGlDZPWwfEY46RrvLwpb7wTh6P8iSP1CxNshq00Ebeb6X4qaaYK6q66_uBFMabmWJXLYLWv7P4yzsnl0FvVJN_LDpbJZ6GAxdtdPJVturq-4a03lRlHXjvqOYTXCFom0c0VBnYdyWK189tltqtptJajWCM1X3-By8netulwAwr5F1vC8Fm5MSv3NFg6UmNPXG-q6ZlrV6-xJ-w2aFzwVrBDs95pasnCYjTOMqvMUflBVlB6vyFA8paYqsuZ2GuL3WkIyFTYVt9TFZaE5vdMYtvrmmpqdYRuJ-fEnPWTjmqCCu-aqcH0534GVReWLJ2oMZm4tAm9Cz7K2rpmsXXdTWte48wL7Avw6UruQlyEmuzYvJqofhm_sai8U2H74T1TYgcGvFtvT4nhywJp48lYp_KKg_vMftc92P3V0m2iH7gmFZOGlztArPbbDl-Zhnoeuzt1POwg2ElfsAO4XiWLb-WnIxSJ4pyGn1iL85OLjXcArXyHqkNvThr30lbyDVMoayeH7a1o8xypE7eD6DdcSm07gT7Qu9lOIVcWSHD7E6B6LAyS_e9d_PfGYfTTxeFdq2v07TIby7izFEc771dTreXHwiI93A_bMA9cQE9yYuPNPJdO8WyV5kb28Kn4lkd-3DF7K4__CLsL0EIlY_O5E7OQ9idj055Ly9zjH5G9YGraYmfVRePHt-vPl_ferxd3NbQDzk-shEDf7hbhXdplzOpOAfFimbvNyOTYloKpGYSR0QVm_Q9OT37KlPyogkQAHw2eC0vXS_xLPu2RmW1RWFZTDc3wJwm5hgEzWZC6TRjK6y1cNO2bXriB5wJ3UGiBvaDciMUPW6y4HMneEoV9xuCMZtFW6Ywg3pXsoQAF_aQSqGc5SUUBpckBe-tMHWwMibPdyCEG-aliipkhpHw8m8pNwk6uEqDKFnKDdFGmWrsyg8wHIuo3bWPsKI12mjRy-jqpBQbymXfvYFRHI_OlECeXmunDHTPLaHbv4rTaWbYUNdDuzLV3ZQQzcgQ93OgNKwCV0wCq4VNt2t-7VHu2xEUvHMFBQEJf9Lo9VfyjVhmX4p4MiKg_1xZInD77J5lJ_f-jGwOsTf2QA1iM_1MCCLgAgBbvUrnwBkfRELge3yXAIu_ayhn87LVX4pYFqtf0Ku_rMr_Sq1rQKdLTjWa_jSJyNTk7G8ezkdBQdpWfjdD6e8yMrbS7OBtML5yqD6SW77MClDJjNYJCcptSepNgfnnC2au1zfXtU6_ysf6hojeWrl0OAAR8oAIS340qr3wA7fHRHSujUxHQ2O5kdZWci4Tzlkygar5azuZhNp6dzMV-dnEQznp7OoiPkliI3pPggjsn2okzJxTGL4-945KuMuBKaFp9jNvRwenkkz-IojqP5aBqP4mgUDWeT1WQezU6i0Woym8YCKS_wL_PdIagjfebUXdZrQ_kw8LM_IXXEjZHrUjgbEn9eA6z6rNKCF8IcuZmduWn9D9iVKxw">