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