blob: 5b00585b8aebd5ce1dbae05becad1d102b1b20bf [file] [log] [blame]
// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed 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 KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "LLVMReactorDebugInfo.hpp"
#ifdef ENABLE_RR_DEBUG_INFO
#include "Reactor.hpp"
#include "LLVMReactor.hpp"
#include "backtrace.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/ExecutionEngine/JITEventListener.h"
#include "llvm/IR/DIBuilder.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/IRBuilder.h"
#include <cctype>
#include <fstream>
#include <regex>
#include <sstream>
#include <string>
#if 0
#define LOG(msg, ...) printf(msg "\n", ##__VA_ARGS__)
#else
#define LOG(msg, ...)
#endif
namespace
{
std::pair<llvm::StringRef, llvm::StringRef> splitPath(const char* path)
{
return llvm::StringRef(path).rsplit('/');
}
} // anonymous namespaces
namespace rr
{
DebugInfo::DebugInfo(
llvm::IRBuilder<> *builder,
llvm::LLVMContext *context,
llvm::Module *module,
llvm::Function *function)
: builder(builder), context(context), module(module), function(function)
{
using namespace ::llvm;
auto location = getCallerLocation();
auto fileAndDir = splitPath(location.function.file.c_str());
diBuilder = new llvm::DIBuilder(*module);
diCU = diBuilder->createCompileUnit(
llvm::dwarf::DW_LANG_C,
diBuilder->createFile(fileAndDir.first, fileAndDir.second),
"Reactor",
0, "", 0);
jitEventListener = llvm::JITEventListener::createGDBRegistrationListener();
registerBasicTypes();
SmallVector<Metadata *, 8> EltTys;
auto funcTy = diBuilder->createSubroutineType(diBuilder->getOrCreateTypeArray(EltTys));
auto file = getOrCreateFile(location.function.file.c_str());
auto sp = diBuilder->createFunction(
file, // scope
"ReactorFunction", // function name
"ReactorFunction", // linkage
file, // file
location.line, // line
funcTy, // type
false, // internal linkage
true, // definition
location.line, // scope line
DINode::FlagPrototyped, // flags
false // is optimized
);
diSubprogram = sp;
function->setSubprogram(sp);
diRootLocation = DILocation::get(*context, location.line, 0, sp);
builder->SetCurrentDebugLocation(diRootLocation);
}
void DebugInfo::Finalize()
{
while (diScope.size() > 0)
{
emitPending(diScope.back(), builder, diBuilder);
diScope.pop_back();
}
diBuilder->finalize();
}
void DebugInfo::EmitLocation()
{
auto const& backtrace = getCallerBacktrace();
syncScope(backtrace);
builder->SetCurrentDebugLocation(getLocation(backtrace, backtrace.size() - 1));
}
void DebugInfo::Flush()
{
emitPending(diScope.back(), builder, diBuilder);
}
void DebugInfo::syncScope(Backtrace const& backtrace)
{
auto shrink = [this](size_t newsize)
{
while (diScope.size() > newsize)
{
auto &scope = diScope.back();
LOG("- STACK(%d): di: %p, location: %s:%d",
int(diScope.size() - 1), scope.di,
scope.location.function.file.c_str(),
int(scope.location.line));
emitPending(scope, builder, diBuilder);
diScope.pop_back();
}
};
if (backtrace.size() < diScope.size())
{
shrink(backtrace.size());
}
for (size_t i = 0; i < diScope.size(); i++)
{
auto &scope = diScope[i];
auto const &oldLocation = scope.location;
auto const &newLocation = backtrace[i];
if (oldLocation.function != newLocation.function)
{
LOG(" STACK(%d): Changed function %s -> %s", int(i),
oldLocation.function.name.c_str(), newLocation.function.name.c_str());
shrink(i);
break;
}
if (oldLocation.line > newLocation.line)
{
// Create a new di block to shadow all the variables in the loop.
auto file = getOrCreateFile(newLocation.function.file.c_str());
auto di = diBuilder->createLexicalBlock(scope.di, file, newLocation.line, 0);
LOG(" STACK(%d): Jumped backwards %d -> %d. di: %p -> %p", int(i),
oldLocation.line, newLocation.line, scope.di, di);
emitPending(scope, builder, diBuilder);
scope = {newLocation, di};
shrink(i+1);
break;
}
scope.location = newLocation;
}
while (backtrace.size() > diScope.size())
{
auto i = diScope.size();
auto location = backtrace[i];
auto file = getOrCreateFile(location.function.file.c_str());
auto funcTy = diBuilder->createSubroutineType(diBuilder->getOrCreateTypeArray({}));
char buf[1024];
size_t size = sizeof(buf);
int status = 0;
llvm::itaniumDemangle(location.function.name.c_str(), buf, &size, &status);
auto name = status == 0 ? buf : location.function.name.c_str();
auto func = diBuilder->createFunction(
file, // scope
name, // function name
"", // linkage
file, // file
location.line, // line
funcTy, // type
false, // internal linkage
true, // definition
location.line, // scope line
llvm::DINode::FlagPrototyped, // flags
false // is optimized
);
diScope.push_back({location, func});
LOG("+ STACK(%d): di: %p, location: %s:%d", int(i), di,
location.function.file.c_str(), int(location.line));
}
}
llvm::DILocation* DebugInfo::getLocation(const Backtrace &backtrace, size_t i)
{
if (backtrace.size() == 0) { return nullptr; }
assert(backtrace.size() == diScope.size());
return llvm::DILocation::get(
*context,
backtrace[i].line,
0,
diScope[i].di,
i > 0 ? getLocation(backtrace, i - 1) : diRootLocation
);
}
void DebugInfo::EmitVariable(Value *variable)
{
auto const& backtrace = getCallerBacktrace();
syncScope(backtrace);
for (int i = backtrace.size() - 1; i >= 0; i--)
{
auto const &location = backtrace[i];
auto tokens = getOrParseFileTokens(location.function.file.c_str());
auto tokIt = tokens->find(location.line);
if (tokIt == tokens->end())
{
break;
}
auto token = tokIt->second;
auto name = token.identifier;
if (token.kind == Token::Return)
{
// This is a:
//
// return <expr>;
//
// Emit this expression as two variables -
// Once as a synthetic 'return_value' variable at this scope.
// Again by bubbling the expression value up the callstack as
// Return Value Optimizations (RVOs) are likely to carry across
// the value to a local without calling a constructor in
// statements like:
//
// auto val = foo();
//
name = "return_value";
}
auto &scope = diScope[i];
if (scope.pending.location != location)
{
emitPending(scope, builder, diBuilder);
}
auto value = V(variable);
auto block = builder->GetInsertBlock();
auto insertAfter = block->size() > 0 ? &block->back() : nullptr;
while (insertAfter != nullptr && insertAfter->isTerminator())
{
insertAfter = insertAfter->getPrevNode();
}
scope.pending = Pending{};
scope.pending.name = name;
scope.pending.location = location;
scope.pending.diLocation = getLocation(backtrace, i);
scope.pending.value = value;
scope.pending.block = block;
scope.pending.insertAfter = insertAfter;
scope.pending.scope = scope.di;
if (token.kind == Token::Return)
{
// Insert a noop instruction so the debugger can inspect the
// return value before the function scope closes.
scope.pending.addNopOnNextLine = true;
}
else
{
break;
}
}
}
void DebugInfo::emitPending(Scope &scope, IRBuilder *builder, llvm::DIBuilder *diBuilder)
{
auto const &pending = scope.pending;
if (pending.value == nullptr)
{
return;
}
if (!scope.symbols.emplace(pending.name).second)
{
return;
}
bool isAlloca = llvm::isa<llvm::AllocaInst>(pending.value);
LOG(" EMIT(%s): di: %p, location: %s:%d, isAlloca: %s", pending.name.c_str(), scope.di,
pending.location.function.file.c_str(), pending.location.line, isAlloca ? "true" : "false");
auto value = pending.value;
IRBuilder::InsertPointGuard guard(*builder);
if (pending.insertAfter != nullptr)
{
builder->SetInsertPoint(pending.block, ++pending.insertAfter->getIterator());
}
else
{
builder->SetInsertPoint(pending.block);
}
builder->SetCurrentDebugLocation(pending.diLocation);
if (!isAlloca)
{
// While insertDbgValueIntrinsic should be enough to declare a
// variable with no storage, variables of RValues can share the same
// llvm::Value, and only one can be named. Take for example:
//
// Int a = 42;
// RValue<Int> b = a;
// RValue<Int> c = b;
//
// To handle this, always promote named RValues to an alloca.
llvm::BasicBlock &entryBlock = function->getEntryBlock();
auto alloca = new llvm::AllocaInst(value->getType(), 0, pending.name);
entryBlock.getInstList().push_front(alloca);
builder->CreateStore(value, alloca);
value = alloca;
}
value->setName(pending.name);
auto diFile = getOrCreateFile(pending.location.function.file.c_str());
auto diType = getOrCreateType(value->getType()->getPointerElementType());
auto diVar = diBuilder->createAutoVariable(scope.di, pending.name, diFile, pending.location.line, diType);
auto di = diBuilder->insertDeclare(value, diVar, diBuilder->createExpression(), pending.diLocation, pending.block);
if (pending.insertAfter != nullptr) { di->moveAfter(pending.insertAfter); }
if (pending.addNopOnNextLine)
{
builder->SetCurrentDebugLocation(llvm::DILocation::get(
*context,
pending.diLocation->getLine() + 1,
0,
pending.diLocation->getScope(),
pending.diLocation->getInlinedAt()
));
Nop();
}
scope.pending = Pending{};
}
void DebugInfo::NotifyObjectEmitted(const llvm::object::ObjectFile &Obj, const llvm::LoadedObjectInfo &L)
{
jitEventListener->NotifyObjectEmitted(Obj, static_cast<const llvm::RuntimeDyld::LoadedObjectInfo&>(L));
}
void DebugInfo::NotifyFreeingObject(const llvm::object::ObjectFile &Obj)
{
jitEventListener->NotifyFreeingObject(Obj);
}
void DebugInfo::registerBasicTypes()
{
using namespace rr;
using namespace llvm;
auto vec4 = diBuilder->getOrCreateArray(diBuilder->getOrCreateSubrange(0, 4));
auto vec8 = diBuilder->getOrCreateArray(diBuilder->getOrCreateSubrange(0, 8));
auto vec16 = diBuilder->getOrCreateArray(diBuilder->getOrCreateSubrange(0, 16));
diTypes.emplace(T(Bool::getType()), diBuilder->createBasicType("Bool", sizeof(bool), dwarf::DW_ATE_boolean));
diTypes.emplace(T(Byte::getType()), diBuilder->createBasicType("Byte", 8, dwarf::DW_ATE_unsigned_char));
diTypes.emplace(T(SByte::getType()), diBuilder->createBasicType("SByte", 8, dwarf::DW_ATE_signed_char));
diTypes.emplace(T(Short::getType()), diBuilder->createBasicType("Short", 16, dwarf::DW_ATE_signed));
diTypes.emplace(T(UShort::getType()), diBuilder->createBasicType("UShort", 16, dwarf::DW_ATE_unsigned));
diTypes.emplace(T(Int::getType()), diBuilder->createBasicType("Int", 32, dwarf::DW_ATE_signed));
diTypes.emplace(T(UInt::getType()), diBuilder->createBasicType("UInt", 32, dwarf::DW_ATE_unsigned));
diTypes.emplace(T(Long::getType()), diBuilder->createBasicType("Long", 64, dwarf::DW_ATE_signed));
diTypes.emplace(T(Half::getType()), diBuilder->createBasicType("Half", 16, dwarf::DW_ATE_float));
diTypes.emplace(T(Float::getType()), diBuilder->createBasicType("Float", 32, dwarf::DW_ATE_float));
diTypes.emplace(T(Byte4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::getType())], {vec16}));
diTypes.emplace(T(SByte4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::getType())], {vec16}));
diTypes.emplace(T(Byte8::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::getType())], {vec16}));
diTypes.emplace(T(SByte8::getType()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::getType())], {vec16}));
diTypes.emplace(T(Byte16::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::getType())], {vec16}));
diTypes.emplace(T(SByte16::getType()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::getType())], {vec16}));
diTypes.emplace(T(Short2::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Short::getType())], {vec8}));
diTypes.emplace(T(UShort2::getType()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::getType())], {vec8}));
diTypes.emplace(T(Short4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Short::getType())], {vec8}));
diTypes.emplace(T(UShort4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::getType())], {vec8}));
diTypes.emplace(T(Short8::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Short::getType())], {vec8}));
diTypes.emplace(T(UShort8::getType()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::getType())], {vec8}));
diTypes.emplace(T(Int2::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Int::getType())], {vec4}));
diTypes.emplace(T(UInt2::getType()), diBuilder->createVectorType(128, 128, diTypes[T(UInt::getType())], {vec4}));
diTypes.emplace(T(Int4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Int::getType())], {vec4}));
diTypes.emplace(T(UInt4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(UInt::getType())], {vec4}));
diTypes.emplace(T(Float2::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Float::getType())], {vec4}));
diTypes.emplace(T(Float4::getType()), diBuilder->createVectorType(128, 128, diTypes[T(Float::getType())], {vec4}));
}
DebugInfo::Location DebugInfo::getCallerLocation() const
{
return getCallerBacktrace(1)[0];
}
DebugInfo::Backtrace DebugInfo::getCallerBacktrace(size_t limit /* = 0 */) const
{
struct callbacks
{
static void onError(void *data, const char *msg, int errnum)
{
fprintf(stderr, "BACKTRACE ERROR %d: %s\n", errnum, msg);
}
static int onPCInfo(void *data, uintptr_t pc, const char *file, int line, const char *function)
{
if (file == nullptr) { return 0; }
auto const &fileSR = llvm::StringRef(file);
if (fileSR.endswith("ReactorDebugInfo.cpp") ||
fileSR.endswith("Reactor.cpp") ||
fileSR.endswith("Reactor.hpp"))
{
return 0;
}
auto cb = reinterpret_cast<callbacks*>(data);
Location location;
location.function.file = file;
location.function.name = function;
location.line = line;
cb->locations.push_back(location);
return (cb->limit == 0 || sizeof(cb->locations) < cb->limit) ? 0 : 1;
}
size_t limit;
std::vector<DebugInfo::Location> locations;
};
callbacks callbacks;
callbacks.limit = limit;
static auto state = backtrace_create_state(nullptr, 0, &callbacks::onError, nullptr);
backtrace_full(state, 1, &callbacks::onPCInfo, &callbacks::onError, &callbacks);
std::reverse(callbacks.locations.begin(), callbacks.locations.end());
return callbacks.locations;
}
llvm::DIType *DebugInfo::getOrCreateType(llvm::Type* type)
{
auto it = diTypes.find(type);
if (it != diTypes.end()) { return it->second; }
if(type->isPointerTy())
{
auto dbgTy = diBuilder->createPointerType(
getOrCreateType(type->getPointerElementType()),
sizeof(void*)*8, alignof(void*)*8);
diTypes.emplace(type, dbgTy);
return dbgTy;
}
llvm::errs() << "Unimplemented debug type: " << type << "\n";
assert(false);
return nullptr;
}
llvm::DIFile *DebugInfo::getOrCreateFile(const char* path)
{
auto it = diFiles.find(path);
if (it != diFiles.end()) { return it->second; }
auto dirAndName = splitPath(path);
auto file = diBuilder->createFile(dirAndName.second, dirAndName.first);
diFiles.emplace(path, file);
return file;
}
DebugInfo::LineTokens const *DebugInfo::getOrParseFileTokens(const char* path)
{
static std::regex reLocalDecl(
"^" // line start
"\\s*" // initial whitespace
"(?:For\\s*\\(\\s*)?" // optional 'For ('
"((?:\\w+(?:<[^>]+>)?)(?:::\\w+(?:<[^>]+>)?)*)" // type (match group 1)
"\\s+" // whitespace between type and name
"(\\w+)" // identifier (match group 2)
"\\s*" // whitespace after identifier
"(\\[.*\\])?"); // optional array suffix (match group 3)
auto it = fileTokens.find(path);
if (it != fileTokens.end())
{
return it->second.get();
}
auto tokens = std::unique_ptr<LineTokens>(new LineTokens());
std::ifstream file(path);
std::string line;
int lineCount = 0;
while (std::getline(file, line))
{
lineCount++;
std::smatch match;
if (std::regex_search(line, match, reLocalDecl) && match.size() > 3)
{
bool isArray = match.str(3) != "";
if (!isArray) // Cannot deal with C-arrays of values.
{
if (match.str(1) == "return")
{
(*tokens)[lineCount] = Token{Token::Return};
}
else
{
(*tokens)[lineCount] = Token{Token::Identifier, match.str(2)};
}
}
}
}
auto out = tokens.get();
fileTokens.emplace(path, std::move(tokens));
return out;
}
} // namespace rr
#endif // ENABLE_RR_DEBUG_INFO