kit

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

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