linker: Link built-in functions instead of including them in every shader
[mesa.git] / src / glsl / linker.cpp
index eb10f90a9112f7fb1fcfe1f6c83340e03eaff2ff..7c30a40a6ce1fb89bb16489637e9989c35b26a52 100644 (file)
 #include <cstdlib>
 #include <cstdio>
 #include <cstdarg>
+#include <climits>
 
 extern "C" {
 #include <talloc.h>
 }
 
 #include "main/mtypes.h"
+#include "main/macros.h"
 #include "glsl_symbol_table.h"
-#include "glsl_parser_extras.h"
 #include "ir.h"
-#include "ir_optimization.h"
 #include "program.h"
-extern "C" {
 #include "hash_table.h"
-}
+#include "shader_api.h"
+#include "linker.h"
+#include "ir_optimization.h"
 
 /**
  * Visitor that determines whether or not a variable is ever written.
@@ -192,11 +193,6 @@ validate_vertex_shader_executable(struct gl_shader_program *prog,
    if (shader == NULL)
       return true;
 
-   if (!shader->symbols->get_function("main")) {
-      linker_error_printf(prog, "vertex shader lacks `main'\n");
-      return false;
-   }
-
    find_assignment_visitor find("gl_Position");
    find.run(shader->ir);
    if (!find.variable_found()) {
@@ -221,11 +217,6 @@ validate_fragment_shader_executable(struct gl_shader_program *prog,
    if (shader == NULL)
       return true;
 
-   if (!shader->symbols->get_function("main")) {
-      linker_error_printf(prog, "fragment shader lacks `main'\n");
-      return false;
-   }
-
    find_assignment_visitor frag_color("gl_FragColor");
    find_assignment_visitor frag_data("gl_FragData");
 
@@ -243,42 +234,80 @@ validate_fragment_shader_executable(struct gl_shader_program *prog,
 
 
 /**
- * Perform validation of uniforms used across multiple shader stages
+ * Generate a string describing the mode of a variable
+ */
+static const char *
+mode_string(const ir_variable *var)
+{
+   switch (var->mode) {
+   case ir_var_auto:
+      return (var->read_only) ? "global constant" : "global variable";
+
+   case ir_var_uniform: return "uniform";
+   case ir_var_in:      return "shader input";
+   case ir_var_out:     return "shader output";
+   case ir_var_inout:   return "shader inout";
+
+   case ir_var_temporary:
+   default:
+      assert(!"Should not get here.");
+      return "invalid variable";
+   }
+}
+
+
+/**
+ * Perform validation of global variables used across multiple shaders
  */
 bool
