/* * Copyright (C) 2011 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef DFGJITCodeGenerator_h #define DFGJITCodeGenerator_h #if ENABLE(DFG_JIT) #include "CodeBlock.h" #include <dfg/DFGGenerationInfo.h> #include <dfg/DFGGraph.h> #include <dfg/DFGJITCompiler.h> #include <dfg/DFGOperations.h> #include <dfg/DFGRegisterBank.h> namespace JSC { namespace DFG { class SpeculateIntegerOperand; class SpeculateStrictInt32Operand; class SpeculateCellOperand; // === JITCodeGenerator === // // This class provides common infrastructure used by the speculative & // non-speculative JITs. Provides common mechanisms for virtual and // physical register management, calls out from JIT code to helper // functions, etc. class JITCodeGenerator { protected: typedef MacroAssembler::TrustedImm32 TrustedImm32; typedef MacroAssembler::Imm32 Imm32; // These constants are used to set priorities for spill order for // the register allocator. enum SpillOrder { SpillOrderNone, SpillOrderConstant = 1, // no spill, and cheap fill SpillOrderSpilled = 2, // no spill SpillOrderJS = 4, // needs spill SpillOrderCell = 4, // needs spill SpillOrderInteger = 5, // needs spill and box SpillOrderDouble = 6, // needs spill and convert SpillOrderMax }; public: GPRReg fillInteger(NodeIndex, DataFormat& returnFormat); FPRReg fillDouble(NodeIndex); GPRReg fillJSValue(NodeIndex); // lock and unlock GPR & FPR registers. void lock(GPRReg reg) { m_gprs.lock(reg); } void lock(FPRReg reg) { m_fprs.lock(reg); } void unlock(GPRReg reg) { m_gprs.unlock(reg); } void unlock(FPRReg reg) { m_fprs.unlock(reg); } // Used to check whether a child node is on its last use, // and its machine registers may be reused. bool canReuse(NodeIndex nodeIndex) { VirtualRegister virtualRegister = m_jit.graph()[nodeIndex].virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; return info.canReuse(); } GPRReg reuse(GPRReg reg) { m_gprs.lock(reg); return reg; } FPRReg reuse(FPRReg reg) { m_fprs.lock(reg); return reg; } // Allocate a gpr/fpr. GPRReg allocate() { VirtualRegister spillMe; GPRReg gpr = m_gprs.allocate(spillMe); if (spillMe != InvalidVirtualRegister) spill(spillMe); return gpr; } FPRReg fprAllocate() { VirtualRegister spillMe; FPRReg fpr = m_fprs.allocate(spillMe); if (spillMe != InvalidVirtualRegister) spill(spillMe); return fpr; } // Check whether a VirtualRegsiter is currently in a machine register. // We use this when filling operands to fill those that are already in // machine registers first (by locking VirtualRegsiters that are already // in machine register before filling those that are not we attempt to // avoid spilling values we will need immediately). bool isFilled(NodeIndex nodeIndex) { VirtualRegister virtualRegister = m_jit.graph()[nodeIndex].virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; return info.registerFormat() != DataFormatNone; } bool isFilledDouble(NodeIndex nodeIndex) { VirtualRegister virtualRegister = m_jit.graph()[nodeIndex].virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; return info.registerFormat() == DataFormatDouble; } protected: JITCodeGenerator(JITCompiler& jit, bool isSpeculative) : m_jit(jit) , m_isSpeculative(isSpeculative) , m_compileIndex(0) , m_generationInfo(m_jit.codeBlock()->m_numCalleeRegisters) , m_blockHeads(jit.graph().m_blocks.size()) { } // These methods convert between doubles, and doubles boxed and JSValues. GPRReg boxDouble(FPRReg fpr, GPRReg gpr) { JITCompiler::FPRegisterID fpReg = JITCompiler::fprToRegisterID(fpr); JITCompiler::RegisterID reg = JITCompiler::gprToRegisterID(gpr); m_jit.moveDoubleToPtr(fpReg, reg); m_jit.subPtr(JITCompiler::tagTypeNumberRegister, reg); return gpr; } FPRReg unboxDouble(GPRReg gpr, FPRReg fpr) { JITCompiler::RegisterID reg = JITCompiler::gprToRegisterID(gpr); JITCompiler::FPRegisterID fpReg = JITCompiler::fprToRegisterID(fpr); m_jit.addPtr(JITCompiler::tagTypeNumberRegister, reg); m_jit.movePtrToDouble(reg, fpReg); return fpr; } GPRReg boxDouble(FPRReg fpr) { return boxDouble(fpr, allocate()); } FPRReg unboxDouble(GPRReg gpr) { return unboxDouble(gpr, fprAllocate()); } // Called on an operand once it has been consumed by a parent node. void use(NodeIndex nodeIndex) { VirtualRegister virtualRegister = m_jit.graph()[nodeIndex].virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; // use() returns true when the value becomes dead, and any // associated resources may be freed. if (!info.use()) return; // Release the associated machine registers. DataFormat registerFormat = info.registerFormat(); if (registerFormat == DataFormatDouble) m_fprs.release(info.fpr()); else if (registerFormat != DataFormatNone) m_gprs.release(info.gpr()); } // Spill a VirtualRegister to the RegisterFile. void spill(VirtualRegister spillMe) { GenerationInfo& info = m_generationInfo[spillMe]; // Check the GenerationInfo to see if this value need writing // to the RegisterFile - if not, mark it as spilled & return. if (!info.needsSpill()) { info.setSpilled(); return; } DataFormat spillFormat = info.registerFormat(); if (spillFormat == DataFormatDouble) { // All values are spilled as JSValues, so box the double via a temporary gpr. GPRReg gpr = boxDouble(info.fpr()); m_jit.storePtr(JITCompiler::gprToRegisterID(gpr), JITCompiler::addressFor(spillMe)); unlock(gpr); info.spill(DataFormatJSDouble); return; } // The following code handles JSValues, int32s, and cells. ASSERT(spillFormat == DataFormatInteger || spillFormat == DataFormatCell || spillFormat & DataFormatJS); JITCompiler::RegisterID reg = JITCompiler::gprToRegisterID(info.gpr()); // We need to box int32 and cell values ... // but on JSVALUE64 boxing a cell is a no-op! if (spillFormat == DataFormatInteger) m_jit.orPtr(JITCompiler::tagTypeNumberRegister, reg); // Spill the value, and record it as spilled in its boxed form. m_jit.storePtr(reg, JITCompiler::addressFor(spillMe)); info.spill((DataFormat)(spillFormat | DataFormatJS)); } // Checks/accessors for constant values. bool isConstant(NodeIndex nodeIndex) { return m_jit.isConstant(nodeIndex); } bool isInt32Constant(NodeIndex nodeIndex) { return m_jit.isInt32Constant(nodeIndex); } bool isDoubleConstant(NodeIndex nodeIndex) { return m_jit.isDoubleConstant(nodeIndex); } bool isJSConstant(NodeIndex nodeIndex) { return m_jit.isJSConstant(nodeIndex); } int32_t valueOfInt32Constant(NodeIndex nodeIndex) { return m_jit.valueOfInt32Constant(nodeIndex); } double valueOfDoubleConstant(NodeIndex nodeIndex) { return m_jit.valueOfDoubleConstant(nodeIndex); } JSValue valueOfJSConstant(NodeIndex nodeIndex) { return m_jit.valueOfJSConstant(nodeIndex); } Identifier* identifier(unsigned index) { return &m_jit.codeBlock()->identifier(index); } // Spill all VirtualRegisters back to the RegisterFile. void flushRegisters() { for (GPRReg gpr = gpr0; gpr < numberOfGPRs; next(gpr)) { VirtualRegister name = m_gprs.name(gpr); if (name != InvalidVirtualRegister) { spill(name); m_gprs.release(gpr); } } for (FPRReg fpr = fpr0; fpr < numberOfFPRs; next(fpr)) { VirtualRegister name = m_fprs.name(fpr); if (name != InvalidVirtualRegister) { spill(name); m_fprs.release(fpr); } } } #ifndef NDEBUG // Used to ASSERT flushRegisters() has been called prior to // calling out from JIT code to a C helper function. bool isFlushed() { for (GPRReg gpr = gpr0; gpr < numberOfGPRs; next(gpr)) { VirtualRegister name = m_gprs.name(gpr); if (name != InvalidVirtualRegister) return false; } for (FPRReg fpr = fpr0; fpr < numberOfFPRs; next(fpr)) { VirtualRegister name = m_fprs.name(fpr); if (name != InvalidVirtualRegister) return false; } return true; } #endif // Get the JSValue representation of a constant. JSValue constantAsJSValue(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; if (isInt32Constant(nodeIndex)) return jsNumber(node.int32Constant()); if (isDoubleConstant(nodeIndex)) return JSValue(JSValue::EncodeAsDouble, node.numericConstant()); ASSERT(isJSConstant(nodeIndex)); return valueOfJSConstant(nodeIndex); } MacroAssembler::ImmPtr constantAsJSValueAsImmPtr(NodeIndex nodeIndex) { return MacroAssembler::ImmPtr(JSValue::encode(constantAsJSValue(nodeIndex))); } // Helper functions to enable code sharing in implementations of bit/shift ops. void bitOp(NodeType op, int32_t imm, MacroAssembler::RegisterID op1, MacroAssembler::RegisterID result) { switch (op) { case BitAnd: m_jit.and32(Imm32(imm), op1, result); break; case BitOr: m_jit.or32(Imm32(imm), op1, result); break; case BitXor: m_jit.xor32(Imm32(imm), op1, result); break; default: ASSERT_NOT_REACHED(); } } void bitOp(NodeType op, MacroAssembler::RegisterID op1, MacroAssembler::RegisterID op2, MacroAssembler::RegisterID result) { switch (op) { case BitAnd: m_jit.and32(op1, op2, result); break; case BitOr: m_jit.or32(op1, op2, result); break; case BitXor: m_jit.xor32(op1, op2, result); break; default: ASSERT_NOT_REACHED(); } } void shiftOp(NodeType op, MacroAssembler::RegisterID op1, int32_t shiftAmount, MacroAssembler::RegisterID result) { switch (op) { case BitRShift: m_jit.rshift32(op1, Imm32(shiftAmount), result); break; case BitLShift: m_jit.lshift32(op1, Imm32(shiftAmount), result); break; case BitURShift: m_jit.urshift32(op1, Imm32(shiftAmount), result); break; default: ASSERT_NOT_REACHED(); } } void shiftOp(NodeType op, MacroAssembler::RegisterID op1, MacroAssembler::RegisterID shiftAmount, MacroAssembler::RegisterID result) { switch (op) { case BitRShift: m_jit.rshift32(op1, shiftAmount, result); break; case BitLShift: m_jit.lshift32(op1, shiftAmount, result); break; case BitURShift: m_jit.urshift32(op1, shiftAmount, result); break; default: ASSERT_NOT_REACHED(); } } // Called once a node has completed code generation but prior to setting // its result, to free up its children. (This must happen prior to setting // the nodes result, since the node may have the same VirtualRegister as // a child, and as such will use the same GeneratioInfo). void useChildren(Node&); // These method called to initialize the the GenerationInfo // to describe the result of an operation. void integerResult(GPRReg reg, NodeIndex nodeIndex, DataFormat format = DataFormatInteger) { Node& node = m_jit.graph()[nodeIndex]; useChildren(node); VirtualRegister virtualRegister = node.virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; if (format == DataFormatInteger) { m_jit.jitAssertIsInt32(reg); m_gprs.retain(reg, virtualRegister, SpillOrderInteger); info.initInteger(nodeIndex, node.refCount, reg); } else { ASSERT(format == DataFormatJSInteger); m_jit.jitAssertIsJSInt32(reg); m_gprs.retain(reg, virtualRegister, SpillOrderJS); info.initJSValue(nodeIndex, node.refCount, reg, format); } } void noResult(NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; useChildren(node); VirtualRegister virtualRegister = node.virtualRegister; GenerationInfo& info = m_generationInfo[virtualRegister]; info.initNone(nodeIndex, node.refCount); } void cellResult(GPRReg reg, NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; useChildren(node); VirtualRegister virtualRegister = node.virtualRegister; m_gprs.retain(reg, virtualRegister, SpillOrderCell); GenerationInfo& info = m_generationInfo[virtualRegister]; info.initCell(nodeIndex, node.refCount, reg); } void jsValueResult(GPRReg reg, NodeIndex nodeIndex, DataFormat format = DataFormatJS) { if (format == DataFormatJSInteger) m_jit.jitAssertIsJSInt32(reg); Node& node = m_jit.graph()[nodeIndex]; useChildren(node); VirtualRegister virtualRegister = node.virtualRegister; m_gprs.retain(reg, virtualRegister, SpillOrderJS); GenerationInfo& info = m_generationInfo[virtualRegister]; info.initJSValue(nodeIndex, node.refCount, reg, format); } void doubleResult(FPRReg reg, NodeIndex nodeIndex) { Node& node = m_jit.graph()[nodeIndex]; useChildren(node); VirtualRegister virtualRegister = node.virtualRegister; m_fprs.retain(reg, virtualRegister, SpillOrderDouble); GenerationInfo& info = m_generationInfo[virtualRegister]; info.initDouble(nodeIndex, node.refCount, reg); } void initConstantInfo(NodeIndex nodeIndex) { ASSERT(isInt32Constant(nodeIndex) || isDoubleConstant(nodeIndex) || isJSConstant(nodeIndex)); Node& node = m_jit.graph()[nodeIndex]; m_generationInfo[node.virtualRegister].initConstant(nodeIndex, node.refCount); } // These methods used to sort arguments into the correct registers. template<GPRReg destA, GPRReg destB> void setupTwoStubArgs(GPRReg srcA, GPRReg srcB) { // Assuming that srcA != srcB, there are 7 interesting states the registers may be in: // (1) both are already in arg regs, the right way around. // (2) both are already in arg regs, the wrong way around. // (3) neither are currently in arg registers. // (4) srcA in in its correct reg. // (5) srcA in in the incorrect reg. // (6) srcB in in its correct reg. // (7) srcB in in the incorrect reg. // // The trivial approach is to simply emit two moves, to put srcA in place then srcB in // place (the MacroAssembler will omit redundant moves). This apporach will be safe in // cases 1, 3, 4, 5, 6, and in cases where srcA==srcB. The two problem cases are 2 // (requires a swap) and 7 (must move srcB first, to avoid trampling.) if (srcB != destA) { // Handle the easy cases - two simple moves. m_jit.move(JITCompiler::gprToRegisterID(srcA), JITCompiler::gprToRegisterID(destA)); m_jit.move(JITCompiler::gprToRegisterID(srcB), JITCompiler::gprToRegisterID(destB)); } else if (srcA != destB) { // Handle the non-swap case - just put srcB in place first. m_jit.move(JITCompiler::gprToRegisterID(srcB), JITCompiler::gprToRegisterID(destB)); m_jit.move(JITCompiler::gprToRegisterID(srcA), JITCompiler::gprToRegisterID(destA)); } else m_jit.swap(JITCompiler::gprToRegisterID(destB), JITCompiler::gprToRegisterID(destB)); } template<FPRReg destA, FPRReg destB> void setupTwoStubArgs(FPRReg srcA, FPRReg srcB) { // Assuming that srcA != srcB, there are 7 interesting states the registers may be in: // (1) both are already in arg regs, the right way around. // (2) both are already in arg regs, the wrong way around. // (3) neither are currently in arg registers. // (4) srcA in in its correct reg. // (5) srcA in in the incorrect reg. // (6) srcB in in its correct reg. // (7) srcB in in the incorrect reg. // // The trivial approach is to simply emit two moves, to put srcA in place then srcB in // place (the MacroAssembler will omit redundant moves). This apporach will be safe in // cases 1, 3, 4, 5, 6, and in cases where srcA==srcB. The two problem cases are 2 // (requires a swap) and 7 (must move srcB first, to avoid trampling.) if (srcB != destA) { // Handle the easy cases - two simple moves. m_jit.moveDouble(JITCompiler::fprToRegisterID(srcA), JITCompiler::fprToRegisterID(destA)); m_jit.moveDouble(JITCompiler::fprToRegisterID(srcB), JITCompiler::fprToRegisterID(destB)); return; } if (srcA != destB) { // Handle the non-swap case - just put srcB in place first. m_jit.moveDouble(JITCompiler::fprToRegisterID(srcB), JITCompiler::fprToRegisterID(destB)); m_jit.moveDouble(JITCompiler::fprToRegisterID(srcA), JITCompiler::fprToRegisterID(destA)); return; } ASSERT(srcB == destA && srcA == destB); // Need to swap; pick a temporary register. FPRReg temp; if (destA != JITCompiler::argumentFPR3 && destA != JITCompiler::argumentFPR3) temp = JITCompiler::argumentFPR3; else if (destA != JITCompiler::argumentFPR2 && destA != JITCompiler::argumentFPR2) temp = JITCompiler::argumentFPR2; else { ASSERT(destA != JITCompiler::argumentFPR1 && destA != JITCompiler::argumentFPR1); temp = JITCompiler::argumentFPR1; } m_jit.moveDouble(JITCompiler::fprToRegisterID(destA), JITCompiler::fprToRegisterID(temp)); m_jit.moveDouble(JITCompiler::fprToRegisterID(destB), JITCompiler::fprToRegisterID(destA)); m_jit.moveDouble(JITCompiler::fprToRegisterID(temp), JITCompiler::fprToRegisterID(destB)); } void setupStubArguments(GPRReg arg1, GPRReg arg2) { setupTwoStubArgs<JITCompiler::argumentGPR1, JITCompiler::argumentGPR2>(arg1, arg2); } void setupStubArguments(GPRReg arg1, GPRReg arg2, GPRReg arg3) { // If neither of arg2/arg3 are in our way, then we can move arg1 into place. // Then we can use setupTwoStubArgs to fix arg2/arg3. if (arg2 != JITCompiler::argumentGPR1 && arg3 != JITCompiler::argumentGPR1) { m_jit.move(JITCompiler::gprToRegisterID(arg1), JITCompiler::argumentRegister1); setupTwoStubArgs<JITCompiler::argumentGPR2, JITCompiler::argumentGPR3>(arg2, arg3); return; } // If neither of arg1/arg3 are in our way, then we can move arg2 into place. // Then we can use setupTwoStubArgs to fix arg1/arg3. if (arg1 != JITCompiler::argumentGPR2 && arg3 != JITCompiler::argumentGPR2) { m_jit.move(JITCompiler::gprToRegisterID(arg2), JITCompiler::argumentRegister2); setupTwoStubArgs<JITCompiler::argumentGPR1, JITCompiler::argumentGPR3>(arg1, arg3); return; } // If neither of arg1/arg2 are in our way, then we can move arg3 into place. // Then we can use setupTwoStubArgs to fix arg1/arg2. if (arg1 != JITCompiler::argumentGPR3 && arg2 != JITCompiler::argumentGPR3) { m_jit.move(JITCompiler::gprToRegisterID(arg3), JITCompiler::argumentRegister3); setupTwoStubArgs<JITCompiler::argumentGPR1, JITCompiler::argumentGPR2>(arg1, arg2); return; } // If we get here, we haven't been able to move any of arg1/arg2/arg3. // Since all three are blocked, then all three must already be in the argument register. // But are they in the right ones? // First, ensure arg1 is in place. if (arg1 != JITCompiler::argumentGPR1) { m_jit.swap(JITCompiler::gprToRegisterID(arg1), JITCompiler::argumentRegister1); // If arg1 wasn't in argumentGPR1, one of arg2/arg3 must be. ASSERT(arg2 == JITCompiler::argumentGPR1 || arg3 == JITCompiler::argumentGPR1); // If arg2 was in argumentGPR1 it no longer is (due to the swap). // Otherwise arg3 must have been. Mark him as moved. if (arg2 == JITCompiler::argumentGPR1) arg2 = arg1; else arg3 = arg1; } // Either arg2 & arg3 need swapping, or we're all done. ASSERT((arg2 == JITCompiler::argumentGPR2 || arg3 == JITCompiler::argumentGPR3) || (arg2 == JITCompiler::argumentGPR3 || arg3 == JITCompiler::argumentGPR2)); if (arg2 != JITCompiler::argumentGPR2) m_jit.swap(JITCompiler::argumentRegister2, JITCompiler::argumentRegister3); } // These methods add calls to C++ helper functions. void callOperation(J_DFGOperation_EJP operation, GPRReg result, GPRReg arg1, void* pointer) { ASSERT(isFlushed()); m_jit.move(JITCompiler::gprToRegisterID(arg1), JITCompiler::argumentRegister1); m_jit.move(JITCompiler::TrustedImmPtr(pointer), JITCompiler::argumentRegister2); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); m_jit.move(JITCompiler::returnValueRegister, JITCompiler::gprToRegisterID(result)); } void callOperation(J_DFGOperation_EJI operation, GPRReg result, GPRReg arg1, Identifier* identifier) { callOperation((J_DFGOperation_EJP)operation, result, arg1, identifier); } void callOperation(J_DFGOperation_EJ operation, GPRReg result, GPRReg arg1) { ASSERT(isFlushed()); m_jit.move(JITCompiler::gprToRegisterID(arg1), JITCompiler::argumentRegister1); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); m_jit.move(JITCompiler::returnValueRegister, JITCompiler::gprToRegisterID(result)); } void callOperation(Z_DFGOperation_EJ operation, GPRReg result, GPRReg arg1) { ASSERT(isFlushed()); m_jit.move(JITCompiler::gprToRegisterID(arg1), JITCompiler::argumentRegister1); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); m_jit.move(JITCompiler::returnValueRegister, JITCompiler::gprToRegisterID(result)); } void callOperation(Z_DFGOperation_EJJ operation, GPRReg result, GPRReg arg1, GPRReg arg2) { ASSERT(isFlushed()); setupStubArguments(arg1, arg2); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); m_jit.move(JITCompiler::returnValueRegister, JITCompiler::gprToRegisterID(result)); } void callOperation(J_DFGOperation_EJJ operation, GPRReg result, GPRReg arg1, GPRReg arg2) { ASSERT(isFlushed()); setupStubArguments(arg1, arg2); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); m_jit.move(JITCompiler::returnValueRegister, JITCompiler::gprToRegisterID(result)); } void callOperation(V_DFGOperation_EJJP operation, GPRReg arg1, GPRReg arg2, void* pointer) { ASSERT(isFlushed()); setupStubArguments(arg1, arg2); m_jit.move(JITCompiler::TrustedImmPtr(pointer), JITCompiler::argumentRegister3); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); } void callOperation(V_DFGOperation_EJJI operation, GPRReg arg1, GPRReg arg2, Identifier* identifier) { callOperation((V_DFGOperation_EJJP)operation, arg1, arg2, identifier); } void callOperation(V_DFGOperation_EJJJ operation, GPRReg arg1, GPRReg arg2, GPRReg arg3) { ASSERT(isFlushed()); setupStubArguments(arg1, arg2, arg3); m_jit.move(JITCompiler::callFrameRegister, JITCompiler::argumentRegister0); appendCallWithExceptionCheck(operation); } void callOperation(D_DFGOperation_DD operation, FPRReg result, FPRReg arg1, FPRReg arg2) { ASSERT(isFlushed()); setupTwoStubArgs<JITCompiler::argumentFPR0, JITCompiler::argumentFPR1>(arg1, arg2); m_jit.appendCall(operation); m_jit.moveDouble(JITCompiler::fpReturnValueRegister, JITCompiler::fprToRegisterID(result)); } void appendCallWithExceptionCheck(const FunctionPtr& function) { m_jit.appendCallWithExceptionCheck(function, m_jit.graph()[m_compileIndex].exceptionInfo); } void addBranch(const MacroAssembler::Jump& jump, BlockIndex destination) { m_branches.append(BranchRecord(jump, destination)); } void linkBranches() { for (size_t i = 0; i < m_branches.size(); ++i) { BranchRecord& branch = m_branches[i]; branch.jump.linkTo(m_blockHeads[branch.destination], &m_jit); } } #ifndef NDEBUG void dump(const char* label = 0); #endif #if DFG_CONSISTENCY_CHECK void checkConsistency(); #else void checkConsistency() {} #endif // The JIT, while also provides MacroAssembler functionality. JITCompiler& m_jit; // This flag is used to distinguish speculative and non-speculative // code generation. This is significant when filling spilled values // from the RegisterFile. When spilling we attempt to store information // as to the type of boxed value being stored (int32, double, cell), and // when filling on the speculative path we will retrieve this type info // where available. On the non-speculative path, however, we cannot rely // on the spill format info, since the a value being loaded might have // been spilled by either the speculative or non-speculative paths (where // we entered the non-speculative path on an intervening bail-out), and // the value may have been boxed differently on the two paths. bool m_isSpeculative; // The current node being generated. BlockIndex m_block; NodeIndex m_compileIndex; // Virtual and physical register maps. Vector<GenerationInfo, 32> m_generationInfo; RegisterBank<GPRReg, numberOfGPRs, SpillOrder, SpillOrderNone, SpillOrderMax> m_gprs; RegisterBank<FPRReg, numberOfFPRs, SpillOrder, SpillOrderNone, SpillOrderMax> m_fprs; Vector<MacroAssembler::Label> m_blockHeads; struct BranchRecord { BranchRecord(MacroAssembler::Jump jump, BlockIndex destination) : jump(jump) , destination(destination) { } MacroAssembler::Jump jump; BlockIndex destination; }; Vector<BranchRecord, 8> m_branches; }; // === Operand types === // // IntegerOperand, DoubleOperand and JSValueOperand. // // These classes are used to lock the operands to a node into machine // registers. These classes implement of pattern of locking a value // into register at the point of construction only if it is already in // registers, and otherwise loading it lazily at the point it is first // used. We do so in order to attempt to avoid spilling one operand // in order to make space available for another. class IntegerOperand { public: explicit IntegerOperand(JITCodeGenerator* jit, NodeIndex index) : m_jit(jit) , m_index(index) , m_gprOrInvalid(InvalidGPRReg) #ifndef NDEBUG , m_format(DataFormatNone) #endif { ASSERT(m_jit); if (jit->isFilled(index)) gpr(); } ~IntegerOperand() { ASSERT(m_gprOrInvalid != InvalidGPRReg); m_jit->unlock(m_gprOrInvalid); } NodeIndex index() const { return m_index; } GPRReg gpr() { if (m_gprOrInvalid == InvalidGPRReg) m_gprOrInvalid = m_jit->fillInteger(index(), m_format); return m_gprOrInvalid; } DataFormat format() { gpr(); // m_format is set when m_gpr is locked. ASSERT(m_format == DataFormatInteger || m_format == DataFormatJSInteger); return m_format; } MacroAssembler::RegisterID registerID() { return JITCompiler::gprToRegisterID(gpr()); } private: JITCodeGenerator* m_jit; NodeIndex m_index; GPRReg m_gprOrInvalid; DataFormat m_format; }; class DoubleOperand { public: explicit DoubleOperand(JITCodeGenerator* jit, NodeIndex index) : m_jit(jit) , m_index(index) , m_fprOrInvalid(InvalidFPRReg) { ASSERT(m_jit); if (jit->isFilledDouble(index)) fpr(); } ~DoubleOperand() { ASSERT(m_fprOrInvalid != InvalidFPRReg); m_jit->unlock(m_fprOrInvalid); } NodeIndex index() const { return m_index; } FPRReg fpr() { if (m_fprOrInvalid == InvalidFPRReg) m_fprOrInvalid = m_jit->fillDouble(index()); return m_fprOrInvalid; } MacroAssembler::FPRegisterID registerID() { return JITCompiler::fprToRegisterID(fpr()); } private: JITCodeGenerator* m_jit; NodeIndex m_index; FPRReg m_fprOrInvalid; }; class JSValueOperand { public: explicit JSValueOperand(JITCodeGenerator* jit, NodeIndex index) : m_jit(jit) , m_index(index) , m_gprOrInvalid(InvalidGPRReg) { ASSERT(m_jit); if (jit->isFilled(index)) gpr(); } ~JSValueOperand() { ASSERT(m_gprOrInvalid != InvalidGPRReg); m_jit->unlock(m_gprOrInvalid); } NodeIndex index() const { return m_index; } GPRReg gpr() { if (m_gprOrInvalid == InvalidGPRReg) m_gprOrInvalid = m_jit->fillJSValue(index()); return m_gprOrInvalid; } MacroAssembler::RegisterID registerID() { return JITCompiler::gprToRegisterID(gpr()); } private: JITCodeGenerator* m_jit; NodeIndex m_index; GPRReg m_gprOrInvalid; }; // === Temporaries === // // These classes are used to allocate temporary registers. // A mechanism is provided to attempt to reuse the registers // currently allocated to child nodes whose value is consumed // by, and not live after, this operation. class GPRTemporary { public: GPRTemporary(JITCodeGenerator*); GPRTemporary(JITCodeGenerator*, SpeculateIntegerOperand&); GPRTemporary(JITCodeGenerator*, SpeculateIntegerOperand&, SpeculateIntegerOperand&); GPRTemporary(JITCodeGenerator*, IntegerOperand&); GPRTemporary(JITCodeGenerator*, IntegerOperand&, IntegerOperand&); GPRTemporary(JITCodeGenerator*, SpeculateCellOperand&); GPRTemporary(JITCodeGenerator*, JSValueOperand&); ~GPRTemporary() { m_jit->unlock(gpr()); } GPRReg gpr() const { ASSERT(m_gpr != InvalidGPRReg); return m_gpr; } MacroAssembler::RegisterID registerID() { ASSERT(m_gpr != InvalidGPRReg); return JITCompiler::gprToRegisterID(m_gpr); } protected: GPRTemporary(JITCodeGenerator* jit, GPRReg lockedGPR) : m_jit(jit) , m_gpr(lockedGPR) { } private: JITCodeGenerator* m_jit; GPRReg m_gpr; }; class FPRTemporary { public: FPRTemporary(JITCodeGenerator*); FPRTemporary(JITCodeGenerator*, DoubleOperand&); FPRTemporary(JITCodeGenerator*, DoubleOperand&, DoubleOperand&); ~FPRTemporary() { m_jit->unlock(fpr()); } FPRReg fpr() const { ASSERT(m_fpr != InvalidFPRReg); return m_fpr; } MacroAssembler::FPRegisterID registerID() { ASSERT(m_fpr != InvalidFPRReg); return JITCompiler::fprToRegisterID(m_fpr); } protected: FPRTemporary(JITCodeGenerator* jit, FPRReg lockedFPR) : m_jit(jit) , m_fpr(lockedFPR) { } private: JITCodeGenerator* m_jit; FPRReg m_fpr; }; // === Results === // // These classes lock the result of a call to a C++ helper function. class GPRResult : public GPRTemporary { public: GPRResult(JITCodeGenerator* jit) : GPRTemporary(jit, lockedResult(jit)) { } private: static GPRReg lockedResult(JITCodeGenerator* jit) { jit->lock(JITCompiler::returnValueGPR); return JITCompiler::returnValueGPR; } }; class FPRResult : public FPRTemporary { public: FPRResult(JITCodeGenerator* jit) : FPRTemporary(jit, lockedResult(jit)) { } private: static FPRReg lockedResult(JITCodeGenerator* jit) { jit->lock(JITCompiler::returnValueFPR); return JITCompiler::returnValueFPR; } }; } } // namespace JSC::DFG #endif #endif