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

import com.pojavdx.dx.rop.code.Insn;
import com.pojavdx.dx.rop.code.LocalItem;
import com.pojavdx.dx.rop.code.RegisterSpec;
import com.pojavdx.dx.rop.code.RegisterSpecList;
import com.pojavdx.dx.rop.code.Rop;
import com.pojavdx.dx.util.ToHuman;

/**
 * An instruction in SSA form
 */
public abstract class SsaInsn implements ToHuman, Cloneable {
    /** {@code non-null;} the block that contains this instance */
    private final SsaBasicBlock block;

    /** {@code null-ok;} result register */
    private RegisterSpec result;

    /**
     * Constructs an instance.
     *
     * @param result {@code null-ok;} initial result register. May be changed.
     * @param block {@code non-null;} block containing this insn. Can
     * never change.
     */
    protected SsaInsn(RegisterSpec result, SsaBasicBlock block) {
        if (block == null) {
            throw new NullPointerException("block == null");
        }

        this.block = block;
        this.result = result;
    }

    /**
     * Makes a new SSA insn form a rop insn.
     *
     * @param insn {@code non-null;} rop insn
     * @param block {@code non-null;} owning block
     * @return {@code non-null;} an appropriately constructed instance
     */
    public static SsaInsn makeFromRop(Insn insn, SsaBasicBlock block) {
        return new NormalSsaInsn(insn, block);
    }

    /** {@inheritDoc} */
    @Override
    public SsaInsn clone() {
        try {
            return (SsaInsn)super.clone();
        } catch (CloneNotSupportedException ex) {
            throw new RuntimeException ("unexpected", ex);
        }
    }

    /**
     * Like {@link com.pojavdx.dx.rop.code.Insn getResult()}.
     *
     * @return result register
     */
    public RegisterSpec getResult() {
        return result;
    }

    /**
     * Set the result register.
     *
     * @param result {@code non-null;} the new result register
     */
    protected void setResult(RegisterSpec result) {
        if (result == null) {
            throw new NullPointerException("result == null");
        }

        this.result = result;
    }

    /**
     * Like {@link com.pojavdx.dx.rop.code.Insn getSources()}.
     *
     * @return {@code non-null;} sources list
     */
    abstract public RegisterSpecList getSources();

    /**
     * Gets the block to which this insn instance belongs.
     *
     * @return owning block
     */
    public SsaBasicBlock getBlock() {
        return block;
    }

    /**
     * Returns whether or not the specified reg is the result reg.
     *
     * @param reg register to test
     * @return true if there is a result and it is stored in the specified
     * register
     */
    public boolean isResultReg(int reg) {
        return result != null && result.getReg() == reg;
    }


    /**
     * Changes the result register if this insn has a result. This is used
     * during renaming.
     *
     * @param reg new result register
     */
    public void changeResultReg(int reg) {
        if (result != null) {
            result = result.withReg(reg);
        }
    }

    /**
     * Sets the local association for the result of this insn. This is
     * sometimes updated during the SsaRenamer process.
     *
     * @param local {@code null-ok;} new debug/local variable info
     */
    public final void setResultLocal(LocalItem local) {
        LocalItem oldItem = result.getLocalItem();

        if (local != oldItem && (local == null
                || !local.equals(result.getLocalItem()))) {
            result = RegisterSpec.makeLocalOptional(
                    result.getReg(), result.getType(), local);
        }
    }

    /**
     * Map registers after register allocation.
     *
     * @param mapper {@code non-null;} mapping from old to new registers
     */
    public final void mapRegisters(RegisterMapper mapper) {
        RegisterSpec oldResult = result;

        result = mapper.map(result);
        block.getParent().updateOneDefinition(this, oldResult);
        mapSourceRegisters(mapper);
    }

    /**
     * Maps only source registers.
     *
     * @param mapper new mapping
     */
    abstract public void mapSourceRegisters(RegisterMapper mapper);

    /**
     * Returns the Rop opcode for this insn, or null if this is a phi insn.
     *
     * TODO: Move this up into NormalSsaInsn.
     *
     * @return {@code null-ok;} Rop opcode if there is one.
     */
    abstract public Rop getOpcode();

    /**
     * Returns the original Rop insn for this insn, or null if this is
     * a phi insn.
     *
     * TODO: Move this up into NormalSsaInsn.
     *
     * @return {@code null-ok;} Rop insn if there is one.
     */
    abstract public Insn getOriginalRopInsn();

    /**
     * Gets the spec of a local variable assignment that occurs at this
     * instruction, or null if no local variable assignment occurs. This
     * may be the result register, or for {@code mark-local} insns
     * it may be the source.
     *
     * @see com.pojavdx.dx.rop.code.Insn#getLocalAssignment()
     *
     * @return {@code null-ok;} a local-associated register spec or null
     */
    public RegisterSpec getLocalAssignment() {
        if (result != null && result.getLocalItem() != null) {
            return result;
        }

        return null;
    }

    /**
     * Indicates whether the specified register is amongst the registers
     * used as sources for this instruction.
     *
     * @param reg the register in question
     * @return true if the reg is a source
     */
    public boolean isRegASource(int reg) {
        return null != getSources().specForRegister(reg);
    }

    /**
     * Transform back to ROP form.
     *
     * TODO: Move this up into NormalSsaInsn.
     *
     * @return {@code non-null;} a ROP representation of this instruction, with
     * updated registers.
     */
    public abstract Insn toRopInsn();

    /**
     * @return true if this is a PhiInsn or a normal move insn
     */
    public abstract boolean isPhiOrMove();

    /**
     * Returns true if this insn is considered to have a side effect beyond
     * that of assigning to the result reg.
     *
     * @return true if this insn is considered to have a side effect beyond
     * that of assigning to the result reg.
     */
    public abstract boolean hasSideEffect();

    /**
     * @return true if this is a move (but not a move-operand or
     * move-exception) instruction
     */
    public boolean isNormalMoveInsn() {
        return false;
    }

    /**
     * @return true if this is a move-exception instruction.
     * These instructions must immediately follow a preceeding invoke*
     */
    public boolean isMoveException() {
        return false;
    }

    /**
     * @return true if this instruction can throw.
     */
    abstract public boolean canThrow();

    /**
     * Accepts a visitor.
     *
     * @param v {@code non-null} the visitor
     */
    public abstract void accept(Visitor v);

    /**
     * Visitor interface for this class.
     */
    public static interface Visitor {
        /**
         * Any non-phi move instruction
         * @param insn {@code non-null;} the instruction to visit
         */
        public void visitMoveInsn(NormalSsaInsn insn);

        /**
         * Any phi insn
         * @param insn {@code non-null;} the instruction to visit
         */
        public void visitPhiInsn(PhiInsn insn);

        /**
         * Any insn that isn't a move or a phi (which is also a move).
         * @param insn {@code non-null;} the instruction to visit
         */
        public void visitNonMoveInsn(NormalSsaInsn insn);
    }
}
