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;
88 std::println(
vars_decls_,
"{} = internal addrspace({}) global {} undef", v_ptr, addr_space,
95 bool uses_libdevice =
false;
96 std::string extra_flags;
100bool reaches_if(
const Def* def,
DefSet& seen, std::predicate<const Def*>
auto pred) {
101 if (
auto [_, ins] = seen.emplace(def); !ins)
return false;
102 if (pred(def))
return true;
103 for (
auto d : def->
deps())
104 if (reaches_if(d, seen, pred))
return true;
112 for (
auto def :
world().externals().muts())
115 for (
auto [kernel, kid] : kernel_ids_) {
116 auto name =
id(kernel).substr(1);
117 std::println(
vars_decls_,
"{}{} = private constant [{} x i8] c\"{}\\00\"", kernel_name_prefix, kid,
120 std::println(
vars_decls_,
"{} = dso_local global [{} x ptr] zeroinitializer", kernel_array_name_,
124 for (
auto mut :
world().externals().muts()) {
125 auto lam = mut->isa_mut<
Lam>();
126 if (!lam || !lam->ret_pi())
continue;
128 if (reaches_if(lam, seen, is_gpu_auto_init)) gpu_touching.emplace(lam);
132 for (
auto lam : gpu_touching) {
133 auto called_by_other = std::ranges::any_of(gpu_touching, [&](
auto other) {
134 if (other == lam)
return false;
136 return reaches_if(other, seen, [lam](
const Def* d) {
return d == lam; });
138 if (!called_by_other) gpu_externals_.emplace(lam);
145 if (
auto [_, ins] = analyzed_.emplace(def); !ins)
return;
147 for (
auto d : def->
deps())
151 auto kernel = launch->decurry()->decurry()->arg();
152 auto kernel_lam = kernel->expect_mut<
Lam>(
"the kernel passed to `gpu.launch` to be a mutable lambda");
153 if (kernel_ids_.contains(kernel_lam))
return;
154 auto kid = kernel_ids_.size();
155 kernel_ids_[kernel_lam] = kid;
179void HostEmitter::emit_cu_error_handling(
ll::BB& bb,
const std::string& cu_result) {
183 std::print(bb.
body().emplace_back(),
"call void @mim_cu_check(i32 {})", cu_result);
186void HostEmitter::emit_gpu_setup(
ll::BB& bb,
const std::string&
name) {
192 emit_cu_error_handling(bb, init_res);
195 auto dev_ptr = bb.
assign(
name +
"_dev_ptr",
"alloca i32");
196 auto dev_get_res = bb.
assign(
name +
"_get_res",
"call i32 @{}(ptr {}, i32 {})",
Cu_Device_Get, dev_ptr, dev_num);
197 emit_cu_error_handling(bb, dev_get_res);
200 if (!cu_globals_declared_) std::println(
vars_decls_,
"{} = global ptr null", ctx_name_);
201 auto dev = bb.
assign(
name +
"_dev",
"load i32, ptr {}", dev_ptr);
203 ctx_name_, ctx_flags, dev);
204 emit_cu_error_handling(bb, ctx_res);
207 if (!cu_globals_declared_) {
208 std::println(
vars_decls_,
"{} = global ptr null", mod_name_);
209 if (device_fatbin_file_.has_value()) {
210 std::ifstream fatbin_file(device_fatbin_file_.value(), std::ios::binary);
212 fe::throwf(
MIM_NVPTX_BE "could not open `{}` as binary file", device_fatbin_file_.value());
214 auto start = std::istreambuf_iterator<char>(fatbin_file);
215 auto end = std::istreambuf_iterator<char>();
216 std::vector<u8> fatbin_bytes(
start, end);
218 std::print(
vars_decls_,
"{} = private constant [{} x i8] c\"", fatbin_name_, fatbin_bytes.size());
219 for (
auto byte : fatbin_bytes) {
220 bool invalid_cstr_char =
byte ==
'"' ||
byte ==
'\\';
221 if (std::isprint(
byte) && !invalid_cstr_char)
228 std::println(
vars_decls_,
"; Add the bytes of your compiled nvptx fatbin binary here:");
230 "{} = private constant [YOUR_FATBIN_DATA_SIZE_GOES_HERE x i8] YOUR_FATBIN_DATA_GOES_HERE",
233 cu_globals_declared_ =
true;
237 emit_cu_error_handling(bb, mod_res);
238 auto mod_inner = bb.
assign(
name +
"_mod_inner",
"load ptr, ptr {}", mod_name_);
241 for (
auto [kernel, kid] : kernel_ids_) {
242 auto kname =
id(kernel).substr(1);
243 auto func_ptr = bb.
assign(
name +
"_" + kname +
"_funcptr",
"getelementptr inbounds ptr, ptr {}, i64 {}",
244 kernel_array_name_, kid);
245 auto func_res = bb.
assign(
name +
"_" + kname +
"_getfuncres",
"call i32 @{}(ptr {}, ptr {}, ptr {}{})",
247 emit_cu_error_handling(bb, func_res);
251void HostEmitter::emit_gpu_teardown(ll::BB& bb,
const std::string&
name) {
253 std::print(bb.body().emplace_back(),
"{}_mod = load ptr, ptr {}",
name, mod_name_);
255 emit_cu_error_handling(bb,
name +
"_mod_unload_res");
258 std::print(bb.body().emplace_back(),
"{}_ctx = load ptr, ptr {}",
name, ctx_name_);
259 std::print(bb.body().emplace_back(),
"{}_ctx_destroy_res = call i32 @{}(ptr {}_ctx)",
name,
Cu_Ctx_Destroy,
name);
260 emit_cu_error_handling(bb,
name +
"_ctx_destroy_res");
266 if (gpu_externals_.contains(
root())) emit_gpu_setup(
lam2bb_[
root()],
"%" +
root()->unique_name());
273 if (gpu_externals_.contains(
root())) {
275 if (
auto app = lam->
body()->isa<
App>(); app && app->
callee() ==
root()->ret_var())
276 emit_gpu_teardown(
lam2bb_[lam],
"%" + lam->unique_name());
282 auto [_, addr_space] = ptr->args<2>();
284 if (lit.value_or(0L) != 0) {
299 emit_gpu_setup(bb,
name);
304 emit_gpu_teardown(bb,
name);
318 auto stream_ptr =
emit(stream_init->arg(3, 2));
321 emit_cu_error_handling(bb, res);
328 auto stream =
emit(stream_deinit->arg(3, 2));
331 emit_cu_error_handling(bb, res);
338 auto stream =
emit(stream_sync->arg(3, 2));
341 emit_cu_error_handling(bb, res);
345 switch (alloc.id()) {
348 default: fe::throwf(
MIM_NVPTX_BE "unhandled `gpu.alloc` id in `{}`", def);
352 auto n = is_async ? 2_n : 1_n;
360 auto alloc_t = alloc->decurry()->arg();
361 World& w = alloc_t->world();
363 auto alloc_size =
emit(type_size);
367 auto alloc_ptr = bb.
assign(
name +
"ptr",
"alloca {}", ptr_t);
368 std::string alloc_res;
370 auto stream =
emit(alloc->arg(n, 1));
376 emit_cu_error_handling(bb, alloc_res);
377 return bb.
assign(
name,
"load {}, {} addrspace(0)* {}", ptr_t, ptr_t, alloc_ptr);
383 default: fe::throwf(
MIM_NVPTX_BE "unhandled `gpu.free` id in `{}`", def);
387 auto n = is_async ? 3_n : 2_n;
395 auto ptr =
emit(free->arg(n, 1));
397 std::string free_res;
399 auto stream =
emit(free->arg(n, 2));
404 emit_cu_error_handling(bb, free_res);
408 switch (copy_to_device.id()) {
411 default: fe::throwf(
MIM_NVPTX_BE "unhandled `gpu.copy_to_device` id in `{}`", def);
415 auto n = is_async ? 5_n : 4_n;
422 auto type = copy_to_device->decurry()->arg();
423 World& w = type->world();
428 auto host_ptr =
emit(copy_to_device->arg(n, 2));
429 auto dev_ptr =
emit(copy_to_device->arg(n, 3));
430 auto size =
emit(type_size);
432 std::string copy_res;
434 auto stream =
emit(copy_to_device->arg(n, 4));
436 dev_ptr, host_ptr, size, stream);
441 emit_cu_error_handling(bb, copy_res);
445 switch (copy_to_host.id()) {
448 default: fe::throwf(
MIM_NVPTX_BE "unhandled `gpu.copy_to_host` id in `{}`", def);
452 auto n = is_async ? 5_n : 4_n;
458 auto type = copy_to_host->decurry()->arg();
459 World& w = type->world();
464 auto dev_ptr =
emit(copy_to_host->arg(n, 2));
465 auto host_ptr =
emit(copy_to_host->arg(n, 3));
466 auto size =
emit(type_size);
468 std::string copy_res;
470 auto stream =
emit(copy_to_host->arg(n, 4));
472 host_ptr, dev_ptr, size, stream);
477 emit_cu_error_handling(bb, copy_res);
481 declare(
"i32 @{}(ptr, i32, i32, i32, i32, i32, i32, i32, ptr, ptr, ptr)",
Cu_Launch_Kernel);
483 auto [implicits, launch_config, kernel_def, arg_def, func_args] = launch->uncurry_args<5>();
484 auto [n_groups_def, n_items_def, stream_def, m, MT] = launch_config->projs<5>();
485 auto [
mem, ret_lam_def] = func_args->projs<2>();
488 if (!lam) fe::throwf(
MIM_NVPTX_BE "kernel `{}` is not a lambda", kernel_def);
489 if (!kernel_ids_.contains(lam)) fe::throwf(
MIM_NVPTX_BE "unknown kernel `{}`", lam);
490 auto kid = kernel_ids_[lam];
492 auto shared_mem_bytes = 0;
493 if (
auto smem_count =
Lit::expect(m,
"a shared-memory allocation count")) {
495 fe::throwf(
MIM_NVPTX_BE "only one dynamic shared-memory allocation is allowed per kernel");
500 auto n_groups =
emit(n_groups_def);
501 auto n_items =
emit(n_items_def);
502 auto stream =
emit(stream_def);
503 auto kernel =
emit(kernel_def);
504 auto arg =
emit(arg_def);
505 auto arg_type =
convert(arg_def->type());
506 auto ret_lam =
emit(ret_lam_def);
508 auto func_ptr = bb.
assign(
name +
"_kernptr",
"getelementptr inbounds [{} x ptr], [{} x ptr]* {}, i64 0, i64 {}",
509 kernel_ids_.size(), kernel_ids_.size(), kernel_array_name_, kid);
510 auto func_inner = bb.
assign(
name +
"_kernel",
"load ptr, ptr {}", func_ptr);
512 auto arg_wrap = bb.
assign(
name +
"_arg_wrap",
"alloca {}", arg_type);
513 std::print(bb.
body().emplace_back(),
"store {} {}, ptr {}", arg_type, arg, arg_wrap);
515 auto args_ptr = bb.
assign(
name +
"_args_ptr",
"alloca [1 x ptr]");
516 std::print(bb.
body().emplace_back(),
"store ptr {}, ptr {}", arg_wrap, args_ptr);
518 = bb.
assign(
name +
"_args_inner",
"getelementptr inbounds [1 x ptr], ptr {}, i64 0, i64 0", args_ptr);
521 "call i32 @{}(ptr {}, i32 {}, i32 1, i32 1, i32 {}, i32 1, i32 1, "
522 "i32 {}, ptr {}, ptr {}, ptr null)",
523 Cu_Launch_Kernel, func_inner, n_groups, n_items, shared_mem_bytes, stream, args_inner);
524 emit_cu_error_handling(bb, launch_res);
531 for (
auto kernel :
world().externals().muts()) {
532 auto kernel_lam = kernel->expect_mut<
Lam>(
"an external kernel to be a mutable lambda");
533 kernels_.emplace(kernel_lam);
539 auto is_kern = kernels_.contains(
root());
541 auto kernel =
root();
545 auto [m1, m3, m4, m5, group_id, item_id, smem, arg, ret_lam] = kernel->vars<9>();
547 auto arg_name =
id(arg);
553 auto register_sreg_idx = [&](
const Def* def, std::string_view sreg) {
555 auto type = def->
type();
556 auto type_name =
convert(type);
559 fe::throwf(
MIM_NVPTX_BE "type of `{}` must be a statically-sized `Idx` but is `{}`", def, type);
560 auto idx_lit = opt_idx_lit.value();
562 declare(
"i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
563 if (type_name ==
"i0") {
565 }
else if (type_name ==
"i32") {
566 bb.
assign(
name,
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
567 }
else if (idx_lit < (1u << 31)) {
568 auto i32 = bb.
assign(
name +
"i32",
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
569 bb.
assign(
name,
"trunc i32 {} to {}", i32, type_name);
571 fe::throwf(
MIM_NVPTX_BE "warp ID too large; must fit into `I32`");
574 register_sreg_idx(group_id,
"ctaid.x");
575 register_sreg_idx(item_id,
"tid.x");
578 if (
auto sigma = smem->type()->isa<
Sigma>()) {
579 if (sigma->num_ops() != 0)
580 fe::throwf(
MIM_NVPTX_BE "shared-memory variable must be an empty sigma, but got `{}`", smem->type());
583 auto [T, a] = ptr->args<2>();
584 if (
Lit::expect(a,
"an address space") != shared_as)
585 fe::throwf(
MIM_NVPTX_BE "shared-memory variable must live in the shared address space, but got `{}`",
587 auto name =
"@" + smem->unique_name();
592 return kernel->unique_name();
599 declare(
"void @llvm.nvvm.barrier0()");
603 std::print(bb.
body().emplace_back(),
"call void @llvm.nvvm.barrier0()");
606 auto arg =
emit(tri->arg());
607 auto type =
convert(tri->arg()->type());
608 auto func_name =
""s;
627 if (func_name.empty()) fe::throwf(
"Trigonometric tag used by {} is currently unused", def);
628 func_name = func_name + ll::detail::math_suffix(tri->arg()->type());
629 auto libdevice_func_name =
"__nv_" + func_name;
630 declare(
"{} @{}({})", type, libdevice_func_name, type);
631 uses_libdevice =
true;
632 bb.
assign(
name,
"call {} @{}({} {})", type, libdevice_func_name, type, arg);
635 auto arg =
emit(exp->arg());
636 auto type =
convert(exp->arg()->type());
637 auto func_name =
""s;
648 if (func_name.empty()) fe::throwf(
"Exponential tag used by {} is currently unused", def);
649 func_name = func_name + ll::detail::math_suffix(exp->arg()->type());
650 auto libdevice_func_name =
"__nv_" + func_name;
651 declare(
"{} @{}({})", type, libdevice_func_name, type);
652 uses_libdevice =
true;
653 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, 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.
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
static std::optional< nat_t > isa_lit(const Def *def)
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.
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 emit(const Def *def)
Recursively emits code.
std::ostream & ostream() const
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.
std::string emit_unsafe(const Def *def)
As above but yielding an empty string is permitted.
DefMap< std::string > locals_
virtual void emit_epilogue(Lam *lam)
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_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 start() final
Actual entry.
bool is_using_libdevice() const
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
DeviceEmitter(World &world, std::ostream &ostream)
const std::string & get_extra_flags() const
void emit_epilogue(Lam *) override
HostEmitter(World &world, std::ostream &ostream, std::optional< std::string > device_fatbin_file)
std::string convert(const Def *, bool simd=true) override
Converts type to its LLVM representation as a value (simd) or in memory (!simd).
void start() final
Actual entry.
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
void find_kernels(const Def *)
std::string prepare() override
constexpr auto Cu_Memcpy_Htod
constexpr auto Cu_Memcpy_Htod_Async
DeviceEmitFlags emit_device(World &, std::ostream &)
constexpr auto Cu_Stream_Sync
constexpr auto Cu_Memcpy_Dtoh
constexpr auto Cu_Mem_Free_Async
constexpr auto Cu_Launch_Kernel
constexpr auto Cu_Mem_Alloc_Async
constexpr auto Cu_Module_Unload
constexpr auto Cu_Ctx_Create
constexpr auto Cu_Ctx_Destroy
constexpr auto Cu_Mem_Free
constexpr auto Cu_Mem_Alloc
constexpr auto Cu_Memcpy_Dtoh_Async
void emit_host(World &, std::ostream &, std::optional< std::string >, ll::Emitter::Rt rt=ll::Emitter::Rt::embed)
constexpr auto Cu_Stream_Destroy
constexpr auto Cu_Stream_Create
constexpr auto Cu_Device_Get
constexpr auto Cu_Module_Get_Function
constexpr auto Cu_Module_Load_Fatbin
GIDMap< Lam *, To > LamMap
GIDSet< const Def * > DefSet
#define MIM_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)