Skip to content

[llvm-c] Expose debug support for LLJIT in Orc C-API bindings #73257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Dec 11, 2023
Merged
2 changes: 1 addition & 1 deletion llvm/include/llvm-c/LLJIT.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings --------*- C++ -*-===*\
/*===----------- llvm-c/LLJIT.h - OrcV2 LLJIT C bindings ----------*- C -*-===*\
|* *|
|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *|
|* Exceptions. *|
Expand Down
52 changes: 52 additions & 0 deletions llvm/include/llvm-c/LLJITUtils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*===------- llvm-c/LLJITUtils.h - Advanced LLJIT features --------*- C -*-===*\
|* *|
|* Part of the LLVM Project, under the Apache License v2.0 with LLVM *|
|* Exceptions. *|
|* See https://llvm.org/LICENSE.txt for license information. *|
|* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception *|
|* *|
|*===----------------------------------------------------------------------===*|
|* *|
|* This header declares the C interface for extra utilities to be used with *|
|* the LLJIT class from the llvm-c/LLJIT.h header. It requires to following *|
|* link libraries in addition to libLLVMOrcJIT.a: *|
|* - libLLVMOrcDebugging.a *|
|* *|
|* Many exotic languages can interoperate with C code but have a harder time *|
|* with C++ due to name mangling. So in addition to C, this interface enables *|
|* tools written in such languages. *|
|* *|
|* Note: This interface is experimental. It is *NOT* stable, and may be *|
|* changed without warning. Only C API usage documentation is *|
|* provided. See the C++ documentation for all higher level ORC API *|
|* details. *|
|* *|
\*===----------------------------------------------------------------------===*/

#ifndef LLVM_C_LLJITUTILS_H
#define LLVM_C_LLJITUTILS_H

#include "llvm-c/LLJIT.h"

LLVM_C_EXTERN_C_BEGIN

/**
* @defgroup LLVMCExecutionEngineLLJITUtils LLJIT Utilities
* @ingroup LLVMCExecutionEngineLLJIT
*
* @{
*/

/**
* Install the plugin that submits debug objects to the executor. Executors must
* expose the llvm_orc_registerJITLoaderGDBWrapper symbol.
*/
LLVMErrorRef LLVMOrcLLJITEnableDebugSupport(LLVMOrcLLJITRef J);

/**
* @}
*/

LLVM_C_EXTERN_C_END

#endif /* LLVM_C_LLJITUTILS_H */
1 change: 1 addition & 0 deletions llvm/lib/ExecutionEngine/Orc/Debugging/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ add_llvm_component_library(LLVMOrcDebugging
DebugInfoSupport.cpp
DebuggerSupport.cpp
DebuggerSupportPlugin.cpp
LLJITUtilsCBindings.cpp
PerfSupportPlugin.cpp

ADDITIONAL_HEADER_DIRS
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/ExecutionEngine/Orc/Debugging/DebuggerSupport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Error enableDebuggerSupport(LLJIT &J) {
if (!Registrar)
return Registrar.takeError();
ObjLinkingLayer->addPlugin(std::make_unique<DebugObjectManagerPlugin>(
ES, std::move(*Registrar), true, true));
ES, std::move(*Registrar), false, true));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the RequireDebugSections flag. Without this change the test won't pass, because the bitcode for the sum() example has no debug info.

This change may cause some overhead, but this shouldn't be a surprise in the context of debugging. And it may avoid headaches for users: Not remembering to generate debug info is a common problem for JITed code. With this change users get at least function names, like in release builds. Without it, the debugger has zero info.

Alternatively, we could expose it in the C-API and wire it up in the internal function here. See my second comment for details.

