kit

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

commit 6990bd41b09bbc146d29fc51a07d07c8fe04de65
parent 3b27e21fb67bd8457611ef5fbefb3bc8e9d73ca0
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 21:29:27 -0700

cleanup wave C-2a: per-arch plumbing dedup + rv64 reentrancy fix

Behavior-preserving extractions; verified by test-aa64-inline / test-x64-inline
/ test-rv64-inline / test-isa / test-cg-api / test-opt / test-toy (all backends)
/ test-smoke-x64 / test-smoke-rv64 / test-asm — all green.

B.1 — x64 callee-save: added x64_cs_int_off / x64_cs_fp_off offset helpers (used
at prologue spill, epilogue restore, CFI, tail-site) and extracted
x64_emit_callee_restores called from both x64_func_end and x64_emit_tail_site,
mirroring aa64's aa_emit_callee_restores. Identical prologue/epilogue/CFI bytes.

B.4 — x64 indirect branch: extracted x64_emit_indirect_rm(mc, r, digit) for the
FF /digit r/m64 encoding; the call (/2) and jmp (/4) sites differ only in the
ModRM digit. Identical encoding. (TLV SIB-form sites left alone — distinct.)

B.3 — riscv pcrel: extracted rv_emit_pcrel_anchor() for the auipc(dst,0) +
.LpcrelHi anchor + LO12 follow-insn idiom (3 sites), parameterized over the HI20
reloc kind (GOT vs PCREL) and the pre-encoded follow-up insn. (The auipc+jalr
R_RV_CALL thunks are a different idiom, correctly left alone.)

H.1 — removed the only mutable function-local static in src/:
rv64_disasm_find_c's `static Rv64InsnDesc dyn` now synthesizes into a
caller-owned scratch (stack-local at the two disasm.c call sites), restoring the
documented reentrant/embeddable invariant; the ~30 near-identical synthesis
blocks collapse into one rv64c_mk() helper. Decoded output byte-identical.

C.3 — RV S/B/J immediate bit-scramble: factored into rv_imm_s/rv_imm_b/rv_imm_j
(isa.h), routed rv_s/rv_b/rv_j and the assembler's enc_s/enc_b/enc_j through
them. Bit-identical encoding.

B.5 (partial) — hoisted the byte-identical backend_make + semantic_target_new
into shared native_direct_target_backend_make / _semantic_target_new
(parameterized over the per-arch NativeTarget ctor + NativeOps); each arch.c
shrinks to one-line calls (rv32 shares transitively). register_at_public dedup
deferred: its clean home needs arch/arch.h + kit/arch.h (not in scope).

Deferred: B.2 (tail-call projection, spans all 3 native.c), A.2 (optimizer-path
*_asm_block_native triplication) — both need a cross-arch native.c batch.

Diffstat:
Mdoc/plan/CLEANUP-2026-06-09.md | 12++++++------
Msrc/arch/aa64/arch.c | 27++++-----------------------
Msrc/arch/riscv/arch.c | 27++++-----------------------
Msrc/arch/riscv/asm.c | 16+++++-----------
Msrc/arch/riscv/disasm.c | 6++++--
Msrc/arch/riscv/isa.c | 277+++++++++++++++++++++----------------------------------------------------------
Msrc/arch/riscv/isa.h | 45++++++++++++++++++++++++++++++++-------------
Msrc/arch/riscv/native.c | 73++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/arch/x64/arch.c | 27++++-----------------------
Msrc/arch/x64/native.c | 103++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/cg/native_direct_target.c | 34++++++++++++++++++++++++++++++++++
Msrc/cg/native_direct_target.h | 20++++++++++++++++++++
12 files changed, 275 insertions(+), 392 deletions(-)

diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md @@ -115,16 +115,16 @@ confirmed finding. 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 +- [x] **B.1** x64 callee-save offset formula open-coded 5+ times; collect+reverse-restore loop duplicated between `x64_func_end` and `x64_emit_tail_site` — `src/arch/x64/native.c:1946-1953, 2655-2666`. aa64 already factored `aa_emit_callee_restores`. *Fix:* `x64_cs_int_off/x64_cs_fp_off` helpers + extract `x64_emit_callee_restores`, mirroring aa64. - [ ] **B.2** `CGCallDesc→NativeCallDesc` tail-call projection (`rv_no_tail`/`aa_no_tail`/ `x64_no_tail`) duplicated. *Fix:* shared projection in `cg/native_direct_target`. -- [ ] **B.3** PC-relative `auipc`+anchor reloc pair emitted 3× in riscv. *Fix:* one helper. -- [ ] **B.4** Indirect `jmp`/`call r/m64` encoding 3× in x64. *Fix:* one encoder. -- [ ] **B.5** `backend_make`/`semantic_target_new`/`register_at_public` byte-identical +- [x] **B.3** PC-relative `auipc`+anchor reloc pair emitted 3× in riscv. *Fix:* one helper. +- [x] **B.4** Indirect `jmp`/`call r/m64` encoding 3× in x64. *Fix:* one encoder. +- [~] **B.5** `backend_make`/`semantic_target_new`/`register_at_public` byte-identical across arches. *Fix:* share via `src/arch/registry` / `cg/native_direct_target`. ### C. ABI / soft-float / reloc lowering hand-copied (med→low, 7) @@ -135,7 +135,7 @@ confirmed finding. - [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. +- [x] **C.3** RV B/J/S immediate bit-scramble reimplemented 3×. *Fix:* one shared encoder. - [ ] **C.4** rv32 ELF reloc map clones rv64 (3 cases differ); rv32 PLT/IPLT emitters clone rv64 (one LW-vs-LD). *Fix:* parameterize on word size. - [ ] **C.5** RISC-V float-ABI→`e_flags` mapping inlined in both ET_REL and ET_EXEC writers @@ -201,7 +201,7 @@ confirmed finding. *Fix:* dispatch on an encoding tag. ### H. Function-local mutable statics (med, 2) — invariant violation -- [ ] **H.1** `rv64_disasm_find_c` synthesizes into a `static Rv64InsnDesc dyn;` and returns +- [x] **H.1** `rv64_disasm_find_c` synthesizes into a `static Rv64InsnDesc dyn;` and returns its address (`src/arch/riscv/isa.c:1230-1338`) — the *only* mutable function-local static in `src/`; violates the documented no-global-state/reentrant invariant; latent race. *Fix:* thread a caller-owned scratch slot (on the `Rv64InsnFormatter` that already hangs diff --git a/src/arch/aa64/arch.c b/src/arch/aa64/arch.c @@ -113,33 +113,14 @@ static int aa64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { static CgTarget* aa64_backend_make(Compiler* c, ObjBuilder* o, const KitCodeOptions* opts) { - MCEmitter* mc = NULL; - Debug* debug = NULL; - CgTarget* t; - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (cg_mc_debug_new(c, o, opts, &mc, &debug) != KIT_OK) return NULL; - native = aa64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = aa64_native_direct_ops(); - t = native_direct_target_new(c, o, &cfg); - if (t) t->debug = debug; - return t; + return native_direct_backend_make(c, o, opts, aa64_native_target_new, + aa64_native_direct_ops()); } static CgTarget* aa64_semantic_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (!mc) mc = mc_new(c, o); - native = aa64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = aa64_native_direct_ops(); - return native_direct_target_new(c, o, &cfg); + return native_direct_semantic_target_new(c, o, mc, aa64_native_target_new, + aa64_native_direct_ops()); } static const KitPredefinedMacro aa64_predefined_macros[] = { diff --git a/src/arch/riscv/arch.c b/src/arch/riscv/arch.c @@ -320,33 +320,14 @@ static void rv64_target_feature_defaults(const Target* target, u64* words, static CgTarget* rv64_backend_make(Compiler* c, ObjBuilder* o, const KitCodeOptions* opts) { - MCEmitter* mc = NULL; - Debug* debug = NULL; - CgTarget* t; - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (cg_mc_debug_new(c, o, opts, &mc, &debug) != KIT_OK) return NULL; - native = rv64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = rv64_native_direct_ops(); - t = native_direct_target_new(c, o, &cfg); - if (t) t->debug = debug; - return t; + return native_direct_backend_make(c, o, opts, rv64_native_target_new, + rv64_native_direct_ops()); } static CgTarget* rv64_semantic_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (!mc) mc = mc_new(c, o); - native = rv64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = rv64_native_direct_ops(); - return native_direct_target_new(c, o, &cfg); + return native_direct_semantic_target_new(c, o, mc, rv64_native_target_new, + rv64_native_direct_ops()); } /* RISC-V emits only the target-C convention; it has no SysV/Win64/AAPCS/WASM diff --git a/src/arch/riscv/asm.c b/src/arch/riscv/asm.c @@ -307,24 +307,18 @@ static u32 enc_i(u32 match, u32 rd, u32 rs1, i32 imm12) { ((rd & 0x1fu) << 7); } static u32 enc_s(u32 match, u32 rs2, u32 rs1, i32 imm12) { - u32 ui = (u32)imm12 & 0xfffu; - return match | ((ui >> 5) << 25) | ((rs2 & 0x1fu) << 20) | - ((rs1 & 0x1fu) << 15) | ((ui & 0x1fu) << 7); + return match | rv_imm_s(imm12) | ((rs2 & 0x1fu) << 20) | + ((rs1 & 0x1fu) << 15); } static u32 enc_b(u32 match, u32 rs1, u32 rs2, i32 imm13) { - u32 ui = (u32)imm13; - return match | (((ui >> 12) & 1u) << 31) | (((ui >> 5) & 0x3fu) << 25) | - ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | - (((ui >> 1) & 0xfu) << 8) | (((ui >> 11) & 1u) << 7); + return match | rv_imm_b(imm13) | ((rs2 & 0x1fu) << 20) | + ((rs1 & 0x1fu) << 15); } static u32 enc_u(u32 match, u32 rd, u32 imm20) { return match | ((imm20 & 0xfffffu) << 12) | ((rd & 0x1fu) << 7); } static u32 enc_j(u32 match, u32 rd, i32 imm21) { - u32 ui = (u32)imm21; - return match | (((ui >> 20) & 1u) << 31) | (((ui >> 1) & 0x3ffu) << 21) | - (((ui >> 11) & 1u) << 20) | (((ui >> 12) & 0xffu) << 12) | - ((rd & 0x1fu) << 7); + return match | rv_imm_j(imm21) | ((rd & 0x1fu) << 7); } static u32 enc_r4(u32 match, u32 rd, u32 rs1, u32 rs2, u32 rs3, u32 rm) { return match | ((rs3 & 0x1fu) << 27) | ((rs2 & 0x1fu) << 20) | diff --git a/src/arch/riscv/disasm.c b/src/arch/riscv/disasm.c @@ -321,6 +321,7 @@ static void rv64_decode_operands(const Rv64InsnDesc* desc, u32 word, u64 pc, static KitStatus rv64_decode_one(Compiler* c, const u8* bytes, size_t len, u64 pc, KitDecodedInsn* out) { const Rv64InsnDesc* desc; + Rv64InsnDesc c_scratch; const RiscvVariant* variant; u8 av; u32 first_hw; @@ -337,7 +338,7 @@ static KitStatus rv64_decode_one(Compiler* c, const u8* bytes, size_t len, first_hw = rv_read_u16_le(bytes); if ((first_hw & 3u) != 3u) { word = first_hw; - desc = rv64_disasm_find_c(first_hw, av); + desc = rv64_disasm_find_c(first_hw, av, &c_scratch); out->nbytes = 2; } else { if (len < 4u) return KIT_MALFORMED; @@ -392,12 +393,13 @@ static KitStatus rv64_format_insn(ArchInsnFormatter* base, const KitDecodedInsn* insn, KitInsn* out) { Rv64InsnFormatter* f = (Rv64InsnFormatter*)base; const Rv64InsnDesc* desc; + Rv64InsnDesc c_scratch; u32 word; if (!f || !insn || !out) return KIT_INVALID; word = (u32)insn->arch[0]; { u8 av = rv_av_for_compiler(f->c); - desc = insn->nbytes == 2u ? rv64_disasm_find_c(word, av) + desc = insn->nbytes == 2u ? rv64_disasm_find_c(word, av, &c_scratch) : rv64_disasm_find(word, av); } if (desc) { diff --git a/src/arch/riscv/isa.c b/src/arch/riscv/isa.c @@ -1219,7 +1219,17 @@ static const Rv64InsnDesc rv64_c_table[] = { #undef MN -const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted) { +/* Synthesize a compressed-instruction descriptor into the caller-owned + * scratch and return it. Every synthesized row shares match=hw, mask=0xffff, + * av=0; only the mnemonic name, format kind, and asm-flags vary. */ +static const Rv64InsnDesc* rv64c_mk(Rv64InsnDesc* dyn, u32 hw, const char* name, + u8 fmt, u8 flags) { + *dyn = (Rv64InsnDesc){slice_from_cstr(name), hw, 0xffffu, fmt, flags, 0, {0}}; + return dyn; +} + +const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted, + Rv64InsnDesc* scratch) { u32 hw = word & 0xffffu; u32 idx = rv64c_lookup_simple(hw); /* True when decoding for rv32: several RVC quadrant slots whose integer @@ -1227,9 +1237,8 @@ const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted) { * (RV32FC), and q1/f3=001 is c.jal not c.addiw. */ bool rv32 = (av_wanted & RV_AV_RV32) != 0u; if (idx) return &rv64_c_table[idx]; - /* Pattern-match remaining common C-instructions. We use a tiny static - * scratch descriptor that the printer interprets by funct3+op. */ - static Rv64InsnDesc dyn; + /* Pattern-match remaining common C-instructions. We synthesize into the + * caller-owned scratch descriptor that the printer interprets by funct3+op. */ u32 op = hw & 0x3u; u32 f3 = (hw >> 13) & 0x7u; if (op == 3u) return NULL; /* uncompressed */ @@ -1240,74 +1249,40 @@ const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted) { u32 rd_rs1 = (hw >> 7) & 0x1fu; u32 rs2 = (hw >> 2) & 0x1fu; if (funct4 == 0x8u) { - dyn = (Rv64InsnDesc){slice_from_cstr(rs2 == 0 ? "c.jr" : "c.mv"), - hw, - 0xffffu, - RV64_FMT_CR, - RV64_ASMFL_C16, - 0, {0}}; - return rd_rs1 == 0 ? NULL : &dyn; + const Rv64InsnDesc* d = + rv64c_mk(scratch, hw, rs2 == 0 ? "c.jr" : "c.mv", RV64_FMT_CR, + RV64_ASMFL_C16); + return rd_rs1 == 0 ? NULL : d; } if (funct4 == 0x9u) { if (rs2 == 0 && rd_rs1 == 0) { - dyn = rv64_c_table[2]; /* c.ebreak */ - return &dyn; + *scratch = rv64_c_table[2]; /* c.ebreak */ + return scratch; } - dyn = (Rv64InsnDesc){slice_from_cstr(rs2 == 0 ? "c.jalr" : "c.add"), - hw, - 0xffffu, - RV64_FMT_CR, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; + return rv64c_mk(scratch, hw, rs2 == 0 ? "c.jalr" : "c.add", RV64_FMT_CR, + RV64_ASMFL_C16); } } /* C.LI / C.ADDI / C.LUI — quadrant 1 */ - if (op == 1u && f3 == 2u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.li"), hw, 0xffffu, RV64_FMT_CI, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; - } + if (op == 1u && f3 == 2u) + return rv64c_mk(scratch, hw, "c.li", RV64_FMT_CI, RV64_ASMFL_C16); if (op == 1u && f3 == 1u) { /* q1/f3=001: c.addiw on rv64, c.jal on rv32 (same encoding). */ - dyn = rv32 ? (Rv64InsnDesc){slice_from_cstr("c.jal"), hw, 0xffffu, - RV64_FMT_CJ, RV64_ASMFL_C16, - 0, {0}} - : (Rv64InsnDesc){slice_from_cstr("c.addiw"), hw, 0xffffu, - RV64_FMT_CI, RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } - if (op == 1u && f3 == 0u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.addi"), - hw, - 0xffffu, - RV64_FMT_CI, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; + return rv32 ? rv64c_mk(scratch, hw, "c.jal", RV64_FMT_CJ, RV64_ASMFL_C16) + : rv64c_mk(scratch, hw, "c.addiw", RV64_FMT_CI, RV64_ASMFL_C16); } + if (op == 1u && f3 == 0u) + return rv64c_mk(scratch, hw, "c.addi", RV64_FMT_CI, RV64_ASMFL_C16); if (op == 1u && f3 == 3u) { u32 rd = (hw >> 7) & 0x1fu; - dyn = (Rv64InsnDesc){slice_from_cstr(rd == 2u ? "c.addi16sp" : "c.lui"), - hw, - 0xffffu, - RV64_FMT_CI, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; + return rv64c_mk(scratch, hw, rd == 2u ? "c.addi16sp" : "c.lui", + RV64_FMT_CI, RV64_ASMFL_C16); } if (op == 1u && f3 == 4u) { u32 top = (hw >> 10) & 0x3u; if (top == 0u || top == 1u || top == 2u) { static const char* const names[3] = {"c.srli", "c.srai", "c.andi"}; - dyn = (Rv64InsnDesc){slice_from_cstr(names[top]), - hw, - 0xffffu, - RV64_FMT_CB, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; + return rv64c_mk(scratch, hw, names[top], RV64_FMT_CB, RV64_ASMFL_C16); } { u32 bit12 = (hw >> 12) & 1u; @@ -1317,173 +1292,67 @@ const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted) { /* bit12==1 selects c.subw/c.addw — RV64-only; reserved on rv32. */ const char* name = bit12 ? (rv32 ? NULL : ca1[subop]) : ca0[subop]; if (!name) return NULL; - dyn = (Rv64InsnDesc){slice_from_cstr(name), hw, 0xffffu, RV64_FMT_CA, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; + return rv64c_mk(scratch, hw, name, RV64_FMT_CA, RV64_ASMFL_C16); } } - if (op == 1u && f3 == 5u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.j"), hw, 0xffffu, RV64_FMT_CJ, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; - } - if (op == 1u && f3 == 6u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.beqz"), - hw, - 0xffffu, - RV64_FMT_CB, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } - if (op == 1u && f3 == 7u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.bnez"), - hw, - 0xffffu, - RV64_FMT_CB, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } + if (op == 1u && f3 == 5u) + return rv64c_mk(scratch, hw, "c.j", RV64_FMT_CJ, RV64_ASMFL_C16); + if (op == 1u && f3 == 6u) + return rv64c_mk(scratch, hw, "c.beqz", RV64_FMT_CB, RV64_ASMFL_C16); + if (op == 1u && f3 == 7u) + return rv64c_mk(scratch, hw, "c.bnez", RV64_FMT_CB, RV64_ASMFL_C16); /* C.LWSP / C.LDSP — quadrant 2, funct3=010/011 */ - if (op == 2u && f3 == 2u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.lwsp"), - hw, - 0xffffu, - RV64_FMT_CI, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } + if (op == 2u && f3 == 2u) + return rv64c_mk(scratch, hw, "c.lwsp", RV64_FMT_CI, RV64_ASMFL_C16); if (op == 2u && f3 == 3u) { /* q2/f3=011: c.ldsp on rv64, c.flwsp on rv32 (same encoding). */ - if (rv32) - dyn = (Rv64InsnDesc){slice_from_cstr("c.flwsp"), - hw, - 0xffffu, - RV64_FMT_CI, - RV64_ASMFL_C16 | RV64_ASMFL_FP, - 0, - {0}}; - else - dyn = (Rv64InsnDesc){slice_from_cstr("c.ldsp"), hw, 0xffffu, - RV64_FMT_CI, RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } - if (op == 2u && f3 == 0u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.slli"), - hw, - 0xffffu, - RV64_FMT_CI, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } - if (op == 2u && f3 == 1u) { - dyn = (Rv64InsnDesc){ - slice_from_cstr("c.fldsp"), hw, 0xffffu, RV64_FMT_CI, - RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}}; - return &dyn; + return rv32 ? rv64c_mk(scratch, hw, "c.flwsp", RV64_FMT_CI, + RV64_ASMFL_C16 | RV64_ASMFL_FP) + : rv64c_mk(scratch, hw, "c.ldsp", RV64_FMT_CI, RV64_ASMFL_C16); } + if (op == 2u && f3 == 0u) + return rv64c_mk(scratch, hw, "c.slli", RV64_FMT_CI, RV64_ASMFL_C16); + if (op == 2u && f3 == 1u) + return rv64c_mk(scratch, hw, "c.fldsp", RV64_FMT_CI, + RV64_ASMFL_C16 | RV64_ASMFL_FP); /* C.SWSP / C.SDSP — quadrant 2, funct3=110/111 */ - if (op == 2u && f3 == 6u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.swsp"), - hw, - 0xffffu, - RV64_FMT_CSS, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } + if (op == 2u && f3 == 6u) + return rv64c_mk(scratch, hw, "c.swsp", RV64_FMT_CSS, RV64_ASMFL_C16); if (op == 2u && f3 == 7u) { /* q2/f3=111: c.sdsp on rv64, c.fswsp on rv32 (same encoding). */ - if (rv32) - dyn = (Rv64InsnDesc){slice_from_cstr("c.fswsp"), - hw, - 0xffffu, - RV64_FMT_CSS, - RV64_ASMFL_C16 | RV64_ASMFL_FP, - 0, - {0}}; - else - dyn = (Rv64InsnDesc){slice_from_cstr("c.sdsp"), - hw, - 0xffffu, - RV64_FMT_CSS, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } - if (op == 2u && f3 == 5u) { - dyn = (Rv64InsnDesc){ - slice_from_cstr("c.fsdsp"), hw, 0xffffu, RV64_FMT_CSS, - RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}}; - return &dyn; + return rv32 ? rv64c_mk(scratch, hw, "c.fswsp", RV64_FMT_CSS, + RV64_ASMFL_C16 | RV64_ASMFL_FP) + : rv64c_mk(scratch, hw, "c.sdsp", RV64_FMT_CSS, RV64_ASMFL_C16); } + if (op == 2u && f3 == 5u) + return rv64c_mk(scratch, hw, "c.fsdsp", RV64_FMT_CSS, + RV64_ASMFL_C16 | RV64_ASMFL_FP); /* C.ADDI4SPN — quadrant 0, funct3=000 */ - if (op == 0u && f3 == 0u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.addi4spn"), - hw, - 0xffffu, - RV64_FMT_CIW, - RV64_ASMFL_C16, - 0, {0}}; - return &dyn; - } + if (op == 0u && f3 == 0u) + return rv64c_mk(scratch, hw, "c.addi4spn", RV64_FMT_CIW, RV64_ASMFL_C16); /* C.LW / C.LD — quadrant 0, funct3=010/011 */ - if (op == 0u && f3 == 2u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.lw"), hw, 0xffffu, RV64_FMT_CL, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; - } + if (op == 0u && f3 == 2u) + return rv64c_mk(scratch, hw, "c.lw", RV64_FMT_CL, RV64_ASMFL_C16); if (op == 0u && f3 == 3u) { /* q0/f3=011: c.ld on rv64, c.flw on rv32 (same encoding). */ - if (rv32) - dyn = (Rv64InsnDesc){slice_from_cstr("c.flw"), - hw, - 0xffffu, - RV64_FMT_CL, - RV64_ASMFL_C16 | RV64_ASMFL_FP, - 0, - {0}}; - else - dyn = (Rv64InsnDesc){slice_from_cstr("c.ld"), hw, 0xffffu, RV64_FMT_CL, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; - } - if (op == 0u && f3 == 1u) { - dyn = (Rv64InsnDesc){ - slice_from_cstr("c.fld"), hw, 0xffffu, RV64_FMT_CL, - RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}}; - return &dyn; - } - if (op == 0u && f3 == 6u) { - dyn = (Rv64InsnDesc){slice_from_cstr("c.sw"), hw, 0xffffu, RV64_FMT_CS, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; + return rv32 ? rv64c_mk(scratch, hw, "c.flw", RV64_FMT_CL, + RV64_ASMFL_C16 | RV64_ASMFL_FP) + : rv64c_mk(scratch, hw, "c.ld", RV64_FMT_CL, RV64_ASMFL_C16); } + if (op == 0u && f3 == 1u) + return rv64c_mk(scratch, hw, "c.fld", RV64_FMT_CL, + RV64_ASMFL_C16 | RV64_ASMFL_FP); + if (op == 0u && f3 == 6u) + return rv64c_mk(scratch, hw, "c.sw", RV64_FMT_CS, RV64_ASMFL_C16); if (op == 0u && f3 == 7u) { /* q0/f3=111: c.sd on rv64, c.fsw on rv32 (same encoding). */ - if (rv32) - dyn = (Rv64InsnDesc){slice_from_cstr("c.fsw"), - hw, - 0xffffu, - RV64_FMT_CS, - RV64_ASMFL_C16 | RV64_ASMFL_FP, - 0, - {0}}; - else - dyn = (Rv64InsnDesc){slice_from_cstr("c.sd"), hw, 0xffffu, RV64_FMT_CS, - RV64_ASMFL_C16, 0, {0}}; - return &dyn; - } - if (op == 0u && f3 == 5u) { - dyn = (Rv64InsnDesc){ - slice_from_cstr("c.fsd"), hw, 0xffffu, RV64_FMT_CS, - RV64_ASMFL_C16 | RV64_ASMFL_FP, 0, {0}}; - return &dyn; + return rv32 ? rv64c_mk(scratch, hw, "c.fsw", RV64_FMT_CS, + RV64_ASMFL_C16 | RV64_ASMFL_FP) + : rv64c_mk(scratch, hw, "c.sd", RV64_FMT_CS, RV64_ASMFL_C16); } + if (op == 0u && f3 == 5u) + return rv64c_mk(scratch, hw, "c.fsd", RV64_FMT_CS, + RV64_ASMFL_C16 | RV64_ASMFL_FP); return NULL; } diff --git a/src/arch/riscv/isa.h b/src/arch/riscv/isa.h @@ -89,26 +89,38 @@ static inline u32 rv_i(i32 imm12, u32 rs1, u32 funct3, u32 rd, u32 op) { return (((u32)imm12 & 0xfffu) << 20) | ((rs1 & 0x1fu) << 15) | ((funct3 & 0x7u) << 12) | ((rd & 0x1fu) << 7) | (op & 0x7fu); } -static inline u32 rv_s(i32 imm12, u32 rs2, u32 rs1, u32 funct3, u32 op) { +/* S/B/J immediate bit-scramble — the immediate-field bits only (no + * register/funct/op). Single source of truth for both the codegen encoders + * (rv_s/rv_b/rv_j below) and the assembler's match-word overlay encoders + * (enc_s/enc_b/enc_j in asm.c), which were byte-identical reimplementations. */ +static inline u32 rv_imm_s(i32 imm12) { u32 ui = (u32)imm12 & 0xfffu; - return ((ui >> 5) << 25) | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | - ((funct3 & 0x7u) << 12) | ((ui & 0x1fu) << 7) | (op & 0x7fu); + return ((ui >> 5) << 25) | ((ui & 0x1fu) << 7); } -static inline u32 rv_b(i32 imm13, u32 rs2, u32 rs1, u32 funct3, u32 op) { +static inline u32 rv_imm_b(i32 imm13) { u32 ui = (u32)imm13; return (((ui >> 12) & 1u) << 31) | (((ui >> 5) & 0x3fu) << 25) | - ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | - ((funct3 & 0x7u) << 12) | (((ui >> 1) & 0xfu) << 8) | - (((ui >> 11) & 1u) << 7) | (op & 0x7fu); + (((ui >> 1) & 0xfu) << 8) | (((ui >> 11) & 1u) << 7); +} +static inline u32 rv_imm_j(i32 imm21) { + u32 ui = (u32)imm21; + return (((ui >> 20) & 1u) << 31) | (((ui >> 1) & 0x3ffu) << 21) | + (((ui >> 11) & 1u) << 20) | (((ui >> 12) & 0xffu) << 12); +} + +static inline u32 rv_s(i32 imm12, u32 rs2, u32 rs1, u32 funct3, u32 op) { + return rv_imm_s(imm12) | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | + ((funct3 & 0x7u) << 12) | (op & 0x7fu); +} +static inline u32 rv_b(i32 imm13, u32 rs2, u32 rs1, u32 funct3, u32 op) { + return rv_imm_b(imm13) | ((rs2 & 0x1fu) << 20) | ((rs1 & 0x1fu) << 15) | + ((funct3 & 0x7u) << 12) | (op & 0x7fu); } static inline u32 rv_u(u32 imm32_hi20, u32 rd, u32 op) { return (imm32_hi20 & 0xfffff000u) | ((rd & 0x1fu) << 7) | (op & 0x7fu); } static inline u32 rv_j(i32 imm21, u32 rd, u32 op) { - u32 ui = (u32)imm21; - return (((ui >> 20) & 1u) << 31) | (((ui >> 1) & 0x3ffu) << 21) | - (((ui >> 11) & 1u) << 20) | (((ui >> 12) & 0xffu) << 12) | - ((rd & 0x1fu) << 7) | (op & 0x7fu); + return rv_imm_j(imm21) | ((rd & 0x1fu) << 7) | (op & 0x7fu); } /* ---- Integer ops (RV32I/RV64I) ---- */ @@ -803,8 +815,15 @@ const Rv64InsnDesc* rv64_disasm_find(u32 word, u8 av_wanted); /* Compressed-instruction (16-bit) variant. Pass the halfword in the low * 16 bits of `word`. `av_wanted` branches the ambiguous quadrant slots * whose meaning differs between rv32 and rv64. Returns NULL if no - * descriptor matches. */ -const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted); + * descriptor matches. + * + * Synthesized C-format descriptors are written into the caller-owned + * `scratch` and that pointer is returned; fixed rows return a pointer into + * the module's static table instead. `scratch` must outlive the returned + * pointer's use. (Keeping the scratch caller-owned avoids a mutable + * function-local static and keeps the decode path reentrant.) */ +const Rv64InsnDesc* rv64_disasm_find_c(u32 word, u8 av_wanted, + Rv64InsnDesc* scratch); /* Mnemonic → descriptor for the assembler. Returns NULL if not found. * Ignores ALIAS-only rows when those would produce ambiguous parses diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -632,37 +632,43 @@ static int rv_addr_legal(NativeTarget* t, const NativeAddr* addr, /* ============================ memory ============================ */ +/* Emit the RISC-V PC-relative HI20/LO12 reloc pair: + * auipc(dst, 0) ; R_RV_*_HI20 against `target_sym` + * .LpcrelHi local anchor at the auipc PC + * <follow_insn> ; R_RV_PCREL_LO12_I against the anchor + * The HI20 reloc kind (`hi20`) and the encoded follow-up instruction word + * (`follow_insn` — e.g. addi dst,dst,0 for a direct addr or ld dst,dst,0 for a + * GOT indirection) vary per call site; the anchor symbol, LO12 reloc kind, and + * all addends/flags are fixed. The LO12 reloc is always *_LO12_I (I-type insn); + * no caller currently needs the *_LO12_S form. Behavior-preserving extraction + * of the auipc+anchor idiom shared by rv_emit_global_addr and + * rv_load_label_addr. */ +static void rv_emit_pcrel_anchor(NativeTarget* t, u32 dst, ObjSymId target_sym, + RelocKind hi20, u32 follow_insn) { + MCEmitter* mc = t->mc; + u32 sec = mc->section_id; + u32 ap = mc->pos(mc); + rv64_emit32(mc, rv_auipc(dst, 0)); + mc->emit_reloc_at(mc, sec, ap, hi20, target_sym, 0, 0, 0); + { + Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi")); + ObjSymId anchor = obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0); + u32 lp = mc->pos(mc); + rv64_emit32(mc, follow_insn); + mc->emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0); + } +} + /* Materialize the runtime address of a global into `dst`, including addend. */ static void rv_emit_global_addr(RvNativeTarget* a, u32 dst, ObjSymId sym, i64 addend) { NativeTarget* t = &a->base; MCEmitter* mc = t->mc; - u32 sec = mc->section_id; - if (obj_symbol_extern_via_got(t->c, t->obj, sym)) { - u32 ap = mc->pos(mc); - rv64_emit32(mc, rv_auipc(dst, 0)); - mc->emit_reloc_at(mc, sec, ap, R_RV_GOT_HI20, sym, 0, 0, 0); - { - Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi")); - ObjSymId anchor = - obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0); - u32 lp = mc->pos(mc); - rv64_emit32(mc, rv_ld_ptr(a->variant, dst, dst, 0)); - mc->emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0); - } - } else { - u32 ap = mc->pos(mc); - rv64_emit32(mc, rv_auipc(dst, 0)); - mc->emit_reloc_at(mc, sec, ap, R_RV_PCREL_HI20, sym, 0, 0, 0); - { - Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi")); - ObjSymId anchor = - obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0); - u32 lp = mc->pos(mc); - rv64_emit32(mc, rv_addi(dst, dst, 0)); - mc->emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0); - } - } + if (obj_symbol_extern_via_got(t->c, t->obj, sym)) + rv_emit_pcrel_anchor(t, dst, sym, R_RV_GOT_HI20, + rv_ld_ptr(a->variant, dst, dst, 0)); + else + rv_emit_pcrel_anchor(t, dst, sym, R_RV_PCREL_HI20, rv_addi(dst, dst, 0)); if (addend) rv_emit_addr_adjust(a->variant, mc, dst, dst, (i32)addend); } @@ -1405,20 +1411,9 @@ static void rv_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) { * rv_emit_global_addr uses for a global — so a compressing/re-encoding * assembler recomputes the displacement (a baked offset would break under * the C extension). */ - MCEmitter* mc = t->mc; u32 rd = loc_reg(dst); - u32 sec = mc->section_id; - ObjSymId sym = mc_label_symbol(mc, l); - u32 ap = mc->pos(mc); - rv64_emit32(mc, rv_auipc(rd, 0)); - mc->emit_reloc_at(mc, sec, ap, R_RV_PCREL_HI20, sym, 0, 0, 0); - { - Sym an = pool_intern_slice(t->c->global, SLICE_LIT(".LpcrelHi")); - ObjSymId anchor = obj_symbol(t->obj, an, SB_LOCAL, SK_OBJ, sec, (u64)ap, 0); - u32 lp = mc->pos(mc); - rv64_emit32(mc, rv_addi(rd, rd, 0)); - mc->emit_reloc_at(mc, sec, lp, R_RV_PCREL_LO12_I, anchor, 0, 0, 0); - } + ObjSymId sym = mc_label_symbol(t->mc, l); + rv_emit_pcrel_anchor(t, rd, sym, R_RV_PCREL_HI20, rv_addi(rd, rd, 0)); } /* ============================ frame / lifecycle ============================ diff --git a/src/arch/x64/arch.c b/src/arch/x64/arch.c @@ -112,33 +112,14 @@ static int x64_register_at_public(uint32_t idx, KitArchReg* out) { static CgTarget* x64_backend_make(Compiler* c, ObjBuilder* o, const KitCodeOptions* opts) { - MCEmitter* mc = NULL; - Debug* debug = NULL; - CgTarget* t; - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (cg_mc_debug_new(c, o, opts, &mc, &debug) != KIT_OK) return NULL; - native = x64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = x64_native_direct_ops(); - t = native_direct_target_new(c, o, &cfg); - if (t) t->debug = debug; - return t; + return native_direct_backend_make(c, o, opts, x64_native_target_new, + x64_native_direct_ops()); } static CgTarget* x64_semantic_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { - NativeTarget* native; - NativeDirectTargetConfig cfg; - if (!mc) mc = mc_new(c, o); - native = x64_native_target_new(c, o, mc); - if (!native) return NULL; - memset(&cfg, 0, sizeof cfg); - cfg.native = native; - cfg.ops = x64_native_direct_ops(); - return native_direct_target_new(c, o, &cfg); + return native_direct_semantic_target_new(c, o, mc, x64_native_target_new, + x64_native_direct_ops()); } /* Which explicit calling conventions x86-64 can emit. SysV and Win64 split on diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -1502,22 +1502,30 @@ static void x64_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop, emit_jcc_rel32(mc, cmp_to_cc(op), l); } -static void x64_indirect_branch(NativeTarget* t, NativeLoc addr, - const MCLabel* valid_targets, u32 ntargets) { - MCEmitter* mc = t->mc; - u32 r = loc_reg(addr); - (void)valid_targets; - (void)ntargets; +/* Emit an indirect `call`/`jmp r/m64` (opcode FF) against register `r`. The + * ModRM reg field is the opcode digit: /2 for `call`, /4 for `jmp`. Byte-for- + * byte the open-coded encoding used by x64_indirect_branch, x64_emit_call's + * indirect (r11-staged) site, and x64_emit_tail_site's indirect (r11) site. */ +static void x64_emit_indirect_rm(MCEmitter* mc, u32 r, u32 digit) { if (r & 8u) { u8 rex = X64_REX_BASE | X64_REX_B; mc->emit_bytes(mc, &rex, 1); } { - u8 buf[2] = {X64_OP_JMP_RM64, modrm(3u, 4u, r & 7u)}; + u8 buf[2] = {X64_OP_JMP_RM64, modrm(3u, digit, r & 7u)}; mc->emit_bytes(mc, buf, 2); } } +static void x64_indirect_branch(NativeTarget* t, NativeLoc addr, + const MCLabel* valid_targets, u32 ntargets) { + MCEmitter* mc = t->mc; + u32 r = loc_reg(addr); + (void)valid_targets; + (void)ntargets; + x64_emit_indirect_rm(mc, r, 4u); /* jmp r/m, /4 */ +} + static void x64_load_label_addr(NativeTarget* t, NativeLoc dst, MCLabel l) { /* `&&label` address-take: `leaq sym(%rip), rd` with an R_PC32 relocation * against the label's per-block local symbol — same form as a global @@ -1596,6 +1604,37 @@ static u32 x64_collect_fp_saves(X64NativeTarget* a, Reg* regs) { return n; } +/* rbp-relative offset of callee-save slot `idx`. The int GPRs sit below the + * 16-byte-aligned XMM save area (`xmm_base`) and the `n_fp` 16-byte XMM saves; + * the XMMs sit just below `xmm_base`. Single source for the prologue spill, the + * epilogue/tail-site restore, and the CFI offsets — all previously open-coded + * with this exact arithmetic. */ +static inline i32 x64_cs_int_off(u32 xmm_base, u32 n_fp, u32 idx) { + return -(i32)xmm_base - (i32)n_fp * 16 - (i32)(idx + 1u) * 8; +} +static inline i32 x64_cs_fp_off(u32 xmm_base, u32 idx) { + return -(i32)xmm_base - (i32)(idx + 1u) * 16; +} + +/* Emit the callee-save restores (reverse spill order: XMMs then GPRs) at the + * current emit cursor. Shared by the function epilogue (x64_func_end) and the + * tail-call site (x64_emit_tail_site); mirrors aa64's aa_emit_callee_restores. + * Frame-size-independent, so it needs only the collected save sets. */ +static void x64_emit_callee_restores(X64NativeTarget* a) { + MCEmitter* mc = a->base.mc; + Reg cs_int[X64_MAX_CS_INT_REGS], cs_fp[X64_MAX_CS_FP_REGS]; + u32 n_int = x64_collect_int_saves(a, cs_int); + u32 n_fp = x64_collect_fp_saves(a, cs_fp); + u32 xmm_base = x64_xmm_base(a, n_fp); + i32 i; + for (i = (i32)n_fp - 1; i >= 0; --i) + emit_sse_load(mc, 0, 0x28, cs_fp[i], X64_RBP, + x64_cs_fp_off(xmm_base, (u32)i)); /* movaps */ + for (i = (i32)n_int - 1; i >= 0; --i) + emit_mov_load(mc, 8, 0, cs_int[i], X64_RBP, + x64_cs_int_off(xmm_base, n_fp, (u32)i)); +} + static ObjSymId x64_chkstk_sym(NativeTarget* t) { Sym name = pool_intern_slice(t->c->global, SLICE_LIT("__chkstk")); ObjSymId s = obj_symbol_find(t->obj, name); @@ -1672,7 +1711,7 @@ static u32 x64_build_prologue(X64NativeTarget* a, u8* buf, u32 cap, /* Spill callee-saved GPRs. */ for (i = 0; i < n_int; ++i) { u32 reg = cs_int[i]; - i32 off = -(i32)xmm_base - (i32)n_fp * 16 - (i32)(i + 1u) * 8; + i32 off = x64_cs_int_off(xmm_base, n_fp, i); if (wi + 8u > cap) x64_panic(a, "prologue placeholder overflow"); buf[wi++] = (u8)(X64_REX_BASE | X64_REX_W | ((reg & 8u) ? X64_REX_R : 0u)); buf[wi++] = X64_OPC_MOV_RM_R; @@ -1681,7 +1720,7 @@ static u32 x64_build_prologue(X64NativeTarget* a, u8* buf, u32 cap, /* Spill callee-saved XMMs (Win64). movaps [rbp+disp], xmm. */ for (i = 0; i < n_fp; ++i) { u32 xmm = cs_fp[i]; - i32 off = -(i32)xmm_base - (i32)(i + 1u) * 16; + i32 off = x64_cs_fp_off(xmm_base, i); u8 rex = (u8)((xmm & 8u) ? (X64_REX_BASE | X64_REX_R) : 0u); u32 need = rex ? 9u : 8u; if (wi + need > cap) x64_panic(a, "prologue placeholder overflow"); @@ -1938,19 +1977,11 @@ static void x64_func_end(NativeTarget* t) { u32 frame_size = x64_compute_frame_size(a, n_int, n_fp); u32 xmm_base = x64_xmm_base(a, n_fp); u32 end; - i32 i; a->frame_size_final = frame_size; /* Epilogue. */ mc->label_place(mc, a->epilogue_label); - for (i = (i32)n_fp - 1; i >= 0; --i) { - i32 off = -(i32)xmm_base - (i32)(i + 1) * 16; - emit_sse_load(mc, 0, 0x28, cs_fp[i], X64_RBP, off); /* movaps */ - } - for (i = (i32)n_int - 1; i >= 0; --i) { - i32 off = -(i32)xmm_base - (i32)n_fp * 16 - (i32)(i + 1) * 8; - emit_mov_load(mc, 8, 0, cs_int[i], X64_RBP, off); - } + x64_emit_callee_restores(a); emit_leave(mc); emit_ret(mc); @@ -2001,7 +2032,7 @@ static void x64_func_end(NativeTarget* t) { mc->cfi_offset(mc, x64_dwarf_from_hw_gpr(X64_RBP), -16); mc->cfi_offset(mc, 16u /* rip */, -8); for (k = 0; k < n_int; ++k) { - i32 off = -(i32)xmm_base - (i32)n_fp * 16 - (i32)(k + 1u) * 8; + i32 off = x64_cs_int_off(xmm_base, n_fp, k); mc->cfi_offset(mc, x64_dwarf_from_hw_gpr(cs_int[k]), off); } } @@ -2652,17 +2683,7 @@ static void x64_emit_tail_site(NativeTarget* t, NativeLoc callee) { * Their rbp-relative offsets are frame-size-independent, and the indirect * callee was staged in r11 by plan_call — a caller-saved scratch — so these * restores never clobber it. Mirrors the x64_func_end epilogue. */ - Reg cs_int[X64_MAX_CS_INT_REGS], cs_fp[X64_MAX_CS_FP_REGS]; - u32 n_int = x64_collect_int_saves(a, cs_int); - u32 n_fp = x64_collect_fp_saves(a, cs_fp); - u32 xmm_base = x64_xmm_base(a, n_fp); - i32 i; - for (i = (i32)n_fp - 1; i >= 0; --i) - emit_sse_load(mc, 0, 0x28, cs_fp[i], X64_RBP, - -(i32)xmm_base - (i32)(i + 1) * 16); /* movaps */ - for (i = (i32)n_int - 1; i >= 0; --i) - emit_mov_load(mc, 8, 0, cs_int[i], X64_RBP, - -(i32)xmm_base - (i32)n_fp * 16 - (i32)(i + 1) * 8); + x64_emit_callee_restores(a); emit_leave(mc); if (callee.kind == NATIVE_LOC_GLOBAL) { u8 op = X64_OPC_JMP_REL32; @@ -2673,16 +2694,9 @@ static void x64_emit_tail_site(NativeTarget* t, NativeLoc callee) { mc->emit_reloc_at(mc, sec, disp_pos, R_X64_PLT32, callee.v.global.sym, callee.v.global.addend - 4, 1, 0); } else if (callee.kind == NATIVE_LOC_REG) { - u32 r = - loc_reg(callee); /* indirect callee was staged in r11 by plan_call */ - if (r & 8u) { - u8 rex = X64_REX_BASE | X64_REX_B; - mc->emit_bytes(mc, &rex, 1); - } - { - u8 buf[2] = {X64_OP_JMP_RM64, modrm(3u, 4u, r & 7u)}; /* jmp r/m, /4 */ - mc->emit_bytes(mc, buf, 2); - } + /* indirect callee was staged in r11 by plan_call */ + u32 r = loc_reg(callee); + x64_emit_indirect_rm(mc, r, 4u); /* jmp r/m, /4 */ } else { x64_panic(a, "unsupported tail call target"); } @@ -2707,14 +2721,7 @@ static void x64_emit_call(NativeTarget* t, const NativeCallPlan* plan) { } if (plan->callee.kind == NATIVE_LOC_REG) { u32 r = loc_reg(plan->callee); - if (r & 8u) { - u8 rex = X64_REX_BASE | X64_REX_B; - mc->emit_bytes(mc, &rex, 1); - } - { - u8 buf[2] = {X64_OP_JMP_RM64, modrm(3u, 2u, r & 7u)}; /* call r/m, /2 */ - mc->emit_bytes(mc, buf, 2); - } + x64_emit_indirect_rm(mc, r, 2u); /* call r/m, /2 */ return; } x64_panic(x64_of(t), "unsupported call target"); diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -1994,3 +1994,37 @@ NativeTarget* native_direct_target_native(CgTarget* t) { NativeDirectTarget* d = t ? nd_of(t) : NULL; return d && d->magic == NATIVE_DIRECT_MAGIC ? d->native : NULL; } + +CgTarget* native_direct_backend_make(Compiler* c, ObjBuilder* o, + const KitCodeOptions* opts, + NativeTargetCtor ctor, + const NativeOps* ops) { + MCEmitter* mc = NULL; + Debug* debug = NULL; + CgTarget* t; + NativeTarget* native; + NativeDirectTargetConfig cfg; + if (cg_mc_debug_new(c, o, opts, &mc, &debug) != KIT_OK) return NULL; + native = ctor(c, o, mc); + if (!native) return NULL; + memset(&cfg, 0, sizeof cfg); + cfg.native = native; + cfg.ops = ops; + t = native_direct_target_new(c, o, &cfg); + if (t) t->debug = debug; + return t; +} + +CgTarget* native_direct_semantic_target_new(Compiler* c, ObjBuilder* o, + MCEmitter* mc, NativeTargetCtor ctor, + const NativeOps* ops) { + NativeTarget* native; + NativeDirectTargetConfig cfg; + if (!mc) mc = mc_new(c, o); + native = ctor(c, o, mc); + if (!native) return NULL; + memset(&cfg, 0, sizeof cfg); + cfg.native = native; + cfg.ops = ops; + return native_direct_target_new(c, o, &cfg); +} diff --git a/src/cg/native_direct_target.h b/src/cg/native_direct_target.h @@ -172,4 +172,24 @@ CgTarget* native_direct_target_new(Compiler*, ObjBuilder*, const NativeDirectTargetConfig*); NativeTarget* native_direct_target_native(CgTarget*); +/* Per-arch NativeTarget constructor, e.g. aa64_native_target_new. */ +typedef NativeTarget* (*NativeTargetCtor)(Compiler*, ObjBuilder*, MCEmitter*); + +/* Shared glue for an ArchImpl's backend.make hook. Builds the MCEmitter + + * optional Debug producer (cg_mc_debug_new), constructs the arch NativeTarget + * via `ctor`, then wraps it in a NativeDirectTarget configured with `ops`. + * Returns NULL on any failure. Each arch backend_make is exactly a call to + * this with its own (ctor, ops). */ +CgTarget* native_direct_backend_make(Compiler*, ObjBuilder*, + const KitCodeOptions*, NativeTargetCtor ctor, + const NativeOps* ops); + +/* Shared glue for an ArchImpl's cgtarget_new hook (semantic/assembler path). + * Reuses the caller's MCEmitter when non-NULL, else mints one with mc_new; no + * Debug producer. Constructs the arch NativeTarget via `ctor` and wraps it in a + * NativeDirectTarget configured with `ops`. Returns NULL on any failure. */ +CgTarget* native_direct_semantic_target_new(Compiler*, ObjBuilder*, MCEmitter*, + NativeTargetCtor ctor, + const NativeOps* ops); + #endif