| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * vim: set ts=8 sts=4 et sw=4 tw=99: |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| #ifndef jit_LIR_h |
| #define jit_LIR_h |
| |
| // This file declares the core data structures for LIR: storage allocations for |
| // inputs and outputs, as well as the interface instructions must conform to. |
| |
| #include "jscntxt.h" |
| #include "IonAllocPolicy.h" |
| #include "InlineList.h" |
| #include "FixedArityList.h" |
| #include "LOpcodes.h" |
| #include "Registers.h" |
| #include "MIR.h" |
| #include "MIRGraph.h" |
| #include "shared/Assembler-shared.h" |
| #include "Safepoints.h" |
| #include "Bailouts.h" |
| #include "VMFunctions.h" |
| |
| namespace js { |
| namespace jit { |
| |
| class LUse; |
| class LGeneralReg; |
| class LFloatReg; |
| class LStackSlot; |
| class LArgument; |
| class LConstantIndex; |
| class MBasicBlock; |
| class MTableSwitch; |
| class MIRGenerator; |
| class MSnapshot; |
| |
| static const uint32_t VREG_INCREMENT = 1; |
| |
| static const uint32_t THIS_FRAME_SLOT = 0; |
| |
| #if defined(JS_NUNBOX32) |
| # define BOX_PIECES 2 |
| static const uint32_t VREG_TYPE_OFFSET = 0; |
| static const uint32_t VREG_DATA_OFFSET = 1; |
| static const uint32_t TYPE_INDEX = 0; |
| static const uint32_t PAYLOAD_INDEX = 1; |
| #elif defined(JS_PUNBOX64) |
| # define BOX_PIECES 1 |
| #else |
| # error "Unknown!" |
| #endif |
| |
| // Represents storage for an operand. For constants, the pointer is tagged |
| // with a single bit, and the untagged pointer is a pointer to a Value. |
| class LAllocation : public TempObject |
| { |
| uintptr_t bits_; |
| |
| protected: |
| static const uintptr_t TAG_BIT = 1; |
| static const uintptr_t TAG_SHIFT = 0; |
| static const uintptr_t TAG_MASK = 1 << TAG_SHIFT; |
| static const uintptr_t KIND_BITS = 4; |
| static const uintptr_t KIND_SHIFT = TAG_SHIFT + TAG_BIT; |
| static const uintptr_t KIND_MASK = (1 << KIND_BITS) - 1; |
| static const uintptr_t DATA_BITS = (sizeof(uint32_t) * 8) - KIND_BITS - TAG_BIT; |
| static const uintptr_t DATA_SHIFT = KIND_SHIFT + KIND_BITS; |
| static const uintptr_t DATA_MASK = (1 << DATA_BITS) - 1; |
| |
| public: |
| enum Kind { |
| USE, // Use of a virtual register, with physical allocation policy. |
| CONSTANT_VALUE, // Constant js::Value. |
| CONSTANT_INDEX, // Constant arbitrary index. |
| GPR, // General purpose register. |
| FPU, // Floating-point register. |
| STACK_SLOT, // 32-bit stack slot. |
| DOUBLE_SLOT, // 64-bit stack slot. |
| INT_ARGUMENT, // Argument slot that gets loaded into a GPR. |
| DOUBLE_ARGUMENT // Argument slot to be loaded into an FPR |
| }; |
| |
| protected: |
| bool isTagged() const { |
| return !!(bits_ & TAG_MASK); |
| } |
| |
| int32_t data() const { |
| return int32_t(bits_) >> DATA_SHIFT; |
| } |
| void setData(int32_t data) { |
| JS_ASSERT(int32_t(data) <= int32_t(DATA_MASK)); |
| bits_ &= ~(DATA_MASK << DATA_SHIFT); |
| bits_ |= (data << DATA_SHIFT); |
| } |
| void setKindAndData(Kind kind, uint32_t data) { |
| JS_ASSERT(int32_t(data) <= int32_t(DATA_MASK)); |
| bits_ = (uint32_t(kind) << KIND_SHIFT) | data << DATA_SHIFT; |
| } |
| |
| LAllocation(Kind kind, uint32_t data) { |
| setKindAndData(kind, data); |
| } |
| explicit LAllocation(Kind kind) { |
| setKindAndData(kind, 0); |
| } |
| |
| public: |
| LAllocation() : bits_(0) |
| { } |
| |
| static LAllocation *New() { |
| return new LAllocation(); |
| } |
| template <typename T> |
| static LAllocation *New(const T &other) { |
| return new LAllocation(other); |
| } |
| |
| // The value pointer must be rooted in MIR and have its low bit cleared. |
| explicit LAllocation(const Value *vp) { |
| bits_ = uintptr_t(vp); |
| JS_ASSERT(!isTagged()); |
| bits_ |= TAG_MASK; |
| } |
| inline explicit LAllocation(const AnyRegister ®); |
| |
| Kind kind() const { |
| if (isTagged()) |
| return CONSTANT_VALUE; |
| return (Kind)((bits_ >> KIND_SHIFT) & KIND_MASK); |
| } |
| |
| bool isUse() const { |
| return kind() == USE; |
| } |
| bool isConstant() const { |
| return isConstantValue() || isConstantIndex(); |
| } |
| bool isConstantValue() const { |
| return kind() == CONSTANT_VALUE; |
| } |
| bool isConstantIndex() const { |
| return kind() == CONSTANT_INDEX; |
| } |
| bool isValue() const { |
| return kind() == CONSTANT_VALUE; |
| } |
| bool isGeneralReg() const { |
| return kind() == GPR; |
| } |
| bool isFloatReg() const { |
| return kind() == FPU; |
| } |
| bool isStackSlot() const { |
| return kind() == STACK_SLOT || kind() == DOUBLE_SLOT; |
| } |
| bool isArgument() const { |
| return kind() == INT_ARGUMENT || kind() == DOUBLE_ARGUMENT; |
| } |
| bool isRegister() const { |
| return isGeneralReg() || isFloatReg(); |
| } |
| bool isMemory() const { |
| return isStackSlot() || isArgument(); |
| } |
| bool isDouble() const { |
| return kind() == DOUBLE_SLOT || kind() == FPU || kind() == DOUBLE_ARGUMENT; |
| } |
| inline LUse *toUse(); |
| inline const LUse *toUse() const; |
| inline const LGeneralReg *toGeneralReg() const; |
| inline const LFloatReg *toFloatReg() const; |
| inline const LStackSlot *toStackSlot() const; |
| inline const LArgument *toArgument() const; |
| inline const LConstantIndex *toConstantIndex() const; |
| inline AnyRegister toRegister() const; |
| |
| const Value *toConstant() const { |
| JS_ASSERT(isConstantValue()); |
| return reinterpret_cast<const Value *>(bits_ & ~TAG_MASK); |
| } |
| |
| bool operator ==(const LAllocation &other) const { |
| return bits_ == other.bits_; |
| } |
| |
| bool operator !=(const LAllocation &other) const { |
| return bits_ != other.bits_; |
| } |
| |
| HashNumber hash() const { |
| return bits_; |
| } |
| |
| #ifdef DEBUG |
| const char *toString() const; |
| #else |
| const char *toString() const { return "???"; } |
| #endif |
| }; |
| |
| class LUse : public LAllocation |
| { |
| static const uint32_t POLICY_BITS = 3; |
| static const uint32_t POLICY_SHIFT = 0; |
| static const uint32_t POLICY_MASK = (1 << POLICY_BITS) - 1; |
| static const uint32_t REG_BITS = 5; |
| static const uint32_t REG_SHIFT = POLICY_SHIFT + POLICY_BITS; |
| static const uint32_t REG_MASK = (1 << REG_BITS) - 1; |
| |
| // Whether the physical register for this operand may be reused for a def. |
| static const uint32_t USED_AT_START_BITS = 1; |
| static const uint32_t USED_AT_START_SHIFT = REG_SHIFT + REG_BITS; |
| static const uint32_t USED_AT_START_MASK = (1 << USED_AT_START_BITS) - 1; |
| |
| public: |
| // Virtual registers get the remaining 20 bits. |
| static const uint32_t VREG_BITS = DATA_BITS - (USED_AT_START_SHIFT + USED_AT_START_BITS); |
| static const uint32_t VREG_SHIFT = USED_AT_START_SHIFT + USED_AT_START_BITS; |
| static const uint32_t VREG_MASK = (1 << VREG_BITS) - 1; |
| |
| enum Policy { |
| // Input should be in a read-only register or stack slot. |
| ANY, |
| |
| // Input must be in a read-only register. |
| REGISTER, |
| |
| // Input must be in a specific, read-only register. |
| FIXED, |
| |
| // Keep the used virtual register alive, and use whatever allocation is |
| // available. This is similar to ANY but hints to the register allocator |
| // that it is never useful to optimize this site. |
| KEEPALIVE, |
| |
| // For snapshot inputs, indicates that the associated instruction will |
| // write this input to its output register before bailing out. |
| // The register allocator may thus allocate that output register, and |
| // does not need to keep the virtual register alive (alternatively, |
| // this may be treated as KEEPALIVE). |
| RECOVERED_INPUT |
| }; |
| |
| void set(Policy policy, uint32_t reg, bool usedAtStart) { |
| setKindAndData(USE, (policy << POLICY_SHIFT) | |
| (reg << REG_SHIFT) | |
| ((usedAtStart ? 1 : 0) << USED_AT_START_SHIFT)); |
| } |
| |
| public: |
| LUse(uint32_t vreg, Policy policy, bool usedAtStart = false) { |
| set(policy, 0, usedAtStart); |
| setVirtualRegister(vreg); |
| } |
| LUse(Policy policy, bool usedAtStart = false) { |
| set(policy, 0, usedAtStart); |
| } |
| LUse(Register reg, bool usedAtStart = false) { |
| set(FIXED, reg.code(), usedAtStart); |
| } |
| LUse(FloatRegister reg, bool usedAtStart = false) { |
| set(FIXED, reg.code(), usedAtStart); |
| } |
| LUse(Register reg, uint32_t virtualRegister) { |
| set(FIXED, reg.code(), false); |
| setVirtualRegister(virtualRegister); |
| } |
| LUse(FloatRegister reg, uint32_t virtualRegister) { |
| set(FIXED, reg.code(), false); |
| setVirtualRegister(virtualRegister); |
| } |
| |
| void setVirtualRegister(uint32_t index) { |
| JS_ASSERT(index < VREG_MASK); |
| |
| uint32_t old = data() & ~(VREG_MASK << VREG_SHIFT); |
| setData(old | (index << VREG_SHIFT)); |
| } |
| |
| Policy policy() const { |
| Policy policy = (Policy)((data() >> POLICY_SHIFT) & POLICY_MASK); |
| return policy; |
| } |
| uint32_t virtualRegister() const { |
| uint32_t index = (data() >> VREG_SHIFT) & VREG_MASK; |
| return index; |
| } |
| uint32_t registerCode() const { |
| JS_ASSERT(policy() == FIXED); |
| return (data() >> REG_SHIFT) & REG_MASK; |
| } |
| bool isFixedRegister() const { |
| return policy() == FIXED; |
| } |
| bool usedAtStart() const { |
| return !!((data() >> USED_AT_START_SHIFT) & USED_AT_START_MASK); |
| } |
| }; |
| |
| static const uint32_t MAX_VIRTUAL_REGISTERS = LUse::VREG_MASK; |
| |
| class LGeneralReg : public LAllocation |
| { |
| public: |
| explicit LGeneralReg(Register reg) |
| : LAllocation(GPR, reg.code()) |
| { } |
| |
| Register reg() const { |
| return Register::FromCode(data()); |
| } |
| }; |
| |
| class LFloatReg : public LAllocation |
| { |
| public: |
| explicit LFloatReg(FloatRegister reg) |
| : LAllocation(FPU, reg.code()) |
| { } |
| |
| FloatRegister reg() const { |
| return FloatRegister::FromCode(data()); |
| } |
| }; |
| |
| // Arbitrary constant index. |
| class LConstantIndex : public LAllocation |
| { |
| explicit LConstantIndex(uint32_t index) |
| : LAllocation(CONSTANT_INDEX, index) |
| { } |
| |
| public: |
| // Used as a placeholder for inputs that can be ignored. |
| static LConstantIndex Bogus() { |
| return LConstantIndex(0); |
| } |
| |
| static LConstantIndex FromIndex(uint32_t index) { |
| return LConstantIndex(index); |
| } |
| |
| uint32_t index() const { |
| return data(); |
| } |
| }; |
| |
| // Stack slots are indexes into the stack, given that each slot is size |
| // STACK_SLOT_SIZE. |
| class LStackSlot : public LAllocation |
| { |
| public: |
| explicit LStackSlot(uint32_t slot, bool isDouble = false) |
| : LAllocation(isDouble ? DOUBLE_SLOT : STACK_SLOT, slot) |
| { } |
| |
| bool isDouble() const { |
| return kind() == DOUBLE_SLOT; |
| } |
| |
| uint32_t slot() const { |
| return data(); |
| } |
| }; |
| |
| // Arguments are reverse indexes into the stack, and as opposed to LStackSlot, |
| // each index is measured in bytes because we have to index the middle of a |
| // Value on 32 bits architectures. |
| class LArgument : public LAllocation |
| { |
| public: |
| explicit LArgument(LAllocation::Kind kind, int32_t index) |
| : LAllocation(kind, index) |
| { |
| JS_ASSERT(kind == INT_ARGUMENT || kind == DOUBLE_ARGUMENT); |
| } |
| |
| int32_t index() const { |
| return data(); |
| } |
| }; |
| |
| // Represents storage for a definition. |
| class LDefinition |
| { |
| // Bits containing policy, type, and virtual register. |
| uint32_t bits_; |
| |
| // Before register allocation, this optionally contains a fixed policy. |
| // Register allocation assigns this field to a physical policy if none is |
| // preset. |
| // |
| // Right now, pre-allocated outputs are limited to the following: |
| // * Physical argument stack slots. |
| // * Physical registers. |
| LAllocation output_; |
| |
| static const uint32_t TYPE_BITS = 3; |
| static const uint32_t TYPE_SHIFT = 0; |
| static const uint32_t TYPE_MASK = (1 << TYPE_BITS) - 1; |
| static const uint32_t POLICY_BITS = 2; |
| static const uint32_t POLICY_SHIFT = TYPE_SHIFT + TYPE_BITS; |
| static const uint32_t POLICY_MASK = (1 << POLICY_BITS) - 1; |
| |
| static const uint32_t VREG_BITS = (sizeof(uint32_t) * 8) - (POLICY_BITS + TYPE_BITS); |
| static const uint32_t VREG_SHIFT = POLICY_SHIFT + POLICY_BITS; |
| static const uint32_t VREG_MASK = (1 << VREG_BITS) - 1; |
| |
| public: |
| // Note that definitions, by default, are always allocated a register, |
| // unless the policy specifies that an input can be re-used and that input |
| // is a stack slot. |
| enum Policy { |
| // A random register of an appropriate class will be assigned. |
| DEFAULT, |
| |
| // The policy is predetermined by the LAllocation attached to this |
| // definition. The allocation may be: |
| // * A register, which may not appear as any fixed temporary. |
| // * A stack slot or argument. |
| // |
| // Register allocation will not modify a preset allocation. |
| PRESET, |
| |
| // One definition per instruction must re-use the first input |
| // allocation, which (for now) must be a register. |
| MUST_REUSE_INPUT, |
| |
| // This definition's virtual register is the same as another; this is |
| // for instructions which consume a register and silently define it as |
| // the same register. It is not legal to use this if doing so would |
| // change the type of the virtual register. |
| PASSTHROUGH |
| }; |
| |
| enum Type { |
| GENERAL, // Generic, integer or pointer-width data (GPR). |
| OBJECT, // Pointer that may be collected as garbage (GPR). |
| DOUBLE, // 64-bit point value (FPU). |
| #ifdef JS_NUNBOX32 |
| // A type virtual register must be followed by a payload virtual |
| // register, as both will be tracked as a single gcthing. |
| TYPE, |
| PAYLOAD |
| #else |
| BOX // Joined box, for punbox systems. (GPR, gcthing) |
| #endif |
| }; |
| |
| void set(uint32_t index, Type type, Policy policy) { |
| JS_STATIC_ASSERT(MAX_VIRTUAL_REGISTERS <= VREG_MASK); |
| bits_ = (index << VREG_SHIFT) | (policy << POLICY_SHIFT) | (type << TYPE_SHIFT); |
| } |
| |
| public: |
| LDefinition(uint32_t index, Type type, Policy policy = DEFAULT) { |
| set(index, type, policy); |
| } |
| |
| LDefinition(Type type, Policy policy = DEFAULT) { |
| set(0, type, policy); |
| } |
| |
| LDefinition(Type type, const LAllocation &a) |
| : output_(a) |
| { |
| set(0, type, PRESET); |
| } |
| |
| LDefinition(uint32_t index, Type type, const LAllocation &a) |
| : output_(a) |
| { |
| set(index, type, PRESET); |
| } |
| |
| LDefinition() : bits_(0) |
| { } |
| |
| static LDefinition BogusTemp() { |
| return LDefinition(GENERAL, LConstantIndex::Bogus()); |
| } |
| |
| Policy policy() const { |
| return (Policy)((bits_ >> POLICY_SHIFT) & POLICY_MASK); |
| } |
| Type type() const { |
| return (Type)((bits_ >> TYPE_SHIFT) & TYPE_MASK); |
| } |
| uint32_t virtualRegister() const { |
| return (bits_ >> VREG_SHIFT) & VREG_MASK; |
| } |
| LAllocation *output() { |
| return &output_; |
| } |
| const LAllocation *output() const { |
| return &output_; |
| } |
| bool isPreset() const { |
| return policy() == PRESET; |
| } |
| bool isBogusTemp() const { |
| return isPreset() && output()->isConstantIndex(); |
| } |
| void setVirtualRegister(uint32_t index) { |
| JS_ASSERT(index < VREG_MASK); |
| bits_ &= ~(VREG_MASK << VREG_SHIFT); |
| bits_ |= index << VREG_SHIFT; |
| } |
| void setOutput(const LAllocation &a) { |
| output_ = a; |
| if (!a.isUse()) { |
| bits_ &= ~(POLICY_MASK << POLICY_SHIFT); |
| bits_ |= PRESET << POLICY_SHIFT; |
| } |
| } |
| void setReusedInput(uint32_t operand) { |
| output_ = LConstantIndex::FromIndex(operand); |
| } |
| uint32_t getReusedInput() const { |
| JS_ASSERT(policy() == LDefinition::MUST_REUSE_INPUT); |
| return output_.toConstantIndex()->index(); |
| } |
| |
| static inline Type TypeFrom(MIRType type) { |
| switch (type) { |
| case MIRType_Boolean: |
| case MIRType_Int32: |
| return LDefinition::GENERAL; |
| case MIRType_String: |
| case MIRType_Object: |
| return LDefinition::OBJECT; |
| case MIRType_Double: |
| return LDefinition::DOUBLE; |
| #if defined(JS_PUNBOX64) |
| case MIRType_Value: |
| return LDefinition::BOX; |
| #endif |
| case MIRType_Slots: |
| case MIRType_Elements: |
| // When we begin allocating slots vectors from the GC, this will |
| // need to change to ::OBJECT. |
| return LDefinition::GENERAL; |
| case MIRType_Pointer: |
| return LDefinition::GENERAL; |
| case MIRType_ForkJoinSlice: |
| return LDefinition::GENERAL; |
| default: |
| JS_NOT_REACHED("unexpected type"); |
| return LDefinition::GENERAL; |
| } |
| } |
| }; |
| |
| // Forward declarations of LIR types. |
| #define LIROP(op) class L##op; |
| LIR_OPCODE_LIST(LIROP) |
| #undef LIROP |
| |
| class LSnapshot; |
| class LSafepoint; |
| class LInstructionVisitor; |
| |
| class LInstruction |
| : public TempObject, |
| public InlineListNode<LInstruction> |
| { |
| uint32_t id_; |
| |
| // This snapshot could be set after a ResumePoint. It is used to restart |
| // from the resume point pc. |
| LSnapshot *snapshot_; |
| |
| // Structure capturing the set of stack slots and registers which are known |
| // to hold either gcthings or Values. |
| LSafepoint *safepoint_; |
| |
| protected: |
| MDefinition *mir_; |
| |
| LInstruction() |
| : id_(0), |
| snapshot_(NULL), |
| safepoint_(NULL), |
| mir_(NULL) |
| { } |
| |
| public: |
| class InputIterator; |
| enum Opcode { |
| # define LIROP(name) LOp_##name, |
| LIR_OPCODE_LIST(LIROP) |
| # undef LIROP |
| LOp_Invalid |
| }; |
| |
| const char *opName() { |
| switch (op()) { |
| # define LIR_NAME_INS(name) \ |
| case LOp_##name: return #name; |
| LIR_OPCODE_LIST(LIR_NAME_INS) |
| # undef LIR_NAME_INS |
| default: |
| return "Invalid"; |
| } |
| } |
| |
| // Hook for opcodes to add extra high level detail about what code will be |
| // emitted for the op. |
| virtual const char *extraName() const { |
| return NULL; |
| } |
| |
| public: |
| virtual Opcode op() const = 0; |
| |
| // Returns the number of outputs of this instruction. If an output is |
| // unallocated, it is an LDefinition, defining a virtual register. |
| virtual size_t numDefs() const = 0; |
| virtual LDefinition *getDef(size_t index) = 0; |
| virtual void setDef(size_t index, const LDefinition &def) = 0; |
| |
| // Returns information about operands. |
| virtual size_t numOperands() const = 0; |
| virtual LAllocation *getOperand(size_t index) = 0; |
| virtual void setOperand(size_t index, const LAllocation &a) = 0; |
| |
| // Returns information about temporary registers needed. Each temporary |
| // register is an LUse with a TEMPORARY policy, or a fixed register. |
| virtual size_t numTemps() const = 0; |
| virtual LDefinition *getTemp(size_t index) = 0; |
| virtual void setTemp(size_t index, const LDefinition &a) = 0; |
| |
| // Returns the number of successors of this instruction, if it is a control |
| // transfer instruction, or zero otherwise. |
| virtual size_t numSuccessors() const = 0; |
| virtual MBasicBlock *getSuccessor(size_t i) const = 0; |
| virtual void setSuccessor(size_t i, MBasicBlock *successor) = 0; |
| |
| virtual bool isCall() const { |
| return false; |
| } |
| uint32_t id() const { |
| return id_; |
| } |
| void setId(uint32_t id) { |
| JS_ASSERT(!id_); |
| JS_ASSERT(id); |
| id_ = id; |
| } |
| LSnapshot *snapshot() const { |
| return snapshot_; |
| } |
| LSafepoint *safepoint() const { |
| return safepoint_; |
| } |
| void setMir(MDefinition *mir) { |
| mir_ = mir; |
| } |
| MDefinition *mirRaw() const { |
| /* Untyped MIR for this op. Prefer mir() methods in subclasses. */ |
| return mir_; |
| } |
| void assignSnapshot(LSnapshot *snapshot); |
| void initSafepoint(); |
| |
| // For an instruction which has a MUST_REUSE_INPUT output, whether that |
| // output register will be restored to its original value when bailing out. |
| virtual bool recoversInput() const { |
| return false; |
| } |
| |
| virtual void print(FILE *fp); |
| static void printName(FILE *fp, Opcode op); |
| virtual void printName(FILE *fp); |
| virtual void printOperands(FILE *fp); |
| virtual void printInfo(FILE *fp) { } |
| |
| public: |
| // Opcode testing and casts. |
| # define LIROP(name) \ |
| bool is##name() const { \ |
| return op() == LOp_##name; \ |
| } \ |
| inline L##name *to##name(); |
| LIR_OPCODE_LIST(LIROP) |
| # undef LIROP |
| |
| virtual bool accept(LInstructionVisitor *visitor) = 0; |
| }; |
| |
| class LInstructionVisitor |
| { |
| LInstruction *ins_; |
| |
| protected: |
| jsbytecode *lastPC_; |
| |
| LInstruction *instruction() { |
| return ins_; |
| } |
| |
| public: |
| void setInstruction(LInstruction *ins) { |
| ins_ = ins; |
| if (ins->mirRaw()) |
| lastPC_ = ins->mirRaw()->trackedPc(); |
| } |
| |
| LInstructionVisitor() |
| : ins_(NULL), |
| lastPC_(NULL) |
| {} |
| |
| public: |
| #define VISIT_INS(op) virtual bool visit##op(L##op *) { JS_NOT_REACHED("NYI: " #op); return false; } |
| LIR_OPCODE_LIST(VISIT_INS) |
| #undef VISIT_INS |
| }; |
| |
| typedef InlineList<LInstruction>::iterator LInstructionIterator; |
| typedef InlineList<LInstruction>::reverse_iterator LInstructionReverseIterator; |
| |
| class LPhi; |
| class LMoveGroup; |
| class LBlock : public TempObject |
| { |
| MBasicBlock *block_; |
| Vector<LPhi *, 4, IonAllocPolicy> phis_; |
| InlineList<LInstruction> instructions_; |
| LMoveGroup *entryMoveGroup_; |
| LMoveGroup *exitMoveGroup_; |
| |
| LBlock(MBasicBlock *block) |
| : block_(block), |
| entryMoveGroup_(NULL), |
| exitMoveGroup_(NULL) |
| { } |
| |
| public: |
| static LBlock *New(MBasicBlock *from) { |
| return new LBlock(from); |
| } |
| void add(LInstruction *ins) { |
| instructions_.pushBack(ins); |
| } |
| bool addPhi(LPhi *phi) { |
| return phis_.append(phi); |
| } |
| size_t numPhis() const { |
| return phis_.length(); |
| } |
| LPhi *getPhi(size_t index) const { |
| return phis_[index]; |
| } |
| void removePhi(size_t index) { |
| phis_.erase(&phis_[index]); |
| } |
| void clearPhis() { |
| phis_.clear(); |
| } |
| MBasicBlock *mir() const { |
| return block_; |
| } |
| LInstructionIterator begin() { |
| return instructions_.begin(); |
| } |
| LInstructionIterator begin(LInstruction *at) { |
| return instructions_.begin(at); |
| } |
| LInstructionIterator end() { |
| return instructions_.end(); |
| } |
| LInstructionReverseIterator rbegin() { |
| return instructions_.rbegin(); |
| } |
| LInstructionReverseIterator rbegin(LInstruction *at) { |
| return instructions_.rbegin(at); |
| } |
| LInstructionReverseIterator rend() { |
| return instructions_.rend(); |
| } |
| InlineList<LInstruction> &instructions() { |
| return instructions_; |
| } |
| void insertAfter(LInstruction *at, LInstruction *ins) { |
| instructions_.insertAfter(at, ins); |
| } |
| void insertBefore(LInstruction *at, LInstruction *ins) { |
| JS_ASSERT(!at->isLabel()); |
| instructions_.insertBefore(at, ins); |
| } |
| uint32_t firstId(); |
| uint32_t lastId(); |
| Label *label(); |
| LMoveGroup *getEntryMoveGroup(); |
| LMoveGroup *getExitMoveGroup(); |
| }; |
| |
| template <size_t Defs, size_t Operands, size_t Temps> |
| class LInstructionHelper : public LInstruction |
| { |
| FixedArityList<LDefinition, Defs> defs_; |
| FixedArityList<LAllocation, Operands> operands_; |
| FixedArityList<LDefinition, Temps> temps_; |
| |
| public: |
| size_t numDefs() const { |
| return Defs; |
| } |
| LDefinition *getDef(size_t index) { |
| return &defs_[index]; |
| } |
| size_t numOperands() const { |
| return Operands; |
| } |
| LAllocation *getOperand(size_t index) { |
| return &operands_[index]; |
| } |
| size_t numTemps() const { |
| return Temps; |
| } |
| LDefinition *getTemp(size_t index) { |
| return &temps_[index]; |
| } |
| |
| void setDef(size_t index, const LDefinition &def) { |
| defs_[index] = def; |
| } |
| void setOperand(size_t index, const LAllocation &a) { |
| operands_[index] = a; |
| } |
| void setTemp(size_t index, const LDefinition &a) { |
| temps_[index] = a; |
| } |
| |
| size_t numSuccessors() const { |
| return 0; |
| } |
| MBasicBlock *getSuccessor(size_t i) const { |
| JS_ASSERT(false); |
| return NULL; |
| } |
| void setSuccessor(size_t i, MBasicBlock *successor) { |
| JS_ASSERT(false); |
| } |
| |
| // Default accessors, assuming a single input and output, respectively. |
| const LAllocation *input() { |
| JS_ASSERT(numOperands() == 1); |
| return getOperand(0); |
| } |
| const LDefinition *output() { |
| JS_ASSERT(numDefs() == 1); |
| return getDef(0); |
| } |
| |
| virtual void printInfo(FILE *fp) { |
| printOperands(fp); |
| } |
| }; |
| |
| template <size_t Defs, size_t Operands, size_t Temps> |
| class LCallInstructionHelper : public LInstructionHelper<Defs, Operands, Temps> |
| { |
| public: |
| virtual bool isCall() const { |
| return true; |
| } |
| }; |
| |
| // An LSnapshot is the reflection of an MResumePoint in LIR. Unlike MResumePoints, |
| // they cannot be shared, as they are filled in by the register allocator in |
| // order to capture the precise low-level stack state in between an |
| // instruction's input and output. During code generation, LSnapshots are |
| // compressed and saved in the compiled script. |
| class LSnapshot : public TempObject |
| { |
| private: |
| uint32_t numSlots_; |
| LAllocation *slots_; |
| MResumePoint *mir_; |
| SnapshotOffset snapshotOffset_; |
| BailoutId bailoutId_; |
| BailoutKind bailoutKind_; |
| |
| LSnapshot(MResumePoint *mir, BailoutKind kind); |
| bool init(MIRGenerator *gen); |
| |
| public: |
| static LSnapshot *New(MIRGenerator *gen, MResumePoint *snapshot, BailoutKind kind); |
| |
| size_t numEntries() const { |
| return numSlots_; |
| } |
| size_t numSlots() const { |
| return numSlots_ / BOX_PIECES; |
| } |
| LAllocation *payloadOfSlot(size_t i) { |
| JS_ASSERT(i < numSlots()); |
| size_t entryIndex = (i * BOX_PIECES) + (BOX_PIECES - 1); |
| return getEntry(entryIndex); |
| } |
| #ifdef JS_NUNBOX32 |
| LAllocation *typeOfSlot(size_t i) { |
| JS_ASSERT(i < numSlots()); |
| size_t entryIndex = (i * BOX_PIECES) + (BOX_PIECES - 2); |
| return getEntry(entryIndex); |
| } |
| #endif |
| LAllocation *getEntry(size_t i) { |
| JS_ASSERT(i < numSlots_); |
| return &slots_[i]; |
| } |
| void setEntry(size_t i, const LAllocation &alloc) { |
| JS_ASSERT(i < numSlots_); |
| slots_[i] = alloc; |
| } |
| MResumePoint *mir() const { |
| return mir_; |
| } |
| SnapshotOffset snapshotOffset() const { |
| return snapshotOffset_; |
| } |
| BailoutId bailoutId() const { |
| return bailoutId_; |
| } |
| void setSnapshotOffset(SnapshotOffset offset) { |
| JS_ASSERT(snapshotOffset_ == INVALID_SNAPSHOT_OFFSET); |
| snapshotOffset_ = offset; |
| } |
| void setBailoutId(BailoutId id) { |
| JS_ASSERT(bailoutId_ == INVALID_BAILOUT_ID); |
| bailoutId_ = id; |
| } |
| BailoutKind bailoutKind() const { |
| return bailoutKind_; |
| } |
| void setBailoutKind(BailoutKind kind) { |
| bailoutKind_ = kind; |
| } |
| void rewriteRecoveredInput(LUse input); |
| }; |
| |
| struct SafepointNunboxEntry { |
| LAllocation type; |
| LAllocation payload; |
| |
| SafepointNunboxEntry() { } |
| SafepointNunboxEntry(LAllocation type, LAllocation payload) |
| : type(type), payload(payload) |
| { } |
| }; |
| |
| class LSafepoint : public TempObject |
| { |
| typedef SafepointNunboxEntry NunboxEntry; |
| |
| public: |
| typedef Vector<uint32_t, 0, IonAllocPolicy> SlotList; |
| typedef Vector<NunboxEntry, 0, IonAllocPolicy> NunboxList; |
| |
| private: |
| // The information in a safepoint describes the registers and gc related |
| // values that are live at the start of the associated instruction. |
| |
| // The set of registers which are live at an OOL call made within the |
| // instruction. This includes any registers for inputs which are not |
| // use-at-start, any registers for temps, and any registers live after the |
| // call except outputs of the instruction. |
| // |
| // For call instructions, the live regs are empty. Call instructions may |
| // have register inputs or temporaries, which will *not* be in the live |
| // registers: if passed to the call, the values passed will be marked via |
| // MarkIonExitFrame, and no registers can be live after the instruction |
| // except its outputs. |
| RegisterSet liveRegs_; |
| |
| // The subset of liveRegs which contains gcthing pointers. |
| GeneralRegisterSet gcRegs_; |
| |
| // Offset to a position in the safepoint stream, or |
| // INVALID_SAFEPOINT_OFFSET. |
| uint32_t safepointOffset_; |
| |
| // Assembler buffer displacement to OSI point's call location. |
| uint32_t osiCallPointOffset_; |
| |
| // List of stack slots which have gcthing pointers. |
| SlotList gcSlots_; |
| |
| // List of stack slots which have Values. |
| SlotList valueSlots_; |
| |
| #ifdef JS_NUNBOX32 |
| // List of registers (in liveRegs) and stack slots which contain pieces of Values. |
| NunboxList nunboxParts_; |
| |
| // Number of nunboxParts which are not completely filled in. |
| uint32_t partialNunboxes_; |
| #elif JS_PUNBOX64 |
| // The subset of liveRegs which have Values. |
| GeneralRegisterSet valueRegs_; |
| #endif |
| |
| public: |
| LSafepoint() |
| : safepointOffset_(INVALID_SAFEPOINT_OFFSET) |
| , osiCallPointOffset_(0) |
| #ifdef JS_NUNBOX32 |
| , partialNunboxes_(0) |
| #endif |
| { } |
| void addLiveRegister(AnyRegister reg) { |
| liveRegs_.addUnchecked(reg); |
| } |
| const RegisterSet &liveRegs() const { |
| return liveRegs_; |
| } |
| void addGcRegister(Register reg) { |
| gcRegs_.addUnchecked(reg); |
| } |
| GeneralRegisterSet gcRegs() const { |
| return gcRegs_; |
| } |
| bool addGcSlot(uint32_t slot) { |
| return gcSlots_.append(slot); |
| } |
| SlotList &gcSlots() { |
| return gcSlots_; |
| } |
| |
| bool addGcPointer(LAllocation alloc) { |
| if (alloc.isStackSlot()) |
| return addGcSlot(alloc.toStackSlot()->slot()); |
| if (alloc.isRegister()) |
| addGcRegister(alloc.toRegister().gpr()); |
| return true; |
| } |
| |
| bool hasGcPointer(LAllocation alloc) { |
| if (alloc.isRegister()) |
| return gcRegs().has(alloc.toRegister().gpr()); |
| if (alloc.isStackSlot()) { |
| for (size_t i = 0; i < gcSlots_.length(); i++) { |
| if (gcSlots_[i] == alloc.toStackSlot()->slot()) |
| return true; |
| } |
| return false; |
| } |
| JS_ASSERT(alloc.isArgument()); |
| return true; |
| } |
| |
| bool addValueSlot(uint32_t slot) { |
| return valueSlots_.append(slot); |
| } |
| SlotList &valueSlots() { |
| return valueSlots_; |
| } |
| |
| bool hasValueSlot(uint32_t slot) { |
| for (size_t i = 0; i < valueSlots_.length(); i++) { |
| if (valueSlots_[i] == slot) |
| return true; |
| } |
| return false; |
| } |
| |
| #ifdef JS_NUNBOX32 |
| |
| bool addNunboxParts(LAllocation type, LAllocation payload) { |
| return nunboxParts_.append(NunboxEntry(type, payload)); |
| } |
| |
| bool addNunboxType(uint32_t typeVreg, LAllocation type) { |
| for (size_t i = 0; i < nunboxParts_.length(); i++) { |
| if (nunboxParts_[i].type == type) |
| return true; |
| if (nunboxParts_[i].type == LUse(typeVreg, LUse::ANY)) { |
| nunboxParts_[i].type = type; |
| partialNunboxes_--; |
| return true; |
| } |
| } |
| partialNunboxes_++; |
| |
| // vregs for nunbox pairs are adjacent, with the type coming first. |
| uint32_t payloadVreg = typeVreg + 1; |
| return nunboxParts_.append(NunboxEntry(type, LUse(payloadVreg, LUse::ANY))); |
| } |
| |
| bool hasNunboxType(LAllocation type) { |
| if (type.isArgument()) |
| return true; |
| if (type.isStackSlot() && hasValueSlot(type.toStackSlot()->slot() + 1)) |
| return true; |
| for (size_t i = 0; i < nunboxParts_.length(); i++) { |
| if (nunboxParts_[i].type == type) |
| return true; |
| } |
| return false; |
| } |
| |
| bool addNunboxPayload(uint32_t payloadVreg, LAllocation payload) { |
| for (size_t i = 0; i < nunboxParts_.length(); i++) { |
| if (nunboxParts_[i].payload == payload) |
| return true; |
| if (nunboxParts_[i].payload == LUse(payloadVreg, LUse::ANY)) { |
| partialNunboxes_--; |
| nunboxParts_[i].payload = payload; |
| return true; |
| } |
| } |
| partialNunboxes_++; |
| |
| // vregs for nunbox pairs are adjacent, with the type coming first. |
| uint32_t typeVreg = payloadVreg - 1; |
| return nunboxParts_.append(NunboxEntry(LUse(typeVreg, LUse::ANY), payload)); |
| } |
| |
| bool hasNunboxPayload(LAllocation payload) { |
| if (payload.isArgument()) |
| return true; |
| if (payload.isStackSlot() && hasValueSlot(payload.toStackSlot()->slot())) |
| return true; |
| for (size_t i = 0; i < nunboxParts_.length(); i++) { |
| if (nunboxParts_[i].payload == payload) |
| return true; |
| } |
| return false; |
| } |
| |
| NunboxList &nunboxParts() { |
| return nunboxParts_; |
| } |
| |
| uint32_t partialNunboxes() { |
| return partialNunboxes_; |
| } |
| |
| #elif JS_PUNBOX64 |
| |
| void addValueRegister(Register reg) { |
| valueRegs_.add(reg); |
| } |
| GeneralRegisterSet valueRegs() { |
| return valueRegs_; |
| } |
| |
| bool addBoxedValue(LAllocation alloc) { |
| if (alloc.isRegister()) { |
| Register reg = alloc.toRegister().gpr(); |
| if (!valueRegs().has(reg)) |
| addValueRegister(reg); |
| return true; |
| } |
| if (alloc.isStackSlot()) { |
| uint32_t slot = alloc.toStackSlot()->slot(); |
| for (size_t i = 0; i < valueSlots().length(); i++) { |
| if (valueSlots()[i] == slot) |
| return true; |
| } |
| return addValueSlot(slot); |
| } |
| JS_ASSERT(alloc.isArgument()); |
| return true; |
| } |
| |
| bool hasBoxedValue(LAllocation alloc) { |
| if (alloc.isRegister()) |
| return valueRegs().has(alloc.toRegister().gpr()); |
| if (alloc.isStackSlot()) |
| return hasValueSlot(alloc.toStackSlot()->slot()); |
| JS_ASSERT(alloc.isArgument()); |
| return true; |
| } |
| |
| #endif // JS_PUNBOX64 |
| |
| bool encoded() const { |
| return safepointOffset_ != INVALID_SAFEPOINT_OFFSET; |
| } |
| uint32_t offset() const { |
| JS_ASSERT(encoded()); |
| return safepointOffset_; |
| } |
| void setOffset(uint32_t offset) { |
| safepointOffset_ = offset; |
| } |
| uint32_t osiReturnPointOffset() const { |
| // In general, pointer arithmetic on code is bad, but in this case, |
| // getting the return address from a call instruction, stepping over pools |
| // would be wrong. |
| return osiCallPointOffset_ + Assembler::patchWrite_NearCallSize(); |
| } |
| uint32_t osiCallPointOffset() const { |
| return osiCallPointOffset_; |
| } |
| void setOsiCallPointOffset(uint32_t osiCallPointOffset) { |
| JS_ASSERT(!osiCallPointOffset_); |
| osiCallPointOffset_ = osiCallPointOffset; |
| } |
| void fixupOffset(MacroAssembler *masm) { |
| osiCallPointOffset_ = masm->actualOffset(osiCallPointOffset_); |
| safepointOffset_ = masm->actualOffset(safepointOffset_); |
| } |
| }; |
| |
| class LInstruction::InputIterator |
| { |
| private: |
| LInstruction &ins_; |
| size_t idx_; |
| bool snapshot_; |
| |
| void handleOperandsEnd() { |
| // Iterate on the snapshot when iteration over all operands is done. |
| if (!snapshot_ && idx_ == ins_.numOperands() && ins_.snapshot()) { |
| idx_ = 0; |
| snapshot_ = true; |
| } |
| } |
| |
| public: |
| InputIterator(LInstruction &ins) : |
| ins_(ins), |
| idx_(0), |
| snapshot_(false) |
| { |
| handleOperandsEnd(); |
| } |
| |
| bool more() const { |
| if (snapshot_) |
| return idx_ < ins_.snapshot()->numEntries(); |
| if (idx_ < ins_.numOperands()) |
| return true; |
| if (ins_.snapshot() && ins_.snapshot()->numEntries()) |
| return true; |
| return false; |
| } |
| |
| bool isSnapshotInput() const { |
| return snapshot_; |
| } |
| |
| void next() { |
| JS_ASSERT(more()); |
| idx_++; |
| handleOperandsEnd(); |
| } |
| |
| void replace(const LAllocation &alloc) { |
| if (snapshot_) |
| ins_.snapshot()->setEntry(idx_, alloc); |
| else |
| ins_.setOperand(idx_, alloc); |
| } |
| |
| LAllocation *operator *() const { |
| if (snapshot_) |
| return ins_.snapshot()->getEntry(idx_); |
| return ins_.getOperand(idx_); |
| } |
| |
| LAllocation *operator ->() const { |
| return **this; |
| } |
| }; |
| |
| class LIRGraph |
| { |
| Vector<LBlock *, 16, IonAllocPolicy> blocks_; |
| Vector<Value, 0, IonAllocPolicy> constantPool_; |
| Vector<LInstruction *, 0, IonAllocPolicy> safepoints_; |
| Vector<LInstruction *, 0, IonAllocPolicy> nonCallSafepoints_; |
| uint32_t numVirtualRegisters_; |
| uint32_t numInstructions_; |
| |
| // Number of stack slots needed for local spills. |
| uint32_t localSlotCount_; |
| // Number of stack slots needed for argument construction for calls. |
| uint32_t argumentSlotCount_; |
| |
| // Snapshot taken before any LIR has been lowered. |
| LSnapshot *entrySnapshot_; |
| |
| // LBlock containing LOsrEntry, or NULL. |
| LBlock *osrBlock_; |
| |
| MIRGraph &mir_; |
| |
| public: |
| LIRGraph(MIRGraph *mir); |
| |
| MIRGraph &mir() const { |
| return mir_; |
| } |
| size_t numBlocks() const { |
| return blocks_.length(); |
| } |
| LBlock *getBlock(size_t i) const { |
| return blocks_[i]; |
| } |
| uint32_t numBlockIds() const { |
| return mir_.numBlockIds(); |
| } |
| bool addBlock(LBlock *block) { |
| return blocks_.append(block); |
| } |
| uint32_t getVirtualRegister() { |
| numVirtualRegisters_ += VREG_INCREMENT; |
| return numVirtualRegisters_; |
| } |
| uint32_t numVirtualRegisters() const { |
| // Virtual registers are 1-based, not 0-based, so add one as a |
| // convenience for 0-based arrays. |
| return numVirtualRegisters_ + 1; |
| } |
| uint32_t getInstructionId() { |
| return numInstructions_++; |
| } |
| uint32_t numInstructions() const { |
| return numInstructions_; |
| } |
| void setLocalSlotCount(uint32_t localSlotCount) { |
| localSlotCount_ = localSlotCount; |
| } |
| uint32_t localSlotCount() const { |
| return AlignBytes(localSlotCount_, StackAlignment / STACK_SLOT_SIZE); |
| } |
| void setArgumentSlotCount(uint32_t argumentSlotCount) { |
| argumentSlotCount_ = argumentSlotCount; |
| } |
| uint32_t argumentSlotCount() const { |
| return argumentSlotCount_; |
| } |
| uint32_t totalSlotCount() const { |
| return localSlotCount() + (argumentSlotCount() * sizeof(Value) / STACK_SLOT_SIZE); |
| } |
| bool addConstantToPool(const Value &v, uint32_t *index); |
| size_t numConstants() const { |
| return constantPool_.length(); |
| } |
| Value *constantPool() { |
| return &constantPool_[0]; |
| } |
| const Value &getConstant(size_t index) const { |
| return constantPool_[index]; |
| } |
| void setEntrySnapshot(LSnapshot *snapshot) { |
| JS_ASSERT(!entrySnapshot_); |
| JS_ASSERT(snapshot->bailoutKind() == Bailout_Normal); |
| snapshot->setBailoutKind(Bailout_ArgumentCheck); |
| entrySnapshot_ = snapshot; |
| } |
| LSnapshot *entrySnapshot() const { |
| JS_ASSERT(entrySnapshot_); |
| return entrySnapshot_; |
| } |
| void setOsrBlock(LBlock *block) { |
| JS_ASSERT(!osrBlock_); |
| osrBlock_ = block; |
| } |
| LBlock *osrBlock() const { |
| return osrBlock_; |
| } |
| bool noteNeedsSafepoint(LInstruction *ins); |
| size_t numNonCallSafepoints() const { |
| return nonCallSafepoints_.length(); |
| } |
| LInstruction *getNonCallSafepoint(size_t i) const { |
| return nonCallSafepoints_[i]; |
| } |
| size_t numSafepoints() const { |
| return safepoints_.length(); |
| } |
| LInstruction *getSafepoint(size_t i) const { |
| return safepoints_[i]; |
| } |
| void removeBlock(size_t i); |
| }; |
| |
| LAllocation::LAllocation(const AnyRegister ®) |
| { |
| if (reg.isFloat()) |
| *this = LFloatReg(reg.fpu()); |
| else |
| *this = LGeneralReg(reg.gpr()); |
| } |
| |
| AnyRegister |
| LAllocation::toRegister() const |
| { |
| JS_ASSERT(isRegister()); |
| if (isFloatReg()) |
| return AnyRegister(toFloatReg()->reg()); |
| return AnyRegister(toGeneralReg()->reg()); |
| } |
| |
| } // namespace jit |
| } // namespace js |
| |
| #define LIR_HEADER(opcode) \ |
| Opcode op() const { \ |
| return LInstruction::LOp_##opcode; \ |
| } \ |
| bool accept(LInstructionVisitor *visitor) { \ |
| visitor->setInstruction(this); \ |
| return visitor->visit##opcode(this); \ |
| } |
| |
| #if defined(JS_NUNBOX32) |
| # define BOX_OUTPUT_ACCESSORS() \ |
| const LDefinition *outputType() { \ |
| return getDef(TYPE_INDEX); \ |
| } \ |
| const LDefinition *outputPayload() { \ |
| return getDef(PAYLOAD_INDEX); \ |
| } |
| #elif defined(JS_PUNBOX64) |
| # define BOX_OUTPUT_ACCESSORS() \ |
| const LDefinition *outputValue() { \ |
| return getDef(0); \ |
| } |
| #endif |
| |
| #include "LIR-Common.h" |
| #if defined(JS_CPU_X86) || defined(JS_CPU_X64) |
| # if defined(JS_CPU_X86) |
| # include "x86/LIR-x86.h" |
| # elif defined(JS_CPU_X64) |
| # include "x64/LIR-x64.h" |
| # endif |
| # include "shared/LIR-x86-shared.h" |
| #elif defined(JS_CPU_ARM) |
| # include "arm/LIR-arm.h" |
| #elif defined(JS_CPU_MIPS) |
| # include "mips/LIR-mips.h" |
| #else |
| # error "Unknown CPU architecture." |
| #endif |
| |
| #undef LIR_HEADER |
| |
| #include "LIR-inl.h" |
| |
| #endif /* jit_LIR_h */ |