К основному содержанию
скилл CX Codex

interpreters — скилл для Claude Code: байткод-интерпретаторы и JIT

Подсказывает агенту, как устроить диспетчеризацию байткода, стековую или регистровую ВМ и простой JIT через mmap.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add mohitmishra786/low-level-dev-skills --skill interpreters -a claude-code
Открыть на GitHub

Что делает interpreters

Скилл разбирает, как писать интерпретаторы и небольшие JIT-компиляторы на C и C++. Внутри — рабочий процесс из нескольких шагов: выбор архитектуры ВМ, стратегия цикла диспетчеризации, представление значений, управление стеком, инлайн-кэширование и минимальный JIT.

По диспетчеризации даны три варианта с примерами кода: switch, computed goto и прямое шитьё. Computed goto помечен как расширение GCC/Clang, не стандартный C, поэтому его советуют оборачивать в #ifdef __GNUC__ и оставлять switch как запасной путь для других компиляторов. Для JIT показан пример на x86-64: выделение исполняемой памяти через mmap, запись байтов машинного кода, вызов. На macOS Apple Silicon нужен pthread_jit_write_protect_np() или флаг MAP_JIT — иначе код просто не выполнится.

Есть раздел с советами по производительности: держать указатель стека в callee-saved регистре, использовать NaN boxing или тегированные указатели, искать узкие места через perf, делать специализированные обработчики под частые комбинации типов. Скилл пригодится, когда интерпретатор тормозит и непонятно, где именно теряется время.

Какие задачи решает

  • выбрать между стековой и регистровой ВМ под свой язык
  • ускорить цикл диспетчеризации байткода
  • добавить простой JIT без внешних библиотек
  • разобраться, почему интерпретатор упирается в предсказание переходов
  • уложить значения в один слот через NaN boxing или тегированный указатель

Примеры запросов агенту

  • Покажи, как переписать мой цикл switch на computed goto с запасным вариантом для MSVC
  • Сравни стековую и регистровую ВМ для языка с замыканиями — что выбрать
  • Напиши минимальный JIT на mmap, который компилирует сложение двух int

Как установить interpreters

1Через Skills CLI (Vercel)

npx skills add mohitmishra786/low-level-dev-skills --skill interpreters -a claude-code

2Вручную: положить файлы

~/.claude/skills/interpreters/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/interpreters/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о interpreters

Как установить interpreters в Claude?

Через установщик скиллов: npx skills add mohitmishra786/low-level-dev-skills --skill interpreters -a claude-code. После этого файл скилла лежит в ~/.claude/skills/interpreters/SKILL.md.

Что делает interpreters?

Проводит агента по реализации байткод-интерпретатора и простого JIT на C/C++: архитектура ВМ, циклы диспетчеризации, представление значений, инлайн-кэши, советы по производительности.

На чём сломается пример с JIT?

На macOS Apple Silicon: обычный mmap с PROT_EXEC там не сработает, нужен pthread_jit_write_protect_np() или флаг MAP_JIT. Computed goto тоже не везде — это расширение GCC и Clang, для остальных компиляторов нужен запасной switch.

Исходный текст

SKILL.md · MIT
---
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)

```c
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)

```c
// 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.

```c
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):

```c
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:

```c
// 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

```c
#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.

```c
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.

```c
#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 Silicon): must use mprotect
    // mprotect(mem, sizeof(code), PROT_READ | PROT_EXEC);

    return (JitFn)mem;
}
```

On macOS Apple Silicon (M-series): use `pthread_jit_write_protect_np()` or `MAP_JIT` flag.

### 7. Performance tips

1. **Dispatch:** Use computed goto over switch on GCC/Clang
2. **Values:** Use NaN boxing or tagged pointers; avoid boxing/unboxing in hot paths
3. **Stack:** Keep stack pointer in a callee-saved register (`register Value *sp asm("r15")` — GCC global register variable)
4. **Locals access:** Keep frequently accessed locals in VM registers (struct fields), not stack
5. **Profiling:** Use `perf` or sampling to find dispatch overhead vs actual work
6. **Specialisation:** Generate specialised handler variants for common type combinations (int+int add vs generic add)
7. **Trace recording:** Trace JITs (LuaJIT approach) compile hot traces instead of full functions

For a benchmark of dispatch strategies, see [references/benchmarks.md](references/benchmarks.md).

## Related skills

- Use `skills/profilers/linux-perf` to profile the interpreter dispatch loop
- Use `skills/low-level-programming/assembly-x86` to understand JIT output
- Use `skills/runtimes/fuzzing` to fuzz the bytecode parser/loader
- Use `skills/compilers/llvm` for LLVM IR-based JIT (MCJIT / ORC JIT)

© mohitmishra786 · MIT · mohitmishra786/low-level-dev-skills

Похожие скиллы

Все скиллы по теме «Языки программирования» →
vm-and-bytecode-reverse yaklang Плейбук по реверсу самописных виртуальных машин и байткода: находит диспетчер, разбирает опкоды, помогает писать дизассемблер. CX Codex +1 3 700установок machine-learning Mindrally Скилл для разработки на JAX: подсказывает агенту функциональные приёмы, JIT и работу с ключами генератора случайных чисел. CX Codex +1 2 100установок jax-best-practices Mindrally Скилл задаёт агенту правила работы с JAX: чистые функции, jit, vmap, grad и профилирование. Пригодится, если вы пишете численный код или обучаете модели на JAX. CX Codex +1 704установок dotnet-pinvoke dotnet Скилл учит агента вызывать функции C и C++ из .NET через DllImport и LibraryImport. Помогает переносить сигнатуры без повреждения памяти и настраивать маршалинг строк. CX Codex +1 3 500установок nodejs-core mcollina Скилл для правок и отладки ядра Node.js: коммиты и PR в nodejs/node, нативные краши, V8, node-gyp, N-API и libuv. CX Codex +1 1 900установок browser-exploitation-v8 yaklang Плейбук по эксплуатации уязвимостей JavaScript-движка: JIT type confusion, обход границ массива, побег из песочницы V8. Для CTF и разбора багов Chrome. CX Codex +1 3 600установок

Из того же набора62

linkers-lto Скилл для настройки линкеров GNU ld, gold и lld, разбора ошибок порядка библиотек и включения LTO в реальных проектах. CX Codex +1 608установок binutils Скилл для работы с бинарниками: статические библиотеки, strip, objcopy, addr2line, strings и c++filt. Пригодится, когда агент должен сам разобраться с постобработкой ELF. CX Codex +1 608установок dynamic-linking Скилл для работы с динамической линковкой в Linux: разбирает ошибки загрузки .so, настройку RPATH и RUNPATH, версионирование soname. CX Codex +1 600установок rust-profiling Скилл для анализа производительности Rust: флеймграфы, размер бинарника, раздувание мономорфизации и микробенчмарки Criterion. CX Codex +1 586установок rust-ffi Ведёт агента по FFI между Rust и C: генерация биндингов через bindgen и cbindgen, безопасные обёртки, линковка системных библиотек. CX Codex +1 563установок meson Скилл для ИИ-агентов, который помогает настроить Meson, разобраться с wrap-зависимостями и кросс-компиляцией в C/C++ проектах. CX Codex +1 560установок cargo-workflows Скилл ведёт агента по воркспейсам Cargo, фичам, build.rs, кэшу сборки и аудиту зависимостей. Пригодится, когда Rust-проект разрастается до нескольких крейтов. CX Codex +1 558установок cpp-modules Скилл для работы с модулями C++20: именованные модули, партиции, header units, сборка через CMake и флаги компиляторов. CX Codex +1 555установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой