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:
| M | src/cg/fold.c | | | 134 | +++++++++++-------------------------------------------------------------------- |
| A | src/cg/ir_eval.c | | | 177 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | src/cg/ir_eval.h | | | 63 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | src/opt/pass_combine.c | | | 53 | +++++++++++++---------------------------------------- |
| M | src/opt/pass_o2.c | | | 171 | +++++-------------------------------------------------------------------------- |
| M | src/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: