/*
 * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.oracle.graal.lir.alloc.trace.lsra;

import static com.oracle.graal.lir.LIRValueUtil.asVariable;
import static com.oracle.graal.lir.LIRValueUtil.isStackSlotValue;
import static com.oracle.graal.lir.LIRValueUtil.isVariable;
import static com.oracle.graal.lir.alloc.trace.TraceRegisterAllocationPhase.Options.TraceRAshareSpillInformation;
import static com.oracle.graal.lir.alloc.trace.TraceRegisterAllocationPhase.Options.TraceRAuseInterTraceHints;
import static com.oracle.graal.lir.alloc.trace.TraceUtil.asShadowedRegisterValue;
import static com.oracle.graal.lir.alloc.trace.TraceUtil.isShadowedRegisterValue;
import static com.oracle.graal.lir.alloc.trace.lsra.TraceLinearScanPhase.isVariableOrRegister;
import static jdk.vm.ci.code.ValueUtil.asRegisterValue;
import static jdk.vm.ci.code.ValueUtil.asStackSlot;
import static jdk.vm.ci.code.ValueUtil.isRegister;
import static jdk.vm.ci.code.ValueUtil.isStackSlot;

import java.util.EnumSet;
import java.util.List;
import java.util.ListIterator;

import com.oracle.graal.compiler.common.LIRKind;
import com.oracle.graal.compiler.common.alloc.Trace;
import com.oracle.graal.compiler.common.alloc.TraceBuilderResult;
import com.oracle.graal.compiler.common.cfg.AbstractBlockBase;
import com.oracle.graal.debug.Debug;
import com.oracle.graal.debug.Debug.Scope;
import com.oracle.graal.debug.GraalError;
import com.oracle.graal.debug.Indent;
import com.oracle.graal.lir.InstructionValueConsumer;
import com.oracle.graal.lir.LIR;
import com.oracle.graal.lir.LIRInstruction;
import com.oracle.graal.lir.LIRInstruction.OperandFlag;
import com.oracle.graal.lir.LIRInstruction.OperandMode;
import com.oracle.graal.lir.LIRValueUtil;
import com.oracle.graal.lir.StandardOp.BlockEndOp;
import com.oracle.graal.lir.StandardOp.LabelOp;
import com.oracle.graal.lir.StandardOp.LoadConstantOp;
import com.oracle.graal.lir.StandardOp.ValueMoveOp;
import com.oracle.graal.lir.ValueProcedure;
import com.oracle.graal.lir.Variable;
import com.oracle.graal.lir.alloc.trace.ShadowedRegisterValue;
import com.oracle.graal.lir.alloc.trace.lsra.TraceInterval.RegisterPriority;
import com.oracle.graal.lir.alloc.trace.lsra.TraceInterval.SpillState;
import com.oracle.graal.lir.alloc.trace.lsra.TraceLinearScanPhase.TraceLinearScan;
import com.oracle.graal.lir.gen.LIRGenerationResult;
import com.oracle.graal.lir.gen.LIRGeneratorTool.MoveFactory;
import com.oracle.graal.lir.ssi.SSIUtil;

import jdk.vm.ci.code.BailoutException;
import jdk.vm.ci.code.Register;
import jdk.vm.ci.code.RegisterArray;
import jdk.vm.ci.code.RegisterValue;
import jdk.vm.ci.code.TargetDescription;
import jdk.vm.ci.meta.AllocatableValue;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.Value;

public final class TraceLinearScanLifetimeAnalysisPhase extends TraceLinearScanAllocationPhase {

    @Override
    protected void run(TargetDescription target, LIRGenerationResult lirGenRes, Trace trace, TraceLinearScanAllocationContext context) {
        TraceBuilderResult traceBuilderResult = context.resultTraces;
        TraceLinearScan allocator = context.allocator;
        new Analyser(allocator, traceBuilderResult).analyze();
    }

    public static final class Analyser {
        private static final int DUMP_DURING_ANALYSIS_LEVEL = 4;
        private final TraceLinearScan allocator;
        private final TraceBuilderResult traceBuilderResult;
        private int numInstructions;

        public Analyser(TraceLinearScan allocator, TraceBuilderResult traceBuilderResult) {
            this.allocator = allocator;
            this.traceBuilderResult = traceBuilderResult;
        }

        private AbstractBlockBase<?>[] sortedBlocks() {
            return allocator.sortedBlocks();
        }

        private LIR getLIR() {
            return allocator.getLIR();
        }

        private RegisterArray getCallerSavedRegisters() {
            return allocator.getRegisterAllocationConfig().getRegisterConfig().getCallerSaveRegisters();
        }

        public void analyze() {
            countInstructions();
            buildIntervals();
        }

        private boolean sameTrace(AbstractBlockBase<?> a, AbstractBlockBase<?> b) {
            return traceBuilderResult.getTraceForBlock(b) == traceBuilderResult.getTraceForBlock(a);
        }

        private boolean isAllocatedOrCurrent(AbstractBlockBase<?> currentBlock, AbstractBlockBase<?> other) {
            return traceBuilderResult.getTraceForBlock(other).getId() <= traceBuilderResult.getTraceForBlock(currentBlock).getId();
        }

        /**
         * Count instructions in all blocks. The numbering follows the
         * {@linkplain TraceLinearScan#sortedBlocks() register allocation order}.
         */
        private void countInstructions() {

            allocator.initIntervals();

            int numberInstructions = 0;
            for (AbstractBlockBase<?> block : sortedBlocks()) {
                numberInstructions += getLIR().getLIRforBlock(block).size();
            }
            numInstructions = numberInstructions;

            // initialize with correct length
            allocator.initOpIdMaps(numberInstructions);
        }

        private final InstructionValueConsumer outputConsumer = new InstructionValueConsumer() {
            @Override
            public void visitValue(LIRInstruction op, Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
                if (isVariableOrRegister(operand)) {
                    addDef((AllocatableValue) operand, op, registerPriorityOfOutputOperand(op));
                    addRegisterHint(op, operand, mode, flags, true);
                }
            }
        };

        private final InstructionValueConsumer tempConsumer = new InstructionValueConsumer() {
            @Override
            public void visitValue(LIRInstruction op, Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
                if (isVariableOrRegister(operand)) {
                    addTemp((AllocatableValue) operand, op.id(), RegisterPriority.MustHaveRegister);
                    addRegisterHint(op, operand, mode, flags, false);
                }
            }
        };
        private final InstructionValueConsumer aliveConsumer = new InstructionValueConsumer() {
            @Override
            public void visitValue(LIRInstruction op, Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
                if (isVariableOrRegister(operand)) {
                    RegisterPriority p = registerPriorityOfInputOperand(flags);
                    int opId = op.id();
                    int blockFrom = 0;
                    addUse((AllocatableValue) operand, blockFrom, opId + 1, p);
                    addRegisterHint(op, operand, mode, flags, false);
                }
            }
        };

        private final InstructionValueConsumer inputConsumer = new InstructionValueConsumer() {
            @Override
            public void visitValue(LIRInstruction op, Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
                if (isVariableOrRegister(operand)) {
                    int opId = op.id();
                    RegisterPriority p = registerPriorityOfInputOperand(flags);
                    int blockFrom = 0;
                    addUse((AllocatableValue) operand, blockFrom, opId, p);
                    addRegisterHint(op, operand, mode, flags, false);
                }
            }

        };

        private final InstructionValueConsumer stateProc = new InstructionValueConsumer() {
            @Override
            public void visitValue(LIRInstruction op, Value operand, OperandMode mode, EnumSet<OperandFlag> flags) {
                if (isVariableOrRegister(operand)) {
                    int opId = op.id();
                    int blockFrom = 0;
                    addUse((AllocatableValue) operand, blockFrom, opId + 1, RegisterPriority.None);
                }
            }
        };

        private void addUse(AllocatableValue operand, int from, int to, RegisterPriority registerPriority) {
            if (!allocator.isProcessed(operand)) {
                return;
            }
            if (isRegister(operand)) {
                addFixedUse(asRegisterValue(operand), from, to);
            } else {
                assert isVariable(operand) : operand;
                addVariableUse(asVariable(operand), from, to, registerPriority);
            }
        }

        private void addFixedUse(RegisterValue reg, int from, int to) {
            FixedInterval interval = allocator.getOrCreateFixedInterval(reg);
            interval.addRange(from, to);
            if (Debug.isLogEnabled()) {
                Debug.log("add fixed use: %s, at %d", interval, to);
            }
        }

        private void addVariableUse(Variable operand, int from, int to, RegisterPriority registerPriority) {
            TraceInterval interval = allocator.getOrCreateInterval(operand);
            interval.addRange(from, to);

            // Register use position at even instruction id.
            interval.addUsePos(to & ~1, registerPriority);

            if (Debug.isLogEnabled()) {
                Debug.log("add use: %s, at %d (%s)", interval, to, registerPriority.name());
            }
        }

        private void addDef(AllocatableValue operand, LIRInstruction op, RegisterPriority registerPriority) {
            if (!allocator.isProcessed(operand)) {
                return;
            }
            if (isRegister(operand)) {
                addFixedDef(asRegisterValue(operand), op);
            } else {
                assert isVariable(operand) : operand;
                addVariableDef(asVariable(operand), op, registerPriority);
            }
        }

        private void addFixedDef(RegisterValue reg, LIRInstruction op) {
            FixedInterval interval = allocator.getOrCreateFixedInterval(reg);
            int defPos = op.id();
            if (interval.from() <= defPos) {
                /*
                 * Update the starting point (when a range is first created for a use, its start is
                 * the beginning of the current block until a def is encountered).
                 */
                interval.setFrom(defPos);

            } else {
                /*
                 * Dead value - make vacuous interval also add register priority for dead intervals
                 */
                interval.addRange(defPos, defPos + 1);
                if (Debug.isLogEnabled()) {
                    Debug.log("Warning: def of operand %s at %d occurs without use", reg, defPos);
                }
            }
            if (Debug.isLogEnabled()) {
                Debug.log("add fixed def: %s, at %d", interval, defPos);
            }
        }

        private void addVariableDef(Variable operand, LIRInstruction op, RegisterPriority registerPriority) {
            int defPos = op.id();

            TraceInterval interval = allocator.getOrCreateInterval(operand);

            if (interval.isEmpty()) {
                /*
                 * Dead value - make vacuous interval also add register priority for dead intervals
                 */
                interval.addRange(defPos, defPos + 1);
                if (Debug.isLogEnabled()) {
                    Debug.log("Warning: def of operand %s at %d occurs without use", operand, defPos);
                }
            } else {
                /*
                 * Update the starting point (when a range is first created for a use, its start is
                 * the beginning of the current block until a def is encountered).
                 */
                interval.setFrom(defPos);
            }
            if (!(op instanceof LabelOp)) {
                // no use positions for labels
                interval.addUsePos(defPos, registerPriority);
            }

            changeSpillDefinitionPos(op, operand, interval, defPos);
            if (registerPriority == RegisterPriority.None && interval.spillState().ordinal() <= SpillState.StartInMemory.ordinal() && isStackSlot(operand)) {
                // detection of method-parameters and roundfp-results
                interval.setSpillState(SpillState.StartInMemory);
            }
            interval.addMaterializationValue(getMaterializedValue(op, operand, interval, allocator.neverSpillConstants(), allocator.getSpillMoveFactory()));

            if (Debug.isLogEnabled()) {
                Debug.log("add def: %s defPos %d (%s)", interval, defPos, registerPriority.name());
            }
        }

        private void addTemp(AllocatableValue operand, int tempPos, RegisterPriority registerPriority) {
            if (!allocator.isProcessed(operand)) {
                return;
            }
            if (isRegister(operand)) {
                addFixedTemp(asRegisterValue(operand), tempPos);
            } else {
                assert isVariable(operand) : operand;
                addVariableTemp(asVariable(operand), tempPos, registerPriority);
            }
        }

        private void addFixedTemp(RegisterValue reg, int tempPos) {
            FixedInterval interval = allocator.getOrCreateFixedInterval(reg);
            interval.addRange(tempPos, tempPos + 1);
            if (Debug.isLogEnabled()) {
                Debug.log("add fixed temp: %s, at %d", interval, tempPos);
            }
        }

        private void addVariableTemp(Variable operand, int tempPos, RegisterPriority registerPriority) {
            TraceInterval interval = allocator.getOrCreateInterval(operand);

            if (interval.isEmpty()) {
                interval.addRange(tempPos, tempPos + 1);
            } else if (interval.from() > tempPos) {
                interval.setFrom(tempPos);
            }

            interval.addUsePos(tempPos, registerPriority);
            interval.addMaterializationValue(null);

            if (Debug.isLogEnabled()) {
                Debug.log("add temp: %s tempPos %d (%s)", interval, tempPos, RegisterPriority.MustHaveRegister.name());
            }
        }

        /**
         * Eliminates moves from register to stack if the stack slot is known to be correct.
         *
         * @param op
         * @param operand
         */
        private void changeSpillDefinitionPos(LIRInstruction op, AllocatableValue operand, TraceInterval interval, int defPos) {
            assert interval.isSplitParent() : "can only be called for split parents";

            switch (interval.spillState()) {
                case NoDefinitionFound:
                    // assert interval.spillDefinitionPos() == -1 : "must no be set before";
                    interval.setSpillDefinitionPos(defPos);
                    if (!(op instanceof LabelOp)) {
                        // Do not update state for labels. This will be done afterwards.
                        interval.setSpillState(SpillState.NoSpillStore);
                    }
                    break;

                case NoSpillStore:
                    assert defPos <= interval.spillDefinitionPos() : "positions are processed in reverse order when intervals are created";
                    if (defPos < interval.spillDefinitionPos() - 2) {
                        /*
                         * Second definition found, so no spill optimization possible for this
                         * interval.
                         */
                        interval.setSpillState(SpillState.NoOptimization);
                    } else {
                        // two consecutive definitions (because of two-operand LIR form)
                        assert allocator.blockForId(defPos) == allocator.blockForId(interval.spillDefinitionPos()) : "block must be equal";
                    }
                    break;

                case NoOptimization:
                    // nothing to do
                    break;

                default:
                    throw new BailoutException("other states not allowed at this time");
            }
        }

        private void addRegisterHint(final LIRInstruction op, final Value targetValue, OperandMode mode, EnumSet<OperandFlag> flags, final boolean hintAtDef) {
            if (flags.contains(OperandFlag.HINT) && isVariableOrRegister(targetValue)) {

                ValueProcedure registerHintProc = new ValueProcedure() {
                    @Override
                    public Value doValue(Value registerHint, OperandMode valueMode, EnumSet<OperandFlag> valueFlags) {
                        if (isVariableOrRegister(registerHint)) {
                            /*
                             * TODO (je): clean up
                             */
                            final AllocatableValue fromValue;
                            final AllocatableValue toValue;
                            /* hints always point from def to use */
                            if (hintAtDef) {
                                fromValue = (AllocatableValue) registerHint;
                                toValue = (AllocatableValue) targetValue;
                            } else {
                                fromValue = (AllocatableValue) targetValue;
                                toValue = (AllocatableValue) registerHint;
                            }
                            Debug.log("addRegisterHint %s to %s", fromValue, toValue);
                            final TraceInterval to;
                            final IntervalHint from;
                            if (isRegister(toValue)) {
                                if (isRegister(fromValue)) {
                                    // fixed to fixed move
                                    return null;
                                }
                                from = getIntervalHint(toValue);
                                to = allocator.getOrCreateInterval(fromValue);
                            } else {
                                to = allocator.getOrCreateInterval(toValue);
                                from = getIntervalHint(fromValue);
                            }

                            to.setLocationHint(from);
                            if (Debug.isLogEnabled()) {
                                Debug.log("operation at opId %d: added hint from interval %s to %s", op.id(), from, to);
                            }

                            return registerHint;
                        }
                        return null;
                    }
                };
                op.forEachRegisterHint(targetValue, mode, registerHintProc);
            }
        }

        private static boolean optimizeMethodArgument(Value value) {
            /*
             * Object method arguments that are passed on the stack are currently not optimized
             * because this requires that the runtime visits method arguments during stack walking.
             */
            return isStackSlot(value) && asStackSlot(value).isInCallerFrame() && LIRKind.isValue(value);
        }

        /**
         * Determines the register priority for an instruction's output/result operand.
         */
        private static RegisterPriority registerPriorityOfOutputOperand(LIRInstruction op) {
            if (op instanceof LabelOp) {
                // skip method header
                return RegisterPriority.None;
            }
            if (op instanceof ValueMoveOp) {
                ValueMoveOp move = (ValueMoveOp) op;
                if (optimizeMethodArgument(move.getInput())) {
                    return RegisterPriority.None;
                }
            }

            // all other operands require a register
            return RegisterPriority.MustHaveRegister;
        }

        /**
         * Determines the priority which with an instruction's input operand will be allocated a
         * register.
         */
        private static RegisterPriority registerPriorityOfInputOperand(EnumSet<OperandFlag> flags) {
            if (flags.contains(OperandFlag.OUTGOING)) {
                return RegisterPriority.None;
            }
            if (flags.contains(OperandFlag.STACK)) {
                return RegisterPriority.ShouldHaveRegister;
            }
            // all other operands require a register
            return RegisterPriority.MustHaveRegister;
        }

        @SuppressWarnings("try")
        private void buildIntervals() {

            try (Indent indent = Debug.logAndIndent("build intervals")) {

                // create a list with all caller-save registers (cpu, fpu, xmm)
                RegisterArray callerSaveRegs = getCallerSavedRegisters();
                int instructionIndex = numInstructions;

                // iterate all blocks in reverse order
                AbstractBlockBase<?>[] blocks = sortedBlocks();
                for (int i = blocks.length - 1; i >= 0; i--) {
                    final AbstractBlockBase<?> block = blocks[i];

                    try (Indent indent2 = Debug.logAndIndent("handle block %d", block.getId())) {

                        /*
                         * Iterate all instructions of the block in reverse order. definitions of
                         * intervals are processed before uses.
                         */
                        List<LIRInstruction> instructions = getLIR().getLIRforBlock(block);
                        ListIterator<LIRInstruction> instIt = instructions.listIterator(instructions.size());
                        while (instIt.hasPrevious()) {
                            final LIRInstruction op = instIt.previous();
                            // number instruction
                            instructionIndex--;
                            final int opId = instructionIndex << 1;
                            numberInstruction(block, op, instructionIndex);

                            try (Indent indent3 = Debug.logAndIndent("handle inst %d: %s", opId, op)) {

                                /*
                                 * Add a temp range for each register if operation destroys
                                 * caller-save registers.
                                 */
                                if (op.destroysCallerSavedRegisters()) {
                                    for (Register r : callerSaveRegs) {
                                        if (allocator.attributes(r).isAllocatable()) {
                                            addTemp(r.asValue(), opId, RegisterPriority.None);
                                        }
                                    }
                                    if (Debug.isLogEnabled()) {
                                        Debug.log("operation destroys all caller-save registers");
                                    }
                                }

                                op.visitEachOutput(outputConsumer);
                                op.visitEachTemp(tempConsumer);
                                op.visitEachAlive(aliveConsumer);
                                op.visitEachInput(inputConsumer);

                                /*
                                 * Add uses of live locals from interpreter's point of view for
                                 * proper debug information generation. Treat these operands as temp
                                 * values (if the live range is extended to a call site, the value
                                 * would be in a register at the call otherwise).
                                 */
                                op.visitEachState(stateProc);
                            }

                        }   // end of instruction iteration
                    }
                    if (Debug.isDumpEnabled(DUMP_DURING_ANALYSIS_LEVEL)) {
                        allocator.printIntervals("After Block " + block);
                    }
                }   // end of block iteration
                assert instructionIndex == 0 : "not at start?" + instructionIndex;

                // fix spill state for phi/sigma intervals
                for (TraceInterval interval : allocator.intervals()) {
                    if (interval != null && interval.spillState().equals(SpillState.NoDefinitionFound) && interval.spillDefinitionPos() != -1) {
                        // there was a definition in a phi/sigma
                        interval.setSpillState(SpillState.NoSpillStore);
                    }
                }
                if (TraceRAuseInterTraceHints.getValue()) {
                    addInterTraceHints();
                }
                for (FixedInterval interval1 : allocator.fixedIntervals()) {
                    if (interval1 != null) {
                        /* We use [-1, 0] to avoid intersection with incoming values. */
                        interval1.addRange(-1, 0);
                    }
                }
            }
        }

        private void numberInstruction(AbstractBlockBase<?> block, LIRInstruction op, int index) {
            int opId = index << 1;
            assert op.id() == -1 || op.id() == opId : "must match";
            op.setId(opId);
            allocator.putOpIdMaps(index, op, block);
            assert allocator.instructionForId(opId) == op : "must match";
        }

        @SuppressWarnings("try")
        private void addInterTraceHints() {
            try (Scope s = Debug.scope("InterTraceHints", allocator)) {
                // set hints for phi/sigma intervals
                for (AbstractBlockBase<?> block : sortedBlocks()) {
                    LabelOp label = SSIUtil.incoming(getLIR(), block);
                    for (AbstractBlockBase<?> pred : block.getPredecessors()) {
                        if (isAllocatedOrCurrent(block, pred)) {
                            BlockEndOp outgoing = SSIUtil.outgoing(getLIR(), pred);
                            // do not look at phi variables as they are not same value!
                            for (int i = outgoing.getPhiSize(); i < outgoing.getOutgoingSize(); i++) {
                                Value toValue = label.getIncomingValue(i);
                                assert !isShadowedRegisterValue(toValue) : "Shadowed Registers are not allowed here: " + toValue;
                                if (isVariable(toValue)) {
                                    Value fromValue = outgoing.getOutgoingValue(i);
                                    assert sameTrace(block, pred) || !isVariable(fromValue) : "Unallocated variable: " + fromValue;
                                    if (!LIRValueUtil.isConstantValue(fromValue)) {
                                        addInterTraceHint(label, (AllocatableValue) toValue, fromValue);
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Throwable e) {
                throw Debug.handle(e);
            }
        }

        private void addInterTraceHint(LabelOp label, AllocatableValue toValue, Value fromValue) {
            assert isVariable(toValue) : "Wrong toValue: " + toValue;
            assert isRegister(fromValue) || isVariable(fromValue) || isStackSlotValue(fromValue) || isShadowedRegisterValue(fromValue) : "Wrong fromValue: " + fromValue;
            TraceInterval to = allocator.getOrCreateInterval(toValue);
            if (isVariableOrRegister(fromValue)) {
                IntervalHint from = getIntervalHint((AllocatableValue) fromValue);
                setHint(label, to, from);
            } else if (isStackSlotValue(fromValue)) {
                setSpillSlot(label, to, (AllocatableValue) fromValue);
            } else if (TraceRAshareSpillInformation.getValue() && isShadowedRegisterValue(fromValue)) {
                ShadowedRegisterValue shadowedRegisterValue = asShadowedRegisterValue(fromValue);
                IntervalHint from = getIntervalHint(shadowedRegisterValue.getRegister());
                setHint(label, to, from);
                setSpillSlot(label, to, shadowedRegisterValue.getStackSlot());
            } else {
                throw GraalError.shouldNotReachHere();
            }
        }

        private static void setHint(final LIRInstruction op, TraceInterval to, IntervalHint from) {
            IntervalHint currentHint = to.locationHint(false);
            if (currentHint == null) {
                /*
                 * Update hint if there was none or if the hint interval starts after the hinted
                 * interval.
                 */
                to.setLocationHint(from);
                if (Debug.isLogEnabled()) {
                    Debug.log("operation at opId %d: added hint from interval %s to %s", op.id(), from, to);
                }
            }
        }

        private static void setSpillSlot(LIRInstruction op, TraceInterval interval, AllocatableValue spillSlot) {
            if (interval.spillSlot() == null) {
                interval.setSpillSlot(spillSlot);
                interval.setSpillState(SpillState.StartInMemory);
                if (Debug.isLogEnabled()) {
                    Debug.log("operation at opId %d: added spill slot %s to interval %s", op.id(), spillSlot, interval);
                }
            } else if (Debug.isLogEnabled()) {
                Debug.log("operation at opId %d: has already a slot assigned %s", op.id(), interval.spillSlot());
            }
        }

        private IntervalHint getIntervalHint(AllocatableValue from) {
            if (isRegister(from)) {
                return allocator.getOrCreateFixedInterval(asRegisterValue(from));
            }
            return allocator.getOrCreateInterval(from);
        }

    }

    /**
     * Returns a value for a interval definition, which can be used for re-materialization.
     *
     * @param op An instruction which defines a value
     * @param operand The destination operand of the instruction
     * @param interval The interval for this defined value.
     * @return Returns the value which is moved to the instruction and which can be reused at all
     *         reload-locations in case the interval of this instruction is spilled. Currently this
     *         can only be a {@link JavaConstant}.
     */
    private static JavaConstant getMaterializedValue(LIRInstruction op, Value operand, TraceInterval interval, boolean neverSpillConstants, MoveFactory spillMoveFactory) {
        if (op instanceof LoadConstantOp) {
            LoadConstantOp move = (LoadConstantOp) op;
            if (move.getConstant() instanceof JavaConstant) {
                if (!neverSpillConstants) {
                    if (!spillMoveFactory.allowConstantToStackMove(move.getConstant())) {
                        return null;
                    }
                    /*
                     * Check if the interval has any uses which would accept an stack location
                     * (priority == ShouldHaveRegister). Rematerialization of such intervals can
                     * result in a degradation, because rematerialization always inserts a constant
                     * load, even if the value is not needed in a register.
                     */
                    int numUsePos = interval.numUsePos();
                    for (int useIdx = 0; useIdx < numUsePos; useIdx++) {
                        TraceInterval.RegisterPriority priority = interval.getUsePosRegisterPriority(useIdx);
                        if (priority == TraceInterval.RegisterPriority.ShouldHaveRegister) {
                            return null;
                        }
                    }
                }
                return (JavaConstant) move.getConstant();
            }
        }
        return null;
    }

}
