https://github.com/JustinStitt updated 
https://github.com/llvm/llvm-project/pull/174892

>From 6e61f3b8d09284d2f03389827e65b4da2c79e1f3 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Tue, 6 Jan 2026 15:36:33 -0800
Subject: [PATCH 1/6] [Clang] Add -fdiagnostics-show-inlining-chain for
 warning/error attributes

When functions marked with __attribute__((warning/error)) are called
through inlined functions, Clang now optionally shows the inlining chain
that led to the call.

Three modes are supported:

- ``none`` is the default and results in no inline notes being shown
  (matches the behavior before this change).

- ``heuristic`` tries to guess which functions will be inlined to
  minimize the amount of source locations kept in memory and visible in
  LLVM IR.

- ``debug`` leverages minimal debug directive tracking infrastructure to
  get more reliable source locations over the heuristic mode while
  having more compile-time overhead.

Fixes: https://github.com/ClangBuiltLinux/linux/issues/1571
Based-on: https://github.com/llvm/llvm-project/pull/73552
Signed-off-by: Justin Stitt <[email protected]>
---
 clang/docs/ReleaseNotes.rst                   |   6 +
 clang/include/clang/Basic/AttrDocs.td         |   7 ++
 clang/include/clang/Basic/CodeGenOptions.def  |   2 +
 clang/include/clang/Basic/CodeGenOptions.h    |   8 ++
 .../clang/Basic/DiagnosticFrontendKinds.td    |   6 +
 clang/include/clang/Options/Options.td        |  15 +++
 clang/lib/CodeGen/CGCall.cpp                  |  23 ++--
 clang/lib/CodeGen/CodeGenAction.cpp           |  31 +++++
 clang/lib/Driver/ToolChains/Clang.cpp         |   7 ++
 clang/lib/Frontend/CompilerInvocation.cpp     |   8 ++
 .../backend-attribute-inlining-cross-tu.c     |  64 ++++++++++
 ...nd-attribute-inlining-debug-vs-heuristic.c |  28 +++++
 .../backend-attribute-inlining-modes.c        |  47 ++++++++
 .../Frontend/backend-attribute-inlining.c     | 109 ++++++++++++++++++
 llvm/include/llvm/IR/DiagnosticInfo.h         |  26 ++++-
 llvm/lib/IR/DiagnosticInfo.cpp                |  38 +++++-
 llvm/lib/Transforms/Utils/InlineFunction.cpp  |  44 +++++++
 17 files changed, 459 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/Frontend/backend-attribute-inlining-cross-tu.c
 create mode 100644 
clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
 create mode 100644 clang/test/Frontend/backend-attribute-inlining-modes.c
 create mode 100644 clang/test/Frontend/backend-attribute-inlining.c

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index c56d2015f7a3f..e90adfb50dd27 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -363,6 +363,12 @@ New Compiler Flags
 - New options for enabling allocation token instrumentation: 
``-fsanitize=alloc-token``, ``-falloc-token-max=``, 
``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``.
 - The ``-resource-dir`` option is now displayed in the list of options shown 
by ``--help``.
 - New option ``-fmatrix-memory-layout`` added to control the memory layout of 
Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or 
``-fmatrix-memory-layout=row-major``).
+- New option ``-fdiagnostics-show-inlining-chain=`` added to show inlining 
chain
+  notes for ``__attribute__((warning))`` and ``__attribute__((error))``
+  diagnostics. When a function with these attributes is called from an inlined
+  context, Clang can now show which functions were inlined to reach the call.
+  Modes: ``none`` (default), ``heuristic`` (uses metadata tracking), ``debug``
+  (uses debug info for accurate source locations).
 
 Lanai Support
 ^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 812b48058d189..97331b1175cc9 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8397,6 +8397,13 @@ pointing to precise locations of the call site in the 
source.
       dontcall(); // No Warning
     sizeof(dontcall()); // No Warning
   }
+
+When the call occurs through inlined functions, the
+``-fdiagnostics-show-inlining-chain=`` option can be used to show the
+inlining chain that led to the call. This helps identify which call site
+triggered the diagnostic when the attributed function is called from
+multiple locations through inline functions. See ``clang --help`` for
+available modes.
   }];
 }
 
diff --git a/clang/include/clang/Basic/CodeGenOptions.def 
b/clang/include/clang/Basic/CodeGenOptions.def
index baf8b093c10e6..59f12a821fa53 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -76,6 +76,8 @@ CODEGENOPT(CallGraphSection, 1, 0, Benign) ///< Emit a call 
graph section into t
                                            ///< object file.
 CODEGENOPT(EmitCallSiteInfo, 1, 0, Benign) ///< Emit call site info only in 
the case of
                                            ///< '-g' + 'O>0' level.
+/// Tracking inlining chain in __attribute__((warning)) and 
__attribute__((error)) diagnostics
+ENUM_CODEGENOPT(InliningChain, InliningChainKind, 2, Inlining_None, Benign)
 CODEGENOPT(IndirectTlsSegRefs, 1, 0, Benign) ///< Set when 
-mno-tls-direct-seg-refs
                                              ///< is specified.
 CODEGENOPT(DisableTailCalls  , 1, 0, Benign) ///< Do not emit tail calls.
diff --git a/clang/include/clang/Basic/CodeGenOptions.h 
b/clang/include/clang/Basic/CodeGenOptions.h
index c60ca507ff917..f34de98126768 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -120,6 +120,14 @@ class CodeGenOptions : public CodeGenOptionsBase {
     Embed_Marker    // Embed a marker as a placeholder for bitcode.
   };
 
+  /// Inlining chain tracking __attribute__((warning)) or 
__attribute__((error))
+  /// diagnostics
+  enum InliningChainKind {
+    Inlining_Heuristic, /// Track via srcloc metadata on inline/static calls.
+    Inlining_Debug,     /// Track via debug info (DILocation inlinedAt chain).
+    Inlining_None       /// No tracking, no inlining notes shown (default).
+  };
+
   enum class ExtendVariableLivenessKind {
     None,
     This,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index e2b257ceae80d..3a7b7ccc73ab8 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -94,6 +94,12 @@ def err_fe_backend_error_attr :
 def warn_fe_backend_warning_attr :
   Warning<"call to '%0' declared with 'warning' attribute: %1">, BackendInfo,
   InGroup<BackendWarningAttributes>;
+def note_fe_backend_in : Note<"called by function '%0'">, BackendInfo;
+def note_fe_backend_inlined : Note<"inlined by function '%0'">, BackendInfo;
+def warn_fe_inlining_debug_requires_g
+    : Warning<"'-fdiagnostics-show-inlining-chain=debug' requires at least "
+              "'-gline-directives-only'; falling back to 'heuristic' mode">,
+      InGroup<BackendWarningAttributes>;
 def warn_toc_unsupported_type : Warning<"-mtocdata option is ignored "
   "for %0 because %1">, InGroup<BackendWarningAttributes>;
 
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index d48ca15864060..6ec594cfab210 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2158,6 +2158,21 @@ defm diagnostics_show_note_include_stack : 
BoolFOption<"diagnostics-show-note-in
     DiagnosticOpts<"ShowNoteIncludeStack">, DefaultFalse,
     PosFlag<SetTrue, [], [ClangOption], "Display include stacks for diagnostic 
notes">,
     NegFlag<SetFalse>, BothFlags<[], [ClangOption, CC1Option]>>;
+def fdiagnostics_show_inlining_chain_EQ
+    : Joined<["-"], "fdiagnostics-show-inlining-chain=">,
+      Group<f_Group>,
+      Visibility<[ClangOption, CC1Option]>,
+      HelpText<"Show inlining chain notes for __attribute__((warning/error)) "
+               "diagnostics: "
+               "'none' (default) shows no chain, 'heuristic' uses best-effort "
+               "metadata tracking, "
+               "'debug' uses debug info for accurate locations with increased "
+               "compile-time overhead">,
+      Values<"heuristic,debug,none">,
+      NormalizedValuesScope<"CodeGenOptions">,
+      NormalizedValues<["Inlining_Heuristic", "Inlining_Debug",
+                        "Inlining_None"]>,
+      MarshallingInfoEnum<CodeGenOpts<"InliningChain">, "Inlining_None">;
 def fdiagnostics_format_EQ : Joined<["-"], "fdiagnostics-format=">, 
Group<f_clang_Group>;
 def fdiagnostics_show_category_EQ : Joined<["-"], 
"fdiagnostics-show-category=">, Group<f_clang_Group>;
 def fdiagnostics_show_template_tree : Flag<["-"], 
"fdiagnostics-show-template-tree">,
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index d7bdeb3981cf8..2bc23d9737f5d 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6048,13 +6048,22 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo 
&CallInfo,
   if (getDebugInfo() && TargetDecl && TargetDecl->hasAttr<MSAllocatorAttr>())
     getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
 
-  // Add metadata if calling an __attribute__((error(""))) or warning fn.
-  if (TargetDecl && TargetDecl->hasAttr<ErrorAttr>()) {
-    llvm::ConstantInt *Line =
-        llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
-    llvm::ConstantAsMetadata *MD = llvm::ConstantAsMetadata::get(Line);
-    llvm::MDTuple *MDT = llvm::MDNode::get(getLLVMContext(), {MD});
-    CI->setMetadata("srcloc", MDT);
+  // Add srcloc metadata for __attribute__((error/warning)) diagnostics.
+  // In heuristic mode, also track inline/static calls for inlining chain.
+  if (TargetDecl) {
+    bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
+    if (!NeedSrcLoc && CGM.getCodeGenOpts().getInliningChain() ==
+                           CodeGenOptions::Inlining_Heuristic) {
+      if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
+        NeedSrcLoc = FD->isInlined() || FD->isInlineSpecified() ||
+                     FD->hasAttr<AlwaysInlineAttr>() ||
+                     FD->getStorageClass() == SC_Static;
+    }
+    if (NeedSrcLoc) {
+      auto *Line = llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
+      auto *MD = llvm::ConstantAsMetadata::get(Line);
+      CI->setMetadata("srcloc", llvm::MDNode::get(getLLVMContext(), {MD}));
+    }
   }
 
   // 4. Finish the call.
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp 
b/clang/lib/CodeGen/CodeGenAction.cpp
index a5ef4ac9d361d..e29db962336b4 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -732,6 +732,37 @@ void BackendConsumer::DontCallDiagHandler(const 
DiagnosticInfoDontCall &D) {
                               ? diag::err_fe_backend_error_attr
                               : diag::warn_fe_backend_warning_attr)
       << llvm::demangle(D.getFunctionName()) << D.getNote();
+
+  if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_None)
+    return;
+
+  auto EmitNote = [&](SourceLocation Loc, StringRef FuncName, bool IsFirst) {
+    if (!Loc.isValid())
+      Loc = LocCookie;
+    unsigned DiagID =
+        IsFirst ? diag::note_fe_backend_in : diag::note_fe_backend_inlined;
+    Diags.Report(Loc, DiagID) << llvm::demangle(FuncName.str());
+  };
+
+  if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_Debug) {
+    SourceManager &SM = Context->getSourceManager();
+    FileManager &FM = SM.getFileManager();
+    for (const auto &[I, Info] : llvm::enumerate(D.getDebugInlineChain())) {
+      SourceLocation Loc;
+      if (Info.Line > 0)
+        if (auto FE = FM.getOptionalFileRef(Info.Filename))
+          Loc = SM.translateFileLineCol(*FE, Info.Line,
+                                        Info.Column ? Info.Column : 1);
+      EmitNote(Loc, Info.FuncName, I == 0);
+    }
+    return;
+  }
+
+  for (const auto &[I, Entry] : llvm::enumerate(D.getInliningDecisions())) {
+    SourceLocation Loc =
+        I == 0 ? LocCookie : SourceLocation::getFromRawEncoding(Entry.second);
+    EmitNote(Loc, Entry.first, I == 0);
+  }
 }
 
 void BackendConsumer::MisExpectDiagHandler(
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 4ca98600d6e93..10db88a499425 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4281,6 +4281,13 @@ static void RenderDiagnosticsOptions(const Driver &D, 
const ArgList &Args,
     CmdArgs.push_back(Args.MakeArgString(Opt));
   }
 
+  if (const Arg *A =
+          Args.getLastArg(options::OPT_fdiagnostics_show_inlining_chain_EQ)) {
+    std::string Opt =
+        std::string("-fdiagnostics-show-inlining-chain=") + A->getValue();
+    CmdArgs.push_back(Args.MakeArgString(Opt));
+  }
+
   if (const Arg *A = Args.getLastArg(options::OPT_fdiagnostics_format_EQ)) {
     CmdArgs.push_back("-fdiagnostics-format");
     CmdArgs.push_back(A->getValue());
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp 
b/clang/lib/Frontend/CompilerInvocation.cpp
index 477406f2526c0..91f5aa160dd47 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2253,6 +2253,14 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions 
&Opts, ArgList &Args,
       Opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo)
     Opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly);
 
