nir/spirv: add gl_spirv_validation method
authorAlejandro Piñeiro <apinheiro@igalia.com>
Thu, 18 Jan 2018 11:31:52 +0000 (12:31 +0100)
committerAlejandro Piñeiro <apinheiro@igalia.com>
Fri, 30 Mar 2018 07:14:56 +0000 (09:14 +0200)
ARB_gl_spirv adds the ability to use SPIR-V binaries, and a new
method, glSpecializeShader. Here we add a new function to do the
validation for this function:

From OpenGL 4.6 spec, section 7.2.1"

   "Shader Specialization", error table:

    INVALID_VALUE is generated if <pEntryPoint> does not name a valid
    entry point for <shader>.

    INVALID_VALUE is generated if any element of <pConstantIndex>
    refers to a specialization constant that does not exist in the
    shader module contained in <shader>.""

v2: rebase update (spirv_to_nir options added, changes on the warning
    logging, and others)

v3: include passing options on common initialization, doesn't call
    setjmp on common_initialization

v4: (after Jason comments):
  * Rename common_initialization to vtn_builder_create
  * Move validation method and their helpers to own source file.
  * Create own handle_constant_decoration_cb instead of reuse existing one

v5: put vtn_build_create refactoring to their own patch (Jason)

v6: update after vtn_builder_create method renamed, add explanatory
    comment, tweak existing comment and commit message (Timothy)

src/compiler/Makefile.sources
src/compiler/nir/meson.build
src/compiler/spirv/gl_spirv.c [new file with mode: 0644]
src/compiler/spirv/nir_spirv.h
src/compiler/spirv/spirv_to_nir.c
src/compiler/spirv/vtn_private.h

index 273ce4cde4937c35a3192f5ebcf5e07c3b5c98b2..aca9dab476ea54f99841db290ca1e638c7d09a2e 100644 (file)
@@ -297,6 +297,7 @@ SPIRV_GENERATED_FILES = \
 SPIRV_FILES = \
        spirv/GLSL.ext.AMD.h \
        spirv/GLSL.std.450.h \
+       spirv/gl_spirv.c \
        spirv/nir_spirv.h \
        spirv/spirv.h \
        spirv/spirv_info.h \
