45#define MIM_LL_BE "ll backend: "
51inline const char* math_suffix(
const Def* type) {
58 type->blame(
MIM_LL_BE "unsupported floating-point type `{}`", type).bail();
61inline const char* llvm_suffix(
const Def* type) {
64 case 16:
return ".f16";
65 case 32:
return ".f32";
66 case 64:
return ".f64";
69 type->blame(
MIM_LL_BE "unsupported floating-point type `{}`", type).bail();
74inline const Def* isa_mem_sigma_2(
const Def* type) {
75 if (
auto sigma = type->isa<Sigma>())
84 BB(
BB&& other)
noexcept =
default;
87 std::deque<std::ostringstream>&
head() {
return parts[0]; }
88 std::deque<std::ostringstream>&
body() {
return parts[1]; }
89 std::deque<std::ostringstream>&
tail() {
return parts[2]; }
91 template<
class... Args>
92 inline std::string
assign(std::string_view name, std::format_string<Args...> s, Args&&... args) {
93 auto& os =
body().emplace_back();
94 std::print(os,
"{} = ", name);
95 std::print(os, s, std::forward<Args>(args)...);
96 return std::string(name);
99 template<
class... Args>
100 inline void tail(std::format_string<Args...> s, Args&&... args) {
101 std::print(
tail().emplace_back(), s, std::forward<Args>(args)...);
106 swap(a.phis, b.phis);
107 swap(a.parts, b.parts);
111 std::array<std::deque<std::ostringstream>, 3>
parts;
143 fe::Tab
tab = fe::Tab::spaces();
145 void start()
override;
155 emit_bb_(*
this, bb, def, res);
161 template<
class... Args>
163 decls_.emplace(
"declare " + std::format(s, std::forward<Args>(
args)...));
186 template<
class... Args>
199 assert(!res.empty());
205 if (
auto s = fe::lookup(
locals_, def))
return *s;
216 std::string
id(
const Def*,
bool force_bb =
false)
const;
220 virtual std::string
convert(
const Def* type,
bool simd =
true) {
222 convert_(*
this, type, simd, res);
228 std::optional<std::pair<nat_t, const Def*>>
vectorize(
const Def* type)
const;
237 callee->
blame(
MIM_LL_BE "`{}` jumps to `{}`, which is not a basic block of `{}`", pred, callee,
root())
239 i->second.phis[phi].emplace_back(std::move(arg),
id(pred,
true));
246 for (
size_t i = 0; i != n; ++i)
248 auto phi = callee->
var(n, i);
250 emit_phi(callee, phi, std::move(arg), pred);
260 std::print(bb.
body().emplace_back(),
"{} = alloca {}", v_ptr,
convert(pointee,
false));
269 std::array<DefMap<std::string>, 2>
types_;
292 std::string convert_impl(
const Def*,
bool simd);
293 void finalize_impl();
294 void emit_epilogue_impl(
Lam*);
295 std::string emit_bb_impl(
BB&,
const Def*);
300 MIM_NOINLINE std::pair<std::string, std::string> emit_gep_index(
BB&,
const std::string&
name,
const Def* index);
308 std::string emit_simd_index(
BB&,
const std::string&
name,
const Def* index);
310 std::string repack(
BB&,
const std::string&
name,
const Def* type,
const std::string& v,
bool simd);
311 const Def* find_common_simd_src(
const App*)
const;
333inline static std::optional<std::pair<nat_t, const Def*>>
is_simd(
const Def* type) {
337 if (
auto l =
Lit::isa(arr->arity()); l && std::has_single_bit(*l)) {
339 return std::pair{*l, arr->body()};
350 if (!
simd_)
return false;
351 if (
is_simd(type))
return true;
352 if (
auto arr = type->isa<
Arr>())
return has_vec(arr->elem());
353 if (
auto sigma = type->isa<
Sigma>())
354 return std::ranges::any_of(sigma->ops(), [
this](
auto t) { return has_vec(t); });
358inline const Def* Emitter::find_common_simd_src(
const App* app)
const {
359 const Def* common_src =
nullptr;
361 for (
auto arg : app->
args()) {
363 auto extract = arg->isa<
Extract>();
364 if (!extract || !
vectorize(extract->tuple()->type()))
return nullptr;
367 if (
auto index =
Lit::isa(extract->index()); !index || *index != lane++)
return nullptr;
369 common_src = extract->tuple();
370 else if (common_src != extract->tuple())
373 if (common_src &&
vectorize(common_src->
type())->first != lane)
return nullptr;
378 if (
auto lam = def->
isa_mut<
Lam>(); lam && !force_bb) {
379 if (lam->type()->ret_pi()) {
380 if (lam->is_external() || !lam->is_set())
381 return std::string(
"@") + lam->sym().str();
382 return std::string(
"@") + lam->unique_name();
391 if (dom ==
world().sigma())
return "void";
403 auto section = [
this, &sep](std::string_view s) {
404 while (s.ends_with(
'\n'))
406 if (s.empty())
return;
407 std::println(
ostream(),
"{}{}", sep, s);
415 "`-X ll:rt=embed` needs the runtime module `mim_rt.ll`, but it "
416 "was not found (build with clang / `MIM_BUILD_LL_RUNTIME=ON`, or use `-X ll:rt=extern`)");
422 std::ostringstream decls;
423 for (
auto&& decl :
decls_)
424 std::println(decls,
"{}", decl);
425 section(decls.str());
433 std::ostringstream md_decls;
434 std::println(md_decls,
"!{} = !{{!\"llvm.loop.vectorize.enable\", i1 true}}",
LoopMdBase);
435 std::println(md_decls,
"!{} = !{{!\"llvm.loop.vectorize.enable\", i1 false}}",
LoopMdBase + 1);
436 for (
const auto& [_, md] :
loop_md_)
437 std::println(md_decls,
"!{} = distinct !{{!{}, !{}}}", md.first, md.first,
439 section(md_decls.str());
450 for (
auto mut : muts)
451 if (
auto lam = mut->isa<
Lam>())
lam2bb_.try_emplace(lam,
BB());
452 auto old_size =
lam2bb_.size();
454 if (!
root()->ret_var())
455 root()->blame(
MIM_LL_BE "top-level function is not a continuation with a return continuation").bail();
462 for (
auto mut : muts) {
463 if (
auto lam = mut->isa<
Lam>()) {
466 lam->
blame(
MIM_LL_BE "neither the entry nor a basic block of `{}`; it needs a phase that "
467 "removes higher-order functions first",
476 assert_unused(
lam2bb_.size() == old_size &&
"really make sure we didn't trigger a rehash");
486 for (
const auto& dir :
world().
driver().rt_paths()) {
487 auto path = dir / std::string(filename);
489 if (!std::filesystem::is_regular_file(path, ec) || ec)
continue;
490 if (
auto ifs = std::ifstream(path)) {
491 world().
log().d(
"ll backend: load runtime module `{}`", path.string());
492 rt_module_.assign(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>());
503 auto doms = lam->
doms();
504 for (
auto sep =
"";
auto dom : doms.view().rsubspan(1)) {
514 auto internal =
root()->is_external() ?
"" :
"internal ";
516 std::print(
func_impls_,
"define {}{} {}(", internal, ret_t,
id(
root()));
518 auto vars =
root()->vars();
519 for (
auto sep =
"";
auto var : vars.view().rsubspan(1)) {
521 if (
auto sigma = var->type()->isa<
Sigma>(); sigma && sigma->
num_ops() == 0)
continue;
529 return root()->unique_name();
auto args(Projector auto f) const noexcept
A (possibly paramterized) Array.
static auto isa(const Def *def)
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
const Def * op(size_t i) const noexcept
const Def * var(nat_t a, nat_t i) noexcept
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
nat_t num_tvars() noexcept
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.
std::string unique_name() const
name + "_" + Def::gid
constexpr size_t num_ops() const noexcept
void load(std::string_view name, Loc loc={})
bool is_loaded(std::string_view name) const
static const Def * isa(const Def *def)
Checks if def is a Idx s and returns s or nullptr otherwise.
static const Lam * isa_basicblock(const Def *d)
auto doms(Projector auto f) const noexcept
static std::optional< T > isa(const Def *def)
const Nest & nest() const
NestPhase(World &world, std::string name, bool elide_empty, bool schedule=false)
Builds a nesting tree for all mutables/binders.
const fe::Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
std::string_view name() const
virtual void start()=0
Actual entry.
A dependent function type.
const Pi * ret_pi() const
Yields the last Pi::dom, if Pi::isa_basicblock.
const Nest::Node * smart(Def *curr, const Def *)
fe::Vector< Def * > Schedule
static Schedule schedule(const Nest &)
bool is_fused() const
Does this span several axes? Also true for a dynamic rank.
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 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.
virtual std::string prepare()
Emitter(World &world, std::string name, std::ostream &ostream)
virtual std::optional< std::string > isa_targetspecific_intrinsic(BB &, const Def *)
void emit_imported(Lam *)
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.
Scheduler::Schedule schedule_
std::array< DefMap< std::string >, 2 > types_
Indexed by the simd flag of Emitter::convert.
std::ostream & ostream() const
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)
bool load_rt_module(std::string_view filename)
Locates the runtime module rt/<filename> (produced by add_mim_runtime) in the driver's search paths,...
std::ostringstream func_decls_
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_
Rt
How the C runtime wrappers (compiled to a <name>.ll via add_mim_runtime) reach the output.
@ 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.
void start() override
Actual entry.
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_
virtual void emit_epilogue(Lam *lam)
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::string emit_bb(BB &bb, const Def *def)
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.
std::set< std::string > decls_
friend void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &)
friend void mim_ll_emit_epilogue(Emitter &, Lam *)
void visit(const Nest &) override
#define MIM_LL_BE
Prefix for this backend's diagnostics; concatenate it with the format literal.
static std::optional< std::pair< nat_t, const Def * > > is_simd(const Def *type)
Emitter::Rt arg_rt(fe::View< std::string > args)
Parses -X <backend>:rt=extern|embed; Emitter::Rt::embed is the default.
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....
void mim_ll_emit_bb(Emitter &, BB &, const Def *, std::string &res)
std::optional< nat_t > isa_f(const Def *def)
const Def * strip_mem_ty(const Def *def)
Removes recusively all occurences of mem from a type (sigma).
GIDMap< const Def *, To > DefMap
GIDMap< Lam *, To > LamMap
std::optional< std::string_view > arg_value(fe::View< std::string > args, Keys... keys)
std::deque< std::ostringstream > & tail()
std::deque< std::ostringstream > & body()
DefMap< std::deque< std::pair< std::string, std::string > > > phis
BB & operator=(BB other) noexcept
BB(BB &&other) noexcept=default
std::array< std::deque< std::ostringstream >, 3 > parts
friend void swap(BB &a, BB &b) noexcept
std::deque< std::ostringstream > & head()
void tail(std::format_string< Args... > s, Args &&... args)
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)