https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/181937
>From 614ea3a54b80c80ef5322bfd474c4d23315caf50 Mon Sep 17 00:00:00 2001 From: Justin Stitt <[email protected]> Date: Tue, 17 Feb 2026 15:18:03 -0800 Subject: [PATCH] [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 <[email protected]> --- clang/docs/ReleaseNotes.rst | 8 + 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 ++++++++++++++++-- 10 files changed, 797 insertions(+), 39 deletions(-) create mode 100644 clang/test/CodeGen/trivial-auto-var-init-c-backward-goto.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index f1e9b2d52ec97..9c8589bd9d749 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -427,6 +427,14 @@ Modified Compiler Flags and now defines the standard macros __DATE__, __TIME__ and __TIMESTAMP__ to "1". The previous functionality remains unchanged. +- ``-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 ---------------------- diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp index 7421733efcc24..74787780935be 100644 --- a/clang/lib/CodeGen/CGDebugInfo.cpp +++ b/clang/lib/CodeGen/CGDebugInfo.cpp @@ -6755,6 +6755,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 7608f8cb6fc7a..ba64979ee696c 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1637,6 +1637,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(); @@ -1834,6 +1872,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) { @@ -1993,16 +2056,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 232094777f233..a7ab94eb1fb96 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -837,6 +837,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())); } @@ -1749,6 +1757,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. @@ -1886,6 +1896,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); @@ -1918,6 +1930,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) @@ -1955,6 +1969,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 b920266b59808..9674e8f216a07 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1545,9 +1545,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 6d0718c243812..9ec2855286c3e 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -301,6 +301,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 @@ -1104,6 +1116,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; @@ -1117,6 +1133,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(); @@ -3537,6 +3556,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 @@ -5534,6 +5558,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" } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
