blob: 04c12e5d89efc3c70715faf4c4458159bc2c7e5d [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 "LLVMReactor.hpp"
# include "Reactor.hpp"
# include "Print.hpp"
# include "boost/stacktrace.hpp"
// TODO(b/143539525): Eliminate when warning has been fixed.
# ifdef _MSC_VER
__pragma(warning(push))
__pragma(warning(disable : 4146)) // unary minus operator applied to unsigned type, result still unsigned
# endif
# include "llvm/Demangle/Demangle.h"
# include "llvm/ExecutionEngine/JITEventListener.h"
# include "llvm/IR/DIBuilder.h"
# include "llvm/IR/IRBuilder.h"
# include "llvm/IR/Intrinsics.h"
# ifdef _MSC_VER
__pragma(warning(pop))
# endif
# include <cctype>
# include <fstream>
# include <mutex>
# 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('/');
}
// Note: createGDBRegistrationListener() returns a pointer to a singleton.
// Nothing is actually created.
auto jitEventListener = llvm::JITEventListener::createGDBRegistrationListener(); // guarded by jitEventListenerMutex
std::mutex jitEventListenerMutex;
} // namespace
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.reset(new llvm::DIBuilder(*module));
diCU = diBuilder->createCompileUnit(
llvm::dwarf::DW_LANG_C,
diBuilder->createFile(fileAndDir.first, fileAndDir.second),
"Reactor",
0, "", 0);
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
location.line, // scope line
DINode::FlagPrototyped, // flags
DISubprogram::SPFlagDefinition // subprogram flags
);
diSubprogram = sp;
function->setSubprogram(sp);
diRootLocation = DILocation::get(*context, location.line, 0, sp);
builder->SetCurrentDebugLocation(diRootLocation);
}
DebugInfo::~DebugInfo() = default;
void DebugInfo::Finalize()
{
while(diScope.size() > 0)
{
emitPending(diScope.back(), builder);
diScope.pop_back();
}
diBuilder->finalize();
}
void DebugInfo::EmitLocation()
{
auto const &backtrace = getCallerBacktrace();
syncScope(backtrace);
builder->SetCurrentDebugLocation(getLocation(backtrace, backtrace.size() - 1));
emitPrintLocation(backtrace);
}
void DebugInfo::Flush()
{
emitPending(diScope.back(), builder);
}
void DebugInfo::syncScope(Backtrace const &backtrace)
{
using namespace ::llvm;
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);
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);
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 = "jit!" + (status == 0 ? std::string(buf) : location.function.name);
auto func = diBuilder->createFunction(
file, // scope
name, // function name
"", // linkage
file, // file
location.line, // line
funcTy, // type
location.line, // scope line
DINode::FlagPrototyped, // flags
DISubprogram::SPFlagDefinition // subprogram flags
);
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);
}
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)
{
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)
{
std::unique_lock<std::mutex> lock(jitEventListenerMutex);
auto key = reinterpret_cast<llvm::JITEventListener::ObjectKey>(&Obj);
jitEventListener->notifyObjectLoaded(key, Obj, static_cast<const llvm::RuntimeDyld::LoadedObjectInfo &>(L));
}
void DebugInfo::NotifyFreeingObject(const llvm::object::ObjectFile &Obj)
{
std::unique_lock<std::mutex> lock(jitEventListenerMutex);
auto key = reinterpret_cast<llvm::JITEventListener::ObjectKey>(&Obj);
jitEventListener->notifyFreeingObject(key);
}
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::type()), diBuilder->createBasicType("Bool", sizeof(bool), dwarf::DW_ATE_boolean));
diTypes.emplace(T(Byte::type()), diBuilder->createBasicType("Byte", 8, dwarf::DW_ATE_unsigned_char));
diTypes.emplace(T(SByte::type()), diBuilder->createBasicType("SByte", 8, dwarf::DW_ATE_signed_char));
diTypes.emplace(T(Short::type()), diBuilder->createBasicType("Short", 16, dwarf::DW_ATE_signed));
diTypes.emplace(T(UShort::type()), diBuilder->createBasicType("UShort", 16, dwarf::DW_ATE_unsigned));
diTypes.emplace(T(Int::type()), diBuilder->createBasicType("Int", 32, dwarf::DW_ATE_signed));
diTypes.emplace(T(UInt::type()), diBuilder->createBasicType("UInt", 32, dwarf::DW_ATE_unsigned));
diTypes.emplace(T(Long::type()), diBuilder->createBasicType("Long", 64, dwarf::DW_ATE_signed));
diTypes.emplace(T(Half::type()), diBuilder->createBasicType("Half", 16, dwarf::DW_ATE_float));
diTypes.emplace(T(Float::type()), diBuilder->createBasicType("Float", 32, dwarf::DW_ATE_float));
diTypes.emplace(T(Byte4::type()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::type())], { vec16 }));
diTypes.emplace(T(SByte4::type()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::type())], { vec16 }));
diTypes.emplace(T(Byte8::type()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::type())], { vec16 }));
diTypes.emplace(T(SByte8::type()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::type())], { vec16 }));
diTypes.emplace(T(Byte16::type()), diBuilder->createVectorType(128, 128, diTypes[T(Byte::type())], { vec16 }));
diTypes.emplace(T(SByte16::type()), diBuilder->createVectorType(128, 128, diTypes[T(SByte::type())], { vec16 }));
diTypes.emplace(T(Short2::type()), diBuilder->createVectorType(128, 128, diTypes[T(Short::type())], { vec8 }));
diTypes.emplace(T(UShort2::type()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::type())], { vec8 }));
diTypes.emplace(T(Short4::type()), diBuilder->createVectorType(128, 128, diTypes[T(Short::type())], { vec8 }));
diTypes.emplace(T(UShort4::type()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::type())], { vec8 }));
diTypes.emplace(T(Short8::type()), diBuilder->createVectorType(128, 128, diTypes[T(Short::type())], { vec8 }));
diTypes.emplace(T(UShort8::type()), diBuilder->createVectorType(128, 128, diTypes[T(UShort::type())], { vec8 }));
diTypes.emplace(T(Int2::type()), diBuilder->createVectorType(128, 128, diTypes[T(Int::type())], { vec4 }));
diTypes.emplace(T(UInt2::type()), diBuilder->createVectorType(128, 128, diTypes[T(UInt::type())], { vec4 }));
diTypes.emplace(T(Int4::type()), diBuilder->createVectorType(128, 128, diTypes[T(Int::type())], { vec4 }));
diTypes.emplace(T(UInt4::type()), diBuilder->createVectorType(128, 128, diTypes[T(UInt::type())], { vec4 }));
diTypes.emplace(T(Float2::type()), diBuilder->createVectorType(128, 128, diTypes[T(Float::type())], { vec4 }));
diTypes.emplace(T(Float4::type()), diBuilder->createVectorType(128, 128, diTypes[T(Float::type())], { vec4 }));
}
Location DebugInfo::getCallerLocation() const
{
return getCallerBacktrace(1)[0];
}
Backtrace DebugInfo::getCallerBacktrace(size_t limit /* = 0 */) const
{
return rr::getCallerBacktrace(limit);
}
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::make_unique<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