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