/interpreters
Bytecode interpreter and JIT compiler skill for implementing language runtimes in C/C++. Use when designing bytecode dispatch loops (switch, computed goto, threaded code), implementing stack-based or register-based VMs, adding a simple JIT using mmap/mprotect, or understanding
$ npx -y skills add mohitmishra786/low-level-dev-skills --skill interpreters --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/interpreters
Context preview
The summary Claude sees to decide when to auto-load this skill.
Bytecode interpreter and JIT compiler skill for implementing language runtimes in C/C++. Use when designing bytecode dispatch loops (switch, computed goto, threaded code), implementing stack-based or register-based VMs, adding a simple JIT using mmap/mprotect, or understanding
SKILL.md
interpreters.SKILL.mdname: interpreters
description: Bytecode interpreter and JIT compiler skill for implementing language runtimes in C/C++. Use when designing bytecode dispatch loops (switch, computed goto, threaded code), implementing stack-based or register-based VMs, adding a simple JIT using mmap/mprotect, or understanding performance trade-offs in interpreter design. Activates on queries about bytecode VMs, dispatch loops, computed goto, JIT compilation basics, tracing JITs, or implementing a scripting language runtime.
Interpreters and Bytecode VMs
Purpose
Guide agents through implementing efficient bytecode interpreters and simple JITs in C/C++: dispatch strategies, VM architecture choices, and performance patterns.
Triggers
- "How do I implement a fast bytecode dispatch loop?"
- "What is the difference between switch dispatch and computed goto?"
- "How do I implement a register-based vs stack-based VM?"
- "How do I add basic JIT compilation to my interpreter?"
- "Why is my interpreter slow?"
Workflow
1. VM architecture choice
| Style | Description | Examples | |-------|-------------|---------| | Stack-based | Operands on a value stack; compact bytecode | JVM, CPython, WebAssembly | | Register-based | Operands in virtual registers; fewer instructions | Lua 5+, Dalvik | | Direct threading | Each "instruction" is a function call | Some Forth implementations | | Continuation-passing | Interpreter functions return continuations | Academic |
**Stack-based:** easier to implement, compile to; code generation is simpler. More instructions per expression. **Register-based:** fewer dispatch iterations; needs register allocation in the compiler; better cache behaviour for complex expressions.
2. Dispatch loop strategies
Switch dispatch (simplest, baseline)
while (1) {
uint8_t op = *ip++;
switch (op) {
case OP_LOAD: push(constants[*ip++]); break;
case OP_ADD: { Value b = pop(); Value a = pop(); push(a + b); } break;
case OP_HALT: return;
// ...
}
}Problem: `switch` compiles to a single indirect branch from a jump table. Modern CPUs can mispredict it heavily because the same indirect branch is used for all opcodes.
Computed goto (GCC/Clang extension — fastest portable approach)
// Table of label addresses
static const void *dispatch_table[] = {
[OP_LOAD] = &&op_load,
[OP_ADD] = &&op_add,
[OP_HALT] = &&op_halt,
// ...
};
#define DISPATCH() goto *dispatch_table[*ip++]
DISPATCH(); // start
op_load:
push(constants[*ip++]);
DISPATCH();
op_add: {
Value b = pop(); Value a = pop(); push(a + b);
DISPATCH();
}
op_halt:
return;Each opcode ends with its own indirect branch. The CPU can train the branch predictor per-opcode, dramatically improving prediction rates.
**Note:** `&&label` is a GCC/Clang extension, not standard C. Use `#ifdef __GNUC__` to guard and fall back to switch for other compilers.
Direct threaded code (most aggressive)
Each bytecode word is a function pointer or label address; the VM is the fetch-decode-execute loop itself.
typedef void (*Handler)(VM *vm);
// Bytecode is an array of handlers
Handler bytecode[] = { op_load, op_push_1, op_add, op_halt };
for (int i = 0; ; i++) {
bytecode[i](vm);
}3. Value representation
**Tagged pointer:** Store type tag in low bits of pointer (pointer alignment guarantees ≥ 2 bits free):
typedef uintptr_t Value;
#define TAG_INT 0x0
#define TAG_FLOAT 0x1
#define TAG_PTR 0x2
#define TAG_MASK 0x3
#define INT_VAL(v) ((int64_t)(v) >> 2)
#define FLOAT_VAL(v) (*(float*)((v) & ~TAG_MASK))
#define IS_INT(v) (((v) & TAG_MASK) == TAG_INT)
**NaN boxing (64-bit):** Store non-double values in NaN bit patterns:
// IEEE 754 quiet NaN: exponent all 1s, mantissa != 0
// Use high mantissa bits as type tag, low 48 bits as payload
// Allows pointer/int/bool/nil to fit in a double-sized slot
Used by V8 (formerly), LuaJIT, JavaScriptCore.
4. Stack management
#define STACK_SIZE 4096
Value stack[STACK_SIZE];
Value *sp = stack; // stack pointer
#define PUSH(v) (*sp++ = (v))
#define POP() (*--sp)
#define TOP() (sp[-1])
#define PEEK(n) (sp[-(n)-1])
// Check for overflow
#define PUSH_SAFE(v) do { \
if (sp >= stack + STACK_SIZE) { vm_error("stack overflow"); } \
PUSH(v); \
} while(0)5. Inline caching (IC)
Inline caching speeds up property lookups and method dispatch by caching the last observed type at each call site.
struct CallSite {
Type cached_type; // Last observed receiver type
void *cached_method; // Cached function pointer
int miss_count; // Number of misses
};
void invoke_method(VM *vm, CallSite *cs, Value receiver, ...) {
Type t = GET_TYPE(receiver);
if (t == cs->cached_type) {
// Cache hit: direct call, no lookup
cs->cached_method(vm, receiver, ...);
} else {
// Cache miss: look up, update cache
void *method = lookup_method(t, name);
cs->cached_type = t;
cs->cached_method = method;
cs->miss_count++;
method(vm, receiver, ...);
}
}Polymorphic IC (PIC): cache up to N (typ. 4) type-method pairs.
6. Simple JIT (mmap + machine code)
For x86-64: allocate executable memory, write machine code bytes, call it.
#include <sys/mman.h>
#include <string.h>
typedef int (*JitFn)(int a, int b);
JitFn jit_compile_add(void) {
// x86-64: add rdi, rsi; mov rax, rdi; ret
static const uint8_t code[] = {
0x48, 0x01, 0xF7, // add rdi, rsi
0x48, 0x89, 0xF8, // mov rax, rdi
0xC3 // ret
};
void *mem = mmap(NULL, sizeof(code),
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) return NULL;
memcpy(mem, code, sizeof(code));
// On some systems (macOS Apple SilicRead more
name: interpreters description: Bytecode interpreter and JIT compiler skill for implementing language runtimes in C/C++. Use when designing bytecode dispatch loops (switch, computed goto, threaded code), implementing stack-based or register-based VMs, adding a simple JIT using mmap/mprotect, or understanding performance trade-offs in interpreter design. Activates on queries about bytecode VMs, dispatch loops, computed goto, JIT compilation basics, tracing JITs, or implementing a scripting language runtime.
Interpreters and Bytecode VMs
Purpose
Guide agents through implementing efficient bytecode interpreters and simple JITs in C/C++: dispatch strategies, VM architecture choices, and performance patterns.
Triggers
- "How do I implement a fast bytecode dispatch loop?"
- "What is the difference between switch dispatch and computed goto?"
- "How do I implement a register-based vs stack-based VM?"
- "How do I add basic JIT compilation to my interpreter?"
- "Why is my interpreter slow?"
Workflow
1. VM architecture choice
| Style | Description | Examples | |-------|-------------|---------| | Stack-based | Operands on a value stack; compact bytecode | JVM, CPython, WebAssembly | | Register-based | Operands in virtual registers; fewer instructions | Lua 5+, Dalvik | | Direct threading | Each "instruction" is a function call | Some Forth implementations | | Continuation-passing | Interpreter functions return continuations | Academic |
**Stack-based:** easier to implement, compile to; code generation is simpler. More instructions per expression. **Register-based:** fewer dispatch iterations; needs register allocation in the compiler; better cache behaviour for complex expressions.
2. Dispatch loop strategies
Switch dispatch (simplest, baseline)
while (1) {
uint8_t op = *ip++;
switch (op) {
case OP_LOAD: push(constants[*ip++]); break;
case OP_ADD: { Value b = pop(); Value a = pop(); push(a + b); } break;
case OP_HALT: return;
// ...
}
}Problem: `switch` compiles to a single indirect branch from a jump table. Modern CPUs can mispredict it heavily because the same indirect branch is used for all opcodes.
Computed goto (GCC/Clang extension — fastest portable approach)
// Table of label addresses
static const void *dispatch_table[] = {
[OP_LOAD] = &&op_load,
[OP_ADD] = &&op_add,
[OP_HALT] = &&op_halt,
// ...
};
#define DISPATCH() goto *dispatch_table[*ip++]
DISPATCH(); // start
op_load:
push(constants[*ip++]);
DISPATCH();
op_add: {
Value b = pop(); Value a = pop(); push(a + b);
DISPATCH();
}
op_halt:
return;Each opcode ends with its own indirect branch. The CPU can train the branch predictor per-opcode, dramatically improving prediction rates.
**Note:** `&&label` is a GCC/Clang extension, not standard C. Use `#ifdef __GNUC__` to guard and fall back to switch for other compilers.
Direct threaded code (most aggressive)
Each bytecode word is a function pointer or label address; the VM is the fetch-decode-execute loop itself.
typedef void (*Handler)(VM *vm);
// Bytecode is an array of handlers
Handler bytecode[] = { op_load, op_push_1, op_add, op_halt };
for (int i = 0; ; i++) {
bytecode[i](vm);
}3. Value representation
**Tagged pointer:** Store type tag in low bits of pointer (pointer alignment guarantees ≥ 2 bits free):
typedef uintptr_t Value; #define TAG_INT 0x0 #define TAG_FLOAT 0x1 #define TAG_PTR 0x2 #define TAG_MASK 0x3 #define INT_VAL(v) ((int64_t)(v) >> 2) #define FLOAT_VAL(v) (*(float*)((v) & ~TAG_MASK)) #define IS_INT(v) (((v) & TAG_MASK) == TAG_INT)
**NaN boxing (64-bit):** Store non-double values in NaN bit patterns:
// IEEE 754 quiet NaN: exponent all 1s, mantissa != 0 // Use high mantissa bits as type tag, low 48 bits as payload // Allows pointer/int/bool/nil to fit in a double-sized slot
Used by V8 (formerly), LuaJIT, JavaScriptCore.
4. Stack management
#define STACK_SIZE 4096
Value stack[STACK_SIZE];
Value *sp = stack; // stack pointer
#define PUSH(v) (*sp++ = (v))
#define POP() (*--sp)
#define TOP() (sp[-1])
#define PEEK(n) (sp[-(n)-1])
// Check for overflow
#define PUSH_SAFE(v) do { \
if (sp >= stack + STACK_SIZE) { vm_error("stack overflow"); } \
PUSH(v); \
} while(0)5. Inline caching (IC)
Inline caching speeds up property lookups and method dispatch by caching the last observed type at each call site.
struct CallSite {
Type cached_type; // Last observed receiver type
void *cached_method; // Cached function pointer
int miss_count; // Number of misses
};
void invoke_method(VM *vm, CallSite *cs, Value receiver, ...) {
Type t = GET_TYPE(receiver);
if (t == cs->cached_type) {
// Cache hit: direct call, no lookup
cs->cached_method(vm, receiver, ...);
} else {
// Cache miss: look up, update cache
void *method = lookup_method(t, name);
cs->cached_type = t;
cs->cached_method = method;
cs->miss_count++;
method(vm, receiver, ...);
}
}Polymorphic IC (PIC): cache up to N (typ. 4) type-method pairs.
6. Simple JIT (mmap + machine code)
For x86-64: allocate executable memory, write machine code bytes, call it.
#include <sys/mman.h>
#include <string.h>
typedef int (*JitFn)(int a, int b);
JitFn jit_compile_add(void) {
// x86-64: add rdi, rsi; mov rax, rdi; ret
static const uint8_t code[] = {
0x48, 0x01, 0xF7, // add rdi, rsi
0x48, 0x89, 0xF8, // mov rax, rdi
0xC3 // ret
};
void *mem = mmap(NULL, sizeof(code),
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (mem == MAP_FAILED) return NULL;
memcpy(mem, code, sizeof(code));
// On some systems (macOS Apple SilicA curated suite of AI agent skills for systems and low-level programming — C/C++, Rust, Zig, GPU, bare-metal firmware, Linux kernel/driver development, computer architecture, compiler internals, HPC, and more.
Repo: mohitmishra786/low-level-dev-skills
Other skills on low-level-dev-skills.
- /custom-allocators
Custom allocator skill for memory allocation strategies. Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc, writing Rust GlobalAlloc, or benchmarking allocator performance. Activates on queries about jemalloc, mimalloc, tcmalloc, arena allocator,
Open skill - /numa-programming
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
Open skill - /af-xdp
AF_XDP skill for high-performance XDP sockets. Use when creating AF_XDP sockets, configuring UMEM and XSK rings, XDP_REDIRECT programs, copy vs zero-copy mode, or comparing with DPDK. Activates on queries about AF_XDP, xsk_umem, XDP_REDIRECT, libbpf xsk, or zero-copy XDP.
Open skill - /dpdk
DPDK skill for userspace packet I/O. Use when initializing EAL, configuring PMD drivers, using mbuf pools and rte_ring, setting up huge pages, RSS, or testpmd validation. Activates on queries about DPDK, EAL, rte_eth_rx_burst, hugepages, PMD, or testpmd.
Open skill - /io-uring
io_uring skill for Linux async I/O. Use when building high-performance servers with liburing, multi-shot operations, provided buffers, fixed files, zero-copy send, or tokio-uring. Activates on queries about io_uring, SQE/CQE, liburing, IORING_OP_PROVIDE_BUFFERS, or io_uring vs
Open skill - /adc-dac-baremetal
Bare-metal ADC and DAC skill. Use when configuring analog sampling, DMA-driven ADC, calibration, or DAC output on MCUs. Activates on queries about ADC bare-metal, sampling time, DMA ADC, or DAC channel setup.
Open skill

