glsl: Avoid extra if statements for logic and/or with no side effects.
[mesa.git] / src / glsl / ast_to_hir.cpp
index 1aebca40facf35a115c8fc06b5a6b340d78e0495..75d7e9d5793079b5c5917c87323a6e13edb0628c 100644 (file)
@@ -54,6 +54,7 @@
 #include "glsl_parser_extras.h"
 #include "ast.h"
 #include "glsl_types.h"
+#include "program/hash_table.h"
 #include "ir.h"
 
 void
@@ -1198,15 +1199,9 @@ ast_expression::hir(exec_list *instructions,
       op[1] = get_scalar_boolean_operand(&rhs_instructions, state, this, 1,
                                         "RHS", &error_emitted);
 
-      ir_constant *op0_const = op[0]->constant_expression_value();
-      if (op0_const) {
-        if (op0_const->value.b[0]) {
-           instructions->append_list(&rhs_instructions);
-           result = op[1];
-        } else {
-           result = op0_const;
-        }
-        type = glsl_type::bool_type;
+      if (rhs_instructions.is_empty()) {
+        result = new(ctx) ir_expression(ir_binop_logic_and, op[0], op[1]);
+        type = result->type;
       } else {
         ir_variable *const tmp = new(ctx) ir_variable(glsl_type::bool_type,
                                                       "and_tmp",
@@ -1240,14 +1235,9 @@ ast_expression::hir(exec_list *instructions,
       op[1] = get_scalar_boolean_operand(&rhs_instructions, state, this, 1,
                                         "RHS", &error_emitted);
 
-      ir_constant *op0_const = op[0]->constant_expression_value();
-      if (op0_const) {
-        if (op0_const->value.b[0]) {
-           result = op0_const;
-        } else {
-           result = op[1];
-        }
-        type = glsl_type::bool_type;
+      if (rhs_instructions.is_empty()) {
+        result = new(ctx) ir_expression(ir_binop_logic_or, op[0], op[1]);
+        type = result->type;
       } else {
         ir_variable *const tmp = new(ctx) ir_variable(glsl_type::bool_type,
                                                       "or_tmp",
@@ -2010,6 +2000,29 @@ apply_type_qualifier_to_variable(const struct ast_type_qualifier *qual,
    else
       var->interpolation = INTERP_QUALIFIER_NONE;
 
+   if (var->interpolation != INTERP_QUALIFIER_NONE &&
+       !(state->target == vertex_shader && var->mode == ir_var_out) &&
+       !(state->target == fragment_shader && var->mode == ir_var_in)) {
+      const char *qual_string = NULL;
+      switch (var->interpolation) {
+      case INTERP_QUALIFIER_FLAT:
+        qual_string = "flat";
+        break;
+      case INTERP_QUALIFIER_NOPERSPECTIVE:
+        qual_string = "noperspective";
+        break;
+      case INTERP_QUALIFIER_SMOOTH:
+        qual_string = "smooth";
+        break;
+      }
+
+      _mesa_glsl_error(loc, state,
+                      "interpolation qualifier `%s' can only be applied to "
+                      "vertex shader outputs and fragment shader inputs.",
+                      qual_string);
+
+   }
+
    var->pixel_center_integer = qual->flags.q.pixel_center_integer;
    var->origin_upper_left = qual->flags.q.origin_upper_left;
    if ((qual->flags.q.origin_upper_left || qual->flags.q.pixel_center_integer)
@@ -3382,7 +3395,7 @@ ast_jump_statement::hir(exec_list *instructions,
                          "continue may only appear in a loop");
       } else if (mode == ast_break &&
                 state->loop_nesting_ast == NULL &&
-                state->switch_nesting_ast == NULL) {
+                state->switch_state.switch_nesting_ast == NULL) {
         YYLTYPE loc = this->get_location();
 
         _mesa_glsl_error(& loc, state,
@@ -3400,11 +3413,11 @@ ast_jump_statement::hir(exec_list *instructions,
                                                          state);
         }
 
-        if (state->is_switch_innermost &&
+        if (state->switch_state.is_switch_innermost &&
             mode == ast_break) {
            /* Force break out of switch by setting is_break switch state.
             */
-           ir_variable *const is_break_var = state->is_break_var;
+           ir_variable *const is_break_var = state->switch_state.is_break_var;
            ir_dereference_variable *const deref_is_break_var =
               new(ctx) ir_dereference_variable(is_break_var);
            ir_constant *const true_val = new(ctx) ir_constant(true);
@@ -3507,25 +3520,25 @@ ast_switch_statement::hir(exec_list *instructions,
 
    /* Track the switch-statement nesting in a stack-like manner.
     */
-   ir_variable *saved_test_var = state->test_var;
-   ir_variable *saved_is_fallthru_var = state->is_fallthru_var;
-   
-   bool save_is_switch_innermost = state->is_switch_innermost;
-   ast_switch_statement *saved_nesting_ast = state->switch_nesting_ast;
+   struct glsl_switch_state saved = state->switch_state;
 
-   state->is_switch_innermost = true;
-   state->switch_nesting_ast = this;
+   state->switch_state.is_switch_innermost = true;
+   state->switch_state.switch_nesting_ast = this;
+   state->switch_state.labels_ht = hash_table_ctor(0, hash_table_pointer_hash,
+                                                  hash_table_pointer_compare);
+   state->switch_state.previous_default = NULL;
 
    /* Initalize is_fallthru state to false.
     */
    ir_rvalue *const is_fallthru_val = new (ctx) ir_constant(false);
-   state->is_fallthru_var = new(ctx) ir_variable(glsl_type::bool_type,
-                                               "switch_is_fallthru_tmp",
-                                               ir_var_temporary);
-   instructions->push_tail(state->is_fallthru_var);
+   state->switch_state.is_fallthru_var =
+      new(ctx) ir_variable(glsl_type::bool_type,
+                          "switch_is_fallthru_tmp",
+                          ir_var_temporary);
+   instructions->push_tail(state->switch_state.is_fallthru_var);
 
    ir_dereference_variable *deref_is_fallthru_var =
-      new(ctx) ir_dereference_variable(state->is_fallthru_var);
+      new(ctx) ir_dereference_variable(state->switch_state.is_fallthru_var);
    instructions->push_tail(new(ctx) ir_assignment(deref_is_fallthru_var,
                                                  is_fallthru_val,
                                                  NULL));
@@ -3533,13 +3546,13 @@ ast_switch_statement::hir(exec_list *instructions,
    /* Initalize is_break state to false.
     */
    ir_rvalue *const is_break_val = new (ctx) ir_constant(false);
-   state->is_break_var = new(ctx) ir_variable(glsl_type::bool_type,
-                                             "switch_is_break_tmp",
-                                             ir_var_temporary);
-   instructions->push_tail(state->is_break_var);
+   state->switch_state.is_break_var = new(ctx) ir_variable(glsl_type::bool_type,
+                                                          "switch_is_break_tmp",
+                                                          ir_var_temporary);
+   instructions->push_tail(state->switch_state.is_break_var);
 
    ir_dereference_variable *deref_is_break_var =
-      new(ctx) ir_dereference_variable(state->is_break_var);
+      new(ctx) ir_dereference_variable(state->switch_state.is_break_var);
    instructions->push_tail(new(ctx) ir_assignment(deref_is_break_var,
                                                  is_break_val,
                                                  NULL));
@@ -3552,254 +3565,294 @@ ast_switch_statement::hir(exec_list *instructions,
     */
    body->hir(instructions, state);
 
-   /* Restore previous nesting before returning.
-    */
-   state->switch_nesting_ast = saved_nesting_ast;
-   state->is_switch_innermost = save_is_switch_innermost;
-
-   state->test_var = saved_test_var;
-   state->is_fallthru_var = saved_is_fallthru_var;
-
-   /* Switch statements do not have r-values.
-    */
-   return NULL;
-}
-
+   hash_table_dtor(state->switch_state.labels_ht);
 
-void
-ast_switch_statement::test_to_hir(exec_list *instructions,
-                                 struct _mesa_glsl_parse_state *state)
-{
-   void *ctx = state;
+   state->switch_state = saved;
 
-   /* Cache value of test expression.
-    */
-   ir_rvalue *const test_val =
-      test_expression->hir(instructions,
-                          state);
+     /* Switch statements do not have r-values.
+      */
+     return NULL;
+  }
 
-   state->test_var = new(ctx) ir_variable(glsl_type::int_type,
-                                         "switch_test_tmp",
-                                         ir_var_temporary);
-   ir_dereference_variable *deref_test_var =
-      new(ctx) ir_dereference_variable(state->test_var);
-
-   instructions->push_tail(state->test_var);
-   instructions->push_tail(new(ctx) ir_assignment(deref_test_var,
-                                                 test_val,
-                                                 NULL));
-}
 
+  void
+  ast_switch_statement::test_to_hir(exec_list *instructions,
+                                   struct _mesa_glsl_parse_state *state)
+  {
+     void *ctx = state;
 
-ir_rvalue *
-ast_switch_body::hir(exec_list *instructions,
-                    struct _mesa_glsl_parse_state *state)
-{
-   if (stmts != NULL)
-      stmts->hir(instructions, state);
-      
-   /* Switch bodies do not have r-values.
-    */
-   return NULL;
-}
+     /* Cache value of test expression.
+      */
+     ir_rvalue *const test_val =
+       test_expression->hir(instructions,
+                            state);
 
+     state->switch_state.test_var = new(ctx) ir_variable(glsl_type::int_type,
+                                                        "switch_test_tmp",
+                                                        ir_var_temporary);
+     ir_dereference_variable *deref_test_var =
+       new(ctx) ir_dereference_variable(state->switch_state.test_var);
 
-ir_rvalue *
-ast_case_statement_list::hir(exec_list *instructions,
-                            struct _mesa_glsl_parse_state *state)
-{
-   foreach_list_typed (ast_case_statement, case_stmt, link, & this->cases)
-      case_stmt->hir(instructions, state);
-         
-   /* Case statements do not have r-values.
-    */
-   return NULL;
-}
+     instructions->push_tail(state->switch_state.test_var);
+     instructions->push_tail(new(ctx) ir_assignment(deref_test_var,
+                                                   test_val,
+                                                   NULL));
+  }
 
 
-ir_rvalue *
-ast_case_statement::hir(exec_list *instructions,
-                       struct _mesa_glsl_parse_state *state)
-{
-   labels->hir(instructions, state);
-   
-   /* Conditionally set fallthru state based on break state.
-    */
-   ir_constant *const false_val = new(state) ir_constant(false);
-   ir_dereference_variable *const deref_is_fallthru_var =
-      new(state) ir_dereference_variable(state->is_fallthru_var);
-   ir_dereference_variable *const deref_is_break_var =
-      new(state) ir_dereference_variable(state->is_break_var);
-   ir_assignment *const reset_fallthru_on_break =
-      new(state) ir_assignment(deref_is_fallthru_var,
-                              false_val,
-                              deref_is_break_var);
-   instructions->push_tail(reset_fallthru_on_break);
-
-   /* Guard case statements depending on fallthru state.
-    */
-   ir_dereference_variable *const deref_fallthru_guard =
-      new(state) ir_dereference_variable(state->is_fallthru_var);
-   ir_if *const test_fallthru = new(state) ir_if(deref_fallthru_guard);
-   
-   foreach_list_typed (ast_node, stmt, link, & this->stmts)
-      stmt->hir(& test_fallthru->then_instructions, state);
+  ir_rvalue *
+  ast_switch_body::hir(exec_list *instructions,
+                      struct _mesa_glsl_parse_state *state)
+  {
+     if (stmts != NULL)
+       stmts->hir(instructions, state);
 
-   instructions->push_tail(test_fallthru);
-         
-   /* Case statements do not have r-values.
-    */
-   return NULL;
-}
+     /* Switch bodies do not have r-values.
+      */
+     return NULL;
+  }
 
 
-ir_rvalue *
-ast_case_label_list::hir(exec_list *instructions,
-                        struct _mesa_glsl_parse_state *state)
-{
-   foreach_list_typed (ast_case_label, label, link, & this->labels)
-      label->hir(instructions, state);
-         
-   /* Case labels do not have r-values.
-    */
-   return NULL;
-}
+  ir_rvalue *
+  ast_case_statement_list::hir(exec_list *instructions,
+                              struct _mesa_glsl_parse_state *state)
+  {
+     foreach_list_typed (ast_case_statement, case_stmt, link, & this->cases)
+       case_stmt->hir(instructions, state);
 
+     /* Case statements do not have r-values.
+      */
+     return NULL;
+  }
 
-ir_rvalue *
-ast_case_label::hir(exec_list *instructions,
-                   struct _mesa_glsl_parse_state *state)
-{
-   void *ctx = state;
 
-   ir_dereference_variable *deref_fallthru_var =
-      new(ctx) ir_dereference_variable(state->is_fallthru_var);
-   
-   ir_rvalue *const true_val = new(ctx) ir_constant(true);
-
-   /* If not default case, ...
-    */
-   if (this->test_value != NULL) {
-      /* Conditionally set fallthru state based on
-       * comparison of cached test expression value to case label.
-       */
-      ir_rvalue *const test_val = this->test_value->hir(instructions, state);
-
-      ir_dereference_variable *deref_test_var =
-        new(ctx) ir_dereference_variable(state->test_var);
-
-      ir_rvalue *const test_cond = new(ctx) ir_expression(ir_binop_all_equal,
-                                                         glsl_type::bool_type,
-                                                         test_val,
-                                                         deref_test_var);
-
-      ir_assignment *set_fallthru_on_test =
-        new(ctx) ir_assignment(deref_fallthru_var,
-                               true_val,
-                               test_cond);
-   
-      instructions->push_tail(set_fallthru_on_test);
-   } else { /* default case */
-      /* Set falltrhu state.
-       */
-      ir_assignment *set_fallthru =
-        new(ctx) ir_assignment(deref_fallthru_var,
-                               true_val,
-                               NULL);
-   
-      instructions->push_tail(set_fallthru);
-   }
-   
-   /* Case statements do not have r-values.
-    */
-   return NULL;
-}
-
-
-void
-ast_iteration_statement::condition_to_hir(ir_loop *stmt,
-                                         struct _mesa_glsl_parse_state *state)
-{
-   void *ctx = state;
-
-   if (condition != NULL) {
-      ir_rvalue *const cond =
-        condition->hir(& stmt->body_instructions, state);
-
-      if ((cond == NULL)
-         || !cond->type->is_boolean() || !cond->type->is_scalar()) {
-        YYLTYPE loc = condition->get_location();
-
-        _mesa_glsl_error(& loc, state,
-                         "loop condition must be scalar boolean");
-      } else {
-        /* As the first code in the loop body, generate a block that looks
-         * like 'if (!condition) break;' as the loop termination condition.
-         */
-        ir_rvalue *const not_cond =
-           new(ctx) ir_expression(ir_unop_logic_not, glsl_type::bool_type, cond,
-                                  NULL);
-
-        ir_if *const if_stmt = new(ctx) ir_if(not_cond);
-
-        ir_jump *const break_stmt =
-           new(ctx) ir_loop_jump(ir_loop_jump::jump_break);
-
-        if_stmt->then_instructions.push_tail(break_stmt);
-        stmt->body_instructions.push_tail(if_stmt);
-      }
-   }
-}
-
-
-ir_rvalue *
-ast_iteration_statement::hir(exec_list *instructions,
-                            struct _mesa_glsl_parse_state *state)
-{
-   void *ctx = state;
-
-   /* For-loops and while-loops start a new scope, but do-while loops do not.
-    */
-   if (mode != ast_do_while)
-      state->symbols->push_scope();
-
-   if (init_statement != NULL)
-      init_statement->hir(instructions, state);
-
-   ir_loop *const stmt = new(ctx) ir_loop();
-   instructions->push_tail(stmt);
-
-   /* Track the current loop nesting.
-    */
-   ast_iteration_statement *nesting_ast = state->loop_nesting_ast;
-
-   state->loop_nesting_ast = this;
-
-   /* Likewise, indicate that following code is closest to a loop,
-    * NOT closest to a switch.
-    */
-   bool saved_is_switch_innermost = state->is_switch_innermost;
-   state->is_switch_innermost = false;
-
-   if (mode != ast_do_while)
-      condition_to_hir(stmt, state);
-
-   if (body != NULL)
-      body->hir(& stmt->body_instructions, state);
-
-   if (rest_expression != NULL)
-      rest_expression->hir(& stmt->body_instructions, state);
-
-   if (mode == ast_do_while)
-      condition_to_hir(stmt, state);
-
-   if (mode != ast_do_while)
-      state->symbols->pop_scope();
+  ir_rvalue *
+  ast_case_statement::hir(exec_list *instructions,
+                         struct _mesa_glsl_parse_state *state)
+  {
+     labels->hir(instructions, state);
+
+     /* Conditionally set fallthru state based on break state.
+      */
+     ir_constant *const false_val = new(state) ir_constant(false);
+     ir_dereference_variable *const deref_is_fallthru_var =
+       new(state) ir_dereference_variable(state->switch_state.is_fallthru_var);
+     ir_dereference_variable *const deref_is_break_var =
+       new(state) ir_dereference_variable(state->switch_state.is_break_var);
+     ir_assignment *const reset_fallthru_on_break =
+       new(state) ir_assignment(deref_is_fallthru_var,
+                                false_val,
+                                deref_is_break_var);
+     instructions->push_tail(reset_fallthru_on_break);
+
+     /* Guard case statements depending on fallthru state.
+      */
+     ir_dereference_variable *const deref_fallthru_guard =
+       new(state) ir_dereference_variable(state->switch_state.is_fallthru_var);
+     ir_if *const test_fallthru = new(state) ir_if(deref_fallthru_guard);
+
+     foreach_list_typed (ast_node, stmt, link, & this->stmts)
+       stmt->hir(& test_fallthru->then_instructions, state);
+
+     instructions->push_tail(test_fallthru);
+
+     /* Case statements do not have r-values.
+      */
+     return NULL;
+  }
+
+
+  ir_rvalue *
+  ast_case_label_list::hir(exec_list *instructions,
+                          struct _mesa_glsl_parse_state *state)
+  {
+     foreach_list_typed (ast_case_label, label, link, & this->labels)
+       label->hir(instructions, state);
+
+     /* Case labels do not have r-values.
+      */
+     return NULL;
+  }
+
+
+  ir_rvalue *
+  ast_case_label::hir(exec_list *instructions,
+                     struct _mesa_glsl_parse_state *state)
+  {
+     void *ctx = state;
+
+     ir_dereference_variable *deref_fallthru_var =
+       new(ctx) ir_dereference_variable(state->switch_state.is_fallthru_var);
+
+     ir_rvalue *const true_val = new(ctx) ir_constant(true);
+
+     /* If not default case, ...
+      */
+     if (this->test_value != NULL) {
+       /* Conditionally set fallthru state based on
+        * comparison of cached test expression value to case label.
+        */
+       ir_rvalue *const label_rval = this->test_value->hir(instructions, state);
+       ir_constant *label_const = label_rval->constant_expression_value();
+
+       if (!label_const) {
+          YYLTYPE loc = this->test_value->get_location();
+
+          _mesa_glsl_error(& loc, state,
+                           "switch statement case label must be a "
+                           "constant expression");
+
+          /* Stuff a dummy value in to allow processing to continue. */
+          label_const = new(ctx) ir_constant(0);
+       } else {
+          ast_expression *previous_label = (ast_expression *)
+             hash_table_find(state->switch_state.labels_ht,
+                             (void *)(uintptr_t)label_const->value.u[0]);
+
+          if (previous_label) {
+             YYLTYPE loc = this->test_value->get_location();
+             _mesa_glsl_error(& loc, state,
+                              "duplicate case value");
+
+             loc = previous_label->get_location();
+             _mesa_glsl_error(& loc, state,
+                              "this is the previous case label");
+          } else {
+             hash_table_insert(state->switch_state.labels_ht,
+                               this->test_value,
+                               (void *)(uintptr_t)label_const->value.u[0]);
+          }
+       }
+
+       ir_dereference_variable *deref_test_var =
+          new(ctx) ir_dereference_variable(state->switch_state.test_var);
+
+       ir_rvalue *const test_cond = new(ctx) ir_expression(ir_binop_all_equal,
+                                                           glsl_type::bool_type,
+                                                           label_const,
+                                                           deref_test_var);
+
+       ir_assignment *set_fallthru_on_test =
+          new(ctx) ir_assignment(deref_fallthru_var,
+                                 true_val,
+                                 test_cond);
+
+       instructions->push_tail(set_fallthru_on_test);
+     } else { /* default case */
+       if (state->switch_state.previous_default) {
+          printf("a\n");
+          YYLTYPE loc = this->get_location();
+          _mesa_glsl_error(& loc, state,
+                              "multiple default labels in one switch");
+
+          printf("b\n");
+
+          loc = state->switch_state.previous_default->get_location();
+          _mesa_glsl_error(& loc, state,
+                           "this is the first default label");
+       }
+       state->switch_state.previous_default = this;
+
+       /* Set falltrhu state.
+        */
+       ir_assignment *set_fallthru =
+          new(ctx) ir_assignment(deref_fallthru_var,
+                                 true_val,
+                                 NULL);
+
+       instructions->push_tail(set_fallthru);
+     }
+
+     /* Case statements do not have r-values.
+      */
+     return NULL;
+  }
+
+
+  void
+  ast_iteration_statement::condition_to_hir(ir_loop *stmt,
+                                           struct _mesa_glsl_parse_state *state)
+  {
+     void *ctx = state;
+
+     if (condition != NULL) {
+       ir_rvalue *const cond =
+          condition->hir(& stmt->body_instructions, state);
+
+       if ((cond == NULL)
+           || !cond->type->is_boolean() || !cond->type->is_scalar()) {
+          YYLTYPE loc = condition->get_location();
+
+          _mesa_glsl_error(& loc, state,
+                           "loop condition must be scalar boolean");
+       } else {
+          /* As the first code in the loop body, generate a block that looks
+           * like 'if (!condition) break;' as the loop termination condition.
+           */
+          ir_rvalue *const not_cond =
+             new(ctx) ir_expression(ir_unop_logic_not, glsl_type::bool_type, cond,
+                                    NULL);
+
+          ir_if *const if_stmt = new(ctx) ir_if(not_cond);
+
+          ir_jump *const break_stmt =
+             new(ctx) ir_loop_jump(ir_loop_jump::jump_break);
+
+          if_stmt->then_instructions.push_tail(break_stmt);
+          stmt->body_instructions.push_tail(if_stmt);
+       }
+     }
+  }
+
+
+  ir_rvalue *
+  ast_iteration_statement::hir(exec_list *instructions,
+                              struct _mesa_glsl_parse_state *state)
+  {
+     void *ctx = state;
+
+     /* For-loops and while-loops start a new scope, but do-while loops do not.
+      */
+     if (mode != ast_do_while)
+       state->symbols->push_scope();
+
+     if (init_statement != NULL)
+       init_statement->hir(instructions, state);
+
+     ir_loop *const stmt = new(ctx) ir_loop();
+     instructions->push_tail(stmt);
+
+     /* Track the current loop nesting.
+      */
+     ast_iteration_statement *nesting_ast = state->loop_nesting_ast;
+
+     state->loop_nesting_ast = this;
+
+     /* Likewise, indicate that following code is closest to a loop,
+      * NOT closest to a switch.
+      */
+     bool saved_is_switch_innermost = state->switch_state.is_switch_innermost;
+     state->switch_state.is_switch_innermost = false;
+
+     if (mode != ast_do_while)
+       condition_to_hir(stmt, state);
+
+     if (body != NULL)
+       body->hir(& stmt->body_instructions, state);
+
+     if (rest_expression != NULL)
+       rest_expression->hir(& stmt->body_instructions, state);
 
-   /* Restore previous nesting before returning.
-    */
-   state->loop_nesting_ast = nesting_ast;
-   state->is_switch_innermost = saved_is_switch_innermost;
+     if (mode == ast_do_while)
+       condition_to_hir(stmt, state);
+
+     if (mode != ast_do_while)
+       state->symbols->pop_scope();
+
+     /* Restore previous nesting before returning.
+      */
+     state->loop_nesting_ast = nesting_ast;
+     state->switch_state.is_switch_innermost = saved_is_switch_innermost;
 
    /* Loops do not have r-values.
     */