1 /*
2  * Copyright © 2022 Imagination Technologies Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * 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 THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 
24 #include "compiler/shader_enums.h"
25 #include "nir/nir.h"
26 #include "rogue.h"
27 #include "rogue_build_data.h"
28 #include "rogue_compiler.h"
29 #include "rogue_dump.h"
30 #include "util/os_file.h"
31 #include "util/ralloc.h"
32 
33 #include <getopt.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 
40 /* Number of hex columns to dump before starting a new line. */
41 #define ARRAY_DUMP_COLS 16
42 
43 /**
44  * \file compiler.c
45  *
46  * \brief Rogue offline compiler.
47  */
48 
49 static const struct option cmdline_opts[] = {
50    /* Arguments. */
51    { "stage", required_argument, NULL, 's' },
52    { "file", required_argument, NULL, 'f' },
53    { "entry", required_argument, NULL, 'e' },
54 
55    /* Options. */
56    { "help", no_argument, NULL, 'h' },
57    { "out", required_argument, NULL, 'o' },
58 
59    { "dump-c-array", no_argument, NULL, 'c' },
60    { "dump-rogue", no_argument, NULL, 'r' },
61    { "dump-nir", no_argument, NULL, 'n' },
62 
63    { NULL, 0, NULL, 0 },
64 };
65 
66 struct compiler_opts {
67    gl_shader_stage stage;
68    char *file;
69    char *entry;
70    char *out_file;
71    bool dump_c_array;
72    bool dump_rogue;
73    bool dump_nir;
74 };
75 
usage(const char *argv0)76 static void usage(const char *argv0)
77 {
78    /* clang-format off */
79    printf("Rogue offline compiler.\n");
80    printf("Usage: %s -s <stage> -f <file> [-e <entry>] [-o <file>] [-c] [-r] [-n] [-h]\n", argv0);
81    printf("\n");
82 
83    printf("Required arguments:\n");
84    printf("\t-s, --stage <stage> Shader stage (supported options: frag, vert).\n");
85    printf("\t-f, --file <file>   Shader SPIR-V filename.\n");
86    printf("\n");
87 
88    printf("Options:\n");
89    printf("\t-h, --help          Prints this help message.\n");
90    printf("\t-e, --entry <entry> Overrides the shader entry-point name (default: 'main').\n");
91    printf("\t-o, --out <file>    Overrides the output filename (default: 'out.bin').\n");
92    printf("\n");
93 
94    printf("\t-c, --dump-c-array  Print the shader binary as a C byte array.\n");
95    printf("\t-r, --dump-rogue    Prints the shader Rogue assembly.\n");
96    printf("\t-n, --dump-nir      Prints the shader NIR.\n");
97    printf("\n");
98    /* clang-format on */
99 }
100 
parse_cmdline(int argc, char *argv[], struct compiler_opts *opts)101 static bool parse_cmdline(int argc, char *argv[], struct compiler_opts *opts)
102 {
103    int opt;
104    int longindex;
105 
106    while (
107       (opt =
108           getopt_long(argc, argv, "crnhs:f:e:o:", cmdline_opts, &longindex)) !=
109       -1) {
110       switch (opt) {
111       case 'c':
112          opts->dump_c_array = true;
113          break;
114 
115       case 'e':
116          if (opts->entry)
117             continue;
118 
119          opts->entry = optarg;
120          break;
121 
122       case 'f':
123          if (opts->file)
124             continue;
125 
126          opts->file = optarg;
127          break;
128 
129       case 'n':
130          opts->dump_nir = true;
131          break;
132 
133       case 'o':
134          if (opts->out_file)
135             continue;
136 
137          opts->out_file = optarg;
138          break;
139 
140       case 'r':
141          opts->dump_rogue = true;
142          break;
143 
144       case 's':
145          if (opts->stage != MESA_SHADER_NONE)
146             continue;
147 
148          if (!strcmp(optarg, "frag"))
149             opts->stage = MESA_SHADER_FRAGMENT;
150          else if (!strcmp(optarg, "vert"))
151             opts->stage = MESA_SHADER_VERTEX;
152          else {
153             fprintf(stderr, "Invalid stage \"%s\".\n", optarg);
154             usage(argv[0]);
155             return false;
156          }
157 
158          break;
159 
160       case 'h':
161       default:
162          usage(argv[0]);
163          return false;
164       }
165    }
166 
167    if (opts->stage == MESA_SHADER_NONE || !opts->file) {
168       fprintf(stderr,
169               "%s: --stage and --file are required arguments.\n",
170               argv[0]);
171       usage(argv[0]);
172       return false;
173    }
174 
175    if (!opts->out_file)
176       opts->out_file = "out.bin";
177 
178    if (!opts->entry)
179       opts->entry = "main";
180 
181    return true;
182 }
183 
main(int argc, char *argv[])184 int main(int argc, char *argv[])
185 {
186    /* Command-line options. */
187    /* N.B. MESA_SHADER_NONE != 0 */
188    struct compiler_opts opts = { .stage = MESA_SHADER_NONE, 0 };
189 
190    /* Input file data. */
191    char *input_data;
192    size_t input_size;
193 
194    /* Compiler context. */
195    struct rogue_compiler *compiler;
196 
197    /* Multi-stage build context. */
198    struct rogue_build_ctx *ctx;
199 
200    /* Output file. */
201    FILE *fp;
202    size_t bytes_written;
203 
204    /* Parse command-line options. */
205    if (!parse_cmdline(argc, argv, &opts))
206       return 1;
207 
208    /* Load SPIR-V input file. */
209    input_data = os_read_file(opts.file, &input_size);
210    if (!input_data) {
211       fprintf(stderr, "Failed to read file \"%s\".\n", opts.file);
212       return 1;
213    }
214 
215    /* Create compiler context. */
216    compiler = rogue_compiler_create(NULL);
217    if (!compiler) {
218       fprintf(stderr, "Failed to set up compiler context.\n");
219       goto err_free_input;
220    }
221 
222    ctx = rogue_create_build_context(compiler);
223    if (!ctx) {
224       fprintf(stderr, "Failed to set up build context.\n");
225       goto err_destroy_compiler;
226    }
227 
228    /* SPIR-V -> NIR. */
229    ctx->nir[opts.stage] = rogue_spirv_to_nir(ctx,
230                                              opts.stage,
231                                              opts.entry,
232                                              input_size / sizeof(uint32_t),
233                                              (uint32_t *)input_data,
234                                              0,
235                                              NULL);
236    if (!ctx->nir[opts.stage]) {
237       fprintf(stderr, "Failed to translate SPIR-V input to NIR.\n");
238       goto err_free_build_context;
239    }
240 
241    /* Dump NIR shader. */
242    if (opts.dump_nir)
243       nir_print_shader(ctx->nir[opts.stage], stdout);
244 
245    /* NIR -> Rogue. */
246    ctx->rogue[opts.stage] = rogue_nir_to_rogue(ctx, ctx->nir[opts.stage]);
247    if (!ctx->rogue[opts.stage]) {
248       fprintf(stderr, "Failed to translate NIR input to Rogue.\n");
249       goto err_free_build_context;
250    }
251 
252    /* Dump Rogue shader. */
253    if (opts.dump_rogue)
254       rogue_dump_shader(ctx->rogue[opts.stage], stdout);
255 
256    /* Rogue -> Binary. */
257    ctx->binary[opts.stage] = rogue_to_binary(ctx, ctx->rogue[opts.stage]);
258    if (!ctx->binary[opts.stage]) {
259       fprintf(stderr, "Failed to translate Rogue to binary.\n");
260       goto err_free_build_context;
261    }
262 
263    /* Dump binary as a C array. */
264    if (opts.dump_c_array) {
265       printf("uint8_t shader_bytes[%zu] = {", ctx->binary[opts.stage]->size);
266       for (size_t u = 0U; u < ctx->binary[opts.stage]->size; ++u) {
267          if (!(u % ARRAY_DUMP_COLS))
268             printf("\n\t");
269 
270          printf("0x%02x, ", ctx->binary[opts.stage]->data[u]);
271       }
272       printf("\n};\n");
273    }
274 
275    /* Write shader binary to disk. */
276    fp = fopen(opts.out_file, "wb");
277    if (!fp) {
278       fprintf(stderr, "Failed to open output file \"%s\".\n", opts.out_file);
279       goto err_free_build_context;
280    }
281 
282    bytes_written = fwrite(ctx->binary[opts.stage]->data,
283                           1,
284                           ctx->binary[opts.stage]->size,
285                           fp);
286    if (bytes_written != ctx->binary[opts.stage]->size) {
287       fprintf(
288          stderr,
289          "Failed to write to output file \"%s\" (%zu bytes of %zu written).\n",
290          opts.out_file,
291          bytes_written,
292          ctx->binary[opts.stage]->size);
293       goto err_close_outfile;
294    }
295 
296    /* Clean up. */
297    fclose(fp);
298    ralloc_free(ctx);
299    rogue_compiler_destroy(compiler);
300    free(input_data);
301 
302    return 0;
303 
304 err_close_outfile:
305    fclose(fp);
306 err_free_build_context:
307    ralloc_free(ctx);
308 err_destroy_compiler:
309    rogue_compiler_destroy(compiler);
310 err_free_input:
311    free(input_data);
312 
313    return 1;
314 }
315