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

    <tr>
        <th>Summary</th>
        <td>
            Saturating conversion to int (llvm.fptosi.sat) does not get vectorized
        </td>
    </tr>

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

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

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

<pre>
    I have some rust code that does a floating point to int conversion in a loop (amongst some other computation). Rust semantics are that this conversion treats NaN as zero and saturates to the min/max values of the integer, which matches the `llvm.fptosi.sat` intrinsic. 

Unfortunately this conversion seems to prevent the whole loop getting vectorized when targeting `skylake` or `skx` architectures. The assembly contains 4 sets of scalar `vminsd`/`vmaxsd`/`vucomisd`/`vcvttsd2si` instructions.

Godbolt link: https://rust.godbolt.org/z/h4EasKzEY

Simplified rust code:

```rust
pub fn f64_to_int(input: &[f64;4], output: &mut [i32; 4]) {
    output.iter_mut().zip(input.iter()).for_each(|(o, i)| {
 *o = *i as i32
    })
}
```

Using a non-saturating conversion gets vectorized, also when manually handling NaN and clamping to the valid range before (Unless I'm missing some edge cases that should have the same semantics as the intrinsic):

```rust
pub unsafe fn f64_to_int_unchecked(input: &[f64; 4], output: &mut [i32; 4]) {
 output.iter_mut().zip(input.iter()).for_each(|(o, i)| {
        *o = i.to_int_unchecked()
    })
}

pub fn f64_to_int_manual(input: &[f64; 4], output: &mut [i32; 4]) {
 output.iter_mut().zip(input.iter()).for_each(|(o, i)| {
        let non_nan = if i.is_nan() {
            0.0
        } else {
 *i
        };
        let clamped = non_nan.clamp(i32::MIN as f64, i32::MAX as f64);
        // value is not nan and inside the range of i32
        *o = unsafe { clamped.to_int_unchecked() }
 })
}
```

Cleaned up llvm ir for all three methods:

```llvm
declare <4 x i32> @llvm.fptosi.sat.v4i32.v4f64(<4 x double>)

define void @to_int(ptr noalias noundef readonly align 8 dereferenceable(32) %input, ptr noalias noundef align 4 dereferenceable(16) %output) {
  %1 = load <4 x double>, ptr %input, align 8
  %2 = tail call <4 x i32> @llvm.fptosi.sat.v4i32.v4f64(<4 x double> %1)
  store <4 x i32> %2, ptr %output, align 4
  ret void
}

define void @to_int_unchecked(ptr noalias noundef readonly align 8 dereferenceable(32) %input, ptr noalias noundef align 4 dereferenceable(16) %output) {
  %1 = load <4 x double>, ptr %input, align 8
  %2 = fptosi <4 x double> %1 to <4 x i32>
  store <4 x i32> %2, ptr %output, align 4
  ret void
}

define void @to_int_manual(ptr noalias noundef readonly align 8 dereferenceable(32) %input, ptr noalias noundef align 4 dereferenceable(16) %output) {
  %1 = load <4 x double>, ptr %input, align 8
  %2 = fcmp ord <4 x double> %1, zeroinitializer
  %3 = select <4 x i1> %2, <4 x double> %1, <4 x double> zeroinitializer
  %4 = fcmp olt <4 x double> %3, <double 0xC1E0000000000000, double 0xC1E0000000000000, double 0xC1E0000000000000, double 0xC1E0000000000000>
  %5 = select <4 x i1> %4, <4 x double> <double 0xC1E0000000000000, double 0xC1E0000000000000, double 0xC1E0000000000000, double 0xC1E0000000000000>, <4 x double> %3
  %6 = fcmp ogt <4 x double> %5, <double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000>
 %7 = select <4 x i1> %6, <4 x double> <double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000, double 0x41DFFFFFFFC00000>, <4 x double> %5
  %8 = fptosi <4 x double> %7 to <4 x i32>
  store <4 x i32> %8, ptr %output, align 4
  ret void
}
```

Conversion to `i64` should also be possible to vectorize, but probably requires avx512 target features.


</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzcWF2P67YR_TX0y-AKNiVZ9oMfdr3r4qJoHpoGaJ8WY2lkMZciVZLyfvz6YkStLa-9SdM0DRrh4no1JIdnzpwhRaL36mCINiK_F_nDDPvQWLf5sbHOhxaNme1t9br5Cg0eCbxtCVzvA5S2IggNBqgseUCotcWgzAE6q0yAYIF_SmuO5LyyBpQBBG1tB0KusLXm4EN0aENDDkrbdn3AoKwRcp3AX3kaTy2aoEoP6Mb5QqP81G9whMHDd_gdoIc3chbQVOAx9A4DeYYSGoJWGSF3Lb7AEXVPHmw92JUJdCAn5BaeG1U20GIoGx7XEIjlXOtjm9RdsF4lHoNYznmIU8arMgExfxDzu_j_D6a2LvQGA-nXK5yeqB3AdI6OxBQ1BM-N1RRZOVAY-DtSGaxTb1TBc0MGAroDDS1iOfffXjV-IwZhXTS88Au6slGBytA78gn8rSFA76nd61fGEFAZDxl4CkPcvkSNw_hjq4yvxHIu5G54xZfpa1_aVk0N5TEEX0mvIg8-uL7klPlkysSfbLW3OoBW5ptI76AJofMivWMncscCSg6xS2LdQcjdm5C7JntE_-e3x39MPX2v2k6rWlF11h07mnRhcMM_7hBNXb-H2kC9zJ6CfVImCLlSpusDgxFyKfL7epmJ9D4T-QNn3vbh3Nr2AUR-r1Ip0nuIXdYgivvoHADG_okK5J7ant2zZt9U9z7R0BTN3FJb90RYNmwptkKuLM-quLXYTlwLeWdBpA_8h2I9M4jTrKJgJGPUxcOH8C-k6FkwCMaaL2MlsGEixwNL4aw1hoPa26i5Fk2PWr9Cg6bSPHIoL1NBqbHt2DBW1RG1qsChORDsqbaOuLx_MJq8h69CFi20yg9ohlqn6kBQoh_qCwP4xva6iqsL-_PY0rTq_XuNxoLj8P-t7PfGY02XInjqTdlQ-Y2jvS0H-I_08FuJYXxOmlDJjThOgvhcILdL4ikm-f-KCk2BJf1k0ERGalCJ8vwefV4P4WeezD9QWjwAaU-XlaeuOon0BoChBKgaAIxgksHGMTIxdyK9-8vXYTtiLjmgk_nu7yfz-sp7XB7j_gTKg7EBOFQuPJZ_FWskVputLxeHC6mM8hfF_TvcT7QDJ5n8gtVlqwkNVdB3wLsjKAe1dYBaQ2gcEbQUGlv5z0qVB0VTRaXmnV2k2wxeIk-PILKPu25yzFQqk2M2MLcau1e232sS6eMZ9-i1VobgaFXFvk5bQBccGItaIXPbm4pqcISVNfoVUKuDgRVU5KgmR6YkZO9ylcqBKJnHQpFbuOUojs9ujF8sx_FjKV3KVMh8MeRMW6zgOrI423T2EenEgRwcBFQaSs7Cr6RzwDRZWHyw1zmSuZyAew_tHV32PtZRGBJxc1G6magLif7BUxZTcuUizhDsJee_UzpO-8QfPRdl24F1107GctgOhwtlVFCo1Ru5iYd08OBJUxlOmVlME_Op06uGz2fJJjh1uOkyHV1GI8xftovH-fTh9t-i8SxPIfP8p-jIbtPx-2D-JDPpJJjlhPXDbdbzj6xni4ddfLbXOP6LjSfWhcyLnyJ9-bOk_w8hf0J6PiF99XPLY_FLl8fVr1keb34FTe4gLJ-l1TLjU_F4pBlOU3uCznqvmItgz-ctnnrfB-ic3SMf0h39s1eOPODxJV_I8dQPNWE80k8nnlWbtFqna5zRZrEsFqt0nq6KWbNZVYsiz7FY5Is97ddzpCJdZ_lyn69XkpbzmdrIuZQLKVO5WKyzIsnWi1Wal4uqWOeYz9cim1OLSifDB4N1h5nyvqdNvl6u5EzjnrQfroqkNPQMQ6OQUuQPM7fhMV_2_cHzB4fywZ-9BBU0bb6_eRIdr4qEXH28apHreLvE38HMxvm4Ouud3lzeKhxUaPp9UtpWyN3whRl_vnTO_khlEHI3wPVC7oZw_hUAAP__vaeUsQ">