kit

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

commit 645999b8a7c37d67a91aece265d39b44ac68d229
parent 5da5c8eea63865c3655b4fd54f7893bcc6c56fed
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Thu, 18 Jun 2026 16:26:49 -0700

Clean up inline asm constraint binding

Diffstat:
Ainclude/kit/asm_constraints.h | 37+++++++++++++++++++++++++++++++++++++
Minclude/kit/cg.h | 1+
Mlang/c/parse/cg.c | 3++-
Mlang/c/parse/cg.h | 2+-
Mlang/c/parse/parse_stmt.c | 7+++++--
Mlang/toy/asm.c | 47-----------------------------------------------
Msrc/arch/aa64/native.c | 193++++++++++++++++++++++++++-----------------------------------------------------
Msrc/arch/arm32/native.c | 167++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Msrc/arch/riscv/native.c | 170+++++++++++++++++++++++--------------------------------------------------------
Msrc/arch/x64/native.c | 182++++++++++++++++++++++++++++---------------------------------------------------
Msrc/cg/asm.c | 22++++++++--------------
Msrc/cg/native_asm.c | 141++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/cg/native_asm.h | 43+++++++++++++++++++++++++++++++++++++++----
Msrc/opt/pass_machinize.c | 9+++++++++
Msrc/opt/pass_native_emit.c | 6+++---
Mtest/toy/err/invalid_asm_input_constraint.toy | 2+-
16 files changed, 500 insertions(+), 532 deletions(-)

