001: /*
002: $Id: CompileStack.java 4352 2006-12-13 15:58:48Z blackdrag $
003:
004: Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005:
006: Redistribution and use of this software and associated documentation
007: ("Software"), with or without modification, are permitted provided
008: that the following conditions are met:
009:
010: 1. Redistributions of source code must retain copyright
011: statements and notices. Redistributions must also contain a
012: copy of this document.
013:
014: 2. Redistributions in binary form must reproduce the
015: above copyright notice, this list of conditions and the
016: following disclaimer in the documentation and/or other
017: materials provided with the distribution.
018:
019: 3. The name "groovy" must not be used to endorse or promote
020: products derived from this Software without prior written
021: permission of The Codehaus. For written permission,
022: please contact info@codehaus.org.
023:
024: 4. Products derived from this Software may not be called "groovy"
025: nor may "groovy" appear in their names without prior written
026: permission of The Codehaus. "groovy" is a registered
027: trademark of The Codehaus.
028:
029: 5. Due credit should be given to The Codehaus -
030: http://groovy.codehaus.org/
031:
032: THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033: ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034: NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
036: THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043: OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: */
046:
047: package org.codehaus.groovy.classgen;
048:
049: import java.util.ArrayList;
050: import java.util.Collections;
051: import java.util.HashMap;
052: import java.util.Iterator;
053: import java.util.LinkedList;
054: import java.util.List;
055: import java.util.ListIterator;
056:
057: import org.codehaus.groovy.GroovyBugError;
058: import org.codehaus.groovy.ast.ClassHelper;
059: import org.codehaus.groovy.ast.ClassNode;
060: import org.codehaus.groovy.ast.Parameter;
061: import org.codehaus.groovy.ast.VariableScope;
062: import org.objectweb.asm.Label;
063: import org.objectweb.asm.MethodVisitor;
064: import org.objectweb.asm.Opcodes;
065:
066: /**
067: * This class is a helper for AsmClassGenerator. It manages
068: * different aspects of the code of a code block like
069: * handling labels, defining variables, and scopes.
070: * After a MethodNode is visited clear should be called, for
071: * initialization the method init should be used.
072: * <p>
073: * Some Notes:
074: * <ul>
075: * <li> every push method will require a later pop call
076: * <li> method parameters may define a category 2 variable, so
077: * don't ignore the type stored in the variable object
078: * <li> the index of the variable may not be as assumed when
079: * the variable is a parameter of a method because the
080: * parameter may be used in a closure, so don't ignore
081: * the stored variable index
082: * <li> the names of temporary variables can be ignored. The names
083: * are only used for debugging and do not conflict with each
084: * other or normal variables. For accessing the index of the
085: * variable must be used.
086: * </ul>
087: *
088: *
089: * @see org.codehaus.groovy.classgen.AsmClassGenerator
090: * @author Jochen Theodorou
091: */
092: public class CompileStack implements Opcodes {
093: /**
094: * @TODO remove optimization of this.foo -> this.@foo
095: *
096: */
097:
098: // state flag
099: private boolean clear = true;
100: // current scope
101: private VariableScope scope;
102: // current label for continue
103: private Label continueLabel;
104: // current label for break
105: private Label breakLabel;
106: // available variables on stack
107: private HashMap stackVariables = new HashMap();
108: // index of the last variable on stack
109: private int currentVariableIndex = 1;
110: // index for the next variable on stack
111: private int nextVariableIndex = 1;
112: // currently temporary variables in use
113: private LinkedList temporaryVariables = new LinkedList();
114: // overall used variables for a method/constructor
115: private LinkedList usedVariables = new LinkedList();
116: // map containing named labels of parenting blocks
117: private HashMap super BlockNamedLabels = new HashMap();
118: // map containing named labels of current block
119: private HashMap currentBlockNamedLabels = new HashMap();
120: // list containing runnables representing a finally block
121: // such a block is created by synchronized or finally and
122: // must be called for break/continue/return
123: private LinkedList finallyBlocks = new LinkedList();
124: // a list of blocks already visiting.
125: private List visitedBlocks = new LinkedList();
126:
127: private Label this StartLabel, this EndLabel;
128:
129: // current class index
130: private int currentClassIndex, currentMetaClassIndex;
131:
132: private MethodVisitor mv;
133: private BytecodeHelper helper;
134:
135: // helper to handle different stack based variables
136: private LinkedList stateStack = new LinkedList();
137:
138: // defines the first variable index useable after
139: // all parameters of a method
140: private int localVariableOffset;
141: // this is used to store the goals for a "break foo" call
142: // in a loop where foo is a label.
143: private HashMap namedLoopBreakLabel = new HashMap();
144: //this is used to store the goals for a "continue foo" call
145: // in a loop where foo is a label.
146: private HashMap namedLoopContinueLabel = new HashMap();
147: private String className;
148:
149: private class StateStackElement {
150: VariableScope _scope;
151: Label _continueLabel;
152: Label _breakLabel;
153: Label _finallyLabel;
154: int _lastVariableIndex;
155: int _nextVariableIndex;
156: HashMap _stackVariables;
157: LinkedList _temporaryVariables = new LinkedList();
158: LinkedList _usedVariables = new LinkedList();
159: HashMap _super BlockNamedLabels;
160: HashMap _currentBlockNamedLabels;
161: LinkedList _finallyBlocks;
162:
163: StateStackElement() {
164: _scope = CompileStack.this .scope;
165: _continueLabel = CompileStack.this .continueLabel;
166: _breakLabel = CompileStack.this .breakLabel;
167: _lastVariableIndex = CompileStack.this .currentVariableIndex;
168: _stackVariables = CompileStack.this .stackVariables;
169: _temporaryVariables = CompileStack.this .temporaryVariables;
170: _nextVariableIndex = nextVariableIndex;
171: _super BlockNamedLabels = super BlockNamedLabels;
172: _currentBlockNamedLabels = currentBlockNamedLabels;
173: _finallyBlocks = finallyBlocks;
174: }
175: }
176:
177: private void pushState() {
178: stateStack.add(new StateStackElement());
179: stackVariables = new HashMap(stackVariables);
180: finallyBlocks = new LinkedList(finallyBlocks);
181: }
182:
183: private void popState() {
184: if (stateStack.size() == 0) {
185: throw new GroovyBugError(
186: "Tried to do a pop on the compile stack without push.");
187: }
188: StateStackElement element = (StateStackElement) stateStack
189: .removeLast();
190: scope = element._scope;
191: continueLabel = element._continueLabel;
192: breakLabel = element._breakLabel;
193: currentVariableIndex = element._lastVariableIndex;
194: stackVariables = element._stackVariables;
195: nextVariableIndex = element._nextVariableIndex;
196: finallyBlocks = element._finallyBlocks;
197: }
198:
199: public Label getContinueLabel() {
200: return continueLabel;
201: }
202:
203: public Label getBreakLabel() {
204: return breakLabel;
205: }
206:
207: public void removeVar(int tempIndex) {
208: for (Iterator iter = temporaryVariables.iterator(); iter
209: .hasNext();) {
210: Variable element = (Variable) iter.next();
211: if (element.getIndex() == tempIndex) {
212: iter.remove();
213: return;
214: }
215: }
216: throw new GroovyBugError(
217: "CompileStack#removeVar: tried to remove a temporary variable with a non existent index");
218: }
219:
220: private void setEndLabels() {
221: Label endLabel = new Label();
222: mv.visitLabel(endLabel);
223: for (Iterator iter = stackVariables.values().iterator(); iter
224: .hasNext();) {
225: Variable var = (Variable) iter.next();
226: var.setEndLabel(endLabel);
227: }
228: this EndLabel = endLabel;
229: }
230:
231: public void pop() {
232: setEndLabels();
233: popState();
234: }
235:
236: public VariableScope getScope() {
237: return scope;
238: }
239:
240: /**
241: * creates a temporary variable.
242: *
243: * @param var defines type and name
244: * @param store defines if the toplevel argument of the stack should be stored
245: * @return the index used for this temporary variable
246: */
247: public int defineTemporaryVariable(
248: org.codehaus.groovy.ast.Variable var, boolean store) {
249: return defineTemporaryVariable(var.getName(), var.getType(),
250: store);
251: }
252:
253: public Variable getVariable(String variableName) {
254: return getVariable(variableName, true);
255: }
256:
257: public Variable getVariable(String variableName, boolean mustExist) {
258: if (variableName.equals("this"))
259: return Variable.THIS_VARIABLE;
260: if (variableName.equals("super"))
261: return Variable.SUPER_VARIABLE;
262: Variable v = (Variable) stackVariables.get(variableName);
263: if (v == null && mustExist)
264: throw new GroovyBugError(
265: "tried to get a variable with the name "
266: + variableName
267: + " as stack variable, but a variable with this name was not created");
268: return v;
269: }
270:
271: /**
272: * creates a temporary variable.
273: *
274: * @param name defines type and name
275: * @param store defines if the toplevel argument of the stack should be stored
276: * @return the index used for this temporary variable
277: */
278: public int defineTemporaryVariable(String name, boolean store) {
279: return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,
280: store);
281: }
282:
283: /**
284: * creates a temporary variable.
285: *
286: * @param name defines the name
287: * @param node defines the node
288: * @param store defines if the toplevel argument of the stack should be stored
289: * @return the index used for this temporary variable
290: */
291: public int defineTemporaryVariable(String name, ClassNode node,
292: boolean store) {
293: Variable answer = defineVar(name, node, false);
294: temporaryVariables.add(answer);
295: usedVariables.removeLast();
296:
297: if (store)
298: mv.visitVarInsn(ASTORE, currentVariableIndex);
299:
300: return answer.getIndex();
301: }
302:
303: private void resetVariableIndex(boolean isStatic) {
304: if (!isStatic) {
305: currentVariableIndex = 1;
306: nextVariableIndex = 1;
307: } else {
308: currentVariableIndex = 0;
309: nextVariableIndex = 0;
310: }
311: }
312:
313: /**
314: * Clears the state of the class. This method should be called
315: * after a MethodNode is visited. Note that a call to init will
316: * fail if clear is not called before
317: */
318: public void clear() {
319: if (stateStack.size() > 1) {
320: int size = stateStack.size() - 1;
321: throw new GroovyBugError("the compile stack contains "
322: + size + " more push instruction"
323: + (size == 1 ? "" : "s") + " than pops.");
324: }
325: clear = true;
326: // br experiment with local var table so debuggers can retrieve variable names
327: if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) {
328: if (this EndLabel == null)
329: setEndLabels();
330:
331: if (!scope.isInStaticContext()) {
332: // write "this"
333: mv.visitLocalVariable("this", className, null,
334: this StartLabel, this EndLabel, 0);
335: }
336:
337: for (Iterator iterator = usedVariables.iterator(); iterator
338: .hasNext();) {
339: Variable v = (Variable) iterator.next();
340: String type = BytecodeHelper.getTypeDescription(v
341: .getType());
342: Label start = v.getStartLabel();
343: Label end = v.getEndLabel();
344: mv.visitLocalVariable(v.getName(), type, null, start,
345: end, v.getIndex());
346: }
347: }
348: pop();
349: stackVariables.clear();
350: usedVariables.clear();
351: scope = null;
352: mv = null;
353: resetVariableIndex(false);
354: super BlockNamedLabels.clear();
355: currentBlockNamedLabels.clear();
356: namedLoopBreakLabel.clear();
357: namedLoopContinueLabel.clear();
358: continueLabel = null;
359: breakLabel = null;
360: helper = null;
361: this StartLabel = null;
362: this EndLabel = null;
363: }
364:
365: /**
366: * initializes this class for a MethodNode. This method will
367: * automatically define varibales for the method parameters
368: * and will create references if needed. the created variables
369: * can be get by getVariable
370: *
371: */
372: protected void init(VariableScope el, Parameter[] parameters,
373: MethodVisitor mv, ClassNode cn) {
374: if (!clear)
375: throw new GroovyBugError(
376: "CompileStack#init called without calling clear before");
377: clear = false;
378: pushVariableScope(el);
379: this .mv = mv;
380: this .helper = new BytecodeHelper(mv);
381: defineMethodVariables(parameters, el.isInStaticContext());
382: this .className = BytecodeHelper.getTypeDescription(cn);
383: currentClassIndex = -1;
384: currentMetaClassIndex = -1;
385: }
386:
387: /**
388: * Causes the statestack to add an element and sets
389: * the given scope as new current variable scope. Creates
390: * a element for the state stack so pop has to be called later
391: */
392: protected void pushVariableScope(VariableScope el) {
393: pushState();
394: scope = el;
395: super BlockNamedLabels = new HashMap(super BlockNamedLabels);
396: super BlockNamedLabels.putAll(currentBlockNamedLabels);
397: currentBlockNamedLabels = new HashMap();
398: }
399:
400: /**
401: * Should be called when decending into a loop that defines
402: * also a scope. Calls pushVariableScope and prepares labels
403: * for a loop structure. Creates a element for the state stack
404: * so pop has to be called later
405: */
406: protected void pushLoop(VariableScope el, String labelName) {
407: pushVariableScope(el);
408: initLoopLabels(labelName);
409: }
410:
411: private void initLoopLabels(String labelName) {
412: continueLabel = new Label();
413: breakLabel = new Label();
414: if (labelName != null) {
415: namedLoopBreakLabel.put(labelName, breakLabel);
416: namedLoopContinueLabel.put(labelName, continueLabel);
417: }
418: }
419:
420: /**
421: * Should be called when decending into a loop that does
422: * not define a scope. Creates a element for the state stack
423: * so pop has to be called later
424: */
425: protected void pushLoop(String labelName) {
426: pushState();
427: initLoopLabels(labelName);
428: }
429:
430: /**
431: * Used for <code>break foo</code> inside a loop to end the
432: * execution of the marked loop. This method will return the
433: * break label of the loop if there is one found for the name.
434: * If not, the current break label is returned.
435: */
436: protected Label getNamedBreakLabel(String name) {
437: Label label = getBreakLabel();
438: Label endLabel = null;
439: if (name != null)
440: endLabel = (Label) namedLoopBreakLabel.get(name);
441: if (endLabel != null)
442: label = endLabel;
443: return label;
444: }
445:
446: /**
447: * Used for <code>continue foo</code> inside a loop to continue
448: * the execution of the marked loop. This method will return
449: * the break label of the loop if there is one found for the
450: * name. If not, getLabel is used.
451: */
452: protected Label getNamedContinueLabel(String name) {
453: Label label = getLabel(name);
454: Label endLabel = null;
455: if (name != null)
456: endLabel = (Label) namedLoopContinueLabel.get(name);
457: if (endLabel != null)
458: label = endLabel;
459: return label;
460: }
461:
462: /**
463: * Creates a new break label and a element for the state stack
464: * so pop has to be called later
465: */
466: protected Label pushSwitch() {
467: pushState();
468: breakLabel = new Label();
469: return breakLabel;
470: }
471:
472: /**
473: * because a boolean Expression may not be evaluated completly
474: * it is important to keep the registers clean
475: */
476: protected void pushBooleanExpression() {
477: pushState();
478: }
479:
480: private Variable defineVar(String name, ClassNode type,
481: boolean methodParameterUsedInClosure) {
482: makeNextVariableID(type);
483: int index = currentVariableIndex;
484: if (methodParameterUsedInClosure) {
485: index = localVariableOffset++;
486: }
487: Variable answer = new Variable(index, type, name);
488: usedVariables.add(answer);
489: answer.setHolder(methodParameterUsedInClosure);
490: return answer;
491: }
492:
493: private void makeLocalVariablesOffset(Parameter[] paras,
494: boolean isInStaticContext) {
495: resetVariableIndex(isInStaticContext);
496:
497: for (int i = 0; i < paras.length; i++) {
498: makeNextVariableID(paras[i].getType());
499: }
500: localVariableOffset = nextVariableIndex;
501:
502: resetVariableIndex(isInStaticContext);
503: }
504:
505: private void defineMethodVariables(Parameter[] paras,
506: boolean isInStaticContext) {
507: Label startLabel = new Label();
508: this StartLabel = startLabel;
509: mv.visitLabel(startLabel);
510:
511: makeLocalVariablesOffset(paras, isInStaticContext);
512:
513: boolean hasHolder = false;
514: for (int i = 0; i < paras.length; i++) {
515: String name = paras[i].getName();
516: Variable answer;
517: if (paras[i].isClosureSharedVariable()) {
518: answer = defineVar(name, ClassHelper
519: .getWrapper(paras[i].getType()), true);
520: ClassNode type = paras[i].getType();
521: helper.load(type, currentVariableIndex);
522: helper.box(type);
523: createReference(answer);
524: hasHolder = true;
525: } else {
526: answer = defineVar(name, paras[i].getType(), false);
527: }
528: answer.setStartLabel(startLabel);
529: stackVariables.put(name, answer);
530: }
531:
532: if (hasHolder) {
533: nextVariableIndex = localVariableOffset;
534: }
535: }
536:
537: private void createReference(Variable reference) {
538: mv.visitTypeInsn(NEW, "groovy/lang/Reference");
539: mv.visitInsn(DUP_X1);
540: mv.visitInsn(SWAP);
541: mv.visitMethodInsn(INVOKESPECIAL, "groovy/lang/Reference",
542: "<init>", "(Ljava/lang/Object;)V");
543: mv.visitVarInsn(ASTORE, reference.getIndex());
544: }
545:
546: /**
547: * Defines a new Variable using an AST variable.
548: * @param initFromStack if true the last element of the
549: * stack will be used to initilize
550: * the new variable. If false null
551: * will be used.
552: */
553: public Variable defineVariable(org.codehaus.groovy.ast.Variable v,
554: boolean initFromStack) {
555: String name = v.getName();
556: Variable answer = defineVar(name, v.getType(), false);
557: if (v.isClosureSharedVariable())
558: answer.setHolder(true);
559: stackVariables.put(name, answer);
560:
561: Label startLabel = new Label();
562: answer.setStartLabel(startLabel);
563: if (answer.isHolder()) {
564: if (!initFromStack)
565: mv.visitInsn(ACONST_NULL);
566: createReference(answer);
567: } else {
568: if (!initFromStack)
569: mv.visitInsn(ACONST_NULL);
570: mv.visitVarInsn(ASTORE, currentVariableIndex);
571: }
572: mv.visitLabel(startLabel);
573: return answer;
574: }
575:
576: /**
577: * Returns true if a varibale is already defined
578: */
579: public boolean containsVariable(String name) {
580: return stackVariables.containsKey(name);
581: }
582:
583: /**
584: * Calculates the index of the next free register stores ir
585: * and sets the current variable index to the old value
586: */
587: private void makeNextVariableID(ClassNode type) {
588: currentVariableIndex = nextVariableIndex;
589: if (type == ClassHelper.long_TYPE
590: || type == ClassHelper.double_TYPE) {
591: nextVariableIndex++;
592: }
593: nextVariableIndex++;
594: }
595:
596: /**
597: * Returns the label for the given name
598: */
599: public Label getLabel(String name) {
600: if (name == null)
601: return null;
602: Label l = (Label) super BlockNamedLabels.get(name);
603: if (l == null)
604: l = createLocalLabel(name);
605: return l;
606: }
607:
608: /**
609: * creates a new named label
610: */
611: public Label createLocalLabel(String name) {
612: Label l = (Label) currentBlockNamedLabels.get(name);
613: if (l == null) {
614: l = new Label();
615: currentBlockNamedLabels.put(name, l);
616: }
617: return l;
618: }
619:
620: public int getCurrentClassIndex() {
621: return currentClassIndex;
622: }
623:
624: public void setCurrentClassIndex(int index) {
625: currentClassIndex = index;
626: }
627:
628: public int getCurrentMetaClassIndex() {
629: return currentMetaClassIndex;
630: }
631:
632: public void setCurrentMetaClassIndex(int index) {
633: currentMetaClassIndex = index;
634: }
635:
636: public void applyFinallyBlocks(Label label, boolean isBreakLabel) {
637: // first find the state defining the label. That is the state
638: // directly after the state not knowing this label. If no state
639: // in the list knows that label, then the defining state is the
640: // current state.
641: StateStackElement result = null;
642: for (ListIterator iter = stateStack.listIterator(stateStack
643: .size()); iter.hasPrevious();) {
644: StateStackElement element = (StateStackElement) iter
645: .previous();
646: if (!element._currentBlockNamedLabels.values().contains(
647: label)) {
648: if (isBreakLabel && element._breakLabel != label) {
649: result = element;
650: break;
651: }
652: if (!isBreakLabel && element._continueLabel != label) {
653: result = element;
654: break;
655: }
656: }
657: }
658:
659: List blocksToRemove;
660: if (result == null) {
661: // all Blocks do know the label, so use all finally blocks
662: blocksToRemove = Collections.EMPTY_LIST;
663: } else {
664: blocksToRemove = result._finallyBlocks;
665: }
666:
667: ArrayList blocks = new ArrayList(finallyBlocks);
668: blocks.removeAll(blocksToRemove);
669: applyFinallyBlocks(blocks);
670: }
671:
672: private void applyFinallyBlocks(List blocks) {
673: for (Iterator iter = blocks.iterator(); iter.hasNext();) {
674: Runnable block = (Runnable) iter.next();
675: if (visitedBlocks.contains(block))
676: continue;
677: visitedBlocks.add(block);
678: block.run();
679: }
680: }
681:
682: public void applyFinallyBlocks() {
683: applyFinallyBlocks(finallyBlocks);
684: }
685:
686: public boolean hasFinallyBlocks() {
687: return !finallyBlocks.isEmpty();
688: }
689:
690: public void pushFinallyBlock(Runnable block) {
691: finallyBlocks.addFirst(block);
692: pushState();
693: }
694:
695: public void popFinallyBlock() {
696: popState();
697: finallyBlocks.removeFirst();
698: }
699: }
|