MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
ll.cpp
Go to the documentation of this file.
1#include "mim/plug/ll/ll.h"
2
3#include <ranges>
4#include <string>
5
6#include <mim/config.h>
7#include <mim/phase.h>
8#include <mim/plugin.h>
9
10#include <mim/plug/core/core.h>
11
12namespace mim::plug::ll {
13
14using namespace std::string_literals;
15
16namespace clos = mim::plug::clos;
17namespace core = mim::plug::core;
18namespace vecp = mim::plug::vec;
19
20/// Pipeline phase for `ll.emit`.
21/// Writes the LLVM IR of the fully lowered world to `<world>.ll` (or `a.ll` if the world is unnamed).
22/// The output path can be overridden via [`-X ll:o=<file>`](@ref xarg_ll) or `-X ll:output=<file>`.
23/// `<file>` may be `-` for stdout.
24/// The runtime-wrapper linking mode is selected via `-X ll:rt=embed` (default) or `-X ll:rt=extern`.
25class Emit : public Phase {
26public:
27 using Phase::Phase;
28
29 void start() override {
30 world().log().d("ll backend args: {}", fe::Join(args()));
31
32 auto name = world().name() ? world().name().str() : "a"s;
33 auto path = name + ".ll"s;
34 if (auto o = arg_value(args(), "o", "output")) path = *o;
35 auto rt = arg_rt(args());
36
37 auto out = Out(path);
38 auto emitter = Emitter(world(), "llvm_emitter", *out.os());
39 emitter.rt_mode(rt);
40 if (rt == Emitter::Rt::embed) emitter.load_rt_module("ll_rt.ll");
41 emitter.run();
42 }
43};
44
45/*
46 * Heavy, target-independent emitter methods.
47 * These live here (compiled once into libmim_ll) instead of the header so that
48 * libmim_nvptx does not recompile them; nvptx reaches them via the
49 * `extern "C"` shims below, looked up with GET_FUN_PTR (see ll.h).
50 */
51
52/// An `Idx n` whose `n` is only known at runtime is bounded by a `Nat`, hence an `i64`.
53static nat_t idx_bitwidth(const Def* type) { return Idx::size2bitwidth(Idx::isa(type)).value_or(64); }
54
55static std::string fmath_flags(math::Mode mode) {
56 if (mode == math::Mode::fast) return " fast";
57 std::string flags;
58 // clang-format off
59 if (fe::has_flag(mode, math::Mode::nnan )) flags += " nnan";
60 if (fe::has_flag(mode, math::Mode::ninf )) flags += " ninf";
61 if (fe::has_flag(mode, math::Mode::nsz )) flags += " nsz";
62 if (fe::has_flag(mode, math::Mode::arcp )) flags += " arcp";
63 if (fe::has_flag(mode, math::Mode::contract)) flags += " contract";
64 if (fe::has_flag(mode, math::Mode::afn )) flags += " afn";
65 if (fe::has_flag(mode, math::Mode::reassoc )) flags += " reassoc";
66 // clang-format on
67 return flags;
68}
69
70static const char* ncmp_pred(core::ncmp id) {
71 switch (id) {
72 // clang-format off
73 case core::ncmp::e: return "eq";
74 case core::ncmp::ne: return "ne";
75 case core::ncmp::g: return "ugt";
76 case core::ncmp::ge: return "uge";
77 case core::ncmp::l: return "ult";
78 case core::ncmp::le: return "ule";
79 // clang-format on
80 default: return nullptr;
81 }
82}
83
84static const char* icmp_pred(core::icmp id) {
85 switch (id) {
86 // clang-format off
87 case core::icmp::e: return "eq";
88 case core::icmp::ne: return "ne";
89 case core::icmp::sg: return "sgt";
90 case core::icmp::sge: return "sge";
91 case core::icmp::sl: return "slt";
92 case core::icmp::sle: return "sle";
93 case core::icmp::ug: return "ugt";
94 case core::icmp::uge: return "uge";
95 case core::icmp::ul: return "ult";
96 case core::icmp::ule: return "ule";
97 // clang-format on
98 default: return nullptr;
99 }
100}
101
102static const char* fcmp_pred(math::cmp id) {
103 switch (id) {
104 // clang-format off
105 case math::cmp:: e: return "oeq";
106 case math::cmp:: l: return "olt";
107 case math::cmp:: le: return "ole";
108 case math::cmp:: g: return "ogt";
109 case math::cmp:: ge: return "oge";
110 case math::cmp:: ne: return "one";
111 case math::cmp:: o: return "ord";
112 case math::cmp:: u: return "uno";
113 case math::cmp:: ue: return "ueq";
114 case math::cmp:: ul: return "ult";
115 case math::cmp::ule: return "ule";
116 case math::cmp:: ug: return "ugt";
117 case math::cmp::uge: return "uge";
118 case math::cmp::une: return "une";
119 // clang-format on
120 default: return nullptr;
121 }
122}
123
124std::string Emitter::convert_impl(const Def* type, bool simd) {
125 if (auto s = fe::lookup(types_[simd], type)) return *s;
126 if (simd && !has_vec(type)) return types_[simd][type] = convert_impl(type, false);
127
128 if (Axm::isa<mem::M>(type)) type->blame(MIM_LL_BE "cannot convert a `mem.M` type").bail();
129 std::ostringstream s;
130 std::string name;
131
132 if (type->isa<Nat>()) {
133 return types_[simd][type] = "i64";
134 } else if (Idx::isa(type)) {
135 // `Idx 1` is zero bits wide information-theoretically, but LLVM has no i0: clamp to i1
136 // (its only value is 0, so any i1 zext/trunc of it stays correct).
137 auto w = idx_bitwidth(type);
138 return types_[simd][type] = "i" + std::to_string(std::max<nat_t>(1, w));
139 } else if (auto w = math::isa_f(type)) {
140 switch (*w) {
141 case 16: return types_[simd][type] = "half";
142 case 32: return types_[simd][type] = "float";
143 case 64: return types_[simd][type] = "double";
144 default: type->blame(MIM_LL_BE "unsupported floating-point width {}", *w).bail();
145 }
146 } else if (auto ptr = Axm::isa<mem::Ptr>(type)) {
147 auto [pointee, addr_space] = ptr->args<2>();
148 std::print(s, "{} addrspace({})*", convert(pointee, false), addr_space);
149 } else if (auto arr = type->isa<Arr>()) {
150 if (auto se = simd ? vectorize(arr) : std::nullopt) {
151 auto [size, elem] = *se;
152 std::print(s, "<{} x {}>", size, convert(elem));
153 } else {
154 u64 size = 0;
155 if (auto arity = Lit::isa(arr->arity())) size = *arity;
156 // One LLVM array level per axis, so a fused Arr nests: `«3, 4; i32»` is `[3 x [4 x i32]]`.
157 std::print(s, "[{} x {}]", size, convert(arr->elem(), simd));
158 }
159 } else if (auto pi = type->isa<Pi>()) {
160 if (!Pi::isa_returning(pi)) pi->blame(MIM_LL_BE "cannot convert the type of a basic block").bail();
161 std::print(s, "{} (", convert_ret_pi(pi->ret_pi()));
162
163 if (auto t = detail::isa_mem_sigma_2(pi->dom()))
164 std::print(s, "{}", convert(t));
165 else {
166 auto doms = pi->doms();
167 for (auto sep = ""; auto dom : doms.view().rsubspan(1)) {
168 if (Axm::isa<mem::M>(dom)) continue;
169 std::print(s, "{}{}", sep, convert(dom));
170 sep = ", ";
171 }
172 }
173 std::print(s, ")*");
174 } else if (auto t = detail::isa_mem_sigma_2(type)) {
175 return convert(t, simd);
176 } else if (auto sigma = type->isa<Sigma>()) {
177 if (sigma->isa_mut()) {
178 name = simd ? id(sigma) + ".simd" : id(sigma);
179 types_[simd][sigma] = name;
180 std::print(s, "{} = type", name);
181 }
182
183 std::print(s, "{{");
184 for (auto sep = ""; auto t : sigma->ops()) {
185 if (Axm::isa<mem::M>(t)) continue;
186 std::print(s, "{}{}", sep, convert(t, simd));
187 sep = ", ";
188 }
189 std::print(s, "}}");
190 } else {
191 type->blame(MIM_LL_BE "cannot convert this type to LLVM").bail();
192 }
193
194 if (name.empty()) return types_[simd][type] = s.str();
195
196 if (s.str().empty()) type->blame(MIM_LL_BE "empty type declaration").bail();
197 std::println(type_decls_, "{}", s.str());
198 return types_[simd][type] = name;
199}
200
201void Emitter::finalize_impl() {
202 for (auto& [lam, bb] : lam2bb_) {
203 for (const auto& [phi, args] : bb.phis) {
204 std::print(bb.head().emplace_back(), "{} = phi {} ", id(phi), convert(phi->type()));
205 for (auto sep = ""; const auto& [arg, pred] : args) {
206 std::print(bb.head().back(), "{}[ {}, {} ]", sep, arg, pred);
207 sep = ", ";
208 }
209 }
210 }
211
212 for (auto sep = ""; auto mut : schedule()) { // cached by Emitter::visit - recomputing it here doubled the work
213 if (auto lam = mut->isa_mut<Lam>()) {
214 if (!lam2bb_.contains(lam)) lam->blame(MIM_LL_BE "no basic block was emitted").bail();
215 auto& bb = lam2bb_[lam];
216 std::println(func_impls_, "{}{}:", sep, lam->unique_name());
217 sep = "\n";
218
219 ++tab;
220 for (const auto& part : bb.parts)
221 for (const auto& line : part)
222 std::println(func_impls_, "{}{}", tab, line.str());
223 --tab;
224 }
225 }
226
227 std::println(func_impls_, "}}\n"); // the blank line separates this function from the next
228}
229
230/*
231 Block type return
232BB:
233 Cn [M, a, A] → 2 phi
234 Cn «2;A» → 2 phi
235 Cn [M, «2;A»] → 1 phi
236Ret:
237 Cn[M,A,A] → 1 phi
238 Cn «2;A» → 1 phi
239 Cn[M, «2;A»] → 1 phi
240
241Fun:
242 Cn[A, A, Cn R] → 2 args + ret
243 Cn[«2; A», Cn R] → 1 args + ret
244 Cn[M, A, A, Cn R] → 2 args + ret
245 Cn[M, «2; A», Cn R] → 1 args + ret
246*/
247void Emitter::emit_epilogue_impl(Lam* lam) {
248 auto app = lam->body()->expect<App>("an application in tail position");
249 auto& bb = lam2bb_[lam];
250 // A target-specific intrinsic in tail position (e.g. gpu.launch) emits its own code and
251 // yields the continuation to branch to.
252 if (auto ret = isa_targetspecific_intrinsic(bb, app)) return bb.tail("br label {}", *ret);
253 if (app->callee() == root()->ret_var()) { // return
254 // Decide this before emitting the args: their lane Extracts would all be dead.
255 if (auto common_src = find_common_simd_src(app)) {
256 for (auto arg : app->args())
257 if (Axm::isa<mem::M>(arg->type())) emit_unsafe(arg);
258 return bb.tail("ret {} {}", convert(common_src->type()), emit(common_src));
259 }
260
261 fe::Vector<std::string> values;
262 DefVec types;
263 for (auto arg : app->args()) {
264 if (auto val = emit_unsafe(arg); !val.empty()) {
265 values.emplace_back(val);
266 types.emplace_back(arg->type());
267 }
268 }
269
270 switch (values.size()) {
271 case 0: return bb.tail("ret void");
272 case 1:
273 return Axm::isa<mem::M>(types[0]) ? bb.tail("ret void")
274 : bb.tail("ret {} {}", convert(types[0]), values[0]);
275 default: {
276 std::string type;
277 std::string prev;
278
279 if (auto se = vectorize(world().sigma(types))) {
280 auto [size, elem] = *se;
281 auto val_t = convert(elem);
282
283 type = std::format("<{} x {}>", size, val_t);
284 bool all_const
285 = std::ranges::all_of(values, [](const std::string& v) { return !v.starts_with('%'); });
286 if (all_const) {
287 for (auto val : values) {
288 if (prev.empty())
289 prev = "<";
290 else
291 prev += ", ";
292 prev += std::format("{} {}", val_t, val);
293 }
294 prev += ">";
295 } else {
296 prev = "undef";
297 for (size_t i = 0, n = values.size(); i != n; ++i) {
298 auto namei = id(lam, true) + ".ret_val." + std::to_string(i);
299 bb.tail("{} = insertelement {} {}, {} {}, i32 {}", namei, type, prev, val_t, values[i], i);
300 prev = namei;
301 }
302 }
303 } else {
304 prev = "undef";
305 type = convert(world().sigma(types));
306 for (size_t i = 0, n = values.size(); i != n; ++i) {
307 if (Axm::isa<mem::M>(types[i])) continue;
308 auto v_elem = values[i];
309 auto t_elem = convert(types[i]);
310 auto namei = id(lam, true) + ".ret_val." + std::to_string(i);
311 bb.tail("{} = insertvalue {} {}, {} {}, {}", namei, type, prev, t_elem, v_elem, i);
312 prev = namei;
313 }
314 }
315 bb.tail("ret {} {}", type, prev);
316 }
317 }
318
319 } else if (auto dispatch = Dispatch(app)) {
320 for (auto callee : dispatch.tuple()->projs([](const Def* def) { return def->isa_mut<Lam>(); }))
321 if (size_t n = callee->num_tvars(); n == 1 && vectorize(callee->var(n, 0)->type()))
322 emit_phi(callee, callee->var(n, 0), emit(app->arg(n, 0)), lam);
323 else
324 emit_phi_args(callee, app, lam);
325
326 auto v_index = emit(dispatch.index());
327 size_t n = dispatch.num_targets();
328 auto bbs = fe::Vector<std::string>(n);
329 for (size_t i = 0; i != n; ++i)
330 bbs[i] = emit(dispatch.target(i));
331
332 if (n == 2) return bb.tail("br i1 {}, label {}, label {}", v_index, bbs[1], bbs[0]);
333
334 auto t_index = convert(dispatch.index()->type());
335 bb.tail("switch {} {}, label {} [ ", t_index, v_index, bbs[0]);
336 for (size_t i = 1; i != n; ++i)
337 std::print(bb.tail().back(), "{} {}, label {} ", t_index, std::to_string(i), bbs[i]);
338 std::print(bb.tail().back(), "]");
339 } else if (app->callee()->isa<Bot>()) {
340 return bb.tail("ret ; bottom: unreachable");
341 } else if (auto callee = Lam::isa_mut_basicblock(app->callee())) { // ordinary jump
342
343 if (auto common_src = find_common_simd_src(app)) {
344 auto v_src = emit(common_src);
345 auto callee_var = callee->var();
346 auto key = simd_phi_.try_emplace(callee, callee_var).first->second;
347 emit_phi(callee, key, v_src, lam);
348 for (auto var : callee->vars())
349 locals_[var] = id(key);
350 locals_[callee_var] = id(key);
351 } else {
352 emit_phi_args(callee, app, lam);
353 }
354 // A loop header whose exit condition is wrapped in `ll.vec` or `ll.novec` (see affine's LowerFor)
355 // carries this loop's `!llvm.loop` vectorize hint on every branch into it; on the latch
356 // `ll.vec` lifts LLVM's tiny-trip-count bailout.
357 if (callee->is_set()) {
358 if (auto head = callee->body()->isa<App>()) {
359 if (auto dispatch = Dispatch(head)) {
360 auto vec = Axm::isa<ll::vec>(dispatch.index());
361 auto novec = Axm::isa<ll::novec>(dispatch.index());
362 if (vec || novec) {
363 auto [it, _] = loop_md_.emplace(callee, std::pair{LoopMdBase + 2 + loop_md_.size(), bool(vec)});
364 return bb.tail("br label {}, !llvm.loop !{}", id(callee), it->second.first);
365 }
366 }
367 }
368 }
369 return bb.tail("br label {}", id(callee));
370
371 } else if (auto longjmp = Axm::isa<clos::longjmp>(app)) {
372 declare("void @longjmp(i8*, i32) noreturn");
373
374 auto [mem, jbuf, tag] = app->args<3>();
375 emit_unsafe(mem);
376 auto v_jb = emit(jbuf);
377 auto v_tag = emit(tag);
378 bb.tail("call void @longjmp(i8* {}, i32 {})", v_jb, v_tag);
379 return bb.tail("unreachable");
380 } else if (auto mslot = Axm::isa<mem::mslot>(app)) {
381 // Continuation-based stack slot: allocate and jump to the passed continuation with the fresh pointer.
382 auto [Ta, rest] = mslot->uncurry_args<2>();
383 auto [pointee, addr_space] = Ta->projs<2>();
384 auto [msize, ret] = rest->projs<2>();
385 emit_unsafe(msize->proj(2, 0)); // mem
386 // TODO array with size
387 auto ret_lam = ret->expect_mut<Lam>("a mem.slot continuation");
388 auto ptr = ret_lam->var(2, 1);
389 auto v_ptr = emit_slot(bb, app, pointee, addr_space);
390 emit_phi(ret_lam, ptr, v_ptr, lam);
391 return bb.tail("br label {}", id(ret_lam));
392 } else if (Pi::isa_returning(app->callee_type())) { // function call
393 auto v_callee = emit(app->callee());
394
395 fe::Vector<std::string> args;
396 auto app_args = app->args();
397 for (auto arg : app_args.view().rsubspan(1))
398 if (auto v_arg = emit_unsafe(arg); !v_arg.empty()) args.emplace_back(convert(arg->type()) + " " + v_arg);
399
400 if (app->args().back()->isa<Bot>()) {
401 // TODO: Perhaps it'd be better to simply η-wrap this prior to the BE...
402 if (convert_ret_pi(app->callee_type()->ret_pi()) != "void")
403 app->blame(MIM_LL_BE "call with a ⊥ return continuation must return void").bail();
404 bb.tail("call void {}({})", v_callee, fe::Join(args));
405 return bb.tail("unreachable");
406 }
407
408 auto ret_lam = app->args().back()->expect_mut<Lam>("a return continuation");
409 size_t n = 0;
410 for (auto var : ret_lam->vars())
411 if (!Axm::isa<mem::M>(var->type())) ++n;
412
413 if (n == 0) {
414 bb.tail("call void {}({})", v_callee, fe::Join(args));
415 } else {
416 auto name = "%" + app->unique_name() + "ret";
417 auto t_ret = convert_ret_pi(ret_lam->type());
418 bb.tail("{} = call {} {}({})", name, t_ret, v_callee, fe::Join(args));
419 emit_phi(ret_lam, ret_lam->var(), name, lam);
420 }
421
422 return bb.tail("br label {}", id(ret_lam));
423 }
424}
425
426std::string Emitter::emit_tuple(BB& bb, const std::string& name, const Def* tuple) {
427 if (detail::isa_mem_sigma_2(tuple->type())) {
428 emit_unsafe(tuple->proj(2, 0));
429 return emit(tuple->proj(2, 1));
430 }
431
432 auto vec = vectorize(tuple->type()).has_value();
433 if (tuple->is_closed()) {
434 bool is_array = tuple->type()->isa<Arr>();
435 std::string s;
436 s += vec ? "<" : is_array ? "[" : "{";
437 auto sep = "";
438 for (size_t i = 0, n = tuple->num_projs(); i != n; ++i) {
439 auto e = tuple->proj(n, i);
440 if (auto v_elem = emit_unsafe(e); !v_elem.empty()) {
441 auto t_elem = convert(e->type());
442 s += sep + t_elem + " " + v_elem;
443 sep = ", ";
444 }
445 }
446
447 return s += vec ? ">" : is_array ? "]" : "}";
448 }
449
450 std::string prev = "undef";
451 auto t = convert(tuple->type());
452 for (size_t src = 0, dst = 0, n = tuple->num_projs(); src != n; ++src) {
453 auto e = tuple->proj(n, src);
454 if (auto elem = emit_unsafe(e); !elem.empty()) {
455 auto elem_t = convert(e->type());
456 // TODO: check dst vs src
457 auto namei = name + "." + std::to_string(dst);
458 if (vec)
459 prev = bb.assign(namei, "insertelement {} {}, {} {}, i32 {}", t, prev, elem_t, elem, dst);
460 else
461 prev = bb.assign(namei, "insertvalue {} {}, {} {}, {}", t, prev, elem_t, elem, dst);
462 dst++;
463 }
464 }
465 return prev;
466}
467
468std::string Emitter::repack(BB& bb, const std::string& name, const Def* type, const std::string& v, bool simd) {
469 if (!has_vec(type)) return v;
470
471 auto t_src = convert(type, !simd);
472 auto t_dst = convert(type, simd);
473 auto vec = vectorize(type).has_value();
474 auto src_vec = vec && !simd;
475 auto dst_vec = vec && simd;
476 auto n = Lit::expect(type->arity(), "a statically-sized aggregate");
477 std::string r = "undef";
478 for (u64 i = 0, j = 0; i != n; ++i) {
479 auto t_i = type->proj(n, i);
480 if (Axm::isa<mem::M>(t_i)) continue;
481 auto name_i = name + ".r" + std::to_string(j);
482 auto v_i = bb.assign(name_i, "{} {} {}, {}{}", src_vec ? "extractelement" : "extractvalue", t_src, v,
483 src_vec ? "i32 " : "", j);
484 v_i = repack(bb, name_i, t_i, v_i, simd);
485 r = bb.assign(name_i + ".r", "{} {} {}, {} {}, {}{}", dst_vec ? "insertelement" : "insertvalue", t_dst, r,
486 convert(t_i, simd), v_i, dst_vec ? "i32 " : "", j);
487 ++j;
488 }
489 return r;
490}
491
492std::pair<std::string, std::string> Emitter::emit_gep_index(BB& bb, const std::string& name, const Def* index) {
493 auto v_i = emit(index);
494 auto t_i = convert(index->type());
495
496 if (Idx::isa(index->type())) {
497 if (auto w = idx_bitwidth(index->type()); w < 64) {
498 v_i = bb.assign(name + ".zext",
499 "zext {} {} to i{} ; add one more bit for gep index as it is treated as signed value", t_i,
500 v_i, w + 1);
501 t_i = "i" + std::to_string(w + 1);
502 }
503 }
504
505 return std::pair(v_i, t_i);
506}
507
508std::string Emitter::emit_simd_index(BB& bb, const std::string& name, const Def* index) {
509 auto v_i = emit(index);
510 if (Lit::isa(index)) return v_i;
511
512 auto t_i = convert(index->type());
513 if (t_i == "i32") return v_i;
514 auto w = idx_bitwidth(index->type());
515 return bb.assign(name + ".idx", "{} {} {} to i32", w < 32 ? "zext" : "trunc", t_i, v_i);
516}
517
518std::string Emitter::emit_lit(const Def* def) {
519 if (auto lit = def->isa<Lit>()) {
520 if (lit->type()->isa<Nat>() || Idx::isa(lit->type())) {
521 return std::to_string(lit->get());
522 } else if (auto w = math::isa_f(lit->type())) {
523 u64 hex;
524
525 switch (*w) {
526 case 16: return std::format("0xH{:04x}", lit->get<u16>());
527 case 32: hex = std::bit_cast<u64>(f64(lit->get<f32>())); break;
528 case 64: hex = lit->get<u64>(); break;
529 default: def->blame(MIM_LL_BE "unsupported floating-point width {} for a literal", *w).bail();
530 }
531
532 return std::format("0x{:016x}", hex);
533 }
534 def->blame(MIM_LL_BE "cannot emit a literal of type `{}`", def->type()).bail();
535 }
536 std::unreachable();
537}
538
539std::optional<std::string> Emitter::emit_builtin(BB& bb, const std::string& name, const Def* def) {
540 if (def->isa<Var>()) {
541 if (vectorize(def->type())) return id(def);
542 if (mem::has_mem_ty(def->type())) return std::string();
543 return emit_tuple(bb, name, def);
544 }
545
546 if (def->isa<Lit>()) {
547 return emit_lit(def);
548 } else if (def->isa<Bot>()) {
549 return "undef";
550 } else if (auto top = def->isa<Top>()) {
551 if (Axm::isa<mem::M>(top->type())) return std::string();
552 // bail out to the dispatcher's error
553 } else if (auto tuple = def->isa<Tuple>()) {
554 return emit_tuple(bb, name, tuple);
555 } else if (auto pack = def->isa<Pack>()) {
556 if (auto lit = Lit::isa(pack->body()); lit && *lit == 0) return "zeroinitializer";
557 return emit_tuple(bb, name, pack);
558 } else if (auto sel = Select(def)) {
559 auto t = convert(sel.extract()->type());
560 auto [elem_a, elem_b] = sel.pair()->projs<2>([&](auto e) { return emit_unsafe(e); });
561 auto cond_t = convert(sel.cond()->type());
562 auto cond = emit(sel.cond());
563 return bb.assign(name, "select {} {}, {} {}, {} {}", cond_t, cond, t, elem_b, t, elem_a);
564 } else if (auto extract = def->isa<Extract>()) {
565 auto tuple = extract->tuple();
566 auto index = extract->index();
567 auto v_tup = emit_unsafe(tuple);
568
569 // this exact location is important: after emitting the tuple -> ordering of mem ops
570 // before emitting the index, as it might be a weird value for mem vars.
571 if (Axm::isa<mem::M>(extract->type())) return std::string();
572 if (auto sigma = extract->type()->isa<Sigma>(); sigma && sigma->num_ops() == 0) return std::string();
573
574 auto t_tup = convert(tuple->type());
575 if (vectorize(tuple->type()))
576 return bb.assign(name, "extractelement {} {}, i32 {}", t_tup, v_tup, emit_simd_index(bb, name, index));
577
578 if (auto li = Lit::isa(index)) {
579 if (detail::isa_mem_sigma_2(tuple->type())) return v_tup;
580 // Adjust index: convert() drops mem.M elements from sigmas,
581 // so subtract the number of mem elements preceding the index.
582 auto v_i = *li;
583 if (auto sigma = tuple->type()->isa<Sigma>())
584 for (u64 i = 0; i < *li; ++i)
585 if (Axm::isa<mem::M>(sigma->op(i))) --v_i;
586
587 return bb.assign(name, "extractvalue {} {}, {}", t_tup, v_tup, v_i);
588 }
589
590 auto t_elem = convert(extract->type());
591 auto [v_i, t_i] = emit_gep_index(bb, name, index);
592
593 std::print(lam2bb_[root()].body().emplace_front(),
594 "{}.alloca = alloca {} ; copy to alloca to emulate extract with store + gep + load", name, t_tup);
595 std::print(bb.body().emplace_back(), "store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup, name);
596 std::print(bb.body().emplace_back(), "{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}", name,
597 t_tup, t_tup, name, t_i, v_i);
598 return bb.assign(name, "load {}, {}* {}.gep", t_elem, t_elem, name);
599 } else if (auto insert = def->isa<Insert>()) {
600 if (Axm::isa<mem::M>(insert->tuple()->proj(insert->tuple()->num_projs(), 0)->type()))
601 insert->blame(MIM_LL_BE "cannot insert into a tuple with a `mem.M` element").bail();
602 auto t_tup = convert(insert->tuple()->type());
603 auto t_val = convert(insert->value()->type());
604 auto v_tup = emit(insert->tuple());
605 auto v_val = emit(insert->value());
606 if (vectorize(insert->tuple()->type()))
607 return bb.assign(name, "insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val,
608 emit_simd_index(bb, name, insert->index()));
609
610 if (auto idx = Lit::isa(insert->index()))
611 return bb.assign(name, "insertvalue {} {}, {} {}, {}", t_tup, v_tup, t_val, v_val, *idx);
612
613 auto t_elem = convert(insert->value()->type());
614 auto [v_i, t_i] = emit_gep_index(bb, name, insert->index());
615 std::print(lam2bb_[root()].body().emplace_front(),
616 "{}.alloca = alloca {} ; copy to alloca to emulate insert with store + gep + load", name, t_tup);
617 std::print(bb.body().emplace_back(), "store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup, name);
618 std::print(bb.body().emplace_back(), "{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}", name,
619 t_tup, t_tup, name, t_i, v_i);
620 std::print(bb.body().emplace_back(), "store {} {}, {}* {}.gep", t_val, v_val, t_val, name);
621 return bb.assign(name, "load {}, {}* {}.alloca", t_tup, t_tup, name);
622 }
623 return std::nullopt;
624}
625
626std::optional<std::string> Emitter::emit_core(BB& bb, const std::string& name, const Def* def) {
627 std::string op;
628 if (auto nat = Axm::isa<core::nat>(def)) {
629 auto [a, b] = nat->args<2>([this](auto def) { return emit(def); });
630
631 switch (nat.id()) {
632 case core::nat::add: return bb.assign(name, "add nsw nuw i64 {}, {}", a, b);
633 case core::nat::sub: {
634 // nat subtraction saturates at 0: cap result when b > a
635 auto ugt = bb.assign(name + ".ugt", "icmp ugt i64 {}, {}", b, a);
636 auto raw = bb.assign(name + ".raw", "sub i64 {}, {}", a, b);
637 return bb.assign(name, "select i1 {}, i64 0, i64 {}", ugt, raw);
638 }
639 case core::nat::mul: return bb.assign(name, "mul nsw nuw i64 {}, {}", a, b);
640 // core.nat.div/mod define division/modulo by zero as `a / 0 = 0` and `a % 0 = a`.
641 // replace a zero divisor by 1 to keep udiv/urem well-defined, then select the
642 // defined result for the zero case (0 for div, a for mod).
643 case core::nat::div: {
644 auto bz = bb.assign(name + ".bz", "icmp eq i64 {}, 0", b);
645 auto bsaf = bb.assign(name + ".bsafe", "select i1 {}, i64 1, i64 {}", bz, b);
646 auto q = bb.assign(name + ".q", "udiv i64 {}, {}", a, bsaf);
647 return bb.assign(name, "select i1 {}, i64 0, i64 {}", bz, q);
648 }
649 case core::nat::rem: {
650 auto bz = bb.assign(name + ".bz", "icmp eq i64 {}, 0", b);
651 auto bsaf = bb.assign(name + ".bsafe", "select i1 {}, i64 1, i64 {}", bz, b);
652 auto r = bb.assign(name + ".r", "urem i64 {}, {}", a, bsaf);
653 return bb.assign(name, "select i1 {}, i64 {}, i64 {}", bz, a, r);
654 }
655 }
656 } else if (auto ncmp = Axm::isa<core::ncmp>(def)) {
657 auto [a, b] = ncmp->args<2>([this](auto def) { return emit(def); });
658 auto pred = ncmp_pred(ncmp.id());
659 if (!pred) def->blame(MIM_LL_BE "unhandled `core.ncmp` id").bail();
660 op = "icmp "s + pred;
661
662 return bb.assign(name, "{} i64 {}, {}", op, a, b);
663 } else if (auto idx = Axm::isa<core::idx>(def)) {
664 auto x = emit(idx->arg());
665 auto s = idx_bitwidth(idx->type());
666 auto t = convert(idx->type());
667 if (s < 64) return bb.assign(name, "trunc i64 {} to {}", x, t);
668 return x;
669 } else if (auto bit1 = Axm::isa<core::bit1>(def)) {
670 if (bit1.id() != core::bit1::neg) def->blame(MIM_LL_BE "unhandled `core.bit1` id").bail();
671 auto x = emit(bit1->arg());
672 auto t = convert(bit1->type());
673 return bb.assign(name, "xor {} -1, {}", t, x);
674 } else if (auto bit2 = Axm::isa<core::bit2>(def)) {
675 auto [a, b] = bit2->args<2>([this](auto def) { return emit(def); });
676 auto t = convert(bit2->type());
677
678 auto neg = [&](std::string_view x) { return bb.assign(name + ".neg", "xor {} -1, {}", t, x); };
679
680 switch (bit2.id()) {
681 // clang-format off
682 case core::bit2::and_: return bb.assign(name, "and {} {}, {}", t, a, b);
683 case core::bit2:: or_: return bb.assign(name, "or {} {}, {}", t, a, b);
684 case core::bit2::xor_: return bb.assign(name, "xor {} {}, {}", t, a, b);
685 case core::bit2::nand: return neg(bb.assign(name, "and {} {}, {}", t, a, b));
686 case core::bit2:: nor: return neg(bb.assign(name, "or {} {}, {}", t, a, b));
687 case core::bit2::nxor: return neg(bb.assign(name, "xor {} {}, {}", t, a, b));
688 case core::bit2:: iff: return bb.assign(name, "or {} {}, {}", t, neg(a), b);
689 case core::bit2::niff: return bb.assign(name, "and {} {}, {}", t, a, neg(b));
690 // clang-format on
691 default: def->blame(MIM_LL_BE "unhandled `core.bit2` id").bail();
692 }
693 } else if (auto shr = Axm::isa<core::shr>(def)) {
694 auto [a, b] = shr->args<2>([this](auto def) { return emit(def); });
695 auto t = convert(shr->type());
696
697 switch (shr.id()) {
698 case core::shr::a: op = "ashr"; break;
699 case core::shr::l: op = "lshr"; break;
700 }
701
702 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
703 } else if (auto wrap = Axm::isa<core::wrap>(def)) {
704 auto [mode, _, ab] = wrap->uncurry_args<3>();
705 auto [a, b] = ab->projs<2>([this](auto def) { return emit(def); });
706 auto t = convert(wrap->type());
707 auto lmode = static_cast<core::Mode>(Lit::expect(mode, "a `core.wrap` mode"));
708
709 switch (wrap.id()) {
710 case core::wrap::add: op = "add"; break;
711 case core::wrap::sub: op = "sub"; break;
712 case core::wrap::mul: op = "mul"; break;
713 case core::wrap::shl: op = "shl"; break;
714 }
715
716 if (fe::has_flag(lmode, core::Mode::nuw)) op += " nuw";
717 if (fe::has_flag(lmode, core::Mode::nsw)) op += " nsw";
718
719 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
720 } else if (auto div = Axm::isa<core::div>(def)) {
721 auto [m, xy] = div->args<2>();
722 auto [x, y] = xy->projs<2>();
723 auto t = convert(x->type());
724 emit_unsafe(m);
725 auto a = emit(x);
726 auto b = emit(y);
727
728 switch (div.id()) {
729 case core::div::sdiv: op = "sdiv"; break;
730 case core::div::udiv: op = "udiv"; break;
731 case core::div::srem: op = "srem"; break;
732 case core::div::urem: op = "urem"; break;
733 }
734
735 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
736 } else if (auto icmp = Axm::isa<core::icmp>(def)) {
737 auto [a, b] = icmp->args<2>([this](auto def) { return emit(def); });
738 auto t = convert(icmp->arg(2, 0)->type());
739 auto pred = icmp_pred(icmp.id());
740 if (!pred) def->blame(MIM_LL_BE "unhandled `core.icmp` id").bail();
741 op = "icmp "s + pred;
742
743 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
744 } else if (auto extr = Axm::isa<core::extrema>(def)) {
745 auto [x, y] = extr->args<2>();
746 auto t = convert(x->type());
747 auto a = emit(x);
748 auto b = emit(y);
749 std::string f = "llvm.";
750 switch (extr.id()) {
751 case core::extrema::Sm: f += "smin."; break;
752 case core::extrema::SM: f += "smax."; break;
753 case core::extrema::sm: f += "umin."; break;
754 case core::extrema::sM: f += "umax."; break;
755 }
756 f += t;
757 declare("{} @{}({}, {})", t, f, t, t);
758 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
759 } else if (auto abs = Axm::isa<core::abs>(def)) {
760 auto [m, x] = abs->args<2>();
761 emit_unsafe(m);
762 auto t = convert(x->type());
763 auto a = emit(x);
764 std::string f = "llvm.abs." + t;
765 declare("{} @{}({}, {})", t, f, t, "i1");
766 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, "i1", "1");
767 } else if (auto conv = Axm::isa<core::conv>(def)) {
768 auto v_src = emit(conv->arg());
769 auto t_src = convert(conv->arg()->type());
770 auto t_dst = convert(conv->type());
771
772 nat_t w_src = idx_bitwidth(conv->arg()->type());
773 nat_t w_dst = idx_bitwidth(conv->type());
774
775 if (w_src == w_dst) return v_src;
776
777 switch (conv.id()) {
778 case core::conv::s: op = w_src < w_dst ? "sext" : "trunc"; break;
779 case core::conv::u: op = w_src < w_dst ? "zext" : "trunc"; break;
780 }
781
782 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
783 } else if (auto i2n = Axm::isa<core::idx2nat>(def)) {
784 auto v_src = emit(i2n->arg());
785 auto w_src = idx_bitwidth(i2n->arg()->type());
786 if (w_src == 64) return v_src;
787 return bb.assign(name, "zext {} {} to {}", convert(i2n->arg()->type()), v_src, convert(i2n->type()));
788 } else if (auto bitcast = Axm::isa<core::bitcast>(def)) {
789 auto dst_type_ptr = Axm::isa<mem::Ptr>(bitcast->type());
790 auto src_type_ptr = Axm::isa<mem::Ptr>(bitcast->arg()->type());
791 auto v_src = emit(bitcast->arg());
792 auto t_src = convert(bitcast->arg()->type());
793 auto t_dst = convert(bitcast->type());
794
795 if (auto lit = Lit::isa(bitcast->arg()); lit && *lit == 0) return "zeroinitializer";
796 // clang-format off
797 if (src_type_ptr && dst_type_ptr) return bb.assign(name, "bitcast {} {} to {}", t_src, v_src, t_dst);
798 if (src_type_ptr) return bb.assign(name, "ptrtoint {} {} to {}", t_src, v_src, t_dst);
799 if (dst_type_ptr) return bb.assign(name, "inttoptr {} {} to {}", t_src, v_src, t_dst);
800 // clang-format on
801
802 auto size2width = [&](const Def* type) {
803 if (type->isa<Nat>()) return 64_n;
804 if (Idx::isa(type)) return idx_bitwidth(type);
805 return 0_n;
806 };
807
808 auto src_size = size2width(bitcast->arg()->type());
809 auto dst_size = size2width(bitcast->type());
810
811 op = "bitcast";
812 if (src_size && dst_size) {
813 if (src_size == dst_size) return v_src;
814 op = (src_size < dst_size) ? "zext" : "trunc";
815 }
816 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
817 }
818 return std::nullopt;
819}
820
821std::optional<std::string> Emitter::emit_mem(BB& bb, const std::string& name, const Def* def) {
822 if (auto lea = Axm::isa<mem::lea>(def)) {
823 auto [ptr, i] = lea->args<2>();
824 auto pointee = Axm::expect<mem::Ptr>(ptr->type(), "a `mem.Ptr`")->arg(2, 0);
825 auto v_ptr = emit(ptr);
826 auto t_pointee = convert(pointee, false);
827 auto t_ptr = convert(ptr->type());
828 if (pointee->isa<Sigma>())
829 return bb.assign(name, "getelementptr inbounds {}, {} {}, i64 0, i32 {}", t_pointee, t_ptr, v_ptr,
830 Lit::expect(i, "a struct-field index"));
831
832 pointee->expect<Arr>("an array as `mem.lea` pointee");
833 auto [v_i, t_i] = emit_gep_index(bb, name, i);
834
835 return bb.assign(name, "getelementptr inbounds {}, {} {}, i64 0, {} {}", t_pointee, t_ptr, v_ptr, t_i, v_i);
836 } else if (auto assume = Axm::isa<mem::assume>(def)) {
837 auto p = assume->arg(2, 0);
838 return bb.assign(name, "bitcast {} {} to {}", convert(p->type()), emit(p), convert(def->type()));
839 } else if (auto malloc = Axm::isa<mem::malloc>(def)) {
840 auto address_space = malloc->decurry()->arg(2, 1);
841 declare("i8* @malloc(i64)");
842
843 auto [m, n] = malloc->args<2>();
844 emit_unsafe(m);
845 auto size = emit(n);
846 auto ptr_t = convert(Axm::expect<mem::Ptr>(def->proj(2, 1)->type(), "a `mem.Ptr`"));
847 auto i8ptr = bb.assign(name + "i8", "call i8* @malloc(i64 {})", size);
848 std::string i8ptr_t = "i8*";
849 if (Lit::expect(address_space, "an address space") != 0) {
850 i8ptr_t = std::format("i8 addrspace({})*", address_space);
851 i8ptr = bb.assign(name + "i8conv", "addrspacecast i8* {} to {}", i8ptr, i8ptr_t);
852 }
853 return bb.assign(name, "bitcast {} {} to {}", i8ptr_t, i8ptr, ptr_t);
854 } else if (auto free = Axm::isa<mem::free>(def)) {
855 auto address_space = free->decurry()->arg(2, 1);
856 declare("void @free(i8*)");
857 auto [m, p] = free->args<2>();
858 emit_unsafe(m);
859 auto ptr = emit(p);
860 auto ptr_t = convert(Axm::expect<mem::Ptr>(p->type(), "a `mem.Ptr`"));
861
862 auto i8ptr = bb.assign(name + "i8", "bitcast {} {} to i8 addrspace({})*", ptr_t, ptr, address_space);
863 if (Lit::expect(address_space, "an address space") != 0)
864 i8ptr = bb.assign(name + "i8conv", "addrspacecast i8 addrspace({})* {} to i8*", address_space, i8ptr);
865 bb.tail("call void @free(i8* {})", i8ptr);
866 return std::string();
867 } else if (auto load = Axm::isa<mem::load>(def)) {
868 auto [m, ptr] = load->args<2>();
869 emit_unsafe(m);
870 auto v_ptr = emit(ptr);
871 auto t_ptr = convert(ptr->type());
872 auto pointee = Axm::expect<mem::Ptr>(ptr->type(), "a `mem.Ptr`")->arg(2, 0);
873 auto t_pointee = convert(pointee, false);
874 return repack(bb, name, pointee, bb.assign(name, "load {}, {} {}", t_pointee, t_ptr, v_ptr), true);
875 } else if (auto store = Axm::isa<mem::store>(def)) {
876 auto [m, ptr, val] = store->args<3>();
877 emit_unsafe(m);
878 auto v_ptr = emit(ptr);
879 auto v_val = repack(bb, name, val->type(), emit(val), false);
880 auto t_ptr = convert(ptr->type());
881 auto t_val = convert(val->type(), false);
882 std::print(bb.body().emplace_back(), "store {} {}, {} {}", t_val, v_val, t_ptr, v_ptr);
883 return std::string();
884 } else if (auto q = Axm::isa<clos::alloc_jmpbuf>(def)) {
885 // The size of a `jmp_buf` is platform/libc-dependent, so it is computed by a C runtime
886 // wrapper (`rt/mim_rt.c`) rather than hard-coded here; see issue #486.
887 declare_rt("i64 @mim_jmpbuf_size()");
888
889 emit_unsafe(q->arg());
890 auto size = name + ".size";
891 bb.assign(size, "call i64 @mim_jmpbuf_size()");
892 return bb.assign(name, "alloca i8, i64 {}", size);
893 } else if (auto setjmp = Axm::isa<clos::setjmp>(def)) {
894 declare("i32 @_setjmp(i8*) returns_twice");
895
896 auto [mem, jmpbuf] = setjmp->arg()->projs<2>();
897 emit_unsafe(mem);
898 auto v_jb = emit(jmpbuf);
899 return bb.assign(name, "call i32 @_setjmp(i8* {})", v_jb);
900 }
901 return std::nullopt;
902}
903
904std::optional<std::string> Emitter::emit_math(BB& bb, const std::string& name, const Def* def) {
905 std::string op;
906 if (auto arith = Axm::isa<math::arith>(def)) {
907 auto [mode, _, ab] = arith->uncurry_args<3>();
908 auto [a, b] = ab->projs<2>([this](auto def) { return emit(def); });
909 auto t = convert(arith->type());
910 auto lmode = static_cast<math::Mode>(Lit::expect(mode, "a `math.arith` mode"));
911
912 switch (arith.id()) {
913 case math::arith::add: op = "fadd"; break;
914 case math::arith::sub: op = "fsub"; break;
915 case math::arith::mul: op = "fmul"; break;
916 case math::arith::div: op = "fdiv"; break;
917 case math::arith::rem: op = "frem"; break;
918 }
919
920 op += fmath_flags(lmode);
921
922 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
923 } else if (auto tri = Axm::isa<math::tri>(def)) {
924 auto a = emit(tri->arg());
925 auto t = convert(tri->type());
926
927 std::string f;
928
929 if (tri.id() == math::tri::sin) {
930 f = std::string("llvm.sin") + detail::llvm_suffix(tri->type());
931 } else if (tri.id() == math::tri::cos) {
932 f = std::string("llvm.cos") + detail::llvm_suffix(tri->type());
933 } else {
934 if (tri.sub() & sub_t(math::tri::a)) f += "a";
935
936 switch (math::tri((fe::to_underlying(tri.id()) & 0x3) | Annex::base<math::tri>())) {
937 case math::tri::sin: f += "sin"; break;
938 case math::tri::cos: f += "cos"; break;
939 case math::tri::tan: f += "tan"; break;
940 case math::tri::ahFF: def->blame(MIM_LL_BE "this axm is not supposed to occur").bail();
941 default: def->blame(MIM_LL_BE "unhandled `math.tri` id").bail();
942 }
943
944 if (tri.sub() & sub_t(math::tri::h)) f += "h";
945 f += detail::math_suffix(tri->type());
946 }
947
948 declare("{} @{}({})", t, f, t);
949 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
950 } else if (auto extrema = Axm::isa<math::extrema>(def)) {
951 auto [a, b] = extrema->args<2>([this](auto def) { return emit(def); });
952 auto t = convert(extrema->type());
953 std::string f = "llvm.";
954 switch (extrema.id()) {
955 case math::extrema::fmin: f += "minnum"; break;
956 case math::extrema::fmax: f += "maxnum"; break;
957 case math::extrema::ieee754min: f += "minimum"; break;
958 case math::extrema::ieee754max: f += "maximum"; break;
959 }
960 f += detail::llvm_suffix(extrema->type());
961
962 declare("{} @{}({}, {})", t, f, t, t);
963 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
964 } else if (auto pow = Axm::isa<math::pow>(def)) {
965 auto [a, b] = pow->args<2>([this](auto def) { return emit(def); });
966 auto t = convert(pow->type());
967 std::string f = "llvm.pow";
968 f += detail::llvm_suffix(pow->type());
969 declare("{} @{}({}, {})", t, f, t, t);
970 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
971 } else if (auto rt = Axm::isa<math::rt>(def)) {
972 auto a = emit(rt->arg());
973 auto t = convert(rt->type());
974 std::string f;
975 if (rt.id() == math::rt::sq)
976 f = std::string("llvm.sqrt") + detail::llvm_suffix(rt->type());
977 else
978 f = std::string("cbrt") += detail::math_suffix(rt->type());
979 declare("{} @{}({})", t, f, t);
980 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
981 } else if (auto exp = Axm::isa<math::exp>(def)) {
982 auto a = emit(exp->arg());
983 auto t = convert(exp->type());
984 std::string f = "llvm.";
985 f += (exp.sub() & sub_t(math::exp::log)) ? "log" : "exp";
986 f += (exp.sub() & sub_t(math::exp::bin)) ? "2" : (exp.sub() & sub_t(math::exp::dec)) ? "10" : "";
987 f += detail::llvm_suffix(exp->type());
988 // TODO doesn't work for exp10"
989 declare("{} @{}({})", t, f, t);
990 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
991 } else if (auto er = Axm::isa<math::er>(def)) {
992 auto a = emit(er->arg());
993 auto t = convert(er->type());
994 auto f = er.id() == math::er::f ? std::string("erf") : std::string("erfc");
995 f += detail::math_suffix(er->type());
996 declare("{} @{}({})", t, f, t);
997 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
998 } else if (auto gamma = Axm::isa<math::gamma>(def)) {
999 auto a = emit(gamma->arg());
1000 auto t = convert(gamma->type());
1001 std::string f = gamma.id() == math::gamma::t ? "tgamma" : "lgamma";
1002 f += detail::math_suffix(gamma->type());
1003 declare("{} @{}({})", t, f, t);
1004 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1005 } else if (auto cmp = Axm::isa<math::cmp>(def)) {
1006 auto [a, b] = cmp->args<2>([this](auto def) { return emit(def); });
1007 auto t = convert(cmp->arg(2, 0)->type());
1008 auto pred = fcmp_pred(cmp.id());
1009 if (!pred) def->blame(MIM_LL_BE "unhandled `math.cmp` id").bail();
1010 op = "fcmp "s + pred;
1011
1012 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
1013 } else if (auto is_finite = Axm::isa<math::is_finite>(def)) {
1014 // https://llvm.org/docs/LangRef.html#llvm-is-fpclass-intrinsic
1015 // declare i1 @llvm.is.fpclass(<fptype> <op>, i32 <test>)
1016 auto a = emit(is_finite->arg());
1017 auto at = convert(is_finite->arg()->type());
1018 auto t = convert(is_finite->type());
1019
1020 auto s = detail::llvm_suffix(is_finite->arg()->type());
1021 auto f = "llvm.is.fpclass";
1022 declare("{} @{}{}({}, i32)", t, f, s, at);
1023 return bb.assign(name, "tail call {} @{}{}({} {}, i32 504)", t, f, s, at, a);
1024 } else if (auto conv = Axm::isa<math::conv>(def)) {
1025 auto v_src = emit(conv->arg());
1026 auto t_src = convert(conv->arg()->type());
1027 auto t_dst = convert(conv->type());
1028
1029 auto s_src = math::isa_f(conv->arg()->type());
1030 auto s_dst = math::isa_f(conv->type());
1031
1032 switch (conv.id()) {
1033 case math::conv::f2f: op = s_src < s_dst ? "fpext" : "fptrunc"; break;
1034 case math::conv::s2f: op = "sitofp"; break;
1035 case math::conv::u2f: op = "uitofp"; break;
1036 case math::conv::f2s: op = "fptosi"; break;
1037 case math::conv::f2u: op = "fptoui"; break;
1038 }
1039
1040 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
1041 } else if (auto abs = Axm::isa<math::abs>(def)) {
1042 auto a = emit(abs->arg());
1043 auto t = convert(abs->type());
1044 std::string f = "llvm.fabs";
1045 f += detail::llvm_suffix(abs->type());
1046 declare("{} @{}({})", t, f, t);
1047 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1048 } else if (auto round = Axm::isa<math::round>(def)) {
1049 auto a = emit(round->arg());
1050 auto t = convert(round->type());
1051 std::string f = "llvm.";
1052 switch (round.id()) {
1053 case math::round::f: f += "floor"; break;
1054 case math::round::c: f += "ceil"; break;
1055 case math::round::r: f += "round"; break;
1056 case math::round::t: f += "trunc"; break;
1057 }
1058 f += detail::llvm_suffix(round->type());
1059 declare("{} @{}({})", t, f, t);
1060 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1061 }
1062 return std::nullopt;
1063}
1064
1065std::optional<std::string> Emitter::emit_vec(BB& bb, const std::string& name, const Def* def) {
1066 // `ll.vec` annotates a loop's exit condition; as a value it is the identity — the
1067 // metadata it requests is attached at the branches into the loop's header.
1068 if (auto v = Axm::isa<ll::vec>(def)) {
1069 return emit(v->arg());
1070 } else if (auto nv = Axm::isa<ll::novec>(def)) {
1071 return emit(nv->arg());
1072 } else if (auto zip = Axm::isa<vecp::zip>(def)) {
1073 auto ni_n = zip->decurry()->decurry()->decurry()->arg();
1074 auto nat_ni = Lit::expect(ni_n->proj(2, 0), "the `vec.zip` inputs count");
1075 auto nat_n = Lit::expect(ni_n->proj(2, 1), "the `vec.zip` lane count");
1076 auto f = zip->decurry()->arg();
1077 auto inputs = zip->arg();
1078 auto t_in = convert(inputs->proj(nat_ni, 0)->type());
1079 auto t_out = convert(def->type()); // <n x T>
1080
1081 std::string op;
1082
1083 if (auto nat_op = Axm::isa<core::nat, 1>(f)) {
1084 switch (nat_op.id()) {
1085 case core::nat::add: op = "add nuw nsw"; break;
1086 case core::nat::sub: {
1087 // nat subtraction saturates at 0: cap per-lane when v2 > v1
1088 auto v1 = emit(inputs->proj(nat_ni, 0));
1089 auto v2 = emit(inputs->proj(nat_ni, 1));
1090 auto ugt = bb.assign(name + ".ugt", "icmp ugt {} {}, {}", t_in, v2, v1);
1091 auto raw = bb.assign(name + ".raw", "sub {} {}, {}", t_in, v1, v2);
1092 return bb.assign(name, "select <{} x i1> {}, {} zeroinitializer, {} {}", nat_n, ugt, t_out, t_out,
1093 raw);
1094 }
1095 case core::nat::mul: op = "mul nuw nsw"; break;
1096 case core::nat::div:
1097 case core::nat::rem: {
1098 // Same zero-divisor semantics as the scalar case: `a / 0 = 0` and `a % 0 = a`.
1099 auto v1 = emit(inputs->proj(nat_ni, 0));
1100 auto v2 = emit(inputs->proj(nat_ni, 1));
1101 auto bz = bb.assign(name + ".bz", "icmp eq {} {}, zeroinitializer", t_in, v2);
1102 auto bz1 = bb.assign(name + ".bz1", "zext <{} x i1> {} to {}", nat_n, bz, t_in);
1103 auto bsaf = bb.assign(name + ".bsafe", "or {} {}, {}", t_in, v2, bz1);
1104 auto div = nat_op.id() == core::nat::div;
1105 auto r = bb.assign(name + ".r", "{} {} {}, {}", div ? "udiv" : "urem", t_in, v1, bsaf);
1106 return bb.assign(name, "select <{} x i1> {}, {} {}, {} {}", nat_n, bz, t_out,
1107 div ? std::string("zeroinitializer") : v1, t_out, r);
1108 }
1109 }
1110 } else if (auto arith_op = Axm::isa<math::arith, 1>(f)) {
1111 auto lmode = static_cast<math::Mode>(
1112 Lit::expect(f->expect<App>("a zipped `math.arith`")->decurry()->arg(), "a `math.arith` mode"));
1113 switch (arith_op.id()) {
1114 case math::arith::add: op = "fadd"; break;
1115 case math::arith::sub: op = "fsub"; break;
1116 case math::arith::mul: op = "fmul"; break;
1117 case math::arith::div: op = "fdiv"; break;
1118 case math::arith::rem: op = "frem"; break;
1119 }
1120 op += fmath_flags(lmode);
1121 } else if (auto ncmp_op = Axm::isa<core::ncmp, 1>(f)) {
1122 auto pred = ncmp_pred(ncmp_op.id());
1123 if (!pred) def->blame(MIM_LL_BE "unhandled zipped `core.ncmp` id").bail();
1124 op = "icmp "s + pred;
1125 } else if (auto icmp_op = Axm::isa<core::icmp, 1>(f)) {
1126 auto pred = icmp_pred(icmp_op.id());
1127 if (!pred) def->blame(MIM_LL_BE "unhandled zipped `core.icmp` id").bail();
1128 op = "icmp "s + pred;
1129 } else if (auto mcmp_op = Axm::isa<math::cmp, 1>(f)) {
1130 auto pred = fcmp_pred(mcmp_op.id());
1131 if (!pred) def->blame(MIM_LL_BE "unhandled zipped `math.cmp` id").bail();
1132 op = "fcmp "s + pred;
1133 } else {
1134 f->blame(MIM_LL_BE "unhandled `vec.zip` operation").bail();
1135 }
1136
1137 auto v1 = emit(inputs->proj(nat_ni, 0));
1138 auto v2 = emit(inputs->proj(nat_ni, 1));
1139 return bb.assign(name, "{} {} {}, {}", op, t_in, v1, v2);
1140 }
1141 return std::nullopt;
1142}
1143
1144std::string Emitter::emit_bb_impl(BB& bb, const Def* def) {
1145 if (auto lam = def->isa<Lam>()) return id(lam);
1146
1147 auto name = id(def);
1148 if (auto res = isa_targetspecific_intrinsic(bb, def)) return *std::move(res);
1149 if (auto res = emit_builtin(bb, name, def)) return *std::move(res);
1150 if (auto res = emit_core(bb, name, def)) return *std::move(res);
1151 if (auto res = emit_mem(bb, name, def)) return *std::move(res);
1152 if (auto res = emit_math(bb, name, def)) return *std::move(res);
1153 if (auto res = emit_vec(bb, name, def)) return *std::move(res);
1154 def->blame(MIM_LL_BE "unhandled def of type `{}`", def->type()).bail();
1155}
1156
1157extern "C" {
1158MIM_EXPORT void mim_ll_convert(Emitter& e, const Def* type, bool simd, std::string& res) {
1159 res = e.convert_impl(type, simd);
1160}
1161MIM_EXPORT void mim_ll_finalize(Emitter& e) { e.finalize_impl(); }
1162MIM_EXPORT void mim_ll_emit_epilogue(Emitter& e, Lam* lam) { e.emit_epilogue_impl(lam); }
1163MIM_EXPORT void mim_ll_emit_bb(Emitter& e, BB& bb, const Def* def, std::string& res) { res = e.emit_bb_impl(bb, def); }
1164}
1165
1166} // namespace mim::plug::ll
1167
1168using namespace mim;
1169
1171
1172// clang-format off
1173static constexpr PluginArg known_args[] = {
1174 {"o=<file>, output=<file>", "Writes the LLVM IR to `<file>` instead of the default `<world>.ll`/`a.ll`; `<file>` may be `-` for stdout."},
1175 {"rt=embed, rt=extern", "How the C runtime wrappers reach the output: `embed` (default) splices their LLVM IR into the module; `extern` only `declare`s them and leaves linking to you."},
1176};
1177// clang-format on
1178
1179// MIM_PLUGIN_SYM stringifies its argument, so these names must stay unqualified.
1180using namespace mim::plug::ll;
1187
1189 plugin.register_phases = reg_phases;
1190 plugin.args = known_args;
1191 plugin.num_args = std::size(known_args);
1192 plugin.syms = known_syms;
1193 plugin.num_syms = std::size(known_syms);
1194}
static auto isa(const Def *def)
Definition axm.h:112
static auto expect(const Def *def, fe::cite_string< Args... > fmt, Args &&... args)
Like Axm::as but - instead of merely asserting in Debug builds - bails via Def::bail_expected when de...
Definition axm.h:142
M * root() const
Definition phase.h:530
Base class for all Defs.
Definition def.h:289
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.
Definition def.cpp:647
T * expect(fe::cite_string< Args... > fmt, Args &&... args)
Like Def::as but - instead of merely asserting in Debug builds - bails via Def::bail_expected when th...
Definition def.h:609
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.h:1119
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
nat_t num_projs() const
Yields Def::arity(), if it is a Lit, or 1 otherwise.
Definition def.h:1140
bool is_closed() const
Same as !has_free_vars().
Definition def.cpp:357
Dispatch(const Def *)
Definition tuple.cpp:105
static constexpr nat_t size2bitwidth(nat_t n)
Definition def.h:1040
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
static Lam * isa_mut_basicblock(const Def *d)
Definition lam.h:130
const Pi * type() const
Definition lam.h:112
const Def * body() const
Definition lam.h:104
static std::optional< T > isa(const Def *def)
Definition def.h:976
static T expect(const Def *def, fe::cite_string< Args... > fmt, Args &&... args)
Like Lit::as but bails via Def::bail_expected instead of merely asserting in Debug; see Def::expect.
Definition def.h:987
Out()=default
static void hook(Flags2Phases &phases)
Definition phase.h:51
Phase(World &world, std::string name)
Definition phase.h:27
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
World & world()
Definition phase.h:58
static const Pi * isa_returning(const Def *d)
Definition lam.h:51
Select(const Def *)
Definition tuple.cpp:92
Sym name() const
Definition world.h:113
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Definition world.cpp:224
Pipeline phase for ll.emit.
Definition ll.cpp:25
void start() override
Actual entry.
Definition ll.cpp:29
Phase(World &world, std::string name)
Definition phase.h:27
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
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
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
std::array< DefMap< std::string >, 2 > types_
Indexed by the simd flag of Emitter::convert.
Definition ll.h:269
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
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::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
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
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 func_impls_
Definition ll.h:276
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
Definition ll.h:168
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
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::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
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
#define MIM_EXPORT
Definition config.h:21
static const PluginSym known_syms[]
Definition ll.cpp:1181
static void reg_phases(Flags2Phases &phases)
Definition ll.cpp:1170
static constexpr PluginArg known_args[]
Definition ll.cpp:1173
#define MIM_LL_BE
Prefix for this backend's diagnostics; concatenate it with the format literal.
Definition ll.h:45
The clos Plugin
Definition clos.h:9
The core Plugin
Definition core.h:8
@ nuw
No Unsigned Wrap around.
Definition core.h:18
@ nsw
No Signed Wrap around.
Definition core.h:17
The ll Plugin
Definition ll.h:41
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
static nat_t idx_bitwidth(const Def *type)
An Idx n whose n is only known at runtime is bounded by a Nat, hence an i64.
Definition ll.cpp:53
static const char * ncmp_pred(core::ncmp id)
Definition ll.cpp:70
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
static const char * fcmp_pred(math::cmp id)
Definition ll.cpp:102
static const char * icmp_pred(core::icmp id)
Definition ll.cpp:84
static std::string fmath_flags(math::Mode mode)
Definition ll.cpp:55
void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &res)
Definition ll.cpp:1163
Mode
Allowed optimizations for a specific operation.
Definition math.h:14
@ arcp
Allow Reciprocal.
Definition math.h:25
@ fast
All flags.
Definition math.h:35
@ afn
Approximate functions.
Definition math.h:29
@ ninf
No Infs.
Definition math.h:20
@ reassoc
Allow reassociation transformations for floating-point operations.
Definition math.h:31
@ contract
Allow floating-point contraction (e.g.
Definition math.h:27
@ nsz
No Signed Zeros.
Definition math.h:23
@ nnan
No NaNs.
Definition math.h:17
std::optional< nat_t > isa_f(const Def *def)
Definition math.h:55
bool has_mem_ty(const Def *type)
Is type mem.M or a Sigma with a mem.M component?
Definition mem.h:54
const Def * pointee(const Def *ptr)
Definition mem.h:81
The vec Plugin
Definition ast.h:16
u64 nat_t
Definition types.h:37
u8 sub_t
Definition types.h:42
double f64
Definition types.h:35
float f32
Definition types.h:34
TExt< true > Top
Definition union.h:40
fe::Vector< const Def * > DefVec
Definition def.h:98
TExt< false > Bot
Definition union.h:39
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
ankerl::unordered_dense::map< flags_t, std::function< std::unique_ptr< Phase >(World &)> > Flags2Phases
Maps an axiom of a Phase to a function that creates one.
Definition plugin.h:30
@ Lam
Definition def.h:125
@ Arr
Definition def.h:125
@ App
Definition def.h:125
uint16_t u16
Definition types.h:27
One -X <plugin>:<arg> a Plugin understands; see Arguments.
Definition plugin.h:34
One symbol a Plugin offers to other Plugins; see Driver::get_fun_ptr.
Definition plugin.h:48
#define MIM_PLUGIN_SYM(f)
Builds a PluginSym for f, so name and address cannot drift apart.
Definition plugin.h:54
#define MIM_PLUGIN_ENTRY(p)
Defines a Plugin's entry point; the body fills in the plugin handed to it, as in MIM_PLUGIN_ENTRY(dem...
Definition plugin.h:196
static consteval flags_t base()
Definition plugin.h:276
std::deque< std::ostringstream > & tail()
Definition ll.h:89
std::deque< std::ostringstream > & body()
Definition ll.h:88
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)
Definition ll.h:92