llvm-project/clang/test/CodeGen/paren-list-agg-init.cpp
Alan Zhao 95a4c0c835 [clang] Reland parenthesized aggregate init patches
This commit relands the patches for implementing P0960R3 and P1975R0,
which describe initializing aggregates via a parenthesized list.

The relanded commits are:

* 40c52159d3ee - P0960R3 and P1975R0: Allow initializing aggregates from
  a parenthesized list of values
* c77a91bb7ba7 - Remove overly restrictive aggregate paren init logic
* 32d7aae04fdb - Fix a clang crash on invalid code in C++20 mode

This patch also fixes a crash in the original implementation.
Previously, if the input tried to call an implicitly deleted copy or
move constructor of a union, we would then try to initialize the union
by initializing it's first element with a reference to a union. This
behavior is incorrect (we should fail to initialize) and if the type of
the first element has a constructor with a single template typename
parameter, then Clang will explode. This patch fixes that issue by
checking that constructor overload resolution did not result in a
deleted function before attempting parenthesized aggregate
initialization.

Additionally, this patch also includes D140159, which contains some
minor fixes made in response to code review comments in the original
implementation that were made after that patch was submitted.

Co-authored-by: Sheng <ox59616e@gmail.com>

Fixes #54040, Fixes #59675

Reviewed By: ilya-biryukov

Differential Revision: https://reviews.llvm.org/D141546
2023-01-12 09:58:15 -08:00

352 lines
15 KiB
C++

