cj
is a small JIT framework written in C, with x86 and arm64 backends.
what
- multi-architecture support: generates native code for:
- x86-64
- ARM64 (except for 26 SIMD different-size instructions)
- low-level API: direct instruction emission, no high level constructs
- no dependencies: pure C with clib, nothing else
how
the backends are autogenerated (check out codegen). x86 uses
asmdb as a data source, arm64 uses a
handgenerated file from mra_tools.
register definitions are hand-crafted.
the rest is handwritten and basically trivial in the grand scheme of jit compilation. examples, tests, and codegen docs contain some llm-generation, so peruse at your own peril.
why
because i wanted to understand the isa for both processor architectures and it seemed like a fun project.
quick start
build
# dev build make dev # "prod" build make all # install (don't) make install
basic usage
#include "ctx.h" #include "op.h" int main(void) { // Create JIT context cj_ctx* cj = create_cj_ctx(); // Emit instructions cj_nop(cj); // NOP cj_ret(cj); // RET // Create executable function cj_fn f = create_cj_fn(cj); // Execute JIT-compiled code! f(); // Cleanup destroy_cj_fn(cj, f); destroy_cj_ctx(cj); return 0; }
you can find some more examples in the examples directory.
builder helpers
For reusable building blocks, the optional builder helpers provide prologue/epilogue setup and structured loops:
#include <stdio.h> #include "builder.h" typedef int (*sum_fn)(int); int main(void) { cj_ctx* cj = create_cj_ctx(); cj_builder_frame frame; cj_builder_fn_prologue(cj, 0, &frame); cj_operand n = cj_builder_arg_int(cj, 0); cj_operand sum = cj_builder_scratch_reg(0); cj_operand i = cj_builder_scratch_reg(1); cj_operand one = cj_make_constant(1); cj_builder_assign(cj, sum, cj_builder_zero_operand()); cj_builder_for_loop loop = cj_builder_for_begin(cj, i, one, n, one, CJ_COND_GE); cj_builder_add_assign(cj, sum, i); cj_builder_for_end(cj, &loop); cj_builder_return_value(cj, &frame, sum); sum_fn fn = (sum_fn)create_cj_fn(cj); printf("triangular(5) = %d\n", fn ? fn(5) : -1); destroy_cj_fn(cj, (cj_fn)fn); destroy_cj_ctx(cj); return 0; }
see also docs/builder.md.
requirements
- c11 compiler (gcc, clang)
- POSIX-compliant OS (for
mmap) - supported architecture (x86-64 or ARM64)
Have fun!