본문으로 건너뛰기
cccskills
무료GitHub에 공개

jit-compilation

Use when adding code generation with LLVM ORC or LLJIT, Cranelift, or dynasm, lazy compilation or inline caches, or fixing W^X faults. Not for AST-to-IR: use compiler-frontend.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(2)

  • SKILL.md9.7 KB
  • agents/openai.yaml203 B

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

JIT compilation

Contract

FieldBound contract
TriggerA user adds a JIT hot path to an interpreter, compiles functions lazily on first call, embeds code generation in a REPL or engine, needs inline caches for polymorphic call sites, or hits symbol lookup, ABI, or W^X failures in JIT code.
AuthorityReversible local: writes only the JIT source files the user names inside the project and scratch build outputs; rollback is version control. No remote mutation.
Side effectJIT source is created or edited, built, and executed on a test function whose result is checked.
DoneThe chosen backend compiles a test function at runtime, the call through the returned pointer produces the expected value, and code memory is never writable and executable at the same time.

Inputs

  1. Host language and backend (required): C++ with LLVM ORC or LLJIT, Rust with Cranelift, or Rust with dynasm. If undecided, step 1 chooses.
  2. The input form (required): AST, bytecode, or an IR the frontend already emits.
  3. A test function (required): source or IR for a function with a known result, for example add(3, 4) == 7.
  4. Installed toolchain (gathered by the skill): llvm-config --version for LLVM (grounded current stable 23.1.0; header paths below are confirmed against it), cargo for Rust crates (Cranelift samples confirmed against cranelift-jit 0.134.4).

Procedure

  1. Choose the backend by the constraint that dominates. LLVM optimizations needed: ORC or LLJIT. Fast compile and a small embeddable dependency: Cranelift. Small hand-written assembly snippets without an LLVM dependency: dynasm. An MLIR pipeline lowers to LLVM IR and then enters ORC. The architecture is the same in every case: source or bytecode to IR, IR to an in-memory object, a runtime linker resolves symbols, code lands in read-and-execute memory, and the caller receives a function pointer. Done when: one backend is named with the constraint that chose it.

  2. For LLVM, start with LLJIT, the packaged ORC setup. Build a module, add it, look up the symbol, and convert the address to a typed pointer:

    #include "llvm/ExecutionEngine/Orc/LLJIT.h"
    #include "llvm/IR/IRBuilder.h"
    #include "llvm/IR/LLVMContext.h"
    #include "llvm/IR/Module.h"
    using namespace llvm;
    using namespace llvm::orc;
    
    int main() {
        auto JIT = cantFail(LLJITBuilder().create());
        auto Ctx = std::make_unique<LLVMContext>();
        auto M = std::make_unique<Module>("jit", *Ctx);
        IRBuilder<> B(*Ctx);
        auto *FT = FunctionType::get(B.getInt32Ty(), {B.getInt32Ty(), B.getInt32Ty()}, false);
        auto *AddFn = Function::Create(FT, Function::ExternalLinkage, "add", M.get());
        B.SetInsertPoint(BasicBlock::Create(*Ctx, "entry", AddFn));
        B.CreateRet(B.CreateAdd(AddFn->getArg(0), AddFn->getArg(1)));
        cantFail(JIT->addIRModule(ThreadSafeModule(std::move(M), std::move(Ctx))));
        auto Add = cantFail(JIT->lookup("add")).toPtr<int (*)(int, int)>();
        return Add(3, 4) == 7 ? 0 : 1;
    }
    
    clang++ -std=c++17 jit.cpp $(llvm-config --cxxflags --ldflags --libs core orcjit native) -o jit
    

    The LLVMContext moves into the ThreadSafeModule with the module; a context that outlives its module on the stack is a use-after-free when the JIT tears down. Done when: the binary exits 0.

  3. When LLJIT's defaults do not fit, assemble the ORC layers directly. ExecutionSession owns symbol lookup and JITDylibs; an object layer links relocatable objects; an IR layer compiles IR into objects for the layer below:

    #include "llvm/ExecutionEngine/Orc/CompileUtils.h"
    #include "llvm/ExecutionEngine/Orc/Core.h"
    #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h"
    #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
    #include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
    #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
    #include "llvm/ExecutionEngine/SectionMemoryManager.h"
    
    auto EPC = cantFail(SelfExecutorProcessControl::Create());
    ExecutionSession ES(std::move(EPC));
    JITDylib &MainJD = ES.createBareJITDylib("main");
    RTDyldObjectLinkingLayer ObjLayer(ES, [] { return std::make_unique<SectionMemoryManager>(); });
    auto JTMB = cantFail(JITTargetMachineBuilder::detectHost());
    IRCompileLayer CompileLayer(ES, ObjLayer, std::make_unique<ConcurrentIRCompiler>(std::move(JTMB)));
    cantFail(CompileLayer.add(MainJD, std::move(TSM)));
    auto Sym = cantFail(ES.lookup({&MainJD}, ES.intern("my_func")));
    

    Lazy compilation on first call goes through ORC's lazy reexports (LazyReexports.h): the symbol resolves to a call-through stub, the first call triggers compilation, and the stub is updated to the compiled address. Done when: a symbol added through the layers resolves and calls correctly, and, when lazy compilation is requested, a counter proves the function compiled on first call and not at add time.

  4. For a dynamic language, add inline caches at call sites. A monomorphic cache stores the receiver's class id and the resolved target; a hit calls the cached target, a miss re-resolves and overwrites the cache. The JIT emits specialized code for the cached type and deoptimizes to the generic path on a miss. Keep the cache check cheap: one compare on the class id. Done when: a call site with a stable receiver type executes the fast path, measured by a hit counter.

  5. For Cranelift, build a JITModule from the host ISA, declare and define the function, finalize, and call through the returned pointer:

    use cranelift::prelude::*;
    use cranelift_jit::{JITBuilder, JITModule};
    use cranelift_module::{Linkage, Module};
    
    let isa = cranelift_native::builder()?.finish(settings::Flags::new(settings::builder()))?;
    let mut module = JITModule::new(JITBuilder::with_isa(isa, cranelift_module::default_libcall_names()));
    let mut sig = module.make_signature();
    sig.params.push(AbiParam::new(types::I32));
    sig.params.push(AbiParam::new(types::I32));
    sig.returns.push(AbiParam::new(types::I32));
    let func_id = module.declare_function("add", Linkage::Export, &sig)?;
    let mut ctx = module.make_context();
    ctx.func.signature = sig;
    // build the body with FunctionBuilder here
    module.define_function(func_id, &mut ctx)?;
    module.clear_context(&mut ctx);
    module.finalize_definitions()?;
    let code = module.get_finalized_function(func_id);
    let add: fn(i32, i32) -> i32 = unsafe { std::mem::transmute(code) };
    assert_eq!(add(3, 4), 7);
    

    Done when: the assertion passes.

  6. For dynasm, assemble into an executable buffer and take the entry offset before the first instruction:

    use dynasmrt::{dynasm, DynasmApi};
    
    let mut asm = dynasmrt::x64::Assembler::new()?;
    let entry = asm.offset();
    dynasm!(asm
        ; .arch x64
        ; mov eax, edi
        ; add eax, esi
        ; ret
    );
    let buf = asm.finalize().expect("no live executors");
    let add: extern "sysv64" fn(i32, i32) -> i32 = unsafe { std::mem::transmute(buf.ptr(entry)) };
    assert_eq!(add(3, 4), 7);
    

    Name the calling convention on the function pointer type; the register choice above is the System V AMD64 convention. Done when: the assertion passes.

  7. Keep code memory under W^X: a page is writable or executable, never both. On Linux, map read-write, write the code, then mprotect to read-execute:

    #include <sys/mman.h>
    void *mem = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (mem == MAP_FAILED) return -1;
    memcpy(mem, code, size);
    if (mprotect(mem, size, PROT_READ | PROT_EXEC) != 0) return -1;
    

    MAP_JIT is a macOS flag for the hardened runtime, not a Linux one. On Linux a sealed memfd_create region (MFD_ALLOW_SEALING) is the option when a policy forbids anonymous executable mappings. The ORC memory managers and the dynasm assembler already follow this rule. Done when: no mapping in /proc/<pid>/maps shows rwx while the JIT runs.

