diff --git a/CMakeLists.txt b/CMakeLists.txt index f60cfcc1..5f0c9979 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ llvm_map_components_to_libnames(LLVM_LIBS Remarks ScalarOpts Support + Symbolize Target TransformUtils Vectorize diff --git a/codon/app/main.cpp b/codon/app/main.cpp index 5a23e45e..7fffcb1c 100644 --- a/codon/app/main.cpp +++ b/codon/app/main.cpp @@ -79,7 +79,8 @@ int docMode(const std::vector &args, const std::string &argv0) { return EXIT_SUCCESS; } -std::unique_ptr processSource(const std::vector &args) { +std::unique_ptr processSource(const std::vector &args, + bool standalone) { llvm::cl::opt input(llvm::cl::Positional, llvm::cl::desc(""), llvm::cl::init("-")); llvm::cl::opt optMode( @@ -133,6 +134,7 @@ std::unique_ptr processSource(const std::vector & const bool isDebug = (optMode == OptMode::Debug); std::vector disabledOptsVec(disabledOpts); auto compiler = std::make_unique(args[0], isDebug, disabledOptsVec); + compiler->getLLVMVisitor()->setStandalone(standalone); // load plugins for (const auto &plugin : plugins) { @@ -168,7 +170,7 @@ int runMode(const std::vector &args) { "l", llvm::cl::desc("Load and link the specified library")); llvm::cl::list seqArgs(llvm::cl::ConsumeAfter, llvm::cl::desc("...")); - auto compiler = processSource(args); + auto compiler = processSource(args, /*standalone=*/false); if (!compiler) return EXIT_FAILURE; std::vector libsVec(libs); @@ -229,7 +231,7 @@ int buildMode(const std::vector &args) { "Write compiled output to specified file. Supported extensions: " "none (executable), .o (object file), .ll (LLVM IR), .bc (LLVM bitcode)")); - auto compiler = processSource(args); + auto compiler = processSource(args, /*standalone=*/true); if (!compiler) return EXIT_FAILURE; std::vector libsVec(libs); diff --git a/codon/compiler/compiler.cpp b/codon/compiler/compiler.cpp index b9337162..55e8153f 100644 --- a/codon/compiler/compiler.cpp +++ b/codon/compiler/compiler.cpp @@ -19,9 +19,10 @@ Compiler::Compiler(const std::string &argv0, bool debug, module(std::make_unique()), pm(std::make_unique(debug && !isTest, disabledPasses)), - llvisitor(std::make_unique(debug)) { + llvisitor(std::make_unique()) { cache->module = module.get(); module->setCache(cache.get()); + llvisitor->setDebug(debug); llvisitor->setPluginManager(plm.get()); } diff --git a/codon/compiler/jit.cpp b/codon/compiler/jit.cpp index 7a6574f2..c3886a69 100644 --- a/codon/compiler/jit.cpp +++ b/codon/compiler/jit.cpp @@ -95,7 +95,7 @@ llvm::Expected JIT::run(const ir::Func *input, try { CaptureOutput(buffer.rdbuf()); (*repl)(); - } catch (const seq_jit_error &err) { + } catch (const JITError &err) { return llvm::make_error(err.getOutput(), err.getType(), err.what(), err.getFile(), err.getLine(), err.getCol()); diff --git a/codon/runtime/exc.cpp b/codon/runtime/exc.cpp index 68c55a57..90f30cc9 100644 --- a/codon/runtime/exc.cpp +++ b/codon/runtime/exc.cpp @@ -7,10 +7,14 @@ #include #include #include +#include #include #include #include +#define UNW_LOCAL_ONLY +#include + struct BacktraceFrame { char *function; char *filename; @@ -42,6 +46,8 @@ struct Backtrace { frames[count++] = {functionDup, filenameDup, pc, lineno}; } + void push_back(uintptr_t pc) { push_back("", "", pc, 0); } + void free() { for (auto i = 0; i < count; i++) { auto *frame = &frames[i]; @@ -55,7 +61,7 @@ struct Backtrace { }; void seq_backtrace_error_callback(void *data, const char *msg, int errnum) { - // nothing to do + // printf("seq_backtrace_error_callback: %s (errnum = %d)\n", msg, errnum); } int seq_backtrace_full_callback(void *data, uintptr_t pc, const char *filename, @@ -140,6 +146,7 @@ static void seq_delete_unwind_exc(_Unwind_Reason_Code reason, } static struct backtrace_state *state = nullptr; +static std::mutex stateLock; SEQ_FUNC void *seq_alloc_exc(int type, void *obj) { const size_t size = sizeof(OurException); @@ -152,11 +159,33 @@ SEQ_FUNC void *seq_alloc_exc(int type, void *obj) { if (seq_flags & SEQ_FLAG_DEBUG) { e->bt.frames = nullptr; e->bt.count = 0; - if (!state) - state = backtrace_create_state(/*filename=*/nullptr, /*threaded=*/0, + + if (seq_flags & SEQ_FLAG_STANDALONE) { + if (!state) { + stateLock.lock(); + if (!state) + state = + backtrace_create_state(/*filename=*/nullptr, /*threaded=*/1, seq_backtrace_error_callback, /*data=*/nullptr); - backtrace_full(state, /*skip=*/1, seq_backtrace_full_callback, - seq_backtrace_error_callback, &e->bt); + stateLock.unlock(); + } + backtrace_full(state, /*skip=*/1, seq_backtrace_full_callback, + seq_backtrace_error_callback, &e->bt); + } else { + unw_cursor_t cursor; + unw_context_t context; + + unw_getcontext(&context); + unw_init_local(&cursor, &context); + + while (unw_step(&cursor) > 0) { + unw_word_t pc; + unw_get_reg(&cursor, UNW_REG_IP, &pc); + if (pc == 0) + break; + e->bt.push_back(pc); + } + } } return &(e->unwindException); } @@ -209,7 +238,7 @@ SEQ_FUNC void seq_terminate(void *exc) { } buf << "\n"; - if (seq_flags & SEQ_FLAG_DEBUG) { + if ((seq_flags & SEQ_FLAG_DEBUG) && (seq_flags & SEQ_FLAG_STANDALONE)) { auto *bt = &base->bt; if (bt->count > 0) { buf << "\n\033[1mBacktrace:\033[0m\n"; @@ -223,14 +252,23 @@ SEQ_FUNC void seq_terminate(void *exc) { } auto output = buf.str(); - if (seq_flags & SEQ_FLAG_JIT) { + if (seq_flags & SEQ_FLAG_STANDALONE) { + fwrite(output.data(), 1, output.size(), stderr); + abort(); + } else { + auto *bt = &base->bt; std::string msg(hdr->msg.str, hdr->msg.len); std::string file(hdr->file.str, hdr->file.len); std::string type(hdr->type.str, hdr->type.len); - throw seq_jit_error(output, msg, type, file, (int)hdr->line, (int)hdr->col); - } else { - fwrite(output.data(), 1, output.size(), stderr); - abort(); + + std::vector backtrace; + if (seq_flags & SEQ_FLAG_DEBUG) { + for (unsigned i = 0; i < bt->count; i++) { + backtrace.push_back(bt->frames[i].pc); + } + } + throw codon::JITError(output, msg, type, file, (int)hdr->line, (int)hdr->col, + backtrace); } } diff --git a/codon/runtime/lib.h b/codon/runtime/lib.h index c479fa67..687d176e 100644 --- a/codon/runtime/lib.h +++ b/codon/runtime/lib.h @@ -7,19 +7,16 @@ #include #include #include +#include -#define SEQ_FLAG_DEBUG (1 << 0) -#define SEQ_FLAG_JIT (1 << 1) +#define SEQ_FLAG_DEBUG (1 << 0) // compiled/running in debug mode +#define SEQ_FLAG_JIT (1 << 1) // compiled/running in JIT mode +#define SEQ_FLAG_STANDALONE (1 << 2) // compiled as a standalone object/binary #define SEQ_FUNC extern "C" typedef int64_t seq_int_t; -struct seq_t { - seq_int_t len; - char *seq; -}; - struct seq_str_t { seq_int_t len; char *str; @@ -80,24 +77,28 @@ SEQ_FUNC void *seq_rlock_new(); SEQ_FUNC bool seq_rlock_acquire(void *lock, bool block, double timeout); SEQ_FUNC void seq_rlock_release(void *lock); -class seq_jit_error : public std::runtime_error { +namespace codon { +class JITError : public std::runtime_error { private: std::string output; std::string type; std::string file; int line; int col; + std::vector backtrace; public: - explicit seq_jit_error(const std::string &output, const std::string &what, - const std::string &type, const std::string &file, int line, - int col) + JITError(const std::string &output, const std::string &what, const std::string &type, + const std::string &file, int line, int col, + std::vector backtrace = {}) : std::runtime_error(what), output(output), type(type), file(file), line(line), - col(col) {} + col(col), backtrace(std::move(backtrace)) {} std::string getOutput() const { return output; } std::string getType() const { return type; } std::string getFile() const { return file; } int getLine() const { return line; } int getCol() const { return col; } + std::vector getBacktrace() const { return backtrace; } }; +} // namespace codon diff --git a/codon/sir/llvm/llvisitor.cpp b/codon/sir/llvm/llvisitor.cpp index 848a10bf..9f63ba55 100644 --- a/codon/sir/llvm/llvisitor.cpp +++ b/codon/sir/llvm/llvisitor.cpp @@ -1,6 +1,8 @@ #include "llvisitor.h" #include +#include +#include #include #include #include @@ -27,11 +29,11 @@ llvm::DIFile *LLVMVisitor::DebugInfo::getFile(const std::string &path) { return builder->createFile(filename, directory); } -LLVMVisitor::LLVMVisitor(bool debug, bool jit, const std::string &flags) +LLVMVisitor::LLVMVisitor() : util::ConstVisitor(), context(std::make_unique()), M(), B(std::make_unique>(*context)), func(nullptr), block(nullptr), - value(nullptr), vars(), funcs(), coro(), loops(), trycatch(), - db(debug, jit, flags), plugins(nullptr) { + value(nullptr), vars(), funcs(), coro(), loops(), trycatch(), db(), + plugins(nullptr) { llvm::InitializeAllTargets(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllAsmPrinters(); @@ -376,6 +378,63 @@ void LLVMVisitor::compile(const std::string &filename, } } +namespace { +class DebugInfoListener : public llvm::JITEventListener { +public: + std::pair, + std::unique_ptr> + saved; + intptr_t start = 0; + + void notifyObjectLoaded(ObjectKey key, const llvm::object::ObjectFile &obj, + const llvm::RuntimeDyld::LoadedObjectInfo &L) override { + start = L.getSectionLoadAddress(*obj.section_begin()); + saved = L.getObjectForDebug(obj).takeBinary(); + if (!saved.first) { + auto buf = llvm::MemoryBuffer::getMemBufferCopy(obj.getData(), obj.getFileName()); + auto newObj = llvm::cantFail( + llvm::object::ObjectFile::createObjectFile(buf->getMemBufferRef())); + saved = std::make_pair(std::move(newObj), std::move(buf)); + } + } +}; + +std::string unmangleType(llvm::StringRef s) { + auto p = s.rsplit('.'); + return (p.second.empty() ? p.first : p.second).str(); +} + +std::string unmangleFunc(llvm::StringRef s) { + // separate type and function + auto p = s.split(':'); + llvm::StringRef func = s; + std::string type; + if (!p.second.empty()) { + type = unmangleType(p.first); + func = p.second; + } + + // trim off "." + p = func.rsplit('.'); + if (!p.second.empty() && p.second.find_if([](char c) { return !std::isdigit(c); }) == + llvm::StringRef::npos) + func = p.first; + + // trim off generics + func = func.split('[').first; + + // trim off qualified name + p = func.rsplit('.'); + if (!p.second.empty()) + func = p.second; + + if (!type.empty()) + return type + "." + func.str(); + else + return func.str(); +} +} // namespace + void LLVMVisitor::run(const std::vector &args, const std::vector &libs, const char *const *envp) { runLLVMPipeline(); @@ -384,14 +443,48 @@ void LLVMVisitor::run(const std::vector &args, EB.setMCJITMemoryManager(std::make_unique()); llvm::ExecutionEngine *eng = EB.create(); - std::string err; + auto dbListener = std::unique_ptr(); + if (db.debug) { + dbListener = std::make_unique(); + eng->RegisterJITEventListener(dbListener.get()); + } + for (auto &lib : libs) { + std::string err; if (llvm::sys::DynamicLibrary::LoadLibraryPermanently(lib.c_str(), &err)) { compilationError(err); } } - eng->runFunctionAsMain(main, args, envp); + try { + eng->runFunctionAsMain(main, args, envp); + } catch (const JITError &e) { + fmt::print(stderr, "{}", e.getOutput()); + if (db.debug) { + llvm::symbolize::LLVMSymbolizer sym; + auto invalid = [](const std::string &name) { return name == ""; }; + + fmt::print(stderr, "\n\033[1mBacktrace:\033[0m\n"); + auto base = dbListener->start; + for (auto pc : e.getBacktrace()) { + auto src = sym.symbolizeCode( + *dbListener->saved.first, + {pc - base, llvm::object::SectionedAddress::UndefSection}); + if (auto err = src.takeError()) + break; + if (invalid(src->FunctionName) || invalid(src->FileName)) + continue; + + auto func = unmangleFunc(src->FunctionName); + auto file = src->FileName; + auto line = src->Line; + auto col = src->Column; + fmt::print(stderr, " [\033[33m0x{:016x}\033[0m] \033[32m{}\033[0m {}:{}:{}\n", + pc, func, file, line, col); + } + } + std::abort(); + } delete eng; } @@ -625,7 +718,8 @@ void LLVMVisitor::visit(const Module *x) { llvm::Value *argStorage = getVar(x->getArgVar()); seqassert(argStorage, "argument storage missing"); B->CreateStore(arr, argStorage); - const int flags = (db.debug ? SEQ_FLAG_DEBUG : 0) | (db.jit ? SEQ_FLAG_JIT : 0); + const int flags = (db.debug ? SEQ_FLAG_DEBUG : 0) | (db.jit ? SEQ_FLAG_JIT : 0) | + (db.standalone ? SEQ_FLAG_STANDALONE : 0); B->CreateCall(initFunc, B->getInt32(flags)); // Put the entire program in a new function diff --git a/codon/sir/llvm/llvisitor.h b/codon/sir/llvm/llvisitor.h index 751da353..10b29797 100644 --- a/codon/sir/llvm/llvisitor.h +++ b/codon/sir/llvm/llvisitor.h @@ -98,11 +98,14 @@ private: bool debug; /// Whether we are compiling in JIT mode bool jit; + /// Whether we are compiling a standalone object/executable + bool standalone; /// Program command-line flags std::string flags; - DebugInfo(bool debug, bool jit, const std::string &flags) - : builder(), unit(nullptr), debug(debug), jit(jit), flags(flags) {} + DebugInfo() + : builder(), unit(nullptr), debug(false), jit(false), standalone(false), + flags() {} llvm::DIFile *getFile(const std::string &path); @@ -223,11 +226,7 @@ public: } /// Constructs an LLVM visitor. - /// @param debug whether to compile in debug mode - /// @param jit whether to compile in JIT mode - /// @param flags command-line flags to be included in debug info - explicit LLVMVisitor(bool debug = false, bool jit = false, - const std::string &flags = ""); + LLVMVisitor(); /// @return true if in debug mode, false otherwise bool getDebug() const { return db.debug; } @@ -241,6 +240,12 @@ public: /// @param j true if JIT mode void setJIT(bool j = true) { db.jit = j; } + /// @return true if in standalone mode, false otherwise + bool getStandalone() const { return db.standalone; } + /// Sets standalone status. + /// @param s true if standalone + void setStandalone(bool s = true) { db.standalone = s; } + /// @return program flags std::string getFlags() const { return db.flags; } /// Sets program flags. diff --git a/codon/sir/llvm/llvm.h b/codon/sir/llvm/llvm.h index d73ef056..d3e2f3e5 100644 --- a/codon/sir/llvm/llvm.h +++ b/codon/sir/llvm/llvm.h @@ -12,8 +12,10 @@ #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/CodeGen/TargetPassConfig.h" +#include "llvm/DebugInfo/Symbolize/Symbolize.h" #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/GenericValue.h" +#include "llvm/ExecutionEngine/JITEventListener.h" #include "llvm/ExecutionEngine/JITSymbol.h" #include "llvm/ExecutionEngine/MCJIT.h" #include "llvm/ExecutionEngine/RuntimeDyld.h"