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 <deque>
4#include <format>
5#include <fstream>
6#include <iterator>
7#include <optional>
8#include <ostream>
9#include <string>
10
11#include <absl/container/btree_set.h>
12
13#include <mim/driver.h>
14
15#include <mim/be/emitter.h>
16
17#include <mim/plug/clos/clos.h>
18#include <mim/plug/math/math.h>
19#include <mim/plug/mem/mem.h>
20#include <mim/plug/vec/vec.h>
21
22// Lessons learned:
23// * **Always** follow all ops - even if you actually want to ignore one.
24// Otherwise, you might end up with an incorrect schedule.
25// This was the case for an Extract of type Mem.
26// While we want to ignore the value obtained from that, since there is no Mem value in LLVM,
27// we still want to **first** recursively emit code for its operands and **then** ignore the Extract itself.
28// * i1 has a different meaning in LLVM then in Mim:
29// * Mim: {0, 1} = i1
30// * LLVM: {0, -1} = i1
31// This is a problem when, e.g., using an index of type i1 as LLVM thinks like this:
32// getelementptr ..., i1 1 == getelementptr .., i1 -1
33namespace mim {
34
35class World;
36
37namespace plug::ll {
38
39namespace math = mim::plug::math;
40namespace mem = mim::plug::mem;
41
42namespace detail {
43inline const char* math_suffix(const Def* type) {
44 if (auto w = math::isa_f(type)) {
45 switch (*w) {
46 case 32: return "f";
47 case 64: return "";
48 }
49 }
50 fe::throwf("unsupported floating point type '{}'", type);
51}
52
53inline const char* llvm_suffix(const Def* type) {
54 if (auto w = math::isa_f(type)) {
55 switch (*w) {
56 case 16: return ".f16";
57 case 32: return ".f32";
58 case 64: return ".f64";
59 }
60 }
61 fe::throwf("unsupported floating point type '{}'", type);
62}
63
64// [%mem.M 0, T] => T
65// TODO there may be more instances where we have to deal with this trickery
66inline const Def* isa_mem_sigma_2(const Def* type) {
67 if (auto sigma = type->isa<Sigma>())
68 if (sigma->num_ops() == 2 && Axm::isa<mem::M>(sigma->op(0))) return sigma->op(1);
69 return {};
70}
71} // namespace detail
72
73struct BB {
74 BB() = default;
75 BB(const BB&) = delete;
76 BB(BB&& other) noexcept = default;
77 BB& operator=(BB other) noexcept { return swap(*this, other), *this; }
78
79 std::deque<std::ostringstream>& head() { return parts[0]; }
80 std::deque<std::ostringstream>& body() { return parts[1]; }
81 std::deque<std::ostringstream>& tail() { return parts[2]; }
82
83 template<class... Args>
84 inline std::string assign(std::string_view name, std::format_string<Args...> s, Args&&... args) {
85 auto& os = body().emplace_back();
86 std::print(os, "{} = ", name);
87 std::print(os, s, std::forward<Args>(args)...);
88 return std::string(name);
89 }
90
91 template<class... Args>
92 inline void tail(std::format_string<Args...> s, Args&&... args) {
93 std::print(tail().emplace_back(), s, std::forward<Args>(args)...);
94 }
95
96 friend inline void swap(BB& a, BB& b) noexcept {
97 using std::swap;
98 swap(a.phis, b.phis);
99 swap(a.parts, b.parts);
100 }
101
103 std::array<std::deque<std::ostringstream>, 3> parts;
104};
105
106class Emitter;
107
108/// The heavy, target-independent emitter methods are compiled once into `libmim_ll` (see ll.cpp).
109/// `libmim_ll_nvptx` reaches them through these `extern "C"` shims instead of recompiling the bodies;
110/// Emitter caches the pointers via `GET_FUN_PTR` in its constructor.
111extern "C" {
112MIM_EXPORT void mim_ll_convert(Emitter&, const Def*, bool simd, std::string& res);
115MIM_EXPORT void mim_ll_emit_bb(Emitter&, BB&, const Def*, std::string& res);
116}
117
118class Emitter : public mim::Emitter<std::string, std::string, BB, Emitter> {
119public:
121
122 Emitter(World& world, std::string name, std::ostream& ostream)
123 : Super(world, name, ostream) {
124 auto& driver = world.driver();
125 // Ensure libmim_ll is loaded so the shims below resolve (e.g. when a derived backend like
126 // ll_nvptx uses us). Loading merely registers %ll.emit; it does not run it.
127 if (auto ll = driver.sym("ll"); !driver.is_loaded(ll)) driver.load(ll);
128 convert_ = driver.GET_FUN_PTR("ll", mim_ll_convert);
129 finalize_ = driver.GET_FUN_PTR("ll", mim_ll_finalize);
130 emit_epilogue_ = driver.GET_FUN_PTR("ll", mim_ll_emit_epilogue);
131 emit_bb_ = driver.GET_FUN_PTR("ll", mim_ll_emit_bb);
132 }
133
134 bool is_valid(std::string_view s) { return !s.empty(); }
135 void start() override;
136 void emit_imported(Lam*);
137 virtual std::string prepare();
138
139 // Thin forwarders into `libmim_ll`; the real bodies (`*_impl`) live in ll.cpp.
140 virtual void emit_epilogue(Lam* lam) { emit_epilogue_(*this, lam); }
141 void finalize() { finalize_(*this); }
142 std::string emit_bb(BB& bb, const Def* def) {
143 std::string res;
144 emit_bb_(*this, bb, def, res);
145 return res;
146 }
147
148 virtual inline std::optional<std::string> isa_targetspecific_intrinsic(BB&, const Def*) { return std::nullopt; }
149
150 template<class... Args>
151 void declare(std::format_string<Args...> s, Args&&... args) {
152 std::ostringstream decl;
153 decl << "declare ";
154 std::print(decl, s, std::forward<Args>(args)...);
155 decls_.emplace(decl.str());
156 }
157
158 /// How the C runtime wrappers (compiled to a `<name>.ll` via `add_mim_runtime`) reach the output.
159 enum class Rt {
160 embed, ///< Splice the wrapper IR into the emitted module so it is self-contained.
161 ext, ///< Only `declare` the wrappers; the runtime is linked in externally.
162 };
163
164 void rt_mode(Rt rt) { rt_ = rt; }
165 /// Provides the textual LLVM IR of the runtime module to splice in `Rt::embed` mode.
166 void rt_module(std::string ll) { rt_module_ = std::move(ll); }
167
168 /// Locates the runtime module `rt/<filename>` (produced by `add_mim_runtime`) in the driver's
169 /// search paths, reads it, and stores it for `Rt::embed` splicing.
170 /// Backends share this instead of duplicating the lookup: `ll` loads `ll_rt.ll`, `ll_nvptx`
171 /// loads `ll_nvptx_rt.ll`, and so on — one merged module per plugin.
172 /// @returns whether the module was found.
173 bool load_rt_module(std::string_view filename);
174
175 /// Declares a runtime wrapper @p sig (implemented in a C runtime, see `add_mim_runtime`) and
176 /// records that the runtime is required by this module.
177 /// In `Rt::ext` mode the declaration is emitted like any other `declare`.
178 /// In `Rt::embed` mode the wrapper's *definition* is spliced into the output, so emitting a
179 /// `declare` as well would be a redefinition — hence it is suppressed here.
180 template<class... Args>
181 void declare_rt(std::format_string<Args...> sig, Args&&... args) {
182 rt_used_ = true;
183 if (rt_ == Rt::ext) declare(sig, std::forward<Args>(args)...);
184 }
185
186protected:
187 std::string id(const Def*, bool force_bb = false) const;
188 virtual std::string convert(const Def* type, bool simd = true) {
189 std::string res;
190 convert_(*this, type, simd, res);
191 return res;
192 }
193 std::string convert_ret_pi(const Pi*);
194
195 /// Registers @p arg as an incoming phi value for @p phi in @p callee, coming from predecessor @p pred.
196 void emit_phi(Lam* callee, const Def* phi, std::string arg, Lam* pred) {
197 lam2bb_[callee].phis[phi].emplace_back(std::move(arg), id(pred, true));
198 locals_[phi] = id(phi);
199 }
200
201 /// Wires all non-`%mem.M` arguments of @p app into @p callee's phis, coming from predecessor @p pred.
202 void emit_phi_args(Lam* callee, const App* app, Lam* pred) {
203 size_t n = callee->num_tvars();
204 for (size_t i = 0; i != n; ++i)
205 if (auto arg = emit_unsafe(app->arg(n, i)); !arg.empty()) {
206 auto phi = callee->var(n, i);
207 if (Axm::isa<mem::M>(phi->type())) continue;
208 emit_phi(callee, phi, std::move(arg), pred);
209 }
210 }
211
212 /// Emits the storage backing a `%mem.slot` of type @p pointee and yields the pointer value.
213 /// The generic backend allocates on the stack; targets may override (e.g. a global in a specific address space).
214 /// Kept inline on purpose so `Emitter` retains no vtable key function (else its vtable would live in a single
215 /// module and break derived backends loaded from a separate plugin).
216 virtual std::string emit_slot(BB& bb, const App* app, const Def* pointee, const Def* /*addr_space*/) {
217 auto v_ptr = "%" + app->unique_name() + ".slot";
218 std::print(bb.body().emplace_back(), "{} = alloca {}", v_ptr, convert(pointee, false));
219 return v_ptr;
220 }
221
222 absl::btree_set<std::string> decls_;
223 std::ostringstream type_decls_;
224 std::ostringstream vars_decls_;
225 std::ostringstream func_decls_;
226 std::ostringstream func_impls_;
228
230 bool rt_used_ = false;
231 std::string rt_module_;
232
233private:
234 // Real implementations; defined in ll.cpp and exported via the `mim_ll_*` shims above.
235 std::string convert_impl(const Def*, bool simd);
236 void finalize_impl();
237 void emit_epilogue_impl(Lam*);
238 std::string emit_bb_impl(BB&, const Def*);
239
240 decltype(&mim_ll_convert) convert_ = nullptr;
241 decltype(&mim_ll_finalize) finalize_ = nullptr;
242 decltype(&mim_ll_emit_epilogue) emit_epilogue_ = nullptr;
243 decltype(&mim_ll_emit_bb) emit_bb_ = nullptr;
244
245 friend void mim_ll_convert(Emitter&, const Def*, bool, std::string&);
246 friend void mim_ll_finalize(Emitter&);
247 friend void mim_ll_emit_epilogue(Emitter&, Lam*);
248 friend void mim_ll_emit_bb(Emitter&, BB&, const Def*, std::string&);
249};
250
251/*
252 * convert
253 */
254
255inline static std::optional<std::pair<nat_t, const Def*>> is_simd(const Def* type) {
256 if (auto arr = type->isa<Arr>()) {
257 if (auto l = Lit::isa(arr->arity())) {
258 if (arr->body()->isa<Nat>() || Idx::isa(arr->body()) || Axm::isa<math::F>(arr->body()))
259 return std::pair{*l, arr->body()};
260 }
261 }
262 return {};
263}
264
265inline static std::optional<std::pair<nat_t, const Def*>> is_simd_aggregate(Defs types) {
266 if (std::ranges::all_of(types, [&](auto i) { return i == types[0]; })) {
267 if (types[0]->isa<Nat>() || Idx::isa(types[0]) || Axm::isa<math::F>(types[0]))
268 return std::pair{types.size(), types[0]};
269 }
270
271 return {};
272}
273
274inline static const Def* find_common_simd_src(const App* app) {
275 const Def* common_src = nullptr;
276 size_t lane = 0;
277 for (auto arg : app->args()) {
278 if (Axm::isa<mem::M>(arg->type())) continue;
279 auto extract = arg->isa<Extract>();
280 if (!extract || !is_simd(extract->tuple()->type())) return nullptr;
281 // Only devectorized args - lane i in position i - may forward the whole vector;
282 // anything else (e.g. a Select with a non-literal index) must stay scalar.
283 if (auto index = Lit::isa(extract->index()); !index || *index != lane++) return nullptr;
284 if (!common_src)
285 common_src = extract->tuple();
286 else if (common_src != extract->tuple())
287 return nullptr;
288 }
289 if (common_src) {
290 auto simd = is_simd(common_src->type());
291 if (!simd || simd->first != lane) return nullptr;
292 }
293 return common_src;
294}
295
296inline std::string Emitter::id(const Def* def, bool force_bb /*= false*/) const {
297 if (auto global = def->isa<Global>()) return "@" + global->unique_name();
298
299 if (auto lam = def->isa_mut<Lam>(); lam && !force_bb) {
300 if (lam->type()->ret_pi()) {
301 if (lam->is_external() || !lam->is_set())
302 return std::string("@") + lam->sym().str(); // TODO or use is_internal or sth like that?
303 return std::string("@") + lam->unique_name();
304 }
305 }
306
307 return std::string("%") + def->unique_name();
308}
309
310inline std::string Emitter::convert_ret_pi(const Pi* pi) {
311 auto dom = mem::strip_mem_ty(pi->dom());
312 if (dom == world().sigma()) return "void";
313 return convert(dom);
314}
315
316/*
317 * emit
318 */
319
320inline void Emitter::start() {
321 Super::start();
322
323 // Splice the runtime wrapper module first (it carries the module's target triple/datalayout).
324 if (rt_used_ && rt_ == Rt::embed) {
325 if (rt_module_.empty())
326 fe::throwf("ll backend: `-X ll:rt=embed` needs the runtime module `mim_rt.ll`, but it "
327 "was not found (build with clang / `MIM_BUILD_LL_RUNTIME=ON`, or use `-X ll:rt=extern`)");
328 ostream() << rt_module_ << '\n';
329 }
330
331 ostream() << type_decls_.str() << '\n';
332 for (auto&& decl : decls_)
333 ostream() << decl << '\n';
334 ostream() << func_decls_.str() << '\n';
335 ostream() << vars_decls_.str() << '\n';
336 ostream() << func_impls_.str() << '\n';
337}
338
339inline bool Emitter::load_rt_module(std::string_view filename) {
340 for (const auto& dir : world().driver().search_paths()) {
341 auto path = dir / "rt" / std::string(filename);
342 std::error_code ec;
343 if (!std::filesystem::is_regular_file(path, ec) || ec) continue;
344 if (auto ifs = std::ifstream(path)) {
345 world().DLOG("ll backend: loaded runtime module `{}`", path.string());
346 rt_module_.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
347 return true;
348 }
349 }
350 return false;
351}
352
353inline void Emitter::emit_imported(Lam* lam) {
354 // TODO merge with declare method
355 std::print(func_decls_, "declare {} {}(", convert_ret_pi(lam->type()->ret_pi()), id(lam));
356
357 auto doms = lam->doms();
358 for (auto sep = ""; auto dom : doms.view().rsubspan(1)) {
359 if (Axm::isa<mem::M>(dom)) continue;
360 std::print(func_decls_, "{}{}", sep, convert(dom));
361 sep = ", ";
362 }
363
364 std::print(func_decls_, ")\n");
365}
366
367inline std::string Emitter::prepare() {
368 auto internal = root()->is_external() ? "" : "internal ";
369 auto ret_t = convert_ret_pi(root()->type()->ret_pi());
370 std::print(func_impls_, "define {} {} {}(", internal, ret_t, id(root()));
371
372 auto vars = root()->vars();
373 for (auto sep = ""; auto var : vars.view().rsubspan(1)) {
374 if (Axm::isa<mem::M>(var->type())) continue;
375 if (auto sigma = var->type()->isa<Sigma>(); sigma && sigma->num_ops() == 0) continue;
376 if (auto arr = var->type()->isa<Arr>())
377 if (is_simd(arr->body())) convert(arr->body()); // pre-add input vector to cache
378 auto name = id(var);
379 locals_[var] = name;
380 std::print(func_impls_, "{}{} {}", sep, convert(var->type()), name);
381 sep = ", ";
382 }
383
384 std::print(func_impls_, ") {{\n");
385 return root()->unique_name();
386}
387
388} // namespace plug::ll
389} // namespace mim
const Def * arg() const
Definition lam.h:285
A (possibly paramterized) Array.
Definition tuple.h:121
static auto isa(const Def *def)
Definition axm.h:107
Lam * root() const
Definition phase.h:483
Base class for all Defs.
Definition def.h:261
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 * op(size_t i) const noexcept
Definition def.h:320
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:441
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
nat_t num_tvars() noexcept
Definition def.h:441
bool is_external() const noexcept
Definition def.h:500
auto vars(F f) noexcept
Definition def.h:441
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:626
constexpr size_t num_ops() const noexcept
Definition def.h:321
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:210
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:658
A function.
Definition lam.h:110
const Pi * type() const
Definition lam.h:130
static std::optional< T > isa(const Def *def)
Definition def.h:878
Driver & driver()
Definition phase.h:78
std::string_view name() const
Definition phase.h:80
virtual void start()=0
Actual entry.
const Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Definition phase.cpp:23
World & world()
Definition phase.h:77
A dependent function type.
Definition lam.h:14
const Def * dom() const
Definition lam.h:35
const Pi * ret_pi() const
Yields the last Pi::dom, if Pi::isa_basicblock.
Definition lam.cpp:13
A dependent tuple type.
Definition tuple.h:22
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:36
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:202
void rt_module(std::string ll)
Provides the textual LLVM IR of the runtime module to splice in Rt::embed mode.
Definition ll.h:166
virtual std::string prepare()
Definition ll.h:367
Emitter(World &world, std::string name, std::ostream &ostream)
Definition ll.h:122
virtual std::optional< std::string > isa_targetspecific_intrinsic(BB &, const Def *)
Definition ll.h:148
void emit_imported(Lam *)
Definition ll.h:353
absl::btree_set< std::string > decls_
Definition ll.h:222
bool is_valid(std::string_view s)
Definition ll.h:134
void rt_mode(Rt rt)
Definition ll.h:164
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:1093
std::string convert_ret_pi(const Pi *)
Definition ll.h:310
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:196
std::string rt_module_
Definition ll.h:231
void declare(std::format_string< Args... > s, Args &&... args)
Definition ll.h:151
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:339
std::ostringstream func_decls_
Definition ll.h:225
std::string id(const Def *, bool force_bb=false) const
Definition ll.h:296
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:181
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:216
std::ostringstream vars_decls_
Definition ll.h:224
std::ostringstream func_impls_
Definition ll.h:226
Rt
How the C runtime wrappers (compiled to a <name>.ll via add_mim_runtime) reach the output.
Definition ll.h:159
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
Definition ll.h:160
@ ext
Only declare the wrappers; the runtime is linked in externally.
Definition ll.h:161
mim::Emitter< std::string, std::string, BB, Emitter > Super
Definition ll.h:120
void start() override
Actual entry.
Definition ll.h:320
virtual void emit_epilogue(Lam *lam)
Definition ll.h:140
friend void mim_ll_finalize(Emitter &)
Definition ll.cpp:1096
virtual std::string convert(const Def *type, bool simd=true)
Definition ll.h:188
std::string emit_bb(BB &bb, const Def *def)
Definition ll.h:142
std::ostringstream type_decls_
Definition ll.h:223
LamMap< const Def * > simd_phi_
Definition ll.h:227
friend void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &)
Definition ll.cpp:1098
friend void mim_ll_emit_epilogue(Emitter &, Lam *)
Definition ll.cpp:1097
#define MIM_EXPORT
Definition config.h:19
The ll Plugin
Definition ll.h:37
static std::optional< std::pair< nat_t, const Def * > > is_simd(const Def *type)
Definition ll.h:255
void mim_ll_finalize(Emitter &)
Definition ll.cpp:1096
void mim_ll_emit_epilogue(Emitter &, Lam *)
Definition ll.cpp:1097
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:1093
static const Def * find_common_simd_src(const App *app)
Definition ll.h:274
static std::optional< std::pair< nat_t, const Def * > > is_simd_aggregate(Defs types)
Definition ll.h:265
void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &res)
Definition ll.cpp:1098
The math Plugin
Definition math.h:8
std::optional< nat_t > isa_f(const Def *def)
Definition math.h:77
The mem Plugin
Definition mem.h:11
const Def * strip_mem_ty(const Def *def)
Removes recusively all occurences of mem from a type (sigma).
Definition mem.h:58
Definition ast.h:14
View< const Def * > Defs
Definition def.h:78
GIDMap< const Def *, To > DefMap
Definition def.h:75
GIDMap< Lam *, To > LamMap
Definition lam.h:219
std::deque< std::ostringstream > & tail()
Definition ll.h:81
std::deque< std::ostringstream > & body()
Definition ll.h:80
DefMap< std::deque< std::pair< std::string, std::string > > > phis
Definition ll.h:102
BB & operator=(BB other) noexcept
Definition ll.h:77
BB(BB &&other) noexcept=default
std::array< std::deque< std::ostringstream >, 3 > parts
Definition ll.h:103
friend void swap(BB &a, BB &b) noexcept
Definition ll.h:96
std::deque< std::ostringstream > & head()
Definition ll.h:79
void tail(std::format_string< Args... > s, Args &&... args)
Definition ll.h:92
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)
Definition ll.h:84
BB(const BB &)=delete