kit

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

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 };