Skip to content

Commit 29bcf18

Browse files
committed
[LLVM] Add ORC CompileLayer
Thanks to Stefan Gränitz for pointing out that this is indeed rather simple.
1 parent a1c1c9f commit 29bcf18

File tree

4 files changed

+140
-17
lines changed

4 files changed

+140
-17
lines changed

tpde-llvm/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,14 @@ target_sources(tpde_llvm PRIVATE
4343
src/JITMapper.cpp
4444
src/LLVMAdaptor.cpp
4545
src/LLVMCompiler.cpp
46+
src/OrcCompiler.cpp
4647

4748
PUBLIC
4849
FILE_SET HEADERS
4950
BASE_DIRS include
5051
FILES
5152
include/tpde-llvm/LLVMCompiler.hpp
53+
include/tpde-llvm/OrcCompiler.hpp
5254

5355
PRIVATE
5456
FILE_SET priv_headers TYPE HEADERS
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
#pragma once
4+
5+
#include <llvm/ExecutionEngine/Orc/IRCompileLayer.h>
6+
#include <llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h>
7+
#include <llvm/Support/Error.h>
8+
#include <llvm/Support/MemoryBuffer.h>
9+
#include <llvm/Target/TargetMachine.h>
10+
11+
#include <memory>
12+
13+
#include "tpde-llvm/LLVMCompiler.hpp"
14+
15+
namespace tpde_llvm {
16+
17+
/// ORC Compiler functor using TPDE-LLVM, which can transparently fall back to
18+
/// LLVM's SimpleCompiler (if a target machine is provided). Intended as a
19+
/// typical drop-in replacement for llvm::orc::SimpleCompiler.
20+
class OrcCompiler : public llvm::orc::IRCompileLayer::IRCompiler {
21+
std::unique_ptr<LLVMCompiler> owned_compiler;
22+
LLVMCompiler *compiler;
23+
llvm::TargetMachine *tm;
24+
25+
public:
26+
/// Constructor. If the TargetMachine non-null, a failure within TPDE
27+
/// (e.g., due to unsupported IR constructs) will fall back to LLVM.
28+
OrcCompiler(LLVMCompiler *compiler, llvm::TargetMachine *tm = nullptr)
29+
: IRCompiler({}), compiler(compiler), tm(tm) {}
30+
/// Constructor. If the TargetMachine non-null, a failure within TPDE
31+
/// (e.g., due to unsupported IR constructs) will fall back to LLVM.
32+
OrcCompiler(const llvm::Triple &triple, llvm::TargetMachine *tm = nullptr)
33+
: IRCompiler({}),
34+
owned_compiler(LLVMCompiler::create(triple)),
35+
compiler(owned_compiler.get()),
36+
tm(tm) {}
37+
/// Constructor, compatible with llvm::orc::SimpleCompiler.
38+
OrcCompiler(llvm::TargetMachine &tm)
39+
: IRCompiler({}),
40+
owned_compiler(LLVMCompiler::create(tm.getTargetTriple())),
41+
compiler(owned_compiler.get()),
42+
tm(&tm) {}
43+
44+
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
45+
operator()(llvm::Module &) override;
46+
};
47+
48+
/// A very simple thread-safe version of OrcCompiler, intended as a typical
49+
/// drop-in replacement for llvm::orc::ConcurrentIRCompiler. This is not the
50+
/// most efficient way, applications could (and probably should) cache a
51+
/// LLVMCompiler and TargetMachine in thread-local storage.
52+
class ConcurrentOrcCompiler : public llvm::orc::IRCompileLayer::IRCompiler {
53+
llvm::orc::JITTargetMachineBuilder jtmb;
54+
55+
public:
56+
/// Constructor. For every compilation, a new LLVMCompiler is constructed
57+
/// based on the target triple of the JITTargetMachineBuilder. Likewise, for
58+
/// every fallback, a new TargetMachine is constructed.
59+
ConcurrentOrcCompiler(llvm::orc::JITTargetMachineBuilder jtmb)
60+
: IRCompiler({}), jtmb(std::move(jtmb)) {}
61+
62+
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
63+
operator()(llvm::Module &) override;
64+
};
65+
66+
} // namespace tpde_llvm

tpde-llvm/src/OrcCompiler.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3+
4+
#include "tpde-llvm/OrcCompiler.hpp"
5+
6+
#include <llvm/ExecutionEngine/Orc/CompileUtils.h>
7+
#include <llvm/Support/Error.h>
8+
#include <llvm/Support/MemoryBuffer.h>
9+
#include <llvm/TargetParser/Triple.h>
10+
11+
#include <cstdint>
12+
#include <memory>
13+
#include <vector>
14+
15+
#include "tpde-llvm/LLVMCompiler.hpp"
16+
17+
namespace tpde_llvm {
18+
19+
namespace {
20+
21+
class VectorMemoryBuffer : public llvm::MemoryBuffer {
22+
std::vector<uint8_t> data;
23+
24+
public:
25+
VectorMemoryBuffer(std::vector<uint8_t> &&v) : data(std::move(v)) {
26+
const char *ptr = reinterpret_cast<const char *>(data.data());
27+
init(ptr, ptr + data.size(), /*RequiresNullTerminator=*/false);
28+
}
29+
30+
llvm::MemoryBuffer::BufferKind getBufferKind() const override {
31+
return llvm::MemoryBuffer::MemoryBuffer_Malloc;
32+
}
33+
};
34+
35+
} // anonymous namespace
36+
37+
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
38+
OrcCompiler::operator()(llvm::Module &mod) {
39+
std::vector<uint8_t> buf;
40+
if (compiler && compiler->compile_to_elf(mod, buf)) {
41+
return std::make_unique<VectorMemoryBuffer>(std::move(buf));
42+
}
43+
if (tm) {
44+
return llvm::orc::SimpleCompiler(*tm)(mod);
45+
}
46+
return llvm::createStringError("TPDE compilation failed");
47+
}
48+
49+
llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
50+
ConcurrentOrcCompiler::operator()(llvm::Module &mod) {
51+
std::vector<uint8_t> buf;
52+
auto compiler = LLVMCompiler::create(jtmb.getTargetTriple());
53+
if (compiler && compiler->compile_to_elf(mod, buf)) {
54+
return std::make_unique<VectorMemoryBuffer>(std::move(buf));
55+
}
56+
auto tm = llvm::cantFail(jtmb.createTargetMachine());
57+
return llvm::orc::SimpleCompiler(*tm)(mod);
58+
}
59+
60+
} // namespace tpde_llvm

tpde-llvm/tools/tpde-lli.cpp

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <llvm/TargetParser/Triple.h>
2626

2727
#include "tpde-llvm/LLVMCompiler.hpp"
28+
#include "tpde-llvm/OrcCompiler.hpp"
2829

2930
#include <dlfcn.h>
3031
#include <iostream>
@@ -86,10 +87,10 @@ int main(int argc, char *argv[]) {
8687
}
8788
#endif
8889

89-
llvm::LLVMContext context;
90+
auto context = std::make_unique<llvm::LLVMContext>();
9091
llvm::SMDiagnostic diag{};
9192

92-
auto mod = llvm::parseIRFile(ir_path.Get(), diag, context);
93+
auto mod = llvm::parseIRFile(ir_path.Get(), diag, *context);
9394
if (!mod) {
9495
diag.print(argv[0], llvm::errs());
9596
return 1;
@@ -103,13 +104,13 @@ int main(int argc, char *argv[]) {
103104

104105
std::string triple_str = llvm::sys::getProcessTriple();
105106
llvm::Triple triple(triple_str);
106-
auto compiler = tpde_llvm::LLVMCompiler::create(triple);
107-
if (!compiler) {
108-
std::cerr << "Unknown architecture: " << triple_str << "\n";
109-
return 1;
110-
}
111107

112108
if (!orc) {
109+
auto compiler = tpde_llvm::LLVMCompiler::create(triple);
110+
if (!compiler) {
111+
std::cerr << "Unknown architecture: " << triple_str << "\n";
112+
return 1;
113+
}
113114
auto mapper = compiler->compile_and_map(*mod, [](std::string_view name) {
114115
return ::dlsym(RTLD_DEFAULT, std::string(name).c_str());
115116
});
@@ -121,29 +122,23 @@ int main(int argc, char *argv[]) {
121122
return ((int (*)(int, char **))main_addr)(0, nullptr);
122123
}
123124

124-
std::vector<uint8_t> buf;
125-
if (!compiler->compile_to_elf(*mod, buf)) {
126-
std::cerr << "Failed to compile\n";
127-
return 1;
128-
}
129-
llvm::StringRef buf_strref(reinterpret_cast<char *>(buf.data()), buf.size());
130-
auto obj_membuf = llvm::MemoryBuffer::getMemBuffer(buf_strref, "", false);
131-
assert(obj_membuf->getBufferSize());
132-
133125
size_t page_size = exit_on_err(llvm::sys::Process::getPageSize());
134126
llvm::orc::ExecutionSession es(
135127
exit_on_err(llvm::orc::SelfExecutorProcessControl::Create()));
136128
llvm::orc::MapperJITLinkMemoryManager memory_manager(
137129
page_size, std::make_unique<llvm::orc::InProcessMemoryMapper>(page_size));
138130
llvm::orc::ObjectLinkingLayer object_layer(es, memory_manager);
131+
llvm::orc::IRCompileLayer compile_layer(
132+
es, object_layer, std::make_unique<tpde_llvm::OrcCompiler>(triple));
139133
llvm::orc::JITDylib &dylib = exit_on_err(es.createJITDylib("<main>"));
140134

141135
// TODO: correct global prefix for MachO/COFF platforms
142136
dylib.addGenerator(exit_on_err(
143137
llvm::orc::DynamicLibrarySearchGenerator::GetForCurrentProcess(
144138
/*GlobalPrefix=*/'\0')));
145139

146-
exit_on_err(object_layer.add(dylib, std::move(obj_membuf)));
140+
llvm::orc::ThreadSafeModule tsm(std::move(mod), std::move(context));
141+
exit_on_err(compile_layer.add(dylib, std::move(tsm)));
147142

148143
#if LLVM_VERSION_MAJOR >= 21
149144
object_layer.addPlugin(

0 commit comments

Comments
 (0)