-cross_validate_uniforms(struct gl_shader_program *prog)
+cross_validate_globals(struct gl_shader_program *prog,
+                      struct gl_shader **shader_list,
+                      unsigned num_shaders,
+                      bool uniforms_only)
 {
    /* Examine all of the uniforms in all of the shaders and cross validate
     * them.
     */
-   glsl_symbol_table uniforms;
-   for (unsigned i = 0; i < prog->_NumLinkedShaders; i++) {
-      foreach_list(node, prog->_LinkedShaders[i]->ir) {
+   glsl_symbol_table variables;
+   for (unsigned i = 0; i < num_shaders; i++) {
+      foreach_list(node, shader_list[i]->ir) {
         ir_variable *const var = ((ir_instruction *) node)->as_variable();
 
-        if ((var == NULL) || (var->mode != ir_var_uniform))
+        if (var == NULL)
            continue;
 
-        /* If a uniform with this name has already been seen, verify that the
-         * new instance has the same type.  In addition, if the uniforms have
+        if (uniforms_only && (var->mode != ir_var_uniform))
+           continue;
+
+        /* Don't cross validate temporaries that are at global scope.  These
+         * will eventually get pulled into the shaders 'main'.
+         */
+        if (var->mode == ir_var_temporary)
+           continue;
+
+        /* If a global with this name has already been seen, verify that the
+         * new instance has the same type.  In addition, if the globals have
          * initializers, the values of the initializers must be the same.
          */
-        ir_variable *const existing = uniforms.get_variable(var->name);
+        ir_variable *const existing = variables.get_variable(var->name);
         if (existing != NULL) {
            if (var->type != existing->type) {
-              linker_error_printf(prog, "uniform `%s' declared as type "
+              linker_error_printf(prog, "%s `%s' declared as type "
                                   "`%s' and type `%s'\n",
+                                  mode_string(var),
                                   var->name, var->type->name,
                                   existing->type->name);
               return false;
            }
 
+           /* FINISHME: Handle non-constant initializers.
+            */
            if (var->constant_value != NULL) {
               if (existing->constant_value != NULL) {
                  if (!var->constant_value->has_value(existing->constant_value)) {
-                    linker_error_printf(prog, "initializers for uniform "
+                    linker_error_printf(prog, "initializers for %s "
                                         "`%s' have differing values\n",
-                                        var->name);
+                                        mode_string(var), var->name);
                     return false;
                  }
               } else
@@ -286,11 +315,18 @@ cross_validate_uniforms(struct gl_shader_program *prog)
                   * have an initializer but a later instance does, copy the
                   * initializer to the version stored in the symbol table.
                   */
-                 existing->constant_value =
-                    (ir_constant *)var->constant_value->clone(NULL);
+                 /* FINISHME: This is wrong.  The constant_value field should
+                  * FINISHME: not be modified!  Imagine a case where a shader
+                  * FINISHME: without an initializer is linked in two different
+                  * FINISHME: programs with shaders that have differing
+                  * FINISHME: initializers.  Linking with the first will
+                  * FINISHME: modify the shader, and linking with the second
+                  * FINISHME: will fail.
+                  */
+                 existing->constant_value = var->constant_value->clone(NULL);
            }
         } else
-           uniforms.add_variable(var->name, var);
+           variables.add_variable(var->name, var);
       }
    }
 
@@ -298,6 +334,17 @@ cross_validate_uniforms(struct gl_shader_program *prog)
 }
 
 
+/**
+ * Perform validation of uniforms used across multiple shader stages
+ */
+bool
+cross_validate_uniforms(struct gl_shader_program *prog)
+{
+   return cross_validate_globals(prog, prog->_LinkedShaders,
+                                prog->_NumLinkedShaders, true);
+}
+
+
 /**
  * Validate that outputs from one stage match inputs of another
  */
@@ -399,6 +446,326 @@ cross_validate_outputs_to_inputs(struct gl_shader_program *prog,
 }
 
 
+/**
+ * Populates a shaders symbol table with all global declarations
+ */
+static void
+populate_symbol_table(gl_shader *sh)
+{
+   sh->symbols = new(sh) glsl_symbol_table;
+
+   foreach_list(node, sh->ir) {
+      ir_instruction *const inst = (ir_instruction *) node;
+      ir_variable *var;
+      ir_function *func;
+
+      if ((func = inst->as_function()) != NULL) {
+        sh->symbols->add_function(func->name, func);
+      } else if ((var = inst->as_variable()) != NULL) {
+        sh->symbols->add_variable(var->name, var);
+      }
+   }
+}
+
+
+/**
+ * Remap variables referenced in an instruction tree
+ *
+ * This is used when instruction trees are cloned from one shader and placed in
+ * another.  These trees will contain references to \c ir_variable nodes that
+ * do not exist in the target shader.  This function finds these \c ir_variable
+ * references and replaces the references with matching variables in the target
+ * shader.
+ *
+ * If there is no matching variable in the target shader, a clone of the
+ * \c ir_variable is made and added to the target shader.  The new variable is
+ * added to \b both the instruction stream and the symbol table.
+ *
+ * \param inst         IR tree that is to be processed.
+ * \param symbols      Symbol table containing global scope symbols in the
+ *                     linked shader.
+ * \param instructions Instruction stream where new variable declarations
+ *                     should be added.
+ */
+void
+remap_variables(ir_instruction *inst, glsl_symbol_table *symbols,
+               exec_list *instructions, hash_table *temps)
+{
+   class remap_visitor : public ir_hierarchical_visitor {
+   public:
+      remap_visitor(glsl_symbol_table *symbols, exec_list *instructions,
+                   hash_table *temps)
+      {
+        this->symbols = symbols;
+        this->instructions = instructions;
+        this->temps = temps;
+      }
+
+      virtual ir_visitor_status visit(ir_dereference_variable *ir)
+      {
+        if (ir->var->mode == ir_var_temporary) {
+           ir_variable *var = (ir_variable *) hash_table_find(temps, ir->var);
+
+           assert(var != NULL);
+           ir->var = var;
+           return visit_continue;
+        }
+
+        ir_variable *const existing =
+           this->symbols->get_variable(ir->var->name);
+        if (existing != NULL)
+           ir->var = existing;
+        else {
+           ir_variable *copy = ir->var->clone(NULL);
+
+           this->symbols->add_variable(copy->name, copy);
+           this->instructions->push_head(copy);
+           ir->var = copy;
+        }
+
+        return visit_continue;
+      }
+
+   private:
+      glsl_symbol_table *symbols;
+      exec_list *instructions;
+      hash_table *temps;
+   };
+
+   remap_visitor v(symbols, instructions, temps);
+
+   inst->accept(&v);
+}
+
+
+/**
+ * Move non-declarations from one instruction stream to another
+ *
+ * The intended usage pattern of this function is to pass the pointer to the
+ * head sentinal of a list (i.e., a pointer to the list cast to an \c exec_node
+ * pointer) for \c last and \c false for \c make_copies on the first
+ * call.  Successive calls pass the return value of the previous call for
+ * \c last and \c true for \c make_copies.
+ *
+ * \param instructions Source instruction stream
+ * \param last         Instruction after which new instructions should be
+ *                     inserted in the target instruction stream
+ * \param make_copies  Flag selecting whether instructions in \c instructions
+ *                     should be copied (via \c ir_instruction::clone) into the
+ *                     target list or moved.
+ *
+ * \return
+ * The new "last" instruction in the target instruction stream.  This pointer
+ * is suitable for use as the \c last parameter of a later call to this
+ * function.
+ */
+exec_node *
+move_non_declarations(exec_list *instructions, exec_node *last,
+                     bool make_copies, gl_shader *target)
+{
+   hash_table *temps = NULL;
+
+   if (make_copies)
+      temps = hash_table_ctor(0, hash_table_pointer_hash,
+                             hash_table_pointer_compare);
+
+   foreach_list_safe(node, instructions) {
+      ir_instruction *inst = (ir_instruction *) node;
+
+      if (inst->as_function())
+        continue;
+
+      ir_variable *var = inst->as_variable();
+      if ((var != NULL) && (var->mode != ir_var_temporary))
+        continue;
+
+      assert(inst->as_assignment()
+            || ((var != NULL) && (var->mode == ir_var_temporary)));
+
+      if (make_copies) {
+        inst = inst->clone(NULL);
+
+        if (var != NULL)
+           hash_table_insert(temps, inst, var);
+        else
+           remap_variables(inst, target->symbols, target->ir, temps);
+      } else {
+        inst->remove();
+      }
+
+      last->insert_after(inst);
+      last = inst;
+   }
+
+   if (make_copies)
+      hash_table_dtor(temps);
+
+   return last;
+}
+
+/**
+ * Get the function signature for main from a shader
+ */
+static ir_function_signature *
+get_main_function_signature(gl_shader *sh)
+{
+   ir_function *const f = sh->symbols->get_function("main");
+   if (f != NULL) {
+      exec_list void_parameters;
+
+      /* Look for the 'void main()' signature and ensure that it's defined.
+       * This keeps the linker from accidentally pick a shader that just
+       * contains a prototype for main.
+       *
+       * We don't have to check for multiple definitions of main (in multiple
+       * shaders) because that would have already been caught above.
+       */
+      ir_function_signature *sig = f->matching_signature(&void_parameters);
+      if ((sig != NULL) && sig->is_defined) {
+        return sig;
+      }
+   }
+
+   return NULL;
+}
+
+
+/**
+ * Combine a group of shaders for a single stage to generate a linked shader
+ *
+ * \note
+ * If this function is supplied a single shader, it is cloned, and the new
+ * shader is returned.
+ */
+static struct gl_shader *
+link_intrastage_shaders(struct gl_shader_program *prog,
+                       struct gl_shader **shader_list,
+                       unsigned num_shaders)
+{
+   /* Check that global variables defined in multiple shaders are consistent.
+    */
+   if (!cross_validate_globals(prog, shader_list, num_shaders, false))
+      return NULL;
+
+   /* Check that there is only a single definition of each function signature
+    * across all shaders.
+    */
+   for (unsigned i = 0; i < (num_shaders - 1); i++) {
+      foreach_list(node, shader_list[i]->ir) {
+        ir_function *const f = ((ir_instruction *) node)->as_function();
+
+        if (f == NULL)
+           continue;
+
+        for (unsigned j = i + 1; j < num_shaders; j++) {
+           ir_function *const other =
+              shader_list[j]->symbols->get_function(f->name);
+
+           /* If the other shader has no function (and therefore no function
+            * signatures) with the same name, skip to the next shader.
+            */
+           if (other == NULL)
+              continue;
+
+           foreach_iter (exec_list_iterator, iter, *f) {
+              ir_function_signature *sig =
+                 (ir_function_signature *) iter.get();
+
+              if (!sig->is_defined || sig->is_built_in)
+                 continue;
+
+              ir_function_signature *other_sig =
+                 other->exact_matching_signature(& sig->parameters);
+
+              if ((other_sig != NULL) && other_sig->is_defined
+                  && !other_sig->is_built_in) {
+                 linker_error_printf(prog,
+                                     "function `%s' is multiply defined",
+                                     f->name);
+                 return NULL;
+              }
+           }
+        }
+      }
+   }
+
+   /* Find the shader that defines main, and make a clone of it.
+    *
+    * Starting with the clone, search for undefined references.  If one is
+    * found, find the shader that defines it.  Clone the reference and add
+    * it to the shader.  Repeat until there are no undefined references or
+    * until a reference cannot be resolved.
+    */
+   gl_shader *main = NULL;
+   for (unsigned i = 0; i < num_shaders; i++) {
+      if (get_main_function_signature(shader_list[i]) != NULL) {
+        main = shader_list[i];
+        break;
+      }
+   }
+
+   if (main == NULL) {
+      linker_error_printf(prog, "%s shader lacks `main'\n",
+                         (shader_list[0]->Type == GL_VERTEX_SHADER)
+                         ? "vertex" : "fragment");
+      return NULL;
+   }
+
+   gl_shader *const linked = _mesa_new_shader(NULL, 0, main->Type);
+   linked->ir = new(linked) exec_list;
+   clone_ir_list(linked->ir, main->ir);
+
+   populate_symbol_table(linked);
+
+   /* The a pointer to the main function in the final linked shader (i.e., the
+    * copy of the original shader that contained the main function).
+    */
+   ir_function_signature *const main_sig = get_main_function_signature(linked);
+
+   /* Move any instructions other than variable declarations or function
+    * declarations into main.
+    */
+   exec_node *insertion_point =
+      move_non_declarations(linked->ir, (exec_node *) &main_sig->body, false,
+                           linked);
+
+   for (unsigned i = 0; i < num_shaders; i++) {
+      if (shader_list[i] == main)
+        continue;
+
+      insertion_point = move_non_declarations(shader_list[i]->ir,
+                                             insertion_point, true, linked);
+   }
+
+   /* Resolve initializers for global variables in the linked shader.
+    */
+   unsigned num_linking_shaders = num_shaders;
+   for (unsigned i = 0; i < num_shaders; i++)
+      num_linking_shaders += shader_list[i]->num_builtins_to_link;
+
+   gl_shader **linking_shaders =
+      (gl_shader **) calloc(num_linking_shaders, sizeof(gl_shader *));
+
+   memcpy(linking_shaders, shader_list,
+         sizeof(linking_shaders[0]) * num_shaders);
+
+   unsigned idx = num_shaders;
+   for (unsigned i = 0; i < num_shaders; i++) {
+      memcpy(&linking_shaders[idx], shader_list[i]->builtins_to_link,
+            sizeof(linking_shaders[0]) * shader_list[i]->num_builtins_to_link);
+      idx += shader_list[i]->num_builtins_to_link;
+   }
+
+   assert(idx == num_linking_shaders);
+
+   link_function_calls(prog, linked, linking_shaders, num_linking_shaders);
+
+   free(linking_shaders);
+
+   return linked;
+}
+
+
 struct uniform_node {
    exec_node link;
    struct gl_uniform *u;
@@ -749,7 +1116,10 @@ assign_varying_locations(gl_shader *producer, gl_shader *consumer)
       /* An 'out' variable is only really a shader output if its value is read
        * by the following stage.
        */
-      var->shader_out = (var->location != -1);
+      if (var->location == -1) {
+        var->shader_out = false;
+        var->mode = ir_var_auto;
+      }
    }
 
    foreach_list(node, consumer->ir) {
@@ -789,7 +1159,12 @@ link_shaders(struct gl_shader_program *prog)
       calloc(2 * prog->NumShaders, sizeof(struct gl_shader *));
    frag_shader_list =  &vert_shader_list[prog->NumShaders];
 
+   unsigned min_version = UINT_MAX;
+   unsigned max_version = 0;
    for (unsigned i = 0; i < prog->NumShaders; i++) {
+      min_version = MIN2(min_version, prog->Shaders[i]->Version);
+      max_version = MAX2(max_version, prog->Shaders[i]->Version);
+
       switch (prog->Shaders[i]->Type) {
       case GL_VERTEX_SHADER:
         vert_shader_list[num_vert_shaders] = prog->Shaders[i];
@@ -806,26 +1181,48 @@ link_shaders(struct gl_shader_program *prog)
       }
    }
 
-   /* FINISHME: Implement intra-stage linking. */
-   assert(num_vert_shaders <= 1);
-   assert(num_frag_shaders <= 1);
-
-   /* Verify that each of the per-target executables is valid.
+   /* Previous to GLSL version 1.30, different compilation units could mix and
+    * match shading language versions.  With GLSL 1.30 and later, the versions
+    * of all shaders must match.
     */
-   if (!validate_vertex_shader_executable(prog, vert_shader_list[0])
-       || !validate_fragment_shader_executable(prog, frag_shader_list[0]))
+   assert(min_version >= 110);
+   assert(max_version <= 130);
+   if ((max_version >= 130) && (min_version != max_version)) {
+      linker_error_printf(prog, "all shaders must use same shading "
+                         "language version\n");
       goto done;
+   }
 
+   prog->Version = max_version;
 
+   /* Link all shaders for a particular stage and validate the result.
+    */
    prog->_NumLinkedShaders = 0;
-
    if (num_vert_shaders > 0) {
-      prog->_LinkedShaders[prog->_NumLinkedShaders] = vert_shader_list[0];
+      gl_shader *const sh =
+        link_intrastage_shaders(prog, vert_shader_list, num_vert_shaders);
+
+      if (sh == NULL)
+        goto done;
+
+      if (!validate_vertex_shader_executable(prog, sh))
+         goto done;
+
+      prog->_LinkedShaders[prog->_NumLinkedShaders] = sh;
       prog->_NumLinkedShaders++;
    }
 
    if (num_frag_shaders > 0) {
-      prog->_LinkedShaders[prog->_NumLinkedShaders] = frag_shader_list[0];
+      gl_shader *const sh =
+        link_intrastage_shaders(prog, frag_shader_list, num_frag_shaders);
+
+      if (sh == NULL)
+        goto done;
+
+      if (!validate_fragment_shader_executable(prog, sh))
+         goto done;
+
+      prog->_LinkedShaders[prog->_NumLinkedShaders] = sh;
       prog->_NumLinkedShaders++;
    }
 
@@ -848,6 +1245,43 @@ link_shaders(struct gl_shader_program *prog)
    }
 
    /* FINISHME: Perform whole-program optimization here. */
+   for (unsigned i = 0; i < prog->_NumLinkedShaders; i++) {
+      /* Optimization passes */
+      bool progress;
+      exec_list *ir = prog->_LinkedShaders[i]->ir;
+
+      /* Lowering */
+      do_mat_op_to_vec(ir);
+      do_mod_to_fract(ir);
+      do_div_to_mul_rcp(ir);
+
+      do {
+        progress = false;
+
+        progress = do_function_inlining(ir) || progress;
+        progress = do_if_simplification(ir) || progress;
+        progress = do_copy_propagation(ir) || progress;
+        progress = do_dead_code_local(ir) || progress;
+#if 0
+        progress = do_dead_code_unlinked(state, ir) || progress;
+#endif
+        progress = do_constant_variable_unlinked(ir) || progress;
+        progress = do_constant_folding(ir) || progress;
+        progress = do_if_return(ir) || progress;
+#if 0
+        if (ctx->Shader.EmitNoIfs)
+           progress = do_if_to_cond_assign(ir) || progress;
+#endif
+
+        progress = do_vec_index_to_swizzle(ir) || progress;
+        /* Do this one after the previous to let the easier pass handle
+         * constant vector indexing.
+         */
+        progress = do_vec_index_to_cond_assign(ir) || progress;
+
+        progress = do_swizzle_swizzle(ir) || progress;
+      } while (progress);
+   }
 
    assign_uniform_locations(prog);