return Error::success();
}
case Triple::MachO: {
Expand Down
22 changes: 22 additions & 0 deletions llvm/lib/ExecutionEngine/Orc/Debugging/LLJITUtilsCBindings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//===--------- LLJITUtilsCBindings.cpp - Advanced LLJIT features ----------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "llvm-c/LLJIT.h"
#include "llvm-c/LLJITUtils.h"

#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"

using namespace llvm;
using namespace llvm::orc;

DEFINE_SIMPLE_CONVERSION_FUNCTIONS(LLJIT, LLVMOrcLLJITRef)

LLVMErrorRef LLVMOrcLLJITEnableDebugSupport(LLVMOrcLLJITRef J) {
return wrap(llvm::orc::enableDebuggerSupport(*unwrap(J)));
}
1 change: 1 addition & 0 deletions llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set(LLVM_LINK_COMPONENTS
IRReader
JITLink
Object
OrcDebugging
OrcJIT
OrcShared
OrcTargetProcess
Expand Down
85 changes: 85 additions & 0 deletions llvm/unittests/ExecutionEngine/Orc/OrcCAPITest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
#include "llvm-c/Core.h"
#include "llvm-c/Error.h"
#include "llvm-c/LLJIT.h"
#include "llvm-c/LLJITUtils.h"
#include "llvm-c/Orc.h"
#include "gtest/gtest.h"

#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IRReader/IRReader.h"
Expand Down Expand Up @@ -211,6 +213,20 @@ constexpr StringRef SumExample =
}
)";

constexpr StringRef SumDebugExample =
R"(
define i32 @sum(i32 %x, i32 %y) {
entry:
%r = add nsw i32 %x, %y
ret i32 %r
}
!llvm.module.flags = !{!0}
!llvm.dbg.cu = !{!1}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_C99, file: !2, emissionKind: FullDebug)
!2 = !DIFile(filename: "sum.c", directory: "/tmp")
)";

} // end anonymous namespace.

// Consumes the given error ref and returns the string error message.
Expand Down Expand Up @@ -494,6 +510,75 @@ TEST_F(OrcCAPITestBase, AddObjectBuffer) {
ASSERT_TRUE(!!SumAddr);
}

// This must be kept in sync with gdb/gdb/jit.h .
extern "C" {

typedef enum {
JIT_NOACTION = 0,
JIT_REGISTER_FN,
JIT_UNREGISTER_FN
} jit_actions_t;

struct jit_code_entry {
struct jit_code_entry *next_entry;
struct jit_code_entry *prev_entry;
const char *symfile_addr;
uint64_t symfile_size;
};

struct jit_descriptor {
uint32_t version;
// This should be jit_actions_t, but we want to be specific about the
// bit-width.
uint32_t action_flag;
struct jit_code_entry *relevant_entry;
struct jit_code_entry *first_entry;
};

// We put information about the JITed function in this global, which the
// debugger reads. Make sure to specify the version statically, because the
// debugger checks the version before we can set it during runtime.
extern struct jit_descriptor __jit_debug_descriptor;

static void *findLastDebugDescriptorEntryPtr() {
struct jit_code_entry *Last = __jit_debug_descriptor.first_entry;
while (Last && Last->next_entry)
Last = Last->next_entry;
return Last;
}
}

#if defined(_AIX) or not(defined(__ELF__) or defined(__MACH__))
TEST_F(OrcCAPITestBase, DISABLED_EnableDebugSupport) {
#else
static LLVM_ATTRIBUTE_USED void linkComponents() {
errs() << "Linking in runtime functions\n"
<< (void *)&llvm_orc_registerJITLoaderGDBWrapper << '\n'
<< (void *)&llvm_orc_registerJITLoaderGDBAllocAction << '\n';
}
TEST_F(OrcCAPITestBase, EnableDebugSupport) {
#endif
if (LLVMErrorRef E = LLVMOrcLLJITEnableDebugSupport(Jit))
FAIL() << "Error testing LLJIT debug support (triple = " << TargetTriple
<< "): " << toString(E);

void *Before = findLastDebugDescriptorEntryPtr();
LLVMMemoryBufferRef ObjBuffer = createTestObject(SumDebugExample, "sum.ll");
LLVMOrcObjectLayerRef ObjLayer = LLVMOrcLLJITGetObjLinkingLayer(Jit);
if (LLVMErrorRef E =
LLVMOrcObjectLayerAddObjectFile(ObjLayer, MainDylib, ObjBuffer))
FAIL() << "Failed to add object file to ObjLinkingLayer (triple = "
<< TargetTriple << "): " << toString(E);

LLVMOrcJITTargetAddress SumAddr;
if (LLVMErrorRef E = LLVMOrcLLJITLookup(Jit, &SumAddr, "sum"))
FAIL() << "Symbol \"sum\" was not added into JIT (triple = " << TargetTriple
<< "): " << toString(E);

void *After = findLastDebugDescriptorEntryPtr();
ASSERT_NE(Before, After);
}

#if defined(_AIX)
TEST_F(OrcCAPITestBase, DISABLED_ExecutionTest) {
#else
Expand Down