18using namespace std::string_literals;
22namespace core = mim::plug::core;
23namespace ll = mim::plug::ll;
24namespace mem = mim::plug::mem;
25namespace gpu = mim::plug::gpu;
33 , device_fatbin_file_(device_fatbin_file) {}
44 std::
string convert(const
Def*,
bool simd = true) override;
47 static constexpr std::string_view mod_name_ =
"@.mimir_cu_mod";
48 static constexpr std::string_view ctx_name_ =
"@.mimir_cu_ctx";
49 static constexpr std::string_view fatbin_name_ =
"@.fatbin";
50 static constexpr std::string_view kernel_array_name_ =
"@.mimir_kernels";
51 static constexpr std::string_view kernel_name_prefix =
"@.kname.";
53 void emit_cu_error_handling(
ll::BB&, const std::
string&);
54 void emit_gpu_setup(
ll::BB&, const std::
string&
name);
55 void emit_gpu_teardown(
ll::BB&, const std::
string&
name);
57 std::optional<std::
string> device_fatbin_file_;
59 bool cu_globals_declared_ = false;
83 std::string
convert(
const Def* def,
bool simd =
false)
override {
84 if (simd)
log().w(
"ignoring simd=true for type conversion in device code");
91 std::print(
vars_decls_,
"{} = internal addrspace({}) global {} undef\n", v_ptr, addr_space,
convert(pointee));
95 absl::btree_map<std::string, int> symbols_;
98 bool uses_libdevice =
false;
99 std::string extra_flags;
104bool reaches_if(
const Def* def,
DefSet& seen, Pred&& pred) {
105 if (
auto [_, ins] = seen.emplace(def); !ins)
return false;
106 if (pred(def))
return true;
107 for (
auto d : def->
deps())
108 if (reaches_if(d, seen, pred))
return true;
116 for (
auto def :
world().annexes().defs())
118 for (
auto def :
world().externals().muts())
121 for (
auto [kernel, kid] : kernel_ids_) {
122 auto name =
id(kernel).substr(1);
123 std::print(
vars_decls_,
"{}{} = private constant [{} x i8] c\"{}\\00\"\n", kernel_name_prefix, kid,
126 std::print(
vars_decls_,
"{} = dso_local global [{} x ptr] zeroinitializer\n", kernel_array_name_,
130 for (
auto mut :
world().externals().muts()) {
131 auto lam = mut->isa_mut<
Lam>();
132 if (!lam || !lam->ret_pi())
continue;
134 if (reaches_if(lam, seen, is_gpu_auto_init)) gpu_touching.emplace(lam);
138 for (
auto lam : gpu_touching) {
139 auto called_by_other = std::ranges::any_of(gpu_touching, [&](
auto other) {
140 if (other == lam)
return false;
142 return reaches_if(other, seen, [lam](
const Def* d) {
return d == lam; });
144 if (!called_by_other) gpu_externals_.emplace(lam);
151 if (
auto [_, ins] = analyzed_.emplace(def); !ins)
return;
153 for (
auto d : def->
deps())
157 auto kernel = launch->decurry()->decurry()->arg();
158 auto kernel_lam = kernel->expect_mut<
Lam>(
"the kernel passed to `gpu.launch` to be a mutable lambda");
159 if (kernel_ids_.contains(kernel_lam))
return;
160 auto kid = kernel_ids_.size();
161 kernel_ids_[kernel_lam] = kid;
185void HostEmitter::emit_cu_error_handling(
ll::BB& bb,
const std::string& cu_result) {
189 std::print(bb.
body().emplace_back(),
"call void @mim_cu_check(i32 {})", cu_result);
192void HostEmitter::emit_gpu_setup(
ll::BB& bb,
const std::string&
name) {
198 emit_cu_error_handling(bb, init_res);
201 auto dev_ptr = bb.
assign(
name +
"_dev_ptr",
"alloca i32");
202 auto dev_get_res = bb.
assign(
name +
"_get_res",
"call i32 @{}(ptr {}, i32 {})",
Cu_Device_Get, dev_ptr, dev_num);
203 emit_cu_error_handling(bb, dev_get_res);
206 if (!cu_globals_declared_) std::print(
vars_decls_,
"{} = global ptr null\n", ctx_name_);
207 auto dev = bb.
assign(
name +
"_dev",
"load i32, ptr {}", dev_ptr);
209 ctx_name_, ctx_flags, dev);
210 emit_cu_error_handling(bb, ctx_res);
213 if (!cu_globals_declared_) {
214 std::print(
vars_decls_,
"{} = global ptr null\n", mod_name_);
215 if (device_fatbin_file_.has_value()) {
216 std::ifstream fatbin_file(device_fatbin_file_.value(), std::ios::binary);
218 fe::throwf(
MIM_LL_NVPTX_BE "could not open `{}` as binary file", device_fatbin_file_.value());
220 auto start = std::istreambuf_iterator<char>(fatbin_file);
221 auto end = std::istreambuf_iterator<char>();
222 std::vector<u8> fatbin_bytes(
start, end);
224 std::print(
vars_decls_,
"{} = private constant [{} x i8] c\"", fatbin_name_, fatbin_bytes.size());
225 for (
auto byte : fatbin_bytes) {
226 bool invalid_cstr_char =
byte ==
'"' ||
byte ==
'\\';
227 if (std::isprint(
byte) && !invalid_cstr_char) {
230 auto byte_val =
static_cast<int>(byte);
231 std::print(
vars_decls_,
"\\{:x}{:x}", byte_val / 16, byte_val % 16);
236 std::print(
vars_decls_,
"; Add the bytes of your compiled nvptx fatbin binary here:\n");
238 "{} = private constant [YOUR_FATBIN_DATA_SIZE_GOES_HERE x i8] YOUR_FATBIN_DATA_GOES_HERE\n",
241 cu_globals_declared_ =
true;
245 emit_cu_error_handling(bb, mod_res);
246 auto mod_inner = bb.
assign(
name +
"_mod_inner",
"load ptr, ptr {}", mod_name_);
249 for (
auto [kernel, kid] : kernel_ids_) {
250 auto kname =
id(kernel).substr(1);
251 auto func_ptr = bb.
assign(
name +
"_" + kname +
"_funcptr",
"getelementptr inbounds ptr, ptr {}, i64 {}",
252 kernel_array_name_, kid);
253 auto func_res = bb.
assign(
name +
"_" + kname +
"_getfuncres",
"call i32 @{}(ptr {}, ptr {}, ptr {}{})",
255 emit_cu_error_handling(bb, func_res);
259void HostEmitter::emit_gpu_teardown(ll::BB& bb,
const std::string&
name) {
261 std::print(bb.body().emplace_back(),
"{}_mod = load ptr, ptr {}",
name, mod_name_);
263 emit_cu_error_handling(bb,
name +
"_mod_unload_res");
266 std::print(bb.body().emplace_back(),
"{}_ctx = load ptr, ptr {}",
name, ctx_name_);
267 std::print(bb.body().emplace_back(),
"{}_ctx_destroy_res = call i32 @{}(ptr {}_ctx)",
name,
Cu_Ctx_Destroy,
name);
268 emit_cu_error_handling(bb,
name +
"_ctx_destroy_res");
274 if (gpu_externals_.contains(
root())) emit_gpu_setup(
lam2bb_[
root()],
"%" +
root()->unique_name());
281 if (gpu_externals_.contains(
root())) {
283 if (
auto app = lam->
body()->isa<
App>(); app && app->
callee() ==
root()->ret_var())
284 emit_gpu_teardown(
lam2bb_[lam],
"%" + lam->unique_name());
290 auto [_, addr_space] = ptr->args<2>();
292 if (lit.value_or(0L) != 0) {
307 emit_gpu_setup(bb,
name);
312 emit_gpu_teardown(bb,
name);
326 auto stream_ptr =
emit(stream_init->arg(2));
329 emit_cu_error_handling(bb, res);
336 auto stream =
emit(stream_deinit->arg(2));
339 emit_cu_error_handling(bb, res);
346 auto stream =
emit(stream_sync->arg(2));
349 emit_cu_error_handling(bb, res);
353 switch (alloc.id()) {
356 default: fe::throwf(
MIM_LL_NVPTX_BE "unhandled `gpu.alloc` id in `{}`", def);
365 auto alloc_t = alloc->decurry()->arg();
366 World& w = alloc_t->world();
368 auto alloc_size =
emit(type_size);
372 auto alloc_ptr = bb.
assign(
name +
"ptr",
"alloca {}", ptr_t);
373 std::string alloc_res;
375 auto stream =
emit(alloc->arg(1));
381 emit_cu_error_handling(bb, alloc_res);
382 return bb.
assign(
name,
"load {}, {} addrspace(0)* {}", ptr_t, ptr_t, alloc_ptr);
388 default: fe::throwf(
MIM_LL_NVPTX_BE "unhandled `gpu.free` id in `{}`", def);
397 auto ptr =
emit(free->arg(1));
399 std::string free_res;
401 auto stream =
emit(free->arg(2));
406 emit_cu_error_handling(bb, free_res);
410 switch (copy_to_device.id()) {
413 default: fe::throwf(
MIM_LL_NVPTX_BE "unhandled `gpu.copy_to_device` id in `{}`", def);
421 auto type = copy_to_device->decurry()->arg();
422 World& w = type->world();
427 auto host_ptr =
emit(copy_to_device->arg(2));
428 auto dev_ptr =
emit(copy_to_device->arg(3));
429 auto size =
emit(type_size);
431 std::string copy_res;
433 auto stream =
emit(copy_to_device->arg(4));
435 dev_ptr, host_ptr, size, stream);
440 emit_cu_error_handling(bb, copy_res);
444 switch (copy_to_host.id()) {
447 default: fe::throwf(
MIM_LL_NVPTX_BE "unhandled `gpu.copy_to_host` id in `{}`", def);
454 auto [type] = copy_to_host->decurry()->args<1>();
455 World& w = type->world();
460 auto dev_ptr =
emit(copy_to_host->arg(2));
461 auto host_ptr =
emit(copy_to_host->arg(3));
462 auto size =
emit(type_size);
464 std::string copy_res;
466 auto stream =
emit(copy_to_host->arg(4));
468 host_ptr, dev_ptr, size, stream);
473 emit_cu_error_handling(bb, copy_res);
477 declare(
"i32 @{}(ptr, i32, i32, i32, i32, i32, i32, i32, ptr, ptr, ptr)",
Cu_Launch_Kernel);
479 auto [implicits, launch_config, kernel_def, arg_def, func_args] = launch->uncurry_args<5>();
480 auto [n_groups_def, n_items_def, stream_def, m, MT] = launch_config->projs<5>();
481 auto [
mem, ret_lam_def] = func_args->projs<2>();
484 if (!lam) fe::throwf(
MIM_LL_NVPTX_BE "kernel `{}` is not a lambda", kernel_def);
485 if (!kernel_ids_.contains(lam)) fe::throwf(
MIM_LL_NVPTX_BE "unknown kernel `{}`", lam);
486 auto kid = kernel_ids_[lam];
488 auto shared_mem_bytes = 0;
489 if (
auto smem_count =
Lit::expect(m,
"a shared-memory allocation count")) {
491 fe::throwf(
MIM_LL_NVPTX_BE "only one dynamic shared-memory allocation is allowed per kernel");
496 auto n_groups =
emit(n_groups_def);
497 auto n_items =
emit(n_items_def);
498 auto stream =
emit(stream_def);
499 auto kernel =
emit(kernel_def);
500 auto arg =
emit(arg_def);
501 auto arg_type =
convert(arg_def->type());
502 auto ret_lam =
emit(ret_lam_def);
504 auto func_ptr = bb.
assign(
name +
"_kernptr",
"getelementptr inbounds [{} x ptr], [{} x ptr]* {}, i64 0, i64 {}",
505 kernel_ids_.size(), kernel_ids_.size(), kernel_array_name_, kid);
506 auto func_inner = bb.
assign(
name +
"_kernel",
"load ptr, ptr {}", func_ptr);
508 auto arg_wrap = bb.
assign(
name +
"_arg_wrap",
"alloca {}", arg_type);
509 std::print(bb.
body().emplace_back(),
"store {} {}, ptr {}", arg_type, arg, arg_wrap);
511 auto args_ptr = bb.
assign(
name +
"_args_ptr",
"alloca [1 x ptr]");
512 std::print(bb.
body().emplace_back(),
"store ptr {}, ptr {}", arg_wrap, args_ptr);
514 = bb.
assign(
name +
"_args_inner",
"getelementptr inbounds [1 x ptr], ptr {}, i64 0, i64 0", args_ptr);
517 "call i32 @{}(ptr {}, i32 {}, i32 1, i32 1, i32 {}, i32 1, i32 1, "
518 "i32 {}, ptr {}, ptr {}, ptr null)",
519 Cu_Launch_Kernel, func_inner, n_groups, n_items, shared_mem_bytes, stream, args_inner);
520 emit_cu_error_handling(bb, launch_res);
527 for (
auto kernel :
world().externals().muts()) {
528 auto kernel_lam = kernel->expect_mut<
Lam>(
"an external kernel to be a mutable lambda");
529 kernels_.emplace(kernel_lam);
536 auto is_kern = kernels_.contains(
root());
538 auto kernel =
root();
542 auto [m1, m3, m4, m5, group_id, item_id, smem, arg, ret_lam] = kernel->vars<9>();
544 auto arg_name =
id(arg);
550 auto register_sreg_idx = [&](
const Def* def, std::string_view sreg) {
552 auto type = def->
type();
553 auto type_name =
convert(type);
556 fe::throwf(
MIM_LL_NVPTX_BE "type of `{}` must be a statically-sized `Idx` but is `{}`", def, type);
557 auto idx_lit = opt_idx_lit.value();
559 declare(
"i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
560 if (type_name ==
"i0") {
562 }
else if (type_name ==
"i32") {
563 bb.
assign(
name,
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
564 }
else if (idx_lit < (1u << 31)) {
565 auto i32 = bb.
assign(
name +
"i32",
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
566 bb.
assign(
name,
"trunc i32 {} to {}", i32, type_name);
571 register_sreg_idx(group_id,
"ctaid.x");
572 register_sreg_idx(item_id,
"tid.x");
575 if (
auto sigma = smem->type()->isa<
Sigma>()) {
576 if (sigma->num_ops() != 0)
577 fe::throwf(
MIM_LL_NVPTX_BE "shared-memory variable must be an empty sigma, but got `{}`", smem->type());
580 auto [T, a] = ptr->args<2>();
581 if (
Lit::expect(a,
"an address space") != shared_as)
582 fe::throwf(
MIM_LL_NVPTX_BE "shared-memory variable must live in the shared address space, but got `{}`",
584 auto name =
"@" + smem->unique_name();
589 return kernel->unique_name();
596 declare(
"void @llvm.nvvm.barrier0()");
600 std::print(bb.
body().emplace_back(),
"call void @llvm.nvvm.barrier0()");
603 auto arg =
emit(tri->arg());
604 auto type =
convert(tri->arg()->type());
605 auto func_name =
""s;
624 if (func_name.empty()) fe::throwf(
"Trigonometric tag used by {} is currently unused", def);
625 func_name = func_name + ll::detail::math_suffix(tri->arg()->type());
626 auto libdevice_func_name =
"__nv_" + func_name;
627 declare(
"{} @{}({})", type, libdevice_func_name, type);
628 uses_libdevice =
true;
629 bb.
assign(
name,
"call {} @{}({} {})", type, libdevice_func_name, type, arg);
632 auto arg =
emit(exp->arg());
633 auto type =
convert(exp->arg()->type());
634 auto func_name =
""s;
645 if (func_name.empty()) fe::throwf(
"Exponential tag used by {} is currently unused", def);
646 func_name = func_name + ll::detail::math_suffix(exp->arg()->type());
647 auto libdevice_func_name =
"__nv_" + func_name;
648 declare(
"{} @{}({})", type, libdevice_func_name, type);
649 uses_libdevice =
true;
650 bb.
assign(
name,
"call {} @{}({} {})", type, libdevice_func_name, type, arg);
const Def * callee() const
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.
Defs deps() const noexcept
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
std::string unique_name() const
name + "_" + Def::gid
DefMap< std::string > locals_
std::ostream & ostream() const
std::string emit_unsafe(const Def *def)
static std::optional< nat_t > isa_lit(const Def *def)
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.
const fe::Log & log() const
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
std::string_view name() const
The World represents the whole program and manages creation of MimIR nodes (Defs).
virtual std::string prepare()
Emitter(World &world, std::string name, std::ostream &ostream)
std::string convert_ret_pi(const Pi *)
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::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...
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.
void start() override
Actual entry.
virtual void emit_epilogue(Lam *lam)
virtual std::string convert(const Def *type, bool simd=true)
bool is_using_libdevice() const
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
const std::string & get_extra_flags() const
std::string convert(const Def *def, bool simd=false) override
void start() final
Actual entry.
DeviceEmitter(World &world, std::ostream &ostream)
std::string emit_slot(ll::BB &, const App *app, const Def *pointee, const Def *addr_space) override
Device slots live in a module-scope global in their requested address space, not on the stack.
std::string prepare() override
void emit_epilogue(Lam *) override
void find_kernels(const Def *)
void start() final
Actual entry.
HostEmitter(World &world, std::ostream &ostream, std::optional< std::string > device_fatbin_file)
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
std::string prepare() override
std::string convert(const Def *, bool simd=true) override
constexpr auto Cu_Device_Get
constexpr auto Cu_Memcpy_Htod_Async
constexpr auto Cu_Mem_Alloc_Async
DeviceEmitFlags emit_device(World &, std::ostream &)
constexpr auto Cu_Module_Unload
constexpr auto Cu_Memcpy_Dtoh_Async
constexpr auto Cu_Memcpy_Htod
constexpr auto Cu_Mem_Free_Async
constexpr auto Cu_Stream_Sync
constexpr auto Cu_Stream_Create
constexpr auto Cu_Launch_Kernel
constexpr auto Cu_Mem_Free
void emit_host(World &, std::ostream &, std::optional< std::string >, ll::Emitter::Rt rt=ll::Emitter::Rt::embed)
constexpr auto Cu_Memcpy_Dtoh
constexpr auto Cu_Mem_Alloc
constexpr auto Cu_Module_Get_Function
constexpr auto Cu_Ctx_Create
constexpr auto Cu_Stream_Destroy
constexpr auto Cu_Ctx_Destroy
constexpr auto Cu_Module_Load_Fatbin
GIDMap< Lam *, To > LamMap
GIDSet< const Def * > DefSet
#define MIM_LL_NVPTX_BE
Prefix for this backend's fe::throwf messages; see MIM_LL_BE.
std::deque< std::ostringstream > & body()
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)