1/*
2 * Copyright © 2020 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#include "nir.h"
25#include "nir_serialize.h"
26#include "nir_spirv.h"
27#include "util/mesa-sha1.h"
28
29#ifdef DYNAMIC_LIBCLC_PATH
30#include <fcntl.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <sys/mman.h>
34#include <unistd.h>
35#endif
36
37#ifdef HAVE_STATIC_LIBCLC_ZSTD
38#include <zstd.h>
39#endif
40
41#ifdef HAVE_STATIC_LIBCLC_SPIRV
42#include "spirv-mesa3d-.spv.h"
43#endif
44
45#ifdef HAVE_STATIC_LIBCLC_SPIRV64
46#include "spirv64-mesa3d-.spv.h"
47#endif
48
49struct clc_file {
50   unsigned bit_size;
51   const char *static_data;
52   size_t static_data_size;
53   const char *sys_path;
54};
55
56static const struct clc_file libclc_files[] = {
57   {
58      .bit_size = 32,
59#ifdef HAVE_STATIC_LIBCLC_SPIRV
60      .static_data = libclc_spirv_mesa3d_spv,
61      .static_data_size = sizeof(libclc_spirv_mesa3d_spv),
62#endif
63#ifdef DYNAMIC_LIBCLC_PATH
64      .sys_path = DYNAMIC_LIBCLC_PATH "spirv-mesa3d-.spv",
65#endif
66   },
67   {
68      .bit_size = 64,
69#ifdef HAVE_STATIC_LIBCLC_SPIRV64
70      .static_data = libclc_spirv64_mesa3d_spv,
71      .static_data_size = sizeof(libclc_spirv64_mesa3d_spv),
72#endif
73#ifdef DYNAMIC_LIBCLC_PATH
74      .sys_path = DYNAMIC_LIBCLC_PATH "spirv64-mesa3d-.spv",
75#endif
76   },
77};
78
79static const struct clc_file *
80get_libclc_file(unsigned ptr_bit_size)
81{
82   assert(ptr_bit_size == 32 || ptr_bit_size == 64);
83   return &libclc_files[ptr_bit_size / 64];
84}
85
86struct clc_data {
87   const struct clc_file *file;
88
89   unsigned char cache_key[20];
90
91   int fd;
92   const void *data;
93   size_t size;
94};
95
96static bool
97open_clc_data(struct clc_data *clc, unsigned ptr_bit_size)
98{
99   memset(clc, 0, sizeof(*clc));
100   clc->file = get_libclc_file(ptr_bit_size);
101   clc->fd = -1;
102
103   if (clc->file->static_data) {
104      snprintf((char *)clc->cache_key, sizeof(clc->cache_key),
105               "libclc-spirv%d", ptr_bit_size);
106      return true;
107   }
108
109#ifdef DYNAMIC_LIBCLC_PATH
110   if (clc->file->sys_path != NULL) {
111      int fd = open(clc->file->sys_path, O_RDONLY);
112      if (fd < 0)
113         return false;
114
115      struct stat stat;
116      int ret = fstat(fd, &stat);
117      if (ret < 0) {
118         fprintf(stderr, "fstat failed on %s: %m\n", clc->file->sys_path);
119         close(fd);
120         return false;
121      }
122
123      struct mesa_sha1 ctx;
124      _mesa_sha1_init(&ctx);
125      _mesa_sha1_update(&ctx, clc->file->sys_path, strlen(clc->file->sys_path));
126      _mesa_sha1_update(&ctx, &stat.st_mtim, sizeof(stat.st_mtim));
127      _mesa_sha1_final(&ctx, clc->cache_key);
128
129      clc->fd = fd;
130
131      return true;
132   }
133#endif
134
135   return false;
136}
137
138#define SPIRV_WORD_SIZE 4
139
140static bool
141map_clc_data(struct clc_data *clc)
142{
143   if (clc->file->static_data) {
144#ifdef HAVE_STATIC_LIBCLC_ZSTD
145      unsigned long long cmp_size =
146         ZSTD_getFrameContentSize(clc->file->static_data,
147                                  clc->file->static_data_size);
148      if (cmp_size == ZSTD_CONTENTSIZE_UNKNOWN ||
149          cmp_size == ZSTD_CONTENTSIZE_ERROR) {
150         fprintf(stderr, "Could not determine the decompressed size of the "
151                         "libclc SPIR-V\n");
152         return false;
153      }
154
155      size_t frame_size =
156         ZSTD_findFrameCompressedSize(clc->file->static_data,
157                                      clc->file->static_data_size);
158      if (ZSTD_isError(frame_size)) {
159         fprintf(stderr, "Could not determine the size of the first ZSTD frame "
160                         "when decompressing libclc SPIR-V: %s\n",
161                 ZSTD_getErrorName(frame_size));
162         return false;
163      }
164
165      void *dest = malloc(cmp_size + 1);
166      size_t size = ZSTD_decompress(dest, cmp_size, clc->file->static_data,
167                                    frame_size);
168      if (ZSTD_isError(size)) {
169         free(dest);
170         fprintf(stderr, "Error decompressing libclc SPIR-V: %s\n",
171                 ZSTD_getErrorName(size));
172         return false;
173      }
174
175      clc->data = dest;
176      clc->size = size;
177#else
178      clc->data = clc->file->static_data;
179      clc->size = clc->file->static_data_size;
180#endif
181      return true;
182   }
183
184#ifdef DYNAMIC_LIBCLC_PATH
185   if (clc->file->sys_path != NULL) {
186      off_t len = lseek(clc->fd, 0, SEEK_END);
187      if (len % SPIRV_WORD_SIZE != 0) {
188         fprintf(stderr, "File length isn't a multiple of the word size\n");
189         return false;
190      }
191      clc->size = len;
192
193      clc->data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, clc->fd, 0);
194      if (clc->data == MAP_FAILED) {
195         fprintf(stderr, "Failed to mmap libclc SPIR-V: %m\n");
196         return false;
197      }
198
199      return true;
200   }
201#endif
202
203   return true;
204}
205
206static void
207close_clc_data(struct clc_data *clc)
208{
209   if (clc->file->static_data) {
210#ifdef HAVE_STATIC_LIBCLC_ZSTD
211      free((void *)clc->data);
212#endif
213      return;
214   }
215
216#ifdef DYNAMIC_LIBCLC_PATH
217   if (clc->file->sys_path != NULL) {
218      if (clc->data)
219         munmap((void *)clc->data, clc->size);
220      close(clc->fd);
221   }
222#endif
223}
224
225/** Returns true if libclc is found
226 *
227 * If libclc is compiled in statically, this always returns true.  If we
228 * depend on a dynamic libclc, this opens and tries to stat the file.
229 */
230bool
231nir_can_find_libclc(unsigned ptr_bit_size)
232{
233   struct clc_data clc;
234   if (open_clc_data(&clc, ptr_bit_size)) {
235      close_clc_data(&clc);
236      return true;
237   } else {
238      return false;
239   }
240}
241
242/** Adds generic pointer variants of libclc functions
243 *
244 * Libclc currently doesn't contain generic variants for a bunch of functions
245 * like `frexp` but the OpenCL spec with generic pointers requires them.  We
246 * really should fix libclc but, in the mean time, we can easily duplicate
247 * every function that works on global memory and make it also work on generic
248 * memory.
249 */
250static void
251libclc_add_generic_variants(nir_shader *shader)
252{
253   nir_foreach_function(func, shader) {
254      /* These don't need generic variants */
255      if (strstr(func->name, "async_work_group_strided_copy"))
256         continue;
257
258      char *U3AS1 = strstr(func->name, "U3AS1");
259      if (U3AS1 == NULL)
260         continue;
261
262      ptrdiff_t offset_1 = U3AS1 - func->name + 4;
263      assert(offset_1 < strlen(func->name) && func->name[offset_1] == '1');
264
265      char *generic_name = ralloc_strdup(shader, func->name);
266      assert(generic_name[offset_1] == '1');
267      generic_name[offset_1] = '4';
268
269      nir_function *gfunc = nir_function_create(shader, generic_name);
270      gfunc->num_params = func->num_params;
271      gfunc->params = ralloc_array(shader, nir_parameter, gfunc->num_params);
272      for (unsigned i = 0; i < gfunc->num_params; i++)
273         gfunc->params[i] = func->params[i];
274
275      gfunc->impl = nir_function_impl_clone(shader, func->impl);
276      gfunc->impl->function = gfunc;
277
278      /* Rewrite any global pointers to generic */
279      nir_foreach_block(block, gfunc->impl) {
280         nir_foreach_instr(instr, block) {
281            if (instr->type != nir_instr_type_deref)
282               continue;
283
284            nir_deref_instr *deref = nir_instr_as_deref(instr);
285            if (!nir_deref_mode_may_be(deref, nir_var_mem_global))
286               continue;
287
288            assert(deref->type != nir_deref_type_var);
289            assert(nir_deref_mode_is(deref, nir_var_mem_global));
290
291            deref->modes = nir_var_mem_generic;
292         }
293      }
294
295      nir_metadata_preserve(gfunc->impl, nir_metadata_none);
296   }
297}
298
299nir_shader *
300nir_load_libclc_shader(unsigned ptr_bit_size,
301                       struct disk_cache *disk_cache,
302                       const struct spirv_to_nir_options *spirv_options,
303                       const nir_shader_compiler_options *nir_options)
304{
305   assert(ptr_bit_size ==
306          nir_address_format_bit_size(spirv_options->global_addr_format));
307
308   struct clc_data clc;
309   if (!open_clc_data(&clc, ptr_bit_size))
310      return NULL;
311
312#ifdef ENABLE_SHADER_CACHE
313   cache_key cache_key;
314   if (disk_cache) {
315      disk_cache_compute_key(disk_cache, clc.cache_key,
316                             sizeof(clc.cache_key), cache_key);
317
318      size_t buffer_size;
319      uint8_t *buffer = disk_cache_get(disk_cache, cache_key, &buffer_size);
320      if (buffer) {
321         struct blob_reader blob;
322         blob_reader_init(&blob, buffer, buffer_size);
323         nir_shader *nir = nir_deserialize(NULL, nir_options, &blob);
324         free(buffer);
325         close_clc_data(&clc);
326         return nir;
327      }
328   }
329#endif
330
331   if (!map_clc_data(&clc)) {
332      close_clc_data(&clc);
333      return NULL;
334   }
335
336   struct spirv_to_nir_options spirv_lib_options = *spirv_options;
337   spirv_lib_options.create_library = true;
338
339   assert(clc.size % SPIRV_WORD_SIZE == 0);
340   nir_shader *nir = spirv_to_nir(clc.data, clc.size / SPIRV_WORD_SIZE,
341                                  NULL, 0, MESA_SHADER_KERNEL, NULL,
342                                  &spirv_lib_options, nir_options);
343   nir_validate_shader(nir, "after nir_load_clc_shader");
344
345   /* nir_inline_libclc will assume that the functions in this shader are
346    * already ready to lower.  This means we need to inline any function_temp
347    * initializers and lower any early returns.
348    */
349   nir->info.internal = true;
350   NIR_PASS_V(nir, nir_lower_variable_initializers, nir_var_function_temp);
351   NIR_PASS_V(nir, nir_lower_returns);
352
353   NIR_PASS_V(nir, libclc_add_generic_variants);
354
355   /* TODO: One day, we may want to run some optimizations on the libclc
356    * shader once and cache them to save time in each shader call.
357    */
358
359#ifdef ENABLE_SHADER_CACHE
360   if (disk_cache) {
361      struct blob blob;
362      blob_init(&blob);
363      nir_serialize(&blob, nir, false);
364      disk_cache_put(disk_cache, cache_key, blob.data, blob.size, NULL);
365   }
366#endif
367
368   close_clc_data(&clc);
369   return nir;
370}
371