MicroJIT is a lightweight Dynamic Compilation Library designed to enable developers with efficient runtime code generation.
MicroJIT makes extensive use of variadic template and attributes, which require C++ 17 or above. It has been tested with clang 15.0.7 and gcc 13.2.1.
To use MicroJIT, start by cloning this repo to your CMake project:
git clone --recurse-submodules https://github.com/cycastic-cumberland/microjit.git
Add these lines to your CMakeLists.txt:
set(MICROJIT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/microjit)
set(ASMJIT_SOURCE_DIR ${MICROJIT_SOURCE_DIR}/asmjit)
add_subdirectory(${MICROJIT_SOURCE_DIR})
add_subdirectory(${ASMJIT_SOURCE_DIR})
include_directories(${ASMJIT_SOURCE_DIR}/src)
include_directories(${MICROJIT_SOURCE_DIR}/src)
target_link_libraries(your_project_name asmjit microjit)
The creation of MicroJIT stems from the need for compiler for my own programming language. Due to its nature, the language must either parse or compile its bytecode at runtime, so I decided to look into JIT compilers. Despite how hard I look, I could only find two libraries that fits my criteria: NativeJIT and AsmJit. NativeJIT is made by the team behind Bing, but it has gone inactive for years now, and I find its documentary lacking. AsmJit is an active project, is well documented and has many contributors, but its APIs are not user-friendly, without the integration of a lot of C++ features. That's why decided to create a library on top of AsmJit, which (will) make it easier for C++ developers to dynamically generate codes.
Start by including microjit/orchestrator.h
The MicroJITOrchestrator
class is the heart of MicroJIT, which compile and function_cache FunctionInstance
objects. By default, it is atomically reference-counted, which allows it to be used concurrently and can be cleaned-up automatically, as long as there's no nested reference.
All other major components of MicroJIT are also reference counted (albeit not atomically), so no further memory management is needed.
A FunctionInstance
is a utility class which manage and compile your functions. FunctionInstance
support lazy compilation, the first time it is invoked, it will forward the compilation request to the Orchestrator, which will in turn return the compiled callback, provided by AsmJit. Any subsequence invocation will use the compiled callback.
The Orchestrator actually return an InstanceWrapper
instead of the FunctionInstance
itself, but you could use it by dereferencing the InstanceWrapper
.
auto re1 = instance(12); // This will compile the function and call it
auto re2 = instance(122); // This will call the compiled code immediately
To edit your function, first call get_function()
from the FunctionInstance
object.
auto function = instance->get_function();
The return value is of type Ref<Function<R, Args...>>
.
This generic object store the argument list and (generic) main scope.
All function generic scopes are just wrapper for RectifiedScope
,
which does not use template (at least at class level),
allowing IntelliSense to work without all template arguments filled.
Despite this, type checking still work thanks to the serialized type data provided by Type
.
template<typename T>
static constexpr Type get_type_info(){
return Type::create<T>();
}
All instructions are issued through Scope<R, Args...>
. All variables can only be constructed inside the scope that owns it.
- Lazy compilation
- x86_64 support
- Generic functions
- Variables
- Literals
- Arguments
- Return value
- Orchestrator's basic functionalities
- Construction/Copy construction/Destruction
- Multiple scopes
- Type casting/Coercion (Non-inline)
- Expressions
- Primitive Operations
- Branches (if/else/while)
- JIT compiled function call
- Compile-and-go for JIT compiled function call
- Native function call
- Documentation
- More optimizations
- (Overloaded) Operations
- Asynchronous compilation
- Function dependencies analysis
- x86 and Windows support
- TBA...
This library has been tested on the following target(s):
gcc (Linux) | clang (Linux) | MSVC (Windows) | MinGW (Windows) | |
---|---|---|---|---|
x86 | ||||
x86_64 | ✔ | ✔ |
MicroJIT currently does not work with clang and gcc when built with -O1 and -O2 flag, so it is highly advisable that you built it with -O0 flag as a library and link it to your project. If you decided to embed MicroJIT instead, make sure your project use the -O0 flag.
See LICENSE.txt
This project use the AsmJit library (See LICENSE)
#include <microjit/orchestrator.h>
#include <iostream>
int main(){
auto orchestrator = microjit::orchestrator();
auto instance = orchestrator->create_instance<int, int>();
// Use this if you have a function as mold
// std::function<int(int)> mold;
// auto instance = orchestrator->create_instance_from_model(mold);
auto main_scope = instance->get_function()->get_main_scope();
// Create stack space for a var1
auto var1 = main_scope->create_variable<int>();
// Assign var1 as the value of the first argument
main_scope->construct_from_argument(var1, 0);
// Return var1
main_scope->function_return(var1);
// Compile and run the function
auto re = instance(12);
if (re == 12)
std::cout << "It worked!\n";
// instance is now compiled and any subsequence invocation will use the compiled function
// To recompile:
// instance->recompile();
return 0;
}