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:
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