+  // Debug mode for inlining chain diagnostics requires at least
+  // -gline-directives-only to track inlining locations via DILocation.
+  if (Opts.getInliningChain() == CodeGenOptions::Inlining_Debug &&
+      Opts.getDebugInfo() < llvm::codegenoptions::DebugDirectivesOnly) {
+    Diags.Report(diag::warn_fe_inlining_debug_requires_g);
+    Opts.setInliningChain(CodeGenOptions::Inlining_Heuristic);
+  }
+
   // Parse -fsanitize-recover= arguments.
   // FIXME: Report unrecoverable sanitizers incorrectly specified here.
   parseSanitizerKinds("-fsanitize-recover=",
diff --git a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c 
b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
new file mode 100644
index 0000000000000..6c1cee3e9e212
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -0,0 +1,64 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S 
%t/main.c -I%t -o /dev/null 2>&1 | FileCheck %s
+
+// Cross-TU inlining: header functions inlined into source file.
+
+//--- overflow.h
+__attribute__((warning("write overflow")))
+void __write_overflow(void);
+
+__attribute__((error("read overflow")))
+void __read_overflow(void);
+
+static inline void check_write(int size) {
+  if (size > 100)
+    __write_overflow();
+}
+
+static inline void check_read(int size) {
+  if (size > 50)
+    __read_overflow();
+}
+
+static inline void check_both(int size) {
+  check_write(size);
+  check_read(size);
+}
+
+//--- main.c
+#include "overflow.h"
+
+void test_simple_cross_tu(void) {
+  check_write(200);
+}
+// CHECK: warning: call to '__write_overflow' declared with 'warning' 
attribute: write overflow
+// CHECK: note: called by function 'check_write'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_simple_cross_tu'
+
+// Nested cross-TU inlining (header -> header -> source).
+static inline void local_wrapper(int x) {
+  check_both(x);
+}
+
+void test_nested_cross_tu(void) {
+  local_wrapper(200);
+}
+// CHECK: warning: call to '__write_overflow' declared with 'warning' 
attribute: write overflow
+// CHECK: note: called by function 'check_write'
+// CHECK: overflow.h:{{.*}}: note: inlined by function 'check_both'
+// CHECK: main.c:{{.*}}: note: inlined by function 'local_wrapper'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_nested_cross_tu'
+
+// CHECK: error: call to '__read_overflow' declared with 'error' attribute: 
read overflow
+// CHECK: note: called by function 'check_read'
+// CHECK: overflow.h:{{.*}}: note: inlined by function 'check_both'
+// CHECK: main.c:{{.*}}: note: inlined by function 'local_wrapper'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_nested_cross_tu'
+
+void test_error_cross_tu(void) {
+  check_read(100);
+}
+// CHECK: error: call to '__read_overflow' declared with 'error' attribute: 
read overflow
+// CHECK: note: called by function 'check_read'
+// CHECK: main.c:{{.*}}: note: inlined by function 'test_error_cross_tu'
diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
new file mode 100644
index 0000000000000..284b23d5cace2
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
@@ -0,0 +1,28 @@
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic 
%s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
+
+__attribute__((warning("dangerous function")))
+void dangerous(void);
+
+// Non-static, non-inline functions that get inlined at -O2.
+void wrapper(void) {
+    dangerous();
+}
+
+void middle(void) {
+    wrapper();
+}
+
+void caller(void) {
+    middle();
+}
+
+// HEURISTIC: :9:{{.*}}: warning: call to 'dangerous'
+// HEURISTIC: :9:{{.*}}: note: called by function 'wrapper'
+// HEURISTIC: :9:{{.*}}: note: inlined by function 'middle'
+// HEURISTIC: :9:{{.*}}: note: inlined by function 'caller'
+
+// DEBUG: :9:{{.*}}: warning: call to 'dangerous'
+// DEBUG: :9:{{.*}}: note: called by function 'wrapper'
+// DEBUG: :13:{{.*}}: note: inlined by function 'middle'
+// DEBUG: :17:{{.*}}: note: inlined by function 'caller'
diff --git a/clang/test/Frontend/backend-attribute-inlining-modes.c 
b/clang/test/Frontend/backend-attribute-inlining-modes.c
new file mode 100644
index 0000000000000..ebee1208a17dd
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -0,0 +1,47 @@
+// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=NONE-DEFAULT
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=none %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=NONE-EXPLICIT
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic 
%s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=FALLBACK
+
+// Tests all three modes plus fallback behavior when debug info
+// (-gline-directives-only) is missing.
+
+__attribute__((warning("do not call")))
+void bad_func(void);
+
+static inline void level1(void) {
+    bad_func();
+}
+
+static inline void level2(void) {
+    level1();
+}
+
+void entry(void) {
+    level2();
+}
+
+// none (default): warning only, no inlining notes.
+// NONE-DEFAULT: warning: call to 'bad_func'
+// NONE-DEFAULT-NOT: note:
+
+// none (explicit): same as default.
+// NONE-EXPLICIT: warning: call to 'bad_func'
+// NONE-EXPLICIT-NOT: note:
+
+// HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
+// HEURISTIC: :14:{{.*}}: note: called by function 'level1'
+// HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
+// HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
+
+// DEBUG: :14:{{.*}}: warning: call to 'bad_func'
+// DEBUG: :14:{{.*}}: note: called by function 'level1'
+// DEBUG: :18:{{.*}}: note: inlined by function 'level2'
+// DEBUG: :22:{{.*}}: note: inlined by function 'entry'
+
+// FALLBACK: warning: '-fdiagnostics-show-inlining-chain=debug' requires at 
least '-gline-directives-only'; falling back to 'heuristic' mode
+// FALLBACK: :14:{{.*}}: warning: call to 'bad_func'
+// FALLBACK: :14:{{.*}}: note: called by function 'level1'
+// FALLBACK: :18:{{.*}}: note: inlined by function 'level2'
+// FALLBACK: :22:{{.*}}: note: inlined by function 'entry'
diff --git a/clang/test/Frontend/backend-attribute-inlining.c 
b/clang/test/Frontend/backend-attribute-inlining.c
new file mode 100644
index 0000000000000..5f91a38ebdc57
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -0,0 +1,109 @@
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %s -o 
/dev/null 2>&1 | FileCheck %s
+
+// Single-level inlining with warning attribute.
+__attribute__((warning("do not call directly")))
+void __warn_single(void);
+
+static inline void warn_wrapper(void) {
+  __warn_single();
+}
+
+void test_single_level(void) {
+  warn_wrapper();
+}
+// CHECK: warning: call to '__warn_single' declared with 'warning' attribute: 
do not call directly
+// CHECK: note: called by function 'warn_wrapper'
+// CHECK: :12:{{.*}}: note: inlined by function 'test_single_level'
+
+// Error attribute with inlining.
+__attribute__((error("never call this")))
+void __error_func(void);
+
+static inline void error_wrapper(void) {
+  __error_func();
+}
+
+void test_error_inlined(void) {
+  error_wrapper();
+}
+// CHECK: error: call to '__error_func' declared with 'error' attribute: never 
call this
+// CHECK: note: called by function 'error_wrapper'
+// CHECK: :27:{{.*}}: note: inlined by function 'test_error_inlined'
+
+// Deep nesting (5 levels).
+__attribute__((warning("deep call")))
+void __warn_deep(void);
+
+static inline void deep1(void) { __warn_deep(); }
+static inline void deep2(void) { deep1(); }
+static inline void deep3(void) { deep2(); }
+static inline void deep4(void) { deep3(); }
+static inline void deep5(void) { deep4(); }
+
+void test_deep_nesting(void) {
+  deep5();
+}
+// CHECK: warning: call to '__warn_deep' declared with 'warning' attribute: 
deep call
+// CHECK: note: called by function 'deep1'
+// CHECK: :38:{{.*}}: note: inlined by function 'deep2'
+// CHECK: :39:{{.*}}: note: inlined by function 'deep3'
+// CHECK: :40:{{.*}}: note: inlined by function 'deep4'
+// CHECK: :41:{{.*}}: note: inlined by function 'deep5'
+// CHECK: :44:{{.*}}: note: inlined by function 'test_deep_nesting'
+
+// Multiple call sites produce distinct diagnostics.
+__attribute__((warning("deprecated")))
+void __warn_multi(void);
+
+static inline void multi_wrapper(void) {
+  __warn_multi();
+}
+
+void call_site_a(void) { multi_wrapper(); }
+void call_site_b(void) { multi_wrapper(); }
+void call_site_c(void) { multi_wrapper(); }
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: 
deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :62:{{.*}}: note: inlined by function 'call_site_a'
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: 
deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :63:{{.*}}: note: inlined by function 'call_site_b'
+
+// CHECK: warning: call to '__warn_multi' declared with 'warning' attribute: 
deprecated
+// CHECK: note: called by function 'multi_wrapper'
+// CHECK: :64:{{.*}}: note: inlined by function 'call_site_c'
+
+// Different nesting depths from same inner function.
+__attribute__((warning("mixed depth")))
+void __warn_mixed(void);
+
+static inline void mixed_inner(void) { __warn_mixed(); }
+static inline void mixed_middle(void) { mixed_inner(); }
+
+void shallow(void) { mixed_inner(); }
+void deep(void) { mixed_middle(); }
+
+// CHECK: warning: call to '__warn_mixed' declared with 'warning' attribute: 
mixed depth
+// CHECK: note: called by function 'mixed_inner'
+// CHECK: :85:{{.*}}: note: inlined by function 'shallow'
+
+// CHECK: warning: call to '__warn_mixed' declared with 'warning' attribute: 
mixed depth
+// CHECK: note: called by function 'mixed_inner'
+// CHECK: :83:{{.*}}: note: inlined by function 'mixed_middle'
+// CHECK: :86:{{.*}}: note: inlined by function 'deep'
+
+// Incidental inlining (function not marked inline/static).
+// The "inlined by" note has no location since heuristic mode doesn't track it.
+__attribute__((warning("incidental")))
+void __warn_incidental(void);
+
+void not_marked_inline(void) { __warn_incidental(); }
+
+void test_incidental(void) { not_marked_inline(); }
+
+// CHECK: warning: call to '__warn_incidental' declared with 'warning' 
attribute: incidental
+// CHECK: note: called by function 'not_marked_inline'
+// CHECK: note: inlined by function 'test_incidental'
+// CHECK-NOT: :{{.*}}: note: inlined by function 'test_incidental'
diff --git a/llvm/include/llvm/IR/DiagnosticInfo.h 
b/llvm/include/llvm/IR/DiagnosticInfo.h
index 8f6fb4da0c839..0daaa462ba715 100644
--- a/llvm/include/llvm/IR/DiagnosticInfo.h
+++ b/llvm/include/llvm/IR/DiagnosticInfo.h
@@ -1192,19 +1192,41 @@ class LLVM_ABI DiagnosticInfoSrcMgr : public 
DiagnosticInfo {
 
 LLVM_ABI void diagnoseDontCall(const CallInst &CI);
 
+/// Inlining location extracted from debug info.
+struct DebugInlineInfo {
+  StringRef FuncName;
+  StringRef Filename;
+  unsigned Line;
+  unsigned Column;
+};
+
 class LLVM_ABI DiagnosticInfoDontCall : public DiagnosticInfo {
   StringRef CalleeName;
   StringRef Note;
   uint64_t LocCookie;
+  MDNode *InlinedFromMD = nullptr;
+  SmallVector<DebugInlineInfo, 4> DebugInlineChain;
 
 public:
   DiagnosticInfoDontCall(StringRef CalleeName, StringRef Note,
-                         DiagnosticSeverity DS, uint64_t LocCookie)
+                         DiagnosticSeverity DS, uint64_t LocCookie,
+                         MDNode *InlinedFromMD = nullptr)
       : DiagnosticInfo(DK_DontCall, DS), CalleeName(CalleeName), Note(Note),
-        LocCookie(LocCookie) {}
+        LocCookie(LocCookie), InlinedFromMD(InlinedFromMD) {}
+
   StringRef getFunctionName() const { return CalleeName; }
   StringRef getNote() const { return Note; }
   uint64_t getLocCookie() const { return LocCookie; }
+  MDNode *getInlinedFromMD() const { return InlinedFromMD; }
+  SmallVector<std::pair<StringRef, uint64_t>> getInliningDecisions() const;
+
+  void setDebugInlineChain(SmallVector<DebugInlineInfo, 4> &&Chain) {
+    DebugInlineChain = std::move(Chain);
+  }
+  ArrayRef<DebugInlineInfo> getDebugInlineChain() const {
+    return DebugInlineChain;
+  }
+
   void print(DiagnosticPrinter &DP) const override;
   static bool classof(const DiagnosticInfo *DI) {
     return DI->getKind() == DK_DontCall;
diff --git a/llvm/lib/IR/DiagnosticInfo.cpp b/llvm/lib/IR/DiagnosticInfo.cpp
index 8e6d654f6afb3..10ee9f27058f8 100644
--- a/llvm/lib/IR/DiagnosticInfo.cpp
+++ b/llvm/lib/IR/DiagnosticInfo.cpp
@@ -487,8 +487,27 @@ void llvm::diagnoseDontCall(const CallInst &CI) {
       if (MDNode *MD = CI.getMetadata("srcloc"))
         LocCookie =
             mdconst::extract<ConstantInt>(MD->getOperand(0))->getZExtValue();
+      MDNode *InlinedFromMD = CI.getMetadata("inlined.from");
       DiagnosticInfoDontCall D(F->getName(), A.getValueAsString(), Sev,
-                               LocCookie);
+                               LocCookie, InlinedFromMD);
+
+      if (const DebugLoc &DL = CI.getDebugLoc()) {
+        SmallVector<DebugInlineInfo, 4> DebugChain;
+        auto AddLocation = [&](const DILocation *Loc) {
+          if (auto *Scope = Loc->getScope())
+            if (auto *SP = Scope->getSubprogram())
+              DebugChain.push_back({SP->getName(), Loc->getFilename(),
+                                    Loc->getLine(), Loc->getColumn()});
+        };
+        if (const DILocation *Loc = DL.get()) {
+          AddLocation(Loc);
+          for (const DILocation *InlinedAt = Loc->getInlinedAt(); InlinedAt;
+               InlinedAt = InlinedAt->getInlinedAt())
+            AddLocation(InlinedAt);
+        }
+        D.setDebugInlineChain(std::move(DebugChain));
+      }
+
       F->getContext().diagnose(D);
     }
   }
@@ -503,3 +522,20 @@ void DiagnosticInfoDontCall::print(DiagnosticPrinter &DP) 
const {
   if (!getNote().empty())
     DP << ": " << getNote();
 }
+
+SmallVector<std::pair<StringRef, uint64_t>>
+DiagnosticInfoDontCall::getInliningDecisions() const {
+  SmallVector<std::pair<StringRef, uint64_t>> Chain;
+  if (!InlinedFromMD)
+    return Chain;
+
+  for (unsigned I = 0, E = InlinedFromMD->getNumOperands(); I + 1 < E; I += 2) 
{
+    auto *NameMD = dyn_cast<MDString>(InlinedFromMD->getOperand(I));
+    auto *LocMD =
+        mdconst::dyn_extract<ConstantInt>(InlinedFromMD->getOperand(I + 1));
+    if (NameMD && !NameMD->getString().empty())
+      Chain.emplace_back(NameMD->getString(),
+                         LocMD ? LocMD->getZExtValue() : 0);
+  }
+  return Chain;
+}
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp 
b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index aa902f687d8aa..2573c0b2d63a1 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -974,6 +974,46 @@ static void PropagateCallSiteMetadata(CallBase &CB, 
Function::iterator FStart,
   }
 }
 
