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 }