1 /*
2 * Copyright © 2015 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 /*
25 * A simple executable that opens a SPIR-V shader, converts it to NIR, and
26 * dumps out the result. This should be useful for testing the
27 * spirv_to_nir code.
28 */
29
30 #include "nir.h"
31 #include "nir_spirv.h"
32 #include "spirv.h"
33 #include "util/u_dynarray.h"
34 #include "vtn_private.h"
35
36 #include <sys/mman.h>
37 #include <sys/types.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <stdio.h>
41 #include <errno.h>
42 #include <string.h>
43 #include <getopt.h>
44
45 #define WORD_SIZE 4
46
47 struct {
48 const char *name;
49 gl_shader_stage stage;
50 } abbrev_stage_table[] = {
51 { "vs", MESA_SHADER_VERTEX },
52 { "tcs", MESA_SHADER_TESS_CTRL },
53 { "tes", MESA_SHADER_TESS_EVAL },
54 { "gs", MESA_SHADER_GEOMETRY },
55 { "fs", MESA_SHADER_FRAGMENT },
56 { "cs", MESA_SHADER_COMPUTE },
57 { "cl", MESA_SHADER_KERNEL },
58 { "task", MESA_SHADER_TASK },
59 { "mesh", MESA_SHADER_MESH },
60
61 /* Keep previously used shader names working. */
62 { "vertex", MESA_SHADER_VERTEX },
63 { "tess-ctrl", MESA_SHADER_TESS_CTRL },
64 { "tess-eval", MESA_SHADER_TESS_EVAL },
65 { "geometry", MESA_SHADER_GEOMETRY },
66 { "fragment", MESA_SHADER_FRAGMENT },
67 { "compute", MESA_SHADER_COMPUTE },
68 { "kernel", MESA_SHADER_KERNEL },
69 };
70
71 static gl_shader_stage
abbrev_to_stage(const char * name)72 abbrev_to_stage(const char *name)
73 {
74 for (unsigned i = 0; i < ARRAY_SIZE(abbrev_stage_table); i++) {
75 if (!strcasecmp(abbrev_stage_table[i].name, name))
76 return abbrev_stage_table[i].stage;
77 }
78 return MESA_SHADER_NONE;
79 }
80
81 static const char *
stage_to_abbrev(gl_shader_stage stage)82 stage_to_abbrev(gl_shader_stage stage)
83 {
84 for (unsigned i = 0; i < ARRAY_SIZE(abbrev_stage_table); i++) {
85 if (abbrev_stage_table[i].stage == stage)
86 return abbrev_stage_table[i].name;
87 }
88 return "UNKNOWN";
89 }
90
91 static void
print_usage(char * exec_name,FILE * f)92 print_usage(char *exec_name, FILE *f)
93 {
94 fprintf(f,
95 "Usage: %s [options] file\n"
96 "Options:\n"
97 " -h --help Print this help.\n"
98 " -s, --stage <stage> Specify the shader stage. Valid stages are:\n"
99 " vs, tcs, tes, gs, fs, cs, cl (OpenCL-style compute),\n"
100 " task and mesh. Case insensitive.\n"
101 " -e, --entry <name> Specify the entry-point name.\n"
102 " -g, --opengl Use OpenGL environment instead of Vulkan for\n"
103 " graphics stages.\n"
104 " --optimize Run basic NIR optimizations in the result.\n"
105 "\n"
106 "Passing the stage and the entry-point name is optional unless there's\n"
107 "ambiguity, in which case the program will print the entry-points\n"
108 "available.",
109 exec_name);
110 }
111
112 struct entry_point {
113 const char *name;
114 gl_shader_stage stage;
115 };
116
117 static struct entry_point
select_entry_point(void * mem_ctx,const uint32_t * words,size_t word_count,struct entry_point args)118 select_entry_point(void *mem_ctx, const uint32_t *words, size_t word_count,
119 struct entry_point args)
120 {
121 /* Create a dummy vtn_builder to use with vtn_string_literal. */
122 struct vtn_builder *b = rzalloc(mem_ctx, struct vtn_builder);
123
124 struct util_dynarray candidates;
125 util_dynarray_init(&candidates, mem_ctx);
126
127 /* Skip header. */
128 const uint32_t *w = words + 5;
129 const uint32_t *end = words + word_count;
130
131 bool seen_entry_point = false;
132 while (w < end) {
133 SpvOp opcode = w[0] & SpvOpCodeMask;
134 unsigned count = w[0] >> SpvWordCountShift;
135 assert(count >= 1 && w + count <= end);
136
137 if (opcode == SpvOpEntryPoint) {
138 seen_entry_point = true;
139
140 unsigned name_words;
141 const char *name = vtn_string_literal(b, &w[3], count - 3, &name_words);
142 gl_shader_stage stage = vtn_stage_for_execution_model(w[1]);
143
144 struct entry_point e = { name, stage };
145 util_dynarray_append(&candidates, struct entry_point, e);
146 } else if (seen_entry_point) {
147 /* List of entry_points is over, we can break now. */
148 break;
149 }
150
151 w += count;
152 }
153
154 struct entry_point r = {0};
155 if (util_dynarray_num_elements(&candidates, struct entry_point) == 0) {
156 fprintf(stderr, "ERROR: No entry-points available.\n");
157 return r;
158 }
159
160 int matches = 0;
161 util_dynarray_foreach(&candidates, struct entry_point, e) {
162 if ((!args.name || !strcmp(args.name, e->name)) &&
163 (args.stage == MESA_SHADER_NONE || args.stage == e->stage)) {
164 if (matches == 0) {
165 /* Save the first match we found. */
166 r = *e;
167 }
168 matches++;
169 }
170 }
171
172 if (matches != 1) {
173 if (matches == 0) {
174 fprintf(stderr, "No matching entry-point for arguments passed.\n");
175 } else {
176 fprintf(stderr, "Multiple entry-points available, select with --stage and/or --entry.\n");
177
178 /* Discard whatever we found before. */
179 r.name = NULL;
180 r.stage = MESA_SHADER_NONE;
181 }
182
183 fprintf(stderr, "Entry-points available:\n");
184 util_dynarray_foreach(&candidates, struct entry_point, e)
185 fprintf(stderr, " --entry e \"%s\" --stage %s\n", e->name, stage_to_abbrev(e->stage));
186 }
187
188 return r;
189 }
190
main(int argc,char ** argv)191 int main(int argc, char **argv)
192 {
193 struct entry_point entry_point = {
194 .name = NULL,
195 .stage = MESA_SHADER_NONE,
196 };
197 int ch;
198 bool optimize = false;
199 enum nir_spirv_execution_environment env = NIR_SPIRV_VULKAN;
200
201 static struct option long_options[] =
202 {
203 {"help", no_argument, 0, 'h'},
204 {"stage", required_argument, 0, 's'},
205 {"entry", required_argument, 0, 'e'},
206 {"opengl", no_argument, 0, 'g'},
207 {"optimize", no_argument, 0, 'O'},
208 {0, 0, 0, 0}
209 };
210
211 while ((ch = getopt_long(argc, argv, "hs:e:g", long_options, NULL)) != -1) {
212 switch (ch) {
213 case 'h':
214 print_usage(argv[0], stdout);
215 return 0;
216 case 's': {
217 gl_shader_stage s = abbrev_to_stage(optarg);
218 if (s == MESA_SHADER_NONE) {
219 fprintf(stderr, "Unknown stage \"%s\"\n", optarg);
220 print_usage(argv[0], stderr);
221 return 1;
222 }
223 entry_point.stage = s;
224 break;
225 }
226 case 'e':
227 entry_point.name = optarg;
228 break;
229 case 'g':
230 env = NIR_SPIRV_OPENGL;
231 break;
232 case 'O':
233 optimize = true;
234 break;
235 default:
236 fprintf(stderr, "Unrecognized option \"%s\".\n", optarg);
237 print_usage(argv[0], stderr);
238 return 1;
239 }
240 }
241
242 const char *filename = argv[optind];
243 int fd = open(filename, O_RDONLY);
244 if (fd < 0) {
245 fprintf(stderr, "Failed to open %s\n", filename);
246 return 1;
247 }
248
249 off_t len = lseek(fd, 0, SEEK_END);
250 if (len % WORD_SIZE != 0) {
251 fprintf(stderr, "File length isn't a multiple of the word size\n");
252 fprintf(stderr, "Are you sure this is a valid SPIR-V shader?\n");
253 close(fd);
254 return 1;
255 }
256
257 size_t word_count = len / WORD_SIZE;
258
259 const void *map = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
260 if (map == MAP_FAILED) {
261 fprintf(stderr, "Failed to mmap the file: errno=%d, %s\n",
262 errno, strerror(errno));
263 close(fd);
264 return 1;
265 }
266
267 const uint32_t *words = map;
268 if (words[0] != SpvMagicNumber) {
269 fprintf(stderr, "ERROR: Not a SPIR-V file. First word was 0x%x, want 0x%x (SPIR-V magic).",
270 words[0], SpvMagicNumber);
271 return 1;
272 }
273
274 void *mem_ctx = ralloc_context(NULL);
275
276 entry_point = select_entry_point(mem_ctx, map, word_count, entry_point);
277 if (!entry_point.name)
278 return 1;
279
280 glsl_type_singleton_init_or_ref();
281
282 struct nir_shader_compiler_options nir_opts = {0};
283
284 struct spirv_to_nir_options spirv_opts = {
285 .environment = env,
286 };
287
288 if (entry_point.stage == MESA_SHADER_KERNEL)
289 spirv_opts.environment = NIR_SPIRV_OPENCL;
290
291 nir_shader *nir = spirv_to_nir(map, word_count, NULL, 0,
292 entry_point.stage, entry_point.name,
293 &spirv_opts, &nir_opts);
294
295 if (nir) {
296 if (optimize) {
297 bool progress;
298 do {
299 progress = false;
300
301 #define OPT(pass, ...) ({ \
302 bool this_progress = false; \
303 NIR_PASS(this_progress, nir, pass, ##__VA_ARGS__); \
304 if (this_progress) \
305 progress = true; \
306 this_progress; \
307 })
308
309 OPT(nir_opt_dce);
310 OPT(nir_opt_cse);
311 OPT(nir_opt_dead_cf);
312 OPT(nir_lower_vars_to_ssa);
313 OPT(nir_copy_prop);
314 OPT(nir_opt_deref);
315 OPT(nir_opt_constant_folding);
316 OPT(nir_opt_copy_prop_vars);
317 OPT(nir_opt_dead_write_vars);
318 OPT(nir_opt_combine_stores, nir_var_all);
319 OPT(nir_remove_dead_variables, nir_var_function_temp, NULL);
320 OPT(nir_opt_algebraic);
321 OPT(nir_opt_if, 0);
322 OPT(nir_opt_loop_unroll);
323
324 #undef OPT
325 } while (progress);
326 }
327 nir_print_shader(nir, stdout);
328 } else {
329 fprintf(stderr, "SPIRV to NIR compilation failed\n");
330 }
331
332 glsl_type_singleton_decref();
333
334 ralloc_free(mem_ctx);
335
336 return 0;
337 }
338