26constexpr int64_t Limit = int64_t{1} << 61;
28std::optional<int64_t> const_val(
const Def* d) {
35std::optional<std::pair<const Def*, int64_t>> as_scaled(
const Def* d) {
38 auto [
a, b] =
wrap->arg()->projs<2>();
39 if (
auto k = const_val(b))
return std::pair{
a, *k};
40 if (
auto k = const_val(a))
return std::pair{b, *k};
45bool divides(
const Def* d, int64_t c) {
46 if (c == 0)
return false;
47 if (
auto k = const_val(d))
return *k %
c == 0;
48 if (
auto s = as_scaled(d))
return s->second %
c == 0 || divides(
s->first, c);
51 auto [
a, b] =
wrap->arg()->projs<2>();
52 return divides(a, c) && divides(b, c);
55const Def* times(World& w,
const Def* d, int64_t k) {
60void flatten_add(World& w,
const Def* d, int64_t k,
DefVec& terms) {
62 auto [
a, b] =
wrap->arg()->projs<2>();
63 flatten_add(w, a, k, terms);
64 flatten_add(w, b, k, terms);
65 }
else if (
auto s = as_scaled(d);
s &&
s->second != 0 && std::abs(k) <= Limit / std::abs(
s->second)) {
66 flatten_add(w,
s->first, k *
s->second, terms);
68 terms.emplace_back(times(w, d, k));
73const Def* exact_div(World& w,
const Def* d, int64_t c) {
75 if (
auto k = const_val(d))
return w.lit_i64(
u64(*k / c));
76 if (
auto s = as_scaled(d)) {
78 return k %
c == 0 ? times(w, x, k / c) : times(
w, exact_div(
w, x,
c), k);
81 auto [
a, b] =
wrap->arg()->projs<2>();
85const Def*
sum(World& w,
Defs terms) {
86 const Def* acc =
nullptr;
89 return acc ? acc :
w.lit_i64(0);
93const Def* emit_div(World& w,
const Def*& mem,
core::div op,
const Def* x,
const Def* c) {
94 auto [m, v] =
w.call(
op,
Defs{mem,
w.tuple({x,
c})})->projs<2>();
101std::optional<LowerIndex::Range> LowerIndex::range_of(
const Def* d)
const {
102 auto bounded = [](int64_t lo, int64_t hi) -> std::optional<Range> {
103 if (lo < -Limit || hi > Limit)
return {};
104 return Range{lo, hi};
107 if (
auto k = const_val(d))
return Range{*k, *k};
109 if (
auto ex =
d->isa<Extract>()) {
111 if (ex->tuple() == dom_.idxs && i && *i < dom_.extents.size())
112 if (
auto n = dom_.extents[*i]; n && *n > 0)
return Range{0, *n - 1};
116 auto [x,
c] =
div->arg(2, 1)->projs<2>();
117 auto k = const_val(c);
118 if (!k || *k <= 0)
return {};
122 return Range{
l->lo / *k,
l->hi / *k};
127 auto [
a, b] =
wrap->arg()->projs<2>();
128 auto l = range_of(a),
r = range_of(b);
129 if (!l || !r)
return {};
135 std::pair{
l->lo,
r->lo},
136 std::pair{
l->lo,
r->hi},
137 std::pair{
l->hi,
r->lo},
138 std::pair{
l->hi,
r->hi}
140 for (
auto [x, y] : cands)
141 if (x != 0 && std::abs(y) > Limit / std::abs(x))
return {};
142 auto prods = cands | std::views::transform([](
auto p) {
return p.first * p.second; });
143 return bounded(std::ranges::min(prods), std::ranges::max(prods));
152LowerIndex::Split LowerIndex::split(
const Def* x, int64_t c) {
157 std::optional<Range> rest = Range{0, 0};
158 for (
auto t : terms) {
160 split.even.emplace_back(t);
162 split.rest.emplace_back(t);
163 auto r = range_of(t);
164 if (rest && r && std::abs(rest->lo) <= Limit && std::abs(rest->hi) <= Limit)
165 rest = Range{rest->lo +
r->lo, rest->hi +
r->hi};
170 split.carries = !rest || rest->lo < 0 || rest->hi >=
c;
175std::optional<LowerIndex::Peel> LowerIndex::peel(
const Def* x, int64_t c) {
177 if (c <= 1)
return {};
178 auto r = range_of(x);
179 if (!r ||
r->lo < 0)
return {};
181 fe::Vector<int64_t> divisors;
182 for (int64_t d = 2;
d *
d <=
c; ++
d)
183 if (c % d == 0) divisors.emplace_back(d), divisors.emplace_back(c / d);
184 std::ranges::sort(divisors, std::greater{});
186 for (
auto k : divisors) {
187 auto s = split(x, k);
188 if (
s.even.empty() ||
s.carries)
continue;
189 auto q =
sum(w,
DefVec(
s.even.size(), [&](
size_t i) { return exact_div(w, s.even[i], k); }));
190 return Peel{q,
sum(w,
s.rest), k};
196const Def* LowerIndex::fold_udiv(
const Def* x, int64_t c) {
198 if (c <= 0)
return nullptr;
199 if (c == 1)
return x;
200 if (
auto k = const_val(x))
return w.lit_i64(
u64(*k / c));
203 auto r = range_of(x);
204 if (!r ||
r->lo < 0)
return nullptr;
205 if (
r->hi < c)
return w.lit_i64(0);
207 auto s = split(x, c);
208 if (
s.even.empty() ||
s.carries)
return nullptr;
209 return sum(w,
DefVec(
s.even.size(), [&](
size_t i) { return exact_div(w, s.even[i], c); }));
213const Def* LowerIndex::fold_urem(
const Def* x, int64_t c) {
215 if (c <= 0)
return nullptr;
216 if (c == 1)
return w.lit_i64(0);
217 if (
auto k = const_val(x))
return w.lit_i64(
u64(*k % c));
219 auto r = range_of(x);
220 if (!r ||
r->lo < 0)
return nullptr;
221 if (
r->hi < c)
return x;
222 if (divides(x, c))
return w.lit_i64(0);
224 auto s = split(x, c);
225 return s.even.empty() ||
s.carries ? nullptr :
sum(w,
s.rest);
229const Def* LowerIndex::udiv(
const Def* x,
const Def* c) {
231 if (
auto k = const_val(c)) {
232 if (
auto folded = fold_udiv(x, *k))
return folded;
233 if (
auto p = peel(x, *k))
return udiv(p->q,
w.lit_i64(
u64(*k / p->k)));
238const Def* LowerIndex::urem(
const Def* x,
const Def* c) {
240 if (
auto k = const_val(c)) {
241 if (
auto folded = fold_urem(x, *k))
return folded;
242 if (
auto p = peel(x, *k)) {
243 auto hi = urem(p->q,
w.lit_i64(
u64(*k / p->k)));
282 fe::throwf(
"`affine.op.mul` should have been rewritten to `affine.semiop.mul` and then to `core.mul`");
292 app->
blame(
"`affine.semiop.mul` called with non-constant second argument").bail();
314 auto strides = [&](
const Def* s,
size_t n) {
316 if (n == 0)
return str;
317 str[n - 1] = w.lit_nat(1);
318 for (
size_t k = n - 1; k-- != 0;)
320 for (
size_t k = 0; k != n; ++k)
328 auto xs = idxs->projs();
329 auto str = strides(s, xs.size());
330 const Def* lin = w.lit(w.type_i64(), 0);
331 for (
size_t k = 0; k != xs.size(); ++k) {
341 auto m = s->num_projs();
342 auto str = strides(s, m);
344 m, [&](
size_t d) {
return urem(udiv(lin, str[d]), w.call<
core::bitcast>(w.type_i64(), s->proj(m, d))); }));
352 auto [mn, sinout, f, idxs, _] = app->
callee()->as<
App>()->uncurry_args<5>();
353 auto [sin, sout] = sinout->projs<2>();
355 auto __ = fe::Restore(mem_);
359 auto f_lam = f->isa_mut<
Lam>();
362 auto& eta = etas_[f];
367 Lam* idx_map_lam =
nullptr;
368 Lam* rw_idx_lam =
nullptr;
369 if (
auto it = specialized_[f_lam].find(sin); it != specialized_[f_lam].end()) {
370 idx_map_lam = it->second;
371 }
else if (
auto idx_lam =
lookup(f_lam)) {
374 if (
auto mut = idx_lam->isa_mut<
Lam>();
375 !(mut && mut->num_vars() == 2 && mut->var(2, 0)->type() ==
mem->type()))
379 auto lam_pi =
rewrite(f_lam->type())->as<
Pi>();
381 idx_map_lam = w.mut_lam(w.pi({mem->type(), lam_pi->dom()}, {mem->type(), lam_pi->codom()}));
382 specialized_[f_lam][sin] = idx_map_lam;
383 if (!
lookup(f_lam))
map(f_lam, idx_map_lam);
387 map(f_lam->var(), idx_map_lam->
var(2, 1));
388 for (
size_t i = 0; i != f_lam->
num_vars(); ++i)
389 map(f_lam->var(f_lam->num_vars(), i), idx_map_lam->
var(2, 1)->
proj(f_lam->num_vars(), i));
390 mem_ = idx_map_lam->
var(2, 0);
392 auto n = sin->num_projs();
393 auto extents = fe::Vector<std::optional<int64_t>>(n, [&](
size_t i) -> std::optional<int64_t> {
394 if (
auto e =
Lit::isa(sin->proj(n, i)); e && *e <=
u64(Limit))
return int64_t(*e);
397 auto restore_dom = fe::Restore(dom_, Domain{idx_map_lam->
var(2, 1), std::move(extents)});
399 auto get_body = [&]() ->
const Def* {
400 if (rw_idx_lam)
return rw_idx_lam->
reduce_body(idx_map_lam->
var(2, 1));
403 idx_map_lam->
set(
true, w.tuple({mem_, get_body()}))->
set(f_lam->dbg_key());
407 auto [out_mem, out_idxs] = w.app(idx_map_lam, {
mem, lifted})->projs<2>();
410 auto n_out = sout_n->num_projs();
411 auto narrowed = w.tuple(
DefVec(
412 n_out, [&](
size_t j) {
return w.call(
core::conv::u, sout_n->proj(n_out, j), out_idxs->proj(n_out, j)); }));
414 return w.tuple({out_mem, narrowed});
const Def * callee() const
static auto isa(const Def *def)
const Def * proj(nat_t a, nat_t i) const
Similar to World::extract while assuming an arity of a, but also works on Sigmas and Arrays.
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
const Def * var(nat_t a, nat_t i) noexcept
nat_t num_vars() noexcept
auto projs(Projector auto f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
fe::Error & blame(fe::cite_string< Args... > s, Args &&... args) const
Reports an error that blames this; chain Error::n for Notes and Error::bail to throw.
const Var * has_var()
Only returns not nullptr, if Var of this mutable has ever been created.
static Lam * eta_expand(Filter, const Def *f)
const Def * reduce_body(const Def *arg) const
static std::optional< T > isa(const Def *def)
A dependent function type.
Pi * set(const Def *dom, const Def *codom)
World & new_world()
Create new Defs into this.
const Def * lookup(const Def *) override
Lookup old_def by searching in reverse through the stack of maps.
const Def * rewrite_imm_App(const App *) override
void bind(const Var *old_var, const Var *new_var)
const Def * map(const Def *old_def, const Def *new_def)
virtual const Def * rewrite(const Def *)
void push(Vars old_vars={}, Vars new_vars={})
const Def * rewrite_imm_App(const App *) final
const Def * rewrite(const Def *) final
fe::View< const Def * > Defs
fe::Vector< const Def * > DefVec