Skip to content

Commit 29679d0

Browse files
committed
Support (optionally) emitting debuginfo w.r.t. LLVM source
This change adds two new environmental variables: - `JULIA_DUMP_IR` - when provided, this is a path that all emitted LLVM IR (post-optimization, just before machine code generation) will be saved to - `JULIA_DEBUGINFO` - when set to "LLVM-IR" this will run an additional pass on any emitted functions to rewrite their debuginfo to refer to the LLVM source, rather than the Julia source it was generated from The `debugir` pass that rewrites the debuginfo is vendored from: https://github.com/vaivaswatha/debugir. For simplicity, this is just a copy of the one file that we need for the pass. Using both of these together allows `gdb` to open the dumped IR and means you can step through LLVM IR line-by-line, print SSA values, etc. This can be very useful for debugging segfaults, or issues in codegen.
1 parent 29a7ce4 commit 29679d0

File tree

8 files changed

+681
-2
lines changed

8 files changed

+681
-2
lines changed

THIRDPARTY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ for exceptions.
55

66
- [crc32c.c](https://stackoverflow.com/questions/17645167/implementing-sse-4-2s-crc32c-in-software) (CRC-32c checksum code by Mark Adler) [[ZLib](https://opensource.org/licenses/Zlib)].
77
- [LDC](https://github.com/ldc-developers/ldc/blob/master/LICENSE) (for ccall/cfunction ABI definitions) [BSD-3]. The portion of code that Julia uses from LDC is [BSD-3] licensed.
8-
- [LLVM](https://releases.llvm.org/3.9.0/LICENSE.TXT) (for parts of src/disasm.cpp) [UIUC]
8+
- [LLVM](https://releases.llvm.org/3.9.0/LICENSE.TXT) (for parts of src/disasm.cpp and src/llvm-debugir.cpp) [UIUC]
99
- [NetBSD](https://www.netbsd.org/about/redistribution.html) (for setjmp, longjmp, and strptime implementations on Windows) [BSD-3]
1010
- [Python](https://docs.python.org/3/license.html) (for strtod implementation on Windows) [PSF]
1111
- [FEMTOLISP](https://github.com/JeffBezanson/femtolisp) [BSD-3]

src/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ RT_LLVMLINK :=
5252
CG_LLVMLINK :=
5353

5454
ifeq ($(JULIACODEGEN),LLVM)
55-
CODEGEN_SRCS := codegen jitlayers aotcompile debuginfo disasm llvm-simdloop \
55+
CODEGEN_SRCS := codegen jitlayers aotcompile debuginfo disasm llvm-simdloop llvm-debugir \
5656
llvm-final-gc-lowering llvm-pass-helpers llvm-late-gc-lowering llvm-ptls \
5757
llvm-lower-handlers llvm-gc-invariant-verifier llvm-propagate-addrspaces \
5858
llvm-multiversioning llvm-alloc-opt llvm-alloc-helpers cgmemmgr llvm-remove-addrspaces \
@@ -327,6 +327,7 @@ $(build_shlibdir)/libllvmcalltest.$(SHLIB_EXT): $(SRCDIR)/llvm-codegen-shared.h
327327
$(BUILDDIR)/llvm-alloc-helpers.o $(BUILDDIR)/llvm-alloc-helpers.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h
328328
$(BUILDDIR)/llvm-alloc-opt.o $(BUILDDIR)/llvm-alloc-opt.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h $(SRCDIR)/llvm-pass-helpers.h $(SRCDIR)/llvm-alloc-helpers.h
329329
$(BUILDDIR)/llvm-cpufeatures.o $(BUILDDIR)/llvm-cpufeatures.dbg.obj: $(SRCDIR)/jitlayers.h
330+
$(BUILDDIR)/llvm-debugir.o $(BUILDDIR)/llvm-debugir.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h
330331
$(BUILDDIR)/llvm-demote-float16.o $(BUILDDIR)/llvm-demote-float16.dbg.obj: $(SRCDIR)/jitlayers.h
331332
$(BUILDDIR)/llvm-final-gc-lowering.o $(BUILDDIR)/llvm-final-gc-lowering.dbg.obj: $(SRCDIR)/llvm-gc-interface-passes.h
332333
$(BUILDDIR)/llvm-gc-invariant-verifier.o $(BUILDDIR)/llvm-gc-invariant-verifier.dbg.obj: $(SRCDIR)/llvm-codegen-shared.h

src/codegen.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10395,6 +10395,24 @@ extern "C" void jl_init_llvm(void)
1039510395
#endif
1039610396
#endif
1039710397

10398+
const char *debuginfo_mode = getenv("JULIA_DEBUGINFO"); // JULIA_DEBUGINFO="LLVM-IR" JULIA_DUMP_IR="path/to/dir"
10399+
if (debuginfo_mode) {
10400+
if (strcasecmp(debuginfo_mode, "julia-source") == 0)
10401+
jl_ExecutionEngine->get_debuginfo_mode() = jl_debuginfo_emission_mode_t::julia_source;
10402+
else if (strcasecmp(debuginfo_mode, "llvm-ir") == 0)
10403+
jl_ExecutionEngine->get_debuginfo_mode() = jl_debuginfo_emission_mode_t::llvm_ir;
10404+
else if (strcmp(debuginfo_mode, "") != 0)
10405+
fprintf(stderr, "warning: unexpected argument to 'JULIA_DEBUGINFO' env var: \"%s\"\n", debuginfo_mode);
10406+
}
10407+
10408+
const char *dump_debugir_directory = getenv("JULIA_DUMP_IR");
10409+
if (dump_debugir_directory && strcmp(dump_debugir_directory, "") != 0) {
10410+
llvm::SmallString<PATH_MAX> AbsoluteFileName{};
10411+
llvm::sys::fs::expand_tilde(Twine(dump_debugir_directory), AbsoluteFileName);
10412+
llvm::sys::fs::make_absolute(AbsoluteFileName);
10413+
jl_ExecutionEngine->get_dump_debugir_directory() = AbsoluteFileName.str();
10414+
}
10415+
1039810416
cl::PrintOptionValues();
1039910417
}
1040010418

src/jitlayers.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,6 +1314,39 @@ static void registerRTDyldJITObject(orc::MaterializationResponsibility &MR,
13141314
#endif
13151315

13161316
namespace {
1317+
1318+
static std::string createDebugIRName(Module const &M) {
1319+
std::string path = jl_ExecutionEngine->get_dump_debugir_directory();
1320+
if (!path.empty()) {
1321+
path += llvm::sys::path::get_separator();
1322+
}
1323+
1324+
std::string filename{};
1325+
for (auto &F : M.functions()) {
1326+
if (F.isDeclaration() || F.getName().starts_with("jfptr_"))
1327+
continue;
1328+
1329+
// Sanitize the filename by allowing only "[a-zA-Z0-9_\-\.]*"
1330+
filename = F.getName().str();
1331+
std::replace_if(std::begin(filename), std::end(filename),[](const char &ch) {
1332+
return !(std::isalnum(ch) || ch == '_' || ch == '-' || ch == '.');
1333+
}, '_');
1334+
1335+
break;
1336+
}
1337+
1338+
// If we couldn't find a name to use, just use some unique integer
1339+
if (filename.empty()) {
1340+
static _Atomic(uint64_t) counter{1};
1341+
filename = std::to_string(jl_atomic_fetch_add_relaxed(&counter, 1));
1342+
}
1343+
1344+
path += filename;
1345+
path += ".ll";
1346+
1347+
return path;
1348+
}
1349+
13171350
static std::unique_ptr<TargetMachine> createTargetMachine() JL_NOTSAFEPOINT {
13181351
TargetOptions options = TargetOptions();
13191352

@@ -1509,6 +1542,44 @@ namespace {
15091542
JL_TIMING(LLVM_JIT, JIT_Opt);
15101543
//Run the optimization
15111544
(****PMs[PoolIdx]).run(M);
1545+
1546+
bool debug_ir = jl_ExecutionEngine->get_debuginfo_mode() == jl_debuginfo_emission_mode_t::llvm_ir;
1547+
bool dump_ir = !jl_ExecutionEngine->get_dump_debugir_directory().empty();
1548+
if (!M.functions().empty() && (debug_ir || dump_ir)) {
1549+
1550+
// Generate a debug filename for the emitted IR
1551+
std::string debug_name = std::move(createDebugIRName(M));
1552+
1553+
// If requested, rewrite all debuginfo to reference the LLVM IR itself
1554+
std::unique_ptr<Module> displayM;
1555+
if (debug_ir) {
1556+
// displayM is the debug-stripped 'source' that the debuginfo now refers to
1557+
displayM = debugir::createDebugInfo(M, "", debug_name);
1558+
}
1559+
1560+
// Emit the IR that was compiled
1561+
if (dump_ir) {
1562+
std::error_code EC;
1563+
raw_fd_ostream OS_dbg(debug_name, EC, sys::fs::OF_Text);
1564+
if (displayM) {
1565+
displayM->print(OS_dbg, nullptr);
1566+
} else {
1567+
M.print(OS_dbg, nullptr);
1568+
}
1569+
1570+
// Emit the "instrumented" IR (unneeded unless you are debugging the debuginfo
1571+
// or running the instrumented IR in isolation)
1572+
if (0 && displayM) {
1573+
// Replace ".ll" suffix with ".dbg.ll"
1574+
debug_name.resize(debug_name.size() - 3);
1575+
debug_name += ".dbg.ll";
1576+
std::error_code EC;
1577+
raw_fd_ostream OS_dbg(debug_name, EC, sys::fs::OF_Text);
1578+
M.print(OS_dbg, nullptr);
1579+
}
1580+
}
1581+
}
1582+
15121583
assert(!verifyLLVMIR(M));
15131584
}
15141585

@@ -1898,6 +1969,7 @@ JuliaOJIT::JuliaOJIT()
18981969
JD(ES.createBareJITDylib("JuliaOJIT")),
18991970
ExternalJD(ES.createBareJITDylib("JuliaExternal")),
19001971
DLSymOpt(std::make_unique<DLSymOptimizer>(false)),
1972+
debuginfo_mode(jl_debuginfo_emission_mode_t::julia_source),
19011973
#ifdef JL_USE_JITLINK
19021974
MemMgr(createJITLinkMemoryManager()),
19031975
ObjectLayer(ES, *MemMgr),

src/jitlayers.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,28 @@ using CompilerResultT = Expected<std::unique_ptr<llvm::MemoryBuffer>>;
367367
using OptimizerResultT = Expected<orc::ThreadSafeModule>;
368368
using SharedBytesT = StringSet<MaxAlignedAllocImpl<sizeof(StringSet<>::MapEntryTy)>>;
369369

370+
enum class jl_debuginfo_emission_mode_t {
371+
372+
// Source-referenced debuginfo (standard behavior)
373+
//
374+
// Preserves and emits any debuginfo in the IR from Julia source.
375+
julia_source = 0,
376+
377+
// Julia IR-referenced debuginfo
378+
//
379+
// Replaces all debuginfo with references to the Julia SSAIR itself (treating the IR code as
380+
// the 'program source'). Emits a text copy of all emitted IR to 'dump_debugir_directory' so
381+
// the IR source is available when using a debugger.
382+
/* julia_ir, not supported (yet) */
383+
384+
// LLVM IR-referenced debuginfo
385+
//
386+
// Replaces all debuginfo with references to the LLVM IR itself (treating the LLVM IR as the
387+
// 'program source'). Emits a text copy of all emitted IR to 'dump_debugir_directory' so the
388+
// IR source is available when using a debugger.
389+
llvm_ir,
390+
};
391+
370392
class JuliaOJIT {
371393
private:
372394
// any verification the user wants to do when adding an OwningResource to the pool
@@ -582,9 +604,16 @@ class JuliaOJIT {
582604
jl_locked_stream &get_dump_llvm_opt_stream() JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER {
583605
return dump_llvm_opt_stream;
584606
}
607+
std::string &get_dump_debugir_directory() JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER {
608+
return dump_debugir_directory;
609+
}
585610
std::string getMangledName(StringRef Name) JL_NOTSAFEPOINT;
586611
std::string getMangledName(const GlobalValue *GV) JL_NOTSAFEPOINT;
587612

613+
jl_debuginfo_emission_mode_t &get_debuginfo_mode() JL_NOTSAFEPOINT JL_NOTSAFEPOINT_ENTER {
614+
return debuginfo_mode;
615+
}
616+
588617
// Note that this is a potential safepoint due to jl_get_library_ and jl_dlsym calls
589618
// but may be called from inside safe-regions due to jit compilation locks
590619
void optimizeDLSyms(Module &M) JL_NOTSAFEPOINT_LEAVE JL_NOTSAFEPOINT_ENTER;
@@ -610,6 +639,9 @@ class JuliaOJIT {
610639
jl_locked_stream dump_emitted_mi_name_stream;
611640
jl_locked_stream dump_compiles_stream;
612641
jl_locked_stream dump_llvm_opt_stream;
642+
std::string dump_debugir_directory;
643+
644+
jl_debuginfo_emission_mode_t debuginfo_mode;
613645

614646
std::mutex llvm_printing_mutex{};
615647
SmallVector<std::function<void()>, 0> PrintLLVMTimers;

src/llvm-codegen-shared.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,3 +527,12 @@ void ConstantUses<U>::forward()
527527
}
528528
}
529529
}
530+
531+
namespace debugir {
532+
533+
// Attaches debug info to M, assuming it is parsed from Directory/Filename.
534+
// Returns a module for display in debugger devoid of any debug info.
535+
std::unique_ptr<llvm::Module>
536+
createDebugInfo(llvm::Module &M, std::string Directory, std::string Filename);
537+
538+
} // namespace debugir

0 commit comments

Comments
 (0)