native_frame.h (7377B)
1 /* see also: cg/native_argmove.h (shared parallel-copy register shuffle) */ 2 #ifndef KIT_CG_NATIVE_FRAME_H 3 #define KIT_CG_NATIVE_FRAME_H 4 5 /* Shared native-frame bookkeeping for the aa64/rv64/x64 NativeTarget backends. 6 * 7 * Every native backend lays out a stack frame the same way at the bookkeeping 8 * level: a table of frame slots (locals, spills, sret/variadic homes, and — on 9 * aa64 — callee-save homes) accumulated below the frame anchor, a running 10 * max-outgoing-arg size, and a set of callee-saved registers the allocator 11 * touched. The *arithmetic* of assigning each slot a cumulative offset, the 12 * frame-final gate that forbids growing the frame after the prologue is 13 * emitted, and the derivation of the used-callee-save set from the optimizer's 14 * per-class masks are identical across all three. They live here. 15 * 16 * What stays in each backend is everything ISA/ABI-specific: the coordinate 17 * transform from a slot's cumulative `off` to an anchor-relative displacement 18 * (fp/s0/rbp-relative, and aa64's top- vs bottom-record choice), the 19 * prologue/epilogue instruction encoding, callee-save placement (aa64 reserves 20 * slots here; rv64/x64 compute offsets below the locals), the slim-prologue 21 * variants, deferred-patch application, and the variadic register-save stores. 22 * 23 * The frame-relevant ABI facts are consulted through this module too: 24 * native_frame_va_save_bytes derives the vararg register-save-area size from 25 * the target ABI's va_list layout, so the per-arch magic numbers (rv64 64, x64 26 * 176, aa64 64+128) all come from one ABI-driven query. */ 27 28 #include "abi/abi.h" 29 #include "arch/native_target.h" 30 #include "core/core.h" 31 32 /* One allocated frame slot. `off` is the cumulative byte offset below the frame 33 * anchor (positive); the backend converts it to an anchor-relative 34 * displacement. Layout matches the per-arch slot structs it replaces. */ 35 typedef struct NativeFrameSlotEntry { 36 u32 off; 37 u32 size; 38 u32 align; 39 NativeFrameSlot free_next; /* next free slot in its size/align bin, when in_free */ 40 u8 kind; /* NativeFrameSlotKind */ 41 u8 in_free; /* on the free list (guards double-release) */ 42 u8 pad[2]; 43 } NativeFrameSlotEntry; 44 45 /* A callee-saved register the function body used and must preserve. `slot` is 46 * the reserved save slot when the backend asked native_frame to allocate one 47 * (aa64), else NATIVE_FRAME_SLOT_NONE (rv64/x64 compute the save offset). */ 48 typedef struct NativeFrameCalleeSave { 49 NativeFrameSlot slot; 50 KitCgTypeId type; 51 u8 cls; /* NativeAllocClass */ 52 Reg reg; 53 } NativeFrameCalleeSave; 54 55 /* Per-class save-slot shape, used only when native_frame_set_callee_saves is 56 * asked to allocate save slots (alloc_slots != 0). Indexed by NativeAllocClass. 57 */ 58 typedef struct NativeFrameSaveSpec { 59 u32 size; 60 u32 align; 61 KitCgTypeId type; 62 } NativeFrameSaveSpec; 63 64 /* x19..x28 (10) + v8..v15 (8) on aa64; s0..s11 + fs0..fs11 on rv64; the Win64 65 * GPR + XMM callee-saved set on x64. 48 covers every target with headroom. */ 66 #define NATIVE_FRAME_MAX_CALLEE_SAVES 48u 67 68 typedef struct NativeFrame { 69 Compiler* c; 70 71 NativeFrameSlotEntry* slots; /* arena-grown; 1-indexed handles */ 72 u32 nslots; 73 u32 slots_cap; 74 u32 cum_off; /* running below-anchor slot bytes (sum of reservations) */ 75 76 u32 max_outgoing; /* max outgoing-arg bytes across all calls */ 77 78 NativeFrameCalleeSave callee_saves[NATIVE_FRAME_MAX_CALLEE_SAVES]; 79 u32 ncallee_saves; 80 81 /* Free list of recyclable transient temp slots, bucketed by exact 82 * (size,align). A NATIVE_FRAME_SLOT_TRANSIENT allocation pops a same-bucket 83 * slot before bumping cum_off; native_frame_release_slot pushes a dead temp's 84 * slot back. Buckets are LIFO stacks threaded through 85 * NativeFrameSlotEntry.free_next; the per-function distinct (size,align) set is 86 * tiny (scalar temps: 4/4, 8/8, 16/16, ...), so a linear bucket scan is O(1) in 87 * practice. Overflow past NATIVE_FRAME_FREE_BINS simply doesn't recycle. */ 88 struct { 89 u32 size; 90 u32 align; 91 NativeFrameSlot head; 92 } free_bins[16]; 93 u32 nfree_bins; 94 95 u8 frame_final; /* set once the prologue is emitted; bars further slots */ 96 u8 known_frame; /* optimizer (known-frame) path vs single-pass path */ 97 u8 has_alloca; /* body contains a dynamic alloca */ 98 u8 pad; 99 } NativeFrame; 100 101 /* One-time setup (call from <arch>_native_target_new). */ 102 void native_frame_init(NativeFrame* f, Compiler* c); 103 104 /* Per-function reset: clears the slot table, outgoing size, callee-save set and 105 * the frame flags. Keeps the slots buffer for reuse across functions. */ 106 void native_frame_reset(NativeFrame* f); 107 108 /* Allocate a frame slot, advancing the cumulative offset by `size` aligned to 109 * `align` (defaults: size 8, align 1). Returns a 1-indexed handle. Panics if 110 * the frame is already final. Identical arithmetic to the per-arch allocators. 111 */ 112 NativeFrameSlot native_frame_slot_alloc(NativeFrame* f, 113 const NativeFrameSlotDesc* d); 114 115 /* Return a previously-allocated transient slot to the free list so a later 116 * NATIVE_FRAME_SLOT_TRANSIENT allocation of the same (size,align) reuses it. 117 * Idempotent (a slot already on the free list is ignored). The slot's home 118 * offset is preserved; only the next-temp ownership is recycled. */ 119 void native_frame_release_slot(NativeFrame* f, NativeFrameSlot slot); 120 121 /* Resolve a 1-indexed handle to its entry. Panics on an out-of-range handle. */ 122 NativeFrameSlotEntry* native_frame_slot_at(NativeFrame* f, 123 NativeFrameSlot slot); 124 125 /* Grow max_outgoing to at least `bytes`. */ 126 void native_frame_note_outgoing(NativeFrame* f, u32 bytes); 127 128 /* Mark the frame final (no further native_frame_slot_alloc allowed). */ 129 void native_frame_set_final(NativeFrame* f); 130 131 static inline u32 native_frame_slot_bytes(const NativeFrame* f) { 132 return f->cum_off; 133 } 134 135 /* Derive the used-callee-save set from the optimizer's per-class masks (one 136 * bitmask of hard registers per NativeAllocClass, already restricted to the 137 * callee-saved set by the caller). Appends one NativeFrameCalleeSave per set 138 * bit. When alloc_slots is non-zero, also reserves an 8/size-byte save slot per 139 * register (aa64); otherwise slot is left NONE and the backend computes the 140 * save offset itself (rv64/x64). `spec_by_class` (indexed by NativeAllocClass) 141 * gives the save-slot shape per class and may be NULL when alloc_slots == 0. 142 * Resets the set first; panics if it would exceed 143 * NATIVE_FRAME_MAX_CALLEE_SAVES. */ 144 void native_frame_set_callee_saves(NativeFrame* f, const u32* used_by_class, 145 u32 nclasses, 146 const NativeFrameSaveSpec* spec_by_class, 147 u32 nspec, int alloc_slots); 148 149 /* Fill `out` with the registers in the callee-save set belonging to `cls`, in 150 * the order they were derived. Returns the count written (capped at `cap`). */ 151 u32 native_frame_collect_saves(const NativeFrame* f, NativeAllocClass cls, 152 Reg* out, u32 cap); 153 154 /* Bytes of the vararg register-save area for `abi`, derived from its va_list 155 * layout: gp_reg_count*gp_slot_size + fp_reg_count*fp_slot_size. 0 for ABIs 156 * with no register-save area (e.g. Win64). The one ABI-consulting frame query. 157 */ 158 u32 native_frame_va_save_bytes(TargetABI* abi); 159 160 #endif