kit

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

native_direct_target.h (11425B)


      1 #ifndef KIT_CG_NATIVE_DIRECT_TARGET_H
      2 #define KIT_CG_NATIVE_DIRECT_TARGET_H
      3 
      4 #include "arch/native_target.h"
      5 #include "cg/cgtarget.h"
      6 
      7 /* NativeDirectTarget is the shared direct -O0 implementation of CgTarget for
      8  * native backends. It owns semantic local homes, direct-mode scratch policy,
      9  * local register caching, and conservative flushes. Arch code supplies a
     10  * NativeTarget plus this small adapter for ABI/frame/legality decisions. */
     11 
     12 typedef struct NativeDirectTarget NativeDirectTarget;
     13 
     14 typedef struct NativeDirectScope {
     15   u8 kind; /* ScopeKind */
     16   u8 owns_break;
     17   u8 pad[2];
     18   Label break_label;
     19   Label continue_label;
     20 } NativeDirectScope;
     21 
     22 typedef enum NativeDirectBarrier {
     23   NATIVE_DIRECT_BARRIER_CALL = 1u << 0,
     24   NATIVE_DIRECT_BARRIER_MEMORY = 1u << 1,
     25   NATIVE_DIRECT_BARRIER_VOLATILE = 1u << 2,
     26   NATIVE_DIRECT_BARRIER_ATOMIC = 1u << 3,
     27   NATIVE_DIRECT_BARRIER_INLINE_ASM = 1u << 4,
     28 } NativeDirectBarrier;
     29 
     30 typedef struct NativeDirectLocal {
     31   KitCgTypeId type;
     32   u32 size;
     33   u32 align;
     34   u32 flags; /* CGLocalFlag */
     35 
     36   NativeFrameSlot home;
     37   Reg reg;
     38   u8 cls; /* NativeAllocClass */
     39   u8 dirty;
     40   u8 address_taken;
     41   u8 memory_required;
     42   u8 transient; /* a per-subexpression temp; its home is reclaimable at a
     43                  * statement boundary (see nd_reclaim_temps) */
     44   /* The cached register holds this local's value zero-extended to the full
     45    * register, straight from a narrow integer load (ldrb/ldrh/ldr-w always zero
     46    * the upper bits). While set, a CV_ZEXT widening it is a no-op and is elided
     47    * by nd_convert. Set only in nd_load; cleared on any other write
     48    * (nd_dst_writeback) and when the register is dropped (flush/invalidate).
     49    * Meaningful only while reg != REG_NONE. */
     50   u8 load_zext;
     51   /* The cached register holds this local's value SIGN-extended to the full
     52    * register, from an MF_SEXT_LOAD narrow load that the backend honored with a
     53    * sign-extending load (aa64 ldrsb/ldrsh, gated on load_sext). While set, a
     54    * CV_SEXT widening it to any wider integer is a no-op and is elided by
     55    * nd_convert. Set only in nd_load; cleared exactly like load_zext (a spill +
     56    * plain reload would zero-extend, so clearing it forces the real extend to run
     57    * on the reloaded value — see nd_flush_local / nd_invalidate_local). */
     58   u8 load_sext;
     59   u32 last_use; /* d->use_tick at the most recent cache touch (LRU victim key)
     60                  */
     61   /* Intrusive doubly-linked list of currently-cached locals, in insertion
     62    * (caching) order so nd_cache_link is O(1) tail insertion. Values are 0-based
     63    * indices into NativeDirectTarget.locals (or -1); only valid while this local
     64    * is cached (l->reg != REG_NONE). */
     65   i32 cache_next;
     66   i32 cache_prev;
     67 } NativeDirectLocal;
     68 
     69 typedef enum NativeDirectAddrLegality {
     70   NATIVE_DIRECT_ADDR_ILLEGAL,
     71   NATIVE_DIRECT_ADDR_LEGAL,
     72   NATIVE_DIRECT_ADDR_LEGAL_IF_UNSCALED,
     73 } NativeDirectAddrLegality;
     74 
     75 typedef struct NativeOps NativeOps;
     76 /* Semantic-only adapter the arch supplies on the NDT path. `ops` is mandatory:
     77  * aa64_backend_make and aa64_semantic_target_new always pass it. Pure
     78  * pass-throughs to NativeTarget (reg_info, func_begin/func_end, frame-slot
     79  * allocation, class_for_type, addr_legal) are not mirrored here -- NDT calls
     80  * d->native->X directly for those. */
     81 struct NativeOps {
     82   void (*bind_param)(NativeDirectTarget*, const CGParamDesc*, CGLocal,
     83                      NativeDirectLocal*);
     84 
     85   int (*operand_legal)(NativeDirectTarget*, const Operand*, NativeAllocClass);
     86   NativeDirectAddrLegality (*semantic_addr_legal)(NativeDirectTarget*,
     87                                                   Operand addr, MemAccess);
     88 
     89   void (*marshal_call)(NativeDirectTarget*, const NativeCallDesc*,
     90                        NativeCallPhase*);
     91   const char* (*tail_call_unrealizable_reason)(NativeDirectTarget*,
     92                                                const CGCallDesc*);
     93   void (*emit_call)(NativeDirectTarget*, const NativeCallPhase*);
     94   /* `value` is the single returned local, or CG_LOCAL_NONE for void. */
     95   void (*emit_ret)(NativeDirectTarget*, CGLocal value);
     96 
     97   void (*va_start_)(NativeDirectTarget*, Operand ap_addr);
     98   void (*va_arg_)(NativeDirectTarget*, Operand dst, Operand ap_addr,
     99                   KitCgTypeId type);
    100   void (*va_end_)(NativeDirectTarget*, Operand ap_addr);
    101   void (*va_copy_)(NativeDirectTarget*, Operand dst_ap_addr,
    102                    Operand src_ap_addr);
    103 
    104   void (*asm_block)(NativeDirectTarget*, const char* tmpl,
    105                     const AsmConstraint* outs, u32 nout, Operand* out_ops,
    106                     const AsmConstraint* ins, u32 nin, const Operand* in_ops,
    107                     const Sym* clobbers, u32 nclob, u32 clobber_abi_sets);
    108 
    109   void (*barrier)(NativeDirectTarget*, u32 flags);
    110 };
    111 
    112 /* Fixed transient buffers for per-op location/label arrays (call args/results,
    113  * return values, intrinsic operands, indirect-branch targets). The common case
    114  * fits these and allocates nothing; overflow falls back to the TU arena. */
    115 #define ND_ARG_BUF 16u
    116 #define ND_RET_BUF 8u
    117 #define ND_LBL_BUF 16u
    118 
    119 typedef struct NativeDirectTargetConfig {
    120   NativeTarget* native;
    121   const NativeOps* ops;
    122   void* user;
    123   u32 flags;
    124 } NativeDirectTargetConfig;
    125 
    126 struct NativeDirectTarget {
    127   CgTarget base;
    128   u32 magic;
    129   NativeTarget* native;
    130   const NativeOps* ops;
    131   void* user;
    132 
    133   /* Register info and per-class info resolved once at construction (constant
    134    * for the program), so scratch acquire / cache alloc / evict do an O(1)
    135    * lookup instead of re-resolving reg_info and linearly scanning ri->classes.
    136    */
    137   const NativeRegInfo* reg_info;
    138   const NativeAllocClassInfo* class_info[NATIVE_REG_CLASS_COUNT];
    139 
    140   /* Per-class -O0 value-cache register pool, precomputed at construction in
    141    * `allocable[]` order. On ndt_caller_saved_only targets (aa64) it is the
    142    * caller-saved subset of allocable, so the deferred prologue never has to
    143    * spill a callee-save. Other targets take the whole allocable list: their NDT
    144    * allocable sets are deliberately hazard-free (no implicit-clobber registers
    145    * such as the x86 shift/divide regs) and any callee-save among them is either
    146    * covered by the reserve_callee_saves prologue path (x64 r13-r15, whose save
    147    * region is fixed-size) or is itself caller-saved (riscv a-regs), so caching
    148    * in them is both safe and free of a single-pass prologue-budget blowup.
    149    * Note: `allocable[]` is read only by the NDT, never the optimizer (which
    150    * allocates over the phys[] ALLOCABLE flags), so each arch tunes it for -O0
    151    * register caching independently of -O1. */
    152   Reg cache_pool[NATIVE_REG_CLASS_COUNT][NATIVE_MAX_HARD_REGS];
    153   u32 ncache_pool[NATIVE_REG_CLASS_COUNT];
    154 
    155   const CGFuncDesc* func;
    156   SrcLoc loc;
    157 
    158   NativeDirectLocal* locals;
    159   u32 nlocals;
    160   u32 locals_cap;
    161 
    162   /* Transient temp locals minted since the last reclaim (statement boundary), in
    163    * allocation order. nd_reclaim_temps drains this to invalidate their cache
    164    * entries and return their frame homes to the free list; bounded by one
    165    * statement's temp depth. */
    166   CGLocal* transient_locals;
    167   u32 ntransient;
    168   u32 transient_cap;
    169 
    170   MCLabel* labels;
    171   u32 nlabels;
    172   u32 labels_cap;
    173 
    174   NativeDirectScope* scopes;
    175   u32 nscopes;
    176   u32 scopes_cap;
    177 
    178   u32 scratch_used[NATIVE_REG_CLASS_COUNT];
    179   /* Per-function callee-saved registers borrowed by direct scratch/cache
    180    * allocation. Reported to the native backend before prologue patching. */
    181   u32 callee_saved_used[NATIVE_REG_CLASS_COUNT];
    182   /* Local register cache (write-back, basic-block-scoped). reg_owner[cls][reg]
    183    * names the semantic local currently cached in that physical register, or
    184    * CG_LOCAL_NONE. scratch_used doubles as the per-class "pinned for the
    185    * current instruction" mask. Per-local cache state (reg/cls/dirty) lives on
    186    * NativeDirectLocal. See doc/CODEGEN.md "local register cache". */
    187   CGLocal reg_owner[NATIVE_REG_CLASS_COUNT][NATIVE_MAX_HARD_REGS];
    188   u32 use_tick; /* monotonic counter stamped onto NativeDirectLocal.last_use */
    189   /* Flat mirror of the currently-owning local's last_use, keyed by physical
    190    * register, so the LRU victim scan reads one contiguous array instead of
    191    * chasing a NativeDirectLocal per candidate. Written by nd_touch_local
    192    * whenever an owned register's last_use changes; only read for registers with
    193    * reg_owner[cls][reg] != CG_LOCAL_NONE, where it equals that local's
    194    * last_use. */
    195   u32 reg_last_use[NATIVE_REG_CLASS_COUNT][NATIVE_MAX_HARD_REGS];
    196   /* Head/tail of the intrusive cached-locals list (in caching order), -1 when
    197    * empty; ncached is its length. Lets nd_flush_all run in O(cached) instead of
    198    * scanning all nlocals on every control-flow / barrier op; cache_tail makes
    199    * nd_cache_link an O(1) tail insertion. */
    200   i32 cache_head;
    201   i32 cache_tail;
    202   u32 ncached;
    203   u32 max_outgoing;
    204 
    205   /* Transient per-op buffers; see ND_*_BUF. Inputs use argbuf, outputs retbuf;
    206    * never live across more than one op, so reuse between ops is safe. */
    207   NativeLoc argbuf[ND_ARG_BUF];
    208   NativeLoc retbuf[ND_RET_BUF];
    209   MCLabel lblbuf[ND_LBL_BUF];
    210 
    211   ObjSecId local_static_sec;
    212   ObjSymId local_static_sym;
    213   u32 local_static_base;
    214   u32 local_static_size;
    215   u8 local_static_active;
    216 };
    217 
    218 CgTarget* native_direct_target_new(Compiler*, ObjBuilder*,
    219                                    const NativeDirectTargetConfig*);
    220 NativeTarget* native_direct_target_native(CgTarget*);
    221 
    222 /* Per-arch NativeTarget constructor, e.g. aa64_native_target_new. */
    223 typedef NativeTarget* (*NativeTargetCtor)(Compiler*, ObjBuilder*, MCEmitter*);
    224 
    225 /* Shared glue for an ArchImpl's backend.make hook. Builds the MCEmitter +
    226  * optional Debug producer (cg_mc_debug_new), constructs the arch NativeTarget
    227  * via `ctor`, then wraps it in a NativeDirectTarget configured with `ops`.
    228  * Returns NULL on any failure. Each arch backend_make is exactly a call to
    229  * this with its own (ctor, ops). */
    230 CgTarget* native_direct_backend_make(Compiler*, ObjBuilder*,
    231                                      const KitCodeOptions*,
    232                                      NativeTargetCtor ctor,
    233                                      const NativeOps* ops);
    234 
    235 /* Shared glue for an ArchImpl's cgtarget_new hook (semantic/assembler path).
    236  * Reuses the caller's MCEmitter when non-NULL, else mints one with mc_new; no
    237  * Debug producer. Constructs the arch NativeTarget via `ctor` and wraps it in a
    238  * NativeDirectTarget configured with `ops`. Returns NULL on any failure. */
    239 CgTarget* native_direct_semantic_target_new(Compiler*, ObjBuilder*, MCEmitter*,
    240                                             NativeTargetCtor ctor,
    241                                             const NativeOps* ops);
    242 
    243 /* Project a semantic CGCallDesc into a NativeCallDesc whose args/results are
    244  * the callee's incoming frame slots (the layout a sibling/tail call must land
    245  * in). `nd` is fully overwritten (memset to zero, then its args/results buffers
    246  * are arena-allocated from d->locals[*].home). This is the arch-neutral core
    247  * the per-arch tail_call_unrealizable_reason hooks share; each backend wraps it
    248  * with its own eligibility guards and call_stack_size / incoming-window
    249  * comparison. Leaves nd.callee / flags / tail_policy / inline_policy zeroed. */
    250 void native_direct_project_tail_call_desc(NativeDirectTarget* d,
    251                                           const CGCallDesc* call,
    252                                           NativeCallDesc* nd);
    253 
    254 #endif