kit

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

commit bcdbeba368da56abbac093222d97d19298491466
parent f616cb096a6817069948ce912baf045bfd0b42ef
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 09:45:00 -0700

perf(cg): cache scalar width on the NativeLoc pad — kill arch type re-query

The -O0 NDT arch backends re-derived a scalar's width on every binop/move/
convert/cmp by calling cg_type_size (and cg_type_is_ptr) on loc.type — a
type-bridge crossing the NDT had already paid when it built the NativeLoc.

Stamp the width once at the single register-NativeLoc producer. NativeLoc's free
pad now carries a szinfo byte {VALID, size_log2}; nd_loc_reg (a thin wrapper
over native_loc_reg used at all 14 NDT register-loc sites) fills it via
native_loc_stamp_size, computing cg_type_size exactly once. The arch hot path
reads the byte through new loc_size32()/loc_is_64() helpers (aarch64, riscv64,
x86-64) instead of re-querying; both fall back to the live type query when the
loc is unstamped (immediates, memory, cold producers), so partial adoption stays
byte-identical.

Only the byte SIZE is cached, never a baked is64 class: each arch derives its
own is64 from the cached size plus its pointer/xlen rule, preserving the rv32
4-byte-pointer and i128 distinctions. Only exact power-of-two scalar widths
(1/2/4/8/16) are stamped, so the cached value is bit-for-bit what cg_type_size
would return.

Byte-identical: perf identity gate 60/60 PASS (objects -O0/-O1, -g DWARF, -S
asm, linked exe) + cross-arch byte-identity diff over aarch64/riscv64/x86-64
(linux+macos) and riscv32, general + atomic/fp/conversion-heavy sources, at
-O0/-O1/-g.

Diffstat:
Msrc/arch/aa64/native.c | 63+++++++++++++++++++++++++++++++++++++--------------------------
Msrc/arch/native_target.h | 57++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/arch/riscv/native.c | 68+++++++++++++++++++++++++++++++++++++++++---------------------------
Msrc/arch/x64/native.c | 74++++++++++++++++++++++++++++++++++++++++++++------------------------------
Msrc/cg/native_direct_target.c | 44+++++++++++++++++++++++++++-----------------
5 files changed, 205 insertions(+), 101 deletions(-)

diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -370,8 +370,21 @@ static u32 size_idx(u32 n) { static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0x1fu; } +/* Scalar byte width of a register operand. Reads the NDT-stamped szinfo + * descriptor (one byte) when present, else falls back to the live type query — + * so partial adoption stays byte-identical. */ +static u32 loc_size32(NativeTarget* t, NativeLoc loc) { + if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo); + return type_size32(t, loc.type); +} + +/* The original predicate exactly, but sourcing the width from the cached + * descriptor when stamped: `size == 8 || is_ptr`. is_ptr is short-circuited + * away whenever the cached size is already 8 (the pointer case on this arch), + * so the common path becomes a single byte test. Byte-identical to the old + * type_size32+cg_type_is_ptr form. */ static int loc_is_64(NativeTarget* t, NativeLoc loc) { - return type_size32(t, loc.type) == 8u || cg_type_is_ptr(t->c, loc.type); + return loc_size32(t, loc) == 8u || cg_type_is_ptr(t->c, loc.type); } /* native_loc_is_fp is shared in native_target.h. */ @@ -1943,10 +1956,10 @@ static void aa_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { native_loc_is_fp(dst) == native_loc_is_fp(src) && dst.v.reg == src.v.reg) return; if (native_loc_is_fp(dst) && native_loc_is_fp(src)) { - if (type_size32(t, dst.type) == 16u) + if (loc_size32(t, dst) == 16u) aa_emit32(t->mc, aa_mov_vec16(loc_reg(dst), loc_reg(src))); else - aa_emit32(t->mc, aa_fmov_fp(type_size32(t, dst.type) == 8u, loc_reg(dst), + aa_emit32(t->mc, aa_fmov_fp(loc_size32(t, dst) == 8u, loc_reg(dst), loc_reg(src))); } else if (native_loc_is_fp(dst)) { aa_emit32(t->mc, @@ -2301,7 +2314,7 @@ static void aa_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc lhs, u32 sf = loc_is_64(t, dst) ? 1u : 0u; u32 rd = loc_reg(dst), rn = loc_reg(lhs), rm = loc_reg(rhs); if (native_loc_is_fp(dst)) { - u32 d = type_size32(t, dst.type) == 8u; + u32 d = loc_size32(t, dst) == 8u; switch (op) { case BO_FADD: aa_emit32(t->mc, aa_fp_bin(0x002800u, d, rd, rn, rm)); @@ -2416,7 +2429,7 @@ static void aa_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { switch (op) { case UO_FNEG: case UO_NEG: - aa_emit32(t->mc, aa_fneg(type_size32(t, dst.type) == 8u, loc_reg(dst), + aa_emit32(t->mc, aa_fneg(loc_size32(t, dst) == 8u, loc_reg(dst), loc_reg(src))); return; default: @@ -2442,7 +2455,7 @@ static void aa_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { static void aa_emit_cmp_to_flags(NativeTarget* t, NativeLoc lhs, NativeLoc rhs) { if (native_loc_is_fp(lhs)) { - aa_emit32(t->mc, aa_fcmp(type_size32(t, lhs.type) == 8u, loc_reg(lhs), + aa_emit32(t->mc, aa_fcmp(loc_size32(t, lhs) == 8u, loc_reg(lhs), loc_reg(rhs))); return; } @@ -2494,8 +2507,8 @@ static void aa_convert(NativeTarget* t, ConvKind op, NativeLoc dst, aa_move(t, dst, src); return; case CV_ZEXT: { - u32 src_bits = type_size32(t, src.type) * 8u; - u32 dst_bits = type_size32(t, dst.type) * 8u; + u32 src_bits = loc_size32(t, src) * 8u; + u32 dst_bits = loc_size32(t, dst) * 8u; u32 sf = dst_bits > 32u; if (src_bits >= dst_bits) { aa_move(t, dst, src); @@ -2514,8 +2527,8 @@ static void aa_convert(NativeTarget* t, ConvKind op, NativeLoc dst, return; } case CV_SEXT: { - u32 src_bits = type_size32(t, src.type) * 8u; - u32 dst_bits = type_size32(t, dst.type) * 8u; + u32 src_bits = loc_size32(t, src) * 8u; + u32 dst_bits = loc_size32(t, dst) * 8u; u32 sf = dst_bits > 32u; if (src_bits >= dst_bits) { aa_move(t, dst, src); @@ -2526,22 +2539,20 @@ static void aa_convert(NativeTarget* t, ConvKind op, NativeLoc dst, return; } case CV_ITOF_S: - aa_emit32(t->mc, aa_scvtf(type_size32(t, dst.type) == 8u, - loc_is_64(t, src), loc_reg(dst), loc_reg(src))); + aa_emit32(t->mc, aa_scvtf(loc_size32(t, dst) == 8u, loc_is_64(t, src), + loc_reg(dst), loc_reg(src))); return; case CV_ITOF_U: - aa_emit32(t->mc, aa_ucvtf(type_size32(t, dst.type) == 8u, - loc_is_64(t, src), loc_reg(dst), loc_reg(src))); + aa_emit32(t->mc, aa_ucvtf(loc_size32(t, dst) == 8u, loc_is_64(t, src), + loc_reg(dst), loc_reg(src))); return; case CV_FTOI_S: - aa_emit32(t->mc, - aa_fcvtzs(loc_is_64(t, dst), type_size32(t, src.type) == 8u, - loc_reg(dst), loc_reg(src))); + aa_emit32(t->mc, aa_fcvtzs(loc_is_64(t, dst), loc_size32(t, src) == 8u, + loc_reg(dst), loc_reg(src))); return; case CV_FTOI_U: - aa_emit32(t->mc, - aa_fcvtzu(loc_is_64(t, dst), type_size32(t, src.type) == 8u, - loc_reg(dst), loc_reg(src))); + aa_emit32(t->mc, aa_fcvtzu(loc_is_64(t, dst), loc_size32(t, src) == 8u, + loc_reg(dst), loc_reg(src))); return; case CV_FEXT: if (dst_fp && src_fp) @@ -3350,7 +3361,7 @@ static void aa_saved_tmp_restore(AANativeTarget* a, u32 reg) { static void aa_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, MemAccess mem, KitCgMemOrder order) { u32 base = AA_TMP0; - u32 sz = size_idx(mem.size ? mem.size : type_size32(t, dst.type)); + u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, dst)); aa_atomic_addr_reg(t, addr, base); aa_emit32(t->mc, aa_order_acquire(order) ? aa_ldar(sz, loc_reg(dst), base) @@ -3362,7 +3373,7 @@ static void aa_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, static void aa_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src, MemAccess mem, KitCgMemOrder order) { u32 base = AA_TMP0; - u32 sz = size_idx(mem.size ? mem.size : type_size32(t, src.type)); + u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, src)); if (order == KIT_CG_MO_SEQ_CST) aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); aa_atomic_addr_reg(t, addr, base); @@ -3382,7 +3393,7 @@ static void aa_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, u32 status = aa_saved_tmp_pick(loc_reg(dst), loc_reg(val), base); NativeLoc next = aa_tmp_loc(dst.type, next_reg); MCLabel retry = mc_label_new(t->mc); - u32 sz = size_idx(mem.size ? mem.size : type_size32(t, dst.type)); + u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, dst)); if (order == KIT_CG_MO_SEQ_CST) aa_emit32(t->mc, aa64_dmb(AA64_BARRIER_OPT_ISH)); aa_saved_tmp_spill(a, status); @@ -3432,7 +3443,7 @@ static void aa_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, KitCgMemOrder success, KitCgMemOrder failure) { u32 base = AA_TMP0; u32 status = AA_TMP1; - u32 sz = size_idx(mem.size ? mem.size : type_size32(t, prior.type)); + u32 sz = size_idx(mem.size ? mem.size : loc_size32(t, prior)); u32 sf = sz == 3u; int acquire = aa_order_acquire(success) || aa_order_acquire(failure); int release = aa_order_release(success); @@ -4929,7 +4940,7 @@ static void aa_asm_load_loc_to_reg(AANativeTarget* a, SrcLoc loc, NativeLoc src, return; } aa_emit_mem(a, 1, dst, aa_asm_loc_to_addr(a, loc, src), - aa_mem_for_type(t, dst.type, type_size32(t, dst.type))); + aa_mem_for_type(t, dst.type, loc_size32(t, dst))); } static void aa_asm_store_reg_to_loc(AANativeTarget* a, SrcLoc loc, @@ -4940,7 +4951,7 @@ static void aa_asm_store_reg_to_loc(AANativeTarget* a, SrcLoc loc, return; } aa_emit_mem(a, 0, src, aa_asm_loc_to_addr(a, loc, dst), - aa_mem_for_type(t, src.type, type_size32(t, src.type))); + aa_mem_for_type(t, src.type, loc_size32(t, src))); } static void aa_asm_bind_native(AANativeTarget* a, SrcLoc loc, Operand* out, diff --git a/src/arch/native_target.h b/src/arch/native_target.h @@ -258,7 +258,20 @@ typedef struct NativeAddr { typedef struct NativeLoc { u8 kind; /* NativeLocKind */ u8 cls; /* NativeAllocClass for register-like locations */ - u8 pad[2]; + /* Cached scalar-width descriptor, stamped once by the NDT at the + * register-NativeLoc choke point (nd_loc_reg) so the per-arch binop/move/ + * convert/cmp hot path reads a byte instead of re-crossing the type bridge + * via cg_type_size on `type`. Layout: + * bit0 SZINFO_VALID — set => the size field is authoritative + * bits 1..3 SZINFO_SIZE_LOG2 — log2(byte_size) clamped: 0=1,1=2,2=4,3=8,4=16 + * Each arch derives its own `is64` predicate from this cached size plus its + * pointer/xlen rule (so the rv32 4-byte-pointer and i128 distinctions stay + * correct per-arch) — the descriptor caches only the size, never a baked + * width class. Left 0 (invalid) for cold locs (immediates, memory, unstamped + * producers); the arch then falls back to the live cg_type_size query, so + * partial adoption stays byte-identical. */ + u8 szinfo; + u8 pad[1]; KitCgTypeId type; union { Reg reg; @@ -617,6 +630,48 @@ static inline int native_loc_is_fp(NativeLoc loc) { return (NativeAllocClass)loc.cls == NATIVE_REG_FP; } +/* NativeLoc.szinfo layout (see the field comment on NativeLoc). */ +#define NATIVE_SZINFO_VALID 0x01u +#define NATIVE_SZINFO_SIZE_LOG2_SHIFT 1u +#define NATIVE_SZINFO_SIZE_LOG2_MASK 0x07u + +/* size (bytes, 1..16) -> log2 bucket (0=1,1=2,2=4,3=8,4=16). */ +static inline u8 native_szinfo_log2(u32 size) { + if (size <= 1u) return 0u; + if (size <= 2u) return 1u; + if (size <= 4u) return 2u; + if (size <= 8u) return 3u; + return 4u; +} + +/* Stamp a register NativeLoc's cached width descriptor from its type, computing + * cg_type_size exactly once. Called by the NDT at its register-loc choke point; + * the arch backends then read loc.szinfo instead of re-querying. Only meaningful + * for scalar (<=16B) reg locs; left invalid for a 0 type or an over-wide size so + * the arch's own (panicking) query path runs. The mapped size is byte-identical + * to native_type_size / aarch64's type_size32 for in-range scalars: a 0 type + * maps to 8, a 0 size to 8 (the register-sized default both helpers apply). */ +static inline void native_loc_stamp_size(NativeTarget* t, NativeLoc* loc) { + u64 n = loc->type ? cg_type_size(t->c, loc->type) : 8u; + if (n == 0) n = 8u; + /* Cache only exact-power-of-two scalar widths (1/2/4/8/16): the szinfo bucket + * recovers the size exactly, so the cached value is bit-for-bit what + * cg_type_size / native_type_size would return. Anything else (over-wide, or a + * non-power-of-two aggregate that should never reach a register loc) is left + * invalid and falls back to the live query. */ + if (n != 1u && n != 2u && n != 4u && n != 8u && n != 16u) return; + loc->szinfo = (u8)(NATIVE_SZINFO_VALID | ((u32)native_szinfo_log2((u32)n) + << NATIVE_SZINFO_SIZE_LOG2_SHIFT)); +} + +/* Byte size (1/2/4/8/16) recovered from a stamped szinfo descriptor. Only valid + * when (szinfo & NATIVE_SZINFO_VALID). */ +static inline u32 native_szinfo_size(u8 szinfo) { + u32 lg = + ((u32)szinfo >> NATIVE_SZINFO_SIZE_LOG2_SHIFT) & NATIVE_SZINFO_SIZE_LOG2_MASK; + return 1u << lg; +} + /* Scalar size/align, clamped to a usable register-sized default. Shared by the * backends whose scalars are at most pointer-width (x64, rv64); aa64 keeps its * own size query because it asserts on over-wide scalars. */ diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -361,6 +361,24 @@ static int rv_is_64(NativeTarget* t, KitCgTypeId type) { (v->xlen == 64u && cg_type_is_ptr(t->c, type)); } +/* Scalar byte width of a register operand. Reads the NDT-stamped szinfo + * descriptor (one byte) when present, else falls back to the live type query — + * byte-identical to native_type_size for in-range scalars. */ +static u32 loc_size32(NativeTarget* t, NativeLoc loc) { + if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo); + return native_type_size(t, loc.type); +} + +/* The original rv_is_64 predicate exactly, but sourcing the width from the + * cached descriptor: `size >= 8 || (xlen==64 && is_ptr)`. The is_ptr clause + * keeps the rv32 4-byte-pointer distinction (a stamped 4-byte pointer caches + * size 4, so >= 8 is false and the xlen guard decides). Byte-identical. */ +static int loc_is_64(NativeTarget* t, NativeLoc loc) { + const RiscvVariant* v = rv_of(t)->variant; + return loc_size32(t, loc) >= 8u || + (v->xlen == 64u && cg_type_is_ptr(t->c, loc.type)); +} + static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0x1fu; } /* ============================ register tables ============================ */ @@ -794,7 +812,7 @@ static void rv_emit_mem(RvNativeTarget* a, int is_load, NativeLoc reg, MCEmitter* mc = t->mc; u32 r = loc_reg(reg); int fp = native_loc_is_fp(reg); - u32 sz = mem.size ? mem.size : native_type_size(t, reg.type); + u32 sz = mem.size ? mem.size : loc_size32(t, reg); u32 base; i32 off; @@ -816,18 +834,18 @@ static void rv_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { int dfp = native_loc_is_fp(dst), sfp = native_loc_is_fp(src); u32 rd = loc_reg(dst), rs = loc_reg(src); if (dfp && sfp) { - u32 fmt = native_type_size(t, dst.type) == 8u ? RV_FMT_D : RV_FMT_S; + u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S; if (rd == rs) return; rv64_emit32(mc, rv_fsgnj(fmt, rd, rs, rs)); return; } if (!dfp && sfp) { - u32 sz = native_type_size(t, src.type); + u32 sz = loc_size32(t, src); rv64_emit32(mc, sz == 8u ? rv_fmv_x_d(rd, rs) : rv_fmv_x_w(rd, rs)); return; } if (dfp && !sfp) { - u32 sz = native_type_size(t, dst.type); + u32 sz = loc_size32(t, dst); rv64_emit32(mc, sz == 8u ? rv_fmv_d_x(rd, rs) : rv_fmv_w_x(rd, rs)); return; } @@ -836,7 +854,7 @@ static void rv_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { } static void rv_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) { - rv_emit_load_imm(rv_of(t)->variant, t->mc, rv_is_64(t, dst.type) ? 1u : 0u, + rv_emit_load_imm(rv_of(t)->variant, t->mc, loc_is_64(t, dst) ? 1u : 0u, loc_reg(dst), imm); } @@ -971,7 +989,7 @@ static void rv_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, MCEmitter* mc = t->mc; u32 rd = loc_reg(dst); u32 ra = loc_reg(aop); - int sf = rv_is_64(t, dst.type); + int sf = loc_is_64(t, dst); /* The W-form ops (ADDW/SUBW/MULW/SLLW/...) are RV64-only and act on a 32-bit * value held in a 64-bit register. They are emitted only for a narrow value * on rv64; on rv32 the BASE ops ARE the 32-bit ops, so `w` is always 0 and we @@ -989,7 +1007,7 @@ static void rv_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, case BO_FSUB: case BO_FMUL: case BO_FDIV: { - u32 fmt = native_type_size(t, dst.type) == 8u ? RV_FMT_D : RV_FMT_S; + u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S; switch (op) { case BO_FADD: rv64_emit32(mc, rv_fadd(fmt, rd, ra, rb)); @@ -1076,14 +1094,14 @@ static void rv_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { const RiscvVariant* v = rv_of(t)->variant; MCEmitter* mc = t->mc; u32 rd = loc_reg(dst), rs = loc_reg(src); - int sf = rv_is_64(t, dst.type); + int sf = loc_is_64(t, dst); int w = !sf && v->has_w_forms; /* SUBW is RV64-only; base SUB on rv32 */ switch (op) { case UO_NEG: rv64_emit32(mc, w ? rv_subw(rd, RV_ZERO, rs) : rv_sub(rd, RV_ZERO, rs)); return; case UO_FNEG: { - u32 fmt = native_type_size(t, dst.type) == 8u ? RV_FMT_D : RV_FMT_S; + u32 fmt = loc_size32(t, dst) == 8u ? RV_FMT_D : RV_FMT_S; rv64_emit32(mc, rv_fsgnjn(fmt, rd, rs, rs)); return; } @@ -1107,7 +1125,7 @@ static u32 rv_cmp_ext(NativeTarget* t, int is_signed, NativeLoc op, u32 tmp) { /* On rv32 a 32-bit operand already fills the whole register — there is no * wider container to canonicalize into, so the extension is a no-op. */ if (v->xlen == 32u) return r; - if (rv_is_64(t, op.type)) return r; + if (loc_is_64(t, op)) return r; if (is_signed) { rv64_emit32(mc, rv_addiw(tmp, r, 0)); /* sign-extend low 32 */ } else { @@ -1197,7 +1215,7 @@ static void rv_cmp(NativeTarget* t, CmpOp op, NativeLoc dst, NativeLoc aop, * primitive and OR the two strict relations (a<b | a>b) via scratch RV_TMP2 * (x7, reserved & never allocable, so it can't alias rd). */ if (op >= CMP_OEQ_F) { - u32 fmt = native_type_size(t, aop.type) == 8u ? RV_FMT_D : RV_FMT_S; + u32 fmt = loc_size32(t, aop) == 8u ? RV_FMT_D : RV_FMT_S; u32 ra = loc_reg(aop), rb = loc_reg(bop); switch (op) { case CMP_OEQ_F: @@ -1263,8 +1281,8 @@ static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst, const RiscvVariant* v = rv_of(t)->variant; MCEmitter* mc = t->mc; u32 rd = loc_reg(dst), rs = loc_reg(src); - u32 src_sz = native_type_size(t, src.type); - u32 dst_sz = native_type_size(t, dst.type); + u32 src_sz = loc_size32(t, src); + u32 dst_sz = loc_size32(t, dst); /* `il` (int-side wide): the 64-bit-integer fcvt L-forms are RV64-only; on * rv32 only the w/wu forms exist and a 64-bit int<->fp is legalized to a * libcall before reaching here. */ @@ -1296,7 +1314,7 @@ static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst, rv64_emit32(mc, rv_addi(rd, rs, 0)); /* low bits; users re-narrow */ return; case CV_ITOF_S: - if (native_type_size(t, dst.type) == 8u) + if (dst_sz == 8u) rv64_emit32( mc, il && src_sz == 8u ? rv_fcvt_d_l(rd, rs) : rv_fcvt_d_w(rd, rs)); else @@ -1304,7 +1322,7 @@ static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst, mc, il && src_sz == 8u ? rv_fcvt_s_l(rd, rs) : rv_fcvt_s_w(rd, rs)); return; case CV_ITOF_U: - if (native_type_size(t, dst.type) == 8u) + if (dst_sz == 8u) rv64_emit32(mc, il && src_sz == 8u ? rv_fcvt_d_lu(rd, rs) : rv_fcvt_d_wu(rd, rs)); else @@ -2213,7 +2231,7 @@ static void rv_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src, return; } if (src.kind == NATIVE_LOC_IMM) { - rv_emit_load_imm(a->variant, t->mc, rv_is_64(t, dst.type) ? 1u : 0u, + rv_emit_load_imm(a->variant, t->mc, loc_is_64(t, dst) ? 1u : 0u, loc_reg(dst), src.v.imm); return; } @@ -2789,8 +2807,7 @@ static void rv_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, MemAccess mem, KitCgMemOrder mo) { RvNativeTarget* a = rv_of(t); MCEmitter* mc = t->mc; - u32 sf = - (mem.size ? mem.size : native_type_size(t, dst.type)) == 8u ? 1u : 0u; + u32 sf = (mem.size ? mem.size : loc_size32(t, dst)) == 8u ? 1u : 0u; u32 base = rv_atomic_addr_reg(a, addr); if (mo == KIT_CG_MO_SEQ_CST) rv64_emit32(mc, rv_fence_rw_rw()); if (rv_order_acquire(mo)) { @@ -2798,10 +2815,9 @@ static void rv_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, rv64_emit32(mc, sf ? rv_lr_d(loc_reg(dst), base, 1, 0) : rv_lr_w(loc_reg(dst), base, 1, 0)); } else { - rv64_emit32( - mc, enc_int_load(a->variant, - mem.size ? mem.size : native_type_size(t, dst.type), 0, - loc_reg(dst), base, 0)); + rv64_emit32(mc, enc_int_load(a->variant, + mem.size ? mem.size : loc_size32(t, dst), 0, + loc_reg(dst), base, 0)); } } @@ -2809,7 +2825,7 @@ static void rv_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src, MemAccess mem, KitCgMemOrder mo) { RvNativeTarget* a = rv_of(t); MCEmitter* mc = t->mc; - u32 sz = mem.size ? mem.size : native_type_size(t, src.type); + u32 sz = mem.size ? mem.size : loc_size32(t, src); /* RV_TMP0 holds the address; never collides with src (an allocable reg). */ u32 base = rv_atomic_addr_reg(a, addr); if (rv_order_release(mo)) rv64_emit32(mc, rv_fence_rw_rw()); @@ -2823,8 +2839,7 @@ static void rv_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, RvNativeTarget* a = rv_of(t); const RiscvVariant* v = a->variant; MCEmitter* mc = t->mc; - u32 sf = - (mem.size ? mem.size : native_type_size(t, dst.type)) == 8u ? 1u : 0u; + u32 sf = (mem.size ? mem.size : loc_size32(t, dst)) == 8u ? 1u : 0u; /* W-form add/sub apply only to a 32-bit value on rv64; on rv32 the base ops * are the 32-bit ops. */ int w = !sf && v->has_w_forms; @@ -2878,8 +2893,7 @@ static void rv_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, KitCgMemOrder success, KitCgMemOrder failure) { RvNativeTarget* a = rv_of(t); MCEmitter* mc = t->mc; - u32 sf = - (mem.size ? mem.size : native_type_size(t, prior.type)) == 8u ? 1u : 0u; + u32 sf = (mem.size ? mem.size : loc_size32(t, prior)) == 8u ? 1u : 0u; u32 base = rv_atomic_addr_reg(a, addr); /* RV_TMP0 */ u32 rprior = loc_reg(prior); u32 rexp = loc_reg(expected); diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c @@ -155,6 +155,20 @@ static int x64_is_64(NativeTarget* t, KitCgTypeId type) { return native_type_size(t, type) >= 8u || cg_type_is_ptr(t->c, type); } +/* Scalar byte width of a register operand. Reads the NDT-stamped szinfo + * descriptor (one byte) when present, else falls back to the live type query — + * byte-identical to native_type_size for in-range scalars. */ +static u32 loc_size32(NativeTarget* t, NativeLoc loc) { + if (loc.szinfo & NATIVE_SZINFO_VALID) return native_szinfo_size(loc.szinfo); + return native_type_size(t, loc.type); +} + +/* The original x64_is_64 predicate exactly, but sourcing the width from the + * cached descriptor: `size >= 8 || is_ptr`. Byte-identical. */ +static int loc_is_64(NativeTarget* t, NativeLoc loc) { + return loc_size32(t, loc) >= 8u || cg_type_is_ptr(t->c, loc.type); +} + static u32 loc_reg(NativeLoc loc) { return loc.v.reg & 0xfu; } /* SSE scalar prefix: F2 (double / 8-byte) vs F3 (single / 4-byte). */ @@ -622,7 +636,7 @@ static void x64_emit_mem(X64NativeTarget* a, int is_load, NativeLoc reg, MCEmitter* mc = t->mc; u32 r = loc_reg(reg); int fp = native_loc_is_fp(reg); - u32 sz = mem.size ? mem.size : native_type_size(t, reg.type); + u32 sz = mem.size ? mem.size : loc_size32(t, reg); u32 base, idx, scale; i32 off; @@ -714,26 +728,26 @@ static void x64_move(NativeTarget* t, NativeLoc dst, NativeLoc src) { u32 rd = loc_reg(dst), rs = loc_reg(src); if (dfp && sfp) { if (rd == rs) return; - emit_sse_rr(mc, sse_scalar_prefix(native_type_size(t, dst.type)), 0x10, rd, + emit_sse_rr(mc, sse_scalar_prefix(loc_size32(t, dst)), 0x10, rd, rs); return; } if (dfp && !sfp) { /* movd/movq gpr -> xmm: 66 0F 6E /r */ - int w = native_type_size(t, dst.type) == 8u; + int w = loc_size32(t, dst) == 8u; emit_sse_rr_w(mc, 0x66, 0x6E, w, rd, rs); return; } if (!dfp && sfp) { /* movd/movq xmm -> gpr: 66 0F 7E /r (xmm is reg field) */ - int w = native_type_size(t, src.type) == 8u; + int w = loc_size32(t, src) == 8u; emit_sse_rr_w(mc, 0x66, 0x7E, w, rs, rd); return; } if (rd == rs) return; - emit_mov_rr(mc, x64_is_64(t, dst.type) ? 1 : 0, rd, rs); + emit_mov_rr(mc, loc_is_64(t, dst) ? 1 : 0, rd, rs); } static void x64_load_imm(NativeTarget* t, NativeLoc dst, i64 imm) { - x64_emit_load_imm(t->mc, x64_is_64(t, dst.type) ? 1 : 0, loc_reg(dst), imm); + x64_emit_load_imm(t->mc, loc_is_64(t, dst) ? 1 : 0, loc_reg(dst), imm); } /* FP constant: materialize the bit pattern in a GPR scratch, then movd/movq @@ -971,7 +985,7 @@ static void x64_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, /* FP binops: two-address. dst = aop op bop. */ if (op == BO_FADD || op == BO_FSUB || op == BO_FMUL || op == BO_FDIV) { u32 ra = loc_reg(aop), rb = loc_reg(bop); - u8 prefix = sse_scalar_prefix(native_type_size(t, dst.type)); + u8 prefix = sse_scalar_prefix(loc_size32(t, dst)); u8 opcode; switch (op) { case BO_FADD: @@ -1004,7 +1018,7 @@ static void x64_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, } { - int w = x64_is_64(t, dst.type) ? 1 : 0; + int w = loc_is_64(t, dst) ? 1 : 0; int b_imm = bop.kind == NATIVE_LOC_IMM; i64 imm = b_imm ? bop.v.imm : 0; u32 ra = loc_reg(aop); @@ -1165,7 +1179,7 @@ static void x64_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { MCEmitter* mc = t->mc; u32 rd = loc_reg(dst), rs = loc_reg(src); if (op == UO_FNEG) { - int dbl = native_type_size(t, dst.type) == 8u; + int dbl = loc_size32(t, dst) == 8u; if (rd != rs) emit_sse_rr(mc, sse_scalar_prefix(dbl ? 8u : 4u), 0x10, rd, rs); /* sign mask into fp scratch via gpr, then XORPS/XORPD. */ @@ -1176,7 +1190,7 @@ static void x64_unop(NativeTarget* t, UnOp op, NativeLoc dst, NativeLoc src) { return; } { - int w = x64_is_64(t, dst.type) ? 1 : 0; + int w = loc_is_64(t, dst) ? 1 : 0; switch (op) { case UO_NEG: if (rd != rs) emit_mov_rr(mc, w, rd, rs); @@ -1240,12 +1254,12 @@ static void x64_emit_cmp_flags(NativeTarget* t, NativeLoc aop, NativeLoc bop, X64NativeTarget* a = x64_of(t); MCEmitter* mc = t->mc; if (fp) { - u8 prefix = native_type_size(t, aop.type) == 8u ? 0x66u : 0u; + u8 prefix = loc_size32(t, aop) == 8u ? 0x66u : 0u; emit_sse_rr(mc, prefix, 0x2E, loc_reg(aop), loc_reg(bop)); /* ucomis */ return; } { - int w = x64_is_64(t, aop.type) ? 1 : 0; + int w = loc_is_64(t, aop) ? 1 : 0; u32 ra = loc_reg(aop); if (bop.kind == NATIVE_LOC_IMM) { i64 imm = bop.v.imm; @@ -1352,14 +1366,14 @@ static void x64_convert(NativeTarget* t, ConvKind k, NativeLoc dst, u32 rd = loc_reg(dst), rs = loc_reg(src); switch (k) { case CV_SEXT: { - u32 src_sz = native_type_size(t, src.type); - int w = x64_is_64(t, dst.type) ? 1 : 0; + u32 src_sz = loc_size32(t, src); + int w = loc_is_64(t, dst) ? 1 : 0; emit_extend_rr(mc, w, 1, src_sz, rd, rs); return; } case CV_ZEXT: { - u32 src_sz = native_type_size(t, src.type); - int w = x64_is_64(t, dst.type) ? 1 : 0; + u32 src_sz = loc_size32(t, src); + int w = loc_is_64(t, dst) ? 1 : 0; emit_extend_rr(mc, w, 0, src_sz, rd, rs); return; } @@ -1368,8 +1382,8 @@ static void x64_convert(NativeTarget* t, ConvKind k, NativeLoc dst, return; case CV_ITOF_S: case CV_ITOF_U: { - int w_src = x64_is_64(t, src.type) ? 1 : 0; - u8 prefix = sse_scalar_prefix(native_type_size(t, dst.type)); + int w_src = loc_is_64(t, src) ? 1 : 0; + u8 prefix = sse_scalar_prefix(loc_size32(t, dst)); if (k == CV_ITOF_U && w_src == 1) { MCLabel L_high = mc_label_new(mc); MCLabel L_done = mc_label_new(mc); @@ -1398,12 +1412,12 @@ static void x64_convert(NativeTarget* t, ConvKind k, NativeLoc dst, } case CV_FTOI_S: case CV_FTOI_U: { - int w_dst = x64_is_64(t, dst.type) ? 1 : 0; - u8 prefix = sse_scalar_prefix(native_type_size(t, src.type)); + int w_dst = loc_is_64(t, dst) ? 1 : 0; + u8 prefix = sse_scalar_prefix(loc_size32(t, src)); /* Unsigned 64-bit FTOI needs the 2^63 bias dance; otherwise cvtt * (with the destination widened to 64 for u32) is exact. */ if (k == CV_FTOI_U && w_dst == 1) { - int dbl = native_type_size(t, src.type) == 8u; + int dbl = loc_size32(t, src) == 8u; MCLabel L_small = mc_label_new(mc); MCLabel L_done = mc_label_new(mc); /* limit = 2^63 in fp scratch. */ @@ -1436,9 +1450,9 @@ static void x64_convert(NativeTarget* t, ConvKind k, NativeLoc dst, return; case CV_BITCAST: if (!native_loc_is_fp(src) && native_loc_is_fp(dst)) { - emit_sse_rr_w(mc, 0x66, 0x6E, x64_is_64(t, dst.type), rd, rs); + emit_sse_rr_w(mc, 0x66, 0x6E, loc_is_64(t, dst), rd, rs); } else if (native_loc_is_fp(src) && !native_loc_is_fp(dst)) { - emit_sse_rr_w(mc, 0x66, 0x7E, x64_is_64(t, src.type), rs, rd); + emit_sse_rr_w(mc, 0x66, 0x7E, loc_is_64(t, src), rs, rd); } else { x64_move(t, dst, src); } @@ -2224,7 +2238,7 @@ static void x64_load_part(NativeTarget* t, NativeLoc dst, NativeLoc src, return; } if (src.kind == NATIVE_LOC_IMM) { - x64_emit_load_imm(t->mc, x64_is_64(t, dst.type) ? 1 : 0, loc_reg(dst), + x64_emit_load_imm(t->mc, loc_is_64(t, dst) ? 1 : 0, loc_reg(dst), src.v.imm); return; } @@ -3100,7 +3114,7 @@ static u32 x64_atomic_base(X64NativeTarget* a, NativeAddr addr) { static void x64_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, MemAccess mem, KitCgMemOrder mo) { X64NativeTarget* a = x64_of(t); - u32 sz = mem.size ? mem.size : native_type_size(t, dst.type); + u32 sz = mem.size ? mem.size : loc_size32(t, dst); u32 base; (void)mo; /* x86 plain MOV is an acquire load. */ base = x64_atomic_base(a, addr); @@ -3111,7 +3125,7 @@ static void x64_atomic_store(NativeTarget* t, NativeAddr addr, NativeLoc src, MemAccess mem, KitCgMemOrder mo) { X64NativeTarget* a = x64_of(t); MCEmitter* mc = t->mc; - u32 sz = mem.size ? mem.size : native_type_size(t, src.type); + u32 sz = mem.size ? mem.size : loc_size32(t, src); int w = sz == 8u ? 1 : 0; u32 base = x64_atomic_base(a, addr); u32 sr = loc_reg(src); @@ -3135,7 +3149,7 @@ static void x64_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, KitCgMemOrder mo) { X64NativeTarget* a = x64_of(t); MCEmitter* mc = t->mc; - u32 sz = mem.size ? mem.size : native_type_size(t, dst.type); + u32 sz = mem.size ? mem.size : loc_size32(t, dst); int w = sz == 8u ? 1 : 0; u32 base = x64_atomic_base(a, addr); u32 dr = loc_reg(dst); @@ -3215,7 +3229,7 @@ static void x64_atomic_cas(NativeTarget* t, NativeLoc prior, NativeLoc ok, KitCgMemOrder success, KitCgMemOrder failure) { X64NativeTarget* a = x64_of(t); MCEmitter* mc = t->mc; - u32 sz = mem.size ? mem.size : native_type_size(t, prior.type); + u32 sz = mem.size ? mem.size : loc_size32(t, prior); int w = sz == 8u ? 1 : 0; u32 base = x64_atomic_base(a, addr); u32 rprior = loc_reg(prior); @@ -4035,7 +4049,7 @@ static void x64_asm_load_loc_to_reg(X64NativeTarget* a, SrcLoc loc, return; } x64_emit_mem(a, 1, dst, x64_asm_loc_to_addr(a, loc, src), - native_mem_for_type(t, dst.type, native_type_size(t, dst.type))); + native_mem_for_type(t, dst.type, loc_size32(t, dst))); } static void x64_asm_store_reg_to_loc(X64NativeTarget* a, SrcLoc loc, @@ -4046,7 +4060,7 @@ static void x64_asm_store_reg_to_loc(X64NativeTarget* a, SrcLoc loc, return; } x64_emit_mem(a, 0, src, x64_asm_loc_to_addr(a, loc, dst), - native_mem_for_type(t, src.type, native_type_size(t, src.type))); + native_mem_for_type(t, src.type, loc_size32(t, src))); } static void x64_asm_bind_native(X64NativeTarget* a, SrcLoc loc, Operand* out, diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -144,7 +144,16 @@ static const NativeAllocClassInfo* nd_class_info(NativeDirectTarget* d, } /* Register-location constructor is shared as native_loc_reg in - * native_target.h (arg order: type, cls, reg). */ + * native_target.h (arg order: type, cls, reg). nd_loc_reg wraps it to stamp + * the cached scalar-width descriptor (szinfo) once, here at the single + * register-loc choke point, so the per-arch binop/move/convert/cmp hot path + * reads loc.szinfo instead of re-crossing the type bridge per operand. */ +static inline NativeLoc nd_loc_reg(NativeDirectTarget* d, KitCgTypeId type, + NativeAllocClass cls, Reg reg) { + NativeLoc loc = native_loc_reg(type, cls, reg); + if (d->native) native_loc_stamp_size(d->native, &loc); + return loc; +} static void nd_flush_local(NativeDirectTarget* d, CGLocal local); static Reg nd_cache_reg_for(NativeDirectTarget* d, CGLocal local, @@ -689,7 +698,7 @@ static void nd_flush_local(NativeDirectTarget* d, CGLocal local) { if (l->dirty) nd_store_reg_to_frame( d, nd_home(d, local), l->type, - native_loc_reg(l->type, (NativeAllocClass)l->cls, l->reg)); + nd_loc_reg(d, l->type, (NativeAllocClass)l->cls, l->reg)); nd_cache_unlink(d, local); d->reg_owner[l->cls][l->reg] = CG_LOCAL_NONE; l->reg = REG_NONE; @@ -770,7 +779,7 @@ static NativeAddr nd_addr_materialize(NativeDirectTarget* d, NativeAddr in, if (out.base_kind == NATIVE_ADDR_BASE_FRAME_VALUE) { NativeAllocClass cls = (NativeAllocClass)out.cls; Reg r = nd_scratch_acquire(d, cls); - NativeLoc dst = native_loc_reg(out.base_type, cls, r); + NativeLoc dst = nd_loc_reg(d, out.base_type, cls, r); nd_load_frame_to_reg(d, dst, out.base.frame, out.base_type); out.base_kind = NATIVE_ADDR_BASE_REG; out.base.reg = r; @@ -780,7 +789,7 @@ static NativeAddr nd_addr_materialize(NativeDirectTarget* d, NativeAddr in, if (out.index_kind == NATIVE_ADDR_INDEX_FRAME_VALUE) { NativeAllocClass cls = (NativeAllocClass)out.index_cls; Reg r = nd_scratch_acquire(d, cls); - NativeLoc dst = native_loc_reg(out.index_type, cls, r); + NativeLoc dst = nd_loc_reg(d, out.index_type, cls, r); nd_load_frame_to_reg(d, dst, out.index.frame, out.index_type); out.index_kind = NATIVE_ADDR_INDEX_REG; out.index.reg = r; @@ -791,8 +800,9 @@ static NativeAddr nd_addr_materialize(NativeDirectTarget* d, NativeAddr in, !d->native->addr_legal(d->native, &out, mem)) { NativeAllocClass cls = NATIVE_REG_INT; Reg r = nd_scratch_acquire(d, cls); - NativeLoc dst = native_loc_reg( - out.base_type ? out.base_type : builtin_id(KIT_CG_BUILTIN_I64), cls, r); + NativeLoc dst = nd_loc_reg( + d, out.base_type ? out.base_type : builtin_id(KIT_CG_BUILTIN_I64), cls, + r); ND_REQUIRE_NATIVE(d, load_addr, "target does not materialize addresses"); d->native->load_addr(d->native, dst, out); nd_addr_temps_release(d, temps); @@ -825,7 +835,7 @@ static NativeLoc nd_materialize_loc(NativeDirectTarget* d, NativeLoc src, NativeLoc dst; if (src.kind == NATIVE_LOC_REG) return src; r = nd_scratch_acquire(d, cls); - dst = native_loc_reg(type ? type : src.type, cls, r); + dst = nd_loc_reg(d, type ? type : src.type, cls, r); nd_copy_to_reg(d, dst, src); return dst; } @@ -944,7 +954,7 @@ static NativeLoc nd_materialize_operand(NativeDirectTarget* d, Operand op) { /* Cache hit: pin and reuse the live register, no reload. */ d->scratch_used[l->cls] |= 1u << l->reg; nd_touch_local(d, l); - return native_loc_reg(op.type, (NativeAllocClass)l->cls, l->reg); + return nd_loc_reg(d, op.type, (NativeAllocClass)l->cls, l->reg); } /* A live entry under a different access width must reach memory before we * bypass the cache for this access. */ @@ -957,7 +967,7 @@ static NativeLoc nd_materialize_operand(NativeDirectTarget* d, Operand op) { static NativeLoc nd_dst_scratch(NativeDirectTarget* d, Operand dst) { NativeAllocClass cls = nd_class_for_type(d, dst.type); Reg r = nd_scratch_acquire(d, cls); - return native_loc_reg(dst.type, cls, r); + return nd_loc_reg(d, dst.type, cls, r); } /* Arithmetic/compare RHS: keep a constant operand as an immediate when the @@ -993,7 +1003,7 @@ static NativeLoc nd_dst_reg(NativeDirectTarget* d, Operand dst) { if (r != REG_NONE) { d->scratch_used[l->cls] |= 1u << r; /* pin for the instruction */ nd_touch_local(d, l); - return native_loc_reg(dst.type, (NativeAllocClass)l->cls, r); + return nd_loc_reg(d, dst.type, (NativeAllocClass)l->cls, r); } } } @@ -1500,8 +1510,8 @@ static void nd_copy(CgTarget* t, Operand dst, Operand src) { if (r != REG_NONE) { /* Register already holds the value as x's; just unpin + mark dirty. */ nd_dst_writeback(d, dst, - native_loc_reg(dst.type, nd_class_for_type(d, dst.type), - r)); + nd_loc_reg(d, dst.type, nd_class_for_type(d, dst.type), + r)); return; } } @@ -1849,8 +1859,8 @@ static void nd_convert(CgTarget* t, ConvKind op, Operand dst, Operand src) { Reg r = nd_rename_killed_to_dst(d, dst, src); if (r != REG_NONE) { NativeAllocClass cls = nd_class_for_type(d, dst.type); - sr = native_loc_reg(src.type, cls, r); - dr = native_loc_reg(dst.type, cls, r); + sr = nd_loc_reg(d, src.type, cls, r); + dr = nd_loc_reg(d, dst.type, cls, r); ND_REQUIRE_NATIVE(d, convert, "target does not emit converts"); d->native->convert(d->native, op, dr, sr); nd_dst_writeback(d, dst, dr); @@ -1890,7 +1900,7 @@ static void nd_call(CgTarget* t, const CGCallDesc* desc) { NativeDirectLocal* l = nd_local(d, desc->args[i]); int dead = i < 64u && ((desc->arg_dead_mask >> i) & 1u); if (dead && l->reg != REG_NONE) { - args[i] = native_loc_reg(l->type, (NativeAllocClass)l->cls, l->reg); + args[i] = nd_loc_reg(d, l->type, (NativeAllocClass)l->cls, l->reg); d->scratch_used[l->cls] |= 1u << l->reg; } else { args[i] = nd_loc_frame(d, desc->args[i], 0); @@ -1921,7 +1931,7 @@ static void nd_call(CgTarget* t, const CGCallDesc* desc) { /* Pin until the post-call ret move so the callee materialize cannot evict * this not-yet-valid entry as a scratch victim. */ d->scratch_used[rl->cls] |= 1u << r; - results[0] = native_loc_reg(rl->type, (NativeAllocClass)rl->cls, r); + results[0] = nd_loc_reg(d, rl->type, (NativeAllocClass)rl->cls, r); } else { results[0] = nd_loc_frame(d, desc->result, 0); } @@ -2009,7 +2019,7 @@ static void nd_ret(CgTarget* t, CGLocal value) { if (value != CG_LOCAL_NONE) { NativeDirectLocal* l = nd_local(d, value); loc = l->reg != REG_NONE - ? native_loc_reg(l->type, (NativeAllocClass)l->cls, l->reg) + ? nd_loc_reg(d, l->type, (NativeAllocClass)l->cls, l->reg) : nd_loc_frame(d, value, 0); locp = &loc; }