mesa/src/compiler/spirv/spirv2nir.c

338 lines
10 KiB
C

/*
* Copyright © 2015 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
/*
* A simple executable that opens a SPIR-V shader, converts it to NIR, and
* dumps out the result. This should be useful for testing the
* spirv_to_nir code.
*/
#include "nir.h"
#include "nir_spirv.h"
#include "spirv.h"
#include "util/u_dynarray.h"
#include "vtn_private.h"
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#define WORD_SIZE 4
struct {
const char *name;
gl_shader_stage stage;
} abbrev_stage_table[] = {
{ "vs", MESA_SHADER_VERTEX },
{ "tcs", MESA_SHADER_TESS_CTRL },
{ "tes", MESA_SHADER_TESS_EVAL },
{ "gs", MESA_SHADER_GEOMETRY },
{ "fs", MESA_SHADER_FRAGMENT },
{ "cs", MESA_SHADER_COMPUTE },
{ "cl", MESA_SHADER_KERNEL },
{ "task", MESA_SHADER_TASK },
{ "mesh", MESA_SHADER_MESH },
/* Keep previously used shader names working. */
{ "vertex", MESA_SHADER_VERTEX },
{ "tess-ctrl", MESA_SHADER_TESS_CTRL },
{ "tess-eval", MESA_SHADER_TESS_EVAL },
{ "geometry", MESA_SHADER_GEOMETRY },
{ "fragment", MESA_SHADER_FRAGMENT },
{ "compute", MESA_SHADER_COMPUTE },
{ "kernel", MESA_SHADER_KERNEL },
};
static gl_shader_stage
abbrev_to_stage(const char *name)
{
for (unsigned i = 0; i < ARRAY_SIZE(abbrev_stage_table); i++) {
if (!strcasecmp(abbrev_stage_table[i].name, name))
return abbrev_stage_table[i].stage;
}
return MESA_SHADER_NONE;
}
static const char *
stage_to_abbrev(gl_shader_stage stage)
{
for (unsigned i = 0; i < ARRAY_SIZE(abbrev_stage_table); i++) {
if (abbrev_stage_table[i].stage == stage)
return abbrev_stage_table[i].name;
}
return "UNKNOWN";
}
static void
print_usage(char *exec_name, FILE *f)
{
fprintf(f,
"Usage: %s [options] file\n"
"Options:\n"
" -h --help Print this help.\n"
" -s, --stage <stage> Specify the shader stage. Valid stages are:\n"
" vs, tcs, tes, gs, fs, cs, cl (OpenCL-style compute),\n"
" task and mesh. Case insensitive.\n"
" -e, --entry <name> Specify the entry-point name.\n"
" -g, --opengl Use OpenGL environment instead of Vulkan for\n"
" graphics stages.\n"
" --optimize Run basic NIR optimizations in the result.\n"
"\n"
"Passing the stage and the entry-point name is optional unless there's\n"
"ambiguity, in which case the program will print the entry-points\n"
"available.",
exec_name);
}
struct entry_point {
const char *name;
gl_shader_stage stage;
};
static struct entry_point
select_entry_point(void *mem_ctx, const uint32_t *words, size_t word_count,
struct entry_point args)
{
/* Create a dummy vtn_builder to use with vtn_string_literal. */
struct vtn_builder *b = rzalloc(mem_ctx, struct vtn_builder);
struct util_dynarray candidates;
util_dynarray_init(&candidates, mem_ctx);
/* Skip header. */
const uint32_t *w = words + 5;
const uint32_t *end = words + word_count;
bool seen_entry_point = false;
while (w < end) {
SpvOp opcode = w[0] & SpvOpCodeMask;
unsigned count = w[0] >> SpvWordCountShift;
assert(count >= 1 && w + count <= end);
if (opcode == SpvOpEntryPoint) {
seen_entry_point = true;
unsigned name_words;
const char *name = vtn_string_literal(b, &w[3], count - 3, &name_words);
gl_shader_stage stage = vtn_stage_for_execution_model(w[1]);
struct entry_point e = { name, stage };
util_dynarray_append(&candidates, struct entry_point, e);
} else if (seen_entry_point) {
/* List of entry_points is over, we can break now. */
break;
}
w += count;
}
struct entry_point r = {0};
if (util_dynarray_num_elements(&candidates, struct entry_point) == 0) {
fprintf(stderr, "ERROR: No entry-points available.\n");
return r;
}
int matches = 0;
util_dynarray_foreach(&candidates, struct entry_point, e) {
if ((!args.name || !strcmp(args.name, e->name)) &&
(args.stage == MESA_SHADER_NONE || args.stage == e->stage)) {
if (matches == 0) {
/* Save the first match we found. */
r = *e;
}
matches++;
}
}
if (matches != 1) {
if (matches == 0) {
fprintf(stderr, "No matching entry-point for arguments passed.\n");
} else {
fprintf(stderr, "Multiple entry-points available, select with --stage and/or --entry.\n");
/* Discard whatever we found before. */
r.name = NULL;
r.stage = MESA_SHADER_NONE;
}
fprintf(stderr, "Entry-points available:\n");
util_dynarray_foreach(&candidates, struct entry_point, e)
fprintf(stderr, " --entry e \"%s\" --stage %s\n", e->name, stage_to_abbrev(e->stage));
}
return r;
}
int main(int argc, char **argv)
{
struct entry_point entry_point = {
.name = NULL,
.stage = MESA_SHADER_NONE,
};
int ch;
bool optimize = false;
enum nir_spirv_execution_environment env = NIR_SPIRV_VULKAN;
static struct option long_options[] =
{
{"help", no_argument, 0, 'h'},
{"stage", required_argument, 0, 's'},
{"entry", required_argument, 0, 'e'},
{"opengl", no_argument, 0, 'g'},
{"optimize", no_argument, 0, 'O'},
{0, 0, 0, 0}
};
while ((ch = getopt_long(argc, argv, "hs:e:g", long_options, NULL)) != -1) {
switch (ch) {
case 'h':
print_usage(argv[0], stdout);
return 0;
case 's': {
gl_shader_stage s = abbrev_to_stage(optarg);
if (s == MESA_SHADER_NONE) {
fprintf(stderr, "Unknown stage \"%s\"\n", optarg);
print_usage(argv[0], stderr);
return 1;
}
entry_point.stage = s;
break;
}
case 'e':
entry_point.name = optarg;
break;
case 'g':
env = NIR_SPIRV_OPENGL;
break;
case 'O':
optimize = true;
break;
default:
fprintf(stderr, "Unrecognized option \"%s\".\n", optarg);
print_usage(argv[0], stderr);
return 1;
}
}
const char *filename = argv[optind];
int fd = open(filename, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Failed to open %s\n", filename);
return 1;
}
off_t len = lseek(fd, 0, SEEK_END);
if (len % WORD_SIZE != 0) {
fprintf(stderr, "File length isn't a multiple of the word size\n");
fprintf(stderr, "Are you sure this is a valid SPIR-V shader?\n");
close(fd);
return 1;
}
size_t word_count = len / WORD_SIZE;
const void *map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "Failed to mmap the file: errno=%d, %s\n",
errno, strerror(errno));
close(fd);
return 1;
}
const uint32_t *words = map;
if (words[0] != SpvMagicNumber) {
fprintf(stderr, "ERROR: Not a SPIR-V file. First word was 0x%x, want 0x%x (SPIR-V magic).",
words[0], SpvMagicNumber);
return 1;
}
void *mem_ctx = ralloc_context(NULL);
entry_point = select_entry_point(mem_ctx, map, word_count, entry_point);
if (!entry_point.name)
return 1;
glsl_type_singleton_init_or_ref();
struct nir_shader_compiler_options nir_opts = {0};
struct spirv_to_nir_options spirv_opts = {
.environment = env,
};
if (entry_point.stage == MESA_SHADER_KERNEL)
spirv_opts.environment = NIR_SPIRV_OPENCL;
nir_shader *nir = spirv_to_nir(map, word_count, NULL, 0,
entry_point.stage, entry_point.name,
&spirv_opts, &nir_opts);
if (nir) {
if (optimize) {
bool progress;
do {
progress = false;
#define OPT(pass, ...) ({ \
bool this_progress = false; \
NIR_PASS(this_progress, nir, pass, ##__VA_ARGS__); \
if (this_progress) \
progress = true; \
this_progress; \
})
OPT(nir_opt_dce);
OPT(nir_opt_cse);
OPT(nir_opt_dead_cf);
OPT(nir_lower_vars_to_ssa);
OPT(nir_copy_prop);
OPT(nir_opt_deref);
OPT(nir_opt_constant_folding);
OPT(nir_opt_copy_prop_vars);
OPT(nir_opt_dead_write_vars);
OPT(nir_opt_combine_stores, nir_var_all);
OPT(nir_remove_dead_variables, nir_var_function_temp, NULL);
OPT(nir_opt_algebraic);
OPT(nir_opt_if, 0);
OPT(nir_opt_loop_unroll);
#undef OPT
} while (progress);
}
nir_print_shader(nir, stdout);
} else {
fprintf(stderr, "SPIRV to NIR compilation failed\n");
}
glsl_type_singleton_decref();
ralloc_free(mem_ctx);
return 0;
}