Issue 184676
Summary [WebAssembly] Assertion failure in isVectorLoadExtDesirable when freeze node is between load and extend
Labels new issue
Assignees
Reporter abadams
    This is causing occasional crashes in Halide CI while fuzzing arithmetic operations. The below is the analysis by Claude Code, so take with a grain of salt, but I have verified that the repro does indeed trigger the crash, and the analysis seems plausible to me.

-----

`WebAssemblyTargetLowering::isVectorLoadExtDesirable` unconditionally does `cast<LoadSDNode>(ExtVal->getOperand(0))`, assuming the operand of a vector extend is always a load. However, LLVM's DAG combiner in `tryToFoldExtOfLoad` (DAGCombiner.cpp:14779) correctly handles `freeze` nodes between loads and extends, but when it then calls `isVectorLoadExtDesirable`, the Wasm implementation crashes because the extend's operand is a `freeze` node, not a `LoadSDNode`.

This triggers an assertion failure:
```
cast<Ty>() argument of incompatible type!
```

## Reproducer

```llvm
; RUN: clang --target=wasm32-unknown-unknown -msimd128 -c -x ir %s -o /dev/null
; (or: llc -mtriple=wasm32-unknown-unknown -mattr=+simd128 %s -o /dev/null)

target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-unknown"

define <4 x i16> @test(ptr %in) {
entry:
  %t = load <4 x i8>, ptr %in, align 1
  %t.fr = freeze <4 x i8> %t
  %w = zext <4 x i8> %t.fr to <4 x i16>
  ret <4 x i16> %w
}
```

The freeze doesn't need to be in the original IR — it's sufficient for LLVM's optimizer to insert one. For example, this also crashes at `-O2` because LLVM inserts a freeze for the poison semantics of `udiv`:

```llvm
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-unknown"

define void @test(ptr %in, ptr %out) {
entry:
  %t = load <4 x i16>, ptr %in, align 2
  %d = udiv <4 x i16> splat (i16 2), %t
  store <4 x i16> %d, ptr %out, align 2
  ret void
}
```

All three type pairs checked by `isVectorLoadExtDesirable` are affected:
- `<8 x i8>` → `<8 x i16>`
- `<4 x i16>` → `<4 x i32>`
- `<2 x i32>` → `<2 x i64>`

## Root Cause

In `WebAssemblyISelLowering.cpp`:

```cpp
bool WebAssemblyTargetLowering::isVectorLoadExtDesirable(SDValue ExtVal) const {
  EVT ExtT = ExtVal.getValueType();
  EVT MemT = cast<LoadSDNode>(ExtVal->getOperand(0))->getValueType(0);  // BUG
  return (ExtT == MVT::v8i16 && MemT == MVT::v8i8) ||
         (ExtT == MVT::v4i32 && MemT == MVT::v4i16) ||
         (ExtT == MVT::v2i64 && MemT == MVT::v2i32);
}
```

The `cast<LoadSDNode>(ExtVal->getOperand(0))` assumes operand 0 of the extend is always a `LoadSDNode`. But the caller `tryToFoldExtOfLoad` in `DAGCombiner.cpp` (line 14777-14779) already handles the case where a `freeze` node sits between the load and extend:

```cpp
bool Frozen = N0.getOpcode() == ISD::FREEZE;
SDValue Freeze = Frozen ? N0 : SDValue();
auto *Load = dyn_cast<LoadSDNode>(Frozen ? N0.getOperand(0) : N0);
```

So `isVectorLoadExtDesirable` is called with an extend whose operand is a freeze, not a load.

## Suggested Fix

Look through the freeze node, and use `dyn_cast` with a null check:

```cpp
bool WebAssemblyTargetLowering::isVectorLoadExtDesirable(SDValue ExtVal) const {
  EVT ExtT = ExtVal.getValueType();
  SDValue N0 = ExtVal->getOperand(0);
  if (N0.getOpcode() == ISD::FREEZE)
    N0 = N0.getOperand(0);
  auto *LN0 = dyn_cast<LoadSDNode>(N0);
  if (!LN0)
    return false;
  EVT MemT = LN0->getValueType(0);
  return (ExtT == MVT::v8i16 && MemT == MVT::v8i8) ||
         (ExtT == MVT::v4i32 && MemT == MVT::v4i16) ||
         (ExtT == MVT::v2i64 && MemT == MVT::v2i32);
}
```

Alternatively, the `TargetLowering::isVectorLoadExtDesirable` interface documentation could note that the operand may be a freeze rather than a direct load, since the DAGCombiner already handles this case before calling the hook.

## Impact

This bug is triggered by the [Halide](https://github.com/halide/Halide) compiler when targeting WebAssembly with SIMD. Any IR that results in a `freeze` between a vector load and a widening extend will crash during instruction selection. The `freeze` is commonly inserted by LLVM's own optimizer for poison semantics around integer division.

Tracked on the Halide side as https://github.com/halide/Halide/issues/8928.

_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to