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

    <tr>
        <th>Summary</th>
        <td>
            [AArch64] Miscompilation of struct containing complex double and BitInt
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            backend:AArch64,
            c23,
            miscompilation
      </td>
    </tr>

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

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

<pre>
    This code is miscompiled when targeting AArch64 at all optimisation levels:

```c
extern void __aeabi_assert(const char *, const char *, int);
#define assert(e) ((e) ? (void)0 : __aeabi_assert(#e, __FILE__, __LINE__))
#include <complex.h>

union S131 {
  double _Complex M0 __attribute__((aligned(16)));
  signed _BitInt(124) M1;
};

void F94(union S131 P3) {
  assert(P3.M0 == 1.0 + 2.0 * _Complex_I);
}

int main() {
  union S131 P3 = {1.0 + 2.0 * _Complex_I};
  F94(P3);

  return 0;
}
```

I think that the IR generated by clang is wrong, before any optimisations:
```
$ /work/llvm/build/bin/clang --target=aarch64--none-elf -march=armv8-a -c test.c -o - -S -O0 -emit-llvm
; ModuleID = 'test.c'
source_filename = "test.c"
target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
target triple = "aarch64-unknown-none-elf"

%union.S131 = type { i124 }

@.str = private unnamed_addr constant [21 x i8] c"P3.M0 == 1.0 + 2.0if\00", align 1
@.str.1 = private unnamed_addr constant [7 x i8] c"test.c\00", align 1
@__const.main.P3 = private unnamed_addr constant { { double, double } } { { double, double } { double 1.000000e+00, double 2.000000e+00 } }, align 16

; Function Attrs: noinline nounwind optnone
define dso_local void @F94(i128 %P3.coerce) #0 {
entry:
  %P3 = alloca %union.S131, align 16
  %coerce.dive = getelementptr inbounds %union.S131, ptr %P3, i32 0, i32 0
  %coerce.val.ii = trunc i128 %P3.coerce to i124
 store i124 %coerce.val.ii, ptr %coerce.dive, align 16
  %P3.realp = getelementptr inbounds { double, double }, ptr %P3, i32 0, i32 0
 %P3.real = load double, ptr %P3.realp, align 16
  %P3.imagp = getelementptr inbounds { double, double }, ptr %P3, i32 0, i32 1
 %P3.imag = load double, ptr %P3.imagp, align 8
  %cmp.r = fcmp oeq double %P3.real, 1.000000e+00
  %cmp.i = fcmp oeq double %P3.imag, 2.000000e+00
  %and.ri = and i1 %cmp.r, %cmp.i
  br i1 %and.ri, label %cond.true, label %cond.false

cond.true: ; preds = %entry
  br label %cond.end

cond.false: ; preds = %entry
  call void @__aeabi_assert(ptr noundef @.str, ptr noundef @.str.1, i32 noundef 10)
 br label %cond.end

cond.end:                                         ; preds = %cond.false, %cond.true
  ret void
}

declare dso_local void @__aeabi_assert(ptr noundef, ptr noundef, i32 noundef) #1

; Function Attrs: noinline nounwind optnone
define dso_local i32 @main() #0 {
entry:
  %retval = alloca i32, align 4
  %P3 = alloca %union.S131, align 16
  store i32 0, ptr %retval, align 4
  call void @llvm.memcpy.p0.p0.i64(ptr align 16 %P3, ptr align 16 @__const.main.P3, i64 16, i1 false)
  %coerce.dive = getelementptr inbounds %union.S131, ptr %P3, i32 0, i32 0
  %0 = load i124, ptr %coerce.dive, align 16
 %coerce.val.ii = zext i124 %0 to i128
  call void @F94(i128 %coerce.val.ii)
  ret i32 0
}

; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i64(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i64, i1 immarg) #2

attributes #0 = { noinline nounwind optnone "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a" }
attributes #1 = { "frame-pointer"="non-leaf" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+fp-armv8,+neon,+v8a" }
attributes #2 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }

!llvm.module.flags = !{!0, !1}
!llvm.ident = !{!2}

!0 = !{i32 1, !"wchar_size", i32 4}
!1 = !{i32 7, !"frame-pointer", i32 1}
!2 = !{!"clang version 19.0.0git (git@github.com:llvm/llvm-project.git 743d090b96e09fe7c2cea60a8962f579684c37ce)"}
```

