MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
eval.cpp
Go to the documentation of this file.
2
3#include <mim/lam.h>
4
5#include <mim/plug/cps/cps.h>
6
8
9using namespace std::literals;
10
12
13// TODO: maybe use template (https://codereview.stackexchange.com/questions/141961/memoization-via-template) to memoize
14const Def* Eval::augment(const Def* def, Lam* f, Lam* f_diff) {
15 if (auto i = augmented.find(def); i != augmented.end()) return i->second;
16 augmented[def] = augment_(def, f, f_diff);
17 return augmented[def];
18}
19
20const Def* Eval::derive(const Def* def) {
21 if (auto i = derived.find(def); i != derived.end()) return i->second;
22 derived[def] = derive_(def);
23 return derived[def];
24}
25
26const Def* Eval::rewrite_imm_App(const App* app) {
27 if (is_bootstrapping()) return RWPhase::rewrite_imm_App(app);
28
29 if (auto ad_app = Axm::isa<ad>(app); ad_app) {
30 // callee = autodiff T
31 // arg = function of type T
32 // (or operator)
33 // Rewrite the argument into the new world first; the whole derivation then works on that copy.
34 auto arg = rewrite(ad_app->arg());
35
36 if (arg->isa<Lam>()) return derive(arg);
37
38 // TODO: handle operators analogous
39
40 assert(0 && "not implemented");
41 return arg;
42 }
43
44 return RWPhase::rewrite_imm_App(app);
45}
46
47/*
48 * toplevel derivation
49 */
50
51/// Additionally to the derivation, the pullback is registered and the maps are initialized.
52const Def* Eval::derive_(const Def* def) {
53 auto lam = def->as_mut<Lam>(); // TODO check if mutable
54 auto deriv_ty = autodiff_type_fun_pi(lam->type());
55 auto deriv = world().mut_lam(deriv_ty)->set(lam->sym().str() + "_deriv");
56
57 // We first pre-register the derivatives.
58 // This knowledge is needed for recursion.
59 // (Alternatively, we could also use projections out the variables instead of pre-partial-pullback
60 // initialization.)
61 derived[lam] = deriv;
62
63 auto [arg_ty, ret_pi] = lam->type()->doms<2>();
64 auto deriv_all_args = deriv->var();
65 const Def* deriv_arg = deriv->var(0uz)->set("arg");
66
67 // We generate the shadow pullbacks dynamically to save work and avoid code duplication.
68 // Only the toplevel pullback for arguments and return continuation is special cased.
69
70 // TODO: check identity: could use identity tangent(arg_ty) = tangent(augment(arg_ty)) with deriv_arg->type() =
71 // augment(arg_ty) We give the argument the identity pullback.
72 auto arg_id_pb = id_pullback(arg_ty);
73 partial_pullback[deriv_arg] = arg_id_pb;
74 // The return continuation has to formally exist but should never be directly accessed.
75 auto ret_var = deriv->var(1);
76 auto ret_pb = zero_pullback(lam->var(1)->type(), arg_ty);
77 partial_pullback[ret_var] = ret_pb;
78
79 shadow_pullback[deriv_all_args] = world().tuple({arg_id_pb, ret_pb});
80
81 // We pre-register the augment replacements.
82 // The function and its variables are replaced by their new derived versions.
83 // TODO: maybe leave out function call (duplication with derived)
84 augmented[def] = deriv;
85 augmented[lam->var()] = deriv->var();
86
87 // already contains the correct application of
88 // deriv->ret_var() by specification
89 // f : cn[R] has a partial derivative (exception to closed rule)
90 // f': cn[R, cn[R, cn[A]]]
91 // this is needed for continuations (without closure conversion)
92 // but also essentially for the return continuation
93
94 // Here a reminder of types:
95 // The expression `e: B` has the implicit function `e_fun: A -> B`
96 // The partial pullback is then `e*: B* -> A*`
97 // The derivatived version is `e': B' × (B* -> A*)` which is an application of `e'_fun: A' -> B' × (B* -> A*)`
98 auto new_body = augment(lam->body(), lam, deriv);
99 deriv->set(true, new_body);
100
101 return deriv;
102}
103
104/*
105 * augment
106 */
107
108const Def* Eval::augment_lit(const Lit* lit, Lam* f, Lam*) {
109 auto pb = zero_pullback(lit->type(), f->dom(2, 0));
110 partial_pullback[lit] = pb;
111 return lit;
112}
113
114const Def* Eval::augment_var(const Var* var, Lam*, Lam*) {
115 assert(augmented.count(var));
116 auto aug_var = augmented[var];
117 assert(partial_pullback.count(aug_var));
118 return var;
119}
120
121const Def* Eval::augment_lam(Lam* lam, Lam* f, Lam* f_diff) {
122 // TODO: we need partial pullbacks for tuples (higher-order / ret-cont application)
123 // also for higher-order args, ret_cont (at another point)
124 // the pullback is not important but formally required by tuple rule
125 if (augmented.count(lam)) {
126 // We already know the function:
127 // * recursion
128 // * higher order arguments
129 // * new encounter of previous function
130 return augmented[lam];
131 }
132 // TODO: better fix (another pass as analysis?)
133 // TODO: handle open functions
134 if (Lam::isa_basicblock(lam) || lam->sym().view().contains("ret") || lam->sym().view().contains("_cont")) {
135 // A open continuation behaves the same as return:
136 // ```
137 // cont: Cn[X]
138 // cont': Cn[X,Cn[X,A]]
139 // ```
140 // There is dependency on the closed function context.
141 // (All derivatives are with respect to the arguments of a closed function.)
142
143 auto cont_dom = lam->type()->dom(); // not only 0 but all
144 auto pb_ty = pullback_type(cont_dom, f->dom(2, 0));
145 auto aug_dom = autodiff_type_fun(cont_dom);
146 auto aug_lam = world().mut_con({aug_dom, pb_ty})->set("aug_"s + lam->sym().str());
147 auto aug_var = aug_lam->var((nat_t)0);
148 augmented[lam->var()] = aug_var;
149 augmented[lam] = aug_lam; // TODO: only one of these two
150 derived[lam] = aug_lam;
151 auto pb = aug_lam->var(1);
152 partial_pullback[aug_var] = pb;
153 // We are still in same closed function.
154 auto new_body = augment(lam->body(), f, f_diff);
155 // TODO we also need to rewrite the filter
156 aug_lam->set(lam->filter(), new_body);
157
158 auto lam_pb = zero_pullback(lam->type(), f->dom(2, 0));
159 partial_pullback[aug_lam] = lam_pb;
160
161 return aug_lam;
162 }
163 // Some general function in the program needs to be differentiated.
164 // The old pass emitted a new `%autodiff.ad` application here and relied on the PassMan to revisit it;
165 // as a Phase we derive eagerly instead (derive() pre-registers itself, so recursion terminates).
166 auto aug_lam = derive(lam);
167 // TODO: directly more association here? => partly inline op_autodiff
168 return aug_lam;
169}
170
171const Def* Eval::augment_extract(const Extract* ext, Lam* f, Lam* f_diff) {
172 auto tuple = ext->tuple();
173 auto index = ext->index();
174
175 auto aug_tuple = augment(tuple, f, f_diff);
176 auto aug_index = augment(index, f, f_diff);
177
178 const Def* pb;
179 if (shadow_pullback.count(aug_tuple)) {
180 auto shadow_tuple_pb = shadow_pullback[aug_tuple];
181 pb = world().extract(shadow_tuple_pb, aug_index);
182 } else {
183 // ```
184 // e:T, b:B
185 // b = e#i
186 // b* = \lambda (s:B). e* (insert s at i in (zero T))
187 // ```
188 assert(partial_pullback.count(aug_tuple));
189 auto tuple_pb = partial_pullback[aug_tuple];
190 auto pb_ty = pullback_type(ext->type(), f->dom(2, 0));
191 auto pb_fun = world().mut_lam(pb_ty)->set("extract_pb");
192 auto pb_tangent = pb_fun->var(0uz)->set("s");
193 auto tuple_tan = world().insert(world().call<zero>(aug_tuple->type()), aug_index, pb_tangent)->set("tup_s");
194 pb_fun->app(true, tuple_pb, {tuple_tan, pb_fun->var(1) /* ret_var but make sure to select correct one */});
195 pb = pb_fun;
196 }
197
198 auto aug_ext = world().extract(aug_tuple, aug_index);
199 partial_pullback[aug_ext] = pb;
200
201 return aug_ext;
202}
203
204const Def* Eval::augment_tuple(const Tuple* tup, Lam* f, Lam* f_diff) {
205 // TODO: should use ops instead?
206 auto aug_ops = tup->projs([&](const Def* op) -> const Def* { return augment(op, f, f_diff); });
207 auto aug_tup = world().tuple(aug_ops);
208
209 auto pbs = DefVec(Defs(aug_ops), [&](const Def* op) { return partial_pullback[op]; });
210 // shadow pb = tuple of pbs
211 auto shadow_pb = world().tuple(pbs);
212 shadow_pullback[aug_tup] = shadow_pb;
213
214 // ```
215 // \lambda (s:[E0,...,Em]).
216 // sum (m,A)
217 // ((cps2ds e0*) (s#0), ..., (cps2ds em*) (s#m))
218 // ```
219 auto pb_ty = pullback_type(tup->type(), f->dom(2, 0));
220 auto pb = world().mut_lam(pb_ty)->set("tup_pb");
221
222 auto pb_tangent = pb->var(0uz)->set("tup_s");
223
224 auto tangents = DefVec(
225 pbs.size(), [&](nat_t i) { return world().app(cps::op_cps2ds_dep(pbs[i]), world().extract(pb_tangent, i)); });
226 pb->app(true, pb->var(1),
227 // summed up tangents
228 op_sum(tangent_type_fun(f->dom(2, 0)), tangents));
229 partial_pullback[aug_tup] = pb;
230
231 return aug_tup;
232}
233
234const Def* Eval::augment_pack(const Pack* pack, Lam* f, Lam* f_diff) {
235 auto arity = pack->arity(); // TODO: arity vs shape
236 auto body = pack->body();
237
238 auto aug_arity = augment_(arity, f, f_diff);
239 auto aug_body = augment(body, f, f_diff);
240
241 auto aug_pack = world().pack(aug_arity, aug_body);
242
243 assert(partial_pullback[aug_body] && "pack pullback should exists");
244 // TODO: or use scale axm
245 auto body_pb = partial_pullback[aug_body];
246 auto pb_pack = world().pack(aug_arity, body_pb);
247 shadow_pullback[aug_pack] = pb_pack;
248
249 auto pb_type = pullback_type(pack->type(), f->dom(2, 0));
250 auto pb = world().mut_lam(pb_type)->set("pack_pb");
251
252 auto f_arg_ty_diff = tangent_type_fun(f->dom(2, 0));
253 auto app_pb = world().mut_pack(world().arr(aug_arity, f_arg_ty_diff));
254
255 // TODO: special case for const width (special tuple)
256
257 // <i:n, cps2ds body_pb (s#i)>
258 app_pb->set(world().app(cps::op_cps2ds_dep(body_pb), world().extract(pb->var((nat_t)0), app_pb->var())));
259
260 auto sumup = world().app(world().annex<sum>(), {aug_arity, f_arg_ty_diff});
261
262 pb->app(true, pb->var(1), world().app(sumup, app_pb));
263
264 partial_pullback[aug_pack] = pb;
265
266 return aug_pack;
267}
268
269const Def* Eval::augment_app(const App* app, Lam* f, Lam* f_diff) {
270 auto callee = app->callee();
271 auto arg = app->arg();
272
273 auto aug_arg = augment(arg, f, f_diff);
274 auto aug_callee = augment(callee, f, f_diff);
275
276 // TODO: move down to if(!is_cont(callee))
277 if (!Pi::isa_cn(callee->type()) && Pi::isa_cn(aug_callee->type())) aug_callee = cps::op_cps2ds_dep(aug_callee);
278
279 // nested (inner application)
280 if (app->type()->isa<Pi>()) {
281 auto aug_app = world().app(aug_callee, aug_arg);
282 // We do not add a pullback as the pullback is bundled in the cps call or returned by the ds call
283 return aug_app;
284 }
285
286 // continuation (ret, if, ...)
287 if (Pi::isa_basicblock(callee->type())) {
288 // TODO: check if function (not operator)
289 // The original function is an open function (return cont / continuation) of type `Cn[E]`
290 // The augmented function `aug_callee` looks like a function but is not really a function has the type `Cn[E,
291 // Cn[E, Cn[A]]]`
292
293 // ret(e) => ret'(e, e*)
294
295 auto arg_pb = partial_pullback[aug_arg];
296 auto aug_app = world().app(aug_callee, {aug_arg, arg_pb});
297 return aug_app;
298 }
299
300 // ds function
301 if (!Pi::isa_cn(callee->type())) {
302 auto aug_app = world().app(aug_callee, aug_arg);
303
304 // The calle is ds function (e.g. operator (or its partial application))
305 auto [aug_res, fun_pb] = aug_app->projs<2>();
306 // We compose `fun_pb` with `argument_pb` to get the result pb
307 // TODO: combine case with cps function case
308 auto arg_pb = partial_pullback[aug_arg];
309 assert(arg_pb);
310 // `fun_pb: out_tan -> arg_tan`
311 // `arg_pb: arg_tan -> fun_tan`
312 auto res_pb = compose_cn(arg_pb, fun_pb);
313 partial_pullback[aug_res] = res_pb;
314 return aug_res;
315 }
316
317 // TODO: dest with a function such that f args != g args
318 {
319 // normal function app
320 // ```
321 // g: cn[E, cn X]
322 // g(args,cont)
323 // g': cn[E, cn[X, cn[X, cn E]]]
324 // g'(aug_args, ____)
325 // ```
326 // At this point g_deriv might still be "autodiff ... g".
327 auto g_deriv = aug_callee;
328
329 auto [real_aug_args, aug_cont] = aug_arg->projs<2>();
330 auto e_pb = partial_pullback[real_aug_args];
331
332 // TODO: better debug names
333 auto ret_g_deriv_ty = g_deriv->type()->as<Pi>()->dom(1);
334 auto c1_ty = ret_g_deriv_ty->as<Pi>();
335 auto c1 = world().mut_lam(c1_ty)->set("c1");
336 auto res = c1->var((nat_t)0);
337 auto r_pb = c1->var(1);
338 c1->app(true, aug_cont, {res, compose_cn(e_pb, r_pb)});
339
340 auto aug_app = world().app(aug_callee, {real_aug_args, c1});
341
342 // The result is * => no pb needed, no composition needed.
343 return aug_app;
344 }
345 assert(false && "should not be reached");
346}
347
348/// Rewrites the given definition in a lambda environment.
349const Def* Eval::augment_(const Def* def, Lam* f, Lam* f_diff) {
350 // Applications are continuations, operators, or full functions.
351 if (auto app = def->isa<App>()) {
352 return augment_app(app, f, f_diff);
353 } else if (auto ext = def->isa<Extract>()) {
354 return augment_extract(ext, f, f_diff);
355 } else if (auto var = def->isa<Var>()) {
356 return augment_var(var, f, f_diff);
357 } else if (auto lam = def->isa_mut<Lam>()) {
358 return augment_lam(lam, f, f_diff);
359 } else if (auto lam = def->isa<Lam>()) {
360 ELOG("Augment lambda: {}", lam);
361 assert(false && "can not handle non-mutable lambdas");
362 } else if (auto lit = def->isa<Lit>()) {
363 return augment_lit(lit, f, f_diff);
364 } else if (auto tup = def->isa<Tuple>()) {
365 return augment_tuple(tup, f, f_diff);
366 } else if (auto pack = def->isa<Pack>()) {
367 // TODO: handle mut packs (dependencies in the pack) (=> see paper about vectors)
368 return augment_pack(pack, f, f_diff);
369 } else if (auto ax = def->isa<Axm>()) {
370 auto diff_name = ax->sym().str();
371 find_and_replace(diff_name, ".", "_");
372 find_and_replace(diff_name, "%", "");
373 diff_name = "internal_diff_" + diff_name;
374
375 // Look the derivative up in the old world: the new world's externals are still being populated while this
376 // phase runs, so an internal_diff_* function may not have been carried over yet.
377 auto old_diff_fun = old_world().externals()[old_world().sym(diff_name)];
378 if (!old_diff_fun) {
379 ELOG("derivation not found: {}", diff_name);
380 auto expected_type = autodiff_type_fun(ax->type());
381 ELOG("expected: {} : {}", diff_name, expected_type);
382 assert(false && "unhandled axm");
383 }
384 // TODO: why does this cause a depth error?
385 return rewrite(old_diff_fun);
386 }
387
388 // TODO: handle Pi for axm app
389 // TODO: remaining (lambda, axm)
390
391 ELOG("did not expect to augment: {} : {}", def, def->type());
392 ELOG("node: {}", def->node_name());
393 assert(false && "augment not implemented on this def");
394 fe::unreachable();
395}
396
397} // namespace mim::plug::autodiff::phase
const Def * callee() const
Definition lam.h:276
const Def * arg() const
Definition lam.h:285
Definition axm.h:9
static auto isa(const Def *def)
Definition axm.h:107
Base class for all Defs.
Definition def.h:261
Def * set(size_t i, const Def *)
Successively set from left to right.
Definition def.cpp:276
T * as_mut() const
Asserts that this is a mutable, casts constness away and performs a static_cast to T.
Definition def.h:536
std::string_view node_name() const
Definition def.cpp:504
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:527
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
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:402
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
Sym sym() const
Definition def.h:558
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:210
const Def * tuple() const
Definition tuple.h:220
const Def * index() const
Definition tuple.h:221
A function.
Definition lam.h:110
const Def * filter() const
Definition lam.h:122
Lam * set(Filter filter, const Def *body)
Definition lam.cpp:29
const Pi * type() const
Definition lam.h:130
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:142
const Def * body() const
Definition lam.h:123
A (possibly paramterized) Tuple.
Definition tuple.h:170
const Def * arity() const final
Number of elements available to Extract / Insert (may be dynamic).
Definition tuple.cpp:19
Pack * set(const Def *body)
Definition tuple.h:187
flags_t annex() const
Definition phase.h:81
A dependent function type.
Definition lam.h:14
static const Pi * isa_cn(const Def *d)
Is this a continuation - i.e. is the Pi::codom mim::Bottom?
Definition lam.h:47
static const Pi * isa_basicblock(const Def *d)
Is this a continuation (Pi::isa_cn) that is not Pi::isa_returning?
Definition lam.h:51
const Def * dom() const
Definition lam.h:35
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:356
World & world()=delete
Hides both and forbids direct access.
World & old_world()
Get old Defs from here.
Definition phase.h:367
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:56
const Def * body() const
Definition tuple.h:95
Data constructor for a Sigma.
Definition tuple.h:70
A variable introduced by a binder (mutable).
Definition def.h:756
const Def * insert(const Def *d, const Def *i, const Def *val)
Definition world.cpp:463
const Def * pack(const Def *arity, const Def *body)
Definition world.h:457
const Def * app(const Def *callee, const Def *arg)
Definition world.cpp:205
const Def * tuple(Defs ops)
Definition world.cpp:308
const Def * extract(const Def *d, const Def *i)
Definition world.cpp:373
Pack * mut_pack(const Def *type)
Definition world.h:455
Sym sym(std::string_view)
Definition world.cpp:105
const Externals & externals() const
Definition world.h:264
Lam * mut_con(const Def *dom)
Definition world.h:400
Lam * mut_lam(const Pi *pi)
Definition world.h:388
const Def * augment_app(const App *, Lam *, Lam *)
Definition eval.cpp:269
const Def * augment_extract(const Extract *, Lam *, Lam *)
Definition eval.cpp:171
const Def * augment_lit(const Lit *, Lam *, Lam *)
Definition eval.cpp:108
const Def * rewrite_imm_App(const App *) final
Detect autodiff calls.
Definition eval.cpp:26
const Def * derive(const Def *)
Acts on toplevel autodiff on closed terms:
Definition eval.cpp:20
const Def * augment_lam(Lam *, Lam *, Lam *)
Definition eval.cpp:121
const Def * augment_tuple(const Tuple *, Lam *, Lam *)
Definition eval.cpp:204
const Def * augment_var(const Var *, Lam *, Lam *)
helper functions for augment
Definition eval.cpp:114
const Def * augment(const Def *, Lam *, Lam *)
Applies to (open) expressions in a functional context.
Definition eval.cpp:14
const Def * augment_pack(const Pack *pack, Lam *f, Lam *f_diff)
Definition eval.cpp:234
const Def * derive_(const Def *)
Additionally to the derivation, the pullback is registered and the maps are initialized.
Definition eval.cpp:52
const Def * augment_(const Def *, Lam *, Lam *)
Rewrites the given definition in a lambda environment.
Definition eval.cpp:349
#define ELOG(...)
Definition log.h:88
const Pi * autodiff_type_fun_pi(const Pi *)
Definition autodiff.cpp:83
const Def * op_sum(const Def *T, Defs)
Definition autodiff.cpp:151
const Def * autodiff_type_fun(const Def *)
Definition autodiff.cpp:105
const Def * tangent_type_fun(const Def *)
Definition autodiff.cpp:54
const Def * zero_pullback(const Def *E, const Def *A)
Definition autodiff.cpp:42
const Def * id_pullback(const Def *)
Definition autodiff.cpp:30
const Pi * pullback_type(const Def *E, const Def *A)
computes pb type E* -> A* E - type of the expression (return type for a function) A - type of the arg...
Definition autodiff.cpp:59
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
The tuple Plugin
View< const Def * > Defs
Definition def.h:78
u64 nat_t
Definition types.h:37
Vector< const Def * > DefVec
Definition def.h:79
void find_and_replace(std::string &str, std::string_view what, std::string_view repl)
Replaces all occurrences of what with repl.
Definition util.h:73
const Def * compose_cn(const Def *f, const Def *g)
The high level view is:
Definition lam.cpp:62