Commit d82db909 by Andrew Tulloch Committed by Tianqi Chen

Emit DWARF debug information (#3420)

parent e686da79
......@@ -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) {
......
......@@ -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
......
......@@ -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));
......
......@@ -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
......
......@@ -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>
......
......@@ -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();
}
......
......@@ -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()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment