Emit DWARF debug information (#3420)
authorAndrew Tulloch <andrew@tullo.ch>
Thu, 18 Jul 2019 21:56:14 +0000 (14:56 -0700)
committerTianqi Chen <tqchen@users.noreply.github.com>
Thu, 18 Jul 2019 21:56:14 +0000 (14:56 -0700)
src/codegen/llvm/codegen_cpu.cc
src/codegen/llvm/codegen_cpu.h
src/codegen/llvm/codegen_llvm.cc
src/codegen/llvm/codegen_llvm.h
src/codegen/llvm/llvm_common.h
src/codegen/llvm/llvm_module.cc
tests/python/unittest/test_codegen_llvm.py

index bd0e947e01ab28d36082c67f55077e0c64c1d2a7..8fe5a3990dcd733e94c974455206224515f304c9 100644 (file)
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -128,6 +128,88 @@ void CodeGenCPU::AddFunction(const LoweredFunc& f) {
     export_system_symbols_.emplace_back(
         std::make_pair(f->name, builder_->CreatePointerCast(function_, t_void_p_)));
   }
+  AddDebugInformation(function_);
+}
+
+  // Following Glow |DebugInfo::generateFunctionDebugInfo|, https://git.io/fjadv
+void CodeGenCPU::AddDebugInformation(llvm::Function* function) {
+#if TVM_LLVM_VERSION >= 50
+  CHECK(!function->getSubprogram());
+  llvm::SmallVector<llvm::Metadata*, 4> paramTys;
+  llvm::DIType* returnTy =
+      getDebugType(builder_.get(), dbg_info_->di_builder_.get(), function->getReturnType());
+  paramTys.push_back(returnTy);
+  for (size_t i = 0; i < function->arg_size(); ++i) {
+    paramTys.push_back(getDebugType(builder_.get(), dbg_info_->di_builder_.get(),
+                                    function->getFunctionType()->getParamType(i)));
+  }
+  auto* DIFunctionTy = dbg_info_->di_builder_->createSubroutineType(
+      dbg_info_->di_builder_->getOrCreateTypeArray(paramTys));
+  auto* DIFunction = dbg_info_->di_builder_->createFunction(
+      dbg_info_->file_, function->getName(), "", dbg_info_->file_, 0 /* line number */,
+      DIFunctionTy, false /* internal linkage */, true /* definition */, 0 /* line number */,
+      llvm::DINode::FlagPrototyped, true /* isOptimized */);
+
+  CHECK(DIFunction);
+  function->setSubprogram(DIFunction);
+  CHECK_EQ(function->getSubprogram(), DIFunction);
+
+  IRBuilder builder(&function->getEntryBlock());
+  if (!function->getEntryBlock().empty()) {
+    builder.SetInsertPoint(&function->getEntryBlock().front());
+  }
+  llvm::DebugLoc DL;
+  builder.SetCurrentDebugLocation(DL);
+  for (size_t i = 0; i < function->arg_size(); ++i) {
+    auto* paramAlloca = builder.CreateAlloca(function->getFunctionType()->getParamType(i));
+    std::string paramName = "arg" + std::to_string(i + 1);
+    auto param = dbg_info_->di_builder_->createParameterVariable(
+        DIFunction, paramName, i + 1, dbg_info_->file_, 0,
+        getDebugType(builder_.get(), dbg_info_->di_builder_.get(),
+                     function->getFunctionType()->getParamType(i)),
+        /* alwaysPreserve */ true);
+    auto* store = builder.CreateStore(function->arg_begin() + i, paramAlloca);
+    dbg_info_->di_builder_->insertDeclare(paramAlloca, param,
+                                          dbg_info_->di_builder_->createExpression(),
+                                          llvm::DebugLoc::get(0, 0, DIFunction), store);
+  }
+  dbg_info_->di_builder_->finalizeSubprogram(function->getSubprogram());
+  auto* scope = function->getSubprogram();
+  if (!scope) {
+    return;
+  }
+  for (auto& BB : *function) {
+    for (auto& I : BB) {
+      if (I.getDebugLoc()) {
+        continue;
+      }
+      I.setDebugLoc(llvm::DebugLoc::get(0, 0, scope));
+    }
+  }
+#endif
+}
+
+llvm::DIType* CodeGenCPU::getDebugType(IRBuilder* builder, llvm::DIBuilder* di_builder,
+                                       llvm::Type* ty) {
+  if (ty == builder->getVoidTy()) {
+    return nullptr;
+  } else if (ty == builder->getFloatTy()) {
+    return di_builder->createBasicType("float", 32, llvm::dwarf::DW_ATE_float);
+  } else if (ty == builder->getInt8Ty()) {
+    return di_builder->createBasicType("int8", 8, llvm::dwarf::DW_ATE_signed);
+  } else if (ty == builder->getInt32Ty()) {
+    return di_builder->createBasicType("int32", 32, llvm::dwarf::DW_ATE_signed);
+  } else if (ty->isPointerTy()) {
+    return di_builder->createPointerType(
+        getDebugType(builder, di_builder, ty->getPointerElementType()),
+        ty->getPrimitiveSizeInBits());
+  } else {
+    std::string type_str;
+    llvm::raw_string_ostream rso(type_str);
+    ty->print(rso);
+    LOG(FATAL) << "Unknown LLVM type:" << rso.str();
+  }
+  return nullptr;
 }
 
 void CodeGenCPU::AddMainFunction(const std::string& entry_func_name) {
index 7f18f4a5071b2b98db4f555902b31535498c7af2..073d0780367f23734c67b0a2e94a4b538702a826 100644 (file)
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -139,6 +139,13 @@ class CodeGenCPU : public CodeGenLLVM {
   std::unordered_map<std::string, llvm::GlobalVariable*> func_handle_map_;
   // List of symbols to be exported to TVM system lib.
   std::vector<std::pair<std::string, llvm::Value*> > export_system_symbols_;
+
+  // Get the DWARF type corresponding to the LLVM type |ty|. The current API in practice only
+  // generates |int32|, and |int8*|.
+  static llvm::DIType* getDebugType(IRBuilder* builder, llvm::DIBuilder* di_builder,
+                                    llvm::Type* ty);
+  // Adds the DWARF debug information for |function| to |dbg_info_|.
+  void AddDebugInformation(llvm::Function* function);
 };
 
 }  // namespace codegen
