/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * 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 com.android.dx.rop.code;

import com.android.dx.util.Bits;
import com.android.dx.util.IntList;

/**
 * Code to figure out which local variables are active at which points in
 * a method.
 */
public final class LocalVariableExtractor {
    /** {@code non-null;} method being extracted from */
    private final RopMethod method;

    /** {@code non-null;} block list for the method */
    private final BasicBlockList blocks;

    /** {@code non-null;} result in-progress */
    private final LocalVariableInfo resultInfo;

    /** {@code non-null;} work set indicating blocks needing to be processed */
    private final int[] workSet;

    /**
     * Extracts out all the local variable information from the given method.
     *
     * @param method {@code non-null;} the method to extract from
     * @return {@code non-null;} the extracted information
     */
    public static LocalVariableInfo extract(RopMethod method) {
        LocalVariableExtractor lve = new LocalVariableExtractor(method);
        return lve.doit();
    }

    /**
     * Constructs an instance. This method is private. Use {@link #extract}.
     *
     * @param method {@code non-null;} the method to extract from
     */
    private LocalVariableExtractor(RopMethod method) {
        if (method == null) {
            throw new NullPointerException("method == null");
        }

        BasicBlockList blocks = method.getBlocks();
        int maxLabel = blocks.getMaxLabel();

        this.method = method;
        this.blocks = blocks;
        this.resultInfo = new LocalVariableInfo(method);
        this.workSet = Bits.makeBitSet(maxLabel);
    }

    /**
     * Does the extraction.
     *
     * @return {@code non-null;} the extracted information
     */
    private LocalVariableInfo doit() {
        for (int label = method.getFirstLabel();
             label >= 0;
             label = Bits.findFirst(workSet, 0)) {
            Bits.clear(workSet, label);
            processBlock(label);
        }

        resultInfo.setImmutable();
        return resultInfo;
    }

    /**
     * Processes a single block.
     *
     * @param label {@code >= 0;} label of the block to process
     */
    private void processBlock(int label) {
        RegisterSpecSet primaryState = resultInfo.mutableCopyOfStarts(label);
        BasicBlock block = blocks.labelToBlock(label);
        InsnList insns = block.getInsns();
        int insnSz = insns.size();

        /*
         * We may have to treat the last instruction specially: If it
         * can (but doesn't always) throw, and the exception can be
         * caught within the same method, then we need to use the
         * state *before* executing it to be what is merged into
         * exception targets.
         */
        boolean canThrowDuringLastInsn = block.hasExceptionHandlers() &&
            (insns.getLast().getResult() != null);
        int freezeSecondaryStateAt = insnSz - 1;
        RegisterSpecSet secondaryState = primaryState;

        /*
         * Iterate over the instructions, adding information for each place
         * that the active variable set changes.
         */

        for (int i = 0; i < insnSz; i++) {
            if (canThrowDuringLastInsn && (i == freezeSecondaryStateAt)) {
                // Until this point, primaryState == secondaryState.
                primaryState.setImmutable();
                primaryState = primaryState.mutableCopy();
            }

            Insn insn = insns.get(i);
            RegisterSpec result;

            result = insn.getLocalAssignment();

            if (result == null) {
                /*
                 * If an assignment assigns over an existing local, make
                 * sure to mark the local as going out of scope.
                 */

                result = insn.getResult();

                if (result != null
                        && primaryState.get(result.getReg()) != null) {
                    primaryState.remove(primaryState.get(result.getReg()));
                }
                continue;
            }

            result = result.withSimpleType();

            RegisterSpec already = primaryState.get(result);
            /*
             * The equals() check ensures we only add new info if
             * the instruction causes a change to the set of
             * active variables.
             */
            if (!result.equals(already)) {
                /*
                 * If this insn represents a local moving from one register
                 * to another, remove the association between the old register
                 * and the local.
                 */
                RegisterSpec previous
                        = primaryState.localItemToSpec(result.getLocalItem());

                if (previous != null
                        && (previous.getReg() != result.getReg())) {

                    primaryState.remove(previous);
                }

                resultInfo.addAssignment(insn, result);
                primaryState.put(result);
            }
        }

        primaryState.setImmutable();

        /*
         * Merge this state into the start state for each successor,
         * and update the work set where required (that is, in cases
         * where the start state for a block changes).
         */

        IntList successors = block.getSuccessors();
        int succSz = successors.size();
        int primarySuccessor = block.getPrimarySuccessor();

        for (int i = 0; i < succSz; i++) {
            int succ = successors.get(i);
            RegisterSpecSet state = (succ == primarySuccessor) ?
                primaryState : secondaryState;

            if (resultInfo.mergeStarts(succ, state)) {
                Bits.set(workSet, succ);
            }
        }
    }
}
