14using namespace std::string_literals;
18namespace core = mim::plug::core;
19namespace ll = mim::plug::ll;
20namespace mem = mim::plug::mem;
21namespace gpu = mim::plug::gpu;
29 , device_fatbin_file_(device_fatbin_file) {}
40 static constexpr
std::string_view mod_name_ =
"@.mimir_cu_mod";
41 static constexpr
std::string_view ctx_name_ =
"@.mimir_cu_ctx";
42 static constexpr
std::string_view fatbin_name_ =
"@.fatbin";
43 static constexpr
std::string_view kernel_array_name_ =
"@.mimir_kernels";
44 static constexpr
std::string_view kernel_name_prefix =
"@.kname.";
46 void emit_cu_error_handling(
ll::BB&, const
std::
string&,
bool at_tail = false);
48 std::optional<
std::
string> device_fatbin_file_;
71 std::string
convert(
const Def* def,
bool simd =
false)
override {
72 if (simd)
WLOG(
"Ignoring simd=true for type conversion in device code.");
79 std::print(
vars_decls_,
"{} = internal addrspace({}) global {} undef\n", v_ptr, addr_space,
convert(pointee));
83 absl::btree_map<std::string, int> symbols_;
87 std::string extra_flags;
91 for (
auto def :
world().annexes().defs())
93 for (
auto def :
world().externals().muts())
96 for (
auto [kernel, kid] : kernel_ids_) {
97 auto name =
id(kernel).substr(1);
98 std::print(
vars_decls_,
"{}{} = private constant [{} x i8] c\"{}\\00\"\n", kernel_name_prefix, kid,
101 std::print(
vars_decls_,
"{} = dso_local global [{} x ptr] zeroinitializer\n", kernel_array_name_,
108 if (
auto [_, ins] = analyzed_.emplace(def); !ins)
return;
110 for (
auto d : def->
deps())
114 auto kernel = launch->decurry()->decurry()->arg();
115 auto kernel_lam = kernel->expect_mut<
Lam>(
"the kernel passed to %gpu.launch to be a mutable lambda");
116 if (kernel_ids_.contains(kernel_lam))
return;
117 auto kid = kernel_ids_.size();
118 kernel_ids_[kernel_lam] = kid;
142void HostEmitter::emit_cu_error_handling(
ll::BB& bb,
const std::string& cu_result,
bool at_tail) {
147 bb.
tail(
"call void @mim_cu_check(i32 {})", cu_result);
149 std::print(bb.
body().emplace_back(),
"call void @mim_cu_check(i32 {})", cu_result);
154 auto [_, addr_space] = ptr->args<2>();
156 if (lit.value_or(0L) != 0) {
176 emit_cu_error_handling(bb, init_res);
179 auto dev_ptr = bb.
assign(
name +
"_dev_ptr",
"alloca i32");
182 emit_cu_error_handling(bb, dev_get_res);
185 std::print(
vars_decls_,
"{} = global ptr null\n", ctx_name_);
186 auto dev = bb.
assign(
name +
"_dev",
"load i32, ptr {}", dev_ptr);
188 ctx_name_, ctx_flags, dev);
189 emit_cu_error_handling(bb, ctx_res);
192 std::print(
vars_decls_,
"{} = global ptr null\n", mod_name_);
193 if (device_fatbin_file_.has_value()) {
194 std::ifstream fatbin_file(device_fatbin_file_.value(), std::ios::binary);
195 if (!fatbin_file) fe::throwf(
"Could not open {} as binary file", device_fatbin_file_.value());
197 auto start = std::istreambuf_iterator<char>(fatbin_file);
198 auto end = std::istreambuf_iterator<char>();
199 std::vector<u8> fatbin_bytes(
start, end);
201 std::print(
vars_decls_,
"{} = private constant [{} x i8] c\"", fatbin_name_, fatbin_bytes.size());
202 for (
auto byte : fatbin_bytes) {
203 bool invalid_cstr_char =
byte ==
'"' ||
byte ==
'\\';
204 if (std::isprint(
byte) && !invalid_cstr_char) {
207 auto byte_val =
static_cast<int>(byte);
208 std::print(
vars_decls_,
"\\{:x}{:x}", byte_val / 16, byte_val % 16);
213 std::print(
vars_decls_,
"; Add the bytes of your compiled nvptx fatbin binary here:\n");
215 "{} = private constant [YOUR_FATBIN_DATA_SIZE_GOES_HERE x i8] YOUR_FATBIN_DATA_GOES_HERE\n",
220 emit_cu_error_handling(bb, mod_res);
221 auto mod_inner = bb.
assign(
name +
"_mod_inner",
"load ptr, ptr {}", mod_name_);
224 for (
auto [kernel, kid] : kernel_ids_) {
225 auto kname =
id(kernel).substr(1);
226 auto func_ptr = bb.
assign(
"%" + kname +
"_funcptr",
"getelementptr inbounds ptr, ptr {}, i64 {}",
227 kernel_array_name_, kid);
228 auto func_res = bb.
assign(
"%" + kname +
"_getfuncres",
"call i32 @{}(ptr {}, ptr {}, ptr {}{})",
230 emit_cu_error_handling(bb, func_res);
233 auto mem = init->arg();
237 bb.
tail(
"{}_mod = load ptr, ptr {}",
name, mod_name_);
239 emit_cu_error_handling(bb,
name +
"_mod_unload_res",
true);
242 bb.
tail(
"{}_ctx = load ptr, ptr {}",
name, ctx_name_);
244 emit_cu_error_handling(bb,
name +
"_ctx_destroy_res",
true);
253 auto stream_ptr =
emit(stream_init->arg(2));
256 emit_cu_error_handling(bb, res);
263 auto stream =
emit(stream_deinit->arg(2));
266 emit_cu_error_handling(bb, res);
273 auto stream =
emit(stream_sync->arg(2));
276 emit_cu_error_handling(bb, res);
280 switch (alloc.id()) {
283 default: fe::throwf(
"ll_nvptx backend: unhandled %gpu.alloc id in '{}'", def);
292 auto alloc_t = alloc->decurry()->arg();
293 World& w = alloc_t->world();
295 auto alloc_size =
emit(type_size);
299 auto alloc_ptr = bb.
assign(
name +
"ptr",
"alloca {}", ptr_t);
300 std::string alloc_res;
302 auto stream =
emit(alloc->arg(1));
308 emit_cu_error_handling(bb, alloc_res);
309 return bb.
assign(
name,
"load {}, {} addrspace(0)* {}", ptr_t, ptr_t, alloc_ptr);
315 default: fe::throwf(
"ll_nvptx backend: unhandled %gpu.free id in '{}'", def);
324 auto ptr =
emit(free->arg(1));
326 std::string free_res;
328 auto stream =
emit(free->arg(2));
333 emit_cu_error_handling(bb, free_res);
337 switch (copy_to_device.id()) {
340 default: fe::throwf(
"ll_nvptx backend: unhandled %gpu.copy_to_device id in '{}'", def);
348 auto type = copy_to_device->decurry()->arg();
349 World& w = type->world();
354 auto host_ptr =
emit(copy_to_device->arg(2));
355 auto dev_ptr =
emit(copy_to_device->arg(3));
356 auto size =
emit(type_size);
358 std::string copy_res;
360 auto stream =
emit(copy_to_device->arg(4));
362 dev_ptr, host_ptr, size, stream);
367 emit_cu_error_handling(bb, copy_res);
371 switch (copy_to_host.id()) {
374 default: fe::throwf(
"ll_nvptx backend: unhandled %gpu.copy_to_host id in '{}'", def);
381 auto [type] = copy_to_host->decurry()->args<1>();
382 World& w = type->world();
387 auto dev_ptr =
emit(copy_to_host->arg(2));
388 auto host_ptr =
emit(copy_to_host->arg(3));
389 auto size =
emit(type_size);
391 std::string copy_res;
393 auto stream =
emit(copy_to_host->arg(4));
395 host_ptr, dev_ptr, size, stream);
400 emit_cu_error_handling(bb, copy_res);
404 declare(
"i32 @{}(ptr, i32, i32, i32, i32, i32, i32, i32, ptr, ptr, ptr)",
Cu_Launch_Kernel);
406 auto [implicits, launch_config, kernel_def, arg_def, func_args] = launch->uncurry_args<5>();
407 auto [n_groups_def, n_items_def, stream_def, m, MT] = launch_config->projs<5>();
408 auto [
mem, ret_lam_def] = func_args->projs<2>();
411 if (!lam) fe::throwf(
"kernel is not a lamda {}", kernel_def);
412 if (!kernel_ids_.contains(lam)) fe::throwf(
"unknown kernel {}", lam);
413 auto kid = kernel_ids_[lam];
415 auto shared_mem_bytes = 0;
416 if (
auto smem_count =
Lit::expect(m,
"a shared-memory allocation count")) {
417 if (smem_count != 1) fe::throwf(
"You can only have one dynamic allocation of shared memory per kernel");
422 auto n_groups =
emit(n_groups_def);
423 auto n_items =
emit(n_items_def);
424 auto stream =
emit(stream_def);
425 auto kernel =
emit(kernel_def);
426 auto arg =
emit(arg_def);
427 auto arg_type =
convert(arg_def->type());
428 auto ret_lam =
emit(ret_lam_def);
430 auto func_ptr = bb.
assign(
name +
"_kernptr",
"getelementptr inbounds [{} x ptr], [{} x ptr]* {}, i64 0, i64 {}",
431 kernel_ids_.size(), kernel_ids_.size(), kernel_array_name_, kid);
432 auto func_inner = bb.
assign(
name +
"_kernel",
"load ptr, ptr {}", func_ptr);
434 auto arg_wrap = bb.
assign(
name +
"_arg_wrap",
"alloca {}", arg_type);
435 std::print(bb.
body().emplace_back(),
"store {} {}, ptr {}", arg_type, arg, arg_wrap);
437 auto args_ptr = bb.
assign(
name +
"_args_ptr",
"alloca [1 x ptr]");
438 std::print(bb.
body().emplace_back(),
"store ptr {}, ptr {}", arg_wrap, args_ptr);
440 = bb.
assign(
name +
"_args_inner",
"getelementptr inbounds [1 x ptr], ptr {}, i64 0, i64 0", args_ptr);
443 "call i32 @{}(ptr {}, i32 {}, i32 1, i32 1, i32 {}, i32 1, i32 1, "
444 "i32 {}, ptr {}, ptr {}, ptr null)",
445 Cu_Launch_Kernel, func_inner, n_groups, n_items, shared_mem_bytes, stream, args_inner);
446 emit_cu_error_handling(bb, launch_res);
453 for (
auto kernel :
world().externals().muts()) {
454 auto kernel_lam = kernel->expect_mut<
Lam>(
"an external kernel to be a mutable lambda");
455 kernels_.emplace(kernel_lam);
462 auto is_kern = kernels_.contains(
root());
464 auto kernel =
root();
468 auto [m1, m3, m4, m5, group_id, item_id, smem, arg, ret_lam] = kernel->vars<9>();
470 auto arg_name =
id(arg);
476 auto register_sreg_idx = [&](
const Def* def, std::string_view sreg) {
478 auto type = def->
type();
479 auto type_name =
convert(type);
481 if (!opt_idx_lit) fe::throwf(
"Type of '{}' must have known index type but has {}", def, type);
482 auto idx_lit = opt_idx_lit.value();
484 declare(
"i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
485 if (type_name ==
"i0") {
487 }
else if (type_name ==
"i32") {
488 bb.assign(
name,
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
489 }
else if (idx_lit < (1u << 31)) {
490 auto i32 = bb.assign(
name +
"i32",
"call i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
491 bb.assign(
name,
"trunc i32 {} to {}", i32, type_name);
493 fe::throwf(
"Warp ID too large, must fit into I32");
496 register_sreg_idx(group_id,
"ctaid.x");
497 register_sreg_idx(item_id,
"tid.x");
500 if (
auto sigma = smem->type()->isa<
Sigma>()) {
501 if (sigma->num_ops() != 0)
502 fe::throwf(
"ll_nvptx backend: shared-memory variable must be an empty sigma, but got '{}'", smem->type());
505 auto [T, a] = ptr->args<2>();
506 if (
Lit::expect(a,
"an address space") != shared_as)
507 fe::throwf(
"ll_nvptx backend: shared-memory variable must live in the shared address space, but got '{}'",
509 auto name =
"@" + smem->unique_name();
514 return kernel->unique_name();
521 declare(
"void @llvm.nvvm.barrier0()");
525 std::print(bb.
body().emplace_back(),
"call void @llvm.nvvm.barrier0()");
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.
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 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 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 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
std::deque< std::ostringstream > & tail()
std::deque< std::ostringstream > & body()
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)