| /* |
| * 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; |
| } |