MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
normalizers.cpp
Go to the documentation of this file.
1#include <type_traits>
2
4
6
7namespace mim::plug::core {
8
9namespace {
10
11constexpr nat_t idx_shift_width(u64 size) { return std::max(1_n, Idx::size2bitwidth(size)); }
12
13constexpr std::optional<unsigned> idx_shift_amount(u64 size, u64 b) {
14 auto width = idx_shift_width(size);
15 if (b >= width) return {};
16 return static_cast<unsigned>(b);
17}
18
19constexpr u64 idx_unsigned_max(u64 size) { return size == 0 ? std::numeric_limits<u64>::max() : size - 1; }
20
21constexpr u64 idx_signed_max(u64 size) {
22 return size == 0 ? static_cast<u64>(std::numeric_limits<s64>::max()) : (size - 1) / 2;
23}
24
25constexpr u64 idx_signed_min_abs(u64 size) {
26 return size == 0 ? static_cast<u64>(std::numeric_limits<s64>::max()) + 1_u64 : size / 2;
27}
28
29constexpr u64 idx_signed_abs(s64 x) { return x >= 0 ? static_cast<u64>(x) : static_cast<u64>(-(x + 1)) + 1_u64; }
30
31constexpr s64 idx_neg(u64 abs) {
32 if (abs == static_cast<u64>(std::numeric_limits<s64>::max()) + 1_u64) return std::numeric_limits<s64>::min();
33 return -static_cast<s64>(abs);
34}
35
36constexpr u64 idx_pow2(unsigned k) { return 1_u64 << k; }
37
38constexpr bool idx_sign(u64 size, u64 x) {
39 // Pre: x is already in range.
40 if (size == 0) return x > static_cast<u64>(std::numeric_limits<s64>::max()); // Idx 0 encodes 2^64.
41
42 // signed representatives in [-floor(size/2), ceil(size/2)-1]
43 return x > (size - 1) / 2;
44}
45
46constexpr s64 idx_sext(u64 size, u64 x) {
47 // Pre: x is already in range.
48 if (size == 0) return static_cast<s64>(x);
49
50 const u64 max_pos = (size - 1) / 2;
51 if (x <= max_pos) return static_cast<s64>(x);
52
53 // Negative representative is -(size - x).
54 return -static_cast<s64>(size - x);
55}
56
57constexpr u64 idx_from_signed(u64 size, s64 x) {
58 if (size == 0) return static_cast<u64>(x);
59 return x >= 0 ? static_cast<u64>(x) : size - static_cast<u64>(-x);
60}
61
62constexpr u64 idx_from_signed_mod(u64 size, s64 x) {
63 if (size == 0) return static_cast<u64>(x);
64 if (x >= 0) return static_cast<u64>(x) % size;
65
66 auto rem = idx_signed_abs(x) % size;
67 return rem == 0 ? 0 : size - rem;
68}
69
70constexpr bool idx_add_nuw(u64 size, u64 a, u64 b) {
71 if (size == 0) return a + b < a;
72 return a > size - 1 - b;
73}
74
75constexpr bool idx_sub_nuw(u64, u64 a, u64 b) { return a < b; }
76
77constexpr bool idx_mul_nuw(u64 size, u64 a, u64 b) {
78 if (a == 0 || b == 0) return false;
79
80 if (size == 0) return b > std::numeric_limits<u64>::max() / a;
81 return b > (size - 1) / a;
82}
83
84constexpr u64 idx_add(u64 size, u64 a, u64 b) {
85 if (size == 0) return a + b;
86 return a >= size - b ? a - (size - b) : a + b;
87}
88
89constexpr u64 idx_sub(u64 size, u64 a, u64 b) {
90 if (size == 0) return a - b;
91 return (a >= b) ? (a - b) : (size - (b - a));
92}
93
94constexpr u64 idx_mul(u64 size, u64 a, u64 b) {
95 if (size == 0) return a * b;
96
97 // Safe double-and-add modulo size, avoids overflow.
98 u64 r = 0;
99 while (b) {
100 if (b % 2_u64 != 0) r = idx_add(size, r, a);
101 b /= 2_u64;
102 if (b) a = idx_add(size, a, a);
103 }
104 return r;
105}
106
107constexpr bool idx_add_nsw(u64 size, u64 a, u64 b) {
108 const bool sa = idx_sign(size, a);
109 const bool sb = idx_sign(size, b);
110 const u64 r = idx_add(size, a, b);
111 const bool sr = idx_sign(size, r);
112 return (sa == sb) && (sr != sa);
113}
114
115constexpr bool idx_sub_nsw(u64 size, u64 a, u64 b) {
116 const bool sa = idx_sign(size, a);
117 const bool sb = idx_sign(size, b);
118 const u64 r = idx_sub(size, a, b);
119 const bool sr = idx_sign(size, r);
120 return (sa != sb) && (sr != sa);
121}
122
123constexpr bool idx_mul_nsw(u64 size, u64 a, u64 b) {
124 const s64 x = idx_sext(size, a);
125 const s64 y = idx_sext(size, b);
126
127 if (x == 0 || y == 0) return false;
128
129 const s64 min_val = size == 0 ? std::numeric_limits<s64>::min() : -static_cast<s64>(size / 2);
130 const s64 max_val = size == 0 ? std::numeric_limits<s64>::max() : static_cast<s64>((size - 1) / 2);
131
132 if (x == -1) return size % 2 == 0 && y == min_val;
133 if (y == -1) return size % 2 == 0 && x == min_val;
134
135 if (x > 0)
136 if (y > 0)
137 return x > max_val / y;
138 else
139 return y < min_val / x;
140 else if (y > 0)
141 return x < min_val / y;
142 else
143 return x < max_val / y;
144}
145
146constexpr std::optional<u64> idx_udiv([[maybe_unused]] u64 size, u64 a, u64 b) {
147 if (b == 0) return {};
148 return a / b;
149}
150
151constexpr std::optional<u64> idx_urem([[maybe_unused]] u64 size, u64 a, u64 b) {
152 if (b == 0) return {};
153 return a % b;
154}
155
156constexpr bool idx_slt(u64 size, u64 a, u64 b) {
157 const bool sa = idx_sign(size, a);
158 const bool sb = idx_sign(size, b);
159
160 if (a == b) return false;
161 if (!sa && sb) return false;
162 if (sa && !sb) return true;
163 return a < b;
164}
165
166constexpr bool idx_sgt(u64 size, u64 a, u64 b) { return idx_slt(size, b, a); }
167
168constexpr bool idx_sdivrem_ub(u64 size, u64 a, u64 b) {
169 const s64 x = idx_sext(size, a);
170 const s64 y = idx_sext(size, b);
171
172 if (y == 0) return true;
173
174 const s64 min_val = [&] {
175 if (size == 0) return std::numeric_limits<s64>::min();
176 return -static_cast<s64>(size / 2);
177 }();
178
179 return size % 2 == 0 && x == min_val && y == -1;
180}
181
182constexpr u64 idx_sdiv(u64 size, u64 a, u64 b) {
183 const s64 x = idx_sext(size, a);
184 const s64 y = idx_sext(size, b);
185 return idx_from_signed(size, x / y);
186}
187
188constexpr u64 idx_srem(u64 size, u64 a, u64 b) {
189 const s64 x = idx_sext(size, a);
190 const s64 y = idx_sext(size, b);
191 return idx_from_signed(size, x % y);
192}
193
194constexpr bool idx_shl_nuw(u64 size, u64 a, unsigned k) {
195 u64 x = a;
196 u64 max = idx_unsigned_max(size);
197
198 while (k--) {
199 if (x > max / 2_u64) return true;
200 x *= 2_u64;
201 }
202
203 return false;
204}
205
206constexpr bool idx_shl_nsw(u64 size, u64 a, unsigned k) {
207 const s64 x = idx_sext(size, a);
208 if (x >= 0) {
209 u64 y = static_cast<u64>(x);
210 u64 max = idx_signed_max(size);
211 while (k--) {
212 if (y > max / 2_u64) return true;
213 y *= 2_u64;
214 }
215 } else {
216 u64 y = idx_signed_abs(x);
217 u64 min = idx_signed_min_abs(size);
218 while (k--) {
219 if (y > min / 2_u64) return true;
220 y *= 2_u64;
221 }
222 }
223
224 return false;
225}
226
227constexpr std::optional<u64> idx_shl(u64 size, u64 a, u64 b, bool nsw, bool nuw) {
228 auto k = idx_shift_amount(size, b);
229 if (!k) return {};
230
231 if (nuw && idx_shl_nuw(size, a, *k)) return {};
232 if (nsw && idx_shl_nsw(size, a, *k)) return {};
233
234 return idx_mul(size, a, idx_pow2(*k));
235}
236
237constexpr std::optional<u64> idx_lshr(u64 size, u64 a, u64 b) {
238 auto k = idx_shift_amount(size, b);
239 if (!k) return {};
240 return a / idx_pow2(*k);
241}
242
243constexpr std::optional<u64> idx_ashr(u64 size, u64 a, u64 b) {
244 auto k = idx_shift_amount(size, b);
245 if (!k) return {};
246
247 auto divisor = idx_pow2(*k);
248 auto x = idx_sext(size, a);
249 if (x >= 0) return idx_from_signed(size, static_cast<s64>(static_cast<u64>(x) / divisor));
250
251 auto q = (idx_signed_abs(x) + divisor - 1_u64) / divisor;
252 return idx_from_signed(size, idx_neg(q));
253}
254
255template<icmp id>
256constexpr bool fold_icmp_idx(u64 size, u64 a, u64 b) {
257 const bool su = idx_sign(size, a);
258 const bool sv = idx_sign(size, b);
259
260 flags_t rel = 0;
261 // clang-format off
262 if (a == b) rel = icmp_mask & flags_t(icmp::xyglE); // equal
263 else if (!su && sv) rel = icmp_mask & flags_t(icmp::Xygle); // plus, minus
264 else if ( su && !sv) rel = icmp_mask & flags_t(icmp::xYgle); // minus, plus
265 else if (a > b) rel = icmp_mask & flags_t(icmp::xyGle); // greater (same sign)
266 else rel = icmp_mask & flags_t(icmp::xygLe); // less (same sign)
267 // clang-format on
268
269 return (flags_t(id) & rel) != 0;
270}
271
272template<class Id, Id id>
273std::optional<u64> fold_idx(u64 size, u64 a, u64 b, [[maybe_unused]] bool nsw, [[maybe_unused]] bool nuw) {
274 // Pre: a, b already in range for Idx size.
275
276 if constexpr (std::is_same_v<Id, wrap>) {
277 if constexpr (id == wrap::add) {
278 if (nuw && idx_add_nuw(size, a, b)) return {};
279 if (nsw && idx_add_nsw(size, a, b)) return {};
280 return idx_add(size, a, b);
281
282 } else if constexpr (id == wrap::sub) {
283 if (nuw && idx_sub_nuw(size, a, b)) return {};
284 if (nsw && idx_sub_nsw(size, a, b)) return {};
285 return idx_sub(size, a, b);
286
287 } else if constexpr (id == wrap::mul) {
288 if (nuw && idx_mul_nuw(size, a, b)) return {};
289 if (nsw && idx_mul_nsw(size, a, b)) return {};
290 return idx_mul(size, a, b);
291
292 } else if constexpr (id == wrap::shl) {
293 return idx_shl(size, a, b, nsw, nuw);
294
295 } else {
296 static_assert(false, "missing wrap subtag");
297 }
298
299 } else if constexpr (std::is_same_v<Id, shr>) {
300 if constexpr (id == shr::a)
301 return idx_ashr(size, a, b);
302 else if constexpr (id == shr::l)
303 return idx_lshr(size, a, b);
304 else
305 static_assert(false, "missing shr subtag");
306
307 } else if constexpr (std::is_same_v<Id, div>) {
308 if constexpr (id == div::udiv) {
309 return idx_udiv(size, a, b);
310
311 } else if constexpr (id == div::urem) {
312 return idx_urem(size, a, b);
313
314 } else if constexpr (id == div::sdiv) {
315 if (idx_sdivrem_ub(size, a, b)) return {};
316 return idx_sdiv(size, a, b);
317
318 } else if constexpr (id == div::srem) {
319 if (idx_sdivrem_ub(size, a, b)) return {};
320 return idx_srem(size, a, b);
321
322 } else {
323 static_assert(false, "missing div subtag");
324 }
325
326 } else if constexpr (std::is_same_v<Id, icmp>) {
327 return u64(fold_icmp_idx<id>(size, a, b));
328
329 } else if constexpr (std::is_same_v<Id, extrema>) {
330 if constexpr (id == extrema::sm)
331 return std::min(a, b);
332
333 else if constexpr (id == extrema::sM)
334 return std::max(a, b);
335
336 else if constexpr (id == extrema::Sm)
337 return idx_slt(size, a, b) ? a : b;
338
339 else if constexpr (id == extrema::SM)
340 return idx_sgt(size, a, b) ? a : b;
341
342 else
343 static_assert(false, "missing extrema subtag");
344
345 } else {
346 static_assert(false, "missing tag");
347 }
348}
349
350template<class Id, Id id>
351const Def* fold(World& world, const Def* type, const Def*& a, const Def*& b, const Def* mode = {}) {
352 if (a->isa<Bot>() || b->isa<Bot>()) return world.bot(type);
353
354 if (auto la = Lit::isa(a)) {
355 if (auto lb = Lit::isa(b)) {
356 assert(a->type() == b->type());
357
358 auto size = Lit::as(Idx::isa(a->type()));
359
360 bool nsw = false, nuw = false;
361 if constexpr (std::is_same_v<Id, wrap>) {
362 auto lm = Lit::isa(mode);
363 if (!lm) return nullptr;
364 nsw = fe::has_flag(static_cast<Mode>(*lm), Mode::nsw);
365 nuw = fe::has_flag(static_cast<Mode>(*lm), Mode::nuw);
366 }
367
368 if (size == 1) {
369 if constexpr (std::is_same_v<Id, div>) {
370 if (*lb == 0) return world.bot(type);
371 }
372 if constexpr (std::is_same_v<Id, icmp>)
373 return world.lit(type, u64(fold_icmp_idx<id>(1, 0, 0)));
374 else
375 return world.lit(type, 0);
376 }
377
378 auto res = fold_idx<Id, id>(size, *la, *lb, nsw, nuw);
379 return res ? world.lit(type, *res) : world.bot(type);
380 }
381 }
382
383 if (::mim::is_commutative(id) && Def::greater(a, b)) std::swap(a, b);
384 return nullptr;
385}
386
387template<class Id>
388const Def* fold(World& world, const Def* type, const Def*& a) {
389 if (a->isa<Bot>()) return world.bot(type);
390
391 if (auto la = Lit::isa(a)) {
392 auto size = Lit::as(Idx::isa(a->type()));
393
394 if constexpr (std::is_same_v<Id, abs>) {
395 auto x = idx_sext(size, *la);
396 if (x >= 0) return world.lit(type, static_cast<u64>(x));
397
398 auto y = idx_signed_abs(x);
399 if (size % 2_u64 == 0 && y == idx_signed_min_abs(size)) return world.lit(type, *la);
400
401 return world.lit(type, y);
402 } else {
403 static_assert(false, "missing tag");
404 }
405 }
406
407 return nullptr;
408}
409
410/// Reassociates @p a and @p b according to following rules.
411/// We use the following naming convention while literals are prefixed with an `l`:
412/// ```
413/// a op b
414/// (x op y) op (z op w)
415///
416/// (1) la op (lz op w) -> (la op lz) op w
417/// (2) (lx op y) op (lz op w) -> (lx op lz) op (y op w)
418/// (3) a op (lz op w) -> lz op (a op w)
419/// (4) (lx op y) op b -> lx op (y op b)
420/// ```
421template<class Id>
422const Def* reassociate(Id id, World& world, const Def* a, const Def* b) {
423 if (!is_associative(id)) return nullptr;
424
425 auto xy = Axm::isa<Id>(id, a);
426 auto zw = Axm::isa<Id>(id, b);
427 auto la = a->isa<Lit>();
428 auto [x, y] = xy ? xy->template args<2>() : std::array<const Def*, 2>{nullptr, nullptr};
429 auto [z, w] = zw ? zw->template args<2>() : std::array<const Def*, 2>{nullptr, nullptr};
430 auto lx = Lit::isa(x);
431 auto lz = Lit::isa(z);
432
433 // if we reassociate, we have to forget about nsw/nuw
434 auto make_op = [&world, id](const Def* a, const Def* b) {
435 if constexpr (std::is_same_v<Id, nat>)
436 return world.call(id, Defs{a, b});
437 else
438 return world.call(id, Mode::none, Defs{a, b});
439 };
440
441 if (la && lz) return make_op(make_op(a, z), w); // (1)
442 if (lx && lz) return make_op(make_op(x, z), make_op(y, w)); // (2)
443 if (lz) return make_op(z, make_op(a, w)); // (3)
444 if (lx) return make_op(x, make_op(y, b)); // (4)
445 return nullptr;
446}
447
448template<class Id>
449const Def* merge_cmps(std::array<std::array<u64, 2>, 2> tab, const Def* a, const Def* b) {
450 static_assert(sizeof(sub_t) == 1, "if this ever changes, please adjust the logic below");
451 static constexpr size_t num_bits = std::bit_width(Annex::num<Id>() - 1_u64);
452
453 auto& world = a->world();
454 auto a_cmp = Axm::isa<Id>(a);
455 auto b_cmp = Axm::isa<Id>(b);
456
457 if (a_cmp && b_cmp && a_cmp->arg() == b_cmp->arg()) {
458 // push sub bits of a_cmp and b_cmp through truth table
459 sub_t res = 0;
460 sub_t a_sub = a_cmp.sub();
461 sub_t b_sub = b_cmp.sub();
462 for (size_t i = 0; i != num_bits; ++i, res >>= 1, a_sub >>= 1, b_sub >>= 1)
463 res |= tab[a_sub & 1][b_sub & 1] << 7_u8;
464 res >>= (7_u8 - u8(num_bits));
465
466 if constexpr (std::is_same_v<Id, math::cmp>)
467 return world.call(math::cmp(res), /*mode*/ a_cmp->decurry()->decurry()->arg(), a_cmp->arg());
468 else
469 return world.call(icmp(Annex::base<icmp>() | res), a_cmp->arg());
470 }
471
472 return nullptr;
473}
474
475} // namespace
476
477template<nat id>
478const Def* normalize_nat(const Def* type, const Def* callee, const Def* arg) {
479 auto& world = type->world();
480 auto [a, b] = arg->projs<2>();
481 if (is_commutative(id) && Def::greater(a, b)) std::swap(a, b);
482 auto la = Lit::isa(a);
483 auto lb = Lit::isa(b);
484
485 if (la) {
486 if (lb) {
487 switch (id) {
488 case nat::add: return world.lit_nat(*la + *lb);
489 case nat::sub: return *la < *lb ? world.lit_nat_0() : world.lit_nat(*la - *lb);
490 case nat::mul: return world.lit_nat(*la * *lb);
491 case nat::div: return *lb == 0 ? world.lit_nat_0() : world.lit_nat(*la / *lb);
492 case nat::rem: return *lb == 0 ? a : world.lit_nat(*la % *lb);
493 }
494 }
495
496 if (*la == 0) {
497 switch (id) {
498 case nat::add: return b;
499 case nat::sub: return a; // 0 - b = 0
500 case nat::mul: return a; // 0 * b = 0
501 case nat::div: return world.lit_nat_0(); // 0 / b = 0
502 case nat::rem: return world.lit_nat_0(); // 0 % b = 0
503 }
504 }
505
506 if (*la == 1 && id == nat::mul) return b; // 1 * b = b
507 }
508
509 if (lb) {
510 if (*lb == 0) {
511 switch (id) {
512 case nat::sub: return a; // a - 0 = a
513 case nat::div: return world.lit_nat_0(); // a / 0 = 0
514 case nat::rem: return a; // a % 0 = a
515 default: break;
516 }
517 }
518 if (*lb == 1) {
519 switch (id) {
520 case nat::div: return a; // a / 1 = a
521 case nat::rem: return world.lit_nat_0(); // a % 1 = 0
522 default: break;
523 }
524 }
525 }
526
527 // (c * x) / b = (c / b) * x and (c * x) % b = 0 if c % b == 0
528 if (lb && *lb != 0 && (id == nat::div || id == nat::rem)) {
529 if (auto m = Axm::isa(nat::mul, a)) {
530 const Def* marg = m->arg();
531 auto [c, x] = marg->projs<2>();
532 if (auto lc = Lit::isa(c); lc && *lc != 0 && *lc % *lb == 0) {
533 if (id == nat::rem) return world.lit_nat_0();
534 return world.call(nat::mul, Defs{world.lit_nat(*lc / *lb), x});
535 }
536 }
537 }
538
539 if (a == b) {
540 switch (id) {
541 case nat::add: return world.call(nat::mul, Defs{world.lit_nat(2), a}); // a + a = 2 * a
542 case nat::sub: return world.lit_nat(0); // a - a = 0
543 case nat::mul: break;
544 case nat::div: break; // 0 / 0 = 0, so we cannot fold a / a = 1 symbolically
545 case nat::rem: return world.lit_nat_0(); // a % a = 0 (even for a = 0, since 0 % 0 = 0)
546 }
547 }
548
549 if (auto res = reassociate<nat>(id, world, a, b)) return res;
550
551 return world.raw_app(type, callee, {a, b});
552}
553
554template<ncmp id>
555const Def* normalize_ncmp(const Def* type, const Def* callee, const Def* arg) {
556 auto& world = type->world();
557
558 if (id == ncmp::t) return world.lit_tt();
559 if (id == ncmp::f) return world.lit_ff();
560
561 auto [a, b] = arg->projs<2>();
562 if (is_commutative(id) && Def::greater(a, b)) std::swap(a, b);
563
564 if (a == b) {
565 constexpr auto eq_mask = fe::to_underlying(ncmp::e) & 0xff;
566 if ((fe::to_underlying(id) & eq_mask) != 0) return world.lit_tt();
567 if (id == ncmp::ne) return world.lit_ff();
568 }
569
570 if (auto la = Lit::isa(a)) {
571 if (auto lb = Lit::isa(b)) {
572 // clang-format off
573 switch (id) {
574 case ncmp:: e: return world.lit_bool(*la == *lb);
575 case ncmp::ne: return world.lit_bool(*la != *lb);
576 case ncmp::l : return world.lit_bool(*la < *lb);
577 case ncmp::le: return world.lit_bool(*la <= *lb);
578 case ncmp::g : return world.lit_bool(*la > *lb);
579 case ncmp::ge: return world.lit_bool(*la >= *lb);
580 default: std::unreachable();
581 }
582 // clang-format on
583 }
584 }
585
586 return world.raw_app(type, callee, {a, b});
587}
588
589template<icmp id>
590const Def* normalize_icmp(const Def* type, const Def* c, const Def* arg) {
591 auto& world = type->world();
592 auto callee = c->as<App>();
593 auto [a, b] = arg->projs<2>();
594
595 if (auto result = fold<icmp, id>(world, type, a, b)) return result;
596 if (id == icmp::f) return world.lit_ff();
597 if (id == icmp::t) return world.lit_tt();
598 if (a == b) {
599 constexpr auto eq_mask = fe::to_underlying(icmp::e) & 0xff;
600 if ((fe::to_underlying(id) & eq_mask) != 0) return world.lit_tt();
601 if (id == icmp::ne) return world.lit_ff();
602 }
603
604 return world.raw_app(type, callee, {a, b});
605}
606
607template<extrema id>
608const Def* normalize_extrema(const Def* type, const Def* c, const Def* arg) {
609 auto& world = type->world();
610 auto callee = c->as<App>();
611 auto [a, b] = arg->projs<2>();
612 if (auto result = fold<extrema, id>(world, type, a, b)) return result;
613 return world.raw_app(type, callee, {a, b});
614}
615
616const Def* normalize_abs(const Def* type, const Def*, const Def* arg) {
617 auto& world = type->world();
618 auto [mem, a] = arg->projs<2>();
619 auto [_, actual_type] = type->projs<2>();
620 auto make_res = [&, mem = mem](const Def* res) { return world.tuple({mem, res}); };
621
622 if (auto result = fold<abs>(world, actual_type, a)) return make_res(result);
623 return {};
624}
625
626template<bit1 id>
627const Def* normalize_bit1(const Def* type, const Def* c, const Def* a) {
628 auto& world = type->world();
629 auto callee = c->as<App>();
630 auto s = callee->arg();
631 // TODO cope with wrap around
632
633 if constexpr (id == bit1::id) return a;
634
635 if (auto ls = Lit::isa(s)) {
636 switch (id) {
637 case bit1::f: return world.lit_idx(*ls, 0);
638 case bit1::t: return world.lit_idx(*ls, *ls - 1_u64);
639 case bit1::id: std::unreachable();
640 default: break;
641 }
642
643 assert(id == bit1::neg);
644 if (auto la = Lit::isa(a)) return world.lit_idx_mod(*ls, ~*la);
645 }
646
647 return {};
648}
649
650template<bit2 id>
651const Def* normalize_bit2(const Def* type, const Def* c, const Def* arg) {
652 auto& world = type->world();
653 auto callee = c->as<App>();
654 auto [a, b] = arg->projs<2>();
655 auto mode = callee->decurry()->arg();
656 auto s = callee->arg();
657 auto ls = Lit::isa(s);
658 // TODO cope with wrap around
659
660 if (is_commutative(id) && Def::greater(a, b)) std::swap(a, b);
661
662 auto tab = make_truth_table(id);
663 if (auto res = merge_cmps<icmp>(tab, a, b)) return res;
664 if (auto res = merge_cmps<math::cmp>(tab, a, b)) return res;
665
666 auto la = Lit::isa(a);
667 auto lb = Lit::isa(b);
668
669 // clang-format off
670 switch (id) {
671 case bit2:: f: return world.lit(type, 0);
672 case bit2:: t: if (ls) return world.lit(type, *ls-1_u64); break;
673 case bit2:: fst: return a;
674 case bit2:: snd: return b;
675 case bit2:: nfst: return world.call(bit1::neg, mode, a);
676 case bit2:: nsnd: return world.call(bit1::neg, mode, b);
677 case bit2:: ciff: return world.call(bit2:: iff, mode, Defs{b, a});
678 case bit2::nciff: return world.call(bit2::niff, mode, Defs{b, a});
679 default: break;
680 }
681
682 if (la && lb && ls) {
683 switch (id) {
684 case bit2::and_: return world.lit_idx (*ls, *la & *lb);
685 case bit2:: or_: return world.lit_idx (*ls, *la | *lb);
686 case bit2::xor_: return world.lit_idx (*ls, *la ^ *lb);
687 case bit2::nand: return world.lit_idx_mod(*ls, ~(*la & *lb));
688 case bit2:: nor: return world.lit_idx_mod(*ls, ~(*la | *lb));
689 case bit2::nxor: return world.lit_idx_mod(*ls, ~(*la ^ *lb));
690 case bit2:: iff: return world.lit_idx_mod(*ls, ~ *la | *lb);
691 case bit2::niff: return world.lit_idx (*ls, *la & ~*lb);
692 default: std::unreachable();
693 }
694 }
695
696 // TODO rewrite using bit2
697 auto unary = [&](bool x, bool y, const Def* a) -> const Def* {
698 if (!x && !y) return world.lit(type, 0);
699 if ( x && y) return ls ? world.lit(type, *ls-1_u64) : nullptr;
700 if (!x && y) return a;
701 if ( x && !y && id != bit2::xor_) return world.call(bit1::neg, mode, a);
702 return nullptr;
703 };
704 // clang-format on
705
706 if (is_commutative(id) && a == b) {
707 if (auto res = unary(tab[0][0], tab[1][1], a)) return res;
708 }
709
710 if (la) {
711 if (*la == 0) {
712 if (auto res = unary(tab[0][0], tab[0][1], b)) return res;
713 } else if (ls && *la == *ls - 1_u64) {
714 if (auto res = unary(tab[1][0], tab[1][1], b)) return res;
715 }
716 }
717
718 if (lb) {
719 if (*lb == 0) {
720 if (auto res = unary(tab[0][0], tab[1][0], a)) return res;
721 } else if (ls && *lb == *ls - 1_u64) {
722 if (auto res = unary(tab[0][1], tab[1][1], a)) return res;
723 }
724 }
725
726 if (auto res = reassociate<bit2>(id, world, a, b)) return res;
727
728 return world.raw_app(type, callee, {a, b});
729}
730
731const Def* normalize_idx(const Def* type, const Def* c, const Def* arg) {
732 auto& world = type->world();
733 auto callee = c->as<App>();
734 if (auto i = Lit::isa(arg)) {
735 if (*i == 0) return world.lit(type, 0); // inhabits every `Idx s`, even a symbolic one
736 if (auto s = Lit::isa(Idx::isa(type))) {
737 if (*s == 0 || *i < *s) return world.lit_idx(*s, *i);
738 if (auto m = Lit::isa(callee->decurry()->arg())) return *m ? world.bot(type) : world.lit_idx_mod(*s, *i);
739 }
740 }
741
742 return {};
743}
744
745template<shr id>
746const Def* normalize_shr(const Def* type, const Def* c, const Def* arg) {
747 auto& world = type->world();
748 auto callee = c->as<App>();
749 auto [a, b] = arg->projs<2>();
750 auto s = Idx::isa(a->type());
751 auto ls = Lit::isa(s);
752 auto width = ls.transform(idx_shift_width);
753
754 if (auto result = fold<shr, id>(world, type, a, b)) return result;
755
756 if (auto la = Lit::isa(a); la && *la == 0) return a;
757
758 if (auto lb = Lit::isa(b)) {
759 if (width && *lb >= *width) return world.bot(type);
760
761 if (*lb == 0) return a;
762 }
763
764 return world.raw_app(type, callee, {a, b});
765}
766
767template<wrap id>
768const Def* normalize_wrap(const Def* type, const Def* c, const Def* arg) {
769 auto& world = type->world();
770 auto callee = c->as<App>();
771 auto [a, b] = arg->projs<2>();
772 auto mode = callee->decurry()->arg();
773 auto s = Idx::isa(a->type());
774 auto ls = Lit::isa(s);
775 auto width = ls.transform(idx_shift_width);
776
777 if (auto result = fold<wrap, id>(world, type, a, b, mode)) return result;
778
779 // clang-format off
780 if (auto la = Lit::isa(a)) {
781 if (*la == 0) {
782 switch (id) {
783 case wrap::add: return b; // 0 + b -> b
784 case wrap::sub: break;
785 case wrap::mul: return a; // 0 * b -> 0
786 case wrap::shl: return a; // 0 << b -> 0
787 }
788 } else if (*la == 1) {
789 switch (id) {
790 case wrap::add: break;
791 case wrap::sub: break;
792 case wrap::mul: return b; // 1 * b -> b
793 case wrap::shl: break;
794 }
795 }
796 }
797
798 if (auto lb = Lit::isa(b)) {
799 if (*lb == 0) {
800 switch (id) {
801 case wrap::add: return a; // a + 0 -> a
802 case wrap::sub: return a; // a - 0 -> a
803 case wrap::mul: return b; // a * 0 -> 0
804 case wrap::shl: return a; // a << 0 -> a
805 }
806 }
807
808 if (auto lm = Lit::isa(mode); lm && ls && *lm == 0 && id == wrap::sub)
809 return world.call(wrap::add, mode, Defs{a, world.lit_idx(*ls, idx_sub(*ls, 0, *lb))}); // a - lb -> a + (0 - lb)
810 else if (id == wrap::shl && width && *lb >= *width)
811 return world.bot(type);
812 }
813
814 if (a == b) {
815 switch (id) {
816 case wrap::add:
817 if (ls && (*ls == 0 || *ls > 2)) return world.call(wrap::mul, mode, Defs{world.lit(type, 2), a}); // a + a -> 2 * a
818 break;
819 case wrap::sub: return world.lit(type, 0); // a - a -> 0
820 case wrap::mul: break;
821 case wrap::shl: break;
822 }
823 }
824 // clang-format on
825
826 if (auto res = reassociate<wrap>(id, world, a, b)) return res;
827
828 return world.raw_app(type, callee, {a, b});
829}
830
831template<div id>
832const Def* normalize_div(const Def* full_type, const Def*, const Def* arg) {
833 auto& world = full_type->world();
834 auto [mem, ab] = arg->projs<2>();
835 auto [a, b] = ab->projs<2>();
836 auto [_, type] = full_type->projs<2>(); // peel off actual type
837 auto make_res = [&, mem = mem](const Def* res) { return world.tuple({mem, res}); };
838
839 if (auto result = fold<div, id>(world, type, a, b)) return make_res(result);
840
841 if (auto la = Lit::isa(a)) {
842 if (*la == 0) return make_res(a); // 0 / b -> 0 and 0 % b -> 0
843 }
844
845 if (auto lb = Lit::isa(b)) {
846 if (*lb == 0) return make_res(world.bot(type)); // a / 0 -> ⊥ and a % 0 -> ⊥
847
848 if (*lb == 1) {
849 switch (id) {
850 case div::sdiv: return make_res(a); // a / 1 -> a
851 case div::udiv: return make_res(a); // a / 1 -> a
852 case div::srem: return make_res(world.lit(type, 0)); // a % 1 -> 0
853 case div::urem: return make_res(world.lit(type, 0)); // a % 1 -> 0
854 }
855 }
856 }
857
858 if (a == b) {
859 switch (id) {
860 case div::sdiv:
861 case div::udiv:
862 if (auto ls = Lit::isa(Idx::isa(type)); ls && *ls != 1)
863 return make_res(world.lit(type, 1)); // a / a -> 1
864 break;
865 case div::srem: return make_res(world.lit(type, 0)); // a % a -> 0
866 case div::urem: return make_res(world.lit(type, 0)); // a % a -> 0
867 }
868 }
869
870 return {};
871}
872
873template<conv id>
874const Def* normalize_conv(const Def* dst_t, const Def*, const Def* x) {
875 auto& world = dst_t->world();
876 auto s_t = x->type()->as<App>();
877 auto d_t = dst_t->as<App>();
878 auto s = s_t->arg();
879 auto d = d_t->arg();
880 auto ls = Lit::isa(s);
881 auto ld = Lit::isa(d);
882
883 if (s_t == d_t) return x;
884 if (x->isa<Bot>()) return world.bot(d_t);
885
886 // `Idx 1` has exactly one inhabitant: any conversion into it is the literal 0₁. This also keeps
887 // its zero-width arithmetic out of the backend (a 1-extent loop dim would otherwise emit i0).
888 if (ld && *ld == 1) return world.lit(d_t, 0);
889 if (ls && *ls == 1) return world.lit(d_t, 0);
890
891 if (auto l = Lit::isa(x); l && ls && ld) {
892 if constexpr (id == conv::u) {
893 if (*ld == 0) return world.lit(d_t, *l); // I64
894 return world.lit(d_t, *l % *ld);
895 }
896
897 return world.lit(d_t, idx_from_signed_mod(*ld, idx_sext(*ls, *l)));
898 }
899
900 if (ls && ld)
901 if (auto c1 = Axm::isa(id, x)) {
902 auto x1 = c1->arg();
903 if (auto ls1 = Lit::isa(x1->type()->as<App>()->arg()))
904 if (*ls == 0 || (*ls1 != 0 && *ls > *ls1)) // the intermediate conv is widening
905 if (*ld == *ls1) return x1; // conv(conv(x)) -> x
906 }
907
908 return {};
909}
910
911const Def* normalize_idx2nat(const Def* type, const Def*, const Def* x) {
912 auto& world = type->world();
913
914 if (x->isa<Bot>()) return world.bot(type);
915 if (auto l = Lit::isa(x)) return world.lit_nat(*l);
916 if (Idx::isa_lit(x->type()) == 1) return world.lit_nat_0();
917
918 return {};
919}
920
921const Def* normalize_bitcast(const Def* dst_t, const Def*, const Def* src) {
922 auto& world = dst_t->world();
923 auto src_t = src->type();
924
925 if (src->isa<Bot>()) return world.bot(dst_t);
926 if (src_t == dst_t) return src;
927
928 if (auto other = Axm::isa<bitcast>(src))
929 return other->arg()->type() == dst_t ? other->arg() : world.call<bitcast>(dst_t, other->arg());
930
931 if (auto i2n = Axm::isa<idx2nat>(src); i2n && Idx::isa(dst_t))
932 return i2n->arg()->type() == dst_t ? i2n->arg() : world.call<bitcast>(dst_t, i2n->arg());
933
934 if (auto l = Lit::isa(src)) {
935 if (dst_t->isa<Nat>()) return world.lit(dst_t, *l);
936 if (auto size = Idx::isa(dst_t)) {
937 if (auto s = Lit::isa(size)) return world.lit_idx_mod(*s, *l);
938 return world.lit(dst_t, *l);
939 }
940 }
941
942 return {};
943}
944
945// TODO this currently hard-codes x86_64 ABI
946// TODO in contrast to C, we might want to give singleton types like 'Idx 1' or '[]' a size of 0 and simply nuke each
947// and every occurance of these types in a later phase
948// TODO Pi and others
949template<trait id>
950const Def* normalize_trait(const Def*, const Def*, const Def* type) {
951 auto& world = type->world();
952 if (Axm::isa<Ptr>(type)) {
953 return world.lit_nat(8);
954 } else if (type->isa<Pi>()) {
955 return world.lit_nat(8); // Gets lowered to function ptr
956 } else if (auto size = Idx::isa(type)) {
957 if (auto w = Idx::size2bitwidth(size)) return world.lit_nat(std::max(1_n, std::bit_ceil(*w) / 8_n));
958 } else if (auto w = math::isa_f(type)) {
959 switch (*w) {
960 case 16: return world.lit_nat(2);
961 case 32: return world.lit_nat(4);
962 case 64: return world.lit_nat(8);
963 default: std::unreachable();
964 }
965 } else if (type->isa<Sigma>()) {
966 u64 offset = 0;
967 u64 align = 1;
968 for (auto t : type->ops()) {
969 auto a = Lit::isa(core::op(trait::align, t));
970 auto s = Lit::isa(core::op(trait::size, t));
971 if (!a || !s) return {};
972
973 align = std::max(align, *a);
974 offset = fe::pad(offset, *a) + *s;
975 }
976
977 offset = fe::pad(offset, align);
978 u64 size = std::max(1_u64, offset);
979
980 switch (id) {
981 case trait::align: return world.lit_nat(align);
982 case trait::size: return world.lit_nat(size);
983 }
984 } else if (auto arr = type->isa_imm<Arr>()) {
985 // One axis at a time, so a fused Arr multiplies in every extent: `«a, b; T»` is `a * size «b; T»`.
986 auto elem = arr->elem();
987 if (!elem) return {};
988 auto align = op(trait::align, elem);
989 if constexpr (id == trait::align) return align;
990 auto b = op(trait::size, elem);
991 if (!Axm::isa(trait::size, b)) return world.call(nat::mul, Defs{arr->arity(), b});
992 }
993
994 return {};
995}
996
997template<pe id>
998const Def* normalize_pe(const Def* type, const Def*, const Def* arg) {
999 auto& world = type->world();
1000
1001 if constexpr (id == pe::is_closed) {
1002 if (Axm::isa(pe::hlt, arg)) return world.lit_ff();
1003 if (arg->is_closed()) return world.lit_tt();
1004 }
1005
1006 return {};
1007}
1008
1010
1011} // namespace mim::plug::core
const Def * arg() const
Definition lam.h:251
A (possibly paramterized) Array.
Definition tuple.h:305
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:289
World & world() const noexcept
Definition def.h:1105
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:457
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
static bool greater(const Def *a, const Def *b)
Definition def.cpp:555
bool is_closed() const
Same as !has_free_vars().
Definition def.cpp:357
static constexpr nat_t size2bitwidth(nat_t n)
Definition def.h:1040
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:670
static std::optional< nat_t > isa_lit(const Def *def)
Definition def.cpp:677
static std::optional< T > isa(const Def *def)
Definition def.h:976
static T as(const Def *def)
Definition def.h:982
A dependent function type.
Definition lam.h:14
A dependent tuple type.
Definition tuple.h:39
#define MIM_core_NORMALIZER_IMPL
Definition autogen.h:325
The core Plugin
Definition core.h:8
const Def * normalize_nat(const Def *type, const Def *callee, const Def *arg)
const Def * normalize_div(const Def *full_type, const Def *, const Def *arg)
const Def * normalize_pe(const Def *type, const Def *, const Def *arg)
const Def * normalize_idx2nat(const Def *type, const Def *, const Def *x)
const Def * normalize_extrema(const Def *type, const Def *c, const Def *arg)
const Def * normalize_icmp(const Def *type, const Def *c, const Def *arg)
const Def * normalize_bit1(const Def *type, const Def *c, const Def *a)
const Def * normalize_conv(const Def *dst_t, const Def *, const Def *x)
const Def * normalize_bit2(const Def *type, const Def *c, const Def *arg)
const Def * normalize_wrap(const Def *type, const Def *c, const Def *arg)
const Def * normalize_trait(const Def *, const Def *, const Def *type)
const Def * op(trait o, const Def *type)
Definition core.h:35
const Def * normalize_abs(const Def *type, const Def *, const Def *arg)
const Def * normalize_idx(const Def *type, const Def *c, const Def *arg)
constexpr std::array< std::array< u64, 2 >, 2 > make_truth_table(bit2 id)
Definition core.h:52
const Def * normalize_bitcast(const Def *dst_t, const Def *, const Def *src)
const Def * normalize_ncmp(const Def *type, const Def *callee, const Def *arg)
constexpr flags_t icmp_mask
Definition core.h:10
@ nuw
No Unsigned Wrap around.
Definition core.h:18
@ none
Wrap around.
Definition core.h:16
@ nsw
No Signed Wrap around.
Definition core.h:17
const Def * normalize_shr(const Def *type, const Def *c, const Def *arg)
std::optional< nat_t > isa_f(const Def *def)
Definition math.h:55
The mem Plugin
u64 nat_t
Definition types.h:37
u8 sub_t
Definition types.h:42
u64 flags_t
Definition types.h:39
fe::View< const Def * > Defs
Definition def.h:96
constexpr bool is_commutative(Id)
Definition axm.h:171
int64_t s64
Definition types.h:27
constexpr bool is_associative(Id id)
Definition axm.h:177
TExt< false > Bot
Definition union.h:39
uint64_t u64
Definition types.h:27
uint8_t u8
Definition types.h:27
@ Lit
Definition def.h:125
static consteval size_t num()
Definition plugin.h:275
static consteval flags_t base()
Definition plugin.h:276