kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

emu.c (25704B)


      1 /* libkit's guest-ISA emulator: load a guest executable, translate one
      2  * basic block at a time into host code via the existing CG/MC/link
      3  * pipeline, dispatch through a code cache. See doc/EMU.md for design
      4  * and ยง6 for the incremental-link discipline.
      5  *
      6  * This file owns KitEmu lifecycle and the translate/dispatch loop.
      7  * Per-ISA decoders/lifters, CPUState synthesis, the code cache and
      8  * reserved-VA region, and the runtime helper trampolines each live
      9  * behind APIs declared in src/emu/emu.h. */
     10 
     11 #include "emu/emu.h"
     12 
     13 #include <kit/config.h>
     14 #include <kit/interp.h>
     15 #include <kit/link.h>
     16 #include <setjmp.h>
     17 #include <string.h>
     18 
     19 #include "arch/arch.h"
     20 #include "core/pool.h"
     21 #include "core/slice.h"
     22 #include "obj/format.h"
     23 #include "obj/obj.h"
     24 
     25 /* ---- Lifecycle ---- */
     26 
     27 struct KitEmu {
     28   Compiler* c;
     29   KitTargetSpec guest_target;
     30   int opt_level;
     31   KitEmuTraceFlags trace;
     32   KitEmuExternalBindings bindings;
     33 
     34   /* Borrowed JIT host (execmem + tls). When NULL, runs of this emu surface
     35    * KIT_UNSUPPORTED. */
     36   const KitJitHost* host;
     37 
     38   EmuProcess process;
     39   EmuThread main_thread;
     40 
     41   EmuCodeCache* cache;
     42   u64 cache_generation;
     43   KitJit** jits;
     44   u32 njits;
     45   u32 jits_cap;
     46 
     47   /* Execution strategy. In INTERP mode each cache payload is an InterpFunc*
     48    * (run via interp_prog/interp_stack) instead of a host code entry. The
     49    * pointers stay NULL in JIT mode. */
     50   KitEmuMode mode;
     51   KitInterpProgram* interp_prog;
     52   KitInterpStack* interp_stack;
     53 
     54   int done;
     55   int exit_code;
     56 };
     57 
     58 /* The block function call ABI: u64 entry(EmuThread*). Cast through
     59  * a typedef so the call site reads cleanly in the dispatcher. */
     60 typedef u64 (*EmuBlockFn)(EmuThread*);
     61 
     62 typedef struct EmuResolvedConfig {
     63   KitTargetSpec target;
     64   const ObjFormatImpl* obj_format;
     65   const ArchImpl* arch;
     66   const KitOsImpl* os;
     67 } EmuResolvedConfig;
     68 
     69 static EmuCPUState* emu_main_cpu(KitEmu* e) {
     70   return e ? e->main_thread.cpu : NULL;
     71 }
     72 
     73 static KitStatus emu_public_syscall_adapter(void* user, EmuProcess* process,
     74                                             EmuThread* thread,
     75                                             const EmuSyscallRequest* req,
     76                                             EmuSyscallResult* out) {
     77   KitEmu* e = (KitEmu*)user;
     78   KitEmuSyscallRequest public_req;
     79   KitEmuSyscallResult public_out;
     80   KitStatus st;
     81   u32 i;
     82   (void)process;
     83   (void)thread;
     84   if (!e || !e->bindings.syscall || !req || !out) return KIT_INVALID;
     85   memset(&public_req, 0, sizeof(public_req));
     86   public_req.number = req->number;
     87   for (i = 0; i < 6u; ++i) public_req.args[i] = req->args[i];
     88   memset(&public_out, 0, sizeof(public_out));
     89   st = e->bindings.syscall(e->bindings.user, e, &public_req, &public_out);
     90   if (st != KIT_OK) return st;
     91   memset(out, 0, sizeof(*out));
     92   out->result = public_out.result;
     93   out->guest_errno = public_out.guest_errno;
     94   out->flags = public_out.flags;
     95   return KIT_OK;
     96 }
     97 
     98 static KitStatus emu_public_import_adapter(void* user, EmuProcess* process,
     99                                            const EmuDynamicImport* req,
    100                                            KitEmuResolvedImport* out) {
    101   KitEmu* e = (KitEmu*)user;
    102   KitEmuImportRequest public_req;
    103   (void)process;
    104   if (!e || !e->bindings.resolve_import || !req || !out) return KIT_INVALID;
    105   memset(&public_req, 0, sizeof(public_req));
    106   public_req.object_name = req->object_name;
    107   public_req.symbol_name = req->symbol_name;
    108   public_req.signature = req->signature;
    109   return e->bindings.resolve_import(e->bindings.user, e, &public_req, out);
    110 }
    111 
    112 static KitStatus emu_public_object_adapter(void* user, EmuProcess* process,
    113                                            KitSlice object_name,
    114                                            KitSlice* out_bytes) {
    115   KitEmu* e = (KitEmu*)user;
    116   KitEmuObjectRequest public_req;
    117   KitEmuResolvedObject public_out;
    118   KitStatus st;
    119   (void)process;
    120   if (!e || !e->bindings.resolve_object || !out_bytes) return KIT_INVALID;
    121   memset(&public_req, 0, sizeof(public_req));
    122   public_req.object_name = object_name;
    123   memset(&public_out, 0, sizeof(public_out));
    124   st =
    125       e->bindings.resolve_object(e->bindings.user, e, &public_req, &public_out);
    126   if (st != KIT_OK) return st;
    127   *out_bytes = public_out.object_bytes;
    128   return public_out.object_bytes.data ? KIT_OK : KIT_NOT_FOUND;
    129 }
    130 
    131 void emu_set_jit_host(KitEmu* e, const KitJitHost* host) {
    132   if (!e) return;
    133   e->host = host;
    134 }
    135 
    136 const KitJitHost* emu_get_jit_host(const KitEmu* e) {
    137   return e ? e->host : NULL;
    138 }
    139 
    140 KitStatus emu_process_os_alloc(Compiler* c, EmuProcess* process, size_t size,
    141                                size_t align) {
    142   Heap* heap;
    143   if (!c || !process || !size || process->os_private) return KIT_INVALID;
    144   heap = c->ctx->heap;
    145   process->os_private = heap->alloc(heap, size, align ? align : 1u);
    146   if (!process->os_private) return KIT_NOMEM;
    147   memset(process->os_private, 0, size);
    148   return KIT_OK;
    149 }
    150 
    151 void emu_process_os_free(Compiler* c, EmuProcess* process, size_t size) {
    152   Heap* heap;
    153   if (!c || !process || !process->os_private) return;
    154   heap = c->ctx->heap;
    155   heap->free(heap, process->os_private, size);
    156   process->os_private = NULL;
    157 }
    158 
    159 KitStatus emu_thread_os_alloc(Compiler* c, EmuThread* thread, size_t size,
    160                               size_t align) {
    161   Heap* heap;
    162   if (!c || !thread || !size || thread->os_private) return KIT_INVALID;
    163   heap = c->ctx->heap;
    164   thread->os_private = heap->alloc(heap, size, align ? align : 1u);
    165   if (!thread->os_private) return KIT_NOMEM;
    166   memset(thread->os_private, 0, size);
    167   return KIT_OK;
    168 }
    169 
    170 void emu_thread_os_free(Compiler* c, EmuThread* thread, size_t size) {
    171   Heap* heap;
    172   if (!c || !thread || !thread->os_private) return;
    173   heap = c->ctx->heap;
    174   heap->free(heap, thread->os_private, size);
    175   thread->os_private = NULL;
    176 }
    177 
    178 static KitStatus emu_resolve_config(Compiler* c, const KitEmuOptions* opts,
    179                                     EmuResolvedConfig* out) {
    180   KitBinFmt bin_fmt;
    181   KitTargetSpec target;
    182   const ObjFormatImpl* obj_format;
    183   const ObjFormatImpl* target_format;
    184   const ArchImpl* arch;
    185   const KitOsImpl* os;
    186   KitStatus st;
    187 
    188   if (!c || !opts || !out || !opts->guest_bytes.data ||
    189       opts->guest_bytes.len == 0)
    190     return KIT_INVALID;
    191   memset(out, 0, sizeof(*out));
    192 
    193   bin_fmt = kit_detect_fmt(opts->guest_bytes.data, opts->guest_bytes.len);
    194   obj_format = obj_format_lookup_bin(bin_fmt);
    195   if (!obj_format || !obj_format->emu || !obj_format->emu->load_executable)
    196     return KIT_UNSUPPORTED;
    197 
    198   if (opts->has_guest_target) {
    199     target = opts->guest_target;
    200   } else {
    201     if (!obj_format->emu->detect_executable) return KIT_UNSUPPORTED;
    202     memset(&target, 0, sizeof(target));
    203     st = obj_format->emu->detect_executable(c, opts->guest_bytes, &target);
    204     if (st != KIT_OK) return st;
    205   }
    206 
    207   target_format = obj_format_lookup(target.obj);
    208   if (target_format != obj_format) return KIT_UNSUPPORTED;
    209 
    210   arch = arch_lookup(target.arch);
    211   os = kit_os_lookup(target.os);
    212   if (!arch || !arch->decode || !arch->decode->decode_block || !arch->emu ||
    213       !arch->emu->cpu_new || !arch->emu->block_fn_type ||
    214       !arch->emu->lift_block || !os || !os->emu) {
    215     return KIT_UNSUPPORTED;
    216   }
    217 
    218   out->target = target;
    219   out->obj_format = obj_format;
    220   out->arch = arch;
    221   out->os = os;
    222   return KIT_OK;
    223 }
    224 
    225 KitStatus kit_emu_new(KitCompiler* c, const KitEmuOptions* opts, KitEmu** out) {
    226   PanicFrame panic;
    227   Heap* heap;
    228   KitEmu* e;
    229   EmuResolvedConfig resolved;
    230   EmuLoadOptions load_opts;
    231   KitStatus st;
    232 
    233   if (out) *out = NULL;
    234   if (!c || !opts || !out) return KIT_INVALID;
    235   if (!opts->guest_bytes.data || opts->guest_bytes.len == 0) return KIT_INVALID;
    236 
    237   compiler_panic_push(c, &panic);
    238   if (setjmp(panic.env)) {
    239     compiler_run_cleanups(c);
    240     compiler_panic_pop(c, &panic);
    241     return KIT_ERR;
    242   }
    243 
    244   heap = c->ctx->heap;
    245   e = (KitEmu*)heap->alloc(heap, sizeof(*e), _Alignof(KitEmu));
    246   if (!e) compiler_panic(c, SRCLOC_NONE, "emu: out of memory");
    247   memset(e, 0, sizeof(*e));
    248   e->c = c;
    249   e->opt_level = opts->optimize;
    250   e->mode = opts->mode;
    251   /* The interpreter consumes the O1 PReg-path IR (opt_run_o1_interp); force at
    252    * least -O1 so the optimizer runs and each block is captured. */
    253   if (e->mode == KIT_EMU_MODE_INTERP && e->opt_level < 1) e->opt_level = 1;
    254   e->trace = opts->trace;
    255   e->bindings = opts->bindings;
    256   e->host = opts->jit_host;
    257 
    258   st = emu_resolve_config(c, opts, &resolved);
    259   if (st != KIT_OK) {
    260     compiler_panic(c, SRCLOC_NONE, "emu: unsupported guest executable");
    261   }
    262   e->guest_target = resolved.target;
    263 
    264   memset(&load_opts, 0, sizeof(load_opts));
    265   load_opts.name = opts->guest_name;
    266   load_opts.bytes = opts->guest_bytes;
    267   load_opts.guest_target = resolved.target;
    268   load_opts.argv = opts->argv;
    269   load_opts.envp = opts->envp;
    270   load_opts.os = resolved.os;
    271   load_opts.process = &e->process;
    272 
    273   e->process.compiler = c;
    274   e->process.guest_target = resolved.target;
    275   e->process.obj_format = resolved.obj_format;
    276   e->process.arch = resolved.arch;
    277   e->process.os = resolved.os;
    278   if (e->bindings.syscall) {
    279     e->process.bindings.syscall = emu_public_syscall_adapter;
    280     e->process.bindings.user = e;
    281   } else {
    282     e->process.bindings.syscall = resolved.os->emu->default_syscall;
    283     e->process.bindings.user = NULL;
    284   }
    285   if (e->bindings.resolve_import) {
    286     e->process.bindings.resolve_import = emu_public_import_adapter;
    287     e->process.bindings.user = e;
    288   }
    289   if (e->bindings.resolve_object) {
    290     e->process.bindings.resolve_object = emu_public_object_adapter;
    291     e->process.bindings.user = e;
    292   }
    293   load_opts.bindings = &e->process.bindings;
    294   e->main_thread.process = &e->process;
    295 
    296   if (resolved.os->emu->init_process_private &&
    297       resolved.os->emu->init_process_private(c, &e->process) != KIT_OK) {
    298     compiler_panic(c, SRCLOC_NONE,
    299                    "emu: failed to initialize OS process state");
    300   }
    301   if (resolved.os->emu->init_thread_private &&
    302       resolved.os->emu->init_thread_private(c, &e->process, &e->main_thread) !=
    303           KIT_OK) {
    304     compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize OS thread state");
    305   }
    306 
    307   /* 1. Load the guest executable through the object-format emu hook. */
    308   st = resolved.obj_format->emu->load_executable(c, &load_opts,
    309                                                  &e->process.image);
    310   if (st != KIT_OK) {
    311     compiler_panic(c, SRCLOC_NONE, "emu: failed to load guest executable");
    312   }
    313   if (resolved.os->emu->init_process &&
    314       resolved.os->emu->init_process(c, &e->process, &load_opts,
    315                                     &e->process.image) != KIT_OK) {
    316     compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize guest process");
    317   }
    318 
    319   /* 2. Allocate per-thread CPU state and seed PC/SP. */
    320   e->main_thread.cpu = resolved.arch->emu->cpu_new(c, e->process.image.entry_pc,
    321                                                    e->process.image.initial_sp);
    322   emu_cpu_set_thread(e->main_thread.cpu, &e->main_thread);
    323   if (!e->main_thread.cpu ||
    324       emu_loaded_image_attach_cpu(e->main_thread.cpu, &e->process.image) != 0 ||
    325       (resolved.os->emu->init_thread &&
    326        resolved.os->emu->init_thread(c, &e->process, &e->main_thread) !=
    327            KIT_OK)) {
    328     compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize guest CPU state");
    329   }
    330 
    331   /* 3. In INTERP mode, attach an interp sink so each translated block is also
    332    * captured as an InterpFunc, and stand up the long-lived stack used to run
    333    * blocks. The sink stays attached for the whole emu lifetime (every
    334    * translate_block compiles a fresh block that must be captured). External
    335    * helper calls in a lifted block resolve through the same runtime resolver
    336    * the JIT path uses; guest memory is reached only via those helpers, so no
    337    * address translate hook is bound (host-identity frame). */
    338 #if KIT_INTERP_ENABLED
    339   if (e->mode == KIT_EMU_MODE_INTERP) {
    340     KitInterpHost ihost;
    341     e->interp_prog = kit_interp_program_new((KitCompiler*)c);
    342     if (!e->interp_prog)
    343       compiler_panic(c, SRCLOC_NONE,
    344                      "emu: failed to create interpreter program");
    345     kit_interp_program_attach(e->interp_prog, (KitCompiler*)c);
    346     memset(&ihost, 0, sizeof(ihost));
    347     ihost.resolve_sym = emu_runtime_extern_resolver;
    348     ihost.ctx = e;
    349     kit_interp_program_set_host(e->interp_prog, &ihost);
    350     e->interp_stack = kit_interp_stack_new(e->interp_prog);
    351     if (!e->interp_stack)
    352       compiler_panic(c, SRCLOC_NONE, "emu: failed to create interpreter stack");
    353   }
    354 #else
    355   if (e->mode == KIT_EMU_MODE_INTERP)
    356     compiler_panic(c, SRCLOC_NONE,
    357                    "emu: interpreter mode not enabled in this build");
    358 #endif
    359 
    360   compiler_panic_pop(c, &panic);
    361   *out = e;
    362   return KIT_OK;
    363 }
    364 
    365 void kit_emu_free(KitEmu* e) {
    366   Heap* heap;
    367   if (!e) return;
    368   heap = e->c->ctx->heap;
    369 
    370 #if KIT_INTERP_ENABLED
    371   /* Detach the sink before freeing the program so a later compile on the
    372    * (borrowed) compiler can't write into freed interp state. */
    373   if (e->interp_prog) kit_interp_program_attach(NULL, (KitCompiler*)e->c);
    374   if (e->interp_stack) kit_interp_stack_free(e->interp_stack);
    375   if (e->interp_prog) kit_interp_program_free(e->interp_prog);
    376 #endif
    377   while (e->njits) kit_jit_free(e->jits[--e->njits]);
    378   if (e->jits) heap->free(heap, e->jits, sizeof(*e->jits) * e->jits_cap);
    379   if (e->cache) emu_cache_free(e->cache);
    380   if (e->process.os && e->process.os->emu &&
    381       e->process.os->emu->destroy_thread_private)
    382     e->process.os->emu->destroy_thread_private(e->c, &e->main_thread);
    383   if (e->main_thread.cpu) emu_cpu_free(e->main_thread.cpu);
    384   emu_tls_destroy_process(e->c, &e->process);
    385   if (e->process.os && e->process.os->emu &&
    386       e->process.os->emu->destroy_process_private)
    387     e->process.os->emu->destroy_process_private(e->c, &e->process);
    388   emu_unload_image(e->c, &e->process.image);
    389 
    390   heap->free(heap, e, sizeof(*e));
    391 }
    392 
    393 /* Lazily allocate the code cache the first time kit_emu_lookup runs.
    394  * Requires a wired JIT host because cold blocks are published as ordinary
    395  * one-block JIT images. */
    396 static KitStatus ensure_runtime(KitEmu* e) {
    397   if (e->cache) return KIT_OK;
    398   if (!e->host || !e->host->execmem) return KIT_UNSUPPORTED;
    399   e->cache = emu_cache_new(e->c);
    400   return KIT_OK;
    401 }
    402 
    403 static void emu_keep_jit(KitEmu* e, KitJit* jit) {
    404   Heap* heap = e->c->ctx->heap;
    405   if (e->njits == e->jits_cap) {
    406     u32 old_cap = e->jits_cap;
    407     u32 new_cap = old_cap ? old_cap * 2u : 8u;
    408     KitJit** grown =
    409         (KitJit**)heap->realloc(heap, e->jits, sizeof(*e->jits) * old_cap,
    410                                 sizeof(*e->jits) * new_cap, _Alignof(KitJit*));
    411     if (!grown) compiler_panic(e->c, SRCLOC_NONE, "emu: out of memory");
    412     e->jits = grown;
    413     e->jits_cap = new_cap;
    414   }
    415   e->jits[e->njits++] = jit;
    416 }
    417 
    418 /* ---- Translation (cold-miss path) ---- */
    419 
    420 static void* translate_block(KitEmu* e, u64 guest_pc) {
    421   KitDecodedInsn* insts;
    422   const ArchImpl* arch;
    423   Heap* heap;
    424   const u8* host_pc;
    425   u64 va_end;
    426   size_t decode_len;
    427   u32 ninsts;
    428   ObjBuilder* ob;
    429   KitCg* cg;
    430   KitCodeOptions copts;
    431   KitCgUnitOptions unit_opts;
    432   Sym block_name;
    433   KitCgDecl block_decl;
    434   KitCgSym block_sym;
    435   EmuLiftCtx ctx;
    436   void* entry;
    437   KitStatus st;
    438   KitLinkSessionOptions lopts;
    439   KitLinkSession* sess;
    440   KitJit* jit;
    441   KitSlice block_slice;
    442 
    443   if (e->trace & KIT_EMU_TRACE_BLOCK) emu_trace_block(e->c, guest_pc);
    444 
    445   arch = e->process.arch;
    446   if (!arch || !arch->decode || !arch->decode->decode_block || !arch->emu ||
    447       !arch->emu->block_fn_type || !arch->emu->lift_block)
    448     return NULL;
    449 
    450   host_pc = emu_cpu_va_to_host_perm(emu_main_cpu(e), guest_pc,
    451                                     arch->decode->min_insn_len, EMU_MEM_EXEC);
    452   if (!host_pc) return NULL;
    453   va_end = emu_addr_space_contig_len(&e->process.image.addr_space, guest_pc,
    454                                      EMU_MEM_EXEC);
    455   if (!va_end) return NULL;
    456   decode_len = (size_t)va_end;
    457   heap = e->c->ctx->heap;
    458   insts = (KitDecodedInsn*)heap->alloc(
    459       heap, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK, _Alignof(KitDecodedInsn));
    460   if (!insts) compiler_panic(e->c, SRCLOC_NONE, "emu: out of memory");
    461   st = arch->decode->decode_block(e->c, host_pc, decode_len, guest_pc, insts,
    462                                   EMU_MAX_INSTS_PER_BLOCK, &ninsts);
    463   if (st != KIT_OK || ninsts == 0) {
    464     heap->free(heap, insts, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK);
    465     return NULL;
    466   }
    467 
    468   if (e->trace & KIT_EMU_TRACE_INSN) {
    469     u32 j;
    470     for (j = 0; j < ninsts; ++j) emu_trace_insn(e->c, guest_pc, &insts[j]);
    471   }
    472 
    473   /* Per-block ObjBuilder + public CG pipeline. The block lands as a single
    474    * host function once per-ISA lifters start emitting real code. */
    475   ob = obj_new(e->c);
    476   memset(&copts, 0, sizeof(copts));
    477   copts.opt_level = e->opt_level;
    478   st = kit_cg_new(e->c, &cg);
    479   if (st == KIT_OK) st = kit_cg_begin(cg, (KitObjBuilder*)ob, &copts);
    480   memset(&unit_opts, 0, sizeof unit_opts);
    481   unit_opts.source_name = KIT_SLICE_LIT("<emu-block>");
    482   if (st == KIT_OK) st = kit_cg_begin_unit(cg, &unit_opts);
    483   if (st != KIT_OK || !cg)
    484     compiler_panic(e->c, SRCLOC_NONE, "emu: kit_cg_new failed");
    485 
    486   block_name = emu_block_sym_name(e->c, guest_pc);
    487   memset(&block_decl, 0, sizeof(block_decl));
    488   block_decl.kind = KIT_CG_DECL_FUNC;
    489   block_decl.linkage_name =
    490       kit_cg_c_linkage_name((KitCompiler*)e->c, (KitSym)block_name);
    491   block_decl.display_name = (KitSym)block_name;
    492   block_decl.type = arch->emu->block_fn_type(e->c);
    493   block_decl.sym.bind = KIT_SB_GLOBAL;
    494   block_decl.sym.visibility = KIT_CG_VIS_DEFAULT;
    495   block_sym = kit_cg_decl(cg, block_decl);
    496   if (block_sym == KIT_CG_SYM_NONE)
    497     compiler_panic(e->c, SRCLOC_NONE, "emu: failed to declare block symbol");
    498 
    499   memset(&ctx, 0, sizeof(ctx));
    500   ctx.compiler = e->c;
    501   ctx.arch = e->guest_target.arch;
    502   ctx.thread_type = emu_thread_type(e->c);
    503   ctx.block_fn_type = arch->emu->block_fn_type(e->c);
    504   ctx.block_sym = block_sym;
    505   ctx.guest_pc = guest_pc;
    506 
    507   st = arch->emu->lift_block(e->c, cg, insts, ninsts, &ctx);
    508   heap->free(heap, insts, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK);
    509   insts = NULL;
    510   if (st != KIT_OK)
    511     compiler_panic(e->c, SRCLOC_NONE, "emu: failed to lift block");
    512 
    513   st = kit_cg_end_unit(cg);
    514   if (st == KIT_OK) st = kit_cg_finish(cg, NULL);
    515   if (st == KIT_OK) st = kit_cg_detach(cg);
    516   if (st != KIT_OK)
    517     compiler_panic(e->c, SRCLOC_NONE, "emu: kit_cg_finish failed");
    518   kit_cg_free(cg);
    519   obj_finalize(ob);
    520 
    521   block_slice = pool_slice(e->c->global, block_name);
    522   memset(&lopts, 0, sizeof(lopts));
    523   lopts.output_kind = KIT_LINK_OUTPUT_JIT;
    524   lopts.jit_host = e->host;
    525   lopts.extern_resolver = emu_runtime_extern_resolver;
    526   lopts.extern_resolver_user = e;
    527 
    528   sess = NULL;
    529   jit = NULL;
    530   st = kit_link_session_new((KitCompiler*)e->c, &lopts, &sess);
    531   if (st == KIT_OK) st = kit_link_session_add_obj(sess, (KitObjBuilder*)ob);
    532   if (st == KIT_OK) st = kit_link_session_jit(sess, &jit);
    533   if (sess) kit_link_session_free(sess);
    534   if (st != KIT_OK || !jit)
    535     compiler_panic(e->c, SRCLOC_NONE, "emu: failed to publish JIT block");
    536 
    537   entry = kit_jit_lookup(jit, *(KitSlice*)&block_slice);
    538   if (!entry) {
    539     kit_jit_free(jit);
    540     return NULL;
    541   }
    542   emu_keep_jit(e, jit);
    543 
    544 #if KIT_INTERP_ENABLED
    545   /* INTERP mode: the JIT image above still resolved the block's helper externs
    546    * and validated the lifted IR, but dispatch runs the captured InterpFunc
    547    * (lowered during kit_cg_finish, above) instead of the host code. Cache the
    548    * InterpFunc*; kit_emu_step disambiguates the payload by e->mode. A rejected
    549    * block is still captured (ifn->ok == 0) and is reported with its reason when
    550    * dispatched, so only a genuine capture miss yields NULL here. */
    551   if (e->mode == KIT_EMU_MODE_INTERP) {
    552     KitInterpFunc* ifn =
    553         kit_interp_lookup(e->interp_prog, *(KitSlice*)&block_slice);
    554     if (!ifn) return NULL;
    555     entry = (void*)ifn;
    556   }
    557 #endif
    558 
    559   emu_cache_insert(e->cache, guest_pc, entry);
    560   emu_addr_space_mark_translated(&e->process.image.addr_space, guest_pc,
    561                                  decode_len);
    562   e->cache_generation = e->process.image.addr_space.generation;
    563   return entry;
    564 }
    565 
    566 void* kit_emu_lookup(KitEmu* e, uint64_t guest_pc) {
    567   PanicFrame panic;
    568   void* entry;
    569 
    570   if (!e) return NULL;
    571 
    572   if (e->cache &&
    573       e->cache_generation != e->process.image.addr_space.generation) {
    574     emu_cache_free(e->cache);
    575     e->cache = NULL;
    576     e->cache_generation = 0;
    577   }
    578 
    579   /* Cache hit short-circuits the panic boundary. */
    580   if (e->cache) {
    581     entry = emu_cache_lookup(e->cache, guest_pc);
    582     if (entry) return entry;
    583   }
    584 
    585   if (ensure_runtime(e) != KIT_OK) return NULL;
    586 
    587   compiler_panic_push(e->c, &panic);
    588   if (setjmp(panic.env)) {
    589     compiler_run_cleanups(e->c);
    590     compiler_panic_pop(e->c, &panic);
    591     return NULL;
    592   }
    593 
    594   entry = translate_block(e, guest_pc);
    595 
    596   compiler_panic_pop(e->c, &panic);
    597   return entry;
    598 }
    599 
    600 /* ---- Dispatcher ---- */
    601 
    602 #if KIT_INTERP_ENABLED
    603 /* Run one lifted guest block through the IR interpreter on the emu's long-lived
    604  * stack, returning the next guest pc. The block's host function takes the
    605  * EmuThread* and returns next_pc, so we seed param0 with the thread pointer
    606  * (host-identity: the interpreter never translates it) and shuttle the scalar
    607  * return back. Guest registers and memory are reached only through the __emu_*
    608  * helpers the block calls โ€” the interpreter holds no guest state itself.
    609  *
    610  * A non-DONE result means the block could not be interpreted (an op the lowerer
    611  * rejected) or trapped inside the interpreter; per the chosen UX we hard-fail
    612  * with the reason rather than falling back to the JIT. A guest fault/exit is
    613  * NOT such a case: the helpers deliver those in-band by setting the CPU trap
    614  * reason and returning a next_pc, which the post-dispatch check below observes
    615  * exactly as in JIT mode. */
    616 static u64 emu_interp_run_block(KitEmu* e, KitInterpFunc* ifn,
    617                                 EmuThread* thread) {
    618   u64 arg = (u64)(uintptr_t)thread;
    619   int64_t ret = 0;
    620   KitInterpStatus s;
    621 
    622   kit_interp_stack_reset(e->interp_stack);
    623   if (kit_interp_call_args_on(e->interp_stack, ifn, &arg, 1u) != KIT_OK)
    624     compiler_panic(e->c, SRCLOC_NONE, "emu: failed to seed interpreter frame");
    625 
    626   s = kit_interp_resume(e->interp_stack, &ret);
    627   if (s == KIT_INTERP_DONE) return (u64)ret;
    628 
    629   {
    630     const char* why = kit_interp_stack_trap_reason(e->interp_stack);
    631     compiler_panic(e->c, SRCLOC_NONE,
    632                    "emu: cannot interpret block at guest_pc=0x%llx: %s",
    633                    (unsigned long long)emu_cpu_pc(thread->cpu),
    634                    why ? why : "unsupported operation");
    635   }
    636   return 0; /* unreachable: compiler_panic longjmps */
    637 }
    638 #endif
    639 
    640 KitStatus kit_emu_step(KitEmu* e, uint32_t nblocks) {
    641   PanicFrame panic;
    642   uint32_t i;
    643   KitStatus st;
    644 
    645   if (!e) return KIT_INVALID;
    646   if (e->done) return KIT_OK;
    647   st = ensure_runtime(e);
    648   if (st != KIT_OK) return st;
    649 
    650   compiler_panic_push(e->c, &panic);
    651   if (setjmp(panic.env)) {
    652     compiler_run_cleanups(e->c);
    653     compiler_panic_pop(e->c, &panic);
    654     return KIT_ERR;
    655   }
    656 
    657   for (i = 0; i < nblocks && !e->done; ++i) {
    658     EmuThread* thread = &e->main_thread;
    659     EmuCPUState* cpu = thread->cpu;
    660     u64 pc = emu_cpu_pc(cpu);
    661     void* entry;
    662     EmuBlockFn fn;
    663     u64 next_pc;
    664     EmuTrapReason trap;
    665 
    666     if (e->trace & KIT_EMU_TRACE_PC) emu_trace_pc(e->c, pc);
    667 
    668     entry = kit_emu_lookup(e, pc);
    669     if (!entry) {
    670       compiler_panic(e->c, SRCLOC_NONE,
    671                      "emu: failed to translate block at guest_pc=0x%llx",
    672                      (unsigned long long)pc);
    673     }
    674 
    675 #if KIT_INTERP_ENABLED
    676     if (e->mode == KIT_EMU_MODE_INTERP) {
    677       next_pc = emu_interp_run_block(e, (KitInterpFunc*)entry, thread);
    678     } else
    679 #endif
    680     {
    681       fn = (EmuBlockFn)entry;
    682       next_pc = fn(thread);
    683     }
    684     emu_cpu_set_pc(cpu, next_pc);
    685 
    686     trap = emu_cpu_trap_reason(cpu);
    687     if (trap == EMU_TRAP_EXIT) {
    688       e->done = 1;
    689       e->exit_code = emu_cpu_exit_code(cpu);
    690     } else if (trap == EMU_TRAP_FAULT) {
    691       compiler_panic(e->c, SRCLOC_NONE, "emu: guest faulted at pc=0x%llx",
    692                      (unsigned long long)next_pc);
    693     }
    694   }
    695 
    696   compiler_panic_pop(e->c, &panic);
    697   return KIT_OK;
    698 }
    699 
    700 KitStatus kit_emu_run(KitCompiler* c, const KitEmuOptions* opts,
    701                       int* out_exit_code) {
    702   KitEmu* e = NULL;
    703   KitStatus st;
    704 
    705   if (out_exit_code) *out_exit_code = 0;
    706   if (!c || !opts) return KIT_INVALID;
    707 
    708   st = kit_emu_new(c, opts, &e);
    709   if (st != KIT_OK) return st;
    710 
    711   while (!e->done) {
    712     st = kit_emu_step(e, 1024);
    713     if (st != KIT_OK) break;
    714   }
    715 
    716   if (st == KIT_OK && out_exit_code) *out_exit_code = e->exit_code;
    717   kit_emu_free(e);
    718   return st;
    719 }
    720 
    721 /* Runtime accessor for the resolver โ€” exposes the running emu's
    722  * CPUState pointer without baking the KitEmu layout into runtime.c.
    723  * Used by emu_runtime_extern_resolver for EMU_SYM_CPU_STATE. */
    724 EmuCPUState* emu_internal_cpu(KitEmu* e) { return emu_main_cpu(e); }
    725 
    726 EmuProcess* emu_internal_process(KitEmu* e) { return e ? &e->process : NULL; }
    727 
    728 /* ---- Block symbol naming ----
    729  * "emu_block_<16-hex-pc>" โ€” fixed-width hex so the linker's hash
    730  * lookup never collides between two blocks at distinct guest PCs.
    731  * Interned in the compiler's global pool; the Sym is stable for the
    732  * Compiler's lifetime, which is what the linker assumes. */
    733 Sym emu_block_sym_name(Compiler* c, u64 guest_pc) {
    734   char buf[32];
    735   static const char hex[] = "0123456789abcdef";
    736   int i;
    737   /* "emu_block_" + 16 hex digits + NUL = 27 chars, fits in 32. */
    738   memcpy(buf, "emu_block_", 10);
    739   for (i = 0; i < 16; ++i) {
    740     buf[10 + 15 - i] = hex[guest_pc & 0xfu];
    741     guest_pc >>= 4;
    742   }
    743   buf[26] = '\0';
    744   return pool_intern_slice(c->global, slice_from_cstr(buf));
    745 }