Repository: rust
Issue: Wasm32 miscompilation when using u128 with multivalue and optimizations
Labels: A-LLVM,P-high,T-compiler,I-unsound,O-wasm,S-has-mcve,I-miscompile,C-external-bug

<!--
Thank you for filing a bug report! 🐛 Please provide a short summary of the bug,
along with any information you feel relevant to replicating the bug.
-->

I am getting a miscompiled wasm that incorrectly performs arithmetic operations on `u128` and `Option<u128>` values. Based on my (poor) analysis of MIR, LLVM IR, and resulting wasm the issue seems to be on llvm side but it would be best for someone more competent to double-check that; 

I managed to get the reproduction down to this code:

```rust
#[inline(never)]
fn get_total_opt(a: u32, b: u32) -> Option<u128> {
    if a > 100 {
        Some(a as u128 + b as u128)
    } else {
        None
    }
}

#[inline(never)]
fn calculate(a: u32, b: u32, diff: u128) -> u128 {
    let total_opt = get_total_opt(a, b);
    // uncommenting any of these lines fixes the issue
    // println!("{}", total_opt.is_some());
    // assert!(total_opt.is_some());
    total_opt.unwrap_or_default() + diff
}

fn main() {
    let mut input_line = std::string::String::new();
    std::io::stdin()
        .read_line(&mut input_line)
        .expect("Failed to read line");
    let x: u32 = input_line.trim().parse().expect("Input not an integer");
    let mut input_line = std::string::String::new();
    std::io::stdin()
        .read_line(&mut input_line)
        .expect("Failed to read line");
    let diff: u128 = input_line.trim().parse().expect("Input not an integer");
    println!("{:?}", calculate(x, x, diff));
}
```

Compiled with

```sh
rustc wasmmain.rs --target wasm32-wasi -C "target-feature=+multivalue" -C opt-level=s
```

And launched with wasmtime

I expected to see this happen: when entering two values above 100 (i.e. 111 and 111) I expect to see correct result (i.e. 333)

Instead, this happened:
```
# wasmtime wasmmain.wasm 
111
111
111
```

The behavior seems to only be exhibited in the `calculate` function; replacing `unwrap_or_default` with `unwrap` or adding a side-effect between the call to `get_total_opt` and the calculation seems to fix the issue. All of the changes to produced wasm are residing in the `calculate` function (if we exclude offsets changes)

Also disabling the optimizations (`opt-level=0`) or turning off the `multivalue` feature both address the issue as well. The issue also happens on `wasm32-unknown-unknown` target but I don't have as clean of a setup to showcase that

<details><summary>Emitted MIR</summary>

```mir
fn calculate(_1: u32, _2: u32, _3: u128) -> u128 {
    debug a => _1;
    debug b => _2;
    debug diff => _3;
    let mut _0: u128;
    let _4: std::option::Option<u128>;
    let mut _5: u128;
    scope 1 {
        debug total_opt => _4;
    }

    bb0: {
        _4 = get_total_opt(move _1, move _2) -> [return: bb1, unwind unreachable];
    }

    bb1: {
        StorageLive(_5);
        _5 = Option::<u128>::unwrap_or_default(move _4) -> [return: bb2, unwind unreachable];
    }

    bb2: {
        _0 = Add(move _5, _3);
        StorageDead(_5);
        return;
    }
}
```

</details>

<details><summary>Emitted llvm-ir</summary>

```llvm
; wasmmain::calculate
; Function Attrs: mustprogress nofree noinline norecurse nosync nounwind optsize willreturn memory(none)
define internal fastcc noundef i128 @_ZN8wasmmain9calculate17hd962bed4287d8d71E(i32 noundef %a, i32 noundef %b, i128 noundef %diff) unnamed_addr #5 {
start:
; call wasmmain::get_total_opt
  %0 = tail call fastcc { i64, i128 } @_ZN8wasmmain13get_total_opt17h693d6b0c9dfa9b41E(i32 noundef %a, i32 noundef %b) #12
  %total_opt.0 = extractvalue { i64, i128 } %0, 0
  %total_opt.1 = extractvalue { i64, i128 } %0, 1
  %1 = and i64 %total_opt.0, 4294967295
  %switch.i = icmp eq i64 %1, 0
  %spec.select.i = select i1 %switch.i, i128 0, i128 %total_opt.1
  %_0 = add i128 %spec.select.i, %diff
  ret i128 %_0
}
```

</details>

<details><summary>Emitted wasm (converted to wat with wasm2wat)</summary>

```wat
(func $_ZN8wasmmain9calculate17hd962bed4287d8d71E (type 9) (param i32 i32) (result i64 i64)
    (local i64 i64 i64 i64)
    local.get 0
    local.get 1
    call $_ZN8wasmmain13get_total_opt17h693d6b0c9dfa9b41E
    local.set 4
    local.set 3
    local.set 2
    local.get 3
    i64.const 100
    i64.add
    local.tee 5
    i64.const 100
    local.get 2
    i32.wrap_i64
    local.tee 1
    select
    local.get 4
    local.get 5
    local.get 3
    i64.lt_u
    i64.extend_i32_u
    i64.add
    i64.const 0
    local.get 1
    select)
```

</details>

Both MIR and llvm-ir seem correct and with some `wasm2c` and simplification it seems like the resulting wasm is unconditionally returning the `p2` and then conditionally returning either `p3` or `p3`+corresponding `u64` of the `Option<u128>` and I was able to confirm that with this modified program

<details><summary>Modified code</summary>

Code:

```rust
#[inline(never)]
fn get_total_opt(a: u32, b: u32) -> Option<u128> {
    if a > 100 {
        Some(a as u128 + b as u128 + u64::MAX as u128)
    } else {
        None
    }
}

#[inline(never)]
fn calculate(a: u32, b: u32, diff: u128) -> u128 {
    let total_opt = get_total_opt(a, b);
    // uncommenting any of these lines fixes the issue
    // println!("{}", total_opt.is_some());
    // assert!(total_opt.is_some());
    total_opt.unwrap_or_default() + diff
}

fn main() {
    let mut input_line = std::string::String::new();
    std::io::stdin()
        .read_line(&mut input_line)
        .expect("Failed to read line");
    let x: u32 = input_line.trim().parse().expect("Input not an integer");
    let mut input_line = std::string::String::new();
    std::io::stdin()
        .read_line(&mut input_line)
        .expect("Failed to read line");
    let diff: u128 = input_line.trim().parse().expect("Input not an integer");
    println!("{:?}", calculate(x, x, diff + u64::MAX as u128));
    println!("{:?}", x as u128 + x as u128 + diff + u64::MAX as u128 + u64::MAX as u128);
}
```

Output:

```
111
111
36893488147419103342
36893488147419103563
```
</details>

### Meta
<!--
If you're using the stable version of the compiler, you should also check if the
bug also exists in the beta or nightly versions.
-->

`rustc --version --verbose`:
```
rustc 1.78.0 (9b00956e5 2024-04-29)
binary: rustc
commit-hash: 9b00956e56009bab2aa15d7bff10916599e3d6d6
commit-date: 2024-04-29
host: x86_64-unknown-linux-gnu
release: 1.78.0
LLVM version: 18.1.2
```

`rustc +nightly --version --verbose`:
```
rustc 1.81.0-nightly (aa1d4f682 2024-07-03)
binary: rustc
commit-hash: aa1d4f6826de006b02fed31a718ce4f674203721
commit-date: 2024-07-03
host: x86_64-unknown-linux-gnu
release: 1.81.0-nightly
LLVM version: 18.1.7
```