MimIR
MimIR is my Intermediate Representation
Loading...
Searching...
No Matches
ll_nvptx.cpp
Go to the documentation of this file.
2
3#include <format>
4
5#include <mim/driver.h>
6
7#include <mim/util/sys.h>
8
10#include <mim/plug/gpu/gpu.h>
12#include <mim/plug/mem/mem.h>
13
14using namespace std::string_literals;
15
16namespace mim::plug::ll_nvptx {
17
18namespace core = mim::plug::core;
19namespace ll = mim::plug::ll;
20namespace mem = mim::plug::mem;
21namespace gpu = mim::plug::gpu;
22
23class HostEmitter : public ll::Emitter {
24public:
26
27 HostEmitter(World& world, std::ostream& ostream, std::optional<std::string> device_fatbin_file)
28 : Super(world, "llvm_nvptx_host_emitter", ostream)
29 , device_fatbin_file_(device_fatbin_file) {}
30
31 void start() final;
32 void find_kernels(const Def*);
33
34 std::optional<std::string> isa_targetspecific_intrinsic(ll::BB&, const Def*) final;
35
36protected:
37 std::string convert(const Def*, bool simd = true) override;
38
39private:
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.";
45
46 void emit_cu_error_handling(ll::BB&, const std::string&, bool at_tail = false);
47
48 std::optional<std::string> device_fatbin_file_;
49 LamMap<int> kernel_ids_;
50
51 DefSet analyzed_;
52};
53
54class DeviceEmitter : public ll::Emitter {
55public:
57
59 : Super(world, "llvm_nvptx_device_emitter", ostream) {}
60
61 void start() final;
62
63 std::string prepare() override;
64
65 std::optional<std::string> isa_targetspecific_intrinsic(ll::BB&, const Def*) final;
66
67 bool is_using_libdevice() const { return uses_libdevice; }
68 const std::string& get_extra_flags() const { return extra_flags; }
69
70private:
71 std::string convert(const Def* def, bool simd = false) override {
72 if (simd) WLOG("Ignoring simd=true for type conversion in device code.");
73 return Super::convert(def, false);
74 }
75
76 /// Device slots live in a module-scope global in their requested address space, not on the stack.
77 std::string emit_slot(ll::BB&, const App* app, const Def* pointee, const Def* addr_space) override {
78 auto v_ptr = "@" + app->unique_name() + ".slot";
79 std::print(vars_decls_, "{} = internal addrspace({}) global {} undef\n", v_ptr, addr_space, convert(pointee));
80 return v_ptr;
81 }
82
83 absl::btree_map<std::string, int> symbols_;
84 LamSet kernels_;
85
86 bool uses_libdevice;
87 std::string extra_flags;
88};
89
91 for (auto def : world().annexes().defs())
92 find_kernels(def);
93 for (auto def : world().externals().muts())
94 find_kernels(def);
95
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,
99 name.size() + 1, name);
100 }
101 std::print(vars_decls_, "{} = dso_local global [{} x ptr] zeroinitializer\n", kernel_array_name_,
102 kernel_ids_.size());
103
104 Super::start();
105}
106
108 if (auto [_, ins] = analyzed_.emplace(def); !ins) return;
109
110 for (auto d : def->deps())
111 find_kernels(d);
112
113 if (auto launch = Axm::isa<gpu::launch>(def)) {
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;
119 }
120}
121
122constexpr auto Cu_Init = "cuInit";
123constexpr auto Cu_Ctx_Create = "cuCtxCreate_v4";
124constexpr auto Cu_Ctx_Destroy = "cuCtxDestroy_v2";
125constexpr auto Cu_Device_Get = "cuDeviceGet";
126constexpr auto Cu_Launch_Kernel = "cuLaunchKernel_ptsz";
127constexpr auto Cu_Mem_Alloc = "cuMemAlloc_v2";
128constexpr auto Cu_Mem_Alloc_Async = "cuMemAllocAsync_ptsz";
129constexpr auto Cu_Mem_Free = "cuMemFree_v2";
130constexpr auto Cu_Mem_Free_Async = "cuMemFreeAsync_ptsz";
131constexpr auto Cu_Memcpy_Htod = "cuMemcpyHtoD_v2";
132constexpr auto Cu_Memcpy_Htod_Async = "cuMemcpyHtoDAsync_v2_ptsz";
133constexpr auto Cu_Memcpy_Dtoh = "cuMemcpyDtoH_v2";
134constexpr auto Cu_Memcpy_Dtoh_Async = "cuMemcpyDtoHAsync_v2_ptsz";
135constexpr auto Cu_Module_Load_Fatbin = "cuModuleLoadFatBinary";
136constexpr auto Cu_Module_Get_Function = "cuModuleGetFunction";
137constexpr auto Cu_Module_Unload = "cuModuleUnload";
138constexpr auto Cu_Stream_Create = "cuStreamCreate";
139constexpr auto Cu_Stream_Destroy = "cuStreamDestroy_v2";
140constexpr auto Cu_Stream_Sync = "cuStreamSynchronize_ptsz";
141
142void HostEmitter::emit_cu_error_handling(ll::BB& bb, const std::string& cu_result, bool at_tail) {
143 // Offload the CUresult check to the C runtime wrapper `mim_cu_check` (see rt/mim_cuda_rt.c)
144 // instead of open-coding it here; showcases the C-runtime system on the ll_nvptx backend.
145 declare_rt("void @mim_cu_check(i32)");
146 if (at_tail)
147 bb.tail("call void @mim_cu_check(i32 {})", cu_result);
148 else
149 std::print(bb.body().emplace_back(), "call void @mim_cu_check(i32 {})", cu_result);
150}
151
152std::string HostEmitter::convert(const Def* type, bool simd) {
153 if (auto ptr = Axm::isa<mem::Ptr>(type)) {
154 auto [_, addr_space] = ptr->args<2>();
155 auto lit = Lit::isa(addr_space);
156 if (lit.value_or(0L) != 0) {
157 // NVIDIA treats all device pointers as i64s in host code
158 return "i64";
159 }
160 }
161 return Super::convert(type, simd);
162}
163
164std::optional<std::string> HostEmitter::isa_targetspecific_intrinsic(ll::BB& bb, const Def* def) {
165 auto name = id(def);
166 std::string op;
167
168 if (auto default_stream = Axm::isa<gpu::default_stream>(def)) {
169 return "null";
170 } else if (auto init = Axm::isa<gpu::init>(def)) {
171 auto dev_num = 0; // TODO: consider parameterizing this
172 auto ctx_flags = 0; // TODO: consider parameterizing this
173
174 declare("i32 @{}(i32)", Cu_Init);
175 auto init_res = bb.assign(name + "_init_res", "call i32 @{}(i32 0)", Cu_Init);
176 emit_cu_error_handling(bb, init_res);
177
178 declare("i32 @{}(ptr, i32)", Cu_Device_Get);
179 auto dev_ptr = bb.assign(name + "_dev_ptr", "alloca i32");
180 auto dev_get_res
181 = bb.assign(name + "_get_res", "call i32 @{}(ptr {}, i32 {})", Cu_Device_Get, dev_ptr, dev_num);
182 emit_cu_error_handling(bb, dev_get_res);
183
184 declare("i32 @{}(ptr, ptr, i32, i32)", Cu_Ctx_Create);
185 std::print(vars_decls_, "{} = global ptr null\n", ctx_name_);
186 auto dev = bb.assign(name + "_dev", "load i32, ptr {}", dev_ptr);
187 auto ctx_res = bb.assign(name + "_ctx_res", "call i32 @{}(ptr {}, ptr null, i32 {}, i32 {})", Cu_Ctx_Create,
188 ctx_name_, ctx_flags, dev);
189 emit_cu_error_handling(bb, ctx_res);
190
191 declare("i32 @{}(ptr, ptr)", Cu_Module_Load_Fatbin);
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());
196
197 auto start = std::istreambuf_iterator<char>(fatbin_file);
198 auto end = std::istreambuf_iterator<char>();
199 std::vector<u8> fatbin_bytes(start, end);
200
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) {
205 std::print(vars_decls_, "{:c}", byte);
206 } else {
207 auto byte_val = static_cast<int>(byte);
208 std::print(vars_decls_, "\\{:x}{:x}", byte_val / 16, byte_val % 16);
209 }
210 }
211 std::print(vars_decls_, "\"\n");
212 } else {
213 std::print(vars_decls_, "; Add the bytes of your compiled nvptx fatbin binary here:\n");
214 std::print(vars_decls_,
215 "{} = private constant [YOUR_FATBIN_DATA_SIZE_GOES_HERE x i8] YOUR_FATBIN_DATA_GOES_HERE\n",
216 fatbin_name_);
217 }
218 auto mod_res = bb.assign(name + "_mod_res", "call i32 @{}(ptr {}, ptr {})", Cu_Module_Load_Fatbin, mod_name_,
219 fatbin_name_);
220 emit_cu_error_handling(bb, mod_res);
221 auto mod_inner = bb.assign(name + "_mod_inner", "load ptr, ptr {}", mod_name_);
222
223 declare("i32 @{}(ptr, ptr, ptr)", Cu_Module_Get_Function);
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 {}{})",
229 Cu_Module_Get_Function, func_ptr, mod_inner, kernel_name_prefix, kid);
230 emit_cu_error_handling(bb, func_res);
231 }
232
233 auto mem = init->arg();
234 return emit_unsafe(mem);
235 } else if (auto deinit = Axm::isa<gpu::deinit>(def)) {
236 declare("i32 @{}(ptr)", Cu_Module_Unload);
237 bb.tail("{}_mod = load ptr, ptr {}", name, mod_name_);
238 bb.tail("{}_mod_unload_res = call i32 @{}(ptr {}_mod)", name, Cu_Module_Unload, name);
239 emit_cu_error_handling(bb, name + "_mod_unload_res", true);
240
241 declare("i32 @{}(ptr)", Cu_Ctx_Destroy);
242 bb.tail("{}_ctx = load ptr, ptr {}", name, ctx_name_);
243 bb.tail("{}_ctx_destroy_res = call i32 @{}(ptr {}_ctx)", name, Cu_Ctx_Destroy, name);
244 emit_cu_error_handling(bb, name + "_ctx_destroy_res", true);
245
246 emit_unsafe(deinit->arg(0));
247 return emit_unsafe(deinit->arg(1));
248 } else if (auto stream_init = Axm::isa<gpu::stream_init>(def)) {
249 declare("i32 @{}(ptr, i32)", Cu_Stream_Create);
250
251 emit_unsafe(stream_init->arg(0));
252 emit_unsafe(stream_init->arg(1));
253 auto stream_ptr = emit(stream_init->arg(2));
254
255 auto res = bb.assign(name, "call i32 @{}(ptr {}, i32 0)", Cu_Stream_Create, stream_ptr);
256 emit_cu_error_handling(bb, res);
257 return res;
258 } else if (auto stream_deinit = Axm::isa<gpu::stream_deinit>(def)) {
259 declare("i32 @{}(ptr)", Cu_Stream_Destroy);
260
261 emit_unsafe(stream_deinit->arg(0));
262 emit_unsafe(stream_deinit->arg(1));
263 auto stream = emit(stream_deinit->arg(2));
264
265 auto res = bb.assign(name, "call i32 @{}(ptr {})", Cu_Stream_Destroy, stream);
266 emit_cu_error_handling(bb, res);
267 return res;
268 } else if (auto stream_sync = Axm::isa<gpu::stream_sync>(def)) {
269 declare("i32 @{}(ptr)", Cu_Stream_Sync);
270
271 emit_unsafe(stream_sync->arg(0));
272 emit_unsafe(stream_sync->arg(1));
273 auto stream = emit(stream_sync->arg(2));
274
275 auto res = bb.assign(name, "call i32 @{}(ptr {})", Cu_Stream_Sync, stream);
276 emit_cu_error_handling(bb, res);
277 return res;
278 } else if (auto alloc = Axm::isa<gpu::alloc>(def)) {
279 bool is_async;
280 switch (alloc.id()) {
281 case gpu::alloc::block: is_async = false; break;
282 case gpu::alloc::asyn: is_async = true; break;
283 default: fe::throwf("ll_nvptx backend: unhandled %gpu.alloc id in '{}'", def);
284 }
285
286 if (is_async)
287 declare("i32 @{}(ptr, i64, ptr)", Cu_Mem_Alloc_Async);
288 else
289 declare("i32 @{}(ptr, i64)", Cu_Mem_Alloc);
290
291 emit_unsafe(alloc->arg(0));
292 auto alloc_t = alloc->decurry()->arg();
293 World& w = alloc_t->world();
294 auto type_size = w.call(core::trait::size, alloc_t);
295 auto alloc_size = emit(type_size);
296
297 auto ptr_t = convert(Axm::expect<mem::Ptr>(def->proj(1)->type(), "a %mem.Ptr"));
298
299 auto alloc_ptr = bb.assign(name + "ptr", "alloca {}", ptr_t);
300 std::string alloc_res;
301 if (is_async) {
302 auto stream = emit(alloc->arg(1));
303 alloc_res = bb.assign(name + "res", "call i32 @{}(ptr {}, i64 {}, ptr {})", Cu_Mem_Alloc_Async, alloc_ptr,
304 alloc_size, stream);
305 } else
306 alloc_res = bb.assign(name + "res", "call i32 @{}(ptr {}, i64 {})", Cu_Mem_Alloc, alloc_ptr, alloc_size);
307
308 emit_cu_error_handling(bb, alloc_res);
309 return bb.assign(name, "load {}, {} addrspace(0)* {}", ptr_t, ptr_t, alloc_ptr);
310 } else if (auto free = Axm::isa<gpu::free>(def)) {
311 bool is_async;
312 switch (free.id()) {
313 case gpu::free::block: is_async = false; break;
314 case gpu::free::asyn: is_async = true; break;
315 default: fe::throwf("ll_nvptx backend: unhandled %gpu.free id in '{}'", def);
316 }
317
318 if (is_async)
319 declare("i32 @{}(i64)", Cu_Mem_Free_Async);
320 else
321 declare("i32 @{}(i64)", Cu_Mem_Free);
322
323 emit_unsafe(free->arg(0));
324 auto ptr = emit(free->arg(1));
325
326 std::string free_res;
327 if (is_async) {
328 auto stream = emit(free->arg(2));
329 free_res = bb.assign(name + "res", "call i32 @{}(i64 {}, ptr {})", Cu_Mem_Free_Async, ptr, stream);
330 } else
331 free_res = bb.assign(name + "res", "call i32 @{}(i64 {})", Cu_Mem_Free, ptr);
332
333 emit_cu_error_handling(bb, free_res);
334 return free_res;
335 } else if (auto copy_to_device = Axm::isa<gpu::copy_to_device>(def)) {
336 bool is_async;
337 switch (copy_to_device.id()) {
338 case gpu::copy_to_device::block: is_async = false; break;
339 case gpu::copy_to_device::asyn: is_async = true; break;
340 default: fe::throwf("ll_nvptx backend: unhandled %gpu.copy_to_device id in '{}'", def);
341 }
342
343 if (is_async)
344 declare("i32 @{}(i64, ptr, i64, ptr)", Cu_Memcpy_Htod_Async);
345 else
346 declare("i32 @{}(i64, ptr, i64)", Cu_Memcpy_Htod);
347
348 auto type = copy_to_device->decurry()->arg();
349 World& w = type->world();
350 auto type_size = w.call(core::trait::size, type);
351
352 emit_unsafe(copy_to_device->arg(0));
353 emit_unsafe(copy_to_device->arg(1));
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);
357
358 std::string copy_res;
359 if (is_async) {
360 auto stream = emit(copy_to_device->arg(4));
361 copy_res = bb.assign(name + "res", "call i32 @{}(i64 {}, ptr {}, i64 {}, ptr {})", Cu_Memcpy_Htod_Async,
362 dev_ptr, host_ptr, size, stream);
363 } else
364 copy_res = bb.assign(name + "res", "call i32 @{}(i64 {}, ptr {}, i64 {})", Cu_Memcpy_Htod, dev_ptr,
365 host_ptr, size);
366
367 emit_cu_error_handling(bb, copy_res);
368 return copy_res;
369 } else if (auto copy_to_host = Axm::isa<gpu::copy_to_host>(def)) {
370 bool is_async;
371 switch (copy_to_host.id()) {
372 case gpu::copy_to_host::block: is_async = false; break;
373 case gpu::copy_to_host::asyn: is_async = true; break;
374 default: fe::throwf("ll_nvptx backend: unhandled %gpu.copy_to_host id in '{}'", def);
375 }
376 if (is_async)
377 declare("i32 @{}(ptr, i64, i64, ptr)", Cu_Memcpy_Dtoh_Async);
378 else
379 declare("i32 @{}(ptr, i64, i64)", Cu_Memcpy_Dtoh);
380
381 auto [type] = copy_to_host->decurry()->args<1>();
382 World& w = type->world();
383 auto type_size = w.call(core::trait::size, type);
384
385 emit_unsafe(copy_to_host->arg(0));
386 emit_unsafe(copy_to_host->arg(1));
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);
390
391 std::string copy_res;
392 if (is_async) {
393 auto stream = emit(copy_to_host->arg(4));
394 copy_res = bb.assign(name + "res", "call i32 @{}(ptr {}, i64 {}, i64 {}, ptr {})", Cu_Memcpy_Dtoh_Async,
395 host_ptr, dev_ptr, size, stream);
396 } else
397 copy_res = bb.assign(name + "res", "call i32 @{}(ptr {}, i64 {}, i64 {})", Cu_Memcpy_Dtoh, host_ptr,
398 dev_ptr, size);
399
400 emit_cu_error_handling(bb, copy_res);
401 return copy_res;
402 } else if (auto launch = Axm::isa<gpu::launch>(def)) {
403 // TODO: rewrite to use modern cuLaunchKernelEx instead
404 declare("i32 @{}(ptr, i32, i32, i32, i32, i32, i32, i32, ptr, ptr, ptr)", Cu_Launch_Kernel);
405
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>();
409
410 Lam* lam = kernel_def->isa_mut<Lam>();
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];
414
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");
418 shared_mem_bytes = Lit::expect(world().call(core::trait::size, MT), "a shared-memory size");
419 }
420
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);
429
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);
433
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);
436
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);
439 auto args_inner
440 = bb.assign(name + "_args_inner", "getelementptr inbounds [1 x ptr], ptr {}, i64 0, i64 0", args_ptr);
441 auto launch_res
442 = bb.assign(name,
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);
447 return ret_lam;
448 }
449 return std::nullopt;
450}
451
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);
456 }
457 Super::start();
458 return;
459}
460
462 auto is_kern = kernels_.contains(root());
463 if (!is_kern) return Super::prepare();
464 auto kernel = root();
465
466 std::print(func_impls_, "define ptx_kernel {} {}(", convert_ret_pi(kernel->type()->ret_pi()), id(kernel));
467
468 auto [m1, m3, m4, m5, group_id, item_id, smem, arg, ret_lam] = kernel->vars<9>();
469
470 auto arg_name = id(arg);
471 locals_[arg] = arg_name;
472 std::print(func_impls_, "{} {}) {{\n", convert(arg->type()), arg_name);
473
474 auto& bb = lam2bb_[kernel];
475
476 auto register_sreg_idx = [&](const Def* def, std::string_view sreg) {
477 auto name = id(def);
478 auto type = def->type();
479 auto type_name = convert(type);
480 auto opt_idx_lit = Idx::isa_lit(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();
483 locals_[def] = name;
484 declare("i32 @llvm.nvvm.read.ptx.sreg.{}()", sreg);
485 if (type_name == "i0") {
486 locals_[def] = "0";
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);
492 } else {
493 fe::throwf("Warp ID too large, must fit into I32");
494 }
495 };
496 register_sreg_idx(group_id, "ctaid.x");
497 register_sreg_idx(item_id, "tid.x");
498
499 auto shared_as = Lit::expect(world().annex<gpu::addr_space_shared>(), "the shared address space");
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());
503 } else {
504 auto ptr = Axm::expect<mem::Ptr>(smem->type(), "a shared-memory pointer 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 '{}'",
508 smem->type());
509 auto name = "@" + smem->unique_name();
510 locals_[smem] = name;
511 std::print(vars_decls_, "{} = internal addrspace({}) global {} undef\n", name, a, convert(T));
512 }
513
514 return kernel->unique_name();
515}
516
517std::optional<std::string> DeviceEmitter::isa_targetspecific_intrinsic(ll::BB& bb, const Def* def) {
518 auto name = id(def);
519
520 if (auto sync_work_items = Axm::isa<gpu::sync_work_items>(def)) {
521 declare("void @llvm.nvvm.barrier0()");
522
523 emit_unsafe(sync_work_items->arg(0));
524 emit_unsafe(sync_work_items->arg(1));
525 std::print(bb.body().emplace_back(), "call void @llvm.nvvm.barrier0()");
526 return name;
527 }
528 return std::nullopt;
529}
530
531void emit_host(World& world, std::ostream& ostream, std::optional<std::string> device_fatbin_file, ll::Emitter::Rt rt) {
532 HostEmitter emitter(world, ostream, device_fatbin_file);
533 emitter.rt_mode(rt);
534 // Same one-liner the `ll` backend uses; each backend just names its own runtime module.
535 if (rt == ll::Emitter::Rt::embed) emitter.load_rt_module("ll_nvptx_rt.ll");
536 emitter.run();
537}
538
540 DeviceEmitter emitter(world, ostream);
541 emitter.run();
542
543 return DeviceEmitFlags{
544 .uses_libdevice = emitter.is_using_libdevice(),
545 };
546}
547
548} // namespace mim::plug::ll_nvptx
static auto isa(const Def *def)
Definition axm.h:107
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...
Definition axm.h:138
Lam * root() const
Definition phase.h:483
Base class for all Defs.
Definition def.h:261
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.
Definition def.cpp:635
Defs deps() const noexcept
Definition def.cpp:514
T * isa_mut() const
If this is mutable, it will cast constness away and perform a dynamic_cast to T.
Definition def.h:527
const Def * type() const noexcept
Yields the "raw" type of this Def (maybe nullptr).
Definition def.cpp:491
std::string unique_name() const
name + "_" + Def::gid
Definition def.cpp:626
static std::optional< nat_t > isa_lit(const Def *def)
Definition def.cpp:666
A function.
Definition lam.h:110
static std::optional< T > isa(const Def *def)
Definition def.h:878
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.
Definition def.h:889
flags_t annex() const
Definition phase.h:81
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
Definition phase.cpp:34
std::string_view name() const
Definition phase.h:80
World & world()
Definition phase.h:77
A dependent tuple type.
Definition tuple.h:22
The World represents the whole program and manages creation of MimIR nodes (Defs).
Definition world.h:36
virtual std::string prepare()
Definition ll.h:367
Emitter(World &world, std::string name, std::ostream &ostream)
Definition ll.h:122
void rt_mode(Rt rt)
Definition ll.h:164
std::string convert_ret_pi(const Pi *)
Definition ll.h:310
void declare(std::format_string< Args... > s, Args &&... args)
Definition ll.h:151
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,...
Definition ll.h:339
std::string id(const Def *, bool force_bb=false) const
Definition ll.h:296
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...
Definition ll.h:181
std::ostringstream vars_decls_
Definition ll.h:224
std::ostringstream func_impls_
Definition ll.h:226
Rt
How the C runtime wrappers (compiled to a <name>.ll via add_mim_runtime) reach the output.
Definition ll.h:159
@ embed
Splice the wrapper IR into the emitted module so it is self-contained.
Definition ll.h:160
void start() override
Actual entry.
Definition ll.h:320
virtual std::string convert(const Def *type, bool simd=true)
Definition ll.h:188
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
Definition ll_nvptx.cpp:517
const std::string & get_extra_flags() const
Definition ll_nvptx.cpp:68
std::string convert(const Def *def, bool simd=false) override
Definition ll_nvptx.cpp:71
void start() final
Actual entry.
Definition ll_nvptx.cpp:452
DeviceEmitter(World &world, std::ostream &ostream)
Definition ll_nvptx.cpp:58
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.
Definition ll_nvptx.cpp:77
std::string prepare() override
Definition ll_nvptx.cpp:461
void start() final
Actual entry.
Definition ll_nvptx.cpp:90
HostEmitter(World &world, std::ostream &ostream, std::optional< std::string > device_fatbin_file)
Definition ll_nvptx.cpp:27
std::optional< std::string > isa_targetspecific_intrinsic(ll::BB &, const Def *) final
Definition ll_nvptx.cpp:164
std::string convert(const Def *, bool simd=true) override
Definition ll_nvptx.cpp:152
#define WLOG(...)
Definition log.h:89
The core Plugin
Definition core.h:8
The gpu Plugin
Definition mem_checks.h:7
The ll_nvptx Plugin
Definition ll_nvptx.h:12
constexpr auto Cu_Device_Get
Definition ll_nvptx.cpp:125
constexpr auto Cu_Memcpy_Htod_Async
Definition ll_nvptx.cpp:132
constexpr auto Cu_Mem_Alloc_Async
Definition ll_nvptx.cpp:128
DeviceEmitFlags emit_device(World &, std::ostream &)
Definition ll_nvptx.cpp:539
constexpr auto Cu_Module_Unload
Definition ll_nvptx.cpp:137
constexpr auto Cu_Memcpy_Dtoh_Async
Definition ll_nvptx.cpp:134
constexpr auto Cu_Memcpy_Htod
Definition ll_nvptx.cpp:131
constexpr auto Cu_Mem_Free_Async
Definition ll_nvptx.cpp:130
constexpr auto Cu_Stream_Sync
Definition ll_nvptx.cpp:140
constexpr auto Cu_Stream_Create
Definition ll_nvptx.cpp:138
constexpr auto Cu_Launch_Kernel
Definition ll_nvptx.cpp:126
constexpr auto Cu_Mem_Free
Definition ll_nvptx.cpp:129
void emit_host(World &, std::ostream &, std::optional< std::string >, ll::Emitter::Rt rt=ll::Emitter::Rt::embed)
Definition ll_nvptx.cpp:531
constexpr auto Cu_Memcpy_Dtoh
Definition ll_nvptx.cpp:133
constexpr auto Cu_Mem_Alloc
Definition ll_nvptx.cpp:127
constexpr auto Cu_Module_Get_Function
Definition ll_nvptx.cpp:136
constexpr auto Cu_Ctx_Create
Definition ll_nvptx.cpp:123
constexpr auto Cu_Stream_Destroy
Definition ll_nvptx.cpp:139
constexpr auto Cu_Ctx_Destroy
Definition ll_nvptx.cpp:124
constexpr auto Cu_Module_Load_Fatbin
Definition ll_nvptx.cpp:135
constexpr auto Cu_Init
Definition ll_nvptx.cpp:122
The ll Plugin
Definition ll.h:37
The mem Plugin
Definition mem.h:11
GIDSet< Lam * > LamSet
Definition lam.h:220
GIDMap< Lam *, To > LamMap
Definition lam.h:219
GIDSet< const Def * > DefSet
Definition def.h:76
Definition span.h:126
std::deque< std::ostringstream > & tail()
Definition ll.h:81
std::deque< std::ostringstream > & body()
Definition ll.h:80
std::string assign(std::string_view name, std::format_string< Args... > s, Args &&... args)
Definition ll.h:84