MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
lower_map_reduce.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4
5#include <fe/log.h>
6#include <fe/vector.h>
7
8#include <mim/driver.h>
9#include <mim/lam.h>
10
14#include <mim/plug/core/core.h>
15#include <mim/plug/cps/cps.h>
16#include <mim/plug/mem/mem.h>
17
18namespace mim::plug::gpu::phase {
19
20namespace {
21
22using fe::Vector;
23
24/// Whether an explicit, user-written `gpu.init` is reachable from `def`.
25bool contains_gpu_init(const Def* def, DefSet& seen) {
26 if (auto [_, ins] = seen.emplace(def); !ins) return false;
27 if (Axm::isa<gpu::init>(def)) return true;
28 for (auto d : def->deps())
29 if (contains_gpu_init(d, seen)) return true;
30 return false;
31}
32
33/// Mirrors `btensor::phase::LowerMapReduce`'s helper of the same name: a counting `affine.For` loop body
34std::pair<Lam*, const Def*> counting_for(const Def* bound, const Def* acc, const Def* exit, Sym name) {
35 auto& w = bound->world();
36 auto acc_ty = acc->type();
37 auto body = w.mut_con({/* iter */ w.type_i64(), /* acc */ acc_ty, /* return */ w.cn(acc_ty)})->set(name);
38 auto for_loop = w.call<affine::For>(body, exit, Defs{w.lit_i64(0), bound, w.lit_i64(1), acc});
39 return {body, for_loop};
40}
41
42/// Mirrors `btensor::phase::LowerMapReduce`'s `affine_map` lambda
43std::pair<const Def*, const Def*>
44affine_map(const Def* f, const Def* m, const Def* n, const Def* sin, const Def* sout, const Def* idxs, const Def* mem) {
45 auto& w = mem->world();
46 auto a = w.app(w.annex<affine::map>(), Defs{m, n});
47 a = w.app(a, Defs{sin, sout});
48 a = w.app(a, f);
49 a = w.app(a, idxs);
50 a = w.app(a, mem->type()->as<App>()->arg());
51 return w.app(a, mem)->projs<2>();
52}
53
54const Def* fold_index(const Def* shape, const Def* idx) {
55 auto& w = shape->world();
56 auto r = shape->num_projs();
57 DefVec out;
58 bool dropped = false;
59 for (size_t i = 0; i != r; ++i)
60 if (auto l = Lit::isa<nat_t>(shape->proj(r, i)); l && *l == 1)
61 dropped = true;
62 else
63 out.push_back(idx->proj(r, i));
64 // Without dropped axes the tuple below would just eta-reduce back to `idx` — but only after
65 // World::tuple's pack normalization has alpha-compared the projections, which walks `idx`'s whole
66 // (mem-threaded, Var-dependent) coordinate chain per elem pair — exponentially. Return `idx` directly.
67 if (!dropped) return idx;
68 return w.tuple(out);
69}
70
71/// Chained `mem.lea` over a coordinate tuple.
72const Def* op_lea_tuple(const Def* ptr, const Def* tuple) {
73 auto n = tuple->num_projs();
74 auto element = ptr;
75 for (size_t i = 0; i != n; ++i)
76 element = mem::op_lea(element, tuple->proj(n, i));
77 return element;
78}
79
80/// Scalarize may flatten an escaped `comb`/`post` from `Cn [[mem, T, ins], Cn ret]` to `Cn [mem, T, ins, Cn ret]`.
81Lam* rebuild_lam_global_mem(Lam* lam, const Def* Tout, Sym name) {
82 auto& w = lam->world();
83 auto global_ty = w.annex<gpu::GlobalM>();
84
85 Lam* new_lam;
86 if (lam->num_vars() == 2) {
87 auto [_, Tin, extra_ty] = lam->var(0)->type()->projs<3>();
88 new_lam = w.mut_con(Defs{w.sigma({global_ty, Tin, extra_ty}), w.cn({global_ty, Tout})})->set(name);
89 } else {
90 auto Tin = lam->var(1)->type();
91 auto extra_ty = lam->var(2)->type();
92 new_lam = w.mut_con(Defs{global_ty, Tin, extra_ty, w.cn({global_ty, Tout})})->set(name);
93 }
94 new_lam->set(true, lam->reduce_body(new_lam->var()));
95 return new_lam;
96}
97
98/// Mirrors `btensor::phase::LowerMapReduce`'s helper of the same name.
99void apply_cps(World& w, Lam* mut, const Def* f, DefVec parts, const Def* k) {
100 auto dom = f->type()->as<Pi>()->dom();
101 if (dom->num_projs() == parts.size() + 1) {
102 parts.emplace_back(k);
103 mut->app(true, f, parts);
104 } else {
105 mut->app(true, f, Defs{w.tuple(parts), k});
106 }
107}
108
109Vector<nat_t> row_major_strides(const Vector<nat_t>& dims) {
110 Vector<nat_t> strides(dims.size());
111 nat_t acc = 1;
112 for (auto i = dims.size(); i-- != 0;) {
113 strides[i] = acc;
114 acc *= dims[i];
115 }
116 return strides;
117}
118
119std::pair<const Def*, DefVec> unflatten_index(World& w, const Def* flat, const Vector<nat_t>& dims, const Def* mem) {
120 auto strides = row_major_strides(dims);
121 DefVec coords(dims.size());
122 for (size_t d = 0; d != dims.size(); ++d) {
123 auto [m1, q] = w.call(core::div::udiv, Defs{mem, w.tuple({flat, w.lit_i64(strides[d])})})->projs<2>();
124 auto [m2, r] = w.call(core::div::urem, Defs{m1, w.tuple({q, w.lit_i64(dims[d])})})->projs<2>();
125 mem = m2;
126 coords[d] = w.call(core::conv::u, w.lit_nat(dims[d]), r);
127 }
128 return {mem, coords};
129}
130
131struct InputDesc {
132 DefVec rs, ss, ts, accs;
133};
134
135InputDesc extract_input_desc(nat_t n, const Def* Rs, const Def* Ss, const Def* Ts, const Def* accs) {
136 InputDesc desc{DefVec(n), DefVec(n), DefVec(n), DefVec(n)};
137 for (nat_t i = 0; i != n; ++i) {
138 desc.rs[i] = Rs->proj(n, i);
139 desc.ss[i] = Ss->proj(n, i);
140 desc.ts[i] = Ts->proj(n, i);
141 desc.accs[i] = accs->proj(n, i);
142 }
143 return desc;
144}
145
146struct Inputs {
147 const Def* mem;
148 const Def* global;
149 DefVec dptrs;
150};
151
152Inputs alloc_copy_inputs(World& w, const Def* m0, const Def* m1, Defs ris, Defs sis, Defs tis, const Def* inputs) {
153 DefVec dptrs(ris.size());
154 for (size_t i = 0; i != ris.size(); ++i) {
155 auto alloc_copy = w.app(w.app(w.annex<gpu::buf_alloc_copy>(), {ris[i], sis[i], tis[i]}),
156 {m0, m1, inputs->proj(ris.size(), i)});
157 auto [m2, g2, ptr] = alloc_copy->projs<3>();
158 m0 = m2;
159 m1 = g2;
160 dptrs[i] = ptr;
161 }
162 return {m0, m1, dptrs};
163}
164
165std::pair<const Def*, const Def*> alloc_output(World& w, const Def* m1, const Def* elem_ty, const Def* So, nat_t ro) {
166 auto arr_ty = elem_ty;
167 for (auto d = ro; d-- != 0;)
168 arr_ty = w.arr(So->proj(ro, d), arr_ty);
169 return w.app(w.app(w.annex<gpu::alloc>(gpu::alloc::block), arr_ty), m1)->projs<2>();
170}
171
172struct Grid {
173 nat_t n_groups, n_items, total;
174};
175
176Grid grid_layout(const Vector<nat_t>& out_dims) {
177 nat_t total = 1;
178 for (auto d : out_dims)
179 total *= d;
180 nat_t n_items = std::min<nat_t>(total, 1024);
181 nat_t n_groups = (total + n_items - 1) / n_items;
182 return {n_groups, n_items, total};
183}
184
185/// Per-input state for `build_kernel`: `InputDesc`'s shapes/access-functions plus `alloc_copy_inputs`'s pointers.
186struct Mapped {
187 DefVec rs, ss, dptrs, accs;
188 nat_t n() const { return dptrs.size(); }
189};
190
191/// Builds the kernel: one thread per output point, reducing sequentially over the `rr` reduction dims.
192Lam* build_kernel(World& w,
193 const Def* Ro,
194 nat_t rr,
195 const Vector<nat_t>& out_dims,
196 const Def* Sr,
197 const Def* So,
198 const Mapped& ins,
199 const Def* To,
200 const Def* acc_out,
201 const Def* init,
202 Lam* global_comb,
203 const Mapped& post_ins,
204 Lam* global_post,
205 const Def* Tp,
206 const Def* out_dptr,
207 const Grid& grid) {
208 auto nis = ins.n();
209 auto nps = post_ins.n();
210 auto ro = out_dims.size();
211 auto nloops_nat = ro + rr;
212 auto n = w.lit_nat(nloops_nat);
213
214 auto global_ty = w.annex<gpu::GlobalM>();
215 auto shared_ty = w.annex<gpu::SharedM>();
216 auto const_ty = w.annex<gpu::ConstM>();
217 auto local_ty = w.annex<gpu::LocalM>();
218
219 DefVec arg_tys(nis + nps + 1);
220 for (size_t i = 0; i != nis; ++i)
221 arg_tys[i] = ins.dptrs[i]->type();
222 for (size_t j = 0; j != nps; ++j)
223 arg_tys[nis + j] = post_ins.dptrs[j]->type();
224 arg_tys[nis + nps] = out_dptr->type();
225
226 auto kernel
227 = w.mut_con(Defs{global_ty, shared_ty, const_ty, local_ty, w.type_idx(grid.n_groups), w.type_idx(grid.n_items),
228 w.sigma(Defs{}), w.sigma(arg_tys), w.cn({global_ty, shared_ty, const_ty, local_ty})})
229 ->set("mapReduceKernel");
230 auto [k_global, k_shared, k_const, k_local, group_id, item_id, k_shared_ptrs, k_args, k_ret] = kernel->vars<9>();
231
232 DefVec k_dptrs(nis);
233 for (size_t i = 0; i != nis; ++i)
234 k_dptrs[i] = k_args->proj(nis + nps + 1, i);
235 DefVec k_post_dptrs(nps);
236 for (size_t j = 0; j != nps; ++j)
237 k_post_dptrs[j] = k_args->proj(nis + nps + 1, nis + j);
238 auto k_out_dptr = k_args->proj(nis + nps + 1, nis + nps);
239
240 auto group_i64 = grid.n_groups == 1 ? w.lit_i64(0) : w.call(core::conv::u, w.lit_nat_0(), group_id);
241 auto item_i64 = grid.n_items == 1 ? w.lit_i64(0) : w.call(core::conv::u, w.lit_nat_0(), item_id);
242 auto flat
244 Defs{w.call(core::wrap::mul, core::Mode::none, Defs{group_i64, w.lit_i64(grid.n_items)}), item_i64});
245
246 auto in_range = w.call(core::icmp::ul, Defs{flat, w.lit_i64(grid.total)});
247 auto early_return = w.mut_con(w.sigma(Defs{}))->set("outOfRange");
248 early_return->app(true, k_ret, Defs{k_global, k_shared, k_const, k_local});
249 auto body = w.mut_con(w.sigma(Defs{}))->set("inRange");
250 kernel->set(true, w.app(w.extract(w.tuple({early_return, body}), in_range), w.tuple()));
251
252 auto write_back = w.mut_con(Defs{global_ty, To})->set("writeBack");
253 auto [wb_mem, acc_final] = write_back->vars<2>();
254 auto [wb_mem2, wb_coords] = unflatten_index(w, flat, out_dims, wb_mem);
255 DefVec wb_idx = wb_coords;
256 for (size_t j = 0; j != rr; ++j)
257 wb_idx.push_back(w.call(core::conv::u, Sr->proj(nloops_nat, ro + j), w.lit_i64(0)));
258 auto [wc_mem, write_coords] = affine_map(acc_out, Ro, n, Sr, So, w.tuple(wb_idx), wb_mem2);
259
260 auto pcur = wc_mem;
261 DefVec post_elems(nps);
262 for (size_t j = 0; j != nps; ++j) {
263 auto [pc_mem, pcoords]
264 = affine_map(post_ins.accs[j], post_ins.rs[j], Ro, So, post_ins.ss[j], write_coords, pcur);
265 pcur = pc_mem;
266 auto [rd_mem, rd_val]
267 = w.call<mem::load>(Defs{pcur, op_lea_tuple(k_post_dptrs[j], fold_index(post_ins.ss[j], pcoords))})
268 ->projs<2>();
269 pcur = rd_mem;
270 post_elems[j] = rd_val;
271 }
272
273 auto after_post = w.mut_con(Defs{global_ty, Tp})->set("afterPost");
274 auto [post_mem, elem_post] = after_post->vars<2>();
275 auto final_mem
276 = w.call<mem::store>(Defs{post_mem, op_lea_tuple(k_out_dptr, fold_index(So, write_coords)), elem_post});
277 after_post->app(true, k_ret, Defs{final_mem, k_shared, k_const, k_local});
278 apply_cps(w, write_back, global_post, {pcur, acc_final, w.tuple(post_elems)}, after_post);
279
280 const Def* acc = w.tuple({k_global, init});
281 const Def* cont = write_back;
282 Lam* current_mut = body;
283 DefVec red_iters;
284 red_iters.reserve(rr);
285 for (size_t j = 0; j != rr; ++j) {
286 auto dim = Sr->proj(nloops_nat, ro + j);
287 auto bound = w.call<core::bitcast>(w.type_i64(), dim);
288 auto [rbody, for_call] = counting_for(bound, acc, cont, w.sym("forRed_" + std::to_string(j)));
289 auto [iter, new_acc, yield] = rbody->vars<3>();
290 cont = yield;
291 red_iters.push_back(w.call(core::conv::u, dim, iter));
292 acc = new_acc;
293 current_mut->set(true, for_call);
294 current_mut = rbody;
295 }
296 auto [red_mem, elem_acc] = acc->projs<2>();
297
298 auto [body_mem, body_coords] = unflatten_index(w, flat, out_dims, red_mem);
299 DefVec iters_v = body_coords;
300 iters_v.insert(iters_v.end(), red_iters.begin(), red_iters.end());
301 auto iters = w.tuple(iters_v);
302
303 auto cur = body_mem;
304 DefVec input_elems(nis);
305 for (size_t i = 0; i != nis; ++i) {
306 auto [mc_mem, coords] = affine_map(ins.accs[i], ins.rs[i], n, Sr, ins.ss[i], iters, cur);
307 cur = mc_mem;
308 auto [rd_mem, rd_val]
309 = w.call<mem::load>(Defs{cur, op_lea_tuple(k_dptrs[i], fold_index(ins.ss[i], coords))})->projs<2>();
310 cur = rd_mem;
311 input_elems[i] = rd_val;
312 }
313
314 apply_cps(w, current_mut, global_comb, {cur, elem_acc, w.tuple(input_elems)}, cont);
315
316 return kernel;
317}
318
319Lam* build_teardown(World& w,
320 const Def* Ro,
321 const Def* So,
322 const Def* Tp,
323 Defs dptrs,
324 Defs post_dptrs,
325 const Def* out_dptr,
326 const Def* cont) {
327 auto global_ty = w.annex<gpu::GlobalM>();
328 auto const_ty = w.annex<gpu::ConstM>();
329 auto mem_ty = w.call<mem::M>(0);
330
331 auto after_launch = w.mut_con(Defs{mem_ty, global_ty, const_ty})->set("afterLaunch");
332 auto [post_mem, post_global, post_const] = after_launch->vars<3>();
333
334 auto [alloc_mem, host_buf] = buffer::op_alloc(Ro, So, Tp, post_mem)->projs<2>();
335 auto copy_back
336 = w.app(w.app(w.annex<gpu::buf_copy_to_host>(), {Ro, So, Tp}), {alloc_mem, post_global, out_dptr, host_buf});
337 auto [cb_mem, cb_global] = copy_back->projs<2>();
338
339 auto cur_global = cb_global;
340 for (auto dptr : dptrs)
341 cur_global = w.call(gpu::free::block, Defs{cur_global, dptr});
342 for (auto dptr : post_dptrs)
343 cur_global = w.call(gpu::free::block, Defs{cur_global, dptr});
344 cur_global = w.call(gpu::free::block, Defs{cur_global, out_dptr});
345
346 auto final_mem = w.app(w.annex<gpu::auto_deinit>(), Defs{cb_mem, cur_global, post_const});
347 after_launch->app(true, cont, Defs{final_mem, host_buf});
348 return after_launch;
349}
350
351} // namespace
352
354 DefSet seen;
355 auto has_gpu_init
356 = std::ranges::any_of(old_world().roots(), [&](auto def) { return contains_gpu_init(def, seen); });
357 if (has_gpu_init) {
358 log().w("not lowering any map-reduce operations to GPU: the program already contains an explicit `gpu.init`");
359 return;
360 }
361 Super::start();
362}
363
365 if (Axm::isa<btensor::map_reduce_post>(app)) return lower_map_reduce_post(app);
366 return Super::rewrite_imm_App(app);
367}
368
369const Def* LowerMapReduce::lower_map_reduce_post(const App* app) {
370 if (is_bootstrapping()) return Super::rewrite_imm_App(app);
371
372 auto& w = new_world();
373 auto c = rewrite(app->callee())->as<App>();
374
375 auto [nis_nps, meta, shapes, in_tys, comb_init, acc_out, accs_all] = c->uncurry_args<7>();
376 auto [nis, nps] = nis_nps->projs<2>([](auto d) { return Lit::isa(d); });
377 auto [To, Tp, Ro, Rn, sched_ty] = meta->projs<5>();
378 auto [So, Sr, sched] = shapes->projs<3>();
379 auto [Tis, Ris, Sis, Tps, Rps, Sps] = in_tys->projs<6>();
380 auto [comb, init, post] = comb_init->projs<3>();
381 auto [accs, post_accs] = accs_all->projs<2>();
382 auto result_ty = rewrite(app->type());
383
384 auto ro_l = Lit::isa<nat_t>(Ro);
385 auto rn_l = Lit::isa<nat_t>(Rn);
386 if (!nis || !nps || !ro_l || !rn_l || *rn_l < *ro_l) {
387 log().w("{} doesn't have lowering-time known rank counts (nis/nps/Ro/Rn)", app);
388 return Super::rewrite_imm_App(app);
389 }
390 auto nis_n = *nis;
391 auto nps_n = *nps;
392 auto ro = *ro_l;
393 auto rr = *rn_l - *ro_l;
394
395 Vector<nat_t> out_dims(ro);
396 nat_t out_total = 1;
397 for (nat_t d = 0; d != ro; ++d) {
398 auto l = Lit::isa<nat_t>(Sr->proj(ro + rr, d));
399 if (!l) {
400 log().w("{} doesn't have a lowering-time known output (grid) shape", app);
401 return Super::rewrite_imm_App(app);
402 }
403 out_dims[d] = *l;
404 out_total *= *l;
405 }
406 if (out_total == 0) {
407 log().w("{} has a zero-sized output, skipping GPU lowering", app);
408 return Super::rewrite_imm_App(app);
409 }
410
411 auto comb_lam = comb->isa_mut<Lam>();
412 auto post_lam = post->isa_mut<Lam>();
413 if (!comb_lam || !post_lam) {
414 log().w("{} doesn't have a lowering-time known combiner/epilogue", app);
415 return Super::rewrite_imm_App(app);
416 }
417
418 auto mem_ty = w.call<mem::M>(0);
419 auto rewritten_arg = rewrite(app->arg());
420 auto [_, rewritten_inputs, rewritten_post_ins] = rewritten_arg->projs<3>();
421 auto fun = w.mut_fun(w.sigma({mem_ty, rewritten_inputs->type(), rewritten_post_ins->type()}), result_ty)
422 ->set("mapReduceAffGpu");
423 auto call = w.app(cps::op_cps2ds_dep(fun), rewritten_arg);
424 auto [fun_mem, new_inputs, new_post_ins] = fun->var(0_n)->projs<3>();
425 auto cont = fun->var(1);
426
427 auto [h_mem, h_global, h_const] = w.app(w.annex<gpu::auto_init>(), fun_mem)->projs<3>();
428
429 auto in_desc = extract_input_desc(nis_n, Ris, Sis, Tis, accs);
430 auto inputs = alloc_copy_inputs(w, h_mem, h_global, in_desc.rs, in_desc.ss, in_desc.ts, new_inputs);
431
432 auto post_desc = extract_input_desc(nps_n, Rps, Sps, Tps, post_accs);
433 auto post_inputs
434 = alloc_copy_inputs(w, inputs.mem, inputs.global, post_desc.rs, post_desc.ss, post_desc.ts, new_post_ins);
435
436 auto [out_global, out_dptr] = alloc_output(w, post_inputs.global, Tp, So, ro);
437
438 auto global_comb = rebuild_lam_global_mem(comb_lam, To, w.sym("combGlobal"));
439 auto global_post = rebuild_lam_global_mem(post_lam, Tp, w.sym("postGlobal"));
440
441 auto grid = grid_layout(out_dims);
442
443 auto kernel = build_kernel(w, Ro, rr, out_dims, Sr, So, Mapped{in_desc.rs, in_desc.ss, inputs.dptrs, in_desc.accs},
444 To, acc_out, init, global_comb,
445 Mapped{post_desc.rs, post_desc.ss, post_inputs.dptrs, post_desc.accs}, global_post, Tp,
446 out_dptr, grid);
447
448 DefVec kernel_arg_tys(nis_n + nps_n + 1);
449 for (nat_t i = 0; i != nis_n; ++i)
450 kernel_arg_tys[i] = inputs.dptrs[i]->type();
451 for (nat_t j = 0; j != nps_n; ++j)
452 kernel_arg_tys[nis_n + j] = post_inputs.dptrs[j]->type();
453 kernel_arg_tys[nis_n + nps_n] = out_dptr->type();
454
455 auto launch = w.app(w.annex<gpu::launch>(), Defs{w.lit_nat(nis_n + nps_n + 1), w.tuple(kernel_arg_tys)});
456 launch = w.app(launch, Defs{w.lit_nat(grid.n_groups), w.lit_nat(grid.n_items), w.annex<gpu::default_stream>(),
457 w.lit_ff(), w.tuple()});
458 launch = w.app(launch, kernel);
459
460 DefVec kernel_args = inputs.dptrs;
461 kernel_args.insert(kernel_args.end(), post_inputs.dptrs.begin(), post_inputs.dptrs.end());
462 kernel_args.push_back(out_dptr);
463 launch = w.app(launch, kernel_args);
464
465 auto after_launch = build_teardown(w, Ro, So, Tp, inputs.dptrs, post_inputs.dptrs, out_dptr, cont);
466 auto launch_call = w.app(launch, Defs{w.tuple({post_inputs.mem, out_global, h_const}), after_launch});
467 fun->set(true, launch_call);
468
469 return call;
470}
471
472} // namespace mim::plug::gpu::phase
const Def * callee() const
Definition lam.h:275
const Def * arg() const
Definition lam.h:284
static auto isa(const Def *def)
Definition axm.h:112
Base class for all Defs.
Definition def.h:273
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 std::optional< T > isa(const Def *def)
Definition def.h:937
const fe::Log & log() const
Definition phase.h:79
bool is_bootstrapping() const
Returns whether we are currently bootstrapping (rewriting annexes).
Definition phase.h:403
World & new_world()
Create new Defs into this.
Definition phase.h:452
void start() override
RWBase::start() and then swaps the two worlds.
Definition phase.cpp:193
World & old_world()
Get old Defs from here.
Definition phase.h:451
virtual const Def * rewrite(const Def *)
Definition rewrite.cpp:55
void start() final
Skips the whole phase if the program already contains an explicit gpu.init.
const Def * rewrite_imm_App(const App *) final
const Def * op_alloc(const Def *r, const Def *s, const Def *T, const Def *mem)
buffer.alloc (r, s, T) mem ↦ [mem.M 0, buffer.Buf (r, s, T)].
Definition buffer.h:16
@ none
Wrap around.
Definition core.h:16
const Def * op_cps2ds_dep(const Def *k)
Definition cps.h:16
const Def * op_lea(const Def *ptr, const Def *index)
Definition mem.h:112
u64 nat_t
Definition types.h:37
fe::View< const Def * > Defs
Definition def.h:91
fe::Vector< const Def * > DefVec
Definition def.h:93
GIDSet< const Def * > DefSet
Definition def.h:89
@ Pi
Definition def.h:122
@ Lam
Definition def.h:122