index db89e276139ada947c5bf460f4c8acc9cf6d4e1e..b28a565d0c6c0873a235f7ed9fab64aa0763140c 100644 (file)
@@ -186,6 +186,7 @@ files_libnir = files(
   'nir_worklist.h',
   '../spirv/GLSL.ext.AMD.h',
   '../spirv/GLSL.std.450.h',
+  '../spirv/gl_spirv.c',
   '../spirv/nir_spirv.h',
   '../spirv/spirv.h',
   '../spirv/spirv_info.h',
diff --git a/src/compiler/spirv/gl_spirv.c b/src/compiler/spirv/gl_spirv.c
new file mode 100644 (file)
index 0000000..edb635a
--- /dev/null
@@ -0,0 +1,271 @@
+/*
+ * Copyright © 2017 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_spirv.h"
+
+#include "vtn_private.h"
+#include "spirv_info.h"
+
+static bool
+vtn_validate_preamble_instruction(struct vtn_builder *b, SpvOp opcode,
+                                  const uint32_t *w, unsigned count)
+{
+   switch (opcode) {
+   case SpvOpSource:
+   case SpvOpSourceExtension:
+   case SpvOpSourceContinued:
+   case SpvOpExtension:
+   case SpvOpCapability:
+   case SpvOpExtInstImport:
+   case SpvOpMemoryModel:
+   case SpvOpString:
+   case SpvOpName:
+   case SpvOpMemberName:
+   case SpvOpExecutionMode:
+   case SpvOpDecorationGroup:
+   case SpvOpMemberDecorate:
+   case SpvOpGroupDecorate:
+   case SpvOpGroupMemberDecorate:
+      break;
+
+   case SpvOpEntryPoint:
+      vtn_handle_entry_point(b, w, count);
+      break;
+
+   case SpvOpDecorate:
+      vtn_handle_decoration(b, opcode, w, count);
+      break;
+
+   default:
+      return false; /* End of preamble */
+   }
+
+   return true;
+}
+
+static void
+spec_constant_decoration_cb(struct vtn_builder *b, struct vtn_value *v,
+                            int member, const struct vtn_decoration *dec,
+                            void *data)
+{
+   vtn_assert(member == -1);
+   if (dec->decoration != SpvDecorationSpecId)
+      return;
+
+   for (unsigned i = 0; i < b->num_specializations; i++) {
+      if (b->specializations[i].id == dec->literals[0]) {
+         b->specializations[i].defined_on_module = true;
+         return;
+      }
+   }
+}
+
+static void
+vtn_validate_handle_constant(struct vtn_builder *b, SpvOp opcode,
+                             const uint32_t *w, unsigned count)
+{
+   struct vtn_value *val = vtn_push_value(b, w[2], vtn_value_type_constant);
+
+   switch (opcode) {
+   case SpvOpConstant:
+   case SpvOpConstantNull:
+   case SpvOpSpecConstantComposite:
+   case SpvOpConstantComposite:
+      /* Nothing to do here for gl_spirv needs */
+      break;
+
+   case SpvOpConstantTrue:
+   case SpvOpConstantFalse:
+   case SpvOpSpecConstantTrue:
+   case SpvOpSpecConstantFalse:
+   case SpvOpSpecConstant:
+   case SpvOpSpecConstantOp:
+      vtn_foreach_decoration(b, val, spec_constant_decoration_cb, NULL);
+      break;
+
+   case SpvOpConstantSampler:
+      vtn_fail("OpConstantSampler requires Kernel Capability");
+      break;
+
+   default:
+      vtn_fail("Unhandled opcode");
+   }
+}
+
+static bool
+vtn_validate_handle_constant_instruction(struct vtn_builder *b, SpvOp opcode,
+                                         const uint32_t *w, unsigned count)
+{
+   switch (opcode) {
+   case SpvOpSource:
+   case SpvOpSourceContinued:
+   case SpvOpSourceExtension:
+   case SpvOpExtension:
+   case SpvOpCapability:
+   case SpvOpExtInstImport:
+   case SpvOpMemoryModel:
+   case SpvOpEntryPoint:
+   case SpvOpExecutionMode:
+   case SpvOpString:
+   case SpvOpName:
+   case SpvOpMemberName:
+   case SpvOpDecorationGroup:
+   case SpvOpDecorate:
+   case SpvOpMemberDecorate:
+   case SpvOpGroupDecorate:
+   case SpvOpGroupMemberDecorate:
+      vtn_fail("Invalid opcode types and variables section");
+      break;
+
+   case SpvOpTypeVoid:
+   case SpvOpTypeBool:
+   case SpvOpTypeInt:
+   case SpvOpTypeFloat:
+   case SpvOpTypeVector:
+   case SpvOpTypeMatrix:
+   case SpvOpTypeImage:
+   case SpvOpTypeSampler:
+   case SpvOpTypeSampledImage:
+   case SpvOpTypeArray:
+   case SpvOpTypeRuntimeArray:
+   case SpvOpTypeStruct:
+   case SpvOpTypeOpaque:
+   case SpvOpTypePointer:
+   case SpvOpTypeFunction:
+   case SpvOpTypeEvent:
+   case SpvOpTypeDeviceEvent:
+   case SpvOpTypeReserveId:
+   case SpvOpTypeQueue:
+   case SpvOpTypePipe:
+      /* We don't need to handle types */
+      break;
+
+   case SpvOpConstantTrue:
+   case SpvOpConstantFalse:
+   case SpvOpConstant:
+   case SpvOpConstantComposite:
+   case SpvOpConstantSampler:
+   case SpvOpConstantNull:
+   case SpvOpSpecConstantTrue:
+   case SpvOpSpecConstantFalse:
+   case SpvOpSpecConstant:
+   case SpvOpSpecConstantComposite:
+   case SpvOpSpecConstantOp:
+      vtn_validate_handle_constant(b, opcode, w, count);
+      break;
+
+   case SpvOpUndef:
+   case SpvOpVariable:
+      /* We don't need to handle them */
+      break;
+
+   default:
+      return false; /* End of preamble */
+   }
+
+   return true;
+}
+
+/*
+ * Since OpenGL 4.6 you can use SPIR-V modules directly on OpenGL. One of the
+ * new methods, glSpecializeShader include some possible errors when trying to
+ * use it.
+ *
+ * From OpenGL 4.6, Section 7.2.1, "Shader Specialization":
+ *
+ * "void SpecializeShaderARB(uint shader,
+ *                           const char* pEntryPoint,
+ *                           uint numSpecializationConstants,
+ *                           const uint* pConstantIndex,
+ *                           const uint* pConstantValue);
+ * <skip>
+ *
+ * INVALID_VALUE is generated if <pEntryPoint> does not name a valid
+ * entry point for <shader>.
+ *
+ * An INVALID_VALUE error is generated if any element of pConstantIndex refers
+ * to a specialization constant that does not exist in the shader module
+ * contained in shader."
+ *
+ * We could do those checks on spirv_to_nir, but we are only interested on the
+ * full translation later, during linking. This method is a simplified version
+ * of spirv_to_nir, looking for only the checks needed by SpecializeShader.
+ *
+ * This method returns NULL if no entry point was found, and fill the
+ * nir_spirv_specialization field "defined_on_module" accordingly. Caller
+ * would need to trigger the specific errors.
+ *
+ */
+bool
+gl_spirv_validation(const uint32_t *words, size_t word_count,
+                    struct nir_spirv_specialization *spec, unsigned num_spec,
+                    gl_shader_stage stage, const char *entry_point_name)
+{
+   /* vtn_warn/vtn_log uses debug.func. Setting a null to prevent crash. Not
+    * need to print the warnings now, would be done later, on the real
+    * spirv_to_nir
+    */
+   const struct spirv_to_nir_options options = { .debug.func = NULL};
+   const uint32_t *word_end = words + word_count;
+
+   struct vtn_builder *b = vtn_create_builder(words, word_count,
+                                              stage, entry_point_name,
+                                              &options);
+
+   if (b == NULL)
+      return false;
+
+   /* See also _vtn_fail() */
+   if (setjmp(b->fail_jump)) {
+      ralloc_free(b);
+      return false;
+   }
+
+   /* Skip the SPIR-V header, handled at vtn_create_builder */
+   words+= 5;
+
+   /* Search entry point from preamble */
+   words = vtn_foreach_instruction(b, words, word_end,
+                                   vtn_validate_preamble_instruction);
+
+   if (b->entry_point == NULL) {
+      ralloc_free(b);
+      return false;
+   }
+
+   b->specializations = spec;
+   b->num_specializations = num_spec;
+
+   /* Handle constant instructions (we don't need to handle
+    * variables or types for gl_spirv)
+    */
+   words = vtn_foreach_instruction(b, words, word_end,
+                                   vtn_validate_handle_constant_instruction);
+
+   ralloc_free(b);
+
+   return true;
+}
+
index a2c40e57d1890a128fe313629c7871458e7c1d8e..d2766abb7f974cf4075df108f3b3d0e7a62f062c 100644 (file)
@@ -41,6 +41,7 @@ struct nir_spirv_specialization {
       uint32_t data32;
       uint64_t data64;
    };
