1/*
2 * Copyright © 2010 Intel Corporation
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
13 * Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
22 */
23
24/**
25 * \file lower_vec_index_to_cond_assign.cpp
26 *
27 * Turns indexing into vector types to a series of conditional moves
28 * of each channel's swizzle into a temporary.
29 *
30 * Most GPUs don't have a native way to do this operation, and this
31 * works around that.  For drivers using both this pass and
32 * ir_vec_index_to_swizzle, there's a risk that this pass will happen
33 * before sufficient constant folding to find that the array index is
34 * constant.  However, we hope that other optimization passes,
35 * particularly constant folding of assignment conditions and copy
36 * propagation, will result in the same code in the end.
37 */
38
39#include "ir.h"
40#include "ir_visitor.h"
41#include "ir_optimization.h"
42#include "glsl_types.h"
43
44/**
45 * Visitor class for replacing expressions with ir_constant values.
46 */
47
48class ir_vec_index_to_cond_assign_visitor : public ir_hierarchical_visitor {
49public:
50   ir_vec_index_to_cond_assign_visitor()
51   {
52      progress = false;
53   }
54
55   ir_rvalue *convert_vec_index_to_cond_assign(ir_rvalue *val);
56
57   virtual ir_visitor_status visit_enter(ir_expression *);
58   virtual ir_visitor_status visit_enter(ir_swizzle *);
59   virtual ir_visitor_status visit_leave(ir_assignment *);
60   virtual ir_visitor_status visit_enter(ir_return *);
61   virtual ir_visitor_status visit_enter(ir_call *);
62   virtual ir_visitor_status visit_enter(ir_if *);
63
64   bool progress;
65};
66
67ir_rvalue *
68ir_vec_index_to_cond_assign_visitor::convert_vec_index_to_cond_assign(ir_rvalue *ir)
69{
70   ir_dereference_array *orig_deref = ir->as_dereference_array();
71   ir_assignment *assign;
72   ir_variable *index, *var;
73   ir_dereference *deref;
74   int i;
75
76   if (!orig_deref)
77      return ir;
78
79   if (orig_deref->array->type->is_matrix() ||
80       orig_deref->array->type->is_array())
81      return ir;
82
83   void *mem_ctx = ralloc_parent(ir);
84
85   assert(orig_deref->array_index->type->base_type == GLSL_TYPE_INT);
86
87   exec_list list;
88
89   /* Store the index to a temporary to avoid reusing its tree. */
90   index = new(base_ir) ir_variable(glsl_type::int_type,
91				    "vec_index_tmp_i",
92				    ir_var_temporary);
93   list.push_tail(index);
94   deref = new(base_ir) ir_dereference_variable(index);
95   assign = new(base_ir) ir_assignment(deref, orig_deref->array_index, NULL);
96   list.push_tail(assign);
97
98   /* Temporary where we store whichever value we swizzle out. */
99   var = new(base_ir) ir_variable(ir->type, "vec_index_tmp_v",
100				  ir_var_temporary);
101   list.push_tail(var);
102
103   /* Generate a single comparison condition "mask" for all of the components
104    * in the vector.
105    */
106   ir_rvalue *const cond_deref =
107      compare_index_block(&list, index, 0,
108			  orig_deref->array->type->vector_elements,
109			  mem_ctx);
110
111   /* Generate a conditional move of each vector element to the temp. */
112   for (i = 0; i < orig_deref->array->type->vector_elements; i++) {
113      ir_rvalue *condition_swizzle =
114	 new(base_ir) ir_swizzle(cond_deref->clone(ir, NULL), i, 0, 0, 0, 1);
115
116      /* Just clone the rest of the deref chain when trying to get at the
117       * underlying variable.
118       */
119      ir_rvalue *swizzle =
120	 new(base_ir) ir_swizzle(orig_deref->array->clone(mem_ctx, NULL),
121				 i, 0, 0, 0, 1);
122
123      deref = new(base_ir) ir_dereference_variable(var);
124      assign = new(base_ir) ir_assignment(deref, swizzle, condition_swizzle);
125      list.push_tail(assign);
126   }
127
128   /* Put all of the new instructions in the IR stream before the old
129    * instruction.
130    */
131   base_ir->insert_before(&list);
132
133   this->progress = true;
134   return new(base_ir) ir_dereference_variable(var);
135}
136
137ir_visitor_status
138ir_vec_index_to_cond_assign_visitor::visit_enter(ir_expression *ir)
139{
140   unsigned int i;
141
142   for (i = 0; i < ir->get_num_operands(); i++) {
143      ir->operands[i] = convert_vec_index_to_cond_assign(ir->operands[i]);
144   }
145
146   return visit_continue;
147}
148
149ir_visitor_status
150ir_vec_index_to_cond_assign_visitor::visit_enter(ir_swizzle *ir)
151{
152   /* Can't be hit from normal GLSL, since you can't swizzle a scalar (which
153    * the result of indexing a vector is.  But maybe at some point we'll end up
154    * using swizzling of scalars for vector construction.
155    */
156   ir->val = convert_vec_index_to_cond_assign(ir->val);
157
158   return visit_continue;
159}
160
161ir_visitor_status
162ir_vec_index_to_cond_assign_visitor::visit_leave(ir_assignment *ir)
163{
164   ir_variable *index, *var;
165   ir_dereference_variable *deref;
166   ir_assignment *assign;
167   int i;
168
169   ir->rhs = convert_vec_index_to_cond_assign(ir->rhs);
170   if (ir->condition)
171      ir->condition = convert_vec_index_to_cond_assign(ir->condition);
172
173   /* Last, handle the LHS */
174   ir_dereference_array *orig_deref = ir->lhs->as_dereference_array();
175
176   if (!orig_deref ||
177       orig_deref->array->type->is_matrix() ||
178       orig_deref->array->type->is_array())
179      return visit_continue;
180
181   void *mem_ctx = ralloc_parent(ir);
182
183   assert(orig_deref->array_index->type->base_type == GLSL_TYPE_INT);
184
185   exec_list list;
186
187   /* Store the index to a temporary to avoid reusing its tree. */
188   index = new(ir) ir_variable(glsl_type::int_type, "vec_index_tmp_i",
189			       ir_var_temporary);
190   list.push_tail(index);
191   deref = new(ir) ir_dereference_variable(index);
192   assign = new(ir) ir_assignment(deref, orig_deref->array_index, NULL);
193   list.push_tail(assign);
194
195   /* Store the RHS to a temporary to avoid reusing its tree. */
196   var = new(ir) ir_variable(ir->rhs->type, "vec_index_tmp_v",
197			     ir_var_temporary);
198   list.push_tail(var);
199   deref = new(ir) ir_dereference_variable(var);
200   assign = new(ir) ir_assignment(deref, ir->rhs, NULL);
201   list.push_tail(assign);
202
203   /* Generate a single comparison condition "mask" for all of the components
204    * in the vector.
205    */
206   ir_rvalue *const cond_deref =
207      compare_index_block(&list, index, 0,
208			  orig_deref->array->type->vector_elements,
209			  mem_ctx);
210
211   /* Generate a conditional move of each vector element to the temp. */
212   for (i = 0; i < orig_deref->array->type->vector_elements; i++) {
213      ir_rvalue *condition_swizzle =
214	 new(ir) ir_swizzle(cond_deref->clone(ir, NULL), i, 0, 0, 0, 1);
215
216
217      /* Just clone the rest of the deref chain when trying to get at the
218       * underlying variable.
219       */
220      ir_rvalue *swizzle =
221	 new(ir) ir_swizzle(orig_deref->array->clone(mem_ctx, NULL),
222			    i, 0, 0, 0, 1);
223
224      deref = new(ir) ir_dereference_variable(var);
225      assign = new(ir) ir_assignment(swizzle, deref, condition_swizzle);
226      list.push_tail(assign);
227   }
228
229   /* If the original assignment has a condition, respect that original
230    * condition!  This is acomplished by wrapping the new conditional
231    * assignments in an if-statement that uses the original condition.
232    */
233   if (ir->condition != NULL) {
234      /* No need to clone the condition because the IR that it hangs on is
235       * going to be removed from the instruction sequence.
236       */
237      ir_if *if_stmt = new(mem_ctx) ir_if(ir->condition);
238
239      list.move_nodes_to(&if_stmt->then_instructions);
240      ir->insert_before(if_stmt);
241   } else {
242      ir->insert_before(&list);
243   }
244
245   ir->remove();
246
247   this->progress = true;
248
249   return visit_continue;
250}
251
252ir_visitor_status
253ir_vec_index_to_cond_assign_visitor::visit_enter(ir_call *ir)
254{
255   foreach_iter(exec_list_iterator, iter, *ir) {
256      ir_rvalue *param = (ir_rvalue *)iter.get();
257      ir_rvalue *new_param = convert_vec_index_to_cond_assign(param);
258
259      if (new_param != param) {
260	 param->replace_with(new_param);
261      }
262   }
263
264   return visit_continue;
265}
266
267ir_visitor_status
268ir_vec_index_to_cond_assign_visitor::visit_enter(ir_return *ir)
269{
270   if (ir->value) {
271      ir->value = convert_vec_index_to_cond_assign(ir->value);
272   }
273
274   return visit_continue;
275}
276
277ir_visitor_status
278ir_vec_index_to_cond_assign_visitor::visit_enter(ir_if *ir)
279{
280   ir->condition = convert_vec_index_to_cond_assign(ir->condition);
281
282   return visit_continue;
283}
284
285bool
286do_vec_index_to_cond_assign(exec_list *instructions)
287{
288   ir_vec_index_to_cond_assign_visitor v;
289
290   visit_list_elements(&v, instructions);
291
292   return v.progress;
293}
294