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