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 <[email protected]> Date: Thu, 20 Nov 2025 05:23:58 -0800 Subject: [PATCH 1/9] 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 <[email protected]> Date: Thu, 20 Nov 2025 19:05:04 -0800 Subject: [PATCH 2/9] 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 <[email protected]> Date: Sun, 23 Nov 2025 01:33:00 -0800 Subject: [PATCH 3/9] 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 <[email protected]> Date: Sun, 23 Nov 2025 03:50:45 -0800 Subject: [PATCH 4/9] 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 <[email protected]> Date: Sun, 23 Nov 2025 04:53:49 -0800 Subject: [PATCH 5/9] 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 <[email protected]> Date: Sun, 7 Dec 2025 23:13:43 -0800 Subject: [PATCH 6/9] Take every except switch > Co-authored-by: Julius Alexandre [email protected] --- 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 <[email protected]> Date: Sun, 7 Dec 2025 23:55:53 -0800 Subject: [PATCH 7/9] 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 <[email protected]> Date: Fri, 5 Dec 2025 23:16:07 -0500 Subject: [PATCH 8/9] [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 <[email protected]> Date: Mon, 8 Dec 2025 05:00:55 -0800 Subject: [PATCH 9/9] 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
