Skip to content

Commit e598aef

Browse files
committed
Implement the ability to swap functions in the JIT.
This patch allows users of CppInterOp to rewire things like printf to enable rich displays, for example.
1 parent 236b49b commit e598aef

File tree

6 files changed

+119
-4
lines changed

6 files changed

+119
-4
lines changed

include/clang/Interpreter/Compatibility.h

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,29 @@ namespace compat {
3939
cling::utils::Analyze::maybeMangleDeclName(GD, mangledName);
4040
}
4141

42+
inline llvm::orc::LLJIT* getExecutionEngine(const cling::Interpreter& I) {
43+
// FIXME: This is a horrible hack finding the llvm::orc::LLJIT by computing
44+
// the object offsets in Cling. We should add getExecutionEngine interface
45+
// to directly.
46+
47+
// sizeof (m_Opts) + sizeof(m_LLVMContext)
48+
const unsigned m_ExecutorOffset = 72;
49+
int* IncrementalExecutor = ((int*)(&I)) + m_ExecutorOffset;
50+
int* IncrementalJit = *(int**)IncrementalExecutor + 0;
51+
int* LLJIT = *(int**)IncrementalJit + 0;
52+
return *(llvm::orc::LLJIT**)LLJIT;
53+
}
54+
4255
inline llvm::Expected<llvm::JITTargetAddress>
4356
getSymbolAddress(const cling::Interpreter &I, llvm::StringRef IRName) {
44-
return (llvm::JITTargetAddress)I.getAddressOfGlobal(IRName);
45-
}
57+
if (void* Addr = I.getAddressOfGlobal(IRName))
58+
return (llvm::JITTargetAddress)Addr;
4659

60+
llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I);
61+
llvm::orc::SymbolNameVector Names;
62+
Names.push_back(Jit.getExecutionSession().intern(IRName));
63+
return llvm::make_error<llvm::orc::SymbolsNotFound>(Names);
64+
}
4765
}
4866

