Compilation Modes
JustJIT supports 12 native compilation modes. Each mode compiles Python functions to work with a specific LLVM type, eliminating Python object overhead.
Mode Summary
Mode |
LLVM Type |
Description |
|---|---|---|
|
PyObject* |
Full Python semantics. Default mode, most compatible. |
|
i64 |
64-bit signed integer. Best for integer math and loops. |
|
f64 |
64-bit floating point. Best for floating-point arithmetic. |
|
i1 |
Boolean values. Best for boolean logic functions. |
|
i32 |
32-bit signed integer. C interop and memory efficiency. |
|
f32 |
32-bit floating point. SIMD preparation and ML. |
|
{f64, f64} |
Double-precision complex number (real, imag). |
|
{f32, f32} |
Single-precision complex number. |
|
ptr |
Pointer to f64 array. Direct array access. |
|
<4 x f32> |
SSE SIMD vector (4 floats). |
|
<8 x i32> |
AVX SIMD vector (8 integers). |
|
{i64, f64} |
Nullable float64 with None handling. |
Object Mode (auto)
The auto mode (also called object mode) is the default. It compiles functions while preserving full Python semantics:
@justjit.jit # mode='auto' is the default
def flexible_add(a, b):
return a + b
flexible_add(1, 2) # Works with integers
flexible_add(1.5, 2.5) # Works with floats
flexible_add("hello", " ") # Works with strings
This mode generates LLVM IR that calls Python C API functions (PyNumber_Add, PyObject_GetAttr, etc.), so it maintains full compatibility with any Python type.
Supported operations:
All Python operations via C API calls
Exception handling (try/except/finally)
Pattern matching (match/case)
Context managers (with statements)
Closures and nested functions
Generators and async functions
Integer Mode (int)
The int mode compiles functions to use native 64-bit integers.
@justjit.jit(mode='int')
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
fibonacci(50) # Returns 12586269025
Supported operations:
Arithmetic:
+,-,*,//,%,**Comparison:
==,!=,<,>,<=,>=Bitwise:
&,|,^,~,<<,>>Range loops:
for i in range(n)
LLVM IR:
define i64 @fibonacci(i64 %n) {
; Native integer operations
%add = add i64 %a, %b
ret i64 %result
}
Float Mode (float)
The float mode compiles functions to use native 64-bit floating point.
@justjit.jit(mode='float')
def distance(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
return (dx * dx + dy * dy) ** 0.5
distance(0.0, 0.0, 3.0, 4.0) # Returns 5.0
Supported operations:
Arithmetic:
+,-,*,/,//,%,**Comparison:
==,!=,<,>,<=,>=Range loops:
for i in range(n)
LLVM IR:
define double @distance(double %x1, double %y1, double %x2, double %y2) {
%dx = fsub double %x2, %x1
%dy = fsub double %y2, %y1
%fadd = fadd double %dx_sq, %dy_sq
ret double %sqrt_result
}
Bool Mode (bool)
The bool mode compiles functions for boolean logic.
@justjit.jit(mode='bool')
def is_valid(a, b):
return a and not b
is_valid(True, False) # Returns True
Supported operations:
Logical:
and,or,notComparison:
==,!=
Complex128 Mode (complex128)
The complex128 mode handles double-precision complex numbers.
@justjit.jit(mode='complex128')
def mandelbrot_step(z, c):
return z * z + c
mandelbrot_step(1+2j, 0.5+0.5j)
The complex number is stored as a struct {double real, double imag}.
Supported operations:
Arithmetic:
+,-,*,/
Complex64 Mode (complex64)
The complex64 mode uses single-precision complex numbers for memory efficiency.
@justjit.jit(mode='complex64')
def complex_multiply(a, b):
return a * b
complex_multiply(3+4j, 1+2j) # Returns (-5+10j)
Same operations as complex128 but with 32-bit floats.
Pointer Mode (ptr)
The ptr mode enables direct array access via pointers.
import ctypes
@justjit.jit(mode='ptr')
def array_sum(arr, length):
# arr is a pointer, length is count
total = 0.0
for i in range(length):
total = total + arr[i]
return total
# Create a C-compatible array
data = (ctypes.c_double * 4)(1.0, 2.0, 3.0, 4.0)
ptr = ctypes.addressof(data)
array_sum(ptr, 4) # Returns 10.0
This mode is useful for NumPy interop and high-performance array operations.
Vec4f Mode (vec4f)
The vec4f mode uses SSE SIMD operations on 4 floats simultaneously.
@justjit.jit(mode='vec4f')
def vec_add(a, b):
return a + b
# Operations on 4 floats at once
# Input: two <4 x float> vectors
# Output: <4 x float> sum
Pointer-Based ABI:
SIMD modes use a pointer-based ABI for Windows x64 compatibility:
// Actual signature: void fn(float* out, float* a, float* b)
// Instead of: <4 x float> fn(<4 x float> a, <4 x float> b)
The callable wrapper handles this transparently.
LLVM IR (internal):
define void @vec_add(ptr %out, ptr %a, ptr %b) {
%vec_a = load <4 x float>, ptr %a, align 16 ; 16-byte alignment for SSE
%vec_b = load <4 x float>, ptr %b, align 16
%result = fadd <4 x float> %vec_a, %vec_b
store <4 x float> %result, ptr %out, align 16
ret void
}
Vec8i Mode (vec8i)
The vec8i mode uses AVX SIMD operations on 8 integers simultaneously.
@justjit.jit(mode='vec8i')
def vec_mul(a, b):
return a * b
# Operations on 8 i32 values at once
Pointer-Based ABI:
Like vec4f, uses pointer-based ABI:
// Actual signature: void fn(int32_t* out, int32_t* a, int32_t* b)
LLVM IR (internal):
define void @vec_mul(ptr %out, ptr %a, ptr %b) {
%vec_a = load <8 x i32>, ptr %a, align 32 ; 32-byte alignment for AVX
%vec_b = load <8 x i32>, ptr %b, align 32
%result = mul <8 x i32> %vec_a, %vec_b
store <8 x i32> %result, ptr %out, align 32
ret void
}
Optional_f64 Mode (optional_f64)
The optional_f64 mode handles nullable float values with None support.
@justjit.jit(mode='optional_f64')
def safe_divide(a, b):
if b == 0:
return None
return a / b
safe_divide(10.0, 2.0) # Returns 5.0
safe_divide(10.0, 0.0) # Returns None
The nullable value is stored as a struct {i64 has_value, double value}.
None propagation:
If any operand is None, the result is None
Binary operations check both operands for None before computing
LLVM IR:
; Struct: {i64 has_value, double value}
define void @safe_divide(ptr %out, ptr %a, ptr %b) {
%a_val = load {i64, double}, ptr %a
%b_val = load {i64, double}, ptr %b
%a_has = extractvalue {i64, double} %a_val, 0
%b_has = extractvalue {i64, double} %b_val, 0
%both_have = and i64 %a_has, %b_has
; ... compute if both have values
store {i64, double} %result, ptr %out
ret void
}
Int32 and Float32 Modes
The int32 and float32 modes use 32-bit values for C interop and memory efficiency:
@justjit.jit(mode='int32')
def small_add(a, b):
return a + b
@justjit.jit(mode='float32')
def ml_operation(a, b):
return a * b
These modes are useful when:
Interfacing with C code that uses 32-bit types
Working with ML frameworks that use float32
Memory bandwidth is a bottleneck
Choosing the Right Mode
Use this decision tree:
Working with integers? Use
intmode.Working with floats? Use
floatmode.Working with complex numbers? Use
complex128orcomplex64.Need None/nullable values? Use
optional_f64.Working with arrays directly? Use
ptrmode.Need SIMD parallelism? Use
vec4forvec8i.C interop with 32-bit types? Use
int32orfloat32.Mixed types or full Python semantics? Use
auto(default).
Performance tip: Native modes avoid Python object overhead entirely. For compute-heavy loops, the speedup can be 1,000x to 100,000x compared to the interpreter.