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 superBlockNamedLabels = 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 thisStartLabel, thisEndLabel; 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 _superBlockNamedLabels; 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 _superBlockNamedLabels = superBlockNamedLabels; 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("Tried to do a pop on the compile stack without push."); 186 } 187 StateStackElement element = (StateStackElement) stateStack.removeLast(); 188 scope = element._scope; 189 continueLabel = element._continueLabel; 190 breakLabel = element._breakLabel; 191 currentVariableIndex = element._lastVariableIndex; 192 stackVariables = element._stackVariables; 193 nextVariableIndex = element._nextVariableIndex; 194 finallyBlocks = element._finallyBlocks; 195 } 196 197 public Label getContinueLabel() { 198 return continueLabel; 199 } 200 201 public Label getBreakLabel() { 202 return breakLabel; 203 } 204 205 public void removeVar(int tempIndex) { 206 for (Iterator iter = temporaryVariables.iterator(); iter.hasNext();) { 207 Variable element = (Variable) iter.next(); 208 if (element.getIndex()==tempIndex) { 209 iter.remove(); 210 return; 211 } 212 } 213 throw new GroovyBugError("CompileStack#removeVar: tried to remove a temporary variable with a non existent index"); 214 } 215 216 private void setEndLabels(){ 217 Label endLabel = new Label(); 218 mv.visitLabel(endLabel); 219 for (Iterator iter = stackVariables.values().iterator(); iter.hasNext();) { 220 Variable var = (Variable) iter.next(); 221 var.setEndLabel(endLabel); 222 } 223 thisEndLabel = endLabel; 224 } 225 226 public void pop() { 227 setEndLabels(); 228 popState(); 229 } 230 231 public VariableScope getScope() { 232 return scope; 233 } 234 235 /** 236 * creates a temporary variable. 237 * 238 * @param var defines type and name 239 * @param store defines if the toplevel argument of the stack should be stored 240 * @return the index used for this temporary variable 241 */ 242 public int defineTemporaryVariable(org.codehaus.groovy.ast.Variable var, boolean store) { 243 return defineTemporaryVariable(var.getName(), var.getType(),store); 244 } 245 246 public Variable getVariable(String variableName ) { 247 return getVariable(variableName,true); 248 } 249 250 public Variable getVariable(String variableName, boolean mustExist) { 251 if (variableName.equals("this")) return Variable.THIS_VARIABLE; 252 if (variableName.equals("super")) return Variable.SUPER_VARIABLE; 253 Variable v = (Variable) stackVariables.get(variableName); 254 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"); 255 return v; 256 } 257 258 /** 259 * creates a temporary variable. 260 * 261 * @param name defines type and name 262 * @param store defines if the toplevel argument of the stack should be stored 263 * @return the index used for this temporary variable 264 */ 265 public int defineTemporaryVariable(String name,boolean store) { 266 return defineTemporaryVariable(name, ClassHelper.DYNAMIC_TYPE,store); 267 } 268 269 /** 270 * creates a temporary variable. 271 * 272 * @param name defines the name 273 * @param node defines the node 274 * @param store defines if the toplevel argument of the stack should be stored 275 * @return the index used for this temporary variable 276 */ 277 public int defineTemporaryVariable(String name, ClassNode node, boolean store) { 278 Variable answer = defineVar(name,node,false); 279 temporaryVariables.add(answer); 280 usedVariables.removeLast(); 281 282 if (store) mv.visitVarInsn(ASTORE, currentVariableIndex); 283 284 return answer.getIndex(); 285 } 286 287 private void resetVariableIndex(boolean isStatic) { 288 if (!isStatic) { 289 currentVariableIndex=1; 290 nextVariableIndex=1; 291 } else { 292 currentVariableIndex=0; 293 nextVariableIndex=0; 294 } 295 } 296 297 /** 298 * Clears the state of the class. This method should be called 299 * after a MethodNode is visited. Note that a call to init will 300 * fail if clear is not called before 301 */ 302 public void clear() { 303 if (stateStack.size()>1) { 304 int size = stateStack.size()-1; 305 throw new GroovyBugError("the compile stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops."); 306 } 307 clear = true; 308 // br experiment with local var table so debuggers can retrieve variable names 309 if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) { 310 if (thisEndLabel==null) setEndLabels(); 311 312 if (!scope.isInStaticContext()) { 313 // write "this" 314 mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0); 315 } 316 317 for (Iterator iterator = usedVariables.iterator(); iterator.hasNext();) { 318 Variable v = (Variable) iterator.next(); 319 String type = BytecodeHelper.getTypeDescription(v.getType()); 320 Label start = v.getStartLabel(); 321 Label end = v.getEndLabel(); 322 mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex()); 323 } 324 } 325 pop(); 326 stackVariables.clear(); 327 usedVariables.clear(); 328 scope = null; 329 mv=null; 330 resetVariableIndex(false); 331 superBlockNamedLabels.clear(); 332 currentBlockNamedLabels.clear(); 333 namedLoopBreakLabel.clear(); 334 namedLoopContinueLabel.clear(); 335 continueLabel=null; 336 breakLabel=null; 337 helper = null; 338 thisStartLabel=null; 339 thisEndLabel=null; 340 } 341 342 /** 343 * initializes this class for a MethodNode. This method will 344 * automatically define varibales for the method parameters 345 * and will create references if needed. the created variables 346 * can be get by getVariable 347 * 348 */ 349 protected void init(VariableScope el, Parameter[] parameters, MethodVisitor mv, ClassNode cn) { 350 if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before"); 351 clear=false; 352 pushVariableScope(el); 353 this.mv = mv; 354 this.helper = new BytecodeHelper(mv); 355 defineMethodVariables(parameters,el.isInStaticContext()); 356 this.className = BytecodeHelper.getTypeDescription(cn); 357 currentClassIndex = -1; currentMetaClassIndex = -1; 358 } 359 360 /** 361 * Causes the statestack to add an element and sets 362 * the given scope as new current variable scope. Creates 363 * a element for the state stack so pop has to be called later 364 */ 365 protected void pushVariableScope(VariableScope el) { 366 pushState(); 367 scope = el; 368 superBlockNamedLabels = new HashMap(superBlockNamedLabels); 369 superBlockNamedLabels.putAll(currentBlockNamedLabels); 370 currentBlockNamedLabels = new HashMap(); 371 } 372 373 /** 374 * Should be called when decending into a loop that defines 375 * also a scope. Calls pushVariableScope and prepares labels 376 * for a loop structure. Creates a element for the state stack 377 * so pop has to be called later 378 */ 379 protected void pushLoop(VariableScope el, String labelName) { 380 pushVariableScope(el); 381 initLoopLabels(labelName); 382 } 383 384 private void initLoopLabels(String labelName) { 385 continueLabel = new Label(); 386 breakLabel = new Label(); 387 if (labelName!=null) { 388 namedLoopBreakLabel.put(labelName,breakLabel); 389 namedLoopContinueLabel.put(labelName,continueLabel); 390 } 391 } 392 393 /** 394 * Should be called when decending into a loop that does 395 * not define a scope. Creates a element for the state stack 396 * so pop has to be called later 397 */ 398 protected void pushLoop(String labelName) { 399 pushState(); 400 initLoopLabels(labelName); 401 } 402 403 /** 404 * Used for <code>break foo</code> inside a loop to end the 405 * execution of the marked loop. This method will return the 406 * break label of the loop if there is one found for the name. 407 * If not, the current break label is returned. 408 */ 409 protected Label getNamedBreakLabel(String name) { 410 Label label = getBreakLabel(); 411 Label endLabel = null; 412 if (name!=null) endLabel = (Label) namedLoopBreakLabel.get(name); 413 if (endLabel!=null) label = endLabel; 414 return label; 415 } 416 417 /** 418 * Used for <code>continue foo</code> inside a loop to continue 419 * the execution of the marked loop. This method will return 420 * the break label of the loop if there is one found for the 421 * name. If not, getLabel is used. 422 */ 423 protected Label getNamedContinueLabel(String name) { 424 Label label = getLabel(name); 425 Label endLabel = null; 426 if (name!=null) endLabel = (Label) namedLoopContinueLabel.get(name); 427 if (endLabel!=null) label = endLabel; 428 return label; 429 } 430 431 /** 432 * Creates a new break label and a element for the state stack 433 * so pop has to be called later 434 */ 435 protected Label pushSwitch(){ 436 pushState(); 437 breakLabel = new Label(); 438 return breakLabel; 439 } 440 441 /** 442 * because a boolean Expression may not be evaluated completly 443 * it is important to keep the registers clean 444 */ 445 protected void pushBooleanExpression(){ 446 pushState(); 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 577 public int getCurrentClassIndex(){ 578 return currentClassIndex; 579 } 580 581 public void setCurrentClassIndex(int index){ 582 currentClassIndex=index; 583 } 584 585 public int getCurrentMetaClassIndex(){ 586 return currentMetaClassIndex; 587 } 588 589 public void setCurrentMetaClassIndex(int index){ 590 currentMetaClassIndex=index; 591 } 592 593 public void applyFinallyBlocks(Label label, boolean isBreakLabel) { 594 // first find the state defining the label. That is the state 595 // directly after the state not knowing this label. If no state 596 // in the list knows that label, then the defining state is the 597 // current state. 598 StateStackElement result = null; 599 for (ListIterator iter = stateStack.listIterator(stateStack.size()); iter.hasPrevious();) { 600 StateStackElement element = (StateStackElement) iter.previous(); 601 if (!element._currentBlockNamedLabels.values().contains(label)) { 602 if (isBreakLabel && element._breakLabel != label) { 603 result = element; 604 break; 605 } 606 if (!isBreakLabel && element._continueLabel != label) { 607 result = element; 608 break; 609 } 610 } 611 } 612 613 List blocksToRemove; 614 if (result==null) { 615 // all Blocks do know the label, so use all finally blocks 616 blocksToRemove = Collections.EMPTY_LIST; 617 } else { 618 blocksToRemove = result._finallyBlocks; 619 } 620 621 ArrayList blocks = new ArrayList(finallyBlocks); 622 blocks.removeAll(blocksToRemove); 623 applyFinallyBlocks(blocks); 624 } 625 626 private void applyFinallyBlocks(List blocks) { 627 for (Iterator iter = blocks.iterator(); iter.hasNext();) { 628 Runnable block = (Runnable) iter.next(); 629 if (visitedBlocks.contains(block)) continue; 630 visitedBlocks.add(block); 631 block.run(); 632 } 633 } 634 635 public void applyFinallyBlocks() { 636 applyFinallyBlocks(finallyBlocks); 637 } 638 639 public boolean hasFinallyBlocks() { 640 return !finallyBlocks.isEmpty(); 641 } 642 643 public void pushFinallyBlock(Runnable block) { 644 finallyBlocks.addFirst(block); 645 pushState(); 646 } 647 648 public void popFinallyBlock() { 649 popState(); 650 finallyBlocks.removeFirst(); 651 } 652 }