https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/181937
>From b31c779d45c8dffcefd432a23580c4515d425935 Mon Sep 17 00:00:00 2001 From: Justin Stitt <[email protected]> Date: Tue, 17 Feb 2026 15:18:03 -0800 Subject: [PATCH 1/3] [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 | 7 +- clang/lib/CodeGen/CGDecl.cpp | 67 +++- clang/lib/CodeGen/CGStmt.cpp | 9 + clang/lib/CodeGen/CodeGenFunction.cpp | 8 +- clang/lib/CodeGen/CodeGenFunction.h | 17 + clang/lib/CodeGen/VarBypassDetector.cpp | 9 +- clang/lib/CodeGen/VarBypassDetector.h | 20 +- .../test/CodeGenCXX/trivial-auto-var-init.cpp | 295 ++++++++++++++++-- 8 files changed, 391 insertions(+), 41 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 061ccd2492c49..76530edec00e7 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -216,9 +216,10 @@ Deprecated Compiler Flags Modified Compiler Flags ----------------------- -- The `-mno-outline` and `-moutline` compiler flags are now allowed on RISC-V and X86, which both support the machine outliner. -- The `-mno-outline` flag will now add the `nooutline` IR attribute, so that - `-mno-outline` and `-moutline` objects can be mixed correctly during LTO. +- ``-ftrivial-auto-var-init=zero`` and ``-ftrivial-auto-var-init=pattern`` now + correctly initialize variables whose declaration is bypassed by ``goto`` or + ``switch`` statements. Previously, such variables were silently left + uninitialized. Removed Compiler Flags ---------------------- diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 3f32fecc50998..885784c70cf4b 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1636,6 +1636,37 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) { } } + if (Bypasses.IsBypassed(&D) && !emission.IsEscapingByRef && + !emission.NRVOFlag && !Ty->isVariablyModifiedType()) { + if (getAutoVarInitKind(Ty, D) != + LangOptions::TrivialAutoVarInitKind::Uninitialized && + isTrivialInitializer(D.getInit())) { + // Record this bypassed var's address for switch-case init emission. + BypassedVarInits.push_back({&D, address}); + + if (Bypasses.isAlwaysBypassed()) { + // Computed gotos: we can't determine jump sources, so init in the + // entry block next to the alloca. + llvm::IRBuilderBase::InsertPointGuard IPG(Builder); + Builder.SetInsertPoint(getPostAllocaInsertPoint()); + emitZeroOrPatternForAutoVarInit(Ty, D, address); + } else { + // For forward gotos that bypass this var, retroactively insert + // init stores before the goto's branch instruction. + for (const auto &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); + } + } + } + } + } + } + if (D.hasAttr<StackProtectorIgnoreAttr>()) { if (auto *AI = dyn_cast<llvm::AllocaInst>(address.getBasePointer())) { llvm::LLVMContext &Ctx = Builder.getContext(); @@ -1833,6 +1864,33 @@ 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) { + for (const auto &[D, Addr] : BypassedVarInits) { + if (D != VD) + continue; + QualType Ty = VD->getType().getNonReferenceType(); + emitZeroOrPatternForAutoVarInit(Ty, *VD, Addr); + break; + } + } +} + void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D, Address Loc) { @@ -1992,16 +2050,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 a75d3dc64c6b4..bd04415ba57a3 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -834,6 +834,11 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) { if (HaveInsertPoint()) EmitStopPoint(&S); + // Record this goto so that EmitAutoVarAlloca can retroactively insert + // trivial-auto-var-init stores for any variables this goto bypasses. + if (HaveInsertPoint()) + BypassingForwardGotos.push_back({Builder.GetInsertBlock(), &S}); + ApplyAtomGroup Grp(getDebugInfo()); EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel())); } @@ -1741,6 +1746,7 @@ void CodeGenFunction::EmitCaseStmtRange(const CaseStmt &S, // switch machinery to enter this block. llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb"); EmitBlockWithFallThrough(CaseDest, &S); + emitBypassedVarInitsForSource(&S); EmitStmt(S.getSubStmt()); // If range is empty, do nothing. @@ -1878,6 +1884,7 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S, llvm::BasicBlock *CaseDest = createBasicBlock("sw.bb"); EmitBlockWithFallThrough(CaseDest, &S); + emitBypassedVarInitsForSource(&S); if (SwitchWeights) SwitchWeights->push_back(getProfileCount(&S)); SwitchInsn->addCase(CaseVal, CaseDest); @@ -1910,6 +1917,7 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S, CaseDest = createBasicBlock("sw.bb"); EmitBlockWithFallThrough(CaseDest, CurCase); } + emitBypassedVarInitsForSource(CurCase); // Since this loop is only executed when the CaseStmt has no attributes // use a hard-coded value. if (SwitchLikelihood) @@ -1947,6 +1955,7 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt &S, SwitchLikelihood->front() = Stmt::getLikelihood(Attrs); EmitBlockWithFallThrough(DefaultBlock, &S); + emitBypassedVarInitsForSource(&S); EmitStmt(S.getSubStmt()); } diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 77c6866bbefa6..947b72d161b17 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1544,8 +1544,12 @@ void CodeGenFunction::GenerateCode(GlobalDecl GD, llvm::Function *Fn, ShouldEmitLifetimeMarkers = true; // Initialize helper which will detect jumps which can cause invalid - // lifetime markers. - if (ShouldEmitLifetimeMarkers) + // lifetime markers or bypass trivial 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 fd474c09044ef..e0727d3b2f5bd 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -25,6 +25,7 @@ #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.h" +#include "clang/AST/Stmt.h" #include "clang/AST/StmtOpenACC.h" #include "clang/AST/StmtOpenMP.h" #include "clang/AST/StmtSYCL.h" @@ -33,6 +34,7 @@ #include "clang/Basic/CapturedStmt.h" #include "clang/Basic/CodeGenOptions.h" #include "clang/Basic/OpenMPKinds.h" +#include "clang/Basic/SourceLocation.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" @@ -299,6 +301,16 @@ class CodeGenFunction : public CodeGenTypeCache { // because of jumps. VarBypassDetector Bypasses; + // Map from bypassed VarDecl to its alloca address, for per-target init. + llvm::SmallVector<std::pair<const VarDecl *, Address>, 4> BypassedVarInits; + + struct BypassingForwardGoto { + 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 @@ -3535,6 +3547,9 @@ class CodeGenFunction : public CodeGenTypeCache { void emitAutoVarTypeCleanup(const AutoVarEmission &emission, QualType::DestructionKind dtorKind); + /// Emit zero/pattern init stores for bypassed variables at a jump target. + void emitBypassedVarInitsForSource(const Stmt *Source); + void MaybeEmitDeferredVarDeclInit(const VarDecl *var); /// Emits the alloca and debug information for the size expressions for each @@ -5515,6 +5530,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..1aabdd32da441 100644 --- a/clang/lib/CodeGen/VarBypassDetector.cpp +++ b/clang/lib/CodeGen/VarBypassDetector.cpp @@ -22,6 +22,7 @@ void VarBypassDetector::Init(CodeGenModule &CGM, const Stmt *Body) { FromScopes.clear(); ToScopes.clear(); Bypasses.clear(); + BypassedVarsAtSource.clear(); Scopes = {{~0U, nullptr}}; unsigned ParentScope = 0; AlwaysBypassed = !BuildScopeInformation(CGM, Body, ParentScope); @@ -144,11 +145,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 +158,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..067a501c16434 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 jump source to the set of variable declarations bypassed by the + // jump. + 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 precise bypass info is unavailable (e.g. computed gotos) + /// and all variables should be treated as bypassed. + bool isAlwaysBypassed() const { return AlwaysBypassed; } + + /// Returns the set of variable declarations bypassed by jumps to the given + /// target statement, or nullptr if no variables are bypassed to that target. + 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/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp index e21a307f33121..af65123172008 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,262 @@ 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); +} + } // extern "C" // CHECK: [[AUTO_INIT]] = !{ !"auto-init" } >From 4137ce32033367c6bb88ea2b463b56905c5599b4 Mon Sep 17 00:00:00 2001 From: Justin Stitt <[email protected]> Date: Thu, 2 Apr 2026 18:30:23 -0700 Subject: [PATCH 2/3] handle backward goto bypass case Signed-off-by: Justin Stitt <[email protected]> --- clang/docs/ReleaseNotes.rst | 4 +++ clang/lib/CodeGen/CGStmt.cpp | 4 ++- clang/lib/CodeGen/CodeGenFunction.h | 2 +- .../test/CodeGenCXX/trivial-auto-var-init.cpp | 26 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 76530edec00e7..3df20528b12ed 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -216,6 +216,10 @@ Deprecated Compiler Flags Modified Compiler Flags ----------------------- +- The `-mno-outline` and `-moutline` compiler flags are now allowed on RISC-V and X86, which both support the machine outliner. +- The `-mno-outline` flag will now add the `nooutline` IR attribute, so that + `-mno-outline` and `-moutline` objects can be mixed correctly during LTO. + - ``-ftrivial-auto-var-init=zero`` and ``-ftrivial-auto-var-init=pattern`` now correctly initialize variables whose declaration is bypassed by ``goto`` or ``switch`` statements. Previously, such variables were silently left diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index bd04415ba57a3..1c9acaba890e6 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -836,8 +836,10 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) { // Record this goto so that EmitAutoVarAlloca can retroactively insert // trivial-auto-var-init stores for any variables this goto bypasses. - if (HaveInsertPoint()) + if (HaveInsertPoint()) { + emitBypassedVarInitsForSource(&S); BypassingForwardGotos.push_back({Builder.GetInsertBlock(), &S}); + } ApplyAtomGroup Grp(getDebugInfo()); EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel())); diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index e0727d3b2f5bd..19d56e672a93c 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -305,7 +305,7 @@ class CodeGenFunction : public CodeGenTypeCache { llvm::SmallVector<std::pair<const VarDecl *, Address>, 4> BypassedVarInits; struct BypassingForwardGoto { - llvm::BasicBlock *Block; + llvm::AssertingVH<llvm::BasicBlock> Block; const GotoStmt *Goto; }; diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp index af65123172008..6dc2e3cc71ebb 100644 --- a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp +++ b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp @@ -565,6 +565,32 @@ void test_goto_multiple_vars() { 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; +} + } // extern "C" // CHECK: [[AUTO_INIT]] = !{ !"auto-init" } >From c5cb892acb55f149e35e88b42b415368e250bd11 Mon Sep 17 00:00:00 2001 From: Justin Stitt <[email protected]> Date: Mon, 6 Apr 2026 14:04:57 -0700 Subject: [PATCH 3/3] use DenseMap to store BypassedVarInits, optimize lookups Signed-off-by: Justin Stitt <[email protected]> --- clang/lib/CodeGen/CGDecl.cpp | 10 ++++------ clang/lib/CodeGen/CodeGenFunction.h | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 885784c70cf4b..d4e902ca1e59f 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1642,7 +1642,7 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) { LangOptions::TrivialAutoVarInitKind::Uninitialized && isTrivialInitializer(D.getInit())) { // Record this bypassed var's address for switch-case init emission. - BypassedVarInits.push_back({&D, address}); + BypassedVarInits.insert({&D, address}); if (Bypasses.isAlwaysBypassed()) { // Computed gotos: we can't determine jump sources, so init in the @@ -1881,12 +1881,10 @@ void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) { if (!Vars) return; for (const VarDecl *VD : *Vars) { - for (const auto &[D, Addr] : BypassedVarInits) { - if (D != VD) - continue; + auto It = BypassedVarInits.find(VD); + if (It != BypassedVarInits.end()) { QualType Ty = VD->getType().getNonReferenceType(); - emitZeroOrPatternForAutoVarInit(Ty, *VD, Addr); - break; + emitZeroOrPatternForAutoVarInit(Ty, *VD, It->second); } } } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 19d56e672a93c..e48a90413c76c 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -302,7 +302,7 @@ class CodeGenFunction : public CodeGenTypeCache { VarBypassDetector Bypasses; // Map from bypassed VarDecl to its alloca address, for per-target init. - llvm::SmallVector<std::pair<const VarDecl *, Address>, 4> BypassedVarInits; + llvm::SmallDenseMap<const VarDecl *, Address, 4> BypassedVarInits; struct BypassingForwardGoto { llvm::AssertingVH<llvm::BasicBlock> Block; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