+/// Track inlining chain via inlined.from metadata for dontcall diagnostics.
+static void PropagateInlinedFromMetadata(CallBase &CB, StringRef 
CalledFuncName,
+                                         StringRef CallerFuncName,
+                                         Function::iterator FStart,
+                                         Function::iterator FEnd) {
+  LLVMContext &Ctx = CB.getContext();
+  uint64_t InlineSiteLoc = 0;
+  if (auto *MD = CB.getMetadata("srcloc"))
+    if (auto *CI = mdconst::dyn_extract<ConstantInt>(MD->getOperand(0)))
+      InlineSiteLoc = CI->getZExtValue();
+
+  auto *I64Ty = Type::getInt64Ty(Ctx);
+  auto MakeMDInt = [&](uint64_t V) {
+    return ConstantAsMetadata::get(ConstantInt::get(I64Ty, V));
+  };
+
+  for (BasicBlock &BB : make_range(FStart, FEnd)) {
+    for (Instruction &I : BB) {
+      auto *CI = dyn_cast<CallInst>(&I);
+      if (!CI || !CI->getMetadata("srcloc"))
+        continue;
+      auto *Callee = CI->getCalledFunction();
+      if (!Callee || (!Callee->hasFnAttribute("dontcall-error") &&
+                      !Callee->hasFnAttribute("dontcall-warn")))
+        continue;
+
+      SmallVector<Metadata *, 8> Ops;
+      if (MDNode *Existing = CI->getMetadata("inlined.from"))
+        append_range(Ops, Existing->operands());
+      else {
+        Ops.push_back(MDString::get(Ctx, CalledFuncName));
+        Ops.push_back(MakeMDInt(0));
+      }
+      Ops.push_back(MDString::get(Ctx, CallerFuncName));
+      Ops.push_back(MakeMDInt(InlineSiteLoc));
+      CI->setMetadata("inlined.from", MDNode::get(Ctx, Ops));
+    }
+  }
+}
+
 /// Bundle operands of the inlined function must be added to inlined call 
