| /* |
| * 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 |
| |