/*
* Copyright 2009-2010 MBTE Sweden AB.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mbte.groovypp.compiler;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.classgen.BytecodeHelper;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.*;
public class CompilerStack implements Opcodes {
// state flag
private boolean clear=true;
// current scope
private VariableScope scope;
// current label for continue
private Label continueLabel;
// current label for break
private Label breakLabel;
// available variables on stack
private Map<String, Register> stackVariables = new HashMap<String, Register>();
// index of the last variable on stack
private int currentVariableIndex = 1;
// index for the next variable on stack
private int nextVariableIndex = 1;
// currently temporary variables in use
private final LinkedList<Register> temporaryVariables = new LinkedList<Register>();
// overall used variables for a method/constructor
private final LinkedList<Register> usedVariables = new LinkedList<Register>();
// map containing named labels of parenting blocks
private Map<String, Label> superBlockNamedLabels = new HashMap<String, Label>();
// map containing named labels of current block
private Map<String, Label> currentBlockNamedLabels = new HashMap<String, Label>();
// list containing runnables representing a finally block
// such a block is created by synchronized or finally and
// must be called for break/continue/return
private LinkedList finallyBlocks = new LinkedList();
// a list of blocks already visiting.
private final List<Runnable> visitedBlocks = new LinkedList<Runnable>();
private Label thisStartLabel, thisEndLabel;
private MethodVisitor mv;
private BytecodeHelper helper;
// helper to handle different stack based variables
private final LinkedList<StateStackElement> stateStack = new LinkedList<StateStackElement>();
// defines the first variable index useable after
// all parameters of a method
private int localVariableOffset;
// this is used to store the goals for a "break foo" call
// in a loop where foo is a label.
private final Map<String, Label> namedLoopBreakLabel = new HashMap<String, Label>();
//this is used to store the goals for a "continue foo" call
// in a loop where foo is a label.
private final Map<String, Label> namedLoopContinueLabel = new HashMap<String, Label>();
private String className;
CompilerStack parent;
public static final ClassNode inferenced_TYPE = ClassHelper.make("TYPE INFERENCE");
static {
inferenced_TYPE.setRedirect(ClassHelper.OBJECT_TYPE);
}
public CompilerStack(CompilerStack compileStack) {
parent = compileStack;
}
private class StateStackElement {
final VariableScope scope;
final Label continueLabel;
final Label breakLabel;
Label finallyLabel;
final int lastVariableIndex;
final int nextVariableIndex;
final Map<String, Register> stackVariables;
List<Register> temporaryVariables = new LinkedList<Register>();
List usedVariables = new LinkedList();
final Map<String, Label> superBlockNamedLabels;
final Map<String, Label> currentBlockNamedLabels;
final LinkedList finallyBlocks;
StateStackElement() {
scope = CompilerStack.this.scope;
continueLabel = CompilerStack.this.continueLabel;
breakLabel = CompilerStack.this.breakLabel;
lastVariableIndex = CompilerStack.this.currentVariableIndex;
stackVariables = CompilerStack.this.stackVariables;
temporaryVariables = CompilerStack.this.temporaryVariables;
nextVariableIndex = CompilerStack.this.nextVariableIndex;
superBlockNamedLabels = CompilerStack.this.superBlockNamedLabels;
currentBlockNamedLabels = CompilerStack.this.currentBlockNamedLabels;
finallyBlocks = CompilerStack.this.finallyBlocks;
}
}
protected void pushState() {
stateStack.add(new StateStackElement());
stackVariables = new HashMap<String, Register>(stackVariables);
finallyBlocks = new LinkedList(finallyBlocks);
}
private void popState() {
if (stateStack.size()==0) {
throw new GroovyBugError("Tried to do a pop on the compiler stack without push.");
}
StateStackElement element = stateStack.removeLast();
scope = element.scope;
continueLabel = element.continueLabel;
breakLabel = element.breakLabel;
currentVariableIndex = element.lastVariableIndex;
stackVariables = element.stackVariables;
nextVariableIndex = element.nextVariableIndex;
finallyBlocks = element.finallyBlocks;
}
public Label getContinueLabel() {
return continueLabel;
}
public Label getBreakLabel() {
return breakLabel;
}
public void removeVar(int tempIndex) {
final Register head = temporaryVariables.removeFirst();
if (head.getIndex() != tempIndex)
throw new GroovyBugError("CompileStack#removeVar: tried to remove a temporary variable in wrong order");
currentVariableIndex = head.getPrevIndex ();
nextVariableIndex = tempIndex;
}
private void setEndLabels(){
Label endLabel = new Label();
mv.visitLabel(endLabel);
for (Iterator<Register> iter = stackVariables.values().iterator(); iter.hasNext();) {
Register var = iter.next();
var.setEndLabel(endLabel);
}
thisEndLabel = endLabel;
}
public void pop() {
setEndLabels();
popState();
}
public VariableScope getScope() {
return scope;
}
/**
* Returns a normal variable.
* <p/>
* If <code>mustExist</code> is true and the normal variable doesn't exist,
* then this method will throw a GroovyBugError. It is not the intention of
* this method to let this happen! And the exception should not be used for
* flow control - it is just acting as an assertion. If the exception is thrown
* then it indicates a bug in the class using CompileStack.
* This method can also not be used to return a temporary variable.
* Temporary variables are not normal variables.
*
* @param variableName name of the variable
* @param mustExist throw exception if variable does not exist
* @return the normal variable or null if not found (and <code>mustExist</code> not true)
*/
public Register getRegister(String variableName, boolean mustExist) {
if (variableName.equals("this")) return Register.THIS_VARIABLE;
if (variableName.equals("super")) return Register.SUPER_VARIABLE;
Register v = stackVariables.get(variableName);
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");
}
return v;
}
/**
* creates a temporary variable.
*
* @param name defines the name
* @param node defines the node
* @param store defines if the toplevel argument of the stack should be stored
* @return the index used for this temporary variable
*/
public int defineTemporaryVariable(String name, ClassNode node, boolean store) {
Register answer = defineVar(name,node,false);
temporaryVariables.addFirst(answer); // TRICK: we add at the beginning so when we find for remove or get we always have the last one
usedVariables.removeLast();
if (store) doStore(node);
return answer.getIndex();
}
private void resetVariableIndex(boolean isStatic) {
if (!isStatic) {
currentVariableIndex=1;
nextVariableIndex=1;
} else {
currentVariableIndex=0;
nextVariableIndex=0;
}
}
/**
* Clears the state of the class. This method should be called
* after a MethodNode is visited. Note that a call to init will
* fail if clear is not called before
*/
public void clear() {
if (stateStack.size()>1) {
int size = stateStack.size()-1;
throw new GroovyBugError("the compiler stack contains "+size+" more push instruction"+(size==1?"":"s")+" than pops.");
}
clear = true;
// br experiment with local var table so debuggers can retrieve variable names
if (true) {//AsmClassGenerator.CREATE_DEBUG_INFO) {
if (thisEndLabel==null) setEndLabels();
if (!scope.isInStaticContext()) {
// write "this"
mv.visitLocalVariable("this", className, null, thisStartLabel, thisEndLabel, 0);
}
for (Iterator<Register> iterator = usedVariables.iterator(); iterator.hasNext();) {
Register v = iterator.next();
String type = BytecodeHelper.getTypeDescription(v.getType());
Label start = v.getStartLabel();
Label end = v.getEndLabel();
mv.visitLocalVariable(v.getName(), type, null, start, end, v.getIndex());
}
}
pop();
stackVariables.clear();
usedVariables.clear();
scope = null;
mv=null;
resetVariableIndex(false);
superBlockNamedLabels.clear();
currentBlockNamedLabels.clear();
namedLoopBreakLabel.clear();
namedLoopContinueLabel.clear();
continueLabel=null;
breakLabel=null;
helper = null;
thisStartLabel=null;
thisEndLabel=null;
}
/**
* initializes this class for a MethodNode. This method will
* automatically define varibales for the method parameters
* and will create references if needed. the created variables
* can be get by getVariable
*
*/
public void init(VariableScope el, Parameter[] parameters, MethodVisitor mv, ClassNode cn) {
if (!clear) throw new GroovyBugError("CompileStack#init called without calling clear before");
clear=false;
pushVariableScope(el);
this.mv = mv;
this.helper = new BytecodeHelper(mv);
defineMethodVariables(parameters,el.isInStaticContext());
this.className = BytecodeHelper.getTypeDescription(cn);
}
/**
* Causes the statestack to add an element and sets
* the given scope as new current variable scope. Creates
* a element for the state stack so pop has to be called later
*/
protected void pushVariableScope(VariableScope el) {
pushState();
scope = el;
superBlockNamedLabels = new HashMap<String, Label>(superBlockNamedLabels);
superBlockNamedLabels.putAll(currentBlockNamedLabels);
currentBlockNamedLabels = new HashMap<String, Label>();
}
/**
* Should be called when decending into a loop that defines
* also a scope. Calls pushVariableScope and prepares labels
* for a loop structure. Creates a element for the state stack
* so pop has to be called later
*/
protected void pushLoop(VariableScope el, String labelName) {
pushVariableScope(el);
initLoopLabels(labelName);
}
private void initLoopLabels(String labelName) {
continueLabel = new Label();
breakLabel = new Label();
if (labelName!=null) {
namedLoopBreakLabel.put(labelName,breakLabel);
namedLoopContinueLabel.put(labelName,continueLabel);
}
}
/**
* Should be called when descending into a loop that does
* not define a scope. Creates a element for the state stack
* so pop has to be called later
*/
protected void pushLoop(String labelName) {
pushState();
initLoopLabels(labelName);
}
protected Label getNamedBreakLabel(String name) {
if (name!=null) return namedLoopBreakLabel.get(name);
return getBreakLabel();
}
protected Label getNamedContinueLabel(String name) {
if (name!=null) return namedLoopContinueLabel.get(name);
return getContinueLabel();
}
/**
* Creates a new break label and a element for the state stack
* so pop has to be called later
*/
protected Label pushSwitch(){
pushState();
breakLabel = new Label();
return breakLabel;
}
/**
* because a boolean Expression may not be evaluated completly
* it is important to keep the registers clean
*/
protected void pushBooleanExpression(){
pushState();
}
private Register defineVar(String name, ClassNode type, boolean methodParameterUsedInClosure) {
makeNextVariableID(type);
int index = currentVariableIndex;
if (methodParameterUsedInClosure) {
index = localVariableOffset++;
type = TypeUtil.wrapSafely(type);
}
Register answer = new Register(index, type, name);
usedVariables.add(answer);
return answer;
}
private Register defineTypeInferenceVar(String name) {
makeNextVariableID(ClassHelper.long_TYPE); // we want to allocate 2 slots
int index = currentVariableIndex;
Register answer = new Register(index, inferenced_TYPE, name);
usedVariables.add(answer);
return answer;
}
private void makeLocalVariablesOffset(Parameter[] paras,boolean isInStaticContext) {
resetVariableIndex(isInStaticContext);
for (int i = 0; i < paras.length; i++) {
makeNextVariableID(paras[i].getType());
}
localVariableOffset = nextVariableIndex;
resetVariableIndex(isInStaticContext);
}
private void defineMethodVariables(Parameter[] paras,boolean isInStaticContext) {
Label startLabel = new Label();
thisStartLabel = startLabel;
mv.visitLabel(startLabel);
makeLocalVariablesOffset(paras,isInStaticContext);
boolean hasHolder = false;
for (int i = 0; i < paras.length; i++) {
String name = paras[i].getName();
Register answer;
ClassNode type = paras[i].getType();
answer = defineVar(name,type,false);
answer.setStartLabel(startLabel);
stackVariables.put(name, answer);
}
if (hasHolder) {
nextVariableIndex = localVariableOffset;
}
}
/**
* Defines a new Register using an AST variable.
* @param initFromStack if true the last element of the
* stack will be used to initilize
* the new variable. If false null
* will be used.
*/
public Register defineVariable(Variable v, boolean initFromStack) {
String name = v.getName();
final ClassNode type = v.getType();
Register answer = defineVar(name, type, false);
stackVariables.put(name, answer);
Label startLabel = new VarStartLabel();
answer.setStartLabel(startLabel);
if (!initFromStack) {
if (ClassHelper.isPrimitiveType(type)) {
if (type == ClassHelper.long_TYPE)
mv.visitInsn(LCONST_0);
else
if (type == ClassHelper.float_TYPE)
mv.visitInsn(FCONST_0);
else
if (type == ClassHelper.double_TYPE)
mv.visitInsn(DCONST_0);
else
mv.visitInsn(ICONST_0);
}
else
mv.visitInsn(ACONST_NULL);
}
doStore(type);
mv.visitLabel(startLabel);
return answer;
}
public Register defineTypeInferenceVariable(Variable v, ClassNode initType) {
String name = v.getName();
Register answer = defineTypeInferenceVar(name);
stackVariables.put(name, answer);
Label startLabel = new VarStartLabel();
answer.setStartLabel(startLabel);
doStore(initType);
mv.visitLabel(startLabel);
return answer;
}
private void doStore(ClassNode type) {
if (ClassHelper.isPrimitiveType(type)) {
if (type == ClassHelper.long_TYPE)
mv.visitVarInsn(LSTORE, currentVariableIndex);
else
if (type == ClassHelper.float_TYPE)
mv.visitVarInsn(FSTORE, currentVariableIndex);
else
if (type == ClassHelper.double_TYPE)
mv.visitVarInsn(DSTORE, currentVariableIndex);
else
mv.visitVarInsn(ISTORE, currentVariableIndex);
}
else
mv.visitVarInsn(ASTORE, currentVariableIndex);
}
/**
* @param name the name of the variable of interest
* @return true if a variable is already defined
*/
public boolean containsVariable(String name) {
return stackVariables.containsKey(name);
}
/**
* Calculates the index of the next free register stores ir
* and sets the current variable index to the old value
*/
private void makeNextVariableID(ClassNode type) {
currentVariableIndex = nextVariableIndex;
if (type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE) {
nextVariableIndex++;
}
nextVariableIndex++;
}
/**
* Returns the label for the given name
*/
public Label getLabel(String name) {
if (name==null) return null;
Label l = superBlockNamedLabels.get(name);
if (l==null) l = createLocalLabel(name);
return l;
}
/**
* creates a new named label
*/
public Label createLocalLabel(String name) {
Label l = currentBlockNamedLabels.get(name);
if (l==null) {
l = new Label();
currentBlockNamedLabels.put(name,l);
}
return l;
}
public void applyFinallyBlocks(Label label, boolean isBreakLabel) {
// first find the state defining the label. That is the state
// directly after the state not knowing this label. If no state
// in the list knows that label, then the defining state is the
// current state.
StateStackElement result = null;
for (ListIterator<StateStackElement> iter = stateStack.listIterator(stateStack.size()); iter.hasPrevious();) {
StateStackElement element = iter.previous();
if (!element.currentBlockNamedLabels.values().contains(label)) {
if (isBreakLabel && element.breakLabel != label) {
result = element;
break;
}
if (!isBreakLabel && element.continueLabel != label) {
result = element;
break;
}
}
}
List blocksToRemove;
if (result==null) {
// all Blocks do know the label, so use all finally blocks
blocksToRemove = Collections.EMPTY_LIST;
} else {
blocksToRemove = result.finallyBlocks;
}
ArrayList blocks = new ArrayList(finallyBlocks);
blocks.removeAll(blocksToRemove);
applyFinallyBlocks(blocks);
}
private void applyFinallyBlocks(List blocks) {
for (Iterator iter = blocks.iterator(); iter.hasNext();) {
Runnable block = (Runnable) iter.next();
if (visitedBlocks.contains(block)) continue;
block.run();
}
}
public void applyFinallyBlocks() {
applyFinallyBlocks(finallyBlocks);
}
public boolean hasFinallyBlocks() {
return !finallyBlocks.isEmpty();
}
public void pushFinallyBlock(Runnable block) {
finallyBlocks.addFirst(block);
pushState();
}
public void popFinallyBlock() {
popState();
finallyBlocks.removeFirst();
}
public void pushFinallyBlockVisit(Runnable block) {
visitedBlocks.add(block);
}
public void popFinallyBlockVisit(Runnable block) {
visitedBlocks.remove(block);
}
public static class VarStartLabel extends Label {
}
}