sites.
 static void PropagateOperandBundles(Function::iterator InlinedBB,
                                     Instruction *CallSiteEHPad) {
@@ -2839,6 +2879,10 @@ void llvm::InlineFunctionImpl(CallBase &CB, 
InlineFunctionInfo &IFI,
       }
     }
 
+    // Propagate inlined.from metadata for dontcall diagnostics.
+    PropagateInlinedFromMetadata(CB, CalledFunc->getName(), Caller->getName(),
+                                 FirstNewBlock, Caller->end());
+
     // Register any cloned assumptions.
     if (IFI.GetAssumptionCache)
       for (BasicBlock &NewBlock :

>From 1954938b8b75876735753c1c6189daa28f3df638 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Thu, 8 Jan 2026 10:27:15 -0800
Subject: [PATCH 2/6] use scoped enum, binary flag, better docs

based on review:

1) use scoped enum
2) switch over to a binary flag instead of a ternary one.
3) improve docs and help text
4) add note regarding -gline-directives-only
5) use modern attribute spellings

Signed-off-by: Justin Stitt <[email protected]>
---
 clang/docs/ReleaseNotes.rst                   | 13 +++--
 clang/include/clang/Basic/AttrDocs.td         | 11 +++-
 clang/include/clang/Basic/CodeGenOptions.def  |  4 +-
 clang/include/clang/Basic/CodeGenOptions.h    |  8 ---
 .../clang/Basic/DiagnosticFrontendKinds.td    |  8 +--
 clang/include/clang/Options/Options.td        | 22 +++----
 clang/lib/CodeGen/CGCall.cpp                  | 11 ++--
 clang/lib/CodeGen/CodeGenAction.cpp           | 16 +++++-
 clang/lib/Driver/ToolChains/Clang.cpp         | 10 ++--
 clang/lib/Frontend/CompilerInvocation.cpp     |  8 ---
 .../backend-attribute-inlining-cross-tu.c     |  9 ++-
 ...nd-attribute-inlining-debug-vs-heuristic.c | 31 ++++++----
 .../backend-attribute-inlining-modes.c        | 57 +++++++++----------
 .../Frontend/backend-attribute-inlining.c     | 17 +++---
 14 files changed, 115 insertions(+), 110 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index e90adfb50dd27..350d742d99be0 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -363,12 +363,13 @@ New Compiler Flags
 - New options for enabling allocation token instrumentation: 
``-fsanitize=alloc-token``, ``-falloc-token-max=``, 
``-fsanitize-alloc-token-fast-abi``, ``-fsanitize-alloc-token-extended``.
 - The ``-resource-dir`` option is now displayed in the list of options shown 
by ``--help``.
 - New option ``-fmatrix-memory-layout`` added to control the memory layout of 
Clang matrix types. (e.g. ``-fmatrix-memory-layout=column-major`` or 
``-fmatrix-memory-layout=row-major``).
-- New option ``-fdiagnostics-show-inlining-chain=`` added to show inlining 
chain
-  notes for ``__attribute__((warning))`` and ``__attribute__((error))``
-  diagnostics. When a function with these attributes is called from an inlined
-  context, Clang can now show which functions were inlined to reach the call.
-  Modes: ``none`` (default), ``heuristic`` (uses metadata tracking), ``debug``
-  (uses debug info for accurate source locations).
+- New option ``-fdiagnostics-show-inlining-chain`` added to show inlining chain
+  notes for ``[[gnu::warning]]`` and ``[[gnu::error]]`` diagnostics. When a
+  function with these attributes is called from an inlined context, Clang can
+  now show which functions were inlined to reach the call. When debug info is
+  available (``-gline-directives-only`` or higher), accurate source locations
+  are used; otherwise, a heuristic fallback is used with a note suggesting
+  how to enable debug info for better accuracy.
 
 Lanai Support
 ^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 97331b1175cc9..f9c6d6ccd606f 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8399,11 +8399,16 @@ pointing to precise locations of the call site in the 
source.
   }
 
 When the call occurs through inlined functions, the
-``-fdiagnostics-show-inlining-chain=`` option can be used to show the
+``-fdiagnostics-show-inlining-chain`` option can be used to show the
 inlining chain that led to the call. This helps identify which call site
 triggered the diagnostic when the attributed function is called from
-multiple locations through inline functions. See ``clang --help`` for
-available modes.
+multiple locations through inline functions.
+
+When enabled, this option automatically uses debug info for accurate source
+locations if available (requires at least ``-gline-directives-only``), or
+falls back to a heuristic based on metadata tracking. When falling back, a
+note is emitted suggesting ``-gline-directives-only`` for more accurate
+locations.
   }];
 }
 
diff --git a/clang/include/clang/Basic/CodeGenOptions.def 
b/clang/include/clang/Basic/CodeGenOptions.def
index 59f12a821fa53..cd0675c6e35a0 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -76,8 +76,8 @@ CODEGENOPT(CallGraphSection, 1, 0, Benign) ///< Emit a call 
graph section into t
                                            ///< object file.
 CODEGENOPT(EmitCallSiteInfo, 1, 0, Benign) ///< Emit call site info only in 
the case of
                                            ///< '-g' + 'O>0' level.
-/// Tracking inlining chain in __attribute__((warning)) and 
__attribute__((error)) diagnostics
-ENUM_CODEGENOPT(InliningChain, InliningChainKind, 2, Inlining_None, Benign)
+/// Show inlining chain notes for [[gnu::warning/error]] diagnostics.
+CODEGENOPT(ShowInliningChain, 1, 0, Benign)
 CODEGENOPT(IndirectTlsSegRefs, 1, 0, Benign) ///< Set when 
-mno-tls-direct-seg-refs
                                              ///< is specified.
 CODEGENOPT(DisableTailCalls  , 1, 0, Benign) ///< Do not emit tail calls.
diff --git a/clang/include/clang/Basic/CodeGenOptions.h 
b/clang/include/clang/Basic/CodeGenOptions.h
index f34de98126768..c60ca507ff917 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -120,14 +120,6 @@ class CodeGenOptions : public CodeGenOptionsBase {
     Embed_Marker    // Embed a marker as a placeholder for bitcode.
   };
 
