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 }