interp.h (9632B)
1 #ifndef KIT_INTERP_INTERNAL_H 2 #define KIT_INTERP_INTERNAL_H 3 4 /* Internal interface for the threaded-bytecode interpreter (src/interp). 5 * 6 * The loader (lower.c) turns a post-opt_run_o1_interp Func into an InterpFunc: 7 * a flat array of fixed-width InterpInsn records plus side tables. The engine 8 * (engine.c) runs the records over an explicit, swappable InterpStack so that 9 * IR-level calls never use host C recursion. Memory + symbol resolution is 10 * pluggable (host-identity or emu/guest) via KitInterpHost. */ 11 12 #include <kit/interp.h> 13 14 #include "abi/abi.h" 15 #include "cg/cgtarget.h" 16 #include "core/core.h" 17 #include "core/slice.h" 18 #include "obj/obj.h" 19 #include "opt/ir.h" 20 21 /* One opcode family per IROp, width/aggregate-specialized where it changes the 22 * handler. The opcode drives the engine's dispatch switch. */ 23 typedef enum InterpOp { 24 IOP_NOP = 0, 25 IOP_LOAD_IMM, 26 IOP_LOAD_CONST, 27 IOP_COPY, /* scalar register/memory move */ 28 IOP_COPY_AGG, /* aggregate / >8B byte copy */ 29 IOP_LOAD, /* scalar load */ 30 IOP_LOAD_AGG, /* aggregate load (byte copy) */ 31 IOP_STORE, /* scalar store */ 32 IOP_STORE_AGG, 33 IOP_ADDR_OF, 34 IOP_TLS_ADDR, 35 IOP_BINOP, 36 IOP_UNOP, 37 IOP_CMP, 38 IOP_CONVERT, 39 IOP_CALL, 40 IOP_BR, 41 IOP_CONDBR, 42 IOP_CMP_BRANCH, 43 IOP_SWITCH, 44 IOP_INDIRECT_BR, 45 IOP_LOAD_LABEL_ADDR, 46 IOP_RET, 47 IOP_RET_VOID, 48 IOP_ALLOCA, 49 IOP_AGG_COPY, 50 IOP_AGG_SET, 51 IOP_BITFIELD_LOAD, 52 IOP_BITFIELD_STORE, 53 IOP_VA_START, 54 IOP_VA_ARG, 55 IOP_VA_END, 56 IOP_VA_COPY, 57 IOP_ATOMIC_LOAD, 58 IOP_ATOMIC_STORE, 59 IOP_ATOMIC_RMW, 60 IOP_ATOMIC_CAS, 61 IOP_FENCE, 62 IOP_INTRINSIC, 63 IOP_UNREACHABLE, /* control terminator: faults (TRAP) if ever reached */ 64 IOP_TRAP, /* unsupported-at-runtime guard */ 65 IOP__COUNT, 66 } InterpOp; 67 68 /* Fixed-width, cache-friendly record. Hot fields (dst preg, resolved branch 69 * pcs, immediate, widths) are cached; `inst` retains the source instruction so 70 * handlers can read full operand detail (types, MemAccess, aux, call desc) 71 * generically. Direct threading via `handler` is reserved for a future pass; 72 * the engine currently dispatches on `op`. */ 73 typedef struct InterpInsn { 74 void* handler; /* reserved: &&label for direct threading */ 75 const Inst* inst; /* source instruction (arena-resident, c->tu lifetime) */ 76 u32 op; /* InterpOp */ 77 u32 sub; /* sub-op tag: BinOp/UnOp/CmpOp/ConvKind/KitCgAtomicOp/KitCgMemOrder 78 */ 79 u32 dst; /* dest PReg id (cache of opnds[0].v.reg); 0 if none */ 80 u32 t0; /* resolved pc of succ[0] / switch-table index / src reg */ 81 u32 t1; /* resolved pc of succ[1] */ 82 i64 imm; /* immediate cache (LOAD_IMM etc.) */ 83 u16 w0; /* primary width in bytes (result/load/store) */ 84 u16 w1; /* secondary width in bytes (CONVERT source) */ 85 u8 fp0; /* result/operand is floating point */ 86 u8 fp1; /* CONVERT source is floating point */ 87 u8 tail; /* IOP_CALL: this is a tail call (terminator, no return) */ 88 u8 pad; 89 } InterpInsn; 90 91 /* Side table for IOP_SWITCH: case values and pre-resolved target pcs. */ 92 typedef struct InterpSwitch { 93 const IRSwitchAux* aux; /* cases[].value (block ids ignored; we use *_pc) */ 94 u32* case_pc; /* ncases entries: case i -> code pc */ 95 u32 ncases; 96 u32 default_pc; 97 KitCgTypeId sel_type; 98 } InterpSwitch; 99 100 typedef struct InterpProgram InterpProgram; 101 102 /* A global symbol referenced by a function: name resolved at lower time (the 103 * obj is available then), host address resolved lazily at run time (after the 104 * JIT image is linked). */ 105 typedef struct InterpGlobal { 106 ObjSymId sym; 107 Slice name; 108 void* cached; /* resolved host base address, or NULL until first use */ 109 u8 resolved; 110 } InterpGlobal; 111 112 typedef struct InterpFunc { 113 InterpProgram* prog; 114 Func* f; /* source opt Func (arena = compiler->tu) */ 115 ObjSymId sym; 116 Slice name; /* unmangled C name (borrowed from the global pool) */ 117 118 InterpGlobal* globals; 119 u32 nglobals; 120 121 InterpInsn* code; 122 u32 ncode; 123 u32* block_pc; /* block id -> code pc; INTERP_PC_NONE if unreachable */ 124 u32 nblocks; 125 126 u32* slot_off; /* frame slot id -> byte offset within the frame */ 127 u32 nslots; 128 u32 frame_align; /* max alignment of any static slot (>=8) */ 129 u32 frame_bytes; /* static frame size; alloca grows beyond at runtime */ 130 u32 npregs; /* register-file size (PReg ids index it) */ 131 132 InterpSwitch* switches; 133 u32 nswitches; 134 135 u8 threaded; 136 u8 ok; /* 0 if lowering rejected an unsupported op */ 137 const char* reject_reason; /* set when !ok */ 138 } InterpFunc; 139 140 #define INTERP_PC_NONE 0xffffffffu 141 142 struct InterpProgram { 143 Compiler* c; 144 InterpFunc** funcs; 145 u32 nfuncs; 146 u32 funcs_cap; 147 KitInterpHost host; 148 u8 have_host; 149 }; 150 151 /* One IR-level activation. Execution state lives here, never on the host C 152 * stack — CALL pushes, RET pops. */ 153 typedef struct InterpFrame { 154 InterpFunc* fn; 155 InterpInsn* ip; /* resume point */ 156 u32 regs_off; /* register file offset in regs_arena (npregs u64s) */ 157 u32 mem_off; /* this frame's addressable bytes offset in mem_arena */ 158 u32 frame_bytes; /* static + alloca high-water */ 159 u32 alloca_top; /* current frame byte usage (>= static frame_bytes) */ 160 u8* sret_ptr; /* aggregate-return destination, or NULL */ 161 u32 ret_dst; /* caller PReg to receive a scalar return */ 162 u8 ret_wanted; /* caller wants a scalar result in ret_dst */ 163 u8 ret_is_fp; 164 u8 has_varargs; /* variadic callee: a vararg buffer was laid out */ 165 u32 vararg_off; /* mem_arena offset of this frame's vararg buffer */ 166 } InterpFrame; 167 168 /* A swappable execution context. interp_resume runs the TOP frame. */ 169 struct KitInterpStack { 170 InterpProgram* prog; 171 InterpFrame* frames; 172 u32 nframes; 173 u32 frames_cap; 174 u8* regs_arena; /* bump region for per-frame register files (bytes) */ 175 u32 regs_top; 176 u32 regs_cap; 177 u8* mem_arena; /* addressable frame bytes (host-identity) */ 178 u32 mem_top; 179 u32 mem_cap; 180 u64 scalar_ret; /* return shuttle between frames */ 181 u8 ret_is_fp; 182 u8 status; /* KitInterpStatus */ 183 u8 mem_fault; /* set by mem_read/write/copy on a translation miss */ 184 const char* trap_reason; 185 }; 186 typedef struct KitInterpStack InterpStack; 187 188 /* ---- interp_program.c ---- */ 189 /* Called by the optimizer (src/opt/opt.c) for each compiled function when an 190 * interp sink is attached: lowers `f` and registers it by sym + name. `obj` 191 * is the builder the function was recorded into, used to resolve referenced 192 * global symbol names while it is still alive. */ 193 void interp_capture_func(void* program, Func* f, ObjSymId sym, const char* name, 194 u32 name_len, const ObjBuilder* obj); 195 /* Resolve an internal call target (defined-in-TU function) by symbol. */ 196 InterpFunc* interp_func_for_sym(InterpProgram*, ObjSymId); 197 /* Reverse-map a resolved host code address back to an interpretable function 198 * (so an indirect call through a function pointer to a TU-internal function is 199 * still interpreted, not run as native). NULL if the address is not one. */ 200 InterpFunc* interp_func_for_addr(InterpProgram*, void* addr); 201 /* Resolve the host base address of a function-referenced global (lazily caches 202 * through interp_resolve_sym). NULL if unresolved. */ 203 void* interp_global_base(InterpFunc*, ObjSymId); 204 /* Resolve a thread-local variable's address (+addend) for the running thread. 205 * Routes through the host's resolve_tls hook (a thread-local symbol may resolve 206 * to a TLV descriptor rather than storage); falls back to treating it as a 207 * plain global when no hook is bound. NULL if unresolved. */ 208 void* interp_tls_addr(InterpFunc*, ObjSymId, i64 addend); 209 210 /* ---- lower.c ---- */ 211 InterpFunc* interp_lower(InterpProgram*, Func*, ObjSymId, Slice name, 212 const ObjBuilder* obj); 213 214 /* ---- engine.c ---- */ 215 KitInterpStatus interp_run_stack(InterpStack*, int64_t* out_ret); 216 /* Translate an abstract address to a host pointer (host-identity if no host 217 * vtable is bound). Returns NULL on a fault. */ 218 u8* interp_translate(InterpProgram*, u64 addr, u64 n, int perms); 219 /* Resolve a global/extern symbol name to a host address (NULL if unresolved). 220 */ 221 void* interp_resolve_sym(InterpProgram*, Slice name); 222 223 /* ---- ffi.c ---- */ 224 /* Marshal an external call: `fp` is the host function pointer, `desc` the 225 * semantic call descriptor. Reads argument values via the engine's accessors 226 * (passed as a callback closure). Returns 0 on success, non-zero (with 227 * *reason set) when the signature is outside the supported thunk family. */ 228 typedef struct InterpFfiArgs { 229 const ABIFuncInfo* fi; 230 /* int/fp register-slot values, already laid out by the engine. */ 231 u64 iargs[8]; 232 u32 nint; 233 double fargs[8]; /* fp args when passed as doubles */ 234 float fargs_f[8]; /* fp args when passed as 32-bit singles */ 235 u32 nfp; 236 u8 args_fp_is_float; /* 1: every fp arg is a 4-byte single (use fargs_f) */ 237 void* sret; /* hidden struct-return buffer, or NULL */ 238 u8 ret_is_void; 239 /* Register-return classification (only when !sret): the value comes back in 240 * ret_nparts (0=void, 1, or 2) registers; out[k] receives each register's raw 241 * 64-bit contents and the engine reassembles the aggregate. */ 242 u8 ret_nparts; 243 u8 ret_fp[2]; /* part k is fp-class (returned in XMM/V) */ 244 u32 ret_size[2]; /* bytes carried by part k */ 245 } InterpFfiArgs; 246 247 /* Invokes `fp` through a cast thunk matching the classified signature. Fills 248 * out[0] (and out[1] for a two-register return) with raw register contents. 249 * Returns non-zero (with *reason) for an unsupported signature. */ 250 int interp_ffi_invoke(void* fp, const InterpFfiArgs*, u64 out[2], 251 const char** reason); 252 253 #endif