Package joust.analysers

Source Code of joust.analysers.Live

package joust.analysers;

import com.sun.tools.javac.util.List;
import joust.utils.logging.LogUtils;
import joust.utils.data.SymbolSet;
import lombok.experimental.ExtensionMethod;
import lombok.extern.java.Log;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.logging.Logger;

import static joust.tree.annotatedtree.AJCTree.*;
import static com.sun.tools.javac.code.Symbol.*;

/**
* Live variable analysis. Used on demand to determine the live variable set at each point.
*/
@Log
@ExtensionMethod({Logger.class, LogUtils.LogExtensions.class})
public class Live extends BackwardsFlowVisitor {
    // If we're currently processing inside a loop, this flag is set.
    private boolean withinLoop = false;

    // The set of symbols that are freshly declared within this loop. These are the only symbols that may be killed
    // while we're within the current loop.
    Set<VarSymbol> symbolWhitelist;

    Set<VarSymbol> currentlyLive = new SymbolSet();

    // Every symbol that has ever been live.
    HashSet<VarSymbol> everLive = new HashSet<VarSymbol>();

    HashSet<VarSymbol> recentlyKilled;
    LinkedList<HashSet<VarSymbol>> recentlyKilledList = new LinkedList<HashSet<VarSymbol>>() {
        {
            recentlyKilled = new HashSet<VarSymbol>();
            push(recentlyKilled);
        }
    };

    private void enterBlock() {
        recentlyKilled = new HashSet<VarSymbol>();
        recentlyKilledList.push(recentlyKilled);
    }

    /**
     * Recent the currentlyLive set for visiting a new block and set the recentlyKilled set to the next outer
     * scope (And throw away the current one).
     */
    private void leaveBlock() {
        currentlyLive.addAll(recentlyKilled);
        recentlyKilledList.pop();
        recentlyKilled = recentlyKilledList.peek();
    }

    private void kill(VarSymbol sym) {
        if (withinLoop && !symbolWhitelist.contains(sym)) {
            return;
        }

        if (!currentlyLive.contains(sym)) {
            return;
        }

        recentlyKilled.add(sym);
        currentlyLive.remove(sym);
    }
    private void kill(Set<VarSymbol> syms) {
        if (withinLoop) {
            syms.retainAll(symbolWhitelist);
        }

        // Drop those that aren't currently live...
        Set<VarSymbol> copy = new HashSet<VarSymbol>(syms);
        copy.retainAll(currentlyLive);

        // And remove it from the in-progress killed set.
        recentlyKilled.addAll(copy);
        currentlyLive.removeAll(copy);
    }

    // Visit loops twice, so the liveness between loops is considered...
    @Override
    public void visitDoWhileLoop(AJCDoWhileLoop jcDoWhileLoop) {
        boolean wasInLoop = enterLoop(jcDoWhileLoop);

        enterBlock();
        super.visitDoWhileLoop(jcDoWhileLoop);
        leaveBlock();

        withinLoop = wasInLoop;
    }

    @Override
    public void visitWhileLoop(AJCWhileLoop jcWhileLoop) {
        boolean wasInLoop = enterLoop(jcWhileLoop);

        enterBlock();
        super.visitWhileLoop(jcWhileLoop);
        leaveBlock();

        withinLoop = wasInLoop;
    }

    @Override
    public void visitForLoop(AJCForLoop jcForLoop) {
        // Loops are annoying - stuff can be made live within the loop that isn't live after the loop and which is
        // assigned within the loop, later than the read. That assignment needs to be kept, though, but possibly
        // redundant ones that shadow it might not.
        // Fun.

        boolean wasInLoop = enterLoop(jcForLoop);

        enterBlock();
        super.visitForLoop(jcForLoop);
        leaveBlock();

        withinLoop = wasInLoop;
    }

