glsl: Rework interface block linking.
authorPaul Berry <stereotype441@gmail.com>
Tue, 29 Oct 2013 21:41:32 +0000 (14:41 -0700)
committerPaul Berry <stereotype441@gmail.com>
Fri, 15 Nov 2013 16:56:28 +0000 (08:56 -0800)
Previously, when doing intrastage and interstage interface block
linking, we only checked the interface type; this prevented us from
catching some link errors.

We now check the following additional constraints:

- For intrastage linking, the presence/absence of interface names must
  match.

- For shader ins/outs, the interface names themselves must match when
  doing intrastage linking (note: it's not clear from the spec whether
  this is necessary, but Mesa's implementation currently relies on
  it).

- Array vs. nonarray must be consistent, taking into account the
  special rules for vertex-geometry linkage.

- Array sizes must be consistent (exception: during intrastage
  linking, an unsized array matches a sized array).

Note: validate_interstage_interface_blocks currently handles both
uniforms and in/out variables.  As a result, if all three shader types
are present (VS, GS, and FS), and a uniform interface block is
mentioned in the VS and FS but not the GS, it won't be validated.  I
plan to address this in later patches.

Fixes the following piglit tests in spec/glsl-1.50/linker:
- interface-blocks-vs-fs-array-size-mismatch
- interface-vs-array-to-fs-unnamed
- interface-vs-unnamed-to-fs-array
- intrastage-interface-unnamed-array

v2: Simplify logic in intrastage_match() for handling array sizes.
Make extra_array_level const.  Use an unnamed temporary
interface_block_definition in validate_interstage_interface_blocks()'s
first call to definitions->store().

Cc: "10.0" <mesa-stable@lists.freedesktop.org>
Reviewed-by: Ian Romanick <ian.d.romanick@intel.com>
Reviewed-by: Jordan Justen <jordan.l.justen@intel.com>
src/glsl/link_interface_blocks.cpp

index 4f1c9d3962ba5e13da80665941f5a1fdc165b5a6..a7fceb9bad63437d39d0c3b44d8e27ec33d3527c 100644 (file)
 #include "glsl_symbol_table.h"
 #include "linker.h"
 #include "main/macros.h"
+#include "program/hash_table.h"
+
+
+namespace {
+
+/**
+ * Information about a single interface block definition that we need to keep
+ * track of in order to check linkage rules.
+ *
+ * Note: this class is expected to be short lived, so it doesn't make copies
+ * of the strings it references; it simply borrows the pointers from the
+ * ir_variable class.
+ */
+struct interface_block_definition
+{
+   /**
+    * Extract an interface block definition from an ir_variable that
+    * represents either the interface instance (for named interfaces), or a
+    * member of the interface (for unnamed interfaces).
+    */
+   explicit interface_block_definition(const ir_variable *var)
+      : type(var->get_interface_type()),
+        instance_name(NULL),
+        array_size(-1)
+   {
+      if (var->is_interface_instance()) {
+         instance_name = var->name;
+         if (var->type->is_array())
+            array_size = var->type->length;
+      }
+   }
+
+   /**
+    * Interface block type
+    */
+   const glsl_type *type;
+
+   /**
+    * For a named interface block, the instance name.  Otherwise NULL.
+    */
+   const char *instance_name;
+
+   /**
+    * For an interface block array, the array size (or 0 if unsized).
+    * Otherwise -1.
+    */
+   int array_size;
+};
+
+
+/**
+ * Check if two interfaces match, according to intrastage interface matching
+ * rules.  If they do, and the first interface uses an unsized array, it will
+ * be updated to reflect the array size declared in the second interface.
+ */
+bool
+intrastage_match(interface_block_definition *a,
+                 const interface_block_definition *b,
+                 ir_variable_mode mode)
+{
+   /* Types must match. */
+   if (a->type != b->type)
+      return false;
+
+   /* Presence/absence of interface names must match. */
+   if ((a->instance_name == NULL) != (b->instance_name == NULL))
+      return false;
+
+   /* For uniforms, instance names need not match.  For shader ins/outs,
+    * it's not clear from the spec whether they need to match, but
+    * Mesa's implementation relies on them matching.
+    */
+   if (a->instance_name != NULL && mode != ir_var_uniform &&
+       strcmp(a->instance_name, b->instance_name) != 0) {
+      return false;
+   }
+
+   /* Array vs. nonarray must be consistent, and sizes must be
+    * consistent, with the exception that unsized arrays match sized
+    * arrays.
+    */
+   if ((a->array_size == -1) != (b->array_size == -1))
+      return false;
+   if (b->array_size != 0) {
+      if (a->array_size == 0)
+         a->array_size = b->array_size;
+      else if (a->array_size != b->array_size)
+         return false;
+   }
+
+   return true;
+}
+
+
+/**
+ * Check if two interfaces match, according to interstage (in/out) interface
+ * matching rules.
+ *
+ * If \c extra_array_level is true, then vertex-to-geometry shader matching
+ * rules are enforced (i.e. a successful match requires the consumer interface
+ * to be an array and the producer interface to be a non-array).
+ */
+bool
+interstage_match(const interface_block_definition *producer,
+                 const interface_block_definition *consumer,
+                 bool extra_array_level)
+{
+   /* Unsized arrays should not occur during interstage linking.  They
+    * should have all been assigned a size by link_intrastage_shaders.
+    */
+   assert(consumer->array_size != 0);
+   assert(producer->array_size != 0);
+
+   /* Types must match. */
+   if (consumer->type != producer->type)
+      return false;
+   if (extra_array_level) {
+      /* Consumer must be an array, and producer must not. */
+      if (consumer->array_size == -1)
+         return false;
+      if (producer->array_size != -1)
+         return false;
+   } else {
+      /* Array vs. nonarray must be consistent, and sizes must be consistent.
+       * Since unsized arrays have been ruled out, we can check this by just
+       * making sure the sizes are equal.
+       */
+      if (consumer->array_size != producer->array_size)
+         return false;
+   }
+   return true;
+}
+
+
+/**
+ * This class keeps track of a mapping from an interface block name to the
+ * necessary information about that interface block to determine whether to
+ * generate a link error.
+ *
+ * Note: this class is expected to be short lived, so it doesn't make copies
+ * of the strings it references; it simply borrows the pointers from the
+ * ir_variable class.
+ */
+class interface_block_definitions
+{
+public:
+   interface_block_definitions()
+      : mem_ctx(ralloc_context(NULL)),
+        ht(hash_table_ctor(0, hash_table_string_hash,
+                           hash_table_string_compare))
+   {
+   }
+
+   ~interface_block_definitions()
+   {
+      hash_table_dtor(ht);
+      ralloc_free(mem_ctx);
+   }
+
+   /**
+    * Lookup the interface definition having the given block name.  Return
+    * NULL if none is found.
+    */
+   interface_block_definition *lookup(const char *block_name)
+   {
+      return (interface_block_definition *) hash_table_find(ht, block_name);
+   }
+
+   /**
+    * Add a new interface definition.
+    */
+   void store(const interface_block_definition &def)
+   {
+      interface_block_definition *hash_entry =
+         rzalloc(mem_ctx, interface_block_definition);
+      *hash_entry = def;
+      hash_table_insert(ht, hash_entry, def.type->name);
+   }
+
+private:
+   /**
+    * Ralloc context for data structures allocated by this class.
+    */
+   void *mem_ctx;
+
+   /**
+    * Hash table mapping interface block name to an \c
+    * interface_block_definition struct.  interface_block_definition structs
+    * are allocated using \c mem_ctx.
+    */
+   hash_table *ht;
+};
+
+
+}; /* anonymous namespace */
+
 
 void
 validate_intrastage_interface_blocks(struct gl_shader_program *prog,
                                      const gl_shader **shader_list,
                                      unsigned num_shaders)
 {
-   glsl_symbol_table interfaces;
+   interface_block_definitions in_interfaces;
+   interface_block_definitions out_interfaces;
+   interface_block_definitions uniform_interfaces;
 
    for (unsigned int i = 0; i < num_shaders; i++) {
       if (shader_list[i] == NULL)
@@ -52,17 +250,36 @@ validate_intrastage_interface_blocks(struct gl_shader_program *prog,
          if (iface_type == NULL)
             continue;
 
-         const glsl_type *old_iface_type =
-            interfaces.get_interface(iface_type->name,
-                                     (enum ir_variable_mode) var->mode);
+         interface_block_definitions *definitions;
+         switch (var->mode) {
+         case ir_var_shader_in:
+            definitions = &in_interfaces;
+            break;
+         case ir_var_shader_out:
+            definitions = &out_interfaces;
+            break;
+         case ir_var_uniform:
+            definitions = &uniform_interfaces;
+            break;
+         default:
+            /* Only in, out, and uniform interfaces are legal, so we should
+             * never get here.
+             */
+            assert(!"illegal interface type");
+            continue;
+         }
 
-         if (old_iface_type == NULL) {
+         const interface_block_definition def(var);
+         interface_block_definition *prev_def =
+            definitions->lookup(iface_type->name);
+
+         if (prev_def == NULL) {
             /* This is the first time we've seen the interface, so save
-             * it into our symbol table.
+             * it into the appropriate data structure.
              */
-            interfaces.add_interface(iface_type->name, iface_type,
-                                     (enum ir_variable_mode) var->mode);
-         } else if (old_iface_type != iface_type) {
+            definitions->store(def);
+         } else if (!intrastage_match(prev_def, &def,
+                                      (ir_variable_mode) var->mode)) {
             linker_error(prog, "definitions of interface block `%s' do not"
                          " match\n", iface_type->name);
             return;
@@ -76,7 +293,9 @@ validate_interstage_interface_blocks(struct gl_shader_program *prog,
                                      const gl_shader *producer,
                                      const gl_shader *consumer)
 {
-   glsl_symbol_table interfaces;
+   interface_block_definitions inout_interfaces;
+   interface_block_definitions uniform_interfaces;
+   const bool extra_array_level = consumer->Type == GL_GEOMETRY_SHADER;
 
    /* Add non-output interfaces from the consumer to the symbol table. */
    foreach_list(node, consumer->ir) {
@@ -84,9 +303,9 @@ validate_interstage_interface_blocks(struct gl_shader_program *prog,
       if (!var || !var->get_interface_type() || var->mode == ir_var_shader_out)
          continue;
 
-      interfaces.add_interface(var->get_interface_type()->name,
-                               var->get_interface_type(),
-                               (enum ir_variable_mode) var->mode);
+      interface_block_definitions *definitions = var->mode == ir_var_uniform ?
+         &uniform_interfaces : &inout_interfaces;
+      definitions->store(interface_block_definition(var));
    }
 
    /* Verify that the producer's interfaces match. */
@@ -95,17 +314,29 @@ validate_interstage_interface_blocks(struct gl_shader_program *prog,
       if (!var || !var->get_interface_type() || var->mode == ir_var_shader_in)
          continue;
 
-      enum ir_variable_mode consumer_mode =
-         var->mode == ir_var_uniform ? ir_var_uniform : ir_var_shader_in;
-      const glsl_type *expected_type =
-         interfaces.get_interface(var->get_interface_type()->name,
-                                  consumer_mode);
+      interface_block_definitions *definitions = var->mode == ir_var_uniform ?
+         &uniform_interfaces : &inout_interfaces;
+      interface_block_definition *consumer_def =
+         definitions->lookup(var->get_interface_type()->name);
 
       /* The consumer doesn't use this output block.  Ignore it. */
-      if (expected_type == NULL)
+      if (consumer_def == NULL)
          continue;
 
-      if (var->get_interface_type() != expected_type) {
+      const interface_block_definition producer_def(var);
+      bool match;
+      if (var->mode == ir_var_uniform) {
+         /* Uniform matching rules are the same for interstage and intrastage
+          * linking.
+          */
+         match = intrastage_match(consumer_def, &producer_def,
+                                  (ir_variable_mode) var->mode);
+      } else {
+         match = interstage_match(&producer_def, consumer_def,
+                                  extra_array_level);
+      }
+
+      if (!match) {
          linker_error(prog, "definitions of interface block `%s' do not "
                       "match\n", var->get_interface_type()->name);
          return;