native_asm.h (9421B)
1 #ifndef KIT_CG_NATIVE_ASM_H 2 #define KIT_CG_NATIVE_ASM_H 3 4 /* Shared, target-neutral native helpers that touch only the base NativeTarget 5 * (its Compiler and MCEmitter): the file-scope-asm assembler entry and the 6 * eh-frame finalize step. Both were byte-identical across the aa64/rv64/x64 7 * backends, so they live here as the single source of truth; each backend's 8 * vtable points its file_scope_asm / finalize slots straight at these. */ 9 10 #include "arch/native_target.h" 11 12 /* Top-level __asm__("...") — assemble through the generic .s parser, which 13 * dispatches instruction lines to the active arch asm driver and handles 14 * directives (.data/.word/.globl/.text/...) itself. */ 15 void native_file_scope_asm(NativeTarget* t, const char* src, size_t len); 16 17 /* Emit the function's eh-frame (CFI) once code emission is complete. */ 18 void native_finalize(NativeTarget* t); 19 20 /* ---- Inline-asm constraint-string helpers ---- 21 * 22 * Pure, target-neutral parsing of a GCC-style operand constraint string. 23 * Definitions wrap the shared public inline helpers in kit/asm_constraints.h so 24 * frontend, CG, native, and test code all use the same grammar. 25 */ 26 27 /* Skip the leading modifier flags ('=', '+', '&', and the '=&' early-clobber 28 * pair) and return a pointer to the constraint body (e.g. "r", "w", "m", "i", 29 * or a matching-constraint digit run). Returns "" for a NULL string. */ 30 const char* native_asm_constraint_body(const char* s); 31 32 /* Whether the constraint marks an early-clobber output ('=&' or a leading 33 * '&'). */ 34 int native_asm_constraint_early(const char* s); 35 36 /* For a matching constraint (a leading decimal digit run naming an output 37 * operand index), return that index; -1 when the constraint is not a matching 38 * constraint. */ 39 int native_asm_match_index(const char* s); 40 41 /* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into 42 * this target's live per-class caller/callee-saved register masks. */ 43 void native_asm_abi_clobber_masks(NativeTarget* t, u32 abi_sets, u32* int_mask, 44 u32* fp_mask); 45 46 typedef void (*NativeAsmClobberMasksFn)(Compiler*, SrcLoc, const Sym*, u32, 47 u32*, u32*); 48 typedef int (*NativeAsmCalleeSavedRegFn)(NativeTarget*, NativeAllocClass, Reg); 49 50 /* Merge allocator-used and inline-asm-clobbered callee-saved registers for a 51 * known frame. Target parsing remains behind clobber_masks; is_callee_saved 52 * defines the live ABI's preservable register set and excludes structural 53 * registers such as the frame pointer. */ 54 u32 native_asm_known_callee_saves( 55 NativeTarget* t, SrcLoc loc, const NativeKnownFrameDesc* frame, u32* out, 56 u32 cap, NativeAsmClobberMasksFn clobber_masks, 57 NativeAsmCalleeSavedRegFn is_callee_saved); 58 59 typedef enum NativeAsmRegPinStatus { 60 NATIVE_ASM_REG_PIN_ABSENT = 0, 61 NATIVE_ASM_REG_PIN_OK = 1, 62 NATIVE_ASM_REG_PIN_UNKNOWN = -1, 63 NATIVE_ASM_REG_PIN_FORBIDDEN = -2, 64 NATIVE_ASM_REG_PIN_BAD_CONSTRAINT = -3, 65 NATIVE_ASM_REG_PIN_CLASS_MISMATCH = -4, 66 NATIVE_ASM_REG_PIN_FIXED_MISMATCH = -5, 67 } NativeAsmRegPinStatus; 68 69 typedef struct NativeAsmRegPin { 70 Reg reg; 71 NativeAllocClass cls; 72 } NativeAsmRegPin; 73 74 typedef struct NativeAsmConstraintInfo { 75 NativeAllocClass cls; 76 Reg fixed_reg; /* REG_NONE when the constraint only names a register class. */ 77 u32 allowed_mask; /* 0 means any valid register in cls. */ 78 } NativeAsmConstraintInfo; 79 80 int native_asm_constraint_reg_info(NativeTarget* t, const char* constraint, 81 NativeAsmConstraintInfo* out); 82 int native_asm_constraint_is_reg(NativeTarget* t, const char* constraint); 83 84 /* Resolve and validate an inline-asm operand's explicit hard-register pin 85 * (AsmConstraint.reg, from a GNU local register variable). Distinguishes no pin 86 * from invalid pins, and verifies that the operand uses a register constraint 87 * of the matching target class. */ 88 NativeAsmRegPinStatus native_asm_resolve_pin(NativeTarget* t, Sym reg, 89 const char* constraint, 90 NativeAsmRegPin* out); 91 const char* native_asm_pin_status_message(NativeAsmRegPinStatus st); 92 int native_asm_constraint_reg_class(const char* constraint, 93 NativeAllocClass* cls_out); 94 95 /* ---- Direct (-O0) inline-asm operand binding ---- 96 * 97 * The direct CgTarget path lowers an inline-asm block by self-allocating a 98 * register/memory location for every operand, saving/restoring the 99 * callee-saved registers the asm clobbers, pre-loading INOUT outputs and all 100 * inputs into their bound registers, running the template, then storing the 101 * register outputs back. That orchestration is arch-neutral: it was a 102 * ~170-line near-identical copy in each of the aa64/x64/rv64 backends. It lives 103 * here as the single source of truth, parameterized over the genuinely 104 * arch-specific bits via NativeAsmDirectHooks; each backend's 105 * *_direct_asm_block is a thin wrapper that supplies the hooks. 106 * 107 * The shared driver depends on NativeDirectTarget only through pointers, so the 108 * full struct is needed at the call site (the .c includes it), not here. */ 109 typedef struct NativeDirectTarget NativeDirectTarget; 110 111 typedef struct NativeAsmDirectHooks { 112 /* Arch inline-operand discriminators: Operand.kind for a bound register 113 * pseudo-operand, and the Operand.pad[0] value marking the FP class. */ 114 u8 opk_reg; 115 u8 opcls_fp; 116 u8 pad[2]; 117 118 /* Panic with the arch's inline-asm message prefix. Does not return. */ 119 void (*panic)(NativeDirectTarget* d, const char* msg); 120 /* Build the arch's bound register / memory pseudo-operand. */ 121 void (*bound_reg)(Operand* out, KitCgTypeId type, NativeAllocClass cls, 122 Reg reg); 123 void (*bound_mem)(Operand* out, KitCgTypeId type, Reg base); 124 /* Parse the asm clobber list into per-class register masks (arch register 125 * naming). */ 126 void (*clobber_masks)(Compiler* c, SrcLoc loc, const Sym* clobbers, u32 nclob, 127 u32* int_mask, u32* fp_mask); 128 /* Spill the callee-saved registers the asm clobbers or the direct binder 129 * uses for operand/address staging, returning an opaque, block-lived save 130 * record plus its element count. restore_one undoes element idx; the driver 131 * replays them in reverse. */ 132 void* (*save_callee_clobbers)(NativeDirectTarget* d, u32 int_mask, 133 u32 fp_mask, u32* nsaved_out); 134 void (*restore_one)(NativeDirectTarget* d, void* saved, u32 idx); 135 /* Load a semantic operand's value / address into a register, or store a 136 * register back to a semantic operand. */ 137 void (*load_operand_to_reg)(NativeDirectTarget* d, Operand op, NativeLoc dst); 138 void (*load_address_to_reg)(NativeDirectTarget* d, Operand op, NativeLoc dst); 139 void (*store_reg_to_operand)(NativeDirectTarget* d, Operand op, 140 NativeLoc src); 141 /* Open the arch asm assembler, bind the resolved operands, run the template, 142 * and close — emitting the asm body into the target's MCEmitter. */ 143 void (*run_template)(NativeDirectTarget* d, const char* tmpl, 144 const AsmConstraint* outs, u32 nout, Operand* bound_outs, 145 const AsmConstraint* ins, u32 nin, Operand* bound_ins, 146 const Sym* clobbers, u32 nclob); 147 } NativeAsmDirectHooks; 148 149 /* Drive the arch-neutral direct inline-asm orchestration: out-loop / in-loop / 150 * save / load / run / store / restore. Behavior is identical to the former 151 * per-arch *_direct_asm_block bodies; the hooks supply every arch-specific 152 * step. */ 153 void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl, 154 const AsmConstraint* outs, u32 nout, 155 Operand* out_ops, const AsmConstraint* ins, 156 u32 nin, const Operand* in_ops, 157 const Sym* clobbers, u32 nclob, 158 u32 clobber_abi_sets, 159 const NativeAsmDirectHooks* h); 160 161 /* ---- Optimized NativeTarget inline-asm binding ---- 162 * 163 * The optimizer's native emitter owns register placement, staging, and output 164 * writeback. This shared driver is deliberately narrower: it validates and 165 * binds the concrete register/immediate locations supplied by the emitter, 166 * materializes memory-operand bases through the backend, and runs the arch 167 * assembler. */ 168 typedef struct NativeAsmNativeHooks { 169 void (*panic)(NativeTarget* t, SrcLoc loc, const char* msg); 170 void (*bound_reg)(Operand* out, KitCgTypeId type, NativeAllocClass cls, 171 Reg reg); 172 void (*bound_mem)(Operand* out, KitCgTypeId type, Reg base); 173 Reg (*mem_base)(NativeTarget* t, SrcLoc loc, NativeLoc src, u32* ntmp); 174 void (*run_template)(NativeTarget* t, const char* tmpl, 175 const AsmConstraint* outs, u32 nout, Operand* bound_outs, 176 const AsmConstraint* ins, u32 nin, Operand* bound_ins, 177 const Sym* clobbers, u32 nclob); 178 } NativeAsmNativeHooks; 179 180 void native_asm_bind_native_operands( 181 NativeTarget* t, SrcLoc loc, const char* tmpl, const AsmConstraint* outs, 182 u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, u32 nin, 183 const NativeLoc* in_locs, const Sym* clobbers, u32 nclob, 184 const NativeAsmNativeHooks* h); 185 186 #endif