[Mlir-commits] [mlir] 72429a4 - Add a pass that builds a debug info scope for LLVMFuncOp (adding a DISubprogramAttr)

Mehdi Amini llvmlistbot at llvm.org
Tue Feb 14 22:43:37 PST 2023


Author: Mehdi Amini
Date: 2023-02-14T22:43:26-08:00
New Revision: 72429a42ac33564fa82449d99dc234da32a05498

URL: https://github.com/llvm/llvm-project/commit/72429a42ac33564fa82449d99dc234da32a05498
DIFF: https://github.com/llvm/llvm-project/commit/72429a42ac33564fa82449d99dc234da32a05498.diff

LOG: Add a pass that builds a debug info scope for LLVMFuncOp (adding a DISubprogramAttr)

This may be seen as a hack, but it allows for any piece of MLIR to be able
to end up with DWARF debug info through LLVM.
Assuming the operations in the function have location such as FileLineCol,
this provides backtraces with line tables and allows to step in a debugger.
That makes this pass a perfect companion to -snapshot-op-locations

It was also the default behavior of MLIR to LLVM IR translation until MLIR
got support for proper debug info attributes.

Differential Revision: https://reviews.llvm.org/D144069

Added: 
    mlir/lib/Dialect/LLVMIR/Transforms/DIScopeForLLVMFuncOp.cpp
    mlir/test/Dialect/LLVMIR/add-debuginfo-func-scope.mlir

Modified: 
    mlir/examples/toy/Ch6/toyc.cpp
    mlir/examples/toy/Ch7/toyc.cpp
    mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.h
    mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
    mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/mlir/examples/toy/Ch6/toyc.cpp b/mlir/examples/toy/Ch6/toyc.cpp
index 3983efd2b6f91..9397874441f3a 100644
--- a/mlir/examples/toy/Ch6/toyc.cpp
+++ b/mlir/examples/toy/Ch6/toyc.cpp
@@ -16,6 +16,7 @@
 #include "toy/Passes.h"
 
 #include "mlir/Dialect/Affine/Passes.h"
+#include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
 #include "mlir/ExecutionEngine/ExecutionEngine.h"
 #include "mlir/ExecutionEngine/OptUtils.h"
 #include "mlir/IR/AsmState.h"