Failure and recovery

Failure classBehavior
Symbol not found on lookupThe name is mangled or not exported. Emit the function with external linkage and an unmangled name; look up the exact string the module defines.
Segfault when calling JIT codeThe pointer type does not match the emitted signature or calling convention. Match parameter types, return type, and convention on both sides.
mprotect or mmap with PROT_EXEC failsA policy denies executable memory: an SELinux execmem denial shows in ausearch -m avc -ts recent. Use the sealed memfd path or adjust the policy with the operator; do not fall back to rwx.
Stale code after recompilationA caller holds the old pointer. Invalidate caches and route calls through a stub that is updated on recompile.
Compile latency dominatesThe JIT optimizes at -O2 on every function. Compile cold code at -O0 and re-optimize functions that a counter marks hot.
Cranelift verifier errorThe CLIF is invalid. Run cranelift_codegen::verify_function on the function with the ISA flags and fix the reported instruction.

No partial result is claimed complete. If a step cannot finish, the report states which steps passed and which are blocked.

Output

A JIT delivery containing:

  1. Backend and reason: the chosen backend and the constraint that chose it.
  2. Files written: each source file created or edited.
  3. Verification: the test function, the observed result of the call through the JIT pointer, and the W^X check.
  4. Lazy and cache evidence: when requested, the counters proving first-call compilation and inline-cache hits.

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when explaining System V AMD64, ARM AAPCS, RISC-V psABI, stack frames, variadic calls, or FFI register rules. Not for the Rust FFI binding layer: use rust-ffi.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

Use when configuring ADC sampling time, DMA-driven ADC, calibration, or DAC channel setup on bare-metal MCUs. Not for the DMA stream itself: use dma-baremetal.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

af-xdp

무료

Use when creating AF_XDP sockets, configuring UMEM and XSK rings, writing an XDP redirect program, or choosing copy versus zero-copy mode. Not for full kernel bypass: use dpdk.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

Use when a completed session needs an agent-environment retrospective. Not for an engineering retrospective from telemetry: use engineering-retrospective.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

Use when a redacted, trimmed agent transcript must be appended to a GitHub PR or issue body, with human approval and preview. Not for automated or model-initiated insertion.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

agents-md

무료

Use when a repo needs agent setup, AGENTS.md added or made lean, CLAUDE.md audited, or agent instructions scored or pruned. Not for remote, credential, publish, deploy, or irreversible changes.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

OutlineDriven/odin-claude-plugin372026년 9월 29일 업데이트

OutlineDriven의 스킬 모두 보기

이 스킬의 문제 신고