/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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_BaselineFrameInfo_h #define jit_BaselineFrameInfo_h #include "mozilla/Attributes.h" #include "mozilla/Maybe.h" #include #include "jit/BaselineFrame.h" #include "jit/BaselineJIT.h" #include "jit/FixedList.h" #include "jit/MacroAssembler.h" #include "jit/SharedICRegisters.h" namespace js { namespace jit { struct BytecodeInfo; class MacroAssembler; // [SMDOC] Baseline FrameInfo overview. // // FrameInfo is used by BaselineCodeGen to track values stored in the frame. // There are two implementations: // // InterpreterFrameInfo // -------------------- // The InterpreterFrameInfo class is used by the interpreter generator and is // a very simple interface on top of the MacroAssembler, because the stack is // always synced. // // CompilerFrameInfo // ----------------- // The CompilerFrameInfo class is more complicated because it maintains a // virtual stack to optimize some common stack operations. Locals and arguments // are always fully synced. Stack values can either be synced, stored as // constant, stored in a Value register or refer to a local slot. Syncing a // StackValue ensures it's stored on the stack, e.g. kind == Stack. // // To see how this works, consider the following statement: // // var y = x + 9; // // Here two values are pushed: StackValue(LocalSlot(0)) and // StackValue(Int32Value(9)). Only when we reach the ADD op, code is generated // to load the operands directly into the right operand registers and sync all // other stack values. // // For stack values, the following invariants hold (and are checked between // ops): // // (1) If a value is synced (kind == Stack), all values below it must also be // synced. In other words, values with kind other than Stack can only appear // on top of the abstract stack. // // (2) When we call a stub or IC, all values still on the stack must be synced. // Represents a value pushed on the stack. Note that StackValue is not used for // locals or arguments since these are always fully synced. class StackValue { public: enum Kind { Constant, Register, Stack, LocalSlot, ArgSlot, ThisSlot, EvalNewTargetSlot #ifdef DEBUG // In debug builds, assert Kind is initialized. , Uninitialized #endif }; private: MOZ_INIT_OUTSIDE_CTOR Kind kind_; MOZ_INIT_OUTSIDE_CTOR union Data { JS::Value constant; ValueOperand reg; uint32_t localSlot; uint32_t argSlot; // |constant| has a non-trivial constructor and therefore MUST be // placement-new'd into existence. MOZ_PUSH_DISABLE_NONTRIVIAL_UNION_WARNINGS Data() {} MOZ_POP_DISABLE_NONTRIVIAL_UNION_WARNINGS } data; MOZ_INIT_OUTSIDE_CTOR JSValueType knownType_; public: StackValue() { reset(); } Kind kind() const { return kind_; } bool hasKnownType() const { return knownType_ != JSVAL_TYPE_UNKNOWN; } bool hasKnownType(JSValueType type) const { MOZ_ASSERT(type != JSVAL_TYPE_UNKNOWN); return knownType_ == type; } JSValueType knownType() const { MOZ_ASSERT(hasKnownType()); return knownType_; } void reset() { #ifdef DEBUG kind_ = Uninitialized; knownType_ = JSVAL_TYPE_UNKNOWN; #endif } Value constant() const { MOZ_ASSERT(kind_ == Constant); return data.constant; } ValueOperand reg() const { MOZ_ASSERT(kind_ == Register); return data.reg; } uint32_t localSlot() const { MOZ_ASSERT(kind_ == LocalSlot); return data.localSlot; } uint32_t argSlot() const { MOZ_ASSERT(kind_ == ArgSlot); return data.argSlot; } void setConstant(const Value& v) { kind_ = Constant; new (&data.constant) Value(v); knownType_ = v.isDouble() ? JSVAL_TYPE_DOUBLE : v.extractNonDoubleType(); } void setRegister(const ValueOperand& val, JSValueType knownType = JSVAL_TYPE_UNKNOWN) { kind_ = Register; new (&data.reg) ValueOperand(val); knownType_ = knownType; } void setLocalSlot(uint32_t slot) { kind_ = LocalSlot; new (&data.localSlot) uint32_t(slot); knownType_ = JSVAL_TYPE_UNKNOWN; } void setArgSlot(uint32_t slot) { kind_ = ArgSlot; new (&data.argSlot) uint32_t(slot); knownType_ = JSVAL_TYPE_UNKNOWN; } void setThis() { kind_ = ThisSlot; knownType_ = JSVAL_TYPE_UNKNOWN; } void setEvalNewTarget() { kind_ = EvalNewTargetSlot; knownType_ = JSVAL_TYPE_UNKNOWN; } void setStack() { kind_ = Stack; knownType_ = JSVAL_TYPE_UNKNOWN; } }; enum StackAdjustment { AdjustStack, DontAdjustStack }; class FrameInfo { protected: MacroAssembler& masm; public: explicit FrameInfo(MacroAssembler& masm) : masm(masm) {} Address addressOfLocal(size_t local) const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(local)); } Address addressOfArg(size_t arg) const { return Address(BaselineFrameReg, BaselineFrame::offsetOfArg(arg)); } Address addressOfThis() const { return Address(BaselineFrameReg, BaselineFrame::offsetOfThis()); } Address addressOfEvalNewTarget() const { return Address(BaselineFrameReg, BaselineFrame::offsetOfEvalNewTarget()); } Address addressOfCalleeToken() const { return Address(BaselineFrameReg, BaselineFrame::offsetOfCalleeToken()); } Address addressOfEnvironmentChain() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfEnvironmentChain()); } Address addressOfICScript() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfICScript()); } Address addressOfFlags() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()); } Address addressOfReturnValue() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue()); } Address addressOfArgsObj() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfArgsObj()); } Address addressOfScratchValue() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValue()); } Address addressOfScratchValueLow32() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValueLow32()); } Address addressOfScratchValueHigh32() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfScratchValueHigh32()); } #ifdef DEBUG Address addressOfDebugFrameSize() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfDebugFrameSize()); } #endif }; class CompilerFrameInfo : public FrameInfo { JSScript* script; FixedList stack; size_t spIndex; public: CompilerFrameInfo(JSScript* script, MacroAssembler& masm) : FrameInfo(masm), script(script), stack(), spIndex(0) {} [[nodiscard]] bool init(TempAllocator& alloc); size_t nlocals() const { return script->nfixed(); } size_t nargs() const { return script->function()->nargs(); } private: inline StackValue* rawPush() { StackValue* val = &stack[spIndex++]; val->reset(); return val; } inline StackValue* peek(int32_t index) const { MOZ_ASSERT(index < 0); return const_cast(&stack[spIndex + index]); } public: inline size_t stackDepth() const { return spIndex; } inline void setStackDepth(uint32_t newDepth) { if (newDepth <= stackDepth()) { spIndex = newDepth; } else { uint32_t diff = newDepth - stackDepth(); for (uint32_t i = 0; i < diff; i++) { StackValue* val = rawPush(); val->setStack(); } MOZ_ASSERT(spIndex == newDepth); } } void assertStackDepth(uint32_t depth) { MOZ_ASSERT(stackDepth() == depth); } void incStackDepth(int32_t diff) { setStackDepth(stackDepth() + diff); } bool hasKnownStackDepth(uint32_t depth) { return stackDepth() == depth; } inline void pop(StackAdjustment adjust = AdjustStack); inline void popn(uint32_t n, StackAdjustment adjust = AdjustStack); inline void push(const Value& val) { StackValue* sv = rawPush(); sv->setConstant(val); } inline void push(const ValueOperand& val, JSValueType knownType = JSVAL_TYPE_UNKNOWN) { StackValue* sv = rawPush(); sv->setRegister(val, knownType); } inline void pushLocal(uint32_t local) { MOZ_ASSERT(local < nlocals()); StackValue* sv = rawPush(); sv->setLocalSlot(local); } inline void pushArg(uint32_t arg) { StackValue* sv = rawPush(); sv->setArgSlot(arg); } inline void pushThis() { StackValue* sv = rawPush(); sv->setThis(); } inline void pushEvalNewTarget() { MOZ_ASSERT(script->isForEval()); StackValue* sv = rawPush(); sv->setEvalNewTarget(); } inline void pushScratchValue() { masm.pushValue(addressOfScratchValue()); StackValue* sv = rawPush(); sv->setStack(); } Address addressOfLocal(size_t local) const { MOZ_ASSERT(local < nlocals()); return FrameInfo::addressOfLocal(local); } Address addressOfArg(size_t arg) const { MOZ_ASSERT(arg < nargs()); return FrameInfo::addressOfArg(arg); } Address addressOfStackValue(int32_t depth) const { const StackValue* value = peek(depth); MOZ_ASSERT(value->kind() == StackValue::Stack); size_t slot = value - &stack[0]; MOZ_ASSERT(slot < stackDepth()); return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfLocal(nlocals() + slot)); } void popValue(ValueOperand dest); void sync(StackValue* val); void syncStack(uint32_t uses); uint32_t numUnsyncedSlots(); void popRegsAndSync(uint32_t uses); void assertSyncedStack() const { MOZ_ASSERT_IF(stackDepth() > 0, peek(-1)->kind() == StackValue::Stack); } bool stackValueHasKnownType(int32_t depth, JSValueType type) const { return peek(depth)->hasKnownType(type); } mozilla::Maybe knownStackValue(int32_t depth) const { StackValue* val = peek(depth); if (val->kind() == StackValue::Constant) { return mozilla::Some(val->constant()); } return mozilla::Nothing(); } void storeStackValue(int32_t depth, const Address& dest, const ValueOperand& scratch); uint32_t frameSize() const { return BaselineFrame::frameSizeForNumValueSlots(nlocals() + stackDepth()); } #ifdef DEBUG // Assert the state is valid before excuting "pc". void assertValidState(const BytecodeInfo& info); #else inline void assertValidState(const BytecodeInfo& info) {} #endif }; class InterpreterFrameInfo : public FrameInfo { public: explicit InterpreterFrameInfo(MacroAssembler& masm) : FrameInfo(masm) {} // These methods are no-ops in the interpreter, because we don't have a // virtual stack there. void syncStack(uint32_t uses) {} void assertSyncedStack() const {} void assertStackDepth(uint32_t depth) {} void incStackDepth(int32_t diff) {} bool hasKnownStackDepth(uint32_t depth) { return false; } uint32_t numUnsyncedSlots() { return 0; } bool stackValueHasKnownType(int32_t depth, JSValueType type) const { return false; } mozilla::Maybe knownStackValue(int32_t depth) const { return mozilla::Nothing(); } Address addressOfStackValue(int depth) const { MOZ_ASSERT(depth < 0); return Address(masm.getStackPointer(), masm.framePushed() + size_t(-(depth + 1)) * sizeof(Value)); } BaseIndex addressOfStackValue(Register index, int32_t offset = 0) const { return BaseIndex(masm.getStackPointer(), index, ValueScale, offset); } void popRegsAndSync(uint32_t uses); void pop() { popn(1); } void popn(uint32_t n) { masm.addToStackPtr(Imm32(n * sizeof(Value))); } void popn(Register reg) { // sp := sp + reg * sizeof(Value) Register spReg = AsRegister(masm.getStackPointer()); masm.computeEffectiveAddress(BaseValueIndex(spReg, reg), spReg); } void popValue(ValueOperand dest) { masm.popValue(dest); } void push(const ValueOperand& val, JSValueType knownType = JSVAL_TYPE_UNKNOWN) { masm.pushValue(val); } void push(const Value& val) { masm.pushValue(val); } void pushThis() { masm.pushValue(addressOfThis()); } void pushEvalNewTarget() { masm.pushValue(addressOfEvalNewTarget()); } void pushScratchValue() { masm.pushValue(addressOfScratchValue()); } void storeStackValue(int32_t depth, const Address& dest, const ValueOperand& scratch) { masm.loadValue(addressOfStackValue(depth), scratch); masm.storeValue(scratch, dest); } void bumpInterpreterICEntry(); Address addressOfInterpreterScript() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfInterpreterScript()); } Address addressOfInterpreterPC() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfInterpreterPC()); } Address addressOfInterpreterICEntry() const { return Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfInterpreterICEntry()); } }; } // namespace jit } // namespace js #endif /* jit_BaselineFrameInfo_h */