MimIR 0.1
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
ll.cpp
Go to the documentation of this file.
2
3#include <deque>
4#include <format>
5#include <fstream>
6#include <iomanip>
7#include <optional>
8#include <ranges>
9#include <string>
10
11#include <absl/container/btree_set.h>
12
13#include <mim/plug/clos/clos.h>
14#include <mim/plug/math/math.h>
15#include <mim/plug/mem/mem.h>
16#include <mim/plug/vec/vec.h>
17
18#include "mim/be/emitter.h"
19#include "mim/util/print.h"
20#include "mim/util/sys.h"
21
22#include "mim/plug/core/core.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
37using namespace std::string_literals;
38
39namespace mim::ll {
40
41namespace clos = mim::plug::clos;
42namespace core = mim::plug::core;
43namespace math = mim::plug::math;
44namespace mem = mim::plug::mem;
45namespace vec = mim::plug::vec;
46
47namespace {
48const char* math_suffix(const Def* type) {
49 if (auto w = math::isa_f(type)) {
50 switch (*w) {
51 case 32: return "f";
52 case 64: return "";
53 }
54 }
55 error("unsupported foating point type '{}'", type);
56}
57
58const char* llvm_suffix(const Def* type) {
59 if (auto w = math::isa_f(type)) {
60 switch (*w) {
61 case 16: return ".f16";
62 case 32: return ".f32";
63 case 64: return ".f64";
64 }
65 }
66 error("unsupported foating point type '{}'", type);
67}
68
69// [%mem.M 0, T] => T
70// TODO there may be more instances where we have to deal with this trickery
71const Def* isa_mem_sigma_2(const Def* type) {
72 if (auto sigma = type->isa<Sigma>())
73 if (sigma->num_ops() == 2 && Axm::isa<mem::M>(sigma->op(0))) return sigma->op(1);
74 return {};
75}
76} // namespace
77
78struct BB {
79 BB() = default;
80 BB(const BB&) = delete;
81 BB(BB&& other) noexcept = default;
82 BB& operator=(BB other) noexcept { return swap(*this, other), *this; }
83
84 std::deque<std::ostringstream>& head() { return parts[0]; }
85 std::deque<std::ostringstream>& body() { return parts[1]; }
86 std::deque<std::ostringstream>& tail() { return parts[2]; }
87
88 template<class... Args>
89 std::string assign(std::string_view name, const char* s, Args&&... args) {
90 print(print(body().emplace_back(), "{} = ", name), s, std::forward<Args>(args)...);
91 return std::string(name);
92 }
93
94 template<class... Args>
95 void tail(const char* s, Args&&... args) {
96 print(tail().emplace_back(), s, std::forward<Args>(args)...);
97 }
98
99 friend void swap(BB& a, BB& b) noexcept {
100 using std::swap;
101 swap(a.phis, b.phis);
102 swap(a.parts, b.parts);
103 }
104
106 std::array<std::deque<std::ostringstream>, 3> parts;
107};
108
109class Emitter : public mim::Emitter<std::string, std::string, BB, Emitter> {
110public:
112
113 Emitter(World& world, std::ostream& ostream)
114 : Super(world, "llvm_emitter", ostream) {}
115
116 bool is_valid(std::string_view s) { return !s.empty(); }
117 void start() override;
118 void emit_imported(Lam*);
119 void emit_epilogue(Lam*);
120 std::string emit_bb(BB&, const Def*);
121 std::string prepare();
122 void finalize();
123
124 template<class... Args>
125 void declare(const char* s, Args&&... args) {
126 std::ostringstream decl;
127 print(decl << "declare ", s, std::forward<Args>(args)...);
128 decls_.emplace(decl.str());
129 }
130
131private:
132 std::string id(const Def*, bool force_bb = false) const;
133 std::string convert(const Def*, bool simd = true);
134 std::string convert_ret_pi(const Pi*);
135
136 absl::btree_set<std::string> decls_;
137 std::ostringstream type_decls_;
138 std::ostringstream vars_decls_;
139 std::ostringstream func_decls_;
140 std::ostringstream func_impls_;
141 LamMap<const Def*> simd_phi_;
142};
143
144/*
145 * convert
146 */
147
148static std::optional<std::pair<nat_t, const Def*>> is_simd(const Def* type) {
149 if (auto arr = type->isa<Arr>()) {
150 if (auto l = Lit::isa(arr->arity())) {
151 if (arr->body()->isa<Nat>() || Idx::isa(arr->body()) || Axm::isa<math::F>(arr->body()))
152 return std::pair{*l, arr->body()};
153 }
154 }
155 return {};
156}
157static std::optional<std::pair<nat_t, const Def*>> is_simd_aggregate(const std::vector<const Def*> types) {
158 if (std::ranges::all_of(types, [&](auto i) { return i == types[0]; })) {
159 if (types[0]->isa<Nat>() || Idx::isa(types[0]) || Axm::isa<math::F>(types[0]))
160 return std::pair{types.size(), types[0]};
161 }
162
163 return {};
164}
165
166static const Def* find_common_simd_src(const App* app) {
167 const Def* common_src = nullptr;
168 for (auto arg : app->args()) {
169 if (Axm::isa<mem::M>(arg->type())) continue;
170 auto extract = arg->isa<Extract>();
171 if (!extract || !is_simd(extract->tuple()->type())) return nullptr;
172 if (!common_src)
173 common_src = extract->tuple();
174 else if (common_src != extract->tuple())
175 return nullptr;
176 }
177 return common_src;
178}
179
180std::string Emitter::id(const Def* def, bool force_bb /*= false*/) const {
181 if (auto global = def->isa<Global>()) return "@" + global->unique_name();
182
183 if (auto lam = def->isa_mut<Lam>(); lam && !force_bb) {
184 if (lam->type()->ret_pi()) {
185 if (lam->is_external() || !lam->is_set())
186 return "@"s + lam->sym().str(); // TODO or use is_internal or sth like that?
187 return "@"s + lam->unique_name();
188 }
189 }
190
191 return "%"s + def->unique_name();
192}
193
194std::string Emitter::convert(const Def* type, bool simd) {
195 if (auto i = types_.find(type); i != types_.end()) return i->second;
196
197 assert(!Axm::isa<mem::M>(type));
198 std::ostringstream s;
199 std::string name;
200
201 if (type->isa<Nat>()) {
202 return types_[type] = "i64";
203 } else if (auto size = Idx::isa(type)) {
204 return types_[type] = "i" + std::to_string(*Idx::size2bitwidth(size));
205 } else if (auto w = math::isa_f(type)) {
206 switch (*w) {
207 case 16: return types_[type] = "half";
208 case 32: return types_[type] = "float";
209 case 64: return types_[type] = "double";
210 default: fe::unreachable();
211 }
212 } else if (auto ptr = Axm::isa<mem::Ptr>(type)) {
213 auto [pointee, addr_space] = ptr->args<2>();
214 // TODO addr_space
215 print(s, "{}*", convert(pointee, false));
216 } else if (auto arr = type->isa<Arr>()) {
217 if (auto se = is_simd(arr); se && simd) {
218 auto [size, elem] = *se;
219 print(s, "<{} x {}>", size, convert(elem));
220 } else {
221 u64 size = 0;
222 if (auto arity = Lit::isa(arr->arity())) size = *arity;
223 print(s, "[{} x {}]", size, convert(arr->body(), false));
224 }
225 } else if (auto pi = type->isa<Pi>()) {
226 assert(Pi::isa_returning(pi) && "should never have to convert type of BB");
227 print(s, "{} (", convert_ret_pi(pi->ret_pi()));
228
229 if (auto t = isa_mem_sigma_2(pi->dom()))
230 s << convert(t);
231 else {
232 auto doms = pi->doms();
233 for (auto sep = ""; auto dom : doms.view().rsubspan(1)) {
234 if (Axm::isa<mem::M>(dom)) continue;
235 s << sep << convert(dom);
236 sep = ", ";
237 }
238 }
239 s << ")*";
240 } else if (auto t = isa_mem_sigma_2(type)) {
241 return convert(t);
242 } else if (auto sigma = type->isa<Sigma>()) {
243 if (sigma->isa_mut()) {
244 name = id(sigma);
245 types_[sigma] = name;
246 print(s, "{} = type", name);
247 }
248
249 print(s, "{{");
250 for (auto sep = ""; auto t : sigma->ops()) {
251 if (Axm::isa<mem::M>(t)) continue;
252 s << sep << convert(t);
253 sep = ", ";
254 }
255 print(s, "}}");
256 } else {
257 fe::unreachable();
258 }
259
260 if (name.empty()) return types_[type] = s.str();
261
262 assert(!s.str().empty());
263 type_decls_ << s.str() << '\n';
264 return types_[type] = name;
265}
266
267std::string Emitter::convert_ret_pi(const Pi* pi) {
268 auto dom = mem::strip_mem_ty(pi->dom());
269 if (dom == world().sigma()) return "void";
270 return convert(dom);
271}
272
273/*
274 * emit
275 */
276
278 Super::start();
279
280 ostream() << type_decls_.str() << '\n';
281 for (auto&& decl : decls_)
282 ostream() << decl << '\n';
283 ostream() << func_decls_.str() << '\n';
284 ostream() << vars_decls_.str() << '\n';
285 ostream() << func_impls_.str() << '\n';
286}
287
289 // TODO merge with declare method
290 print(func_decls_, "declare {} {}(", convert_ret_pi(lam->type()->ret_pi()), id(lam));
291
292 auto doms = lam->doms();
293 for (auto sep = ""; auto dom : doms.view().rsubspan(1)) {
294 if (Axm::isa<mem::M>(dom)) continue;
295 print(func_decls_, "{}{}", sep, convert(dom));
296 sep = ", ";
297 }
298
299 print(func_decls_, ")\n");
300}
301
302std::string Emitter::prepare() {
303 auto internal = root()->is_external() ? "" : "internal ";
304 auto ret_t = convert_ret_pi(root()->type()->ret_pi());
305 print(func_impls_, "define {} {} {}(", internal, ret_t, id(root()));
306
307 auto vars = root()->vars();
308 for (auto sep = ""; auto var : vars.view().rsubspan(1)) {
309 if (Axm::isa<mem::M>(var->type())) continue;
310 if (auto arr = var->type()->isa<Arr>())
311 if (is_simd(arr->body())) convert(arr->body()); // pre-add input vector to cache
312 auto name = id(var);
313 locals_[var] = name;
314 print(func_impls_, "{}{} {}", sep, convert(var->type()), name);
315 sep = ", ";
316 }
317
318 print(func_impls_, ") {{\n");
319 return root()->unique_name();
320}
321
323 for (auto& [lam, bb] : lam2bb_) {
324 for (const auto& [phi, args] : bb.phis) {
325 print(bb.head().emplace_back(), "{} = phi {} ", id(phi), convert(phi->type()));
326 for (auto sep = ""; const auto& [arg, pred] : args) {
327 print(bb.head().back(), "{}[ {}, {} ]", sep, arg, pred);
328 sep = ", ";
329 }
330 }
331 }
332
333 for (auto mut : Scheduler::schedule(nest())) {
334 if (auto lam = mut->isa_mut<Lam>()) {
335 assert(lam2bb_.contains(lam));
336 auto& bb = lam2bb_[lam];
337 print(func_impls_, "{}:\n", lam->unique_name());
338
339 ++tab;
340 for (const auto& part : bb.parts)
341 for (const auto& line : part)
342 tab.print(func_impls_, "{}\n", line.str());
343 --tab;
344 func_impls_ << std::endl;
345 }
346 }
347
348 print(func_impls_, "}}\n\n");
349}
350
351/*
352 Block type return
353BB:
354 Cn [M, a, A] → 2 phi
355 Cn «2;A» → 2 phi
356 Cn [M, «2;A» → 1 phi
357Ret:
358 Cn[M,A,A] → 1 phi
359 Cn «2;A» → 1 phi
360 Cn[M, «2;A»] → 1 phi
361
362Fun:
363 Cn[A, A, Cn R] → 2 args + ret
364 Cn[«2; A», Cn R] → 1 args + ret
365 Cn[M, A, A, Cn R] → 2 args + ret
366 Cn[M, «2; A», Cn R] → 1 args + ret
367*/
369 auto app = lam->body()->as<App>();
370 auto& bb = lam2bb_[lam];
371 if (app->callee() == root()->ret_var()) { // return
372 std::vector<std::string> values;
373 std::vector<const Def*> types;
374 for (auto arg : app->args()) {
375 if (auto val = emit_unsafe(arg); !val.empty()) {
376 values.emplace_back(val);
377 types.emplace_back(arg->type());
378 }
379 }
380
381 switch (values.size()) {
382 case 0: return bb.tail("ret void");
383 case 1: return bb.tail("ret {} {}", convert(types[0]), values[0]);
384 default: {
385 std::string type;
386 std::string prev;
387
388 if (auto se = is_simd_aggregate(types)) {
389 auto common_src = find_common_simd_src(app);
390 if (common_src) {
391 auto v_src = emit(common_src);
392 auto t = convert(common_src->type());
393 return bb.tail("ret {} {}", t, v_src);
394 }
395 auto [size, elem] = *se;
396 auto val_t = convert(elem);
397
398 type = std::format("<{} x {}>", size, val_t);
399 for (auto val : values) {
400 if (prev.empty())
401 prev = "<";
402 else
403 prev += ", ";
404 prev += std::format("{} {}", val_t, val);
405 }
406 prev += ">";
407 } else {
408 prev = "undef";
409 type = convert(world().sigma(types));
410 for (size_t i = 0, n = values.size(); i != n; ++i) {
411 auto v_elem = values[i];
412 auto t_elem = convert(types[i]);
413 auto namei = "%ret_val." + std::to_string(i);
414 bb.tail("{} = insertvalue {} {}, {} {}, {}", namei, type, prev, t_elem, v_elem, i);
415 prev = namei;
416 }
417 }
418 bb.tail("ret {} {}", type, prev);
419 }
420 }
421
422 } else if (auto dispatch = Dispatch(app)) {
423 for (auto callee : dispatch.tuple()->projs([](const Def* def) { return def->isa_mut<Lam>(); })) {
424 size_t n = callee->num_tvars();
425 if (n == 1 && is_simd(callee->var(0)->type())) {
426 auto phi = callee->var(0);
427 auto arg = emit(app->arg(n, 0));
428 lam2bb_[callee].phis[phi].emplace_back(arg, id(lam, true));
429 locals_[phi] = id(phi);
430 } else {
431 for (size_t i = 0; i != n; ++i) {
432 if (auto arg = emit_unsafe(app->arg(n, i)); !arg.empty()) {
433 auto phi = callee->var(n, i);
434 assert(!Axm::isa<mem::M>(phi->type()));
435 lam2bb_[callee].phis[phi].emplace_back(arg, id(lam, true));
436 locals_[phi] = id(phi);
437 }
438 }
439 }
440 }
441
442 auto v_index = emit(dispatch.index());
443 size_t n = dispatch.num_targets();
444 auto bbs = absl::FixedArray<std::string>(n);
445 for (size_t i = 0; i != n; ++i)
446 bbs[i] = emit(dispatch.target(i));
447
448 if (auto branch = Branch(app)) return bb.tail("br i1 {}, label {}, label {}", v_index, bbs[1], bbs[0]);
449
450 auto t_index = convert(dispatch.index()->type());
451 bb.tail("switch {} {}, label {} [ ", t_index, v_index, bbs[0]);
452 for (size_t i = 1; i != n; ++i)
453 print(bb.tail().back(), "{} {}, label {} ", t_index, std::to_string(i), bbs[i]);
454 print(bb.tail().back(), "]");
455 } else if (app->callee()->isa<Bot>()) {
456 return bb.tail("ret ; bottom: unreachable");
457 } else if (auto callee = Lam::isa_mut_basicblock(app->callee())) { // ordinary jump
458
459 auto common_src = find_common_simd_src(app);
460 if (common_src) {
461 auto v_src = emit(common_src);
462 auto callee_var = callee->var();
463 if (simd_phi_.find(callee) == simd_phi_.end()) simd_phi_[callee] = callee_var;
464 auto key = simd_phi_[callee];
465 lam2bb_[callee].phis[key].emplace_back(v_src, id(lam, true));
466 locals_[key] = id(key);
467 for (auto var : callee->vars())
468 locals_[var] = id(key);
469 locals_[callee_var] = id(key);
470 } else {
471 size_t n = callee->num_tvars();
472 for (size_t i = 0; i != n; ++i) {
473 if (auto arg = emit_unsafe(app->arg(n, i)); !arg.empty()) {
474 auto phi = callee->var(n, i);
475 assert(!Axm::isa<mem::M>(phi->type()));
476 lam2bb_[callee].phis[phi].emplace_back(arg, id(lam, true));
477 locals_[phi] = id(phi);
478 }
479 }
480 }
481 return bb.tail("br label {}", id(callee));
482
483 } else if (auto longjmp = Axm::isa<clos::longjmp>(app)) {
484 declare("void @longjmp(i8*, i32) noreturn");
485
486 auto [mem, jbuf, tag] = app->args<3>();
487 emit_unsafe(mem);
488 auto v_jb = emit(jbuf);
489 auto v_tag = emit(tag);
490 bb.tail("call void @longjmp(i8* {}, i32 {})", v_jb, v_tag);
491 return bb.tail("unreachable");
492 } else if (Pi::isa_returning(app->callee_type())) { // function call
493 auto v_callee = emit(app->callee());
494
495 std::vector<std::string> args;
496 auto app_args = app->args();
497 for (auto arg : app_args.view().rsubspan(1))
498 if (auto v_arg = emit_unsafe(arg); !v_arg.empty()) args.emplace_back(convert(arg->type()) + " " + v_arg);
499
500 if (app->args().back()->isa<Bot>()) {
501 // TODO: Perhaps it'd be better to simply η-wrap this prior to the BE...
502 assert(convert_ret_pi(app->callee_type()->ret_pi()) == "void");
503 bb.tail("call void {}({, })", v_callee, args);
504 return bb.tail("unreachable");
505 }
506
507 auto ret_lam = app->args().back()->as_mut<Lam>();
508 size_t num_vars = ret_lam->num_vars();
509 size_t n = 0;
510 DefVec values(num_vars);
511 DefVec types(num_vars);
512 for (auto var : ret_lam->vars()) {
513 if (Axm::isa<mem::M>(var->type())) continue;
514 values[n] = var;
515 types[n] = var->type();
516 ++n;
517 }
518
519 if (n == 0) {
520 bb.tail("call void {}({, })", v_callee, args);
521 } else {
522 auto name = "%" + app->unique_name() + "ret";
523 auto t_ret = convert_ret_pi(ret_lam->type());
524 bb.tail("{} = call {} {}({, })", name, t_ret, v_callee, args);
525 auto phi = ret_lam->var();
526 lam2bb_[ret_lam].phis[phi].emplace_back(name, id(lam, true));
527 locals_[phi] = id(phi);
528 }
529
530 return bb.tail("br label {}", id(ret_lam));
531 }
532}
533
534std::string Emitter::emit_bb(BB& bb, const Def* def) {
535 if (auto lam = def->isa<Lam>()) return id(lam);
536
537 auto name = id(def);
538 std::string op;
539
540 auto emit_tuple = [&](const Def* tuple) {
541 if (isa_mem_sigma_2(tuple->type())) {
542 emit_unsafe(tuple->proj(2, 0));
543 return emit(tuple->proj(2, 1));
544 }
545
546 if (tuple->is_closed()) {
547 bool is_array = tuple->type()->isa<Arr>();
548 auto simd_array = convert(tuple->type()).front() == '<'; // needed to respect pointer context
549 std::string s;
550 s += simd_array ? "<" : is_array ? "[" : "{";
551 auto sep = "";
552 for (size_t i = 0, n = tuple->num_projs(); i != n; ++i) {
553 auto e = tuple->proj(n, i);
554 if (auto v_elem = emit_unsafe(e); !v_elem.empty()) {
555 auto t_elem = convert(e->type());
556 s += sep + t_elem + " " + v_elem;
557 sep = ", ";
558 }
559 }
560
561 return s += simd_array ? ">" : is_array ? "]" : "}";
562 }
563
564 std::string prev = "undef";
565 auto t = convert(tuple->type());
566 for (size_t src = 0, dst = 0, n = tuple->num_projs(); src != n; ++src) {
567 auto e = tuple->proj(n, src);
568 if (auto elem = emit_unsafe(e); !elem.empty()) {
569 auto elem_t = convert(e->type());
570 // TODO: check dst vs src
571 auto namei = name + "." + std::to_string(dst);
572 if (t.front() == '<') // not using is_simd to respect the pointer context (Pointer Pointee case)
573 prev = bb.assign(namei, "insertelement {} {}, {} {}, {} {}", t, prev, elem_t, elem, elem_t, dst);
574 else
575 prev = bb.assign(namei, "insertvalue {} {}, {} {}, {}", t, prev, elem_t, elem, dst);
576 dst++;
577 }
578 }
579 return prev;
580 };
581
582 if (def->isa<Var>()) {
583 if (is_simd(def->type())) return id(def);
584 auto ts = def->type()->projs();
585 if (std::ranges::any_of(ts, [](auto t) { return Axm::isa<mem::M>(t); })) return {};
586 return emit_tuple(def);
587 }
588
589 auto emit_gep_index = [&](const Def* index) {
590 auto v_i = emit(index);
591 auto t_i = convert(index->type());
592
593 if (auto size = Idx::isa(index->type())) {
594 if (auto w = Idx::size2bitwidth(size); w && *w < 64) {
595 v_i = bb.assign(name + ".zext",
596 "zext {} {} to i{} ; add one more bit for gep index as it is treated as signed value",
597 t_i, v_i, *w + 1);
598 t_i = "i" + std::to_string(*w + 1);
599 }
600 }
601
602 return std::pair(v_i, t_i);
603 };
604
605 if (auto lit = def->isa<Lit>()) {
606 if (lit->type()->isa<Nat>() || Idx::isa(lit->type())) {
607 return std::to_string(lit->get());
608 } else if (auto w = math::isa_f(lit->type())) {
609 std::stringstream s;
610 u64 hex;
611
612 switch (*w) {
613 case 16:
614 s << "0xH" << std::setfill('0') << std::setw(4) << std::right << std::hex << lit->get<u16>();
615 return s.str();
616 case 32: {
617 hex = std::bit_cast<u64>(f64(lit->get<f32>()));
618 break;
619 }
620 case 64: hex = lit->get<u64>(); break;
621 default: fe::unreachable();
622 }
623
624 s << "0x" << std::setfill('0') << std::setw(16) << std::right << std::hex << hex;
625 return s.str();
626 }
627 fe::unreachable();
628 } else if (def->isa<Bot>()) {
629 return "undef";
630 } else if (auto top = def->isa<Top>()) {
631 if (Axm::isa<mem::M>(top->type())) return {};
632 // bail out to error below
633 } else if (auto tuple = def->isa<Tuple>()) {
634 return emit_tuple(tuple);
635 } else if (auto pack = def->isa<Pack>()) {
636 if (auto lit = Lit::isa(pack->body()); lit && *lit == 0) return "zeroinitializer";
637 return emit_tuple(pack);
638 } else if (auto sel = Select(def)) {
639 auto t = convert(sel.extract()->type());
640 auto [elem_a, elem_b] = sel.pair()->projs<2>([&](auto e) { return emit_unsafe(e); });
641 auto cond_t = convert(sel.cond()->type());
642 auto cond = emit(sel.cond());
643 return bb.assign(name, "select {} {}, {} {}, {} {}", cond_t, cond, t, elem_b, t, elem_a);
644 } else if (auto extract = def->isa<Extract>()) {
645 auto tuple = extract->tuple();
646 auto index = extract->index();
647 auto v_tup = emit_unsafe(tuple);
648 if (is_simd(tuple->type()) && !Axm::isa<mem::M>(tuple->type())) return v_tup;
649
650 // this exact location is important: after emitting the tuple -> ordering of mem ops
651 // before emitting the index, as it might be a weird value for mem vars.
652 if (Axm::isa<mem::M>(extract->type())) return {};
653
654 auto t_tup = convert(tuple->type());
655 if (auto li = Lit::isa(index)) {
656 if (isa_mem_sigma_2(tuple->type())) return v_tup;
657 // Adjust index, if mem is present.
658 auto v_i = Axm::isa<mem::M>(tuple->proj(0)->type()) ? std::to_string(*li - 1) : std::to_string(*li);
659
660 return bb.assign(name, "extractvalue {} {}, {}", t_tup, v_tup, v_i);
661 }
662
663 auto t_elem = convert(extract->type());
664 auto [v_i, t_i] = emit_gep_index(index);
665
666 print(lam2bb_[root()].body().emplace_front(),
667 "{}.alloca = alloca {} ; copy to alloca to emulate extract with store + gep + load", name, t_tup);
668 print(bb.body().emplace_back(), "store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup, name);
669 print(bb.body().emplace_back(), "{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}", name, t_tup,
670 t_tup, name, t_i, v_i);
671 return bb.assign(name, "load {}, {}* {}.gep", t_elem, t_elem, name);
672 } else if (auto insert = def->isa<Insert>()) {
673 assert(!Axm::isa<mem::M>(insert->tuple()->proj(0)->type()));
674 auto t_tup = convert(insert->tuple()->type());
675 auto t_val = convert(insert->value()->type());
676 auto v_tup = emit(insert->tuple());
677 auto v_val = emit(insert->value());
678 if (auto idx = Lit::isa(insert->index())) {
679 auto v_idx = emit(insert->index());
680 if (is_simd(insert->tuple()->type()))
681
682 return bb.assign(name, "insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val, v_idx);
683 else
684
685 return bb.assign(name, " insertvalue {} {}, {} {}, {}", t_tup, v_tup, t_val, v_val, v_idx);
686 } else {
687 if (is_simd(insert->tuple()->type())) {
688 auto v_i = emit(insert->index());
689 auto t_i = convert(insert->index()->type());
690 if (t_i != "i32") {
691 auto w_src = *Idx::size2bitwidth(Idx::isa(insert->index()->type()));
692 v_i = bb.assign(name + ".idx", "{} {} {} to i32", w_src < 32 ? "zext" : "trunc", t_i, v_i);
693 }
694 return bb.assign(name, "insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val, v_i);
695 }
696 auto t_elem = convert(insert->value()->type());
697 auto [v_i, t_i] = emit_gep_index(insert->index());
698 print(lam2bb_[root()].body().emplace_front(),
699 "{}.alloca = alloca {} ; copy to alloca to emulate insert with store + gep + load", name, t_tup);
700 print(bb.body().emplace_back(), "store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup, name);
701 print(bb.body().emplace_back(), "{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}", name,
702 t_tup, t_tup, name, t_i, v_i);
703 print(bb.body().emplace_back(), "store {} {}, {}* {}.gep", t_val, v_val, t_val, name);
704 return bb.assign(name, "load {}, {}* {}.alloca", t_tup, t_tup, name);
705 }
706 } else if (auto global = def->isa<Global>()) {
707 auto v_init = emit(global->init());
708 auto [pointee, addr_space] = Axm::as<mem::Ptr>(global->type())->args<2>();
709 print(vars_decls_, "{} = global {} {}\n", name, convert(pointee), v_init);
710 return globals_[global] = name;
711 } else if (auto nat = Axm::isa<core::nat>(def)) {
712 auto [a, b] = nat->args<2>([this](auto def) { return emit(def); });
713
714 switch (nat.id()) {
715 case core::nat::add: op = "add"; break;
716 case core::nat::sub: op = "sub"; break;
717 case core::nat::mul: op = "mul"; break;
718 }
719
720 return bb.assign(name, "{} nsw nuw i64 {}, {}", op, a, b);
721 } else if (auto ncmp = Axm::isa<core::ncmp>(def)) {
722 auto [a, b] = ncmp->args<2>([this](auto def) { return emit(def); });
723 op = "icmp ";
724
725 switch (ncmp.id()) {
726 // clang-format off
727 case core::ncmp::e: op += "eq" ; break;
728 case core::ncmp::ne: op += "ne" ; break;
729 case core::ncmp::g: op += "ugt"; break;
730 case core::ncmp::ge: op += "uge"; break;
731 case core::ncmp::l: op += "ult"; break;
732 case core::ncmp::le: op += "ule"; break;
733 // clang-format on
734 default: fe::unreachable();
735 }
736
737 return bb.assign(name, "{} i64 {}, {}", op, a, b);
738 } else if (auto idx = Axm::isa<core::idx>(def)) {
739 auto x = emit(idx->arg());
740 auto s = *Idx::size2bitwidth(Idx::isa(idx->type()));
741 auto t = convert(idx->type());
742 if (s < 64) return bb.assign(name, "trunc i64 {} to {}", x, t);
743 return x;
744 } else if (auto bit1 = Axm::isa<core::bit1>(def)) {
745 assert(bit1.id() == core::bit1::neg);
746 auto x = emit(bit1->arg());
747 auto t = convert(bit1->type());
748 return bb.assign(name, "xor {} -1, {}", t, x);
749 } else if (auto bit2 = Axm::isa<core::bit2>(def)) {
750 auto [a, b] = bit2->args<2>([this](auto def) { return emit(def); });
751 auto t = convert(bit2->type());
752
753 auto neg = [&](std::string_view x) { return bb.assign(name + ".neg", "xor {} -1, {}", t, x); };
754
755 switch (bit2.id()) {
756 // clang-format off
757 case core::bit2::and_: return bb.assign(name, "and {} {}, {}", t, a, b);
758 case core::bit2:: or_: return bb.assign(name, "or {} {}, {}", t, a, b);
759 case core::bit2::xor_: return bb.assign(name, "xor {} {}, {}", t, a, b);
760 case core::bit2::nand: return neg(bb.assign(name, "and {} {}, {}", t, a, b));
761 case core::bit2:: nor: return neg(bb.assign(name, "or {} {}, {}", t, a, b));
762 case core::bit2::nxor: return neg(bb.assign(name, "xor {} {}, {}", t, a, b));
763 case core::bit2:: iff: return bb.assign(name, "and {} {}, {}", neg(a), b);
764 case core::bit2::niff: return bb.assign(name, "or {} {}, {}", neg(a), b);
765 // clang-format on
766 default: fe::unreachable();
767 }
768 } else if (auto shr = Axm::isa<core::shr>(def)) {
769 auto [a, b] = shr->args<2>([this](auto def) { return emit(def); });
770 auto t = convert(shr->type());
771
772 switch (shr.id()) {
773 case core::shr::a: op = "ashr"; break;
774 case core::shr::l: op = "lshr"; break;
775 }
776
777 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
778 } else if (auto wrap = Axm::isa<core::wrap>(def)) {
779 auto [mode, ab] = wrap->uncurry_args<2>();
780 auto [a, b] = ab->projs<2>([this](auto def) { return emit(def); });
781 auto t = convert(wrap->type());
782 auto lmode = Lit::as(mode);
783
784 switch (wrap.id()) {
785 case core::wrap::add: op = "add"; break;
786 case core::wrap::sub: op = "sub"; break;
787 case core::wrap::mul: op = "mul"; break;
788 case core::wrap::shl: op = "shl"; break;
789 }
790
791 if (lmode & core::Mode::nuw) op += " nuw";
792 if (lmode & core::Mode::nsw) op += " nsw";
793
794 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
795 } else if (auto div = Axm::isa<core::div>(def)) {
796 auto [m, xy] = div->args<2>();
797 auto [x, y] = xy->projs<2>();
798 auto t = convert(x->type());
799 emit_unsafe(m);
800 auto a = emit(x);
801 auto b = emit(y);
802
803 switch (div.id()) {
804 case core::div::sdiv: op = "sdiv"; break;
805 case core::div::udiv: op = "udiv"; break;
806 case core::div::srem: op = "srem"; break;
807 case core::div::urem: op = "urem"; break;
808 }
809
810 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
811 } else if (auto icmp = Axm::isa<core::icmp>(def)) {
812 auto [a, b] = icmp->args<2>([this](auto def) { return emit(def); });
813 auto t = convert(icmp->arg(0)->type());
814 op = "icmp ";
815
816 switch (icmp.id()) {
817 // clang-format off
818 case core::icmp::e: op += "eq" ; break;
819 case core::icmp::ne: op += "ne" ; break;
820 case core::icmp::sg: op += "sgt"; break;
821 case core::icmp::sge: op += "sge"; break;
822 case core::icmp::sl: op += "slt"; break;
823 case core::icmp::sle: op += "sle"; break;
824 case core::icmp::ug: op += "ugt"; break;
825 case core::icmp::uge: op += "uge"; break;
826 case core::icmp::ul: op += "ult"; break;
827 case core::icmp::ule: op += "ule"; break;
828 // clang-format on
829 default: fe::unreachable();
830 }
831
832 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
833 } else if (auto extr = Axm::isa<core::extrema>(def)) {
834 auto [x, y] = extr->args<2>();
835 auto t = convert(x->type());
836 auto a = emit(x);
837 auto b = emit(y);
838 std::string f = "llvm.";
839 switch (extr.id()) {
840 case core::extrema::Sm: f += "smin."; break;
841 case core::extrema::SM: f += "smax."; break;
842 case core::extrema::sm: f += "umin."; break;
843 case core::extrema::sM: f += "umax."; break;
844 }
845 f += t;
846 declare("{} @{}({}, {})", t, f, t, t);
847 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
848 } else if (auto abs = Axm::isa<core::abs>(def)) {
849 auto [m, x] = abs->args<2>();
850 auto t = convert(x->type());
851 auto a = emit(x);
852 std::string f = "llvm.abs." + t;
853 declare("{} @{}({}, {})", t, f, t, "i1");
854 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, "i1", "1");
855 } else if (auto conv = Axm::isa<core::conv>(def)) {
856 auto v_src = emit(conv->arg());
857 auto t_src = convert(conv->arg()->type());
858 auto t_dst = convert(conv->type());
859
860 nat_t w_src = *Idx::size2bitwidth(Idx::isa(conv->arg()->type()));
861 nat_t w_dst = *Idx::size2bitwidth(Idx::isa(conv->type()));
862
863 if (w_src == w_dst) return v_src;
864
865 switch (conv.id()) {
866 case core::conv::s: op = w_src < w_dst ? "sext" : "trunc"; break;
867 case core::conv::u: op = w_src < w_dst ? "zext" : "trunc"; break;
868 }
869
870 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
871 } else if (auto bitcast = Axm::isa<core::bitcast>(def)) {
872 auto dst_type_ptr = Axm::isa<mem::Ptr>(bitcast->type());
873 auto src_type_ptr = Axm::isa<mem::Ptr>(bitcast->arg()->type());
874 auto v_src = emit(bitcast->arg());
875 auto t_src = convert(bitcast->arg()->type());
876 auto t_dst = convert(bitcast->type());
877
878 if (auto lit = Lit::isa(bitcast->arg()); lit && *lit == 0) return "zeroinitializer";
879 // clang-format off
880 if (src_type_ptr && dst_type_ptr) return bb.assign(name, "bitcast {} {} to {}", t_src, v_src, t_dst);
881 if (src_type_ptr) return bb.assign(name, "ptrtoint {} {} to {}", t_src, v_src, t_dst);
882 if (dst_type_ptr) return bb.assign(name, "inttoptr {} {} to {}", t_src, v_src, t_dst);
883 // clang-format on
884
885 auto size2width = [&](const Def* type) {
886 if (type->isa<Nat>()) return 64_n;
887 if (auto size = Idx::isa(type)) return *Idx::size2bitwidth(size);
888 return 0_n;
889 };
890
891 auto src_size = size2width(bitcast->arg()->type());
892 auto dst_size = size2width(bitcast->type());
893
894 op = "bitcast";
895 if (src_size && dst_size) {
896 if (src_size == dst_size) return v_src;
897 op = (src_size < dst_size) ? "zext" : "trunc";
898 }
899 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
900 } else if (auto lea = Axm::isa<mem::lea>(def)) {
901 auto [ptr, i] = lea->args<2>();
902 auto pointee = Axm::as<mem::Ptr>(ptr->type())->arg(0);
903 auto v_ptr = emit(ptr);
904 auto t_pointee = convert(pointee);
905 auto t_ptr = convert(ptr->type());
906 if (pointee->isa<Sigma>())
907 return bb.assign(name, "getelementptr inbounds {}, {} {}, i64 0, i32 {}", t_pointee, t_ptr, v_ptr,
908 Lit::as(i));
909
910 assert(pointee->isa<Arr>());
911 auto [v_i, t_i] = emit_gep_index(i);
912
913 return bb.assign(name, "getelementptr inbounds {}, {} {}, i64 0, {} {}", t_pointee, t_ptr, v_ptr, t_i, v_i);
914 } else if (auto malloc = Axm::isa<mem::malloc>(def)) {
915 declare("i8* @malloc(i64)");
916
917 emit_unsafe(malloc->arg(0));
918 auto size = emit(malloc->arg(1));
919 auto ptr_t = convert(Axm::as<mem::Ptr>(def->proj(1)->type()));
920 bb.assign(name + "i8", "call i8* @malloc(i64 {})", size);
921 return bb.assign(name, "bitcast i8* {} to {}", name + "i8", ptr_t);
922 } else if (auto free = Axm::isa<mem::free>(def)) {
923 declare("void @free(i8*)");
924 emit_unsafe(free->arg(0));
925 auto ptr = emit(free->arg(1));
926 auto ptr_t = convert(Axm::as<mem::Ptr>(free->arg(1)->type()));
927
928 bb.assign(name + "i8", "bitcast {} {} to i8*", ptr_t, ptr);
929 bb.tail("call void @free(i8* {})", name + "i8");
930 return {};
931 } else if (auto mslot = Axm::isa<mem::mslot>(def)) {
932 auto [Ta, msi] = mslot->uncurry_args<2>();
933 auto [pointee, addr_space] = Ta->projs<2>();
934 auto [mem, _, __] = msi->projs<3>();
935 emit_unsafe(mslot->arg(0));
936 // TODO array with size
937 // auto v_size = emit(mslot->arg(1));
938 print(bb.body().emplace_back(), "{} = alloca {}", name, convert(pointee, false));
939 return name;
940 } else if (auto free = Axm::isa<mem::free>(def)) {
941 declare("void @free(i8*)");
942
943 emit_unsafe(free->arg(0));
944 auto v_ptr = emit(free->arg(1));
945 auto t_ptr = convert(Axm::as<mem::Ptr>(free->arg(1)->type()));
946
947 bb.assign(name + "i8", "bitcast {} {} to i8*", t_ptr, v_ptr);
948 bb.tail("call void @free(i8* {})", name + "i8");
949 return {};
950 } else if (auto load = Axm::isa<mem::load>(def)) {
951 emit_unsafe(load->arg(0));
952 auto v_ptr = emit(load->arg(1));
953 auto t_ptr = convert(load->arg(1)->type());
954 auto t_pointee = convert(Axm::as<mem::Ptr>(load->arg(1)->type())->arg(0), false);
955 return bb.assign(name, "load {}, {} {}", t_pointee, t_ptr, v_ptr);
956 } else if (auto store = Axm::isa<mem::store>(def)) {
957 emit_unsafe(store->arg(0));
958 auto v_ptr = emit(store->arg(1));
959 auto v_val = emit(store->arg(2));
960 auto t_ptr = convert(store->arg(1)->type());
961 auto t_val = convert(store->arg(2)->type(), false);
962 print(bb.body().emplace_back(), "store {} {}, {} {}", t_val, v_val, t_ptr, v_ptr);
963 return {};
964 } else if (auto q = Axm::isa<clos::alloc_jmpbuf>(def)) {
965 declare("i64 @jmpbuf_size()");
966
967 emit_unsafe(q->arg());
968 auto size = name + ".size";
969 bb.assign(size, "call i64 @jmpbuf_size()");
970 return bb.assign(name, "alloca i8, i64 {}", size);
971 } else if (auto setjmp = Axm::isa<clos::setjmp>(def)) {
972 declare("i32 @_setjmp(i8*) returns_twice");
973
974 auto [mem, jmpbuf] = setjmp->arg()->projs<2>();
975 emit_unsafe(mem);
976 auto v_jb = emit(jmpbuf);
977 return bb.assign(name, "call i32 @_setjmp(i8* {})", v_jb);
978 } else if (auto arith = Axm::isa<math::arith>(def)) {
979 auto [mode, ab] = arith->uncurry_args<2>();
980 auto [a, b] = ab->projs<2>([this](auto def) { return emit(def); });
981 auto t = convert(arith->type());
982 auto lmode = Lit::as(mode);
983
984 switch (arith.id()) {
985 case math::arith::add: op = "fadd"; break;
986 case math::arith::sub: op = "fsub"; break;
987 case math::arith::mul: op = "fmul"; break;
988 case math::arith::div: op = "fdiv"; break;
989 case math::arith::rem: op = "frem"; break;
990 }
991
992 if (lmode == math::Mode::fast)
993 op += " fast";
994 else {
995 // clang-format off
996 if (lmode & math::Mode::nnan ) op += " nnan";
997 if (lmode & math::Mode::ninf ) op += " ninf";
998 if (lmode & math::Mode::nsz ) op += " nsz";
999 if (lmode & math::Mode::arcp ) op += " arcp";
1000 if (lmode & math::Mode::contract) op += " contract";
1001 if (lmode & math::Mode::afn ) op += " afn";
1002 if (lmode & math::Mode::reassoc ) op += " reassoc";
1003 // clang-format on
1004 }
1005
1006 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
1007 } else if (auto tri = Axm::isa<math::tri>(def)) {
1008 auto a = emit(tri->arg());
1009 auto t = convert(tri->type());
1010
1011 std::string f;
1012
1013 if (tri.id() == math::tri::sin) {
1014 f = "llvm.sin"s + llvm_suffix(tri->type());
1015 } else if (tri.id() == math::tri::cos) {
1016 f = "llvm.cos"s + llvm_suffix(tri->type());
1017 } else {
1018 if (tri.sub() & sub_t(math::tri::a)) f += "a";
1019
1020 switch (math::tri((tri.id() & 0x3) | Annex::base<math::tri>())) {
1021 case math::tri::sin: f += "sin"; break;
1022 case math::tri::cos: f += "cos"; break;
1023 case math::tri::tan: f += "tan"; break;
1024 case math::tri::ahFF: error("this axm is supposed to be unused");
1025 default: fe::unreachable();
1026 }
1027
1028 if (tri.sub() & sub_t(math::tri::h)) f += "h";
1029 f += math_suffix(tri->type());
1030 }
1031
1032 declare("{} @{}({})", t, f, t);
1033 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1034 } else if (auto extrema = Axm::isa<math::extrema>(def)) {
1035 auto [a, b] = extrema->args<2>([this](auto def) { return emit(def); });
1036 auto t = convert(extrema->type());
1037 std::string f = "llvm.";
1038 switch (extrema.id()) {
1039 case math::extrema::fmin: f += "minnum"; break;
1040 case math::extrema::fmax: f += "maxnum"; break;
1041 case math::extrema::ieee754min: f += "minimum"; break;
1042 case math::extrema::ieee754max: f += "maximum"; break;
1043 }
1044 f += llvm_suffix(extrema->type());
1045
1046 declare("{} @{}({}, {})", t, f, t, t);
1047 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
1048 } else if (auto pow = Axm::isa<math::pow>(def)) {
1049 auto [a, b] = pow->args<2>([this](auto def) { return emit(def); });
1050 auto t = convert(pow->type());
1051 std::string f = "llvm.pow";
1052 f += llvm_suffix(pow->type());
1053 declare("{} @{}({}, {})", t, f, t, t);
1054 return bb.assign(name, "tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
1055 } else if (auto rt = Axm::isa<math::rt>(def)) {
1056 auto a = emit(rt->arg());
1057 auto t = convert(rt->type());
1058 std::string f;
1059 if (rt.id() == math::rt::sq)
1060 f = "llvm.sqrt"s + llvm_suffix(rt->type());
1061 else
1062 f = "cbrt"s += math_suffix(rt->type());
1063 declare("{} @{}({})", t, f, t);
1064 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1065 } else if (auto exp = Axm::isa<math::exp>(def)) {
1066 auto a = emit(exp->arg());
1067 auto t = convert(exp->type());
1068 std::string f = "llvm.";
1069 f += (exp.sub() & sub_t(math::exp::log)) ? "log" : "exp";
1070 f += (exp.sub() & sub_t(math::exp::bin)) ? "2" : (exp.sub() & sub_t(math::exp::dec)) ? "10" : "";
1071 f += llvm_suffix(exp->type());
1072 // TODO doesn't work for exp10"
1073 declare("{} @{}({})", t, f, t);
1074 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1075 } else if (auto er = Axm::isa<math::er>(def)) {
1076 auto a = emit(er->arg());
1077 auto t = convert(er->type());
1078 auto f = er.id() == math::er::f ? "erf"s : "erfc"s;
1079 f += math_suffix(er->type());
1080 declare("{} @{}({})", t, f, t);
1081 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1082 } else if (auto gamma = Axm::isa<math::gamma>(def)) {
1083 auto a = emit(gamma->arg());
1084 auto t = convert(gamma->type());
1085 std::string f = gamma.id() == math::gamma::t ? "tgamma" : "lgamma";
1086 f += math_suffix(gamma->type());
1087 declare("{} @{}({})", t, f, t);
1088 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1089 } else if (auto cmp = Axm::isa<math::cmp>(def)) {
1090 auto [a, b] = cmp->args<2>([this](auto def) { return emit(def); });
1091 auto t = convert(cmp->arg(0)->type());
1092 op = "fcmp ";
1093
1094 switch (cmp.id()) {
1095 // clang-format off
1096 case math::cmp:: e: op += "oeq"; break;
1097 case math::cmp:: l: op += "olt"; break;
1098 case math::cmp:: le: op += "ole"; break;
1099 case math::cmp:: g: op += "ogt"; break;
1100 case math::cmp:: ge: op += "oge"; break;
1101 case math::cmp:: ne: op += "one"; break;
1102 case math::cmp:: o: op += "ord"; break;
1103 case math::cmp:: u: op += "uno"; break;
1104 case math::cmp:: ue: op += "ueq"; break;
1105 case math::cmp:: ul: op += "ult"; break;
1106 case math::cmp::ule: op += "ule"; break;
1107 case math::cmp:: ug: op += "ugt"; break;
1108 case math::cmp::uge: op += "uge"; break;
1109 case math::cmp::une: op += "une"; break;
1110 // clang-format on
1111 default: fe::unreachable();
1112 }
1113
1114 return bb.assign(name, "{} {} {}, {}", op, t, a, b);
1115 } else if (auto conv = Axm::isa<math::conv>(def)) {
1116 auto v_src = emit(conv->arg());
1117 auto t_src = convert(conv->arg()->type());
1118 auto t_dst = convert(conv->type());
1119
1120 auto s_src = math::isa_f(conv->arg()->type());
1121 auto s_dst = math::isa_f(conv->type());
1122
1123 switch (conv.id()) {
1124 case math::conv::f2f: op = s_src < s_dst ? "fpext" : "fptrunc"; break;
1125 case math::conv::s2f: op = "sitofp"; break;
1126 case math::conv::u2f: op = "uitofp"; break;
1127 case math::conv::f2s: op = "fptosi"; break;
1128 case math::conv::f2u: op = "fptoui"; break;
1129 }
1130
1131 return bb.assign(name, "{} {} {} to {}", op, t_src, v_src, t_dst);
1132 } else if (auto abs = Axm::isa<math::abs>(def)) {
1133 auto a = emit(abs->arg());
1134 auto t = convert(abs->type());
1135 std::string f = "llvm.fabs";
1136 f += llvm_suffix(abs->type());
1137 declare("{} @{}({})", t, f, t);
1138 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1139 } else if (auto round = Axm::isa<math::round>(def)) {
1140 auto a = emit(round->arg());
1141 auto t = convert(round->type());
1142 std::string f = "llvm.";
1143 switch (round.id()) {
1144 case math::round::f: f += "floor"; break;
1145 case math::round::c: f += "ceil"; break;
1146 case math::round::r: f += "round"; break;
1147 case math::round::t: f += "trunc"; break;
1148 }
1149 f += llvm_suffix(round->type());
1150 declare("{} @{}({})", t, f, t);
1151 return bb.assign(name, "tail call {} @{}({} {})", t, f, t, a);
1152 } else if (auto zip = Axm::isa<vec::zip>(def)) {
1153 auto ni_n = zip->decurry()->decurry()->decurry()->arg();
1154 auto nat_ni = *Lit::isa(ni_n->proj(2, 0));
1155 auto f = zip->decurry()->arg();
1156 auto inputs = zip->arg();
1157 auto t_in = convert(inputs->proj(nat_ni, 0)->type());
1158 auto t_out = convert(def->type()); // <n x T>
1159
1160 std::string op;
1161 std::string prev;
1162
1163 if (auto nat_op = Axm::isa<core::nat, 1>(f)) {
1164 switch (nat_op.id()) {
1165 case core::nat::add: op = "add nuw nsw"; break;
1166 case core::nat::sub: op = "sub nuw nsw"; break;
1167 case core::nat::mul: op = "mul nuw nsw"; break;
1168 }
1169 } else if (auto arith_op = Axm::isa<math::arith, 1>(f)) {
1170 auto lmode = Lit::as(f->as<App>()->arg());
1171 switch (arith_op.id()) {
1172 case math::arith::add: op = "fadd"; break;
1173 case math::arith::sub: op = "fsub"; break;
1174 case math::arith::mul: op = "fmul"; break;
1175 case math::arith::div: op = "fdiv"; break;
1176 case math::arith::rem: op = "frem"; break;
1177 }
1178
1179 if (lmode == math::Mode::fast)
1180 op += " fast";
1181 else {
1182 if (lmode & math::Mode::nnan) op += " nnan";
1183 if (lmode & math::Mode::ninf) op += " ninf";
1184 if (lmode & math::Mode::nsz) op += " nsz";
1185 if (lmode & math::Mode::arcp) op += " arcp";
1186 if (lmode & math::Mode::contract) op += " contract";
1187 if (lmode & math::Mode::afn) op += " afn";
1188 if (lmode & math::Mode::reassoc) op += " reassoc";
1189 }
1190 } else if (auto ncmp_op = Axm::isa<core::ncmp, 1>(f)) {
1191 op = "icmp ";
1192 switch (ncmp_op.id()) {
1193 case core::ncmp::e: op += "eq"; break;
1194 case core::ncmp::ne: op += "ne"; break;
1195 case core::ncmp::g: op += "ugt"; break;
1196 case core::ncmp::ge: op += "uge"; break;
1197 case core::ncmp::l: op += "ult"; break;
1198 case core::ncmp::le: op += "ule"; break;
1199 default: fe::unreachable();
1200 }
1201 } else if (auto icmp_op = Axm::isa<core::icmp, 1>(f)) {
1202 op = "icmp ";
1203 switch (icmp_op.id()) {
1204 case core::icmp::e: op += "eq"; break;
1205 case core::icmp::ne: op += "ne"; break;
1206 case core::icmp::sg: op += "sgt"; break;
1207 case core::icmp::sge: op += "sge"; break;
1208 case core::icmp::sl: op += "slt"; break;
1209 case core::icmp::sle: op += "sle"; break;
1210 case core::icmp::ug: op += "ugt"; break;
1211 case core::icmp::uge: op += "uge"; break;
1212 case core::icmp::ul: op += "ult"; break;
1213 case core::icmp::ule: op += "ule"; break;
1214 default: fe::unreachable();
1215 }
1216 } else if (auto mcmp_op = Axm::isa<math::cmp, 1>(f)) {
1217 op = "fcmp ";
1218 switch (mcmp_op.id()) {
1219 case math::cmp::e: op += "oeq"; break;
1220 case math::cmp::l: op += "olt"; break;
1221 case math::cmp::le: op += "ole"; break;
1222 case math::cmp::g: op += "ogt"; break;
1223 case math::cmp::ge: op += "oge"; break;
1224 case math::cmp::ne: op += "one"; break;
1225 case math::cmp::o: op += "ord"; break;
1226 case math::cmp::u: op += "uno"; break;
1227 case math::cmp::ue: op += "ueq"; break;
1228 case math::cmp::ul: op += "ult"; break;
1229 case math::cmp::ule: op += "ule"; break;
1230 case math::cmp::ug: op += "ugt"; break;
1231 case math::cmp::uge: op += "uge"; break;
1232 case math::cmp::une: op += "une"; break;
1233 default: fe::unreachable();
1234 }
1235 } else {
1236 error("unhandled vec.zip operation: {}", f);
1237 }
1238
1239 auto v1 = emit(inputs->proj(nat_ni, 0));
1240 auto v2 = emit(inputs->proj(nat_ni, 1));
1241 prev = bb.assign(name, "{} {} {}, {}", op, t_in, v1, v2);
1242 return prev;
1243 error("unhandled vec.zip operation: {}", f);
1244 }
1245 error("unhandled def in LLVM backend: {} : {}", def, def->type());
1246}
1247
1248void emit(World& world, std::ostream& ostream) {
1249 Emitter emitter(world, ostream);
1250 emitter.run();
1251}
1252
1253int compile(World& world, std::string name) {
1254#ifdef _WIN32
1255 auto exe = name + ".exe"s;
1256#else
1257 auto exe = name;
1258#endif
1259 return compile(world, name + ".ll"s, exe);
1260}
1261
1262int compile(World& world, std::string ll, std::string out) {
1263 std::ofstream ofs(ll);
1264 emit(world, ofs);
1265 ofs.close();
1266 auto cmd = fmt("clang \"{}\" -o \"{}\" -Wno-override-module", ll, out);
1267 return sys::system(cmd);
1268}
1269
1270int compile_and_run(World& world, std::string name, std::string args) {
1271 if (compile(world, name) == 0) return sys::run(name, args);
1272 error("compilation failed");
1273}
1274
1275} // namespace mim::ll
const Def * arg() const
Definition lam.h:285
A (possibly paramterized) Array.
Definition tuple.h:117
static auto isa(const Def *def)
Definition axm.h:107
static auto as(const Def *def)
Definition axm.h:130
Matches (ff, tt)#cond arg where cond is not a Literal.
Definition tuple.h:279
Lam * root() const
Definition phase.h:304
Base class for all Defs.
Definition def.h:251
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:587
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:486
const Def * var(nat_t a, nat_t i) noexcept
Definition def.h:429
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
Definition def.h:390
nat_t num_vars() noexcept
Definition def.h:429
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:446
bool is_external() const noexcept
Definition def.h:467
auto vars(F f) noexcept
Definition def.h:429
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:578
Matches a dispatch through a jump table of the form: (target_0, target_1, ...)#index arg where index ...
Definition tuple.h:304
Extracts from a Sigma or Array-typed Extract::tuple the element at position Extract::index.
Definition tuple.h:206
static constexpr nat_t size2bitwidth(nat_t n)
Definition def.h:893
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
Definition def.cpp:610
Creates a new Tuple / Pack by inserting Insert::value at position Insert::index into Insert::tuple.
Definition tuple.h:233
A function.
Definition lam.h:110
static Lam * isa_mut_basicblock(const Def *d)
Only for mutables.
Definition lam.h:145
const Pi * type() const
Definition lam.h:130
const Def * body() const
Definition lam.h:123
static std::optional< T > isa(const Def *def)
Definition def.h:826
static T as(const Def *def)
Definition def.h:832
const Nest & nest() const
Definition phase.h:320
A (possibly paramterized) Tuple.
Definition tuple.h:166
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
Definition phase.cpp:13
virtual void start()=0
Actual entry.
A dependent function type.
Definition lam.h:14
const Pi * ret_pi() const
Yields the last Pi::dom, if Pi::isa_basicblock.
Definition lam.cpp:13
static const Pi * isa_returning(const Def *d)
Is this a continuation (Pi::isa_cn) which has a Pi::ret_pi?
Definition lam.h:49
static Schedule schedule(const Nest &)
Definition schedule.cpp:118
Matches (ff, tt)#cond - where cond is not a Literal.
Definition tuple.h:261
A dependent tuple type.
Definition tuple.h:20
World & world()
Definition pass.h:64
std::string_view name() const
Definition pass.h:67
Data constructor for a Sigma.
Definition tuple.h:68
A variable introduced by a binder (mutable).
Definition def.h:702
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:31
void emit_imported(Lam *)
Definition ll.cpp:288
Emitter(World &world, std::ostream &ostream)
Definition ll.cpp:113
std::string prepare()
Definition ll.cpp:302
void finalize()
Definition ll.cpp:322
void start() override
Actual entry.
Definition ll.cpp:277
std::string emit_bb(BB &, const Def *)
Definition ll.cpp:534
bool is_valid(std::string_view s)
Definition ll.cpp:116
mim::Emitter< std::string, std::string, BB, Emitter > Super
Definition ll.cpp:111
void emit_epilogue(Lam *)
Definition ll.cpp:368
void declare(const char *s, Args &&... args)
Definition ll.cpp:125
Definition ll.h:9
static const Def * find_common_simd_src(const App *app)
Definition ll.cpp:166
static std::optional< std::pair< nat_t, const Def * > > is_simd_aggregate(const std::vector< const Def * > types)
Definition ll.cpp:157
static std::optional< std::pair< nat_t, const Def * > > is_simd(const Def *type)
Definition ll.cpp:148
int compile_and_run(World &, std::string name, std::string args={})
Definition ll.cpp:1270
void emit(World &, std::ostream &)
Definition ll.cpp:1248
int compile(World &, std::string name)
Definition ll.cpp:1253
The clos Plugin
Definition clos.h:7
The core Plugin
Definition core.h:8
The math Plugin
Definition math.h:8
The mem Plugin
Definition mem.h:11
The vec Plugin
int run(std::string cmd, std::string args={})
Wraps sys::system and puts .exe at the back (Windows) and ./ at the front (otherwise) of cmd.
Definition sys.cpp:77
int system(std::string)
Wraps std::system and makes the return value usable.
Definition sys.cpp:71
u64 nat_t
Definition types.h:44
Vector< const Def * > DefVec
Definition def.h:77
u8 sub_t
Definition types.h:49
D bitcast(const S &src)
A bitcast from src of type S to D.
Definition util.h:23
std::ostream & print(std::ostream &os, const char *s)
Base case.
Definition print.cpp:5
double f64
Definition types.h:42
std::string fmt(const char *s, Args &&... args)
Wraps mim::print to output a formatted std:string.
Definition print.h:163
void error(Loc loc, const char *f, Args &&... args)
Definition dbg.h:125
float f32
Definition types.h:41
GIDMap< const Def *, To > DefMap
Definition def.h:73
TExt< true > Top
Definition lattice.h:172
GIDMap< Lam *, To > LamMap
Definition lam.h:219
TExt< false > Bot
Definition lattice.h:171
uint64_t u64
Definition types.h:35
@ Nat
Definition def.h:114
@ Pi
Definition def.h:114
@ Arr
Definition def.h:114
@ Sigma
Definition def.h:114
uint16_t u16
Definition types.h:35
static consteval flags_t base()
Definition plugin.h:119
std::deque< std::ostringstream > & tail()
Definition ll.cpp:86
void tail(const char *s, Args &&... args)
Definition ll.cpp:95
DefMap< std::deque< std::pair< std::string, std::string > > > phis
Definition ll.cpp:105
BB(const BB &)=delete
BB()=default
std::deque< std::ostringstream > & body()
Definition ll.cpp:85
BB(BB &&other) noexcept=default
friend void swap(BB &a, BB &b) noexcept
Definition ll.cpp:99
std::array< std::deque< std::ostringstream >, 3 > parts
Definition ll.cpp:106
std::deque< std::ostringstream > & head()
Definition ll.cpp:84
BB & operator=(BB other) noexcept
Definition ll.cpp:82
std::string assign(std::string_view name, const char *s, Args &&... args)
Definition ll.cpp:89