File Coverage

blib/lib/Language/P/Intermediate/Transform.pm
Criterion Covered Total %
statement 239 244 97.9
branch 61 70 87.1
condition 32 42 76.1
subroutine 24 25 96.0
pod 1 5 20.0
total 357 386 92.4


line stmt bran cond sub pod time code
1             package Language::P::Intermediate::Transform;
2              
3 19     19   18666 use strict;
  19         3860  
  19         1069  
4 19     19   122 use warnings;
  19         45  
  19         744  
5 19     19   106 use base qw(Class::Accessor::Fast);
  19         31  
  19         2401  
6              
7             __PACKAGE__->mk_accessors( qw(_temporary_count _current_basic_block
8             _converting _queue _stack _converted
9             _converted_segments) );
10              
11 19     19   112 use Language::P::Opcodes qw(:all);
  19         36  
  19         19225  
12 19     19   124 use Language::P::Assembly qw(:all);
  19         30  
  19         74550  
13              
14             my %op_map =
15             ( OP_MAKE_LIST() => '_make_list',
16             OP_POP() => '_pop',
17             OP_SWAP() => '_swap',
18             OP_DUP() => '_dup',
19             OP_CONSTANT_SUB() => '_const_sub',
20             OP_JUMP_IF_TRUE() => '_cond_jump',
21             OP_JUMP_IF_FALSE() => '_cond_jump',
22             OP_JUMP_IF_NULL() => '_cond_jump',
23             OP_JUMP_IF_F_GT() => '_cond_jump',
24             OP_JUMP_IF_F_GE() => '_cond_jump',
25             OP_JUMP_IF_F_EQ() => '_cond_jump',
26             OP_JUMP_IF_F_NE() => '_cond_jump',
27             OP_JUMP_IF_F_LE() => '_cond_jump',
28             OP_JUMP_IF_F_LT() => '_cond_jump',
29             OP_JUMP_IF_S_GT() => '_cond_jump',
30             OP_JUMP_IF_S_GE() => '_cond_jump',
31             OP_JUMP_IF_S_EQ() => '_cond_jump',
32             OP_JUMP_IF_S_NE() => '_cond_jump',
33             OP_JUMP_IF_S_LE() => '_cond_jump',
34             OP_JUMP_IF_S_LT() => '_cond_jump',
35             OP_JUMP() => '_jump',
36             );
37              
38 63     63   352 sub _local_name { sprintf "t%d", ++$_[0]->{_temporary_count} }
39              
40             sub new {
41 27     27 1 479 my( $class, $args ) = @_;
42 27         176 my $self = $class->SUPER::new( $args );
43              
44 27         350 $self->_temporary_count( 0 );
45              
46 27         259 return $self;
47             }
48              
49             sub _add_bytecode {
50 404     404   1266 my( $self, @bytecode ) = @_;
51              
52 404         479 push @{$self->_current_basic_block->bytecode}, @bytecode;
  404         994  
53             }
54              
55             sub all_to_tree {
56 14     14 0 81 my( $self, $code_segments ) = @_;
57 14         55 my $all_ssa = $self->all_to_ssa( $code_segments );
58              
59 14         74 _ssa_to_tree( $self, $_ ) foreach @$all_ssa;
60              
61 14         67 return $all_ssa;
62             }
63              
64             sub all_to_ssa {
65 27     27 0 101 my( $self, $code_segments ) = @_;
66              
67 27         135 $self->_converted_segments( {} );
68 27   66     220 my @converted = map $self->_converted_segments->{$_}
69             || $self->to_ssa( $_ ), @$code_segments;
70 27         151 $self->_converted_segments( {} );
71              
72 27         251 return \@converted;
73             }
74              
75             sub to_ssa {
76 32     32 0 347 my( $self, $code_segment ) = @_;
77              
78 32         100 $self->_temporary_count( 0 );
79 32         247 $self->_stack( [] );
80 32         246 $self->_converted( {} );
81              
82 32         259 my $new_code = Language::P::Intermediate::Code->new
83             ( { type => $code_segment->type,
84             name => $code_segment->name,
85             basic_blocks => [],
86             lexicals => $code_segment->lexicals,
87             } );
88 32         149 $self->_converted_segments->{$code_segment} = $new_code;
89              
90 32         213 foreach my $inner ( @{$code_segment->inner} ) {
  32         121  
91 5         82 my $new_inner = $self->to_ssa( $inner );
92 5         14 $new_inner->{outer} = $new_code;
93             }
94              
95             # find all non-empty blocks without predecessors and enqueue them
96             # (there can be more than one only if there is dead code)
97 32         252 $self->_queue( [] );
98 32         179 foreach my $block ( @{$code_segment->basic_blocks} ) {
  32         108  
99 135 100       655 next unless @{$block->bytecode};
  135         377  
100 129 100       792 next if @{$block->predecessors};
  129         300  
101 34         201 push @{$self->_queue}, $block;
  34         108  
102             }
103              
104 32         245 my $stack = $self->_stack;
105 32         146 while( @{$self->_queue} ) {
  193         1517  
106 163         802 my $block = shift @{$self->_queue};
  163         450  
107              
108 163 100       868 next if $self->_converted->{$block}{converted};
109             # process a node with input values after all its predecessors
110             # might not be possible if more values become temporaries,
111             # works for now
112 131 100 100     1060 if( ( $self->_converted->{$block}{depth} || 0 ) > 0
  60   100     521  
113             && grep !$self->_converted->{$_}{converted}, @{$block->predecessors} ) {
114 2         65 push @{$self->_queue}, $block;
  2         8  
115 2         13 redo;
116             }
117 129         323 $self->_converted->{$block} =
118 129         1609 { %{$self->_converted->{$block}},
119             converted => 1,
120             created => 0,
121             };
122 129         1817 $self->_converting( $self->_converted->{$block} );
123 129   66     1300 my $cblock = $self->_converting->{block} ||=
124             Language::P::Intermediate::BasicBlock
125             ->new_from_label( $block->start_label );
126              
127 129         576 push @{$new_code->basic_blocks}, $cblock;
  129         337  
128 129         783 $self->_current_basic_block( $cblock );
129 129 100       654 @$stack = @{$self->_converting->{in_stack} || []};
  129         298  
130              
131             # remove dummy phi values that all get the same value
132 129         1142 foreach my $value ( @$stack ) {
133 63 100       256 next unless $value->{opcode_n} == OP_PHI;
134 23         51 my $t = $value->{parameters}[1];
135 23 100       42 if( !grep $value->{parameters}[$_] ne $t,
  23         175  
136             grep $_ & 1,
137             1 .. $#{$value->{parameters}} ) {
138 4         16 $value = opcode_n( OP_GET, $t );
139             }
140             }
141              
142 129         336 foreach my $bc ( @{$block->bytecode} ) {
  129         349  
143 586 100       2295 next if $bc->{label};
144 457   100     1655 my $meth = $op_map{$bc->{opcode_n}} || '_generic';
145              
146 457         1256 $self->$meth( $bc );
147             }
148              
149 129   33     1614 _add_bytecode $self,
150             grep $_->{opcode_n} != OP_PHI && $_->{opcode_n} != OP_GET, @$stack;
151             }
152              
153 32         312 return $new_code;
154             }
155              
156             sub to_tree {
157 0     0 0 0 my( $self, $code_segment ) = @_;
158 0         0 my $ssa = $self->to_ssa( $code_segment );
159              
160 0         0 return _ssa_to_tree( $self, $ssa );
161             }
162              
163             sub _ssa_to_tree {
164 15     15   27 my( $self, $ssa ) = @_;
165              
166 15         64 $self->_temporary_count( 0 );
167              
168 15         82 foreach my $block ( @{$ssa->basic_blocks} ) {
  15         53  
169 56         296 my $op_off = 0;
170 56         83 while( $op_off <= $#{$block->bytecode} ) {
  230         590  
171 174         1050 my $op = $block->bytecode->[$op_off];
172 174         664 ++$op_off;
173 174 100 100     926 next if $op->{label}
      100        
174             || $op->{opcode_n} != OP_SET
175             || $op->{parameters}[1]->{opcode_n} != OP_PHI;
176              
177 8         15 my %block_variable = @{$op->{parameters}[1]->{parameters}};
  8         58  
178              
179 8         34 while( my( $label, $variable ) = each %block_variable ) {
180 17         250 my( $block_from ) = grep $_ eq $label,
181 17         176 @{$ssa->basic_blocks};
182 17         162 my $op_from_off = $#{$block_from->bytecode};
  17         42  
183              
184             # find the jump coming to this block
185 17         99 while( $op_from_off >= 0 ) {
186 17         44 my $op_from = $block_from->bytecode->[$op_from_off];
187 17         145 last if $op_from->{parameters}
188 17 50 33     91 && @{$op_from->{parameters}}
      33        
189             && $op_from->{parameters}[-1] eq $block;
190 0         0 --$op_from_off;
191             }
192              
193 17 50       47 die "Can't find jump: ", $block_from->start_label,
194             " => ", $block->start_label
195             if $op_from_off < 0;
196              
197             # add SET nodes to rename the variables
198 17         27 splice @{$block_from->bytecode}, $op_from_off, 0,
  17         51  
199             opcode_n( OP_SET, $op->{parameters}[0],
200             opcode_n( OP_GET, $variable ) );
201             }
202              
203 8         138 --$op_off;
204 8         14 splice @{$block->bytecode}, $op_off, 1;
  8         23  
205             }
206             }
207              
208 15         124 return $ssa;
209             }
210              
211             sub _get_stack {
212 141     141   309 my( $self, $count, $force_get ) = @_;
213 141 100       310 return unless $count;
214 139         179 my @values = splice @{$self->_stack}, -$count;
  139         356  
215 139         998 _created( $self, -$count );
216              
217 139         908 foreach my $value ( @values ) {
218 196 100 100     986 next if $value->{opcode_n} != OP_PHI && !$force_get;
219 33         79 my $name = _local_name( $self );
220 33         115 _add_bytecode $self, opcode_n( OP_SET, $name, $value );
221 33         341 $value = opcode_n( OP_GET, $name );
222             }
223              
224 139         839 return @values;
225             }
226              
227             sub _jump_to {
228 127     127   212 my( $self, $op, $to, $out_names ) = @_;
229              
230 127         364 my $stack = $self->_stack;
231 127         721 my $converted_blocks = $self->_converted;
232 127   100     1170 my $converted = $converted_blocks->{$to} ||= {};
233              
234             # check that input stack height is the same on all in branches
235 127 100       373 if( defined $converted->{depth} ) {
236 32 50       136 die sprintf "Inconsistent depth %d != %d in %s => %s",
237             $converted->{depth}, scalar @$stack,
238             $self->_current_basic_block->start_label, $to->start_label
239             if $converted->{depth} != scalar @$stack;
240             }
241              
242             # emit as OP_SET all stack elements created in the basic block
243             # and construct the input stack of the next basic block
244 127 100       376 if( @$stack ) {
245 80 100       251 @$out_names = _emit_out_stack( $self ) unless @$out_names;
246              
247 80         216 my $created_elements = $self->_converting->{created};
248 80         398 my $inherited_elements = @$stack - $created_elements;
249              
250             # copy inherited elements, generated GET or PHI for created elements
251 80 100       196 if( !$converted->{in_stack} ) {
252 58 100       83 if( @{$to->predecessors} > 1 ) {
  58         159  
253 20         164 $converted->{in_stack} = [ map opcode_n( OP_PHI ), @$stack ];
254             } else {
255 38         274 $converted->{in_stack} = [ map opcode_n( OP_GET, $_ ),
256             @$out_names ];
257             }
258             }
259              
260             # update PHI nodes with the (block, value) pair
261 80 100       827 if( @{$to->predecessors} > 1 ) {
  80         212  
262 42         227 my $i = 0;
263 42         91 foreach my $out ( @$out_names ) {
264 48 50       148 die "Node with multiple predecessors has no phi ($i)"
265             unless $converted->{in_stack}[$i]->{opcode_n} == OP_PHI;
266 48         67 push @{$converted->{in_stack}[$i]{parameters}},
  48         213  
267             $self->_current_basic_block, $out;
268 48         427 ++$i;
269             }
270             }
271             }
272              
273 127         478 $converted->{depth} = @$stack;
274 127   66     530 $converted->{block} ||= Language::P::Intermediate::BasicBlock
275             ->new_from_label( $to->start_label );
276 127         280 push @{$op->{parameters}}, $converted->{block};
  127         352  
277 127         170 push @{$self->_queue}, $to;
  127         357  
278              
279 127         663 return $out_names;
280             }
281              
282             sub _emit_out_stack {
283 162     162   214 my( $self ) = @_;
284 162         410 my $stack = $self->_stack;
285 162 100       899 return unless @$stack;
286              
287             # add named targets for all trees in stack, emit
288             # them and replace stack with the targets
289 64         78 my( @out_names, @out_stack );
290 64         183 my $i = @$stack - $self->_converting->{created};
291              
292             # copy inherited stack elements and all created GET opcodes add a
293             # SET in the block and a GET in the out stack for all other
294             # created ops
295 64         392 @out_stack = @{$stack}[0 .. $i - 1];
  64         166  
296 64         167 @out_names = map $_->{parameters}[0], @out_stack;
297 64         208 for( my $j = $i; $i < @$stack; ++$i, ++$j ) {
298 48         90 my $op = $stack->[$i];
299 48 100       135 if( $op->{opcode_n} == OP_GET ) {
300 18         54 $out_names[$j] = $op->{parameters}[0];
301 18         65 $out_stack[$i] = $op;
302             } else {
303 30         69 $out_names[$j] = _local_name( $self );
304 30         94 $out_stack[$i] = opcode_n( OP_GET, $out_names[$j] );
305 30         414 _add_bytecode $self, opcode_n( OP_SET, $out_names[$j], $op );
306             }
307             }
308 64         394 @$stack = @out_stack;
309              
310 64         223 return @out_names;
311             }
312              
313             sub _generic {
314 244     244   331 my( $self, $op ) = @_;
315 244         610 my $attrs = $OP_ATTRIBUTES{$op->{opcode_n}};
316 244 100       808 my @in = $attrs->{in_args} ? _get_stack( $self, $attrs->{in_args} ) : ();
317 244         280 my $new_op;
318              
319 244 100       642 if( $op->{attributes} ) {
    100          
320 110         182 $new_op = opcode_nm( $op->{opcode_n}, %{$op->{attributes}} );
  110         478  
321 110 100       1606 $new_op->{parameters} = \@in if @in;
322             } elsif( $op->{parameters} ) {
323 43 50       115 die "Can't handle fixed and dynamic parameters" if @in;
324 43         104 $new_op = opcode_n( $op->{opcode_n}, @{$op->{parameters}} );
  43         143  
325             } else {
326 91         272 $new_op = opcode_n( $op->{opcode_n}, @in );
327             }
328              
329 244 100       2159 if( !$attrs->{out_args} ) {
    50          
330 49         122 _emit_out_stack( $self );
331 49         151 _add_bytecode $self, $new_op;
332             } elsif( $attrs->{out_args} == 1 ) {
333 195         230 push @{$self->_stack}, $new_op;
  195         532  
334 195         1025 _created( $self, 1 );
335             } else {
336 0         0 die "Unhandled out_args value: ", $attrs->{out_args};
337             }
338             }
339              
340             sub _const_sub {
341 2     2   3 my( $self, $op ) = @_;
342 2         8 my $new_seg = $self->_converted_segments->{$op->{parameters}[0]};
343 2         16 my $new_op = opcode_n( OP_CONSTANT_SUB(), $new_seg );
344              
345 2         23 push @{$self->_stack}, $new_op;
  2         6  
346 2         11 _created( $self, 1 );
347             }
348              
349             sub _pop {
350 52     52   121 my( $self, $op ) = @_;
351              
352 52 50       75 die 'Empty stack in pop' unless @{$self->_stack} >= 1;
  52         141  
353 52         313 my $top = pop @{$self->_stack};
  52         157  
354 52 100 66     509 _add_bytecode $self, $top if $top->{opcode_n} != OP_PHI
355             && $top->{opcode_n} != OP_GET;
356 52         346 _emit_out_stack( $self );
357 52         145 _created( $self, -1 );
358             }
359              
360             sub _dup {
361 18     18   28 my( $self, $op ) = @_;
362              
363 18 50       31 die 'Empty stack in dup' unless @{$self->_stack} >= 1;
  18         52  
364 18         143 my( $v ) = _get_stack( $self, 1, 1 );
365 18         28 push @{$self->_stack}, $v, $v;
  18         43  
366 18         100 _created( $self, 2 );
367             }
368              
369             sub _swap {
370 24     24   55 my( $self, $op ) = @_;
371 24         79 my $stack = $self->_stack;
372 24         114 my $t = $stack->[-1];
373              
374 24 50       33 die 'Empty stack in swap' unless @{$self->_stack} >= 2;
  24         68  
375 24         172 $stack->[-1] = $stack->[-2];
376 24         64 $stack->[-2] = $t;
377             }
378              
379             sub _make_list {
380 20     20   36 my( $self, $op ) = @_;
381              
382 20         28 push @{$self->_stack},
  20         70  
383             opcode_n( OP_MAKE_LIST, _get_stack( $self, $op->{attributes}{count} ) );
384 20         261 _created( $self, 1 );
385             }
386              
387             sub _cond_jump {
388 30     30   75 my( $self, $op ) = @_;
389 30         89 my $attrs = $OP_ATTRIBUTES{$op->{opcode_n}};
390 30         109 my @in = _get_stack( $self, $attrs->{in_args} );
391 30         123 my $new_cond = opcode_n( $op->{opcode_n}, @in );
392 30         424 my $new_jump = opcode_n( OP_JUMP );
393              
394 30         342 my @out_names;
395 30         117 _jump_to( $self, $new_cond, $op->{attributes}{true}, \@out_names );
396 30         78 _add_bytecode $self, $new_cond;
397 30         297 _jump_to( $self,$new_jump, $op->{attributes}{false}, \@out_names );
398 30         81 _add_bytecode $self, $new_jump;
399             }
400              
401             sub _jump {
402 67     67   102 my( $self, $op ) = @_;
403 67         254 my $new_jump = opcode_nm( $op->{opcode_n} );
404              
405 67         987 _jump_to( $self, $new_jump, $op->{attributes}{to}, [] );
406 67         161 _add_bytecode $self, $new_jump;
407             }
408              
409             sub _created {
410 426     426   626 my( $self, $count ) = @_;
411              
412 426         1050 $self->_converting->{created} += $count;
413 426 100 100     2973 if( $count < 0 && $self->_converting->{created} < 0 ) {
414 38         287 $self->_converting->{created} = 0;
415             }
416             }
417              
418             1;