This is using `%union.S131`, which is a 124-bit type, to store the value of `P3`, but it needs 128 bits of storage to hold the `double _Complex` value. The `memcpy` in main copies 16 bytes into an allocation which is only 15 bytes.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzkWF9vqzoS_zTuywgEhkDy0IekuZEq3bN7tOe-Rw5MEm_BZo1JT--nX40NBJK2p1rd3ZetmoQ_M_ObGc8_W7StPCnER7bYsMX2QXT2rM2jbq1QSpjy4aDLt8c_zrKFQpcIsoVatoWuG1lhCa9nVGCFOaGV6gTrtSnOWQrCgqgq0I2VtWyFlVpBhResWpasWbRl0fCdRf6_8Pf406JRcNGyhP1eoDjIvWhbNJbxZaFVa6E4CwOMrxl_gvsnUlnGVyzZ9PJ5UuJRKoRRCjK-AsaX42Wyo1uCZHwVAUvW99CMJ0ji9_vd8--_7ff--vfnv7nrFf0PeFIVVVcisOSJ3FThz_DMkt-mVneKHPIjTmJgea8pQKm7Q4Wwf_Jc8C0iPaw18tBZJBzSWVS0XiXjyzjrkSf2ArjlLGG_kfaZfLGMeUpmfouvTsm312v37fy9W6WMLye6fU-cf64aju74noTfyFNblmwhDiNgfAPc_a5HA_bPs5XIt1NEqSzUQipn1AxkpgBh0MtPMCbGQG-DU_zGRACDtjMKondUGqJwyvAM9izVC9izsGDPCM__gBMqNMJiCYc3KCqhTpQQr0arE0XEAY_aIAj1Ngv9SdDfAPEUGN-9avPC-K6qLjXju0Mnq5J-yTk7DxIEPsdYshXCpVgQKK0wwOoIQU2P6JWpL8tAQFCAxdaGBQQaAgh-QPD3CAKspQ0ciAdPNvBNl12Fz1vvZp57NsZzT9LqzhS4P8oKlaixp-IDFfdUXjUohRWVeNOdHegwqFmyxkAuWbKmT8IDGWcsWbsvustSlqyzNJAxJ4KYLwOVcP_sBz27AbFGNtWoyOCLTr0o_apGl4xcg5sXLqhCn3HJFuxbgxRWIGNagnlosjQKW2scYWPkRViETpEDyr0oS-OrjlAW2GLDY_gJcskWWyCPfJQY8sgWT1FEivEncDkM8QwujL8GmM_w-qX4RPZ-77hDSrawT6hfgOQb9_H1iGT2lYnl2_7zKcHwgux3f8j4hvQb6fjsxSB3on02W49kA7tOFa6LrK01lE-gtFQVFXalO_UqVUkpRwHgmfqqX7Z6X-lCVL6lsDTyFYLiDRhffE_CQqMp-qaQRNdKhMqatzFzwVM794mKRMIsrO6VdxxeeFjKiw_aE1qssEZlG2tAqoPuVNnei6K3DtD1tIRDdL24k34RVSilD2zTqQLurAOrXaj3vK2lMuVj_0bKBHyi-wfWfU9Cg6JqPjXtg0D5mpVXEIdRaVFOpI38Xo2PtZS1OP0XtIxnWhLI51o6Na5aLqdLWTehLznHom5A479GLUYDifMmp-YC5CcCCJsE8I8ECFWGxksQqgQZj2oR24AwMBxMT-HZiKQSB6x84KgytKbD-6dHUbU4Te4rbbIGyvTGIK2HK_ALn4NXyLkwVOWdKA_wS1kFjadDSbib9mjFqKyUeIS-PA8LefM4jIdYGF7E0TgMfk1jepas4at_d3ZNzO7XaXTpdfBxtr47iJVYVMK8Vyk_c8uNO26c0BfT-K-v4gTC0mg6Of6qaBu0l7589IVbJpNOmf7n9b0vo0NF6PPc470DMIs5msPCGuuieQubiP5pFvI-HnCuVWf-9K6pO_dnKalGVzH04bD6n3Wi6Fr4XJ_5ahd5t4n9iT_t2J2ivnct33XjvJvfdLLVNAEmGt_Oex9EJgEdRPECSh8NTkL0VVZVv5mosdbmjbZl5lQjjbtgUJSvRtqr_4cU-8raKy0qKVoH39jOIDhZWlVv15y7JSHIgcKJckEg61qYU58js4F43FS2ffr4TdbH2UjD9tGIGoNGS2XR0LCZbBnnSqugQkEjN7jbwBrRNFKdglrY80jn24Gjaa0oXoLGaIuF1SY4dMcjmqCVf-JIvuxp_dgfFE03vnJ7MFnMCY4oyBPtSMX45tgEbkfE-BPjG4Va-avLUjjmIQrm3ohHb_z_2MwnEfAXRP3dhorHPuTdbjM8VuI0dK-YKjePI9-64vjK2fPIEpWdE_N78dGEwg9mXh7j_LU4C7MfHN2XrnSKE98w51fmu_UfB78JP5-rxzj3m_YLmpZKSrwKozA6SQuML0_SspRuzt0hLDT5rt_30w9FyD-xsCFR52lSRqvosMowWh0xL3iBIovEcpXx4yJfZcu0SHK3dSHVPj3McAd4soWuleoE9HJW5zO3AK9nWZyJSkDM0-Agrdso0yur-25nzwgXUXUI-khyvic986GzIC0opNGE6vFB2paIiE-c3BbkrKvSSWBZdHPaxbLIyw3hD0_gCyQ9l8odFUGhG4kttcDDG0WuVFaDUH2_duV7NIHKIcQLTxk-lI9JuUpW4gEf4zxO0jRL0vzh_BhlK5GmBV8JseRRLvIsL8sywZhHx6hIywf5yCOeRmm8jJN4FeVhFOX5MUuzlPPskBWCpRHWQlahC1dtTg-ybTt8XK54Ej248a91h6ucU1r5aa8_J_URRQHDk_F6PF51FtHjxfbBPLrwOHSnlhqIbG17BbTSVu4AdxC72MK3mRS_DqYrLG3zrZCKwqA_nhz2CTT1-1PDh85Uj2drG3dyxXeM7yYRO55UTSOW8Z2zu2V850z_dwAAAP__h_iBdw">