View Javadoc

1   /*
2    $Id: CompileStack.java,v 1.7 2006/06/06 14:29:36 blackdrag Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  
47  package org.codehaus.groovy.classgen;
48  
49  import java.util.HashMap;
50  import java.util.Iterator;
51  import java.util.LinkedList;
52  
53  import org.codehaus.groovy.GroovyBugError;
54  import org.codehaus.groovy.ast.ClassHelper;
55  import org.codehaus.groovy.ast.ClassNode;
56  import org.codehaus.groovy.ast.Parameter;
57  import org.codehaus.groovy.ast.VariableScope;
58  import org.objectweb.asm.Label;
59  import org.objectweb.asm.MethodVisitor;
60  import org.objectweb.asm.Opcodes;
61  
62  /***
63   * This class is a helper for AsmClassGenerator. It manages
64   * different aspects of the code of a code block like 
65   * handling labels, defining variables, and scopes. 
66   * After a MethodNode is visited clear should be called, for 
67   * initialization the method init should be used.
68   * <p> 
69   * Some Notes:
70   * <ul>
71   * <li> every push method will require a later pop call
72   * <li> method parameters may define a category 2 variable, so
73   *      don't ignore the type stored in the variable object
74   * <li> the index of the variable may not be as assumed when
75   *      the variable is a parameter of a method because the 
76   *      parameter may be used in a closure, so don't ignore
77   *      the stored variable index
78   * </ul>
79   * 
80   * 
81   * @see org.codehaus.groovy.classgen.AsmClassGenerator
82   * @author Jochen Theodorou
83   */
84  public class CompileStack implements Opcodes {
85      /***
86       * @TODO remove optimization of this.foo -> this.@foo
87       * 
88       */
89      
90      // state flag
91      private boolean clear=true;
92      // current scope
93      private VariableScope scope;
94      // current label for continue
95      private Label continueLabel;
96      // current label for break
97      private Label breakLabel;
98      // current label for finally 
99      private Label finallyLabel;
100     // available variables on stack
101     private HashMap stackVariables = new HashMap();
102     // index of the last variable on stack
103     private int currentVariableIndex = 1;
104     // index for the next variable on stack
105     private int nextVariableIndex = 1;
106     // currently temporary variables in use
107     private LinkedList temporaryVariables = new LinkedList();
108     // overall used variables for a method/constructor
109     private LinkedList usedVariables = new LinkedList();
110     // map containing named labels of parenting blocks
111     private HashMap superBlockNamedLabels = new HashMap();
112     // map containing named labels of current block
113     private HashMap currentBlockNamedLabels = new HashMap();
114     
115     private Label thisStartLabel, thisEndLabel;
116 
117     
118     private MethodVisitor mv;
119     private BytecodeHelper helper;
120     
121     // helper to handle different stack based variables    
122     private LinkedList stateStack = new LinkedList();
123     
124     // defines the first variable index useable after
125     // all parameters of a method 
126     private int localVariableOffset;
127     // this is used to store the goals for a "break foo" call
128     // in a loop where foo is a label.
129 	private HashMap namedLoopBreakLabel = new HashMap();
130 	//this is used to store the goals for a "continue foo" call
131     // in a loop where foo is a label.
132 	private HashMap namedLoopContinueLabel = new HashMap();
133     private String className;
134 	
135     private class StateStackElement {
136         VariableScope _scope;
137         Label _continueLabel;
138         Label _breakLabel;
139         Label _finallyLabel;
140         int _lastVariableIndex;
141         int _nextVariableIndex;
142         HashMap _stackVariables;
143         LinkedList _temporaryVariables = new LinkedList();
144         LinkedList _usedVariables = new LinkedList();
145         HashMap _superBlockNamedLabels;
146         HashMap _currentBlockNamedLabels;
147         
148         StateStackElement() {
149             _scope = CompileStack.this.scope;
150             _continueLabel = CompileStack.this.continueLabel;
151             _breakLabel = CompileStack.this.breakLabel;
152             _lastVariableIndex = CompileStack.this.currentVariableIndex;
153             _stackVariables = CompileStack.this.stackVariables;
154             _temporaryVariables = CompileStack.this.temporaryVariables;
155             _nextVariableIndex = nextVariableIndex;
156             _finallyLabel = finallyLabel;
157             _superBlockNamedLabels = superBlockNamedLabels;
158             _currentBlockNamedLabels = currentBlockNamedLabels;
159         }
160     }
161     
162     private void pushState() {
163         stateStack.add(new StateStackElement());
164         stackVariables = new HashMap(stackVariables);
165     }
166     
167     private void popState() {
168         if (stateStack.size()==0) {
169             throw new GroovyBugError("Tried to do a pop on the compile stack without push.");
170         }
171         StateStackElement element = (StateStackElement) stateStack.removeLast();
172         scope = element._scope;
173         continueLabel = element._continueLabel;
174         breakLabel = element._breakLabel;
175         finallyLabel = element._finallyLabel;
176         currentVariableIndex = element._lastVariableIndex;
177         stackVariables = element._stackVariables;
178         nextVariableIndex = element._nextVariableIndex;
179     }
180     
181     public Label getContinueLabel() {
182         return continueLabel;
183     }
184 
185     public Label getBreakLabel() {
186         return breakLabel;
187     }
188 
189     public void removeVar(int tempIndex) {
190         for (Iterator iter = temporaryVariables.iterator(); iter.hasNext();) {
191             Variable element = (Variable) iter.next();
192             if (element.getIndex()==tempIndex) {
193                 iter.remove();
194                 return;
195             }
196         }
197         throw new GroovyBugError("CompileStack#removeVar: tried to remove a temporary variable with a non existent index");
198     }
199 
200     private void setEndLabels(){
201         Label endLabel = new Label();
202         mv.visitLabel(endLabel);
203         for (Iterator iter = stackVariables.values().iterator(); iter.hasNext();) {
204             Variable var = (Variable) iter.next();
205             var.setEndLabel(endLabel);
206         }
207         thisEndLabel = endLabel;
208     }
209     
210     public void pop() {
211         setEndLabels();
212         popState();
213     }
214 
215     public VariableScope getScope() {
216         return scope;
217     }
218 
219     /***
220      * creates a temporary variable. 
221      * 
222      * @param var defines type and name
223      * @param store defines if the toplevel argument of the stack should be stored
224      * @return the index used for this temporary variable
225      */
226     public int defineTemporaryVariable(org.codehaus.groovy.ast.Variable var, boolean store) {
227         return defineTemporaryVariable(var.getName(), var.getType(),store);
228     }
229 
230     public Variable getVariable(String variableName ) {
231         return getVariable(variableName,true);
232     }
233     
234     public Variable getVariable(String variableName, boolean mustExist) {
235         if (variableName.equals("this")) return Variable.THIS_VARIABLE;
236         if (variableName.equals("super")) return Variable.SUPER_VARIABLE;
237         Variable v = (Variable) stackVariables.get(variableName);
238         if (v==null && mustExist) throw new GroovyBugError("tried to get a variable with the name "+variableName+" as stack variable, but a variable with this name was not created");
239         return v;
240     }
241 
242     /***
243      * creates a temporary variable. 
244      * 
245      * @param name defines type and name
246      * @param store defines if the toplevel argument of the stack should be stored
247      * @return the index used for this temporary variable
248      */
249 
250     public int defineTemporaryVariable(String name,boolean store) {
251         return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store);
252     }
253 
254     /***
255      * creates a temporary variable. 
256      * 
257      * @param name defines the name
258      * @param type defines the type
259      * @param store defines if the toplevel argument of the stack should be stored 
260      * @return the index used for this temporary variable
261      */
262     public int defineTemporaryVariable(String name, ClassNode node, boolean store) {
263         Variable answer = defineVar(name,node,false);
264         temporaryVariables.add(answer);
265         usedVariables.removeLast();
266         
267         if (store) mv.visitVarInsn(ASTORE, currentVariableIndex);
268         
269         return answer.getIndex();
270     }
271     
272     private void resetVariableIndex(boolean isStatic) {
273         if (!isStatic) {
274             currentVariableIndex=1;
275             nextVariableIndex=1;
276         } else {
277             currentVariableIndex=0;
278             nextVariableIndex=0;
279         }
280     }
281   
282     /***
283      * Clears the state of the class. This method should be called 
284      * after a MethodNode is visited. Note that a call to init will
285      * fail if clear is not called before
286      */
287     public void clear() {
288         if (stateStack.size()>1) {
289             int size = stateStack.size()-1;
290             throw new GroovyBugError("the compile stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops.");
291         }
292         clear = true;
293         // br experiment with local var table so debuggers can retrieve variable names
294         if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) {
295             if (thisEndLabel==null) setEndLabels();
296             
297             if (!scope.isInStaticContext()) {
298                 // write "this"
299                 mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0);
300             }
301            
302             for (Iterator iterator = usedVariables.iterator(); iterator.hasNext();) {
303                 Variable v = (Variable) iterator.next();
304                 String type = BytecodeHelper.getTypeDescription(v.getType());
305                 Label start = v.getStartLabel();
306                 Label end = v.getEndLabel();
307                 mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex());
308             }
309         }
310         pop();
311         stackVariables.clear();
312         usedVariables.clear();
313         scope = null;
314         mv=null;
315         resetVariableIndex(false);
316         superBlockNamedLabels.clear();
317         currentBlockNamedLabels.clear();
318         namedLoopBreakLabel.clear();
319         namedLoopContinueLabel.clear();
320         continueLabel=null;
321         breakLabel=null;
322         finallyLabel=null;
323         helper = null;
324         thisStartLabel=null;
325         thisEndLabel=null;
326     }
327     
328     /***
329      * initializes this class for a MethodNode. This method will
330      * automatically define varibales for the method parameters
331      * and will create references if needed. the created variables
332      * can be get by getVariable
333      * 
334      */
335     protected void init(VariableScope el, Parameter[] parameters, MethodVisitor mv, String className) {
336         if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before");
337         clear=false;
338         pushVariableScope(el);
339         this.mv = mv;
340         this.helper = helper = new BytecodeHelper(mv);
341         defineMethodVariables(parameters,el.isInStaticContext());
342         this.className = className;
343     }
344 
345     /***
346      * Causes the statestack to add an element and sets
347      * the given scope as new current variable scope. Creates 
348      * a element for the state stack so pop has to be called later
349      */
350     protected void pushVariableScope(VariableScope el) {
351         pushState();
352         scope = el;
353         superBlockNamedLabels = new HashMap(superBlockNamedLabels);
354         superBlockNamedLabels.putAll(currentBlockNamedLabels);
355         currentBlockNamedLabels = new HashMap();
356     }
357     
358     /***
359      * Should be called when decending into a loop that defines
360      * also a scope. Calls pushVariableScope and prepares labels 
361      * for a loop structure. Creates a element for the state stack
362      * so pop has to be called later 
363      */
364     protected void pushLoop(VariableScope el, String labelName) {
365         pushVariableScope(el);
366         initLoopLabels(labelName);
367     }
368 
369     private void initLoopLabels(String labelName) {
370         continueLabel = new Label();
371         breakLabel = new Label();
372         if (labelName!=null) {
373         	namedLoopBreakLabel.put(labelName,breakLabel);
374         	namedLoopContinueLabel.put(labelName,continueLabel);
375         }
376     }
377     
378     /***
379      * Should be called when decending into a loop that does 
380      * not define a scope. Creates a element for the state stack
381      * so pop has to be called later
382      */
383     protected void pushLoop(String labelName) {
384         pushState();
385         initLoopLabels(labelName);
386     }
387     
388     /***
389      * Used for <code>break foo</code> inside a loop to end the
390      * execution of the marked loop. This method will return the
391      * break label of the loop if there is one found for the name.
392      * If not, the current break label is returned.
393      */
394     protected Label getNamedBreakLabel(String name) {
395     	Label label = getBreakLabel();
396     	Label endLabel = (Label) namedLoopBreakLabel.get(name);
397     	if (endLabel!=null) label = endLabel;
398         return label;
399     }
400     
401     /***
402      * Used for <code>continue foo</code> inside a loop to continue
403      * the execution of the marked loop. This method will return 
404      * the break label of the loop if there is one found for the 
405      * name. If not, getLabel is used.
406      */
407     protected Label getNamedContinueLabel(String name) {
408     	Label label = getLabel(name);
409     	Label endLabel = (Label) namedLoopContinueLabel.get(name);
410     	if (endLabel!=null) label = endLabel;
411         return label;
412     }
413     
414     /***
415      * Creates a new Finally label and a element for the state stack
416      * so pop has to be called later
417      */
418     protected Label pushFinally() {
419         pushState();
420         finallyLabel = new Label();
421         return finallyLabel;
422     }
423     
424     /***
425      * Creates a new break label and a element for the state stack
426      * so pop has to be called later
427      */
428     protected Label pushSwitch(){
429         pushState();
430         breakLabel = new Label();
431         return breakLabel;
432     }
433     
434     /***
435      * because a boolean Expression may not be evaluated completly
436      * it is important to keep the registers clean
437      */
438     protected void pushBooleanExpression(){
439         pushState();
440     }
441     
442     /***
443      * returns the current finally label
444      */
445     public Label getFinallyLabel() {
446         return finallyLabel;
447     }
448     
449     private Variable defineVar(String name, ClassNode type, boolean methodParameterUsedInClosure) {
450         makeNextVariableID(type);
451         int index = currentVariableIndex;
452         if (methodParameterUsedInClosure) {
453             index = localVariableOffset++;
454         }
455         Variable answer = new Variable(index, type, name);
456         usedVariables.add(answer);
457         answer.setHolder(methodParameterUsedInClosure);
458         return answer;
459     }
460     
461     private void makeLocalVariablesOffset(Parameter[] paras,boolean isInStaticContext) {
462         resetVariableIndex(isInStaticContext);
463         
464         for (int i = 0; i < paras.length; i++) {
465             makeNextVariableID(paras[i].getType());
466         }
467         localVariableOffset = nextVariableIndex;
468         
469         resetVariableIndex(isInStaticContext);
470     }
471     
472     private void defineMethodVariables(Parameter[] paras,boolean isInStaticContext) {
473         Label startLabel  = new Label();
474         thisStartLabel = startLabel;
475         mv.visitLabel(startLabel);
476         
477         makeLocalVariablesOffset(paras,isInStaticContext);      
478         
479         boolean hasHolder = false;
480         for (int i = 0; i < paras.length; i++) {
481             String name = paras[i].getName();
482             Variable answer;
483             if (paras[i].isClosureSharedVariable()) {
484                 answer = defineVar(name, ClassHelper.getWrapper(paras[i].getType()), true);
485                 ClassNode type = paras[i].getType();
486                 helper.load(type,currentVariableIndex);
487                 helper.box(type);
488                 createReference(answer);
489                 hasHolder = true;
490             } else {
491                 answer = defineVar(name,paras[i].getType(),false);
492             }
493             answer.setStartLabel(startLabel);
494             stackVariables.put(name, answer);
495         }
496         
497         if (hasHolder) {
498             nextVariableIndex = localVariableOffset;
499         }
500     }
501 
502     private void createReference(Variable reference) {
503         mv.visitTypeInsn(NEW, "groovy/lang/Reference");
504         mv.visitInsn(DUP_X1);
505         mv.visitInsn(SWAP);
506         mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference", "<init>", "(Ljava/lang/Object;)V");
507         mv.visitVarInsn(ASTORE, reference.getIndex());
508     }
509     
510     /***
511      * Defines a new Variable using an AST variable.
512      * @param initFromStack if true the last element of the 
513      *                      stack will be used to initilize
514      *                      the new variable. If false null
515      *                      will be used.
516      */
517     public Variable defineVariable(org.codehaus.groovy.ast.Variable v, boolean initFromStack) {
518         String name = v.getName();
519         Variable answer = defineVar(name,v.getType(),false);
520         if (v.isClosureSharedVariable()) answer.setHolder(true);
521         stackVariables.put(name, answer);
522         
523         Label startLabel  = new Label();
524         answer.setStartLabel(startLabel);
525         if (answer.isHolder())  {
526             if (!initFromStack) mv.visitInsn(ACONST_NULL);
527             createReference(answer);
528         } else {
529             if (!initFromStack) mv.visitInsn(ACONST_NULL);
530             mv.visitVarInsn(ASTORE, currentVariableIndex);            
531         } 
532         mv.visitLabel(startLabel);
533         return answer;
534     }
535 
536     /***
537      * Returns true if a varibale is already defined
538      */
539     public boolean containsVariable(String name) {
540         return stackVariables.containsKey(name);
541     }
542     
543     /***
544      * Calculates the index of the next free register stores ir
545      * and sets the current variable index to the old value
546      */
547     private void makeNextVariableID(ClassNode type) {
548         currentVariableIndex = nextVariableIndex;
549         if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) {
550             nextVariableIndex++;
551         }
552         nextVariableIndex++;
553     }
554     
555     /***
556      * Returns the label for the given name 
557      */
558     public Label getLabel(String name) {
559         if (name==null) return null;
560         Label l = (Label) superBlockNamedLabels.get(name);
561         if (l==null) l = createLocalLabel(name);
562         return l;
563     }
564     
565     /***
566      * creates a new named label
567      */
568     public Label createLocalLabel(String name) {
569         Label l = (Label) currentBlockNamedLabels.get(name);
570         if (l==null) {
571             l = new Label();
572             currentBlockNamedLabels.put(name,l);
573         }
574         return l;
575     }
576 }