[clang] [Clang] Initialize bypassed variables w/ trivial-auto-var-init (PR #181937)
Justin Stitt via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 10 14:06:41 PDT 2026
https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/181937
>From bebcabea3a3c1bfddcd246790d8d174891750f36 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Tue, 17 Feb 2026 15:18:03 -0800
Subject: [PATCH 1/5] [Clang] Always initialize bypassed variables with
-ftrivial-auto-var-init
When -ftrivial-auto-var-init=zero or -ftrivial-auto-var-init=pattern is
enabled, variables whose declarations are bypassed by goto or switch
statements were silently left uninitialized. This patch ensures they are
initialized, matching GCC 16's behavior.
The initialization is emitted at the jump source rather than the jump
target. This ensures correctness in loops: a goto whose source and
destination are both inside the variable's scope does not spuriously
reinitialize it, while a goto that actually bypasses the declaration
does. For computed gotos (where jump sources cannot be determined
statically), we fall back to initializing in the entry block.
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/lib/CodeGen/CGDebugInfo.cpp | 7 +
clang/lib/CodeGen/CGDecl.cpp | 72 +++-
clang/lib/CodeGen/CGStmt.cpp | 16 +
clang/lib/CodeGen/CodeGenFunction.cpp | 9 +-
clang/lib/CodeGen/CodeGenFunction.h | 26 ++
clang/lib/CodeGen/VarBypassDetector.cpp | 36 +-
clang/lib/CodeGen/VarBypassDetector.h | 20 +-
.../trivial-auto-var-init-c-backward-goto.c | 236 ++++++++++
.../test/CodeGenCXX/trivial-auto-var-init.cpp | 406 ++++++++++++++++--
9 files changed, 789 insertions(+), 39 deletions(-)
create mode 100644 clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 02864621d60a3..6a636e431f521 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -6802,6 +6802,13 @@ CodeGenFunction::LexicalScope::LexicalScope(CodeGenFunction &CGF,
SourceRange Range)
: RunCleanupsScope(CGF), Range(Range), ParentScope(CGF.CurLexicalScope) {
CGF.CurLexicalScope = this;
+ // Record the block this scope is entered through, used to initialize
+ // potentially bypassed variables under -ftrivial-auto-var-init, adhereing to
+ // C6.2.4p6. Also see CodeGenFunction::EmitAutoVarAlloca(). A scope with no
+ // insertion point (e.g. a switch body) inherits its parent's.
+ EntryBlock = CGF.Builder.GetInsertBlock();
+ if (!EntryBlock && ParentScope)
+ EntryBlock = ParentScope->EntryBlock;
if (CGDebugInfo *DI = CGF.getDebugInfo())
DI->EmitLexicalBlockStart(CGF.Builder, Range.getBegin());
}
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 1ed8989d3f627..2e6f2b4b9bea1 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1644,6 +1644,44 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) {
}
}
+ // A variable whose declaration is bypassed by a goto or switch is not
+ // initialized by EmitAutoVarInit (that runs at the declaration). Emit the
+ // trivial-auto-var-init separately, following each language's lifetime
+ // rules.
+ if (Bypasses.IsBypassed(&D) && !emission.IsEscapingByRef &&
+ !Ty->isVariablyModifiedType() &&
+ getAutoVarInitKind(Ty, D) !=
+ LangOptions::TrivialAutoVarInitKind::Uninitialized) {
+ if (getLangOpts().CPlusPlus && !Bypasses.isAlwaysBypassed()) {
+ // C++ [basic.stc.auto]: the lifetime restarts on each scope re-entry,
+ // so reinitialize at every bypassing jump. Switch cases and backward
+ // gotos are emitted at the jump source (after this alloca); forward
+ // gotos already emitted are patchd before their branch.
+ BypassedVarInits.insert({&D, address});
+ for (const BypassingForwardGoto &FG : BypassingForwardGotos) {
+ const auto *Vars = Bypasses.getBypassedVarsForSource(FG.Goto);
+ if (Vars && Vars->contains(&D))
+ if (llvm::Instruction *Term = FG.Block->getTerminator()) {
+ llvm::IRBuilderBase::InsertPointGuard IPG(Builder);
+ Builder.SetInsertPoint(Term);
+ emitZeroOrPatternForAutoVarInit(Ty, D, address);
+ }
+ }
+ } else {
+ // C (C6.2.4p6) and computed gotos: the lifetime begins at entry into
+ // the enclosing block, so initialize at that block's entry once for a
+ // function-scoped variable and every iteration for a loop-scoped one.
+ llvm::BasicBlock *Entry =
+ CurLexicalScope ? CurLexicalScope->getEntryBlock() : nullptr;
+ llvm::IRBuilderBase::InsertPointGuard IPG(Builder);
+ if (Entry && Entry->getTerminator())
+ Builder.SetInsertPoint(Entry->getTerminator());
+ else
+ Builder.SetInsertPoint(getPostAllocaInsertPoint());
+ emitZeroOrPatternForAutoVarInit(Ty, D, address);
+ }
+ }
+
if (D.hasAttr<StackProtectorIgnoreAttr>()) {
if (auto *AI = dyn_cast<llvm::AllocaInst>(address.getBasePointer())) {
llvm::LLVMContext &Ctx = Builder.getContext();
@@ -1841,6 +1879,31 @@ bool CodeGenFunction::isTrivialInitializer(const Expr *Init) {
return false;
}
+LangOptions::TrivialAutoVarInitKind
+CodeGenFunction::getAutoVarInitKind(QualType type, const VarDecl &D) {
+ auto hasNoTrivialAutoVarInitAttr = [](const Decl *D) {
+ return D && D->hasAttr<NoTrivialAutoVarInitAttr>();
+ };
+ if (D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
+ hasNoTrivialAutoVarInitAttr(type->getAsTagDecl()) ||
+ hasNoTrivialAutoVarInitAttr(CurFuncDecl))
+ return LangOptions::TrivialAutoVarInitKind::Uninitialized;
+ return getContext().getLangOpts().getTrivialAutoVarInit();
+}
+
+void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) {
+ const auto *Vars = Bypasses.getBypassedVarsForSource(Source);
+ if (!Vars)
+ return;
+ for (const VarDecl *VD : *Vars) {
+ auto It = BypassedVarInits.find(VD);
+ if (It != BypassedVarInits.end()) {
+ QualType Ty = VD->getType().getNonReferenceType();
+ emitZeroOrPatternForAutoVarInit(Ty, *VD, It->second);
+ }
+ }
+}
+
void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type,
const VarDecl &D,
Address Loc) {
@@ -2000,16 +2063,9 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
const Address Loc =
locIsByrefHeader ? emission.getObjectAddress(*this) : emission.Addr;
- auto hasNoTrivialAutoVarInitAttr = [&](const Decl *D) {
- return D && D->hasAttr<NoTrivialAutoVarInitAttr>();
- };
// Note: constexpr already initializes everything correctly.
LangOptions::TrivialAutoVarInitKind trivialAutoVarInit =
- ((D.isConstexpr() || D.getAttr<UninitializedAttr>() ||
- hasNoTrivialAutoVarInitAttr(type->getAsTagDecl()) ||
- hasNoTrivialAutoVarInitAttr(CurFuncDecl))
- ? LangOptions::TrivialAutoVarInitKind::Uninitialized
- : getContext().getLangOpts().getTrivialAutoVarInit());
+ getAutoVarInitKind(type, D);
auto initializeWhatIsTechnicallyUninitialized = [&](Address Loc) {
if (trivialAutoVarInit ==
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index bf6e6eb50f555..db74e6e830485 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -855,6 +855,14 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
if (HaveInsertPoint())
EmitStopPoint(&S);
+ // For C++ scope re-entry we need to reinitialize variables this goto
+ // bypasses. Backward gotos reinit here while forward gotos are recorded for
+ // EmitAutoVarAlloca to patch once the alloca exists.
+ if (HaveInsertPoint() && getLangOpts().CPlusPlus) {
+ emitBypassedVarInitsForSource(&S);
+ BypassingForwardGotos.push_back({Builder.GetInsertBlock(), &S});
+ }
+
ApplyAtomGroup Grp(getDebugInfo());
EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel()));
}
@@ -1793,6 +1801,8 @@ void CodeGenFunction::EmitCaseStmtRange(const CaseStmt &S,
// switch machinery to enter this block.
llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, &S);
+ if (getLangOpts().CPlusPlus)
+ emitBypassedVarInitsForSource(&S);
EmitStmt(S.getSubStmt());
// If range is empty, do nothing.
@@ -1931,6 +1941,8 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S,
llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, &S);
+ if (getLangOpts().CPlusPlus)
+ emitBypassedVarInitsForSource(&S);
if (SwitchWeights)
SwitchWeights->push_back(getProfileCount(&S));
SwitchInsn->addCase(CaseVal, CaseDest);
@@ -1963,6 +1975,8 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S,
CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, CurCase);
}
+ if (getLangOpts().CPlusPlus)
+ emitBypassedVarInitsForSource(CurCase);
// Since this loop is only executed when the CaseStmt has no attributes
// use a hard-coded value.
if (SwitchLikelihood)
@@ -2000,6 +2014,8 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt &S,
SwitchLikelihood->front() = Stmt::getLikelihood(Attrs);
EmitBlockWithFallThrough(DefaultBlock, &S);
+ if (getLangOpts().CPlusPlus)
+ emitBypassedVarInitsForSource(&S);
EmitStmt(S.getSubStmt());
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 7e7f9a072f765..c585df4a46e93 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1556,9 +1556,12 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn,
if (isa<CoroutineBodyStmt>(Body))
ShouldEmitLifetimeMarkers = true;
- // Initialize helper which will detect jumps which can cause invalid
- // lifetime markers.
- if (ShouldEmitLifetimeMarkers)
+ // Detect jumps that invalidate lifetime markers or bypass auto-var-init.
+ bool NeedsBypassDetection =
+ ShouldEmitLifetimeMarkers ||
+ (CGM.getLangOpts().getTrivialAutoVarInit() !=
+ LangOptions::TrivialAutoVarInitKind::Uninitialized);
+ if (NeedsBypassDetection)
Bypasses.Init(CGM, Body);
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 3c8188c4cefdd..c52590b6011f4 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -302,6 +302,18 @@ class CodeGenFunction : public CodeGenTypeCache {
// because of jumps.
VarBypassDetector Bypasses;
+ // Addresses of bypassed variables, for re-emitting their
+ // trivial-auto-var-init at a bypassing jump (C++ scope-reentry).
+ llvm::SmallDenseMap<const VarDecl *, Address, 4> BypassedVarInits;
+
+ // Forward gotos that may bypass a not-yet-emitted declaration;
+ // EmitAutoVarAlloca patches the init in before the branch.
+ struct BypassingForwardGoto {
+ llvm::AssertingVH<llvm::BasicBlock> Block;
+ const GotoStmt *Goto;
+ };
+ llvm::SmallVector<BypassingForwardGoto, 4> BypassingForwardGotos;
+
/// List of recently emitted OMPCanonicalLoops.
///
/// Since OMPCanonicalLoops are nested inside other statements (in particular
@@ -1109,6 +1121,10 @@ class CodeGenFunction : public CodeGenTypeCache {
SourceRange Range;
SmallVector<const LabelDecl *, 4> Labels;
LexicalScope *ParentScope;
+ // Block through which this scope is entered, used to place
+ // trivial-auto-var-init for bypassed variables in C. There can only be one
+ // EntryBlock for a variable.
+ llvm::BasicBlock *EntryBlock = nullptr;
LexicalScope(const LexicalScope &) = delete;
void operator=(const LexicalScope &) = delete;
@@ -1122,6 +1138,9 @@ class CodeGenFunction : public CodeGenTypeCache {
Labels.push_back(label);
}
+ /// The block through which this scope is entered, or null.
+ llvm::BasicBlock *getEntryBlock() const { return EntryBlock; }
+
/// Exit this cleanup scope, emitting any accumulated
/// cleanups.
~LexicalScope();
@@ -3557,6 +3576,11 @@ class CodeGenFunction : public CodeGenTypeCache {
void emitAutoVarTypeCleanup(const AutoVarEmission &emission,
QualType::DestructionKind dtorKind);
+ /// Re-emit trivial-auto-var-init stores for variables bypassed by the jump
+ /// Source (C++ only). No-op in C, where bypassed variables are initialized
+ /// once in the entry block.
+ void emitBypassedVarInitsForSource(const Stmt *Source);
+
void MaybeEmitDeferredVarDeclInit(const VarDecl *var);
/// Emits the alloca and debug information for the size expressions for each
@@ -5585,6 +5609,8 @@ class CodeGenFunction : public CodeGenTypeCache {
void emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D,
Address Loc);
+ LangOptions::TrivialAutoVarInitKind getAutoVarInitKind(QualType type,
+ const VarDecl &D);
public:
enum class EvaluationOrder {
diff --git a/clang/lib/CodeGen/VarBypassDetector.cpp b/clang/lib/CodeGen/VarBypassDetector.cpp
index 7b2b3542928ad..c4788e5f461f4 100644
--- a/clang/lib/CodeGen/VarBypassDetector.cpp
+++ b/clang/lib/CodeGen/VarBypassDetector.cpp
@@ -16,13 +16,41 @@
using namespace clang;
using namespace CodeGen;
+/// True if the body contains a goto, switch, or indirect goto. Lets Init()
+/// skip scope building for the common jump-free function, this is a minor
+/// optimization win.
+static bool hasJumpStmts(const Stmt *Body) {
+ llvm::SmallVector<const Stmt *, 32> Worklist;
+ Worklist.push_back(Body);
+ while (!Worklist.empty()) {
+ const Stmt *S = Worklist.pop_back_val();
+ if (!S)
+ continue;
+ switch (S->getStmtClass()) {
+ case Stmt::GotoStmtClass:
+ case Stmt::SwitchStmtClass:
+ case Stmt::IndirectGotoStmtClass:
+ return true;
+ default:
+ break;
+ }
+ for (const Stmt *Child : S->children())
+ Worklist.push_back(Child);
+ }
+ return false;
+}
+
/// Clear the object and pre-process for the given statement, usually function
/// body statement.
void VarBypassDetector::Init(CodeGenModule &CGM, const Stmt *Body) {
FromScopes.clear();
ToScopes.clear();
Bypasses.clear();
+ BypassedVarsAtSource.clear();
Scopes = {{~0U, nullptr}};
+ AlwaysBypassed = false;
+ if (!hasJumpStmts(Body))
+ return;
unsigned ParentScope = 0;
AlwaysBypassed = !BuildScopeInformation(CGM, Body, ParentScope);
if (!AlwaysBypassed)
@@ -144,11 +172,11 @@ void VarBypassDetector::Detect() {
unsigned from = S.second;
if (const GotoStmt *GS = dyn_cast<GotoStmt>(St)) {
if (const LabelStmt *LS = GS->getLabel()->getStmt())
- Detect(from, ToScopes[LS]);
+ Detect(from, ToScopes[LS], GS);
} else if (const SwitchStmt *SS = dyn_cast<SwitchStmt>(St)) {
for (const SwitchCase *SC = SS->getSwitchCaseList(); SC;
SC = SC->getNextSwitchCase()) {
- Detect(from, ToScopes[SC]);
+ Detect(from, ToScopes[SC], SC);
}
} else {
llvm_unreachable("goto or switch was expected");
@@ -157,13 +185,15 @@ void VarBypassDetector::Detect() {
}
/// Checks the jump and stores each variable declaration it bypasses.
-void VarBypassDetector::Detect(unsigned From, unsigned To) {
+void VarBypassDetector::Detect(unsigned From, unsigned To, const Stmt *Source) {
while (From != To) {
if (From < To) {
assert(Scopes[To].first < To);
const auto &ScopeTo = Scopes[To];
To = ScopeTo.first;
Bypasses.insert(ScopeTo.second);
+ if (ScopeTo.second)
+ BypassedVarsAtSource[Source].insert(ScopeTo.second);
} else {
assert(Scopes[From].first < From);
From = Scopes[From].first;
diff --git a/clang/lib/CodeGen/VarBypassDetector.h b/clang/lib/CodeGen/VarBypassDetector.h
index cc4d387aeaa5b..80e447454ccbd 100644
--- a/clang/lib/CodeGen/VarBypassDetector.h
+++ b/clang/lib/CodeGen/VarBypassDetector.h
@@ -47,6 +47,10 @@ class VarBypassDetector {
llvm::DenseMap<const Stmt *, unsigned> ToScopes;
// Set of variables which were bypassed by some jump.
llvm::DenseSet<const VarDecl *> Bypasses;
+ // Map from a bypassing jump (goto/switch case) to the variable declarations
+ // it bypasses. Used to reinitialize those variables at the jump (C++ only).
+ llvm::DenseMap<const Stmt *, llvm::DenseSet<const VarDecl *>>
+ BypassedVarsAtSource;
// If true assume that all variables are being bypassed.
bool AlwaysBypassed = false;
@@ -59,13 +63,27 @@ class VarBypassDetector {
return AlwaysBypassed || Bypasses.contains(D);
}
+ /// Returns true if jump sources cannot be determined (e.g. computed gotos),
+ /// so all variables must be treated as bypassed.
+ bool isAlwaysBypassed() const { return AlwaysBypassed; }
+
+ /// Returns the variables bypassed by jumps from the given source statement,
+ /// or nullptr if it bypasses none.
+ const llvm::DenseSet<const VarDecl *> *
+ getBypassedVarsForSource(const Stmt *Source) const {
+ auto It = BypassedVarsAtSource.find(Source);
+ if (It == BypassedVarsAtSource.end())
+ return nullptr;
+ return &It->second;
+ }
+
private:
bool BuildScopeInformation(CodeGenModule &CGM, const Decl *D,
unsigned &ParentScope);
bool BuildScopeInformation(CodeGenModule &CGM, const Stmt *S,
unsigned &origParentScope);
void Detect();
- void Detect(unsigned From, unsigned To);
+ void Detect(unsigned From, unsigned To, const Stmt *Source);
};
}
}
diff --git a/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c b/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
new file mode 100644
index 0000000000000..892125517b17f
--- /dev/null
+++ b/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
@@ -0,0 +1,236 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefix=ZERO
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefix=PATTERN
+
+// In C, a bypassed variable's lifetime begins at entry into its enclosing
+// block (C6.2.4p6), so it is initialized at that block's entry: once for a
+// function-scoped variable, every iteration for a loop-scoped one. C++ differs
+// (lifetime restarts on scope re-entry); see CodeGenCXX/trivial-auto-var-init.cpp.
+
+void use_int(int *);
+
+// Not bypassed: declaration is reached each iteration, so init is at BEGIN.
+// ZERO-LABEL: define {{.*}}@backward_goto_pointer(
+// ZERO: entry:
+// ZERO-NOT: !annotation
+// ZERO: BEGIN:
+// ZERO: store ptr null, ptr %p, {{.*}}!annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: define {{.*}}@backward_goto_pointer(
+// PATTERN: entry:
+// PATTERN-NOT: !annotation
+// PATTERN: BEGIN:
+// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %p, {{.*}}!annotation [[AUTO_INIT:!.+]]
+int backward_goto_pointer(void) {
+ int b = 0;
+BEGIN:;
+ int *p;
+ if (b)
+ *p = 10;
+ p = &b;
+ if (!b) {
+ b = 1;
+ goto BEGIN;
+ }
+ return b;
+}
+
+// Scalar variant of the above.
+// ZERO-LABEL: define {{.*}}@backward_goto_scalar(
+// ZERO: entry:
+// ZERO-NOT: !annotation
+// ZERO: BEGIN:
+// ZERO: store i32 0, ptr %c, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN-LABEL: define {{.*}}@backward_goto_scalar(
+// PATTERN: entry:
+// PATTERN-NOT: !annotation
+// PATTERN: BEGIN:
+// PATTERN: store i32 -1431655766, ptr %c, {{.*}}!annotation [[AUTO_INIT]]
+int backward_goto_scalar(void) {
+ int b = 0;
+BEGIN:;
+ int c;
+ if (b) return c;
+ c = 5;
+ b = 1;
+ goto BEGIN;
+}
+
+// Bypassed, function-scoped: init once in entry, so p = &b survives the
+// backward goto (returns 10).
+// ZERO-LABEL: define {{.*}}@backward_goto_around_decl(
+// ZERO: entry:
+// ZERO: store ptr null, ptr %p, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: BEGIN:
+// ZERO-NOT: !annotation
+// ZERO: ret
+// PATTERN-LABEL: define {{.*}}@backward_goto_around_decl(
+// PATTERN: entry:
+// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %p, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: BEGIN:
+// PATTERN-NOT: !annotation
+// PATTERN: ret
+int backward_goto_around_decl(void) {
+ int b = 0;
+BEGIN:;
+ goto CONT;
+ int *p;
+CONT:
+ if (b)
+ *p = 10;
+ p = &b;
+ if (!b) {
+ b = 1;
+ goto BEGIN;
+ }
+ return b;
+}
+
+// Loop-scoped: init at the loop body's entry (while.body), so it reruns each
+// iteration rather than once in entry.
+// ZERO-LABEL: define {{.*}}@loop_bypass(
+// ZERO: entry:
+// ZERO-NOT: !annotation
+// ZERO: while.body:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN-LABEL: define {{.*}}@loop_bypass(
+// PATTERN: entry:
+// PATTERN-NOT: !annotation
+// PATTERN: while.body:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+void loop_bypass(void) {
+ while (1) {
+ goto X;
+ int x;
+ X:
+ use_int(&x);
+ }
+}
+
+// Switch bypass, function-scoped: init once in entry before the dispatch.
+// ZERO-LABEL: define {{.*}}@switch_bypass(
+// ZERO: entry:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: switch i32
+// PATTERN-LABEL: define {{.*}}@switch_bypass(
+// PATTERN: entry:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: switch i32
+int switch_bypass(int c) {
+ switch (c) {
+ int x;
+ case 0:
+ x = 1;
+ use_int(&x);
+ return x;
+ default:
+ use_int(&x);
+ return x;
+ }
+}
+
+// Switch bypass in a loop: init at the dispatch block (while.body), per
+// iteration.
+// ZERO-LABEL: define {{.*}}@switch_bypass_in_loop(
+// ZERO: entry:
+// ZERO-NOT: !annotation
+// ZERO: while.body:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: switch i32
+// PATTERN-LABEL: define {{.*}}@switch_bypass_in_loop(
+// PATTERN: entry:
+// PATTERN-NOT: !annotation
+// PATTERN: while.body:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: switch i32
+void switch_bypass_in_loop(int c) {
+ while (1) {
+ switch (c) {
+ int x;
+ case 0:
+ x = 1;
+ use_int(&x);
+ break;
+ default:
+ use_int(&x);
+ break;
+ }
+ }
+}
+
+// Computed goto: sources unknown, so init once in entry.
+// ZERO-LABEL: define {{.*}}@computed_goto(
+// ZERO: entry:
+// ZERO: store i32 0, ptr %y, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: indirectbr
+// PATTERN-LABEL: define {{.*}}@computed_goto(
+// PATTERN: entry:
+// PATTERN: store i32 -1431655766, ptr %y, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: indirectbr
+void computed_goto(int n) {
+ void *t[] = {&&L1, &&L2};
+ goto *t[n];
+ int y;
+L1:
+ use_int(&y);
+ return;
+L2:
+ return;
+}
+
+// Nested loops: init at the inner body's entry (while.body3), per inner
+// iteration -- not in entry or the outer body.
+// ZERO-LABEL: define {{.*}}@nested_loops(
+// ZERO: entry:
+// ZERO-NOT: store {{.*}}%x{{.*}}!annotation
+// ZERO: while.body3:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN-LABEL: define {{.*}}@nested_loops(
+// PATTERN: entry:
+// PATTERN-NOT: store {{.*}}%x{{.*}}!annotation
+// PATTERN: while.body3:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+void nested_loops(int n) {
+ while (n) {
+ while (n) {
+ goto X;
+ int x;
+ X:
+ use_int(&x);
+ n--;
+ }
+ }
+}
+
+// Nested loops + switch: init at the inner dispatch block (while.body3), per
+// inner iteration.
+// ZERO-LABEL: define {{.*}}@nested_loops_switch(
+// ZERO: entry:
+// ZERO-NOT: store {{.*}}%x{{.*}}!annotation
+// ZERO: while.body3:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: switch i32
+// PATTERN-LABEL: define {{.*}}@nested_loops_switch(
+// PATTERN: entry:
+// PATTERN-NOT: store {{.*}}%x{{.*}}!annotation
+// PATTERN: while.body3:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: switch i32
+void nested_loops_switch(int n, int c) {
+ while (n) {
+ while (n) {
+ switch (c) {
+ int x;
+ case 0:
+ x = 1;
+ use_int(&x);
+ break;
+ default:
+ use_int(&x);
+ break;
+ }
+ n--;
+ }
+ }
+}
+
+// ZERO: [[AUTO_INIT]] = !{!"auto-init"}
+// PATTERN: [[AUTO_INIT]] = !{!"auto-init"}
diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
index e21a307f33121..efed1f17f525c 100644
--- a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
+++ b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
@@ -77,13 +77,16 @@ void test_block_captures_self_after_init() {
});
}
-// This type of code is currently not handled by zero / pattern initialization.
-// The test will break when that is fixed.
+// Bypassed variables are initialized at the goto source (before the branch).
// UNINIT-LABEL: test_goto_unreachable_value(
// ZERO-LABEL: test_goto_unreachable_value(
-// ZERO-NOT: store {{.*}}%oops
+// ZERO: %oops = alloca i32, align 4
+// ZERO: store i32 0, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: br label %jump
// PATTERN-LABEL: test_goto_unreachable_value(
-// PATTERN-NOT: store {{.*}}%oops
+// PATTERN: %oops = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: br label %jump
void test_goto_unreachable_value() {
goto jump;
int oops;
@@ -91,21 +94,14 @@ void test_goto_unreachable_value() {
used(oops);
}
-// This type of code is currently not handled by zero / pattern initialization.
-// The test will break when that is fixed.
+// Bypassed variables are initialized at the jump target.
// UNINIT-LABEL: test_goto(
// ZERO-LABEL: test_goto(
-// ZERO: if.then:
-// ZERO: br label %jump
+// ZERO: %oops = alloca i32, align 4
// ZERO: store i32 0, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
-// ZERO: br label %jump
-// ZERO: jump:
// PATTERN-LABEL: test_goto(
-// PATTERN: if.then:
-// PATTERN: br label %jump
+// PATTERN: %oops = alloca i32, align 4
// PATTERN: store i32 -1431655766, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
-// PATTERN: br label %jump
-// PATTERN: jump:
void test_goto(int i) {
if (i)
goto jump;
@@ -114,19 +110,14 @@ void test_goto(int i) {
used(oops);
}
-// This type of code is currently not handled by zero / pattern initialization.
-// The test will break when that is fixed.
+// Bypassed variables are initialized at the case target.
// UNINIT-LABEL: test_switch(
// ZERO-LABEL: test_switch(
-// ZERO: sw.bb:
-// ZERO-NEXT: store i32 0, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
-// ZERO: sw.bb1:
-// ZERO-NEXT: call void @{{.*}}used
+// ZERO: %oops = alloca i32, align 4
+// ZERO: store i32 0, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
// PATTERN-LABEL: test_switch(
-// PATTERN: sw.bb:
-// PATTERN-NEXT: store i32 -1431655766, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
-// PATTERN: sw.bb1:
-// PATTERN-NEXT: call void @{{.*}}used
+// PATTERN: %oops = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %oops, align 4, !annotation [[AUTO_INIT:!.+]]
void test_switch(int i) {
switch (i) {
case 0:
@@ -318,6 +309,373 @@ void test_huge_larger_init() {
used(big);
}
+// UNINIT-LABEL: test_goto_multiple_bypassed(
+// ZERO-LABEL: test_goto_multiple_bypassed(
+// ZERO: %a = alloca i32, align 4
+// ZERO: %b = alloca i32, align 4
+// ZERO-DAG: store i32 0, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO-DAG: store i32 0, ptr %b, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_goto_multiple_bypassed(
+// PATTERN: %a = alloca i32, align 4
+// PATTERN: %b = alloca i32, align 4
+// PATTERN-DAG: store i32 -1431655766, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-DAG: store i32 -1431655766, ptr %b, align 4, !annotation [[AUTO_INIT:!.+]]
+void test_goto_multiple_bypassed() {
+ goto jump;
+ int a;
+ int b;
+ jump:
+ used(a);
+ used(b);
+}
+
+// UNINIT-LABEL: test_goto_bypassed_uninitialized_attr(
+// ZERO-LABEL: test_goto_bypassed_uninitialized_attr(
+// ZERO-NOT: store {{.*}}%skip_me
+// ZERO: call void @{{.*}}used
+// PATTERN-LABEL: test_goto_bypassed_uninitialized_attr(
+// PATTERN-NOT: store {{.*}}%skip_me
+// PATTERN: call void @{{.*}}used
+void test_goto_bypassed_uninitialized_attr() {
+ goto jump;
+ [[clang::uninitialized]] int skip_me;
+ jump:
+ used(skip_me);
+}
+
+// UNINIT-LABEL: test_switch_between_cases(
+// ZERO-LABEL: test_switch_between_cases(
+// ZERO: %x = alloca i32, align 4
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_switch_between_cases(
+// PATTERN: %x = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+void test_switch_between_cases(int c) {
+ switch (c) {
+ case 0:
+ int x;
+ x = 42;
+ used(x);
+ break;
+ case 1:
+ used(x);
+ break;
+ }
+}
+
+// UNINIT-LABEL: test_switch_precase(
+// ZERO-LABEL: test_switch_precase(
+// ZERO: %x = alloca i32, align 4
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_switch_precase(
+// PATTERN: %x = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+void test_switch_precase(int c) {
+ switch (c) {
+ int x;
+ case 0:
+ x = 1;
+ used(x);
+ break;
+ }
+}
+
+// UNINIT-LABEL: test_computed_goto(
+// ZERO-LABEL: test_computed_goto(
+// ZERO: %y = alloca i32, align 4
+// ZERO: store i32 0, ptr %y, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_computed_goto(
+// PATTERN: %y = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %y, align 4, !annotation [[AUTO_INIT:!.+]]
+void test_computed_goto(int x) {
+ void *targets[] = {&&label1, &&label2};
+ goto *targets[x];
+ int y;
+label1:
+ used(y);
+ return;
+label2:
+ return;
+}
+
+// UNINIT-LABEL: test_loop_bypass(
+// ZERO-LABEL: test_loop_bypass(
+// ZERO: %x = alloca i32, align 4
+// ZERO: while.body:
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: br label %X
+// PATTERN-LABEL: test_loop_bypass(
+// PATTERN: %x = alloca i32, align 4
+// PATTERN: while.body:
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: br label %X
+void test_loop_bypass() {
+ while (true) {
+ goto X;
+ int x;
+ X:
+ used(x);
+ if (x) break;
+ }
+}
+
+// UNINIT-LABEL: test_complex_multi_goto(
+// ZERO-LABEL: test_complex_multi_goto(
+// ZERO: Z:
+// ZERO-NEXT: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO-NEXT: br label %Y
+// ZERO: X:
+// ZERO-NEXT: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO-NEXT: br label %Y
+// ZERO: sw.bb:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: br label %X
+// ZERO: sw.bb1:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: br label %Z
+// ZERO: sw.epilog:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: br label %Y
+// PATTERN-LABEL: test_complex_multi_goto(
+// PATTERN: Z:
+// PATTERN-NEXT: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-NEXT: br label %Y
+// PATTERN: X:
+// PATTERN-NEXT: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-NEXT: br label %Y
+// PATTERN: sw.bb:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: br label %X
+// PATTERN: sw.bb1:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: br label %Z
+// PATTERN: sw.epilog:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: br label %Y
+void test_complex_multi_goto(int g(int*)) {
+ while (true) {
+ Z:
+ goto Y;
+ X:
+ goto Y;
+ int x;
+ Y:
+ switch (g(&x)) {
+ case 0:
+ goto X;
+ case 1:
+ goto Z;
+ }
+ goto Y;
+ }
+}
+
+// UNINIT-LABEL: test_no_reinit_in_scope(
+// ZERO-LABEL: test_no_reinit_in_scope(
+// ZERO: while.body:
+// ZERO-NEXT: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO-NEXT: br label %Y
+// ZERO: if.end:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: br label %Y
+// PATTERN-LABEL: test_no_reinit_in_scope(
+// PATTERN: while.body:
+// PATTERN-NEXT: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-NEXT: br label %Y
+// PATTERN: if.end:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: br label %Y
+void test_no_reinit_in_scope(int g(int*)) {
+ while (true) {
+ goto Y;
+ int x;
+ Y:
+ if (g(&x))
+ break;
+ goto Y;
+ }
+}
+
+// Backward goto: x is already in scope, no bypass init should occur at the
+// goto.
+// UNINIT-LABEL: test_backward_goto_no_init(
+// ZERO-LABEL: test_backward_goto_no_init(
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: L:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: if.then:
+// ZERO-NOT: store {{.*}}%x
+// ZERO: br label %L
+// PATTERN-LABEL: test_backward_goto_no_init(
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: L:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: if.then:
+// PATTERN-NOT: store {{.*}}%x
+// PATTERN: br label %L
+void test_backward_goto_no_init() {
+ int x;
+ L:
+ used(x);
+ if (x)
+ goto L;
+}
+
+// Switch with default case bypassing a variable declared in case 0.
+// UNINIT-LABEL: test_switch_default_bypass(
+// ZERO-LABEL: test_switch_default_bypass(
+// ZERO: sw.default:
+// ZERO-NEXT: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_switch_default_bypass(
+// PATTERN: sw.default:
+// PATTERN-NEXT: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+void test_switch_default_bypass(int c) {
+ switch (c) {
+ case 0:
+ int x;
+ x = 10;
+ used(x);
+ break;
+ default:
+ used(x);
+ break;
+ }
+}
+
+// Multipe variables bypassed by the same goto so both must be initialized.
+// UNINIT-LABEL: test_goto_multiple_vars(
+// ZERO-LABEL: test_goto_multiple_vars(
+// ZERO: %a = alloca i32, align 4
+// ZERO: %b = alloca i32, align 4
+// ZERO: store i32 0, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: store i32 0, ptr %b, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: br label %jump
+// PATTERN-LABEL: test_goto_multiple_vars(
+// PATTERN: %a = alloca i32, align 4
+// PATTERN: %b = alloca i32, align 4
+// PATTERN: store i32 -1431655766, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: store i32 -1431655766, ptr %b, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: br label %jump
+void test_goto_multiple_vars() {
+ goto jump;
+ int a;
+ int b;
+ jump:
+ used(a);
+ used(b);
+}
+
+// UNINIT-LABEL: test_backward_goto_bypass(
+// ZERO-LABEL: test_backward_goto_bypass(
+// ZERO: jump:
+// ZERO: call void @{{.*}}used
+// ZERO: call void @{{.*}}used
+// ZERO-DAG: store i32 0, ptr %b, align 4
+// ZERO-DAG: store i32 0, ptr %a, align 4
+// ZERO: br label %jump
+// PATTERN-LABEL: test_backward_goto_bypass(
+// PATTERN: jump:
+// PATTERN: call void @{{.*}}used
+// PATTERN: call void @{{.*}}used
+// PATTERN-DAG: store i32 -1431655766, ptr %b
+// PATTERN-DAG: store i32 -1431655766, ptr %a
+// PATTERN: br label %jump
+void test_backward_goto_bypass() {
+ {
+ int a;
+ int b;
+jump:
+ used(a);
+ used(b);
+ }
+ goto jump;
+}
+
+// C++ [basic.stc.auto]: scope re-entry restarts the lifetime, so the init is
+// emitted at the goto source and reruns each iteration (store in BEGIN, not
+// entry). Contrast the C version, which inits once in entry and returns 10.
+// UNINIT-LABEL: test_backward_goto_around_decl(
+// ZERO-LABEL: test_backward_goto_around_decl(
+// ZERO: entry:
+// ZERO-NOT: !annotation
+// ZERO: BEGIN:
+// ZERO: store ptr null, ptr %p, align 8, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: test_backward_goto_around_decl(
+// PATTERN: entry:
+// PATTERN-NOT: !annotation
+// PATTERN: BEGIN:
+// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %p, align 8, !annotation [[AUTO_INIT:!.+]]
+int test_backward_goto_around_decl(int b) {
+BEGIN:;
+ goto CONT;
+ int *p;
+CONT:
+ if (b)
+ *p = 10;
+ p = &b;
+ if (!b) {
+ b = 1;
+ goto BEGIN;
+ }
+ return b;
+}
+
+// Nested loops: goto source is in the inner body, so reinit lands there
+// (while.body3), every inner iteration.
+// UNINIT-LABEL: nested_loops(
+// ZERO-LABEL: nested_loops(
+// ZERO: entry:
+// ZERO-NOT: store {{.*}}%x{{.*}}!annotation
+// ZERO: while.body3:
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-LABEL: nested_loops(
+// PATTERN: entry:
+// PATTERN-NOT: store {{.*}}%x{{.*}}!annotation
+// PATTERN: while.body3:
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+void nested_loops(int n) {
+ while (n) {
+ while (n) {
+ goto X;
+ int x;
+ X:
+ used(x);
+ n--;
+ }
+ }
+}
+
+// Nested loops + switch: in C++ the reinit is emitted at each case target, so
+// it runs on every case entry (one store per case).
+// UNINIT-LABEL: nested_loops_switch(
+// ZERO-LABEL: nested_loops_switch(
+// ZERO: while.body3:
+// ZERO: switch i32
+// ZERO-DAG: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO-DAG: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT]]
+// PATTERN-LABEL: nested_loops_switch(
+// PATTERN: while.body3:
+// PATTERN: switch i32
+// PATTERN-DAG: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN-DAG: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT]]
+void nested_loops_switch(int n, int c) {
+ while (n) {
+ while (n) {
+ switch (c) {
+ int x;
+ case 0:
+ x = 1;
+ used(x);
+ break;
+ default:
+ used(x);
+ break;
+ }
+ n--;
+ }
+ }
+}
+
} // extern "C"
// CHECK: [[AUTO_INIT]] = !{ !"auto-init" }
>From 52829b4505fb0717ce9c4188c237e5fc4b537172 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 29 Jun 2026 15:56:08 -0700
Subject: [PATCH 2/5] better handle computed goto
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/lib/CodeGen/CGDecl.cpp | 6 ++++
clang/lib/CodeGen/CGStmt.cpp | 6 ++--
clang/lib/CodeGen/CodeGenFunction.h | 3 +-
.../trivial-auto-var-init-c-backward-goto.c | 29 +++++++++++++++++
.../test/CodeGenCXX/trivial-auto-var-init.cpp | 32 +++++++++++++++++++
5 files changed, 73 insertions(+), 3 deletions(-)
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 2e6f2b4b9bea1..d09bd6eb4e547 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1892,6 +1892,12 @@ CodeGenFunction::getAutoVarInitKind(QualType type, const VarDecl &D) {
}
void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) {
+ // C++ scope-reentry reinit is only sound when jump sources are known. With a
+ // computed goto we can't tell whether a jump leaves a variable's scope, so
+ // EmitAutoVarAlloca falls back to a single function-scope init and we must
+ // not reinitialize here -- doing so could clobber a still-live variable.
+ if (Bypasses.isAlwaysBypassed())
+ return;
const auto *Vars = Bypasses.getBypassedVarsForSource(Source);
if (!Vars)
return;
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index db74e6e830485..01e2013a2a791 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -857,8 +857,10 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
// For C++ scope re-entry we need to reinitialize variables this goto
// bypasses. Backward gotos reinit here while forward gotos are recorded for
- // EmitAutoVarAlloca to patch once the alloca exists.
- if (HaveInsertPoint() && getLangOpts().CPlusPlus) {
+ // EmitAutoVarAlloca to patch once the alloca exists. Skip when jump sources
+ // are unknown (computed goto); EmitAutoVarAlloca then uses function-scope init.
+ if (HaveInsertPoint() && getLangOpts().CPlusPlus &&
+ !Bypasses.isAlwaysBypassed()) {
emitBypassedVarInitsForSource(&S);
BypassingForwardGotos.push_back({Builder.GetInsertBlock(), &S});
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index c52590b6011f4..cfe22d0f85fdc 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3578,7 +3578,8 @@ class CodeGenFunction : public CodeGenTypeCache {
/// Re-emit trivial-auto-var-init stores for variables bypassed by the jump
/// Source (C++ only). No-op in C, where bypassed variables are initialized
- /// once in the entry block.
+ /// once in the entry block, and in any function with a computed goto, where
+ /// jump sources are unknown and a single function-scope init is used instead.
void emitBypassedVarInitsForSource(const Stmt *Source);
void MaybeEmitDeferredVarDeclInit(const VarDecl *var);
diff --git a/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c b/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
index 892125517b17f..8cd0ffaa981ad 100644
--- a/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
+++ b/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
@@ -232,5 +232,34 @@ void nested_loops_switch(int n, int c) {
}
}
+// Computed goto with multiple scopes: sources unknown, so every bypassed
+// variable gets a single function-scope init in entry. The switch case targets
+// must not add a second init store for %x.
+// ZERO-LABEL: define {{.*}}@computed_goto_multi_scope(
+// ZERO: entry:
+// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// ZERO: indirectbr
+// ZERO-NOT: store i32 0, ptr %x, {{.*}}!annotation
+// PATTERN-LABEL: define {{.*}}@computed_goto_multi_scope(
+// PATTERN: entry:
+// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
+// PATTERN: indirectbr
+// PATTERN-NOT: store i32 -1431655766, ptr %x, {{.*}}!annotation
+void computed_goto_multi_scope(int n, int c) {
+ void *t[] = {&&L1, &&L2};
+ goto *t[n];
+ int x;
+ switch (c) {
+ case 0:
+ L1:
+ use_int(&x);
+ break;
+ default:
+ L2:
+ use_int(&x);
+ break;
+ }
+}
+
// ZERO: [[AUTO_INIT]] = !{!"auto-init"}
// PATTERN: [[AUTO_INIT]] = !{!"auto-init"}
diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
index efed1f17f525c..eedb12c3930fd 100644
--- a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
+++ b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp
@@ -676,6 +676,38 @@ void nested_loops_switch(int n, int c) {
}
}
+// Computed goto with multiple scopes: jump sources are unknown, so all bypassed
+// variables fall back to a single function-scope init in entry. Even though a
+// regular switch is also present, its case targets must NOT reinitialize -- that
+// could clobber a variable still live across the computed jump. One init in
+// entry, none after the indirectbr.
+// UNINIT-LABEL: test_computed_goto_multi_scope(
+// ZERO-LABEL: test_computed_goto_multi_scope(
+// ZERO: entry:
+// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// ZERO: indirectbr
+// ZERO-NOT: store i32 0, ptr %x, align 4, !annotation
+// PATTERN-LABEL: test_computed_goto_multi_scope(
+// PATTERN: entry:
+// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]]
+// PATTERN: indirectbr
+// PATTERN-NOT: store i32 -1431655766, ptr %x, align 4, !annotation
+void test_computed_goto_multi_scope(int n, int c) {
+ void *targets[] = {&&L1, &&L2};
+ goto *targets[n];
+ int x;
+ switch (c) {
+ case 0:
+ L1:
+ used(x);
+ break;
+ default:
+ L2:
+ used(x);
+ break;
+ }
+}
+
} // extern "C"
// CHECK: [[AUTO_INIT]] = !{ !"auto-init" }
>From c51f8dbd7d578298ba7116556cd9df420744dc1e Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 29 Jun 2026 16:03:54 -0700
Subject: [PATCH 3/5] fix release notes migration
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/docs/ReleaseNotes.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f8eba5237ba3a..bccc4c414397d 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -273,6 +273,14 @@ features cannot lower the translation-unit ABI level;
leaves suffixed data symbols unchanged. The option remains opt-in, and
variables with explicit assembly labels keep their original names.
+- ``-ftrivial-auto-var-init=zero`` and ``-ftrivial-auto-var-init=pattern`` now
+ initialize variables whose declaration is bypassed by ``goto`` or ``switch``,
+ which were previously left uninitialized. Initialization follows the lifetime
+ rules of each language: in C++ a variables lifetime restarts whenever its
+ scope is re-entered, so it is reinitialized through bypassing jumps. In C,
+ its lifetime begins at entry into the enclosing block, so it is initialized
+ at that block's entry.
+
### Removed Compiler Flags
### Attribute Changes in Clang
>From b625275c5c4f4e6a29c247715538572acd0a020b Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Mon, 29 Jun 2026 16:04:25 -0700
Subject: [PATCH 4/5] run formatter
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/lib/CodeGen/CGStmt.cpp | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 01e2013a2a791..da1874f1f8e42 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -858,7 +858,8 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
// For C++ scope re-entry we need to reinitialize variables this goto
// bypasses. Backward gotos reinit here while forward gotos are recorded for
// EmitAutoVarAlloca to patch once the alloca exists. Skip when jump sources
- // are unknown (computed goto); EmitAutoVarAlloca then uses function-scope init.
+ // are unknown (computed goto); EmitAutoVarAlloca then uses function-scope
+ // init.
if (HaveInsertPoint() && getLangOpts().CPlusPlus &&
!Bypasses.isAlwaysBypassed()) {
emitBypassedVarInitsForSource(&S);
>From 8b2e9fcf3596e884ca64f7ddfdfb4e4ab036ef72 Mon Sep 17 00:00:00 2001
From: Justin Stitt <justinstitt at google.com>
Date: Thu, 10 Sep 2026 13:55:18 -0700
Subject: [PATCH 5/5] Land bypassed-var init with two modes only
drop the C block-entry mode so the rest of the patch is
reviewable, leaving two modes:
1. Scope-reentry, used for both C and C++. A jump that bypasses a
declaration re-enters the variable's scope, so the initialization is
emitted at the jump source.
2. Computed goto. The bypassing jumps can't be identified, so the
variable is initialized once in the function's entry block.
C therefore follows the C++ rule rather than C 6.2.4p6, under which an
object's lifetime would begin at entry into its enclosing block. That is
a deliberate deviation. The C block-entry semantics are left to a
follow-up.
This removes LexicalScope::EntryBlock and its capture in CGDebugInfo.cpp,
so the patch no longer touches debug info, and removes every
getLangOpts().CPlusPlus check from the CGStmt.cpp call sites, so a jump
into a scope is now handled in C as well as C++.
Also drop the getNonReferenceType() call in emitBypassedVarInitsForSource:
a bypassed declaration can't be a reference, since a reference always has
an initializer and Sema rejects a jump past a declaration with one.
Replace trivial-auto-var-init-c-backward-goto.c, which pinned the C
block-entry behavior, with trivial-auto-var-init-bypass.c, generated by
update_cc_test_checks.py.
Assisted-By: claude
Signed-off-by: Justin Stitt <justinstitt at google.com>
---
clang/docs/ReleaseNotes.md | 20 +-
clang/lib/CodeGen/CGDebugInfo.cpp | 7 -
clang/lib/CodeGen/CGDecl.cpp | 37 +-
clang/lib/CodeGen/CGStmt.cpp | 19 +-
clang/lib/CodeGen/CodeGenFunction.h | 14 +-
.../CodeGen/trivial-auto-var-init-bypass.c | 422 ++++++++++++++++++
.../trivial-auto-var-init-c-backward-goto.c | 265 -----------
7 files changed, 460 insertions(+), 324 deletions(-)
create mode 100644 clang/test/CodeGen/trivial-auto-var-init-bypass.c
delete mode 100644 clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bccc4c414397d..cf0fab7969151 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -273,13 +273,19 @@ features cannot lower the translation-unit ABI level;
leaves suffixed data symbols unchanged. The option remains opt-in, and
variables with explicit assembly labels keep their original names.
-- ``-ftrivial-auto-var-init=zero`` and ``-ftrivial-auto-var-init=pattern`` now
- initialize variables whose declaration is bypassed by ``goto`` or ``switch``,
- which were previously left uninitialized. Initialization follows the lifetime
- rules of each language: in C++ a variables lifetime restarts whenever its
- scope is re-entered, so it is reinitialized through bypassing jumps. In C,
- its lifetime begins at entry into the enclosing block, so it is initialized
- at that block's entry.
+- `-ftrivial-auto-var-init=zero` and `-ftrivial-auto-var-init=pattern` now
+ initialize variables whose declaration is bypassed by a `goto` or `switch`,
+ which were previously left uninitialized. A bypassing jump re-enters the
+ variable's scope, so the variable is reinitialized at the jump. In a function
+ containing a computed `goto` the jumps that bypass a declaration cannot be
+ identified, so such variables are instead initialized once, in the function's
+ entry block.
+
+ Note that in C this deliberately does not follow C 6.2.4p6, under which an
+ object's lifetime begins at entry into the block it is declared in rather than
+ at its declaration. Modelling that would mean initializing at block entry,
+ which is both more expensive and, for the jumps this feature exists to
+ protect, no more useful. Clang uses the C++ rule in both languages.
### Removed Compiler Flags
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 6a636e431f521..02864621d60a3 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -6802,13 +6802,6 @@ CodeGenFunction::LexicalScope::LexicalScope(CodeGenFunction &CGF,
SourceRange Range)
: RunCleanupsScope(CGF), Range(Range), ParentScope(CGF.CurLexicalScope) {
CGF.CurLexicalScope = this;
- // Record the block this scope is entered through, used to initialize
- // potentially bypassed variables under -ftrivial-auto-var-init, adhereing to
- // C6.2.4p6. Also see CodeGenFunction::EmitAutoVarAlloca(). A scope with no
- // insertion point (e.g. a switch body) inherits its parent's.
- EntryBlock = CGF.Builder.GetInsertBlock();
- if (!EntryBlock && ParentScope)
- EntryBlock = ParentScope->EntryBlock;
if (CGDebugInfo *DI = CGF.getDebugInfo())
DI->EmitLexicalBlockStart(CGF.Builder, Range.getBegin());
}
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index d09bd6eb4e547..c26e33c03cece 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -1645,18 +1645,18 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) {
}
// A variable whose declaration is bypassed by a goto or switch is not
- // initialized by EmitAutoVarInit (that runs at the declaration). Emit the
- // trivial-auto-var-init separately, following each language's lifetime
- // rules.
+ // initialized by EmitAutoVarInit, which runs at the declaration. Emit the
+ // trivial-auto-var-init separately.
if (Bypasses.IsBypassed(&D) && !emission.IsEscapingByRef &&
!Ty->isVariablyModifiedType() &&
getAutoVarInitKind(Ty, D) !=
LangOptions::TrivialAutoVarInitKind::Uninitialized) {
- if (getLangOpts().CPlusPlus && !Bypasses.isAlwaysBypassed()) {
- // C++ [basic.stc.auto]: the lifetime restarts on each scope re-entry,
- // so reinitialize at every bypassing jump. Switch cases and backward
- // gotos are emitted at the jump source (after this alloca); forward
- // gotos already emitted are patchd before their branch.
+ if (!Bypasses.isAlwaysBypassed()) {
+ // The variable's lifetime restarts on each re-entry into its scope, so
+ // reinitialize at every bypassing jump. Switch cases and backward gotos
+ // are emitted at the jump source, which comes after this alloca;
+ // forward gotos have already been emitted, so patch their init in
+ // before the branch.
BypassedVarInits.insert({&D, address});
for (const BypassingForwardGoto &FG : BypassingForwardGotos) {
const auto *Vars = Bypasses.getBypassedVarsForSource(FG.Goto);
@@ -1668,16 +1668,11 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) {
}
}
} else {
- // C (C6.2.4p6) and computed gotos: the lifetime begins at entry into
- // the enclosing block, so initialize at that block's entry once for a
- // function-scoped variable and every iteration for a loop-scoped one.
- llvm::BasicBlock *Entry =
- CurLexicalScope ? CurLexicalScope->getEntryBlock() : nullptr;
+ // A computed goto can jump anywhere, so we can't identify the jumps
+ // that bypass this declaration. Fall back to initializing once, in the
+ // function's entry block.
llvm::IRBuilderBase::InsertPointGuard IPG(Builder);
- if (Entry && Entry->getTerminator())
- Builder.SetInsertPoint(Entry->getTerminator());
- else
- Builder.SetInsertPoint(getPostAllocaInsertPoint());
+ Builder.SetInsertPoint(getPostAllocaInsertPoint());
emitZeroOrPatternForAutoVarInit(Ty, D, address);
}
}
@@ -1892,7 +1887,7 @@ CodeGenFunction::getAutoVarInitKind(QualType type, const VarDecl &D) {
}
void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) {
- // C++ scope-reentry reinit is only sound when jump sources are known. With a
+ // Scope-reentry reinit is only sound when jump sources are known. With a
// computed goto we can't tell whether a jump leaves a variable's scope, so
// EmitAutoVarAlloca falls back to a single function-scope init and we must
// not reinitialize here -- doing so could clobber a still-live variable.
@@ -1903,10 +1898,8 @@ void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) {
return;
for (const VarDecl *VD : *Vars) {
auto It = BypassedVarInits.find(VD);
- if (It != BypassedVarInits.end()) {
- QualType Ty = VD->getType().getNonReferenceType();
- emitZeroOrPatternForAutoVarInit(Ty, *VD, It->second);
- }
+ if (It != BypassedVarInits.end())
+ emitZeroOrPatternForAutoVarInit(VD->getType(), *VD, It->second);
}
}
diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index da1874f1f8e42..2b9ff459a86a1 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -855,13 +855,12 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) {
if (HaveInsertPoint())
EmitStopPoint(&S);
- // For C++ scope re-entry we need to reinitialize variables this goto
- // bypasses. Backward gotos reinit here while forward gotos are recorded for
+ // Reinitialize the variables this goto bypasses, whose scope it re-enters.
+ // Backward gotos reinit here while forward gotos are recorded for
// EmitAutoVarAlloca to patch once the alloca exists. Skip when jump sources
// are unknown (computed goto); EmitAutoVarAlloca then uses function-scope
// init.
- if (HaveInsertPoint() && getLangOpts().CPlusPlus &&
- !Bypasses.isAlwaysBypassed()) {
+ if (HaveInsertPoint() && !Bypasses.isAlwaysBypassed()) {
emitBypassedVarInitsForSource(&S);
BypassingForwardGotos.push_back({Builder.GetInsertBlock(), &S});
}
@@ -1804,8 +1803,7 @@ void CodeGenFunction::EmitCaseStmtRange(const CaseStmt &S,
// switch machinery to enter this block.
llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, &S);
- if (getLangOpts().CPlusPlus)
- emitBypassedVarInitsForSource(&S);
+ emitBypassedVarInitsForSource(&S);
EmitStmt(S.getSubStmt());
// If range is empty, do nothing.
@@ -1944,8 +1942,7 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S,
llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, &S);
- if (getLangOpts().CPlusPlus)
- emitBypassedVarInitsForSource(&S);
+ emitBypassedVarInitsForSource(&S);
if (SwitchWeights)
SwitchWeights->push_back(getProfileCount(&S));
SwitchInsn->addCase(CaseVal, CaseDest);
@@ -1978,8 +1975,7 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S,
CaseDest = createBasicBlock("sw.bb");
EmitBlockWithFallThrough(CaseDest, CurCase);
}
- if (getLangOpts().CPlusPlus)
- emitBypassedVarInitsForSource(CurCase);
+ emitBypassedVarInitsForSource(CurCase);
// Since this loop is only executed when the CaseStmt has no attributes
// use a hard-coded value.
if (SwitchLikelihood)
@@ -2017,8 +2013,7 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt &S,
SwitchLikelihood->front() = Stmt::getLikelihood(Attrs);
EmitBlockWithFallThrough(DefaultBlock, &S);
- if (getLangOpts().CPlusPlus)
- emitBypassedVarInitsForSource(&S);
+ emitBypassedVarInitsForSource(&S);
EmitStmt(S.getSubStmt());
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index cfe22d0f85fdc..34f838eef4315 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -303,7 +303,7 @@ class CodeGenFunction : public CodeGenTypeCache {
VarBypassDetector Bypasses;
// Addresses of bypassed variables, for re-emitting their
- // trivial-auto-var-init at a bypassing jump (C++ scope-reentry).
+ // trivial-auto-var-init at a jump that re-enters their scope.
llvm::SmallDenseMap<const VarDecl *, Address, 4> BypassedVarInits;
// Forward gotos that may bypass a not-yet-emitted declaration;
@@ -1121,10 +1121,6 @@ class CodeGenFunction : public CodeGenTypeCache {
SourceRange Range;
SmallVector<const LabelDecl *, 4> Labels;
LexicalScope *ParentScope;
- // Block through which this scope is entered, used to place
- // trivial-auto-var-init for bypassed variables in C. There can only be one
- // EntryBlock for a variable.
- llvm::BasicBlock *EntryBlock = nullptr;
LexicalScope(const LexicalScope &) = delete;
void operator=(const LexicalScope &) = delete;
@@ -1138,9 +1134,6 @@ class CodeGenFunction : public CodeGenTypeCache {
Labels.push_back(label);
}
- /// The block through which this scope is entered, or null.
- llvm::BasicBlock *getEntryBlock() const { return EntryBlock; }
-
/// Exit this cleanup scope, emitting any accumulated
/// cleanups.
~LexicalScope();
@@ -3577,9 +3570,8 @@ class CodeGenFunction : public CodeGenTypeCache {
QualType::DestructionKind dtorKind);
/// Re-emit trivial-auto-var-init stores for variables bypassed by the jump
- /// Source (C++ only). No-op in C, where bypassed variables are initialized
- /// once in the entry block, and in any function with a computed goto, where
- /// jump sources are unknown and a single function-scope init is used instead.
+ /// Source. No-op in a function containing a computed goto, where jump sources
+ /// are unknown and a single function-scope init is used instead.
void emitBypassedVarInitsForSource(const Stmt *Source);
void MaybeEmitDeferredVarDeclInit(const VarDecl *var);
diff --git a/clang/test/CodeGen/trivial-auto-var-init-bypass.c b/clang/test/CodeGen/trivial-auto-var-init-bypass.c
new file mode 100644
index 0000000000000..a261f0bc5e208
--- /dev/null
+++ b/clang/test/CodeGen/trivial-auto-var-init-bypass.c
@@ -0,0 +1,422 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefix=ZERO
+// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefix=PATTERN
+
+// A variable whose declaration is bypassed by a goto or switch is initialized
+// at each bypassing jump, since the jump re-enters the variable's scope. C uses
+// the same rule as C++ here; see CodeGenCXX/trivial-auto-var-init.cpp. Note
+// that this deliberately does not model C 6.2.4p6, under which the lifetime
+// would instead begin at entry into the enclosing block.
+
+void use(int *);
+
+// Not bypassed: initialized at the declaration, as usual.
+// ZERO-LABEL: define dso_local i32 @no_bypass(
+// ZERO-SAME: ) #[[ATTR0:[0-9]+]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1:![0-9]+]]
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[X]], align 4
+// ZERO-NEXT: ret i32 [[TMP0]]
+//
+// PATTERN-LABEL: define dso_local i32 @no_bypass(
+// PATTERN-SAME: ) #[[ATTR0:[0-9]+]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1:![0-9]+]]
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[X]], align 4
+// PATTERN-NEXT: ret i32 [[TMP0]]
+//
+int no_bypass(void) {
+ int x;
+ use(&x);
+ return x;
+}
+
+// Forward goto over the declaration.
+// ZERO-LABEL: define dso_local i32 @goto_bypass(
+// ZERO-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// ZERO-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// ZERO-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// ZERO: [[IF_THEN]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: br label %[[SKIP:.*]]
+// ZERO: [[IF_END]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: br label %[[SKIP]]
+// ZERO: [[SKIP]]:
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// ZERO-NEXT: ret i32 [[TMP1]]
+//
+// PATTERN-LABEL: define dso_local i32 @goto_bypass(
+// PATTERN-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// PATTERN-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// PATTERN: [[IF_THEN]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[SKIP:.*]]
+// PATTERN: [[IF_END]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[SKIP]]
+// PATTERN: [[SKIP]]:
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// PATTERN-NEXT: ret i32 [[TMP1]]
+//
+int goto_bypass(int c) {
+ if (c)
+ goto SKIP;
+ int x;
+SKIP:
+ use(&x);
+ return x;
+}
+
+// Declaration before the first case label: every case bypasses it.
+// ZERO-LABEL: define dso_local i32 @switch_bypass(
+// ZERO-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// ZERO-NEXT: switch i32 [[TMP0]], label %[[SW_EPILOG:.*]] [
+// ZERO-NEXT: i32 1, label %[[SW_BB:.*]]
+// ZERO-NEXT: i32 2, label %[[SW_BB1:.*]]
+// ZERO-NEXT: ]
+// ZERO: [[SW_BB]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// ZERO-NEXT: store i32 [[TMP1]], ptr [[RETVAL]], align 4
+// ZERO-NEXT: br label %[[RETURN:.*]]
+// ZERO: [[SW_BB1]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// ZERO-NEXT: store i32 [[TMP2]], ptr [[RETVAL]], align 4
+// ZERO-NEXT: br label %[[RETURN]]
+// ZERO: [[SW_EPILOG]]:
+// ZERO-NEXT: store i32 0, ptr [[RETVAL]], align 4
+// ZERO-NEXT: br label %[[RETURN]]
+// ZERO: [[RETURN]]:
+// ZERO-NEXT: [[TMP3:%.*]] = load i32, ptr [[RETVAL]], align 4
+// ZERO-NEXT: ret i32 [[TMP3]]
+//
+// PATTERN-LABEL: define dso_local i32 @switch_bypass(
+// PATTERN-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[RETVAL:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: switch i32 [[TMP0]], label %[[SW_EPILOG:.*]] [
+// PATTERN-NEXT: i32 1, label %[[SW_BB:.*]]
+// PATTERN-NEXT: i32 2, label %[[SW_BB1:.*]]
+// PATTERN-NEXT: ]
+// PATTERN: [[SW_BB]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: [[TMP1:%.*]] = load i32, ptr [[X]], align 4
+// PATTERN-NEXT: store i32 [[TMP1]], ptr [[RETVAL]], align 4
+// PATTERN-NEXT: br label %[[RETURN:.*]]
+// PATTERN: [[SW_BB1]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// PATTERN-NEXT: store i32 [[TMP2]], ptr [[RETVAL]], align 4
+// PATTERN-NEXT: br label %[[RETURN]]
+// PATTERN: [[SW_EPILOG]]:
+// PATTERN-NEXT: store i32 0, ptr [[RETVAL]], align 4
+// PATTERN-NEXT: br label %[[RETURN]]
+// PATTERN: [[RETURN]]:
+// PATTERN-NEXT: [[TMP3:%.*]] = load i32, ptr [[RETVAL]], align 4
+// PATTERN-NEXT: ret i32 [[TMP3]]
+//
+int switch_bypass(int c) {
+ switch (c) {
+ int x;
+ case 1:
+ use(&x);
+ return x;
+ case 2:
+ use(&x);
+ return x;
+ }
+ return 0;
+}
+
+// A case label inside a nested block jumps into the middle of that block's
+// scope, so case 1 bypasses the declaration while case 0 reaches it.
+// ZERO-LABEL: define dso_local void @switch_bypass_into_block(
+// ZERO-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// ZERO-NEXT: switch i32 [[TMP0]], label %[[SW_EPILOG:.*]] [
+// ZERO-NEXT: i32 0, label %[[SW_BB:.*]]
+// ZERO-NEXT: i32 1, label %[[SW_BB1:.*]]
+// ZERO-NEXT: ]
+// ZERO: [[SW_BB]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: br label %[[SW_BB1]]
+// ZERO: [[SW_BB1]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: br label %[[SW_EPILOG]]
+// ZERO: [[SW_EPILOG]]:
+// ZERO-NEXT: ret void
+//
+// PATTERN-LABEL: define dso_local void @switch_bypass_into_block(
+// PATTERN-SAME: i32 noundef [[C:%.*]]) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[C_ADDR:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store i32 [[C]], ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[C_ADDR]], align 4
+// PATTERN-NEXT: switch i32 [[TMP0]], label %[[SW_EPILOG:.*]] [
+// PATTERN-NEXT: i32 0, label %[[SW_BB:.*]]
+// PATTERN-NEXT: i32 1, label %[[SW_BB1:.*]]
+// PATTERN-NEXT: ]
+// PATTERN: [[SW_BB]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[SW_BB1]]
+// PATTERN: [[SW_BB1]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: br label %[[SW_EPILOG]]
+// PATTERN: [[SW_EPILOG]]:
+// PATTERN-NEXT: ret void
+//
+void switch_bypass_into_block(int c) {
+ switch (c) {
+ case 0: {
+ int x;
+ case 1:
+ use(&x);
+ }
+ }
+}
+
+// Bypassed inside a loop: reinitialized on every iteration, not once.
+// ZERO-LABEL: define dso_local void @loop_bypass(
+// ZERO-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4
+// ZERO-NEXT: br label %[[WHILE_COND:.*]]
+// ZERO: [[WHILE_COND]]:
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4
+// ZERO-NEXT: [[DEC:%.*]] = add nsw i32 [[TMP0]], -1
+// ZERO-NEXT: store i32 [[DEC]], ptr [[N_ADDR]], align 4
+// ZERO-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// ZERO-NEXT: br i1 [[TOBOOL]], label %[[WHILE_BODY:.*]], label %[[WHILE_END:.*]]
+// ZERO: [[WHILE_BODY]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: br label %[[SKIP:.*]]
+// ZERO: [[SKIP]]:
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: br label %[[WHILE_COND]], !llvm.loop [[LOOP2:![0-9]+]]
+// ZERO: [[WHILE_END]]:
+// ZERO-NEXT: ret void
+//
+// PATTERN-LABEL: define dso_local void @loop_bypass(
+// PATTERN-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4
+// PATTERN-NEXT: br label %[[WHILE_COND:.*]]
+// PATTERN: [[WHILE_COND]]:
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4
+// PATTERN-NEXT: [[DEC:%.*]] = add nsw i32 [[TMP0]], -1
+// PATTERN-NEXT: store i32 [[DEC]], ptr [[N_ADDR]], align 4
+// PATTERN-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// PATTERN-NEXT: br i1 [[TOBOOL]], label %[[WHILE_BODY:.*]], label %[[WHILE_END:.*]]
+// PATTERN: [[WHILE_BODY]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[SKIP:.*]]
+// PATTERN: [[SKIP]]:
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: br label %[[WHILE_COND]], !llvm.loop [[LOOP2:![0-9]+]]
+// PATTERN: [[WHILE_END]]:
+// PATTERN-NEXT: ret void
+//
+void loop_bypass(int n) {
+ while (n--) {
+ goto SKIP;
+ int x;
+ SKIP:
+ use(&x);
+ }
+}
+
+// Eli Friedman's case from the review. Under C 6.2.4p6 x's lifetime would begin
+// at entry to the function body block, so `goto BEGIN` would preserve it and
+// the store through p would be visible. We use the C++ rule instead: `goto
+// CONT` re-enters p's scope and reinitializes it.
+// ZERO-LABEL: define dso_local i32 @backward_goto_around_decl(
+// ZERO-SAME: ) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[B:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[P:%.*]] = alloca ptr, align 8
+// ZERO-NEXT: store i32 0, ptr [[B]], align 4
+// ZERO-NEXT: br label %[[BEGIN:.*]]
+// ZERO: [[BEGIN]]:
+// ZERO-NEXT: store ptr null, ptr [[P]], align 8, !annotation [[META1]]
+// ZERO-NEXT: br label %[[CONT:.*]]
+// ZERO: [[CONT]]:
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[B]], align 4
+// ZERO-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// ZERO-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// ZERO: [[IF_THEN]]:
+// ZERO-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 8
+// ZERO-NEXT: store i32 10, ptr [[TMP1]], align 4
+// ZERO-NEXT: br label %[[IF_END]]
+// ZERO: [[IF_END]]:
+// ZERO-NEXT: store ptr [[B]], ptr [[P]], align 8
+// ZERO-NEXT: [[TMP2:%.*]] = load i32, ptr [[B]], align 4
+// ZERO-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP2]], 0
+// ZERO-NEXT: br i1 [[TOBOOL1]], label %[[IF_END3:.*]], label %[[IF_THEN2:.*]]
+// ZERO: [[IF_THEN2]]:
+// ZERO-NEXT: store i32 1, ptr [[B]], align 4
+// ZERO-NEXT: br label %[[BEGIN]]
+// ZERO: [[IF_END3]]:
+// ZERO-NEXT: [[TMP3:%.*]] = load i32, ptr [[B]], align 4
+// ZERO-NEXT: ret i32 [[TMP3]]
+//
+// PATTERN-LABEL: define dso_local i32 @backward_goto_around_decl(
+// PATTERN-SAME: ) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[B:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[P:%.*]] = alloca ptr, align 8
+// PATTERN-NEXT: store i32 0, ptr [[B]], align 4
+// PATTERN-NEXT: br label %[[BEGIN:.*]]
+// PATTERN: [[BEGIN]]:
+// PATTERN-NEXT: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr [[P]], align 8, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[CONT:.*]]
+// PATTERN: [[CONT]]:
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[B]], align 4
+// PATTERN-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// PATTERN-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// PATTERN: [[IF_THEN]]:
+// PATTERN-NEXT: [[TMP1:%.*]] = load ptr, ptr [[P]], align 8
+// PATTERN-NEXT: store i32 10, ptr [[TMP1]], align 4
+// PATTERN-NEXT: br label %[[IF_END]]
+// PATTERN: [[IF_END]]:
+// PATTERN-NEXT: store ptr [[B]], ptr [[P]], align 8
+// PATTERN-NEXT: [[TMP2:%.*]] = load i32, ptr [[B]], align 4
+// PATTERN-NEXT: [[TOBOOL1:%.*]] = icmp ne i32 [[TMP2]], 0
+// PATTERN-NEXT: br i1 [[TOBOOL1]], label %[[IF_END3:.*]], label %[[IF_THEN2:.*]]
+// PATTERN: [[IF_THEN2]]:
+// PATTERN-NEXT: store i32 1, ptr [[B]], align 4
+// PATTERN-NEXT: br label %[[BEGIN]]
+// PATTERN: [[IF_END3]]:
+// PATTERN-NEXT: [[TMP3:%.*]] = load i32, ptr [[B]], align 4
+// PATTERN-NEXT: ret i32 [[TMP3]]
+//
+int backward_goto_around_decl(void) {
+ int b = 0;
+BEGIN:;
+ goto CONT;
+ int *p;
+CONT:
+ if (b)
+ *p = 10;
+ p = &b;
+ if (!b) {
+ b = 1;
+ goto BEGIN;
+ }
+ return b;
+}
+
+// A computed goto can land anywhere, so the bypassing jumps can't be
+// identified. Fall back to a single initialization in the entry block.
+// ZERO-LABEL: define dso_local void @computed_goto(
+// ZERO-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] {
+// ZERO-NEXT: [[ENTRY:.*:]]
+// ZERO-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4
+// ZERO-NEXT: [[TARGET:%.*]] = alloca ptr, align 8
+// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4
+// ZERO-NEXT: store ptr null, ptr [[TARGET]], align 8, !annotation [[META1]]
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4
+// ZERO-NEXT: store ptr blockaddress(@computed_goto, %[[SKIP:.*]]), ptr [[TARGET]], align 8
+// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4
+// ZERO-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// ZERO-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// ZERO: [[IF_THEN]]:
+// ZERO-NEXT: [[TMP1:%.*]] = load ptr, ptr [[TARGET]], align 8
+// ZERO-NEXT: br label %[[INDIRECTGOTO:.*]]
+// ZERO: [[IF_END]]:
+// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]]
+// ZERO-NEXT: br label %[[SKIP]]
+// ZERO: [[SKIP]]:
+// ZERO-NEXT: call void @use(ptr noundef [[X]])
+// ZERO-NEXT: ret void
+// ZERO: [[INDIRECTGOTO]]:
+// ZERO-NEXT: [[INDIRECT_GOTO_DEST:%.*]] = phi ptr [ [[TMP1]], %[[IF_THEN]] ]
+// ZERO-NEXT: indirectbr ptr [[INDIRECT_GOTO_DEST]], [label %[[SKIP]]]
+//
+// PATTERN-LABEL: define dso_local void @computed_goto(
+// PATTERN-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] {
+// PATTERN-NEXT: [[ENTRY:.*:]]
+// PATTERN-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: [[TARGET:%.*]] = alloca ptr, align 8
+// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4
+// PATTERN-NEXT: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr [[TARGET]], align 8, !annotation [[META1]]
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4
+// PATTERN-NEXT: store ptr blockaddress(@computed_goto, %[[SKIP:.*]]), ptr [[TARGET]], align 8
+// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4
+// PATTERN-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP0]], 0
+// PATTERN-NEXT: br i1 [[TOBOOL]], label %[[IF_THEN:.*]], label %[[IF_END:.*]]
+// PATTERN: [[IF_THEN]]:
+// PATTERN-NEXT: [[TMP1:%.*]] = load ptr, ptr [[TARGET]], align 8
+// PATTERN-NEXT: br label %[[INDIRECTGOTO:.*]]
+// PATTERN: [[IF_END]]:
+// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]]
+// PATTERN-NEXT: br label %[[SKIP]]
+// PATTERN: [[SKIP]]:
+// PATTERN-NEXT: call void @use(ptr noundef [[X]])
+// PATTERN-NEXT: ret void
+// PATTERN: [[INDIRECTGOTO]]:
+// PATTERN-NEXT: [[INDIRECT_GOTO_DEST:%.*]] = phi ptr [ [[TMP1]], %[[IF_THEN]] ]
+// PATTERN-NEXT: indirectbr ptr [[INDIRECT_GOTO_DEST]], [label %[[SKIP]]]
+//
+void computed_goto(int n) {
+ void *target = &&SKIP;
+ if (n)
+ goto *target;
+ int x;
+SKIP:
+ use(&x);
+}
+//.
+// ZERO: [[META1]] = !{!"auto-init"}
+// ZERO: [[LOOP2]] = distinct !{[[LOOP2]], [[META3:![0-9]+]]}
+// ZERO: [[META3]] = !{!"llvm.loop.mustprogress"}
+//.
+// PATTERN: [[META1]] = !{!"auto-init"}
+// PATTERN: [[LOOP2]] = distinct !{[[LOOP2]], [[META3:![0-9]+]]}
+// PATTERN: [[META3]] = !{!"llvm.loop.mustprogress"}
+//.
diff --git a/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c b/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
deleted file mode 100644
index 8cd0ffaa981ad..0000000000000
--- a/clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c
+++ /dev/null
@@ -1,265 +0,0 @@
-// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=zero %s -emit-llvm -o - | FileCheck %s --check-prefix=ZERO
-// RUN: %clang_cc1 -triple x86_64-unknown-unknown -ftrivial-auto-var-init=pattern %s -emit-llvm -o - | FileCheck %s --check-prefix=PATTERN
-
-// In C, a bypassed variable's lifetime begins at entry into its enclosing
-// block (C6.2.4p6), so it is initialized at that block's entry: once for a
-// function-scoped variable, every iteration for a loop-scoped one. C++ differs
-// (lifetime restarts on scope re-entry); see CodeGenCXX/trivial-auto-var-init.cpp.
-
-void use_int(int *);
-
-// Not bypassed: declaration is reached each iteration, so init is at BEGIN.
-// ZERO-LABEL: define {{.*}}@backward_goto_pointer(
-// ZERO: entry:
-// ZERO-NOT: !annotation
-// ZERO: BEGIN:
-// ZERO: store ptr null, ptr %p, {{.*}}!annotation [[AUTO_INIT:!.+]]
-// PATTERN-LABEL: define {{.*}}@backward_goto_pointer(
-// PATTERN: entry:
-// PATTERN-NOT: !annotation
-// PATTERN: BEGIN:
-// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %p, {{.*}}!annotation [[AUTO_INIT:!.+]]
-int backward_goto_pointer(void) {
- int b = 0;
-BEGIN:;
- int *p;
- if (b)
- *p = 10;
- p = &b;
- if (!b) {
- b = 1;
- goto BEGIN;
- }
- return b;
-}
-
-// Scalar variant of the above.
-// ZERO-LABEL: define {{.*}}@backward_goto_scalar(
-// ZERO: entry:
-// ZERO-NOT: !annotation
-// ZERO: BEGIN:
-// ZERO: store i32 0, ptr %c, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN-LABEL: define {{.*}}@backward_goto_scalar(
-// PATTERN: entry:
-// PATTERN-NOT: !annotation
-// PATTERN: BEGIN:
-// PATTERN: store i32 -1431655766, ptr %c, {{.*}}!annotation [[AUTO_INIT]]
-int backward_goto_scalar(void) {
- int b = 0;
-BEGIN:;
- int c;
- if (b) return c;
- c = 5;
- b = 1;
- goto BEGIN;
-}
-
-// Bypassed, function-scoped: init once in entry, so p = &b survives the
-// backward goto (returns 10).
-// ZERO-LABEL: define {{.*}}@backward_goto_around_decl(
-// ZERO: entry:
-// ZERO: store ptr null, ptr %p, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: BEGIN:
-// ZERO-NOT: !annotation
-// ZERO: ret
-// PATTERN-LABEL: define {{.*}}@backward_goto_around_decl(
-// PATTERN: entry:
-// PATTERN: store ptr inttoptr (i64 -6148914691236517206 to ptr), ptr %p, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: BEGIN:
-// PATTERN-NOT: !annotation
-// PATTERN: ret
-int backward_goto_around_decl(void) {
- int b = 0;
-BEGIN:;
- goto CONT;
- int *p;
-CONT:
- if (b)
- *p = 10;
- p = &b;
- if (!b) {
- b = 1;
- goto BEGIN;
- }
- return b;
-}
-
-// Loop-scoped: init at the loop body's entry (while.body), so it reruns each
-// iteration rather than once in entry.
-// ZERO-LABEL: define {{.*}}@loop_bypass(
-// ZERO: entry:
-// ZERO-NOT: !annotation
-// ZERO: while.body:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN-LABEL: define {{.*}}@loop_bypass(
-// PATTERN: entry:
-// PATTERN-NOT: !annotation
-// PATTERN: while.body:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-void loop_bypass(void) {
- while (1) {
- goto X;
- int x;
- X:
- use_int(&x);
- }
-}
-
-// Switch bypass, function-scoped: init once in entry before the dispatch.
-// ZERO-LABEL: define {{.*}}@switch_bypass(
-// ZERO: entry:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: switch i32
-// PATTERN-LABEL: define {{.*}}@switch_bypass(
-// PATTERN: entry:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: switch i32
-int switch_bypass(int c) {
- switch (c) {
- int x;
- case 0:
- x = 1;
- use_int(&x);
- return x;
- default:
- use_int(&x);
- return x;
- }
-}
-
-// Switch bypass in a loop: init at the dispatch block (while.body), per
-// iteration.
-// ZERO-LABEL: define {{.*}}@switch_bypass_in_loop(
-// ZERO: entry:
-// ZERO-NOT: !annotation
-// ZERO: while.body:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: switch i32
-// PATTERN-LABEL: define {{.*}}@switch_bypass_in_loop(
-// PATTERN: entry:
-// PATTERN-NOT: !annotation
-// PATTERN: while.body:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: switch i32
-void switch_bypass_in_loop(int c) {
- while (1) {
- switch (c) {
- int x;
- case 0:
- x = 1;
- use_int(&x);
- break;
- default:
- use_int(&x);
- break;
- }
- }
-}
-
-// Computed goto: sources unknown, so init once in entry.
-// ZERO-LABEL: define {{.*}}@computed_goto(
-// ZERO: entry:
-// ZERO: store i32 0, ptr %y, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: indirectbr
-// PATTERN-LABEL: define {{.*}}@computed_goto(
-// PATTERN: entry:
-// PATTERN: store i32 -1431655766, ptr %y, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: indirectbr
-void computed_goto(int n) {
- void *t[] = {&&L1, &&L2};
- goto *t[n];
- int y;
-L1:
- use_int(&y);
- return;
-L2:
- return;
-}
-
-// Nested loops: init at the inner body's entry (while.body3), per inner
-// iteration -- not in entry or the outer body.
-// ZERO-LABEL: define {{.*}}@nested_loops(
-// ZERO: entry:
-// ZERO-NOT: store {{.*}}%x{{.*}}!annotation
-// ZERO: while.body3:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN-LABEL: define {{.*}}@nested_loops(
-// PATTERN: entry:
-// PATTERN-NOT: store {{.*}}%x{{.*}}!annotation
-// PATTERN: while.body3:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-void nested_loops(int n) {
- while (n) {
- while (n) {
- goto X;
- int x;
- X:
- use_int(&x);
- n--;
- }
- }
-}
-
-// Nested loops + switch: init at the inner dispatch block (while.body3), per
-// inner iteration.
-// ZERO-LABEL: define {{.*}}@nested_loops_switch(
-// ZERO: entry:
-// ZERO-NOT: store {{.*}}%x{{.*}}!annotation
-// ZERO: while.body3:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: switch i32
-// PATTERN-LABEL: define {{.*}}@nested_loops_switch(
-// PATTERN: entry:
-// PATTERN-NOT: store {{.*}}%x{{.*}}!annotation
-// PATTERN: while.body3:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: switch i32
-void nested_loops_switch(int n, int c) {
- while (n) {
- while (n) {
- switch (c) {
- int x;
- case 0:
- x = 1;
- use_int(&x);
- break;
- default:
- use_int(&x);
- break;
- }
- n--;
- }
- }
-}
-
-// Computed goto with multiple scopes: sources unknown, so every bypassed
-// variable gets a single function-scope init in entry. The switch case targets
-// must not add a second init store for %x.
-// ZERO-LABEL: define {{.*}}@computed_goto_multi_scope(
-// ZERO: entry:
-// ZERO: store i32 0, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// ZERO: indirectbr
-// ZERO-NOT: store i32 0, ptr %x, {{.*}}!annotation
-// PATTERN-LABEL: define {{.*}}@computed_goto_multi_scope(
-// PATTERN: entry:
-// PATTERN: store i32 -1431655766, ptr %x, {{.*}}!annotation [[AUTO_INIT]]
-// PATTERN: indirectbr
-// PATTERN-NOT: store i32 -1431655766, ptr %x, {{.*}}!annotation
-void computed_goto_multi_scope(int n, int c) {
- void *t[] = {&&L1, &&L2};
- goto *t[n];
- int x;
- switch (c) {
- case 0:
- L1:
- use_int(&x);
- break;
- default:
- L2:
- use_int(&x);
- break;
- }
-}
-
-// ZERO: [[AUTO_INIT]] = !{!"auto-init"}
-// PATTERN: [[AUTO_INIT]] = !{!"auto-init"}
More information about the cfe-commits
mailing list