kit

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

commit 3b27e21fb67bd8457611ef5fbefb3bc8e9d73ca0
parent 36feeb5b099d9e835e67636a5ec87911283aead4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 21:19:21 -0700

cleanup wave C-1: behavior-preserving backend extractions

Adversarial review backlog, Wave C-1 (the "use the shared seam, don't bypass
it" core). All behavior-preserving — verified by test-aa64-inline /
test-rv64-inline / test-x64-inline / test-abi-classify / test-cg-api / test-opt
/ test-isa / test-toy (all backends) / test-smoke-x64 / test-smoke-rv64 /
test-libc / test-asm. Net -134 LOC, but the win is de-triplication.

A.1 — inline-asm operand-binding orchestration: aa_/x64_/rv_direct_asm_block
were ~170-line near-identical copies over the already-shared cg/native_asm.*
primitives. Hoisted the arch-neutral out/in/save/load/run/store/restore skeleton
into native_asm_bind_direct_operands (cg/native_asm.c), parameterized via a new
NativeAsmDirectHooks struct (scratch masks, OPK_REG/FP-class constants, panic,
and a few per-arch adapters). Each backend's *_direct_asm_block is now a thin
wrapper (−222 LOC each across the 3 native.c). The direct copies were not
actually drifted (only the scratch-mask seed differed); the genuinely-drifted
optimizer-path *_asm_block_native family is a separate follow-up (A.2, tracked).

C.1 — per-ABI argument classification: hoisted abi_compute_func_info_generic
(taking a classify_one callback + sret_consumes_int_arg) and
abi_classify_scalar_reg_part into abi.c; routed sysv_x64 / win64_x64 / aapcs64 /
rv64 through them, each keeping only its aggregate/scalar-specific classify_one.
SysV's vararg gp/fp-offset pass preserved verbatim. apple_x64 / apple_arm64 /
aapcs64_windows already delegate to these bases. ABI classification is
byte-identical (calling-convention correctness).

C.2 — soft-float: kit_cg_fp_binop's three f128/double/single blocks and the 8
float<->int conversions now go through api_softfp_binop + api_int_builtin_for_size
+ an (op,width)->suffix table (mirroring the existing api_softfp_cmp). Emitted
compiler-rt libcall names and operand types are byte-identical.

Also: const-corrected the NativeAsmDirectHooks.run_template bound-operand params
to non-const Operand* (they feed the arch inline_bind, matching the original
per-arch convention) — fixes a -Werror discards-qualifiers break.

Diffstat:
Mdoc/plan/CLEANUP-2026-06-09.md | 24+++++++++++++++---------
Msrc/abi/abi.c | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/abi/abi_aapcs64.c | 41++++-------------------------------------
Msrc/abi/abi_internal.h | 25+++++++++++++++++++++++++
Msrc/abi/abi_rv64.c | 40++++------------------------------------
Msrc/abi/abi_sysv_x64.c | 39++++-----------------------------------
Msrc/abi/abi_win64_x64.c | 40++++------------------------------------
Msrc/arch/aa64/native.c | 222++++++++++++++++++-------------------------------------------------------------
Msrc/arch/riscv/native.c | 224+++++++++++++++++++------------------------------------------------------------
Msrc/arch/x64/native.c | 223++++++++++++++++++-------------------------------------------------------------
Msrc/cg/arith.c | 182++++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Msrc/cg/native_asm.c | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/cg/native_asm.h | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
13 files changed, 619 insertions(+), 747 deletions(-)

diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md @@ -100,13 +100,19 @@ The core of "use the abstraction, don't bypass it." Grouped by theme; each bulle confirmed finding. ### A. Inline-asm orchestration triplicated (med, 4) -- [ ] **A.1** `aa_/x64_/rv_direct_asm_block` (`aa64/native.c:4711`, `x64/native.c:4383`, - `riscv/native.c:4083`) are ~170-line line-for-line copies on top of the *already-shared* - `cg/native_asm.c` primitives, and already drifting (3 different frame-slot conditions; - rv64 has an extra output-staging branch). *Fix:* hoist - `native_asm_bind_direct_operands(d, dir, constraints, n, ops, masks, bound[])` + one - shared driver into `cg/native_asm.c`; each backend keeps only its register table + a - thin vtable. (Single agent owns all 4 files.) +- [x] **A.1** `aa_/x64_/rv_direct_asm_block` (`aa64/native.c:4711`, `x64/native.c:4383`, + `riscv/native.c:4083`) were ~170-line copies on top of the *already-shared* + `cg/native_asm.c` primitives. DONE: hoisted `native_asm_bind_direct_operands` + the shared + driver + `NativeAsmDirectHooks` into `cg/native_asm.{c,h}`; each backend's `*_direct_asm_block` + is now a thin wrapper supplying scratch masks / OPK constants / a panic + a few adapters + (−222 LOC × 3 native.c). The direct copies turned out **not** drifted (only the scratch-mask + seed differed); behavior preserved, verified by inline-asm × 3 / toy / smoke. +- [ ] **A.2 (surfaced by A.1)** the *optimizer-path* hooks `aa_/x64_/rv_asm_block_native` + (`aa64/native.c:~4869`, `x64/native.c:~4000`, `riscv/native.c:~3753`) are the *genuinely* + drifted triplication: rv64's `rv_asm_block_native` carries an extra `staged_outs[i]=1` branch + (`riscv/native.c:~3789`) and the input loops have differing frame-slot scratch-load + conditions. Left verbatim by A.1. *Fix:* a second shared driver (or extend the direct one) + for the optimizer path — and reconcile/flag the rv64 staging drift (possible latent bug). ### B. Per-arch codegen plumbing duplicated (med, 7) - [ ] **B.1** x64 callee-save offset formula open-coded 5+ times; collect+reverse-restore @@ -122,11 +128,11 @@ confirmed finding. across arches. *Fix:* share via `src/arch/registry` / `cg/native_direct_target`. ### C. ABI / soft-float / reloc lowering hand-copied (med→low, 7) -- [ ] **C.1** `compute_func_info` driver + `classify_one` dispatch + single-register scalar +- [x] **C.1** `compute_func_info` driver + `classify_one` dispatch + single-register scalar tail near-identical across all 4 ABI vtable TUs (`src/abi/abi_sysv_x64.c:184-202,218-279` + siblings). *Fix:* `abi_compute_func_info_generic(classify_one cb, sret_consumes_int)` + `abi_classify_scalar_reg_part` in `abi.c`; each TU keeps only aggregate/scalar specifics. -- [ ] **C.2** `kit_cg_fp_binop` + 8 float↔int conversions open-code 16 width→type ternaries +- [x] **C.2** `kit_cg_fp_binop` + 8 float↔int conversions open-code 16 width→type ternaries (`src/cg/arith.c:1253-1704`) while `api_softfp_cmp` proves the parameterized form. *Fix:* `api_softfp_binop` mirroring `api_softfp_cmp` + an `(op,width)→suffix` table. - [ ] **C.3** RV B/J/S immediate bit-scramble reimplemented 3×. *Fix:* one shared encoder. diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -182,6 +182,59 @@ void abi_classify_int128_pair(TargetABI* a, ABIArgInfo* out) { out->indirect_align = 0; } +/* The single-register scalar tail shared by every per-ABI classify_scalar: a + * DIRECT argument with exactly one register part carrying the whole scalar. + * `is_fp` selects the part class; the caller owns the FP-eligibility decision. */ +void abi_classify_scalar_reg_part(TargetABI* a, ABIArgInfo* out, ABITypeInfo ti, + int is_fp) { + out->kind = ABI_ARG_DIRECT; + out->flags = ABI_AF_NONE; + out->indirect_align = 0; + + ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); + memset(parts, 0, sizeof *parts); + parts->cls = is_fp ? ABI_CLASS_FP : ABI_CLASS_INT; + parts->loc = ABI_LOC_REG; + parts->size = ti.size; + parts->align = ti.align; + parts->src_offset = 0; + + out->parts = parts; + out->nparts = 1; +} + +/* Generic compute_func_info scaffold shared by the per-ABI vtables. Classifies + * the result then every parameter through `classify_one`, filling the shared + * has_sret / sret_consumes_int_arg / variadic / nparams fields. The per-ABI + * vararg metadata pass (SysV-x64 gp/fp offsets) runs in the caller on the + * returned info; all vararg_* fields are left zero here. */ +ABIFuncInfo* abi_compute_func_info_generic(TargetABI* a, KitCgTypeId fn, + ABIClassifyOneFn classify_one, + int sret_consumes_int_arg) { + ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); + const CgType* fnty = cg_type_get(a->c, fn); + memset(info, 0, sizeof *info); + + classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); + info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; + info->sret_consumes_int_arg = + (sret_consumes_int_arg && info->has_sret) ? 1 : 0; + info->variadic = fnty->func.abi_variadic; + + info->nparams = (u16)fnty->func.nparams; + if (fnty->func.nparams) { + ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); + memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); + for (u32 i = 0; i < fnty->func.nparams; ++i) { + classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); + } + info->params = arr; + } else { + info->params = NULL; + } + return info; +} + /* ---- function classification (vtabled) ---- */ const ABIFuncInfo* abi_cg_func_info(TargetABI* a, KitCgTypeId fn_type) { diff --git a/src/abi/abi_aapcs64.c b/src/abi/abi_aapcs64.c @@ -24,20 +24,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { abi_classify_int128_pair(a, out); return; } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->indirect_align = 0; - - ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); - memset(parts, 0, sizeof *parts); - parts->cls = (ti.scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; - parts->loc = ABI_LOC_REG; - parts->size = ti.size; - parts->align = ti.align; - parts->src_offset = 0; - - out->parts = parts; - out->nparts = 1; + abi_classify_scalar_reg_part(a, out, ti, ti.scalar_kind == ABI_SC_FLOAT); } static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, @@ -101,30 +88,10 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, /* Non-static so apple_arm64_compute_func_info can delegate to it during * the Phase 1 alias period — see abi_apple_arm64.c. */ ABIFuncInfo* aapcs64_compute_func_info(TargetABI* a, KitCgTypeId fn) { - ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); - const CgType* fnty = cg_type_get(a->c, fn); - memset(info, 0, sizeof *info); - - classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); - info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; /* AArch64 returns the sret pointer in the dedicated x8 register, so it never - * consumes an x0..x7 argument slot. (memset above already cleared the field; - * set explicitly for documentation.) */ - info->sret_consumes_int_arg = 0; - info->variadic = fnty->func.abi_variadic; - - info->nparams = (u16)fnty->func.nparams; - if (fnty->func.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); - for (u32 i = 0; i < fnty->func.nparams; ++i) { - classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); - } - info->params = arr; - } else { - info->params = NULL; - } - return info; + * consumes an x0..x7 argument slot: sret_consumes_int_arg stays 0. */ + return abi_compute_func_info_generic(a, fn, classify_one, + /*sret_consumes_int_arg=*/0); } const ABIVtable aapcs64_vtable = { diff --git a/src/abi/abi_internal.h b/src/abi/abi_internal.h @@ -77,4 +77,29 @@ struct TargetABI { void abi_classify_void(ABIArgInfo* out); void abi_classify_int128_pair(TargetABI* a, ABIArgInfo* out); +/* The single-register scalar tail shared by every per-ABI classify_scalar: + * a DIRECT argument with one register part carrying the whole scalar. `is_fp` + * selects ABI_CLASS_FP vs ABI_CLASS_INT for the part (the per-ABI caller has + * already applied its own FP-eligibility rule). */ +void abi_classify_scalar_reg_part(TargetABI* a, ABIArgInfo* out, ABITypeInfo ti, + int is_fp); + +/* The per-argument dispatch each ABI plugs into the generic driver: classify a + * single function argument/result `t` into `out` (`is_return` chooses + * sret vs byval for indirect aggregates). */ +typedef void (*ABIClassifyOneFn)(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, + int is_return); + +/* Generic compute_func_info scaffold shared by the per-ABI vtables: allocate + * the ABIFuncInfo, classify the result and every parameter through + * `classify_one`, and fill the shared has_sret / variadic / nparams fields. + * `sret_consumes_int_arg` is stored verbatim into ABIFuncInfo.has_sret's + * companion flag (ABIs that return the sret pointer in a dedicated register + * pass 0; ABIs that consume the first integer arg slot pass has_sret-derived 1 + * by setting this to nonzero). The ABI-specific vararg metadata pass (SysV-x64) + * runs in the caller on the returned info. */ +ABIFuncInfo* abi_compute_func_info_generic(TargetABI* a, KitCgTypeId fn, + ABIClassifyOneFn classify_one, + int sret_consumes_int_arg); + #endif diff --git a/src/abi/abi_rv64.c b/src/abi/abi_rv64.c @@ -162,20 +162,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out) { out->indirect_align = 0; return; } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->indirect_align = 0; - - ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); - memset(parts, 0, sizeof *parts); - parts->cls = fp_part ? ABI_CLASS_FP : ABI_CLASS_INT; - parts->loc = ABI_LOC_REG; - parts->size = ti.size; - parts->align = ti.align; - parts->src_offset = 0; - - out->parts = parts; - out->nparts = 1; + abi_classify_scalar_reg_part(a, out, ti, fp_part); } static u32 riscv32_scalar_split_lane_size(TargetABI* a, KitCgTypeId t) { @@ -294,29 +281,10 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, } static ABIFuncInfo* riscv_compute_func_info(TargetABI* a, KitCgTypeId fn) { - ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); - const CgType* fnty = cg_type_get(a->c, fn); - memset(info, 0, sizeof *info); - - classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); - info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; /* RISC-V passes the sret pointer in a0 (the first integer arg register), - * consuming that slot. */ - info->sret_consumes_int_arg = info->has_sret; - info->variadic = fnty->func.abi_variadic; - - info->nparams = (u16)fnty->func.nparams; - if (fnty->func.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); - for (u32 i = 0; i < fnty->func.nparams; ++i) { - classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); - } - info->params = arr; - } else { - info->params = NULL; - } - return info; + * consuming that slot, so sret_consumes_int_arg follows has_sret. */ + return abi_compute_func_info_generic(a, fn, classify_one, + /*sret_consumes_int_arg=*/1); } const ABIVtable rv64_vtable = { diff --git a/src/abi/abi_sysv_x64.c b/src/abi/abi_sysv_x64.c @@ -33,20 +33,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, return; } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->indirect_align = 0; - - ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); - memset(parts, 0, sizeof *parts); - parts->cls = (ti.scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; - parts->loc = ABI_LOC_REG; - parts->size = ti.size; - parts->align = ti.align; - parts->src_offset = 0; - - out->parts = parts; - out->nparts = 1; + abi_classify_scalar_reg_part(a, out, ti, ti.scalar_kind == ABI_SC_FLOAT); } typedef enum SysVClass { @@ -216,28 +203,10 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, (SYSV_X64_FP_BASE_OFFSET + SYSV_X64_FP_REG_COUNT * SYSV_X64_FP_SLOT_SIZE) static ABIFuncInfo* sysv_x64_compute_func_info(TargetABI* a, KitCgTypeId fn) { - ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); - const CgType* fnty = cg_type_get(a->c, fn); - memset(info, 0, sizeof *info); - - classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); - info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; /* SysV-x64 passes the sret pointer in rdi (the first integer arg register), - * consuming that slot. */ - info->sret_consumes_int_arg = info->has_sret; - info->variadic = fnty->func.abi_variadic; - - info->nparams = (u16)fnty->func.nparams; - if (fnty->func.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); - for (u32 i = 0; i < fnty->func.nparams; ++i) { - classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); - } - info->params = arr; - } else { - info->params = NULL; - } + * consuming that slot, so sret_consumes_int_arg follows has_sret. */ + ABIFuncInfo* info = abi_compute_func_info_generic(a, fn, classify_one, + /*sret_consumes_int_arg=*/1); /* Variadic register-save-area offsets at function entry. Counts the * GP/FP register slots consumed by the fixed (named) parameters; va_start diff --git a/src/abi/abi_win64_x64.c b/src/abi/abi_win64_x64.c @@ -61,20 +61,7 @@ static void classify_scalar(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, return; } - out->kind = ABI_ARG_DIRECT; - out->flags = ABI_AF_NONE; - out->indirect_align = 0; - - ABIArgPart* parts = arena_new(a->c->tu, ABIArgPart); - memset(parts, 0, sizeof *parts); - parts->cls = (ti.scalar_kind == ABI_SC_FLOAT) ? ABI_CLASS_FP : ABI_CLASS_INT; - parts->loc = ABI_LOC_REG; - parts->size = ti.size; - parts->align = ti.align; - parts->src_offset = 0; - - out->parts = parts; - out->nparts = 1; + abi_classify_scalar_reg_part(a, out, ti, ti.scalar_kind == ABI_SC_FLOAT); } static void classify_aggregate(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, @@ -131,29 +118,10 @@ static void classify_one(TargetABI* a, KitCgTypeId t, ABIArgInfo* out, } static ABIFuncInfo* win64_x64_compute_func_info(TargetABI* a, KitCgTypeId fn) { - ABIFuncInfo* info = arena_new(a->c->tu, ABIFuncInfo); - const CgType* fnty = cg_type_get(a->c, fn); - memset(info, 0, sizeof *info); - - classify_one(a, cg_func_ret_type(fnty), &info->ret, /*is_return=*/1); - info->has_sret = (info->ret.kind == ABI_ARG_INDIRECT) ? 1 : 0; /* Win64 passes the sret pointer in rcx (the first integer arg register), - * consuming that slot. */ - info->sret_consumes_int_arg = info->has_sret; - info->variadic = fnty->func.abi_variadic; - - info->nparams = (u16)fnty->func.nparams; - if (fnty->func.nparams) { - ABIArgInfo* arr = arena_array(a->c->tu, ABIArgInfo, fnty->func.nparams); - memset(arr, 0, sizeof(ABIArgInfo) * fnty->func.nparams); - for (u32 i = 0; i < fnty->func.nparams; ++i) { - classify_one(a, fnty->func.params[i].type, &arr[i], /*is_return=*/0); - } - info->params = arr; - } else { - info->params = NULL; - } - return info; + * consuming that slot, so sret_consumes_int_arg follows has_sret. */ + return abi_compute_func_info_generic(a, fn, classify_one, + /*sret_consumes_int_arg=*/1); } const ABIVtable win64_x64_vtable = { diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -4566,16 +4566,7 @@ AA_UNUSED_FN static Reg aa_asm_alloc_reg(NativeDirectTarget* d, return REG_NONE; } -static int aa_asm_resolve_pin_or_panic(NativeDirectTarget* d, Sym reg, - const char* constraint, - NativeAsmRegPin* pin) { - NativeAsmRegPinStatus st = - native_asm_resolve_pin(d->native, reg, constraint, pin); - if (st == NATIVE_ASM_REG_PIN_ABSENT) return 0; - if (st != NATIVE_ASM_REG_PIN_OK) - aa_asm_panic(d, native_asm_pin_status_message(st)); - return 1; -} +/* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */ AA_UNUSED_FN static void aa_direct_load_operand_to_reg(NativeDirectTarget* d, Operand op, @@ -4695,175 +4686,60 @@ AA_UNUSED_FN static AAAsmSavedClobber* aa_asm_save_callee_clobbers( return saved; } +/* Hook adapters that bridge the arch-typed save/restore + assembler entry to + * the shared NativeAsmDirectHooks signatures (which speak NativeDirectTarget + + * an opaque save record). */ +static void* aa_asm_hook_save_callee_clobbers(NativeDirectTarget* d, + u32 int_mask, u32 fp_mask, + u32* nsaved_out) { + return aa_asm_save_callee_clobbers(aa_of(d->native), int_mask, fp_mask, + nsaved_out); +} +static void aa_asm_hook_restore_one(NativeDirectTarget* d, void* saved, + u32 idx) { + aa_asm_restore_one(aa_of(d->native), &((AAAsmSavedClobber*)saved)[idx]); +} +static void aa_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* bound_outs, + const AsmConstraint* ins, u32 nin, + Operand* bound_ins, + const Sym* clobbers, u32 nclob) { + AA64Asm* a = aa64_asm_open(d->base.c); + aa64_inline_bind(a, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + aa64_asm_run_template(a, d->native->mc, tmpl); + aa64_asm_close(a); +} + static void aa_direct_asm_block(NativeDirectTarget* d, const char* tmpl, const AsmConstraint* outs, u32 nout, Operand* out_ops, const AsmConstraint* ins, u32 nin, const Operand* in_ops, const Sym* clobbers, u32 nclob, u32 clobber_abi_sets) { - Operand* bound_outs = - nout ? arena_zarray(d->base.c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(d->base.c->tu, Operand, nin) : NULL; - u32 clob_int, clob_fp, abi_int, abi_fp, used_int, used_fp; - AAAsmSavedClobber* saved; - u32 nsaved; - AA64Asm* a; - - aa_asm_clobber_masks(d->base.c, d->loc, clobbers, nclob, &clob_int, &clob_fp); - native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp); - clob_int |= abi_int; - clob_fp |= abi_fp; - used_int = clob_int | (1u << AA_TMP0) | (1u << AA_TMP1) | (1u << 18u) | - (1u << AA_FP) | (1u << AA_LR) | (1u << AA_SP); - used_fp = clob_fp | (1u << 20u) | (1u << 21u); - - for (u32 i = 0; i < nout; ++i) { - const char* body = native_asm_constraint_body(outs[i].str); - NativeAsmRegPin pin; - if (aa_asm_resolve_pin_or_panic(d, outs[i].reg, outs[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - aa_asm_bound_reg(&bound_outs[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, outs[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : aa_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - aa_asm_bound_reg(&bound_outs[i], type, info.cls, reg); - } else if (body[0] == 'm') { - Reg reg = aa_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; - aa_asm_bound_mem(&bound_outs[i], type, reg); - } else { - aa_asm_panic(d, "unsupported output constraint"); - } - } - } - - for (u32 i = 0; i < nin; ++i) { - const char* body = native_asm_constraint_body(ins[i].str); - int matched = native_asm_match_index(body); - if (matched >= 0) { - if ((u32)matched >= nout) - aa_asm_panic(d, "matching constraint out of range"); - if (native_asm_constraint_early(outs[matched].str)) - aa_asm_panic(d, "matching input names early-clobber output"); - if (bound_outs[matched].kind != AA64_INLINE_OPK_REG) - aa_asm_panic(d, "matching constraint requires register output"); - bound_ins[i] = bound_outs[matched]; - continue; - } - NativeAsmRegPin pin; - if (aa_asm_resolve_pin_or_panic(d, ins[i].reg, ins[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - aa_asm_bound_reg(&bound_ins[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, ins[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : aa_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - aa_asm_bound_reg(&bound_ins[i], type, info.cls, reg); - } else if (body[0] == 'i') { - if (in_ops[i].kind != OPK_IMM) - aa_asm_panic(d, "immediate constraint requires immediate operand"); - bound_ins[i] = in_ops[i]; - } else if (body[0] == 'm') { - Reg reg = aa_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; - aa_asm_bound_mem(&bound_ins[i], type, reg); - } else { - aa_asm_panic(d, "unsupported input constraint"); - } - } - } - - saved = - aa_asm_save_callee_clobbers(aa_of(d->native), clob_int, clob_fp, &nsaved); - for (u32 i = 0; i < nout; ++i) { - if (bound_outs[i].kind == AA64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_outs[i].pad[0] == AA64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - if (outs[i].dir == KIT_CG_ASM_INOUT) { - aa_direct_load_operand_to_reg( - d, out_ops[i], - native_loc_reg(bound_outs[i].type, cls, - (Reg)bound_outs[i].v.local)); - } - } else if (bound_outs[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_outs[i].v.ind.base); - aa_direct_load_address_to_reg(d, out_ops[i], loc); - } - } - for (u32 i = 0; i < nin; ++i) { - if (bound_ins[i].kind == AA64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_ins[i].pad[0] == AA64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - aa_direct_load_operand_to_reg( - d, in_ops[i], - native_loc_reg(bound_ins[i].type, cls, (Reg)bound_ins[i].v.local)); - } else if (bound_ins[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_ins[i].v.ind.base); - aa_direct_load_address_to_reg(d, in_ops[i], loc); - } - } - a = aa64_asm_open(d->base.c); - aa64_inline_bind(a, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - aa64_asm_run_template(a, d->native->mc, tmpl); - aa64_asm_close(a); - - for (u32 i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (bound_outs[i].kind != AA64_INLINE_OPK_REG) continue; - cls = bound_outs[i].pad[0] == AA64_INLINE_OPCLS_FP ? NATIVE_REG_FP - : NATIVE_REG_INT; - src = native_loc_reg(bound_outs[i].type, cls, (Reg)bound_outs[i].v.local); - aa_direct_store_reg_to_operand(d, out_ops[i], src); - } - for (u32 i = nsaved; i > 0; --i) - aa_asm_restore_one(aa_of(d->native), &saved[i - 1u]); + static const NativeAsmDirectHooks hooks = { + /* Reserve the two emit scratch regs, x18 (platform reg), fp/lr/sp. */ + .scratch_int = (1u << AA_TMP0) | (1u << AA_TMP1) | (1u << 18u) | + (1u << AA_FP) | (1u << AA_LR) | (1u << AA_SP), + .scratch_fp = (1u << 20u) | (1u << 21u), + .opk_reg = AA64_INLINE_OPK_REG, + .opcls_fp = AA64_INLINE_OPCLS_FP, + .panic = aa_asm_panic, + .bound_reg = aa_asm_bound_reg, + .bound_mem = aa_asm_bound_mem, + .alloc_reg = aa_asm_alloc_reg, + .clobber_masks = aa_asm_clobber_masks, + .save_callee_clobbers = aa_asm_hook_save_callee_clobbers, + .restore_one = aa_asm_hook_restore_one, + .load_operand_to_reg = aa_direct_load_operand_to_reg, + .load_address_to_reg = aa_direct_load_address_to_reg, + .store_reg_to_operand = aa_direct_store_reg_to_operand, + .run_template = aa_asm_hook_run_template, + }; + native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, + in_ops, clobbers, nclob, clobber_abi_sets, + &hooks); } /* ---- NativeTarget (optimizer) asm hook ---- diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -3409,16 +3409,7 @@ static void rv_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, } } -static int rv_asm_resolve_pin_or_panic(NativeDirectTarget* d, Sym reg, - const char* constraint, - NativeAsmRegPin* pin) { - NativeAsmRegPinStatus st = - native_asm_resolve_pin(d->native, reg, constraint, pin); - if (st == NATIVE_ASM_REG_PIN_ABSENT) return 0; - if (st != NATIVE_ASM_REG_PIN_OK) - rv_asm_panic(d, native_asm_pin_status_message(st)); - return 1; -} +/* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */ /* Pick a free register from the arch's caller-saved allocable pools for an * asm operand the direct path must self-allocate. */ @@ -4091,174 +4082,63 @@ static void rv_va_copy_(NativeDirectTarget* d, Operand dst, Operand src) { rv_va_copy_core(a, dst_ap, src_ap); } +/* Hook adapters bridging the arch-typed save/restore + assembler entry to the + * shared NativeAsmDirectHooks signatures. */ +static void* rv_asm_hook_save_callee_clobbers(NativeDirectTarget* d, + u32 int_mask, u32 fp_mask, + u32* nsaved_out) { + return rv_asm_save_callee_clobbers(rv_of(d->native), int_mask, fp_mask, + nsaved_out); +} +static void rv_asm_hook_restore_one(NativeDirectTarget* d, void* saved, + u32 idx) { + rv_asm_restore_one(rv_of(d->native), &((RvAsmSavedClobber*)saved)[idx]); +} +static void rv_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* bound_outs, + const AsmConstraint* ins, u32 nin, + Operand* bound_ins, + const Sym* clobbers, u32 nclob) { + Rv64Asm* asmh = rv64_asm_open(d->base.c); + rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + rv64_asm_run_template(asmh, d->native->mc, tmpl); + rv64_asm_close(asmh); +} + static void rv_direct_asm_block(NativeDirectTarget* d, const char* tmpl, const AsmConstraint* outs, u32 nout, Operand* out_ops, const AsmConstraint* ins, u32 nin, const Operand* in_ops, const Sym* clobbers, u32 nclob, u32 clobber_abi_sets) { - RvNativeTarget* a = rv_of(d->native); - Compiler* c = d->base.c; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u32 clob_int, clob_fp, abi_int, abi_fp, used_int, used_fp; - RvAsmSavedClobber* saved; - u32 nsaved, i; - Rv64Asm* asmh; - - rv_asm_clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp); - native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp); - clob_int |= abi_int; - clob_fp |= abi_fp; - /* Reserve emit scratch (t0/t1/t2/t3), sp/gp/tp/zero/ra and the frame pointer - * so the operand allocator never hands them out. */ - used_int = clob_int | (1u << RV_ZERO) | (1u << RV_RA) | (1u << RV_SP) | - (1u << RV_GP) | (1u << RV_TP) | (1u << RV_TMP0) | (1u << RV_TMP1) | - (1u << RV_TMP2) | (1u << RV_TMP3) | (1u << RV_S0); - used_fp = - clob_fp | (1u << RV_FTMP0) | (1u << RV_FTMP1) | (1u << 2u) | (1u << 3u); - - for (i = 0; i < nout; ++i) { - const char* body = native_asm_constraint_body(outs[i].str); - KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; - NativeAsmRegPin pin; - if (rv_asm_resolve_pin_or_panic(d, outs[i].reg, outs[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - rv_asm_bound_reg(&bound_outs[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, outs[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : rv_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - rv_asm_bound_reg(&bound_outs[i], type, info.cls, reg); - } else if (body[0] == 'm') { - Reg reg = rv_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - rv_asm_bound_mem(&bound_outs[i], type, reg); - } else { - rv_asm_panic(d, "unsupported output constraint"); - } - } - } - - for (i = 0; i < nin; ++i) { - const char* body = native_asm_constraint_body(ins[i].str); - int matched = native_asm_match_index(body); - KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; - if (matched >= 0) { - if ((u32)matched >= nout) - rv_asm_panic(d, "matching constraint out of range"); - if (native_asm_constraint_early(outs[matched].str)) - rv_asm_panic(d, "matching input names early-clobber output"); - if (bound_outs[matched].kind != RV64_INLINE_OPK_REG) - rv_asm_panic(d, "matching constraint requires register output"); - bound_ins[i] = bound_outs[matched]; - continue; - } - NativeAsmRegPin pin; - if (rv_asm_resolve_pin_or_panic(d, ins[i].reg, ins[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - rv_asm_bound_reg(&bound_ins[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, ins[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : rv_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - rv_asm_bound_reg(&bound_ins[i], type, info.cls, reg); - } else if (body[0] == 'i') { - if (in_ops[i].kind != OPK_IMM) - rv_asm_panic(d, "immediate constraint requires immediate operand"); - bound_ins[i] = in_ops[i]; - } else if (body[0] == 'm') { - Reg reg = rv_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - rv_asm_bound_mem(&bound_ins[i], type, reg); - } else { - rv_asm_panic(d, "unsupported input constraint"); - } - } - } - - saved = rv_asm_save_callee_clobbers(a, clob_int, clob_fp, &nsaved); - for (i = 0; i < nout; ++i) { - if (bound_outs[i].kind == RV64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_outs[i].pad[0] == RV64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - if (outs[i].dir == KIT_CG_ASM_INOUT) { - rv_direct_load_operand_to_reg( - d, out_ops[i], - native_loc_reg(bound_outs[i].type, cls, - (Reg)bound_outs[i].v.local)); - } - } else if (bound_outs[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_outs[i].v.ind.base); - rv_direct_load_address_to_reg(d, out_ops[i], loc); - } - } - for (i = 0; i < nin; ++i) { - if (bound_ins[i].kind == RV64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_ins[i].pad[0] == RV64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - rv_direct_load_operand_to_reg( - d, in_ops[i], - native_loc_reg(bound_ins[i].type, cls, (Reg)bound_ins[i].v.local)); - } else if (bound_ins[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_ins[i].v.ind.base); - rv_direct_load_address_to_reg(d, in_ops[i], loc); - } - } - asmh = rv64_asm_open(c); - rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - rv64_asm_run_template(asmh, d->native->mc, tmpl); - rv64_asm_close(asmh); - - for (i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (bound_outs[i].kind != RV64_INLINE_OPK_REG) continue; - cls = bound_outs[i].pad[0] == RV64_INLINE_OPCLS_FP ? NATIVE_REG_FP - : NATIVE_REG_INT; - src = native_loc_reg(bound_outs[i].type, cls, (Reg)bound_outs[i].v.local); - rv_direct_store_reg_to_operand(d, out_ops[i], src); - } - for (i = nsaved; i > 0; --i) rv_asm_restore_one(a, &saved[i - 1u]); + static const NativeAsmDirectHooks hooks = { + /* Reserve emit scratch (t0/t1/t2/t3), sp/gp/tp/zero/ra and the frame + * pointer so the operand allocator never hands them out. */ + .scratch_int = (1u << RV_ZERO) | (1u << RV_RA) | (1u << RV_SP) | + (1u << RV_GP) | (1u << RV_TP) | (1u << RV_TMP0) | + (1u << RV_TMP1) | (1u << RV_TMP2) | (1u << RV_TMP3) | + (1u << RV_S0), + .scratch_fp = + (1u << RV_FTMP0) | (1u << RV_FTMP1) | (1u << 2u) | (1u << 3u), + .opk_reg = RV64_INLINE_OPK_REG, + .opcls_fp = RV64_INLINE_OPCLS_FP, + .panic = rv_asm_panic, + .bound_reg = rv_asm_bound_reg, + .bound_mem = rv_asm_bound_mem, + .alloc_reg = rv_asm_alloc_reg, + .clobber_masks = rv_asm_clobber_masks, + .save_callee_clobbers = rv_asm_hook_save_callee_clobbers, + .restore_one = rv_asm_hook_restore_one, + .load_operand_to_reg = rv_direct_load_operand_to_reg, + .load_address_to_reg = rv_direct_load_address_to_reg, + .store_reg_to_operand = rv_direct_store_reg_to_operand, + .run_template = rv_asm_hook_run_template, + }; + native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, + in_ops, clobbers, nclob, clobber_abi_sets, + &hooks); } static const NativeOps rv_direct_ops = { diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -3692,16 +3692,7 @@ static void x64_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, } } -static int x64_asm_resolve_pin_or_panic(NativeDirectTarget* d, Sym reg, - const char* constraint, - NativeAsmRegPin* pin) { - NativeAsmRegPinStatus st = - native_asm_resolve_pin(d->native, reg, constraint, pin); - if (st == NATIVE_ASM_REG_PIN_ABSENT) return 0; - if (st != NATIVE_ASM_REG_PIN_OK) - x64_asm_panic(d, native_asm_pin_status_message(st)); - return 1; -} +/* Pin resolution + panic is the shared native_asm_bind_direct_operands path. */ /* Pick a free register from caller-saved allocable pools for an asm operand the * direct path self-allocates. */ @@ -4380,174 +4371,62 @@ static void x64_va_copy_(NativeDirectTarget* d, Operand dst, Operand src) { x64_va_copy_core(a, dst_ap, src_ap); } +/* Hook adapters bridging the arch-typed save/restore + assembler entry to the + * shared NativeAsmDirectHooks signatures. */ +static void* x64_asm_hook_save_callee_clobbers(NativeDirectTarget* d, + u32 int_mask, u32 fp_mask, + u32* nsaved_out) { + return x64_asm_save_callee_clobbers(x64_of(d->native), int_mask, fp_mask, + nsaved_out); +} +static void x64_asm_hook_restore_one(NativeDirectTarget* d, void* saved, + u32 idx) { + x64_asm_restore_one(x64_of(d->native), &((X64AsmSavedClobber*)saved)[idx]); +} +static void x64_asm_hook_run_template(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* bound_outs, + const AsmConstraint* ins, u32 nin, + Operand* bound_ins, + const Sym* clobbers, u32 nclob) { + X64Asm* asmh = x64_asm_open(d->base.c); + x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + x64_asm_run_template(asmh, d->native->mc, tmpl); + x64_asm_close(asmh); +} + static void x64_direct_asm_block(NativeDirectTarget* d, const char* tmpl, const AsmConstraint* outs, u32 nout, Operand* out_ops, const AsmConstraint* ins, u32 nin, const Operand* in_ops, const Sym* clobbers, u32 nclob, u32 clobber_abi_sets) { - X64NativeTarget* a = x64_of(d->native); - Compiler* c = d->base.c; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u32 clob_int, clob_fp, abi_int, abi_fp, used_int, used_fp; - X64AsmSavedClobber* saved; - u32 nsaved, i; - X64Asm* asmh; - - x64_asm_clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp); - native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp); - clob_int |= abi_int; - clob_fp |= abi_fp; - /* Reserve emit scratch (r10,r11), driver scratch (r8,r9), rax (reserved; - * only self-allocated here when explicitly pinned), sp/bp, and clobbers. */ - used_int = clob_int | (1u << X64_RAX) | (1u << X64_R11) | (1u << X64_RSP) | - (1u << X64_RBP) | (1u << X64_R8) | (1u << X64_R9) | - (1u << X64_R10); - used_fp = clob_fp | (1u << X64_XMM4) | (1u << X64_XMM5) | - (1u << (X64_XMM0 + 14)) | (1u << X64_XMM15); - - for (i = 0; i < nout; ++i) { - const char* body = native_asm_constraint_body(outs[i].str); - KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; - NativeAsmRegPin pin; - if (x64_asm_resolve_pin_or_panic(d, outs[i].reg, outs[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - x64_asm_bound_reg(&bound_outs[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, outs[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : x64_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - x64_asm_bound_reg(&bound_outs[i], type, info.cls, reg); - } else if (body[0] == 'm') { - Reg reg = x64_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - x64_asm_bound_mem(&bound_outs[i], type, reg); - } else { - x64_asm_panic(d, "unsupported output constraint"); - } - } - } - - for (i = 0; i < nin; ++i) { - const char* body = native_asm_constraint_body(ins[i].str); - int matched = native_asm_match_index(body); - KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; - if (matched >= 0) { - if ((u32)matched >= nout) - x64_asm_panic(d, "matching constraint out of range"); - if (native_asm_constraint_early(outs[matched].str)) - x64_asm_panic(d, "matching input names early-clobber output"); - if (bound_outs[matched].kind != X64_INLINE_OPK_REG) - x64_asm_panic(d, "matching constraint requires register output"); - bound_ins[i] = bound_outs[matched]; - continue; - } - NativeAsmRegPin pin; - if (x64_asm_resolve_pin_or_panic(d, ins[i].reg, ins[i].str, &pin)) { - /* GNU local register variable: pin to the named hard register. */ - if (pin.cls == NATIVE_REG_FP) { - used_fp |= 1u << pin.reg; - clob_fp |= 1u << pin.reg; - } else { - used_int |= 1u << pin.reg; - clob_int |= 1u << pin.reg; - } - x64_asm_bound_reg(&bound_ins[i], type, pin.cls, pin.reg); - } else { - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(d->native, ins[i].str, &info)) { - Reg reg = info.fixed_reg != REG_NONE - ? info.fixed_reg - : x64_asm_alloc_reg(d, info.cls, info.allowed_mask, - &used_int, &used_fp); - if (info.cls == NATIVE_REG_FP) { - used_fp |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; - } else { - used_int |= 1u << reg; - if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; - } - x64_asm_bound_reg(&bound_ins[i], type, info.cls, reg); - } else if (body[0] == 'i') { - if (in_ops[i].kind != OPK_IMM) - x64_asm_panic(d, "immediate constraint requires immediate operand"); - bound_ins[i] = in_ops[i]; - } else if (body[0] == 'm') { - Reg reg = x64_asm_alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); - x64_asm_bound_mem(&bound_ins[i], type, reg); - } else { - x64_asm_panic(d, "unsupported input constraint"); - } - } - } - - saved = x64_asm_save_callee_clobbers(a, clob_int, clob_fp, &nsaved); - for (i = 0; i < nout; ++i) { - if (bound_outs[i].kind == X64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_outs[i].pad[0] == X64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - if (outs[i].dir == KIT_CG_ASM_INOUT) { - x64_direct_load_operand_to_reg( - d, out_ops[i], - native_loc_reg(bound_outs[i].type, cls, - (Reg)bound_outs[i].v.local)); - } - } else if (bound_outs[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_outs[i].v.ind.base); - x64_direct_load_address_to_reg(d, out_ops[i], loc); - } - } - for (i = 0; i < nin; ++i) { - if (bound_ins[i].kind == X64_INLINE_OPK_REG) { - NativeAllocClass cls = bound_ins[i].pad[0] == X64_INLINE_OPCLS_FP - ? NATIVE_REG_FP - : NATIVE_REG_INT; - x64_direct_load_operand_to_reg( - d, in_ops[i], - native_loc_reg(bound_ins[i].type, cls, (Reg)bound_ins[i].v.local)); - } else if (bound_ins[i].kind == OPK_INDIRECT) { - NativeLoc loc = - native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, - (Reg)bound_ins[i].v.ind.base); - x64_direct_load_address_to_reg(d, in_ops[i], loc); - } - } - asmh = x64_asm_open(c); - x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - x64_asm_run_template(asmh, d->native->mc, tmpl); - x64_asm_close(asmh); - - for (i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (bound_outs[i].kind != X64_INLINE_OPK_REG) continue; - cls = bound_outs[i].pad[0] == X64_INLINE_OPCLS_FP ? NATIVE_REG_FP - : NATIVE_REG_INT; - src = native_loc_reg(bound_outs[i].type, cls, (Reg)bound_outs[i].v.local); - x64_direct_store_reg_to_operand(d, out_ops[i], src); - } - for (i = nsaved; i > 0; --i) x64_asm_restore_one(a, &saved[i - 1u]); + static const NativeAsmDirectHooks hooks = { + /* Reserve emit scratch (r10,r11), driver scratch (r8,r9), rax (reserved; + * only self-allocated here when explicitly pinned), and sp/bp. */ + .scratch_int = (1u << X64_RAX) | (1u << X64_R11) | (1u << X64_RSP) | + (1u << X64_RBP) | (1u << X64_R8) | (1u << X64_R9) | + (1u << X64_R10), + .scratch_fp = (1u << X64_XMM4) | (1u << X64_XMM5) | + (1u << (X64_XMM0 + 14)) | (1u << X64_XMM15), + .opk_reg = X64_INLINE_OPK_REG, + .opcls_fp = X64_INLINE_OPCLS_FP, + .panic = x64_asm_panic, + .bound_reg = x64_asm_bound_reg, + .bound_mem = x64_asm_bound_mem, + .alloc_reg = x64_asm_alloc_reg, + .clobber_masks = x64_asm_clobber_masks, + .save_callee_clobbers = x64_asm_hook_save_callee_clobbers, + .restore_one = x64_asm_hook_restore_one, + .load_operand_to_reg = x64_direct_load_operand_to_reg, + .load_address_to_reg = x64_direct_load_address_to_reg, + .store_reg_to_operand = x64_direct_store_reg_to_operand, + .run_template = x64_asm_hook_run_template, + }; + native_asm_bind_direct_operands(d, tmpl, outs, nout, out_ops, ins, nin, + in_ops, clobbers, nclob, clobber_abi_sets, + &hooks); } static const NativeOps x64_direct_ops = { diff --git a/src/cg/arith.c b/src/cg/arith.c @@ -1241,6 +1241,25 @@ static const char* api_softsf_binop_helper(KitCgFpBinOp op) { return NULL; } +/* Soft-float binary arithmetic via a single libcall `name(a,b)`, both operands + * (and the result) of type `opty`. Consumes the two operands on the stack and + * pushes the result. Shared by the f128 (tf), soft-double (df) and soft-single + * (sf) paths — only the helper name and operand type differ; the runtime ABI + * (two same-type args, same-type result) is width-neutral. `what` names the + * width in the panic raised when `op` has no helper. Mirrors api_softfp_cmp. */ +static void api_softfp_binop(KitCg* g, const char* name, KitCgTypeId opty, + const char* what) { + KitCgTypeId ps[2]; + ApiSValue args[2]; + if (!name) + compiler_panic(g->c, g->cur_loc, "KitCg: unsupported %s binop", what); + args[1] = api_pop(g); + args[0] = api_pop(g); + ps[0] = opty; + ps[1] = opty; + api_runtime_call_values(g, name, opty, ps, 2, args); +} + void api_f128_call_unary(KitCg* g, const char* name, KitCgTypeId ret, KitCgTypeId param) { ApiSValue args[1]; @@ -1253,45 +1272,18 @@ void api_f128_call_unary(KitCg* g, const char* name, KitCgTypeId ret, void kit_cg_fp_binop(KitCg* g, KitCgFpBinOp op, uint32_t flags) { (void)flags; if (api_f128_stack_top(g, 0) || api_f128_stack_top(g, 1)) { - KitCgTypeId f128 = builtin_id(KIT_CG_BUILTIN_F128); - KitCgTypeId ps[2]; - ApiSValue args[2]; - const char* name = api_f128_binop_helper(op); - if (!name) - compiler_panic(g->c, g->cur_loc, "KitCg: unsupported f128 binop"); - args[1] = api_pop(g); - args[0] = api_pop(g); - ps[0] = f128; - ps[1] = f128; - api_runtime_call_values(g, name, f128, ps, 2, args); + api_softfp_binop(g, api_f128_binop_helper(op), + builtin_id(KIT_CG_BUILTIN_F128), "f128"); return; } if (api_soft_double_stack_top(g, 0) || api_soft_double_stack_top(g, 1)) { - KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64); - KitCgTypeId ps[2]; - ApiSValue args[2]; - const char* name = api_softdf_binop_helper(op); - if (!name) - compiler_panic(g->c, g->cur_loc, "KitCg: unsupported soft double binop"); - args[1] = api_pop(g); - args[0] = api_pop(g); - ps[0] = f64; - ps[1] = f64; - api_runtime_call_values(g, name, f64, ps, 2, args); + api_softfp_binop(g, api_softdf_binop_helper(op), + builtin_id(KIT_CG_BUILTIN_F64), "soft double"); return; } if (api_soft_single_stack_top(g, 0) || api_soft_single_stack_top(g, 1)) { - KitCgTypeId f32 = builtin_id(KIT_CG_BUILTIN_F32); - KitCgTypeId ps[2]; - ApiSValue args[2]; - const char* name = api_softsf_binop_helper(op); - if (!name) - compiler_panic(g->c, g->cur_loc, "KitCg: unsupported soft single binop"); - args[1] = api_pop(g); - args[0] = api_pop(g); - ps[0] = f32; - ps[1] = f32; - api_runtime_call_values(g, name, f32, ps, 2, args); + api_softfp_binop(g, api_softsf_binop_helper(op), + builtin_id(KIT_CG_BUILTIN_F32), "soft single"); return; } api_cg_binop(g, api_map_fp_binop(op), 0); @@ -1525,19 +1517,69 @@ void kit_cg_fptrunc(KitCg* g, KitCgTypeId dst) { api_cg_convert_kind(g, dst, CV_FTRUNC); } +/* The integer builtin a soft-float int<->float conversion uses for an operand + * whose ABI size is `sz` bytes: i128 (>8), i64 (>4), else i32. Shared by all + * eight conversion blocks (the same width ladder appeared in each). */ +static KitCgTypeId api_int_builtin_for_size(u32 sz) { + return sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) + : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) + : builtin_id(KIT_CG_BUILTIN_I32)); +} + +/* The compiler-rt integer-width suffix matching api_int_builtin_for_size: + * "ti" (i128), "di" (i64), "si" (i32). */ +static const char* api_int_suffix_for_size(u32 sz) { + return sz > 8 ? "ti" : (sz > 4 ? "di" : "si"); +} + +/* The four width-laddered soft-float int<->float conversion families. The + * compiler-rt name is a fixed prefix, the width-derived integer suffix + * (api_int_suffix_for_size) and a fixed float suffix ("tf" for f128, "df" for + * soft double); int->float places the integer suffix before the float suffix, + * float->int after it. */ +typedef enum ApiFpConvOp { + API_FPCONV_SINT_TO_FLOAT, /* __float<int><flt> */ + API_FPCONV_UINT_TO_FLOAT, /* __floatun<int><flt> */ + API_FPCONV_FLOAT_TO_SINT, /* __fix<flt><int> */ + API_FPCONV_FLOAT_TO_UINT, /* __fixuns<flt><int> */ +} ApiFpConvOp; + +typedef struct FpConvDesc { + const char* prefix; + int int_first; /* 1: prefix+int+flt (int->float); 0: prefix+flt+int */ +} FpConvDesc; + +static const FpConvDesc kFpConvTable[] = { + [API_FPCONV_SINT_TO_FLOAT] = {"__float", 1}, + [API_FPCONV_UINT_TO_FLOAT] = {"__floatun", 1}, + [API_FPCONV_FLOAT_TO_SINT] = {"__fix", 0}, + [API_FPCONV_FLOAT_TO_UINT] = {"__fixuns", 0}, +}; + +/* Build the compiler-rt conversion libcall name for `op` with the given integer + * width (`sz` bytes) and float suffix `flt` ("tf"/"df") into `buf`. The result + * is byte-identical to the names the eight blocks previously open-coded. */ +static void api_fp_conv_name(char* buf, size_t cap, ApiFpConvOp op, + const char* flt, u32 sz) { + const FpConvDesc* d = &kFpConvTable[op]; + const char* is = api_int_suffix_for_size(sz); + if (d->int_first) + snprintf(buf, cap, "%s%s%s", d->prefix, is, flt); + else + snprintf(buf, cap, "%s%s%s", d->prefix, flt, is); +} + void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { (void)rounding; if (api_is_f128_type(g->c, resolve_type(g->c, dst))) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty); - KitCgTypeId pty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__floattitf" : (sz > 4 ? "__floatditf" : "__floatsitf"); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "tf", sz); api_push(g, v); - api_f128_call_unary(g, name, resolve_type(g->c, dst), pty); + api_f128_call_unary(g, name, resolve_type(g->c, dst), + api_int_builtin_for_size(sz)); return; } /* signed int -> soft double: __floatsidf (i32) / __floatdidf (i64). */ @@ -1545,13 +1587,11 @@ void kit_cg_sint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty); - KitCgTypeId pty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__floattidf" : (sz > 4 ? "__floatdidf" : "__floatsidf"); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_SINT_TO_FLOAT, "df", sz); api_push(g, v); - api_f128_call_unary(g, name, resolve_type(g->c, dst), pty); + api_f128_call_unary(g, name, resolve_type(g->c, dst), + api_int_builtin_for_size(sz)); return; } /* signed split-i64 -> hardware single float: use __floatdisf. */ @@ -1575,13 +1615,11 @@ void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty); - KitCgTypeId pty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__floatuntitf" : (sz > 4 ? "__floatunditf" : "__floatunsitf"); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "tf", sz); api_push(g, v); - api_f128_call_unary(g, name, resolve_type(g->c, dst), pty); + api_f128_call_unary(g, name, resolve_type(g->c, dst), + api_int_builtin_for_size(sz)); return; } /* unsigned int -> soft double: __floatunsidf (i32) / __floatundidf (i64). */ @@ -1589,13 +1627,11 @@ void kit_cg_uint_to_float(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { ApiSValue v = api_pop(g); KitCgTypeId sty = api_unalias_type(g->c, api_sv_type(&v)); u32 sz = (u32)abi_cg_sizeof(g->c->abi, sty); - KitCgTypeId pty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__floatuntidf" : (sz > 4 ? "__floatundidf" : "__floatunsidf"); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_UINT_TO_FLOAT, "df", sz); api_push(g, v); - api_f128_call_unary(g, name, resolve_type(g->c, dst), pty); + api_f128_call_unary(g, name, resolve_type(g->c, dst), + api_int_builtin_for_size(sz)); return; } /* unsigned i64 -> hardware single float: __floatundisf. */ @@ -1618,11 +1654,9 @@ void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { if (api_f128_stack_top(g, 0)) { KitCgTypeId dty = resolve_type(g->c, dst); u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); - KitCgTypeId rty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__fixtfti" : (sz > 4 ? "__fixtfdi" : "__fixtfsi"); + KitCgTypeId rty = api_int_builtin_for_size(sz); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_SINT, "tf", sz); api_f128_call_unary(g, name, rty, builtin_id(KIT_CG_BUILTIN_F128)); if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC); return; @@ -1632,11 +1666,9 @@ void kit_cg_float_to_sint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { KitCgTypeId dty = resolve_type(g->c, dst); KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64); u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); - KitCgTypeId rty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__fixdfti" : (sz > 4 ? "__fixdfdi" : "__fixdfsi"); + KitCgTypeId rty = api_int_builtin_for_size(sz); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_SINT, "df", sz); api_f128_call_unary(g, name, rty, f64); if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC); return; @@ -1663,11 +1695,9 @@ void kit_cg_float_to_uint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { if (api_f128_stack_top(g, 0)) { KitCgTypeId dty = resolve_type(g->c, dst); u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); - KitCgTypeId rty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__fixunstfti" : (sz > 4 ? "__fixunstfdi" : "__fixunstfsi"); + KitCgTypeId rty = api_int_builtin_for_size(sz); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_UINT, "tf", sz); api_f128_call_unary(g, name, rty, builtin_id(KIT_CG_BUILTIN_F128)); if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC); return; @@ -1677,11 +1707,9 @@ void kit_cg_float_to_uint(KitCg* g, KitCgTypeId dst, KitCgRounding rounding) { KitCgTypeId dty = resolve_type(g->c, dst); KitCgTypeId f64 = builtin_id(KIT_CG_BUILTIN_F64); u32 sz = (u32)abi_cg_sizeof(g->c->abi, dty); - KitCgTypeId rty = sz > 8 ? builtin_id(KIT_CG_BUILTIN_I128) - : (sz > 4 ? builtin_id(KIT_CG_BUILTIN_I64) - : builtin_id(KIT_CG_BUILTIN_I32)); - const char* name = - sz > 8 ? "__fixunsdfti" : (sz > 4 ? "__fixunsdfdi" : "__fixunsdfsi"); + KitCgTypeId rty = api_int_builtin_for_size(sz); + char name[16]; + api_fp_conv_name(name, sizeof name, API_FPCONV_FLOAT_TO_UINT, "df", sz); api_f128_call_unary(g, name, rty, f64); if (rty != dty) api_cg_convert_kind(g, dty, CV_TRUNC); return; diff --git a/src/cg/native_asm.c b/src/cg/native_asm.c @@ -3,6 +3,9 @@ #include "arch/mc.h" #include "asm/asm.h" #include "asm/asm_lex.h" +#include "cg/native_direct_target.h" /* NativeDirectTarget, for the direct binder */ +#include "cg/type.h" /* builtin_id */ +#include "core/arena.h" /* arena_zarray */ #include "core/pool.h" /* pool_slice for native_asm_resolve_pin */ void native_file_scope_asm(NativeTarget* t, const char* src, size_t len) { @@ -202,3 +205,178 @@ const char* native_asm_pin_status_message(NativeAsmRegPinStatus st) { } return "invalid asm register variable"; } + +/* Resolve an operand's explicit hard-register pin, panicking (through the + * arch prefix) on any invalid pin. Returns 1 when a valid pin exists, 0 when + * the operand has no pin -- the same contract the former per-arch + * *_asm_resolve_pin_or_panic helpers had. */ +static int native_asm_direct_resolve_pin(NativeDirectTarget* d, + const NativeAsmDirectHooks* h, Sym reg, + const char* constraint, + NativeAsmRegPin* pin) { + NativeAsmRegPinStatus st = + native_asm_resolve_pin(d->native, reg, constraint, pin); + if (st == NATIVE_ASM_REG_PIN_ABSENT) return 0; + if (st != NATIVE_ASM_REG_PIN_OK) + h->panic(d, native_asm_pin_status_message(st)); + return 1; +} + +void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* out_ops, const AsmConstraint* ins, + u32 nin, const Operand* in_ops, + const Sym* clobbers, u32 nclob, + u32 clobber_abi_sets, + const NativeAsmDirectHooks* h) { + Compiler* c = d->base.c; + Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; + Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; + u32 clob_int, clob_fp, abi_int, abi_fp, used_int, used_fp; + void* saved; + u32 nsaved, i; + + h->clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp); + native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp); + clob_int |= abi_int; + clob_fp |= abi_fp; + used_int = clob_int | h->scratch_int; + used_fp = clob_fp | h->scratch_fp; + + for (i = 0; i < nout; ++i) { + const char* body = native_asm_constraint_body(outs[i].str); + KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type; + NativeAsmRegPin pin; + if (native_asm_direct_resolve_pin(d, h, outs[i].reg, outs[i].str, &pin)) { + /* GNU local register variable: pin to the named hard register. */ + if (pin.cls == NATIVE_REG_FP) { + used_fp |= 1u << pin.reg; + clob_fp |= 1u << pin.reg; + } else { + used_int |= 1u << pin.reg; + clob_int |= 1u << pin.reg; + } + h->bound_reg(&bound_outs[i], type, pin.cls, pin.reg); + } else { + NativeAsmConstraintInfo info; + if (native_asm_constraint_reg_info(d->native, outs[i].str, &info)) { + Reg reg = info.fixed_reg != REG_NONE + ? info.fixed_reg + : h->alloc_reg(d, info.cls, info.allowed_mask, &used_int, + &used_fp); + if (info.cls == NATIVE_REG_FP) { + used_fp |= 1u << reg; + if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; + } else { + used_int |= 1u << reg; + if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; + } + h->bound_reg(&bound_outs[i], type, info.cls, reg); + } else if (body[0] == 'm') { + Reg reg = h->alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); + h->bound_mem(&bound_outs[i], type, reg); + } else { + h->panic(d, "unsupported output constraint"); + } + } + } + + for (i = 0; i < nin; ++i) { + const char* body = native_asm_constraint_body(ins[i].str); + int matched = native_asm_match_index(body); + KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type; + if (matched >= 0) { + if ((u32)matched >= nout) + h->panic(d, "matching constraint out of range"); + if (native_asm_constraint_early(outs[matched].str)) + h->panic(d, "matching input names early-clobber output"); + if (bound_outs[matched].kind != h->opk_reg) + h->panic(d, "matching constraint requires register output"); + bound_ins[i] = bound_outs[matched]; + continue; + } + NativeAsmRegPin pin; + if (native_asm_direct_resolve_pin(d, h, ins[i].reg, ins[i].str, &pin)) { + /* GNU local register variable: pin to the named hard register. */ + if (pin.cls == NATIVE_REG_FP) { + used_fp |= 1u << pin.reg; + clob_fp |= 1u << pin.reg; + } else { + used_int |= 1u << pin.reg; + clob_int |= 1u << pin.reg; + } + h->bound_reg(&bound_ins[i], type, pin.cls, pin.reg); + } else { + NativeAsmConstraintInfo info; + if (native_asm_constraint_reg_info(d->native, ins[i].str, &info)) { + Reg reg = info.fixed_reg != REG_NONE + ? info.fixed_reg + : h->alloc_reg(d, info.cls, info.allowed_mask, &used_int, + &used_fp); + if (info.cls == NATIVE_REG_FP) { + used_fp |= 1u << reg; + if (info.fixed_reg != REG_NONE) clob_fp |= 1u << reg; + } else { + used_int |= 1u << reg; + if (info.fixed_reg != REG_NONE) clob_int |= 1u << reg; + } + h->bound_reg(&bound_ins[i], type, info.cls, reg); + } else if (body[0] == 'i') { + if (in_ops[i].kind != OPK_IMM) + h->panic(d, "immediate constraint requires immediate operand"); + bound_ins[i] = in_ops[i]; + } else if (body[0] == 'm') { + Reg reg = h->alloc_reg(d, NATIVE_REG_INT, 0, &used_int, &used_fp); + h->bound_mem(&bound_ins[i], type, reg); + } else { + h->panic(d, "unsupported input constraint"); + } + } + } + + saved = h->save_callee_clobbers(d, clob_int, clob_fp, &nsaved); + for (i = 0; i < nout; ++i) { + if (bound_outs[i].kind == h->opk_reg) { + NativeAllocClass cls = + bound_outs[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT; + if (outs[i].dir == KIT_CG_ASM_INOUT) { + h->load_operand_to_reg( + d, out_ops[i], + native_loc_reg(bound_outs[i].type, cls, + (Reg)bound_outs[i].v.local)); + } + } else if (bound_outs[i].kind == OPK_INDIRECT) { + NativeLoc loc = + native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, + (Reg)bound_outs[i].v.ind.base); + h->load_address_to_reg(d, out_ops[i], loc); + } + } + for (i = 0; i < nin; ++i) { + if (bound_ins[i].kind == h->opk_reg) { + NativeAllocClass cls = + bound_ins[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT; + h->load_operand_to_reg( + d, in_ops[i], + native_loc_reg(bound_ins[i].type, cls, (Reg)bound_ins[i].v.local)); + } else if (bound_ins[i].kind == OPK_INDIRECT) { + NativeLoc loc = + native_loc_reg(builtin_id(KIT_CG_BUILTIN_I64), NATIVE_REG_INT, + (Reg)bound_ins[i].v.ind.base); + h->load_address_to_reg(d, in_ops[i], loc); + } + } + + h->run_template(d, tmpl, outs, nout, bound_outs, ins, nin, bound_ins, + clobbers, nclob); + + for (i = 0; i < nout; ++i) { + NativeAllocClass cls; + NativeLoc src; + if (bound_outs[i].kind != h->opk_reg) continue; + cls = bound_outs[i].pad[0] == h->opcls_fp ? NATIVE_REG_FP : NATIVE_REG_INT; + src = native_loc_reg(bound_outs[i].type, cls, (Reg)bound_outs[i].v.local); + h->store_reg_to_operand(d, out_ops[i], src); + } + for (i = nsaved; i > 0; --i) h->restore_one(d, saved, i - 1u); +} diff --git a/src/cg/native_asm.h b/src/cg/native_asm.h @@ -92,4 +92,79 @@ const char* native_asm_pin_status_message(NativeAsmRegPinStatus st); int native_asm_constraint_reg_class(const char* constraint, NativeAllocClass* cls_out); +/* ---- Direct (-O0) inline-asm operand binding ---- + * + * The direct CgTarget path lowers an inline-asm block by self-allocating a + * register/memory location for every operand, saving/restoring the + * callee-saved registers the asm clobbers, pre-loading INOUT outputs and all + * inputs into their bound registers, running the template, then storing the + * register outputs back. That orchestration is arch-neutral: it was a + * ~170-line near-identical copy in each of the aa64/x64/rv64 backends. It lives + * here as the single source of truth, parameterized over the genuinely + * arch-specific bits via NativeAsmDirectHooks; each backend's *_direct_asm_block + * is a thin wrapper that supplies the hooks. + * + * The shared driver depends on NativeDirectTarget only through pointers, so the + * full struct is needed at the call site (the .c includes it), not here. */ +typedef struct NativeDirectTarget NativeDirectTarget; + +typedef struct NativeAsmDirectHooks { + /* Registers the operand allocator must never hand out (emit scratch, ABI + * fixed regs, frame pointer, ...), seeded into the used masks alongside the + * clobber masks. Per-arch. */ + u32 scratch_int; + u32 scratch_fp; + /* Arch inline-operand discriminators: Operand.kind for a bound register + * pseudo-operand, and the Operand.pad[0] value marking the FP class. */ + u8 opk_reg; + u8 opcls_fp; + u8 pad[2]; + + /* Panic with the arch's inline-asm message prefix. Does not return. */ + void (*panic)(NativeDirectTarget* d, const char* msg); + /* Build the arch's bound register / memory pseudo-operand. */ + void (*bound_reg)(Operand* out, KitCgTypeId type, NativeAllocClass cls, + Reg reg); + void (*bound_mem)(Operand* out, KitCgTypeId type, Reg base); + /* Pick a free register from the arch's allocable pool for cls, honoring + * allowed_mask and the running used masks (which it updates). Panics on + * exhaustion. */ + Reg (*alloc_reg)(NativeDirectTarget* d, NativeAllocClass cls, u32 allowed_mask, + u32* used_int, u32* used_fp); + /* Parse the asm clobber list into per-class register masks (arch register + * naming). */ + void (*clobber_masks)(Compiler* c, SrcLoc loc, const Sym* clobbers, u32 nclob, + u32* int_mask, u32* fp_mask); + /* Spill the callee-saved registers the asm clobbers, returning an opaque, + * block-lived save record plus its element count. restore_one undoes element + * idx; the driver replays them in reverse. */ + void* (*save_callee_clobbers)(NativeDirectTarget* d, u32 int_mask, u32 fp_mask, + u32* nsaved_out); + void (*restore_one)(NativeDirectTarget* d, void* saved, u32 idx); + /* Load a semantic operand's value / address into a register, or store a + * register back to a semantic operand. */ + void (*load_operand_to_reg)(NativeDirectTarget* d, Operand op, NativeLoc dst); + void (*load_address_to_reg)(NativeDirectTarget* d, Operand op, NativeLoc dst); + void (*store_reg_to_operand)(NativeDirectTarget* d, Operand op, NativeLoc src); + /* Open the arch asm assembler, bind the resolved operands, run the template, + * and close — emitting the asm body into the target's MCEmitter. */ + void (*run_template)(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* bound_outs, const AsmConstraint* ins, + u32 nin, Operand* bound_ins, const Sym* clobbers, + u32 nclob); +} NativeAsmDirectHooks; + +/* Drive the arch-neutral direct inline-asm orchestration: out-loop / in-loop / + * save / load / run / store / restore. Behavior is identical to the former + * per-arch *_direct_asm_block bodies; the hooks supply every arch-specific + * step. */ +void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl, + const AsmConstraint* outs, u32 nout, + Operand* out_ops, const AsmConstraint* ins, + u32 nin, const Operand* in_ops, + const Sym* clobbers, u32 nclob, + u32 clobber_abi_sets, + const NativeAsmDirectHooks* h); + #endif