blob: a28c0c6e33e1f636012fdaaca3e85befed71b7fc [file] [log] [blame]
/*
* Copyright 2024 Valve Corporation
* Copyright 2023 Alyssa Rosenzweig
* SPDX-License-Identifier: MIT
*/
#include "compiler/spirv/nir_spirv.h"
#include "util/u_printf.h"
#include "glsl_types.h"
#include "nir.h"
#include "nir_builder.h"
#include "nir_builder_opcodes.h"
#include "nir_precompiled.h"
#include "nir_serialize.h"
static const struct spirv_to_nir_options spirv_options = {
.environment = NIR_SPIRV_OPENCL,
.shared_addr_format = nir_address_format_62bit_generic,
.global_addr_format = nir_address_format_62bit_generic,
.temp_addr_format = nir_address_format_62bit_generic,
.constant_addr_format = nir_address_format_64bit_global,
.create_library = true,
.printf = true,
};
struct nir_shader_compiler_options generic_opts = {
/* TODO: Do we want to set has_*? Will drivers be able to lower
* appropriately?
*/
.fuse_ffma16 = true,
.fuse_ffma32 = true,
.fuse_ffma64 = true,
.max_unroll_iterations = 32,
.max_unroll_iterations_fp64 = 32,
};
static bool
rewrite_return(nir_builder *b, nir_intrinsic_instr *intr, void *return_deref)
{
if (intr->intrinsic != nir_intrinsic_load_param)
return false;
unsigned idx = nir_intrinsic_param_idx(intr);
if (idx == 0)
nir_def_replace(&intr->def, return_deref);
else
nir_intrinsic_set_param_idx(intr, idx - 1);
return true;
}
static void
lower_to_bindgen_return(nir_shader *nir)
{
nir_foreach_function(libfunc, nir) {
bool returns = libfunc->params[0].is_return;
libfunc->pass_flags = returns;
if (!returns)
continue;
nir_variable *ret = nir_local_variable_create(
libfunc->impl, libfunc->params[0].type, "return");
nir_builder b = nir_builder_at(nir_before_impl(libfunc->impl));
nir_deref_instr *deref = nir_build_deref_var(&b, ret);
nir_function_intrinsics_pass(libfunc->impl, rewrite_return,
nir_metadata_control_flow, &deref->def);
b.cursor = nir_after_impl(libfunc->impl);
nir_bindgen_return(&b, nir_load_var(&b, ret));
/* Remove the first parameter (the return deref), leaving only the true
* parameters.
*/
libfunc->num_params--;
memmove(libfunc->params, libfunc->params + 1,
sizeof(libfunc->params[0]) * libfunc->num_params);
}
}
/* Standard optimization loop */
static void
optimize(nir_shader *nir)
{
bool progress;
do {
progress = false;
NIR_PASS(progress, nir, nir_lower_vars_to_ssa);
NIR_PASS(progress, nir, nir_copy_prop);
NIR_PASS(progress, nir, nir_opt_remove_phis);
NIR_PASS(progress, nir, nir_lower_all_phis_to_scalar);
NIR_PASS(progress, nir, nir_opt_dce);
NIR_PASS(progress, nir, nir_opt_dead_cf);
NIR_PASS(progress, nir, nir_opt_cse);
nir_opt_peephole_select_options peephole_select_options = {
.limit = 64,
.expensive_alu_ok = true,
};
NIR_PASS(progress, nir, nir_opt_peephole_select, &peephole_select_options);
NIR_PASS(progress, nir, nir_opt_phi_precision);
NIR_PASS(progress, nir, nir_opt_algebraic);
NIR_PASS(progress, nir, nir_opt_constant_folding);
NIR_PASS(progress, nir, nir_opt_deref);
NIR_PASS(progress, nir, nir_opt_copy_prop_vars);
NIR_PASS(progress, nir, nir_opt_undef);
NIR_PASS(progress, nir, nir_opt_loop_unroll);
NIR_PASS(progress, nir, nir_opt_loop);
} while (progress);
NIR_PASS(progress, nir, nir_opt_shrink_vectors, true);
}
static nir_shader *
compile(void *memctx, const uint32_t *spirv, size_t spirv_size)
{
const nir_shader_compiler_options *nir_options = &generic_opts;
assert(spirv_size % 4 == 0);
nir_shader *nir =
spirv_to_nir(spirv, spirv_size / 4, NULL, 0, MESA_SHADER_KERNEL,
"library", &spirv_options, nir_options);
nir_validate_shader(nir, "after spirv_to_nir");
ralloc_steal(memctx, nir);
nir_fixup_is_exported(nir);
/* At the moment, entrypoints will be compiled to binaries by a different
* tool, remove them as we are only interested in library functions for
* bindgen.
*
* A future version of vtn_bindgen will handle the entrypoints too.
*/
nir_remove_entrypoints(nir);
NIR_PASS(_, nir, nir_lower_system_values);
NIR_PASS(_, nir, nir_lower_calls_to_builtins);
nir_lower_compute_system_values_options cs = {.global_id_is_32bit = true};
NIR_PASS(_, nir, nir_lower_compute_system_values, &cs);
NIR_PASS(_, nir, nir_lower_printf,
&(const struct nir_lower_printf_options){
.hash_format_strings = true,
});
/* We have to lower away local constant initializers right before we
* inline functions. That way they get properly initialized at the top
* of the function and not at the top of its caller.
*/
NIR_PASS(_, nir, nir_lower_variable_initializers, nir_var_function_temp);
NIR_PASS(_, nir, nir_lower_returns);
NIR_PASS(_, nir, nir_inline_functions);
nir_remove_non_exported(nir);
NIR_PASS(_, nir, nir_copy_prop);
NIR_PASS(_, nir, nir_opt_deref);
/* We can't deal with constant data, get rid of it */
nir_lower_constant_to_temp(nir);
/* We can go ahead and lower the rest of the constant initializers. We do
* this here so that nir_remove_dead_variables and split_per_member_structs
* below see the corresponding stores.
*/
NIR_PASS(_, nir, nir_lower_variable_initializers, ~0);
/* LLVM loves take advantage of the fact that vec3s in OpenCL are 16B
* aligned and so it can just read/write them as vec4s. This results in a
* LOT of vec4->vec3 casts on loads and stores. One solution to this
* problem is to get rid of all vec3 variables.
*/
NIR_PASS(_, nir, nir_lower_vec3_to_vec4,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global | nir_var_mem_constant);
/* Bit more lowering... this doesn't seem to be load-bearing though.. */
NIR_PASS(_, nir, nir_split_var_copies);
NIR_PASS(_, nir, nir_split_struct_vars, nir_var_function_temp);
NIR_PASS(_, nir, nir_lower_var_copies);
/* We assign explicit types early so that the optimizer can take advantage
* of that information and hopefully get rid of some of our memcpys.
*/
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
nir_var_uniform | nir_var_shader_temp | nir_var_function_temp |
nir_var_mem_shared | nir_var_mem_global,
glsl_get_cl_type_size_align);
NIR_PASS(_, nir, nir_remove_dead_variables, nir_var_all, NULL);
/* Lower again, this time after dead-variables to get more compact variable
* layouts.
*/
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global | nir_var_mem_constant,
glsl_get_cl_type_size_align);
assert(nir->constant_data_size == 0);
NIR_PASS(_, nir, nir_lower_memcpy);
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_mem_constant,
nir_address_format_64bit_global);
NIR_PASS(_, nir, nir_lower_explicit_io, nir_var_uniform,
nir_address_format_32bit_offset_as_64bit);
lower_to_bindgen_return(nir);
NIR_PASS(_, nir, nir_opt_deref);
NIR_PASS(_, nir, nir_lower_convert_alu_types, NULL);
NIR_PASS(_, nir, nir_opt_if, 0);
optimize(nir);
/* Now lower returns so we can get rid of derefs */
NIR_PASS(_, nir, nir_lower_vars_to_ssa);
NIR_PASS(_, nir, nir_lower_vars_to_explicit_types,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global | nir_var_mem_constant,
glsl_get_cl_type_size_align);
NIR_PASS(_, nir, nir_lower_explicit_io,
nir_var_shader_temp | nir_var_function_temp | nir_var_mem_shared |
nir_var_mem_global,
nir_address_format_62bit_generic);
/* Try to optimize scratch access, since LLVM loooves its scratch. If this
* makes progress, we need to lower the results.
*/
bool scratch_lowered = false;
NIR_PASS(scratch_lowered, nir, nir_lower_scratch_to_var);
if (scratch_lowered) {
NIR_PASS(_, nir, nir_lower_indirect_derefs, nir_var_function_temp, ~0);
}
/* Prune derefs/variables late, since scratch lowering leaves dead
* derefs/variables and there's no point rerunning these passes.
*/
NIR_PASS(_, nir, nir_remove_dead_derefs);
NIR_PASS(_, nir, nir_remove_dead_variables,
nir_var_function_temp | nir_var_shader_temp, NULL);
/* Do a last round of clean up after the extra lowering */
NIR_PASS(_, nir, nir_copy_prop);
NIR_PASS(_, nir, nir_opt_constant_folding);
NIR_PASS(_, nir, nir_opt_algebraic);
NIR_PASS(_, nir, nir_opt_cse);
NIR_PASS(_, nir, nir_opt_dce);
/* Re-index SSA defs at the very end to make the NIR more legible. This
* doesn't matter for correctness, but it's polite.
*/
nir_foreach_function_impl(it, nir) {
nir_index_ssa_defs(it);
}
return nir;
}
static void
print_signature(FILE *fp, nir_function *f)
{
bool returns = f->pass_flags;
fprintf(fp, "%s\n", returns ? "nir_def *" : "void");
fprintf(fp, "%s(nir_builder *b", f->name);
for (unsigned i = 0; i < f->num_params; ++i) {
fprintf(fp, ", nir_def *%s", f->params[i].name);
}
fprintf(fp, ")");
}
int
main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "Usage: %s [input spir-v] [output .c] [output .h]\n",
argv[0]);
return 1;
}
const char *infile = argv[1];
const char *outcfile = argv[2];
const char *outhfile = argv[3];
void *mem_ctx = ralloc_context(NULL);
FILE *fin = fopen(infile, "rb");
if (!fin) {
fprintf(stderr, "Failed to open %s\n", infile);
return 1;
}
fseek(fin, 0L, SEEK_END);
size_t len = ftell(fin);
rewind(fin);
uint32_t *map = malloc(ALIGN_POT(len, 4));
if (!map) {
fprintf(stderr, "Failed to allocate");
fclose(fin);
return 1;
}
fread(map, 1, len, fin);
fclose(fin);
FILE *fp_c = fopen(outcfile, "w");
if (!fp_c) {
fprintf(stderr, "Failed to open %s\n", outcfile);
free(map);
return 1;
}
FILE *fp_h = fopen(outhfile, "w");
if (!fp_h) {
fprintf(stderr, "Failed to open %s\n", outhfile);
free(map);
fclose(fp_c);
return 1;
}
glsl_type_singleton_init_or_ref();
for (unsigned i = 0; i < 2; ++i) {
FILE *fp = i ? fp_c : fp_h;
fprintf(fp, "/*\n");
fprintf(fp, " * Copyright Mesa3D Contributors\n");
fprintf(fp, " * SPDX-License-Identifier: MIT\n");
fprintf(fp, " *\n");
fprintf(fp, " * Autogenerated file, do not edit\n");
fprintf(fp, " */\n\n");
if (fp == fp_h) {
fprintf(fp, "#pragma once\n\n");
}
fprintf(fp, "#include \"compiler/nir/nir.h\"\n");
fprintf(fp, "#include \"compiler/nir/nir_builder.h\"\n\n");
fprintf(fp, "#include \"util/u_printf.h\"\n\n");
fprintf(fp, "#ifdef __cplusplus\n");
fprintf(fp, "extern \"C\" {\n");
fprintf(fp, "#endif\n");
}
nir_shader *nir = compile(mem_ctx, map, len);
nir_foreach_function(libfunc, nir) {
bool returns = libfunc->pass_flags;
/* Declare the function in the generated header */
print_signature(fp_h, libfunc);
fprintf(fp_h, ";\n\n");
/* We don't know where the header will end up on the file system, so we
* manually declare the signatures.
*/
print_signature(fp_c, libfunc);
fprintf(fp_c, ";\n\n");
print_signature(fp_c, libfunc);
fprintf(fp_c, "\n{\n");
struct blob blob;
blob_init(&blob);
nir_serialize_function(&blob, libfunc);
fprintf(fp_c, " /*\n");
nir_print_function_body(libfunc->impl, fp_c);
fprintf(fp_c, " */\n");
fprintf(fp_c, " ");
nir_precomp_print_blob(fp_c, "impl", "nir", 0,
(const uint32_t *)blob.data, blob.size, true);
blob_finish(&blob);
if (libfunc->num_params > 0) {
fprintf(fp_c, " nir_def *args[%u] = { ", libfunc->num_params);
for (unsigned a = 0; a < libfunc->num_params; ++a) {
fprintf(fp_c, "%s%s", a ? ", " : "", libfunc->params[a].name);
}
fprintf(fp_c, " };\n");
}
fprintf(fp_c, " ");
if (returns)
fprintf(fp_c, "return ");
fprintf(fp_c,
"nir_call_serialized(b, impl_0_nir, sizeof(impl_0_nir), %s);",
libfunc->num_params > 0 ? "args" : "NULL");
fprintf(fp_c, "\n}\n\n");
}
for (unsigned i = 0; i < 2; ++i) {
FILE *fp = i ? fp_c : fp_h;
fprintf(fp, "#ifdef __cplusplus\n");
fprintf(fp, "} /* extern C */\n");
fprintf(fp, "#endif\n");
}
fprintf(fp_c, "namespace {\n");
fprintf(fp_c, " struct vtn_bindgen_dummy {\n");
fprintf(fp_c, " vtn_bindgen_dummy() {\n");
fprintf(fp_c, " /* Format strings:\n");
fprintf(fp_c, " *\n");
for (unsigned i = 0; i < nir->printf_info_count; ++i) {
u_printf_info *info = &nir->printf_info[i];
const char *str = info->strings;
fprintf(fp_c, " * ");
for (unsigned j = 0; j < strlen(str); ++j) {
char c = str[j];
if (c == '\n')
fprintf(fp_c, "\\n");
else if (c == '/' && j && str[j - 1] == '*')
fprintf(fp_c, "\\/");
else
fprintf(fp_c, "%c", c);
}
fprintf(fp_c, "\n");
}
fprintf(fp_c, " */\n");
/* Stuff printf info into Mesa's singleton */
struct blob blob;
blob_init(&blob);
u_printf_serialize_info(&blob, nir->printf_info, nir->printf_info_count);
nir_precomp_print_blob(fp_c, "printf", "blob", 0,
(const uint32_t *)blob.data, blob.size, false);
blob_finish(&blob);
fprintf(fp_c, " u_printf_singleton_init_or_ref();\n");
fprintf(
fp_c,
" u_printf_singleton_add_serialized((const void*)printf_0_blob, sizeof(printf_0_blob));\n");
fprintf(fp_c, " }\n");
fprintf(fp_c, "\n");
fprintf(fp_c, " ~vtn_bindgen_dummy() {\n");
fprintf(fp_c, " u_printf_singleton_decref();\n");
fprintf(fp_c, " }\n");
fprintf(fp_c, " };\n");
fprintf(fp_c, "\n");
fprintf(fp_c, " static vtn_bindgen_dummy vtn_bindgen_dummy_instance;\n");
fprintf(fp_c, "}\n");
glsl_type_singleton_decref();
fclose(fp_c);
fclose(fp_h);
free(map);
ralloc_free(mem_ctx);
return 0;
}