MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
ll.h
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <deque>
5#include <format>
6#include <fstream>
7#include <iterator>
8#include <optional>
9#include <ostream>
10#include <set>
11#include <string>
12
13#include <fe/term.h>
14
15#include <mim/driver.h>
16#include <mim/phase.h>
17#include <mim/schedule.h>
18
19#include <mim/plug/clos/clos.h>
20#include <mim/plug/math/math.h>
21#include <mim/plug/mem/mem.h>
22#include <mim/plug/vec/vec.h>
23
24#include "mim/plug/ll/autogen.h"
25
26// Lessons learned:
27// * **Always** follow all ops - even if you actually want to ignore one.
28// Otherwise, you might end up with an incorrect schedule.
29// This was the case for an Extract of type Mem.
30// While we want to ignore the value obtained from that, since there is no Mem value in LLVM,
31// we still want to **first** recursively emit code for its operands and **then** ignore the Extract itself.
32// * i1 has a different meaning in LLVM then in Mim:
33// * Mim: {0, 1} = i1
34// * LLVM: {0, -1} = i1
35// This is a problem when, e.g., using an index of type i1 as LLVM thinks like this:
36// getelementptr ..., i1 1 == getelementptr .., i1 -1
37namespace mim {
38
39class World;
40
41namespace plug::ll {
42
43/// Prefix for this backend's diagnostics; concatenate it with the format literal.
44/// @note Not usable with the Def::expect family: those take the *inner* "what was expected" description.
45#define MIM_LL_BE "ll backend: "
46
47namespace math = mim::plug::math;
48namespace mem = mim::plug::mem;
49
50namespace detail {
51inline const char* math_suffix(const Def* type) {
52 if (auto w = math::isa_f(type)) {
53 switch (*w) {
54 case 32: return "f";
55 case 64: return "";
56 }
57 }
58 type->blame(MIM_LL_BE "unsupported floating-point type `{}`", type).bail();
59}
60
61inline const char* llvm_suffix(const Def* type) {
62 if (auto w = math::isa_f(type)) {
63 switch (*w) {
64 case 16: return ".f16";
65 case 32: return ".f32";
66 case 64: return ".f64";
67 }
68 }
69 type->blame(MIM_LL_BE "unsupported floating-point type `{}`", type).bail();
70}
71
72// [mem.M 0, T] => T
73// TODO there may be more instances where we have to deal with this trickery
74inline const Def* isa_mem_sigma_2(const Def* type) {
75 if (auto sigma = type->isa<Sigma>())
76 if (sigma->num_ops() == 2 && Axm::isa<mem::M>(sigma->op(0))) return sigma->op(1);
77 return {};
78}
79} // namespace detail
80
81struct BB {
82 BB() = default;
83 BB(const BB&) = delete;
84 BB(BB&& other) noexcept = default;
85 BB& operator=(BB other) noexcept { return swap(*this, other), *this; }
86
87 std::deque<std::ostringstream>& head() { return parts[0]; }
88 std::deque<std::ostringstream>& body() { return parts[1]; }
89 std::deque<std::ostringstream>& tail() { return parts[2]; }
90
91 template<class... Args>
92 inline std::string assign(std::string_view name, std::format_string<Args...> s, Args&&... args) {
93 auto& os = body().emplace_back();
94 std::print(os, "{} = ", name);
95 std::print(os, s, std::forward<Args>(args)...);
96 return std::string(name);
97 }
98
99 template<class... Args>
100 inline void tail(std::format_string<Args...> s, Args&&... args) {
101 std::print(tail().emplace_back(), s, std::forward<Args>(args)...);
102 }
103
104 friend inline void swap(BB& a, BB& b) noexcept {
105 using std::swap;
106 swap(a.phis, b.phis);
107 swap(a.parts, b.parts);
108 }
109
111 std::array<std::deque<std::ostringstream>, 3> parts;
112};
113
114class Emitter;
115
116/// The heavy, target-independent emitter methods are compiled once into `libmim_ll` (see ll.cpp).
117/// `libmim_nvptx` reaches them through these `extern "C"` shims instead of recompiling the bodies;
118/// Emitter caches the pointers via `GET_FUN_PTR` in its constructor.
119extern "C" {
120MIM_EXPORT void mim_ll_convert(Emitter&, const Def*, bool simd, std::string& res);
123MIM_EXPORT void mim_ll_emit_bb(Emitter&, BB&, const Def*, std::string& res);
124}
125
126class Emitter : public NestPhase<Lam> {
127public:
129
130 Emitter(World& world, std::string name, std::ostream& ostream)
131 : Super(world, std::move(name), false)
132 , ostream_(ostream) {
133 auto& driver = world.driver();
134 // Ensure libmim_ll is loaded so the shims below resolve (e.g. when a derived backend like
135 // nvptx uses us). Loading merely registers ll.emit; it does not run it.
136 if (!driver.is_loaded("ll")) driver.load("ll");
137 convert_ = driver.GET_FUN_PTR("ll", mim_ll_convert);
138 finalize_ = driver.GET_FUN_PTR("ll", mim_ll_finalize);
139 emit_epilogue_ = driver.GET_FUN_PTR("ll", mim_ll_emit_epilogue);
140 emit_bb_ = driver.GET_FUN_PTR("ll", mim_ll_emit_bb);
141 }
142
143 fe::Tab tab = fe::Tab::spaces();
144
145 void start() override;
146 void visit(const Nest&) override;
147 void emit_imported(Lam*);
148 virtual std::string prepare();
149
150 // Thin forwarders into `libmim_ll`; the real bodies (`*_impl`) live in ll.cpp.
151 virtual void emit_epilogue(Lam* lam) { emit_epilogue_(*this, lam); }
152 void finalize() { finalize_(*this); }
153 std::string emit_bb(BB& bb, const Def* def) {
154 std::string res;
155 emit_bb_(*this, bb, def, res);
156 return res;
157 }
158
159 virtual inline std::optional<std::string> isa_targetspecific_intrinsic(BB&, const Def*) { return std::nullopt; }
160
161 template<class... Args>
162 void declare(std::format_string<Args...> s, Args&&... args) {
163 decls_.emplace("declare " + std::format(s, std::forward<Args>(args)...));
164 }
165
166 /// How the C runtime wrappers (compiled to a `<name>.ll` via `add_mim_runtime`) reach the output.
167 enum class Rt {
168 embed, ///< Splice the wrapper IR into the emitted module so it is self-contained.
169 ext, ///< Only `declare` the wrappers; the runtime is linked in externally.
170 };
171
172 void rt_mode(Rt rt) { rt_ = rt; }
173
174 /// Locates the runtime module `rt/<filename>` (produced by `add_mim_runtime`) in the driver's
175 /// search paths, reads it, and stores it for `Rt::embed` splicing.
176 /// Backends share this instead of duplicating the lookup: `ll` loads `ll_rt.ll`, `nvptx`
177 /// loads `nvptx_rt.ll`, and so on — one merged module per plugin.
178 /// @returns whether the module was found.
179 bool load_rt_module(std::string_view filename);
180
181 /// Declares a runtime wrapper @p sig (implemented in a C runtime, see `add_mim_runtime`) and
182 /// records that the runtime is required by this module.
183 /// In `Rt::ext` mode the declaration is emitted like any other `declare`.
184 /// In `Rt::embed` mode the wrapper's *definition* is spliced into the output, so emitting a
185 /// `declare` as well would be a redefinition — hence it is suppressed here.
186 template<class... Args>
187 void declare_rt(std::format_string<Args...> sig, Args&&... args) {
188 rt_used_ = true;
189 if (rt_ == Rt::ext) declare(sig, std::forward<Args>(args)...);
190 }
191
192protected:
193 std::ostream& ostream() const { return ostream_; }
194
195 /// Recursively emits code.
196 /// `mem`-typed @p def%s yield an empty string, which this variant asserts against.
197 std::string emit(const Def* def) {
198 auto res = emit_unsafe(def);
199 assert(!res.empty());
200 return res;
201 }
202
203 /// As above but yielding an empty string is permitted.
204 std::string emit_unsafe(const Def* def) {
205 if (auto s = fe::lookup(locals_, def)) return *s;
206
207 auto place = scheduler_.smart(curr_lam_, def);
208 auto& bb = lam2bb_[place->mut()->as<Lam>()];
209 auto val = emit_bb(bb, def);
210 return locals_[def] = val;
211 }
212
213 /// The Scheduler::schedule of the function currently being emitted; see Emitter::visit.
214 const Scheduler::Schedule& schedule() const { return schedule_; }
215
216 std::string id(const Def*, bool force_bb = false) const;
217
218 /// Converts @p type to its LLVM representation as a value (@p simd) or in memory (`!simd`).
219 /// Only values use LLVM vectors; memory keeps the plain array layout C expects.
220 virtual std::string convert(const Def* type, bool simd = true) {
221 std::string res;
222 convert_(*this, type, simd, res);
223 return res;
224 }
225 std::string convert_ret_pi(const Pi*);
226
227 /// Lane count and element type if a value of @p type is an LLVM vector.
228 std::optional<std::pair<nat_t, const Def*>> vectorize(const Def* type) const;
229 /// Whether @p type's value representation contains an LLVM vector and hence differs from its memory one.
230 bool has_vec(const Def* type) const;
231
232 /// Registers @p arg as an incoming phi value for @p phi in @p callee, coming from predecessor @p pred.
233 void emit_phi(Lam* callee, const Def* phi, std::string arg, Lam* pred) {
234 // Inserting here would invalidate the `BB&` our caller holds.
235 auto i = lam2bb_.find(callee);
236 if (i == lam2bb_.end())
237 callee->blame(MIM_LL_BE "`{}` jumps to `{}`, which is not a basic block of `{}`", pred, callee, root())
238 .bail();
239 i->second.phis[phi].emplace_back(std::move(arg), id(pred, true));
240 locals_[phi] = id(phi);
241 }
242
243 /// Wires all non-`mem.M` arguments of @p app into @p callee's phis, coming from predecessor @p pred.
244 void emit_phi_args(Lam* callee, const App* app, Lam* pred) {
245 size_t n = callee->num_tvars();
246 for (size_t i = 0; i != n; ++i)
247 if (auto arg = emit_unsafe(app->arg(n, i)); !arg.empty()) {
248 auto phi = callee->var(n, i);
249 if (Axm::isa<mem::M>(phi->type())) continue;
250 emit_phi(callee, phi, std::move(arg), pred);
251 }
252 }
253
254 /// Emits the storage backing a `mem.slot` of type @p pointee and yields the pointer value.
255 /// The generic backend allocates on the stack; targets may override (e.g. a global in a specific address space).
256 /// Kept inline on purpose so `Emitter` retains no vtable key function (else its vtable would live in a single
257 /// module and break derived backends loaded from a separate plugin).
258 virtual std::string emit_slot(BB& bb, const App* app, const Def* pointee, const Def* /*addr_space*/) {
259 auto v_ptr = "%" + app->unique_name() + ".slot";
260 std::print(bb.body().emplace_back(), "{} = alloca {}", v_ptr, convert(pointee, false));
261 return v_ptr;
262 }
263
264 Lam* curr_lam_ = nullptr;
265 std::ostream& ostream_;
269 std::array<DefMap<std::string>, 2> types_; ///< Indexed by the `simd` flag of Emitter::convert.
271
272 std::set<std::string> decls_;
273 std::ostringstream type_decls_;
274 std::ostringstream vars_decls_;
275 std::ostringstream func_decls_;
276 std::ostringstream func_impls_;
278
279 /// Loop-metadata node id per `ll.vec`/`ll.novec`-annotated loop header, and whether it may vectorize;
280 /// `LoopMdBase` and `LoopMdBase + 1` hold the shared enable/disable hints, the per-loop nodes follow,
281 /// clear of the ids the embedded runtime module brings along.
282 static constexpr u64 LoopMdBase = 1000;
284
285 bool simd_ = true;
287 bool rt_used_ = false;
288 std::string rt_module_;
289
290private:
291 // Real implementations; defined in ll.cpp and exported via the `mim_ll_*` shims above.
292 std::string convert_impl(const Def*, bool simd);
293 void finalize_impl();
294 void emit_epilogue_impl(Lam*);
295 std::string emit_bb_impl(BB&, const Def*);
296
297 // Case groups of emit_bb_impl, split so one recursion level only pays the frame of the group it hits.
298 MIM_NOINLINE std::string emit_lit(const Def*);
299 MIM_NOINLINE std::string emit_tuple(BB&, const std::string& name, const Def* tuple);
300 MIM_NOINLINE std::pair<std::string, std::string> emit_gep_index(BB&, const std::string& name, const Def* index);
301 MIM_NOINLINE std::optional<std::string> emit_builtin(BB&, const std::string& name, const Def*);
302 MIM_NOINLINE std::optional<std::string> emit_core(BB&, const std::string& name, const Def*);
303 MIM_NOINLINE std::optional<std::string> emit_mem(BB&, const std::string& name, const Def*);
304 MIM_NOINLINE std::optional<std::string> emit_math(BB&, const std::string& name, const Def*);
305 MIM_NOINLINE std::optional<std::string> emit_vec(BB&, const std::string& name, const Def*);
306
307 /// An `extractelement`/`insertelement` lane index; LLVM wants a plain `i32`, not the `Idx` carrier.
308 std::string emit_simd_index(BB&, const std::string& name, const Def* index);
309 /// Converts the value @p v of @p type into its value (@p simd) or memory (`!simd`) representation.
310 std::string repack(BB&, const std::string& name, const Def* type, const std::string& v, bool simd);
311 const Def* find_common_simd_src(const App*) const;
312
313 decltype(&mim_ll_convert) convert_ = nullptr;
314 decltype(&mim_ll_finalize) finalize_ = nullptr;
315 decltype(&mim_ll_emit_epilogue) emit_epilogue_ = nullptr;
316 decltype(&mim_ll_emit_bb) emit_bb_ = nullptr;
317
318 friend void mim_ll_convert(Emitter&, const Def*, bool, std::string&);
319 friend void mim_ll_finalize(Emitter&);
320 friend void mim_ll_emit_epilogue(Emitter&, Lam*);
321 friend void mim_ll_emit_bb(Emitter&, BB&, const Def*, std::string&);
322};
323
324/// Parses `-X <backend>:rt=extern|embed`; Emitter::Rt::embed is the default.
325inline Emitter::Rt arg_rt(fe::View<std::string> args) {
326 return arg_value(args, "rt") == "extern" ? Emitter::Rt::ext : Emitter::Rt::embed;
327}
328
329/*
330 * convert
331 */
332
333inline static std::optional<std::pair<nat_t, const Def*>> is_simd(const Def* type) {
334 if (auto arr = type->isa<Arr>(); arr && !arr->shape().is_fused()) {
335 // LLVM rounds a vector's store size up to a power of two, so only then does `<n x T>` occupy the
336 // `n * sizeof(T)` bytes that `mem`'s size arithmetic assumes.
337 if (auto l = Lit::isa(arr->arity()); l && std::has_single_bit(*l)) {
338 if (arr->body()->isa<Nat>() || Idx::isa(arr->body()) || Axm::isa<math::F>(arr->body()))
339 return std::pair{*l, arr->body()};
340 }
341 }
342 return {};
343}
344
345inline std::optional<std::pair<nat_t, const Def*>> Emitter::vectorize(const Def* type) const {
346 return simd_ ? is_simd(type) : std::nullopt;
347}
348
349inline bool Emitter::has_vec(const Def* type) const {
350 if (!simd_) return false;
351 if (is_simd(type)) return true;
352 if (auto arr = type->isa<Arr>()) return has_vec(arr->elem());
353 if (auto sigma = type->isa<Sigma>())
354 return std::ranges::any_of(sigma->ops(), [this](auto t) { return has_vec(t); });
355 return false;
356}
357
358inline const Def* Emitter::find_common_simd_src(const App* app) const {
359 const Def* common_src = nullptr;
360 size_t lane = 0;
361 for (auto arg : app->args()) {
362 if (Axm::isa<mem::M>(arg->type())) continue;
363 auto extract = arg->isa<Extract>();
364 if (!extract || !vectorize(extract->tuple()->type())) return nullptr;
365 // Only devectorized args - lane i in position i - may forward the whole vector;
366 // anything else (e.g. a Select with a non-literal index) must stay scalar.
367 if (auto index = Lit::isa(extract->index()); !index || *index != lane++) return nullptr;
368 if (!common_src)
369 common_src = extract->tuple();
370 else if (common_src != extract->tuple())
371 return nullptr;
372 }
373 if (common_src && vectorize(common_src->type())->first != lane) return nullptr;
374 return common_src;
375}
376
377inline std::string Emitter::id(const Def* def, bool force_bb /*= false*/) const {
378 if (auto lam = def->isa_mut<Lam>(); lam && !force_bb) {
379 if (lam->type()->ret_pi()) {
380 if (lam->is_external() || !lam->is_set())
381 return std::string("@") + lam->sym().str(); // TODO or use is_internal or sth like that?
382 return std::string("@") + lam->unique_name();
383 }
384 }
385
386 return std::string("%") + def->unique_name();
387}
388
389inline std::string Emitter::convert_ret_pi(const Pi* pi) {
390 auto dom = mem::strip_mem_ty(pi->dom());
391 if (dom == world().sigma()) return "void";
392 return convert(dom);
393}
394
395/*
396 * emit
397 */
398
399inline void Emitter::start() {
400 Super::start();
401
402 auto sep = "";
403 auto section = [this, &sep](std::string_view s) {
404 while (s.ends_with('\n'))
405 s.remove_suffix(1);
406 if (s.empty()) return;
407 std::println(ostream(), "{}{}", sep, s);
408 sep = "\n";
409 };
410
411 // Splice the runtime wrapper module first (it carries the module's target triple/datalayout).
412 if (rt_used_ && rt_ == Rt::embed) {
413 if (rt_module_.empty())
414 fe::throwf(MIM_LL_BE
415 "`-X ll:rt=embed` needs the runtime module `mim_rt.ll`, but it "
416 "was not found (build with clang / `MIM_BUILD_LL_RUNTIME=ON`, or use `-X ll:rt=extern`)");
417 section(rt_module_);
418 }
419
420 section(type_decls_.str());
421
422 std::ostringstream decls;
423 for (auto&& decl : decls_)
424 std::println(decls, "{}", decl);
425 section(decls.str());
426
427 section(func_decls_.str());
428 section(vars_decls_.str());
429 section(func_impls_.str());
430
431 // One distinct `!llvm.loop` node per hinted loop header, sharing the vectorize-enable or -disable hint.
432 if (!loop_md_.empty()) {
433 std::ostringstream md_decls;
434 std::println(md_decls, "!{} = !{{!\"llvm.loop.vectorize.enable\", i1 true}}", LoopMdBase);
435 std::println(md_decls, "!{} = !{{!\"llvm.loop.vectorize.enable\", i1 false}}", LoopMdBase + 1);
436 for (const auto& [_, md] : loop_md_)
437 std::println(md_decls, "!{} = distinct !{{!{}, !{}}}", md.first, md.first,
438 md.second ? LoopMdBase : LoopMdBase + 1);
439 section(md_decls.str());
440 }
441}
442
443inline void Emitter::visit(const Nest& nest) {
444 if (!root()->is_set()) return emit_imported(root());
445
446 schedule_ = Scheduler::schedule(nest); // cached; finalize needs the very same schedule
447 const auto& muts = schedule_;
448
449 // make sure that we don't need to rehash later on
450 for (auto mut : muts)
451 if (auto lam = mut->isa<Lam>()) lam2bb_.try_emplace(lam, BB());
452 auto old_size = lam2bb_.size();
453
454 if (!root()->ret_var())
455 root()->blame(MIM_LL_BE "top-level function is not a continuation with a return continuation").bail();
456
457 prepare();
458
459 Scheduler new_scheduler(nest);
460 swap(scheduler_, new_scheduler);
461
462 for (auto mut : muts) {
463 if (auto lam = mut->isa<Lam>()) {
464 curr_lam_ = lam;
465 if (lam != root() && !Lam::isa_basicblock(lam))
466 lam->blame(MIM_LL_BE "neither the entry nor a basic block of `{}`; it needs a phase that "
467 "removes higher-order functions first",
468 root())
469 .bail();
470 emit_epilogue(lam);
471 }
472 }
473
474 finalize();
475 locals_.clear();
476 assert_unused(lam2bb_.size() == old_size && "really make sure we didn't trigger a rehash");
477 // A BB never crosses a function boundary: Nest::contains is `def->has_free_vars_in(vars())`,
478 // so a *closed* Lam is never a member of another Lam's Nest - and it cannot belong to two Nests either,
479 // since a Lam free in the vars of two closed Lams would make the outer one open.
480 // Every `BB&` handed out by emit_unsafe died with the calls above, so clearing here is safe.
481 // Without it, finalize re-walks the BBs of all previously emitted functions - O(n²) in program size.
482 lam2bb_.clear();
483}
484
485inline bool Emitter::load_rt_module(std::string_view filename) {
486 for (const auto& dir : world().driver().rt_paths()) {
487 auto path = dir / std::string(filename);
488 std::error_code ec;
489 if (!std::filesystem::is_regular_file(path, ec) || ec) continue;
490 if (auto ifs = std::ifstream(path)) {
491 world().log().d("ll backend: load runtime module `{}`", path.string());
492 rt_module_.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
493 return true;
494 }
495 }
496 return false;
497}
498
499inline void Emitter::emit_imported(Lam* lam) {
500 // TODO merge with declare method
501 std::print(func_decls_, "declare {} {}(", convert_ret_pi(lam->type()->ret_pi()), id(lam));
502
503 auto doms = lam->doms();
504 for (auto sep = ""; auto dom : doms.view().rsubspan(1)) {
505 if (Axm::isa<mem::M>(dom)) continue;
506 std::print(func_decls_, "{}{}", sep, convert(dom));
507 sep = ", ";
508 }
509
510 std::println(func_decls_, ")");
511}
512
513inline std::string Emitter::prepare() {
514 auto internal = root()->is_external() ? "" : "internal ";
515 auto ret_t = convert_ret_pi(root()->type()->ret_pi());
516 std::print(func_impls_, "define {}{} {}(", internal, ret_t, id(root()));
517
518 auto vars = root()->vars();
519 for (auto sep = ""; auto var : vars.view().rsubspan(1)) {
520 if (Axm::isa<mem::M>(var->type())) continue;
521 if (auto sigma = var->type()->isa<Sigma>(); sigma && sigma->num_ops() == 0) continue;
522 auto name = id(var);
523 locals_[var] = name;
524 std::print(func_impls_, "{}{} {}", sep, convert(var->type()), name);
525 sep = ", ";
526 }
527
528 std::println(func_impls_, ") {{");
529 return root()->unique_name();
530}
531
532} // namespace plug::ll
533} // namespace mim
auto args(Projector auto f) const noexcept
Definition lam.h:252
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
M * root() const
Definition phase.h:530
Base class for all Defs.
Definition def.h:289
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:588
const Def * op(size_t i) const noexcept
Definition def.h:377
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:492
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
nat_t num_tvars() noexcept
Definition def.h:492
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.
Definition def.h:333
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:634
constexpr size_t num_ops() const noexcept
Definition def.h:378
void load(std::string_view name, Loc loc={})
Definition driver.cpp:150
bool is_loaded(std::string_view name) const
Definition driver.h:193
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:360
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:670
A function.
Definition lam.h:91
const Pi * type() const
Definition lam.h:112
static const Lam * isa_basicblock(const Def *d)
Definition lam.h:127
auto doms(Projector auto f) const noexcept
Definition lam.h:117
static std::optional< T > isa(const Def *def)
Definition def.h:976
const Nest & nest() const
Definition phase.h:548
NestPhase(World &world, std::string name, bool elide_empty, bool schedule=false)
Definition phase.h:543
Builds a nesting tree for all mutables/binders.
Definition nest.h:25
Driver & driver()
Definition phase.h:59
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
std::string_view name() const
Definition phase.h:61
virtual void start()=0
Actual entry.
World & world()
Definition phase.h:58
A dependent function type.
Definition lam.h:14
const Def * dom() const
Definition lam.h:38
const Pi * ret_pi() const
Yields the last Pi::dom, if Pi::isa_basicblock.
Definition lam.cpp:13
const Nest::Node * smart(Def *curr, const Def *)
Definition schedule.cpp:76
fe::Vector< Def * > Schedule
Definition schedule.h:78
static Schedule schedule(const Nest &)
Definition schedule.cpp:103
Shape shape() const
Definition tuple.h:279
bool is_fused() const
Does this span several axes? Also true for a dynamic rank.
Definition tuple.h:211
A dependent tuple type.
Definition tuple.h:39
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:43
Driver & driver()
Definition world.h:107
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:224
void emit_phi_args(Lam *callee, const App *app, Lam *pred)
Wires all non-mem.M arguments of app into callee's phis, coming from predecessor pred.
Definition ll.h:244
virtual std::string prepare()
Definition ll.h:513
Emitter(World &world, std::string name, std::ostream &ostream)
Definition ll.h:130
virtual std::optional< std::string > isa_targetspecific_intrinsic(BB &, const Def *)
Definition ll.h:159
void emit_imported(Lam *)
Definition ll.h:499
Scheduler scheduler_
Definition ll.h:266
std::string emit(const Def *def)
Recursively emits code.
Definition ll.h:197
const Scheduler::Schedule & schedule() const
The Scheduler::schedule of the function currently being emitted; see Emitter::visit.
Definition ll.h:214
Scheduler::Schedule schedule_
Definition ll.h:267
std::array< DefMap< std::string >, 2 > types_
Indexed by the simd flag of Emitter::convert.
Definition ll.h:269
std::ostream & ostream() const
Definition ll.h:193
static constexpr u64 LoopMdBase
Loop-metadata node id per ll.vec/ll.novec-annotated loop header, and whether it may vectorize; LoopMd...
Definition ll.h:282
NestPhase< Lam > Super
Definition ll.h:128
void rt_mode(Rt rt)
Definition ll.h:172
friend void mim_ll_convert(Emitter &, const Def *, bool, std::string &)
The heavy, target-independent emitter methods are compiled once into libmim_ll (see ll....
Definition ll.cpp:1158
std::ostream & ostream_
Definition ll.h:265
std::string convert_ret_pi(const Pi *)
Definition ll.h:389
void emit_phi(Lam *callee, const Def *phi, std::string arg, Lam *pred)
Registers arg as an incoming phi value for phi in callee, coming from predecessor pred.
Definition ll.h:233
std::string rt_module_
Definition ll.h:288
bool has_vec(const Def *type) const
Whether type's value representation contains an LLVM vector and hence differs from its memory one.
Definition ll.h:349
void declare(std::format_string< Args... > s, Args &&... args)
Definition ll.h:162
bool load_rt_module(std::string_view filename)
Locates the runtime module rt/<filename> (produced by add_mim_runtime) in the driver's search paths,...
Definition ll.h:485
std::ostringstream func_decls_
Definition ll.h:275
LamMap< BB > lam2bb_
Definition ll.h:270
std::string id(const Def *, bool force_bb=false) const
Definition ll.h:377
void declare_rt(std::format_string< Args... > sig, Args &&... args)
Declares a runtime wrapper sig (implemented in a C runtime, see add_mim_runtime) and records that the...
Definition ll.h:187
virtual std::string emit_slot(BB &bb, const App *app, const Def *pointee, const Def *)
Emits the storage backing a mem.slot of type pointee and yields the pointer value.
Definition ll.h:258
std::ostringstream vars_decls_
Definition ll.h:274
std::ostringstream func_impls_
Definition ll.h:276
Rt
How the C runtime wrappers (compiled to a <name>.ll via add_mim_runtime) reach the output.
Definition ll.h:167
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
Definition ll.h:168
@ ext
Only declare the wrappers; the runtime is linked in externally.
Definition ll.h:169
void start() override
Actual entry.
Definition ll.h:399
std::string emit_unsafe(const Def *def)
As above but yielding an empty string is permitted.
Definition ll.h:204
DefMap< std::string > locals_
Definition ll.h:268
LamMap< std::pair< u64, bool > > loop_md_
Definition ll.h:283
virtual void emit_epilogue(Lam *lam)
Definition ll.h:151
friend void mim_ll_finalize(Emitter &)
Definition ll.cpp:1161
virtual std::string convert(const Def *type, bool simd=true)
Converts type to its LLVM representation as a value (simd) or in memory (!simd).
Definition ll.h:220
std::string emit_bb(BB &bb, const Def *def)
Definition ll.h:153
std::ostringstream type_decls_
Definition ll.h:273
LamMap< const Def * > simd_phi_
Definition ll.h:277
std::optional< std::pair< nat_t, const Def * > > vectorize(const Def *type) const
Lane count and element type if a value of type is an LLVM vector.
Definition ll.h:345
std::set< std::string > decls_
Definition ll.h:272
friend void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &)
Definition ll.cpp:1163
friend void mim_ll_emit_epilogue(Emitter &, Lam *)
Definition ll.cpp:1162
void visit(const Nest &) override
Definition ll.h:443
#define MIM_NOINLINE
Definition config.h:23
#define MIM_EXPORT
Definition config.h:21
#define MIM_LL_BE
Prefix for this backend's diagnostics; concatenate it with the format literal.
Definition ll.h:45
The ll Plugin
Definition ll.h:41
static std::optional< std::pair< nat_t, const Def * > > is_simd(const Def *type)
Definition ll.h:333
Emitter::Rt arg_rt(fe::View< std::string > args)
Parses -X <backend>:rt=extern|embed; Emitter::Rt::embed is the default.
Definition ll.h:325
void mim_ll_finalize(Emitter &)
Definition ll.cpp:1161
void mim_ll_emit_epilogue(Emitter &, Lam *)
Definition ll.cpp:1162
void mim_ll_convert(Emitter &, const Def *, bool simd, std::string &res)
The heavy, target-independent emitter methods are compiled once into libmim_ll (see ll....
Definition ll.cpp:1158
void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &res)
Definition ll.cpp:1163
The math Plugin
Definition math.h:8
std::optional< nat_t > isa_f(const Def *def)
Definition math.h:55
The mem Plugin
const Def * strip_mem_ty(const Def *def)
Removes recusively all occurences of mem from a type (sigma).
Definition mem.h:62
The tuple Plugin
Definition ast.h:16
GIDMap< const Def *, To > DefMap
Definition def.h:93
GIDMap< Lam *, To > LamMap
Definition lam.h:210
uint64_t u64
Definition types.h:27
std::optional< std::string_view > arg_value(fe::View< std::string > args, Keys... keys)
Definition plugin.h:64
std::deque< std::ostringstream > & tail()
Definition ll.h:89
std::deque< std::ostringstream > & body()
Definition ll.h:88
DefMap< std::deque< std::pair< std::string, std::string > > > phis
Definition ll.h:110
BB & operator=(BB other) noexcept
Definition ll.h:85
BB(BB &&other) noexcept=default
std::array< std::deque< std::ostringstream >, 3 > parts
Definition ll.h:111
friend void swap(BB &a, BB &b) noexcept
Definition ll.h:104
std::deque< std::ostringstream > & head()
Definition ll.h:87
void tail(std::format_string< Args... > s, Args &&... args)
Definition ll.h:100
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)
Definition ll.h:92
BB(const BB &)=delete