/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.jikesrvm.compilers.opt.regalloc.ia32; import static org.jikesrvm.compilers.opt.driver.OptConstants.PRIMITIVE_TYPE_FOR_WORD; import static org.jikesrvm.compilers.opt.ir.Operators.BBEND; import static org.jikesrvm.compilers.opt.ir.Operators.NOP; import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_BACKEDGE; import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_EPILOGUE; import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_OSR; import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_PROLOGUE; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.ADVISE_ESP; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_ADD; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FCLEAR; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FMOV; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FMOV_opcode; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FNINIT; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FNSAVE; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_FRSTOR; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_LEA; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOV; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOVQ; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOVSD; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOVSD_opcode; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOVSS; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOVSS_opcode; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_MOV_opcode; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_POP; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_PUSH; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_RET_opcode; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_SYSCALL; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.IA32_TRAPIF; import static org.jikesrvm.compilers.opt.ir.ia32.ArchOperators.REQUIRE_ESP; import static org.jikesrvm.compilers.opt.regalloc.ia32.PhysicalRegisterConstants.DOUBLE_REG; import static org.jikesrvm.compilers.opt.regalloc.ia32.PhysicalRegisterConstants.INT_REG; import static org.jikesrvm.compilers.opt.regalloc.ia32.PhysicalRegisterConstants.SPECIAL_REG; import static org.jikesrvm.ia32.ArchConstants.SSE2_FULL; import static org.jikesrvm.ia32.StackframeLayoutConstants.OPT_SAVE_VOLATILE_SPACE_FOR_FPU_STATE; import static org.jikesrvm.ia32.StackframeLayoutConstants.OPT_SAVE_VOLATILE_SPACE_FOR_VOLATILE_GPRS; import static org.jikesrvm.ia32.StackframeLayoutConstants.OPT_SAVE_VOLATILE_TOTAL_SIZE; import static org.jikesrvm.ia32.StackframeLayoutConstants.STACKFRAME_ALIGNMENT; import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_DOUBLE; import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_FLOAT; import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_INT; import java.util.Enumeration; import java.util.Iterator; import org.jikesrvm.VM; import org.jikesrvm.classloader.TypeReference; import org.jikesrvm.compilers.opt.OptimizingCompilerException; import org.jikesrvm.compilers.opt.ir.Empty; import org.jikesrvm.compilers.opt.ir.GenericPhysicalRegisterSet; import org.jikesrvm.compilers.opt.ir.IR; import org.jikesrvm.compilers.opt.ir.Instruction; import org.jikesrvm.compilers.opt.ir.Operator; import org.jikesrvm.compilers.opt.ir.Register; import org.jikesrvm.compilers.opt.ir.ia32.MIR_BinaryAcc; import org.jikesrvm.compilers.opt.ir.ia32.MIR_FSave; import org.jikesrvm.compilers.opt.ir.ia32.MIR_Lea; import org.jikesrvm.compilers.opt.ir.ia32.MIR_Move; import org.jikesrvm.compilers.opt.ir.ia32.MIR_Nullary; import org.jikesrvm.compilers.opt.ir.ia32.MIR_TrapIf; import org.jikesrvm.compilers.opt.ir.ia32.MIR_UnaryNoRes; import org.jikesrvm.compilers.opt.ir.ia32.PhysicalDefUse; import org.jikesrvm.compilers.opt.ir.ia32.PhysicalRegisterSet; import org.jikesrvm.compilers.opt.ir.operand.MemoryOperand; import org.jikesrvm.compilers.opt.ir.operand.Operand; import org.jikesrvm.compilers.opt.ir.operand.RegisterOperand; import org.jikesrvm.compilers.opt.ir.operand.StackLocationOperand; import org.jikesrvm.compilers.opt.ir.operand.TrapCodeOperand; import org.jikesrvm.compilers.opt.ir.operand.ia32.IA32ConditionOperand; import org.jikesrvm.compilers.opt.regalloc.GenericStackManager; import org.jikesrvm.runtime.ArchEntrypoints; import org.jikesrvm.runtime.Entrypoints; import org.vmmagic.unboxed.Offset; /** * Class to manage the allocation of the "compiler-specific" portion of * the stackframe. This class holds only the architecture-specific * functions. */ public final class StackManager extends GenericStackManager { /** * the minimum size that a frame must have to be considered * a big frame for a stack overflow check. In contrast to * a small frame, a big frame is not allowed to leak into the * guard region of the stack. */ private static final int BIG_FRAME_MINIMUM_SIZE = 256; /** * the amount of bytes that a stack pointer is allowed * to differ from the stack limit beyond {@link #BIG_FRAME_MINIMUM_SIZE}. * <p> * This was set based on the historic values so it's probably * safe to change this if you have appropriate test cases. */ private static final int HISTORIC_SAFETY_MARGIN = 128; /** * the maximum difference between the stack pointer and the stack limit * that can possibly occur when handling a stack overflow */ public static final int MAX_DIFFERENCE_TO_STACK_LIMIT = BIG_FRAME_MINIMUM_SIZE + HISTORIC_SAFETY_MARGIN; /** * A frame offset for 108 bytes of stack space to store the * floating point state in the SaveVolatile protocol. */ private int fsaveLocation; /** * We allow the stack pointer to float from its normal position at the * bottom of the frame. This field holds the 'current' offset of the * SP. */ private int ESPOffset = 0; /** * Should we allow the stack pointer to float in order to avoid scratch * registers in move instructions. Note: as of Feb. 02, we think this * is a bad idea. */ private static final boolean FLOAT_ESP = false; @Override public int getFrameFixedSize() { return frameSize - WORDSIZE; } /** * @param type one of INT_VALUE, FLOAT_VALUE, or DOUBLE_VALUE * @return the size of a type of value, in bytes. * NOTE: For the purpose of register allocation, an x87 FLOAT_VALUE is 64 bits! */ private static byte getSizeOfType(Register type) { if (type.isNatural()) { if (VM.BuildFor64Addr && type.isInteger()) { return (byte) BYTES_IN_INT; } else { return (byte) WORDSIZE; } } else if (type.isFloat()) { if (SSE2_FULL) return (byte) BYTES_IN_FLOAT; return (byte) BYTES_IN_DOUBLE; } else if (type.isDouble()) { return (byte) BYTES_IN_DOUBLE; } else { OptimizingCompilerException.TODO("getSizeOfValue: unsupported: " + type); return (byte) -1; } } /** * @param type one of INT_VALUE, FLOAT_VALUE, or DOUBLE_VALUE * @return the move operator for a type of value. */ private static Operator getMoveOperator(Register type) { if (type.isNatural()) { return IA32_MOV; } else if (type.isDouble()) { return SSE2_FULL ? IA32_MOVSD : IA32_FMOV; } else if (type.isFloat()) { return SSE2_FULL ? IA32_MOVSS : IA32_FMOV; } else { OptimizingCompilerException.TODO("getMoveOperator: unsupported: " + type); return null; } } @Override public int allocateNewSpillLocation(int type) { // increment by the spill size spillPointer += getSpillSize(type); if (spillPointer + WORDSIZE > frameSize) { frameSize = spillPointer + WORDSIZE; } return spillPointer; } /** * @param type one of INT_REG, DOUBLE_REG, SPECIAL_REG * @return the spill size for a register with the given type */ @Override public int getSpillSize(int type) { if (VM.VerifyAssertions) { VM._assert((type == INT_REG) || (type == DOUBLE_REG) || (type == SPECIAL_REG)); } if (VM.BuildFor32Addr) { if (type == DOUBLE_REG) { return 8; } else { return 4; } } else { return 8; } } @Override public void insertSpillBefore(Instruction s, Register r, Register type, int location) { Operator move = getMoveOperator(type); byte size = getSizeOfType(type); RegisterOperand rOp; if (type.isFloat()) { rOp = F(r); } else if (type.isDouble()) { rOp = D(r); } else { if (VM.BuildFor64Addr && type.isInteger()) { rOp = new RegisterOperand(r, TypeReference.Int); } else { rOp = new RegisterOperand(r, PRIMITIVE_TYPE_FOR_WORD); } } StackLocationOperand spillLoc = new StackLocationOperand(true, -location, size); Instruction spillOp = MIR_Move.create(move, spillLoc, rOp); if (VERBOSE_DEBUG) { System.out.println("INSERT_SPILL_BEFORE: " + "Inserting " + spillOp + " before " + s); } s.insertBefore(spillOp); } @Override public void insertUnspillBefore(Instruction s, Register r, Register type, int location) { Operator move = getMoveOperator(type); byte size = getSizeOfType(type); RegisterOperand rOp; if (type.isFloat()) { rOp = F(r); } else if (type.isDouble()) { rOp = D(r); } else { if (VM.BuildFor64Addr && type.isInteger()) { rOp = new RegisterOperand(r, TypeReference.Int); } else { rOp = new RegisterOperand(r, PRIMITIVE_TYPE_FOR_WORD); } } StackLocationOperand spillLoc = new StackLocationOperand(true, -location, size); Instruction unspillOp = MIR_Move.create(move, rOp, spillLoc); if (VERBOSE_DEBUG) { System.out.println("INSERT_UNSPILL_BEFORE: " + "Inserting " + unspillOp + " before " + s); } s.insertBefore(unspillOp); } @Override public void computeNonVolatileArea() { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); if (ir.compiledMethod.isSaveVolatile()) { // Record that we use every nonvolatile GPR int numGprNv = PhysicalRegisterSet.getNumberOfNonvolatileGPRs(); ir.compiledMethod.setNumberOfNonvolatileGPRs((short) numGprNv); // set the frame size frameSize += numGprNv * WORDSIZE; frameSize = align(frameSize, STACKFRAME_ALIGNMENT); // TODO!! ir.compiledMethod.setNumberOfNonvolatileFPRs((short) 0); // Record that we need a stack frame. setFrameRequired(); int fpuStateSaveAreaBegin = spillPointer; // Calculate FPU state save area for restoreFloatingPointState(..) // and saveFloatingPointState(..) if (SSE2_FULL) { for (int i = 0; i < 8; i++) { fsaveLocation = allocateNewSpillLocation(DOUBLE_REG); } } else { // Grab 108 bytes (same as 27 4-byte spills) in the stack // frame, as a place to store the floating-point state with FSAVE for (int i = 0; i < 27; i++) { fsaveLocation = allocateNewSpillLocation(INT_REG); } } int fpuStateSaveAreaEnd = spillPointer; int fpuStateSize = fpuStateSaveAreaEnd - fpuStateSaveAreaBegin; if (VM.VerifyAssertions) { VM._assert(fpuStateSize == OPT_SAVE_VOLATILE_SPACE_FOR_FPU_STATE); } int volatileGPRSaveAreaBegin = spillPointer; // Map each volatile register to a spill location. int i = 0; for (Enumeration<Register> e = phys.enumerateVolatileGPRs(); e.hasMoreElements(); i++) { e.nextElement(); // Note that as a side effect, the following call bumps up the // frame size. saveVolatileGPRLocation[i] = allocateNewSpillLocation(INT_REG); } int volatileGPRSaveAreaEnd = spillPointer; int volatileGPRSaveAreaSize = volatileGPRSaveAreaEnd - volatileGPRSaveAreaBegin; if (VM.VerifyAssertions) { VM._assert(volatileGPRSaveAreaSize == OPT_SAVE_VOLATILE_SPACE_FOR_VOLATILE_GPRS); VM._assert((volatileGPRSaveAreaSize + fpuStateSize) == OPT_SAVE_VOLATILE_TOTAL_SIZE); } // Map each non-volatile register to a spill location. i = 0; for (Enumeration<Register> e = phys.enumerateNonvolatileGPRs(); e.hasMoreElements(); i++) { e.nextElement(); // Note that as a side effect, the following call bumps up the // frame size. nonVolatileGPRLocation[i] = allocateNewSpillLocation(INT_REG); } // Set the offset to find non-volatiles. int gprOffset = getNonvolatileGPROffset(0); ir.compiledMethod.setUnsignedNonVolatileOffset(gprOffset); } else { // Count the number of nonvolatiles used. int numGprNv = 0; int i = 0; for (Enumeration<Register> e = phys.enumerateNonvolatileGPRs(); e.hasMoreElements();) { Register r = e.nextElement(); if (r.isTouched()) { // Note that as a side effect, the following call bumps up the // frame size. nonVolatileGPRLocation[i++] = allocateNewSpillLocation(INT_REG); numGprNv++; } } // Update the OptCompiledMethod object. ir.compiledMethod.setNumberOfNonvolatileGPRs((short) numGprNv); if (numGprNv > 0) { int gprOffset = getNonvolatileGPROffset(0); ir.compiledMethod.setUnsignedNonVolatileOffset(gprOffset); // record that we need a stack frame setFrameRequired(); } else { ir.compiledMethod.setUnsignedNonVolatileOffset(0); } ir.compiledMethod.setNumberOfNonvolatileFPRs((short) 0); } } @Override public void cleanUpAndInsertEpilogue() { Instruction inst = ir.firstInstructionInCodeOrder().nextInstructionInCodeOrder(); for (; inst != null; inst = inst.nextInstructionInCodeOrder()) { switch (inst.getOpcode()) { case IA32_MOV_opcode: // remove frivolous moves Operand result = MIR_Move.getResult(inst); Operand val = MIR_Move.getValue(inst); if (result.similar(val)) { inst = inst.remove(); } break; case IA32_FMOV_opcode: case IA32_MOVSS_opcode: case IA32_MOVSD_opcode: // remove frivolous moves result = MIR_Move.getResult(inst); val = MIR_Move.getValue(inst); if (result.similar(val)) { inst = inst.remove(); } break; case IA32_RET_opcode: if (frameIsRequired()) { insertEpilogue(inst); } default: break; } } // now that the frame size is fixed, fix up the spill location code rewriteStackLocations(); } /** * Insert an explicit stack overflow check in the prologue <em>after</em> * buying the stack frame.<p> * * SIDE EFFECT: mutates the plg into a trap instruction. We need to * mutate so that the trap instruction is in the GC map data structures. * * @param plg the prologue instruction */ private void insertNormalStackOverflowCheck(Instruction plg) { if (!ir.method.isInterruptible()) { plg.remove(); return; } if (ir.compiledMethod.isSaveVolatile()) { return; } PhysicalRegisterSet phys = (PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet(); Register ESP = phys.getESP(); MemoryOperand M = MemoryOperand.BD(ir.regpool.makeTROp(), Entrypoints.stackLimitField.getOffset(), (byte) WORDSIZE, null, null); // Trap if ESP <= active Thread Stack Limit MIR_TrapIf.mutate(plg, IA32_TRAPIF, null, new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), M, IA32ConditionOperand.LE(), TrapCodeOperand.StackOverflow()); } /** * Insert an explicit stack overflow check in the prologue <em>before</em> * buying the stack frame. * SIDE EFFECT: mutates the plg into a trap instruction. We need to * mutate so that the trap instruction is in the GC map data structures. * * @param plg the prologue instruction */ private void insertBigFrameStackOverflowCheck(Instruction plg) { if (!ir.method.isInterruptible()) { plg.remove(); return; } if (ir.compiledMethod.isSaveVolatile()) { return; } PhysicalRegisterSet phys = (PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet(); Register ESP = phys.getESP(); Register ECX = phys.getECX(); // ECX := active Thread Stack Limit MemoryOperand M = MemoryOperand.BD(ir.regpool.makeTROp(), Entrypoints.stackLimitField.getOffset(), (byte) WORDSIZE, null, null); plg.insertBefore(MIR_Move.create(IA32_MOV, new RegisterOperand((ECX), PRIMITIVE_TYPE_FOR_WORD), M)); // ECX += frame Size int frameSize = getFrameFixedSize(); plg.insertBefore(MIR_BinaryAcc.create(IA32_ADD, new RegisterOperand(ECX, PRIMITIVE_TYPE_FOR_WORD), VM.BuildFor32Addr ? IC(frameSize) : LC(frameSize))); // Trap if ESP <= ECX MIR_TrapIf.mutate(plg, IA32_TRAPIF, null, new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), new RegisterOperand(ECX, PRIMITIVE_TYPE_FOR_WORD), IA32ConditionOperand.LE(), TrapCodeOperand.StackOverflow()); } /** * Insert the prologue for a normal method. * * Assume we are inserting the prologue for method B called from method * A. * <ul> * <li> Perform a stack overflow check. * <li> Store a back pointer to A's frame * <li> Store B's compiled method id * <li> Adjust frame pointer to point to B's frame * <li> Save any used non-volatile registers * </ul> */ @Override public void insertNormalPrologue() { PhysicalRegisterSet phys = (PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet(); Register ESP = phys.getESP(); MemoryOperand fpHome = MemoryOperand.BD(ir.regpool.makeTROp(), ArchEntrypoints.framePointerField.getOffset(), (byte) WORDSIZE, null, null); // the prologue instruction Instruction plg = ir.firstInstructionInCodeOrder().nextInstructionInCodeOrder(); // inst is the instruction immediately after the IR_PROLOGUE // instruction Instruction inst = plg.nextInstructionInCodeOrder(); int frameFixedSize = getFrameFixedSize(); ir.compiledMethod.setFrameFixedSize(frameFixedSize); // I. Buy a stackframe (including overflow check) // NOTE: We play a little game here. If the frame we are buying is // very small then we can be sloppy with the // stackoverflow check and actually allocate the frame in the guard // region. We'll notice when this frame calls someone and take the // stackoverflow in the callee. We can't do this if the frame is too big, // because growing the stack in the callee and/or handling a hardware trap // in this frame will require most of the guard region to complete. // See sysSignal_ia32.c if (frameFixedSize >= BIG_FRAME_MINIMUM_SIZE) { // 1. Insert Stack overflow check. insertBigFrameStackOverflowCheck(plg); // 2. Save caller's frame pointer inst.insertBefore(MIR_UnaryNoRes.create(IA32_PUSH, fpHome)); // 3. Set my frame pointer to current value of stackpointer inst.insertBefore(MIR_Move.create(IA32_MOV, fpHome.copy(), new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD))); // 4. Store my compiled method id int cmid = ir.compiledMethod.getId(); inst.insertBefore(MIR_UnaryNoRes.create(IA32_PUSH, VM.BuildFor32Addr ? IC(cmid) : LC(cmid))); } else { // 1. Save caller's frame pointer inst.insertBefore(MIR_UnaryNoRes.create(IA32_PUSH, fpHome)); // 2. Set my frame pointer to current value of stackpointer inst.insertBefore(MIR_Move.create(IA32_MOV, fpHome.copy(), new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD))); // 3. Store my compiled method id int cmid = ir.compiledMethod.getId(); inst.insertBefore(MIR_UnaryNoRes.create(IA32_PUSH, VM.BuildFor32Addr ? IC(cmid) : LC(cmid))); // 4. Insert Stack overflow check. insertNormalStackOverflowCheck(plg); } // II. Save any used volatile and non-volatile registers if (ir.compiledMethod.isSaveVolatile()) { saveVolatiles(inst); saveFloatingPointState(inst); } saveNonVolatiles(inst); } /** * Insert code into the prologue to save any used non-volatile * registers. * * @param inst the first instruction after the prologue. */ private void saveNonVolatiles(Instruction inst) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); int nNonvolatileGPRS = ir.compiledMethod.getNumberOfNonvolatileGPRs(); // Save each non-volatile GPR used by this method. int n = nNonvolatileGPRS - 1; for (Enumeration<Register> e = phys.enumerateNonvolatileGPRsBackwards(); e.hasMoreElements() && n >= 0; n--) { Register nv = e.nextElement(); int offset = getNonvolatileGPROffset(n); Operand M = new StackLocationOperand(true, -offset, WORDSIZE); inst.insertBefore(MIR_Move.create(IA32_MOV, M, new RegisterOperand(nv, PRIMITIVE_TYPE_FOR_WORD))); } } /** * Insert code before a return instruction to restore the nonvolatile * registers. * * @param inst the return instruction */ private void restoreNonVolatiles(Instruction inst) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); int nNonvolatileGPRS = ir.compiledMethod.getNumberOfNonvolatileGPRs(); int n = nNonvolatileGPRS - 1; for (Enumeration<Register> e = phys.enumerateNonvolatileGPRsBackwards(); e.hasMoreElements() && n >= 0; n--) { Register nv = e.nextElement(); int offset = getNonvolatileGPROffset(n); Operand M = new StackLocationOperand(true, -offset, WORDSIZE); inst.insertBefore(MIR_Move.create(IA32_MOV, new RegisterOperand(nv, PRIMITIVE_TYPE_FOR_WORD), M)); } } /** * Insert code into the prologue to save the floating point state. * * @param inst the first instruction after the prologue. */ private void saveFloatingPointState(Instruction inst) { if (SSE2_FULL) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); for (int i = 0; i < 8; i++) { inst.insertBefore(MIR_Move.create(IA32_MOVQ, new StackLocationOperand(true, -fsaveLocation + (i * BYTES_IN_DOUBLE), BYTES_IN_DOUBLE), new RegisterOperand(phys.getFPR(i), TypeReference.Double))); } } else { Operand M = new StackLocationOperand(true, -fsaveLocation, 4); inst.insertBefore(MIR_FSave.create(IA32_FNSAVE, M)); } } /** * Insert code into the epilogue to restore the floating point state. * * @param inst the return instruction after the epilogue. */ private void restoreFloatingPointState(Instruction inst) { if (SSE2_FULL) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); for (int i = 0; i < 8; i++) { inst.insertBefore(MIR_Move.create(IA32_MOVQ, new RegisterOperand(phys.getFPR(i), TypeReference.Double), new StackLocationOperand(true, -fsaveLocation + (i * BYTES_IN_DOUBLE), BYTES_IN_DOUBLE))); } } else { Operand M = new StackLocationOperand(true, -fsaveLocation, 4); inst.insertBefore(MIR_FSave.create(IA32_FRSTOR, M)); } } /** * Insert code into the prologue to save all volatile * registers. * * @param inst the first instruction after the prologue. */ private void saveVolatiles(Instruction inst) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); // Save each GPR. int i = 0; for (Enumeration<Register> e = phys.enumerateVolatileGPRs(); e.hasMoreElements(); i++) { Register r = e.nextElement(); int location = saveVolatileGPRLocation[i]; Operand M = new StackLocationOperand(true, -location, WORDSIZE); inst.insertBefore(MIR_Move.create(IA32_MOV, M, new RegisterOperand(r, PRIMITIVE_TYPE_FOR_WORD))); } } /** * Insert code before a return instruction to restore the volatile * and volatile registers. * * @param inst the return instruction */ private void restoreVolatileRegisters(Instruction inst) { GenericPhysicalRegisterSet phys = ir.regpool.getPhysicalRegisterSet(); // Restore every GPR int i = 0; for (Enumeration<Register> e = phys.enumerateVolatileGPRs(); e.hasMoreElements(); i++) { Register r = e.nextElement(); int location = saveVolatileGPRLocation[i]; Operand M = new StackLocationOperand(true, -location, WORDSIZE); inst.insertBefore(MIR_Move.create(IA32_MOV, new RegisterOperand(r, PRIMITIVE_TYPE_FOR_WORD), M)); } } /** * Insert the epilogue before a particular return instruction. * * @param ret the return instruction. */ private void insertEpilogue(Instruction ret) { // 1. Restore any saved registers if (ir.compiledMethod.isSaveVolatile()) { restoreVolatileRegisters(ret); restoreFloatingPointState(ret); } restoreNonVolatiles(ret); // 2. Restore caller's stackpointer and framepointer int frameSize = getFrameFixedSize(); ret.insertBefore(MIR_UnaryNoRes.create(REQUIRE_ESP, IC(frameSize))); MemoryOperand fpHome = MemoryOperand.BD(ir.regpool.makeTROp(), ArchEntrypoints.framePointerField.getOffset(), (byte) WORDSIZE, null, null); ret.insertBefore(MIR_Nullary.create(IA32_POP, fpHome)); } @Override public void replaceOperandWithSpillLocation(Instruction s, RegisterOperand symb) { // Get the spill location previously assigned to the symbolic // register. int location = regAllocState.getSpill(symb.getRegister()); // Create a memory operand M representing the spill location. int size; if (VM.BuildFor32Addr) { if (SSE2_FULL) { size = symb.getType().getMemoryBytes(); if (size < WORDSIZE) size = WORDSIZE; } else { int type = PhysicalRegisterSet.getPhysicalRegisterType(symb.getRegister()); size = getSpillSize(type); } } else { if (VM.BuildFor64Addr && symb.getType().getMemoryBytes() <= BYTES_IN_INT) { // Int-like types and floats need 32-bit locations size = BYTES_IN_INT; } else { size = WORDSIZE; } } StackLocationOperand M = new StackLocationOperand(true, -location, (byte) size); if (VERBOSE_DEBUG) { System.out.println("REPLACE_OP_WITH_SPILL_LOC: " + "Instruction before replacement: " + s); } // replace the register operand with the memory operand s.replaceOperand(symb, M); if (VERBOSE_DEBUG) { System.out.println("REPLACE_OP_WITH_SPILL_LOC: " + "Instruction after replacement: " + s); } } private boolean hasSymbolicRegister(MemoryOperand M) { if (M.base != null && !M.base.getRegister().isPhysical()) return true; if (M.index != null && !M.index.getRegister().isPhysical()) return true; return false; } /** * @param s the instruction to check * @return {@code true} if and only if the instruction is a MOVE instruction * that can be generated without resorting to scratch registers */ private boolean isScratchFreeMove(Instruction s) { if (s.operator() != IA32_MOV) return false; // if we don't allow ESP to float, we will always use scratch // registers in these move instructions. if (!FLOAT_ESP) return false; Operand result = MIR_Move.getResult(s); Operand value = MIR_Move.getValue(s); // TODO Remove duplication and check if code and documentation // are correct. // We need scratch registers for spilled registers that appear in // memory operands. if (result.isMemory()) { MemoryOperand M = result.asMemory(); if (hasSymbolicRegister(M)) return false; // We will perform this transformation by changing the MOV to a PUSH // or POP. Note that IA32 cannot PUSH/POP >WORDSIZE quantities, so // disable the transformation for that case. Also, (TODO), our // assembler does not emit the prefix to allow 16-bit push/pops, so // disable these too. What's left? WORDSIZE only. if (M.size != WORDSIZE) return false; } if (value.isMemory()) { MemoryOperand M = value.asMemory(); if (hasSymbolicRegister(M)) return false; // We will perform this transformation by changing the MOV to a PUSH // or POP. Note that IA32 cannot PUSH/POP >WORDSIZE quantities, so // disable the transformation for that case. Also, (TODO), our // assembler does not emit the prefix to allow 16-bit push/pops, so // disable these too. What's left? WORDSIZE only. if (M.size != WORDSIZE) return false; } // If we get here, all is kosher. return true; } @Override public boolean needScratch(Register r, Instruction s) { // We never need a scratch register for a floating point value in an // FMOV instruction. if (r.isFloatingPoint() && s.operator() == IA32_FMOV) return false; // never need a scratch register for a YIELDPOINT_OSR if (s.operator() == YIELDPOINT_OSR) return false; // Some MOVEs never need scratch registers if (isScratchFreeMove(s)) return false; // If s already has a memory operand, it is illegal to introduce // another. if (s.hasMemoryOperand()) return true; // If r appears more than once in the instruction, we can't // use a memory operand for all occurrences, so we will need a scratch int count = 0; for (Enumeration<Operand> ops = s.getOperands(); ops.hasMoreElements();) { Operand op = ops.nextElement(); if (op.isRegister()) { RegisterOperand rop = op.asRegister(); if (rop.getRegister() == r) { count++; } } } if (count > 1) return true; // Check the architecture restrictions. if (RegisterRestrictions.mustBeInRegister(r, s)) return true; // Otherwise, everything is OK. return false; } /** * Before instruction s, insert code to adjust ESP so that it lies at a * particular offset from its usual location. * * @param s the instruction before which ESP must have the desired offset * @param desiredOffset the desired offset */ private void moveESPBefore(Instruction s, int desiredOffset) { PhysicalRegisterSet phys = (PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet(); Register ESP = phys.getESP(); int delta = desiredOffset - ESPOffset; if (delta != 0) { if (canModifyEFLAGS(s)) { s.insertBefore(MIR_BinaryAcc.create(IA32_ADD, new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), VM.BuildFor32Addr ? IC(delta) : LC(delta))); } else { MemoryOperand M = MemoryOperand.BD(new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), Offset.fromIntSignExtend(delta), (byte) WORDSIZE, null, null); s.insertBefore(MIR_Lea.create(IA32_LEA, new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), M)); } ESPOffset = desiredOffset; } } private boolean canModifyEFLAGS(Instruction s) { if (PhysicalDefUse.usesEFLAGS(s.operator())) { return false; } if (PhysicalDefUse.definesEFLAGS(s.operator())) { return true; } if (s.operator() == BBEND) return true; return canModifyEFLAGS(s.nextInstructionInCodeOrder()); } /** * Attempt to rewrite a move instruction to a NOP. * * @param s the instruction to rewrite * @return {@code true} if and only if the transformation applies */ private boolean mutateMoveToNop(Instruction s) { Operand result = MIR_Move.getResult(s); Operand val = MIR_Move.getValue(s); if (result.isStackLocation() && val.isStackLocation()) { if (result.similar(val)) { Empty.mutate(s, NOP); return true; } } return false; } /** * Rewrites a move instruction if it has 2 memory operands. * One of the 2 memory operands must be a stack location operand. Move * the SP to the appropriate location and use a push or pop instruction. * * @param s the instruction to rewrite */ private void rewriteMoveInstruction(Instruction s) { // first attempt to mutate the move into a noop if (mutateMoveToNop(s)) return; Operand result = MIR_Move.getResult(s); Operand val = MIR_Move.getValue(s); if (result instanceof StackLocationOperand) { if (val instanceof MemoryOperand || val instanceof StackLocationOperand) { int offset = ((StackLocationOperand) result).getOffset(); byte size = ((StackLocationOperand) result).getSize(); offset = FPOffset2SPOffset(offset) + size; moveESPBefore(s, offset); MIR_UnaryNoRes.mutate(s, IA32_PUSH, val); } } else { if (result instanceof MemoryOperand) { if (val instanceof StackLocationOperand) { int offset = ((StackLocationOperand) val).getOffset(); offset = FPOffset2SPOffset(offset); moveESPBefore(s, offset); MIR_Nullary.mutate(s, IA32_POP, result); } } } } /** * Walks through the IR. For each StackLocationOperand, replace the * operand with the appropriate MemoryOperand. */ private void rewriteStackLocations() { // ESP is initially WORDSIZE above where the framepointer is going to be. ESPOffset = getFrameFixedSize() + WORDSIZE; Register ESP = ((PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet()).getESP(); boolean seenReturn = false; for (Enumeration<Instruction> e = ir.forwardInstrEnumerator(); e.hasMoreElements();) { Instruction s = e.nextElement(); if (s.isReturn()) { seenReturn = true; continue; } if (s.isBranch()) { // restore ESP to home location at end of basic block. moveESPBefore(s, 0); continue; } if (s.operator() == BBEND) { if (seenReturn) { // at a return ESP will be at FrameFixedSize, seenReturn = false; ESPOffset = 0; } else { moveESPBefore(s, 0); } continue; } if (s.operator() == ADVISE_ESP) { ESPOffset = MIR_UnaryNoRes.getVal(s).asIntConstant().value; continue; } if (s.operator() == REQUIRE_ESP) { // ESP is required to be at the given offset from the bottom of the frame moveESPBefore(s, MIR_UnaryNoRes.getVal(s).asIntConstant().value); continue; } if (s.operator() == YIELDPOINT_PROLOGUE || s.operator() == YIELDPOINT_BACKEDGE || s.operator() == YIELDPOINT_EPILOGUE) { moveESPBefore(s, 0); continue; } if (s.operator() == IA32_MOV) { rewriteMoveInstruction(s); } // pop computes the effective address of its operand after ESP // is incremented. Therefore update ESPOffset before rewriting // stacklocation and memory operands. if (s.operator() == IA32_POP) { ESPOffset += WORDSIZE; } for (Enumeration<Operand> ops = s.getOperands(); ops.hasMoreElements();) { Operand op = ops.nextElement(); if (op instanceof StackLocationOperand) { StackLocationOperand sop = (StackLocationOperand) op; int offset = sop.getOffset(); if (sop.isFromTop()) { offset = FPOffset2SPOffset(offset); } offset -= ESPOffset; byte size = sop.getSize(); MemoryOperand M = MemoryOperand.BD(new RegisterOperand(ESP, PRIMITIVE_TYPE_FOR_WORD), Offset.fromIntSignExtend(offset), size, null, null); s.replaceOperand(op, M); } else if (op instanceof MemoryOperand) { MemoryOperand M = op.asMemory(); if ((M.base != null && M.base.getRegister() == ESP) || (M.index != null && M.index.getRegister() == ESP)) { M.disp = M.disp.minus(ESPOffset); } } } // push computes the effective address of its operand after ESP // is decremented. Therefore update ESPOffset after rewriting // stacklocation and memory operands. if (s.operator() == IA32_PUSH) { ESPOffset -= WORDSIZE; } } } /** * PRECONDITION: The final frameSize is calculated before calling this * routine. * * @param fpOffset offset in bytes from the top of the stack frame * @return offset in bytes from the stack pointer. */ private int FPOffset2SPOffset(int fpOffset) { // Note that SP = FP - frameSize + WORDSIZE; // So, FP + fpOffset = SP + frameSize - WORDSIZE // + fpOffset return frameSize + fpOffset - WORDSIZE; } @Override public void restoreScratchRegistersBefore(Instruction s) { for (Iterator<ScratchRegister> i = scratchInUse.iterator(); i.hasNext();) { ScratchRegister scratch = i.next(); if (scratch.getCurrentContents() == null) continue; if (VERBOSE_DEBUG) { System.out.println("RESTORE: consider " + scratch); } boolean removed = false; boolean unloaded = false; if (definedIn(scratch.scratch, s) || (s.isCall() && !s.operator().isCallSaveVolatile() && scratch.scratch.isVolatile()) || (s.operator() == IA32_FNINIT && scratch.scratch.isFloatingPoint()) || (s.operator() == IA32_FCLEAR && scratch.scratch.isFloatingPoint())) { // s defines the scratch register, so save its contents before they // are killed. if (VERBOSE_DEBUG) { System.out.println("RESTORE : unload because defined " + scratch); } unloadScratchRegisterBefore(s, scratch); // update mapping information if (VERBOSE_DEBUG) { System.out.println("RSRB: End scratch interval " + scratch.scratch + " " + s); } scratchMap.endScratchInterval(scratch.scratch, s); Register scratchContents = scratch.getCurrentContents(); if (scratchContents != null) { if (VERBOSE_DEBUG) { System.out.println("RSRB: End symbolic interval " + scratch.getCurrentContents() + " " + s); } scratchMap.endSymbolicInterval(scratch.getCurrentContents(), s); } i.remove(); removed = true; unloaded = true; } if (usedIn(scratch.scratch, s) || !isLegal(scratch.getCurrentContents(), scratch.scratch, s) || (s.operator() == IA32_FCLEAR && scratch.scratch.isFloatingPoint())) { // first spill the currents contents of the scratch register to // memory if (!unloaded) { if (VERBOSE_DEBUG) { System.out.println("RESTORE : unload because used " + scratch); } unloadScratchRegisterBefore(s, scratch); // update mapping information if (VERBOSE_DEBUG) { System.out.println("RSRB2: End scratch interval " + scratch.scratch + " " + s); } scratchMap.endScratchInterval(scratch.scratch, s); Register scratchContents = scratch.getCurrentContents(); if (scratchContents != null) { if (VERBOSE_DEBUG) { System.out.println("RSRB2: End symbolic interval " + scratch.getCurrentContents() + " " + s); } scratchMap.endSymbolicInterval(scratch.getCurrentContents(), s); } } // s or some future instruction uses the scratch register, // so restore the correct contents. if (VERBOSE_DEBUG) { System.out.println("RESTORE : reload because used " + scratch); } reloadScratchRegisterBefore(s, scratch); if (!removed) { i.remove(); removed = true; } } } } /** * Initialize some architecture-specific state needed for register * allocation. * * @param ir the IR that's being processed */ @Override public void initForArch(IR ir) { PhysicalRegisterSet phys = (PhysicalRegisterSet)ir.regpool.getPhysicalRegisterSet(); // We reserve the last (bottom) slot in the FPR stack as a scratch register. // This allows us to do one push/pop sequence in order to use the // top of the stack as a scratch location phys.getFPR(7).reserveRegister(); } @Override public boolean isSysCall(Instruction s) { return s.operator() == IA32_SYSCALL; } }