    /**
     * Update the state as required to enter a loop.
     * @param loop The loop node we're about to process.
     * @return If the loop has readInternal == UNIVERSAL_SET.
     */
    private boolean enterLoop(AJCEffectAnnotatedTree loop) {
        AssignmentLocator asgLocator = new AssignmentLocator();
        asgLocator.visitTree(loop);

        symbolWhitelist = asgLocator.declarations;

        boolean wasInLoop = withinLoop;
        withinLoop = true;

        // Add to the live set every variable read somewhere in the loop that isn't declared within the loop.
        // TODO: This makes us miss some redundant assignments... :(

        // Do evil stupidity to locate the set of locally-touched symbols... TODO: Different EffectSet representation?
        TouchedSymbolLocator touch = new TouchedSymbolLocator();
        touch.visitTree(loop);
        Set<VarSymbol> syms = touch.touched;

        if (syms != null) {
            Set<VarSymbol> symCopy = new HashSet<VarSymbol>(syms);
            symCopy.removeAll(symbolWhitelist);

            currentlyLive.addAll(symCopy);
        }

        return wasInLoop;
    }

    @Override
    public void visitIf(AJCIf jcIf) {
        // New recently-killed set created...
        enterBlock();

        // Populate it with the stuff killed by the elsepart.
        visit(jcIf.elsepart);

        // Keep a reference to the stuff killed by else and strip it from the list.
        HashSet<VarSymbol> killedInElse = recentlyKilled;
        leaveBlock();

        // New recently-killed set...
        enterBlock();
        visit(jcIf.thenpart);

        HashSet<VarSymbol> killedInThen = recentlyKilled;
        leaveBlock();

        // We now have the original recentlyKilled set (The one for the context containing this if) once again.
        // We now want to figure out what changes this if made to it - any symbols killed in both branches are really
        // dead now.

        // Take the intersection of the kill sets...
        killedInThen.retainAll(killedInElse);

        kill(killedInThen);

        visit(jcIf.cond);
    }

    @Override
    public void visitSwitch(AJCSwitch jcSwitch) {
        // Tracks things killed in every branch. Such things, if there also exists a default branch, may be culled.
        HashSet<VarSymbol> killedEverywhere = null;
        boolean haveDefaultBranch = false;
        for (List<AJCCase> l = jcSwitch.cases; l.nonEmpty(); l = l.tail) {
            if (l.head != null) {
                if (l.head.pat.isEmptyExpression()) {
                    haveDefaultBranch = true;
                }

                enterBlock();

                visit(l.head);
                // Drop everything that was killed in the other case but not in this one (We want ones killed
                // everywhere only).
                if (killedEverywhere == null) {
                    // First time we set it to the kill set from the first branch.
                    killedEverywhere = new HashSet<VarSymbol>(recentlyKilled);
                } else {
                    // Every other time we throw away everything this new branch didn't kill.
                    killedEverywhere.retainAll(recentlyKilled);
                }

                // And then we restore the live set to the original state for the next case.
                leaveBlock();
            }
        }

        if (killedEverywhere != null && haveDefaultBranch) {
            kill(killedEverywhere);
        }

        visit(jcSwitch.selector);
    }

    @Override
    public void visitTry(AJCTry jcTry) {
        // If there's a finaliser, the analysis of everything else takes place in series with it.
        enterBlock();
        visit(jcTry.finalizer);
        HashSet<VarSymbol> finaliserDeaths = new HashSet<VarSymbol>(recentlyKilled);
        leaveBlock();

        finaliserDeaths.retainAll(currentlyLive);

        // If it's killed in the finaliser, it's dead in the catchers/body.
        recentlyKilled.addAll(finaliserDeaths);
        currentlyLive.removeAll(finaliserDeaths);

        // Find the things killed in every catcher, similarly to how we handle switch cases.
        HashSet<VarSymbol> catcherDeaths = null;
        for (AJCCatch catcher : jcTry.catchers) {
            enterBlock();
            visit(catcher);

            // Track everything that is killed in every catcher.
            if (catcherDeaths == null) {
                catcherDeaths = new HashSet<VarSymbol>(recentlyKilled);
            } else {
                catcherDeaths.retainAll(recentlyKilled);
            }

            // And then we restore the live set to the original state for the next one.
            leaveBlock();
        }

        enterBlock();
        visit(jcTry.body);
        HashSet<VarSymbol> bodyDeaths = new HashSet<VarSymbol>(recentlyKilled);

        leaveBlock();

        // If there are no catchers, we can't kill anything at all - it'll jump from the try to the finally at some
        // point. Without a system to determine where that jump occurs, we're stuck. TODO: That!
        if (catcherDeaths == null) {
            return;
        }

        // If it's dead in all catchers and the body, it's also dead.
        bodyDeaths.retainAll(catcherDeaths);

        kill(bodyDeaths);
    }

