• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2010 Luca Barbieri
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_jumps.cpp
26  *
27  * This pass lowers jumps (break, continue, and return) to if/else structures.
28  *
29  * It can be asked to:
30  * 1. Pull jumps out of ifs where possible
31  * 2. Remove all "continue"s, replacing them with an "execute flag"
32  * 3. Replace all "break" with a single conditional one at the end of the loop
33  * 4. Replace all "return"s with a single return at the end of the function,
34  *    for the main function and/or other functions
35  *
36  * Applying this pass gives several benefits:
37  * 1. All functions can be inlined.
38  * 2. nv40 and other pre-DX10 chips without "continue" can be supported
39  * 3. nv30 and other pre-DX10 chips with no control flow at all are better
40  *    supported
41  *
42  * Continues are lowered by adding a per-loop "execute flag", initialized to
43  * true, that when cleared inhibits all execution until the end of the loop.
44  *
45  * Breaks are lowered to continues, plus setting a "break flag" that is checked
46  * at the end of the loop, and trigger the unique "break".
47  *
48  * Returns are lowered to breaks/continues, plus adding a "return flag" that
49  * causes loops to break again out of their enclosing loops until all the
50  * loops are exited: then the "execute flag" logic will ignore everything
51  * until the end of the function.
52  *
53  * Note that "continue" and "return" can also be implemented by adding
54  * a dummy loop and using break.
55  * However, this is bad for hardware with limited nesting depth, and
56  * prevents further optimization, and thus is not currently performed.
57  */
58 
59 #include "glsl_types.h"
60 #include <string.h>
61 #include "ir.h"
62 
63 enum jump_strength
64 {
65    strength_none,
66    strength_always_clears_execute_flag,
67    strength_continue,
68    strength_break,
69    strength_return,
70 };
71 
72 struct block_record
73 {
74    /* minimum jump strength (of lowered IR, not pre-lowering IR)
75     *
76     * If the block ends with a jump, must be the strength of the jump.
77     * Otherwise, the jump would be dead and have been deleted before)
78     *
79     * If the block doesn't end with a jump, it can be different than strength_none if all paths before it lead to some jump
80     * (e.g. an if with a return in one branch, and a break in the other, while not lowering them)
81     * Note that identical jumps are usually unified though.
82     */
83    jump_strength min_strength;
84 
85    /* can anything clear the execute flag? */
86    bool may_clear_execute_flag;
87 
block_recordblock_record88    block_record()
89    {
90       this->min_strength = strength_none;
91       this->may_clear_execute_flag = false;
92    }
93 };
94 
95 struct loop_record
96 {
97    ir_function_signature* signature;
98    ir_loop* loop;
99 
100    /* used to avoid lowering the break used to represent lowered breaks */
101    unsigned nesting_depth;
102    bool in_if_at_the_end_of_the_loop;
103 
104    bool may_set_return_flag;
105 
106    ir_variable* break_flag;
107    ir_variable* execute_flag; /* cleared to emulate continue */
108 
loop_recordloop_record109    loop_record(ir_function_signature* p_signature = 0, ir_loop* p_loop = 0)
110    {
111       this->signature = p_signature;
112       this->loop = p_loop;
113       this->nesting_depth = 0;
114       this->in_if_at_the_end_of_the_loop = false;
115       this->may_set_return_flag = false;
116       this->break_flag = 0;
117       this->execute_flag = 0;
118    }
119 
get_execute_flagloop_record120    ir_variable* get_execute_flag()
121    {
122       /* also supported for the "function loop" */
123       if(!this->execute_flag) {
124          exec_list& list = this->loop ? this->loop->body_instructions : signature->body;
125          this->execute_flag = new(this->signature) ir_variable(glsl_type::bool_type, "execute_flag", ir_var_temporary);
126          list.push_head(new(this->signature) ir_assignment(new(this->signature) ir_dereference_variable(execute_flag), new(this->signature) ir_constant(true), 0));
127          list.push_head(this->execute_flag);
128       }
129       return this->execute_flag;
130    }
131 
get_break_flagloop_record132    ir_variable* get_break_flag()
133    {
134       assert(this->loop);
135       if(!this->break_flag) {
136          this->break_flag = new(this->signature) ir_variable(glsl_type::bool_type, "break_flag", ir_var_temporary);
137          this->loop->insert_before(this->break_flag);
138          this->loop->insert_before(new(this->signature) ir_assignment(new(this->signature) ir_dereference_variable(break_flag), new(this->signature) ir_constant(false), 0));
139       }
140       return this->break_flag;
141    }
142 };
143 
144 struct function_record
145 {
146    ir_function_signature* signature;
147    ir_variable* return_flag; /* used to break out of all loops and then jump to the return instruction */
148    ir_variable* return_value;
149    bool is_main;
150    unsigned nesting_depth;
151 
function_recordfunction_record152    function_record(ir_function_signature* p_signature = 0)
153    {
154       this->signature = p_signature;
155       this->return_flag = 0;
156       this->return_value = 0;
157       this->nesting_depth = 0;
158       this->is_main = this->signature && (strcmp(this->signature->function_name(), "main") == 0);
159    }
160 
get_return_flagfunction_record161    ir_variable* get_return_flag()
162    {
163       if(!this->return_flag) {
164          this->return_flag = new(this->signature) ir_variable(glsl_type::bool_type, "return_flag", ir_var_temporary);
165          this->signature->body.push_head(new(this->signature) ir_assignment(new(this->signature) ir_dereference_variable(return_flag), new(this->signature) ir_constant(false), 0));
166          this->signature->body.push_head(this->return_flag);
167       }
168       return this->return_flag;
169    }
170 
get_return_valuefunction_record171    ir_variable* get_return_value()
172    {
173       if(!this->return_value) {
174          assert(!this->signature->return_type->is_void());
175          return_value = new(this->signature) ir_variable(this->signature->return_type, "return_value", ir_var_temporary);
176          this->signature->body.push_head(this->return_value);
177       }
178       return this->return_value;
179    }
180 };
181 
182 struct ir_lower_jumps_visitor : public ir_control_flow_visitor {
183    bool progress;
184 
185    struct function_record function;
186    struct loop_record loop;
187    struct block_record block;
188 
189    bool pull_out_jumps;
190    bool lower_continue;
191    bool lower_break;
192    bool lower_sub_return;
193    bool lower_main_return;
194 
ir_lower_jumps_visitorir_lower_jumps_visitor195    ir_lower_jumps_visitor()
196    {
197       this->progress = false;
198    }
199 
truncate_after_instructionir_lower_jumps_visitor200    void truncate_after_instruction(exec_node *ir)
201    {
202       if (!ir)
203          return;
204 
205       while (!ir->get_next()->is_tail_sentinel()) {
206          ((ir_instruction *)ir->get_next())->remove();
207          this->progress = true;
208       }
209    }
210 
move_outer_block_insideir_lower_jumps_visitor211    void move_outer_block_inside(ir_instruction *ir, exec_list *inner_block)
212    {
213       while (!ir->get_next()->is_tail_sentinel()) {
214          ir_instruction *move_ir = (ir_instruction *)ir->get_next();
215 
216          move_ir->remove();
217          inner_block->push_tail(move_ir);
218       }
219    }
220 
visitir_lower_jumps_visitor221    virtual void visit(class ir_loop_jump * ir)
222    {
223       truncate_after_instruction(ir);
224       this->block.min_strength = ir->is_break() ? strength_break : strength_continue;
225    }
226 
visitir_lower_jumps_visitor227    virtual void visit(class ir_return * ir)
228    {
229       truncate_after_instruction(ir);
230       this->block.min_strength = strength_return;
231    }
232 
visitir_lower_jumps_visitor233    virtual void visit(class ir_discard * ir)
234    {
235    }
236 
get_jump_strengthir_lower_jumps_visitor237    enum jump_strength get_jump_strength(ir_instruction* ir)
238    {
239       if(!ir)
240          return strength_none;
241       else if(ir->ir_type == ir_type_loop_jump) {
242          if(((ir_loop_jump*)ir)->is_break())
243             return strength_break;
244          else
245             return strength_continue;
246       } else if(ir->ir_type == ir_type_return)
247          return strength_return;
248       else
249          return strength_none;
250    }
251 
should_lower_jumpir_lower_jumps_visitor252    bool should_lower_jump(ir_jump* ir)
253    {
254       unsigned strength = get_jump_strength(ir);
255       bool lower;
256       switch(strength)
257       {
258       case strength_none:
259          lower = false; /* don't change this, code relies on it */
260          break;
261       case strength_continue:
262          lower = lower_continue;
263          break;
264       case strength_break:
265          assert(this->loop.loop);
266          /* never lower "canonical break" */
267          if(ir->get_next()->is_tail_sentinel() && (this->loop.nesting_depth == 0
268                || (this->loop.nesting_depth == 1 && this->loop.in_if_at_the_end_of_the_loop)))
269             lower = false;
270          else
271             lower = lower_break;
272          break;
273       case strength_return:
274          /* never lower return at the end of a this->function */
275          if(this->function.nesting_depth == 0 && ir->get_next()->is_tail_sentinel())
276             lower = false;
277          else if (this->function.is_main)
278             lower = lower_main_return;
279          else
280             lower = lower_sub_return;
281          break;
282       }
283       return lower;
284    }
285 
visit_blockir_lower_jumps_visitor286    block_record visit_block(exec_list* list)
287    {
288       block_record saved_block = this->block;
289       this->block = block_record();
290       visit_exec_list(list, this);
291       block_record ret = this->block;
292       this->block = saved_block;
293       return ret;
294    }
295 
visitir_lower_jumps_visitor296    virtual void visit(ir_if *ir)
297    {
298       if(this->loop.nesting_depth == 0 && ir->get_next()->is_tail_sentinel())
299          this->loop.in_if_at_the_end_of_the_loop = true;
300 
301       ++this->function.nesting_depth;
302       ++this->loop.nesting_depth;
303 
304       block_record block_records[2];
305       ir_jump* jumps[2];
306 
307       block_records[0] = visit_block(&ir->then_instructions);
308       block_records[1] = visit_block(&ir->else_instructions);
309 
310 retry: /* we get here if we put code after the if inside a branch */
311    for(unsigned i = 0; i < 2; ++i) {
312       exec_list& list = i ? ir->else_instructions : ir->then_instructions;
313       jumps[i] = 0;
314       if(!list.is_empty() && get_jump_strength((ir_instruction*)list.get_tail()))
315          jumps[i] = (ir_jump*)list.get_tail();
316    }
317 
318       for(;;) {
319          jump_strength jump_strengths[2];
320 
321          for(unsigned i = 0; i < 2; ++i) {
322             if(jumps[i]) {
323                jump_strengths[i] = block_records[i].min_strength;
324                assert(jump_strengths[i] == get_jump_strength(jumps[i]));
325             } else
326                jump_strengths[i] = strength_none;
327          }
328 
329          /* move both jumps out if possible */
330          if(pull_out_jumps && jump_strengths[0] == jump_strengths[1]) {
331             bool unify = true;
332             if(jump_strengths[0] == strength_continue)
333                ir->insert_after(new(ir) ir_loop_jump(ir_loop_jump::jump_continue));
334             else if(jump_strengths[0] == strength_break)
335                ir->insert_after(new(ir) ir_loop_jump(ir_loop_jump::jump_break));
336             /* FINISHME: unify returns with identical expressions */
337             else if(jump_strengths[0] == strength_return && this->function.signature->return_type->is_void())
338                ir->insert_after(new(ir) ir_return(NULL));
339 	    else
340 	       unify = false;
341 
342             if(unify) {
343                jumps[0]->remove();
344                jumps[1]->remove();
345                this->progress = true;
346 
347                jumps[0] = 0;
348                jumps[1] = 0;
349                block_records[0].min_strength = strength_none;
350                block_records[1].min_strength = strength_none;
351                break;
352             }
353          }
354 
355          /* lower a jump: if both need to lowered, start with the strongest one, so that
356           * we might later unify the lowered version with the other one
357           */
358          bool should_lower[2];
359          for(unsigned i = 0; i < 2; ++i)
360             should_lower[i] = should_lower_jump(jumps[i]);
361 
362          int lower;
363          if(should_lower[1] && should_lower[0])
364             lower = jump_strengths[1] > jump_strengths[0];
365          else if(should_lower[0])
366             lower = 0;
367          else if(should_lower[1])
368             lower = 1;
369          else
370             break;
371 
372          if(jump_strengths[lower] == strength_return) {
373             ir_variable* return_flag = this->function.get_return_flag();
374             if(!this->function.signature->return_type->is_void()) {
375                ir_variable* return_value = this->function.get_return_value();
376                jumps[lower]->insert_before(new(ir) ir_assignment(new (ir) ir_dereference_variable(return_value), ((ir_return*)jumps[lower])->value, NULL));
377             }
378             jumps[lower]->insert_before(new(ir) ir_assignment(new (ir) ir_dereference_variable(return_flag), new (ir) ir_constant(true), NULL));
379             this->loop.may_set_return_flag = true;
380             if(this->loop.loop) {
381                ir_loop_jump* lowered = 0;
382                lowered = new(ir) ir_loop_jump(ir_loop_jump::jump_break);
383                block_records[lower].min_strength = strength_break;
384                jumps[lower]->replace_with(lowered);
385                jumps[lower] = lowered;
386             } else
387                goto lower_continue;
388             this->progress = true;
389          } else if(jump_strengths[lower] == strength_break) {
390             /* We can't lower to an actual continue because that would execute the increment.
391              *
392              * In the lowered code, we instead put the break check between the this->loop body and the increment,
393              * which is impossible with a real continue as defined by the GLSL IR currently.
394              *
395              * Smarter options (such as undoing the increment) are possible but it's not worth implementing them,
396              * because if break is lowered, continue is almost surely lowered too.
397              */
398             jumps[lower]->insert_before(new(ir) ir_assignment(new (ir) ir_dereference_variable(this->loop.get_break_flag()), new (ir) ir_constant(true), 0));
399             goto lower_continue;
400          } else if(jump_strengths[lower] == strength_continue) {
401 lower_continue:
402             ir_variable* execute_flag = this->loop.get_execute_flag();
403             jumps[lower]->replace_with(new(ir) ir_assignment(new (ir) ir_dereference_variable(execute_flag), new (ir) ir_constant(false), 0));
404             jumps[lower] = 0;
405             block_records[lower].min_strength = strength_always_clears_execute_flag;
406             block_records[lower].may_clear_execute_flag = true;
407             this->progress = true;
408             break;
409          }
410       }
411 
412       /* move out a jump out if possible */
413       if(pull_out_jumps) {
414          int move_out = -1;
415          if(jumps[0] && block_records[1].min_strength >= strength_continue)
416             move_out = 0;
417          else if(jumps[1] && block_records[0].min_strength >= strength_continue)
418             move_out = 1;
419 
420          if(move_out >= 0)
421          {
422             jumps[move_out]->remove();
423             ir->insert_after(jumps[move_out]);
424             jumps[move_out] = 0;
425             block_records[move_out].min_strength = strength_none;
426             this->progress = true;
427          }
428       }
429 
430       if(block_records[0].min_strength < block_records[1].min_strength)
431          this->block.min_strength = block_records[0].min_strength;
432       else
433          this->block.min_strength = block_records[1].min_strength;
434       this->block.may_clear_execute_flag = this->block.may_clear_execute_flag || block_records[0].may_clear_execute_flag || block_records[1].may_clear_execute_flag;
435 
436       if(this->block.min_strength)
437          truncate_after_instruction(ir);
438       else if(this->block.may_clear_execute_flag)
439       {
440          int move_into = -1;
441          if(block_records[0].min_strength && !block_records[1].may_clear_execute_flag)
442             move_into = 1;
443          else if(block_records[1].min_strength && !block_records[0].may_clear_execute_flag)
444             move_into = 0;
445 
446          if(move_into >= 0) {
447             assert(!block_records[move_into].min_strength && !block_records[move_into].may_clear_execute_flag); /* otherwise, we just truncated */
448 
449             exec_list* list = move_into ? &ir->else_instructions : &ir->then_instructions;
450             exec_node* next = ir->get_next();
451             if(!next->is_tail_sentinel()) {
452                move_outer_block_inside(ir, list);
453 
454                exec_list list;
455                list.head = next;
456                block_records[move_into] = visit_block(&list);
457 
458                this->progress = true;
459                goto retry;
460             }
461          } else {
462             ir_instruction* ir_after;
463             for(ir_after = (ir_instruction*)ir->get_next(); !ir_after->is_tail_sentinel();)
464             {
465                ir_if* ir_if = ir_after->as_if();
466                if(ir_if && ir_if->else_instructions.is_empty()) {
467                   ir_dereference_variable* ir_if_cond_deref = ir_if->condition->as_dereference_variable();
468                   if(ir_if_cond_deref && ir_if_cond_deref->var == this->loop.execute_flag) {
469                      ir_instruction* ir_next = (ir_instruction*)ir_after->get_next();
470                      ir_after->insert_before(&ir_if->then_instructions);
471                      ir_after->remove();
472                      ir_after = ir_next;
473                      continue;
474                   }
475                }
476                ir_after = (ir_instruction*)ir_after->get_next();
477 
478                /* only set this if we find any unprotected instruction */
479                this->progress = true;
480             }
481 
482             if(!ir->get_next()->is_tail_sentinel()) {
483                assert(this->loop.execute_flag);
484                ir_if* if_execute = new(ir) ir_if(new(ir) ir_dereference_variable(this->loop.execute_flag));
485                move_outer_block_inside(ir, &if_execute->then_instructions);
486                ir->insert_after(if_execute);
487             }
488          }
489       }
490       --this->loop.nesting_depth;
491       --this->function.nesting_depth;
492    }
493 
visitir_lower_jumps_visitor494    virtual void visit(ir_loop *ir)
495    {
496       ++this->function.nesting_depth;
497       loop_record saved_loop = this->loop;
498       this->loop = loop_record(this->function.signature, ir);
499 
500       block_record body = visit_block(&ir->body_instructions);
501 
502       if(body.min_strength >= strength_break) {
503          /* FINISHME: turn the this->loop into an if, or replace it with its body */
504       }
505 
506       if(this->loop.break_flag) {
507          ir_if* break_if = new(ir) ir_if(new(ir) ir_dereference_variable(this->loop.break_flag));
508          break_if->then_instructions.push_tail(new(ir) ir_loop_jump(ir_loop_jump::jump_break));
509          ir->body_instructions.push_tail(break_if);
510       }
511 
512       if(this->loop.may_set_return_flag) {
513          assert(this->function.return_flag);
514          ir_if* return_if = new(ir) ir_if(new(ir) ir_dereference_variable(this->function.return_flag));
515          saved_loop.may_set_return_flag = true;
516          if(saved_loop.loop)
517             return_if->then_instructions.push_tail(new(ir) ir_loop_jump(ir_loop_jump::jump_break));
518          else
519             move_outer_block_inside(ir, &return_if->else_instructions);
520          ir->insert_after(return_if);
521       }
522 
523       this->loop = saved_loop;
524       --this->function.nesting_depth;
525    }
526 
visitir_lower_jumps_visitor527    virtual void visit(ir_function_signature *ir)
528    {
529       /* these are not strictly necessary */
530       assert(!this->function.signature);
531       assert(!this->loop.loop);
532 
533       function_record saved_function = this->function;
534       loop_record saved_loop = this->loop;
535       this->function = function_record(ir);
536       this->loop = loop_record(ir);
537 
538       assert(!this->loop.loop);
539       visit_block(&ir->body);
540 
541       if(this->function.return_value)
542          ir->body.push_tail(new(ir) ir_return(new (ir) ir_dereference_variable(this->function.return_value)));
543 
544       this->loop = saved_loop;
545       this->function = saved_function;
546    }
547 
visitir_lower_jumps_visitor548    virtual void visit(class ir_function * ir)
549    {
550       visit_block(&ir->signatures);
551    }
552 };
553 
554 bool
do_lower_jumps(exec_list * instructions,bool pull_out_jumps,bool lower_sub_return,bool lower_main_return,bool lower_continue,bool lower_break)555 do_lower_jumps(exec_list *instructions, bool pull_out_jumps, bool lower_sub_return, bool lower_main_return, bool lower_continue, bool lower_break)
556 {
557    ir_lower_jumps_visitor v;
558    v.pull_out_jumps = pull_out_jumps;
559    v.lower_continue = lower_continue;
560    v.lower_break = lower_break;
561    v.lower_sub_return = lower_sub_return;
562    v.lower_main_return = lower_main_return;
563 
564    do {
565       v.progress = false;
566       visit_exec_list(instructions, &v);
567    } while (v.progress);
568 
569    return v.progress;
570 }
571