diff --git a/include/kit/asm_constraints.h b/include/kit/asm_constraints.h @@ -0,0 +1,37 @@ +#ifndef KIT_ASM_CONSTRAINTS_H +#define KIT_ASM_CONSTRAINTS_H + +/* Shared parsing for the target-neutral part of GCC-style inline-asm operand + * constraints. These helpers know only about generic modifiers and matching + * digits; target register letters are resolved by the selected backend. */ + +static inline const char* kit_cg_asm_constraint_body(const char* s) { + if (!s) return ""; + if (s[0] == '=' && s[1] == '&') return s + 2; + if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; + return s; +} + +static inline int kit_cg_asm_constraint_early(const char* s) { + if (!s) return 0; + return (s[0] == '=' && s[1] == '&') || s[0] == '&'; +} + +static inline int kit_cg_asm_constraint_match_index(const char* s) { + int n; + if (!s || s[0] < '0' || s[0] > '9') return -1; + n = 0; + for (; *s >= '0' && *s <= '9'; ++s) n = n * 10 + (*s - '0'); + return n; +} + +static inline int kit_cg_asm_constraint_is_decimal(const char* s) { + if (!s || s[0] < '0' || s[0] > '9') return 0; + while (*s) { + if (*s < '0' || *s > '9') return 0; + ++s; + } + return 1; +} + +#endif diff --git a/include/kit/cg.h b/include/kit/cg.h @@ -3,6 +3,7 @@ #include <kit/core.h> #include <kit/object.h> +#include <kit/asm_constraints.h> /* ============================================================ * Handles diff --git a/lang/c/parse/cg.c b/lang/c/parse/cg.c @@ -748,7 +748,7 @@ void c_cg_frame_or_return_address(Parser* p, int is_return, u32 level) { void c_cg_inline_asm(Parser* p, const char* tmpl, const AsmConstraint* outs, u32 nout, const AsmConstraint* ins, u32 nin, - const Sym* clobbers, u32 nclob) { + const Sym* clobbers, u32 nclob, u32 flags) { KitCgInlineAsm a; KitCgAsmOperand* o = NULL; KitCgAsmOperand* in = NULL; @@ -787,6 +787,7 @@ void c_cg_inline_asm(Parser* p, const char* tmpl, const AsmConstraint* outs, a.ninputs = nin; a.clobbers = cl; a.nclobbers = nclob; + a.flags = flags; kit_cg_inline_asm(p->cg, a); for (u32 i = 0; i < nout; ++i) { u32 depth = nout - 1u - i; diff --git a/lang/c/parse/cg.h b/lang/c/parse/cg.h @@ -279,6 +279,6 @@ void c_cg_syscall(Parser*, u32 nargs, const Type* long_ty); void c_cg_frame_or_return_address(Parser*, int is_return, u32 level); void c_cg_readcyclecounter(Parser*); void c_cg_inline_asm(Parser*, const char*, const AsmConstraint*, u32, - const AsmConstraint*, u32, const Sym*, u32); + const AsmConstraint*, u32, const Sym*, u32, u32); #endif diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -647,12 +647,15 @@ static void parse_asm_stmt(Parser* p) { AsmOutLValue* out_lvs = NULL; u32 nout = 0, nin = 0, nclob = 0; u32 cap_out = 0, cap_in = 0, cap_clob = 0; + u32 flags = 0; int saw_goto = 0; SrcLoc loc = tok_loc_stmt(p, &p->cur); for (;;) { - if (accept_kw_stmt(p, KW_VOLATILE)) + if (accept_kw_stmt(p, KW_VOLATILE)) { + flags |= KIT_CG_ASM_VOLATILE; continue; /* `volatile` or `__volatile__` */ + } break; } if (accept_kw_stmt(p, KW_GOTO)) saw_goto = 1; @@ -838,7 +841,7 @@ static void parse_asm_stmt(Parser* p) { } c_cg_set_loc(p, loc); - c_cg_inline_asm(p, tmpl, outs, nout, ins, nin, clobbers, nclob); + c_cg_inline_asm(p, tmpl, outs, nout, ins, nin, clobbers, nclob, flags); if (nout > 0) { u32 i; diff --git a/lang/toy/asm.c b/lang/toy/asm.c @@ -43,51 +43,6 @@ static int toy_asm_append_clobber(ToyParser* p, ToyAsmClobberList* list, return 1; } -static const char* toy_asm_constraint_body(const char* s) { - if (!s) return ""; - if (s[0] == '=' && s[1] == '&') return s + 2; - if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; - return s; -} - -static int toy_asm_is_decimal_constraint(const char* s) { - if (!s || s[0] < '0' || s[0] > '9') return 0; - while (*s) { - if (*s < '0' || *s > '9') return 0; - ++s; - } - return 1; -} - -static int toy_validate_asm_output_constraint(ToyParser* p, - const KitCgAsmOperand* op) { - const char* s = kit_sym_str(p->c, op->constraint).s; - const char* body = toy_asm_constraint_body(s); - if (op->dir == KIT_CG_ASM_OUT) { - if (!s || s[0] != '=' || body[0] != 'r' || body[1] != '\0') { - toy_error(p, p->cur.loc, "unsupported asm output constraint"); - return 0; - } - } else if (op->dir == KIT_CG_ASM_INOUT) { - if (!s || s[0] != '+' || body[0] != 'r' || body[1] != '\0') { - toy_error(p, p->cur.loc, "unsupported asm output constraint"); - return 0; - } - } - return 1; -} - -static int toy_validate_asm_input_constraint(ToyParser* p, - const KitCgAsmOperand* op) { - const char* s = kit_sym_str(p->c, op->constraint).s; - if ((s && s[0] && !s[1] && (s[0] == 'r' || s[0] == 'i' || s[0] == 'm')) || - toy_asm_is_decimal_constraint(s)) { - return 1; - } - toy_error(p, p->cur.loc, "unsupported asm input constraint"); - return 0; -} - static int toy_parse_asm_output_operand(ToyParser* p, KitCgAsmOperand* operand) { KitSym op_name; @@ -206,7 +161,6 @@ static int toy_parse_asm_outputs(ToyParser* p, ToyAsmOperandList* outputs) { while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { KitCgAsmOperand operand; if (!toy_parse_asm_output_operand(p, &operand) || - !toy_validate_asm_output_constraint(p, &operand) || !toy_asm_append_operand(p, outputs, &operand)) { return 0; } @@ -227,7 +181,6 @@ static int toy_parse_asm_inputs(ToyParser* p, ToyAsmOperandList* inputs) { while (p->cur.kind != TOK_RPAREN && p->cur.kind != TOK_EOF) { KitCgAsmOperand operand; if (!toy_parse_asm_input_operand(p, &operand) || - !toy_validate_asm_input_constraint(p, &operand) || !toy_asm_append_operand(p, inputs, &operand)) { return 0; } diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -5587,6 +5587,20 @@ static Reg aa_asm_native_mem_base(AANativeTarget* a, SrcLoc loc, NativeLoc src, return dst; } +static Reg aa_asm_stage_reg(AANativeTarget* a, SrcLoc loc, + NativeAllocClass cls, u32* nint, u32* nfp) { + static const Reg int_regs[] = {AA_TMP0, AA_TMP1}; + static const Reg fp_regs[] = {20u, 21u}; + if (cls == NATIVE_REG_FP) { + if (*nfp >= (u32)(sizeof fp_regs / sizeof fp_regs[0])) + aa_asm_panic_at(a->base.c, loc, "too many staged fp asm operands"); + return fp_regs[(*nfp)++]; + } + if (*nint >= (u32)(sizeof int_regs / sizeof int_regs[0])) + aa_asm_panic_at(a->base.c, loc, "too many staged integer asm operands"); + return int_regs[(*nint)++]; +} + static void aa_asm_load_loc_to_reg(AANativeTarget* a, SrcLoc loc, NativeLoc src, NativeLoc dst) { NativeTarget* t = &a->base; @@ -5617,38 +5631,41 @@ static void aa_asm_store_reg_to_loc(AANativeTarget* a, SrcLoc loc, aa_mem_for_type(t, src.type, loc_size32(t, src))); } -static void aa_asm_bind_native(AANativeTarget* a, SrcLoc loc, Operand* out, - const char* constraint, KitCgTypeId type, - NativeLoc src, u32* ntmp) { - const char* body = native_asm_constraint_body(constraint); - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(&a->base, constraint, &info)) { - if (src.kind != NATIVE_LOC_REG) - aa_asm_panic_at(a->base.c, loc, "register asm operand not in a register"); - if (info.fixed_reg != REG_NONE && info.fixed_reg != (Reg)src.v.reg) - aa_asm_panic_at(a->base.c, loc, - "fixed-register asm operand in wrong register"); - if (info.allowed_mask && - ((Reg)src.v.reg >= 32 || - (info.allowed_mask & (1u << (Reg)src.v.reg)) == 0)) - compiler_panic( - a->base.c, loc, - "aarch64 inline asm: constraint %s got cls%u reg%u outside %08x", - constraint, (unsigned)info.cls, (unsigned)src.v.reg, - (unsigned)info.allowed_mask); - aa_asm_bound_reg(out, type, info.cls, (Reg)src.v.reg); - } else if (body[0] == 'i') { - if (src.kind != NATIVE_LOC_IMM) - aa_asm_panic_at(a->base.c, loc, "immediate asm operand is not immediate"); - memset(out, 0, sizeof *out); - out->kind = OPK_IMM; - out->type = type; - out->v.imm = src.v.imm; - } else if (body[0] == 'm') { - aa_asm_bound_mem(out, type, aa_asm_native_mem_base(a, loc, src, ntmp)); - } else { - aa_asm_panic_at(a->base.c, loc, "unsupported asm constraint"); - } +static void aa_asm_native_panic(NativeTarget* t, SrcLoc loc, const char* msg) { + aa_asm_panic_at(t->c, loc, msg); +} + +static Reg aa_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, u32* ntmp) { + return aa_asm_native_mem_base(aa_of(t), loc, src, ntmp); +} + +static Reg aa_asm_native_stage_reg_hook(NativeTarget* t, SrcLoc loc, + NativeAllocClass cls, u32* ntmp, + u32* nstage_int, u32* nstage_fp) { + (void)nstage_int; + return aa_asm_stage_reg(aa_of(t), loc, cls, ntmp, nstage_fp); +} + +static void aa_asm_native_load_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, NativeLoc dst) { + aa_asm_load_loc_to_reg(aa_of(t), loc, src, dst); +} + +static void aa_asm_native_store_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc dst, NativeLoc src) { + aa_asm_store_reg_to_loc(aa_of(t), loc, dst, src); +} + +static void aa_asm_native_run_template_hook( + NativeTarget* t, 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* asmh = aa64_asm_open(t->c); + aa64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + aa64_asm_run_template(asmh, t->mc, tmpl); + aa64_asm_close(asmh); } static void aa_asm_block_native(NativeTarget* t, const char* tmpl, @@ -5657,105 +5674,21 @@ static void aa_asm_block_native(NativeTarget* t, const char* tmpl, u32 nin, const NativeLoc* in_locs, const Sym* clobbers, u32 nclob) { AANativeTarget* a = aa_of(t); - Compiler* c = t->c; SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u8* staged_outs = nout ? arena_zarray(c->tu, u8, nout) : NULL; - u32 ntmp = 0; - AA64Asm* asmh; - - for (u32 i = 0; i < nout; ++i) { - KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; - NativeLoc outloc = out_locs[i]; - NativeAsmPinnedLoc pinned = native_asm_prepare_pinned_loc( - t, outs[i].reg, outs[i].str, type, outloc); - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - aa_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - aa_asm_panic_at(c, loc, "hard-register asm operand in wrong register"); - outloc = pinned.loc; - if (pinned.needs_stage) { - staged_outs[i] = 1u; - if (outs[i].dir == KIT_CG_ASM_INOUT) - aa_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); - } - } - aa_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, - &ntmp); - } - for (u32 i = 0; i < nin; ++i) { - const char* body = native_asm_constraint_body(ins[i].str); - int matched = native_asm_match_index(body); - KitCgTypeId type; - if (matched >= 0) { - if ((u32)matched >= nout) - aa_asm_panic_at(c, loc, "matching constraint out of range"); - bound_ins[i] = bound_outs[matched]; - continue; - } - type = ins[i].type ? ins[i].type : in_locs[i].type; - { - const char* in_body = native_asm_constraint_body(ins[i].str); - NativeAsmConstraintInfo info; - NativeLoc inloc = in_locs[i]; - NativeAsmPinnedLoc pinned = - native_asm_prepare_pinned_loc(t, ins[i].reg, ins[i].str, type, inloc); - /* A register-constrained input whose value is an address-taken local - * arrives in a frame slot: the optimizer cannot keep an address-taken - * local live in a register across the block, so the "inputs are already - * in registers" contract does not hold for it. Load it into a reserved - * scratch register (as the direct path does) before binding. With no - * hard pin, only unrestricted integer constraints can use this scratch; - * restricted register sets must already arrive in an allowed hard - * register. */ - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - aa_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - aa_asm_panic_at(c, loc, - "hard-register asm operand in wrong register"); - inloc = pinned.loc; - if (pinned.needs_stage) - aa_asm_load_loc_to_reg(a, loc, in_locs[i], inloc); - } else if (native_asm_constraint_reg_info(t, ins[i].str, &info) && - info.cls == NATIVE_REG_INT && info.allowed_mask == 0 && - inloc.kind != NATIVE_LOC_REG) { - Reg r; - if (ntmp >= 2u) aa_asm_panic_at(c, loc, "too many memory asm operands"); - r = (ntmp == 0u) ? AA_TMP0 : AA_TMP1; - ntmp++; - inloc = native_loc_reg(type, NATIVE_REG_INT, r); - aa_emit_mem(a, 1, inloc, aa_asm_loc_to_addr(a, loc, in_locs[i]), - aa_mem_for_type(t, type, type_size32(t, type))); - } - (void)in_body; - aa_asm_bind_native(a, loc, &bound_ins[i], ins[i].str, type, inloc, &ntmp); - } - } - - /* No per-block callee-saved spill here: plan_frame forwarded the asm clobber - * masks and aa_known_callee_saves folded the callee-saved ones into the - * function's saved set, so the prologue/epilogue already preserve them. */ - asmh = aa64_asm_open(c); - aa64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - aa64_asm_run_template(asmh, t->mc, tmpl); - aa64_asm_close(asmh); - - for (u32 i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (!staged_outs || !staged_outs[i]) continue; - 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_asm_store_reg_to_loc(a, loc, out_locs[i], src); - } + static const NativeAsmNativeHooks hooks = { + .opk_reg = AA64_INLINE_OPK_REG, + .opcls_fp = AA64_INLINE_OPCLS_FP, + .panic = aa_asm_native_panic, + .bound_reg = aa_asm_bound_reg, + .bound_mem = aa_asm_bound_mem, + .mem_base = aa_asm_native_mem_base_hook, + .stage_reg = aa_asm_native_stage_reg_hook, + .load_loc_to_reg = aa_asm_native_load_loc_hook, + .store_reg_to_loc = aa_asm_native_store_loc_hook, + .run_template = aa_asm_native_run_template_hook, + }; + native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, + in_locs, clobbers, nclob, &hooks); } static const NativeOps aa_direct_ops = { diff --git a/src/arch/arm32/native.c b/src/arch/arm32/native.c @@ -269,6 +269,8 @@ static int arm_asm_constraint_reg(const NativeRegInfo* ri, const char* body, if (allowed_mask_out) *allowed_mask_out = 0; if ((body[0] == 'r' || body[0] == 'l') && body[1] == '\0') { if (cls_out) *cls_out = NATIVE_REG_INT; + if (body[0] == 'l' && allowed_mask_out) + *allowed_mask_out = 0x000000ffu; /* r0..r7, filtered by reg_ok. */ return 1; } return 0; @@ -3506,90 +3508,97 @@ static Reg arm_asm_stage_reg(NativeTarget* t, u32* nstage) { return r; } -/* NativeTarget (optimizer / -O1) inline-asm path: bind operands to registers - * (no self-allocation — the optimizer pre-allocated and forwarded clobbers via - * plan_frame), then run the template. The minimal runner needs only the bound - * register numbers, which arrive in out_locs/in_locs. */ +static void arm_asm_native_panic(NativeTarget* t, SrcLoc loc, + const char* msg) { + arm_asm_panic_at(t->c, loc, msg); +} + +static Reg arm_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, u32* ntmp) { + Reg base = arm_asm_stage_reg(t, ntmp); + NativeAddr ma = arm_asm_in_loc_addr(t, src, src.type); + (void)loc; + arm_load_addr( + t, native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, base), + ma); + return base; +} + +static Reg arm_asm_native_stage_reg_hook(NativeTarget* t, SrcLoc loc, + NativeAllocClass cls, u32* ntmp, + u32* nstage_int, u32* nstage_fp) { + (void)loc; + (void)nstage_int; + (void)nstage_fp; + if (cls != NATIVE_REG_INT) + arm_asm_panic_at(t->c, arm_of(t)->loc, + "floating-point asm operands are unsupported"); + return arm_asm_stage_reg(t, ntmp); +} + +static void arm_asm_native_load_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, NativeLoc dst) { + (void)loc; + if (dst.cls != NATIVE_REG_INT) + arm_asm_panic_at(t->c, arm_of(t)->loc, + "floating-point asm operands are unsupported"); + if (src.kind == NATIVE_LOC_REG) { + if (src.v.reg != dst.v.reg || src.cls != dst.cls) arm_move(t, dst, src); + return; + } + if (src.kind == NATIVE_LOC_IMM) { + arm_load_imm(t, dst, src.v.imm); + return; + } + arm_load(t, dst, arm_asm_in_loc_addr(t, src, dst.type), + native_mem_for_type(t, dst.type, native_type_size(t, dst.type))); +} + +static void arm_asm_native_store_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc dst, NativeLoc src) { + (void)loc; + if (src.cls != NATIVE_REG_INT) + arm_asm_panic_at(t->c, arm_of(t)->loc, + "floating-point asm operands are unsupported"); + if (dst.kind == NATIVE_LOC_REG) { + if (dst.v.reg != src.v.reg || dst.cls != src.cls) arm_move(t, dst, src); + return; + } + arm_store(t, arm_asm_in_loc_addr(t, dst, src.type), src, + native_mem_for_type(t, src.type, native_type_size(t, src.type))); +} + +static void arm_asm_native_run_template_hook( + NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout, + Operand* bound_outs, const AsmConstraint* ins, u32 nin, Operand* bound_ins, + const Sym* clobbers, u32 nclob) { + Arm32Asm* asmh = arm32_asm_open(t->c); + arm32_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + arm32_asm_run_template(asmh, t->mc, tmpl); + arm32_asm_close(asmh); +} + static void arm_asm_block_native(NativeTarget* t, const char* tmpl, const AsmConstraint* outs, u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, u32 nin, const NativeLoc* in_locs, const Sym* clobbers, u32 nclob) { - Compiler* c = t->c; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u32 i; - u32 nstage = 0u; /* reserved scratch (IP, then LR) consumed for staged inputs */ - (void)clobbers; - (void)nclob; - for (i = 0; i < nout; ++i) { - KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; - if (out_locs[i].kind != NATIVE_LOC_REG) - arm_asm_panic_at(c, arm_of(t)->loc, - "optimizer asm output not in a register"); - arm_asm_bound_reg(&bound_outs[i], type, (NativeAllocClass)out_locs[i].cls, - (Reg)out_locs[i].v.reg); - } - 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; - if (matched >= 0) { - if ((u32)matched >= nout) - arm_asm_panic_at(c, arm_of(t)->loc, "matching constraint out of range"); - bound_ins[i] = bound_outs[matched]; - continue; - } - type = ins[i].type ? ins[i].type : in_locs[i].type; - if (in_locs[i].kind == NATIVE_LOC_REG) { - arm_asm_bound_reg(&bound_ins[i], type, (NativeAllocClass)in_locs[i].cls, - (Reg)in_locs[i].v.reg); - } else if (body[0] == 'i' && in_locs[i].kind == NATIVE_LOC_IMM) { - /* Immediate ("i"/"n") constraint: bind the literal value, no register. */ - memset(&bound_ins[i], 0, sizeof bound_ins[i]); - bound_ins[i].kind = OPK_IMM; - bound_ins[i].type = type; - bound_ins[i].v.imm = in_locs[i].v.imm; - } else if (body[0] == 'm') { - /* Memory ("m") constraint: bind a base register holding the operand's - * address. The optimizer hands a computed address (NATIVE_LOC_ADDR) or a - * frame/stack home; either way materialize the address into a reserved - * staging scratch and bind it as the memory base. */ - Reg base = arm_asm_stage_reg(t, &nstage); - NativeAddr ma = arm_asm_in_loc_addr(t, in_locs[i], type); - arm_load_addr( - t, native_loc_reg(builtin_id(KIT_CG_BUILTIN_I32), NATIVE_REG_INT, base), - ma); - arm_asm_bound_mem(&bound_ins[i], type, base); - } else if (body[0] == 'r' && body[1] == '\0' && !ins[i].reg) { - /* Plain register ("r") input whose value is NOT already in a register: an - * address-stable / frame-resident local (the optimizer cannot keep an - * address-taken local live in a register across the asm block), or a - * folded immediate. Stage the VALUE into a reserved scratch and bind that - * register, mirroring the aa64 native asm path. Only the unrestricted "r" - * class can use IP/LR; the low-reg "l" class (and hard-register pins) must - * already arrive in an allowed register, so they fall through to the - * diagnostic below. */ - Reg sr = arm_asm_stage_reg(t, &nstage); - NativeLoc sd = native_loc_reg(type, NATIVE_REG_INT, sr); - if (in_locs[i].kind == NATIVE_LOC_IMM) - arm_load_imm(t, sd, in_locs[i].v.imm); - else - arm_load(t, sd, arm_asm_in_loc_addr(t, in_locs[i], type), - native_mem_for_type(t, type, native_type_size(t, type))); - arm_asm_bound_reg(&bound_ins[i], type, NATIVE_REG_INT, sr); - } else { - arm_asm_panic_at(c, arm_of(t)->loc, - "optimizer asm input not in a register"); - } - } - { - Arm32Asm* asmh = arm32_asm_open(c); - arm32_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, - clobbers, nclob); - arm32_asm_run_template(asmh, t->mc, tmpl); - arm32_asm_close(asmh); - } + SrcLoc loc = arm_of(t)->loc; + static const NativeAsmNativeHooks hooks = { + .opk_reg = ARM32_INLINE_OPK_REG, + .opcls_fp = ARM32_INLINE_OPCLS_FP, + .panic = arm_asm_native_panic, + .bound_reg = arm_asm_bound_reg, + .bound_mem = arm_asm_bound_mem, + .mem_base = arm_asm_native_mem_base_hook, + .stage_reg = arm_asm_native_stage_reg_hook, + .load_loc_to_reg = arm_asm_native_load_loc_hook, + .store_reg_to_loc = arm_asm_native_store_loc_hook, + .run_template = arm_asm_native_run_template_hook, + }; + native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, + in_locs, clobbers, nclob, &hooks); } /* ============================ construction ============================ */ diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -3918,35 +3918,41 @@ static void rv_asm_store_reg_to_loc(RvNativeTarget* a, SrcLoc loc, native_mem_for_type(t, src.type, rv_asm_reg_mem_size(a, cls, src.type))); } -static void rv_asm_bind_native(RvNativeTarget* a, SrcLoc loc, Operand* out, - const char* constraint, KitCgTypeId type, - NativeLoc src, u32* ntmp) { - const char* body = native_asm_constraint_body(constraint); - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(&a->base, constraint, &info)) { - if (src.kind != NATIVE_LOC_REG) - rv_asm_panic_at(a->base.c, loc, "register asm operand not in a register"); - if (info.fixed_reg != REG_NONE && info.fixed_reg != (Reg)src.v.reg) - rv_asm_panic_at(a->base.c, loc, - "fixed-register asm operand in wrong register"); - if (info.allowed_mask && - ((Reg)src.v.reg >= 32 || - (info.allowed_mask & (1u << (Reg)src.v.reg)) == 0)) - rv_asm_panic_at(a->base.c, loc, - "register asm operand violates constraint register set"); - rv_asm_bound_reg(out, type, info.cls, (Reg)src.v.reg); - } else if (body[0] == 'i') { - if (src.kind != NATIVE_LOC_IMM) - rv_asm_panic_at(a->base.c, loc, "immediate asm operand is not immediate"); - memset(out, 0, sizeof *out); - out->kind = OPK_IMM; - out->type = type; - out->v.imm = src.v.imm; - } else if (body[0] == 'm') { - rv_asm_bound_mem(out, type, rv_asm_native_mem_base(a, loc, src, ntmp)); - } else { - rv_asm_panic_at(a->base.c, loc, "unsupported asm constraint"); - } +static void rv_asm_native_panic(NativeTarget* t, SrcLoc loc, const char* msg) { + rv_asm_panic_at(t->c, loc, msg); +} + +static Reg rv_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, u32* ntmp) { + return rv_asm_native_mem_base(rv_of(t), loc, src, ntmp); +} + +static Reg rv_asm_native_stage_reg_hook(NativeTarget* t, SrcLoc loc, + NativeAllocClass cls, u32* ntmp, + u32* nstage_int, u32* nstage_fp) { + (void)ntmp; + return rv_asm_stage_reg(rv_of(t), loc, cls, nstage_int, nstage_fp); +} + +static void rv_asm_native_load_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, NativeLoc dst) { + rv_asm_load_loc_to_reg(rv_of(t), loc, src, dst); +} + +static void rv_asm_native_store_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc dst, NativeLoc src) { + rv_asm_store_reg_to_loc(rv_of(t), loc, dst, src); +} + +static void rv_asm_native_run_template_hook( + NativeTarget* t, 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(t->c); + rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + rv64_asm_run_template(asmh, t->mc, tmpl); + rv64_asm_close(asmh); } static void rv_asm_block_native(NativeTarget* t, const char* tmpl, @@ -3955,99 +3961,21 @@ static void rv_asm_block_native(NativeTarget* t, const char* tmpl, u32 nin, const NativeLoc* in_locs, const Sym* clobbers, u32 nclob) { RvNativeTarget* a = rv_of(t); - Compiler* c = t->c; SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u8* staged_outs = nout ? arena_zarray(c->tu, u8, nout) : NULL; - u32 ntmp = 0, nstage_int = 0, nstage_fp = 0, i; - Rv64Asm* asmh; - - for (i = 0; i < nout; ++i) { - KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; - NativeLoc outloc = out_locs[i]; - NativeAsmConstraintInfo info; - NativeAsmPinnedLoc pinned = native_asm_prepare_pinned_loc( - t, outs[i].reg, outs[i].str, type, outloc); - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - rv_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - rv_asm_panic_at(c, loc, "hard-register asm operand in wrong register"); - outloc = pinned.loc; - if (pinned.needs_stage) { - staged_outs[i] = 1u; - if (outs[i].dir == KIT_CG_ASM_INOUT) - rv_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); - } - } else if (native_asm_constraint_reg_info(t, outs[i].str, &info) && - info.allowed_mask == 0 && outloc.kind != NATIVE_LOC_REG) { - Reg r = rv_asm_stage_reg(a, loc, info.cls, &nstage_int, &nstage_fp); - outloc = native_loc_reg(type, info.cls, r); - staged_outs[i] = 1u; - if (outs[i].dir == KIT_CG_ASM_INOUT) - rv_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); - } - rv_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, - &ntmp); - } - 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; - NativeLoc inloc; - if (matched >= 0) { - if ((u32)matched >= nout) - rv_asm_panic_at(c, loc, "matching constraint out of range"); - bound_ins[i] = bound_outs[matched]; - continue; - } - type = ins[i].type ? ins[i].type : in_locs[i].type; - inloc = in_locs[i]; - { - NativeAsmConstraintInfo info; - NativeAsmPinnedLoc pinned = - native_asm_prepare_pinned_loc(t, ins[i].reg, ins[i].str, type, inloc); - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - rv_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - rv_asm_panic_at(c, loc, - "hard-register asm operand in wrong register"); - inloc = pinned.loc; - if (pinned.needs_stage) - rv_asm_load_loc_to_reg(a, loc, in_locs[i], inloc); - } else if (native_asm_constraint_reg_info(t, ins[i].str, &info) && - info.allowed_mask == 0 && inloc.kind != NATIVE_LOC_REG) { - Reg r = rv_asm_stage_reg(a, loc, info.cls, &nstage_int, &nstage_fp); - inloc = native_loc_reg(type, info.cls, r); - rv_asm_load_loc_to_reg(a, loc, in_locs[i], inloc); - } - } - rv_asm_bind_native(a, loc, &bound_ins[i], ins[i].str, type, inloc, &ntmp); - } - - /* No per-block callee-saved spill here: plan_frame forwarded the asm clobber - * masks and rv_known_callee_saves folded the callee-saved ones into the - * function's saved set, so the prologue/epilogue already preserve them. */ - asmh = rv64_asm_open(c); - rv64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - rv64_asm_run_template(asmh, t->mc, tmpl); - rv64_asm_close(asmh); - - for (i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (!staged_outs || !staged_outs[i]) continue; - 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_asm_store_reg_to_loc(a, loc, out_locs[i], src); - } + static const NativeAsmNativeHooks hooks = { + .opk_reg = RV64_INLINE_OPK_REG, + .opcls_fp = RV64_INLINE_OPCLS_FP, + .panic = rv_asm_native_panic, + .bound_reg = rv_asm_bound_reg, + .bound_mem = rv_asm_bound_mem, + .mem_base = rv_asm_native_mem_base_hook, + .stage_reg = rv_asm_native_stage_reg_hook, + .load_loc_to_reg = rv_asm_native_load_loc_hook, + .store_reg_to_loc = rv_asm_native_store_loc_hook, + .run_template = rv_asm_native_run_template_hook, + }; + native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, + in_locs, clobbers, nclob, &hooks); } /* file_scope_asm + finalize are shared (cg/native_asm.h). */ diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -4083,6 +4083,20 @@ static Reg x64_asm_native_mem_base(X64NativeTarget* a, SrcLoc loc, return dst; } +static Reg x64_asm_stage_reg(X64NativeTarget* a, SrcLoc loc, + NativeAllocClass cls, u32* nint, u32* nfp) { + static const Reg int_regs[] = {X64_TMP_INT, X64_TMP_INT2}; + static const Reg fp_regs[] = {X64_TMP_FP, X64_TMP_FP2}; + if (cls == NATIVE_REG_FP) { + if (*nfp >= (u32)(sizeof fp_regs / sizeof fp_regs[0])) + x64_asm_panic_at(a->base.c, loc, "too many staged fp asm operands"); + return fp_regs[(*nfp)++]; + } + if (*nint >= (u32)(sizeof int_regs / sizeof int_regs[0])) + x64_asm_panic_at(a->base.c, loc, "too many staged integer asm operands"); + return int_regs[(*nint)++]; +} + static void x64_asm_load_loc_to_reg(X64NativeTarget* a, SrcLoc loc, NativeLoc src, NativeLoc dst) { NativeTarget* t = &a->base; @@ -4113,37 +4127,42 @@ static void x64_asm_store_reg_to_loc(X64NativeTarget* a, SrcLoc loc, native_mem_for_type(t, src.type, loc_size32(t, src))); } -static void x64_asm_bind_native(X64NativeTarget* a, SrcLoc loc, Operand* out, - const char* constraint, KitCgTypeId type, - NativeLoc src, u32* ntmp) { - const char* body = native_asm_constraint_body(constraint); - NativeAsmConstraintInfo info; - if (native_asm_constraint_reg_info(&a->base, constraint, &info)) { - if (src.kind != NATIVE_LOC_REG) - x64_asm_panic_at(a->base.c, loc, - "register asm operand not in a register"); - if (info.fixed_reg != REG_NONE && info.fixed_reg != (Reg)src.v.reg) - x64_asm_panic_at(a->base.c, loc, - "fixed-register asm operand in wrong register"); - if (info.allowed_mask && - ((Reg)src.v.reg >= 32 || - (info.allowed_mask & (1u << (Reg)src.v.reg)) == 0)) - x64_asm_panic_at(a->base.c, loc, - "register asm operand violates constraint register set"); - x64_asm_bound_reg(out, type, info.cls, (Reg)src.v.reg); - } else if (body[0] == 'i') { - if (src.kind != NATIVE_LOC_IMM) - x64_asm_panic_at(a->base.c, loc, - "immediate asm operand is not immediate"); - memset(out, 0, sizeof *out); - out->kind = OPK_IMM; - out->type = type; - out->v.imm = src.v.imm; - } else if (body[0] == 'm') { - x64_asm_bound_mem(out, type, x64_asm_native_mem_base(a, loc, src, ntmp)); - } else { - x64_asm_panic_at(a->base.c, loc, "unsupported asm constraint"); - } +static void x64_asm_native_panic(NativeTarget* t, SrcLoc loc, + const char* msg) { + x64_asm_panic_at(t->c, loc, msg); +} + +static Reg x64_asm_native_mem_base_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, u32* ntmp) { + return x64_asm_native_mem_base(x64_of(t), loc, src, ntmp); +} + +static Reg x64_asm_native_stage_reg_hook(NativeTarget* t, SrcLoc loc, + NativeAllocClass cls, u32* ntmp, + u32* nstage_int, u32* nstage_fp) { + (void)nstage_int; + return x64_asm_stage_reg(x64_of(t), loc, cls, ntmp, nstage_fp); +} + +static void x64_asm_native_load_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc src, NativeLoc dst) { + x64_asm_load_loc_to_reg(x64_of(t), loc, src, dst); +} + +static void x64_asm_native_store_loc_hook(NativeTarget* t, SrcLoc loc, + NativeLoc dst, NativeLoc src) { + x64_asm_store_reg_to_loc(x64_of(t), loc, dst, src); +} + +static void x64_asm_native_run_template_hook( + NativeTarget* t, 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(t->c); + x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + x64_asm_run_template(asmh, t->mc, tmpl); + x64_asm_close(asmh); } static void x64_asm_block_native(NativeTarget* t, const char* tmpl, @@ -4152,94 +4171,21 @@ static void x64_asm_block_native(NativeTarget* t, const char* tmpl, u32 nin, const NativeLoc* in_locs, const Sym* clobbers, u32 nclob) { X64NativeTarget* a = x64_of(t); - Compiler* c = t->c; SrcLoc loc = a->func ? a->func->loc : (SrcLoc){0, 0, 0}; - Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; - Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; - u8* staged_outs = nout ? arena_zarray(c->tu, u8, nout) : NULL; - u32 ntmp = 0, i; - X64Asm* asmh; - - for (i = 0; i < nout; ++i) { - KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; - NativeLoc outloc = out_locs[i]; - NativeAsmPinnedLoc pinned = native_asm_prepare_pinned_loc( - t, outs[i].reg, outs[i].str, type, outloc); - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - x64_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - x64_asm_panic_at(c, loc, "hard-register asm operand in wrong register"); - outloc = pinned.loc; - if (pinned.needs_stage) { - staged_outs[i] = 1u; - if (outs[i].dir == KIT_CG_ASM_INOUT) - x64_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); - } - } - x64_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, - &ntmp); - } - 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; - NativeLoc inloc; - if (matched >= 0) { - if ((u32)matched >= nout) - x64_asm_panic_at(c, loc, "matching constraint out of range"); - bound_ins[i] = bound_outs[matched]; - continue; - } - type = ins[i].type ? ins[i].type : in_locs[i].type; - inloc = in_locs[i]; - { - NativeAsmPinnedLoc pinned = - native_asm_prepare_pinned_loc(t, ins[i].reg, ins[i].str, type, inloc); - if (pinned.has_pin) { - if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) - x64_asm_panic_at(c, loc, - native_asm_pin_status_message(pinned.pin_status)); - if (pinned.wrong_reg) - x64_asm_panic_at(c, loc, - "hard-register asm operand in wrong register"); - inloc = pinned.loc; - if (pinned.needs_stage) - x64_asm_load_loc_to_reg(a, loc, in_locs[i], inloc); - } else if ((body[0] == 'r') && inloc.kind != NATIVE_LOC_REG) { - Reg r; - if (ntmp >= 2u) - x64_asm_panic_at(c, loc, "too many memory asm operands"); - r = (ntmp == 0u) ? (Reg)X64_TMP_INT : (Reg)X64_TMP_INT2; - ntmp++; - inloc = native_loc_reg(type, NATIVE_REG_INT, r); - x64_emit_mem(a, 1, inloc, x64_asm_loc_to_addr(a, loc, in_locs[i]), - native_mem_for_type(t, type, native_type_size(t, type))); - } - } - x64_asm_bind_native(a, loc, &bound_ins[i], ins[i].str, type, inloc, &ntmp); - } - - /* No per-block callee-saved spill here: plan_frame forwarded the asm clobber - * masks and x64_known_callee_saves folded the callee-saved ones into the - * function's saved set, so the prologue/epilogue already preserve them. */ - asmh = x64_asm_open(c); - x64_inline_bind(asmh, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, - nclob); - x64_asm_run_template(asmh, t->mc, tmpl); - x64_asm_close(asmh); - - for (i = 0; i < nout; ++i) { - NativeAllocClass cls; - NativeLoc src; - if (!staged_outs || !staged_outs[i]) continue; - 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_asm_store_reg_to_loc(a, loc, out_locs[i], src); - } + static const NativeAsmNativeHooks hooks = { + .opk_reg = X64_INLINE_OPK_REG, + .opcls_fp = X64_INLINE_OPCLS_FP, + .panic = x64_asm_native_panic, + .bound_reg = x64_asm_bound_reg, + .bound_mem = x64_asm_bound_mem, + .mem_base = x64_asm_native_mem_base_hook, + .stage_reg = x64_asm_native_stage_reg_hook, + .load_loc_to_reg = x64_asm_native_load_loc_hook, + .store_reg_to_loc = x64_asm_native_store_loc_hook, + .run_template = x64_asm_native_run_template_hook, + }; + native_asm_bind_native_operands(t, loc, tmpl, outs, nout, out_locs, ins, nin, + in_locs, clobbers, nclob, &hooks); } /* file_scope_asm + finalize are shared (cg/native_asm.h). */ diff --git a/src/cg/asm.c b/src/cg/asm.c @@ -8,25 +8,15 @@ const char* api_sym_cstr(KitCg* g, KitSym sym) { } int api_asm_parse_match_index(const char* s) { - int n; - if (!s || s[0] < '0' || s[0] > '9') return -1; - n = 0; - for (const char* p = s; *p >= '0' && *p <= '9'; ++p) { - n = n * 10 + (*p - '0'); - } - return n; + return kit_cg_asm_constraint_match_index(s); } const char* api_asm_constraint_body(const char* s) { - if (!s) return ""; - if (s[0] == '=' && s[1] == '&') return s + 2; - if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; - return s; + return kit_cg_asm_constraint_body(s); } int api_asm_is_early_clobber(const char* s) { - if (!s) return 0; - return (s[0] == '=' && s[1] == '&') || s[0] == '&'; + return kit_cg_asm_constraint_early(s); } /* Does this constraint body name a register operand (one that binds to a temp @@ -99,8 +89,12 @@ void kit_cg_inline_asm(KitCg* g, KitCgInlineAsm asm_block) { const KitSym* clobbers = asm_block.clobbers; uint32_t nclobbers = asm_block.nclobbers; uint32_t clobber_abi_sets = asm_block.clobber_abi_sets; - (void)asm_block.flags; + uint32_t flags = asm_block.flags; if (!g) return; + if (flags & ~KIT_CG_ASM_VOLATILE) { + compiler_panic(g->c, g->cur_loc, + "KitCg: unsupported inline asm flags"); + } if (api_unevaluated(g)) { uint32_t ninout_u = 0; for (u32 i = 0; i < noutputs; ++i) diff --git a/src/cg/native_asm.c b/src/cg/native_asm.c @@ -1,5 +1,7 @@ #include "cg/native_asm.h" +#include <kit/asm_constraints.h> + #include "arch/mc.h" #include "asm/asm.h" #include "asm/asm_lex.h" @@ -19,23 +21,15 @@ void native_finalize(NativeTarget* t) { } const char* native_asm_constraint_body(const char* s) { - if (!s) return ""; - if (s[0] == '=' && s[1] == '&') return s + 2; - if (s[0] == '=' || s[0] == '+' || s[0] == '&') return s + 1; - return s; + return kit_cg_asm_constraint_body(s); } int native_asm_constraint_early(const char* s) { - if (!s) return 0; - return (s[0] == '=' && s[1] == '&') || s[0] == '&'; + return kit_cg_asm_constraint_early(s); } int native_asm_match_index(const char* s) { - int n = 0; - const char* p; - if (!s || s[0] < '0' || s[0] > '9') return -1; - for (p = s; *p >= '0' && *p <= '9'; ++p) n = n * 10 + (*p - '0'); - return n; + return kit_cg_asm_constraint_match_index(s); } void native_asm_abi_clobber_masks(NativeTarget* t, u32 abi_sets, u32* int_mask, @@ -378,3 +372,128 @@ void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl, } for (i = nsaved; i > 0; --i) h->restore_one(d, saved, i - 1u); } + +static void native_asm_bind_native_one(NativeTarget* t, SrcLoc loc, + Operand* out, const char* constraint, + KitCgTypeId type, NativeLoc src, + u32* ntmp, + const NativeAsmNativeHooks* h) { + const char* body = native_asm_constraint_body(constraint); + NativeAsmConstraintInfo info; + if (native_asm_constraint_reg_info(t, constraint, &info)) { + if (src.kind != NATIVE_LOC_REG) + h->panic(t, loc, "register asm operand not in a register"); + if (info.fixed_reg != REG_NONE && info.fixed_reg != (Reg)src.v.reg) + h->panic(t, loc, "fixed-register asm operand in wrong register"); + if (info.allowed_mask && + ((Reg)src.v.reg >= 32 || + (info.allowed_mask & (1u << (Reg)src.v.reg)) == 0)) + h->panic(t, loc, "register asm operand violates constraint register set"); + h->bound_reg(out, type, info.cls, (Reg)src.v.reg); + } else if (body[0] == 'i') { + if (src.kind != NATIVE_LOC_IMM) + h->panic(t, loc, "immediate asm operand is not immediate"); + memset(out, 0, sizeof *out); + out->kind = OPK_IMM; + out->type = type; + out->v.imm = src.v.imm; + } else if (body[0] == 'm') { + h->bound_mem(out, type, h->mem_base(t, loc, src, ntmp)); + } else { + h->panic(t, loc, "unsupported asm constraint"); + } +} + +void native_asm_bind_native_operands( + NativeTarget* t, SrcLoc loc, const char* tmpl, const AsmConstraint* outs, + u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, u32 nin, + const NativeLoc* in_locs, const Sym* clobbers, u32 nclob, + const NativeAsmNativeHooks* h) { + Compiler* c = t->c; + Operand* bound_outs = nout ? arena_zarray(c->tu, Operand, nout) : NULL; + Operand* bound_ins = nin ? arena_zarray(c->tu, Operand, nin) : NULL; + u8* staged_outs = nout ? arena_zarray(c->tu, u8, nout) : NULL; + u32 ntmp = 0, nstage_int = 0, nstage_fp = 0; + + for (u32 i = 0; i < nout; ++i) { + KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; + NativeLoc outloc = out_locs[i]; + NativeAsmConstraintInfo info; + NativeAsmPinnedLoc pinned = + native_asm_prepare_pinned_loc(t, outs[i].reg, outs[i].str, type, outloc); + if (pinned.has_pin) { + if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) + h->panic(t, loc, native_asm_pin_status_message(pinned.pin_status)); + if (pinned.wrong_reg) + h->panic(t, loc, "hard-register asm operand in wrong register"); + outloc = pinned.loc; + if (pinned.needs_stage) { + staged_outs[i] = 1u; + if (outs[i].dir == KIT_CG_ASM_INOUT) + h->load_loc_to_reg(t, loc, out_locs[i], outloc); + } + } else if (native_asm_constraint_reg_info(t, outs[i].str, &info) && + info.allowed_mask == 0 && outloc.kind != NATIVE_LOC_REG) { + Reg r = + h->stage_reg(t, loc, info.cls, &ntmp, &nstage_int, &nstage_fp); + outloc = native_loc_reg(type, info.cls, r); + staged_outs[i] = 1u; + if (outs[i].dir == KIT_CG_ASM_INOUT) + h->load_loc_to_reg(t, loc, out_locs[i], outloc); + } + native_asm_bind_native_one(t, loc, &bound_outs[i], outs[i].str, type, + outloc, &ntmp, h); + } + + for (u32 i = 0; i < nin; ++i) { + const char* body = native_asm_constraint_body(ins[i].str); + int matched = native_asm_match_index(body); + KitCgTypeId type; + NativeLoc inloc; + if (matched >= 0) { + if ((u32)matched >= nout) + h->panic(t, loc, "matching constraint out of range"); + if (native_asm_constraint_early(outs[matched].str)) + h->panic(t, loc, "matching input names early-clobber output"); + bound_ins[i] = bound_outs[matched]; + continue; + } + type = ins[i].type ? ins[i].type : in_locs[i].type; + inloc = in_locs[i]; + { + NativeAsmConstraintInfo info; + NativeAsmPinnedLoc pinned = + native_asm_prepare_pinned_loc(t, ins[i].reg, ins[i].str, type, inloc); + if (pinned.has_pin) { + if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) + h->panic(t, loc, native_asm_pin_status_message(pinned.pin_status)); + if (pinned.wrong_reg) + h->panic(t, loc, "hard-register asm operand in wrong register"); + inloc = pinned.loc; + if (pinned.needs_stage) + h->load_loc_to_reg(t, loc, in_locs[i], inloc); + } else if (native_asm_constraint_reg_info(t, ins[i].str, &info) && + info.allowed_mask == 0 && inloc.kind != NATIVE_LOC_REG) { + Reg r = + h->stage_reg(t, loc, info.cls, &ntmp, &nstage_int, &nstage_fp); + inloc = native_loc_reg(type, info.cls, r); + h->load_loc_to_reg(t, loc, in_locs[i], inloc); + } + } + native_asm_bind_native_one(t, loc, &bound_ins[i], ins[i].str, type, inloc, + &ntmp, h); + } + + h->run_template(t, tmpl, outs, nout, bound_outs, ins, nin, bound_ins, clobbers, + nclob); + + for (u32 i = 0; i < nout; ++i) { + NativeAllocClass cls; + NativeLoc src; + if (!staged_outs || !staged_outs[i]) continue; + 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_loc(t, loc, out_locs[i], src); + } +} diff --git a/src/cg/native_asm.h b/src/cg/native_asm.h @@ -19,10 +19,9 @@ void native_finalize(NativeTarget* t); /* ---- Inline-asm constraint-string helpers ---- * - * Pure, target-neutral parsing of a GCC-style operand constraint string. These - * were byte-identical across the aa64/rv64/x64 inline-asm lowering paths, so - * they live here as the single source of truth. They read only the string, - * never any target state. + * Pure, target-neutral parsing of a GCC-style operand constraint string. + * Definitions wrap the shared public inline helpers in kit/asm_constraints.h so + * frontend, CG, native, and test code all use the same grammar. */ /* Skip the leading modifier flags ('=', '+', '&', and the '=&' early-clobber @@ -167,4 +166,40 @@ void native_asm_bind_direct_operands(NativeDirectTarget* d, const char* tmpl, u32 clobber_abi_sets, const NativeAsmDirectHooks* h); +/* ---- Optimized NativeTarget inline-asm binding ---- + * + * The optimizer allocates the operand locations before final native emission. + * This shared driver validates those locations against the same + * NativeAsmConstraintInfo used by regalloc, stages frame-resident unrestricted + * register operands through reserved scratch registers, binds memory operands to + * concrete base registers, runs the arch assembler, and stores staged outputs + * back. */ +typedef struct NativeAsmNativeHooks { + u8 opk_reg; + u8 opcls_fp; + u8 pad[2]; + + void (*panic)(NativeTarget* t, SrcLoc loc, const char* msg); + void (*bound_reg)(Operand* out, KitCgTypeId type, NativeAllocClass cls, + Reg reg); + void (*bound_mem)(Operand* out, KitCgTypeId type, Reg base); + Reg (*mem_base)(NativeTarget* t, SrcLoc loc, NativeLoc src, u32* ntmp); + Reg (*stage_reg)(NativeTarget* t, SrcLoc loc, NativeAllocClass cls, u32* ntmp, + u32* nstage_int, u32* nstage_fp); + void (*load_loc_to_reg)(NativeTarget* t, SrcLoc loc, NativeLoc src, + NativeLoc dst); + void (*store_reg_to_loc)(NativeTarget* t, SrcLoc loc, NativeLoc dst, + NativeLoc src); + void (*run_template)(NativeTarget* t, const char* tmpl, + const AsmConstraint* outs, u32 nout, Operand* bound_outs, + const AsmConstraint* ins, u32 nin, Operand* bound_ins, + const Sym* clobbers, u32 nclob); +} NativeAsmNativeHooks; + +void native_asm_bind_native_operands( + NativeTarget* t, SrcLoc loc, const char* tmpl, const AsmConstraint* outs, + u32 nout, NativeLoc* out_locs, const AsmConstraint* ins, u32 nin, + const NativeLoc* in_locs, const Sym* clobbers, u32 nclob, + const NativeAsmNativeHooks* h); + #endif diff --git a/src/opt/pass_machinize.c b/src/opt/pass_machinize.c @@ -54,6 +54,15 @@ static void asm_prepare_constraints(Func* f, NativeTarget* target, if (native_resolve_reg(target, nm, &r, &cls) != 0) continue; if ((u32)cls < OPT_REG_CLASSES && r < 32) aux->clobber_mask[cls] |= 1u << r; } + if (aux->clobber_abi_sets) { + u32 int_mask, fp_mask; + native_asm_abi_clobber_masks(target, aux->clobber_abi_sets, &int_mask, + &fp_mask); + if (NATIVE_REG_INT < OPT_REG_CLASSES) + aux->clobber_mask[NATIVE_REG_INT] |= int_mask; + if (NATIVE_REG_FP < OPT_REG_CLASSES) + aux->clobber_mask[NATIVE_REG_FP] |= fp_mask; + } for (u32 i = 0; i < aux->nout; ++i) { NativeAsmRegPin pin; NativeAsmRegPinStatus st = native_asm_resolve_pin(target, aux->outs[i].reg, diff --git a/src/opt/pass_native_emit.c b/src/opt/pass_native_emit.c @@ -1521,9 +1521,9 @@ static void plan_frame(NativeEmitCtx* e, const CGFuncDesc* fd) { } } /* Gather the union of every asm block's clobber names and hard-register - * operand pins. The backend resolves them with its own clobber parser - * (machinize's resolve_name is unset on every backend, so aux->clobber_mask - * is unreliable here). */ + * operand pins for known-frame callee-save reservation. Keep the raw names: + * each backend's frame planner uses its clobber parser so diagnostics and + * ABI exclusions match final emission. */ if (nasm_clob) { u32 n = 0; asm_clobbers = arena_array(e->f->arena, Sym, nasm_clob); diff --git a/test/toy/err/invalid_asm_input_constraint.toy b/test/toy/err/invalid_asm_input_constraint.toy @@ -1,4 +1,4 @@ fn main(): i64 { - @asm<void>("", outputs(), inputs(in("x", 1))); + @asm<void>("", outputs(), inputs(in("?", 1))); return 0; }