diff --git a/meson_options.txt b/meson_options.txt index bf031eeb11fe3..5bbf28f98dce2 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -156,6 +156,13 @@ option( value : false, description : 'build gallium "clover" OpenCL frontend with SPIR-V binary support.', ) +option( + 'static-libclc', + type : 'array', + value : [], + choices : ['spirv', 'spirv64', 'all'], + description : 'Link libclc SPIR-V statically.', +) option( 'd3d-drivers-path', type : 'string', diff --git a/src/compiler/Makefile.sources b/src/compiler/Makefile.sources index 0c69218ed6ff6..f2051bf0f1214 100644 --- a/src/compiler/Makefile.sources +++ b/src/compiler/Makefile.sources @@ -376,6 +376,7 @@ SPIRV_FILES = \ spirv/GLSL.ext.AMD.h \ spirv/GLSL.std.450.h \ spirv/gl_spirv.c \ + spirv/nir_load_libclc.c \ spirv/nir_spirv.h \ spirv/OpenCL.std.h \ spirv/spirv.h \ diff --git a/src/compiler/nir/meson.build b/src/compiler/nir/meson.build index 992047ff5dcd3..828b23527ae74 100644 --- a/src/compiler/nir/meson.build +++ b/src/compiler/nir/meson.build @@ -251,6 +251,7 @@ files_libnir = files( '../spirv/GLSL.ext.AMD.h', '../spirv/GLSL.std.450.h', '../spirv/gl_spirv.c', + '../spirv/nir_load_libclc.c', '../spirv/nir_spirv.h', '../spirv/OpenCL.std.h', '../spirv/spirv.h', @@ -266,13 +267,62 @@ files_libnir = files( '../spirv/vtn_variables.c', ) +_libnir_args = [] +if dep_clc.found() + _basedir = dep_clc.get_variable(pkgconfig : 'libexecdir') + + _static_libclc = get_option('static-libclc') + if _static_libclc.length() > 0 + if _static_libclc.contains('all') + _static_libclc = ['spirv', 'spirv64'] + endif + + prog_zstd = find_program('zstd', required : false) + _zstd_static_libclc = dep_zstd.found() and prog_zstd.found() + if _zstd_static_libclc + _libnir_args += '-DHAVE_STATIC_LIBCLC_ZSTD' + endif + + foreach s : _static_libclc + _libnir_args += '-DHAVE_STATIC_LIBCLC_@0@'.format(s.to_upper()) + f = '@0@-mesa3d-.spv'.format(s) + _libclc_file = _basedir / f + + if _zstd_static_libclc + _libclc_file = custom_target( + '@0@.zstd'.format(f), + command : [prog_zstd, '-f', '@INPUT@', '-o', '@OUTPUT@'], + input : [_libclc_file], + output : '@0@.zstd'.format(f), + ) + endif + + files_libnir += custom_target( + '@0@.h'.format(f), + command : [ + prog_python, files_xxd, '-b', '@INPUT@', '@OUTPUT@', + '-n', 'libclc_@0@_mesa3d_spv'.format(s), + ], + input : [_libclc_file], + output : '@0@.h'.format(f), + depend_files : files_xxd, + ) + endforeach + else + _libnir_args += ['-DDYNAMIC_LIBCLC_PATH="@0@/"'.format(_basedir)] + if not cc.has_function('mmap') + error('mmap required for dynamic libCLC loading') + endif + endif +endif + _libnir = static_library( 'nir', [files_libnir, spirv_info_c, nir_opt_algebraic_c, nir_opcodes_c, nir_opcodes_h, nir_constant_expressions_c, nir_builder_opcodes_h, vtn_gather_types_c, nir_intrinsics_c, nir_intrinsics_h], include_directories : [inc_include, inc_src, inc_mapi, inc_mesa, inc_gallium, inc_gallium_aux, inc_compiler, include_directories('../spirv')], - c_args : [c_msvc_compat_args, no_override_init_args], + c_args : [c_msvc_compat_args, no_override_init_args, _libnir_args], gnu_symbol_visibility : 'hidden', link_with : libcompiler, build_by_default : false, diff --git a/src/compiler/spirv/nir_load_libclc.c b/src/compiler/spirv/nir_load_libclc.c new file mode 100644 index 0000000000000..18da4b92586e4 --- /dev/null +++ b/src/compiler/spirv/nir_load_libclc.c @@ -0,0 +1,310 @@ +/* + * Copyright © 2020 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. + */ + +#include "nir.h" +#include "nir_serialize.h" +#include "nir_spirv.h" +#include "util/mesa-sha1.h" + +#ifdef DYNAMIC_LIBCLC_PATH +#include +#include +#include +#include +#include +#endif + +#ifdef HAVE_STATIC_LIBCLC_ZSTD +#include +#endif + +#ifdef HAVE_STATIC_LIBCLC_SPIRV +#include "spirv-mesa3d-.spv.h" +#endif + +#ifdef HAVE_STATIC_LIBCLC_SPIRV64 +#include "spirv64-mesa3d-.spv.h" +#endif + +struct clc_file { + unsigned bit_size; + const char *static_data; + size_t static_data_size; + const char *sys_path; +}; + +static const struct clc_file libclc_files[] = { + { + .bit_size = 32, +#ifdef HAVE_STATIC_LIBCLC_SPIRV + .static_data = libclc_spirv_mesa3d_spv, + .static_data_size = sizeof(libclc_spirv_mesa3d_spv), +#endif +#ifdef DYNAMIC_LIBCLC_PATH + .sys_path = DYNAMIC_LIBCLC_PATH "spirv-mesa3d-.spv", +#endif + }, + { + .bit_size = 64, +#ifdef HAVE_STATIC_LIBCLC_SPIRV64 + .static_data = libclc_spirv64_mesa3d_spv, + .static_data_size = sizeof(libclc_spirv64_mesa3d_spv), +#endif +#ifdef DYNAMIC_LIBCLC_PATH + .sys_path = DYNAMIC_LIBCLC_PATH "spirv64-mesa3d-.spv", +#endif + }, +}; + +static const struct clc_file * +get_libclc_file(unsigned ptr_bit_size) +{ + assert(ptr_bit_size == 32 || ptr_bit_size == 64); + return &libclc_files[ptr_bit_size / 64]; +} + +struct clc_data { + const struct clc_file *file; + + unsigned char cache_key[20]; + + int fd; + const void *data; + size_t size; +}; + +static bool +open_clc_data(struct clc_data *clc, unsigned ptr_bit_size) +{ + memset(clc, 0, sizeof(*clc)); + clc->file = get_libclc_file(ptr_bit_size); + clc->fd = -1; + + if (clc->file->static_data) { + snprintf((char *)clc->cache_key, sizeof(clc->cache_key), + "libclc-spirv%d", ptr_bit_size); + return true; + } + +#ifdef DYNAMIC_LIBCLC_PATH + if (clc->file->sys_path != NULL) { + int fd = open(clc->file->sys_path, O_RDONLY); + if (fd < 0) + return false; + + struct stat stat; + int ret = fstat(fd, &stat); + if (ret < 0) { + fprintf(stderr, "fstat failed on %s: %m\n", clc->file->sys_path); + close(fd); + return false; + } + + struct mesa_sha1 ctx; + _mesa_sha1_init(&ctx); + _mesa_sha1_update(&ctx, clc->file->sys_path, strlen(clc->file->sys_path)); + _mesa_sha1_update(&ctx, &stat.st_mtim, sizeof(stat.st_mtim)); + _mesa_sha1_final(&ctx, clc->cache_key); + + clc->fd = fd; + + return true; + } +#endif + + return false; +} + +#define SPIRV_WORD_SIZE 4 + +static bool +map_clc_data(struct clc_data *clc) +{ + if (clc->file->static_data) { +#ifdef HAVE_STATIC_LIBCLC_ZSTD + unsigned long long cmp_size = + ZSTD_getFrameContentSize(clc->file->static_data, + clc->file->static_data_size); + if (cmp_size == ZSTD_CONTENTSIZE_UNKNOWN || + cmp_size == ZSTD_CONTENTSIZE_ERROR) { + fprintf(stderr, "Could not determine the decompressed size of the " + "libclc SPIR-V\n"); + return false; + } + + size_t frame_size = + ZSTD_findFrameCompressedSize(clc->file->static_data, + clc->file->static_data_size); + if (ZSTD_isError(frame_size)) { + fprintf(stderr, "Could not determine the size of the first ZSTD frame " + "when decompressing libclc SPIR-V: %s\n", + ZSTD_getErrorName(frame_size)); + return false; + } + + void *dest = malloc(cmp_size + 1); + size_t size = ZSTD_decompress(dest, cmp_size, clc->file->static_data, + frame_size); + if (ZSTD_isError(size)) { + free(dest); + fprintf(stderr, "Error decompressing libclc SPIR-V: %s\n", + ZSTD_getErrorName(size)); + return false; + } + + clc->data = dest; + clc->size = size; +#else + clc->data = clc->file->static_data; + clc->size = clc->file->static_data_size; +#endif + return true; + } + +#ifdef DYNAMIC_LIBCLC_PATH + if (clc->file->sys_path != NULL) { + off_t len = lseek(clc->fd, 0, SEEK_END); + if (len % SPIRV_WORD_SIZE != 0) { + fprintf(stderr, "File length isn't a multiple of the word size\n"); + return false; + } + clc->size = len; + + clc->data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, clc->fd, 0); + if (clc->data == MAP_FAILED) { + fprintf(stderr, "Failed to mmap libclc SPIR-V: %m\n"); + return false; + } + + return true; + } +#endif + + return true; +} + +static void +close_clc_data(struct clc_data *clc) +{ + if (clc->file->static_data) { +#ifdef HAVE_STATIC_LIBCLC_ZSTD + free((void *)clc->data); +#endif + return; + } + +#ifdef DYNAMIC_LIBCLC_PATH + if (clc->file->sys_path != NULL) { + if (clc->data) + munmap((void *)clc->data, clc->size); + close(clc->fd); + } +#endif +} + +/** Returns true if libclc is found + * + * If libclc is compiled in statically, this always returns true. If we + * depend on a dynamic libclc, this opens and tries to stat the file. + */ +bool +nir_can_find_libclc(unsigned ptr_bit_size) +{ + struct clc_data clc; + if (open_clc_data(&clc, ptr_bit_size)) { + close_clc_data(&clc); + return true; + } else { + return false; + } +} + +nir_shader * +nir_load_libclc_shader(unsigned ptr_bit_size, + struct disk_cache *disk_cache, + const struct spirv_to_nir_options *spirv_options, + const nir_shader_compiler_options *nir_options) +{ + assert(ptr_bit_size == + nir_address_format_bit_size(spirv_options->global_addr_format)); + + struct clc_data clc; + if (!open_clc_data(&clc, ptr_bit_size)) + return NULL; + +#ifdef ENABLE_SHADER_CACHE + cache_key cache_key; + if (disk_cache) { + disk_cache_compute_key(disk_cache, clc.cache_key, + sizeof(clc.cache_key), cache_key); + + size_t buffer_size; + uint8_t *buffer = disk_cache_get(disk_cache, cache_key, &buffer_size); + if (buffer) { + struct blob_reader blob; + blob_reader_init(&blob, buffer, buffer_size); + nir_shader *nir = nir_deserialize(NULL, nir_options, &blob); + free(buffer); + close_clc_data(&clc); + return nir; + } + } +#endif + + if (!map_clc_data(&clc)) { + close_clc_data(&clc); + return NULL; + } + + struct spirv_to_nir_options spirv_lib_options = *spirv_options; + spirv_lib_options.create_library = true; + + assert(clc.size % SPIRV_WORD_SIZE == 0); + nir_shader *nir = spirv_to_nir(clc.data, clc.size / SPIRV_WORD_SIZE, + NULL, 0, MESA_SHADER_KERNEL, NULL, + &spirv_lib_options, nir_options); + nir_validate_shader(nir, "after nir_load_clc_shader"); + + /* nir_inline_libclc will assume that the functions in this shader are + * already ready to lower. This means we need to inline any function_temp + * initializers and lower any early returns. + */ + NIR_PASS_V(nir, nir_lower_variable_initializers, nir_var_function_temp); + NIR_PASS_V(nir, nir_lower_returns); + + /* TODO: One day, we may want to run some optimizations on the libclc + * shader once and cache them to save time in each shader call. + */ + +#ifdef ENABLE_SHADER_CACHE + if (disk_cache) { + struct blob blob; + blob_init(&blob); + nir_serialize(&blob, nir, false); + disk_cache_put(disk_cache, cache_key, blob.data, blob.size, NULL); + } +#endif + + close_clc_data(&clc); + return nir; +} diff --git a/src/compiler/spirv/nir_spirv.h b/src/compiler/spirv/nir_spirv.h index 576e4a7d35eb4..49921fa7e40de 100644 --- a/src/compiler/spirv/nir_spirv.h +++ b/src/compiler/spirv/nir_spirv.h @@ -28,6 +28,7 @@ #ifndef _NIR_SPIRV_H_ #define _NIR_SPIRV_H_ +#include "util/disk_cache.h" #include "compiler/nir/nir.h" #include "compiler/shader_info.h" @@ -102,6 +103,14 @@ nir_shader *spirv_to_nir(const uint32_t *words, size_t word_count, const struct spirv_to_nir_options *options, const nir_shader_compiler_options *nir_options); +bool nir_can_find_libclc(unsigned ptr_bit_size); + +nir_shader * +nir_load_libclc_shader(unsigned ptr_bit_size, + struct disk_cache *disk_cache, + const struct spirv_to_nir_options *spirv_options, + const nir_shader_compiler_options *nir_options); + #ifdef __cplusplus } #endif