org.mbte.groovypp.compiler.CompilerStack.java Source code

Java tutorial

Introduction

Here is the source code for org.mbte.groovypp.compiler.CompilerStack.java

Source

/*
 * Copyright 2009-2011 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.mbte.groovypp.compiler.Register;
import org.mbte.groovypp.compiler.TypeUtil;
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.OBJECT_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 {
    }
}