0%

LLVM-JIT

阅读更多

1 Introduction

LLVM JIT stands for “Just-In-Time” compilation in the context of the LLVM (Low Level Virtual Machine) compiler infrastructure. LLVM is a collection of modular and reusable compiler and toolchain technologies designed to optimize and compile programming languages.

The LLVM JIT refers to a feature within LLVM that enables dynamic compilation and execution of code at runtime. Instead of statically compiling the entire program before execution, the JIT compiler compiles parts of the code on-the-fly as they are needed during runtime. This dynamic compilation allows for various optimizations to be applied based on runtime information, potentially improving performance.

Here’s a general overview of how LLVM JIT works:

  1. Parsing: The source code or intermediate representation (IR) of a program is parsed and transformed into an LLVM-specific representation called LLVM IR.
  2. Optimization: The LLVM IR goes through various optimization passes to improve performance, reduce code size, and eliminate redundant operations.
  3. JIT Compilation: When the program is executed, the JIT compiler dynamically compiles LLVM IR into machine code for the target platform. The JIT compilation can occur on a per-function basis or even smaller code units, depending on the specific implementation.
  4. Execution: The compiled machine code is executed immediately, providing the desired functionality to the running program.

The LLVM JIT approach offers several advantages. It allows for dynamic code generation, which is useful in scenarios such as just-in-time language implementations (e.g., dynamic languages like Python, JavaScript) or runtime optimization of performance-critical sections of code. It enables runtime profiling and adaptation, where the JIT compiler can gather information about program behavior during execution and adapt the generated code accordingly. Additionally, the JIT compilation process can leverage the extensive optimization infrastructure of LLVM to generate highly optimized machine code.

Overall, LLVM JIT is a powerful feature of the LLVM framework that enables dynamic compilation and execution, providing flexibility, performance optimizations, and dynamic adaptation for various programming language implementations and runtime environments.

2 Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <iostream>

#include "llvm/ExecutionEngine/Orc/LLJIT.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/TargetSelect.h"

using namespace llvm;
using namespace llvm::orc;

using AddFunctionType = int (*)(int, int);

int main() {
// Initialize LLVM components.
InitializeNativeTarget();
InitializeNativeTargetAsmPrinter();

// Create an LLJIT instance.
auto jit = LLJITBuilder().create();
if (!jit) {
std::cerr << "Failed to create LLJIT: " << toString(jit.takeError()) << std::endl;
return 1;
}

// Create an LLVM module.
LLVMContext ctx;
auto module = std::make_unique<Module>("add_module", ctx);

// Create the add function.
FunctionType* fn_type =
FunctionType::get(Type::getInt32Ty(ctx), {Type::getInt32Ty(ctx), Type::getInt32Ty(ctx)}, false);
Function* fn = Function::Create(fn_type, Function::ExternalLinkage, "add", module.get());

// Create a basic block and set the insertion point.
BasicBlock* block = BasicBlock::Create(ctx, "Entry", fn);
IRBuilder<> builder(block);

// Retrieve arguments, add them, and create a return instruction.
auto args = fn->args().begin();
Value* arg1 = &*args++;
Value* arg2 = &*args;
Value* sum = builder.CreateAdd(arg1, arg2, "sum");
builder.CreateRet(sum);

// Add the module to the JIT and get a handle to the added module.
if (auto err = jit->get()->addIRModule(ThreadSafeModule(std::move(module), std::make_unique<LLVMContext>()))) {
std::cerr << "Failed to add module to LLJIT: " << toString(std::move(err)) << std::endl;
return 1;
}

// Look up the JIT'd function, cast it to a function pointer, then call it.
auto add_function_symbol = jit->get()->lookup("add");
if (!add_function_symbol) {
std::cerr << "Failed to look up function: " << toString(add_function_symbol.takeError()) << std::endl;
return 1;
}

// Cast the symbol's address to a function pointer and call it.
AddFunctionType add_function = (AddFunctionType)add_function_symbol->getAddress();

std::cout << "Result of add(10, 20): " << add_function(10, 20) << std::endl;

return 0;
}
1
2
3
4
5
6
7
8
9
# It works fine for version 13.x and 14.x
# -lpthread: Links against the POSIX threads library.
# -ldl: Links against the dynamic linking loader.
# -lz: Links against the zlib compression library.
# -lncurses: Links against the ncurses library.
clang++ -std=c++17 add_example.cpp `llvm-config --cxxflags --ldflags --libs core orcjit native` -lpthread -ldl -lz -lncurses -o add_example

# Execute
./add_example

3 Reference