-  /// Inlining chain tracking __attribute__((warning)) or 
__attribute__((error))
-  /// diagnostics
-  enum InliningChainKind {
-    Inlining_Heuristic, /// Track via srcloc metadata on inline/static calls.
-    Inlining_Debug,     /// Track via debug info (DILocation inlinedAt chain).
-    Inlining_None       /// No tracking, no inlining notes shown (default).
-  };
-
   enum class ExtendVariableLivenessKind {
     None,
     This,
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 3a7b7ccc73ab8..ff924b3ace3dd 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -96,10 +96,10 @@ def warn_fe_backend_warning_attr :
   InGroup<BackendWarningAttributes>;
 def note_fe_backend_in : Note<"called by function '%0'">, BackendInfo;
 def note_fe_backend_inlined : Note<"inlined by function '%0'">, BackendInfo;
-def warn_fe_inlining_debug_requires_g
-    : Warning<"'-fdiagnostics-show-inlining-chain=debug' requires at least "
-              "'-gline-directives-only'; falling back to 'heuristic' mode">,
-      InGroup<BackendWarningAttributes>;
+def note_fe_backend_inlining_debug_info
+    : Note<"use '-gline-directives-only' or higher for more accurate "
+           "inlining chain locations">,
+      BackendInfo;
 def warn_toc_unsupported_type : Warning<"-mtocdata option is ignored "
   "for %0 because %1">, InGroup<BackendWarningAttributes>;
 
diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index 6ec594cfab210..18b77bbc08715 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -2158,21 +2158,13 @@ defm diagnostics_show_note_include_stack : 
BoolFOption<"diagnostics-show-note-in
     DiagnosticOpts<"ShowNoteIncludeStack">, DefaultFalse,
     PosFlag<SetTrue, [], [ClangOption], "Display include stacks for diagnostic 
notes">,
     NegFlag<SetFalse>, BothFlags<[], [ClangOption, CC1Option]>>;
-def fdiagnostics_show_inlining_chain_EQ
-    : Joined<["-"], "fdiagnostics-show-inlining-chain=">,
-      Group<f_Group>,
-      Visibility<[ClangOption, CC1Option]>,
-      HelpText<"Show inlining chain notes for __attribute__((warning/error)) "
-               "diagnostics: "
-               "'none' (default) shows no chain, 'heuristic' uses best-effort "
-               "metadata tracking, "
-               "'debug' uses debug info for accurate locations with increased "
-               "compile-time overhead">,
-      Values<"heuristic,debug,none">,
-      NormalizedValuesScope<"CodeGenOptions">,
-      NormalizedValues<["Inlining_Heuristic", "Inlining_Debug",
-                        "Inlining_None"]>,
-      MarshallingInfoEnum<CodeGenOpts<"InliningChain">, "Inlining_None">;
+defm diagnostics_show_inlining_chain
+    : BoolFOption<"diagnostics-show-inlining-chain",
+                  CodeGenOpts<"ShowInliningChain">, DefaultFalse,
+                  PosFlag<SetTrue, [], [ClangOption, CC1Option],
+                          "Show inlining chain notes for "
+                          "[[gnu::warning/error]] diagnostics">,
+                  NegFlag<SetFalse, [], [ClangOption, CC1Option]>>;
 def fdiagnostics_format_EQ : Joined<["-"], "fdiagnostics-format=">, 
Group<f_clang_Group>;
 def fdiagnostics_show_category_EQ : Joined<["-"], 
"fdiagnostics-show-category=">, Group<f_clang_Group>;
 def fdiagnostics_show_template_tree : Flag<["-"], 
"fdiagnostics-show-template-tree">,
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 2bc23d9737f5d..76e504a5aa47b 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6048,16 +6048,17 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo 
&CallInfo,
   if (getDebugInfo() && TargetDecl && TargetDecl->hasAttr<MSAllocatorAttr>())
     getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
 
-  // Add srcloc metadata for __attribute__((error/warning)) diagnostics.
-  // In heuristic mode, also track inline/static calls for inlining chain.
+  // Add srcloc metadata for [[gnu::error/warning]] diagnostics.
+  // When ShowInliningChain is enabled, also track inline/static calls for the
+  // heuristic fallback when debug info is not available.
   if (TargetDecl) {
     bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
-    if (!NeedSrcLoc && CGM.getCodeGenOpts().getInliningChain() ==
-                           CodeGenOptions::Inlining_Heuristic) {
+    if (!NeedSrcLoc && CGM.getCodeGenOpts().ShowInliningChain) {
       if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
         NeedSrcLoc = FD->isInlined() || FD->isInlineSpecified() ||
                      FD->hasAttr<AlwaysInlineAttr>() ||
-                     FD->getStorageClass() == SC_Static;
+                     FD->getStorageClass() == SC_Static ||
+                     FD->isInAnonymousNamespace();
     }
     if (NeedSrcLoc) {
       auto *Line = llvm::ConstantInt::get(Int64Ty, Loc.getRawEncoding());
diff --git a/clang/lib/CodeGen/CodeGenAction.cpp 
b/clang/lib/CodeGen/CodeGenAction.cpp
index e29db962336b4..3a46acf0e17f0 100644
--- a/clang/lib/CodeGen/CodeGenAction.cpp
+++ b/clang/lib/CodeGen/CodeGenAction.cpp
@@ -733,7 +733,7 @@ void BackendConsumer::DontCallDiagHandler(const 
DiagnosticInfoDontCall &D) {
                               : diag::warn_fe_backend_warning_attr)
       << llvm::demangle(D.getFunctionName()) << D.getNote();
 
-  if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_None)
+  if (!CodeGenOpts.ShowInliningChain)
     return;
 
   auto EmitNote = [&](SourceLocation Loc, StringRef FuncName, bool IsFirst) {
@@ -744,7 +744,8 @@ void BackendConsumer::DontCallDiagHandler(const 
DiagnosticInfoDontCall &D) {
     Diags.Report(Loc, DiagID) << llvm::demangle(FuncName.str());
   };
 
-  if (CodeGenOpts.getInliningChain() == CodeGenOptions::Inlining_Debug) {
+  // Try debug info first for accurate source locations.
+  if (!D.getDebugInlineChain().empty()) {
     SourceManager &SM = Context->getSourceManager();
     FileManager &FM = SM.getFileManager();
     for (const auto &[I, Info] : llvm::enumerate(D.getDebugInlineChain())) {
@@ -758,11 +759,20 @@ void BackendConsumer::DontCallDiagHandler(const 
DiagnosticInfoDontCall &D) {
     return;
   }
 
-  for (const auto &[I, Entry] : llvm::enumerate(D.getInliningDecisions())) {
+  // Fall back to heuristic (srcloc metadata) when debug info is unavailable.
+  auto InliningDecisions = D.getInliningDecisions();
+  if (InliningDecisions.empty())
+    return;
+
+  for (const auto &[I, Entry] : llvm::enumerate(InliningDecisions)) {
     SourceLocation Loc =
         I == 0 ? LocCookie : SourceLocation::getFromRawEncoding(Entry.second);
     EmitNote(Loc, Entry.first, I == 0);
   }
+
+  // Suggest enabling debug info (at least -gline-directives-only) for more
+  // accurate locations.
+  Diags.Report(LocCookie, diag::note_fe_backend_inlining_debug_info);
 }
 
 void BackendConsumer::MisExpectDiagHandler(
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp 
b/clang/lib/Driver/ToolChains/Clang.cpp
index 10db88a499425..fc9cebbdbe21b 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -4282,10 +4282,12 @@ static void RenderDiagnosticsOptions(const Driver &D, 
const ArgList &Args,
   }
 
   if (const Arg *A =
-          Args.getLastArg(options::OPT_fdiagnostics_show_inlining_chain_EQ)) {
-    std::string Opt =
-        std::string("-fdiagnostics-show-inlining-chain=") + A->getValue();
-    CmdArgs.push_back(Args.MakeArgString(Opt));
+          Args.getLastArg(options::OPT_fdiagnostics_show_inlining_chain,
+                          options::OPT_fno_diagnostics_show_inlining_chain)) {
+    if (A->getOption().matches(options::OPT_fdiagnostics_show_inlining_chain))
+      CmdArgs.push_back("-fdiagnostics-show-inlining-chain");
+    else
+      CmdArgs.push_back("-fno-diagnostics-show-inlining-chain");
   }
 
   if (const Arg *A = Args.getLastArg(options::OPT_fdiagnostics_format_EQ)) {
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp 
b/clang/lib/Frontend/CompilerInvocation.cpp
index 91f5aa160dd47..477406f2526c0 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -2253,14 +2253,6 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions 
&Opts, ArgList &Args,
       Opts.getDebugInfo() == llvm::codegenoptions::NoDebugInfo)
     Opts.setDebugInfo(llvm::codegenoptions::LocTrackingOnly);
 
-  // Debug mode for inlining chain diagnostics requires at least
-  // -gline-directives-only to track inlining locations via DILocation.
-  if (Opts.getInliningChain() == CodeGenOptions::Inlining_Debug &&
-      Opts.getDebugInfo() < llvm::codegenoptions::DebugDirectivesOnly) {
-    Diags.Report(diag::warn_fe_inlining_debug_requires_g);
-    Opts.setInliningChain(CodeGenOptions::Inlining_Heuristic);
-  }
-
   // Parse -fsanitize-recover= arguments.
   // FIXME: Report unrecoverable sanitizers incorrectly specified here.
   parseSanitizerKinds("-fsanitize-recover=",
diff --git a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c 
b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
index 6c1cee3e9e212..cf259cc77f232 100644
--- a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -1,14 +1,14 @@
 // RUN: rm -rf %t
 // RUN: split-file %s %t
-// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S 
%t/main.c -I%t -o /dev/null 2>&1 | FileCheck %s
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain -S %t/main.c -I%t -o 
/dev/null 2>&1 | FileCheck %s
 
 // Cross-TU inlining: header functions inlined into source file.
 
 //--- overflow.h
-__attribute__((warning("write overflow")))
+[[gnu::warning("write overflow")]]
 void __write_overflow(void);
 
-__attribute__((error("read overflow")))
+[[gnu::error("read overflow")]]
 void __read_overflow(void);
 
 static inline void check_write(int size) {
@@ -62,3 +62,6 @@ void test_error_cross_tu(void) {
 // CHECK: error: call to '__read_overflow' declared with 'error' attribute: 
read overflow
 // CHECK: note: called by function 'check_read'
 // CHECK: main.c:{{.*}}: note: inlined by function 'test_error_cross_tu'
+
+// Fallback note should appear (no debug info).
+// CHECK: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations
diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
index 284b23d5cace2..4760b95f49c39 100644
--- a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
@@ -1,7 +1,14 @@
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic 
%s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
 
-__attribute__((warning("dangerous function")))
+// Verify auto-selection works between debug info and heuristic fallback. When
+// we have at least -gline-directives-only we can use DILocation for accurate
+// inline locations.
+
+// Without that debug info we fall back to a heuristic approach using srcloc
+// metadata.
+
+[[gnu::warning("dangerous function")]]
 void dangerous(void);
 
 // Non-static, non-inline functions that get inlined at -O2.
@@ -17,12 +24,14 @@ void caller(void) {
     middle();
 }
 
-// HEURISTIC: :9:{{.*}}: warning: call to 'dangerous'
-// HEURISTIC: :9:{{.*}}: note: called by function 'wrapper'
-// HEURISTIC: :9:{{.*}}: note: inlined by function 'middle'
-// HEURISTIC: :9:{{.*}}: note: inlined by function 'caller'
+// HEURISTIC: :16:{{.*}}: warning: call to 'dangerous'
+// HEURISTIC: :16:{{.*}}: note: called by function 'wrapper'
+// HEURISTIC: :16:{{.*}}: note: inlined by function 'middle'
+// HEURISTIC: :16:{{.*}}: note: inlined by function 'caller'
+// HEURISTIC: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations
 
-// DEBUG: :9:{{.*}}: warning: call to 'dangerous'
-// DEBUG: :9:{{.*}}: note: called by function 'wrapper'
-// DEBUG: :13:{{.*}}: note: inlined by function 'middle'
-// DEBUG: :17:{{.*}}: note: inlined by function 'caller'
+// DEBUG: :16:{{.*}}: warning: call to 'dangerous'
+// DEBUG: :16:{{.*}}: note: called by function 'wrapper'
+// DEBUG: :20:{{.*}}: note: inlined by function 'middle'
+// DEBUG: :24:{{.*}}: note: inlined by function 'caller'
+// DEBUG-NOT: note: use '-gline-directives-only'
diff --git a/clang/test/Frontend/backend-attribute-inlining-modes.c 
b/clang/test/Frontend/backend-attribute-inlining-modes.c
index ebee1208a17dd..9768845458754 100644
--- a/clang/test/Frontend/backend-attribute-inlining-modes.c
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -1,13 +1,13 @@
-// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=NONE-DEFAULT
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=none %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=NONE-EXPLICIT
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=heuristic 
%s -o /dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain=debug %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=FALLBACK
+// RUN: %clang_cc1 -O2 -emit-obj %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DISABLED
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=ENABLED-HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=ENABLED-DEBUG
 
-// Tests all three modes plus fallback behavior when debug info
-// (-gline-directives-only) is missing.
+// Test -fdiagnostics-show-inlining-chain behavior:
+// - Disabled (default): warning only, no inlining notes.
+// - Enabled without debug info: heuristic fallback + suggestion note.
+// - Enabled with debug info: accurate locations from DILocation.
 
-__attribute__((warning("do not call")))
+[[gnu::warning("do not call")]]
 void bad_func(void);
 
 static inline void level1(void) {
@@ -22,26 +22,21 @@ void entry(void) {
     level2();
 }
 
-// none (default): warning only, no inlining notes.
-// NONE-DEFAULT: warning: call to 'bad_func'
-// NONE-DEFAULT-NOT: note:
-
-// none (explicit): same as default.
-// NONE-EXPLICIT: warning: call to 'bad_func'
-// NONE-EXPLICIT-NOT: note:
-
-// HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
-// HEURISTIC: :14:{{.*}}: note: called by function 'level1'
-// HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
-// HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
-
-// DEBUG: :14:{{.*}}: warning: call to 'bad_func'
-// DEBUG: :14:{{.*}}: note: called by function 'level1'
-// DEBUG: :18:{{.*}}: note: inlined by function 'level2'
-// DEBUG: :22:{{.*}}: note: inlined by function 'entry'
-
-// FALLBACK: warning: '-fdiagnostics-show-inlining-chain=debug' requires at 
least '-gline-directives-only'; falling back to 'heuristic' mode
-// FALLBACK: :14:{{.*}}: warning: call to 'bad_func'
-// FALLBACK: :14:{{.*}}: note: called by function 'level1'
-// FALLBACK: :18:{{.*}}: note: inlined by function 'level2'
-// FALLBACK: :22:{{.*}}: note: inlined by function 'entry'
+// Disabled (default): warning only, no inlining notes.
+// DISABLED: warning: call to 'bad_func'
+// DISABLED-NOT: note:
+
+// Enabled without debug info: heuristic fallback.
+// All notes point to original call site (:14).
+// ENABLED-HEURISTIC: :14:{{.*}}: warning: call to 'bad_func'
+// ENABLED-HEURISTIC: :14:{{.*}}: note: called by function 'level1'
+// ENABLED-HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
+// ENABLED-HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
+// ENABLED-HEURISTIC: note: use '-gline-directives-only' or higher for more 
accurate inlining chain locations
+
+// Enabled with debug info: accurate locations.
+// ENABLED-DEBUG: :14:{{.*}}: warning: call to 'bad_func'
+// ENABLED-DEBUG: :14:{{.*}}: note: called by function 'level1'
+// ENABLED-DEBUG: :18:{{.*}}: note: inlined by function 'level2'
+// ENABLED-DEBUG: :22:{{.*}}: note: inlined by function 'entry'
+// ENABLED-DEBUG-NOT: note: use '-gline-directives-only'
diff --git a/clang/test/Frontend/backend-attribute-inlining.c 
b/clang/test/Frontend/backend-attribute-inlining.c
index 5f91a38ebdc57..45a8e601e805f 100644
--- a/clang/test/Frontend/backend-attribute-inlining.c
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -1,7 +1,7 @@
-// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain=heuristic -S %s -o 
/dev/null 2>&1 | FileCheck %s
+// RUN: not %clang -O2 -fdiagnostics-show-inlining-chain -S %s -o /dev/null 
2>&1 | FileCheck %s
 
 // Single-level inlining with warning attribute.
-__attribute__((warning("do not call directly")))
+[[gnu::warning("do not call directly")]]
 void __warn_single(void);
 
 static inline void warn_wrapper(void) {
@@ -16,7 +16,7 @@ void test_single_level(void) {
 // CHECK: :12:{{.*}}: note: inlined by function 'test_single_level'
 
 // Error attribute with inlining.
-__attribute__((error("never call this")))
+[[gnu::error("never call this")]]
 void __error_func(void);
 
 static inline void error_wrapper(void) {
@@ -31,7 +31,7 @@ void test_error_inlined(void) {
 // CHECK: :27:{{.*}}: note: inlined by function 'test_error_inlined'
 
 // Deep nesting (5 levels).
-__attribute__((warning("deep call")))
+[[gnu::warning("deep call")]]
 void __warn_deep(void);
 
 static inline void deep1(void) { __warn_deep(); }
@@ -52,7 +52,7 @@ void test_deep_nesting(void) {
 // CHECK: :44:{{.*}}: note: inlined by function 'test_deep_nesting'
 
 // Multiple call sites produce distinct diagnostics.
-__attribute__((warning("deprecated")))
+[[gnu::warning("deprecated")]]
 void __warn_multi(void);
 
 static inline void multi_wrapper(void) {
@@ -76,7 +76,7 @@ void call_site_c(void) { multi_wrapper(); }
 // CHECK: :64:{{.*}}: note: inlined by function 'call_site_c'
 
 // Different nesting depths from same inner function.
-__attribute__((warning("mixed depth")))
+[[gnu::warning("mixed depth")]]
 void __warn_mixed(void);
 
 static inline void mixed_inner(void) { __warn_mixed(); }
@@ -96,7 +96,7 @@ void deep(void) { mixed_middle(); }
 
 // Incidental inlining (function not marked inline/static).
 // The "inlined by" note has no location since heuristic mode doesn't track it.
-__attribute__((warning("incidental")))
+[[gnu::warning("incidental")]]
 void __warn_incidental(void);
 
 void not_marked_inline(void) { __warn_incidental(); }
@@ -107,3 +107,6 @@ void test_incidental(void) { not_marked_inline(); }
 // CHECK: note: called by function 'not_marked_inline'
 // CHECK: note: inlined by function 'test_incidental'
 // CHECK-NOT: :{{.*}}: note: inlined by function 'test_incidental'
+
+// Fallback note should appear (no debug info).
+// CHECK: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations

>From b4351e9ef577e1f52981b6ff052603e8bee97e31 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Thu, 8 Jan 2026 15:01:22 -0800
Subject: [PATCH 3/6] mention -g1 in docs, better explain heuristic intentions

---
 clang/docs/ReleaseNotes.rst  | 6 +++---
 clang/lib/CodeGen/CGCall.cpp | 8 +++++---
 2 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 350d742d99be0..31897a8031441 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -367,9 +367,9 @@ New Compiler Flags
   notes for ``[[gnu::warning]]`` and ``[[gnu::error]]`` diagnostics. When a
   function with these attributes is called from an inlined context, Clang can
   now show which functions were inlined to reach the call. When debug info is
-  available (``-gline-directives-only`` or higher), accurate source locations
-  are used; otherwise, a heuristic fallback is used with a note suggesting
-  how to enable debug info for better accuracy.
+  available (``-gline-directives-only`` (implicitly enabled at ``-g1``) or
+  higher), accurate source locations are used; otherwise, a heuristic fallback
+  is used with a note suggesting how to enable debug info for better accuracy.
 
 Lanai Support
 ^^^^^^^^^^^^^^
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 76e504a5aa47b..8c9bfa7c103f2 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6048,9 +6048,11 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo 
&CallInfo,
   if (getDebugInfo() && TargetDecl && TargetDecl->hasAttr<MSAllocatorAttr>())
     getDebugInfo()->addHeapAllocSiteMetadata(CI, RetTy->getPointeeType(), Loc);
 
-  // Add srcloc metadata for [[gnu::error/warning]] diagnostics.
-  // When ShowInliningChain is enabled, also track inline/static calls for the
-  // heuristic fallback when debug info is not available.
+  // Add srcloc metadata for [[gnu::error/warning]] diagnostics. When
+  // ShowInliningChain is enabled, also track inline/static calls for the
+  // heuristic fallback when debug info is not available. This heuristic is
+  // conservative and best-effort since static or inline-annotated functions
+  // are still not guaranteed to be inlined.
   if (TargetDecl) {
     bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
     if (!NeedSrcLoc && CGM.getCodeGenOpts().ShowInliningChain) {

>From 71d20fd9b8002fb01566bb80e97085800fc85b02 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Thu, 8 Jan 2026 16:41:11 -0800
Subject: [PATCH 4/6] drop isInlineSpecified, test anon namespace, add more -g1
 notes

change clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c to a
c++ file so I can test anonymous namespaces.

add more -g1 notes in AttrDocs and diagnostic note.
---
 clang/include/clang/Basic/AttrDocs.td         |  8 +--
 .../clang/Basic/DiagnosticFrontendKinds.td    |  4 +-
 clang/lib/CodeGen/CGCall.cpp                  |  3 +-
 .../backend-attribute-inlining-cross-tu.c     |  2 +-
 ...nd-attribute-inlining-debug-vs-heuristic.c | 37 ----------
 ...-attribute-inlining-debug-vs-heuristic.cpp | 68 +++++++++++++++++++
 .../backend-attribute-inlining-modes.c        |  2 +-
 .../Frontend/backend-attribute-inlining.c     |  2 +-
 8 files changed, 78 insertions(+), 48 deletions(-)
 delete mode 100644 
clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
 create mode 100644 
clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp

diff --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index f9c6d6ccd606f..6ea1a007d58cf 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8405,10 +8405,10 @@ triggered the diagnostic when the attributed function 
is called from
 multiple locations through inline functions.
 
 When enabled, this option automatically uses debug info for accurate source
-locations if available (requires at least ``-gline-directives-only``), or
-falls back to a heuristic based on metadata tracking. When falling back, a
-note is emitted suggesting ``-gline-directives-only`` for more accurate
-locations.
+locations if available (``-gline-directives-only`` (implicitly enabled at
+``-g1``) or higher), or falls back to a heuristic based on metadata tracking.
+When falling back, a note is emitted suggesting ``-gline-directives-only`` for
+more accurate locations.
   }];
 }
 
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td 
b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index ff924b3ace3dd..8180766d63464 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -97,8 +97,8 @@ def warn_fe_backend_warning_attr :
 def note_fe_backend_in : Note<"called by function '%0'">, BackendInfo;
 def note_fe_backend_inlined : Note<"inlined by function '%0'">, BackendInfo;
 def note_fe_backend_inlining_debug_info
-    : Note<"use '-gline-directives-only' or higher for more accurate "
-           "inlining chain locations">,
+    : Note<"use '-gline-directives-only' (implied by '-g1') or higher for "
+           "more accurate inlining chain locations">,
       BackendInfo;
 def warn_toc_unsupported_type : Warning<"-mtocdata option is ignored "
   "for %0 because %1">, InGroup<BackendWarningAttributes>;
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 8c9bfa7c103f2..039b552fe5042 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -6057,8 +6057,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo 
&CallInfo,
     bool NeedSrcLoc = TargetDecl->hasAttr<ErrorAttr>();
     if (!NeedSrcLoc && CGM.getCodeGenOpts().ShowInliningChain) {
       if (const auto *FD = dyn_cast<FunctionDecl>(TargetDecl))
-        NeedSrcLoc = FD->isInlined() || FD->isInlineSpecified() ||
-                     FD->hasAttr<AlwaysInlineAttr>() ||
+        NeedSrcLoc = FD->isInlined() || FD->hasAttr<AlwaysInlineAttr>() ||
                      FD->getStorageClass() == SC_Static ||
                      FD->isInAnonymousNamespace();
     }
diff --git a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c 
b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
index cf259cc77f232..3d67ae946a230 100644
--- a/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
+++ b/clang/test/Frontend/backend-attribute-inlining-cross-tu.c
@@ -64,4 +64,4 @@ void test_error_cross_tu(void) {
 // CHECK: main.c:{{.*}}: note: inlined by function 'test_error_cross_tu'
 
 // Fallback note should appear (no debug info).
-// CHECK: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations
+// CHECK: note: use '-gline-directives-only' (implied by '-g1') or higher for 
more accurate inlining chain locations
diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
deleted file mode 100644
index 4760b95f49c39..0000000000000
--- a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.c
+++ /dev/null
@@ -1,37 +0,0 @@
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
-// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
-
-// Verify auto-selection works between debug info and heuristic fallback. When
-// we have at least -gline-directives-only we can use DILocation for accurate
-// inline locations.
-
-// Without that debug info we fall back to a heuristic approach using srcloc
-// metadata.
-
-[[gnu::warning("dangerous function")]]
-void dangerous(void);
-
-// Non-static, non-inline functions that get inlined at -O2.
-void wrapper(void) {
-    dangerous();
-}
-
-void middle(void) {
-    wrapper();
-}
-
-void caller(void) {
-    middle();
-}
-
-// HEURISTIC: :16:{{.*}}: warning: call to 'dangerous'
-// HEURISTIC: :16:{{.*}}: note: called by function 'wrapper'
-// HEURISTIC: :16:{{.*}}: note: inlined by function 'middle'
-// HEURISTIC: :16:{{.*}}: note: inlined by function 'caller'
-// HEURISTIC: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations
-
-// DEBUG: :16:{{.*}}: warning: call to 'dangerous'
-// DEBUG: :16:{{.*}}: note: called by function 'wrapper'
-// DEBUG: :20:{{.*}}: note: inlined by function 'middle'
-// DEBUG: :24:{{.*}}: note: inlined by function 'caller'
-// DEBUG-NOT: note: use '-gline-directives-only'
diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
new file mode 100644
index 0000000000000..ffdc59fa2852d
--- /dev/null
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
@@ -0,0 +1,68 @@
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain %s -o 
/dev/null 2>&1 | FileCheck %s --check-prefix=HEURISTIC
+// RUN: %clang_cc1 -O2 -emit-obj -fdiagnostics-show-inlining-chain 
-debug-info-kind=line-directives-only %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=DEBUG
+
+// Verify auto-selection works between debug info and heuristic fallback. When
+// we have at least -gline-directives-only we can use DILocation for accurate
+// inline locations.
+
+// Without that debug info we fall back to a heuristic approach using srcloc
+// metadata.
+
+[[gnu::warning("dangerous function")]]
+void dangerous();
+
+// Non-static, non-inline functions that get inlined at -O2.
+void wrapper() {
+    dangerous();
+}
+
+void middle() {
+    wrapper();
+}
+
+void caller() {
+    middle();
+}
+
+// HEURISTIC: :16:{{.*}}: warning: call to 'dangerous()'
+// HEURISTIC: :16:{{.*}}: note: called by function 'wrapper()'
+// HEURISTIC: :16:{{.*}}: note: inlined by function 'middle()'
+// HEURISTIC: :16:{{.*}}: note: inlined by function 'caller()'
+// HEURISTIC: note: use '-gline-directives-only' (implied by '-g1') or higher 
for more accurate inlining chain locations
+
+// DEBUG: :16:{{.*}}: warning: call to 'dangerous()'
+// DEBUG: :16:{{.*}}: note: called by function 'wrapper'
+// DEBUG: :20:{{.*}}: note: inlined by function 'middle'
+// DEBUG: :24:{{.*}}: note: inlined by function 'caller'
+// DEBUG-NOT: note: use '-gline-directives-only'
+
+// Test that functions in anonymous namespaces are properly tracked for
+// inlining chain diagnostics. Anonymous namespace functions have internal
+// linkage and are prime candidates for inlining.
+
+[[gnu::warning("do not call")]]
+void bad_func();
+
+namespace {
+void anon_helper() {
+    bad_func();
+}
+
+void anon_middle() {
+    anon_helper();
+}
+} // namespace
+
+void public_caller() {
+    anon_middle();
+}
+
+// HEURISTIC: :48:{{.*}}: warning: call to 'bad_func()'
+// HEURISTIC: :48:{{.*}}: note: called by function '(anonymous 
namespace)::anon_helper()'
+// HEURISTIC: :52:{{.*}}: note: inlined by function '(anonymous 
namespace)::anon_middle()'
+// HEURISTIC: :57:{{.*}}: note: inlined by function 'public_caller()'
+
+// DEBUG: :48:{{.*}}: warning: call to 'bad_func()'
+// DEBUG: :48:{{.*}}: note: called by function 'anon_helper'
+// DEBUG: :52:{{.*}}: note: inlined by function 'anon_middle'
+// DEBUG: :57:{{.*}}: note: inlined by function 'public_caller'
diff --git a/clang/test/Frontend/backend-attribute-inlining-modes.c 
b/clang/test/Frontend/backend-attribute-inlining-modes.c
index 9768845458754..adc010671fffd 100644
--- a/clang/test/Frontend/backend-attribute-inlining-modes.c
+++ b/clang/test/Frontend/backend-attribute-inlining-modes.c
@@ -32,7 +32,7 @@ void entry(void) {
 // ENABLED-HEURISTIC: :14:{{.*}}: note: called by function 'level1'
 // ENABLED-HEURISTIC: :18:{{.*}}: note: inlined by function 'level2'
 // ENABLED-HEURISTIC: :22:{{.*}}: note: inlined by function 'entry'
-// ENABLED-HEURISTIC: note: use '-gline-directives-only' or higher for more 
accurate inlining chain locations
+// ENABLED-HEURISTIC: note: use '-gline-directives-only' (implied by '-g1') or 
higher for more accurate inlining chain locations
 
 // Enabled with debug info: accurate locations.
 // ENABLED-DEBUG: :14:{{.*}}: warning: call to 'bad_func'
diff --git a/clang/test/Frontend/backend-attribute-inlining.c 
b/clang/test/Frontend/backend-attribute-inlining.c
index 45a8e601e805f..734ce0fc30bba 100644
--- a/clang/test/Frontend/backend-attribute-inlining.c
+++ b/clang/test/Frontend/backend-attribute-inlining.c
@@ -109,4 +109,4 @@ void test_incidental(void) { not_marked_inline(); }
 // CHECK-NOT: :{{.*}}: note: inlined by function 'test_incidental'
 
 // Fallback note should appear (no debug info).
-// CHECK: note: use '-gline-directives-only' or higher for more accurate 
inlining chain locations
+// CHECK: note: use '-gline-directives-only' (implied by '-g1') or higher for 
more accurate inlining chain locations

>From 9733f3ae58beafd2889cf030abe344bdee996729 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Thu, 8 Jan 2026 16:49:35 -0800
Subject: [PATCH 5/6] add test for always_inline

Signed-off-by: Justin Stitt <[email protected]>
---
 ...-attribute-inlining-debug-vs-heuristic.cpp | 23 +++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
index ffdc59fa2852d..edaaf8e7814d2 100644
--- a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
@@ -66,3 +66,26 @@ void public_caller() {
 // DEBUG: :48:{{.*}}: note: called by function 'anon_helper'
 // DEBUG: :52:{{.*}}: note: inlined by function 'anon_middle'
 // DEBUG: :57:{{.*}}: note: inlined by function 'public_caller'
+
+// always_inline forces inlining but doesn't imply
+// isInlined() in the language sense.
+
+[[gnu::warning("always inline warning")]]
+void always_inline_target();
+
+__attribute__((always_inline))
+void always_inline_wrapper() {
+    always_inline_target();
+}
+
+void always_inline_caller() {
+    always_inline_wrapper();
+}
+
+// HEURISTIC: :78:{{.*}}: warning: call to 'always_inline_target()'
+// HEURISTIC: :78:{{.*}}: note: called by function 'always_inline_wrapper()'
+// HEURISTIC: :82:{{.*}}: note: inlined by function 'always_inline_caller()'
+
+// DEBUG: :78:{{.*}}: warning: call to 'always_inline_target()'
+// DEBUG: :78:{{.*}}: note: called by function 'always_inline_wrapper'
+// DEBUG: :82:{{.*}}: note: inlined by function 'always_inline_caller'

>From 2995e7b87cc65ced7e41ef4b0e8c22da1d197c95 Mon Sep 17 00:00:00 2001
From: Justin Stitt <[email protected]>
Date: Fri, 9 Jan 2026 09:40:10 -0800
Subject: [PATCH 6/6] use broader pattern matching for msvc tests

Signed-off-by: Justin Stitt <[email protected]>
---
 ...-attribute-inlining-debug-vs-heuristic.cpp | 45 ++++++++++---------
 1 file changed, 23 insertions(+), 22 deletions(-)

diff --git 
a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp 
b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
index edaaf8e7814d2..3a58be1327844 100644
--- a/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
+++ b/clang/test/Frontend/backend-attribute-inlining-debug-vs-heuristic.cpp
@@ -24,16 +24,17 @@ void caller() {
     middle();
 }
 
-// HEURISTIC: :16:{{.*}}: warning: call to 'dangerous()'
-// HEURISTIC: :16:{{.*}}: note: called by function 'wrapper()'
-// HEURISTIC: :16:{{.*}}: note: inlined by function 'middle()'
-// HEURISTIC: :16:{{.*}}: note: inlined by function 'caller()'
+
+// HEURISTIC: :16:{{.*}}: warning: call to '{{.*}}dangerous{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: called by function '{{.*}}wrapper{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: inlined by function '{{.*}}middle{{.*}}'
+// HEURISTIC: :16:{{.*}}: note: inlined by function '{{.*}}caller{{.*}}'
 // HEURISTIC: note: use '-gline-directives-only' (implied by '-g1') or higher 
for more accurate inlining chain locations
 
-// DEBUG: :16:{{.*}}: warning: call to 'dangerous()'
-// DEBUG: :16:{{.*}}: note: called by function 'wrapper'
-// DEBUG: :20:{{.*}}: note: inlined by function 'middle'
-// DEBUG: :24:{{.*}}: note: inlined by function 'caller'
+// DEBUG: :16:{{.*}}: warning: call to '{{.*}}dangerous{{.*}}'
+// DEBUG: :16:{{.*}}: note: called by function '{{.*}}wrapper{{.*}}'
+// DEBUG: :20:{{.*}}: note: inlined by function '{{.*}}middle{{.*}}'
+// DEBUG: :24:{{.*}}: note: inlined by function '{{.*}}caller{{.*}}'
 // DEBUG-NOT: note: use '-gline-directives-only'
 
 // Test that functions in anonymous namespaces are properly tracked for
@@ -57,15 +58,15 @@ void public_caller() {
     anon_middle();
 }
 
-// HEURISTIC: :48:{{.*}}: warning: call to 'bad_func()'
-// HEURISTIC: :48:{{.*}}: note: called by function '(anonymous 
namespace)::anon_helper()'
-// HEURISTIC: :52:{{.*}}: note: inlined by function '(anonymous 
namespace)::anon_middle()'
-// HEURISTIC: :57:{{.*}}: note: inlined by function 'public_caller()'
+// HEURISTIC: :49:{{.*}}: warning: call to '{{.*}}bad_func{{.*}}'
+// HEURISTIC: :49:{{.*}}: note: called by function '{{.*}}anon_helper{{.*}}'
+// HEURISTIC: :53:{{.*}}: note: inlined by function '{{.*}}anon_middle{{.*}}'
+// HEURISTIC: :58:{{.*}}: note: inlined by function '{{.*}}public_caller{{.*}}'
 
-// DEBUG: :48:{{.*}}: warning: call to 'bad_func()'
-// DEBUG: :48:{{.*}}: note: called by function 'anon_helper'
-// DEBUG: :52:{{.*}}: note: inlined by function 'anon_middle'
-// DEBUG: :57:{{.*}}: note: inlined by function 'public_caller'
+// DEBUG: :49:{{.*}}: warning: call to '{{.*}}bad_func{{.*}}'
+// DEBUG: :49:{{.*}}: note: called by function '{{.*}}anon_helper{{.*}}'
+// DEBUG: :53:{{.*}}: note: inlined by function '{{.*}}anon_middle{{.*}}'
+// DEBUG: :58:{{.*}}: note: inlined by function '{{.*}}public_caller{{.*}}'
 
 // always_inline forces inlining but doesn't imply
 // isInlined() in the language sense.
@@ -82,10 +83,10 @@ void always_inline_caller() {
     always_inline_wrapper();
 }
 
-// HEURISTIC: :78:{{.*}}: warning: call to 'always_inline_target()'
-// HEURISTIC: :78:{{.*}}: note: called by function 'always_inline_wrapper()'
-// HEURISTIC: :82:{{.*}}: note: inlined by function 'always_inline_caller()'
+// HEURISTIC: :79:{{.*}}: warning: call to '{{.*}}always_inline_target{{.*}}'
+// HEURISTIC: :79:{{.*}}: note: called by function 
'{{.*}}always_inline_wrapper{{.*}}'
+// HEURISTIC: :83:{{.*}}: note: inlined by function 
'{{.*}}always_inline_caller{{.*}}'
 
-// DEBUG: :78:{{.*}}: warning: call to 'always_inline_target()'
-// DEBUG: :78:{{.*}}: note: called by function 'always_inline_wrapper'
-// DEBUG: :82:{{.*}}: note: inlined by function 'always_inline_caller'
+// DEBUG: :79:{{.*}}: warning: call to '{{.*}}always_inline_target{{.*}}'
+// DEBUG: :79:{{.*}}: note: called by function 
'{{.*}}always_inline_wrapper{{.*}}'
+// DEBUG: :83:{{.*}}: note: inlined by function 
'{{.*}}always_inline_caller{{.*}}'

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to