14using namespace std::string_literals;
16namespace clos = mim::plug::clos;
17namespace core = mim::plug::core;
18namespace vecp = mim::plug::vec;
30 world().
log().d(
"ll backend args: {}", fe::Join(
args()));
33 auto path =
name +
".ll"s;
38 auto emitter =
Emitter(
world(),
"llvm_emitter", *out.os());
80 default:
return nullptr;
98 default:
return nullptr;
105 case math::cmp:: e:
return "oeq";
106 case math::cmp:: l:
return "olt";
108 case math::cmp:: g:
return "ogt";
111 case math::cmp:: o:
return "ord";
112 case math::cmp:: u:
return "uno";
120 default:
return nullptr;
124std::string Emitter::convert_impl(
const Def* type,
bool simd) {
125 if (
auto s = fe::lookup(
types_[simd], type))
return *s;
126 if (simd && !
has_vec(type))
return types_[simd][type] = convert_impl(type,
false);
129 std::ostringstream s;
132 if (type->isa<
Nat>()) {
133 return types_[simd][type] =
"i64";
138 return types_[simd][type] =
"i" + std::to_string(std::max<nat_t>(1, w));
141 case 16:
return types_[simd][type] =
"half";
142 case 32:
return types_[simd][type] =
"float";
143 case 64:
return types_[simd][type] =
"double";
144 default: type->blame(
MIM_LL_BE "unsupported floating-point width {}", *w).bail();
147 auto [
pointee, addr_space] = ptr->args<2>();
148 std::print(s,
"{} addrspace({})*",
convert(pointee,
false), addr_space);
149 }
else if (
auto arr =
type->isa<Arr>()) {
150 if (
auto se = simd ?
vectorize(arr) : std::nullopt) {
151 auto [
size, elem] = *se;
152 std::print(s,
"<{} x {}>", size,
convert(elem));
157 std::print(s,
"[{} x {}]", size,
convert(arr->elem(), simd));
159 }
else if (
auto pi =
type->isa<Pi>()) {
163 if (
auto t = detail::isa_mem_sigma_2(pi->dom()))
164 std::print(s,
"{}",
convert(t));
166 auto doms = pi->doms();
167 for (
auto sep =
"";
auto dom : doms.view().rsubspan(1)) {
169 std::print(s,
"{}{}", sep,
convert(dom));
174 }
else if (
auto t = detail::isa_mem_sigma_2(type)) {
176 }
else if (
auto sigma =
type->isa<Sigma>()) {
177 if (sigma->isa_mut()) {
178 name = simd ?
id(sigma) +
".simd" :
id(sigma);
180 std::print(s,
"{} = type",
name);
184 for (
auto sep =
"";
auto t : sigma->ops()) {
186 std::print(s,
"{}{}", sep,
convert(t, simd));
191 type->blame(
MIM_LL_BE "cannot convert this type to LLVM").bail();
196 if (
s.str().empty())
type->blame(
MIM_LL_BE "empty type declaration").bail();
201void Emitter::finalize_impl() {
202 for (
auto& [lam, bb] :
lam2bb_) {
203 for (
const auto& [phi,
args] : bb.phis) {
204 std::print(bb.head().emplace_back(),
"{} = phi {} ",
id(phi),
convert(phi->type()));
205 for (
auto sep =
"";
const auto& [arg, pred] :
args) {
206 std::print(bb.head().back(),
"{}[ {}, {} ]", sep, arg, pred);
212 for (
auto sep =
"";
auto mut :
schedule()) {
213 if (
auto lam = mut->isa_mut<Lam>()) {
214 if (!
lam2bb_.contains(lam)) lam->blame(
MIM_LL_BE "no basic block was emitted").bail();
216 std::println(
func_impls_,
"{}{}:", sep, lam->unique_name());
220 for (
const auto& part : bb.parts)
221 for (
const auto& line : part)
247void Emitter::emit_epilogue_impl(Lam* lam) {
248 auto app = lam->
body()->
expect<
App>(
"an application in tail position");
253 if (app->callee() ==
root()->ret_var()) {
255 if (
auto common_src = find_common_simd_src(app)) {
256 for (
auto arg : app->args())
258 return bb.tail(
"ret {} {}",
convert(common_src->type()),
emit(common_src));
261 fe::Vector<std::string> values;
263 for (
auto arg : app->args()) {
265 values.emplace_back(val);
266 types.emplace_back(arg->type());
270 switch (values.size()) {
271 case 0:
return bb.tail(
"ret void");
274 : bb.tail(
"ret {} {}",
convert(types[0]), values[0]);
280 auto [
size, elem] = *se;
283 type = std::format(
"<{} x {}>", size, val_t);
285 = std::ranges::all_of(values, [](
const std::string& v) {
return !v.starts_with(
'%'); });
287 for (
auto val : values) {
292 prev += std::format(
"{} {}", val_t, val);
297 for (
size_t i = 0, n = values.size(); i != n; ++i) {
298 auto namei =
id(lam,
true) +
".ret_val." + std::to_string(i);
299 bb.tail(
"{} = insertelement {} {}, {} {}, i32 {}", namei, type, prev, val_t, values[i], i);
306 for (
size_t i = 0, n = values.size(); i != n; ++i) {
308 auto v_elem = values[i];
309 auto t_elem =
convert(types[i]);
310 auto namei =
id(lam,
true) +
".ret_val." + std::to_string(i);
311 bb.tail(
"{} = insertvalue {} {}, {} {}, {}", namei, type, prev, t_elem, v_elem, i);
315 bb.tail(
"ret {} {}", type, prev);
319 }
else if (
auto dispatch =
Dispatch(app)) {
320 for (
auto callee : dispatch.tuple()->projs([](
const Def* def) { return def->isa_mut<Lam>(); }))
321 if (
size_t n = callee->num_tvars(); n == 1 &&
vectorize(callee->var(n, 0)->type()))
322 emit_phi(callee, callee->var(n, 0),
emit(app->arg(n, 0)), lam);
326 auto v_index =
emit(dispatch.index());
327 size_t n = dispatch.num_targets();
328 auto bbs = fe::Vector<std::string>(n);
329 for (
size_t i = 0; i != n; ++i)
330 bbs[i] =
emit(dispatch.target(i));
332 if (n == 2)
return bb.tail(
"br i1 {}, label {}, label {}", v_index, bbs[1], bbs[0]);
334 auto t_index =
convert(dispatch.index()->type());
335 bb.tail(
"switch {} {}, label {} [ ", t_index, v_index, bbs[0]);
336 for (
size_t i = 1; i != n; ++i)
337 std::print(bb.tail().back(),
"{} {}, label {} ", t_index, std::to_string(i), bbs[i]);
338 std::print(bb.tail().back(),
"]");
339 }
else if (app->callee()->isa<
Bot>()) {
340 return bb.tail(
"ret ; bottom: unreachable");
343 if (
auto common_src = find_common_simd_src(app)) {
344 auto v_src =
emit(common_src);
345 auto callee_var = callee->var();
346 auto key =
simd_phi_.try_emplace(callee, callee_var).first->second;
348 for (
auto var : callee->vars())
357 if (callee->is_set()) {
358 if (
auto head = callee->body()->isa<App>()) {
359 if (
auto dispatch =
Dispatch(head)) {
364 return bb.tail(
"br label {}, !llvm.loop !{}",
id(callee), it->second.first);
369 return bb.tail(
"br label {}",
id(callee));
372 declare(
"void @longjmp(i8*, i32) noreturn");
374 auto [mem, jbuf, tag] = app->args<3>();
376 auto v_jb =
emit(jbuf);
377 auto v_tag =
emit(tag);
378 bb.tail(
"call void @longjmp(i8* {}, i32 {})", v_jb, v_tag);
379 return bb.tail(
"unreachable");
382 auto [Ta, rest] =
mslot->uncurry_args<2>();
383 auto [
pointee, addr_space] = Ta->projs<2>();
384 auto [msize, ret] = rest->projs<2>();
387 auto ret_lam = ret->expect_mut<
Lam>(
"a mem.slot continuation");
388 auto ptr = ret_lam->var(2, 1);
389 auto v_ptr =
emit_slot(bb, app, pointee, addr_space);
391 return bb.tail(
"br label {}",
id(ret_lam));
393 auto v_callee =
emit(app->callee());
395 fe::Vector<std::string>
args;
396 auto app_args = app->args();
397 for (
auto arg : app_args.view().rsubspan(1))
400 if (app->args().back()->isa<
Bot>()) {
403 app->blame(
MIM_LL_BE "call with a ⊥ return continuation must return void").bail();
404 bb.tail(
"call void {}({})", v_callee, fe::Join(
args));
405 return bb.tail(
"unreachable");
408 auto ret_lam = app->args().back()->expect_mut<
Lam>(
"a return continuation");
410 for (
auto var : ret_lam->vars())
414 bb.tail(
"call void {}({})", v_callee, fe::Join(
args));
416 auto name =
"%" + app->unique_name() +
"ret";
418 bb.tail(
"{} = call {} {}({})",
name, t_ret, v_callee, fe::Join(
args));
422 return bb.tail(
"br label {}",
id(ret_lam));
426std::string Emitter::emit_tuple(
BB& bb,
const std::string& name,
const Def* tuple) {
427 if (detail::isa_mem_sigma_2(tuple->
type())) {
434 bool is_array = tuple->
type()->isa<
Arr>();
436 s +=
vec ?
"<" : is_array ?
"[" :
"{";
438 for (
size_t i = 0, n = tuple->
num_projs(); i != n; ++i) {
439 auto e = tuple->
proj(n, i);
440 if (
auto v_elem =
emit_unsafe(e); !v_elem.empty()) {
442 s += sep + t_elem +
" " + v_elem;
447 return s +=
vec ?
">" : is_array ?
"]" :
"}";
450 std::string prev =
"undef";
452 for (
size_t src = 0, dst = 0, n = tuple->
num_projs(); src != n; ++src) {
453 auto e = tuple->
proj(n, src);
457 auto namei =
name +
"." + std::to_string(dst);
459 prev = bb.
assign(namei,
"insertelement {} {}, {} {}, i32 {}", t, prev, elem_t, elem, dst);
461 prev = bb.
assign(namei,
"insertvalue {} {}, {} {}, {}", t, prev, elem_t, elem, dst);
468std::string Emitter::repack(
BB& bb,
const std::string& name,
const Def* type,
const std::string& v,
bool simd) {
471 auto t_src =
convert(type, !simd);
472 auto t_dst =
convert(type, simd);
474 auto src_vec =
vec && !simd;
475 auto dst_vec =
vec && simd;
477 std::string
r =
"undef";
478 for (
u64 i = 0, j = 0; i != n; ++i) {
479 auto t_i =
type->proj(n, i);
481 auto name_i =
name +
".r" + std::to_string(j);
482 auto v_i = bb.
assign(name_i,
"{} {} {}, {}{}", src_vec ?
"extractelement" :
"extractvalue", t_src, v,
483 src_vec ?
"i32 " :
"", j);
484 v_i = repack(bb, name_i, t_i, v_i, simd);
485 r = bb.
assign(name_i +
".r",
"{} {} {}, {} {}, {}{}", dst_vec ?
"insertelement" :
"insertvalue", t_dst, r,
486 convert(t_i, simd), v_i, dst_vec ?
"i32 " :
"", j);
492std::pair<std::string, std::string> Emitter::emit_gep_index(
BB& bb,
const std::string& name,
const Def* index) {
493 auto v_i =
emit(index);
499 "zext {} {} to i{} ; add one more bit for gep index as it is treated as signed value", t_i,
501 t_i =
"i" + std::to_string(w + 1);
505 return std::pair(v_i, t_i);
508std::string Emitter::emit_simd_index(
BB& bb,
const std::string& name,
const Def* index) {
509 auto v_i =
emit(index);
513 if (t_i ==
"i32")
return v_i;
515 return bb.
assign(
name +
".idx",
"{} {} {} to i32", w < 32 ?
"zext" :
"trunc", t_i, v_i);
518std::string Emitter::emit_lit(
const Def* def) {
519 if (
auto lit = def->isa<Lit>()) {
521 return std::to_string(
lit->get());
526 case 16:
return std::format(
"0xH{:04x}",
lit->get<
u16>());
527 case 32: hex = std::bit_cast<u64>(
f64(
lit->get<
f32>()));
break;
528 case 64: hex =
lit->get<
u64>();
break;
529 default: def->
blame(
MIM_LL_BE "unsupported floating-point width {} for a literal", *w).bail();
532 return std::format(
"0x{:016x}", hex);
539std::optional<std::string> Emitter::emit_builtin(
BB& bb,
const std::string& name,
const Def* def) {
540 if (def->isa<Var>()) {
543 return emit_tuple(bb,
name, def);
546 if (def->isa<Lit>()) {
547 return emit_lit(def);
548 }
else if (def->isa<
Bot>()) {
550 }
else if (
auto top = def->isa<
Top>()) {
553 }
else if (
auto tuple = def->isa<Tuple>()) {
554 return emit_tuple(bb,
name, tuple);
555 }
else if (
auto pack = def->isa<Pack>()) {
556 if (
auto lit =
Lit::isa(pack->body()); lit && *lit == 0)
return "zeroinitializer";
557 return emit_tuple(bb,
name, pack);
558 }
else if (
auto sel =
Select(def)) {
559 auto t =
convert(sel.extract()->type());
560 auto [elem_a, elem_b] = sel.pair()->projs<2>([&](
auto e) {
return emit_unsafe(e); });
561 auto cond_t =
convert(sel.cond()->type());
563 return bb.
assign(
name,
"select {} {}, {} {}, {} {}", cond_t, cond, t, elem_b, t, elem_a);
564 }
else if (
auto extract = def->isa<Extract>()) {
565 auto tuple = extract->tuple();
566 auto index = extract->index();
572 if (
auto sigma = extract->type()->isa<Sigma>(); sigma && sigma->num_ops() == 0)
return std::string();
576 return bb.
assign(
name,
"extractelement {} {}, i32 {}", t_tup, v_tup, emit_simd_index(bb,
name, index));
579 if (detail::isa_mem_sigma_2(tuple->
type()))
return v_tup;
583 if (
auto sigma = tuple->
type()->isa<Sigma>())
584 for (
u64 i = 0; i < *li; ++i)
587 return bb.
assign(
name,
"extractvalue {} {}, {}", t_tup, v_tup, v_i);
590 auto t_elem =
convert(extract->type());
591 auto [v_i, t_i] = emit_gep_index(bb,
name, index);
594 "{}.alloca = alloca {} ; copy to alloca to emulate extract with store + gep + load",
name, t_tup);
595 std::print(bb.
body().emplace_back(),
"store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup,
name);
596 std::print(bb.
body().emplace_back(),
"{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}",
name,
597 t_tup, t_tup,
name, t_i, v_i);
598 return bb.
assign(
name,
"load {}, {}* {}.gep", t_elem, t_elem,
name);
599 }
else if (
auto insert = def->isa<Insert>()) {
601 insert->blame(
MIM_LL_BE "cannot insert into a tuple with a `mem.M` element").bail();
607 return bb.
assign(
name,
"insertelement {} {}, {} {}, i32 {}", t_tup, v_tup, t_val, v_val,
611 return bb.
assign(
name,
"insertvalue {} {}, {} {}, {}", t_tup, v_tup, t_val, v_val, *idx);
614 auto [v_i, t_i] = emit_gep_index(bb,
name,
insert->index());
616 "{}.alloca = alloca {} ; copy to alloca to emulate insert with store + gep + load",
name, t_tup);
617 std::print(bb.
body().emplace_back(),
"store {} {}, {}* {}.alloca", t_tup, v_tup, t_tup,
name);
618 std::print(bb.
body().emplace_back(),
"{}.gep = getelementptr inbounds {}, {}* {}.alloca, i64 0, {} {}",
name,
619 t_tup, t_tup,
name, t_i, v_i);
620 std::print(bb.
body().emplace_back(),
"store {} {}, {}* {}.gep", t_val, v_val, t_val,
name);
621 return bb.
assign(
name,
"load {}, {}* {}.alloca", t_tup, t_tup,
name);
626std::optional<std::string> Emitter::emit_core(
BB& bb,
const std::string& name,
const Def* def) {
629 auto [
a, b] =
nat->args<2>([
this](
auto def) {
return emit(def); });
635 auto ugt = bb.
assign(
name +
".ugt",
"icmp ugt i64 {}, {}", b, a);
636 auto raw = bb.
assign(
name +
".raw",
"sub i64 {}, {}", a, b);
637 return bb.
assign(
name,
"select i1 {}, i64 0, i64 {}", ugt, raw);
644 auto bz = bb.
assign(
name +
".bz",
"icmp eq i64 {}, 0", b);
645 auto bsaf = bb.
assign(
name +
".bsafe",
"select i1 {}, i64 1, i64 {}", bz, b);
646 auto q = bb.
assign(
name +
".q",
"udiv i64 {}, {}", a, bsaf);
647 return bb.
assign(
name,
"select i1 {}, i64 0, i64 {}", bz, q);
650 auto bz = bb.
assign(
name +
".bz",
"icmp eq i64 {}, 0", b);
651 auto bsaf = bb.
assign(
name +
".bsafe",
"select i1 {}, i64 1, i64 {}", bz, b);
652 auto r = bb.
assign(
name +
".r",
"urem i64 {}, {}", a, bsaf);
653 return bb.
assign(
name,
"select i1 {}, i64 {}, i64 {}", bz, a, r);
657 auto [
a, b] =
ncmp->args<2>([
this](
auto def) {
return emit(def); });
659 if (!pred) def->
blame(
MIM_LL_BE "unhandled `core.ncmp` id").bail();
660 op =
"icmp "s + pred;
662 return bb.
assign(
name,
"{} i64 {}, {}", op, a, b);
667 if (s < 64)
return bb.
assign(
name,
"trunc i64 {} to {}", x, t);
673 return bb.
assign(
name,
"xor {} -1, {}", t, x);
675 auto [
a, b] =
bit2->args<2>([
this](
auto def) {
return emit(def); });
678 auto neg = [&](std::string_view x) {
return bb.
assign(
name +
".neg",
"xor {} -1, {}", t, x); };
694 auto [
a, b] =
shr->args<2>([
this](
auto def) {
return emit(def); });
702 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
704 auto [
mode, _, ab] =
wrap->uncurry_args<3>();
705 auto [
a, b] = ab->projs<2>([
this](
auto def) {
return emit(def); });
719 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
721 auto [m, xy] =
div->args<2>();
722 auto [x, y] = xy->projs<2>();
735 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
737 auto [
a, b] =
icmp->args<2>([
this](
auto def) {
return emit(def); });
740 if (!pred) def->
blame(
MIM_LL_BE "unhandled `core.icmp` id").bail();
741 op =
"icmp "s + pred;
743 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
745 auto [x, y] = extr->args<2>();
749 std::string
f =
"llvm.";
757 declare(
"{} @{}({}, {})", t, f, t, t);
758 return bb.
assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
760 auto [m, x] =
abs->args<2>();
764 std::string
f =
"llvm.abs." +
t;
765 declare(
"{} @{}({}, {})", t, f, t,
"i1");
766 return bb.
assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a,
"i1",
"1");
775 if (w_src == w_dst)
return v_src;
782 return bb.
assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
784 auto v_src =
emit(i2n->arg());
786 if (w_src == 64)
return v_src;
795 if (
auto lit =
Lit::isa(
bitcast->arg()); lit && *lit == 0)
return "zeroinitializer";
797 if (src_type_ptr && dst_type_ptr)
return bb.
assign(
name,
"bitcast {} {} to {}", t_src, v_src, t_dst);
798 if (src_type_ptr)
return bb.
assign(
name,
"ptrtoint {} {} to {}", t_src, v_src, t_dst);
799 if (dst_type_ptr)
return bb.
assign(
name,
"inttoptr {} {} to {}", t_src, v_src, t_dst);
802 auto size2width = [&](
const Def*
type) {
803 if (
type->isa<Nat>())
return 64_n;
808 auto src_size = size2width(
bitcast->arg()->type());
809 auto dst_size = size2width(
bitcast->type());
812 if (src_size && dst_size) {
813 if (src_size == dst_size)
return v_src;
814 op = (src_size < dst_size) ?
"zext" :
"trunc";
816 return bb.
assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
821std::optional<std::string> Emitter::emit_mem(
BB& bb,
const std::string& name,
const Def* def) {
823 auto [ptr, i] =
lea->args<2>();
825 auto v_ptr =
emit(ptr);
826 auto t_pointee =
convert(pointee,
false);
829 return bb.
assign(
name,
"getelementptr inbounds {}, {} {}, i64 0, i32 {}", t_pointee, t_ptr, v_ptr,
833 auto [v_i, t_i] = emit_gep_index(bb,
name, i);
835 return bb.
assign(
name,
"getelementptr inbounds {}, {} {}, i64 0, {} {}", t_pointee, t_ptr, v_ptr, t_i, v_i);
837 auto p =
assume->arg(2, 0);
840 auto address_space =
malloc->decurry()->arg(2, 1);
843 auto [m, n] =
malloc->args<2>();
847 auto i8ptr = bb.
assign(
name +
"i8",
"call i8* @malloc(i64 {})", size);
848 std::string i8ptr_t =
"i8*";
849 if (
Lit::expect(address_space,
"an address space") != 0) {
850 i8ptr_t = std::format(
"i8 addrspace({})*", address_space);
851 i8ptr = bb.
assign(
name +
"i8conv",
"addrspacecast i8* {} to {}", i8ptr, i8ptr_t);
853 return bb.
assign(
name,
"bitcast {} {} to {}", i8ptr_t, i8ptr, ptr_t);
855 auto address_space =
free->decurry()->arg(2, 1);
857 auto [m, p] =
free->args<2>();
862 auto i8ptr = bb.
assign(
name +
"i8",
"bitcast {} {} to i8 addrspace({})*", ptr_t, ptr, address_space);
863 if (
Lit::expect(address_space,
"an address space") != 0)
864 i8ptr = bb.
assign(
name +
"i8conv",
"addrspacecast i8 addrspace({})* {} to i8*", address_space, i8ptr);
865 bb.
tail(
"call void @free(i8* {})", i8ptr);
866 return std::string();
868 auto [m, ptr] =
load->args<2>();
870 auto v_ptr =
emit(ptr);
873 auto t_pointee =
convert(pointee,
false);
874 return repack(bb,
name, pointee, bb.
assign(
name,
"load {}, {} {}", t_pointee, t_ptr, v_ptr),
true);
876 auto [m, ptr, val] =
store->args<3>();
878 auto v_ptr =
emit(ptr);
879 auto v_val = repack(bb,
name, val->type(),
emit(val),
false);
880 auto t_ptr =
convert(ptr->type());
881 auto t_val =
convert(val->type(),
false);
882 std::print(bb.
body().emplace_back(),
"store {} {}, {} {}", t_val, v_val, t_ptr, v_ptr);
883 return std::string();
891 bb.
assign(size,
"call i64 @mim_jmpbuf_size()");
892 return bb.
assign(
name,
"alloca i8, i64 {}", size);
894 declare(
"i32 @_setjmp(i8*) returns_twice");
896 auto [mem, jmpbuf] =
setjmp->arg()->projs<2>();
898 auto v_jb =
emit(jmpbuf);
899 return bb.
assign(
name,
"call i32 @_setjmp(i8* {})", v_jb);
904std::optional<std::string> Emitter::emit_math(
BB& bb,
const std::string& name,
const Def* def) {
907 auto [
mode, _, ab] =
arith->uncurry_args<3>();
908 auto [
a, b] = ab->projs<2>([
this](
auto def) {
return emit(def); });
912 switch (
arith.id()) {
922 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
930 f = std::string(
"llvm.sin") + detail::llvm_suffix(
tri->type());
932 f = std::string(
"llvm.cos") + detail::llvm_suffix(
tri->type());
945 f += detail::math_suffix(
tri->type());
948 declare(
"{} @{}({})", t, f, t);
949 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
951 auto [
a, b] =
extrema->args<2>([
this](
auto def) {
return emit(def); });
953 std::string
f =
"llvm.";
960 f += detail::llvm_suffix(
extrema->type());
962 declare(
"{} @{}({}, {})", t, f, t, t);
963 return bb.
assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
965 auto [
a, b] =
pow->args<2>([
this](
auto def) {
return emit(def); });
967 std::string
f =
"llvm.pow";
968 f += detail::llvm_suffix(
pow->type());
969 declare(
"{} @{}({}, {})", t, f, t, t);
970 return bb.
assign(
name,
"tail call {} @{}({} {}, {} {})", t, f, t, a, t, b);
976 f = std::string(
"llvm.sqrt") + detail::llvm_suffix(
rt->type());
978 f = std::string(
"cbrt") += detail::math_suffix(
rt->type());
979 declare(
"{} @{}({})", t, f, t);
980 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
984 std::string
f =
"llvm.";
987 f += detail::llvm_suffix(
exp->type());
989 declare(
"{} @{}({})", t, f, t);
990 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
994 auto f =
er.id() ==
math::er::f ? std::string(
"erf") : std::string(
"erfc");
995 f += detail::math_suffix(
er->type());
996 declare(
"{} @{}({})", t, f, t);
997 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
1002 f += detail::math_suffix(
gamma->type());
1003 declare(
"{} @{}({})", t, f, t);
1004 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
1006 auto [
a, b] =
cmp->args<2>([
this](
auto def) {
return emit(def); });
1009 if (!pred) def->
blame(
MIM_LL_BE "unhandled `math.cmp` id").bail();
1010 op =
"fcmp "s + pred;
1012 return bb.
assign(
name,
"{} {} {}, {}", op, t, a, b);
1020 auto s = detail::llvm_suffix(
is_finite->arg()->type());
1021 auto f =
"llvm.is.fpclass";
1022 declare(
"{} @{}{}({}, i32)", t, f, s, at);
1023 return bb.
assign(
name,
"tail call {} @{}{}({} {}, i32 504)", t, f, s, at, a);
1032 switch (
conv.id()) {
1040 return bb.
assign(
name,
"{} {} {} to {}", op, t_src, v_src, t_dst);
1044 std::string
f =
"llvm.fabs";
1045 f += detail::llvm_suffix(
abs->type());
1046 declare(
"{} @{}({})", t, f, t);
1047 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
1051 std::string
f =
"llvm.";
1052 switch (
round.id()) {
1058 f += detail::llvm_suffix(
round->type());
1059 declare(
"{} @{}({})", t, f, t);
1060 return bb.
assign(
name,
"tail call {} @{}({} {})", t, f, t, a);
1062 return std::nullopt;
1065std::optional<std::string> Emitter::emit_vec(
BB& bb,
const std::string& name,
const Def* def) {
1069 return emit(v->arg());
1071 return emit(nv->arg());
1073 auto ni_n =
zip->decurry()->decurry()->decurry()->arg();
1074 auto nat_ni =
Lit::expect(ni_n->proj(2, 0),
"the `vec.zip` inputs count");
1075 auto nat_n =
Lit::expect(ni_n->proj(2, 1),
"the `vec.zip` lane count");
1076 auto f =
zip->decurry()->arg();
1077 auto inputs =
zip->arg();
1078 auto t_in =
convert(inputs->proj(nat_ni, 0)->type());
1084 switch (nat_op.id()) {
1088 auto v1 =
emit(inputs->proj(nat_ni, 0));
1089 auto v2 =
emit(inputs->proj(nat_ni, 1));
1090 auto ugt = bb.
assign(
name +
".ugt",
"icmp ugt {} {}, {}", t_in, v2, v1);
1091 auto raw = bb.
assign(
name +
".raw",
"sub {} {}, {}", t_in, v1, v2);
1092 return bb.
assign(
name,
"select <{} x i1> {}, {} zeroinitializer, {} {}", nat_n, ugt, t_out, t_out,
1099 auto v1 =
emit(inputs->proj(nat_ni, 0));
1100 auto v2 =
emit(inputs->proj(nat_ni, 1));
1101 auto bz = bb.
assign(
name +
".bz",
"icmp eq {} {}, zeroinitializer", t_in, v2);
1102 auto bz1 = bb.
assign(
name +
".bz1",
"zext <{} x i1> {} to {}", nat_n, bz, t_in);
1103 auto bsaf = bb.
assign(
name +
".bsafe",
"or {} {}, {}", t_in, v2, bz1);
1105 auto r = bb.
assign(
name +
".r",
"{} {} {}, {}", div ?
"udiv" :
"urem", t_in, v1, bsaf);
1106 return bb.
assign(
name,
"select <{} x i1> {}, {} {}, {} {}", nat_n, bz, t_out,
1107 div ? std::string(
"zeroinitializer") : v1, t_out, r);
1112 Lit::expect(
f->expect<App>(
"a zipped `math.arith`")->decurry()->arg(),
"a `math.arith` mode"));
1113 switch (arith_op.id()) {
1123 if (!pred) def->
blame(
MIM_LL_BE "unhandled zipped `core.ncmp` id").bail();
1124 op =
"icmp "s + pred;
1127 if (!pred) def->
blame(
MIM_LL_BE "unhandled zipped `core.icmp` id").bail();
1128 op =
"icmp "s + pred;
1131 if (!pred) def->
blame(
MIM_LL_BE "unhandled zipped `math.cmp` id").bail();
1132 op =
"fcmp "s + pred;
1134 f->blame(
MIM_LL_BE "unhandled `vec.zip` operation").bail();
1137 auto v1 =
emit(inputs->proj(nat_ni, 0));
1138 auto v2 =
emit(inputs->proj(nat_ni, 1));
1139 return bb.
assign(
name,
"{} {} {}, {}", op, t_in, v1, v2);
1141 return std::nullopt;
1144std::string Emitter::emit_bb_impl(
BB& bb,
const Def* def) {
1145 if (
auto lam = def->isa<Lam>())
return id(lam);
1149 if (
auto res = emit_builtin(bb,
name, def))
return *std::move(res);
1150 if (
auto res = emit_core(bb,
name, def))
return *std::move(res);
1151 if (
auto res = emit_mem(bb,
name, def))
return *std::move(res);
1152 if (
auto res = emit_math(bb,
name, def))
return *std::move(res);
1153 if (
auto res = emit_vec(bb,
name, def))
return *std::move(res);
1159 res = e.convert_impl(type, simd);
1174 {
"o=<file>, output=<file>",
"Writes the LLVM IR to `<file>` instead of the default `<world>.ll`/`a.ll`; `<file>` may be `-` for stdout."},
1175 {
"rt=embed, rt=extern",
"How the C runtime wrappers reach the output: `embed` (default) splices their LLVM IR into the module; `extern` only `declare`s them and leaves linking to you."},
1180using namespace mim::plug::ll;
static auto isa(const Def *def)
static auto expect(const Def *def, fe::cite_string< Args... > fmt, Args &&... args)
Like Axm::as but - instead of merely asserting in Debug builds - bails via Def::bail_expected when de...
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.
T * expect(fe::cite_string< Args... > fmt, Args &&... args)
Like Def::as but - instead of merely asserting in Debug builds - bails via Def::bail_expected when th...
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
fe::Error & blame(fe::cite_string< Args... > s, Args &&... args) const
Reports an error that blames this; chain Error::n for Notes and Error::bail to throw.
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().
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 Lam * isa_mut_basicblock(const Def *d)
static std::optional< T > isa(const Def *def)
static T expect(const Def *def, fe::cite_string< Args... > fmt, Args &&... args)
Like Lit::as but bails via Def::bail_expected 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)
const fe::Log & log() const
Log via log().e("...", args) etc.; owned by the Driver.
Pipeline phase for ll.emit.
void start() override
Actual entry.
Phase(World &world, std::string name)
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 *)
std::string emit(const Def *def)
Recursively emits code.
const Scheduler::Schedule & schedule() const
The Scheduler::schedule of the function currently being emitted; see Emitter::visit.
std::array< DefMap< std::string >, 2 > types_
Indexed by the simd flag of Emitter::convert.
static constexpr u64 LoopMdBase
Loop-metadata node id per ll.vec/ll.novec-annotated loop header, and whether it may vectorize; 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.
bool has_vec(const Def *type) const
Whether type's value representation contains an LLVM vector and hence differs from its memory one.
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 func_impls_
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
std::string emit_unsafe(const Def *def)
As above but yielding an empty string is permitted.
DefMap< std::string > locals_
LamMap< std::pair< u64, bool > > loop_md_
friend void mim_ll_finalize(Emitter &)
virtual std::string convert(const Def *type, bool simd=true)
Converts type to its LLVM representation as a value (simd) or in memory (!simd).
std::ostringstream type_decls_
LamMap< const Def * > simd_phi_
std::optional< std::pair< nat_t, const Def * > > vectorize(const Def *type) const
Lane count and element type if a value of type is an LLVM vector.
friend void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &)
friend void mim_ll_emit_epilogue(Emitter &, Lam *)
static const PluginSym known_syms[]
static void reg_phases(Flags2Phases &phases)
static constexpr PluginArg known_args[]
#define MIM_LL_BE
Prefix for this backend's diagnostics; concatenate it with the format literal.
@ nuw
No Unsigned Wrap around.
@ nsw
No Signed Wrap around.
Emitter::Rt arg_rt(fe::View< std::string > args)
Parses -X <backend>:rt=extern|embed; Emitter::Rt::embed is the default.
static nat_t idx_bitwidth(const Def *type)
An Idx n whose n is only known at runtime is bounded by a Nat, hence an i64.
static const char * ncmp_pred(core::ncmp id)
void mim_ll_finalize(Emitter &)
void mim_ll_emit_epilogue(Emitter &, Lam *)
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....
static const char * fcmp_pred(math::cmp id)
static const char * icmp_pred(core::icmp id)
static std::string fmath_flags(math::Mode mode)
void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &res)
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)
bool has_mem_ty(const Def *type)
Is type mem.M or a Sigma with a mem.M component?
const Def * pointee(const Def *ptr)
fe::Vector< const Def * > DefVec
std::optional< std::string_view > arg_value(fe::View< std::string > args, Keys... keys)
ankerl::unordered_dense::map< flags_t, std::function< std::unique_ptr< Phase >(World &)> > Flags2Phases
Maps an axiom of a Phase to a function that creates one.
One -X <plugin>:<arg> a Plugin understands; see Arguments.
One symbol a Plugin offers to other Plugins; see Driver::get_fun_ptr.
#define MIM_PLUGIN_SYM(f)
Builds a PluginSym for f, so name and address cannot drift apart.
#define MIM_PLUGIN_ENTRY(p)
Defines a Plugin's entry point; the body fills in the plugin handed to it, as in MIM_PLUGIN_ENTRY(dem...
static consteval flags_t base()
std::deque< std::ostringstream > & tail()
std::deque< std::ostringstream > & body()
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)