12using namespace std::string_literals;
13using namespace std::string_view_literals;
19struct NvptxCompileArgs {
20 std::string host_ll_name, dev_ll_name, dev_ptx_name, dev_cubin_name, dev_fatbin_name, dev_bc_raw_name,
23 bool embed_device_code =
true;
25 bool embed_device_code =
false;
27 bool embed_ptx =
true;
28 bool embed_cubin =
true;
29 std::string compute_cap, libdevice_path;
30 std::string link_llvm_args, opt_args = R
"(-passes="default<O2>,nvvm-reflect")", llc_args, ptxas_args,
34constexpr auto Default_Compute_Cap =
"75";
36std::string get_compute_capability() {
38 auto out =
sys::exec(std::format(
"{} --query-gpu=compute_cap --format=csv,noheader", nvidia_smi));
39 std::erase_if(out, ::isspace);
42 auto dot_pos = out.find(
'.');
43 if (dot_pos == std::string::npos) {
44 std::println(std::cerr,
"Could not determine compute capability, continuing with default: '{}'.",
46 return Default_Compute_Cap;
49 for (
size_t i = 0; i < out.size(); ++i) {
50 if (i == dot_pos)
continue;
51 if (!std::isdigit(out[i])) {
52 std::println(std::cerr,
"Could not determine compute capability, continuing with default: '{}'.",
54 return Default_Compute_Cap;
58 auto compute_cap = std::format(
"{}{}", out.substr(0, dot_pos), out.substr(dot_pos + 1));
59 std::println(std::cout,
"Determined compute capability to be '{}'", compute_cap);
63constexpr auto Libdevice_Name =
"libdevice.10.bc"sv;
65std::optional<std::filesystem::path> parse_nvcc_profile(
const std::filesystem::path& cuda_bin_path) {
66 auto profile_path = cuda_bin_path /
"nvcc.profile";
67 if (!std::filesystem::exists(profile_path))
return std::nullopt;
69 std::ifstream file(profile_path);
70 if (!file.is_open())
return std::nullopt;
72 std::string line, top_dir, lib_dir;
74 while (std::getline(file, line)) {
75 std::erase_if(line, ::isspace);
76 if (line.starts_with(
"TOP=")) {
77 auto macro_pos = line.find(
"$(_HERE_)/");
78 if (macro_pos == std::string::npos)
break;
79 top_dir = line.substr(macro_pos + 10);
81 if (line.starts_with(
"NVVMIR_LIBRARY_DIR=")) {
82 auto macro_pos = line.find(
"$(TOP)/");
83 if (macro_pos == std::string::npos)
break;
84 lib_dir = line.substr(macro_pos + 7);
87 if (top_dir.empty() || lib_dir.empty())
return std::nullopt;
88 auto path = cuda_bin_path / top_dir / lib_dir / Libdevice_Name;
89 auto resolved_path = path.lexically_normal();
90 if (!std::filesystem::exists(resolved_path))
return std::nullopt;
94std::string find_libdevice() {
96 if (std::filesystem::exists(nvcc)) {
97 auto nvcc_path = std::filesystem::canonical(nvcc);
98 auto cuda_bin_path = nvcc_path.parent_path();
99 if (
auto libdevice_path = parse_nvcc_profile(cuda_bin_path))
return libdevice_path->string();
101 if (
const char* cuda_home_env = std::getenv(
"CUDA_HOME")) {
102 auto libdevice_path = std::filesystem::path(cuda_home_env) /
"nvvm" /
"libdevice" / Libdevice_Name;
103 if (std::filesystem::exists(libdevice_path))
return libdevice_path.string();
105 auto debian_fallback = std::filesystem::path(
"/usr/lib/nvidia-cuda-toolkit/libdevice/") / Libdevice_Name;
106 if (std::filesystem::exists(debian_fallback))
return debian_fallback.string();
108 fe::throwf<sys::CmdNotFound>(
"Unable to find '{}'. Try setting the CUDA_HOME environment variable.",
112void link_libdevice(
const NvptxCompileArgs& c) {
113 if (!std::filesystem::exists(
c.libdevice_path)) fe::throwf(
"libdevice path does not exist: {}",
c.libdevice_path);
115 sys::require_run(std::format(
"{} {} {} {} -o {}", llvm_link,
c.link_llvm_args,
c.dev_ll_name,
c.libdevice_path,
119void optimize_bytecode(
const NvptxCompileArgs& c) {
121 sys::require_run(std::format(
"{} {} {} -o {}", opt,
c.opt_args,
c.dev_bc_raw_name,
c.dev_bc_opt_name));
124void compile2ptx(
const NvptxCompileArgs& c,
bool uses_libdevice) {
125 auto compile_input = uses_libdevice ?
c.dev_bc_opt_name :
c.dev_ll_name;
127 sys::require_run(std::format(
"{} -march=nvptx64 -mcpu=sm_{} {} {} -o {}", llc,
c.compute_cap,
c.llc_args,
128 compile_input,
c.dev_ptx_name));
131void compile2cubin(
const NvptxCompileArgs& c) {
133 sys::require_run(std::format(
"{} -arch=sm_{} {} {} -o {}", ptxas,
c.compute_cap,
c.ptxas_args,
c.dev_ptx_name,
137void compile2fatbin(
const NvptxCompileArgs& c) {
141 ptx_args = std::format(
"--image3=kind=ptx,sm={},file={}",
c.compute_cap,
c.dev_ptx_name);
142 if (!
c.ptxas_args.empty()) ptx_args += std::format(
" --cmdline={}",
c.ptxas_args);
144 auto cubin_args =
""s;
145 if (
c.embed_cubin) cubin_args = std::format(
"--image3=kind=elf,sm={},file={}",
c.compute_cap,
c.dev_cubin_name);
146 sys::require_run(std::format(
"{} --create={} -64 {} {} {}", fatbinary,
c.dev_fatbin_name,
c.fatbinary_args,
147 ptx_args, cubin_args));
160 auto c = NvptxCompileArgs{};
161 c.host_ll_name =
name +
".ll"s;
162 c.dev_ll_name =
name +
"_dev.ll"s;
163 c.dev_ptx_name =
name +
"_dev.ptx"s;
164 c.dev_cubin_name =
name +
"_dev.cubin"s;
165 c.dev_fatbin_name =
name +
"_dev.fatbin"s;
166 c.dev_bc_raw_name =
name +
"_dev_raw.bc"s;
167 c.dev_bc_opt_name =
name +
"_dev_opt.bc"s;
170 for (
const auto& arg :
args()) {
171 world().DLOG(
"ll backend arg: `{}`", arg);
174 else if (arg.starts_with(
"o=")) c.host_ll_name = arg.substr(2);
175 else if (arg.starts_with(
"output=")) c.host_ll_name = arg.substr(7);
176 else if (arg.starts_with(
"o-dev=")) c.dev_ll_name = arg.substr(6);
177 else if (arg.starts_with(
"output-dev=")) c.dev_ll_name = arg.substr(11);
178 else if (arg.starts_with(
"sm=")) c.compute_cap = arg.substr(3);
179 else if (arg.starts_with(
"libdevice=")) c.libdevice_path = arg.substr(10);
180 else if (arg.starts_with(
"Xlink_llvm=")) c.link_llvm_args = arg.substr(11);
181 else if (arg.starts_with(
"Xopt=")) c.opt_args = arg.substr(5);
182 else if (arg.starts_with(
"Xllc=")) c.llc_args = arg.substr(5);
183 else if (arg.starts_with(
"Xptxas=")) c.ptxas_args = arg.substr(7);
184 else if (arg.starts_with(
"Xfatbinary=")) c.fatbinary_args = arg.substr(11);
185 else if (arg ==
"no-embed") c.embed_device_code =
false;
186 else if (arg ==
"no-ptx-embed") c.embed_ptx =
false;
187 else if (arg ==
"no-cubin-embed") c.embed_cubin =
false;
194 auto setup_phase = split_apply_phase.get()->expect<
RWPhase>(
"%gpu.split_apply to be an RWPhase");
199 auto dev_ofs = std::ofstream(c.dev_ll_name);
200 device_flags =
emit_device(setup_phase->new_world(), dev_ofs);
202 if (c.embed_device_code) {
203 if (!c.embed_ptx && !c.embed_cubin)
204 fe::throwf(
"Embedding requested with no images (neither PTX nor CUBIN).");
206 if (c.compute_cap.empty()) c.compute_cap = get_compute_capability();
208 if (c.libdevice_path.empty()) c.libdevice_path = find_libdevice();
210 optimize_bytecode(c);
216 WLOG(
"{}", e.what());
217 WLOG(
"Falling back to not embedding device code.");
218 c.embed_device_code =
false;
221 auto device_fatbin_file = c.embed_device_code ? std::optional(c.dev_fatbin_name) : std::nullopt;
222 auto host_ofs = std::ofstream(c.host_ll_name);
223 emit_host(setup_phase->old_world(), host_ofs, device_fatbin_file, rt);
225 if (c.embed_device_code) {
226 std::println(std::cout,
"Unified (Fat) LLVM IR written to {}", c.host_ll_name);
228 std::println(std::cout,
"Host-only LLVM IR written to {}", c.host_ll_name);
229 std::println(std::cout,
"Device-only LLVM IR written to {}", c.dev_ll_name);
void reg_phases(Flags2Phases &phases)
static void hook(Flags2Phases &phases)
Phase(World &world, std::string name)
static std::unique_ptr< Phase > create(const Flags2Phases &phases, const Def *def)
virtual void run()
Entry point and generates some debug output; invokes Phase::start.
std::string_view name() const
const Vector< std::string > & args()
Command-line arguments passed to this Phase's plugin via -X <plugin>:<arg>.
Rebuilds old_world() into new_world() and then swaps them.
The World represents the whole program and manages creation of MimIR nodes (Defs).
@ 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.
Emit(World &world, flags_t annex)
Thrown by sys::require_cmd when a command cannot be located on the system.
static void reg_phases(Flags2Phases &phases)
DeviceEmitFlags emit_device(World &, std::ostream &)
void emit_host(World &, std::ostream &, std::optional< std::string >, ll::Emitter::Rt rt=ll::Emitter::Rt::embed)
void require_run(const std::string &cmd)
Runs cmd via sys::system and throws if it exits with a non-zero status.
std::string find_cmd(std::string)
std::string require_cmd(std::string_view name)
Locates name on the system or throws CmdNotFound.
std::string exec(std::string cmd)
Executes command cmd.
absl::flat_hash_map< flags_t, std::function< std::unique_ptr< Phase >(World &)> > Flags2Phases
Maps an axiom of a Phase to a function that creates one.
mim::Plugin mim_get_plugin()
Basic info and registration function pointer to be returned from a specific plugin.