+   bool defined_on_module;
 };
 
 enum nir_spirv_debug_level {
@@ -69,6 +70,10 @@ struct spirv_to_nir_options {
    } debug;
 };
 
+bool gl_spirv_validation(const uint32_t *words, size_t word_count,
+                         struct nir_spirv_specialization *spec, unsigned num_spec,
+                         gl_shader_stage stage, const char *entry_point_name);
+
 nir_function *spirv_to_nir(const uint32_t *words, size_t word_count,
                            struct nir_spirv_specialization *specializations,
                            unsigned num_specializations,
index d895d82055c330e73c059f644c1d21c39762ddd9..2550ef0a233946684b957397ab3a4d3b734149c5 100644 (file)
@@ -466,7 +466,7 @@ vtn_foreach_execution_mode(struct vtn_builder *b, struct vtn_value *value,
    }
 }
 
-static void
+void
 vtn_handle_decoration(struct vtn_builder *b, SpvOp opcode,
                       const uint32_t *w, unsigned count)
 {
@@ -3191,6 +3191,24 @@ stage_for_execution_model(struct vtn_builder *b, SpvExecutionModel model)
                   spirv_capability_to_string(cap));     \
    } while(0)
 
+
+void
+vtn_handle_entry_point(struct vtn_builder *b, const uint32_t *w,
+                       unsigned count)
+{
+   struct vtn_value *entry_point = &b->values[w[2]];
+   /* Let this be a name label regardless */
+   unsigned name_words;
+   entry_point->name = vtn_string_literal(b, &w[3], count - 3, &name_words);
+
+   if (strcmp(entry_point->name, b->entry_point_name) != 0 ||
+       stage_for_execution_model(b, w[1]) != b->entry_point_stage)
+      return;
+
+   vtn_assert(b->entry_point == NULL);
+   b->entry_point = entry_point;
+}
+
 static bool
 vtn_handle_preamble_instruction(struct vtn_builder *b, SpvOp opcode,
                                 const uint32_t *w, unsigned count)
@@ -3379,20 +3397,9 @@ vtn_handle_preamble_instruction(struct vtn_builder *b, SpvOp opcode,
                  w[2] == SpvMemoryModelGLSL450);
       break;
 
-   case SpvOpEntryPoint: {
-      struct vtn_value *entry_point = &b->values[w[2]];
-      /* Let this be a name label regardless */
-      unsigned name_words;
-      entry_point->name = vtn_string_literal(b, &w[3], count - 3, &name_words);
-
-      if (strcmp(entry_point->name, b->entry_point_name) != 0 ||
-          stage_for_execution_model(b, w[1]) != b->entry_point_stage)
-         break;
-
-      vtn_assert(b->entry_point == NULL);
-      b->entry_point = entry_point;
+   case SpvOpEntryPoint:
+      vtn_handle_entry_point(b, w, count);
       break;
-   }
 
    case SpvOpString:
       vtn_push_value(b, w[1], vtn_value_type_string)->str =
index 47d06f0f919f3b39392df2b51724657364825872..d8a00f99b97e865052505ec92e37b09cec62cc6b 100644 (file)
@@ -723,6 +723,12 @@ struct vtn_builder* vtn_create_builder(const uint32_t *words, size_t word_count,
                                        gl_shader_stage stage, const char *entry_point_name,
                                        const struct spirv_to_nir_options *options);
 
+void vtn_handle_entry_point(struct vtn_builder *b, const uint32_t *w,
+                            unsigned count);
+
+void vtn_handle_decoration(struct vtn_builder *b, SpvOp opcode,
+                           const uint32_t *w, unsigned count);
+
 static inline uint32_t
 vtn_align_u32(uint32_t v, uint32_t a)
 {