4967
#endif //USE_CLING
@@ -92,9 +110,10 @@ namespace compat {
92110
// getSymbolAddress, getSymbolAddressFromLinkerName
93111
// Clang 15 - Add new Interpreter methods: Undo
94112

95-
inline const llvm::orc::LLJIT *getExecutionEngine(clang::Interpreter &I) {
113+
inline llvm::orc::LLJIT* getExecutionEngine(clang::Interpreter& I) {
96114
#if CLANG_VERSION_MAJOR >= 14
97-
return &llvm::cantFail(I.getExecutionEngine());
115+
auto* engine = &llvm::cantFail(I.getExecutionEngine());
116+
return const_cast<llvm::orc::LLJIT*>(engine);
98117
#else
99118
assert(0 && "Not implemented in Clang <14!");
100119
return nullptr;

include/clang/Interpreter/CppInterOp.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,17 @@ namespace Cpp {
455455
/// function.
456456
bool LoadLibrary(const char *lib_path, bool lookup = true);
457457

458+
/// Inserts or replaces a symbol in the JIT with the one provided. This is
459+
/// useful for providing our own implementations of facilities such as printf.
460+
///
461+
///\param[in] linker_mangled_name - the name of the symbol to be inserted or
462+
/// replaced.
463+
///\param[in] address - the new address of the symbol.
464+
///
465+
///\returns true on failure.
466+
bool InsertOrReplaceJitSymbol(const char* linker_mangled_name,
467+
uint64_t address);
468+
458469
/// Tries to load provided objects in a string format (prettyprint).
459470
std::string ObjToString(const char *type, void *obj);
460471

include/clang/Interpreter/CppInterOpInterpreter.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class Interpreter {
154154
~Interpreter() {}
155155

156156
operator const clang::Interpreter&() const { return *inner; }
157+
operator clang::Interpreter&() { return *inner; }
157158

158159
///\brief Describes the return result of the different routines that do the
159160
/// incremental compilation.

lib/Interpreter/CppInterOp.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2563,6 +2563,57 @@ namespace Cpp {
25632563
return res == compat::Interpreter::kSuccess;
25642564
}
25652565

2566+
bool InsertOrReplaceJitSymbol(const char* linker_mangled_name,
2567+
uint64_t address) {
2568+
using namespace llvm;
2569+
using namespace llvm::orc;
2570+
2571+
auto& I = getInterp();
2572+
auto Symbol = compat::getSymbolAddress(I, linker_mangled_name);
2573+
if (Error Err = Symbol.takeError()) {
2574+
logAllUnhandledErrors(std::move(Err), errs(),
2575+
"[InsertOrReplaceJitSymbol] error: ");
2576+
return true;
2577+
}
2578+
2579+
// Nothing to define, we are redefining the same function.
2580+
if (*Symbol && *Symbol == address) {
2581+
errs() << "[InsertOrReplaceJitSymbol] warning: redefining '"
2582+
<< linker_mangled_name << "' with the same address\n";
2583+
return true;
2584+
}
2585+
2586+
// Let's inject it.
2587+
bool Inserted;
2588+
SymbolMap::iterator It;
2589+
llvm::orc::SymbolMap InjectedSymbols;
2590+
2591+
llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I);
2592+
JITDylib& DyLib = Jit.getMainJITDylib();
2593+
2594+
std::tie(It, Inserted) = InjectedSymbols.try_emplace(
2595+
Jit.getExecutionSession().intern(linker_mangled_name),
2596+
JITEvaluatedSymbol(address, JITSymbolFlags::Exported));
2597+
assert(Inserted && "Why wasn't this found in the initial Jit lookup?");
2598+
2599+
// We want to replace a symbol with a custom provided one.
2600+
if (Symbol && address)
2601+
// The symbol be in the DyLib or in-process.
2602+
if (auto Err = DyLib.remove({It->first})) {
2603+
logAllUnhandledErrors(std::move(Err), errs(),
2604+
"[InsertOrReplaceJitSymbol] error: ");
2605+
return true;
2606+
}
2607+
2608+
if (Error Err = DyLib.define(absoluteSymbols({*It}))) {
2609+
logAllUnhandledErrors(std::move(Err), errs(),
2610+
"[InsertOrReplaceJitSymbol] error: ");
2611+
return true;
2612+
}
2613+
2614+
return false;
2615+
}
2616+
25662617
std::string ObjToString(const char *type, void *obj) {
25672618
return getInterp().toString(type, obj);
25682619
}

unittests/CppInterOp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ add_cppinterop_unittest(CppInterOpTests
66
EnumReflectionTest.cpp
77
FunctionReflectionTest.cpp
88
InterpreterTest.cpp
9+
JitTest.cpp
910
ScopeReflectionTest.cpp
1011
TypeReflectionTest.cpp
1112
Utils.cpp

unittests/CppInterOp/JitTest.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include "Utils.h"
2+
3+
#include "clang/Interpreter/CppInterOp.h"
4+
5+
#include "gtest/gtest.h"
6+
7+
using namespace TestUtils;
8+
9+
static int printf_jit(const char* format, ...) {
10+
llvm::errs() << "printf_jit called!\n";
11+
return 0;
12+
}
13+
14+
TEST(JitTest, InsertOrReplaceJitSymbol) {
15+
std::vector<Decl*> Decls;
16+
std::string code = R"(
17+
extern "C" int printf(const char*,...);
18+
)";
19+
20+
GetAllTopLevelDecls(code, Decls);
21+
22+
EXPECT_FALSE(Cpp::InsertOrReplaceJitSymbol("printf", (uint64_t)&printf_jit));
23+
24+
testing::internal::CaptureStderr();
25+
Cpp::Process("printf(\"Blah\");");
26+
std::string cerrs = testing::internal::GetCapturedStderr();
27+
EXPECT_STREQ(cerrs.c_str(), "printf_jit called!\n");
28+
29+
EXPECT_TRUE(
30+
Cpp::InsertOrReplaceJitSymbol("non_existent", (uint64_t)&printf_jit));
31+
EXPECT_TRUE(Cpp::InsertOrReplaceJitSymbol("non_existent", 0));
32+
}

0 commit comments

Comments
 (0)