    // Stuff should start being live if it's live *anywhere* and stop being live if it stops *everywhere*.
    @Override
    public void visitMethodDef(AJCMethodDecl jcMethodDecl) {
        everLive = new HashSet<VarSymbol>();

        super.visitMethodDef(jcMethodDecl);

        jcMethodDecl.everLive = everLive;
    }

    @Override
    public void visitArrayAccess(AJCArrayAccess jcArrayAccess) {
        super.visitArrayAccess(jcArrayAccess);
    }

    @Override
    public void visitVariableDecl(AJCVariableDecl jcVariableDecl) {
        if (!jcVariableDecl.getInit().isEmptyExpression()) {
            visit(jcVariableDecl.getInit());
        }

        markLive(jcVariableDecl);

        if (!jcVariableDecl.getInit().isEmptyExpression()) {
            // If there's an assignment, we just killed this symbol (But may have added some, too!).
            VarSymbol referenced = jcVariableDecl.getTargetSymbol();
            kill(referenced);
        }
    }

    @Override
    public void visitAssign(AJCAssign jcAssign) {
        log.debug("VisitAsg: {}", jcAssign);
        log.debug(Arrays.toString(currentlyLive.toArray()));
        visit(jcAssign.rhs);

        markLive(jcAssign);

        // Array accesses aren't *really* writes.
        if (jcAssign.lhs instanceof AJCArrayAccess) {
            return;
        }

        VarSymbol referenced = jcAssign.lhs.getTargetSymbol();

        kill(referenced);
    }

    @Override
    public void visitAssignop(AJCAssignOp jcAssignOp) {
        log.debug("VisitAsgOp: {}", jcAssignOp);
        log.debug(Arrays.toString(currentlyLive.toArray()));
        visit(jcAssignOp.rhs);

        markLive(jcAssignOp);

        VarSymbol referenced = jcAssignOp.lhs.getTargetSymbol();
        currentlyLive.add(referenced);
    }

    @Override
    public void visitUnaryAsg(AJCUnaryAsg that) {
        log.debug("Arg is: {}", that.arg);
        log.debug(Arrays.toString(currentlyLive.toArray()));
        markLive(that);
        visit(that.arg);
    }

    @Override
    public void visitFieldAccess(AJCFieldAccess jcFieldAccess) {
        super.visitFieldAccess(jcFieldAccess);
        processReference(jcFieldAccess);
    }

    @Override
    public void visitIdent(AJCIdent jcIdent) {
        super.visitIdent(jcIdent);
        processReference(jcIdent);
    }

    /**
     * Consider an expression that may refer to a VarSymbol and add it to the currently live set.
     * @param tree The JCIdent or JCFieldAccess to consider.
     */
    private void processReference(AJCSymbolRef tree) {
        if (!(tree.getTargetSymbol() instanceof VarSymbol)) {
            return;
        }
        log.debug("Visit ref: {}", tree);
        log.debug(Arrays.toString(currentlyLive.toArray()));

        VarSymbol referenced = (VarSymbol) tree.getTargetSymbol();

        currentlyLive.add(referenced);
        everLive.add(referenced);
    }

    private void markLive(AJCEffectAnnotatedTree tree) {
        // TODO: Datastructure-fu to enable portions of this map to be shared...
        tree.liveVariables = new SymbolSet(currentlyLive);
    }
}
TOP

Related Classes of joust.analysers.Live

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.