hellerve-pl-experiments/cj: a simple x86/arm jit framework for c

is a small JIT framework written in C with x86 and Arm64 backends.

  • multi-architecture support: Generates native code for:
    • x86-64
    • arm64 (except for 26 SIMD instructions of different sizes)
  • low-level API: Direct instruction emission, no high level construction
  • no dependencies:pure C with clib, nothing else

Backends are automatically generated (check out). codegenx86 uses asmdb as data source, Arm64 uses handpicked file from mra_tools. Register definitions are hand-crafted.

Everything else is handwritten and basically trivial in the grand scheme of GIT compilation. The examples, tests, and Codegen documentation involve some LLM-generation, so study at your own risk.

Because I wanted to understand ISA for both processor architectures and it seemed like a fun project.

# dev build
make dev

# "prod" build
make all

# install (don't)
make install
#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 examples Directory.

For reusable building blocks, optional builder Helpful prologue/epilogue setup and structured loops provide:

#include 
#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,

  • c11 compiler (gcc, clang)
  • POSIX-compliant OS(s) mmap,
  • Supported Architecture (x86-64 or ARM64)

have fun!



Leave a Comment