kit

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

commit 7adf047d2fff8c690dbee32572964fb1dd63c8fa
parent 82b58f328c50dd20e30489b5f331384ee727d1ac
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed,  3 Jun 2026 10:33:54 -0700

cg/opt: single-source width-aware integer constant-fold semantics

The width-mask / shift-mask / sign-extend / binop / unop / cmp / convert and
commutativity logic was transcribed four times: cg/fold.c (canonical),
opt/pass_o2.c gvn_* (a near-verbatim copy), opt/pass_combine.c (a third copy in
bytes), and opt/pass_simplify.c (a fourth). Extract the arithmetic core into a
new dependency-light src/cg/ir_eval.{c,h} that operates on plain
(op tag, width-in-bits, i64) values -- no Compiler, no cg/internal.h, no opt
types -- so both cg and opt can include it.

Each caller keeps its own type-selection policy (gvn: int-like/no-PTR; fold:
strict-int/PTR-aware; simplify/combine: PTR-aware width derivation) and routes
only the value arithmetic through kit_ir_eval_*. fold.h's api_width_mask/
api_mask_width/api_sign_extend_width stay as thin delegators for their external
consumers (arith.c, control.c). div/rem fault behavior stays with its owner.

Commutativity unifies only the INTEGER predicate (kit_ir_binop_is_commutative_int
/ kit_ir_cmp_is_commutative_int); combine's extra FP cases and simplify's
documented FP-exclusion are preserved at their call sites.

make lib green; test-opt test-cg-api test-opt-inline pass.

(cherry picked from commit e2d9e47ff9e4dd88a0b04bcb66f6d75e9f6f12f5)

Diffstat:
Msrc/cg/fold.c | 134+++++++++++--------------------------------------------------------------------
Asrc/cg/ir_eval.c | 177+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cg/ir_eval.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/opt/pass_combine.c | 53+++++++++++++----------------------------------------
Msrc/opt/pass_o2.c | 171+++++--------------------------------------------------------------------------
Msrc/opt/pass_simplify.c | 39++++++++++++++-------------------------
6 files changed, 294 insertions(+), 343 deletions(-)

