abi_rv64.c (12718B)
1 /* RISC-V ABI dispatch (shared LP64* / ILP32* classifier). 2 * 3 * One descriptor-parameterized classifier serves both XLENs. The descriptor is 4 * derived per call from a->c->target: 5 * gpr_bytes = target.ptr_size (4 on rv32, 8 on rv64) 6 * aggregate_gpr_bytes = 2 * gpr_bytes (8 on rv32, 16 on rv64) 7 * flen = FP register width in bytes from target.float_abi: 8 * DOUBLE -> 8, SINGLE -> 4, SOFT -> 0, 9 * DEFAULT(unset) -> gpr_bytes (preserves the old 10 * rv64 LP64D behavior byte-for-byte). 11 * 12 * Covers the subset the cg test harness exercises plus the RISC-V psABI 13 * floating-point aggregate refinements: 14 * void -> IGNORE 15 * integer ≤ XLEN -> DIRECT, one INT part (a0..a7 for args; a0 for return) 16 * pointer -> DIRECT, one INT part 17 * float/double -> DIRECT, one FP part when FP-eligible (fa0..fa7 for args; 18 * fa0 for return); otherwise INT (and a 2*XLEN scalar 19 * becomes a GPR pair). 20 * small struct -> DIRECT: 21 * * homogeneous FP aggregate (1 or 2 same-kind FP fields, 22 * ignoring empty/zero-size fields and zero-length arrays) 23 * -> FP parts (fa pair) when FP-eligible; 24 * * one FP + one INT scalar (in either order, ≤ 2*XLEN) 25 * -> (fa, a) or (a, fa) pair; 26 * * otherwise INT parts up to 2*XLEN (passed in up to 2 27 * GPRs). large struct -> INDIRECT (sret for return; byval for args) 28 * 29 * Long double is IEEE-754 binary128 (quad) and __int128 are 16-byte scalars 30 * passed/returned in an aligned pair of integer registers (low-order half in 31 * the lower-numbered register). On rv64 this is the size==2*gpr_bytes pair 32 * path; there are no 128-bit FP registers. On rv32 a 64-bit scalar (i64, or a 33 * soft-float double) is the size==2*gpr_bytes even-GPR pair. 34 * 35 * Variadic args bypass these rules entirely and always go through the 36 * integer register file / stack (handled at the caller / callee sites). */ 37 38 #include <string.h> 39 40 #include "abi/abi_internal.h" 41 #include "cg/type.h" 42 #include "core/arena.h" 43 #include "core/core.h" 44 45 /* Per-call ABI descriptor derived from the target spec. */ 46 typedef struct RiscvAbiDesc { 47 u32 gpr_bytes; /* XLEN in bytes: 4 (rv32) or 8 (rv64) */ 48 u32 aggregate_gpr_bytes; /* 2 * gpr_bytes: the small-struct register cap */ 49 u32 flen; /* FP register width in bytes: 0, 4, or 8 */ 50 } RiscvAbiDesc; 51 52 static RiscvAbiDesc riscv_abi_desc(TargetABI* a) { 53 RiscvAbiDesc d; 54 d.gpr_bytes = a->c->target.ptr_size ? a->c->target.ptr_size : 8u; 55 d.aggregate_gpr_bytes = 2u * d.gpr_bytes; 56 switch (a->c->target.float_abi) { 57 case KIT_FLOAT_ABI_DOUBLE: 58 d.flen = 8u; 59 break; 60 case KIT_FLOAT_ABI_SINGLE: 61 d.flen = 4u; 62 break; 63 case KIT_FLOAT_ABI_SOFT: 64 d.flen = 0u; 65 break; 66 case KIT_FLOAT_ABI_DEFAULT: 67 default: 68 /* Unset: preserve the historical rv64 LP64D behavior, i.e. treat the FP 69 * register width as the GPR width (flen == 8 on rv64). */ 70 d.flen = d.gpr_bytes; 71 break; 72 } 73 return d; 74 } 75 76 /* An FP scalar of `size` bytes can be carried in an FP register iff the float 77 * ABI is hard and the value fits: float (4) needs flen>=4; double (8) needs 78 * flen>=8. With soft float (flen==0) nothing is FP-eligible. */ 79 static int riscv_fp_eligible(u32 flen, u32 size) { 80 return flen != 0u && size <= flen; 81 } 82 83 /* Walk a record collecting the leaf scalars in ABI order, skipping 84 * zero-size members (empty structs, zero-length arrays, zero-width 85 * bitfields). Returns the number of leaves collected, or > cap if the 86 * record has too many leaves to inspect (caller falls back to GPR pair). */ 87 typedef struct AbiLeaf { 88 u32 offset; /* byte offset within the outermost aggregate */ 89 u32 size; /* leaf scalar size in bytes */ 90 u8 scalar_kind; /* ABIScalarKind */ 91 } AbiLeaf; 92 93 static u32 riscv_collect_leaves(TargetABI* a, KitCgTypeId tid, u32 base_off, 94 AbiLeaf* out, u32 cap, u32 written) { 95 const CgType* t = cg_type_get(a->c, tid); 96 if (!t) return written + 1u; /* poison: treat as too-many */ 97 if (t->kind == KIT_CG_TYPE_RECORD) { 98 if (t->record.is_union) return cap + 1u; /* unions: bail */ 99 for (u32 i = 0; i < t->record.nfields; ++i) { 100 const CgTypeField* f = &t->record.fields[i]; 101 /* Skip bitfields explicitly: a bitfield with bit_width 0 is a layout 102 * barrier, a non-zero bitfield kills FP-aggregate classification per 103 * the psABI (treat the whole record as GPR-pair). */ 104 if (f->bit_width != 0) return cap + 1u; 105 u32 off = base_off + (u32)f->offset; 106 written = riscv_collect_leaves(a, f->type, off, out, cap, written); 107 if (written > cap) return written; 108 } 109 return written; 110 } 111 if (t->kind == KIT_CG_TYPE_ARRAY) { 112 if (t->array.count == 0) return written; /* zero-length array: skip */ 113 ABITypeInfo elem = abi_cg_type_info(a, t->array.elem); 114 if (elem.size == 0) return written; 115 for (u64 i = 0; i < t->array.count; ++i) { 116 u32 off = base_off + (u32)(i * elem.size); 117 written = riscv_collect_leaves(a, t->array.elem, off, out, cap, written); 118 if (written > cap) return written; 119 } 120 return written; 121 } 122 /* Scalar leaf (including pointer). */ 123 ABITypeInfo ti = abi_cg_type_info(a, tid); 124 if (ti.size == 0) return written; 125 if (written >= cap) return written + 1u; 126 out[written].offset = base_off; 127 out[written].size = ti.size; 128 out[written].scalar_kind = ti.scalar_kind; 129 return written + 1u; 130 } 131 132 static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { 133 RiscvAbiDesc d = riscv_abi_desc(a); 134 ABITypeInfo ti = abi_cg_type_info(a, t); 135 /* A scalar twice the GPR width that lives in the integer/long-double space 136 * (or a soft-float double) is carried as an aligned pair of GPRs. On rv64 137 * this is the 16-byte long double / __int128 pair; on rv32 it is the 8-byte 138 * i64 / soft-double pair. A double is only excluded from the pair here when 139 * it is FP-eligible (handled by the single-FP-part path below). */ 140 int fp_part = 141 (ti.scalar_kind == ABI_SC_FLOAT) && riscv_fp_eligible(d.flen, ti.size); 142 if (ti.size == 2u * d.gpr_bytes && !fp_part && 143 (ti.scalar_kind == ABI_SC_INT || ti.scalar_kind == ABI_SC_FLOAT)) { 144 ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, 2); 145 memset(parts, 0, sizeof(ABIArgPart) * 2); 146 parts[0].cls = ABI_CLASS_INT; 147 parts[0].loc = ABI_LOC_REG; 148 parts[0].size = d.gpr_bytes; 149 parts[0].align = d.gpr_bytes; 150 parts[0].src_offset = 0; 151 parts[1].cls = ABI_CLASS_INT; 152 parts[1].loc = ABI_LOC_REG; 153 parts[1].size = d.gpr_bytes; 154 parts[1].align = d.gpr_bytes; 155 parts[1].src_offset = d.gpr_bytes; 156 out->kind = ABI_ARG_DIRECT; 157 out->flags = ABI_AF_NONE; 158 out->parts = parts; 159 out->nparts = 2; 160 out->indirect_align = 0; 161 return; 162 } 163 abi_classify_scalar_reg_part(a, out, ti, fp_part); 164 } 165 166 static u32 riscv32_scalar_split_lane_size(TargetABI* a, KitCgTypeId t) { 167 RiscvAbiDesc d = riscv_abi_desc(a); 168 ABITypeInfo ti = abi_cg_type_info(a, t); 169 int fp_part; 170 if (d.gpr_bytes != 4u) return 0; 171 fp_part = 172 (ti.scalar_kind == ABI_SC_FLOAT) && riscv_fp_eligible(d.flen, ti.size); 173 if (ti.size == 2u * d.gpr_bytes && !fp_part && 174 (ti.scalar_kind == ABI_SC_INT || ti.scalar_kind == ABI_SC_FLOAT)) 175 return d.gpr_bytes; 176 return 0; 177 } 178 179 /* Try the psABI floating-point aggregate refinements. Returns 1 if `out` 180 * was populated, 0 to fall back to the generic GPR-pair packing. */ 181 static int riscv_classify_fp_aggregate(TargetABI* a, KitCgTypeId t, 182 const RiscvAbiDesc* d, ABIArgInfo* out) { 183 AbiLeaf leaves[2]; 184 u32 n = riscv_collect_leaves(a, t, 0, leaves, /*cap=*/2u, /*written=*/0u); 185 /* n > 2: bail; n == 0: caller already handled zero-size aggregates. */ 186 if (n == 0 || n > 2) return 0; 187 188 u32 nfp = 0; 189 for (u32 i = 0; i < n; ++i) { 190 if (leaves[i].scalar_kind == ABI_SC_FLOAT) { 191 /* An FP leaf only stays in the FP file when it is FP-eligible. With 192 * soft float, or a double wider than flen, the aggregate must fall 193 * back to the GPR-pair path. */ 194 if (!riscv_fp_eligible(d->flen, leaves[i].size)) return 0; 195 ++nfp; 196 } 197 /* ABI_SC_INT, ABI_SC_BOOL, ABI_SC_PTR all go to the GPR side. */ 198 } 199 if (nfp == 0) return 0; /* pure-INT goes through the GPR-pair path. */ 200 201 /* Build the part list in source order so that downstream codegen can 202 * align src_offset with the record's field layout. */ 203 ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, n); 204 memset(parts, 0, sizeof(ABIArgPart) * n); 205 for (u32 i = 0; i < n; ++i) { 206 parts[i].loc = ABI_LOC_REG; 207 parts[i].size = leaves[i].size; 208 parts[i].align = leaves[i].size ? leaves[i].size : 1u; 209 parts[i].src_offset = leaves[i].offset; 210 parts[i].cls = 211 (leaves[i].scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; 212 } 213 out->kind = ABI_ARG_DIRECT; 214 out->flags = ABI_AF_NONE; 215 out->parts = parts; 216 out->nparts = n; 217 out->indirect_align = 0; 218 return 1; 219 } 220 221 static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, 222 int is_return) { 223 RiscvAbiDesc d = riscv_abi_desc(a); 224 ABITypeInfo ti = abi_cg_type_info(a, t); 225 if (ti.size == 0) { 226 abi_classify_void(out); 227 return; 228 } 229 if (ti.size <= d.aggregate_gpr_bytes) { 230 /* Per psABI: try the FP-aware refinement first (HFA / fp+int pair). */ 231 if (riscv_classify_fp_aggregate(a, t, &d, out)) return; 232 u32 nparts = (ti.size + d.gpr_bytes - 1u) / d.gpr_bytes; 233 ABIArgPart* parts = arena_array(a->c->tu, ABIArgPart, nparts); 234 memset(parts, 0, sizeof(ABIArgPart) * nparts); 235 u32 off = 0; 236 for (u32 i = 0; i < nparts; ++i) { 237 u32 chunk = (ti.size - off > d.gpr_bytes) ? d.gpr_bytes : (ti.size - off); 238 parts[i].cls = ABI_CLASS_INT; 239 parts[i].loc = ABI_LOC_REG; 240 parts[i].size = chunk; 241 parts[i].align = d.gpr_bytes; 242 parts[i].src_offset = off; 243 off += chunk; 244 } 245 out->kind = ABI_ARG_DIRECT; 246 out->flags = ABI_AF_NONE; 247 out->parts = parts; 248 out->nparts = nparts; 249 out->indirect_align = 0; 250 } else { 251 out->kind = ABI_ARG_INDIRECT; 252 out->flags = is_return ? ABI_AF_SRET : ABI_AF_BYVAL; 253 out->indirect_align = ti.align ? ti.align : d.gpr_bytes; 254 out->parts = NULL; 255 out->nparts = 0; 256 } 257 (void)is_return; 258 } 259 260 static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, 261 int is_return) { 262 const CgType* ty = cg_type_get(a->c, t); 263 if (!ty || ty->kind == KIT_CG_TYPE_VOID) { 264 abi_classify_void(out); 265 return; 266 } 267 switch (ty->kind) { 268 case KIT_CG_TYPE_RECORD: 269 classify_aggregate(a, t, out, is_return); 270 return; 271 default: 272 classify_scalar(a, t, out); 273 return; 274 } 275 } 276 277 static ABIFuncInfo* riscv_compute_func_info(TargetABI* a, KitCgTypeId fn) { 278 /* RISC-V passes the sret pointer in a0 (the first integer arg register), 279 * consuming that slot, so sret_consumes_int_arg follows has_sret. */ 280 return abi_compute_func_info_generic(a, fn, classify_one, 281 /*sret_consumes_int_arg=*/1); 282 } 283 284 const ABIVtable rv64_vtable = { 285 .compute_func_info = riscv_compute_func_info, 286 .va_list_info = {8, 8, ABI_SC_PTR, 0, 0, 0}, 287 /* LP64D va_list is a plain pointer, but the variadic register-save area is 288 * the 8 integer arg registers (a0..a7) spilled contiguously = 64 bytes; FP 289 * varargs are passed in GPRs, so there is no separate FP save area. The 290 * gp_reg_count/gp_slot_size fields let native_frame_va_save_bytes size that 291 * area from the ABI rather than a backend constant. */ 292 .va_list_layout = {.kind = ABI_VA_LIST_POINTER, 293 .gp_reg_count = 8, 294 .fp_reg_count = 0, 295 .gp_slot_size = 8, 296 .fp_slot_size = 0}, 297 }; 298 299 const ABIVtable rv32_vtable = { 300 .compute_func_info = riscv_compute_func_info, 301 .scalar_split_lane_size = riscv32_scalar_split_lane_size, 302 .va_list_info = {4, 4, ABI_SC_PTR, 0, 0, 0}, 303 /* ILP32* va_list is a plain 4-byte pointer; the variadic register-save 304 * area is the 8 integer arg registers (a0..a7) spilled contiguously = 305 * 32 bytes. FP varargs are passed in GPRs, so there is no FP save area. */ 306 .va_list_layout = {.kind = ABI_VA_LIST_POINTER, 307 .gp_reg_count = 8, 308 .fp_reg_count = 0, 309 .gp_slot_size = 4, 310 .fp_slot_size = 0}, 311 };