Skip to content

[clang] Skip auto-init on scalar vars that have a non-constant Init and no self-ref #94642

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion clang/lib/CodeGen/CGDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1972,7 +1972,21 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
}

if (!constant) {
initializeWhatIsTechnicallyUninitialized(Loc);
if (trivialAutoVarInit !=
LangOptions::TrivialAutoVarInitKind::Uninitialized) {
// At this point, we know D has an Init expression, but isn't a constant.
// - If D is not a scalar, auto-var-init conservatively (members may be
// left uninitialized by constructor Init expressions for example).
// - If D is a scalar, we only need to auto-var-init if there is a
// self-reference. Otherwise, the Init expression should be sufficient.
// It may be that the Init expression uses other uninitialized memory,
// but auto-var-init here would not help, as auto-init would get
// overwritten by Init.
if (!D.getType()->isScalarType() || capturedByInit ||
isAccessedBy(D, Init)) {
initializeWhatIsTechnicallyUninitialized(Loc);
}
}
LValue lv = MakeAddrLValue(Loc, type);
lv.setNonGC(true);
return EmitExprAsInit(Init, &D, lv, capturedByInit);
Expand Down
38 changes: 28 additions & 10 deletions clang/test/CodeGenCXX/auto-var-init-max-size.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ struct Foo {

int foo(unsigned n) {
bool var_size_1;
long var_size_8 = 123;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we have both the test cases in which scalar variables are auto-inited and where the initialization is skipped?
Something along the lines of:

long var_size_8_init = 123;
long var_size_8_noinit;
long var_size_8_init_later;
...
var_size_8_init_later = 456;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure! Added those cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

long var_size_8_init = 123;
long var_size_8_noinit;
long var_size_8_init_later;
void *var_size_8p;
int var_size_1024[256];
Foo var_size_1028;
Expand All @@ -24,29 +26,39 @@ int foo(unsigned n) {
int var_vla[n];
// builtin, non-constant size
var_size_8p = __builtin_alloca(sizeof(unsigned long long) * n);
// There are 8 variables: var_size_1, var_size_8, var_size_8p, var_size_1024,
var_size_8_init_later = 456;
// There are 10 variables: var_size_1, var_size_8_init, var_size_8_noinit,
// var_size_8_init_later, var_size_8p, var_size_1024,
// var_size_1028, var_size_4096, var_vla, and a builtin anonymous var ("%5").
// - Doesn't need auto-init: var_size_8_init
// - COMMON (auto-init regardless of the max size): "var_vla", and "%5"
// - Max size 1: "var_size_1"
// - Max size 1024: "var_size_1", "var_size_8", "var_size_8p", "var_size_1024"
// - Max size 4096: "var_size_1", "var_size_8", "var_size_8p", "var_size_1024", "var_size_1028", "var_size_4096"
// - Max size 1024: "var_size_1", "var_size_8_noinit", "var_size_8_init_later", "var_size_8p", "var_size_1024"
// - Max size 4096: "var_size_1", "var_size_8_noinit", "var_size_8_init_later", "var_size_8p", "var_size_1024",
// "var_size_1028", "var_size_4096"
//
// PATTERN-MAX-1: store i8 -86, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: store i64 -6148914691236517206, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NEXT: store i64 123, ptr %var_size_8_init, align 8
// PATTERN-MAX-1-NOT: store i64 -6148914691236517206, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: store i64 -6148914691236517206, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 -86, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 -86, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_4096, i8 -86, i64 4096, i1 false), !annotation [[AUTO_INIT:!.+]]

// PATTERN-MAX-1024: store i8 -86, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024: store i64 -6148914691236517206, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024-NEXT: store i64 123, ptr %var_size_8_init, align 8
// PATTERN-MAX-1024: store i64 -6148914691236517206, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024: store i64 -6148914691236517206, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 -86, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024-NOT: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 -86, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-1024-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_4096, i8 -86, i64 4096, i1 false), !annotation [[AUTO_INIT:!.+]]

// PATTERN-MAX-4096: store i8 -86, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096: store i64 -6148914691236517206, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096-NEXT: store i64 123, ptr %var_size_8_init, align 8
// PATTERN-MAX-4096: store i64 -6148914691236517206, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096: store i64 -6148914691236517206, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 -86, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// PATTERN-MAX-4096: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 -86, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
Expand All @@ -56,21 +68,27 @@ int foo(unsigned n) {
// PATTERN-COMMON: call void @llvm.memset.p0.i64(ptr align 16 %5, i8 -86, i64 %mul, i1 false), !annotation [[AUTO_INIT:!.+]]

// ZERO-MAX-1: store i8 0, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: store i64 0, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NEXT: store i64 123, ptr %var_size_8_init, align 8
// ZERO-MAX-1-NOT: store i64 0, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: store i64 0, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: store ptr null, ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 0, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 0, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_4096, i8 0, i64 4096, i1 false), !annotation [[AUTO_INIT:!.+]]

// ZERO-MAX-1024: store i8 0, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024: store i64 0, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024-NEXT: store i64 123, ptr %var_size_8_init, align 8
// ZERO-MAX-1024: store i64 0, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024: store i64 0, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024: store ptr null, ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 0, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024-NOT: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 0, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-1024-NOT: call void @llvm.memset.p0.i64(ptr align 16 %var_size_4096, i8 0, i64 4096, i1 false), !annotation [[AUTO_INIT:!.+]]

// ZERO-MAX-4096: store i8 0, ptr %var_size_1, align 1, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096: store i64 0, ptr %var_size_8, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096-NEXT: store i64 123, ptr %var_size_8_init, align 8
// ZERO-MAX-4096: store i64 0, ptr %var_size_8_noinit, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096: store i64 0, ptr %var_size_8_init_later, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096: store ptr null, ptr %var_size_8p, align 8, !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096: call void @llvm.memset.p0.i64(ptr align 16 %var_size_1024, i8 0, i64 1024, i1 false), !annotation [[AUTO_INIT:!.+]]
// ZERO-MAX-4096: call void @llvm.memset.p0.i64(ptr align 4 %var_size_1028, i8 0, i64 1028, i1 false), !annotation [[AUTO_INIT:!.+]]
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGenCXX/auto-var-init-stop-after.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ typedef struct {

int foo(unsigned n) {
// scalar variable
long a = 888;
long a;
// array
S arr[ARRLEN];
// VLA
Expand Down
8 changes: 0 additions & 8 deletions clang/test/CodeGenCXX/auto-var-init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,16 +146,8 @@ struct notlockfree { long long a[4]; };
// PATTERN-O1-NOT: @__const.test_atomictailpad_uninit.uninit
// PATTERN-O0: @__const.test_complexfloat_uninit.uninit = private unnamed_addr constant { float, float } { float 0xFFFFFFFFE0000000, float 0xFFFFFFFFE0000000 }, align 4
// PATTERN-O1-NOT: @__const.test_complexfloat_uninit.uninit
// PATTERN-O0: @__const.test_complexfloat_braces.braces = private unnamed_addr constant { float, float } { float 0xFFFFFFFFE0000000, float 0xFFFFFFFFE0000000 }, align 4
// PATTERN-O1-NOT: @__const.test_complexfloat_braces.braces
// PATTERN-O0: @__const.test_complexfloat_custom.custom = private unnamed_addr constant { float, float } { float 0xFFFFFFFFE0000000, float 0xFFFFFFFFE0000000 }, align 4
// PATTERN-O1-NOT: @__const.test_complexfloat_custom.custom
// PATTERN-O0: @__const.test_complexdouble_uninit.uninit = private unnamed_addr constant { double, double } { double 0xFFFFFFFFFFFFFFFF, double 0xFFFFFFFFFFFFFFFF }, align 8
// PATTERN-O1-NOT: @__const.test_complexdouble_uninit.uninit
// PATTERN-O0: @__const.test_complexdouble_braces.braces = private unnamed_addr constant { double, double } { double 0xFFFFFFFFFFFFFFFF, double 0xFFFFFFFFFFFFFFFF }, align 8
// PATTERN-O1-NOT: @__const.test_complexdouble_braces.braces
// PATTERN-O0: @__const.test_complexdouble_custom.custom = private unnamed_addr constant { double, double } { double 0xFFFFFFFFFFFFFFFF, double 0xFFFFFFFFFFFFFFFF }, align 8
// PATTERN-O1-NOT: @__const.test_complexdouble_custom.custom
// PATTERN-O0: @__const.test_semivolatile_uninit.uninit = private unnamed_addr constant %struct.semivolatile { i32 [[I32]], i32 [[I32]] }, align 4
// PATTERN-O0: @__const.test_semivolatile_custom.custom = private unnamed_addr constant %struct.semivolatile { i32 1145324612, i32 1145324612 }, align 4
// PATTERN-O1-NOT: @__const.test_semivolatile_custom.custom
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// RUN: %clang_cc1 -triple x86_64-unknown-unknown %s -emit-llvm -o - | FileCheck %s -check-prefix=UNINIT
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s -check-prefix=PATTERN
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s -check-prefix=ZERO

template<typename T> void used(T &) noexcept;

extern "C" {

extern int get_int(int) noexcept;
struct C {
int x;
int y;
};
extern C make_c() noexcept;

// Scalar with a self-reference: does need auto-init.
// UNINIT-LABEL: test_selfinit_call(
// ZERO-LABEL: test_selfinit_call(
// ZERO: store i32 0, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_selfinit_call(
// PATTERN: store i32 -1431655766, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
void test_selfinit_call() {
int self = get_int(self);
used(self);
}

// Scalar without a self-reference: no auto-init needed.
// UNINIT-LABEL: test_nonself_call(
// ZERO-LABEL: test_nonself_call(
// ZERO-NOT: !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_nonself_call(
// PATTERN-NOT: !annotation [[AUTO_INIT:!.+]]
void test_nonself_call() {
int x = get_int(2);
used(x);
}

// Scalar with a self-reference: does need auto-init.
// UNINIT-LABEL: test_selfinit_lambda_call(
// ZERO-LABEL: test_selfinit_lambda_call(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, if "UNINIT-LABEL", "ZERO-LABEL", and "PATTERN-LABEL" are always the same, just define a common label like "CHECK-LABEL:" to avoid redundancy?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if a common "CHECK-LABEL:" would have the same semantics and help split the stream for matching when there is a different prefix like "ZERO: " (or "PATTERN:") that follows, or if the CHECK and the ZERO end up as a independent scans. If it's an independent scan, then "CHECK-LABEL" wouldn't have the same semantics as "ZERO-LABEL:" (which helps split the stream for the "ZERO:" checks that follow -- e.g., in case there a multiple places that could match something generic like "store i32 0, ", the preceding ZERO-LABEL help disambiguate which to match).

I'd prefer to keep the redundancy just to be safe and more clear that the X-LABEL are for the X that it's associated with, unless you're pretty sure it can cross over different check prefixes.

// ZERO: store i32 0, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_selfinit_lambda_call(
// PATTERN: store i32 -1431655766, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
void test_selfinit_lambda_call() {
int self = [&](){ return self; }();
used(self);
}

// Scalar with a self-reference: does need auto-init.
// UNINIT-LABEL: test_selfinit_gnu_stmt_expression(
// ZERO-LABEL: test_selfinit_gnu_stmt_expression(
// ZERO: store i32 0, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_selfinit_gnu_stmt_expression(
// PATTERN: store i32 -1431655766, ptr %self, align 4, !annotation [[AUTO_INIT:!.+]]
void test_selfinit_gnu_stmt_expression() {
int self = ({int x = self; x + 1; });
used(self);
}

// Not a scalar: auto-init just in case
// UNINIT-LABEL: test_nonscalar_call(
// ZERO-LABEL: test_nonscalar_call(
// ZERO: call void @llvm.memset{{.*}}, i8 0, i64 8, {{.*}} !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_nonscalar_call(
// PATTERN: call void @llvm.memcpy{{.*}}, i64 8, {{.*}} !annotation [[AUTO_INIT:!.+]]
void test_nonscalar_call() {
C c = make_c();
used(c);
}

// Scalar with a self-reference: does need auto-init.
// UNINIT-LABEL: test_self_ptr(
// ZERO-LABEL: test_self_ptr(
// ZERO: store ptr null, ptr %self, align 8, !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_self_ptr(
// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %self, align 8, !annotation [[AUTO_INIT:!.+]]
void test_self_ptr() {
void* self = self;
used(self);
}

// Scalar without a self-reference: no auto-init needed.
// UNINIT-LABEL: test_nonself_ptr(
// ZERO-LABEL: test_nonself_ptr(
// ZERO-NOT: !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_nonself_ptr(
// PATTERN-NOT: !annotation [[AUTO_INIT:!.+]]
void test_nonself_ptr() {
int y = 0;
void* x = &y;
used(x);
}

// Scalar with a self-reference: does need auto-init.
// UNINIT-LABEL: test_self_complex(
// ZERO-LABEL: test_self_complex(
// ZERO: call void @llvm.memset{{.*}} !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_self_complex(
// PATTERN: call void @llvm.memcpy{{.*}} !annotation [[AUTO_INIT:!.+]]
void test_self_complex() {
_Complex float self = 3.0 * 3.0 * self;
used(self);
}

// Scalar without a self-reference: no auto-init needed.
// UNINIT-LABEL: test_nonself_complex(
// ZERO-LABEL: test_nonself_complex(
// ZERO-NOT: !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_nonself_complex(
// PATTERN-NOT: !annotation [[AUTO_INIT:!.+]]
void test_nonself_complex() {
_Complex float y = 0.0;
_Complex float x = 3.0 * 3.0 * y;
used(x);
}

} // extern "C"

// ZERO: [[AUTO_INIT]] = !{!"auto-init"}
// PATTERN: [[AUTO_INIT]] = !{!"auto-init"}

Loading