/*
 * 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.ssa;

import com.android.dx.cf.code.Merger;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.LocalItem;
import com.android.dx.rop.type.Type;
import com.android.dx.rop.type.TypeBearer;

import java.util.BitSet;
import java.util.List;

/**
 * Resolves the result types of phi instructions. When phi instructions
 * are inserted, their result types are set to BT_VOID (which is a nonsensical
 * type for a register) but must be resolve to a real type before converting
 * out of SSA form.<p>
 *
 * The resolve is done as an iterative merge of each phi's operand types.
 * Phi operands may be themselves be the result of unresolved phis,
 * and the algorithm tries to find the most-fit type (for example, if every
 * operand is the same constant value or the same local variable info, we want
 * that to be reflected).<p>
 *
 * This algorithm assumes a dead-code remover has already removed all
 * circular-only phis that may have been inserted.
 */
public class PhiTypeResolver {

    SsaMethod ssaMeth;
    /** indexed by register; all registers still defined by unresolved phis */
    private final BitSet worklist;

    /**
     * Resolves all phi types in the method
     * @param ssaMeth method to process
     */
    public static void process (SsaMethod ssaMeth) {
        new PhiTypeResolver(ssaMeth).run();
    }

    private PhiTypeResolver(SsaMethod ssaMeth) {
        this.ssaMeth = ssaMeth;
        worklist = new BitSet(ssaMeth.getRegCount());
    }

    /**
     * Runs the phi-type resolver.
     */
    private void run() {

        int regCount = ssaMeth.getRegCount();

        for (int reg = 0; reg < regCount; reg++) {
            SsaInsn definsn = ssaMeth.getDefinitionForRegister(reg);

            if (definsn != null
                    && (definsn.getResult().getBasicType() == Type.BT_VOID)) {
                worklist.set(reg);
            }
        }

        int reg;
        while ( 0 <= (reg = worklist.nextSetBit(0))) {
            worklist.clear(reg);

            /*
             * definitions on the worklist have a type of BT_VOID, which
             * must have originated from a PhiInsn.
             */
            PhiInsn definsn = (PhiInsn)ssaMeth.getDefinitionForRegister(reg);

            if (resolveResultType(definsn)) {
                /*
                 * If the result type has changed, re-resolve all phis
                 * that use this.
                 */

                List<SsaInsn> useList = ssaMeth.getUseListForRegister(reg);

                int sz = useList.size();
                for (int i = 0; i < sz; i++ ) {
                    SsaInsn useInsn = useList.get(i);
                    RegisterSpec resultReg = useInsn.getResult();
                    if (resultReg != null && useInsn instanceof PhiInsn) {
                        worklist.set(resultReg.getReg());
                    }
                }
            }
        }
    }

    /**
     * Returns true if a and b are equal, whether
     * or not either of them are null.
     * @param a
     * @param b
     * @return true if equal
     */
    private static boolean equalsHandlesNulls(LocalItem a, LocalItem b) {
        return (a == b) || ((a != null) && a.equals(b));
    }

    /**
     * Resolves the result of a phi insn based on its operands. The "void"
     * type, which is a nonsensical type for a register, is used for
     * registers defined by as-of-yet-unresolved phi operations.
     *
     * @return true if the result type changed, false if no change
     */
    boolean resolveResultType(PhiInsn insn) {
        insn.updateSourcesToDefinitions(ssaMeth);

        RegisterSpecList sources = insn.getSources();

        // Start by finding the first non-void operand
        RegisterSpec first = null;
        int firstIndex = -1;

        int szSources = sources.size();
        for (int i = 0 ; i <szSources ; i++) {
            RegisterSpec rs = sources.get(i);

            if (rs.getBasicType() != Type.BT_VOID) {
                first = rs;
                firstIndex = i;
            }
        }

        if (first == null) {
            // All operands are void -- we're not ready to resolve yet
            return false;
        }

        LocalItem firstLocal = first.getLocalItem();
        TypeBearer mergedType = first.getType();
        boolean sameLocals = true;
        for (int i = 0 ; i < szSources ; i++) {
            if (i == firstIndex) {
                continue;
            }

            RegisterSpec rs = sources.get(i);

            // Just skip void (unresolved phi results) for now
            if (rs.getBasicType() == Type.BT_VOID){
                continue;
            }

            sameLocals = sameLocals
                    && equalsHandlesNulls(firstLocal, rs.getLocalItem());

            mergedType = Merger.mergeType(mergedType, rs.getType());
        }

        TypeBearer newResultType;

        if (mergedType != null) {
            newResultType = mergedType;
        } else {
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < szSources; i++) {
                sb.append(sources.get(i).toString());
                sb.append(' ');
            }

            throw new RuntimeException ("Couldn't map types in phi insn:" + sb);
        }

        LocalItem newLocal = sameLocals ? firstLocal : null;

        RegisterSpec result = insn.getResult();

        if ((result.getTypeBearer() == newResultType)
                && equalsHandlesNulls(newLocal, result.getLocalItem())) {
            return false;
        }

        insn.changeResultType(newResultType, newLocal);

        return true;
    }
}