// RUN: %clang_cc1 -std=c++20 %s -emit-llvm -triple x86_64-unknown-linux-gnu -o - | FileCheck %s
template <typename T>
struct IsChar {
constexpr operator bool() const { return false; }
};
template<>
struct IsChar<char> {
constexpr operator bool() const { return true; }
};
template <typename T>
concept SameAsChar = (bool)IsInt<T>();
// CHECK-DAG: [[STRUCT_A:%.*]] = type { i8, double }
struct A {
char i;
double j;
template <SameAsChar T>
operator T() const { return i; };
};
// CHECK-DAG: [[STRUCT_B:%.*]] = type { [[STRUCT_A]], i32 }
struct B {
A a;
int b;
};
// CHECK-DAG: [[STRUCT_C:%.*]] = type <{ [[STRUCT_B]], [[STRUCT_A]], i32, [4 x i8] }>
struct C : public B, public A {
int c;
};
// CHECK-DAG: [[STRUCT_D:%.*]] = type { [[STRUCT_A]], [[STRUCT_A]], i8, [[STRUCT_A]] }
struct D {
A a;
A b = A{2, 2.0};
unsigned : 2;
A c;
};
// CHECK-DAG: [[STRUCT_E:%.*]] = type { i32, ptr }
struct E {
int a;
const char* fn = __builtin_FUNCTION();
~E() {};
};
// CHECK-DAG: [[STRUCT_F:%.*]] = type { i8 }
struct F {
F (int i = 1);
F (const F &f) = delete;
F (F &&f) = default;
};
// CHECK-DAG: [[STRUCT_G:%.*]] = type <{ i32, [[STRUCT_F]], [3 x i8] }>
struct G {
int a;
F f;
};
// CHECK-DAG: [[UNION_U:%.*]] = type { [[STRUCT_A]] }
// CHECK-DAG: [[STR:@.*]] = private unnamed_addr constant [6 x i8] {{.*}}foo18{{.*}}, align 1
union U {
unsigned : 1;
A a;
char b;
};
// CHECK-DAG: [[A1:@.*a1.*]] = internal constant [[STRUCT_A]] { i8 3, double 2.000000e+00 }, align 8
constexpr A a1(3.1, 2.0);
// CHECK-DAG: [[A2:@.*a2.*]] = internal constant [[STRUCT_A]] { i8 99, double 0.000000e+00 }, align 8
constexpr auto a2 = static_cast<A>('c');
// CHECK-DAG: [[B1:@.*b1.*]] = internal constant [[STRUCT_B]] { [[STRUCT_A]] { i8 99, double 0.000000e+00 }, i32 0 }, align 8
constexpr B b1(A('c'));
// CHECK-DAG: [[C1:@.*c1.*]] = internal constant { [[STRUCT_A]], i32, [4 x i8], i8, double, i32 } { [[STRUCT_A]] { i8 99, double 0.000000e+00 }, i32 0, [4 x i8] undef, i8 3, double 2.000000e+00, i32 0 }, align
constexpr C c1(b1, a1);
// CHECK-DAG: [[U1:@.*u1.*]] = internal constant [[UNION_U]] { [[STRUCT_A]] { i8 1, double 1.000000e+00 } }, align 8
constexpr U u1(A(1, 1));
// CHECK-DAG: [[D1:@.*d1.*]] = internal constant { [[STRUCT_A]], [[STRUCT_A]], [8 x i8], [[STRUCT_A]] } { [[STRUCT_A]] { i8 2, double 2.000000e+00 }, [[STRUCT_A]] { i8 2, double 2.000000e+00 }, [8 x i8] undef, [[STRUCT_A]] zeroinitializer }, align 8
constexpr D d1(A(2, 2));
// CHECK-DAG: [[ARR1:@.*arr1.*]] = internal constant [3 x i32] [i32 1, i32 2, i32 0], align 4
constexpr int arr1[3](1, 2);
// CHECK-DAG: [[ARR4:@.*arr4.*]] = internal constant [1 x i32] [i32 1], align 4
constexpr int arr4[](1);
// CHECK-DAG: [[ARR5:@.*arr5.*]] = internal constant [2 x i32] [i32 2, i32 0], align 4
constexpr int arr5[2](2);
// CHECK: define dso_local { i8, double } @{{.*foo1.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_A]], align 8
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[RETVAL]], ptr align 8 [[A1]], i64 16, i1 false)
// CHECK-NEXT: [[TMP_0:%.*]] = load { i8, double }, ptr [[RETVAL]], align 8
// CHECK-NEXT: ret { i8, double } [[TMP_0]]
A foo1() {
return a1;
}
// CHECK: define dso_local void @{{.*foo2.*}}(ptr noalias sret([[STRUCT_B]]) align 8 [[AGG_RESULT:%.*]])
// CHECK-NEXT: entry:
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_RESULT]], ptr align 8 [[B1]], i64 24, i1 false)
// CHECK-NEXT: ret void
B foo2() {
return b1;
}
// CHECK: define dso_local void @{{.*foo3.*}}(ptr noalias sret([[STRUCT_C]]) align 8 [[AGG_RESULT:%.*]])
// CHECK-NEXT: entry:
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_RESULT]], ptr align 8 [[C1]], i64 48, i1 false)
// CHECK-NEXT: ret void
C foo3() {
return c1;
}
// CHECK: define dso_local void @{{.*foo4.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[C2:%.*]] = alloca [[STRUCT_C:%.*]], align 8
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [[STRUCT_B:%.*]], align 8
// CHECK-NEXT: [[REF_TMP_1:%.*]] = alloca [[STRUCT_A:%.*]], align 8
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds [[STRUCT_B]], ptr [[REF_TMP]], i32 0, i32 0
// CHECK-NEXT: [[I:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 0
// CHECK-NEXT: store i8 1, ptr [[I]], align 8
// CHECK-NEXT: [[J:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 1
// CHECK-NEXT: store double 1.000000e+00, ptr [[J]], align 8
// CHECK-NEXT: [[B:%.*]] = getelementptr inbounds [[STRUCT_B]], ptr [[REF_TMP]], i32 0, i32 1
// CHECK-NEXT: store i32 1, ptr [[B]], align 8
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[C2]], ptr align 8 [[REF_TMP]], i64 24, i1 false)
// CHECK-NEXT: [[TMP_0:%.*]] = getelementptr inbounds i8, ptr [[C2]], i64 24
// CHECK-NEXT: [[I2:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[REF_TMP_1]], i32 0, i32 0
// CHECK-NEXT: store i8 97, ptr [[I2]], align 8
// CHECK-NEXT: [[J3:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[REF_TMP_1]], i32 0, i32 1
// CHECK-NEXT: store double 0.000000e+00, ptr [[J3]], align 8
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[TMP_0]], ptr align 8 [[REF_TMP_1]], i64 16, i1 false)
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds %struct.C, ptr %c2, i32 0, i32 2
// CHECK-NEXT: store i32 2, ptr %c, align 8
// CHECK-NEXT: ret void
void foo4() {
C c2(B(A(1, 1), 1), A('a'), 2);
}
// CHECK: define dso_local { i64, double } @{{.*foo5.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT [[RETVAL:%.*]] = alloca [[UNION_U]], align 8
// CHECK-NEXT call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[RETVAL]], ptr align 8 [[U1]], i64 16, i1 false)
// CHECK-NEXT [[COERCE_DIVE:%.*]] = getelementptr inbounds [[UNION_U]], ptr %retval, i32 0, i32 0
// CHECK-NEXT [[TMP_0:%.*]] = load { i64, double }, ptr [[COERCE_DIVE]], align 8
// CHECK-NEXT ret { i64, double } [[TMP_0]]
U foo5() {
return u1;
}
// CHECK: define dso_local { i64, double } @{{.*foo6.*}}(i8 [[A_COERCE_0:%.*]], double [[A_COERCE_1:%.*]])
// CHECK-NEXT: entry:
// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[UNION_U]], align 8
// CHECK-NEXT: [[A:%.*]] = alloca [[STRUCT_A]], align 8
// CHECK-NEXT: [[TMP_0:%.*]] = getelementptr inbounds { i8, double }, ptr [[A]], i32 0, i32 0
// CHECK-NEXT: store i8 [[A_COERCE_0]], ptr [[TMP_0]], align 8
// CHECK-NEXT: [[TMP_1:%.*]] = getelementptr inbounds { i8, double }, ptr [[A]], i32 0, i32 1
// CHECK-NEXT: store double [[A_COERCE_1]], ptr [[TMP_1]], align 8
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[RETVAL]], ptr align 8 [[A]], i64 16, i1 false)
// CHECK-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds [[UNION_U]], ptr [[RETVAL]], i32 0, i32 0
// CHECK-NEXT: [[TMP_2:%.*]] = load { i64, double }, ptr [[COERCE_DIVE:%.*]], align 8
// CHECK-NEXT: ret { i64, double } [[TMP_2]]
U foo6(A a) {
return U(a);
}
// CHECK: define dso_local void @{{.*foo7.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[D:%.*]] = alloca [[STRUCT_D:%.*]], align 8
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 0
// CHECK-NEXT: [[I]] = getelementptr inbounds [[STRUCT_A:%.*]], ptr [[A]], i32 0, i32 0
// CHECK-NEXT: store i8 1, ptr [[I]], align 8
// CHECK-NEXT: [[J:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 1
// CHECK-NEXT: store double 1.000000e+00, ptr [[J]], align 8
// CHECK-NEXT: [[B:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 1
// CHECK-NEXT: [[I1:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[B]], i32 0, i32 0
// CHECK-NEXT: store i8 11, ptr [[I1]], align 8
// CHECK-NEXT: [[J2:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[B]], i32 0, i32 1
// CHECK-NEXT: store double 1.100000e+01, ptr [[J2]], align 8
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 3
// CHECK-NEXT: [[I3:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[C]], i32 0, i32 0
// CHECK-NEXT: store i8 111, ptr [[I3]], align 8
// CHECK-NEXT: [[J4:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[C]], i32 0, i32 1
// CHECK-NEXT: store double 1.110000e+02, ptr [[J4]], align 8
// CHECK-NEXT: ret void
void foo7() {
D d(A(1, 1), A(11, 11), A(111, 111));
}
// CHECK: dso_local void @{{.*foo8.*}}(ptr noalias sret([[STRUCT_D]]) align 8 [[AGG_RESULT:%.*]])
// CHECK-NEXT: entry:
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_RESULT]], ptr align 8 [[D1]], i64 56, i1 false)
// CHECK-NEXT: ret void
D foo8() {
return d1;
}
// CHECK: define dso_local void @{{.*foo9.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[D:%.*]] = alloca [[STRUCT_D:%.*]], align 8
// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 0
// CHECK-NEXT: [[I]] = getelementptr inbounds [[STRUCT_A:%.*]], ptr [[A]], i32 0, i32 0
// CHECK-NEXT: store i8 1, ptr [[I]], align 8
// CHECK-NEXT: [[J:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[A]], i32 0, i32 1
// CHECK-NEXT: store double 1.000000e+00, ptr [[J]], align 8
// CHECK-NEXT: [[B:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 1
// CHECK-NEXT: [[I1:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[B]], i32 0, i32 0
// CHECK-NEXT: store i8 2, ptr [[I1]], align 8
// CHECK-NEXT: [[J2:%.*]] = getelementptr inbounds [[STRUCT_A]], ptr [[B]], i32 0, i32 1
// CHECK-NEXT: store double 2.000000e+00, ptr [[J2]], align 8
// CHECK-NEXT: [[C:%.*]] = getelementptr inbounds [[STRUCT_D]], ptr [[D]], i32 0, i32 3
// CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 8 [[C]], i8 0, i64 16, i1 false)
// CHECK-NEXT: ret void
void foo9() {
D d(A(1, 1));
}
// CHECK: define dso_local noundef ptr @{{.*foo10.*}}()
// CHECK-NEXT: entry:
// CHECK-NEXT: ret ptr [[ARR1]]
const int* foo10() {
return arr1;
}
// CHECK: define dso_local void @{{.*foo11.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[ARR_2:%.*]] = alloca [4 x i32], align 16
// CHECK-NEXT: store i32 [[A:%.*]], ptr [[A_ADDR]], align 4
// CHECK-NEXT: store i32 [[B:%.*]], ptr [[B_ADDR]], align 4
// CHECK-NEXT: [[ARRINIT_BEGIN:%.*]] = getelementptr inbounds [4 x i32], ptr [[ARR_2]], i64 0, i64 0
// CHECK-NEXT: [[TMP_0:%.*]] = load i32, ptr [[A_ADDR]], align 4
// CHECK-NEXT: store i32 [[TMP_0]], ptr [[ARRINIT_BEGIN]], align 4
// CHECK-NEXT: [[ARRINIT_ELEM:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_BEGIN]], i64 1
// CHECK-NEXT: [[TMP_1:%.*]] = load i32, ptr [[B_ADDR]], align 4
// CHECK-NEXT: store i32 [[TMP_1]], ptr [[ARRINIT_ELEM]], align 4
// CHECK-NEXT: [[ARRINIT_START:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_ELEM]], i64 1
// CHECK-NEXT: [[ARRINIT_END:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_BEGIN]], i64 4
// CHECK-NEXT: br label [[ARRINIT_BODY:%.*]]
// CHECK: [[ARRINIT_CUR:%.*]] = phi ptr [ [[ARRINIT_START]], %entry ], [ [[ARRINIT_NEXT:%.*]], [[ARRINIT_BODY]] ]
// CHECK-NEXT: store i32 0, ptr [[ARRINIT_CUR]], align 4
// CHECK-NEXT: [[ARRINIT_NEXT]] = getelementptr inbounds i32, ptr [[ARRINIT_CUR]], i64 1
// CHECK-NEXT: [[ARRINIT_DONE:%.*]] = icmp eq ptr [[ARRINIT_NEXT]], [[ARRINIT_END:%.*]]
// CHECK-NEXT: br i1 [[ARRINIT_DONE]], label [[ARRINIT_END1:%.*]], label [[ARRINIT_BODY]]
// CHECK: ret void
void foo11(int a, int b) {
int arr2[4](a, b);
}
// CHECK: define dso_local void @{{.*foo12.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[A_ADDR:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[B_ADDR:%.*]] = alloca i32, align 4
// CHECK-NEXT: [[ARR_3:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: store i32 [[A:%.*]], ptr [[A_ADDR]], align 4
// CHECK-NEXT: store i32 [[B:%.*]], ptr [[B_ADDR]], align 4
// CHECK-NEXT: [[ARRINIT_BEGIN:%.*]] = getelementptr inbounds [2 x i32], ptr [[ARR_3]], i64 0, i64 0
// CHECK-NEXT: [[TMP_0:%.*]] = load i32, ptr [[A_ADDR]], align 4
// CHECK-NEXT: store i32 [[TMP_0]], ptr [[ARRINIT_BEGIN]], align 4
// CHECK-NEXT: [[ARRINIT_ELEMENT:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_BEGIN]], i64 1
// CHECK-NEXT: [[TMP_1:%.*]] = load i32, ptr [[B_ADDR]], align 4
// CHECK-NEXT: store i32 [[TMP_1]], ptr [[ARRINIT_ELEMENT]], align 4
// CHECK-NEXT: ret void
void foo12(int a, int b) {
int arr3[](a, b);
}
// CHECK: define dso_local { i8, double } @{{.*foo13.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[RETVAL:%.*]] = alloca [[STRUCT_A]], align 8
// CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[RETVAL]], ptr align 8 [[A2]], i64 16, i1 false)
// CHECK-NEXT: [[TMP_0:%.*]] = load { i8, double }, ptr [[RETVAL]], align 8
// CHECK-NEXT: ret { i8, double } [[TMP_0]]
A foo13() {
return a2;
}
// CHECK: define dso_local noundef ptr @{{.*foo14.*}}()
// CHECK-NEXT: entry:
// CHECK-NEXT: ret ptr [[ARR4]]
const int* foo14() {
return arr4;
}
// CHECK: define dso_local noundef ptr @{{.*foo15.*}}()
// CHECK-NEXT: entry:
// CHECK-NEXT: ret ptr [[ARR5]]
const int* foo15() {
return arr5;
}
// CHECK: define dso_local void @{{.*foo16.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[ARR_6:%.*arr6.*]] = alloca ptr, align 8
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [1 x i32], align 4
// CHECK-NEXT: [[ARRINIT_BEGIN:%.*]] = getelementptr inbounds [1 x i32], ptr [[REF_TMP]], i64 0, i64 0
// CHECK-NEXT: store i32 3, ptr [[ARRINIT_BEGIN]], align 4
// CHECK-NEXT: store ptr [[REF_TMP]], ptr [[ARR_6]], align 8
// CHECK-NEXT: ret void
void foo16() {
int (&&arr6)[] = static_cast<int[]>(3);
}
// CHECK: define dso_local void @{{.*foo17.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[ARR_7:%.*arr7.*]] = alloca ptr, align 8
// CHECK-NEXT: [[REF_TMP:%.*]] = alloca [2 x i32], align 4
// CHECK-NEXT: [[ARRINIT_BEGIN:%.*]] = getelementptr inbounds [2 x i32], ptr [[REF_TMP]], i64 0, i64 0
// CHECK-NEXT: store i32 4, ptr [[ARRINIT_BEGIN]], align 4
// CHECK-NEXT: [[ARRINIT_START:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_BEGIN]], i64 1
// CHECK-NEXT: [[ARRINIT_END:%.*]] = getelementptr inbounds i32, ptr [[ARRINIT_BEGIN]], i64 2
// CHECK-NEXT: br label [[ARRINIT_BODY]]
// CHECK: [[ARRINIT_CUR:%.*]] = phi ptr [ [[ARRINIT_START]], %entry ], [ [[ARRINIT_NEXT:%.*]], [[ARRINIT_BODY]] ]
// CHECK-NEXT: store i32 0, ptr [[ARRINIT_CUR]], align 4
// CHECK-NEXT: [[ARRINIT_NEXT]] = getelementptr inbounds i32, ptr [[ARRINIT_CUR]], i64 1
// CHECK-NEXT: [[ARRINIT_DONE:%.*]] = icmp eq ptr [[ARRINIT_NEXT]], [[ARRINIT_END:%.*]]
// CHECK-NEXT: br i1 [[ARRINIT_DONE]], label [[ARRINIT_END1:%.*]], label [[ARRINIT_BODY]]
// CHECK: store ptr [[REF_TMP]], ptr [[ARR_7]], align 8
// CHECK: ret void
void foo17() {
int (&&arr7)[2] = static_cast<int[2]>(4);
}
// CHECK: define dso_local void @{{.*foo18.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[E:%.*e.*]] = alloca [[STRUCT_E]], align 8
// CHECK-NEXT: [[A:%.*a.*]] = getelementptr inbounds [[STRUCT_E]], ptr [[E]], i32 0, i32 0
// CHECK-NEXT: store i32 1, ptr [[A]], align 8
// CHECK-NEXT: [[FN:%.*fn.*]] = getelementptr inbounds [[STRUCT_E]], ptr [[E]], i32 0, i32 1
// CHECK-NEXT: store ptr [[STR]], ptr [[FN]], align 8
// CHECK: ret void
void foo18() {
E e(1);
}
// CHECK: define dso_local void @{{.*foo19.*}}
// CHECK-NEXT: entry:
// CHECK-NEXT: [[G:%.*g.*]] = alloca [[STRUCT_G]], align 4
// CHECK-NEXT: [[A:%.*a.*]] = getelementptr inbounds [[STRUCT_G]], ptr [[G]], i32 0, i32 0
// CHECK-NEXT: store i32 2, ptr [[A]], align 4
// CHECK-NEXT: [[F:%.*f.*]] = getelementptr inbounds [[STRUCT_G]], ptr [[G]], i32 0, i32 1
// CHECk-NEXT: call void @{{.*F.*}}(ptr noundef nonnull align 1 dereferenceable(1)) [[F]], ie32 noundef 1)
// CHECK: ret void
void foo19() {
G g(2);
}