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