#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.
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()) {
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");
/**
- * 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
* 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);
}
}
}
+/**
+ * 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
*/
}
+/**
+ * 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;
/* 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) {
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];
}
}
- /* 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++;
}
}
/* 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);