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
49static 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
66struct 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
76static 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
101static 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
184int 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
304err_close_outfile:
305   fclose(fp);
306err_free_build_context:
307   ralloc_free(ctx);
308err_destroy_compiler:
309   rogue_compiler_destroy(compiler);
310err_free_input:
311   free(input_data);
312
313   return 1;
314}
315