Skip to content

[clang-repl] Factor out CreateJITBuilder() and allow specialization in derived classes #84461

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 2 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions clang/include/clang/Interpreter/Interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
namespace llvm {
namespace orc {
class LLJIT;
class LLJITBuilder;
class ThreadSafeContext;
} // namespace orc
} // namespace llvm
Expand Down Expand Up @@ -127,6 +128,13 @@ class Interpreter {
// custom runtime.
virtual std::unique_ptr<RuntimeInterfaceBuilder> FindRuntimeInterface();

// Lazily construct thev ORCv2 JITBuilder. This called when the internal
// IncrementalExecutor is created. The default implementation populates an
// in-process JIT with debugging support. Override this to configure the JIT
// engine used for execution.
virtual llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
CreateJITBuilder(CompilerInstance &CI);

public:
virtual ~Interpreter();

Expand Down
33 changes: 18 additions & 15 deletions clang/lib/Interpreter/IncrementalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/JITLoaderGDB.h"
Expand All @@ -36,26 +37,28 @@ LLVM_ATTRIBUTE_USED void linkComponents() {

namespace clang {

llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
IncrementalExecutor::createDefaultJITBuilder(
llvm::orc::JITTargetMachineBuilder JTMB) {
auto JITBuilder = std::make_unique<llvm::orc::LLJITBuilder>();
JITBuilder->setJITTargetMachineBuilder(std::move(JTMB));
JITBuilder->setPrePlatformSetup([](llvm::orc::LLJIT &J) {
// Try to enable debugging of JIT'd code (only works with JITLink for
// ELF and MachO).
consumeError(llvm::orc::enableDebuggerSupport(J));
return llvm::Error::success();
});
return std::move(JITBuilder);
}

IncrementalExecutor::IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC,
llvm::Error &Err,
const clang::TargetInfo &TI)
llvm::orc::LLJITBuilder &JITBuilder,
llvm::Error &Err)
: TSCtx(TSC) {
using namespace llvm::orc;
llvm::ErrorAsOutParameter EAO(&Err);

auto JTMB = JITTargetMachineBuilder(TI.getTriple());
JTMB.addFeatures(TI.getTargetOpts().Features);
LLJITBuilder Builder;
Builder.setJITTargetMachineBuilder(JTMB);
Builder.setPrePlatformSetup(
[](LLJIT &J) {
// Try to enable debugging of JIT'd code (only works with JITLink for
// ELF and MachO).
consumeError(enableDebuggerSupport(J));
return llvm::Error::success();
});

if (auto JitOrErr = Builder.create())
if (auto JitOrErr = JITBuilder.create())
Jit = std::move(*JitOrErr);
else {
Err = JitOrErr.takeError();
Expand Down
9 changes: 7 additions & 2 deletions clang/lib/Interpreter/IncrementalExecutor.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
namespace llvm {
class Error;
namespace orc {
class JITTargetMachineBuilder;
class LLJIT;
class LLJITBuilder;
class ThreadSafeContext;
} // namespace orc
} // namespace llvm
Expand All @@ -44,8 +46,8 @@ class IncrementalExecutor {
public:
enum SymbolNameKind { IRName, LinkerName };

IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC, llvm::Error &Err,
const clang::TargetInfo &TI);
IncrementalExecutor(llvm::orc::ThreadSafeContext &TSC,
llvm::orc::LLJITBuilder &JITBuilder, llvm::Error &Err);
~IncrementalExecutor();

llvm::Error addModule(PartialTranslationUnit &PTU);
Expand All @@ -56,6 +58,9 @@ class IncrementalExecutor {
getSymbolAddress(llvm::StringRef Name, SymbolNameKind NameKind) const;

llvm::orc::LLJIT &GetExecutionEngine() { return *Jit; }

static llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
createDefaultJITBuilder(llvm::orc::JITTargetMachineBuilder JTMB);
};

} // end namespace clang
Expand Down
26 changes: 23 additions & 3 deletions clang/lib/Interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -372,15 +372,35 @@ Interpreter::Parse(llvm::StringRef Code) {
return IncrParser->Parse(Code);
}

static llvm::Expected<llvm::orc::JITTargetMachineBuilder>
createJITTargetMachineBuilder(const std::string &TT) {
if (TT == llvm::sys::getProcessTriple())
// This fails immediately if the target backend is not registered
return llvm::orc::JITTargetMachineBuilder::detectHost();

// If the target backend is not registered, LLJITBuilder::create() will fail
return llvm::orc::JITTargetMachineBuilder(llvm::Triple(TT));
}

llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
Interpreter::CreateJITBuilder(CompilerInstance &CI) {
auto JTMB = createJITTargetMachineBuilder(CI.getTargetOpts().Triple);
if (!JTMB)
return JTMB.takeError();
return IncrementalExecutor::createDefaultJITBuilder(std::move(*JTMB));
}

llvm::Error Interpreter::CreateExecutor() {
const clang::TargetInfo &TI =
getCompilerInstance()->getASTContext().getTargetInfo();
if (IncrExecutor)
return llvm::make_error<llvm::StringError>("Operation failed. "
"Execution engine exists",
std::error_code());
llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>> JB =
CreateJITBuilder(*getCompilerInstance());
Copy link
Member Author

Choose a reason for hiding this comment

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

We keep creating a new LLJITBuilder for each new IncrementalExecutor. This is questionable, but I didn't want to clutter the patch unnecessarily. I am happy to change that in a follow-up PR.

if (!JB)
return JB.takeError();
llvm::Error Err = llvm::Error::success();
auto Executor = std::make_unique<IncrementalExecutor>(*TSCtx, Err, TI);
auto Executor = std::make_unique<IncrementalExecutor>(*TSCtx, **JB, Err);
if (!Err)
IncrExecutor = std::move(Executor);

Expand Down
121 changes: 119 additions & 2 deletions clang/unittests/Interpreter/InterpreterExtensionsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,21 @@
#include "clang/Sema/Sema.h"

#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/ExecutionEngine/Orc/Shared/ExecutorAddress.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Threading.h"
#include "llvm/Testing/Support/Error.h"

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include <system_error>

#if defined(_AIX)
#define CLANG_INTERPRETER_PLATFORM_CANNOT_CREATE_LLJIT
#endif

using namespace clang;
namespace {

Expand All @@ -41,6 +48,10 @@ struct LLVMInitRAII {
LLVMInitRAII() {
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
LLVMInitializeARMTarget();
LLVMInitializeARMTargetInfo();
LLVMInitializeARMTargetMC();
LLVMInitializeARMAsmPrinter();
}
~LLVMInitRAII() { llvm::llvm_shutdown(); }
} LLVMInit;
Expand All @@ -51,12 +62,30 @@ class TestCreateResetExecutor : public Interpreter {
llvm::Error &Err)
: Interpreter(std::move(CI), Err) {}

llvm::Error testCreateExecutor() { return Interpreter::CreateExecutor(); }
llvm::Error testCreateJITBuilderError() {
JB = nullptr;
return Interpreter::CreateExecutor();
}

llvm::Error testCreateExecutor() {
JB = std::make_unique<llvm::orc::LLJITBuilder>();
return Interpreter::CreateExecutor();
}

void resetExecutor() { Interpreter::ResetExecutor(); }

private:
llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
CreateJITBuilder(CompilerInstance &CI) override {
if (JB)
return std::move(JB);
return llvm::make_error<llvm::StringError>("TestError", std::error_code());
}

std::unique_ptr<llvm::orc::LLJITBuilder> JB;
};

#ifdef _AIX
#ifdef CLANG_INTERPRETER_PLATFORM_CANNOT_CREATE_LLJIT
TEST(InterpreterExtensionsTest, DISABLED_ExecutorCreateReset) {
#else
TEST(InterpreterExtensionsTest, ExecutorCreateReset) {
Expand All @@ -69,6 +98,8 @@ TEST(InterpreterExtensionsTest, ExecutorCreateReset) {
llvm::Error ErrOut = llvm::Error::success();
TestCreateResetExecutor Interp(cantFail(CB.CreateCpp()), ErrOut);
cantFail(std::move(ErrOut));
EXPECT_THAT_ERROR(Interp.testCreateJITBuilderError(),
llvm::FailedWithMessage("TestError"));
cantFail(Interp.testCreateExecutor());
Interp.resetExecutor();
cantFail(Interp.testCreateExecutor());
Expand Down Expand Up @@ -126,4 +157,90 @@ TEST(InterpreterExtensionsTest, FindRuntimeInterface) {
EXPECT_EQ(1U, Interp.RuntimeIBPtr->TransformerQueries);
}

class CustomJBInterpreter : public Interpreter {
using CustomJITBuilderCreatorFunction =
std::function<llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>()>;
CustomJITBuilderCreatorFunction JBCreator = nullptr;

public:
CustomJBInterpreter(std::unique_ptr<CompilerInstance> CI, llvm::Error &ErrOut)
: Interpreter(std::move(CI), ErrOut) {}

~CustomJBInterpreter() override {
// Skip cleanUp() because it would trigger LLJIT default dtors
Interpreter::ResetExecutor();
}

void setCustomJITBuilderCreator(CustomJITBuilderCreatorFunction Fn) {
JBCreator = std::move(Fn);
}

llvm::Expected<std::unique_ptr<llvm::orc::LLJITBuilder>>
CreateJITBuilder(CompilerInstance &CI) override {
if (JBCreator)
return JBCreator();
return Interpreter::CreateJITBuilder(CI);
}

llvm::Error CreateExecutor() { return Interpreter::CreateExecutor(); }
};

#ifdef CLANG_INTERPRETER_PLATFORM_CANNOT_CREATE_LLJIT
TEST(InterpreterExtensionsTest, DISABLED_DefaultCrossJIT) {
#else
TEST(InterpreterExtensionsTest, DefaultCrossJIT) {
#endif
IncrementalCompilerBuilder CB;
CB.SetTargetTriple("armv6-none-eabi");
auto CI = cantFail(CB.CreateCpp());
llvm::Error ErrOut = llvm::Error::success();
CustomJBInterpreter Interp(std::move(CI), ErrOut);
cantFail(std::move(ErrOut));
cantFail(Interp.CreateExecutor());
}

#ifdef CLANG_INTERPRETER_PLATFORM_CANNOT_CREATE_LLJIT
TEST(InterpreterExtensionsTest, DISABLED_CustomCrossJIT) {
#else
TEST(InterpreterExtensionsTest, CustomCrossJIT) {
#endif
std::string TargetTriple = "armv6-none-eabi";

IncrementalCompilerBuilder CB;
CB.SetTargetTriple(TargetTriple);
auto CI = cantFail(CB.CreateCpp());
llvm::Error ErrOut = llvm::Error::success();
CustomJBInterpreter Interp(std::move(CI), ErrOut);
cantFail(std::move(ErrOut));

using namespace llvm::orc;
LLJIT *JIT = nullptr;
std::vector<std::unique_ptr<llvm::MemoryBuffer>> Objs;
Interp.setCustomJITBuilderCreator([&]() {
auto JTMB = JITTargetMachineBuilder(llvm::Triple(TargetTriple));
JTMB.setCPU("cortex-m0plus");
auto JB = std::make_unique<LLJITBuilder>();
JB->setJITTargetMachineBuilder(JTMB);
JB->setPlatformSetUp(setUpInactivePlatform);
JB->setNotifyCreatedCallback([&](LLJIT &J) {
ObjectLayer &ObjLayer = J.getObjLinkingLayer();
auto *JITLinkObjLayer = llvm::dyn_cast<ObjectLinkingLayer>(&ObjLayer);
Copy link
Member Author

Choose a reason for hiding this comment

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

We know that the ARM target uses JITLink, but we could also check explicitly. It seems nice to check that we produce some actual code. Maybe we want checks on the object code in the future? This might be a good template for such tests.

JITLinkObjLayer->setReturnObjectBuffer(
[&Objs](std::unique_ptr<llvm::MemoryBuffer> MB) {
Objs.push_back(std::move(MB));
});
JIT = &J;
return llvm::Error::success();
});
return JB;
});

EXPECT_EQ(0U, Objs.size());
cantFail(Interp.CreateExecutor());
cantFail(Interp.ParseAndExecute("int a = 1;"));
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 works cross-platform, because we don't actually execute code. InactivePlatform suppresses execution of the respective initializers.

ExecutorAddr Addr = cantFail(JIT->lookup("a"));
EXPECT_NE(0U, Addr.getValue());
EXPECT_EQ(1U, Objs.size());
}

} // end anonymous namespace