1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 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
91 private boolean clear=true;
92
93 private VariableScope scope;
94
95 private Label continueLabel;
96
97 private Label breakLabel;
98
99 private Label finallyLabel;
100
101 private HashMap stackVariables = new HashMap();
102
103 private int currentVariableIndex = 1;
104
105 private int nextVariableIndex = 1;
106
107 private LinkedList temporaryVariables = new LinkedList();
108
109 private LinkedList usedVariables = new LinkedList();
110
111 private HashMap superBlockNamedLabels = new HashMap();
112
113 private HashMap currentBlockNamedLabels = new HashMap();
114
115 private Label thisStartLabel, thisEndLabel;
116
117
118 private MethodVisitor mv;
119 private BytecodeHelper helper;
120
121
122 private LinkedList stateStack = new LinkedList();
123
124
125
126 private int localVariableOffset;
127
128
129 private HashMap namedLoopBreakLabel = new HashMap();
130
131
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
294 if (true) {
295 if (thisEndLabel==null) setEndLabels();
296
297 if (!scope.isInStaticContext()) {
298
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 }