[clang] Users/clang cir cfg (PR #169081)
Jasmine Tang via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 1 11:00:37 PST 2026
https://github.com/badumbatish updated https://github.com/llvm/llvm-project/pull/169081
>From d015af2cb7ca61a0445a78781b31178e3f6fa9f2 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Thu, 20 Nov 2025 05:23:58 -0800
Subject: [PATCH 01/11] Skeleton
---
.../include/clang/CIR/Sema/CIRAnalysisKind.h | 117 ++++++++++++
.../clang/CIR/Sema/FallThroughWarning.h | 66 +++++++
.../include/clang/Frontend/FrontendOptions.h | 3 +
clang/include/clang/Options/Options.td | 5 +
clang/lib/CIR/CMakeLists.txt | 1 +
clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 36 ++++
clang/lib/CIR/FrontendAction/CMakeLists.txt | 1 +
clang/lib/CIR/Sema/CIRAnalysisKind.cpp | 67 +++++++
clang/lib/CIR/Sema/CMakeLists.txt | 19 ++
clang/lib/CIR/Sema/FallThroughWarning.cpp | 170 ++++++++++++++++++
10 files changed, 485 insertions(+)
create mode 100644 clang/include/clang/CIR/Sema/CIRAnalysisKind.h
create mode 100644 clang/include/clang/CIR/Sema/FallThroughWarning.h
create mode 100644 clang/lib/CIR/Sema/CIRAnalysisKind.cpp
create mode 100644 clang/lib/CIR/Sema/CMakeLists.txt
create mode 100644 clang/lib/CIR/Sema/FallThroughWarning.cpp
diff --git a/clang/include/clang/CIR/Sema/CIRAnalysisKind.h b/clang/include/clang/CIR/Sema/CIRAnalysisKind.h
new file mode 100644
index 0000000000000..304acfcf433db
--- /dev/null
+++ b/clang/include/clang/CIR/Sema/CIRAnalysisKind.h
@@ -0,0 +1,117 @@
+//===--- CIRAnalysisKind.h - CIR Analysis Pass Kinds -----------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Defines the CIR analysis pass kinds enum and related utilities.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H
+#define LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+#include <optional>
+#include <string>
+#include <vector>
+
+namespace cir {
+
+/// Enumeration of available CIR semantic analysis passes
+enum class CIRAnalysisKind : unsigned {
+ Unrecognized = 0,
+ FallThrough = 1 << 0, // Fallthrough warning analysis
+ UnreachableCode = 1 << 1, // Unreachable code detection
+ NullCheck = 1 << 2, // Null pointer checks
+ UninitializedVar = 1 << 3, // Uninitialized variable detection
+ // Add more analysis passes here as needed
+};
+
+/// A set of CIR analysis passes (bitmask)
+class CIRAnalysisSet {
+ unsigned mask = 0;
+
+public:
+ CIRAnalysisSet() = default;
+ explicit CIRAnalysisSet(CIRAnalysisKind kind)
+ : mask(static_cast<unsigned>(kind)) {}
+ explicit CIRAnalysisSet(unsigned mask) : mask(mask) {}
+
+ /// Check if a specific analysis is enabled
+ bool has(CIRAnalysisKind kind) const {
+ return (mask & static_cast<unsigned>(kind)) != 0;
+ }
+
+ /// Enable a specific analysis
+ void enable(CIRAnalysisKind kind) {
+ mask |= static_cast<unsigned>(kind);
+ }
+
+ /// Disable a specific analysis
+ void disable(CIRAnalysisKind kind) {
+ mask &= ~static_cast<unsigned>(kind);
+ }
+
+ /// Check if any analysis is enabled
+ bool hasAny() const { return mask != 0; }
+
+ /// Check if no analysis is enabled
+ bool empty() const { return mask == 0; }
+
+ /// Get the raw mask value
+ unsigned getMask() const { return mask; }
+
+ /// Union with another set
+ CIRAnalysisSet &operator|=(const CIRAnalysisSet &other) {
+ mask |= other.mask;
+ return *this;
+ }
+
+ /// Union operator
+ CIRAnalysisSet operator|(const CIRAnalysisSet &other) const {
+ return CIRAnalysisSet(mask | other.mask);
+ }
+
+ /// Intersection with another set
+ CIRAnalysisSet &operator&=(const CIRAnalysisSet &other) {
+ mask &= other.mask;
+ return *this;
+ }
+
+ /// Intersection operator
+ CIRAnalysisSet operator&(const CIRAnalysisSet &other) const {
+ return CIRAnalysisSet(mask & other.mask);
+ }
+
+ bool operator==(const CIRAnalysisSet &other) const {
+ return mask == other.mask;
+ }
+
+ bool operator!=(const CIRAnalysisSet &other) const {
+ return mask != other.mask;
+ }
+
+ /// Print the analysis set to an output stream
+ void print(llvm::raw_ostream &OS) const;
+};
+
+/// Parse a single analysis name into a CIRAnalysisKind
+/// Returns std::nullopt if the name is not recognized
+CIRAnalysisKind parseCIRAnalysisKind(llvm::StringRef name);
+
+/// Parse a list of analysis names (from command line) into a CIRAnalysisSet
+/// Handles comma and semicolon separators
+/// Invalid names are ignored and optionally reported via InvalidNames
+CIRAnalysisSet parseCIRAnalysisList(
+ const std::vector<std::string> &analysisList,
+ llvm::SmallVectorImpl<std::string> *invalidNames = nullptr);
+
+} // namespace cir
+
+#endif // LLVM_CLANG_CIR_SEMA_CIRANALYSISKIND_H
diff --git a/clang/include/clang/CIR/Sema/FallThroughWarning.h b/clang/include/clang/CIR/Sema/FallThroughWarning.h
new file mode 100644
index 0000000000000..808cf43f92ce0
--- /dev/null
+++ b/clang/include/clang/CIR/Sema/FallThroughWarning.h
@@ -0,0 +1,66 @@
+//===--- FallThroughWarning.h - CIR Fall-Through Analysis ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+/// \file
+/// Defines the FallThroughWarningPass for CIR fall-through analysis.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H
+#define LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H
+
+#include "clang/AST/Type.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Sema/AnalysisBasedWarnings.h"
+
+#include "mlir/IR/Block.h"
+#include "mlir/IR/Operation.h"
+#include "mlir/Support/LLVM.h"
+namespace cir {
+class FuncOp;
+} // namespace cir
+
+namespace clang {
+class Sema;
+
+enum ControlFlowKind {
+ UnknownFallThrough,
+ NeverFallThrough,
+ MaybeFallThrough,
+ AlwaysFallThrough,
+ NeverFallThroughOrReturn
+};
+
+/// Configuration for fall-through diagnostics
+struct CheckFallThroughDiagnostics {
+ unsigned diagFallThrough = 0;
+ unsigned diagReturn = 0;
+ unsigned diagFallThroughAttr = 0;
+ unsigned funKind = 0;
+ SourceLocation funcLoc;
+
+ bool checkDiagnostics(DiagnosticsEngine &d, bool returnsVoid,
+ bool hasNoReturn) const;
+};
+
+/// Pass for analyzing fall-through behavior in CIR functions
+class FallThroughWarningPass {
+public:
+ FallThroughWarningPass() = default;
+
+ /// Check fall-through behavior for a CIR function body
+ void checkFallThroughForFuncBody(Sema &s, cir::FuncOp cfg, QualType blockType,
+ const CheckFallThroughDiagnostics &cd);
+ ControlFlowKind checkFallThrough(cir::FuncOp cfg);
+ mlir::DenseSet<mlir::Block *> getLiveSet(cir::FuncOp cfg);
+};
+
+} // namespace clang
+
+#endif // LLVM_CLANG_CIR_SEMA_FALLTHROUGHWARNING_H
diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h
index c919a53ae089e..a152309212bf8 100644
--- a/clang/include/clang/Frontend/FrontendOptions.h
+++ b/clang/include/clang/Frontend/FrontendOptions.h
@@ -420,6 +420,9 @@ class FrontendOptions {
LLVM_PREFERRED_TYPE(bool)
unsigned ClangIRDisableCIRVerifier : 1;
+ /// List of ClangIR semantic analysis passes to enable
+ std::vector<std::string> ClangIRAnalysisList;
+
CodeCompleteOptions CodeCompleteOpts;
/// Specifies the output format of the AST.
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 2f7434d8afe11..16821a7b6f230 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -3150,6 +3150,11 @@ def clangir_disable_verifier : Flag<["-"], "clangir-disable-verifier">,
HelpText<"ClangIR: Disable MLIR module verifier">,
MarshallingInfoFlag<FrontendOpts<"ClangIRDisableCIRVerifier">>;
+def fclangir_analysis_EQ : CommaJoined<["-"], "fclangir-analysis=">,
+ Visibility<[ClangOption, CC1Option]>,
+ HelpText<"Enable ClangIR semantic analysis passes. Pass comma or semicolon separated list of analysis names">,
+ MarshallingInfoStringVector<FrontendOpts<"ClangIRAnalysisList">>;
+
defm clangir : BoolFOption<"clangir",
FrontendOpts<"UseClangIRPipeline">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption, CC1Option], "Use the ClangIR pipeline to compile">,
diff --git a/clang/lib/CIR/CMakeLists.txt b/clang/lib/CIR/CMakeLists.txt
index 7bdf3fcc59035..f86ad0933bce6 100644
--- a/clang/lib/CIR/CMakeLists.txt
+++ b/clang/lib/CIR/CMakeLists.txt
@@ -17,3 +17,4 @@ add_subdirectory(CodeGen)
add_subdirectory(FrontendAction)
add_subdirectory(Interfaces)
add_subdirectory(Lowering)
+add_subdirectory(Sema)
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 67bb5657d4001..fb86b5f848628 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -10,12 +10,20 @@
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
#include "clang/Basic/DiagnosticFrontend.h"
+#include "clang/Basic/DiagnosticSema.h"
#include "clang/CIR/CIRGenerator.h"
#include "clang/CIR/CIRToCIRPasses.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/LowerToLLVM.h"
+#include "clang/CIR/Sema/CIRAnalysisKind.h"
+#include "clang/CIR/Sema/FallThroughWarning.h"
#include "clang/CodeGen/BackendUtil.h"
#include "clang/Frontend/CompilerInstance.h"
+#include "clang/Sema/AnalysisBasedWarnings.h"
+#include "clang/Sema/Sema.h"
#include "llvm/IR/Module.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
using namespace cir;
using namespace clang;
@@ -108,6 +116,34 @@ class CIRGenConsumer : public clang::ASTConsumer {
mlir::ModuleOp MlirModule = Gen->getModule();
mlir::MLIRContext &MlirCtx = Gen->getMLIRContext();
+ // Run CIR analysis passes if requested
+ if (!FEOptions.ClangIRAnalysisList.empty()) {
+ CIRAnalysisSet AnalysisSet = parseCIRAnalysisList(FEOptions.ClangIRAnalysisList);
+ AnalysisSet.print(llvm::errs());
+
+ if (AnalysisSet.has(CIRAnalysisKind::FallThrough)) {
+ // Get Sema for diagnostics
+ if (CI.hasSema()) {
+ Sema &S = CI.getSema();
+ FallThroughWarningPass FallThroughPass;
+
+ // Iterate over all functions in the CIR module
+ MlirModule.walk([&](cir::FuncOp FuncOp) {
+ // TODO: Get the proper QualType for the function
+ // For now, use an invalid QualType as placeholder
+
+ QualType FuncType;
+
+ // Set up diagnostics configuration
+ CheckFallThroughDiagnostics Diags;
+
+ // Run fall-through analysis on this function
+ FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType, Diags);
+ });
+ }
+ }
+ }
+
if (!FEOptions.ClangIRDisablePasses) {
// Setup and run CIR pipeline.
if (runCIRToCIRPasses(MlirModule, MlirCtx, C,
diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt b/clang/lib/CIR/FrontendAction/CMakeLists.txt
index 50d6ea7108ce1..f43ef2d8cbbf5 100644
--- a/clang/lib/CIR/FrontendAction/CMakeLists.txt
+++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt
@@ -18,6 +18,7 @@ add_clang_library(clangCIRFrontendAction
clangBasic
clangFrontend
clangCIR
+ clangCIRSema
clangCIRLoweringCommon
clangCIRLoweringDirectToLLVM
clangCodeGen
diff --git a/clang/lib/CIR/Sema/CIRAnalysisKind.cpp b/clang/lib/CIR/Sema/CIRAnalysisKind.cpp
new file mode 100644
index 0000000000000..3ae24979cfe68
--- /dev/null
+++ b/clang/lib/CIR/Sema/CIRAnalysisKind.cpp
@@ -0,0 +1,67 @@
+//===--- CIRAnalysisKind.cpp - CIR Analysis Pass Kinds -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/CIR/Sema/CIRAnalysisKind.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/raw_ostream.h"
+#include <optional>
+
+namespace cir {
+
+CIRAnalysisKind parseCIRAnalysisKind(llvm::StringRef name) {
+ auto parseResult = llvm::StringSwitch<CIRAnalysisKind>(name)
+ .Case("fallthrough", CIRAnalysisKind::FallThrough)
+ .Case("fall-through", CIRAnalysisKind::FallThrough)
+ .Default(CIRAnalysisKind::Unrecognized);
+
+ return parseResult;
+}
+
+
+CIRAnalysisSet parseCIRAnalysisList(
+ const std::vector<std::string> &analysisList,
+ llvm::SmallVectorImpl<std::string> *invalidNames) {
+ CIRAnalysisSet result;
+
+ for (const std::string &item : analysisList) {
+ llvm::StringRef remaining = item;
+ CIRAnalysisKind parseKind = parseCIRAnalysisKind(remaining);
+ if (parseKind == CIRAnalysisKind::Unrecognized) {
+ llvm::errs() << "Unrecognized CIR analysis option: " << remaining << "\n";
+ continue;
+ }
+ result.enable(parseKind);
+ }
+
+ return result;
+}
+
+void CIRAnalysisSet::print(llvm::raw_ostream &OS) const {
+ if (empty()) {
+ OS << "none";
+ return;
+ }
+
+ bool first = true;
+ auto printIfEnabled = [&](CIRAnalysisKind kind, llvm::StringRef name) {
+ if (has(kind)) {
+ if (!first)
+ OS << ", ";
+ OS << name;
+ first = false;
+ }
+ };
+
+ printIfEnabled(CIRAnalysisKind::FallThrough, "fallthrough");
+ printIfEnabled(CIRAnalysisKind::UnreachableCode, "unreachable-code");
+ printIfEnabled(CIRAnalysisKind::NullCheck, "null-check");
+ printIfEnabled(CIRAnalysisKind::UninitializedVar, "uninitialized-var");
+}
+
+} // namespace cir
diff --git a/clang/lib/CIR/Sema/CMakeLists.txt b/clang/lib/CIR/Sema/CMakeLists.txt
new file mode 100644
index 0000000000000..acf49742d8e4c
--- /dev/null
+++ b/clang/lib/CIR/Sema/CMakeLists.txt
@@ -0,0 +1,19 @@
+add_clang_library(clangCIRSema
+ FallThroughWarning.cpp
+ CIRAnalysisKind.cpp
+
+ DEPENDS
+ MLIRCIRPassIncGen
+
+ LINK_LIBS PUBLIC
+ clangAST
+ clangBasic
+
+ MLIRAnalysis
+ MLIRIR
+ MLIRPass
+ MLIRTransformUtils
+
+ MLIRCIR
+ MLIRCIRInterfaces
+)
diff --git a/clang/lib/CIR/Sema/FallThroughWarning.cpp b/clang/lib/CIR/Sema/FallThroughWarning.cpp
new file mode 100644
index 0000000000000..5fdc9ffc083cb
--- /dev/null
+++ b/clang/lib/CIR/Sema/FallThroughWarning.cpp
@@ -0,0 +1,170 @@
+#include "clang/CIR/Sema/FallThroughWarning.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/Basic/DiagnosticSema.h"
+#include "clang/Basic/SourceLocation.h"
+#include "clang/Basic/TargetInfo.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "clang/Sema/Sema.h"
+#include "llvm/Support/raw_ostream.h"
+
+using namespace mlir;
+using namespace cir;
+using namespace clang;
+
+namespace clang {
+
+//===----------------------------------------------------------------------===//
+// Check for missing return value.
+//===----------------------------------------------------------------------===//
+
+bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d,
+ bool returnsVoid,
+ bool hasNoReturn) const {
+ if (funKind == diag::FalloffFunctionKind::Function) {
+ return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (!hasNoReturn ||
+ d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc)) &&
+ (!returnsVoid ||
+ d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc));
+ }
+ if (funKind == diag::FalloffFunctionKind::Coroutine) {
+ return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (!hasNoReturn);
+ }
+ // For blocks / lambdas.
+ return returnsVoid && !hasNoReturn;
+}
+
+// TODO: Add a class for fall through config later
+
+void FallThroughWarningPass::checkFallThroughForFuncBody(
+ Sema &s, cir::FuncOp cfg, QualType blockType,
+ const CheckFallThroughDiagnostics &cd) {
+
+ llvm::errs() << "Hello world, you're in CIR sema analysis\n";
+ bool returnsVoid = false;
+ bool hasNoReturn = false;
+
+ // Supposedly all function in cir is FuncOp
+ // 1. If normal function (FunctionDecl), check if it's coroutine.
+ // 1a. if coroutine -> check the fallthrough handler (idk what this means,
+ // TODO for now)
+ if (cfg.getCoroutine()) {
+ // TODO: Let's not worry about coroutine for now
+ } else
+ returnsVoid = isa<cir::VoidType>(cfg.getFunctionType().getReturnType());
+
+ // TODO: Do we need to check for InferredNoReturnAttr just like in OG?
+ hasNoReturn = cfg.getFunctionType().getReturnTypes().empty();
+
+ DiagnosticsEngine &diags = s.getDiagnostics();
+ if (cd.checkDiagnostics(diags, returnsVoid, hasNoReturn)) {
+ return;
+ }
+
+ // cpu_dispatch functions permit empty function bodies for ICC compatibility.
+ // TODO: Do we have isCPUDispatchMultiVersion?
+ checkFallThrough(cfg);
+}
+
+mlir::DenseSet<mlir::Block *>
+FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
+ mlir::DenseSet<mlir::Block *> liveSet;
+ if (cfg.getBody().empty())
+ return liveSet;
+
+ auto &first = cfg.getBody().getBlocks().front();
+
+ for (auto &block : cfg.getBody()) {
+ if (block.isReachable(&first))
+ liveSet.insert(&block);
+ }
+ return liveSet;
+}
+
+ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
+
+ assert(cfg && "there can't be a null func op");
+
+ // TODO: Is no CFG akin to a declaration?
+ if (cfg.isDeclaration()) {
+ return UnknownFallThrough;
+ }
+
+ mlir::DenseSet<mlir::Block *> liveSet = this->getLiveSet(cfg);
+
+ unsigned count = liveSet.size();
+
+ bool hasLiveReturn = false;
+ bool hasFakeEdge = false;
+ bool hasPlainEdge = false;
+ bool hasAbnormalEdge = false;
+
+ auto &exitBlock = cfg.getBody().back();
+ // INFO: in OG clang CFG, they have an empty exit block, so when they query
+ // pred of exit OG, they get all exit blocks
+ //
+ // I guess in CIR, we can pretend exit blocks are all blocks that have no
+ // successor?
+ for (mlir::Block &pred : cfg.getBody().getBlocks()) {
+ if (!liveSet.contains(&pred))
+ continue;
+
+ // We consider no predecessors as 'exit blocks'
+ if (!pred.hasNoSuccessors())
+ continue;
+
+ if (!pred.mightHaveTerminator())
+ continue;
+
+ mlir::Operation *term = pred.getTerminator();
+ if (isa<cir::ReturnOp>(term)) {
+ hasAbnormalEdge = true;
+ continue;
+ }
+
+ // INFO: In OG, we'll be looking for destructor since it can appear past
+ // return but i guess not in CIR? In this case we'll only be examining the
+ // terminator
+
+ if (isa<cir::TryOp>(term)) {
+ hasAbnormalEdge = true;
+ continue;
+ }
+
+ // INFO: OG clang has this equals true whenever ri == re, which means this
+ // is true only when a block only has the terminator, or its size is 1.
+ hasPlainEdge = std::distance(pred.begin(), pred.end()) == 1;
+
+ if (isa<cir::ReturnOp>(term)) {
+ hasLiveReturn = true;
+ continue;
+ }
+ if (isa<cir::TryOp>(term)) {
+ hasLiveReturn = true;
+ continue;
+ }
+
+ // TODO: Maybe one day throw will be terminator?
+ //
+ // TODO: We need to add a microsoft inline assembly enum
+
+ // TODO: We don't concer with try op either since it's not terminator
+
+ hasPlainEdge = true;
+ }
+
+ if (!hasPlainEdge) {
+ if (hasLiveReturn)
+ return NeverFallThrough;
+ return NeverFallThroughOrReturn;
+ }
+ if (hasAbnormalEdge || hasFakeEdge || hasLiveReturn)
+ return MaybeFallThrough;
+ // This says AlwaysFallThrough for calls to functions that are not marked
+ // noreturn, that don't return. If people would like this warning to be more
+ // accurate, such functions should be marked as noreturn.
+ return AlwaysFallThrough;
+}
+} // namespace clang
>From 0eafeed1b29169fa5b8100084da13086855ca263 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Thu, 20 Nov 2025 19:05:04 -0800
Subject: [PATCH 02/11] Fix reachable behaviors
---
clang/lib/CIR/Sema/FallThroughWarning.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/clang/lib/CIR/Sema/FallThroughWarning.cpp b/clang/lib/CIR/Sema/FallThroughWarning.cpp
index 5fdc9ffc083cb..dd22317fb48ba 100644
--- a/clang/lib/CIR/Sema/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Sema/FallThroughWarning.cpp
@@ -77,7 +77,7 @@ FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
auto &first = cfg.getBody().getBlocks().front();
for (auto &block : cfg.getBody()) {
- if (block.isReachable(&first))
+ if (first.isReachable(&block))
liveSet.insert(&block);
}
return liveSet;
>From 0e416f49e38bfef8b7a4520424e1495e17b78f86 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Sun, 23 Nov 2025 01:33:00 -0800
Subject: [PATCH 03/11] Rename sema/ to analysis/ and add new test
---
.../CIR/{Sema => Analysis}/CIRAnalysisKind.h | 0
.../{Sema => Analysis}/FallThroughWarning.h | 6 +-
.../{Sema => Analysis}/CIRAnalysisKind.cpp | 2 +-
.../lib/CIR/{Sema => Analysis}/CMakeLists.txt | 0
.../{Sema => Analysis}/FallThroughWarning.cpp | 58 ++++++++++++++++++-
clang/lib/CIR/CMakeLists.txt | 2 +-
clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 4 +-
clang/test/CIR/Analysis/fallthrough_1.c | 33 +++++++++++
clang/test/CIR/Analysis/fallthrough_2.c | 37 ++++++++++++
9 files changed, 132 insertions(+), 10 deletions(-)
rename clang/include/clang/CIR/{Sema => Analysis}/CIRAnalysisKind.h (100%)
rename clang/include/clang/CIR/{Sema => Analysis}/FallThroughWarning.h (93%)
rename clang/lib/CIR/{Sema => Analysis}/CIRAnalysisKind.cpp (97%)
rename clang/lib/CIR/{Sema => Analysis}/CMakeLists.txt (100%)
rename clang/lib/CIR/{Sema => Analysis}/FallThroughWarning.cpp (74%)
create mode 100644 clang/test/CIR/Analysis/fallthrough_1.c
create mode 100644 clang/test/CIR/Analysis/fallthrough_2.c
diff --git a/clang/include/clang/CIR/Sema/CIRAnalysisKind.h b/clang/include/clang/CIR/Analysis/CIRAnalysisKind.h
similarity index 100%
rename from clang/include/clang/CIR/Sema/CIRAnalysisKind.h
rename to clang/include/clang/CIR/Analysis/CIRAnalysisKind.h
diff --git a/clang/include/clang/CIR/Sema/FallThroughWarning.h b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
similarity index 93%
rename from clang/include/clang/CIR/Sema/FallThroughWarning.h
rename to clang/include/clang/CIR/Analysis/FallThroughWarning.h
index 808cf43f92ce0..3f87b3f867d65 100644
--- a/clang/include/clang/CIR/Sema/FallThroughWarning.h
+++ b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
@@ -39,9 +39,9 @@ enum ControlFlowKind {
/// Configuration for fall-through diagnostics
struct CheckFallThroughDiagnostics {
- unsigned diagFallThrough = 0;
- unsigned diagReturn = 0;
- unsigned diagFallThroughAttr = 0;
+ unsigned diagFallThroughHasNoReturn = 0;
+ unsigned diagFallThroughReturnsNonVoid = 0;
+ unsigned diagNeverFallThroughOrReturn = 0;
unsigned funKind = 0;
SourceLocation funcLoc;
diff --git a/clang/lib/CIR/Sema/CIRAnalysisKind.cpp b/clang/lib/CIR/Analysis/CIRAnalysisKind.cpp
similarity index 97%
rename from clang/lib/CIR/Sema/CIRAnalysisKind.cpp
rename to clang/lib/CIR/Analysis/CIRAnalysisKind.cpp
index 3ae24979cfe68..f27f8dbeb731d 100644
--- a/clang/lib/CIR/Sema/CIRAnalysisKind.cpp
+++ b/clang/lib/CIR/Analysis/CIRAnalysisKind.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-#include "clang/CIR/Sema/CIRAnalysisKind.h"
+#include "clang/CIR/Analysis/CIRAnalysisKind.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
diff --git a/clang/lib/CIR/Sema/CMakeLists.txt b/clang/lib/CIR/Analysis/CMakeLists.txt
similarity index 100%
rename from clang/lib/CIR/Sema/CMakeLists.txt
rename to clang/lib/CIR/Analysis/CMakeLists.txt
diff --git a/clang/lib/CIR/Sema/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
similarity index 74%
rename from clang/lib/CIR/Sema/FallThroughWarning.cpp
rename to clang/lib/CIR/Analysis/FallThroughWarning.cpp
index dd22317fb48ba..d437eb6c377c4 100644
--- a/clang/lib/CIR/Sema/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -1,4 +1,4 @@
-#include "clang/CIR/Sema/FallThroughWarning.h"
+#include "clang/CIR/Analysis/FallThroughWarning.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/SourceLocation.h"
@@ -6,6 +6,7 @@
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/Sema/Sema.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
using namespace mlir;
@@ -14,6 +15,36 @@ using namespace clang;
namespace clang {
+//===----------------------------------------------------------------------===//
+// Helper function to lookup a Decl by name from ASTContext
+//===----------------------------------------------------------------------===//
+
+/// Lookup a declaration by name in the translation unit.
+/// \param Context The ASTContext to search in
+/// \param Name The name of the declaration to find
+/// \return The found Decl, or nullptr if not found
+
+/// WARN: I have to say, we only use this because a lot of the time, attribute
+/// that we might need are not port to CIR currently so this function is
+/// basically a crutch for that
+static Decl *getDeclByName(ASTContext &context, StringRef name) {
+ // Get the identifier for the name
+ IdentifierInfo *ii = &context.Idents.get(name);
+
+ // Create a DeclarationName from the identifier
+ DeclarationName dName(ii);
+
+ // Lookup in the translation unit
+ TranslationUnitDecl *tu = context.getTranslationUnitDecl();
+ DeclContext::lookup_result result = tu->lookup(dName);
+
+ // Return the first match, or nullptr if not found
+ if (result.empty())
+ return nullptr;
+
+ return result.front();
+}
+
//===----------------------------------------------------------------------===//
// Check for missing return value.
//===----------------------------------------------------------------------===//
@@ -41,8 +72,11 @@ bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d,
void FallThroughWarningPass::checkFallThroughForFuncBody(
Sema &s, cir::FuncOp cfg, QualType blockType,
const CheckFallThroughDiagnostics &cd) {
-
llvm::errs() << "Hello world, you're in CIR sema analysis\n";
+
+ auto *d = getDeclByName(s.getASTContext(), cfg.getName());
+ assert(d && "we need non null decl");
+
bool returnsVoid = false;
bool hasNoReturn = false;
@@ -65,7 +99,25 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
// cpu_dispatch functions permit empty function bodies for ICC compatibility.
// TODO: Do we have isCPUDispatchMultiVersion?
- checkFallThrough(cfg);
+
+ switch (ControlFlowKind fallThroughType = checkFallThrough(cfg)) {
+ case UnknownFallThrough:
+ case MaybeFallThrough:
+ case AlwaysFallThrough:
+ if (hasNoReturn && cd.diagFallThroughHasNoReturn) {
+
+ } else if (!returnsVoid && cd.diagFallThroughReturnsNonVoid) {
+
+ }
+ break;
+ case NeverFallThroughOrReturn:
+ if (returnsVoid && !hasNoReturn && cd.diagNeverFallThroughOrReturn) {
+ }
+ break;
+
+ case NeverFallThrough: {
+ } break;
+ }
}
mlir::DenseSet<mlir::Block *>
diff --git a/clang/lib/CIR/CMakeLists.txt b/clang/lib/CIR/CMakeLists.txt
index f86ad0933bce6..c698267b04ef5 100644
--- a/clang/lib/CIR/CMakeLists.txt
+++ b/clang/lib/CIR/CMakeLists.txt
@@ -17,4 +17,4 @@ add_subdirectory(CodeGen)
add_subdirectory(FrontendAction)
add_subdirectory(Interfaces)
add_subdirectory(Lowering)
-add_subdirectory(Sema)
+add_subdirectory(Analysis)
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index fb86b5f848628..bcb8e8fbb644d 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -15,8 +15,8 @@
#include "clang/CIR/CIRToCIRPasses.h"
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/LowerToLLVM.h"
-#include "clang/CIR/Sema/CIRAnalysisKind.h"
-#include "clang/CIR/Sema/FallThroughWarning.h"
+#include "clang/CIR/Analysis/CIRAnalysisKind.h"
+#include "clang/CIR/Analysis/FallThroughWarning.h"
#include "clang/CodeGen/BackendUtil.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Sema/AnalysisBasedWarnings.h"
diff --git a/clang/test/CIR/Analysis/fallthrough_1.c b/clang/test/CIR/Analysis/fallthrough_1.c
new file mode 100644
index 0000000000000..267745afeba5d
--- /dev/null
+++ b/clang/test/CIR/Analysis/fallthrough_1.c
@@ -0,0 +1,33 @@
+// REQUIRES: false
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w
+// INFO: These test cases are derived from clang/test/Sema/return.c
+int unknown(void);
+
+int test7(void) {
+ unknown();
+} // expected-warning {{non-void function does not return a value}}
+
+int test8(void) {
+ (void)(1 + unknown());
+} // expected-warning {{non-void function does not return a value}}
+
+
+
+int test14(void) {
+ (void)(1 || unknown());
+} // expected-warning {{non-void function does not return a value}}
+
+int test15(void) {
+ (void)(0 || unknown());
+} // expected-warning {{non-void function does not return a value}}
+
+int test16(void) {
+ (void)(0 && unknown());
+} // expected-warning {{non-void function does not return a value}}
+
+int test17(void) {
+ (void)(1 && unknown());
+} // expected-warning {{non-void function does not return a value}}
+
+
diff --git a/clang/test/CIR/Analysis/fallthrough_2.c b/clang/test/CIR/Analysis/fallthrough_2.c
new file mode 100644
index 0000000000000..22c399730a4ff
--- /dev/null
+++ b/clang/test/CIR/Analysis/fallthrough_2.c
@@ -0,0 +1,37 @@
+// REQUIRES: false
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w
+// INFO: These test cases are derived from clang/test/Sema/return.c
+
+int test1(void) {
+} // expected-warning {{non-void function does not return a value}}
+
+int test3(void) {
+ goto a;
+ a: ;
+} // expected-warning {{non-void function does not return a value}}
+
+int test20(void) {
+ int i;
+ if (i)
+ return 0;
+ else if (0)
+ return 2;
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
+int test22(void) {
+ int i;
+ switch (i) default: ;
+} // expected-warning {{non-void function does not return a value}}
+
+int test23(void) {
+ int i;
+ switch (i) {
+ case 0:
+ return 0;
+ case 2:
+ return 2;
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
+
>From f7aa0ff92dcf744d463e058b921ed2f41fb09fbd Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Sun, 23 Nov 2025 03:50:45 -0800
Subject: [PATCH 04/11] test1 working
---
.../clang/CIR/Analysis/FallThroughWarning.h | 10 +-
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 228 +++++++++++++++---
clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 15 +-
clang/lib/Sema/AnalysisBasedWarnings.cpp | 1 +
4 files changed, 215 insertions(+), 39 deletions(-)
diff --git a/clang/include/clang/CIR/Analysis/FallThroughWarning.h b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
index 3f87b3f867d65..d0213bd677f85 100644
--- a/clang/include/clang/CIR/Analysis/FallThroughWarning.h
+++ b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
@@ -21,6 +21,7 @@
#include "mlir/IR/Block.h"
#include "mlir/IR/Operation.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "mlir/Support/LLVM.h"
namespace cir {
class FuncOp;
@@ -28,7 +29,7 @@ class FuncOp;
namespace clang {
class Sema;
-
+Decl *getDeclByName(ASTContext &context, StringRef name);
enum ControlFlowKind {
UnknownFallThrough,
NeverFallThrough,
@@ -45,9 +46,16 @@ struct CheckFallThroughDiagnostics {
unsigned funKind = 0;
SourceLocation funcLoc;
+ static CheckFallThroughDiagnostics makeForFunction(Sema &s,
+ const Decl *func);
+ static CheckFallThroughDiagnostics makeForCoroutine(const Decl *func);
+ static CheckFallThroughDiagnostics makeForBlock();
+ static CheckFallThroughDiagnostics makeForLambda();
bool checkDiagnostics(DiagnosticsEngine &d, bool returnsVoid,
bool hasNoReturn) const;
};
+/// Check if a return operation returns a phony value (uninitialized __retval)
+bool isPhonyReturn(cir::ReturnOp returnOp);
/// Pass for analyzing fall-through behavior in CIR functions
class FallThroughWarningPass {
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index d437eb6c377c4..d6bee5f826b4c 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -27,7 +27,7 @@ namespace clang {
/// WARN: I have to say, we only use this because a lot of the time, attribute
/// that we might need are not port to CIR currently so this function is
/// basically a crutch for that
-static Decl *getDeclByName(ASTContext &context, StringRef name) {
+Decl *getDeclByName(ASTContext &context, StringRef name) {
// Get the identifier for the name
IdentifierInfo *ii = &context.Idents.get(name);
@@ -45,6 +45,122 @@ static Decl *getDeclByName(ASTContext &context, StringRef name) {
return result.front();
}
+CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForFunction(Sema &s,
+ const Decl *func) {
+ CheckFallThroughDiagnostics d;
+ d.funcLoc = func->getLocation();
+ d.diagFallThroughHasNoReturn = diag::warn_noreturn_has_return_expr;
+ d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid;
+
+ // Don't suggest that virtual functions be marked "noreturn", since they
+ // might be overridden by non-noreturn functions.
+ bool isVirtualMethod = false;
+ if (const CXXMethodDecl *method = dyn_cast<CXXMethodDecl>(func))
+ isVirtualMethod = method->isVirtual();
+
+ // Don't suggest that template instantiations be marked "noreturn"
+ bool isTemplateInstantiation = false;
+ if (const FunctionDecl *function = dyn_cast<FunctionDecl>(func)) {
+ isTemplateInstantiation = function->isTemplateInstantiation();
+ if (!s.getLangOpts().CPlusPlus && !s.getLangOpts().C99 &&
+ function->isMain()) {
+ d.diagFallThroughReturnsNonVoid = diag::ext_main_no_return;
+ }
+ }
+
+ if (!isVirtualMethod && !isTemplateInstantiation)
+ d.diagNeverFallThroughOrReturn = diag::warn_suggest_noreturn_function;
+
+ d.funKind = diag::FalloffFunctionKind::Function;
+ return d;
+}
+
+CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForCoroutine(const Decl *func) {
+ CheckFallThroughDiagnostics d;
+ d.funcLoc = func->getLocation();
+ d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid;
+ d.funKind = diag::FalloffFunctionKind::Coroutine;
+ return d;
+}
+
+CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForBlock() {
+ CheckFallThroughDiagnostics D;
+ D.diagFallThroughHasNoReturn = diag::err_noreturn_has_return_expr;
+ D.diagFallThroughReturnsNonVoid = diag::err_falloff_nonvoid;
+ D.funKind = diag::FalloffFunctionKind::Block;
+ return D;
+}
+
+CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForLambda() {
+ CheckFallThroughDiagnostics d;
+ d.diagFallThroughHasNoReturn = diag::err_noreturn_has_return_expr;
+ d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid;
+ d.funKind = diag::FalloffFunctionKind::Lambda;
+ return d;
+}
+
+
+//===----------------------------------------------------------------------===//
+// Check for phony return values (returning uninitialized __retval)
+//===----------------------------------------------------------------------===//
+
+/// Check if a return operation returns a phony value.
+/// A phony return is when a function returns a value loaded from an
+/// uninitialized __retval alloca, which indicates the function doesn't
+/// actually return a meaningful value.
+///
+/// Example of phony return:
+/// \code
+/// cir.func @test1() -> !s32i {
+/// %0 = cir.alloca !s32i, !cir.ptr<!s32i>, ["__retval"]
+/// %1 = cir.load %0 : !cir.ptr<!s32i>, !s32i
+/// cir.return %1 : !s32i
+/// }
+/// \endcode
+///
+/// \param returnOp The return operation to check
+/// \return true if this is a phony return, false otherwise
+bool isPhonyReturn(cir::ReturnOp returnOp) {
+ if (!returnOp)
+ return false;
+
+ // Get the returned value - return operations use $input as the operand
+ if (!returnOp.hasOperand())
+ return false;
+
+ auto returnValue = returnOp.getInput()[0];
+
+ // Check if the return value comes from a load operation
+ auto loadOp = returnValue.getDefiningOp<cir::LoadOp>();
+ if (!loadOp)
+ return false;
+
+ // Check if the load is from an alloca
+ auto allocaOp = loadOp.getAddr().getDefiningOp<cir::AllocaOp>();
+ if (!allocaOp)
+ return false;
+
+ // Check if the alloca is named "__retval"
+ auto name = allocaOp.getName();
+ if (name != "__retval")
+ return false;
+
+ // Check if the alloca has any stores to it (if not, it's uninitialized)
+ // We need to search for store operations that write to this alloca
+ mlir::Value allocaResult = allocaOp.getResult();
+
+ for (auto *user : allocaResult.getUsers()) {
+ if (auto storeOp = dyn_cast<cir::StoreOp>(user)) {
+ // If there's a store to this alloca, it's not phony
+ // (assuming the store happens before the load in control flow)
+ return false;
+ }
+ }
+
+ // No stores found to __retval alloca - this is a phony return
+ return true;
+}
+
//===----------------------------------------------------------------------===//
// Check for missing return value.
//===----------------------------------------------------------------------===//
@@ -52,19 +168,21 @@ static Decl *getDeclByName(ASTContext &context, StringRef name) {
bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d,
bool returnsVoid,
bool hasNoReturn) const {
- if (funKind == diag::FalloffFunctionKind::Function) {
- return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
- (!hasNoReturn ||
- d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc)) &&
- (!returnsVoid ||
- d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc));
- }
- if (funKind == diag::FalloffFunctionKind::Coroutine) {
- return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
- (!hasNoReturn);
- }
- // For blocks / lambdas.
- return returnsVoid && !hasNoReturn;
+ if (funKind == diag::FalloffFunctionKind::Function) {
+ return (returnsVoid ||
+ d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc) ||
+ !hasNoReturn) &&
+ (!returnsVoid ||
+ d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc));
+ }
+ if (funKind == diag::FalloffFunctionKind::Coroutine) {
+ return (returnsVoid ||
+ d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (!hasNoReturn);
+ }
+ // For blocks / lambdas.
+ return returnsVoid && !hasNoReturn;
}
// TODO: Add a class for fall through config later
@@ -72,41 +190,83 @@ bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d,
void FallThroughWarningPass::checkFallThroughForFuncBody(
Sema &s, cir::FuncOp cfg, QualType blockType,
const CheckFallThroughDiagnostics &cd) {
- llvm::errs() << "Hello world, you're in CIR sema analysis\n";
auto *d = getDeclByName(s.getASTContext(), cfg.getName());
+ auto *body = d->getBody();
assert(d && "we need non null decl");
bool returnsVoid = false;
bool hasNoReturn = false;
+ SourceLocation lBrace = body->getBeginLoc(), rBrace = body->getEndLoc();
// Supposedly all function in cir is FuncOp
// 1. If normal function (FunctionDecl), check if it's coroutine.
// 1a. if coroutine -> check the fallthrough handler (idk what this means,
// TODO for now)
- if (cfg.getCoroutine()) {
- // TODO: Let's not worry about coroutine for now
- } else
- returnsVoid = isa<cir::VoidType>(cfg.getFunctionType().getReturnType());
-
- // TODO: Do we need to check for InferredNoReturnAttr just like in OG?
- hasNoReturn = cfg.getFunctionType().getReturnTypes().empty();
+ if (const auto *fd = dyn_cast<FunctionDecl>(d)) {
+ if (const auto *cBody = dyn_cast<CoroutineBodyStmt>(d->getBody()))
+ returnsVoid = cBody->getFallthroughHandler() != nullptr;
+ else
+ returnsVoid = fd->getReturnType()->isVoidType();
+ hasNoReturn = fd->isNoReturn() || fd->hasAttr<InferredNoReturnAttr>();
+ }
+ else if (const auto *md = dyn_cast<ObjCMethodDecl>(d)) {
+ returnsVoid = md->getReturnType()->isVoidType();
+ hasNoReturn = md->hasAttr<NoReturnAttr>();
+ }
+ else if (isa<BlockDecl>(d)) {
+ if (const FunctionType *ft =
+ blockType->getPointeeType()->getAs<FunctionType>()) {
+ if (ft->getReturnType()->isVoidType())
+ returnsVoid = true;
+ if (ft->getNoReturnAttr())
+ hasNoReturn = true;
+ }
+ }
DiagnosticsEngine &diags = s.getDiagnostics();
- if (cd.checkDiagnostics(diags, returnsVoid, hasNoReturn)) {
- return;
- }
+
+ // Short circuit for compilation speed.
+ if (cd.checkDiagnostics(diags, returnsVoid, hasNoReturn))
+ return;
// cpu_dispatch functions permit empty function bodies for ICC compatibility.
// TODO: Do we have isCPUDispatchMultiVersion?
switch (ControlFlowKind fallThroughType = checkFallThrough(cfg)) {
case UnknownFallThrough:
+ [[fallthrough]];
case MaybeFallThrough:
+ [[fallthrough]];
case AlwaysFallThrough:
if (hasNoReturn && cd.diagFallThroughHasNoReturn) {
} else if (!returnsVoid && cd.diagFallThroughReturnsNonVoid) {
+ // If the final statement is a call to an always-throwing function,
+ // don't warn about the fall-through.
+ if (d->getAsFunction()) {
+ if (const auto *cs = dyn_cast<CompoundStmt>(body);
+ cs && !cs->body_empty()) {
+ const Stmt *lastStmt = cs->body_back();
+ // Unwrap ExprWithCleanups if necessary.
+ if (const auto *ewc = dyn_cast<ExprWithCleanups>(lastStmt)) {
+ lastStmt = ewc->getSubExpr();
+ }
+ if (const auto *ce = dyn_cast<CallExpr>(lastStmt)) {
+ if (const FunctionDecl *callee = ce->getDirectCallee();
+ callee && callee->hasAttr<InferredNoReturnAttr>()) {
+ return; // Don't warn about fall-through.
+ }
+ }
+ // Direct throw.
+ if (isa<CXXThrowExpr>(lastStmt)) {
+ return; // Don't warn about fall-through.
+ }
+ }
+ }
+ bool notInAllControlPaths = fallThroughType == MaybeFallThrough;
+ s.Diag(rBrace, cd.diagFallThroughReturnsNonVoid)
+ << cd.funKind << notInAllControlPaths;
}
break;
@@ -129,7 +289,7 @@ FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
auto &first = cfg.getBody().getBlocks().front();
for (auto &block : cfg.getBody()) {
- if (first.isReachable(&block))
+ if (&first == &block || first.isReachable(&block))
liveSet.insert(&block);
}
return liveSet;
@@ -171,10 +331,8 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
continue;
mlir::Operation *term = pred.getTerminator();
- if (isa<cir::ReturnOp>(term)) {
- hasAbnormalEdge = true;
- continue;
- }
+
+ // TODO: hasNoReturnElement() in OG here, not sure how to work it in here yet
// INFO: In OG, we'll be looking for destructor since it can appear past
// return but i guess not in CIR? In this case we'll only be examining the
@@ -189,9 +347,11 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
// is true only when a block only has the terminator, or its size is 1.
hasPlainEdge = std::distance(pred.begin(), pred.end()) == 1;
- if (isa<cir::ReturnOp>(term)) {
- hasLiveReturn = true;
- continue;
+ if (auto returnOp = dyn_cast<cir::ReturnOp>(term)) {
+ if (!isPhonyReturn(returnOp)) {
+ hasLiveReturn = true;
+ continue;
+ }
}
if (isa<cir::TryOp>(term)) {
hasLiveReturn = true;
@@ -217,6 +377,8 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
// This says AlwaysFallThrough for calls to functions that are not marked
// noreturn, that don't return. If people would like this warning to be more
// accurate, such functions should be marked as noreturn.
+ //
+ // llvm_unreachable("");
return AlwaysFallThrough;
}
} // namespace clang
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index bcb8e8fbb644d..1b06c311447af 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -119,10 +119,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
// Run CIR analysis passes if requested
if (!FEOptions.ClangIRAnalysisList.empty()) {
CIRAnalysisSet AnalysisSet = parseCIRAnalysisList(FEOptions.ClangIRAnalysisList);
- AnalysisSet.print(llvm::errs());
if (AnalysisSet.has(CIRAnalysisKind::FallThrough)) {
- // Get Sema for diagnostics
if (CI.hasSema()) {
Sema &S = CI.getSema();
FallThroughWarningPass FallThroughPass;
@@ -135,10 +133,17 @@ class CIRGenConsumer : public clang::ASTConsumer {
QualType FuncType;
// Set up diagnostics configuration
- CheckFallThroughDiagnostics Diags;
-
+ // INFO: This is not full
+ Decl *D = getDeclByName(S.getASTContext(), FuncOp.getName());
+ const CheckFallThroughDiagnostics &CD =
+ (isa<BlockDecl>(D) ? CheckFallThroughDiagnostics::makeForBlock()
+ : (isa<CXXMethodDecl>(D) &&
+ cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
+ cast<CXXMethodDecl>(D)->getParent()->isLambda())
+ ? CheckFallThroughDiagnostics::makeForLambda()
+ : CheckFallThroughDiagnostics::makeForFunction(S, D));
// Run fall-through analysis on this function
- FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType, Diags);
+ FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType, CD);
});
}
}
diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp
index 41a98323450e4..a2f7f61b57d95 100644
--- a/clang/lib/Sema/AnalysisBasedWarnings.cpp
+++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp
@@ -53,6 +53,7 @@
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Debug.h"
+#include "llvm/Support/ErrorHandling.h"
#include <algorithm>
#include <deque>
#include <iterator>
>From e4a96eff701bd35fe07a4316c3c62a33658ac99d Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Sun, 23 Nov 2025 04:53:49 -0800
Subject: [PATCH 05/11] Unknown should break
---
.../clang/CIR/Analysis/FallThroughWarning.h | 5 +-
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 54 +++++++++----------
clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 11 ++--
3 files changed, 34 insertions(+), 36 deletions(-)
diff --git a/clang/include/clang/CIR/Analysis/FallThroughWarning.h b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
index d0213bd677f85..f78a07d5212c3 100644
--- a/clang/include/clang/CIR/Analysis/FallThroughWarning.h
+++ b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
@@ -21,8 +21,8 @@
#include "mlir/IR/Block.h"
#include "mlir/IR/Operation.h"
-#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "mlir/Support/LLVM.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
namespace cir {
class FuncOp;
} // namespace cir
@@ -46,8 +46,7 @@ struct CheckFallThroughDiagnostics {
unsigned funKind = 0;
SourceLocation funcLoc;
- static CheckFallThroughDiagnostics makeForFunction(Sema &s,
- const Decl *func);
+ static CheckFallThroughDiagnostics makeForFunction(Sema &s, const Decl *func);
static CheckFallThroughDiagnostics makeForCoroutine(const Decl *func);
static CheckFallThroughDiagnostics makeForBlock();
static CheckFallThroughDiagnostics makeForLambda();
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index d6bee5f826b4c..0b4ddfa5cd113 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -45,8 +45,8 @@ Decl *getDeclByName(ASTContext &context, StringRef name) {
return result.front();
}
-CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForFunction(Sema &s,
- const Decl *func) {
+CheckFallThroughDiagnostics
+CheckFallThroughDiagnostics::makeForFunction(Sema &s, const Decl *func) {
CheckFallThroughDiagnostics d;
d.funcLoc = func->getLocation();
d.diagFallThroughHasNoReturn = diag::warn_noreturn_has_return_expr;
@@ -75,7 +75,8 @@ CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForFunction(Sema &s
return d;
}
-CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForCoroutine(const Decl *func) {
+CheckFallThroughDiagnostics
+CheckFallThroughDiagnostics::makeForCoroutine(const Decl *func) {
CheckFallThroughDiagnostics d;
d.funcLoc = func->getLocation();
d.diagFallThroughReturnsNonVoid = diag::warn_falloff_nonvoid;
@@ -99,7 +100,6 @@ CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForLambda() {
return d;
}
-
//===----------------------------------------------------------------------===//
// Check for phony return values (returning uninitialized __retval)
//===----------------------------------------------------------------------===//
@@ -168,21 +168,19 @@ bool isPhonyReturn(cir::ReturnOp returnOp) {
bool CheckFallThroughDiagnostics::checkDiagnostics(DiagnosticsEngine &d,
bool returnsVoid,
bool hasNoReturn) const {
- if (funKind == diag::FalloffFunctionKind::Function) {
- return (returnsVoid ||
- d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
- (d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc) ||
- !hasNoReturn) &&
- (!returnsVoid ||
- d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc));
- }
- if (funKind == diag::FalloffFunctionKind::Coroutine) {
- return (returnsVoid ||
- d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
- (!hasNoReturn);
- }
- // For blocks / lambdas.
- return returnsVoid && !hasNoReturn;
+ if (funKind == diag::FalloffFunctionKind::Function) {
+ return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (d.isIgnored(diag::warn_noreturn_has_return_expr, funcLoc) ||
+ !hasNoReturn) &&
+ (!returnsVoid ||
+ d.isIgnored(diag::warn_suggest_noreturn_block, funcLoc));
+ }
+ if (funKind == diag::FalloffFunctionKind::Coroutine) {
+ return (returnsVoid || d.isIgnored(diag::warn_falloff_nonvoid, funcLoc)) &&
+ (!hasNoReturn);
+ }
+ // For blocks / lambdas.
+ return returnsVoid && !hasNoReturn;
}
// TODO: Add a class for fall through config later
@@ -209,14 +207,12 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
else
returnsVoid = fd->getReturnType()->isVoidType();
hasNoReturn = fd->isNoReturn() || fd->hasAttr<InferredNoReturnAttr>();
- }
- else if (const auto *md = dyn_cast<ObjCMethodDecl>(d)) {
+ } else if (const auto *md = dyn_cast<ObjCMethodDecl>(d)) {
returnsVoid = md->getReturnType()->isVoidType();
hasNoReturn = md->hasAttr<NoReturnAttr>();
- }
- else if (isa<BlockDecl>(d)) {
+ } else if (isa<BlockDecl>(d)) {
if (const FunctionType *ft =
- blockType->getPointeeType()->getAs<FunctionType>()) {
+ blockType->getPointeeType()->getAs<FunctionType>()) {
if (ft->getReturnType()->isVoidType())
returnsVoid = true;
if (ft->getNoReturnAttr())
@@ -228,14 +224,14 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
// Short circuit for compilation speed.
if (cd.checkDiagnostics(diags, returnsVoid, hasNoReturn))
- return;
+ return;
// cpu_dispatch functions permit empty function bodies for ICC compatibility.
// TODO: Do we have isCPUDispatchMultiVersion?
switch (ControlFlowKind fallThroughType = checkFallThrough(cfg)) {
case UnknownFallThrough:
- [[fallthrough]];
+ break;
case MaybeFallThrough:
[[fallthrough]];
case AlwaysFallThrough:
@@ -267,7 +263,6 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
bool notInAllControlPaths = fallThroughType == MaybeFallThrough;
s.Diag(rBrace, cd.diagFallThroughReturnsNonVoid)
<< cd.funKind << notInAllControlPaths;
-
}
break;
case NeverFallThroughOrReturn:
@@ -332,7 +327,8 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
mlir::Operation *term = pred.getTerminator();
- // TODO: hasNoReturnElement() in OG here, not sure how to work it in here yet
+ // TODO: hasNoReturnElement() in OG here, not sure how to work it in here
+ // yet
// INFO: In OG, we'll be looking for destructor since it can appear past
// return but i guess not in CIR? In this case we'll only be examining the
@@ -348,7 +344,7 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
hasPlainEdge = std::distance(pred.begin(), pred.end()) == 1;
if (auto returnOp = dyn_cast<cir::ReturnOp>(term)) {
- if (!isPhonyReturn(returnOp)) {
+ if (!isPhonyReturn(returnOp)) {
hasLiveReturn = true;
continue;
}
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 1b06c311447af..627265cec3d3b 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -118,7 +118,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
// Run CIR analysis passes if requested
if (!FEOptions.ClangIRAnalysisList.empty()) {
- CIRAnalysisSet AnalysisSet = parseCIRAnalysisList(FEOptions.ClangIRAnalysisList);
+ CIRAnalysisSet AnalysisSet =
+ parseCIRAnalysisList(FEOptions.ClangIRAnalysisList);
if (AnalysisSet.has(CIRAnalysisKind::FallThrough)) {
if (CI.hasSema()) {
@@ -138,12 +139,14 @@ class CIRGenConsumer : public clang::ASTConsumer {
const CheckFallThroughDiagnostics &CD =
(isa<BlockDecl>(D) ? CheckFallThroughDiagnostics::makeForBlock()
: (isa<CXXMethodDecl>(D) &&
- cast<CXXMethodDecl>(D)->getOverloadedOperator() == OO_Call &&
+ cast<CXXMethodDecl>(D)->getOverloadedOperator() ==
+ OO_Call &&
cast<CXXMethodDecl>(D)->getParent()->isLambda())
? CheckFallThroughDiagnostics::makeForLambda()
- : CheckFallThroughDiagnostics::makeForFunction(S, D));
+ : CheckFallThroughDiagnostics::makeForFunction(S, D));
// Run fall-through analysis on this function
- FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType, CD);
+ FallThroughPass.checkFallThroughForFuncBody(S, FuncOp, FuncType,
+ CD);
});
}
}
>From fc26681cbb80327882c8b1b67692aedc19c363cb Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Sun, 7 Dec 2025 23:13:43 -0800
Subject: [PATCH 06/11] Take every except switch
> Co-authored-by: Julius Alexandre juliuswoosebert at gmail.com
---
clang/include/clang/Basic/LangOptions.def | 2 ++
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 25 +++++++++++++------
clang/lib/Frontend/CompilerInvocation.cpp | 6 +++++
clang/lib/Sema/SemaDecl.cpp | 3 +++
clang/test/CIR/Analysis/fallthrough_1.c | 4 +--
clang/test/CIR/Analysis/fallthrough_2.c | 4 +--
6 files changed, 30 insertions(+), 14 deletions(-)
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 40fc66ea12e34..6d44b4a382151 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -501,6 +501,8 @@ LANGOPT(EnableLifetimeSafety, 1, 0, NotCompatible, "Experimental lifetime safety
LANGOPT(PreserveVec3Type, 1, 0, NotCompatible, "Preserve 3-component vector type")
+LANGOPT(CIRFallThroughAnalysis, 1, 0, NotCompatible, "Use CIR fallthrough analysis")
+
#undef LANGOPT
#undef ENUM_LANGOPT
#undef VALUE_LANGOPT
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index 0b4ddfa5cd113..1c3d16ae5f7ef 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -1,4 +1,5 @@
#include "clang/CIR/Analysis/FallThroughWarning.h"
+#include "mlir/IR/OpDefinition.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/SourceLocation.h"
@@ -130,7 +131,6 @@ bool isPhonyReturn(cir::ReturnOp returnOp) {
auto returnValue = returnOp.getInput()[0];
- // Check if the return value comes from a load operation
auto loadOp = returnValue.getDefiningOp<cir::LoadOp>();
if (!loadOp)
return false;
@@ -145,19 +145,23 @@ bool isPhonyReturn(cir::ReturnOp returnOp) {
if (name != "__retval")
return false;
- // Check if the alloca has any stores to it (if not, it's uninitialized)
- // We need to search for store operations that write to this alloca
+ // Check if there are ANY stores to __retval in the entire function.
+ // This is intentionally path-INsensitive - if there are stores on some
+ // paths, then this return is considered non-phony.
+ // The control flow analysis (hasLiveReturn + hasPlainEdge) will determine
+ // if all paths return properly.
mlir::Value allocaResult = allocaOp.getResult();
for (auto *user : allocaResult.getUsers()) {
if (auto storeOp = dyn_cast<cir::StoreOp>(user)) {
- // If there's a store to this alloca, it's not phony
- // (assuming the store happens before the load in control flow)
- return false;
+ if (storeOp.getAddr() == allocaResult) {
+ // There's a store to __retval somewhere - not a phony return
+ return false;
+ }
}
}
- // No stores found to __retval alloca - this is a phony return
+ // No stores to __retval anywhere - this is a phony return (uninitialized)
return true;
}
@@ -190,8 +194,13 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
const CheckFallThroughDiagnostics &cd) {
auto *d = getDeclByName(s.getASTContext(), cfg.getName());
- auto *body = d->getBody();
assert(d && "we need non null decl");
+ auto *body = d->getBody();
+
+ // Functions without bodies (declarations only) don't need fall-through
+ // analysis
+ if (!body)
+ return;
bool returnsVoid = false;
bool hasNoReturn = false;
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index f584a2a5824b2..7c0de4686d1bc 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -5066,6 +5066,12 @@ bool CompilerInvocation::CreateFromArgsImpl(
isCodeGenAction(Res.getFrontendOpts().ProgramAction))
Diags.Report(diag::warn_drv_openacc_without_cir);
+ // Enable CIR Fallthrough analysis LangOpt if requested
+ for (const auto &Analysis : Res.getFrontendOpts().ClangIRAnalysisList) {
+ if (Analysis == "fallthrough")
+ LangOpts.CIRFallThroughAnalysis = true;
+ }
+
// Set the triple of the host for OpenMP device compile.
if (LangOpts.OpenMPIsTargetDevice)
Res.getTargetOpts().HostTriple = Res.getFrontendOpts().AuxTriple;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index 25b89d65847ad..39008e73a17ad 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -16525,6 +16525,9 @@ Decl *Sema::ActOnFinishFunctionBody(Decl *dcl, Stmt *Body, bool IsInstantiation,
FD->hasAttr<NakedAttr>())
WP.disableCheckFallThrough();
+ if (getLangOpts().CIRFallThroughAnalysis)
+ WP.disableCheckFallThrough();
+
// MSVC permits the use of pure specifier (=0) on function definition,
// defined at class scope, warn about this non-standard construct.
if (getLangOpts().MicrosoftExt && FD->isPureVirtual() &&
diff --git a/clang/test/CIR/Analysis/fallthrough_1.c b/clang/test/CIR/Analysis/fallthrough_1.c
index 267745afeba5d..6d5a215d6e8ff 100644
--- a/clang/test/CIR/Analysis/fallthrough_1.c
+++ b/clang/test/CIR/Analysis/fallthrough_1.c
@@ -1,6 +1,4 @@
-// REQUIRES: false
-
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir -fclangir-analysis="fallthrough" -emit-cir -o /dev/null -verify
// INFO: These test cases are derived from clang/test/Sema/return.c
int unknown(void);
diff --git a/clang/test/CIR/Analysis/fallthrough_2.c b/clang/test/CIR/Analysis/fallthrough_2.c
index 22c399730a4ff..57931de219491 100644
--- a/clang/test/CIR/Analysis/fallthrough_2.c
+++ b/clang/test/CIR/Analysis/fallthrough_2.c
@@ -1,6 +1,4 @@
-// REQUIRES: false
-
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir-analysis="fallthrough" -w
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir -fclangir-analysis="fallthrough" -emit-cir -o /dev/null -verify
// INFO: These test cases are derived from clang/test/Sema/return.c
int test1(void) {
>From af22e7b3f81baa1d62131f57fddc9d1af6f6bbb7 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Sun, 7 Dec 2025 23:55:53 -0800
Subject: [PATCH 07/11] Fix getLiveSet() via DCE
---
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 57 ++++++++++++-------
1 file changed, 38 insertions(+), 19 deletions(-)
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index 1c3d16ae5f7ef..edb96f3f4a115 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -1,5 +1,8 @@
#include "clang/CIR/Analysis/FallThroughWarning.h"
+#include "mlir/Analysis/DataFlowFramework.h"
+#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
#include "mlir/IR/OpDefinition.h"
+#include "mlir/IR/Operation.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/SourceLocation.h"
@@ -7,9 +10,12 @@
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/Sema/Sema.h"
+#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
+#define DEBUG_TYPE "cir-fallthrough"
+
using namespace mlir;
using namespace cir;
using namespace clang;
@@ -290,12 +296,29 @@ FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
if (cfg.getBody().empty())
return liveSet;
- auto &first = cfg.getBody().getBlocks().front();
-
- for (auto &block : cfg.getBody()) {
- if (&first == &block || first.isReachable(&block))
- liveSet.insert(&block);
+ DataFlowSolver solver;
+ solver.load<mlir::dataflow::DeadCodeAnalysis>();
+ int blockCount = 0;
+ auto result = solver.initializeAndRun(cfg);
+ if (result.failed()) {
+ llvm::errs() << "Failure to perform deadcode analysis for ClangIR analyzer, returning empty live set\n";
+ } else {
+ cfg->walk([&](mlir::Block *op) {
+ blockCount++;
+ if (solver.lookupState<mlir::dataflow::Executable>(solver.getProgramPointBefore(op))) {
+ liveSet.insert(op);
+ }
+ });
}
+
+ LLVM_DEBUG({
+ llvm::dbgs() << "=== DCA result for cir analysis fallthrough for " << cfg.getName() << " ===\n";
+ llvm::dbgs() << "Live set size: " << liveSet.size() << "\n";
+ llvm::dbgs() << "Block traversal count: " << blockCount << "\n";
+ llvm::dbgs() << "Dead blocks: " << (blockCount - liveSet.size()) << "\n";
+ llvm::dbgs() << "If this is unexpected, please file an issue via GitHub with mlir tag" << "\n";
+ });
+
return liveSet;
}
@@ -323,18 +346,17 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
//
// I guess in CIR, we can pretend exit blocks are all blocks that have no
// successor?
- for (mlir::Block &pred : cfg.getBody().getBlocks()) {
- if (!liveSet.contains(&pred))
- continue;
-
+ for (mlir::Block *pred : liveSet) {
// We consider no predecessors as 'exit blocks'
- if (!pred.hasNoSuccessors())
+ if (!pred->hasNoSuccessors())
continue;
- if (!pred.mightHaveTerminator())
+ if (!pred->mightHaveTerminator())
continue;
- mlir::Operation *term = pred.getTerminator();
+ mlir::Operation *term = pred->getTerminator();
+
+ LLVM_DEBUG(pred->dump());
// TODO: hasNoReturnElement() in OG here, not sure how to work it in here
// yet
@@ -343,14 +365,10 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
// return but i guess not in CIR? In this case we'll only be examining the
// terminator
- if (isa<cir::TryOp>(term)) {
- hasAbnormalEdge = true;
- continue;
- }
-
// INFO: OG clang has this equals true whenever ri == re, which means this
// is true only when a block only has the terminator, or its size is 1.
- hasPlainEdge = std::distance(pred.begin(), pred.end()) == 1;
+ //
+ // equivalent is std::distance(pred.begin(), pred.end()) == 1;
if (auto returnOp = dyn_cast<cir::ReturnOp>(term)) {
if (!isPhonyReturn(returnOp)) {
@@ -358,8 +376,9 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
continue;
}
}
+
if (isa<cir::TryOp>(term)) {
- hasLiveReturn = true;
+ hasAbnormalEdge = true;
continue;
}
>From fc3d9f1f7c3afbb17d98792b0f76c2bf9016a2bd Mon Sep 17 00:00:00 2001
From: wizardengineer <juliuswoosebert at gmail.com>
Date: Fri, 5 Dec 2025 23:16:07 -0500
Subject: [PATCH 08/11] [CIR][Analyzer] Cherry pick Julius's "Revamping
codebase and switch implementation"
---
.../clang/CIR/Analysis/FallThroughWarning.h | 2 +-
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 96 ++++++++++++----
clang/test/CIR/Analysis/fallthrough_switch.c | 106 ++++++++++++++++++
3 files changed, 181 insertions(+), 23 deletions(-)
create mode 100644 clang/test/CIR/Analysis/fallthrough_switch.c
diff --git a/clang/include/clang/CIR/Analysis/FallThroughWarning.h b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
index f78a07d5212c3..0173beba97d2d 100644
--- a/clang/include/clang/CIR/Analysis/FallThroughWarning.h
+++ b/clang/include/clang/CIR/Analysis/FallThroughWarning.h
@@ -65,7 +65,7 @@ class FallThroughWarningPass {
void checkFallThroughForFuncBody(Sema &s, cir::FuncOp cfg, QualType blockType,
const CheckFallThroughDiagnostics &cd);
ControlFlowKind checkFallThrough(cir::FuncOp cfg);
- mlir::DenseSet<mlir::Block *> getLiveSet(cir::FuncOp cfg);
+ mlir::SetVector<mlir::Block *> getLiveSet(cir::FuncOp cfg);
};
} // namespace clang
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index edb96f3f4a115..44d0d60efc2ad 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -1,8 +1,10 @@
#include "clang/CIR/Analysis/FallThroughWarning.h"
-#include "mlir/Analysis/DataFlowFramework.h"
#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h"
+#include "mlir/Analysis/DataFlowFramework.h"
+#include "mlir/IR/Block.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/Operation.h"
+#include "mlir/Support/LLVM.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticSema.h"
#include "clang/Basic/SourceLocation.h"
@@ -10,6 +12,7 @@
#include "clang/CIR/Dialect/IR/CIRDialect.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/Sema/Sema.h"
+#include "llvm/Support/Casting.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/raw_ostream.h"
@@ -290,9 +293,9 @@ void FallThroughWarningPass::checkFallThroughForFuncBody(
}
}
-mlir::DenseSet<mlir::Block *>
+mlir::SetVector<mlir::Block *>
FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
- mlir::DenseSet<mlir::Block *> liveSet;
+ mlir::SetVector<mlir::Block *> liveSet;
if (cfg.getBody().empty())
return liveSet;
@@ -301,27 +304,77 @@ FallThroughWarningPass::getLiveSet(cir::FuncOp cfg) {
int blockCount = 0;
auto result = solver.initializeAndRun(cfg);
if (result.failed()) {
- llvm::errs() << "Failure to perform deadcode analysis for ClangIR analyzer, returning empty live set\n";
+ llvm::errs() << "Failure to perform deadcode analysis for ClangIR "
+ "analyzer, returning empty live set\n";
} else {
cfg->walk([&](mlir::Block *op) {
blockCount++;
- if (solver.lookupState<mlir::dataflow::Executable>(solver.getProgramPointBefore(op))) {
+ if (solver.lookupState<mlir::dataflow::Executable>(
+ solver.getProgramPointBefore(op))) {
liveSet.insert(op);
}
});
}
-
LLVM_DEBUG({
- llvm::dbgs() << "=== DCA result for cir analysis fallthrough for " << cfg.getName() << " ===\n";
+ llvm::dbgs() << "=== DCA result for cir analysis fallthrough for "
+ << cfg.getName() << " ===\n";
llvm::dbgs() << "Live set size: " << liveSet.size() << "\n";
llvm::dbgs() << "Block traversal count: " << blockCount << "\n";
llvm::dbgs() << "Dead blocks: " << (blockCount - liveSet.size()) << "\n";
- llvm::dbgs() << "If this is unexpected, please file an issue via GitHub with mlir tag" << "\n";
+ llvm::dbgs() << "If this is unexpected, please file an issue via GitHub "
+ "with mlir tag"
+ << "\n";
});
return liveSet;
}
+//===----------------------------------------------------------------------===//
+// Switch/Case Analysis Helpers
+//===----------------------------------------------------------------------===//
+
+/// Check if a switch operation returns on all code paths. Right now this only
+/// works with case default Returns true if:
+/// - Switch is in simple form AND
+/// - Has a default case that returns
+///
+/// \param switchOp The switch operation to analyze
+/// \return true if all paths through the switch return a value
+static bool switchDefaultsWithCoveredEnums(cir::SwitchOp switchOp) {
+ llvm::SmallVector<cir::CaseOp> cases;
+ if (!switchOp.isSimpleForm(cases))
+ return false;
+
+ // Check if there's a default case
+ bool hasDefault = false;
+
+ // TODO: Cover the enum case once switchOp comes out with input as enum
+ for (auto caseOp : cases) {
+ if (caseOp.getKind() == cir::CaseOpKind::Default) {
+ hasDefault = true;
+ break;
+ }
+ }
+
+ return hasDefault;
+}
+static bool
+ignoreDefaultsWithCoveredEnums(mlir::Block *block,
+ mlir::DenseSet<mlir::Block *> &shouldNotVisit) {
+ mlir::Operation *potentialSwitchOp = block->getParentOp();
+ if (shouldNotVisit.contains(block))
+ return true;
+ if (cir::SwitchOp switchOp =
+ llvm::dyn_cast_or_null<cir::SwitchOp>(potentialSwitchOp)) {
+ if (switchDefaultsWithCoveredEnums(switchOp)) {
+ switchOp->walk(
+ [&shouldNotVisit](mlir::Block *op) { shouldNotVisit.insert(op); });
+ }
+ return true;
+ }
+
+ return false;
+}
ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
assert(cfg && "there can't be a null func op");
@@ -331,26 +384,31 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
return UnknownFallThrough;
}
- mlir::DenseSet<mlir::Block *> liveSet = this->getLiveSet(cfg);
-
- unsigned count = liveSet.size();
+ mlir::SetVector<mlir::Block *> liveSet = this->getLiveSet(cfg);
bool hasLiveReturn = false;
bool hasFakeEdge = false;
bool hasPlainEdge = false;
bool hasAbnormalEdge = false;
- auto &exitBlock = cfg.getBody().back();
+ // This corresponds to OG's IgnoreDefaultsWithCoveredEnums
+ mlir::DenseSet<mlir::Block *> shouldNotVisit;
+
// INFO: in OG clang CFG, they have an empty exit block, so when they query
// pred of exit OG, they get all exit blocks
//
// I guess in CIR, we can pretend exit blocks are all blocks that have no
// successor?
for (mlir::Block *pred : liveSet) {
- // We consider no predecessors as 'exit blocks'
+ if (ignoreDefaultsWithCoveredEnums(pred, shouldNotVisit))
+ continue;
+
+ // We consider no successor as 'exit blocks'
if (!pred->hasNoSuccessors())
continue;
+ // Walk all ReturnOp operations to find returns in nested regions
+
if (!pred->mightHaveTerminator())
continue;
@@ -382,13 +440,7 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
continue;
}
- // TODO: Maybe one day throw will be terminator?
- //
- // TODO: We need to add a microsoft inline assembly enum
-
- // TODO: We don't concer with try op either since it's not terminator
-
- hasPlainEdge = true;
+ hasPlainEdge = true;
}
if (!hasPlainEdge) {
@@ -399,8 +451,8 @@ ControlFlowKind FallThroughWarningPass::checkFallThrough(cir::FuncOp cfg) {
if (hasAbnormalEdge || hasFakeEdge || hasLiveReturn)
return MaybeFallThrough;
// This says AlwaysFallThrough for calls to functions that are not marked
- // noreturn, that don't return. If people would like this warning to be more
- // accurate, such functions should be marked as noreturn.
+ // noreturn, that don't return. If people would like this warning to be
+ // more accurate, such functions should be marked as noreturn.
//
// llvm_unreachable("");
return AlwaysFallThrough;
diff --git a/clang/test/CIR/Analysis/fallthrough_switch.c b/clang/test/CIR/Analysis/fallthrough_switch.c
new file mode 100644
index 0000000000000..6a234dda5e559
--- /dev/null
+++ b/clang/test/CIR/Analysis/fallthrough_switch.c
@@ -0,0 +1,106 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu %s -fclangir -fclangir-analysis="fallthrough" -emit-cir -o /dev/null -verify
+// INFO: Switch statement test cases for CIR fallthrough analysis
+// Derived from clang/test/Sema/return.c
+
+// ===========================================================================
+// Test cases where switch SHOULD NOT warn (all paths return)
+// ===========================================================================
+
+// Switch with default - all cases return
+int switch_all_return(int x) {
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ return 1;
+ default:
+ return -1;
+ }
+}
+
+// Test from return.c: enum switch covering all cases
+enum Cases { C1, C2, C3, C4 };
+int test_enum_cases(enum Cases C) {
+ switch (C) {
+ case C1: return 1;
+ case C2: return 2;
+ case C4: return 3;
+ case C3: return 4;
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+// TODO: Should not warn - enum covers all cases (needs enum coverage analysis)
+
+// ===========================================================================
+// Test cases where switch SHOULD warn
+// ===========================================================================
+
+// Switch without default
+int switch_no_default(int x) {
+ switch (x) {
+ case 0:
+ return 0;
+ case 2:
+ return 2;
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
+// Switch with only default that doesn't return
+int switch_default_no_return(int x) {
+ switch (x) default: ;
+} // expected-warning {{non-void function does not return a value}}
+
+// Switch with break in one case
+int switch_with_break(int x) {
+ switch (x) {
+ case 0:
+ return 0;
+ case 1:
+ break;
+ default:
+ return -1;
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
+// Fallthrough case (using fallthrough, not return)
+int switch_fallthrough(int x) {
+ switch (x) {
+ case 0:
+ case 1: // fallthrough from case 0
+ return 1;
+ default:
+ break; // falls through to end
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
+// ===========================================================================
+// Nested switch statements
+// ===========================================================================
+
+int nested_switch(int x, int y) {
+ switch (x) {
+ case 0:
+ switch (y) {
+ case 0:
+ return 0;
+ default:
+ return 1;
+ }
+ default:
+ return -1;
+ }
+}
+
+int nested_switch_missing(int x, int y) {
+ switch (x) {
+ case 0:
+ switch (y) {
+ case 0:
+ return 0;
+ // No default in inner switch
+ }
+ break; // Falls through after inner switch
+ default:
+ return -1;
+ }
+} // expected-warning {{non-void function does not return a value in all control paths}}
+
>From 8cbe846b28cc6a6ba99673802e33f347f1ff08f8 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Mon, 8 Dec 2025 05:00:55 -0800
Subject: [PATCH 09/11] Add switch case covered cirops.td
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 3 ++-
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 2 ++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 16258513239d9..b8baac21bb990 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1131,7 +1131,8 @@ def CIR_SwitchOp : CIR_Op<"switch", [
```
}];
- let arguments = (ins CIR_IntType:$condition);
+ let arguments = (ins CIR_IntType:$condition,
+ DefaultValuedAttr<BoolAttr, "false">:$allEnumCasesCovered);
let regions = (region AnyRegion:$body);
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 7bb8c2153056a..0d9cb627efdda 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -1081,6 +1081,8 @@ mlir::LogicalResult CIRGenFunction::emitSwitchStmt(const clang::SwitchStmt &s) {
terminateBody(builder, caseOp.getCaseRegion(), caseOp.getLoc());
terminateBody(builder, swop.getBody(), swop.getLoc());
+ swop.setAllEnumCasesCovered(s.isAllEnumCasesCovered());
+
return res;
}
>From e873fa5740d3dcfe2cfd4db72f695bf07718abc3 Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Mon, 15 Dec 2025 13:14:39 -0800
Subject: [PATCH 10/11] Add implicit return to return op
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 2 +-
clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 8 ++++----
clang/lib/CIR/CodeGen/CIRGenFunction.h | 2 +-
3 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 240459428b22c..6ba81a1cbd20f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -714,7 +714,7 @@ def CIR_ReturnOp : CIR_Op<"return", [
// The return operation takes an optional input operand to return. This
// value must match the return type of the enclosing function.
- let arguments = (ins Variadic<CIR_AnyType>:$input);
+ let arguments = (ins Variadic<CIR_AnyType>:$input, UnitAttr:$is_implicit);
// The return operation only emits the input in the format if it is present.
let assemblyFormat = "($input^ `:` type($input))? attr-dict ";
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
index ac66d00950f05..48039747d5a33 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp
@@ -352,7 +352,7 @@ void CIRGenFunction::LexicalScope::cleanup() {
insertCleanupAndLeave(curBlock);
}
-cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
+cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc, bool isImplicit) {
CIRGenBuilderTy &builder = cgf.getBuilder();
// If we are on a coroutine, add the coro_end builtin call.
@@ -365,9 +365,9 @@ cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
auto value = cir::LoadOp::create(
builder, loc, fn.getFunctionType().getReturnType(), *cgf.fnRetAlloca);
return cir::ReturnOp::create(builder, loc,
- llvm::ArrayRef(value.getResult()));
+ llvm::ArrayRef(value.getResult()), isImplicit);
}
- return cir::ReturnOp::create(builder, loc);
+ return cir::ReturnOp::create(builder, loc, {}, isImplicit);
}
// This is copied from CodeGenModule::MayDropFunctionReturn. This is a
@@ -410,7 +410,7 @@ void CIRGenFunction::LexicalScope::emitImplicitReturn() {
}
}
- (void)emitReturn(localScope->endLoc);
+ (void)emitReturn(localScope->endLoc, /*isImplicit=*/true);
}
cir::TryOp CIRGenFunction::LexicalScope::getClosestTryParent() {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index cfe9b37c2c725..2eab96ab55a78 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1194,7 +1194,7 @@ class CIRGenFunction : public CIRGenTypeCache {
return b;
}
- cir::ReturnOp emitReturn(mlir::Location loc);
+ cir::ReturnOp emitReturn(mlir::Location loc, bool isImplicit = false);
void emitImplicitReturn();
public:
>From 773285be2e185995eff33f28bbcb3c07bc60460e Mon Sep 17 00:00:00 2001
From: Jasmine Tang <jjasmine at igalia.com>
Date: Mon, 15 Dec 2025 13:18:15 -0800
Subject: [PATCH 11/11] Redo isPhony
---
clang/lib/CIR/Analysis/FallThroughWarning.cpp | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang/lib/CIR/Analysis/FallThroughWarning.cpp b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
index 44d0d60efc2ad..56f3886749d07 100644
--- a/clang/lib/CIR/Analysis/FallThroughWarning.cpp
+++ b/clang/lib/CIR/Analysis/FallThroughWarning.cpp
@@ -131,8 +131,10 @@ CheckFallThroughDiagnostics CheckFallThroughDiagnostics::makeForLambda() {
/// \param returnOp The return operation to check
/// \return true if this is a phony return, false otherwise
bool isPhonyReturn(cir::ReturnOp returnOp) {
- if (!returnOp)
- return false;
+ assert(returnOp && "ReturnOp should be non-null");
+
+ if (returnOp.getIsImplicit())
+ return true;
// Get the returned value - return operations use $input as the operand
if (!returnOp.hasOperand())
More information about the cfe-commits
mailing list