kit

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

arch.c (23984B)


      1 #include "arch/arch.h"
      2 
      3 #include <string.h>
      4 
      5 #include "arch/riscv/asm.h"
      6 #include "arch/riscv/disasm.h"
      7 #include "arch/riscv/regs.h"
      8 #include "arch/riscv/rv64.h"
      9 #include "arch/riscv/variant.h"
     10 #include "cg/native_direct_target.h"
     11 #include "core/bytes.h"
     12 #include "core/strbuf.h"
     13 #include "link/link_arch.h"
     14 #include "obj/obj.h"
     15 
     16 extern const LinkArchDesc link_arch_rv64;
     17 extern const ArchDbgOps rv64_dbg_ops;
     18 extern const ArchEmuOps rv64_emu_ops;
     19 extern const ArchDwarfOps rv64_dwarf_ops;
     20 extern const ArchAsmOps rv64_asm_ops;
     21 extern const LinkArchDesc link_arch_rv32;
     22 extern const ArchDbgOps rv32_dbg_ops;
     23 
     24 static int rv64_register_at_public(uint32_t idx, KitArchReg* out) {
     25   return arch_register_at_public(idx, out, rv64_register_iter_get);
     26 }
     27 
     28 static SrcLoc rv64_no_loc(void) {
     29   SrcLoc l = {0, 0, 0};
     30   return l;
     31 }
     32 
     33 static int rv64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) {
     34   const Section* s;
     35   u8 cur[4];
     36   u32 word;
     37   u32 b;
     38 
     39   (void)c;
     40   if (!fx) return 1;
     41   s = obj_section_get(fx->obj, fx->sec_id);
     42   if (!s) return 0;
     43 
     44   /* INTRA_AUIPC_ADDI is a width=8 pair; other kinds patch a single 4-byte
     45    * instruction. Read the first word only for the 4-byte cases. */
     46   if (fx->kind != R_RV_INTRA_AUIPC_ADDI) {
     47     if (fx->width != 4) return 1;
     48     buf_read(&s->bytes, fx->offset, cur, 4);
     49     word = rd_u32_le(cur);
     50   } else {
     51     buf_read(&s->bytes, fx->offset, cur, 4);
     52     word = rd_u32_le(cur);
     53   }
     54   b = (u32)fx->disp;
     55 
     56   switch (fx->kind) {
     57     case R_RV_BRANCH:
     58       /* B-type reaches ±4 KiB. Conditional branches are emitted over a jal
     59        * (see rv_cmp_branch) so this only carries small fixed displacements;
     60        * a violation is a backend bug, not silently-truncated code. */
     61       if ((i64)fx->disp < -(i64)(1 << 12) || (i64)fx->disp >= (i64)(1 << 12))
     62         compiler_panic(c, rv64_no_loc(), "rv64: BRANCH out of range (±4KiB)");
     63       word &= 0x01fff07fu;
     64       word |= ((b >> 12) & 1u) << 31;
     65       word |= ((b >> 5) & 0x3fu) << 25;
     66       word |= ((b >> 1) & 0xfu) << 8;
     67       word |= ((b >> 11) & 1u) << 7;
     68       break;
     69     case R_RV_JAL:
     70       /* J-type reaches ±1 MiB — ample for intra-function jumps (including the
     71        * long leg of a conditional branch). Fail loudly rather than wrap. */
     72       if ((i64)fx->disp < -(i64)(1 << 20) || (i64)fx->disp >= (i64)(1 << 20))
     73         compiler_panic(c, rv64_no_loc(), "rv64: JAL out of range (±1MiB)");
     74       word &= 0x00000fffu;
     75       word |= ((b >> 20) & 1u) << 31;
     76       word |= ((b >> 1) & 0x3ffu) << 21;
     77       word |= ((b >> 11) & 1u) << 20;
     78       word |= ((b >> 12) & 0xffu) << 12;
     79       break;
     80     case R_RV_INTRA_AUIPC_ADDI: {
     81       /* width=8: patch both the AUIPC at fx->offset and the ADDI at
     82        * fx->offset+4. disp is the byte offset from the AUIPC PC to the
     83        * target label. */
     84       u8 cur2[4];
     85       u32 word2;
     86       i32 disp = (i32)fx->disp;
     87       /* hi20 is the top 20 bits of (disp + 0x800) so the sign-extended
     88        * 12-bit lo12 cancels out. */
     89       u32 hi20 = (u32)((disp + 0x800) >> 12) & 0xfffffu;
     90       u32 lo12 = (u32)disp & 0xfffu;
     91       if (fx->width != 8) return 1;
     92       /* AUIPC: keep rd (bits 11:7) and opcode (bits 6:0); patch imm[31:12]. */
     93       word = (word & 0x00000fffu) | (hi20 << 12);
     94       wr_u32_le(cur, word);
     95       obj_patch(fx->obj, fx->sec_id, fx->offset, cur, 4);
     96       buf_read(&s->bytes, fx->offset + 4, cur2, 4);
     97       word2 = rd_u32_le(cur2);
     98       /* ADDI: keep rs1/funct3/rd/opcode (bits 19:0); patch imm[11:0]. */
     99       word2 = (word2 & 0x000fffffu) | (lo12 << 20);
    100       wr_u32_le(cur2, word2);
    101       obj_patch(fx->obj, fx->sec_id, fx->offset + 4, cur2, 4);
    102       return 0;
    103     }
    104     default:
    105       return 1;
    106   }
    107 
    108   wr_u32_le(cur, word);
    109   obj_patch(fx->obj, fx->sec_id, fx->offset, cur, 4);
    110   return 0;
    111 }
    112 
    113 /* Mirrors `clang --target=riscv64-linux-gnu -E -dM` for the in-scope
    114  * RV64GC profile: I/M/F/D/A/C + Zicsr-minimal. Macros that depend on
    115  * extensions outside scope (V, B, Zve*, Zfh, …) are deliberately
    116  * absent. The float-ABI-dependent macros (__riscv_float_abi_*, __riscv_flen,
    117  * __riscv_fdiv, __riscv_fsqrt) are emitted per resolved float_abi by
    118  * rv64_float_predefines, not baked here — see that hook. Default ABI is lp64d. */
    119 static const KitPredefinedMacro rv64_predefined_macros[] = {
    120     {KIT_SLICE_LIT("__riscv"), KIT_SLICE_LIT("1")},
    121     {KIT_SLICE_LIT("__riscv_xlen"), KIT_SLICE_LIT("64")},
    122     {KIT_SLICE_LIT("__riscv_atomic"), KIT_SLICE_LIT("1")},
    123     {KIT_SLICE_LIT("__riscv_mul"), KIT_SLICE_LIT("1")},
    124     {KIT_SLICE_LIT("__riscv_div"), KIT_SLICE_LIT("1")},
    125     {KIT_SLICE_LIT("__riscv_muldiv"), KIT_SLICE_LIT("1")},
    126     {KIT_SLICE_LIT("__riscv_compressed"), KIT_SLICE_LIT("1")},
    127     {KIT_SLICE_LIT("__riscv_zicsr"), KIT_SLICE_LIT("1")},
    128     {KIT_SLICE_LIT("__riscv_zifencei"), KIT_SLICE_LIT("1")},
    129     {KIT_SLICE_LIT("__riscv_arch_test"), KIT_SLICE_LIT("1")},
    130     {KIT_SLICE_LIT("__LP64__"), KIT_SLICE_LIT("1")},
    131     {KIT_SLICE_LIT("_LP64"), KIT_SLICE_LIT("1")},
    132     {KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__"), KIT_SLICE_LIT("1234")},
    133     {KIT_SLICE_LIT("__ORDER_BIG_ENDIAN__"), KIT_SLICE_LIT("4321")},
    134     {KIT_SLICE_LIT("__BYTE_ORDER__"), KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__")},
    135     {KIT_SLICE_LIT("__LITTLE_ENDIAN__"), KIT_SLICE_LIT("1")},
    136 };
    137 
    138 /* Mirrors `clang --target=riscv32-linux-gnu -march=rv32imac_zicsr_zifencei
    139  * -mabi=ilp32 -E -dM` for the DEFAULT ilp32 SOFT-float MCU profile:
    140  * I/M/A/C + Zicsr-minimal, no hardware float. __ILP32__/_ILP32 replace
    141  * __LP64__/_LP64.
    142  *
    143  * The float-ABI-dependent macros (__riscv_float_abi_*, __riscv_flen,
    144  * __riscv_fdiv, __riscv_fsqrt) are NOT baked here: a static table can encode
    145  * only one float profile, but rv32 supports soft (ilp32, the default), single
    146  * (ilp32f) and a soft-double layout, and the preprocessor must agree with the
    147  * codegen ABI (rt/lib/coro keys on __riscv_flen). rv64_float_predefines emits
    148  * them per resolved c->target.float_abi instead — shared by rv32 and rv64. */
    149 static const KitPredefinedMacro rv32_predefined_macros[] = {
    150     {KIT_SLICE_LIT("__riscv"), KIT_SLICE_LIT("1")},
    151     {KIT_SLICE_LIT("__riscv_xlen"), KIT_SLICE_LIT("32")},
    152     {KIT_SLICE_LIT("__riscv_atomic"), KIT_SLICE_LIT("1")},
    153     {KIT_SLICE_LIT("__riscv_mul"), KIT_SLICE_LIT("1")},
    154     {KIT_SLICE_LIT("__riscv_div"), KIT_SLICE_LIT("1")},
    155     {KIT_SLICE_LIT("__riscv_muldiv"), KIT_SLICE_LIT("1")},
    156     {KIT_SLICE_LIT("__riscv_compressed"), KIT_SLICE_LIT("1")},
    157     {KIT_SLICE_LIT("__riscv_zicsr"), KIT_SLICE_LIT("1")},
    158     {KIT_SLICE_LIT("__riscv_zifencei"), KIT_SLICE_LIT("1")},
    159     {KIT_SLICE_LIT("__riscv_arch_test"), KIT_SLICE_LIT("1")},
    160     {KIT_SLICE_LIT("__ILP32__"), KIT_SLICE_LIT("1")},
    161     {KIT_SLICE_LIT("_ILP32"), KIT_SLICE_LIT("1")},
    162     {KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__"), KIT_SLICE_LIT("1234")},
    163     {KIT_SLICE_LIT("__ORDER_BIG_ENDIAN__"), KIT_SLICE_LIT("4321")},
    164     {KIT_SLICE_LIT("__BYTE_ORDER__"), KIT_SLICE_LIT("__ORDER_LITTLE_ENDIAN__")},
    165     {KIT_SLICE_LIT("__LITTLE_ENDIAN__"), KIT_SLICE_LIT("1")},
    166 };
    167 
    168 enum {
    169   RV64_FEAT_I = 0,
    170   RV64_FEAT_M,
    171   RV64_FEAT_A,
    172   RV64_FEAT_F,
    173   RV64_FEAT_D,
    174   RV64_FEAT_C,
    175   RV64_FEAT_ZICSR,
    176   RV64_FEAT_ZIFENCEI,
    177 };
    178 
    179 static const ArchTargetFeature rv64_target_features[] = {
    180     {"i"}, {"m"}, {"a"}, {"f"}, {"d"}, {"c"}, {"zicsr"}, {"zifencei"},
    181 };
    182 
    183 static void rv64_feature_set(u64* words, u32 nwords, u32 idx) {
    184   if (!words || idx / 64u >= nwords) return;
    185   words[idx / 64u] |= 1ull << (idx % 64u);
    186 }
    187 
    188 static void rv64_feature_clear(u64* words, u32 nwords, u32 idx) {
    189   if (!words || idx / 64u >= nwords) return;
    190   words[idx / 64u] &= ~(1ull << (idx % 64u));
    191 }
    192 
    193 static void rv64_feature_disable_all(u64* words, u32 nwords) {
    194   rv64_feature_clear(words, nwords, RV64_FEAT_I);
    195   rv64_feature_clear(words, nwords, RV64_FEAT_M);
    196   rv64_feature_clear(words, nwords, RV64_FEAT_A);
    197   rv64_feature_clear(words, nwords, RV64_FEAT_F);
    198   rv64_feature_clear(words, nwords, RV64_FEAT_D);
    199   rv64_feature_clear(words, nwords, RV64_FEAT_C);
    200   rv64_feature_clear(words, nwords, RV64_FEAT_ZICSR);
    201   rv64_feature_clear(words, nwords, RV64_FEAT_ZIFENCEI);
    202 }
    203 
    204 static void rv64_feature_enable_g(u64* words, u32 nwords) {
    205   rv64_feature_set(words, nwords, RV64_FEAT_I);
    206   rv64_feature_set(words, nwords, RV64_FEAT_M);
    207   rv64_feature_set(words, nwords, RV64_FEAT_A);
    208   rv64_feature_set(words, nwords, RV64_FEAT_F);
    209   rv64_feature_set(words, nwords, RV64_FEAT_D);
    210   rv64_feature_set(words, nwords, RV64_FEAT_ZICSR);
    211   rv64_feature_set(words, nwords, RV64_FEAT_ZIFENCEI);
    212 }
    213 
    214 static int rv64_has_prefix(const char* p, const char* end, const char* lit) {
    215   size_t n = strlen(lit);
    216   return (size_t)(end - p) >= n && memcmp(p, lit, n) == 0;
    217 }
    218 
    219 static void rv64_skip_version(const char** pp, const char* end) {
    220   const char* p = *pp;
    221   while (p < end && ((*p >= '0' && *p <= '9') || *p == 'p')) ++p;
    222   *pp = p;
    223 }
    224 
    225 static KitStatus rv64_target_feature_apply_isa(const Target* target,
    226                                                KitSlice isa, u64* words,
    227                                                u32 nwords) {
    228   const char* p;
    229   const char* end;
    230   const RiscvVariant* v = riscv_variant_for_kind(target->arch);
    231   if (isa.len < 5 || memcmp(isa.s, v->isa_prefix, 4) != 0)
    232     return KIT_UNSUPPORTED;
    233   p = isa.s + 4;
    234   end = isa.s + isa.len;
    235   rv64_feature_disable_all(words, nwords);
    236   while (p < end) {
    237     if (*p == '_') {
    238       ++p;
    239       continue;
    240     }
    241     switch (*p) {
    242       case 'i':
    243         rv64_feature_set(words, nwords, RV64_FEAT_I);
    244         ++p;
    245         rv64_skip_version(&p, end);
    246         continue;
    247       case 'm':
    248         rv64_feature_set(words, nwords, RV64_FEAT_M);
    249         ++p;
    250         rv64_skip_version(&p, end);
    251         continue;
    252       case 'a':
    253         rv64_feature_set(words, nwords, RV64_FEAT_A);
    254         ++p;
    255         rv64_skip_version(&p, end);
    256         continue;
    257       case 'f':
    258         rv64_feature_set(words, nwords, RV64_FEAT_F);
    259         ++p;
    260         rv64_skip_version(&p, end);
    261         continue;
    262       case 'd':
    263         rv64_feature_set(words, nwords, RV64_FEAT_D);
    264         ++p;
    265         rv64_skip_version(&p, end);
    266         continue;
    267       case 'c':
    268         rv64_feature_set(words, nwords, RV64_FEAT_C);
    269         ++p;
    270         rv64_skip_version(&p, end);
    271         continue;
    272       case 'g':
    273         rv64_feature_enable_g(words, nwords);
    274         ++p;
    275         rv64_skip_version(&p, end);
    276         continue;
    277       case 'z':
    278         if (rv64_has_prefix(p, end, "zicsr")) {
    279           rv64_feature_set(words, nwords, RV64_FEAT_ZICSR);
    280           p += 5;
    281           rv64_skip_version(&p, end);
    282           continue;
    283         }
    284         if (rv64_has_prefix(p, end, "zifencei")) {
    285           rv64_feature_set(words, nwords, RV64_FEAT_ZIFENCEI);
    286           p += 8;
    287           rv64_skip_version(&p, end);
    288           continue;
    289         }
    290         break;
    291     }
    292     return KIT_UNSUPPORTED;
    293   }
    294   return KIT_OK;
    295 }
    296 
    297 static void rv64_target_feature_defaults(const Target* target, u64* words,
    298                                          u32 nwords) {
    299   rv64_feature_set(words, nwords, RV64_FEAT_I);
    300   rv64_feature_set(words, nwords, RV64_FEAT_M);
    301   rv64_feature_set(words, nwords, RV64_FEAT_A);
    302   /* rv32 default profile is rv32imac_zicsr_zifencei (ilp32 SOFT float): most
    303    * 32-bit RISC-V microcontrollers (e.g. ESP32-C3 = rv32imc) ship no F/D
    304    * hardware, and a hard-float default silently emits FLW/FSW that fault on the
    305    * chip. Opt into the FPU explicitly with -march=rv32imafc -mabi=ilp32f. rv64
    306    * keeps the full G+C (lp64d) profile including F+D. */
    307   if (target->arch != KIT_ARCH_RV32) {
    308     rv64_feature_set(words, nwords, RV64_FEAT_F);
    309     rv64_feature_set(words, nwords, RV64_FEAT_D);
    310   }
    311   rv64_feature_set(words, nwords, RV64_FEAT_C);
    312   rv64_feature_set(words, nwords, RV64_FEAT_ZICSR);
    313   rv64_feature_set(words, nwords, RV64_FEAT_ZIFENCEI);
    314 }
    315 
    316 static CgTarget* rv64_backend_make(Compiler* c, ObjBuilder* o,
    317                                    const KitCodeOptions* opts) {
    318   return native_direct_backend_make(c, o, opts, rv64_native_target_new,
    319                                     rv64_native_direct_ops());
    320 }
    321 
    322 static CgTarget* rv64_semantic_target_new(Compiler* c, ObjBuilder* o,
    323                                           MCEmitter* mc) {
    324   return native_direct_semantic_target_new(c, o, mc, rv64_native_target_new,
    325                                            rv64_native_direct_ops());
    326 }
    327 
    328 /* RISC-V emits only the target-C convention; it has no SysV/Win64/AAPCS/WASM
    329  * variant. Shared by rv64 and rv32 (one backend, one answer). */
    330 static int rv64_supports_call_conv(const Compiler* c, KitCgCallConv cc) {
    331   (void)c;
    332   return cc == KIT_CG_CC_TARGET_C;
    333 }
    334 
    335 /* Capability twin of rv_intrinsic (src/arch/riscv/native.c); keep the two in
    336  * sync. rv32 and rv64 share one backend, so they share this answer (the old
    337  * type.c matrix normalized rv32->rv64 for exactly this reason). No default
    338  * case, so a new KitCgIntrinsic trips -Wswitch here. */
    339 static int rv64_supports_intrinsic(const Compiler* c, KitCgIntrinsic intrin) {
    340   switch (intrin) {
    341     case KIT_CG_INTRIN_TRAP:
    342     case KIT_CG_INTRIN_CLZ:
    343     case KIT_CG_INTRIN_CTZ:
    344     case KIT_CG_INTRIN_POPCOUNT:
    345     case KIT_CG_INTRIN_BSWAP:
    346     case KIT_CG_INTRIN_SADD_OVERFLOW:
    347     case KIT_CG_INTRIN_UADD_OVERFLOW:
    348     case KIT_CG_INTRIN_SSUB_OVERFLOW:
    349     case KIT_CG_INTRIN_USUB_OVERFLOW:
    350     case KIT_CG_INTRIN_SMUL_OVERFLOW:
    351     case KIT_CG_INTRIN_UMUL_OVERFLOW:
    352     case KIT_CG_INTRIN_PREFETCH:
    353     case KIT_CG_INTRIN_EXPECT:
    354     case KIT_CG_INTRIN_ASSUME_ALIGNED:
    355     case KIT_CG_INTRIN_CPU_NOP:
    356     case KIT_CG_INTRIN_CPU_YIELD:
    357     case KIT_CG_INTRIN_ISB:
    358     case KIT_CG_INTRIN_DMB:
    359     case KIT_CG_INTRIN_DSB:
    360     case KIT_CG_INTRIN_WFI:
    361     case KIT_CG_INTRIN_FRAME_ADDRESS:
    362     case KIT_CG_INTRIN_RETURN_ADDRESS:
    363       return 1;
    364     case KIT_CG_INTRIN_READCYCLECOUNTER:
    365       /* rv64 reads the 64-bit `cycle` CSR inline with a single RDCYCLE. rv32's
    366        * result spans the cycle/cycleh CSR pair (a register pair the single-
    367        * register intrinsic path can't carry), so it routes to the
    368        * __kit_readcyclecounter libkit_rt helper instead (src/cg/arith.c). */
    369       return c->target.arch == KIT_ARCH_RV64 || c->target.arch == KIT_ARCH_RV32;
    370     case KIT_CG_INTRIN_SMUL_HIGH:
    371     case KIT_CG_INTRIN_UMUL_HIGH:
    372       return 1;
    373     case KIT_CG_INTRIN_SYSCALL:
    374       return c->target.os == KIT_OS_LINUX ||
    375              c->target.os == KIT_OS_FREESTANDING;
    376     case KIT_CG_INTRIN_SETJMP:
    377     case KIT_CG_INTRIN_LONGJMP:
    378     case KIT_CG_INTRIN_FMA:
    379     case KIT_CG_INTRIN_IRQ_SAVE:
    380     case KIT_CG_INTRIN_IRQ_RESTORE:
    381     case KIT_CG_INTRIN_IRQ_DISABLE:
    382     case KIT_CG_INTRIN_IRQ_ENABLE:
    383     case KIT_CG_INTRIN_WFE:
    384     case KIT_CG_INTRIN_SEV:
    385     case KIT_CG_INTRIN_DCACHE_CLEAN:
    386     case KIT_CG_INTRIN_DCACHE_INVALIDATE:
    387     case KIT_CG_INTRIN_DCACHE_CLEAN_INVALIDATE:
    388     case KIT_CG_INTRIN_ICACHE_INVALIDATE:
    389     case KIT_CG_INTRIN_CORO_SWITCH:
    390       return 0;
    391   }
    392   return 0;
    393 }
    394 
    395 static int rv64_feature_get(const u64* words, u32 nwords, u32 idx) {
    396   if (!words || idx / 64u >= nwords) return 0;
    397   return (words[idx / 64u] & (1ull << (idx % 64u))) != 0;
    398 }
    399 
    400 /* RISC-V float-ABI resolution + validation, factored out of src/api/core.c so
    401  * non-arch code resolves the ABI by capability (arch_resolve_float_abi) instead
    402  * of an `arch == RV32 || RV64` branch. Shared by rv32 and rv64 (one backend).
    403  * Mirrors the historical core.c logic exactly: an explicit -mabi (`abi`) picks
    404  * the ABI and must match the pointer width; otherwise the ABI is derived from
    405  * the resolved -march F/D bits. A hard single/double ABI requires the matching
    406  * extension. Writes spec->float_abi on success; on error returns KIT_INVALID
    407  * and fills `err`. */
    408 static KitStatus rv64_resolve_float_abi(const ArchImpl* impl,
    409                                         KitTargetSpec* spec,
    410                                         const u64* feature_words,
    411                                         u32 nfeature_words, KitSlice abi,
    412                                         char* err, size_t errcap) {
    413   u32 fidx, didx;
    414   int has_f;
    415   int has_d;
    416   KitFloatAbi fa;
    417   StrBuf sb;
    418   strbuf_init(&sb, err, errcap);
    419   has_f = arch_target_feature_index(impl, kit_slice_cstr("f"), &fidx) &&
    420           rv64_feature_get(feature_words, nfeature_words, fidx);
    421   has_d = arch_target_feature_index(impl, kit_slice_cstr("d"), &didx) &&
    422           rv64_feature_get(feature_words, nfeature_words, didx);
    423 
    424   if (abi.s && abi.len) {
    425     int is_ilp32 = 0;
    426     int is_lp64 = 0;
    427     if (kit_slice_eq_cstr(abi, "ilp32") || kit_slice_eq_cstr(abi, "ilp32f") ||
    428         kit_slice_eq_cstr(abi, "ilp32d")) {
    429       is_ilp32 = 1;
    430     } else if (kit_slice_eq_cstr(abi, "lp64") ||
    431                kit_slice_eq_cstr(abi, "lp64f") ||
    432                kit_slice_eq_cstr(abi, "lp64d")) {
    433       is_lp64 = 1;
    434     } else {
    435       strbuf_puts(&sb, "unsupported ABI for ");
    436       strbuf_puts(&sb, impl->name);
    437       strbuf_puts(&sb, ": ");
    438       strbuf_put_slice(&sb, abi);
    439       return KIT_INVALID;
    440     }
    441     /* Width prefix must match pointer size. */
    442     if ((is_ilp32 && spec->ptr_size != 4u) ||
    443         (is_lp64 && spec->ptr_size != 8u)) {
    444       strbuf_puts(&sb, "ABI ");
    445       strbuf_put_slice(&sb, abi);
    446       strbuf_puts(&sb, " does not match pointer width for ");
    447       strbuf_puts(&sb, impl->name);
    448       return KIT_INVALID;
    449     }
    450     if (kit_slice_eq_cstr(abi, "ilp32d") || kit_slice_eq_cstr(abi, "lp64d")) {
    451       fa = KIT_FLOAT_ABI_DOUBLE;
    452     } else if (kit_slice_eq_cstr(abi, "ilp32f") ||
    453                kit_slice_eq_cstr(abi, "lp64f")) {
    454       fa = KIT_FLOAT_ABI_SINGLE;
    455     } else {
    456       fa = KIT_FLOAT_ABI_SOFT;
    457     }
    458   } else {
    459     /* Derive from the resolved -march feature bits. */
    460     if (has_d)
    461       fa = KIT_FLOAT_ABI_DOUBLE;
    462     else if (has_f)
    463       fa = KIT_FLOAT_ABI_SINGLE;
    464     else
    465       fa = KIT_FLOAT_ABI_SOFT;
    466   }
    467 
    468   if (fa == KIT_FLOAT_ABI_SINGLE && !has_f) {
    469     strbuf_puts(&sb,
    470                 "hardware single-float ABI requires the 'f' extension for ");
    471     strbuf_puts(&sb, impl->name);
    472     return KIT_INVALID;
    473   }
    474   if (fa == KIT_FLOAT_ABI_DOUBLE && !has_d) {
    475     strbuf_puts(&sb,
    476                 "hardware double-float ABI requires the 'd' extension for ");
    477     strbuf_puts(&sb, impl->name);
    478     return KIT_INVALID;
    479   }
    480   spec->float_abi = (uint8_t)fa;
    481   return KIT_OK;
    482 }
    483 
    484 /* Float-ABI-dependent predefined macros, keyed on the resolved float ABI (see
    485  * ArchImpl.float_predefines). kit's RISC-V codegen only touches FP registers
    486  * under a hardware-float ABI, so __riscv_flen (and the coro FP-save it gates)
    487  * tracks float_abi rather than the raw F/D ISA bits. Shared by rv32 + rv64. */
    488 static const KitPredefinedMacro rv_float_macros_soft[] = {
    489     {KIT_SLICE_LIT("__riscv_float_abi_soft"), KIT_SLICE_LIT("1")},
    490 };
    491 static const KitPredefinedMacro rv_float_macros_single[] = {
    492     {KIT_SLICE_LIT("__riscv_float_abi_single"), KIT_SLICE_LIT("1")},
    493     {KIT_SLICE_LIT("__riscv_flen"), KIT_SLICE_LIT("32")},
    494     {KIT_SLICE_LIT("__riscv_fdiv"), KIT_SLICE_LIT("1")},
    495     {KIT_SLICE_LIT("__riscv_fsqrt"), KIT_SLICE_LIT("1")},
    496 };
    497 static const KitPredefinedMacro rv_float_macros_double[] = {
    498     {KIT_SLICE_LIT("__riscv_float_abi_double"), KIT_SLICE_LIT("1")},
    499     {KIT_SLICE_LIT("__riscv_flen"), KIT_SLICE_LIT("64")},
    500     {KIT_SLICE_LIT("__riscv_fdiv"), KIT_SLICE_LIT("1")},
    501     {KIT_SLICE_LIT("__riscv_fsqrt"), KIT_SLICE_LIT("1")},
    502 };
    503 
    504 static u32 rv64_float_predefines(const ArchImpl* impl,
    505                                  const KitTargetSpec* spec,
    506                                  const KitPredefinedMacro** out) {
    507   (void)impl;
    508   switch ((KitFloatAbi)spec->float_abi) {
    509     case KIT_FLOAT_ABI_SINGLE:
    510       *out = rv_float_macros_single;
    511       return (u32)(sizeof rv_float_macros_single / sizeof rv_float_macros_single[0]);
    512     case KIT_FLOAT_ABI_DOUBLE:
    513       *out = rv_float_macros_double;
    514       return (u32)(sizeof rv_float_macros_double / sizeof rv_float_macros_double[0]);
    515     case KIT_FLOAT_ABI_SOFT:
    516     case KIT_FLOAT_ABI_DEFAULT:
    517       break;
    518   }
    519   /* SOFT (and the unreached DEFAULT — RISC-V always resolves a concrete ABI). */
    520   *out = rv_float_macros_soft;
    521   return (u32)(sizeof rv_float_macros_soft / sizeof rv_float_macros_soft[0]);
    522 }
    523 
    524 const ArchImpl arch_impl_rv64 = {
    525     .backend = {.name = "rv64", .make = rv64_backend_make},
    526     .kind = KIT_ARCH_RV64,
    527     .name = "rv64",
    528     .cgtarget_new = rv64_semantic_target_new,
    529     .asm_new = rv64_arch_asm_new,
    530     .disasm_new = rv64_disasm_new,
    531     .apply_label_fixup = rv64_apply_label_fixup,
    532     .decode = &rv64_decode_ops,
    533     .emu = &rv64_emu_ops,
    534     .link = &link_arch_rv64,
    535     .dwarf = &rv64_dwarf_ops,
    536     .dbg = &rv64_dbg_ops,
    537     .asm_ops = &rv64_asm_ops,
    538     .predefined_macros = rv64_predefined_macros,
    539     .npredefined_macros =
    540         (u32)(sizeof rv64_predefined_macros / sizeof rv64_predefined_macros[0]),
    541     .target_features = rv64_target_features,
    542     .ntarget_features =
    543         (u32)(sizeof rv64_target_features / sizeof rv64_target_features[0]),
    544     .target_feature_defaults = rv64_target_feature_defaults,
    545     .target_feature_apply_isa = rv64_target_feature_apply_isa,
    546     .register_name = rv64_register_name,
    547     .register_index = rv64_register_index,
    548     .register_count = rv64_register_iter_size,
    549     .register_at = rv64_register_at_public,
    550     /* RISC-V psABI: return address in x1 (ra). 4-byte aligned insns
    551      * (cover 2-byte C-ext too via code_align=2). Data align -8 for
    552      * doubleword stack stride. CFA = sp at entry. */
    553     .cfi_return_addr_reg = 1u,
    554     .cfi_code_align_factor = 2,
    555     .cfi_data_align_factor = -8,
    556     .cfi_cfa_init_reg = 2u,
    557     .cfi_cfa_init_offset = 0,
    558     .backend_features = KIT_CG_BACKEND_STRICT_ALIGNMENT,
    559     .atomic_lock_free_max = 8u,
    560     .supports_call_conv = rv64_supports_call_conv,
    561     .supports_intrinsic = rv64_supports_intrinsic,
    562     .resolve_float_abi = rv64_resolve_float_abi,
    563     .float_predefines = rv64_float_predefines,
    564 };
    565 
    566 /* RV32 shares nearly all of the RISC-V backend with rv64 — the per-XLEN
    567  * differences are threaded through RiscvVariant inside the shared functions.
    568  * Differs only in: backend/arch names + kind, the link descriptor + dbg ops
    569  * (rv_lw / 2-byte min insn), the ilp32 (soft-float) predefined-macro table, and
    570  * the CFI data alignment factor (-4 word stride vs rv64's -8 doubleword). */
    571 const ArchImpl arch_impl_rv32 = {
    572     .backend = {.name = "rv32", .make = rv64_backend_make},
    573     .kind = KIT_ARCH_RV32,
    574     .name = "rv32",
    575     .cgtarget_new = rv64_semantic_target_new,
    576     .asm_new = rv64_arch_asm_new,
    577     .disasm_new = rv64_disasm_new,
    578     .apply_label_fixup = rv64_apply_label_fixup,
    579     .decode = &rv64_decode_ops,
    580     .emu = &rv64_emu_ops,
    581     .link = &link_arch_rv32,
    582     .dwarf = &rv64_dwarf_ops,
    583     .dbg = &rv32_dbg_ops,
    584     .asm_ops = &rv64_asm_ops,
    585     .predefined_macros = rv32_predefined_macros,
    586     .npredefined_macros =
    587         (u32)(sizeof rv32_predefined_macros / sizeof rv32_predefined_macros[0]),
    588     .target_features = rv64_target_features,
    589     .ntarget_features =
    590         (u32)(sizeof rv64_target_features / sizeof rv64_target_features[0]),
    591     .target_feature_defaults = rv64_target_feature_defaults,
    592     .target_feature_apply_isa = rv64_target_feature_apply_isa,
    593     .register_name = rv64_register_name,
    594     .register_index = rv64_register_index,
    595     .register_count = rv64_register_iter_size,
    596     .register_at = rv64_register_at_public,
    597     /* RISC-V psABI: return address in x1 (ra). 4-byte aligned insns
    598      * (cover 2-byte C-ext too via code_align=2). Data align -4 for
    599      * word stack stride (rv32). CFA = sp at entry. */
    600     .cfi_return_addr_reg = 1u,
    601     .cfi_code_align_factor = 2,
    602     .cfi_data_align_factor = -4,
    603     .cfi_cfa_init_reg = 2u,
    604     .cfi_cfa_init_offset = 0,
    605     .backend_features = KIT_CG_BACKEND_STRICT_ALIGNMENT,
    606     /* rv32 has no native 64-bit atomics (no lr.d/sc.d/amo*.d). */
    607     .atomic_lock_free_max = 4u,
    608     .supports_call_conv = rv64_supports_call_conv,
    609     .supports_intrinsic = rv64_supports_intrinsic,
    610     .resolve_float_abi = rv64_resolve_float_abi,
    611     .float_predefines = rv64_float_predefines,
    612 };