[mono][llvm] Malformed uaddlp/saddlp intrinsic declaration breaks LLVM AOT with LLVM 23
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
Mono's LLVM AOT compiler emits a malformed declaration for the `llvm.aarch64.neon.uaddlp` / `llvm.aarch64.neon.saddlp` intrinsics:
```llvm
declare <8 x i8> @llvm.aarch64.neon.uaddlp.v8i8.f64(double)
declare <8 x i8> @llvm.aarch64.neon.saddlp.v8i8.f64(double)
```
The argument type should be a vector, not `double`. With the recent bump to **LLVM 23**, the IR verifier now rejects this, so any LLVM-AOT compilation of an assembly that references `AdvSimd.AddPairwiseWidening` (or `AddPairwiseWideningScalar` / `AddPairwiseWideningAndAdd`) fails:
```
intrinsic argument 0 type (overload type 1) expected any vector type, but got double
declare <8 x i8> @llvm.aarch64.neon.uaddlp.v8i8.f64(double)
intrinsic argument 0 type (overload type 1) expected any vector type, but got double
declare <8 x i8> @llvm.aarch64.neon.saddlp.v8i8.f64(double)
LLVM ERROR: Broken module found, compilation aborted!
```
In practice this shows up as an AOT compilation failure of `System.Private.CoreLib.dll` in untrimmed Mono iOS apps.
### Root cause
`add_intrinsic ()` in `src/mono/mono/mini/mini-llvm.c` reads out of bounds for the `INTRIN_kind_widen` case:
https://github.com/dotnet/runtime/blob/main/src/mono/mono/mini/mini-llvm.c#L14021-L14027
```c
} else if (kind == INTRIN_kind_widen) {
/*
* @llvm.aarch64.neon.saddlp.v2i64.v4i32
* @llvm.aarch64.neon.saddlp.v4i16.v8i8
*/
intrins = add_intrins2 (module, id, distinguishing_type, intrin_types [vw][ew - 1], &intrins_type);
```
`intrin_types [vw][ew - 1]` is the *narrower* source type. But the two `Widen`-kind intrinsics are declared with `I1` (8-bit elements) in their overload spec:
https://github.com/dotnet/runtime/blob/main/src/mono/mono/mini/llvm-intrinsics.h#L436-L437
```c
INTRINS_OVR_TAG_KIND(AARCH64_ADV_SIMD_SADDLP, aarch64_neon_saddlp, Arm64, Widen, V64 | V128 | I1 | I2 | I4 | I8)
INTRINS_OVR_TAG_KIND(AARCH64_ADV_SIMD_UADDLP, aarch64_neon_uaddlp, Arm64, Widen, V64 | V128 | I1 | I2 | I4 | I8)
```
so the loop reaches `ew == 0` and evaluates `intrin_types [vw][-1]`, which reads the last element of the previous row: `intrin_types [1][-1]` is `intrin_types [0][5]` = `LLVMDoubleType ()`. Hence the `f64` operand.
There is no `uaddlp`/`saddlp` form with an 8-bit **result** element — the valid result element widths are 16/32/64 bits — so `I1` simply doesn't belong in these two specs. `SADDLP`/`UADDLP` are the only two `Widen`-kind entries in `llvm-intrinsics.h`, so no other intrinsic is affected.
The out-of-bounds read has been there since dotnet/runtime#51993 (2021); it only started failing now because of the LLVM 23 bump.
### Why it only breaks with LLVM 23
The intrinsic definition itself is unchanged (`AdvSIMD_1VectorArg_Expand_Intrinsic` is `[llvm_anyvector_ty], [llvm_anyvector_ty]` in both `release/22.x` and `main`). What changed is `llvm/lib/IR/Intrinsics.cpp`, which in LLVM 23 gained the more thorough overload-type checking that produces the `... (overload type N) expected ..., but got ...` diagnostics (that message machinery does not exist in `release/22.x`).
Verified with a file containing nothing but the malformed declaration:
| `opt` version | `opt -passes=verify` |
|-------------------------|----------------------|
| 22.1.2 | accepts |
| 23.1.0-rc2 (mono cross) | rejects |
### Reproduction Steps
Any Mono LLVM-AOT compilation of an assembly that references `AdvSimd.AddPairwiseWidening` reproduces this. Minimal .NET for iOS app:
```csharp
using System;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.Arm;
public class Program {
static void Main ()
{
if (AdvSimd.IsSupported)
Console.WriteLine (AdvSimd.AddPairwiseWidening (Vector64.Create ((byte) 1)));
}
}
```
```xml
net11.0-ios
Exe
iossimulator-arm64
true
com.example.uaddlprepro
```
`dotnet build -c Release`
### Expected behavior
The app builds.
### Actual behavior
```
error : Failed to AOT compile System.Private.CoreLib.dll, the AOT compiler exited with code 1.
```
with the `LLVM ERROR: Broken module found, compilation aborted!` output shown above.
Replacing the single `AdvSimd.AddPairwiseWidening` call with `AdvSimd.AddPairwise` makes the build succeed, which confirms the trigger. Note the intrinsic is registered lazily (`get_intrins` → `add_intrinsic`), and in a stock app the only thing referencing UADDLP/SADDLP is the body of `AdvSimd.AddPairwiseWidening` itself — so trimmed apps are unaffected, and only untrimmed ones fail.
### Configuration
* .NET 11 (`11.0.100-rc.1.26411.119` / runtime `11.0.0-rc.1.26411.119`)
* `Microsoft.NETCore.App.Runtime.AOT.osx-arm64.Cross.iossimulator-arm64`, `tools/opt --version` reports LLVM 23.1.0-rc2
* Mono runtime, LLVM AOT, arm64
* Found via dotnet/macios: the `tests/linker/dont link` (untrimmed) iOS test started failing on the dotnet/dotnet bump from `11.0.0-preview.7.26365.101` to `11.0.0-rc.1.26411.119`.
### Suggested fix
Drop `I1` from both specs:
```c
INTRINS_OVR_TAG_KIND(AARCH64_ADV_SIMD_SADDLP, aarch64_neon_saddlp, Arm64, Widen, V64 | V128 | I2 | I4 | I8)
INTRINS_OVR_TAG_KIND(AARCH64_ADV_SIMD_UADDLP, aarch64_neon_uaddlp, Arm64, Widen, V64 | V128 | I2 | I4 | I8)
```
Contributor guide
Research direction
Start with the SADDLP and UADDLP specifications in src/mono/mono/mini/llvm-intrinsics.h, then inspect add_intrinsic() in src/mono/mono/mini/mini-llvm.c to understand the invalid type lookup. Verify the change with the provided AdvSimd.AddPairwiseWidening reproduction and confirm LLVM 23 accepts the generated IR and the iOS AOT build succeeds.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, csharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 1/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 84/100