emu.c (25575B)
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 = 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) { 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, "emu: failed to initialize OS process state"); 299 } 300 if (resolved.os->emu_init_thread_private && 301 resolved.os->emu_init_thread_private(c, &e->process, &e->main_thread) != 302 KIT_OK) { 303 compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize OS thread state"); 304 } 305 306 /* 1. Load the guest executable through the object-format emu hook. */ 307 st = resolved.obj_format->emu->load_executable(c, &load_opts, 308 &e->process.image); 309 if (st != KIT_OK) { 310 compiler_panic(c, SRCLOC_NONE, "emu: failed to load guest executable"); 311 } 312 if (resolved.os->emu_init_process && 313 resolved.os->emu_init_process(c, &e->process, &load_opts, 314 &e->process.image) != KIT_OK) { 315 compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize guest process"); 316 } 317 318 /* 2. Allocate per-thread CPU state and seed PC/SP. */ 319 e->main_thread.cpu = resolved.arch->emu->cpu_new(c, e->process.image.entry_pc, 320 e->process.image.initial_sp); 321 emu_cpu_set_thread(e->main_thread.cpu, &e->main_thread); 322 if (!e->main_thread.cpu || 323 emu_loaded_image_attach_cpu(e->main_thread.cpu, &e->process.image) != 0 || 324 (resolved.os->emu_init_thread && 325 resolved.os->emu_init_thread(c, &e->process, &e->main_thread) != 326 KIT_OK)) { 327 compiler_panic(c, SRCLOC_NONE, "emu: failed to initialize guest CPU state"); 328 } 329 330 /* 3. In INTERP mode, attach an interp sink so each translated block is also 331 * captured as an InterpFunc, and stand up the long-lived stack used to run 332 * blocks. The sink stays attached for the whole emu lifetime (every 333 * translate_block compiles a fresh block that must be captured). External 334 * helper calls in a lifted block resolve through the same runtime resolver 335 * the JIT path uses; guest memory is reached only via those helpers, so no 336 * address translate hook is bound (host-identity frame). */ 337 #if KIT_INTERP_ENABLED 338 if (e->mode == KIT_EMU_MODE_INTERP) { 339 KitInterpHost ihost; 340 e->interp_prog = kit_interp_program_new((KitCompiler*)c); 341 if (!e->interp_prog) 342 compiler_panic(c, SRCLOC_NONE, "emu: failed to create interpreter program"); 343 kit_interp_program_attach(e->interp_prog, (KitCompiler*)c); 344 memset(&ihost, 0, sizeof(ihost)); 345 ihost.resolve_sym = emu_runtime_extern_resolver; 346 ihost.ctx = e; 347 kit_interp_program_set_host(e->interp_prog, &ihost); 348 e->interp_stack = kit_interp_stack_new(e->interp_prog); 349 if (!e->interp_stack) 350 compiler_panic(c, SRCLOC_NONE, "emu: failed to create interpreter stack"); 351 } 352 #else 353 if (e->mode == KIT_EMU_MODE_INTERP) 354 compiler_panic(c, SRCLOC_NONE, 355 "emu: interpreter mode not enabled in this build"); 356 #endif 357 358 compiler_panic_pop(c, &panic); 359 *out = e; 360 return KIT_OK; 361 } 362 363 void kit_emu_free(KitEmu* e) { 364 Heap* heap; 365 if (!e) return; 366 heap = e->c->ctx->heap; 367 368 #if KIT_INTERP_ENABLED 369 /* Detach the sink before freeing the program so a later compile on the 370 * (borrowed) compiler can't write into freed interp state. */ 371 if (e->interp_prog) kit_interp_program_attach(NULL, (KitCompiler*)e->c); 372 if (e->interp_stack) kit_interp_stack_free(e->interp_stack); 373 if (e->interp_prog) kit_interp_program_free(e->interp_prog); 374 #endif 375 while (e->njits) kit_jit_free(e->jits[--e->njits]); 376 if (e->jits) heap->free(heap, e->jits, sizeof(*e->jits) * e->jits_cap); 377 if (e->cache) emu_cache_free(e->cache); 378 if (e->process.os && e->process.os->emu_destroy_thread_private) 379 e->process.os->emu_destroy_thread_private(e->c, &e->main_thread); 380 if (e->main_thread.cpu) emu_cpu_free(e->main_thread.cpu); 381 emu_tls_destroy_process(e->c, &e->process); 382 if (e->process.os && e->process.os->emu_destroy_process_private) 383 e->process.os->emu_destroy_process_private(e->c, &e->process); 384 emu_unload_image(e->c, &e->process.image); 385 386 heap->free(heap, e, sizeof(*e)); 387 } 388 389 /* Lazily allocate the code cache the first time kit_emu_lookup runs. 390 * Requires a wired JIT host because cold blocks are published as ordinary 391 * one-block JIT images. */ 392 static KitStatus ensure_runtime(KitEmu* e) { 393 if (e->cache) return KIT_OK; 394 if (!e->host || !e->host->execmem) return KIT_UNSUPPORTED; 395 e->cache = emu_cache_new(e->c); 396 return KIT_OK; 397 } 398 399 static void emu_keep_jit(KitEmu* e, KitJit* jit) { 400 Heap* heap = e->c->ctx->heap; 401 if (e->njits == e->jits_cap) { 402 u32 old_cap = e->jits_cap; 403 u32 new_cap = old_cap ? old_cap * 2u : 8u; 404 KitJit** grown = 405 (KitJit**)heap->realloc(heap, e->jits, sizeof(*e->jits) * old_cap, 406 sizeof(*e->jits) * new_cap, _Alignof(KitJit*)); 407 if (!grown) compiler_panic(e->c, SRCLOC_NONE, "emu: out of memory"); 408 e->jits = grown; 409 e->jits_cap = new_cap; 410 } 411 e->jits[e->njits++] = jit; 412 } 413 414 /* ---- Translation (cold-miss path) ---- */ 415 416 static void* translate_block(KitEmu* e, u64 guest_pc) { 417 KitDecodedInsn* insts; 418 const ArchImpl* arch; 419 Heap* heap; 420 const u8* host_pc; 421 u64 va_end; 422 size_t decode_len; 423 u32 ninsts; 424 ObjBuilder* ob; 425 KitCg* cg; 426 KitCodeOptions copts; 427 KitCgUnitOptions unit_opts; 428 Sym block_name; 429 KitCgDecl block_decl; 430 KitCgSym block_sym; 431 EmuLiftCtx ctx; 432 void* entry; 433 KitStatus st; 434 KitLinkSessionOptions lopts; 435 KitLinkSession* sess; 436 KitJit* jit; 437 KitSlice block_slice; 438 439 if (e->trace & KIT_EMU_TRACE_BLOCK) emu_trace_block(e->c, guest_pc); 440 441 arch = e->process.arch; 442 if (!arch || !arch->decode || !arch->decode->decode_block || !arch->emu || 443 !arch->emu->block_fn_type || !arch->emu->lift_block) 444 return NULL; 445 446 host_pc = emu_cpu_va_to_host_perm(emu_main_cpu(e), guest_pc, 447 arch->decode->min_insn_len, EMU_MEM_EXEC); 448 if (!host_pc) return NULL; 449 va_end = emu_addr_space_contig_len(&e->process.image.addr_space, guest_pc, 450 EMU_MEM_EXEC); 451 if (!va_end) return NULL; 452 decode_len = (size_t)va_end; 453 heap = e->c->ctx->heap; 454 insts = (KitDecodedInsn*)heap->alloc( 455 heap, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK, _Alignof(KitDecodedInsn)); 456 if (!insts) compiler_panic(e->c, SRCLOC_NONE, "emu: out of memory"); 457 st = arch->decode->decode_block(e->c, host_pc, decode_len, guest_pc, insts, 458 EMU_MAX_INSTS_PER_BLOCK, &ninsts); 459 if (st != KIT_OK || ninsts == 0) { 460 heap->free(heap, insts, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK); 461 return NULL; 462 } 463 464 if (e->trace & KIT_EMU_TRACE_INSN) { 465 u32 j; 466 for (j = 0; j < ninsts; ++j) emu_trace_insn(e->c, guest_pc, &insts[j]); 467 } 468 469 /* Per-block ObjBuilder + public CG pipeline. The block lands as a single 470 * host function once per-ISA lifters start emitting real code. */ 471 ob = obj_new(e->c); 472 memset(&copts, 0, sizeof(copts)); 473 copts.opt_level = e->opt_level; 474 st = kit_cg_new(e->c, &cg); 475 if (st == KIT_OK) st = kit_cg_begin(cg, (KitObjBuilder*)ob, &copts); 476 memset(&unit_opts, 0, sizeof unit_opts); 477 unit_opts.source_name = KIT_SLICE_LIT("<emu-block>"); 478 if (st == KIT_OK) st = kit_cg_begin_unit(cg, &unit_opts); 479 if (st != KIT_OK || !cg) 480 compiler_panic(e->c, SRCLOC_NONE, "emu: kit_cg_new failed"); 481 482 block_name = emu_block_sym_name(e->c, guest_pc); 483 memset(&block_decl, 0, sizeof(block_decl)); 484 block_decl.kind = KIT_CG_DECL_FUNC; 485 block_decl.linkage_name = 486 kit_cg_c_linkage_name((KitCompiler*)e->c, (KitSym)block_name); 487 block_decl.display_name = (KitSym)block_name; 488 block_decl.type = arch->emu->block_fn_type(e->c); 489 block_decl.sym.bind = KIT_SB_GLOBAL; 490 block_decl.sym.visibility = KIT_CG_VIS_DEFAULT; 491 block_sym = kit_cg_decl(cg, block_decl); 492 if (block_sym == KIT_CG_SYM_NONE) 493 compiler_panic(e->c, SRCLOC_NONE, "emu: failed to declare block symbol"); 494 495 memset(&ctx, 0, sizeof(ctx)); 496 ctx.compiler = e->c; 497 ctx.arch = e->guest_target.arch; 498 ctx.thread_type = emu_thread_type(e->c); 499 ctx.block_fn_type = arch->emu->block_fn_type(e->c); 500 ctx.block_sym = block_sym; 501 ctx.guest_pc = guest_pc; 502 503 st = arch->emu->lift_block(e->c, cg, insts, ninsts, &ctx); 504 heap->free(heap, insts, sizeof(*insts) * EMU_MAX_INSTS_PER_BLOCK); 505 insts = NULL; 506 if (st != KIT_OK) compiler_panic(e->c, SRCLOC_NONE, "emu: failed to lift block"); 507 508 st = kit_cg_end_unit(cg); 509 if (st == KIT_OK) st = kit_cg_finish(cg, NULL); 510 if (st == KIT_OK) st = kit_cg_detach(cg); 511 if (st != KIT_OK) 512 compiler_panic(e->c, SRCLOC_NONE, "emu: kit_cg_finish failed"); 513 kit_cg_free(cg); 514 obj_finalize(ob); 515 516 block_slice = pool_slice(e->c->global, block_name); 517 memset(&lopts, 0, sizeof(lopts)); 518 lopts.output_kind = KIT_LINK_OUTPUT_JIT; 519 lopts.jit_host = e->host; 520 lopts.extern_resolver = emu_runtime_extern_resolver; 521 lopts.extern_resolver_user = e; 522 523 sess = NULL; 524 jit = NULL; 525 st = kit_link_session_new((KitCompiler*)e->c, &lopts, &sess); 526 if (st == KIT_OK) st = kit_link_session_add_obj(sess, (KitObjBuilder*)ob); 527 if (st == KIT_OK) st = kit_link_session_jit(sess, &jit); 528 if (sess) kit_link_session_free(sess); 529 if (st != KIT_OK || !jit) 530 compiler_panic(e->c, SRCLOC_NONE, "emu: failed to publish JIT block"); 531 532 entry = kit_jit_lookup(jit, *(KitSlice*)&block_slice); 533 if (!entry) { 534 kit_jit_free(jit); 535 return NULL; 536 } 537 emu_keep_jit(e, jit); 538 539 #if KIT_INTERP_ENABLED 540 /* INTERP mode: the JIT image above still resolved the block's helper externs 541 * and validated the lifted IR, but dispatch runs the captured InterpFunc 542 * (lowered during kit_cg_finish, above) instead of the host code. Cache the 543 * InterpFunc*; kit_emu_step disambiguates the payload by e->mode. A rejected 544 * block is still captured (ifn->ok == 0) and is reported with its reason when 545 * dispatched, so only a genuine capture miss yields NULL here. */ 546 if (e->mode == KIT_EMU_MODE_INTERP) { 547 KitInterpFunc* ifn = 548 kit_interp_lookup(e->interp_prog, *(KitSlice*)&block_slice); 549 if (!ifn) return NULL; 550 entry = (void*)ifn; 551 } 552 #endif 553 554 emu_cache_insert(e->cache, guest_pc, entry); 555 emu_addr_space_mark_translated(&e->process.image.addr_space, guest_pc, 556 decode_len); 557 e->cache_generation = e->process.image.addr_space.generation; 558 return entry; 559 } 560 561 void* kit_emu_lookup(KitEmu* e, uint64_t guest_pc) { 562 PanicFrame panic; 563 void* entry; 564 565 if (!e) return NULL; 566 567 if (e->cache && 568 e->cache_generation != e->process.image.addr_space.generation) { 569 emu_cache_free(e->cache); 570 e->cache = NULL; 571 e->cache_generation = 0; 572 } 573 574 /* Cache hit short-circuits the panic boundary. */ 575 if (e->cache) { 576 entry = emu_cache_lookup(e->cache, guest_pc); 577 if (entry) return entry; 578 } 579 580 if (ensure_runtime(e) != KIT_OK) return NULL; 581 582 compiler_panic_push(e->c, &panic); 583 if (setjmp(panic.env)) { 584 compiler_run_cleanups(e->c); 585 compiler_panic_pop(e->c, &panic); 586 return NULL; 587 } 588 589 entry = translate_block(e, guest_pc); 590 591 compiler_panic_pop(e->c, &panic); 592 return entry; 593 } 594 595 /* ---- Dispatcher ---- */ 596 597 #if KIT_INTERP_ENABLED 598 /* Run one lifted guest block through the IR interpreter on the emu's long-lived 599 * stack, returning the next guest pc. The block's host function takes the 600 * EmuThread* and returns next_pc, so we seed param0 with the thread pointer 601 * (host-identity: the interpreter never translates it) and shuttle the scalar 602 * return back. Guest registers and memory are reached only through the __emu_* 603 * helpers the block calls โ the interpreter holds no guest state itself. 604 * 605 * A non-DONE result means the block could not be interpreted (an op the lowerer 606 * rejected) or trapped inside the interpreter; per the chosen UX we hard-fail 607 * with the reason rather than falling back to the JIT. A guest fault/exit is 608 * NOT such a case: the helpers deliver those in-band by setting the CPU trap 609 * reason and returning a next_pc, which the post-dispatch check below observes 610 * exactly as in JIT mode. */ 611 static u64 emu_interp_run_block(KitEmu* e, KitInterpFunc* ifn, 612 EmuThread* thread) { 613 u64 arg = (u64)(uintptr_t)thread; 614 int64_t ret = 0; 615 KitInterpStatus s; 616 617 kit_interp_stack_reset(e->interp_stack); 618 if (kit_interp_call_args_on(e->interp_stack, ifn, &arg, 1u) != KIT_OK) 619 compiler_panic(e->c, SRCLOC_NONE, "emu: failed to seed interpreter frame"); 620 621 s = kit_interp_resume(e->interp_stack, &ret); 622 if (s == KIT_INTERP_DONE) return (u64)ret; 623 624 { 625 const char* why = kit_interp_stack_trap_reason(e->interp_stack); 626 compiler_panic(e->c, SRCLOC_NONE, 627 "emu: cannot interpret block at guest_pc=0x%llx: %s", 628 (unsigned long long)emu_cpu_pc(thread->cpu), 629 why ? why : "unsupported operation"); 630 } 631 return 0; /* unreachable: compiler_panic longjmps */ 632 } 633 #endif 634 635 KitStatus kit_emu_step(KitEmu* e, uint32_t nblocks) { 636 PanicFrame panic; 637 uint32_t i; 638 KitStatus st; 639 640 if (!e) return KIT_INVALID; 641 if (e->done) return KIT_OK; 642 st = ensure_runtime(e); 643 if (st != KIT_OK) return st; 644 645 compiler_panic_push(e->c, &panic); 646 if (setjmp(panic.env)) { 647 compiler_run_cleanups(e->c); 648 compiler_panic_pop(e->c, &panic); 649 return KIT_ERR; 650 } 651 652 for (i = 0; i < nblocks && !e->done; ++i) { 653 EmuThread* thread = &e->main_thread; 654 EmuCPUState* cpu = thread->cpu; 655 u64 pc = emu_cpu_pc(cpu); 656 void* entry; 657 EmuBlockFn fn; 658 u64 next_pc; 659 EmuTrapReason trap; 660 661 if (e->trace & KIT_EMU_TRACE_PC) emu_trace_pc(e->c, pc); 662 663 entry = kit_emu_lookup(e, pc); 664 if (!entry) { 665 compiler_panic(e->c, SRCLOC_NONE, 666 "emu: failed to translate block at guest_pc=0x%llx", 667 (unsigned long long)pc); 668 } 669 670 #if KIT_INTERP_ENABLED 671 if (e->mode == KIT_EMU_MODE_INTERP) { 672 next_pc = emu_interp_run_block(e, (KitInterpFunc*)entry, thread); 673 } else 674 #endif 675 { 676 fn = (EmuBlockFn)entry; 677 next_pc = fn(thread); 678 } 679 emu_cpu_set_pc(cpu, next_pc); 680 681 trap = emu_cpu_trap_reason(cpu); 682 if (trap == EMU_TRAP_EXIT) { 683 e->done = 1; 684 e->exit_code = emu_cpu_exit_code(cpu); 685 } else if (trap == EMU_TRAP_FAULT) { 686 compiler_panic(e->c, SRCLOC_NONE, "emu: guest faulted at pc=0x%llx", 687 (unsigned long long)next_pc); 688 } 689 } 690 691 compiler_panic_pop(e->c, &panic); 692 return KIT_OK; 693 } 694 695 KitStatus kit_emu_run(KitCompiler* c, const KitEmuOptions* opts, 696 int* out_exit_code) { 697 KitEmu* e = NULL; 698 KitStatus st; 699 700 if (out_exit_code) *out_exit_code = 0; 701 if (!c || !opts) return KIT_INVALID; 702 703 st = kit_emu_new(c, opts, &e); 704 if (st != KIT_OK) return st; 705 706 while (!e->done) { 707 st = kit_emu_step(e, 1024); 708 if (st != KIT_OK) break; 709 } 710 711 if (st == KIT_OK && out_exit_code) *out_exit_code = e->exit_code; 712 kit_emu_free(e); 713 return st; 714 } 715 716 /* Runtime accessor for the resolver โ exposes the running emu's 717 * CPUState pointer without baking the KitEmu layout into runtime.c. 718 * Used by emu_runtime_extern_resolver for EMU_SYM_CPU_STATE. */ 719 EmuCPUState* emu_internal_cpu(KitEmu* e) { return emu_main_cpu(e); } 720 721 EmuProcess* emu_internal_process(KitEmu* e) { return e ? &e->process : NULL; } 722 723 /* ---- Block symbol naming ---- 724 * "emu_block_<16-hex-pc>" โ fixed-width hex so the linker's hash 725 * lookup never collides between two blocks at distinct guest PCs. 726 * Interned in the compiler's global pool; the Sym is stable for the 727 * Compiler's lifetime, which is what the linker assumes. */ 728 Sym emu_block_sym_name(Compiler* c, u64 guest_pc) { 729 char buf[32]; 730 static const char hex[] = "0123456789abcdef"; 731 int i; 732 /* "emu_block_" + 16 hex digits + NUL = 27 chars, fits in 32. */ 733 memcpy(buf, "emu_block_", 10); 734 for (i = 0; i < 16; ++i) { 735 buf[10 + 15 - i] = hex[guest_pc & 0xfu]; 736 guest_pc >>= 4; 737 } 738 buf[26] = '\0'; 739 return pool_intern_slice(c->global, slice_from_cstr(buf)); 740 }