forked from jruby/jruby
-
Notifications
You must be signed in to change notification settings - Fork 0
/
IRScope.java
1317 lines (1090 loc) · 48.3 KB
/
IRScope.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package org.jruby.compiler.ir;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.compiler.ir.compiler_pass.CFGBuilder;
import org.jruby.compiler.ir.compiler_pass.CompilerPass;
import org.jruby.compiler.ir.compiler_pass.DominatorTreeBuilder;
import org.jruby.compiler.ir.compiler_pass.IRPrinter;
import org.jruby.compiler.ir.compiler_pass.InlineTest;
import org.jruby.compiler.ir.compiler_pass.LinearizeCFG;
import org.jruby.compiler.ir.compiler_pass.LiveVariableAnalysis;
import org.jruby.compiler.ir.compiler_pass.opts.DeadCodeElimination;
import org.jruby.compiler.ir.compiler_pass.opts.LocalOptimizationPass;
import org.jruby.compiler.ir.dataflow.DataFlowProblem;
import org.jruby.compiler.ir.instructions.CallBase;
import org.jruby.compiler.ir.instructions.CopyInstr;
import org.jruby.compiler.ir.instructions.Instr;
import org.jruby.compiler.ir.instructions.ReceiveClosureInstr;
import org.jruby.compiler.ir.instructions.ReceiveSelfInstr;
import org.jruby.compiler.ir.instructions.ResultInstr;
import org.jruby.compiler.ir.instructions.Specializeable;
import org.jruby.compiler.ir.instructions.ThreadPollInstr;
import org.jruby.compiler.ir.instructions.ZSuperInstr;
import org.jruby.compiler.ir.operands.InstrResult;
import org.jruby.compiler.ir.operands.Label;
import org.jruby.compiler.ir.operands.LocalVariable;
import org.jruby.compiler.ir.operands.Operand;
import org.jruby.compiler.ir.operands.Self;
import org.jruby.compiler.ir.operands.TemporaryVariable;
import org.jruby.compiler.ir.operands.Variable;
import org.jruby.compiler.ir.operands.WrappedIRClosure;
import org.jruby.compiler.ir.representations.BasicBlock;
import org.jruby.compiler.ir.representations.CFG;
import org.jruby.compiler.ir.representations.CFGInliner;
import org.jruby.compiler.ir.representations.CFGLinearizer;
import org.jruby.parser.StaticScope;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
/**
* Right now, this class abstracts the following execution scopes:
* Method, Closure, Module, Class, MetaClass
* Top-level Script, and Eval Script
*
* In the compiler-land, IR versions of these scopes encapsulate only as much
* information as is required to convert Ruby code into equivalent Java code.
*
* But, in the non-compiler land, there will be a corresponding java object for
* some of these scopes which encapsulates the runtime semantics and data needed
* for implementing them. In the case of Module, Class, MetaClass, and Method,
* they also happen to be instances of the corresponding Ruby classes -- so,
* in addition to providing code that help with this specific ruby implementation,
* they also have code that let them behave as ruby instances of their corresponding
* classes.
*
* Examples:
* - the runtime class object might have refs. to the runtime method objects.
* - the runtime method object might have a slot for a heap frame (for when it
* has closures that need access to the method's local variables), it might
* have version information, it might have references to other methods that
* were optimized with the current version number, etc.
* - the runtime closure object will have a slot for a heap frame (for when it
* has closures within) and might get reified as a method in the java land
* (but inaccessible in ruby land). So, passing closures in Java land might
* be equivalent to passing around the method handles.
*
* and so on ...
*/
public abstract class IRScope {
private static final Logger LOG = LoggerFactory.getLogger("IRScope");
private static Integer globalScopeCount = 0;
/** Unique global scope id */
private int scopeId;
/** Name */
private String name;
/** File within which this scope has been defined */
private final String fileName;
/** Starting line for this scope's definition */
private final int lineNumber;
/** Lexical parent scope */
private IRScope lexicalParent;
/** Parser static-scope that this IR scope corresponds to */
private StaticScope staticScope;
/** Live version of module within whose context this method executes */
private RubyModule containerModule;
/** List of IR instructions for this method */
private List<Instr> instrList;
/** Control flow graph representation of this method's instructions */
private CFG cfg;
/** List of (nested) closures in this scope */
private List<IRClosure> nestedClosures;
/** Local variables defined in this scope */
private Set<Variable> definedLocalVars;
/** Local variables used in this scope */
private Set<Variable> usedLocalVars;
/** Is %block implicit block arg unused? */
private boolean hasUnusedImplicitBlockArg;
/** Map of name -> dataflow problem */
private Map<String, DataFlowProblem> dfProbs;
private Instr[] linearizedInstrArray;
private List<BasicBlock> linearizedBBList;
protected int temporaryVariableIndex;
/** Keeps track of types of prefix indexes for variables and labels */
private Map<String, Integer> nextVarIndex;
// Index values to guarantee we don't assign same internal index twice
private int nextClosureIndex;
protected static class LocalVariableAllocator {
public int nextSlot;
public Map<String, LocalVariable> varMap;
public LocalVariableAllocator() {
varMap = new HashMap<String, LocalVariable>();
nextSlot = 0;
}
public final LocalVariable getVariable(String name) {
return varMap.get(name);
}
public final void putVariable(String name, LocalVariable var) {
varMap.put(name, var);
nextSlot++;
}
}
LocalVariableAllocator localVars;
LocalVariableAllocator evalScopeVars;
/* *****************************************************************************************************
* Does this execution scope (applicable only to methods) receive a block and use it in such a way that
* all of the caller's local variables need to be materialized into a heap binding?
* Ex:
* def foo(&b)
* eval 'puts a', b
* end
*
* def bar
* a = 1
* foo {} # prints out '1'
* end
*
* Here, 'foo' can access all of bar's variables because it captures the caller's closure.
*
* There are 2 scenarios when this can happen (even this is conservative -- but, good enough for now)
* 1. This method receives an explicit block argument (in this case, the block can be stored, passed around,
* eval'ed against, called, etc.).
* CAVEAT: This is conservative ... it may not actually be stored & passed around, evaled, called, ...
* 2. This method has a 'super' call (ZSuper AST node -- ZSuperInstr IR instruction)
* In this case, the parent (in the inheritance hierarchy) can access the block and store it, etc. So, in reality,
* rather than assume that the parent will always do this, we can query the parent, if we can precisely identify
* the parent method (which in the face of Ruby's dynamic hierarchy, we cannot). So, be pessimistic.
*
* This logic was extracted from an email thread on the JRuby mailing list -- Yehuda Katz & Charles Nutter
* contributed this analysis above.
* ********************************************************************************************************/
private boolean canCaptureCallersBinding;
/* ****************************************************************************
* Does this scope define code, i.e. does it (or anybody in the downward call chain)
* do class_eval, module_eval? In the absence of any other information, we default
* to yes -- which basically leads to pessimistic but safe optimizations. But, for
* library and internal methods, this might be false.
* **************************************************************************** */
private boolean canModifyCode;
/* ****************************************************************************
* Does this scope require a binding to be materialized?
* Yes if any of the following holds true:
* - calls 'Proc.new'
* - calls 'eval'
* - calls 'call' (could be a call on a stored block which could be local!)
* - calls 'send' and we cannot resolve the message (method name) that is being sent!
* - calls methods that can access the caller's binding
* - calls a method which we cannot resolve now!
* - has a call whose closure requires a binding
* **************************************************************************** */
private boolean bindingHasEscaped;
/** Does this scope call any eval */
private boolean usesEval;
/** Does this scope call any zsuper */
private boolean usesZSuper;
/** Does this scope have loops? */
private boolean hasLoops;
/** # of thread poll instrs added to this scope */
private int threadPollInstrsCount;
/** Should we re-run compiler passes -- yes after we've inlined, for example */
private boolean relinearizeCFG;
private IRManager manager;
// Used by cloning code
protected IRScope(IRScope s, IRScope lexicalParent) {
this.lexicalParent = lexicalParent;
this.manager = s.manager;
this.fileName = s.fileName;
this.lineNumber = s.lineNumber;
this.staticScope = s.staticScope;
this.threadPollInstrsCount = s.threadPollInstrsCount;
this.nextClosureIndex = s.nextClosureIndex;
this.temporaryVariableIndex = s.temporaryVariableIndex;
this.hasLoops = s.hasLoops;
this.hasUnusedImplicitBlockArg = s.hasUnusedImplicitBlockArg;
this.instrList = null;
this.nestedClosures = new ArrayList<IRClosure>();
this.dfProbs = new HashMap<String, DataFlowProblem>();
this.nextVarIndex = new HashMap<String, Integer>(); // SSS FIXME: clone!
this.cfg = null;
this.linearizedInstrArray = null;
this.linearizedBBList = null;
this.canModifyCode = s.canModifyCode;
this.canCaptureCallersBinding = s.canCaptureCallersBinding;
this.bindingHasEscaped = s.bindingHasEscaped;
this.usesEval = s.usesEval;
this.usesZSuper = s.usesZSuper;
this.localVars = new LocalVariableAllocator(); // SSS FIXME: clone!
this.localVars.nextSlot = s.localVars.nextSlot;
this.relinearizeCFG = false;
}
public IRScope(IRManager manager, IRScope lexicalParent, String name,
String fileName, int lineNumber, StaticScope staticScope) {
this.manager = manager;
this.lexicalParent = lexicalParent;
this.name = name;
this.fileName = fileName;
this.lineNumber = lineNumber;
this.staticScope = staticScope;
this.threadPollInstrsCount = 0;
this.nextClosureIndex = 0;
this.temporaryVariableIndex = -1;
this.instrList = new ArrayList<Instr>();
this.nestedClosures = new ArrayList<IRClosure>();
this.dfProbs = new HashMap<String, DataFlowProblem>();
this.nextVarIndex = new HashMap<String, Integer>();
this.cfg = null;
this.linearizedInstrArray = null;
this.linearizedBBList = null;
this.hasLoops = false;
this.hasUnusedImplicitBlockArg = false;
// These flags are true by default!
this.canModifyCode = true;
this.canCaptureCallersBinding = true;
this.bindingHasEscaped = true;
this.usesEval = true;
this.usesZSuper = true;
this.localVars = new LocalVariableAllocator();
synchronized(globalScopeCount) { this.scopeId = globalScopeCount++; }
this.relinearizeCFG = false;
}
@Override
public int hashCode() {
return scopeId;
}
public void addClosure(IRClosure c) {
nestedClosures.add(c);
}
public Instr getLastInstr() {
return instrList.get(instrList.size() - 1);
}
public void addInstr(Instr i) {
if (i instanceof ThreadPollInstr) threadPollInstrsCount++;
instrList.add(i);
}
public LocalVariable getNewFlipStateVariable() {
return getLocalVariable("%flip_" + allocateNextPrefixedName("%flip"), 0);
}
public void initFlipStateVariable(Variable v, Operand initState) {
// Add it to the beginning
instrList.add(0, new CopyInstr(v, initState));
}
public boolean isForLoopBody() {
return false;
}
public Label getNewLabel(String prefix) {
return new Label(prefix + "_" + allocateNextPrefixedName(prefix));
}
public Label getNewLabel() {
return getNewLabel("LBL");
}
public List<IRClosure> getClosures() {
return nestedClosures;
}
public IRManager getManager() {
return manager;
}
/**
* Returns the lexical scope that contains this scope definition
*/
public IRScope getLexicalParent() {
return lexicalParent;
}
public StaticScope getStaticScope() {
return staticScope;
}
public IRMethod getNearestMethod() {
IRScope current = this;
while (current != null && !(current instanceof IRMethod)) {
current = current.getLexicalParent();
}
return (IRMethod) current;
}
public IRScope getNearestFlipVariableScope() {
IRScope current = this;
while (current != null && !current.isFlipScope()) {
current = current.getLexicalParent();
}
return current;
}
public IRScope getNearestTopLocalVariableScope() {
IRScope current = this;
while (current != null && !current.isTopLocalVariableScope()) {
current = current.getLexicalParent();
}
return current;
}
/**
* Returns the nearest scope which we can extract a live module from. If
* this returns null (like for evals), then it means it cannot be statically
* determined.
*/
public IRScope getNearestModuleReferencingScope() {
IRScope current = this;
while (!(current instanceof IRModuleBody)) {
// When eval'ing, we dont have a lexical view of what module we are nested in
// because binding_eval, class_eval, module_eval, instance_eval can switch
// around the lexical scope for evaluation to be something else.
if (current == null || current instanceof IREvalScript) return null;
current = current.getLexicalParent();
}
return current;
}
public String getName() {
return name;
}
public void setName(String name) { // This is for IRClosure ;(
this.name = name;
}
public String getFileName() {
return fileName;
}
public int getLineNumber() {
return lineNumber;
}
/**
* Returns the top level scope
*/
public IRScope getTopLevelScope() {
IRScope current = this;
for (; current != null && !current.isScriptScope(); current = current.getLexicalParent()) {}
return current;
}
public boolean isNestedInClosure(IRClosure closure) {
for (IRScope s = this; s != null && !s.isTopLocalVariableScope(); s = s.getLexicalParent()) {
if (s == closure) return true;
}
return false;
}
public void setHasLoopsFlag(boolean f) {
hasLoops = true;
}
public boolean hasLoops() {
return hasLoops;
}
public void setCodeModificationFlag(boolean f) {
canModifyCode = f;
}
public boolean modifiesCode() {
return canModifyCode;
}
public boolean bindingHasEscaped() {
return bindingHasEscaped;
}
public boolean usesEval() {
return usesEval;
}
public boolean usesZSuper() {
return usesZSuper;
}
public boolean canCaptureCallersBinding() {
return canCaptureCallersBinding;
}
public CFG buildCFG() {
cfg = new CFG(this);
cfg.build(instrList);
// Clear out instruction list after CFG has been built.
this.instrList = null;
return cfg;
}
protected void setCFG(CFG cfg) {
this.cfg = cfg;
}
public CFG getCFG() {
return cfg;
}
public void runCompilerPassOnNestedScopes(CompilerPass p) {
// Do nothing...subclasses should override
}
public void runCompilerPass(CompilerPass p) {
boolean isPreOrder = p.isPreOrder();
if (isPreOrder) p.run(this);
runCompilerPassOnNestedScopes(p);
if (!isPreOrder) p.run(this);
}
private void allocVar(Operand oldVar, IRScope s, List<TemporaryVariable> freeVarsList, Map<Operand, Operand> newVarMap) {
// If we dont have a var mapping, get a new var -- try the free list first
// and if none available, allocate a fresh one
if (newVarMap.get(oldVar) == null) {
newVarMap.put(oldVar, freeVarsList.isEmpty() ? getNewTemporaryVariable() : freeVarsList.remove(0));
}
}
private void freeVar(TemporaryVariable newVar, List<TemporaryVariable> freeVarsList) {
// Put the new var onto the free list (but only if it is not already there).
if (!freeVarsList.contains(newVar)) freeVarsList.add(0, newVar);
}
private void buildExprTreesAndMinimizeTmpVars(List<Instr> instrs) {
Map<Instr, BasicBlock> instrToBBMap = new HashMap<Instr, BasicBlock>();
for (BasicBlock b: cfg.getBasicBlocks()) {
for (Instr i: b.getInstrs()) instrToBBMap.put(i, b);
}
// Compute last use of temporary variables -- this effectively is the
// end of the live range that started with its first definition. This implicitly
// encodes the live range of the temporary variable.
//
// These live ranges are valid because these instructions are generated from an AST
// and they haven't been rearranged yet. In addition, since temporaries are used to
// communicate results from lower levels to higher levels in the tree, a temporary
// defined outside a loop cannot be used within the loop. So, the first definition
// of a temporary and the last use of the temporary delimit its live range.
//
// %current-scope and %current-module are the two "temporary" variables that violate
// this contract right now since they are used everywhere in the scope.
// So, in the presence of loops, we:
// - either assume that the live range of these variables extends to
// the end of the outermost loop in which they are used
// - or we do not rename %current-scope and %current-module in such scopes.
//
// SSS FIXME: For now, we just extend the live range of these vars all the
// way to the end of the scope!
//
// NOTE: It is sufficient to just track last use for renaming purposes.
// At the first definition, we allocate a variable which then starts the live range
Map<TemporaryVariable, List<Instr>> tmpVarUses = new HashMap<TemporaryVariable, List<Instr>>();
Map<TemporaryVariable, List<Instr>> tmpVarDefs = new HashMap<TemporaryVariable, List<Instr>>();
Map<TemporaryVariable, Integer> lastVarUseOrDef = new HashMap<TemporaryVariable, Integer>();
int iCount = -1;
for (Instr i: instrs) {
iCount++;
if (i instanceof ResultInstr) {
Variable v = ((ResultInstr)i).getResult();
if (v instanceof TemporaryVariable) {
TemporaryVariable tv = (TemporaryVariable)v;
List<Instr> defs = tmpVarDefs.get(tv);
if (defs == null) {
defs = new ArrayList<Instr>();
tmpVarDefs.put(tv, defs);
}
defs.add(i);
lastVarUseOrDef.put(tv, iCount);
}
}
// compute last use
for (Variable v: i.getUsedVariables()) {
if (v instanceof TemporaryVariable) {
TemporaryVariable tv = (TemporaryVariable)v;
List<Instr> uses = tmpVarUses.get(tv);
if (uses == null) {
uses = new ArrayList<Instr>();
tmpVarUses.put(tv, uses);
}
uses.add(i);
lastVarUseOrDef.put(tv, iCount);
}
}
}
// If the scope has loops, extend live range of %current-module and %current-scope
// to end of scope (see note earlier).
if (hasLoops()) {
lastVarUseOrDef.put((TemporaryVariable)getCurrentScopeVariable(), iCount);
lastVarUseOrDef.put((TemporaryVariable)getCurrentModuleVariable(), iCount);
}
// Pass 2: Reallocate temporaries based on last uses to minimize # of unique vars.
Map<Operand, Operand> newVarMap = new HashMap<Operand, Operand>();
List<TemporaryVariable> freeVarsList = new ArrayList<TemporaryVariable>();
iCount = -1;
resetTemporaryVariables();
boolean buildExprTrees = RubyInstanceConfig.IR_EXPR_TREE;
ListIterator<Instr> instrsIterator = instrs.listIterator();
while (instrsIterator.hasNext()) {
Instr i = instrsIterator.next();
// if (buildExprTrees) i = i.clone();
iCount++;
// Assign new vars
Variable result = null;
if (i instanceof ResultInstr) {
result = ((ResultInstr)i).getResult();
if (result instanceof TemporaryVariable) {
boolean removed = false;
if (buildExprTrees) {
List<Instr> uses = tmpVarUses.get((TemporaryVariable)result);
List<Instr> defs = tmpVarDefs.get((TemporaryVariable)result);
if ((uses != null) && (uses.size() == 1) && (defs != null) && (defs.size() == 1)) {
Instr use = uses.get(0);
Instr def = defs.get(0);
// Constraints:
// 1. We cannot create an expression trees across basic-block boundaries.
// 2. The interpreter currently cannot distinguish breaks originating from the break itself
// versus orginating in a call that has been collapsed into the break. The two scenarios
// have to be handled differently. So, for now, breaks cannot be part of expression trees.
// 3. (Hack!) Dont collapse define_* instructions since they need access to "block" and Operand.retrieve
// dont get block passed into them. So, a hack till we upgrade that API to pass block into them
Operation useOp = use.getOperation();
Operation defOp = def.getOperation();
if (instrToBBMap.get(use) == instrToBBMap.get(def) && (useOp != Operation.BREAK) && !defOp.modifiesCode()) {
i.markDead();
((ResultInstr)i).updateResult(null);
instrsIterator.remove();
newVarMap.put(result, new InstrResult(this, i));
removed = true;
}
}
}
if (!removed) allocVar(result, this, freeVarsList, newVarMap);
}
}
for (Variable v: i.getUsedVariables()) {
if (v instanceof TemporaryVariable) {
// Dont rename vars that will get replaced with a chained instr.
if (!(newVarMap.get(v) instanceof InstrResult)) allocVar(v, this, freeVarsList, newVarMap);
}
}
// Free dead vars
if ((result instanceof TemporaryVariable) && lastVarUseOrDef.get((TemporaryVariable)result) == iCount) {
freeVar((TemporaryVariable)newVarMap.get(result), freeVarsList);
}
for (Variable v: i.getUsedVariables()) {
if ((v instanceof TemporaryVariable) && !(newVarMap.get(v) instanceof InstrResult)) {
TemporaryVariable tv = (TemporaryVariable)v;
if (lastVarUseOrDef.get(tv) == iCount) freeVar((TemporaryVariable)newVarMap.get(tv), freeVarsList);
}
}
// Rename -- everything if this is still alive, only uses if this has been targeted for chaining
if (i.isDead()) i.simplifyOperands(newVarMap, true);
else i.renameVars(newVarMap);
}
//System.out.println("# tmp vars: " + getTemporaryVariableSize());
}
private Instr[] prepareInstructionsForInterpretation() {
if (relinearizeCFG) {
linearizedBBList = null;
relinearizeCFG = false;
}
if (linearizedInstrArray != null) return linearizedInstrArray; // Already prepared
try {
buildLinearization(); // FIXME: compiler passes should have done this
depends(linearization());
} catch (RuntimeException e) {
LOG.error("Error linearizing cfg: ", e);
CFG c = cfg();
LOG.error("\nGraph:\n" + c.toStringGraph());
LOG.error("\nInstructions:\n" + c.toStringInstrs());
throw e;
}
List<Instr> newInstrs = new ArrayList<Instr>();
for (BasicBlock b : linearizedBBList) {
for (Instr i : b.getInstrs()) {
if (!(i instanceof ReceiveSelfInstr)) newInstrs.add(i);
}
}
// Instruction chaining + tmp var minimization
buildExprTreesAndMinimizeTmpVars(newInstrs);
// Set up IPCs
HashMap<Label, Integer> labelIPCMap = new HashMap<Label, Integer>();
List<Label> labelsToFixup = new ArrayList<Label>();
int ipc = 0;
for (BasicBlock b : linearizedBBList) {
labelIPCMap.put(b.getLabel(), ipc);
labelsToFixup.add(b.getLabel());
ListIterator<Instr> bbInstrs = b.getInstrs().listIterator();
while (bbInstrs.hasNext()) {
Instr instr = bbInstrs.next();
if (instr.isDead()) {
bbInstrs.remove();
} else {
if (instr instanceof Specializeable) {
instr = ((Specializeable) instr).specializeForInterpretation();
bbInstrs.set(instr);
}
if (!(instr instanceof ReceiveSelfInstr)) {
newInstrs.add(instr);
ipc++;
}
}
}
}
// Fix up labels
for (Label l : labelsToFixup) {
l.setTargetPC(labelIPCMap.get(l));
}
// Exit BB ipc
cfg().getExitBB().getLabel().setTargetPC(ipc + 1);
linearizedInstrArray = newInstrs.toArray(new Instr[newInstrs.size()]);
return linearizedInstrArray;
}
private void printPass(String message) {
if (RubyInstanceConfig.IR_COMPILER_DEBUG) {
LOG.info("################## " + message + "##################");
runCompilerPass(new IRPrinter());
}
}
private void runCompilerPasses() {
// forcibly clear out the shared eval-scope variable allocator each time this method executes
initEvalScopeVariableAllocator(true);
// SSS FIXME: We should configure different optimization levels
// and run different kinds of analysis depending on time budget. Accordingly, we need to set
// IR levels/states (basic, optimized, etc.) and the
// ENEBO: If we use a MT optimization mechanism we cannot mutate CFG
// while another thread is using it. This may need to happen on a clone()
// and we may need to update the method to return the new method. Also,
// if this scope is held in multiple locations how do we update all references?
printPass("Before local optimization pass");
runCompilerPass(new LocalOptimizationPass());
printPass("After local optimization pass");
runCompilerPass(new CFGBuilder());
if (!RubyInstanceConfig.IR_TEST_INLINER.equals("none")) {
if (RubyInstanceConfig.IR_COMPILER_DEBUG) {
LOG.info("Asked to inline " + RubyInstanceConfig.IR_TEST_INLINER);
}
runCompilerPass(new InlineTest(RubyInstanceConfig.IR_TEST_INLINER));
runCompilerPass(new LocalOptimizationPass());
printPass("After inline");
}
// Do not run dead-code-elimination on eval-scripts because they might
// update their enclosing environments.
if (!(this instanceof IREvalScript)) {
if (RubyInstanceConfig.IR_LIVE_VARIABLE) runCompilerPass(new LiveVariableAnalysis());
if (RubyInstanceConfig.IR_DEAD_CODE) runCompilerPass(new DeadCodeElimination());
if (RubyInstanceConfig.IR_DEAD_CODE) printPass("After DCE ");
}
runCompilerPass(new LinearizeCFG());
printPass("After CFG Linearize");
}
/** Run any necessary passes to get the IR ready for interpretation */
public synchronized Instr[] prepareForInterpretation() {
// If the instruction array exists, someone has taken care of setting up the CFG and preparing the instructions
if (relinearizeCFG) {
linearizedBBList = null;
relinearizeCFG = false;
}
if (linearizedInstrArray != null) return linearizedInstrArray;
// Build CFG and run compiler passes, if necessary
if (getCFG() == null) runCompilerPasses();
// Linearize CFG, etc.
return prepareInstructionsForInterpretation();
}
/* SSS FIXME: Do we need to synchronize on this? Cache this info in a scope field? */
/** Run any necessary passes to get the IR ready for compilation */
public Tuple<Instr[], Map<Integer,Label[]>> prepareForCompilation() {
// Build CFG and run compiler passes, if necessary
if (getCFG() == null) runCompilerPasses();
try {
buildLinearization(); // FIXME: compiler passes should have done this
depends(linearization());
} catch (RuntimeException e) {
LOG.error("Error linearizing cfg: ", e);
CFG c = cfg();
LOG.error("\nGraph:\n" + c.toStringGraph());
LOG.error("\nInstructions:\n" + c.toStringInstrs());
throw e;
}
// Set up IPCs
// FIXME: Would be nice to collapse duplicate labels; for now, using Label[]
HashMap<Integer, Label[]> ipcLabelMap = new HashMap<Integer, Label[]>();
List<Instr> newInstrs = new ArrayList<Instr>();
int ipc = 0;
for (BasicBlock b : linearizedBBList) {
ipcLabelMap.put(ipc, catLabels(ipcLabelMap.get(ipc), b.getLabel()));
for (Instr i : b.getInstrs()) {
if (!(i instanceof ReceiveSelfInstr)) {
newInstrs.add(i);
ipc++;
}
}
}
return new Tuple<Instr[], Map<Integer,Label[]>>(newInstrs.toArray(new Instr[newInstrs.size()]), ipcLabelMap);
}
private static Label[] catLabels(Label[] labels, Label cat) {
if (labels == null) return new Label[] {cat};
Label[] newLabels = new Label[labels.length + 1];
System.arraycopy(labels, 0, newLabels, 0, labels.length);
newLabels[labels.length] = cat;
return newLabels;
}
private boolean computeScopeFlags(boolean receivesClosureArg, List<Instr> instrs) {
for (Instr i: instrs) {
if (i instanceof ReceiveClosureInstr)
receivesClosureArg = true;
if (i instanceof ZSuperInstr) {
canCaptureCallersBinding = true;
usesZSuper = true;
}
if (i instanceof CallBase) {
CallBase call = (CallBase) i;
Operand o = ((CallBase) i).getClosureArg(null);
if (o != null) {
if (o instanceof WrappedIRClosure) {
IRClosure cl = ((WrappedIRClosure)o).getClosure();
cl.computeScopeFlags();
if (cl.usesZSuper()) usesZSuper = true;
} else {
usesZSuper = true;
}
}
if (call.canBeEval()) {
usesEval = true;
// If this method receives a closure arg, and this call is an eval that has more than 1 argument,
// it could be using the closure as a binding -- which means it could be using pretty much any
// variable from the caller's binding!
if (receivesClosureArg && (call.getCallArgs().length > 1))
canCaptureCallersBinding = true;
}
}
}
return receivesClosureArg;
}
// SSS FIXME: This method does nothing a whole lot useful right now.
// hasEscapedBinding is the crucial flag and it continues to be unconditionally true.
//
// This can help use eliminate writes to %block that are not used since this is
// a special local-variable, not programmer-defined local-variable
public void computeScopeFlags() {
// init
canModifyCode = true;
canCaptureCallersBinding = false;
usesZSuper = false;
usesEval = false;
// recompute flags -- we could be calling this method different times
// definitely once after ir generation and local optimizations propagates constants locally
// but potentially at a later time after doing ssa generation and constant propagation
if (cfg == null) {
computeScopeFlags(false, getInstrs());
} else {
boolean receivesClosureArg = false;
for (BasicBlock b: cfg.getBasicBlocks()) {
receivesClosureArg = computeScopeFlags(receivesClosureArg, b.getInstrs());
}
}
}
public abstract String getScopeName();
@Override
public String toString() {
return getScopeName() + " " + getName() + "[" + getFileName() + ":" + getLineNumber() + "]";
}
public String toStringInstrs() {
StringBuilder b = new StringBuilder();
int i = 0;
for (Instr instr : instrList) {
if (i > 0) b.append("\n");
b.append(" ").append(i).append('\t').append(instr);
i++;
}
if (!nestedClosures.isEmpty()) {
b.append("\n\n------ Closures encountered in this scope ------\n");
for (IRClosure c: nestedClosures)
b.append(c.toStringBody());
b.append("------------------------------------------------\n");
}
return b.toString();
}
public String toStringVariables() {
Map<Variable, Integer> ends = new HashMap<Variable, Integer>();
Map<Variable, Integer> starts = new HashMap<Variable, Integer>();
SortedSet<Variable> variables = new TreeSet<Variable>();
for (int i = instrList.size() - 1; i >= 0; i--) {
Instr instr = instrList.get(i);
if (instr instanceof ResultInstr) {
Variable var = ((ResultInstr) instr).getResult();
variables.add(var);
starts.put(var, i);
}
for (Operand operand : instr.getOperands()) {
if (operand != null && operand instanceof Variable && ends.get((Variable)operand) == null) {
ends.put((Variable)operand, i);
variables.add((Variable)operand);
}
}
}
StringBuilder sb = new StringBuilder();
int i = 0;
for (Variable var : variables) {
Integer end = ends.get(var);
if (end != null) { // Variable is actually used somewhere and not dead
if (i > 0) sb.append("\n");
i++;
sb.append(" ").append(var).append(": ").append(starts.get(var)).append("-").append(end);
}
}
return sb.toString();
}
/** ---------------------------------------
* SSS FIXME: What is this method for?
@Interp
public void calculateParameterCounts() {
for (int i = instrList.size() - 1; i >= 0; i--) {
Instr instr = instrList.get(i);
}
}
------------------------------------------ **/
public LocalVariable getSelf() {
return Self.SELF;
}
public Variable getCurrentModuleVariable() {
return getNewTemporaryVariable(Variable.CURRENT_MODULE);
}
public Variable getCurrentScopeVariable() {
return getNewTemporaryVariable(Variable.CURRENT_SCOPE);
}
public abstract LocalVariable getImplicitBlockArg();
public void markUnusedImplicitBlockArg() {
hasUnusedImplicitBlockArg = true;
}
public LocalVariable findExistingLocalVariable(String name, int depth) {
return localVars.getVariable(name);
}
/**
* Find or create a local variable. By default, scopes are assumed to
* only check current depth. Blocks/Closures override this because they
* have special nesting rules.
*/
public LocalVariable getLocalVariable(String name, int scopeDepth) {
LocalVariable lvar = findExistingLocalVariable(name, scopeDepth);
if (lvar == null) {
lvar = new LocalVariable(name, scopeDepth, localVars.nextSlot);
localVars.putVariable(name, lvar);
}
return lvar;
}
public LocalVariable getNewLocalVariable(String name, int depth) {
throw new RuntimeException("getNewLocalVariable should be called for: " + this.getClass().getName());
}
protected void initEvalScopeVariableAllocator(boolean reset) {
if (reset || evalScopeVars == null) evalScopeVars = new LocalVariableAllocator();
}