17using namespace std::string_literals;
19namespace clos = mim::plug::clos;
20namespace core = mim::plug::core;
21namespace vecp = mim::plug::vec;
33 world().
log().d(
"ll backend args: {}", fe::Join(
args()));
36 auto path =
name +
".ll"s;
41 auto emitter =
Emitter(
world(),
"llvm_emitter", *out.os());
55std::string Emitter::convert_impl(
const Def* type,
bool simd) {
56 if (
auto i =
types_.find(type); i !=
types_.end())
return i->second;
62 if (type->isa<
Nat>()) {
63 return types_[type] =
"i64";
68 return types_[type] =
"i" + std::to_string(std::max<nat_t>(1, 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);
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));
86 std::print(s,
"[{} x {}]", size,
convert(arr->body(),
false));
88 }
else if (
auto pi =
type->isa<Pi>()) {
92 if (
auto t = detail::isa_mem_sigma_2(pi->dom()))
95 auto doms = pi->doms();
96 for (
auto sep =
"";
auto dom : doms.view().rsubspan(1)) {
103 }
else if (
auto t = detail::isa_mem_sigma_2(type)) {
105 }
else if (
auto sigma =
type->isa<Sigma>()) {
106 if (sigma->isa_mut()) {
109 std::print(s,
"{} = type",
name);
113 for (
auto sep =
"";
auto t : sigma->ops()) {
120 fe::throwf(
MIM_LL_BE "cannot convert type `{}` to LLVM", type);
125 if (
s.str().empty()) fe::throwf(
MIM_LL_BE "empty type declaration for `{}`", type);
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);
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);
145 std::print(
func_impls_,
"{}:\n", lam->unique_name());
148 for (
const auto& part : bb.parts)
149 for (
const auto& line : part)
176void Emitter::emit_epilogue_impl(Lam* lam) {
177 auto app = lam->
body()->expect<
App>(
"an application in tail position");
182 if (app->callee() ==
root()->ret_var()) {
183 fe::Vector<std::string> values;
185 for (
auto arg : app->args()) {
187 values.emplace_back(val);
188 types.emplace_back(arg->type());
192 switch (values.size()) {
193 case 0:
return bb.tail(
"ret void");
196 : bb.tail(
"ret {} {}",
convert(types[0]), values[0]);
201 if (
auto se = is_simd_aggregate(types)) {
202 auto common_src = find_common_simd_src(app);
204 auto v_src =
emit(common_src);
205 auto t =
convert(common_src->type());
206 return bb.tail(
"ret {} {}", t, v_src);
208 auto [
size, elem] = *se;
211 type = std::format(
"<{} x {}>", size, val_t);
212 for (
auto val : values) {
217 prev += std::format(
"{} {}", val_t, val);
223 for (
size_t i = 0, n = values.size(); i != n; ++i) {
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);
232 bb.tail(
"ret {} {}", type, prev);
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);
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));
249 if (
auto branch =
Branch(app))
return bb.tail(
"br i1 {}, label {}, label {}", v_index, bbs[1], bbs[0]);
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");
260 if (
auto common_src = find_common_simd_src(app)) {
261 auto v_src =
emit(common_src);
262 auto callee_var = callee->var();
266 for (
auto var : callee->vars())
275 if (callee->is_set()) {
276 if (
auto head = callee->body()->isa<App>()) {
279 return bb.tail(
"br label {}, !llvm.loop !{}",
id(callee), it->second);
283 return bb.tail(
"br label {}",
id(callee));
286 declare(
"void @longjmp(i8*, i32) noreturn");
288 auto [mem, jbuf, tag] = app->args<3>();
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");
296 auto [Ta, rest] =
mslot->uncurry_args<2>();
297 auto [
pointee, addr_space] = Ta->projs<2>();
298 auto [msize, ret] = rest->projs<2>();
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);
305 return bb.tail(
"br label {}",
id(ret_lam));
307 auto v_callee =
emit(app->callee());
309 fe::Vector<std::string>
args;
310 auto app_args = app->args();
311 for (
auto arg : app_args.view().rsubspan(1))
314 if (app->args().back()->isa<
Bot>()) {
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");
322 auto ret_lam = app->args().back()->expect_mut<
Lam>(
"a return continuation");
324 for (
auto var : ret_lam->vars())
328 bb.tail(
"call void {}({})", v_callee, fe::Join(
args));
330 auto name =
"%" + app->unique_name() +
"ret";
332 bb.tail(
"{} = call {} {}({})",
name, t_ret, v_callee, fe::Join(
args));
336 return bb.tail(
"br label {}",
id(ret_lam));
340std::string Emitter::emit_tuple(
BB& bb,
const std::string& name,
const Def* tuple) {
341 if (detail::isa_mem_sigma_2(tuple->
type())) {
347 bool is_array = tuple->
type()->isa<
Arr>();
348 auto simd_array =
convert(tuple->
type()).front() ==
'<';
350 s += simd_array ?
"<" : is_array ?
"[" :
"{";
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()) {
356 s += sep + t_elem +
" " + v_elem;
361 return s += simd_array ?
">" : is_array ?
"]" :
"}";
364 std::string prev =
"undef";
366 for (
size_t src = 0, dst = 0, n = tuple->
num_projs(); src != n; ++src) {
367 auto e = tuple->
proj(n, src);
371 auto namei =
name +
"." + std::to_string(dst);
372 if (
t.front() ==
'<')
373 prev = bb.assign(namei,
"insertelement {} {}, {} {}, {} {}", t, prev, elem_t, elem, elem_t, dst);
375 prev = bb.assign(namei,
"insertvalue {} {}, {} {}, {}", t, prev, elem_t, elem, dst);
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);
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,
391 t_i =
"i" + std::to_string(*w + 1);
395 return std::pair(v_i, t_i);
398std::string Emitter::emit_lit(
const Def* def) {
399 if (
auto lit = def->isa<Lit>()) {
401 return std::to_string(
lit->get());
408 s <<
"0xH" << std::setfill(
'0') << std::setw(4) << std::right << std::hex <<
lit->get<
u16>();
411 hex = std::bit_cast<u64>(
f64(
lit->get<
f32>()));
414 case 64: hex =
lit->get<
u64>();
break;
415 default: fe::throwf(
MIM_LL_BE "unsupported floating-point width {} for literal `{}`", *w, def);
418 s <<
"0x" << std::setfill(
'0') << std::setw(16) << std::right << std::hex << hex;
421 fe::throwf(
MIM_LL_BE "cannot emit literal `{}` of type `{}`", def, def->
type());
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);
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);
434 if (def->isa<Lit>()) {
435 return emit_lit(def);
436 }
else if (def->isa<
Bot>()) {
438 }
else if (
auto top = def->isa<
Top>()) {
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());
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();
461 if (
auto sigma = extract->type()->isa<Sigma>(); sigma && sigma->num_ops() == 0)
return std::string();
465 if (detail::isa_mem_sigma_2(tuple->
type()))
return v_tup;
469 if (
auto sigma = tuple->
type()->isa<Sigma>())
470 for (
u64 i = 0; i < *li; ++i)
473 return bb.assign(
name,
"extractvalue {} {}, {}", t_tup, v_tup, v_i);
476 auto t_elem =
convert(extract->type());
477 auto [v_i, t_i] = emit_gep_index(bb,
name, index);
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>()) {
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()))
496 return bb.assign(
name,
"insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val, v_idx);
499 return bb.assign(
name,
" insertvalue {} {}, {} {}, {}", t_tup, v_tup, t_val, v_val, v_idx);
501 if (is_simd(insert->tuple()->type())) {
502 auto v_i =
emit(insert->index());
503 auto t_i =
convert(insert->index()->type());
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);
508 return bb.assign(
name,
"insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val, v_i);
510 auto t_elem =
convert(insert->value()->type());
511 auto [v_i, t_i] = emit_gep_index(bb,
name, insert->index());
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);
520 }
else if (
auto global = def->isa<Global>()) {
521 auto v_init =
emit(global->init());
529std::optional<std::string> Emitter::emit_core(
BB& bb,
const std::string& name,
const Def* def) {
532 auto [
a, b] =
nat->args<2>([
this](
auto def) {
return emit(def); });
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);
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);
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);
560 auto [
a, b] =
ncmp->args<2>([
this](
auto def) {
return emit(def); });
572 default: fe::throwf(
MIM_LL_BE "unhandled `core.ncmp` id in `{}`", def);
575 return bb.assign(
name,
"{} i64 {}, {}", op, a, b);
580 if (s < 64)
return bb.assign(
name,
"trunc i64 {} to {}", x, t);
586 return bb.assign(
name,
"xor {} -1, {}", t, x);
588 auto [
a, b] =
bit2->args<2>([
this](
auto def) {
return emit(def); });
591 auto neg = [&](std::string_view x) {
return bb.assign(
name +
".neg",
"xor {} -1, {}", t, x); };
604 default: fe::throwf(
MIM_LL_BE "unhandled `core.bit2` id in `{}`", def);
607 auto [
a, b] =
shr->args<2>([
this](
auto def) {
return emit(def); });
615 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
617 auto [
mode, _, ab] =
wrap->uncurry_args<3>();
618 auto [
a, b] = ab->projs<2>([
this](
auto def) {
return emit(def); });
632 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
634 auto [m, xy] =
div->args<2>();
635 auto [x, y] = xy->projs<2>();
648 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
650 auto [
a, b] =
icmp->args<2>([
this](
auto def) {
return emit(def); });
667 default: fe::throwf(
MIM_LL_BE "unhandled `core.icmp` id in `{}`", def);
670 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
672 auto [x, y] = extr->args<2>();
676 std::string
f =
"llvm.";
684 declare(
"{} @{}({}, {})", t, f, t, t);
685 return bb.assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
687 auto [m, x] =
abs->args<2>();
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");
701 if (w_src == w_dst)
return v_src;
708 return bb.assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
716 if (
auto lit =
Lit::isa(
bitcast->arg()); lit && *lit == 0)
return "zeroinitializer";
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);
723 auto size2width = [&](
const Def*
type) {
724 if (
type->isa<Nat>())
return 64_n;
729 auto src_size = size2width(
bitcast->arg()->type());
730 auto dst_size = size2width(
bitcast->type());
733 if (src_size && dst_size) {
734 if (src_size == dst_size)
return v_src;
735 op = (src_size < dst_size) ?
"zext" :
"trunc";
737 return bb.assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
742std::optional<std::string> Emitter::emit_mem(
BB& bb,
const std::string& name,
const Def* def) {
744 auto [ptr, i] =
lea->args<2>();
746 auto v_ptr =
emit(ptr);
747 auto t_pointee =
convert(pointee);
750 return bb.assign(
name,
"getelementptr inbounds {}, {} {}, i64 0, i32 {}", t_pointee, t_ptr, v_ptr,
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);
756 return bb.assign(
name,
"getelementptr inbounds {}, {} {}, i64 0, {} {}", t_pointee, t_ptr, v_ptr, t_i, v_i);
758 auto address_space =
malloc->decurry()->arg(1);
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);
770 return bb.assign(
name,
"bitcast {} {} to {}", i8ptr_t, i8ptr, ptr_t);
772 auto address_space =
free->decurry()->arg(1);
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();
788 return bb.assign(
name,
"load {}, {} {}", t_pointee, t_ptr, v_ptr);
795 std::print(bb.body().emplace_back(),
"store {} {}, {} {}", t_val, v_val, t_ptr, v_ptr);
796 return std::string();
804 bb.assign(size,
"call i64 @mim_jmpbuf_size()");
805 return bb.assign(
name,
"alloca i8, i64 {}", size);
807 declare(
"i32 @_setjmp(i8*) returns_twice");
809 auto [mem, jmpbuf] =
setjmp->arg()->projs<2>();
811 auto v_jb =
emit(jmpbuf);
812 return bb.assign(
name,
"call i32 @_setjmp(i8* {})", v_jb);
817std::optional<std::string> Emitter::emit_math(
BB& bb,
const std::string& name,
const Def* def) {
820 auto [
mode, _, ab] =
arith->uncurry_args<3>();
821 auto [
a, b] = ab->projs<2>([
this](
auto def) {
return emit(def); });
825 switch (
arith.id()) {
847 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
855 f = std::string(
"llvm.sin") + detail::llvm_suffix(
tri->type());
857 f = std::string(
"llvm.cos") + detail::llvm_suffix(
tri->type());
866 default: fe::throwf(
MIM_LL_BE "unhandled `math.tri` id in `{}`", def);
870 f += detail::math_suffix(
tri->type());
873 declare(
"{} @{}({})", t, f, t);
874 return bb.assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
876 auto [
a, b] =
extrema->args<2>([
this](
auto def) {
return emit(def); });
878 std::string
f =
"llvm.";
885 f += detail::llvm_suffix(
extrema->type());
887 declare(
"{} @{}({}, {})", t, f, t, t);
888 return bb.assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
890 auto [
a, b] =
pow->args<2>([
this](
auto def) {
return emit(def); });
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);
901 f = std::string(
"llvm.sqrt") + detail::llvm_suffix(
rt->type());
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);
909 std::string
f =
"llvm.";
912 f += detail::llvm_suffix(
exp->type());
914 declare(
"{} @{}({})", t, f, t);
915 return bb.assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
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);
927 f += detail::math_suffix(
gamma->type());
928 declare(
"{} @{}({})", t, f, t);
929 return bb.assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
931 auto [
a, b] =
cmp->args<2>([
this](
auto def) {
return emit(def); });
937 case math::cmp:: e:
op +=
"oeq";
break;
938 case math::cmp:: l:
op +=
"olt";
break;
940 case math::cmp:: g:
op +=
"ogt";
break;
943 case math::cmp:: o:
op +=
"ord";
break;
944 case math::cmp:: u:
op +=
"uno";
break;
952 default: fe::throwf(
MIM_LL_BE "unhandled `math.cmp` id in `{}`", def);
955 return bb.assign(
name,
"{} {} {}, {}", op, t, a, b);
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);
983 return bb.assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
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);
994 std::string
f =
"llvm.";
995 switch (
round.id()) {
1001 f += detail::llvm_suffix(
round->type());
1002 declare(
"{} @{}({})", t, f, t);
1003 return bb.assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
1005 return std::nullopt;
1008std::optional<std::string> Emitter::emit_vec(
BB& bb,
const std::string& name,
const Def* def) {
1012 return emit(v->arg());
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());
1026 switch (nat_op.id()) {
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,
1043 Lit::expect(
f->expect<App>(
"a zipped `math.arith`")->decurry()->arg(),
"a `math.arith` mode"));
1044 switch (arith_op.id()) {
1065 switch (ncmp_op.id()) {
1072 default: fe::throwf(
MIM_LL_BE "unhandled zipped `core.ncmp` id in `{}`", def);
1076 switch (icmp_op.id()) {
1087 default: fe::throwf(
MIM_LL_BE "unhandled zipped `core.icmp` id in `{}`", def);
1091 switch (mcmp_op.id()) {
1106 default: fe::throwf(
MIM_LL_BE "unhandled zipped `math.cmp` id in `{}`", def);
1109 fe::throwf(
MIM_LL_BE "unhandled `vec.zip` operation `{}`", f);
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);
1117 return std::nullopt;
1120std::string Emitter::emit_bb_impl(
BB& bb,
const Def* def) {
1121 if (
auto lam = def->isa<Lam>())
return id(lam);
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());
1135 res = e.convert_impl(type, simd);
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."},
void reg_phases(Flags2Phases &phases)
static auto isa(const Def *def)
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...
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.
auto projs(F f) const
Splits this Def via Def::projections into an Array (if A == std::dynamic_extent) or std::array (other...
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
nat_t num_projs() const
Yields Def::arity(), if it is a Lit, or 1 otherwise.
bool is_closed() const
Same as !has_free_vars().
DefMap< std::string > locals_
const Scheduler::Schedule & schedule() const
std::string emit(const Def *def)
std::string emit_unsafe(const Def *def)
DefMap< std::string > globals_
DefMap< std::string > types_
static constexpr nat_t size2bitwidth(nat_t n)
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
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...
static Lam * isa_mut_basicblock(const Def *d)
Only for mutables.
static std::optional< T > isa(const Def *def)
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.
static void hook(Flags2Phases &phases)
Phase(World &world, std::string name)
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
std::string_view name() const
static const Pi * isa_returning(const Def *d)
Is this a continuation (Pi::isa_cn) which has a Pi::ret_pi?
The World represents the whole program and manages creation of MimIR nodes (Defs).
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
void start() override
Actual entry.
Emit(World &world, flags_t annex)
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.
Emitter(World &world, std::string name, std::ostream &ostream)
virtual std::optional< std::string > isa_targetspecific_intrinsic(BB &, const Def *)
static constexpr u64 LoopMdBase
Loop-metadata node id per ll.vec-annotated loop header (see emit_epilogue_impl); numbered from LoopMd...
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....
std::string convert_ret_pi(const Pi *)
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.
void declare(std::format_string< Args... > s, Args &&... args)
std::string id(const Def *, bool force_bb=false) const
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...
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.
std::ostringstream vars_decls_
std::ostringstream func_impls_
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
@ ext
Only declare the wrappers; the runtime is linked in externally.
friend void mim_ll_finalize(Emitter &)
virtual std::string convert(const Def *type, bool simd=true)
std::ostringstream type_decls_
LamMap< const Def * > simd_phi_
friend void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &)
friend void mim_ll_emit_epilogue(Emitter &, Lam *)
static constexpr PluginArg known_args[]
#define MIM_LL_BE
Prefix for this backend's fe::throwf messages; concatenate it with the format literal.
@ nuw
No Unsigned Wrap around.
@ nsw
No Signed Wrap around.
Mode
Allowed optimizations for a specific operation.
@ afn
Approximate functions.
@ reassoc
Allow reassociation transformations for floating-point operations.
@ contract
Allow floating-point contraction (e.g.
std::optional< nat_t > isa_f(const Def *def)
const Def * pointee(const Def *ptr)
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.
mim::Plugin mim_get_plugin()
fe::Vector< const Def * > DefVec
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.
One -X <plugin>:<arg> a Plugin understands; see Arguments.
static consteval flags_t base()
Basic info and registration function pointer to be returned from a specific plugin.