Issue 76017
Summary Clang generates incorrect code for unions of types with padding
Labels clang
Assignees
Reporter ivafanas
    Reproducer:

```c++
#include <cstdio>
#include <cstdint>

struct my_struct_1 {
  float a;
 float b;
};

struct my_struct_2 {
  float x;
  double y;
};

union my_union {
  my_struct_1 s1;
  my_struct_2 s2;
};

my_union my_func() {
  my_union u;
  u.s1 = my_struct_1{100.f, 200.f};
  return u;
}

int main() {
 my_union u = my_func();

  if (u.s1.a != 100.f)
    std::puts("a ooops");

  if (u.s1.b != 200.f)
    std::puts("b ooops");

  return 0;
}
```

Please, note that `my_struct_1` has field `b` which fits into padding of `my_struct_2` and `sizeof(my_struct_2) > sizeof(my_struct_1)`.

Bug is reproduced on X86 on clang 16 release and on the latest main branch (clang++-17 is not tested, but I assume its behaviour is the same):


```sh
# clang 16 release
clang++-16 -O1 example.cpp && ./a.out
b ooops

# clang main 5caae72d1a4f
clang++ -O1 example.cpp && ./a.out
b ooops
```

The problem is that C++ unions are represented in IR level as a struct of member with the largets size:

```
%union.my_union = type { %struct.my_struct_2 }
%struct.my_struct_2 = type { float, double }
%struct.my_struct_1 = type { float, float }
```

Information about `my_struct_1` layout is lost when union `llvm::StructType` is constructed. SROA pass operates on `my_struct_2` layout only.

`my_func` IR before SROA:

```
*** IR Dump After SimplifyCFGPass on _Z7my_funcv ***
; Function Attrs: mustprogress nounwind uwtable
define dso_local { float, double } @_Z7my_funcv() #0 {
entry:
  %retval = alloca %union.my_union, align 8
  %ref.tmp = alloca %struct.my_struct_1, align 4
  call void @llvm.lifetime.start.p0(i64 8, ptr %ref.tmp) #5
  %a = getelementptr inbounds %struct.my_struct_1, ptr %ref.tmp, i32 0, i32 0
  store float 1.000000e+02, ptr %a, align 4, !tbaa !5
  %b = getelementptr inbounds %struct.my_struct_1, ptr %ref.tmp, i32 0, i32 1
  store float 2.000000e+02, ptr %b, align 4, !tbaa !10
  call void @llvm.memcpy.p0.p0.i64(ptr align 8 %retval, ptr align 4 %ref.tmp, i64 8, i1 false), !tbaa.struct !11
  call void @llvm.lifetime.end.p0(i64 8, ptr %ref.tmp) #5
  %coerce.dive = getelementptr inbounds %union.my_union, ptr %retval, i32 0, i32 0
  %0 = load { float, double }, ptr %coerce.dive, align 8
  ret { float, double } %0
}
```

`my_func` IR after SROA:

```
*** IR Dump After SROAPass on _Z7my_funcv ***
; Function Attrs: mustprogress nounwind uwtable
define dso_local { float, double } @_Z7my_funcv() #0 {
entry:
  %.fca.0.insert = insertvalue { float, double } poison, float 1.000000e+02, 0
  %.fca.1.insert = insertvalue { float, double } %.fca.0.insert, double undef, 1
  ret { float, double } %.fca.1.insert
}
```

As you can see, `200.f` value is lost and `undef ` value insertion happens here.  
On the other side, IR before SROA also looks suspicious, because 32bits of double value are undef -> whole double is undef.

`gcc` works well on this example.

Possibly related discussion:
https://discourse.llvm.org/t/struct-copy/11330

Possibly related issue:
https://github.com/llvm/llvm-project/issues/53710
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to