diff --git a/src/cg/fold.c b/src/cg/fold.c @@ -7,6 +7,7 @@ * fold.h entry points. */ #include "cg/internal.h" +#include "cg/ir_eval.h" /* ============================================================ * 1. Integer constant folding @@ -31,18 +32,15 @@ int api_type_is_bool(Compiler* c, KitCgTypeId id) { return ty->kind == KIT_CG_TYPE_BOOL; } -u64 api_width_mask(u32 width) { - if (width >= 64) return UINT64_MAX; - return (1ull << width) - 1ull; -} +/* The width-arithmetic core now lives in cg/ir_eval.{c,h} (shared with opt). The + * fold.h surface (still consumed by arith.c / control.c) stays as thin + * delegators. */ +u64 api_width_mask(u32 width) { return kit_ir_width_mask(width); } -u64 api_mask_width(u64 v, u32 width) { return v & api_width_mask(width); } +u64 api_mask_width(u64 v, u32 width) { return kit_ir_mask_width(v, width); } i64 api_sign_extend_width(u64 v, u32 width) { - v = api_mask_width(v, width); - if (width >= 64) return (i64)v; - u64 sign = 1ull << (width - 1u); - return (i64)((v ^ sign) - sign); + return kit_ir_sign_extend_width(v, width); } int api_foldable_int_like_type(Compiler* c, KitCgTypeId ty, u32* width_out) { @@ -66,127 +64,31 @@ i64 api_fold_result(Compiler* c, KitCgTypeId ty, u64 v, u32 width) { int api_try_fold_int_binop(KitCg* g, BinOp op, KitCgTypeId ty, i64 a, i64 b, i64* out) { u32 width; - u64 ua, ub, r; + i64 r; + /* Strict-int / PTR-aware foldability is fold's own type policy; the wrapping + * arithmetic is the shared core. fold_result re-masks + bool-coerces. */ if (!g || !out || !api_foldable_int_type(g->c, ty, &width)) return 0; - ua = api_mask_width((u64)a, width); - ub = api_mask_width((u64)b, width); - r = 0; - switch (op) { - case BO_IADD: - r = ua + ub; - break; - case BO_ISUB: - r = ua - ub; - break; - case BO_IMUL: - r = ua * ub; - break; - case BO_AND: - r = ua & ub; - break; - case BO_OR: - r = ua | ub; - break; - case BO_XOR: - r = ua ^ ub; - break; - case BO_SHL: { - u32 sh = (u32)(ub & (u64)(width - 1u)); - r = ua << sh; - break; - } - case BO_SHR_U: { - u32 sh = (u32)(ub & (u64)(width - 1u)); - r = ua >> sh; - break; - } - case BO_SHR_S: { - u32 sh = (u32)(ub & (u64)(width - 1u)); - if (!sh) { - r = ua; - } else { - u64 sign = 1ull << (width - 1u); - r = ua >> sh; - if (ua & sign) r |= api_width_mask(width) << (width - sh); - } - break; - } - default: - return 0; - } - *out = api_fold_result(g->c, ty, r, width); + if (!kit_ir_eval_binop(op, width, a, b, &r)) return 0; + *out = api_fold_result(g->c, ty, (u64)r, width); return 1; } int api_try_fold_int_unop(KitCg* g, UnOp op, KitCgTypeId ty, i64 a, i64* out) { u32 width; - u64 ua, r; + i64 r; if (!g || !out || !api_foldable_int_type(g->c, ty, &width)) return 0; - ua = api_mask_width((u64)a, width); - switch (op) { - case UO_NEG: - r = 0u - ua; - break; - case UO_NOT: - r = ua == 0; - break; - case UO_BNOT: - r = ~ua; - break; - default: - return 0; - } - *out = api_fold_result(g->c, ty, r, width); + if (!kit_ir_eval_unop(op, width, a, &r)) return 0; + *out = api_fold_result(g->c, ty, (u64)r, width); return 1; } int api_try_fold_int_cmp(KitCg* g, CmpOp op, KitCgTypeId ty, i64 a, i64 b, i64* out) { u32 width; - u64 ua, ub; - i64 sa, sb; - int r; + /* fold's cmp policy is int-LIKE (bool/enum/ptr admitted); the predicate eval + * is shared. */ if (!g || !out || !api_foldable_int_like_type(g->c, ty, &width)) return 0; - ua = api_mask_width((u64)a, width); - ub = api_mask_width((u64)b, width); - sa = api_sign_extend_width(ua, width); - sb = api_sign_extend_width(ub, width); - switch (op) { - case CMP_EQ: - r = ua == ub; - break; - case CMP_NE: - r = ua != ub; - break; - case CMP_LT_S: - r = sa < sb; - break; - case CMP_LE_S: - r = sa <= sb; - break; - case CMP_GT_S: - r = sa > sb; - break; - case CMP_GE_S: - r = sa >= sb; - break; - case CMP_LT_U: - r = ua < ub; - break; - case CMP_LE_U: - r = ua <= ub; - break; - case CMP_GT_U: - r = ua > ub; - break; - case CMP_GE_U: - r = ua >= ub; - break; - default: - return 0; - } - *out = r ? 1 : 0; - return 1; + return kit_ir_eval_cmp(op, width, a, b, out); } /* ============================================================ diff --git a/src/cg/ir_eval.c b/src/cg/ir_eval.c @@ -0,0 +1,177 @@ +/* The single definition of the width-aware integer constant-fold semantics. + * See cg/ir_eval.h for the contract and rationale. Plain (op, width, i64) + * arithmetic only — no Compiler, no IR types — so cg/ and opt/ share it. */ + +#include "cg/ir_eval.h" + +u64 kit_ir_width_mask(u32 width) { + if (width >= 64u) return UINT64_MAX; + return (1ull << width) - 1ull; +} + +u64 kit_ir_mask_width(u64 v, u32 width) { return v & kit_ir_width_mask(width); } + +i64 kit_ir_sign_extend_width(u64 v, u32 width) { + v = kit_ir_mask_width(v, width); + if (!width || width >= 64u) return (i64)v; + u64 sign = 1ull << (width - 1u); + return (i64)((v ^ sign) - sign); +} + +int kit_ir_eval_binop(BinOp op, u32 width, i64 a, i64 b, i64* out) { + u64 ua = kit_ir_mask_width((u64)a, width); + u64 ub = kit_ir_mask_width((u64)b, width); + u64 r = 0; + if (!out) return 0; + switch (op) { + case BO_IADD: + r = ua + ub; + break; + case BO_ISUB: + r = ua - ub; + break; + case BO_IMUL: + r = ua * ub; + break; + case BO_AND: + r = ua & ub; + break; + case BO_OR: + r = ua | ub; + break; + case BO_XOR: + r = ua ^ ub; + break; + case BO_SHL: { + u32 sh = (u32)(ub & (u64)(width - 1u)); + r = ua << sh; + break; + } + case BO_SHR_U: { + u32 sh = (u32)(ub & (u64)(width - 1u)); + r = ua >> sh; + break; + } + case BO_SHR_S: { + u32 sh = (u32)(ub & (u64)(width - 1u)); + if (!sh) { + r = ua; + } else { + u64 sign = 1ull << (width - 1u); + r = ua >> sh; + if (ua & sign) r |= kit_ir_width_mask(width) << (width - sh); + } + break; + } + default: + return 0; + } + *out = (i64)kit_ir_mask_width(r, width); + return 1; +} + +int kit_ir_eval_unop(UnOp op, u32 width, i64 a, i64* out) { + u64 ua = kit_ir_mask_width((u64)a, width); + u64 r; + if (!out) return 0; + switch (op) { + case UO_NEG: + r = 0u - ua; + break; + case UO_NOT: + r = ua == 0; + break; + case UO_BNOT: + r = ~ua; + break; + default: + return 0; + } + *out = (i64)kit_ir_mask_width(r, width); + return 1; +} + +int kit_ir_eval_cmp(CmpOp op, u32 width, i64 a, i64 b, i64* out) { + u64 ua = kit_ir_mask_width((u64)a, width); + u64 ub = kit_ir_mask_width((u64)b, width); + i64 sa = kit_ir_sign_extend_width(ua, width); + i64 sb = kit_ir_sign_extend_width(ub, width); + int r; + if (!out) return 0; + switch (op) { + case CMP_EQ: + r = ua == ub; + break; + case CMP_NE: + r = ua != ub; + break; + case CMP_LT_S: + r = sa < sb; + break; + case CMP_LE_S: + r = sa <= sb; + break; + case CMP_GT_S: + r = sa > sb; + break; + case CMP_GE_S: + r = sa >= sb; + break; + case CMP_LT_U: + r = ua < ub; + break; + case CMP_LE_U: + r = ua <= ub; + break; + case CMP_GT_U: + r = ua > ub; + break; + case CMP_GE_U: + r = ua >= ub; + break; + default: + return 0; + } + *out = r ? 1 : 0; + return 1; +} + +int kit_ir_eval_convert(ConvKind k, u32 src_width, u32 dst_width, i64 src, + i64* out) { + u64 v; + if (!out) return 0; + switch (k) { + case CV_TRUNC: + case CV_ZEXT: + v = kit_ir_mask_width((u64)src, src_width); + break; + case CV_SEXT: + v = (u64)kit_ir_sign_extend_width((u64)src, src_width); + break; + case CV_BITCAST: + if (src_width != dst_width) return 0; + v = kit_ir_mask_width((u64)src, src_width); + break; + default: + return 0; + } + *out = (i64)kit_ir_mask_width(v, dst_width); + return 1; +} + +int kit_ir_binop_is_commutative_int(BinOp op) { + switch (op) { + case BO_IADD: + case BO_IMUL: + case BO_AND: + case BO_OR: + case BO_XOR: + return 1; + default: + return 0; + } +} + +int kit_ir_cmp_is_commutative_int(CmpOp op) { + return op == CMP_EQ || op == CMP_NE; +} diff --git a/src/cg/ir_eval.h b/src/cg/ir_eval.h @@ -0,0 +1,63 @@ +#ifndef KIT_CG_IR_EVAL_H +#define KIT_CG_IR_EVAL_H + +/* Width-aware integer constant-fold core: the single definition of the + * arithmetic semantics that several layers used to transcribe in lockstep + * (cg/fold.c's api_try_fold_int_*, opt/pass_o2.c's gvn_fold_*, and the + * width/convert helpers in opt/pass_combine.c + pass_simplify.c). + * + * Dependency-light by design: it pulls in only cg/cgtarget.h (for the BinOp / + * UnOp / CmpOp / ConvKind op tags and the integer typedefs) and operates on + * plain (op tag, width-in-bits, i64 bit-patterns). It knows NOTHING about + * Compiler, cg/internal.h, or the optimizer's IR types, so BOTH the semantic + * codegen (cg/) and the optimizer (opt/) can include it. + * + * Each caller keeps its own TYPE-SELECTION policy (which types are foldable, + * how a width is derived from a type, where div/rem faults live): this header + * shares only the value arithmetic once a width has been chosen. `width` is the + * scalar bit width (1..64); the eval entry points return 0 for op tags they do + * not evaluate, leaving *out untouched. */ + +#include "cg/cgtarget.h" + +/* Low `width` bits set (width>=64 -> all ones). */ +u64 kit_ir_width_mask(u32 width); + +/* `v` masked to its low `width` bits. */ +u64 kit_ir_mask_width(u64 v, u32 width); + +/* `v` interpreted as a `width`-bit value, sign-extended to a full i64. */ +i64 kit_ir_sign_extend_width(u64 v, u32 width); + +/* Fold an integer binop at the given width. Operands are masked to `width` + * first; the wrapping result is masked back to `width`. Shifts mask the count + * to (width-1). Returns 1 and stores the masked result for the wrapping + * arithmetic/bitwise/shift ops; returns 0 (and leaves *out untouched) for + * div/rem/float/anything else — those stay with their owning caller. */ +int kit_ir_eval_binop(BinOp op, u32 width, i64 a, i64 b, i64* out); + +/* Fold an integer unop at the given width (NEG/NOT/BNOT). Returns 1 and stores + * the masked result, or 0 for unhandled tags. */ +int kit_ir_eval_unop(UnOp op, u32 width, i64 a, i64* out); + +/* Fold an integer compare at the given width. Operands are masked to `width`; + * signed predicates compare the sign-extended values. Returns 1 and stores the + * 0/1 boolean for the 10 integer predicates, or 0 for the FP predicates. */ +int kit_ir_eval_cmp(CmpOp op, u32 width, i64 a, i64 b, i64* out); + +/* Fold a bit-preserving integer/pointer convert from `src_width` to `dst_width` + * bits (TRUNC / ZEXT / SEXT / BITCAST). BITCAST requires equal widths. Returns + * 1 and stores the converted constant (masked to `dst_width`); returns 0 for + * the float-domain conversions, which reinterpret the bits and must not fold + * this way. */ +int kit_ir_eval_convert(ConvKind k, u32 src_width, u32 dst_width, i64 src, + i64* out); + +/* Integer commutativity: the binops/compares whose operands may be swapped + * without changing the integer result. (FP commutativity is intentionally NOT + * unified here: callers that fold FP keep their own predicate, since NaN + * payloads and ordered/unordered routing can make operand order observable.) */ +int kit_ir_binop_is_commutative_int(BinOp op); +int kit_ir_cmp_is_commutative_int(CmpOp op); + +#endif /* KIT_CG_IR_EVAL_H */ diff --git a/src/opt/pass_combine.c b/src/opt/pass_combine.c @@ -2,6 +2,7 @@ #include <stdint.h> #include <string.h> +#include "cg/ir_eval.h" #include "core/arena.h" #include "opt/opt_internal.h" @@ -605,58 +606,30 @@ static u32 combine_scalar_width_bytes(Func* f, KitCgTypeId t) { return 0; } -static u64 combine_width_mask(u32 bytes) { - return bytes >= 8u ? UINT64_MAX : ((1ull << (bytes * 8u)) - 1ull); -} - /* Compute the constant produced by applying an integer/pointer convert `k` - * (src width `sb`, dst width `db`, both bytes) to immediate `imm`. Returns 0 + * (src width `sb`, dst width `db`, both BYTES) to immediate `imm`. Returns 0 * for kinds that aren't a bit-preserving integer/pointer move (the float * conversions reinterpret the value and must not be folded this way). The * materialized-constant model: a load_imm puts (u64)imm into a register, so a * widening move/zext keeps the low `sb` bits and a trunc/narrowing keeps the - * low `db` bits. */ + * low `db` bits. + * + * The arithmetic is the shared cg/ir_eval convert core (in BITS); combine's + * byte widths convert at the boundary. combine's BITCAST is the bit-preserving + * register move (== ZEXT here, no equal-width requirement), so it maps to + * CV_ZEXT rather than the shared eval's stricter equal-width BITCAST. */ static int const_convert_value(ConvKind k, i64 imm, u32 sb, u32 db, i64* out) { if (!sb || !db) return 0; - u64 src_mask = combine_width_mask(sb); - u64 dst_mask = combine_width_mask(db); - u64 v = (u64)imm; - switch (k) { - case CV_BITCAST: - case CV_ZEXT: - *out = (i64)((v & src_mask) & dst_mask); - return 1; - case CV_TRUNC: - *out = (i64)(v & dst_mask); - return 1; - case CV_SEXT: { - u64 low = v & src_mask; - u32 sbits = sb * 8u; - if (sbits < 64u && (low & (1ull << (sbits - 1u)))) - low |= ~src_mask; /* replicate the sign bit into the high bits */ - *out = (i64)(low & dst_mask); - return 1; - } - default: - return 0; /* CV_ITOF_*, CV_FTOI_*, CV_FEXT, CV_FTRUNC */ - } + if (k == CV_BITCAST) k = CV_ZEXT; + return kit_ir_eval_convert(k, sb * 8u, db * 8u, imm, out); } /* ---- producer-retarget legality (for sink rewrite) ---- */ +/* combine retargets the destination of a commutative producer, so it includes + * the FP commutatives (FADD/FMUL) on top of the shared integer set. */ static int binop_is_commutative(BinOp op) { - switch (op) { - case BO_IADD: - case BO_IMUL: - case BO_FADD: - case BO_FMUL: - case BO_AND: - case BO_OR: - case BO_XOR: - return 1; - default: - return 0; - } + return kit_ir_binop_is_commutative_int(op) || op == BO_FADD || op == BO_FMUL; } /* Is `producer` an op whose destination register can be safely retargeted diff --git a/src/opt/pass_o2.c b/src/opt/pass_o2.c @@ -1,6 +1,7 @@ #include <kit/cg.h> #include <string.h> +#include "cg/ir_eval.h" #include "opt/opt_internal.h" #define OPT_BLOCK_NONE 0xffffffffu @@ -619,22 +620,8 @@ void opt_addr_xform(Func* f) { opt_rebuild_def_use(f); } -static u64 gvn_width_mask(u32 width) { - if (width >= 64u) return ~0ull; - return (1ull << width) - 1ull; -} - -static u64 gvn_mask_width(u64 v, u32 width) { - return v & gvn_width_mask(width); -} - -static i64 gvn_sign_extend_width(u64 v, u32 width) { - v = gvn_mask_width(v, width); - if (!width || width >= 64u) return (i64)v; - u64 sign = 1ull << (width - 1u); - return (i64)((v ^ sign) - sign); -} - +/* gvn's TYPE policy is int-LIKE width (no-PTR); the value arithmetic is the + * shared cg/ir_eval core (see src/cg/ir_eval.h). */ static int gvn_int_like_width(Func* f, KitCgTypeId ty, u32* width_out) { u32 width = kit_cg_type_int_width((KitCompiler*)f->c, ty); if (!width || width > 64u) return 0; @@ -642,156 +629,33 @@ static int gvn_int_like_width(Func* f, KitCgTypeId ty, u32* width_out) { return 1; } -static i64 gvn_fold_result(u64 v, u32 width) { - return (i64)gvn_mask_width(v, width); -} - static int gvn_fold_binop(Func* f, BinOp op, KitCgTypeId ty, i64 a, i64 b, i64* out) { u32 width; - u64 ua, ub, r; if (!gvn_int_like_width(f, ty, &width)) return 0; - ua = gvn_mask_width((u64)a, width); - ub = gvn_mask_width((u64)b, width); - r = 0; - switch (op) { - case BO_IADD: - r = ua + ub; - break; - case BO_ISUB: - r = ua - ub; - break; - case BO_IMUL: - r = ua * ub; - break; - case BO_AND: - r = ua & ub; - break; - case BO_OR: - r = ua | ub; - break; - case BO_XOR: - r = ua ^ ub; - break; - case BO_SHL: - r = ua << (u32)(ub & (u64)(width - 1u)); - break; - case BO_SHR_U: - r = ua >> (u32)(ub & (u64)(width - 1u)); - break; - case BO_SHR_S: { - u32 sh = (u32)(ub & (u64)(width - 1u)); - if (!sh) { - r = ua; - } else { - u64 sign = 1ull << (width - 1u); - r = ua >> sh; - if (ua & sign) r |= gvn_width_mask(width) << (width - sh); - } - break; - } - default: - return 0; - } - *out = gvn_fold_result(r, width); - return 1; + return kit_ir_eval_binop(op, width, a, b, out); } static int gvn_fold_unop(Func* f, UnOp op, KitCgTypeId ty, i64 a, i64* out) { u32 width; - u64 ua, r; if (!gvn_int_like_width(f, ty, &width)) return 0; - ua = gvn_mask_width((u64)a, width); - switch (op) { - case UO_NEG: - r = 0u - ua; - break; - case UO_NOT: - r = ua == 0; - break; - case UO_BNOT: - r = ~ua; - break; - default: - return 0; - } - *out = gvn_fold_result(r, width); - return 1; + return kit_ir_eval_unop(op, width, a, out); } static int gvn_fold_cmp(Func* f, CmpOp op, KitCgTypeId ty, i64 a, i64 b, i64* out) { u32 width; - u64 ua, ub; - i64 sa, sb; - int r; if (!gvn_int_like_width(f, ty, &width)) return 0; - ua = gvn_mask_width((u64)a, width); - ub = gvn_mask_width((u64)b, width); - sa = gvn_sign_extend_width(ua, width); - sb = gvn_sign_extend_width(ub, width); - switch (op) { - case CMP_EQ: - r = ua == ub; - break; - case CMP_NE: - r = ua != ub; - break; - case CMP_LT_S: - r = sa < sb; - break; - case CMP_LE_S: - r = sa <= sb; - break; - case CMP_GT_S: - r = sa > sb; - break; - case CMP_GE_S: - r = sa >= sb; - break; - case CMP_LT_U: - r = ua < ub; - break; - case CMP_LE_U: - r = ua <= ub; - break; - case CMP_GT_U: - r = ua > ub; - break; - case CMP_GE_U: - r = ua >= ub; - break; - default: - return 0; - } - *out = r ? 1 : 0; - return 1; + return kit_ir_eval_cmp(op, width, a, b, out); } static int gvn_fold_convert(Func* f, ConvKind k, KitCgTypeId dst_ty, KitCgTypeId src_ty, i64 src, i64* out) { u32 sw, dw; - u64 v; if (!gvn_int_like_width(f, src_ty, &sw) || !gvn_int_like_width(f, dst_ty, &dw)) return 0; - switch (k) { - case CV_TRUNC: - case CV_ZEXT: - v = gvn_mask_width((u64)src, sw); - break; - case CV_SEXT: - v = (u64)gvn_sign_extend_width((u64)src, sw); - break; - case CV_BITCAST: - if (sw != dw) return 0; - v = gvn_mask_width((u64)src, sw); - break; - default: - return 0; - } - *out = gvn_fold_result(v, dw); - return 1; + return kit_ir_eval_convert(k, sw, dw, src, out); } static Val gvn_find(GvnCtx* ctx, Val v) { @@ -1405,23 +1269,6 @@ static int gvn_operand_key_less(const GvnOperandKey* a, return a->v.imm < b->v.imm; } -static int gvn_commutative_binop(BinOp op) { - switch (op) { - case BO_IADD: - case BO_IMUL: - case BO_AND: - case BO_OR: - case BO_XOR: - return 1; - default: - return 0; - } -} - -static int gvn_commutative_cmp(CmpOp op) { - return op == CMP_EQ || op == CMP_NE; -} - static int gvn_make_key(GvnCtx* ctx, const Inst* in, GvnKey* key) { memset(key, 0, sizeof *key); if (in->def == VAL_NONE || in->def >= ctx->f->nvals) return 0; @@ -1453,9 +1300,9 @@ static int gvn_make_key(GvnCtx* ctx, const Inst* in, GvnKey* key) { !gvn_make_operand_key(ctx, &in->opnds[2], &key->ops[1])) return 0; if (((IROp)in->op == IR_BINOP && - gvn_commutative_binop((BinOp)in->extra.imm)) || + kit_ir_binop_is_commutative_int((BinOp)in->extra.imm)) || ((IROp)in->op == IR_CMP && - gvn_commutative_cmp((CmpOp)in->extra.imm))) { + kit_ir_cmp_is_commutative_int((CmpOp)in->extra.imm))) { if (gvn_operand_key_less(&key->ops[1], &key->ops[0])) { GvnOperandKey tmp = key->ops[0]; key->ops[0] = key->ops[1]; diff --git a/src/opt/pass_simplify.c b/src/opt/pass_simplify.c @@ -1,8 +1,11 @@ #include <kit/cg.h> #include <string.h> +#include "cg/ir_eval.h" #include "opt/opt_internal.h" +/* simplify's PTR-aware width derivation is its own type policy; the masking and + * commutativity below are the shared cg/ir_eval core (src/cg/ir_eval.h). */ static u32 simplify_width(Func* f, KitCgTypeId ty) { u32 width = kit_cg_type_int_width((KitCompiler*)f->c, ty); if (width && width <= 64u) return width; @@ -13,14 +16,10 @@ static u32 simplify_width(Func* f, KitCgTypeId ty) { return 0; } -static u64 width_mask(u32 width) { - return width >= 64u ? UINT64_MAX : ((1ull << width) - 1ull); -} - static int imm_value(Func* f, const Operand* op, u32 width, i64* out) { (void)f; if (!op || op->kind != OPK_IMM) return 0; - *out = (i64)((u64)op->v.imm & width_mask(width)); + *out = (i64)kit_ir_mask_width((u64)op->v.imm, width); return 1; } @@ -45,7 +44,7 @@ static int operand_const(Func* f, const Operand* op, u32 width, i64* out) { if (imm_value(f, op, width, out)) return 1; if (!f->opt_reg_ssa || !op || op->kind != OPK_REG) return 0; if (!val_load_imm(f, (Val)op->v.reg, out)) return 0; - *out = (i64)((u64)*out & width_mask(width)); + *out = (i64)kit_ir_mask_width((u64)*out, width); return 1; } @@ -129,23 +128,13 @@ static int convert_noop(Func* f, const Inst* in) { } } -/* Commutative integer binops the canonicalizer below may swap operands of. - * Floating-point commutative ops are intentionally excluded: the backend - * routes them through the fp branch (which doesn't consult imm_legal) so - * canonicalization gains nothing, and IEEE-754 may distinguish operand order - * for NaN payloads. Non-commutative ops (sub, shifts, div/rem) are skipped. */ -static int is_commutative_binop(BinOp op) { - switch (op) { - case BO_IADD: - case BO_IMUL: - case BO_AND: - case BO_OR: - case BO_XOR: - return 1; - default: - return 0; - } -} +/* Commutative integer binops the canonicalizer below may swap operands of: the + * shared integer predicate (kit_ir_binop_is_commutative_int). Floating-point + * commutative ops are intentionally excluded here -- the shared predicate + * excludes them too: the backend routes FP through the fp branch (which doesn't + * consult imm_legal) so canonicalization gains nothing, and IEEE-754 may + * distinguish operand order for NaN payloads. Non-commutative ops (sub, shifts, + * div/rem) are skipped. */ static int simplify_binop(Func* f, Inst* in, int ssa) { if (!in || (IROp)in->op != IR_BINOP || in->flags || in->nopnds < 3) return 0; @@ -158,7 +147,7 @@ static int simplify_binop(Func* f, Inst* in, int ssa) { * for imm-legality; putting the constant there lets aa64 strength-reduce * `mul x, 5` to a shifted-add and lets the hoist pass lift loop-invariant * non-foldable imms. Value-preserving by commutativity. */ - if (is_commutative_binop((BinOp)in->extra.imm) && + if (kit_ir_binop_is_commutative_int((BinOp)in->extra.imm) && in->opnds[1].kind == OPK_IMM && in->opnds[2].kind != OPK_IMM) { Operand tmp = in->opnds[1]; in->opnds[1] = in->opnds[2]; @@ -171,7 +160,7 @@ static int simplify_binop(Func* f, Inst* in, int ssa) { i64 bv = 0; int ac = ssa ? operand_const(f, a, width, &av) : imm_value(f, a, width, &av); int bc = ssa ? operand_const(f, b, width, &bv) : imm_value(f, b, width, &bv); - u64 all = width_mask(width); + u64 all = kit_ir_width_mask(width); switch ((BinOp)in->extra.imm) { case BO_IADD: