#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;
+ }
+ explicitly_declared = (var->how_declared != ir_var_declared_implicitly);
+ }
+
+ /**
+ * 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;
+
+ /**
+ * True if this interface block was explicitly declared in the shader;
+ * false if it was an implicitly declared built-in interface block.
+ */
+ bool explicitly_declared;
+};
+
+
+/**
+ * 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) {
+ /* Exception: if both the interface blocks are implicitly declared,
+ * don't force their types to match. They might mismatch due to the two
+ * shaders using different GLSL versions, and that's ok.
+ */
+ if (a->explicitly_declared || b->explicitly_declared)
+ 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) {
+ /* Exception: if both the interface blocks are implicitly declared,
+ * don't force their types to match. They might mismatch due to the two
+ * shaders using different GLSL versions, and that's ok.
+ */
+ if (consumer->explicitly_declared || producer->explicitly_declared)
+ 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)
if (!var)
continue;
- const glsl_type *iface_type = var->interface_type;
+ const glsl_type *iface_type = var->get_interface_type();
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;
+ }
+
+ const interface_block_definition def(var);
+ interface_block_definition *prev_def =
+ definitions->lookup(iface_type->name);
- if (old_iface_type == NULL) {
+ 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;
}
void
-validate_interstage_interface_blocks(struct gl_shader_program *prog,
- const gl_shader *producer,
- const gl_shader *consumer)
+validate_interstage_inout_blocks(struct gl_shader_program *prog,
+ const gl_shader *producer,
+ const gl_shader *consumer)
{
- glsl_symbol_table interfaces;
+ interface_block_definitions definitions;
+ const bool extra_array_level = consumer->Type == GL_GEOMETRY_SHADER;
- /* Add non-output interfaces from the consumer to the symbol table. */
+ /* Add input interfaces from the consumer to the symbol table. */
foreach_list(node, consumer->ir) {
ir_variable *var = ((ir_instruction *) node)->as_variable();
- if (!var || !var->interface_type || var->mode == ir_var_shader_out)
+ if (!var || !var->get_interface_type() || var->mode != ir_var_shader_in)
continue;
- interfaces.add_interface(var->interface_type->name,
- var->interface_type,
- (enum ir_variable_mode) var->mode);
+ definitions.store(interface_block_definition(var));
}
- /* Verify that the producer's interfaces match. */
+ /* Verify that the producer's output interfaces match. */
foreach_list(node, producer->ir) {
ir_variable *var = ((ir_instruction *) node)->as_variable();
- if (!var || !var->interface_type || var->mode == ir_var_shader_in)
+ if (!var || !var->get_interface_type() || var->mode != ir_var_shader_out)
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->interface_type->name, consumer_mode);
+ 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->interface_type != expected_type) {
+ const interface_block_definition producer_def(var);
+
+ if (!interstage_match(&producer_def, consumer_def, extra_array_level)) {
linker_error(prog, "definitions of interface block `%s' do not "
- "match\n", var->interface_type->name);
+ "match\n", var->get_interface_type()->name);
return;
}
}
}
+
+
+void
+validate_interstage_uniform_blocks(struct gl_shader_program *prog,
+ gl_shader **stages, int num_stages)
+{
+ interface_block_definitions definitions;
+
+ for (int i = 0; i < num_stages; i++) {
+ if (stages[i] == NULL)
+ continue;
+
+ const gl_shader *stage = stages[i];
+ foreach_list(node, stage->ir) {
+ ir_variable *var = ((ir_instruction *) node)->as_variable();
+ if (!var || !var->get_interface_type() || var->mode != ir_var_uniform)
+ continue;
+
+ interface_block_definition *old_def =
+ definitions.lookup(var->get_interface_type()->name);
+ const interface_block_definition new_def(var);
+ if (old_def == NULL) {
+ definitions.store(new_def);
+ } else {
+ /* Interstage uniform matching rules are the same as intrastage
+ * uniform matchin rules (for uniforms, it is as though all
+ * shaders are in the same shader stage).
+ */
+ if (!intrastage_match(old_def, &new_def, ir_var_uniform)) {
+ linker_error(prog, "definitions of interface block `%s' do not "
+ "match\n", var->get_interface_type()->name);
+ return;
+ }
+ }
+ }
+ }
+}