A simple Just-In-Time (JIT) written in C that compiles bytecode instructions to native machine code using template-driven dispatch. It executes the compiled code directly in memory and supports basic math, comparison and bitwise operations with register manipulation.
Note
This project is not production-ready and should be used as a learning resource. It intended for educational purposes to demonstrate JIT compilation concepts as part of an upcoming tutorial I'm creating.
- Bytecode Compilation: Converts virtual machine bytecode to native x86-64 instructions
- Register Management: Handles virtual register to native register mapping
- Constant Pool: Supports loading constants into registers
- Basic Operations: Implements a variety of arithmetic, comparison, and bitwise operations
- Memory Management: Handles executable code generation and memory allocation
- Cross-Platform Build: Supports multiple compilers (GCC, clang, Cosmopolitan
cosmocc)
The current implementation supports the following bytecode operations:
OP_LOADK- Load constant into registerOP_MOV- Move value between registersOP_ADD- Add two registersOP_SUB- Subtract two registersOP_MUL- Multiply two registersOP_DIV- Divide two registersOP_MOD- Floating point remainderOP_NEG- Negate a valueOP_EQ- Equality comparisonOP_NE- Inequality comparisonOP_LT- Less than comparisonOP_LE- Less than or equal to comparisonOP_GT- Greater than comparisonOP_GE- Greater than or equal to comparisonOP_AND- Bitwise ANDOP_OR- Bitwise OROP_XOR- Bitwise XOROP_NOT- Bitwise NOTOP_SHL- Bitwise shift leftOP_SHR- Bitwise shift rightOP_CALL- Function callOP_RETURN- Return from functionOP_JMP- Unconditional jumpOP_TEST- Conditional jumpOP_GETPROP- Property accessOP_SETPROP- Property assignment
The Makefile defaults to clang but can be configured to use others:
# Use Clang
make TOOLCHAIN=clang
# Use GCC
make TOOLCHAIN=gcc
# Use Cosmocc
make TOOLCHAIN=cosmo
# Auto-detect compiler
make TOOLCHAIN=auto# Build the project
make
# Clean build artifacts
make clean
# Rebuild from scratch
make rebuild
# Run the compiled program
make run
# Show build configuration
make info# Install to default location (~/.local/bin)
make install
# Install to custom location
make PREFIX=/usr/local install
# Uninstall
make uninstallThe main program demonstrates a simple JIT compilation by adding 10.00 and 20.0 with a result of 30.0:
// Sample bytecode: LOADK R0, #0; LOADK R1, #1; ADD R2, R0, R1
Instruction bytecode[] = {
(OP_LOADK) | (0 << 8) | (0 << 16), // LOADK R0, constant[0]
(OP_LOADK) | (1 << 8) | (1 << 16), // LOADK R1, constant[1]
(OP_ADD) | (2 << 8) | (0 << 16) | (1 << 24) // ADD R2, R0, R1
};
// Constants
Value constants[2];
constants[0].number = 10.0;
constants[1].number = 20.0;
// Compile and execute
void* native_code = Compile(bytecode, 3, registers, constants, 2);Before JIT compilation:
R0: 0.000000
R1: 0.000000
R2: 0.000000
JIT compilation successful, executing...
After execution:
R0: 10.000000
R1: 20.000000
R2: 30.000000 (should be 30.0)
Instructions are encoded as 32-bit values:
- Bits 0-7: Opcode
- Bits 8-15: Register A
- Bits 16-23: Register B
- Bits 24-31: Register C
Uses NaN-boxing for efficient value storage:
- Numbers stored as
double - Other types tagged in unused NaN bits
- Supports numbers, strings, objects, booleans, null, and undefined
- Maps virtual registers (0-255) to native x86-64 registers
- Tracks register usage to prevent conflicts
- Supports 16 native registers (
RAX,RCX,RDX, etc.)
# Build with clang and debug with LLDB
make TOOLCHAIN=clang debug
# Build with GCC and debug with GDB
make TOOLCHAIN=gcc debug- Define the opcode in
src/bytecode.h - Implement the template function in
src/bytecode.c - Add the template to the opcode table in
src/compiler.c - Test with sample bytecode
This project is licensed under the MIT License - see the LICENSE file for details.
