kit

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

arch.h (23425B)


      1 #ifndef KIT_INTERNAL_ARCH_H
      2 #define KIT_INTERNAL_ARCH_H
      3 
      4 #include <kit/arch.h>
      5 #include <kit/compile.h>
      6 #include <kit/disasm.h>
      7 
      8 #include "abi/abi.h"
      9 #include "arch/mc.h"
     10 #include "cg/cgtarget.h"
     11 #include "core/core.h"
     12 #include "obj/obj.h"
     13 
     14 /* Generic ArchImpl.register_at body: adapt a per-arch register_iter_get (which
     15  * yields a DWARF index + a C-string name) into the public KitArchReg out
     16  * struct. Each arch's register_at is a one-line wrapper delegating here with
     17  * its own iter_get, so the conversion lives in exactly one place. */
     18 static inline int arch_register_at_public(uint32_t idx, KitArchReg* out,
     19                                           int (*iter_get)(uint32_t, uint32_t*,
     20                                                           const char**)) {
     21   const char* nm = NULL;
     22   int rc;
     23   if (!out) return 1;
     24   rc = iter_get(idx, &out->dwarf_idx, &nm);
     25   if (rc == 0) out->name = kit_slice_cstr(nm);
     26   return rc;
     27 }
     28 
     29 typedef struct AsmDriver AsmDriver;
     30 
     31 typedef struct ArchAsm ArchAsm;
     32 struct ArchAsm {
     33   void (*insn)(ArchAsm*, AsmDriver*, Sym mnemonic);
     34   void (*destroy)(ArchAsm*);
     35 };
     36 
     37 /* ---- Disassembler hook ----
     38  * Bytes -> records, not frontend-driven lowering, so this is a separate
     39  * hook from CgTarget/MCEmitter. The internal implementation may share
     40  * encoding tables with the per-arch backend (sequencing concern, not an
     41  * interface concern). Constructed for c->target.
     42  *
     43  * arch_disasm_decode returns the number of bytes consumed, or 0 if input
     44  * is too short or undecodable (in which case the public iterator advances
     45  * by the arch's minimum unit). ArchDisasm owns the mnemonic / operands /
     46  * annotation string buffers placed into *out; they are valid until the
     47  * next decode or arch_disasm_free, whichever comes first. */
     48 typedef struct ArchDisasm ArchDisasm;
     49 struct ArchDisasm {
     50   u32 (*decode)(ArchDisasm*, const u8* bytes, size_t len, u64 vaddr,
     51                 KitInsn* out);
     52   void (*destroy)(ArchDisasm*);
     53 };
     54 
     55 #define KIT_DECODE_MAX_OPERANDS 6u
     56 
     57 typedef enum KitDecodeFlag {
     58   KIT_DECODE_TERMINATOR = 1u << 0,
     59   KIT_DECODE_BRANCH = 1u << 1,
     60   KIT_DECODE_CALL = 1u << 2,
     61   KIT_DECODE_RET = 1u << 3,
     62   KIT_DECODE_MEMORY = 1u << 4,
     63   KIT_DECODE_TRAP = 1u << 5,
     64 } KitDecodeFlag;
     65 
     66 typedef enum KitDecodedOperandKind {
     67   KIT_DECOP_NONE,
     68   KIT_DECOP_REG,
     69   KIT_DECOP_IMM,
     70   KIT_DECOP_MEM,
     71   KIT_DECOP_PCREL,
     72   KIT_DECOP_SYSREG,
     73 } KitDecodedOperandKind;
     74 
     75 typedef struct KitDecodedOperand {
     76   u8 kind;
     77   u8 width_bits;
     78   u16 flags;
     79   u32 reg;
     80   u32 index_reg;
     81   i64 imm;
     82   u8 scale;
     83   u8 pad[7];
     84 } KitDecodedOperand;
     85 
     86 typedef struct KitDecodedInsn {
     87   u64 pc;
     88   const u8* bytes;
     89   u8 nbytes;
     90   u8 noperands;
     91   u16 flags;
     92   u32 opcode;      /* Arch-owned stable opcode id. */
     93   u32 encoding_id; /* Optional row/table id for formatting. */
     94   KitDecodedOperand operands[KIT_DECODE_MAX_OPERANDS];
     95   u64 arch[2]; /* Small arch-private payload. */
     96 } KitDecodedInsn;
     97 
     98 typedef struct ArchInsnFormatter ArchInsnFormatter;
     99 typedef struct KitCg KitCg;
    100 typedef struct EmuCPUState EmuCPUState;
    101 typedef struct EmuLiftCtx EmuLiftCtx;
    102 typedef struct EmuProcess EmuProcess;
    103 typedef struct EmuThread EmuThread;
    104 struct ArchInsnFormatter {
    105   KitStatus (*format)(ArchInsnFormatter*, const KitDecodedInsn*, KitInsn* out);
    106   void (*destroy)(ArchInsnFormatter*);
    107 };
    108 
    109 typedef struct ArchDecodeOps {
    110   u8 min_insn_len;
    111   u8 max_insn_len;
    112 
    113   KitStatus (*decode_one)(Compiler*, const u8* bytes, size_t len, u64 pc,
    114                           KitDecodedInsn* out);
    115   KitStatus (*decode_block)(Compiler*, const u8* bytes, size_t len, u64 pc,
    116                             KitDecodedInsn* out, u32 cap, u32* n_out);
    117 
    118   ArchInsnFormatter* (*formatter_new)(Compiler*);
    119   KitStatus (*format)(ArchInsnFormatter*, const KitDecodedInsn*, KitInsn* out);
    120   void (*formatter_destroy)(ArchInsnFormatter*);
    121 } ArchDecodeOps;
    122 
    123 typedef struct ArchEmuOps {
    124   EmuCPUState* (*cpu_new)(Compiler*, u64 initial_pc, u64 initial_sp);
    125   KitCgTypeId (*block_fn_type)(Compiler*);
    126   KitStatus (*lift_block)(Compiler*, KitCg*, const KitDecodedInsn*, u32 n,
    127                           const EmuLiftCtx*);
    128   u64 (*get_gpr)(EmuThread*, u32 reg);
    129   void (*set_gpr)(EmuThread*, u32 reg, u64 value);
    130   u64 (*get_syscall_no)(EmuThread*);
    131   u64 (*get_syscall_arg)(EmuThread*, u32 index);
    132   void (*set_syscall_result)(EmuThread*, u64 value);
    133   u64 (*get_sp)(EmuThread*);
    134   void (*set_sp)(EmuThread*, u64 value);
    135   u64 (*get_tp)(EmuThread*);
    136   void (*set_tp)(EmuThread*, u64 value);
    137   u64 (*signal_context_size)(EmuProcess*, EmuThread*);
    138   KitStatus (*save_signal_context)(EmuProcess*, EmuThread*, u8* dst, u64 size);
    139   KitStatus (*restore_signal_context)(EmuProcess*, EmuThread*, const u8* src,
    140                                       u64 size);
    141   KitStatus (*set_signal_handler_args)(EmuProcess*, EmuThread*, int signo,
    142                                        u64 siginfo, u64 ucontext);
    143   u64 (*signal_stack_align)(EmuProcess*, EmuThread*);
    144   u32 import_thunk_size;
    145   KitStatus (*emit_import_thunk)(EmuProcess*, u64 thunk_vaddr);
    146   void* (*resolve_runtime_helper)(void* emu, KitSlice name);
    147 } ArchEmuOps;
    148 
    149 typedef struct LinkArchDesc LinkArchDesc;
    150 
    151 typedef struct ArchDwarfOps {
    152   /* DWARF .debug_line minimum instruction length and maximum operations per
    153    * instruction. Fixed-width ISAs normally use their instruction width; x86_64
    154    * uses 1 because line-program PC advances are byte granular. */
    155   u8 min_inst_len;
    156   u8 max_ops_per_inst;
    157   u8 pad[2];
    158 } ArchDwarfOps;
    159 
    160 typedef struct ArchTargetFeature {
    161   const char* name;
    162 } ArchTargetFeature;
    163 
    164 #define ARCH_DBG_MAX_TRAP_BYTES 8u
    165 #define ARCH_DBG_MAX_INSN_BYTES 15u
    166 
    167 typedef struct ArchDbgInsn {
    168   u64 pc;
    169   u8 bytes[ARCH_DBG_MAX_INSN_BYTES];
    170   u32 len;
    171 } ArchDbgInsn;
    172 
    173 typedef struct ArchDbgOps {
    174   u32 min_insn_len;
    175   u32 max_insn_len;
    176 
    177   KitStatus (*breakpoint_patch)(u8* out, u32 cap, u32* len_out);
    178   u64 (*breakpoint_addr_from_fault_pc)(u64 fault_pc);
    179 
    180   KitStatus (*decode_insn)(const u8* bytes, u32 len, u64 pc, ArchDbgInsn* out);
    181   KitStatus (*build_displaced_shim)(const ArchDbgInsn* insn,
    182                                     void* scratch_write, u64 scratch_runtime,
    183                                     u32 scratch_cap, u32* sentinel_off,
    184                                     u64* fallthrough_pc);
    185   int (*is_call)(const ArchDbgInsn* insn);
    186   KitStatus (*direct_call_target)(const ArchDbgInsn* insn, u64* target_out);
    187   KitStatus (*direct_jump_target)(const ArchDbgInsn* insn, u64* target_out);
    188   KitStatus (*link_register_return_address)(const KitUnwindFrame* frame,
    189                                             u64* target_out);
    190 } ArchDbgOps;
    191 
    192 /* ---- textual-assembly operand syntax (printer <-> parser) ----------------
    193  *
    194  * How a relocated operand is spelled in `cc -S` output. The shape selects
    195  * which part of the disassembled operand text the symbolizer rewrites; the
    196  * prefix/suffix are the relocation-modifier spelling for the target object
    197  * format (e.g. aarch64 ELF `:lo12:sym` is a prefix; Mach-O `sym@PAGEOFF` is a
    198  * suffix). At most one of prefix/suffix is non-empty for a given (kind, fmt).
    199  * This is the inverse of the arch assembler's operand reloc-modifier parser. */
    200 typedef enum ArchRelocSurg {
    201   ARCH_RELOC_SURG_NONE = 0, /* not symbolizable here; keep numeric operand */
    202   ARCH_RELOC_SURG_TAIL, /* replace last comma component (or whole operand) */
    203   ARCH_RELOC_SURG_MEM,  /* rewrite the offset inside [...]  (aarch64 ldst) */
    204   ARCH_RELOC_SURG_RIP,  /* insert sym before disp(%rip)     (x86-64 RIP-rel) */
    205   /* RISC-V `%pcrel_lo`/`%lo` low-half operand. A single reloc kind covers two
    206    * disassembled shapes: a register-immediate ADDI (printed as `mv rd, rs`
    207    * when the immediate is 0) where the modifier becomes a new trailing
    208    * operand (`mv rd, rs, %pcrel_lo(L)`, which the assembler folds back into
    209    * ADDI), and a `disp(base)` load/store where the modifier replaces the
    210    * displacement (`%pcrel_lo(L)(base)`). The shape is picked from the operand
    211    * text: a trailing `(...)` group selects the memory form. */
    212   ARCH_RELOC_SURG_RV_LO12,
    213 } ArchRelocSurg;
    214 
    215 typedef struct ArchRelocOperand {
    216   ArchRelocSurg surg;
    217   const char* prefix; /* e.g. ":lo12:" (ELF); "" if none */
    218   const char* suffix; /* e.g. "@PAGEOFF" / "@GOTPCREL"; "" if none */
    219   /* Added to the relocation's stored addend before spelling `sym[+/-N]`. Undoes
    220    * an instruction-encoding bias so the printed offset is the *symbol* offset:
    221    * 0 for aarch64; +4 for x86-64 rel32 (PC32/PLT32/GOTPCREL store addend-4). */
    222   int addend_bias;
    223   /* hi/lo anchor pairing (RISC-V `%pcrel_hi`/`%pcrel_lo`). A high-half reloc
    224    * (AUIPC `%pcrel_hi(sym)`) sets `emit_anchor` so the symbolizer defines a
    225    * unique local label at this instruction. The paired low-half reloc
    226    * (`%pcrel_lo`) sets `ref_anchor`: its operand references that synthesized
    227    * anchor label (the nearest preceding anchor) instead of the reloc's own
    228    * symbol — matching the RISC-V ABI, where `%pcrel_lo` names the AUIPC's
    229    * label, not the target symbol. Other arches leave both 0. */
    230   u8 emit_anchor;
    231   u8 ref_anchor;
    232 } ArchRelocOperand;
    233 
    234 typedef struct ArchAsmOps {
    235   /* Map (reloc kind, target object format) to the operand syntax the cc -S
    236    * symbolizer must emit (and that this arch's .s parser accepts back).
    237    * Returns 1 and fills *out when the kind is symbolizable for fmt; 0
    238    * otherwise (printer keeps the numeric operand). The symbolizer picks the
    239    * surgery site from the operand text (an `(%rip)` operand always uses RIP
    240    * surgery regardless of `out->surg`), so a single reloc kind can serve both
    241    * a branch target and a RIP-relative memory operand (x86-64 R_PC32). */
    242   int (*reloc_operand)(u16 reloc_kind, KitObjFmt fmt, ArchRelocOperand* out);
    243   /* 1 if `mnemonic` is an intra-section local branch whose un-relocated
    244    * numeric target the symbolizer should replace with a synthesized label
    245    * (aarch64 b/b.cc/cbz/...; x86-64 jmp/jcc). Calls are excluded — they carry
    246    * relocations. NULL hook = no local-branch symbolization for the arch. */
    247   int (*is_local_branch)(KitSlice mnemonic);
    248   /* Fuse a relocation that the disassembler renders as a 2-instruction pair
    249    * back into a single relocated pseudo-instruction line. RISC-V R_RV_CALL
    250    * sits on an AUIPC whose JALR partner carries no reloc; the canonical `.s`
    251    * spelling is a single `call`/`tail sym`. When `kind` names such a reloc,
    252    * the hook returns 1 and sets *mnemonic_out to the fused mnemonic — the
    253    * symbolizer then emits "<mnemonic>\t<sym[+addend]>" in place of BOTH
    254    * instructions (skipping the partner). `pair_mnemonic`/`pair_ops` are the
    255    * SECOND instruction's disassembled text (the JALR), used to disambiguate
    256    * (e.g. call vs tail by its link register). Returns 0 to leave the pair
    257    * un-fused (per-instruction operand symbolization applies). NULL hook = no
    258    * pair fusion for the arch. */
    259   int (*reloc_call_pair)(u16 reloc_kind, KitSlice pair_mnemonic,
    260                          KitSlice pair_ops, const char** mnemonic_out);
    261   /* 1 if this arch tags function symbols with a low-bit (LSB) ISA-state
    262    * marker, the way the 32-bit ARM EABI distinguishes Thumb entry points: a
    263    * defined function symbol carries value|1, the `.thumb_func` directive marks
    264    * the next/named label as such, and `.size SYM, . - SYM` masks that bit back
    265    * off before differencing. The standalone assembler keys all of this Thumb
    266    * bookkeeping off this flag instead of switching on arch identity. 0 for
    267    * every arch with no ISA-state symbol bit (aarch64, x86-64, RISC-V, …),
    268    * which yields the plain "value = offset, no LSB" behavior. */
    269   unsigned thumb_function_symbols : 1;
    270 } ArchAsmOps;
    271 
    272 typedef struct ArchImpl {
    273   /* First field, so `(const CGBackend*)&arch_impl_x` is the arch's backend
    274    * view. Every machine-code arch is a CGBackend by composition; c_target
    275    * is a standalone CGBackend with no ArchImpl. */
    276   CGBackend backend;
    277 
    278   KitArchKind kind;
    279   const char* name;
    280 
    281   /* Low-level CgTarget constructor: caller supplies the MCEmitter. Tests use
    282    * this directly via the cgtarget_new() wrapper; the arch's `backend.make`
    283    * also calls it after creating an MCEmitter internally. */
    284   CgTarget* (*cgtarget_new)(Compiler*, ObjBuilder*, MCEmitter*);
    285   ArchAsm* (*asm_new)(Compiler*);
    286   ArchDisasm* (*disasm_new)(Compiler*);
    287   int (*apply_label_fixup)(Compiler*, const ArchLabelFixup*);
    288 
    289   const ArchDecodeOps* decode;
    290   const ArchEmuOps* emu;
    291   const LinkArchDesc* link;
    292   const ArchDwarfOps* dwarf;
    293   const ArchDbgOps* dbg;
    294   const ArchAsmOps*
    295       asm_ops; /* textual-asm operand syntax; NULL = keep numeric */
    296 
    297   const KitPredefinedMacro* predefined_macros;
    298   u32 npredefined_macros;
    299   const ArchTargetFeature* target_features;
    300   u32 ntarget_features;
    301   void (*target_feature_defaults)(const Target*, u64* words, u32 nwords);
    302   KitStatus (*target_feature_apply_isa)(const Target*, KitSlice isa, u64* words,
    303                                         u32 nwords);
    304   /* Apply a CPU/core selector (-mcpu=) onto the feature words, the same way
    305    * target_feature_apply_isa applies -march=. A CPU typically pins an ISA
    306    * profile plus extensions (e.g. ARM cortex-m4 -> armv7e-m + dsp). Returns
    307    * KIT_OK on success, KIT_UNSUPPORTED for an unknown CPU. NULL hook means the
    308    * arch has no CPU axis (arch_target_feature_apply_cpu no-ops to KIT_OK). */
    309   KitStatus (*target_feature_apply_cpu)(const Target*, KitSlice cpu, u64* words,
    310                                         u32 nwords);
    311 
    312   const char* (*register_name)(uint32_t dwarf_idx);
    313   int (*register_index)(const char* name, uint32_t* idx_out);
    314   uint32_t (*register_count)(void);
    315   int (*register_at)(uint32_t idx, KitArchReg* out);
    316 
    317   /* DWARF CFI defaults per psABI, used by the CIE the .eh_frame
    318    * producer emits. cfi_cfa_init_{reg,offset} describe the at-entry
    319    * CFA state — before any cfi_def_cfa override — so an unwinder can
    320    * recover the caller's stack pointer at the very first instruction. */
    321   u32 cfi_return_addr_reg;
    322   i32 cfi_code_align_factor;
    323   i32 cfi_data_align_factor;
    324   u32 cfi_cfa_init_reg;
    325   i32 cfi_cfa_init_offset;
    326 
    327   /* === Generic-layer capability queries =====================================
    328    * Let generic (non-backend) code in src/cg and src/link decide by capability
    329    * instead of by arch identity (target.arch == KIT_ARCH_*). Each backend
    330    * declares its answer here once. */
    331 
    332   /* Backend codegen capability bitmask (KitCgBackendFeatureFlag). Per-arch
    333    * constant: the x86 family sets UNALIGNED_MEMORY|RED_ZONE|SIMD, every other
    334    * arch sets STRICT_ALIGNMENT. Read via kit_cg_target_backend_features. */
    335   u64 backend_features;
    336 
    337   /* Largest power-of-two byte width this arch lowers as a lock-free native
    338    * atomic: 8 for aa64/x64/rv64/wasm, 4 for rv32 (no lr.d/sc.d/amo*.d). The
    339    * single source of truth for kit_cg_atomic_is_lock_free and the C front-end's
    340    * __atomic_always_lock_free. */
    341   u32 atomic_lock_free_max;
    342 
    343   /* 1 if call convention `cc` is selectable for this compiler's (arch, os).
    344    * Today the only convention is KIT_CG_CC_TARGET_C (the target's C ABI); a
    345    * backend that grows a real variant convention reports it here. May read
    346    * c->target.os (a property, not arch identity). Read via
    347    * kit_cg_target_supports_call_conv. */
    348   int (*supports_call_conv)(const Compiler* c, KitCgCallConv cc);
    349 
    350   /* 1 if this arch has a legal lowering for `intrin`. Kept in sync with the
    351    * backend's IntrinKind lowering switch (x64_intrinsic / aa_intrinsic /
    352    * rv_intrinsic / wasm_intrinsic). Read via kit_cg_target_supports_intrinsic.
    353    */
    354   int (*supports_intrinsic)(const Compiler* c, KitCgIntrinsic intrin);
    355 
    356   /* Resolve & validate the float ABI for the target being constructed, given
    357    * the explicit -mabi string (`abi`, empty for none) and the already-resolved
    358    * -march feature bits (`feature_words`/`nfeature_words`). On success returns
    359    * KIT_OK with `spec->float_abi` set to the chosen KitFloatAbi. On a bad/
    360    * mismatched ABI returns KIT_INVALID and writes a NUL-terminated message into
    361    * `err` (capacity `errcap`) for the caller to surface as a diagnostic. NULL
    362    * hook means the arch has no float-ABI axis: leave spec->float_abi at
    363    * KIT_FLOAT_ABI_DEFAULT (the arch_resolve_float_abi wrapper no-ops). Set for
    364    * RISC-V (handles rv32 + rv64); read via arch_resolve_float_abi. */
    365   KitStatus (*resolve_float_abi)(const struct ArchImpl* impl,
    366                                  KitTargetSpec* spec, const u64* feature_words,
    367                                  u32 nfeature_words, KitSlice abi, char* err,
    368                                  size_t errcap);
    369 
    370   /* Float-ABI-dependent predefined macros for the resolved target. A single
    371    * static `predefined_macros` table cannot encode both the soft and the
    372    * hard-float profiles, yet the preprocessor must agree with the codegen ABI:
    373    * rt/lib/coro keys on __riscv_flen to decide whether a context switch saves
    374    * the FP registers, and user code branches on __riscv_float_abi_*. RISC-V
    375    * sets this hook to emit __riscv_float_abi_{soft,single,double} (plus
    376    * __riscv_flen / __riscv_fdiv / __riscv_fsqrt for the hard cases) keyed on
    377    * spec->float_abi, so those macros stay out of the static table. NULL means
    378    * the arch has no float-ABI axis and its static table is complete. The
    379    * returned entries are static-lifetime. Read via arch_float_predefines. */
    380   u32 (*float_predefines)(const struct ArchImpl* impl, const KitTargetSpec* spec,
    381                           const KitPredefinedMacro** out);
    382 
    383   /* Feature-keyed predefined macros for the resolved target, selected from the
    384    * resolved -march/-mcpu/-mattr feature words (`words`/`nwords`) and the spec
    385    * (so an arch may also key on spec->float_abi here). The same single-static-
    386    * table limitation that motivates float_predefines applies to any macro that
    387    * varies by extension (e.g. ARM's __ARM_ARCH_7M__ vs __ARM_ARCH_7EM__ and
    388    * __ARM_FEATURE_DSP). An arch must return a STATIC-lifetime table selected
    389    * among precomputed `static const` tables per discrete config; the returned
    390    * entries are borrowed. NULL hook means the arch's static `predefined_macros`
    391    * table is complete. ARM folds its float macros into this hook (and leaves
    392    * float_predefines NULL) because they share the feature words. Read via
    393    * arch_feature_predefines. */
    394   u32 (*feature_predefines)(const struct ArchImpl* impl,
    395                             const KitTargetSpec* spec, const u64* words,
    396                             u32 nwords, const KitPredefinedMacro** out);
    397 } ArchImpl;
    398 
    399 const ArchImpl* arch_lookup(KitArchKind);
    400 const ArchImpl* arch_for_compiler(const Compiler*);
    401 int arch_target_feature_index(const ArchImpl*, KitSlice name, u32* idx_out);
    402 void arch_target_feature_defaults(const ArchImpl*, const Target*, u64* words,
    403                                   u32 nwords);
    404 KitStatus arch_target_feature_apply_isa(const ArchImpl*, const Target*,
    405                                         KitSlice isa, u64* words, u32 nwords);
    406 KitStatus arch_target_feature_apply_cpu(const ArchImpl*, const Target*,
    407                                         KitSlice cpu, u64* words, u32 nwords);
    408 
    409 /* Resolve & validate `spec->float_abi` from the explicit -mabi string and the
    410  * resolved -march feature bits, dispatching to `impl->resolve_float_abi`. When
    411  * `impl` is NULL or the arch sets no hook this is a no-op returning KIT_OK and
    412  * leaving spec->float_abi untouched (KIT_FLOAT_ABI_DEFAULT) — exactly the old
    413  * "non-RISC-V arches leave the float ABI at default" behavior. On a bad ABI the
    414  * hook returns KIT_INVALID and fills `err` (NUL-terminated, capacity `errcap`).
    415  * Header-only thin dispatch: the matching `arch_reloc_*` wrappers live in
    416  * src/arch/registry.c, but float-ABI resolution runs during target
    417  * construction (no Compiler yet), so this wrapper takes the ArchImpl directly.
    418  */
    419 static inline KitStatus arch_resolve_float_abi(const ArchImpl* impl,
    420                                                KitTargetSpec* spec,
    421                                                const u64* feature_words,
    422                                                u32 nfeature_words, KitSlice abi,
    423                                                char* err, size_t errcap) {
    424   if (!impl || !impl->resolve_float_abi) return KIT_OK;
    425   return impl->resolve_float_abi(impl, spec, feature_words, nfeature_words, abi,
    426                                  err, errcap);
    427 }
    428 
    429 /* Float-ABI-dependent predefined macros for `spec` (see ArchImpl.float_predefines).
    430  * Sets *out to a borrowed static-lifetime array and returns its length; 0 (with
    431  * *out=NULL) when the arch sets no hook. */
    432 static inline u32 arch_float_predefines(const ArchImpl* impl,
    433                                         const KitTargetSpec* spec,
    434                                         const KitPredefinedMacro** out) {
    435   if (out) *out = NULL;
    436   if (!impl || !impl->float_predefines) return 0;
    437   return impl->float_predefines(impl, spec, out);
    438 }
    439 
    440 /* Feature-keyed predefined macros for `spec` + resolved feature `words`/`nwords`
    441  * (see ArchImpl.feature_predefines). Sets *out to a borrowed static-lifetime
    442  * array and returns its length; 0 (with *out=NULL) when the arch sets no hook. */
    443 static inline u32 arch_feature_predefines(const ArchImpl* impl,
    444                                           const KitTargetSpec* spec,
    445                                           const u64* words, u32 nwords,
    446                                           const KitPredefinedMacro** out) {
    447   if (out) *out = NULL;
    448   if (!impl || !impl->feature_predefines) return 0;
    449   return impl->feature_predefines(impl, spec, words, nwords, out);
    450 }
    451 
    452 /* Spelling for a relocated operand in `cc -S` text, for the compiler's target
    453  * arch+format. Returns 1 and fills *out when symbolizable, 0 to keep numeric
    454  * (also when the arch provides no asm_ops). Thin dispatch over ArchAsmOps. */
    455 int arch_reloc_operand(const Compiler* c, u16 reloc_kind,
    456                        ArchRelocOperand* out);
    457 
    458 /* 1 if `mnemonic` is an intra-section local branch for the compiler's target
    459  * arch (so cc -S synthesizes a label at its un-relocated target). 0 when the
    460  * arch has no asm_ops/is_local_branch hook. */
    461 int arch_is_local_branch(const Compiler* c, KitSlice mnemonic);
    462 
    463 /* 1 if `reloc_kind` names a 2-instruction call pair the symbolizer should fuse
    464  * into a single pseudo line (RISC-V R_RV_CALL -> `call`/`tail`), with
    465  * *mnemonic_out set to the fused mnemonic. `pair_*` are the partner (second)
    466  * instruction's disassembled text. 0 when not fused / no hook. Thin dispatch
    467  * over ArchAsmOps.reloc_call_pair. */
    468 int arch_reloc_call_pair(const Compiler* c, u16 reloc_kind,
    469                          KitSlice pair_mnemonic, KitSlice pair_ops,
    470                          const char** mnemonic_out);
    471 
    472 ArchDisasm* arch_disasm_new(Compiler*);
    473 u32 arch_disasm_decode(ArchDisasm*, const u8* bytes, size_t len, u64 vaddr,
    474                        KitInsn* out);
    475 void arch_disasm_free(ArchDisasm*);
    476 KitStatus arch_decode_one(Compiler*, const u8* bytes, size_t len, u64 pc,
    477                           KitDecodedInsn* out);
    478 KitStatus arch_decode_block(Compiler*, const u8* bytes, size_t len, u64 pc,
    479                             KitDecodedInsn* out, u32 cap, u32* n_out);
    480 ArchInsnFormatter* arch_insn_formatter_new(Compiler*);
    481 KitStatus arch_format_insn(ArchInsnFormatter*, const KitDecodedInsn*,
    482                            KitInsn* out);
    483 void arch_insn_formatter_free(ArchInsnFormatter*);
    484 
    485 #endif