commit 82b58f328c50dd20e30489b5f331384ee727d1ac
parent 0282ae36cdb7c754f96c931b6c0101d5ee56d31f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Wed, 3 Jun 2026 10:45:07 -0700
econ(native-shared): share inline-asm constraint + abi-clobber helpers
Sub-refactor (C), partial: the inline-asm constraint-string parsers
(constraint_body / constraint_early / match_index) were byte-identical across
aa64/rv64/x64, and abi_clobber_masks differed only in which static register
table it read. Hoist all four into cg/native_asm as native_asm_constraint_body
/early/match_index and native_asm_abi_clobber_masks (the latter reading the
masks from t->regs->classes, so it needs no per-arch table). Each backend now
calls the shared versions.
The full native_asm_block_direct/optimizer vtable extraction is NOT done (see
report): the arch-private Operand-binding ABI (*_INLINE_OPK/OPCLS), per-arch
*_inline_bind / *_asm_run_template / *_emit_mem / *_addr_base, and the
save/restore predicates make it XL/high-risk on the -O0 codegen path.
(cherry picked from commit f1a7f310da6d07faf407242e49a8a32df1d0c9f3)
Diffstat:
5 files changed, 107 insertions(+), 128 deletions(-)
diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c
@@ -1557,9 +1557,8 @@ static int aa_frame_slot_debug_loc(NativeTarget* t, NativeFrameSlot slot,
static void aa_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers,
u32 nclob, u32* int_mask, u32* fp_mask);
-/* Defined after aa_classes (below); forward-declared so the frame helper can
- * use it. Expands KitCgAsmClobberAbiSet bits into per-class register masks. */
-static void aa_abi_clobber_masks(u32 abi_sets, u32* int_mask, u32* fp_mask);
+/* abi_clobber_masks is shared as native_asm_abi_clobber_masks
+ * (cg/native_asm.h); it reads the masks from t->regs->classes. */
/* Build the callee-saved set the prologue must preserve: the allocator-assigned
* callee-saved registers (frame->callee_saved_used) plus any an inline-asm
@@ -1584,7 +1583,8 @@ static u32 aa_known_callee_saves(NativeTarget* t,
aa_asm_clobber_masks(t->c, loc, frame->asm_clobbers, frame->nasm_clobbers,
&clob_int, &clob_fp);
}
- aa_abi_clobber_masks(frame->asm_clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(t, frame->asm_clobber_abi_sets, &abi_int,
+ &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
for (Reg r = 0; r < 32u; ++r) {
@@ -3666,23 +3666,6 @@ static const NativeRegInfo aa_reg_info = {
.nclasses = sizeof aa_classes / sizeof aa_classes[0],
};
-/* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into
- * this target's per-class caller/callee-saved register masks. Forward-declared
- * earlier for aa_known_callee_saves; defined here where aa_classes is in scope.
- */
-static void aa_abi_clobber_masks(u32 abi_sets, u32* int_mask, u32* fp_mask) {
- *int_mask = 0;
- *fp_mask = 0;
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED) {
- *int_mask |= aa_classes[NATIVE_REG_INT].caller_saved_mask;
- *fp_mask |= aa_classes[NATIVE_REG_FP].caller_saved_mask;
- }
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLEE_SAVED) {
- *int_mask |= aa_classes[NATIVE_REG_INT].callee_saved_mask;
- *fp_mask |= aa_classes[NATIVE_REG_FP].callee_saved_mask;
- }
-}
-
static void aa_va_start_native(NativeTarget* t, NativeLoc ap_ptr);
static void aa_va_arg_native(NativeTarget* t, NativeLoc dst, NativeLoc ap_ptr,
KitCgTypeId type);
@@ -4216,26 +4199,8 @@ static void aa_va_copy_native(NativeTarget* t, NativeLoc dst_ap_ptr,
aa_va_addr_from_ptr(src_ap_ptr));
}
-AA_UNUSED_FN static const char* aa_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;
-}
-
-AA_UNUSED_FN static int aa_asm_constraint_early(const char* s) {
- if (!s) return 0;
- return (s[0] == '=' && s[1] == '&') || s[0] == '&';
-}
-
-AA_UNUSED_FN static int aa_asm_match_index(const char* s) {
- int n = 0;
- if (!s || s[0] < '0' || s[0] > '9') return -1;
- for (const char* p = s; *p >= '0' && *p <= '9'; ++p) {
- n = n * 10 + (*p - '0');
- }
- return n;
-}
+/* constraint_body / constraint_early / match_index are shared
+ * (cg/native_asm.h). */
_Noreturn static void aa_asm_panic_at(Compiler* c, SrcLoc loc,
const char* msg) {
@@ -4470,7 +4435,7 @@ static void aa_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
AA64Asm* a;
aa_asm_clobber_masks(d->base.c, d->loc, clobbers, nclob, &clob_int, &clob_fp);
- aa_abi_clobber_masks(clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
used_int = clob_int | (1u << AA_TMP0) | (1u << AA_TMP1) | (1u << 18u) |
@@ -4478,7 +4443,7 @@ static void aa_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
used_fp = clob_fp | (1u << 20u) | (1u << 21u);
for (u32 i = 0; i < nout; ++i) {
- const char* body = aa_asm_constraint_body(outs[i].str);
+ const char* body = native_asm_constraint_body(outs[i].str);
if (body[0] == 'r' || body[0] == 'w') {
NativeAllocClass cls = aa_asm_constraint_class(d, body);
Reg reg = aa_asm_alloc_reg(d, cls, &used_int, &used_fp);
@@ -4501,12 +4466,12 @@ static void aa_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
}
for (u32 i = 0; i < nin; ++i) {
- const char* body = aa_asm_constraint_body(ins[i].str);
- int matched = aa_asm_match_index(body);
+ const char* body = native_asm_constraint_body(ins[i].str);
+ int matched = native_asm_match_index(body);
if (matched >= 0) {
if ((u32)matched >= nout)
aa_asm_panic(d, "matching constraint out of range");
- if (aa_asm_constraint_early(outs[matched].str))
+ if (native_asm_constraint_early(outs[matched].str))
aa_asm_panic(d, "matching input names early-clobber output");
if (bound_outs[matched].kind != AA64_INLINE_OPK_REG)
aa_asm_panic(d, "matching constraint requires register output");
@@ -4626,7 +4591,7 @@ static Reg aa_asm_native_mem_base(AANativeTarget* a, SrcLoc loc, NativeLoc 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 = aa_asm_constraint_body(constraint);
+ const char* body = native_asm_constraint_body(constraint);
if (body[0] == 'r' || body[0] == 'w') {
NativeAllocClass cls = (body[0] == 'w') ? NATIVE_REG_FP : NATIVE_REG_INT;
if (src.kind != NATIVE_LOC_REG)
@@ -4665,8 +4630,8 @@ static void aa_asm_block_native(NativeTarget* t, const char* tmpl,
&ntmp);
}
for (u32 i = 0; i < nin; ++i) {
- const char* body = aa_asm_constraint_body(ins[i].str);
- int matched = aa_asm_match_index(body);
+ 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)
@@ -4676,7 +4641,7 @@ static void aa_asm_block_native(NativeTarget* t, const char* tmpl,
}
type = ins[i].type ? ins[i].type : in_locs[i].type;
{
- const char* in_body = aa_asm_constraint_body(ins[i].str);
+ const char* in_body = native_asm_constraint_body(ins[i].str);
NativeLoc inloc = in_locs[i];
/* A register-constrained input whose value is an address-taken local
* arrives in a frame slot: the optimizer cannot keep an address-taken
diff --git a/src/arch/rv64/native.c b/src/arch/rv64/native.c
@@ -1588,18 +1588,8 @@ static void rv_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers,
/* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into
* this target's per-class caller/callee-saved register masks. */
-static void rv_abi_clobber_masks(u32 abi_sets, u32* int_mask, u32* fp_mask) {
- *int_mask = 0;
- *fp_mask = 0;
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED) {
- *int_mask |= rv_classes[NATIVE_REG_INT].caller_saved_mask;
- *fp_mask |= rv_classes[NATIVE_REG_FP].caller_saved_mask;
- }
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLEE_SAVED) {
- *int_mask |= rv_classes[NATIVE_REG_INT].callee_saved_mask;
- *fp_mask |= rv_classes[NATIVE_REG_FP].callee_saved_mask;
- }
-}
+/* abi_clobber_masks is shared as native_asm_abi_clobber_masks
+ * (cg/native_asm.h); it reads the masks from t->regs->classes. */
/* Build the callee-saved set the prologue must preserve: the allocator-assigned
* callee-saved registers (frame->callee_saved_used) plus any an inline-asm
@@ -1625,7 +1615,8 @@ static u32 rv_known_callee_saves(NativeTarget* t,
rv_asm_clobber_masks(t->c, loc, frame->asm_clobbers, frame->nasm_clobbers,
&clob_int, &clob_fp);
}
- rv_abi_clobber_masks(frame->asm_clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(t, frame->asm_clobber_abi_sets, &abi_int,
+ &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
for (Reg r = 0; r < 32u; ++r) {
@@ -3045,23 +3036,8 @@ _Noreturn static void rv_asm_panic(NativeDirectTarget* d, const char* msg) {
rv_asm_panic_at(d->base.c, d->loc, msg);
}
-static const char* rv_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 rv_asm_constraint_early(const char* s) {
- if (!s) return 0;
- return (s[0] == '=' && s[1] == '&') || s[0] == '&';
-}
-static int rv_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;
-}
+/* constraint_body / constraint_early / match_index are shared
+ * (cg/native_asm.h). */
/* Build a bound register pseudo-operand in the rv64 inline shape. */
static void rv_asm_bound_reg(Operand* out, KitCgTypeId type,
@@ -3383,7 +3359,7 @@ static Reg rv_asm_native_mem_base(RvNativeTarget* a, SrcLoc loc, NativeLoc src,
static void rv_asm_bind_native(RvNativeTarget* a, SrcLoc loc, Operand* out,
const char* constraint, KitCgTypeId type,
NativeLoc src, u32* ntmp) {
- const char* body = rv_asm_constraint_body(constraint);
+ const char* body = native_asm_constraint_body(constraint);
if (body[0] == 'r' || body[0] == 'f') {
NativeAllocClass cls = (body[0] == 'f') ? NATIVE_REG_FP : NATIVE_REG_INT;
if (src.kind != NATIVE_LOC_REG)
@@ -3422,8 +3398,8 @@ static void rv_asm_block_native(NativeTarget* t, const char* tmpl,
&ntmp);
}
for (i = 0; i < nin; ++i) {
- const char* body = rv_asm_constraint_body(ins[i].str);
- int matched = rv_asm_match_index(body);
+ const char* body = native_asm_constraint_body(ins[i].str);
+ int matched = native_asm_match_index(body);
KitCgTypeId type;
NativeLoc inloc;
if (matched >= 0) {
@@ -3691,7 +3667,7 @@ static void rv_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
Rv64Asm* asmh;
rv_asm_clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp);
- rv_abi_clobber_masks(clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
/* Reserve emit scratch (t0/t1/t2/t3), sp/gp/tp/zero/ra and the frame pointer
@@ -3703,7 +3679,7 @@ static void rv_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
clob_fp | (1u << RV_FTMP0) | (1u << RV_FTMP1) | (1u << 2u) | (1u << 3u);
for (i = 0; i < nout; ++i) {
- const char* body = rv_asm_constraint_body(outs[i].str);
+ const char* body = native_asm_constraint_body(outs[i].str);
KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type;
if (body[0] == 'r' || body[0] == 'f') {
NativeAllocClass cls = rv_asm_constraint_class(d, body);
@@ -3724,13 +3700,13 @@ static void rv_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
}
for (i = 0; i < nin; ++i) {
- const char* body = rv_asm_constraint_body(ins[i].str);
- int matched = rv_asm_match_index(body);
+ const char* body = native_asm_constraint_body(ins[i].str);
+ int matched = native_asm_match_index(body);
KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type;
if (matched >= 0) {
if ((u32)matched >= nout)
rv_asm_panic(d, "matching constraint out of range");
- if (rv_asm_constraint_early(outs[matched].str))
+ if (native_asm_constraint_early(outs[matched].str))
rv_asm_panic(d, "matching input names early-clobber output");
if (bound_outs[matched].kind != RV64_INLINE_OPK_REG)
rv_asm_panic(d, "matching constraint requires register output");
diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c
@@ -1680,20 +1680,8 @@ static int x64_reg_is_callee_fp(const X64ABIRegs* abi, Reg r);
static void x64_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers,
u32 nclob, u32* int_mask, u32* fp_mask);
-/* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into
- * this target's per-class caller/callee-saved register masks. */
-static void x64_abi_clobber_masks(u32 abi_sets, u32* int_mask, u32* fp_mask) {
- *int_mask = 0;
- *fp_mask = 0;
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED) {
- *int_mask |= x64_classes[NATIVE_REG_INT].caller_saved_mask;
- *fp_mask |= x64_classes[NATIVE_REG_FP].caller_saved_mask;
- }
- if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLEE_SAVED) {
- *int_mask |= x64_classes[NATIVE_REG_INT].callee_saved_mask;
- *fp_mask |= x64_classes[NATIVE_REG_FP].callee_saved_mask;
- }
-}
+/* abi_clobber_masks is shared as native_asm_abi_clobber_masks
+ * (cg/native_asm.h); it reads the masks from t->regs->classes. */
/* Build the callee-saved set the prologue must preserve: the allocator-assigned
* callee-saved registers (frame->callee_saved_used) plus any an inline-asm
@@ -1719,7 +1707,8 @@ static u32 x64_known_callee_saves(NativeTarget* t, const X64ABIRegs* abi,
x64_asm_clobber_masks(t->c, loc, frame->asm_clobbers, frame->nasm_clobbers,
&clob_int, &clob_fp);
}
- x64_abi_clobber_masks(frame->asm_clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(t, frame->asm_clobber_abi_sets, &abi_int,
+ &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
for (Reg r = 0; r < 16u; ++r) {
@@ -3519,23 +3508,8 @@ _Noreturn static void x64_asm_panic(NativeDirectTarget* d, const char* msg) {
x64_asm_panic_at(d->base.c, d->loc, msg);
}
-static const char* x64_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 x64_asm_constraint_early(const char* s) {
- if (!s) return 0;
- return (s[0] == '=' && s[1] == '&') || s[0] == '&';
-}
-static int x64_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;
-}
+/* constraint_body / constraint_early / match_index are shared
+ * (cg/native_asm.h). */
static void x64_asm_bound_reg(Operand* out, KitCgTypeId type,
NativeAllocClass cls, Reg reg) {
@@ -3850,7 +3824,7 @@ static Reg x64_asm_native_mem_base(X64NativeTarget* a, SrcLoc loc,
static void x64_asm_bind_native(X64NativeTarget* a, SrcLoc loc, Operand* out,
const char* constraint, KitCgTypeId type,
NativeLoc src, u32* ntmp) {
- const char* body = x64_asm_constraint_body(constraint);
+ const char* body = native_asm_constraint_body(constraint);
if (body[0] == 'r' || body[0] == 'x') {
NativeAllocClass cls = (body[0] == 'x') ? NATIVE_REG_FP : NATIVE_REG_INT;
if (src.kind != NATIVE_LOC_REG)
@@ -3891,8 +3865,8 @@ static void x64_asm_block_native(NativeTarget* t, const char* tmpl,
&ntmp);
}
for (i = 0; i < nin; ++i) {
- const char* body = x64_asm_constraint_body(ins[i].str);
- int matched = x64_asm_match_index(body);
+ const char* body = native_asm_constraint_body(ins[i].str);
+ int matched = native_asm_match_index(body);
KitCgTypeId type;
NativeLoc inloc;
if (matched >= 0) {
@@ -4203,7 +4177,7 @@ static void x64_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
X64Asm* asmh;
x64_asm_clobber_masks(c, d->loc, clobbers, nclob, &clob_int, &clob_fp);
- x64_abi_clobber_masks(clobber_abi_sets, &abi_int, &abi_fp);
+ native_asm_abi_clobber_masks(d->native, clobber_abi_sets, &abi_int, &abi_fp);
clob_int |= abi_int;
clob_fp |= abi_fp;
/* Reserve emit scratch (rax,r11), driver scratch, sp/bp, and clobbers. */
@@ -4214,7 +4188,7 @@ static void x64_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
(1u << (X64_XMM0 + 14)) | (1u << X64_XMM15);
for (i = 0; i < nout; ++i) {
- const char* body = x64_asm_constraint_body(outs[i].str);
+ const char* body = native_asm_constraint_body(outs[i].str);
KitCgTypeId type = outs[i].type ? outs[i].type : out_ops[i].type;
if (body[0] == 'r' || body[0] == 'x') {
NativeAllocClass cls = x64_asm_constraint_class(d, body);
@@ -4235,13 +4209,13 @@ static void x64_direct_asm_block(NativeDirectTarget* d, const char* tmpl,
}
for (i = 0; i < nin; ++i) {
- const char* body = x64_asm_constraint_body(ins[i].str);
- int matched = x64_asm_match_index(body);
+ const char* body = native_asm_constraint_body(ins[i].str);
+ int matched = native_asm_match_index(body);
KitCgTypeId type = ins[i].type ? ins[i].type : in_ops[i].type;
if (matched >= 0) {
if ((u32)matched >= nout)
x64_asm_panic(d, "matching constraint out of range");
- if (x64_asm_constraint_early(outs[matched].str))
+ if (native_asm_constraint_early(outs[matched].str))
x64_asm_panic(d, "matching input names early-clobber output");
if (bound_outs[matched].kind != X64_INLINE_OPK_REG)
x64_asm_panic(d, "matching constraint requires register output");
diff --git a/src/cg/native_asm.c b/src/cg/native_asm.c
@@ -13,3 +13,38 @@ void native_file_scope_asm(NativeTarget* t, const char* src, size_t len) {
void native_finalize(NativeTarget* t) {
if (t->mc) mc_emit_eh_frame(t->mc);
}
+
+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;
+}
+
+int native_asm_constraint_early(const char* s) {
+ if (!s) return 0;
+ return (s[0] == '=' && s[1] == '&') || s[0] == '&';
+}
+
+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;
+}
+
+void native_asm_abi_clobber_masks(NativeTarget* t, u32 abi_sets, u32* int_mask,
+ u32* fp_mask) {
+ const NativeAllocClassInfo* classes = t->regs->classes;
+ *int_mask = 0;
+ *fp_mask = 0;
+ if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLER_SAVED) {
+ *int_mask |= classes[NATIVE_REG_INT].caller_saved_mask;
+ *fp_mask |= classes[NATIVE_REG_FP].caller_saved_mask;
+ }
+ if (abi_sets & KIT_CG_ASM_CLOBBER_ABI_CALLEE_SAVED) {
+ *int_mask |= classes[NATIVE_REG_INT].callee_saved_mask;
+ *fp_mask |= classes[NATIVE_REG_FP].callee_saved_mask;
+ }
+}
diff --git a/src/cg/native_asm.h b/src/cg/native_asm.h
@@ -17,4 +17,33 @@ void native_file_scope_asm(NativeTarget* t, const char* src, size_t len);
/* Emit the function's eh-frame (CFI) once code emission is complete. */
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.
+ */
+
+/* Skip the leading modifier flags ('=', '+', '&', and the '=&' early-clobber
+ * pair) and return a pointer to the constraint body (e.g. "r", "w", "m", "i",
+ * or a matching-constraint digit run). Returns "" for a NULL string. */
+const char* native_asm_constraint_body(const char* s);
+
+/* Whether the constraint marks an early-clobber output ('=&' or a leading
+ * '&'). */
+int native_asm_constraint_early(const char* s);
+
+/* For a matching constraint (a leading decimal digit run naming an output
+ * operand index), return that index; -1 when the constraint is not a matching
+ * constraint. */
+int native_asm_match_index(const char* s);
+
+/* Expand the arch-neutral clobber-ABI sets (KitCgAsmClobberAbiSet bits) into
+ * this target's per-class caller/callee-saved register masks, read straight
+ * from the target's register file (t->regs->classes). Byte-identical across the
+ * backends apart from which register table they consulted, so it lives here. */
+void native_asm_abi_clobber_masks(NativeTarget* t, u32 abi_sets, u32* int_mask,
+ u32* fp_mask);
+
#endif