@@ -171,6 +172,11 @@ int loadAndProcessMLIR(mlir::MLIRContext &context,
   if (isLoweringToLLVM) {
     // Finish lowering the toy IR to the LLVM dialect.
     pm.addPass(mlir::toy::createLowerToLLVMPass());
+    // This is necessary to have line tables emitted and basic
+    // debugger working. In the future we will add proper debug information
+    // emission directly from our frontend.
+    pm.addNestedPass<mlir::LLVM::LLVMFuncOp>(
+        mlir::LLVM::createDIScopeForLLVMFuncOpPass());
   }
 
   if (mlir::failed(pm.run(*module)))

diff  --git a/mlir/examples/toy/Ch7/toyc.cpp b/mlir/examples/toy/Ch7/toyc.cpp
index ddf6e4671d268..78dcc1abcc45c 100644
--- a/mlir/examples/toy/Ch7/toyc.cpp
+++ b/mlir/examples/toy/Ch7/toyc.cpp
@@ -16,6 +16,7 @@
 #include "toy/Passes.h"
 
 #include "mlir/Dialect/Affine/Passes.h"
+#include "mlir/Dialect/LLVMIR/Transforms/Passes.h"
 #include "mlir/ExecutionEngine/ExecutionEngine.h"
 #include "mlir/ExecutionEngine/OptUtils.h"
 #include "mlir/IR/AsmState.h"
@@ -172,6 +173,11 @@ int loadAndProcessMLIR(mlir::MLIRContext &context,
   if (isLoweringToLLVM) {
     // Finish lowering the toy IR to the LLVM dialect.
     pm.addPass(mlir::toy::createLowerToLLVMPass());
+    // This is necessary to have line tables emitted and basic
+    // debugger working. In the future we will add proper debug information
+    // emission directly from our frontend.
+    pm.addNestedPass<mlir::LLVM::LLVMFuncOp>(
+        mlir::LLVM::createDIScopeForLLVMFuncOpPass());
   }
 
   if (mlir::failed(pm.run(*module)))

diff  --git a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.h b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.h
index b475488c7ee97..eefbbcbf66a5c 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.h
+++ b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.h
@@ -18,6 +18,9 @@ namespace mlir {
 
 namespace LLVM {
 
+/// Create a pass to add DIScope to LLVMFuncOp that are missing it.
+std::unique_ptr<Pass> createDIScopeForLLVMFuncOpPass();
+
 /// Generate the code for registering conversion passes.
 #define GEN_PASS_REGISTRATION
 #include "mlir/Dialect/LLVMIR/Transforms/Passes.h.inc"

diff  --git a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
index 604865e59956e..dcf6615f889fd 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/Transforms/Passes.td
@@ -35,4 +35,19 @@ def NVVMOptimizeForTarget : Pass<"llvm-optimize-for-nvvm-target"> {
   let constructor = "::mlir::NVVM::createOptimizeForTargetPass()";
 }
 
+def DIScopeForLLVMFuncOp : Pass<"ensure-debug-info-scope-on-llvm-func", "LLVM::LLVMFuncOp"> {
+  let summary = "Materialize LLVM debug info subprogram attribute on every LLVMFuncOp";
+  let description = [{
+    Having a debug info subprogram attribute on a function is required for
+    emitting line tables from MLIR FileLocCol locations.
+
+    This is not intended to be a proper replacement for frontends to emit
+    complete debug informations, however it is a convenient way to get line
+    tables for debugging purposes. This allow to step trough in a debugger
+    line-by-line or get a backtrace with line numbers.
+  }];
+
+  let constructor = "mlir::LLVM::createDIScopeForLLVMFuncOpPass()";
+}
+
 #endif // MLIR_DIALECT_LLVMIR_TRANSFORMS_PASSES

diff  --git a/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt b/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
index b1406be17bd5b..e5a9446dfd2fd 100644
--- a/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_mlir_dialect_library(MLIRLLVMIRTransforms
+  DIScopeForLLVMFuncOp.cpp
   LegalizeForExport.cpp
   OptimizeForNVVM.cpp
   RequestCWrappers.cpp

diff  --git a/mlir/lib/Dialect/LLVMIR/Transforms/DIScopeForLLVMFuncOp.cpp b/mlir/lib/Dialect/LLVMIR/Transforms/DIScopeForLLVMFuncOp.cpp
new file mode 100644
index 0000000000000..9f38b0caddb7d
--- /dev/null
+++ b/mlir/lib/Dialect/LLVMIR/Transforms/DIScopeForLLVMFuncOp.cpp
@@ -0,0 +1,102 @@
+//===- DILineTableFromLocations.cpp - -------------------------------------===//
+//
+// 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 "mlir/Dialect/LLVMIR/Transforms/Passes.h"
+
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
+#include "mlir/Pass/Pass.h"
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/Path.h"
+
+namespace mlir {
+namespace LLVM {
+#define GEN_PASS_DEF_DISCOPEFORLLVMFUNCOP
+#include "mlir/Dialect/LLVMIR/Transforms/Passes.h.inc"
+} // namespace LLVM
+} // namespace mlir
+
+using namespace mlir;
+
+/// Attempt to extract a filename for the given loc.
+static FileLineColLoc extractFileLoc(Location loc) {
+  if (auto fileLoc = loc.dyn_cast<FileLineColLoc>())
+    return fileLoc;
+  if (auto nameLoc = loc.dyn_cast<NameLoc>())
+    return extractFileLoc(nameLoc.getChildLoc());
+  if (auto opaqueLoc = loc.dyn_cast<OpaqueLoc>())
+    return extractFileLoc(opaqueLoc.getFallbackLocation());
+  return FileLineColLoc();
+}
+
+namespace {
+/// Add a debug info scope to LLVMFuncOp that are missing it.
+struct DIScopeForLLVMFuncOp
+    : public LLVM::impl::DIScopeForLLVMFuncOpBase<DIScopeForLLVMFuncOp> {
+  void runOnOperation() override {
+    LLVM::LLVMFuncOp llvmFunc = getOperation();
+    Location loc = llvmFunc.getLoc();
+    if (loc->findInstanceOf<mlir::FusedLocWith<LLVM::DISubprogramAttr>>())
+      return;
+
+    MLIRContext *context = &getContext();
+
+    // To find a DICompileUnitAttr attached to a parent (the module for
+    // example), otherwise create a default one.
+    LLVM::DICompileUnitAttr compileUnitAttr;
+    if (ModuleOp module = llvmFunc->getParentOfType<ModuleOp>()) {
+      auto fusedCompileUnitAttr =
+          module->getLoc()
+              ->findInstanceOf<mlir::FusedLocWith<LLVM::DICompileUnitAttr>>();
+      if (fusedCompileUnitAttr)
+        compileUnitAttr = fusedCompileUnitAttr.getMetadata();
+    }
+
+    // Filename, line and colmun to associate to the function.
+    LLVM::DIFileAttr fileAttr;
+    int64_t line = 1, col = 1;
+    FileLineColLoc fileLoc = extractFileLoc(loc);
+    if (!fileLoc && compileUnitAttr) {
+      fileAttr = compileUnitAttr.getFile();
+    } else if (!fileLoc) {
+      fileAttr = LLVM::DIFileAttr::get(context, "<unknown>", "");
+    } else {
+      line = fileLoc.getLine();
+      col = fileLoc.getColumn();
+      StringRef inputFilePath = fileLoc.getFilename().getValue();
+      fileAttr = LLVM::DIFileAttr::get(
+          context, llvm::sys::path::filename(inputFilePath),
+          llvm::sys::path::parent_path(inputFilePath));
+    }
+    if (!compileUnitAttr) {
+      compileUnitAttr = LLVM::DICompileUnitAttr::get(
+          context, llvm::dwarf::DW_LANG_C, fileAttr,
+          StringAttr::get(context, "MLIR"), /*isOptimized=*/true,
+          LLVM::DIEmissionKind::LineTablesOnly);
+    }
+    auto subroutineTypeAttr =
+        LLVM::DISubroutineTypeAttr::get(context, llvm::dwarf::DW_CC_normal, {});
+
+    StringAttr funcNameAttr = llvmFunc.getNameAttr();
+    auto subprogramAttr =
+        LLVM::DISubprogramAttr::get(context, compileUnitAttr, fileAttr,
+                                    funcNameAttr, funcNameAttr, fileAttr,
+                                    /*line=*/line,
+                                    /*scopeline=*/col,
+                                    LLVM::DISubprogramFlags::Definition |
+                                        LLVM::DISubprogramFlags::Optimized,
+                                    subroutineTypeAttr);
+    llvmFunc->setLoc(FusedLoc::get(context, {loc}, subprogramAttr));
+  }
+};
+
+} // end anonymous namespace
+
+std::unique_ptr<Pass> mlir::LLVM::createDIScopeForLLVMFuncOpPass() {
+  return std::make_unique<DIScopeForLLVMFuncOp>();
+}
\ No newline at end of file

diff  --git a/mlir/test/Dialect/LLVMIR/add-debuginfo-func-scope.mlir b/mlir/test/Dialect/LLVMIR/add-debuginfo-func-scope.mlir
new file mode 100644
index 0000000000000..eff4c55e6bc0b
--- /dev/null
+++ b/mlir/test/Dialect/LLVMIR/add-debuginfo-func-scope.mlir
@@ -0,0 +1,59 @@
+// RUN: mlir-opt %s --pass-pipeline="builtin.module(llvm.func(ensure-debug-info-scope-on-llvm-func))" --split-input-file --mlir-print-debuginfo | FileCheck %s
+
+
+
+// CHECK-LABEL: llvm.func @func_no_debug()
+// CHECK: llvm.return loc(#loc
+// CHECK: loc(#loc[[LOC:[0-9]+]])
+// CHECK: #di_file = #llvm.di_file<"<unknown>" in "">
+// CHECK: #di_subprogram = #llvm.di_subprogram<compileUnit = #di_compile_unit, scope = #di_file, name = "func_no_debug", linkageName = "func_no_debug", file = #di_file, line = 1, scopeLine = 1, subprogramFlags = "Definition|Optimized", type = #di_subroutine_type>
+// CHECK: #loc[[LOC]] = loc(fused<#di_subprogram>
+llvm.func @func_no_debug() {
+  llvm.return loc(unknown)
+} loc(unknown)
+
+
+// -----
+
+// Test that existing debug info is not overwritten.
+// CHECK-LABEL: llvm.func @func_with_debug()
+// CHECK: llvm.return loc(#loc
+// CHECK: loc(#loc[[LOC:[0-9]+]])
+// CHECK: #di_file = #llvm.di_file<"<unknown>" in "">
+// CHECK: #di_subprogram = #llvm.di_subprogram<compileUnit = #di_compile_unit, scope = #di_file, name = "func_with_debug", linkageName = "func_with_debug", file = #di_file, line = 42, scopeLine = 42, subprogramFlags = "Definition|Optimized", type = #di_subroutine_type>
+// CHECK: #loc[[LOC]] = loc(fused<#di_subprogram>
+module {
+  llvm.func @func_with_debug() {
+    llvm.return loc(#loc1)
+  } loc(#loc2)
+} loc(#loc)
+#di_file = #llvm.di_file<"<unknown>" in "">
+#di_subroutine_type = #llvm.di_subroutine_type<callingConvention = DW_CC_normal>
+#loc = loc("foo":0:0)
+#loc1 = loc(unknown)
+#di_compile_unit = #llvm.di_compile_unit<sourceLanguage = DW_LANG_C, file = #di_file, producer = "MLIR", isOptimized = true, emissionKind = LineTablesOnly>
+#di_subprogram = #llvm.di_subprogram<compileUnit = #di_compile_unit, scope = #di_file, name = "func_with_debug", linkageName = "func_with_debug", file = #di_file, line = 42, scopeLine = 42, subprogramFlags = "Definition|Optimized", type = #di_subroutine_type>
+#loc2 = loc(fused<#di_subprogram>[#loc1])
+
+// -----
+
+// Test that a compile unit present on a module op is used for the function.
+// CHECK-LABEL: llvm.func @propagate_compile_unit()
+// CHECK: llvm.return loc(#loc
+// CHECK: loc(#loc[[FUNCLOC:[0-9]+]])
+// CHECK: loc(#loc[[MODULELOC:[0-9]+]])
+// CHECK-DAG: #[[DI_FILE_MODULE:.+]] = #llvm.di_file<"bar.mlir" in "baz">
+// CHECK-DAG: #[[DI_FILE_FUNC:.+]] = #llvm.di_file<"file.mlir" in ""> 
+// CHECK-DAG: #loc[[FUNCFILELOC:[0-9]+]] = loc("file.mlir":9:8)
+// CHECK-DAG: #di_compile_unit = #llvm.di_compile_unit<sourceLanguage = DW_LANG_C, file = #[[DI_FILE_MODULE]], producer = "MLIR", isOptimized = true, emissionKind = LineTablesOnly>
+// CHECK-DAG: #di_subprogram = #llvm.di_subprogram<compileUnit = #di_compile_unit, scope = #[[DI_FILE_FUNC]], name = "propagate_compile_unit", linkageName = "propagate_compile_unit", file = #[[DI_FILE_FUNC]], line = 9, scopeLine = 8, subprogramFlags = "Definition|Optimized", type = #di_subroutine_type>
+// CHECK-DAG: #loc[[MODULELOC]] = loc(fused<#di_compile_unit>[#loc])
+// CHECK-DAG: #loc[[FUNCLOC]] = loc(fused<#di_subprogram>[#loc[[FUNCFILELOC]]
+module {
+  llvm.func @propagate_compile_unit() {
+    llvm.return loc(unknown)
+  } loc("file.mlir":9:8)
+} loc(#loc)
+#di_file = #llvm.di_file<"bar.mlir" in "baz">
+#di_compile_unit = #llvm.di_compile_unit<sourceLanguage = DW_LANG_C, file = #di_file, producer = "MLIR", isOptimized = true, emissionKind = LineTablesOnly>
+#loc = loc(fused<#di_compile_unit>["foo.mlir":2:1])


        


More information about the Mlir-commits mailing list