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_mat_op_to_vec.cpp
26 *
27 * Breaks matrix operation expressions down to a series of vector operations.
28 *
29 * Generally this is how we have to codegen matrix operations for a
30 * GPU, so this gives us the chance to constant fold operations on a
31 * column or row.
32 */
33
34#include "ir.h"
35#include "ir_expression_flattening.h"
36#include "glsl_types.h"
37
38class ir_mat_op_to_vec_visitor : public ir_hierarchical_visitor {
39public:
40   ir_mat_op_to_vec_visitor()
41   {
42      this->made_progress = false;
43      this->mem_ctx = NULL;
44   }
45
46   ir_visitor_status visit_leave(ir_assignment *);
47
48   ir_dereference *get_column(ir_dereference *val, int col);
49   ir_rvalue *get_element(ir_dereference *val, int col, int row);
50
51   void do_mul_mat_mat(ir_dereference *result,
52		       ir_dereference *a, ir_dereference *b);
53   void do_mul_mat_vec(ir_dereference *result,
54		       ir_dereference *a, ir_dereference *b);
55   void do_mul_vec_mat(ir_dereference *result,
56		       ir_dereference *a, ir_dereference *b);
57   void do_mul_mat_scalar(ir_dereference *result,
58			  ir_dereference *a, ir_dereference *b);
59   void do_equal_mat_mat(ir_dereference *result, ir_dereference *a,
60			 ir_dereference *b, bool test_equal);
61
62   void *mem_ctx;
63   bool made_progress;
64};
65
66static bool
67mat_op_to_vec_predicate(ir_instruction *ir)
68{
69   ir_expression *expr = ir->as_expression();
70   unsigned int i;
71
72   if (!expr)
73      return false;
74
75   for (i = 0; i < expr->get_num_operands(); i++) {
76      if (expr->operands[i]->type->is_matrix())
77	 return true;
78   }
79
80   return false;
81}
82
83bool
84do_mat_op_to_vec(exec_list *instructions)
85{
86   ir_mat_op_to_vec_visitor v;
87
88   /* Pull out any matrix expression to a separate assignment to a
89    * temp.  This will make our handling of the breakdown to
90    * operations on the matrix's vector components much easier.
91    */
92   do_expression_flattening(instructions, mat_op_to_vec_predicate);
93
94   visit_list_elements(&v, instructions);
95
96   return v.made_progress;
97}
98
99ir_rvalue *
100ir_mat_op_to_vec_visitor::get_element(ir_dereference *val, int col, int row)
101{
102   val = get_column(val, col);
103
104   return new(mem_ctx) ir_swizzle(val, row, 0, 0, 0, 1);
105}
106
107ir_dereference *
108ir_mat_op_to_vec_visitor::get_column(ir_dereference *val, int row)
109{
110   val = val->clone(mem_ctx, NULL);
111
112   if (val->type->is_matrix()) {
113      val = new(mem_ctx) ir_dereference_array(val,
114					      new(mem_ctx) ir_constant(row));
115   }
116
117   return val;
118}
119
120void
121ir_mat_op_to_vec_visitor::do_mul_mat_mat(ir_dereference *result,
122					 ir_dereference *a,
123					 ir_dereference *b)
124{
125   int b_col, i;
126   ir_assignment *assign;
127   ir_expression *expr;
128
129   for (b_col = 0; b_col < b->type->matrix_columns; b_col++) {
130      /* first column */
131      expr = new(mem_ctx) ir_expression(ir_binop_mul,
132					get_column(a, 0),
133					get_element(b, b_col, 0));
134
135      /* following columns */
136      for (i = 1; i < a->type->matrix_columns; i++) {
137	 ir_expression *mul_expr;
138
139	 mul_expr = new(mem_ctx) ir_expression(ir_binop_mul,
140					       get_column(a, i),
141					       get_element(b, b_col, i));
142	 expr = new(mem_ctx) ir_expression(ir_binop_add,
143					   expr,
144					   mul_expr);
145      }
146
147      assign = new(mem_ctx) ir_assignment(get_column(result, b_col), expr);
148      base_ir->insert_before(assign);
149   }
150}
151
152void
153ir_mat_op_to_vec_visitor::do_mul_mat_vec(ir_dereference *result,
154					 ir_dereference *a,
155					 ir_dereference *b)
156{
157   int i;
158   ir_assignment *assign;
159   ir_expression *expr;
160
161   /* first column */
162   expr = new(mem_ctx) ir_expression(ir_binop_mul,
163				     get_column(a, 0),
164				     get_element(b, 0, 0));
165
166   /* following columns */
167   for (i = 1; i < a->type->matrix_columns; i++) {
168      ir_expression *mul_expr;
169
170      mul_expr = new(mem_ctx) ir_expression(ir_binop_mul,
171					    get_column(a, i),
172					    get_element(b, 0, i));
173      expr = new(mem_ctx) ir_expression(ir_binop_add, expr, mul_expr);
174   }
175
176   result = result->clone(mem_ctx, NULL);
177   assign = new(mem_ctx) ir_assignment(result, expr);
178   base_ir->insert_before(assign);
179}
180
181void
182ir_mat_op_to_vec_visitor::do_mul_vec_mat(ir_dereference *result,
183					 ir_dereference *a,
184					 ir_dereference *b)
185{
186   int i;
187
188   for (i = 0; i < b->type->matrix_columns; i++) {
189      ir_rvalue *column_result;
190      ir_expression *column_expr;
191      ir_assignment *column_assign;
192
193      column_result = result->clone(mem_ctx, NULL);
194      column_result = new(mem_ctx) ir_swizzle(column_result, i, 0, 0, 0, 1);
195
196      column_expr = new(mem_ctx) ir_expression(ir_binop_dot,
197					       a->clone(mem_ctx, NULL),
198					       get_column(b, i));
199
200      column_assign = new(mem_ctx) ir_assignment(column_result,
201						 column_expr);
202      base_ir->insert_before(column_assign);
203   }
204}
205
206void
207ir_mat_op_to_vec_visitor::do_mul_mat_scalar(ir_dereference *result,
208					    ir_dereference *a,
209					    ir_dereference *b)
210{
211   int i;
212
213   for (i = 0; i < a->type->matrix_columns; i++) {
214      ir_expression *column_expr;
215      ir_assignment *column_assign;
216
217      column_expr = new(mem_ctx) ir_expression(ir_binop_mul,
218					       get_column(a, i),
219					       b->clone(mem_ctx, NULL));
220
221      column_assign = new(mem_ctx) ir_assignment(get_column(result, i),
222						 column_expr);
223      base_ir->insert_before(column_assign);
224   }
225}
226
227void
228ir_mat_op_to_vec_visitor::do_equal_mat_mat(ir_dereference *result,
229					   ir_dereference *a,
230					   ir_dereference *b,
231					   bool test_equal)
232{
233   /* This essentially implements the following GLSL:
234    *
235    * bool equal(mat4 a, mat4 b)
236    * {
237    *   return !any(bvec4(a[0] != b[0],
238    *                     a[1] != b[1],
239    *                     a[2] != b[2],
240    *                     a[3] != b[3]);
241    * }
242    *
243    * bool nequal(mat4 a, mat4 b)
244    * {
245    *   return any(bvec4(a[0] != b[0],
246    *                    a[1] != b[1],
247    *                    a[2] != b[2],
248    *                    a[3] != b[3]);
249    * }
250    */
251   const unsigned columns = a->type->matrix_columns;
252   const glsl_type *const bvec_type =
253      glsl_type::get_instance(GLSL_TYPE_BOOL, columns, 1);
254
255   ir_variable *const tmp_bvec =
256      new(this->mem_ctx) ir_variable(bvec_type, "mat_cmp_bvec",
257				     ir_var_temporary);
258   this->base_ir->insert_before(tmp_bvec);
259
260   for (unsigned i = 0; i < columns; i++) {
261      ir_expression *const cmp =
262	 new(this->mem_ctx) ir_expression(ir_binop_any_nequal,
263					  get_column(a, i),
264					  get_column(b, i));
265
266      ir_dereference *const lhs =
267	 new(this->mem_ctx) ir_dereference_variable(tmp_bvec);
268
269      ir_assignment *const assign =
270	 new(this->mem_ctx) ir_assignment(lhs, cmp, NULL, (1U << i));
271
272      this->base_ir->insert_before(assign);
273   }
274
275   ir_rvalue *const val = new(this->mem_ctx) ir_dereference_variable(tmp_bvec);
276   ir_expression *any = new(this->mem_ctx) ir_expression(ir_unop_any, val);
277
278   if (test_equal)
279      any = new(this->mem_ctx) ir_expression(ir_unop_logic_not, any);
280
281   ir_assignment *const assign =
282      new(mem_ctx) ir_assignment(result->clone(mem_ctx, NULL), any);
283   base_ir->insert_before(assign);
284}
285
286static bool
287has_matrix_operand(const ir_expression *expr, unsigned &columns)
288{
289   for (unsigned i = 0; i < expr->get_num_operands(); i++) {
290      if (expr->operands[i]->type->is_matrix()) {
291	 columns = expr->operands[i]->type->matrix_columns;
292	 return true;
293      }
294   }
295
296   return false;
297}
298
299
300ir_visitor_status
301ir_mat_op_to_vec_visitor::visit_leave(ir_assignment *orig_assign)
302{
303   ir_expression *orig_expr = orig_assign->rhs->as_expression();
304   unsigned int i, matrix_columns = 1;
305   ir_dereference *op[2];
306
307   if (!orig_expr)
308      return visit_continue;
309
310   if (!has_matrix_operand(orig_expr, matrix_columns))
311      return visit_continue;
312
313   assert(orig_expr->get_num_operands() <= 2);
314
315   mem_ctx = ralloc_parent(orig_assign);
316
317   ir_dereference_variable *result =
318      orig_assign->lhs->as_dereference_variable();
319   assert(result);
320
321   /* Store the expression operands in temps so we can use them
322    * multiple times.
323    */
324   for (i = 0; i < orig_expr->get_num_operands(); i++) {
325      ir_assignment *assign;
326      ir_dereference *deref = orig_expr->operands[i]->as_dereference();
327
328      /* Avoid making a temporary if we don't need to to avoid aliasing. */
329      if (deref &&
330	  deref->variable_referenced() != result->variable_referenced()) {
331	 op[i] = deref;
332	 continue;
333      }
334
335      /* Otherwise, store the operand in a temporary generally if it's
336       * not a dereference.
337       */
338      ir_variable *var = new(mem_ctx) ir_variable(orig_expr->operands[i]->type,
339						  "mat_op_to_vec",
340						  ir_var_temporary);
341      base_ir->insert_before(var);
342
343      /* Note that we use this dereference for the assignment.  That means
344       * that others that want to use op[i] have to clone the deref.
345       */
346      op[i] = new(mem_ctx) ir_dereference_variable(var);
347      assign = new(mem_ctx) ir_assignment(op[i], orig_expr->operands[i]);
348      base_ir->insert_before(assign);
349   }
350
351   /* OK, time to break down this matrix operation. */
352   switch (orig_expr->operation) {
353   case ir_unop_neg: {
354      /* Apply the operation to each column.*/
355      for (i = 0; i < matrix_columns; i++) {
356	 ir_expression *column_expr;
357	 ir_assignment *column_assign;
358
359	 column_expr = new(mem_ctx) ir_expression(orig_expr->operation,
360						  get_column(op[0], i));
361
362	 column_assign = new(mem_ctx) ir_assignment(get_column(result, i),
363						    column_expr);
364	 assert(column_assign->write_mask != 0);
365	 base_ir->insert_before(column_assign);
366      }
367      break;
368   }
369   case ir_binop_add:
370   case ir_binop_sub:
371   case ir_binop_div:
372   case ir_binop_mod: {
373      /* For most operations, the matrix version is just going
374       * column-wise through and applying the operation to each column
375       * if available.
376       */
377      for (i = 0; i < matrix_columns; i++) {
378	 ir_expression *column_expr;
379	 ir_assignment *column_assign;
380
381	 column_expr = new(mem_ctx) ir_expression(orig_expr->operation,
382						  get_column(op[0], i),
383						  get_column(op[1], i));
384
385	 column_assign = new(mem_ctx) ir_assignment(get_column(result, i),
386						    column_expr);
387	 assert(column_assign->write_mask != 0);
388	 base_ir->insert_before(column_assign);
389      }
390      break;
391   }
392   case ir_binop_mul:
393      if (op[0]->type->is_matrix()) {
394	 if (op[1]->type->is_matrix()) {
395	    do_mul_mat_mat(result, op[0], op[1]);
396	 } else if (op[1]->type->is_vector()) {
397	    do_mul_mat_vec(result, op[0], op[1]);
398	 } else {
399	    assert(op[1]->type->is_scalar());
400	    do_mul_mat_scalar(result, op[0], op[1]);
401	 }
402      } else {
403	 assert(op[1]->type->is_matrix());
404	 if (op[0]->type->is_vector()) {
405	    do_mul_vec_mat(result, op[0], op[1]);
406	 } else {
407	    assert(op[0]->type->is_scalar());
408	    do_mul_mat_scalar(result, op[1], op[0]);
409	 }
410      }
411      break;
412
413   case ir_binop_all_equal:
414   case ir_binop_any_nequal:
415      do_equal_mat_mat(result, op[1], op[0],
416		       (orig_expr->operation == ir_binop_all_equal));
417      break;
418
419   default:
420      printf("FINISHME: Handle matrix operation for %s\n",
421	     orig_expr->operator_string());
422      abort();
423   }
424   orig_assign->remove();
425   this->made_progress = true;
426
427   return visit_continue;
428}
429