index f90829fa5e536963bea74a5f5534b406a26f8c86..229b9c08ebc4279246900b4dc256e37ab41f1e21 100644 (file)
@@ -73,6 +73,7 @@ void CodeGenLLVM::Init(const std::string& module_name,
   md_tbaa_root_ = md_builder_->createTBAARoot("tvm-tbaa");
   md_tbaa_alias_set_ = md_builder_->createTBAANode("tvm-alias", md_tbaa_root_);
   this->InitTarget(tm);
+  dbg_info_ = CreateDebugInfo(module_.get());
 }
 
 void CodeGenLLVM::InitTarget(llvm::TargetMachine* tm) {
@@ -109,6 +110,7 @@ void CodeGenLLVM::InitFuncState() {
   analyzer_.reset(new arith::Analyzer());
 }
 
+
 void CodeGenLLVM::AddFunctionInternal(const LoweredFunc& f, bool ret_void) {
   this->InitFuncState();
   std::vector<llvm::Type*> arg_types;
@@ -166,9 +168,11 @@ void CodeGenLLVM::AddFunctionInternal(const LoweredFunc& f, bool ret_void) {
   }
 }
 
+
 std::unique_ptr<llvm::Module> CodeGenLLVM::Finish() {
   this->AddStartupFunction();
   // link modules
+  dbg_info_->di_builder_->finalize();
   for (size_t i = 0; i < link_modules_.size(); ++i) {
     CHECK(!llvm::Linker::linkModules(*module_, std::move(link_modules_[i])))
         << "Failed to link modules";
@@ -419,6 +423,19 @@ void CodeGenLLVM::GetAlignment(Type t,
   *p_alignment = align_bits / 8;
 }
 
+std::unique_ptr<CodeGenLLVM::DebugInfo> CodeGenLLVM::CreateDebugInfo(llvm::Module* module) {
+  auto debug_info = llvm::make_unique<CodeGenLLVM::DebugInfo>();
+  debug_info->di_builder_ = llvm::make_unique<llvm::DIBuilder>(*module);
+  // TODO(tulloch): pass this information through relay::Span classes to the LoweredFunc instance?
+  debug_info->file_ = debug_info->di_builder_->createFile("model.tvm", "/tmp/");
+  debug_info->compilation_unit_ = debug_info->di_builder_->createCompileUnit(
+      llvm::dwarf::DW_LANG_C, debug_info->file_, "TVM", 0, "", 0, "",
+      llvm::DICompileUnit::DebugEmissionKind::FullDebug,
+      /* SplitDebugInlining */ true,
+      /* DebugInfoForProfiling */ true);
+  return debug_info;
+}
+
 llvm::Value* CodeGenLLVM::CreateBroadcast(llvm::Value* value, int lanes) {
   llvm::Constant* undef = llvm::UndefValue::get(
       llvm::VectorType::get(value->getType(), lanes));
index 0006b4cabbaf3eae204c665dac74eab6688c52db..fc986c8be4bfdf4674333933777e746c016c5b99 100644 (file)
@@ -293,6 +293,17 @@ class CodeGenLLVM :
   std::unordered_set<const Variable*> alias_var_set_;
   // set of volatile buffer.
   std::unordered_set<const Variable*> volatile_buf_;
+
+  struct DebugInfo {
+    std::unique_ptr<llvm::DIBuilder> di_builder_;
+    llvm::DICompileUnit* compilation_unit_{nullptr};
+    llvm::DIFile* file_{nullptr};
+  };
+  std::unique_ptr<DebugInfo> dbg_info_;
+
+  // Create a new DebugInfo struct from the given Module that initializes the |file_| and
+  // |compilation_unit_| to TVM defaults.
+  static std::unique_ptr<DebugInfo> CreateDebugInfo(llvm::Module* module);
 };
 }  // namespace codegen
 }  // namespace tvm
index 3d0f6c9da36455dffac9e37d70a20427ad868409..33ea8ebd19addcf34dcdc95167018d0ab66ebf63 100644 (file)
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -38,6 +38,7 @@
 #include <llvm/IR/BasicBlock.h>
 #include <llvm/IR/Constants.h>
 #include <llvm/IR/DerivedTypes.h>
+#include <llvm/IR/DIBuilder.h>
 #include <llvm/IR/Function.h>
 #include <llvm/IR/IRBuilder.h>
 #include <llvm/IR/Instructions.h>
index 2b3f316eaba57bb985c236f1e772fd83e84a33b9..554aec3289005390b6751231bba2e35f0befb292 100644 (file)
@@ -187,14 +187,20 @@ class LLVMModuleNode final : public runtime::ModuleNode {
     }
     cg->AddMainFunction(funcs[0]->name);
     module_ = cg->Finish();
+
+    module_->addModuleFlag(llvm::Module::Warning, "tvm_target", llvm::MDString::get(*ctx_, target));
+    module_->addModuleFlag(llvm::Module::Override, "Debug Info Version",
+                           llvm::DEBUG_METADATA_VERSION);
+
+    if (tm_->getTargetTriple().isOSDarwin()) {
+      module_->addModuleFlag(llvm::Module::Override, "Dwarf Version", 2);
+    }
+
     std::string verify_errors_storage;
     llvm::raw_string_ostream verify_errors(verify_errors_storage);
     LOG_IF(FATAL, llvm::verifyModule(*module_, &verify_errors))
         << "LLVM module verification failed with the following errors: \n"
         << verify_errors.str();
-    module_->addModuleFlag(
-        llvm::Module::Warning, "tvm_target",
-        llvm::MDString::get(*ctx_, target));
     target_ = target;
     mptr_ = module_.get();
   }
index 81a5e7c74d599b0c0c0220e66d0448240ae78442..526f2d70d74a5e1f1cb452b7050e6f667099feeb 100644 (file)
@@ -471,6 +471,78 @@ def test_llvm_fp_math():
     check_llvm_sigmoid(8)
     check_llvm_sigmoid(16)
 
+
+def test_dwarf_debug_information():
+    nn = 1024
+    n = tvm.convert(nn)
+    A = tvm.placeholder((n,), name='A')
+    B = tvm.placeholder((n,), name='B')
+    C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C')
+    s = tvm.create_schedule(C.op)
+    xo, xi = s[C].split(C.op.axis[0], factor=4)
+    s[C].parallel(xo)
+    s[C].vectorize(xi)
+    def check_llvm_object():
+        if not tvm.module.enabled("llvm"):
+            return
+        if tvm.codegen.llvm_version_major() < 5:
+            return
+        # build two functions
+        f2 = tvm.lower(s, [A, B, C], name="fadd1")
+        f1 = tvm.lower(s, [A, B, C], name="fadd2")
+        m = tvm.build([f1, f2], "llvm")
+        temp = util.tempdir()
+        o_path = temp.relpath("temp.o")
+        m.save(o_path)
+        import re
+        import shutil
+        import subprocess
+        import sys
+
+        # Try the dwarfdump utility (OS X)
+        if shutil.which("dwarfdump"):
+            output = subprocess.check_output(["dwarfdump", o_path])
+            assert re.search(r"""DW_AT_name\\t\("fadd1"\)""", str(output))
+            assert re.search(r"""DW_AT_name\\t\("fadd2"\)""", str(output))
+
+        # Try gobjdump (OS X)
+        if shutil.which("gobjdump"):
+            output = subprocess.check_output(["gobjdump", "--dwarf", o_path])
+            assert re.search(r"""DW_AT_name.*fadd1""", str(output))
+            assert re.search(r"""DW_AT_name.*fadd2""", str(output))
+
+        # Try objdump (Linux) - Darwin objdump has different DWARF syntax.
+        if shutil.which("objdump") and sys.platform != 'darwin':
+            output = subprocess.check_output(["objdump", "--dwarf", o_path])
+            assert re.search(r"""DW_AT_name.*fadd1""", str(output))
+            assert re.search(r"""DW_AT_name.*fadd2""", str(output))
+
+    def check_llvm_ir():
+        if not tvm.module.enabled("llvm"):
+            return
+        if tvm.codegen.llvm_version_major() < 5:
+            return
+        # build two functions
+        f2 = tvm.lower(s, [A, B, C], name="fadd1")
+        f1 = tvm.lower(s, [A, B, C], name="fadd2")
+        m = tvm.build([f1, f2], target="llvm -target=aarch64-linux-gnu")
+        ll = m.get_source("ll")
+
+        # On non-Darwin OS, don't explicitly specify DWARF version.
+        import re
+        assert not re.search(r""""Dwarf Version""""", ll)
+        assert re.search(r"""llvm.dbg.value""", ll)
+
+        # Try Darwin, require DWARF-2
+        m = tvm.build([f1, f2],
+                      target="llvm -target=x86_64-apple-darwin-macho")
+        ll = m.get_source("ll")
+        assert re.search(r"""i32 4, !"Dwarf Version", i32 2""", ll)
+        assert re.search(r"""llvm.dbg.value""", ll)
+
+    check_llvm_object()
+    check_llvm_ir()
+
 if __name__ == "__main__":
     test_llvm_import()
     test_alignment()
@@ -489,3 +561,4 @@ if __name__ == "__main__":
     test_llvm_lookup_intrin()
     test_llvm_div()
     test_llvm_fp_math()
+    test_dwarf_debug_information()