https://github.com/JustinStitt updated https://github.com/llvm/llvm-project/pull/181937
>From 9c8b0d4c756001eea2d91ce6cec1594579b448c6 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.md | 6 + clang/docs/UsersManual.md | 48 ++ clang/lib/CodeGen/CGDecl.cpp | 72 +- clang/lib/CodeGen/CGStmt.cpp | 21 + clang/lib/CodeGen/CodeGenFunction.cpp | 9 +- clang/lib/CodeGen/CodeGenFunction.h | 20 + clang/lib/CodeGen/VarBypassDetector.cpp | 38 +- clang/lib/CodeGen/VarBypassDetector.h | 21 +- .../CodeGen/trivial-auto-var-init-bypass.c | 646 ++++++++++++++++++ .../test/CodeGenCXX/trivial-auto-var-init.cpp | 447 +++++++++++- 10 files changed, 1289 insertions(+), 39 deletions(-) create mode 100644 clang/test/CodeGen/trivial-auto-var-init-bypass.c diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index b82a8b4e00b44b..16a575fda7bef5 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -277,6 +277,12 @@ features cannot lower the translation-unit ABI level; - All options of the `-fzero-call-used-regs` compiler flag are now allowed on RISC-V. +- `-ftrivial-auto-var-init=` now initializes variables whose declaration is + bypassed by a `goto` or `switch`, which were previously left uninitialized. + The initialization is emitted at each bypassing jump. See the + `-ftrivial-auto-var-init=` entry in the User's Manual for the details, + including where C deliberately departs from C 6.2.4p6. + ### Removed Compiler Flags ### Attribute Changes in Clang diff --git a/clang/docs/UsersManual.md b/clang/docs/UsersManual.md index 70c248724150d6..7a0bd54c219fbb 100644 --- a/clang/docs/UsersManual.md +++ b/clang/docs/UsersManual.md @@ -2984,6 +2984,54 @@ such as kernels and boot loaders, that performs pointer arithmetic over externally defined memory layouts rather than ordinary C or C++ objects. ::: +:::{option} -ftrivial-auto-var-init=[uninitialized, zero, pattern] + +Initialize automatic variables that would otherwise be left uninitialized. +`zero` stores zeroes and `pattern` stores a repeated, target-specific byte +pattern chosen to be likely to fault or to look obviously wrong if it is ever +used as a pointer or a length. The default, `uninitialized`, disables the +feature. + +This is a hardening measure, not a correctness feature: reading an +uninitialized variable remains undefined behavior, and the flag only bounds the +damage by making the value deterministic. Use `-Wuninitialized`, or a tool like +{doc}`MemorySanitizer`, to find such reads in the first place. + +Individual variables can opt out with `__attribute__((uninitialized))`, and +whole functions with `__attribute__((no_trivial_auto_var_init))`. +`-ftrivial-auto-var-init-max-size=` skips variables above a given size, and +`-ftrivial-auto-var-init-stop-after=` stops after a given number of variables; +both exist to bound code size while triaging. + +The initialization is normally emitted where the variable is declared. A `goto` +or a `switch` can jump over a declaration, in which case that point is never +reached: + +```c +switch (c) { + int x; // jumped over by every case label +case 1: + use(&x); // x would be uninitialized without special handling +} +``` + +Clang emits the initialization at each jump that bypasses the declaration, +since such a jump re-enters the variable's scope. A variable bypassed inside a +loop is therefore re-initialized on every iteration, and a jump whose source and +destination are both inside the variable's scope does not re-initialize it, +because that jump never ends the variable's lifetime. + +In a function containing a computed `goto` (`goto *ptr`), the jumps that bypass +a given declaration cannot be identified, so every variable in the function is +instead initialized once, in the function's entry block. + +Note that in C this deliberately does not implement C 6.2.4p6, under which an +object's lifetime begins at entry into the block containing its declaration +rather than at the declaration itself. Modelling that would mean initializing +at block entry, which is more expensive and no more useful for the jumps this +feature exists to guard against. Clang applies the C++ rule in both languages. +::: + (strict_aliasing)= (strict-aliasing)= diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index e1ed66ae71243e..835e5d6cbe97a2 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1643,6 +1643,40 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) { } } + // A variable whose declaration is bypassed by a goto or switch is not + // 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 (!Bypasses.isAlwaysBypassed()) { + // The variable's lifetime restarts on each re-entry into its scope, so + // reinitialize at every bypassing jump. Backward gotos are emitted at + // the jump source, which comes after this alloca; forward gotos and the + // switch dispatch have already been emitted, so patch their init in + // before the jump. + BypassedVarInits.insert({&D, address}); + for (const BypassingForwardJump &FG : BypassingForwardJumps) { + const auto *Vars = Bypasses.getBypassedVarsForSource(FG.Source); + if (!Vars || !Vars->contains(&D)) + continue; + if (llvm::Instruction *Term = FG.Block->getTerminator()) { + llvm::IRBuilderBase::InsertPointGuard IPG(Builder); + Builder.SetInsertPoint(Term); + emitZeroOrPatternForAutoVarInit(Ty, D, address); + } + } + } else { + // 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); + 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(); @@ -1840,6 +1874,35 @@ bool CodeGenFunction::isTrivialInitializer(const Expr *Init) { return false; } +LangOptions::TrivialAutoVarInitKind +CodeGenFunction::getAutoVarInitKind(QualType Ty, const VarDecl &D) { + auto hasNoTrivialAutoVarInitAttr = [](const Decl *D) { + return D && D->hasAttr<NoTrivialAutoVarInitAttr>(); + }; + if (D.isConstexpr() || D.getAttr<UninitializedAttr>() || + hasNoTrivialAutoVarInitAttr(Ty->getAsTagDecl()) || + hasNoTrivialAutoVarInitAttr(CurFuncDecl)) + return LangOptions::TrivialAutoVarInitKind::Uninitialized; + return getContext().getLangOpts().getTrivialAutoVarInit(); +} + +void CodeGenFunction::emitBypassedVarInitsForSource(const Stmt *Source) { + // 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; + for (const VarDecl *VD : *Vars) { + auto It = BypassedVarInits.find(VD); + if (It != BypassedVarInits.end()) + emitZeroOrPatternForAutoVarInit(VD->getType(), *VD, It->second); + } +} + void CodeGenFunction::emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D, Address Loc) { @@ -1999,16 +2062,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 09db2ce69f6ecf..bcc055782096ae 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -858,6 +858,16 @@ void CodeGenFunction::EmitGotoStmt(const GotoStmt &S) { if (HaveInsertPoint()) EmitStopPoint(&S); + // 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() && !Bypasses.isAlwaysBypassed()) { + emitBypassedVarInitsForSource(&S); + BypassingForwardJumps.push_back({Builder.GetInsertBlock(), &S}); + } + ApplyAtomGroup Grp(getDebugInfo()); EmitBranchThroughCleanup(getJumpDestForLabel(S.getLabel())); } @@ -2445,6 +2455,17 @@ void CodeGenFunction::EmitSwitchStmt(const SwitchStmt &S) { // explicit case ranges tests can have a place to jump to on // failure. llvm::BasicBlock *DefaultBlock = createBasicBlock("sw.default"); + + // The dispatch is the jump that bypasses any declarations sitting between the + // switch and its case labels, so the initialization goes here, ahead of the + // switch instruction -- not at the case labels. A case label is also reached + // by falling through from the case above it, and that edge bypasses nothing; + // initializing there would clobber a variable the previous case had written. + // The declarations are inside the body and so have no alloca yet, hence the + // patch-it-in-later handling in EmitAutoVarAlloca. + if (!Bypasses.isAlwaysBypassed()) + BypassingForwardJumps.push_back({Builder.GetInsertBlock(), &S}); + SwitchInsn = Builder.CreateSwitch(CondV, DefaultBlock); addInstToNewSourceAtom(SwitchInsn, CondV); diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index 1b19b3ce066781..79bcae4c5f7a0d 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1568,9 +1568,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 653e883012229b..9976a411fd6739 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -302,6 +302,19 @@ class CodeGenFunction : public CodeGenTypeCache { // because of jumps. VarBypassDetector Bypasses; + // Addresses of bypassed variables, for re-emitting their + // trivial-auto-var-init at a jump that re-enters their scope. + llvm::SmallDenseMap<const VarDecl *, Address, 4> BypassedVarInits; + + // Jumps, like gotos or switches, that may bypass a declaration that has not + // been emitted yet. EmitAutoVarAlloca patches the init in before the jump + // once the alloca exists. + struct BypassingForwardJump { + llvm::AssertingVH<llvm::BasicBlock> Block; + const Stmt *Source; + }; + llvm::SmallVector<BypassingForwardJump, 4> BypassingForwardJumps; + /// List of recently emitted OMPCanonicalLoops. /// /// Since OMPCanonicalLoops are nested inside other statements (in particular @@ -3592,6 +3605,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. 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); /// Emits the alloca and debug information for the size expressions for each @@ -5625,6 +5643,8 @@ class CodeGenFunction : public CodeGenTypeCache { void emitZeroOrPatternForAutoVarInit(QualType type, const VarDecl &D, Address Loc); + LangOptions::TrivialAutoVarInitKind getAutoVarInitKind(QualType Ty, + const VarDecl &D); public: enum class EvaluationOrder { diff --git a/clang/lib/CodeGen/VarBypassDetector.cpp b/clang/lib/CodeGen/VarBypassDetector.cpp index 7b2b3542928ad7..f19ac2de6ea45b 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,13 @@ 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)) { + // Keyed by the switch, not by each case: the dispatch is the jump + // source, and that is where the initialization is emitted. for (const SwitchCase *SC = SS->getSwitchCaseList(); SC; SC = SC->getNextSwitchCase()) { - Detect(from, ToScopes[SC]); + Detect(from, ToScopes[SC], SS); } } else { llvm_unreachable("goto or switch was expected"); @@ -157,13 +187,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 cc4d387aeaa5ba..edcf17d1edd24d 100644 --- a/clang/lib/CodeGen/VarBypassDetector.h +++ b/clang/lib/CodeGen/VarBypassDetector.h @@ -18,6 +18,7 @@ #include "clang/AST/Decl.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/SmallVector.h" namespace clang { @@ -47,6 +48,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) to the variable declarations it + // bypasses. Used to reinitialize those variables at the jump. + llvm::DenseMap<const Stmt *, llvm::SmallSetVector<const VarDecl *, 4>> + BypassedVarsAtSource; // If true assume that all variables are being bypassed. bool AlwaysBypassed = false; @@ -59,13 +64,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::SmallSetVector<const VarDecl *, 4> * + 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-bypass.c b/clang/test/CodeGen/trivial-auto-var-init-bypass.c new file mode 100644 index 00000000000000..ea993dc4e8ba69 --- /dev/null +++ b/clang/test/CodeGen/trivial-auto-var-init-bypass.c @@ -0,0 +1,646 @@ +// 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: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// 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: 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: 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: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// 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: 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: 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: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// 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: 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: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// 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: 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); + } +} + +// 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); +} + +// Fallthrough between cases must not re-initialize. `case 1:` is reached two +// ways: from the switch dispatch, which bypasses the declaration, and by +// falling out of `case 0:`, which does not. Initializing at the label would +// fire on both and clobber the 42 written by case 0, so the init goes ahead of +// the dispatch instead. Expect exactly one annotated store, before the switch. +// ZERO-LABEL: define dso_local void @switch_fallthrough( +// ZERO-SAME: i32 noundef [[Y:%.*]]) #[[ATTR0]] { +// ZERO-NEXT: [[ENTRY:.*:]] +// ZERO-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4 +// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4 +// ZERO-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4 +// ZERO-NEXT: [[TMP0:%.*]] = load i32, ptr [[Y_ADDR]], align 4 +// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// 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 42, ptr [[X]], align 4 +// ZERO-NEXT: br label %[[SW_BB1]] +// ZERO: [[SW_BB1]]: +// 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_fallthrough( +// PATTERN-SAME: i32 noundef [[Y:%.*]]) #[[ATTR0]] { +// PATTERN-NEXT: [[ENTRY:.*:]] +// PATTERN-NEXT: [[Y_ADDR:%.*]] = alloca i32, align 4 +// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4 +// PATTERN-NEXT: store i32 [[Y]], ptr [[Y_ADDR]], align 4 +// PATTERN-NEXT: [[TMP0:%.*]] = load i32, ptr [[Y_ADDR]], align 4 +// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// 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 42, ptr [[X]], align 4 +// PATTERN-NEXT: br label %[[SW_BB1]] +// PATTERN: [[SW_BB1]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: br label %[[SW_EPILOG]] +// PATTERN: [[SW_EPILOG]]: +// PATTERN-NEXT: ret void +// +void switch_fallthrough(int y) { + switch (y) { + int x; + case 0: + x = 42; + // fallthrough + case 1: + use(&x); + break; + } +} + +// ZERO-LABEL: define dso_local void @equivalence_switch_form( +// 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: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// ZERO-NEXT: switch i32 [[TMP0]], label %[[SW_DEFAULT:.*]] [ +// ZERO-NEXT: i32 0, label %[[SW_BB:.*]] +// ZERO-NEXT: ] +// ZERO: [[SW_BB]]: +// ZERO-NEXT: call void @use(ptr noundef [[X]]) +// ZERO-NEXT: br label %[[SW_DEFAULT]] +// ZERO: [[SW_DEFAULT]]: +// 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 @equivalence_switch_form( +// 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: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// PATTERN-NEXT: switch i32 [[TMP0]], label %[[SW_DEFAULT:.*]] [ +// PATTERN-NEXT: i32 0, label %[[SW_BB:.*]] +// PATTERN-NEXT: ] +// PATTERN: [[SW_BB]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: br label %[[SW_DEFAULT]] +// PATTERN: [[SW_DEFAULT]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: br label %[[SW_EPILOG:.*]] +// PATTERN: [[SW_EPILOG]]: +// PATTERN-NEXT: ret void +// +void equivalence_switch_form(int c) { + switch (c) { + int x; + case 0: + use(&x); + default: + use(&x); + } +} + +// ZERO-LABEL: define dso_local void @equivalence_goto_form( +// 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_DEFAULT:.*]] [ +// ZERO-NEXT: i32 0, label %[[SW_BB:.*]] +// ZERO-NEXT: ] +// ZERO: [[SW_BB]]: +// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// ZERO-NEXT: br label %[[CASE0:.*]] +// ZERO: [[SW_DEFAULT]]: +// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// ZERO-NEXT: br label %[[DEFAULT:.*]] +// ZERO: [[CASE0]]: +// ZERO-NEXT: call void @use(ptr noundef [[X]]) +// ZERO-NEXT: br label %[[DEFAULT]] +// ZERO: [[DEFAULT]]: +// ZERO-NEXT: call void @use(ptr noundef [[X]]) +// ZERO-NEXT: ret void +// +// PATTERN-LABEL: define dso_local void @equivalence_goto_form( +// 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_DEFAULT:.*]] [ +// PATTERN-NEXT: i32 0, label %[[SW_BB:.*]] +// PATTERN-NEXT: ] +// PATTERN: [[SW_BB]]: +// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// PATTERN-NEXT: br label %[[CASE0:.*]] +// PATTERN: [[SW_DEFAULT]]: +// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// PATTERN-NEXT: br label %[[DEFAULT:.*]] +// PATTERN: [[CASE0]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: br label %[[DEFAULT]] +// PATTERN: [[DEFAULT]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: ret void +// +void equivalence_goto_form(int c) { + switch (c) { + case 0: + goto CASE0; + default: + goto DEFAULT; + } + { + int x; + CASE0: + use(&x); + DEFAULT: + use(&x); + } +} + +// A goto inside a statement expression in a loop condition. The goto's block is +// recorded in BypassingForwardJumps as an AssertingVH and patched later, so this +// exercises that the recorded block outlives the unreachable code the goto +// leaves behind. Expect the init in while.cond, ahead of the branch to L. +// ZERO-LABEL: define dso_local void @stmt_expr_goto( +// ZERO-SAME: ) #[[ATTR0]] { +// ZERO-NEXT: [[ENTRY:.*:]] +// ZERO-NEXT: [[TMP:%.*]] = alloca i32, align 4 +// ZERO-NEXT: [[X:%.*]] = alloca i32, align 4 +// ZERO-NEXT: br label %[[WHILE_COND:.*]] +// ZERO: [[WHILE_COND]]: +// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// ZERO-NEXT: br label %[[L:.*]] +// ZERO: [[BB0:.*:]] +// ZERO-NEXT: store i32 1, ptr [[TMP]], align 4 +// ZERO-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP]], align 4 +// ZERO-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0 +// ZERO-NEXT: br i1 [[TOBOOL]], label %[[WHILE_BODY:.*]], label %[[WHILE_END:.*]] +// ZERO: [[WHILE_BODY]]: +// ZERO-NEXT: br label %[[WHILE_COND]], !llvm.loop [[LOOP4:![0-9]+]] +// ZERO: [[WHILE_END]]: +// ZERO-NEXT: store i32 0, ptr [[X]], align 4, !annotation [[META1]] +// ZERO-NEXT: br label %[[L]] +// ZERO: [[L]]: +// ZERO-NEXT: call void @use(ptr noundef [[X]]) +// ZERO-NEXT: ret void +// +// PATTERN-LABEL: define dso_local void @stmt_expr_goto( +// PATTERN-SAME: ) #[[ATTR0]] { +// PATTERN-NEXT: [[ENTRY:.*:]] +// PATTERN-NEXT: [[TMP:%.*]] = alloca i32, align 4 +// PATTERN-NEXT: [[X:%.*]] = alloca i32, align 4 +// PATTERN-NEXT: br label %[[WHILE_COND:.*]] +// PATTERN: [[WHILE_COND]]: +// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// PATTERN-NEXT: br label %[[L:.*]] +// PATTERN: [[BB0:.*:]] +// PATTERN-NEXT: store i32 1, ptr [[TMP]], align 4 +// PATTERN-NEXT: [[TMP1:%.*]] = load i32, ptr [[TMP]], align 4 +// PATTERN-NEXT: [[TOBOOL:%.*]] = icmp ne i32 [[TMP1]], 0 +// PATTERN-NEXT: br i1 [[TOBOOL]], label %[[WHILE_BODY:.*]], label %[[WHILE_END:.*]] +// PATTERN: [[WHILE_BODY]]: +// PATTERN-NEXT: br label %[[WHILE_COND]], !llvm.loop [[LOOP4:![0-9]+]] +// PATTERN: [[WHILE_END]]: +// PATTERN-NEXT: store i32 -1431655766, ptr [[X]], align 4, !annotation [[META1]] +// PATTERN-NEXT: br label %[[L]] +// PATTERN: [[L]]: +// PATTERN-NEXT: call void @use(ptr noundef [[X]]) +// PATTERN-NEXT: ret void +// +void stmt_expr_goto(void) { + while (({ goto L; 1; })) {} + int x; +L: + use(&x); +} +//. +// ZERO: [[META1]] = !{!"auto-init"} +// ZERO: [[LOOP2]] = distinct !{[[LOOP2]], [[META3:![0-9]+]]} +// ZERO: [[META3]] = !{!"llvm.loop.mustprogress"} +// ZERO: [[LOOP4]] = distinct !{[[LOOP4]], [[META3]]} +//. +// PATTERN: [[META1]] = !{!"auto-init"} +// PATTERN: [[LOOP2]] = distinct !{[[LOOP2]], [[META3:![0-9]+]]} +// PATTERN: [[META3]] = !{!"llvm.loop.mustprogress"} +// PATTERN: [[LOOP4]] = distinct !{[[LOOP4]], [[META3]]} +//. diff --git a/clang/test/CodeGenCXX/trivial-auto-var-init.cpp b/clang/test/CodeGenCXX/trivial-auto-var-init.cpp index e21a307f33121c..77f7d1538fc130 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 goto source (before the branch). // 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 before the switch dispatch. // 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,414 @@ void test_huge_larger_init() { used(big); } +// Multiple variables bypassed by the same goto, so both must be initialized, +// and both stores must land ahead of the branch to the label. +// 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: store i32 0, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]] +// ZERO-NEXT: store i32 0, ptr %b, align 4, !annotation [[AUTO_INIT]] +// ZERO-NEXT: br label %jump +// PATTERN-LABEL: test_goto_multiple_bypassed( +// PATTERN: %a = alloca i32, align 4 +// PATTERN: %b = alloca i32, align 4 +// PATTERN: store i32 -1431655766, ptr %a, align 4, !annotation [[AUTO_INIT:!.+]] +// PATTERN-NEXT: store i32 -1431655766, ptr %b, align 4, !annotation [[AUTO_INIT]] +// PATTERN-NEXT: br label %jump +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. The init +// goes ahead of the dispatch, not at the bypassing label: a label is also +// reached by falling through from the case above it, and that edge bypasses +// nothing. Case 0 reaches the declaration and initializes there as usual. +// UNINIT-LABEL: test_switch_default_bypass( +// ZERO-LABEL: test_switch_default_bypass( +// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]] +// ZERO-NEXT: switch i32 +// ZERO: sw.default: +// ZERO-NOT: !annotation +// ZERO: call void @_Z4usedIiEvRT_ +// PATTERN-LABEL: test_switch_default_bypass( +// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]] +// PATTERN-NEXT: switch i32 +// PATTERN: sw.default: +// PATTERN-NOT: !annotation +// PATTERN: call void @_Z4usedIiEvRT_ +void test_switch_default_bypass(int c) { + switch (c) { + case 0: + int x; + x = 10; + used(x); + break; + default: + used(x); + break; + } +} + +// UNINIT-LABEL: test_backward_goto_bypass( +// ZERO-LABEL: test_backward_goto_bypass( +// ZERO: jump: +// ZERO: call void @{{.*}}used +// ZERO: call void @{{.*}}used +// ZERO: store i32 0, ptr %b, align 4 +// ZERO-NEXT: 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: store i32 -1431655766, ptr %b +// PATTERN-NEXT: 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; +} + +// Scope re-entry restarts the lifetime, so the init is emitted at the goto +// source and reruns each iteration (store in BEGIN, not entry). C uses the same +// rule; see CodeGen/trivial-auto-var-init-bypass.c. +// 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: the init sits ahead of the dispatch, inside the inner +// loop body, so it runs once per iteration rather than once per case. +// UNINIT-LABEL: nested_loops_switch( +// ZERO-LABEL: nested_loops_switch( +// ZERO: while.body3: +// ZERO: store i32 0, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]] +// ZERO-NEXT: switch i32 +// PATTERN-LABEL: nested_loops_switch( +// PATTERN: while.body3: +// PATTERN: store i32 -1431655766, ptr %x, align 4, !annotation [[AUTO_INIT:!.+]] +// PATTERN-NEXT: switch i32 +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--; + } + } +} + +// 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; + } +} + +// UNINIT-LABEL: test_nrvo_bypass( +// UNINIT-NOT: !annotation +// ZERO-LABEL: test_nrvo_bypass( +// ZERO: if.then: +// ZERO-NEXT: call void @llvm.memset{{.*}}(ptr align 4 %agg.result, i8 0, i64 32, i1 false), !annotation [[AUTO_INIT:!.+]] +// ZERO: if.end: +// ZERO-NEXT: call void @llvm.memset{{.*}}(ptr align 4 %agg.result, i8 0, i64 32, i1 false), !annotation [[AUTO_INIT]] +// PATTERN-LABEL: test_nrvo_bypass( +// PATTERN: if.then: +// PATTERN-NEXT: call void @llvm.memcpy{{.*}}(ptr align 4 %agg.result, ptr align 4 @__const.test_nrvo_bypass.s, i64 32, i1 false), !annotation [[AUTO_INIT:!.+]] +// PATTERN: if.end: +// PATTERN-NEXT: call void @llvm.memcpy{{.*}}(ptr align 4 %agg.result, ptr align 4 @__const.test_nrvo_bypass.s, i64 32, i1 false), !annotation [[AUTO_INIT]] +struct Big { int a[8]; }; +Big test_nrvo_bypass(int cond) { + if (cond) + goto skip; + Big s; +skip: + return s; +} + } // extern "C" // CHECK: [[AUTO_INIT]] = !{ !"auto-init" } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
