[llvm-branch-commits] [flang] [flang][debug] generate llvm.fake.use for arguments at -g and O0 (PR #187044)
via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Mar 17 08:29:08 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-flang-codegen
Author: None (jeanPerier)
<details>
<summary>Changes</summary>
Second step toward a fix for https://github.com/llvm/llvm-project/issues/185432.
The issue is that the current DWARF generated for argument variables is pointing to the passing register which are usually short-lived (another call will make them invalid), and if the variable are not used until the end of the function, LLVM will not extend the maintain the addresses from these registers for the sole purpose of debugging. This leads to `<optimized out>` in the debugger when querying about arguments after their last use in the code.
The proposed solution is to extend the liveness of arguments under `-g -O0` using llvm.fake.use to maximize their availability in the debugger.
To properly do something, this fix needs a third step which is to support llvm.fake.use in fast-isel (currently dropped).
Note that an alternative solution was drafted [here](https://github.com/llvm/llvm-project/pull/185858) to copy the arguments on the stack before LLVM, but this require debugger to identify that this copy to the stack is part of the prologue and skip it when breaking on the function, otherwise the info printed for the arguments is garbage on entry (and then correct).
Note that llvm.fake.use may lead to similar issue currently because the argument may be promoted to the stack in LLVM and fast-isel is currently only emitting the stack location in the DWARF. But I am hopping this can be improved (working on draft patch), the promotion is not systematic, and LLVM emits a prologue_end correctly in such case, which means modern debugger will not hit the issue (with the manual alloca, there is a bit of cooking to ensure the store to the stack is placed in the prologue (one need to ensure no lined numbered operation is placed before the store in FIR code generation)).
---
Full diff: https://github.com/llvm/llvm-project/pull/187044.diff
7 Files Affected:
- (modified) flang/include/flang/Optimizer/Dialect/FIROps.td (+10)
- (modified) flang/include/flang/Optimizer/Transforms/Passes.td (+4-2)
- (modified) flang/lib/Optimizer/CodeGen/CodeGen.cpp (+13-1)
- (modified) flang/lib/Optimizer/Passes/Pipelines.cpp (+7)
- (modified) flang/lib/Optimizer/Transforms/AddDebugInfo.cpp (+20-1)
- (added) flang/test/Fir/fake_use-codegen.fir (+12)
- (added) flang/test/Transforms/debug-fake-use.fir (+56)
``````````diff
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 2a849a98903e6..e530c065c5bf3 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -549,6 +549,16 @@ def fir_UndefOp : fir_OneResultOp<"undefined", [NoMemoryEffect]> {
let hasVerifier = 0;
}
+def fir_FakeUseOp : fir_Op<"fake_use", []> {
+ let summary = "fake use of values to extend their lifetime";
+ let description = [{
+ This operation is used to keep values alive for debug information purposes.
+ }];
+
+ let arguments = (ins Variadic<AnyType>:$args);
+ let assemblyFormat = "$args attr-dict `:` type($args)";
+}
+
def fir_ZeroOp : fir_OneResultOp<"zero_bits", [NoMemoryEffect]> {
let summary = "explicit polymorphic zero value of some type";
let description = [{
diff --git a/flang/include/flang/Optimizer/Transforms/Passes.td b/flang/include/flang/Optimizer/Transforms/Passes.td
index 38c5fc8db911c..53d3e42fc3445 100644
--- a/flang/include/flang/Optimizer/Transforms/Passes.td
+++ b/flang/include/flang/Optimizer/Transforms/Passes.td
@@ -263,8 +263,10 @@ def AddDebugInfo : Pass<"add-debug-info", "mlir::ModuleOp"> {
"Name of the split dwarf file">,
Option<"dwarfDebugFlags", "dwarf-debug-flags",
"std::string", /*default=*/"std::string{}",
- "Command-line flags to append to DWARF producer">
-
+ "Command-line flags to append to DWARF producer">,
+ Option<"emitFakeUseForArguments", "emit-fake-use-for-arguments",
+ "bool", /*default=*/"false",
+ "Emit fake use for function arguments to extend their lifetime">
];
}
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index b4950746164b6..4b9aea85eef68 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -4016,6 +4016,18 @@ struct ZeroOpConversion : public fir::FIROpConversion<fir::ZeroOp> {
}
};
+/// convert to LLVM IR dialect `fake_use`
+struct FakeUseOpConversion : public fir::FIROpConversion<fir::FakeUseOp> {
+ using FIROpConversion::FIROpConversion;
+
+ llvm::LogicalResult
+ matchAndRewrite(fir::FakeUseOp op, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ rewriter.replaceOpWithNewOp<mlir::LLVM::FakeUseOp>(op, adaptor.getArgs());
+ return mlir::success();
+ }
+};
+
/// `fir.unreachable` --> `llvm.unreachable`
struct UnreachableOpConversion
: public fir::FIROpConversion<fir::UnreachableOp> {
@@ -4602,7 +4614,7 @@ void fir::populateFIRToLLVMConversionPatterns(
TypeInfoOpConversion, UnboxCharOpConversion, UnboxProcOpConversion,
UndefOpConversion, UnreachableOpConversion, UseStmtOpConversion,
XArrayCoorOpConversion, XEmboxOpConversion, XReboxOpConversion,
- ZeroOpConversion>(converter, options);
+ ZeroOpConversion, FakeUseOpConversion>(converter, options);
// Patterns that are populated without a type converter do not trigger
// target materializations for the operands of the root op.
diff --git a/flang/lib/Optimizer/Passes/Pipelines.cpp b/flang/lib/Optimizer/Passes/Pipelines.cpp
index e9cd5da56083e..77080ac74ff0d 100644
--- a/flang/lib/Optimizer/Passes/Pipelines.cpp
+++ b/flang/lib/Optimizer/Passes/Pipelines.cpp
@@ -16,6 +16,11 @@
static llvm::cl::opt<bool> forceNoAlias("force-no-alias", llvm::cl::Hidden,
llvm::cl::init(true));
+/// Disable the use of fake use for arguments.
+static llvm::cl::opt<bool> disableArgumentFakeUse("disable-argument-fake-use",
+ llvm::cl::Hidden,
+ llvm::cl::init(false));
+
namespace fir {
template <typename F>
@@ -106,6 +111,8 @@ void addDebugInfoPass(mlir::PassManager &pm,
options.dwarfVersion = dwarfVersion;
options.splitDwarfFile = splitDwarfFile;
options.dwarfDebugFlags = dwarfDebugFlags;
+ options.emitFakeUseForArguments =
+ (optLevel == llvm::OptimizationLevel::O0) && !disableArgumentFakeUse;
addPassConditionally(pm, disableDebugInfo,
[&]() { return fir::createAddDebugInfoPass(options); });
}
diff --git a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
index c42f5a29ecd62..66ac603b1306a 100644
--- a/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
+++ b/flang/lib/Optimizer/Transforms/AddDebugInfo.cpp
@@ -294,8 +294,27 @@ void AddDebugInfoPass::handleLocalVariable(Op declOp, llvm::StringRef name,
// Get the dummy argument position from the explicit attribute.
unsigned argNo = 0;
if (dummyScope && declOp.getDummyScope() == dummyScope) {
- if (auto argNoOpt = declOp.getDummyArgNo())
+ if (auto argNoOpt = declOp.getDummyArgNo()) {
argNo = *argNoOpt;
+ if (emitFakeUseForArguments) {
+ if constexpr (std::is_same_v<Op, fir::cg::XDeclareOp>) {
+ if (auto funcOp =
+ declOp->template getParentOfType<mlir::func::FuncOp>()) {
+ if (declOp->getBlock() == &funcOp.getBody().front()) {
+ for (mlir::Block &block : funcOp.getBody()) {
+ if (auto returnOp = mlir::dyn_cast<mlir::func::ReturnOp>(
+ block.getTerminator())) {
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ builder.setInsertionPoint(returnOp);
+ fir::FakeUseOp::create(builder, declOp.getLoc(),
+ declOp.getMemref());
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
auto tyAttr =
diff --git a/flang/test/Fir/fake_use-codegen.fir b/flang/test/Fir/fake_use-codegen.fir
new file mode 100644
index 0000000000000..a1f50cf7b495d
--- /dev/null
+++ b/flang/test/Fir/fake_use-codegen.fir
@@ -0,0 +1,12 @@
+// RUN: fir-opt --fir-to-llvm-ir %s | FileCheck %s
+
+// CHECK-LABEL: llvm.func @test_fake_use(
+// CHECK-SAME: %[[ARG0:.*]]: !llvm.ptr) {
+// CHECK: llvm.intr.fake.use %[[ARG0]] : !llvm.ptr
+// CHECK: llvm.return
+// CHECK: }
+
+func.func @test_fake_use(%arg0: !fir.ref<i32>) {
+ fir.fake_use %arg0 : !fir.ref<i32>
+ return
+}
diff --git a/flang/test/Transforms/debug-fake-use.fir b/flang/test/Transforms/debug-fake-use.fir
new file mode 100644
index 0000000000000..d86cc9b3afee4
--- /dev/null
+++ b/flang/test/Transforms/debug-fake-use.fir
@@ -0,0 +1,56 @@
+// RUN: fir-opt --add-debug-info="emit-fake-use-for-arguments=true" %s | FileCheck %s --check-prefix=FAKE-USE
+// RUN: fir-opt --add-debug-info="emit-fake-use-for-arguments=false" %s | FileCheck %s --check-prefix=NO-FAKE-USE
+
+// FAKE-USE-LABEL: func.func @test_
+// FAKE-USE: %[[UNDEF:.*]] = fir.undefined !fir.dscope
+// FAKE-USE: %[[DECL:.*]] = fircg.ext_declare %arg0 dummy_scope %[[UNDEF]] arg 1
+// FAKE-USE: fir.call @foo() : () -> ()
+// FAKE-USE: fir.fake_use %arg0
+// FAKE-USE: return
+
+// NO-FAKE-USE-LABEL: func.func @test_
+// NO-FAKE-USE: %[[UNDEF:.*]] = fir.undefined !fir.dscope
+// NO-FAKE-USE: %[[DECL:.*]] = fircg.ext_declare %arg0 dummy_scope %[[UNDEF]] arg 1
+// NO-FAKE-USE: fir.call @foo() : () -> ()
+// NO-FAKE-USE-NOT: fir.fake_use
+// NO-FAKE-USE: return
+
+// FAKE-USE-LABEL: func.func @test_local
+// FAKE-USE: %[[UNDEF:.*]] = fir.undefined !fir.dscope
+// FAKE-USE: %[[ALLOCA:.*]] = fir.alloca i32
+// FAKE-USE: %[[DECL_LOCAL:.*]] = fircg.ext_declare %[[ALLOCA]] dummy_scope %[[UNDEF]] {uniq_name = "_QFtestElocal"}
+// FAKE-USE: fir.call @foo() : () -> ()
+// FAKE-USE-NOT: fir.fake_use
+// FAKE-USE: return
+
+// NO-FAKE-USE-LABEL: func.func @test_local
+// NO-FAKE-USE: %[[UNDEF:.*]] = fir.undefined !fir.dscope
+// NO-FAKE-USE: %[[ALLOCA:.*]] = fir.alloca i32
+// NO-FAKE-USE: %[[DECL_LOCAL:.*]] = fircg.ext_declare %[[ALLOCA]] dummy_scope %[[UNDEF]] {uniq_name = "_QFtestElocal"}
+// NO-FAKE-USE: fir.call @foo() : () -> ()
+// NO-FAKE-USE-NOT: fir.fake_use
+// NO-FAKE-USE: return
+
+#loc1 = loc("debug-fake-use.f90":1:1)
+#loc3 = loc("debug-fake-use.f90":3:14)
+#loc4 = loc("debug-fake-use.f90":4:14)
+#loc5 = loc("debug-fake-use.f90":5:1)
+#loc = loc("debug-fake-use.f90":0:0)
+
+module attributes {dlti.dl_spec = #dlti.dl_spec<i128 = dense<128> : vector<2xi64>, f80 = dense<128> : vector<2xi64>, i1 = dense<8> : vector<2xi64>, !llvm.ptr<271> = dense<32> : vector<4xi64>, !llvm.ptr = dense<64> : vector<4xi64>, i64 = dense<64> : vector<2xi64>, !llvm.ptr<272> = dense<64> : vector<4xi64>, i32 = dense<32> : vector<2xi64>, i16 = dense<16> : vector<2xi64>, !llvm.ptr<270> = dense<32> : vector<4xi64>, i8 = dense<8> : vector<2xi64>, f128 = dense<128> : vector<2xi64>, f16 = dense<16> : vector<2xi64>, f64 = dense<64> : vector<2xi64>, "dlti.stack_alignment" = 128 : i64, "dlti.mangling_mode" = "e", "dlti.endianness" = "little">, fir.defaultkind = "a1c4d8i4l4r4", fir.kindmap = "", fir.target_cpu = "x86-64", llvm.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", llvm.ident = "flang", llvm.target_triple = "x86_64-unknown-linux-gnu"} {
+ func.func private @foo()
+ func.func @test_(%arg0: !fir.ref<i32> {fir.bindc_name = "expected"} loc("debug-fake-use.f90":1:1)) attributes {fir.internal_name = "_QPtest"} {
+ %0 = fir.undefined !fir.dscope loc(#loc1)
+ %1 = fircg.ext_declare %arg0 dummy_scope %0 arg 1 {uniq_name = "_QFtestEexpected"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32> loc(#loc3)
+ fir.call @foo() : () -> ()
+ return loc(#loc5)
+ } loc(#loc1)
+
+ func.func @test_local() attributes {fir.internal_name = "_QPtest_local"} {
+ %0 = fir.undefined !fir.dscope loc(#loc1)
+ %1 = fir.alloca i32 loc(#loc4)
+ %2 = fircg.ext_declare %1 dummy_scope %0 {uniq_name = "_QFtestElocal"} : (!fir.ref<i32>, !fir.dscope) -> !fir.ref<i32> loc(#loc4)
+ fir.call @foo() : () -> ()
+ return loc(#loc5)
+ } loc(#loc1)
+} loc(#loc)
``````````
</details>
https://github.com/llvm/llvm-project/pull/187044
More information about the llvm-branch-commits
mailing list