Fix egal codegen on large (>512 bytes), split representation isbitsegal types (#62095)
Large (> 512 bytes) struct that are isbitsegal emit a memcmp instead of
a comparison for each field. However, this requires that both the
arguments to the memcmp be in the canonical struct layout, which is a
problem for structs in the split layout because of the calling
convention.
Here's an example:
```julia
mutable struct Box
v::Int
end
struct Mixed
obj::Box # Mixed must be bitsegal (Any won't work)
num::Int
end
const N = 40
const T = NTuple{N, Mixed}
f(a::T, b::T) = a === b
x = ntuple(i -> Mixed(Box(i), i), Val(N))
x === x # true
f(x, x) # false
```
`N=40` is chosen to make the resulting `NTuple{N, Mixed}` at least 512
bytes.
To make the IR a little more readable, here's an example of the code
we'd generate if the threshold for emitting `memcmp` for `===` was 64
bytes:
```llvm
define swiftcc i8 @julia_f_0(ptr nonnull swiftself "gcstack" %0, ptr addrspace(11) noundef nonnull readonly align 8 captures(none) dereferenceable(32) %1, ptr noundef nonnull readonly align 8 captures(none) dereferenceable(16) %2, ptr addrspace(11) noundef nonnull readonly align 8 captures(none) dereferenceable(32) %3, ptr noundef nonnull readonly align 8 captures(none) dereferenceable(16) %4) #0 {
; Copy of each argument combined by `recombine_value`.
%6 = alloca [2 x { ptr addrspace(10), i64 }], align 8
%7 = alloca [2 x { ptr addrspace(10), i64 }], align 8
; Stores generated by `value_to_pointer`
store ptr addrspace(10) null, ptr %7, align 8
%8 = getelementptr inbounds i8, ptr %7, i32 16
store ptr addrspace(10) null, ptr %8, align 8
store ptr addrspace(10) null, ptr %6, align 8
%9 = getelementptr inbounds i8, ptr %6, i32 16
store ptr addrspace(10) null, ptr %9, align 8
%10 = getelementptr inbounds i8, ptr %0, i32 -152
%11 = getelementptr inbounds i8, ptr %10, i32 168
%12 = load ptr, ptr %11, align 8, !tbaa !3
%13 = getelementptr inbounds i8, ptr %12, i32 16
%14 = load atomic ptr, ptr %13 monotonic, align 8, !tbaa !7, !invariant.load !9
fence syncscope("singlethread") seq_cst
call void @julia.safepoint(ptr %14)
fence syncscope("singlethread") seq_cst
; Copies from `recombine_value`
%15 = load ptr addrspace(10), ptr %2, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
store ptr addrspace(10) %15, ptr %6, align 8, !tbaa !18, !alias.scope !20, !noalias !21
%16 = getelementptr inbounds i8, ptr %6, i32 8
%17 = getelementptr inbounds i8, ptr addrspace(11) %1, i32 8
call void @llvm.memcpy.p0.p11.i64(ptr align 8 %16, ptr addrspace(11) align 8 %17, i64 8, i1 false), !tbaa !22, !alias.scope !23, !noalias !24
%18 = getelementptr inbounds i8, ptr %2, i32 8
%19 = load ptr addrspace(10), ptr %18, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%20 = getelementptr inbounds i8, ptr %6, i32 16
store ptr addrspace(10) %19, ptr %20, align 8, !tbaa !18, !alias.scope !20, !noalias !21
%21 = getelementptr inbounds i8, ptr %6, i32 24
%22 = getelementptr inbounds i8, ptr addrspace(11) %1, i32 24
call void @llvm.memcpy.p0.p11.i64(ptr align 8 %21, ptr addrspace(11) align 8 %22, i64 8, i1 false), !tbaa !22, !alias.scope !23, !noalias !24
%23 = load ptr addrspace(10), ptr %4, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
store ptr addrspace(10) %23, ptr %7, align 8, !tbaa !18, !alias.scope !20, !noalias !21
%24 = getelementptr inbounds i8, ptr %7, i32 8
%25 = getelementptr inbounds i8, ptr addrspace(11) %3, i32 8
call void @llvm.memcpy.p0.p11.i64(ptr align 8 %24, ptr addrspace(11) align 8 %25, i64 8, i1 false), !tbaa !22, !alias.scope !23, !noalias !24
%26 = getelementptr inbounds i8, ptr %4, i32 8
%27 = load ptr addrspace(10), ptr %26, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%28 = getelementptr inbounds i8, ptr %7, i32 16
store ptr addrspace(10) %27, ptr %28, align 8, !tbaa !18, !alias.scope !20, !noalias !21
%29 = getelementptr inbounds i8, ptr %7, i32 24
%30 = getelementptr inbounds i8, ptr addrspace(11) %3, i32 24
call void @llvm.memcpy.p0.p11.i64(ptr align 8 %29, ptr addrspace(11) align 8 %30, i64 8, i1 false), !tbaa !22, !alias.scope !23, !noalias !24
%31 = load ptr addrspace(10), ptr %2, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%32 = getelementptr inbounds i8, ptr %2, i32 8
%33 = load ptr addrspace(10), ptr %32, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%34 = load ptr addrspace(10), ptr %4, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%35 = getelementptr inbounds i8, ptr %4, i32 8
%36 = load ptr addrspace(10), ptr %35, align 8, !tbaa !7, !invariant.load !9, !alias.scope !10, !noalias !13
%37 = call i32 @memcmp(ptr %6, ptr %7, i64 32) [ "jl_roots"(ptr addrspace(10) %31, ptr addrspace(10) %33, ptr addrspace(10) %34, ptr addrspace(10) %36) ], !tbaa !7, !alias.scope !10, !noalias !13
%38 = icmp eq i32 %37, 0
%39 = zext i1 %38 to i8
ret i8 %39
}
!0 = !{i32 2, !"Dwarf Version", i32 4}
!1 = !{i32 2, !"Debug Info Version", i32 3}
!2 = !{i32 2, !"julia.optlevel", i32 2}
!3 = !{!4, !4, i64 0}
!4 = !{!"jtbaa_gcframe", !5, i64 0}
!5 = !{!"jtbaa", !6, i64 0}
!6 = !{!"jtbaa"}
!7 = !{!8, !8, i64 0, i64 1}
!8 = !{!"jtbaa_const", !5, i64 0}
!9 = !{}
!10 = !{!11}
!11 = !{!"jnoalias_const", !12}
!12 = !{!"jnoalias"}
!13 = !{!14, !15, !16, !17}
!14 = !{!"jnoalias_gcframe", !12}
!15 = !{!"jnoalias_stack", !12}
!16 = !{!"jnoalias_data", !12}
!17 = !{!"jnoalias_typemd", !12}
!18 = !{!19, !19, i64 0}
!19 = !{!"jtbaa_stack", !5, i64 0}
!20 = !{!15}
!21 = !{!14, !16, !17, !11}
!22 = !{!5, !5, i64 0}
!23 = !{!11, !15}
!24 = !{!14, !16, !17}
!25 = !{!14}
!26 = !{!15, !16, !17, !11}
!27 = !{i64 32}
!28 = !{i64 8}
!29 = !{!30, !30, i64 0}
!30 = !{!"jtbaa_immut", !31, i64 0}
!31 = !{!"jtbaa_value", !32, i64 0}
!32 = !{!"jtbaa_data", !5, i64 0}
!33 = !{!16}
!34 = !{!14, !15, !17, !11}
!35 = !{i64 1}
```
The `memcmp` inherits its alias info from the arguments, meaning it gets
`jtbaa_const` as its TBAA, and the `jnoalias_const` alias scope, with
every other scope in the noalias set. LLVM concludes the copies from the
arguments to the combined versions on the stack aren't read and deletes
them, leaving us with uninitialized stack memory where the non-pointers
should be.
```
Trying to eliminate MemoryDefs at the end of the function
Check if def 16 = MemoryDef(15) ( store i64 %31, ptr %29, align 8, !tbaa !22, !alias.scope !23, !noalias !24) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 15 = MemoryDef(14) ( store ptr addrspace(10) %28, ptr %8, align 8, !tbaa !18, !alias.scope !20, !noalias !21) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 14 = MemoryDef(13) ( store i64 %26, ptr %24, align 8, !tbaa !22, !alias.scope !23, !noalias !24) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 13 = MemoryDef(12) ( store ptr addrspace(10) %23, ptr %7, align 8, !tbaa !18, !alias.scope !20, !noalias !21) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 12 = MemoryDef(11) ( store i64 %22, ptr %20, align 8, !tbaa !22, !alias.scope !23, !noalias !24) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 11 = MemoryDef(10) ( store ptr addrspace(10) %19, ptr %9, align 8, !tbaa !18, !alias.scope !20, !noalias !21) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 10 = MemoryDef(9) ( store i64 %17, ptr %15, align 8, !tbaa !22, !alias.scope !23, !noalias !24) is at the end the function
... MemoryDef is not accessed until the end of the function
Check if def 9 = MemoryDef(8) ( store ptr addrspace(10) %14, ptr %6, align 8, !tbaa !18, !alias.scope !20, !noalias !21) is at the end the function
... MemoryDef is not accessed until the end of the function
```
Claude Fable 5 ran into this bug while I was having make a reproducer
for a different bug.