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