mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-09 01:08:39 +09:00
Performance improvements jit
1322724: IonMonkey - Add the hit count information on the extra false branch blocks 1322932: IonMonkey - Only iterate the backedge of the inner-loop when it has already be visited 1329901 - Remove expensive isObservableOperand() loop guards. 1330667: IonMonkey - Create a new constant for every optimized arguments use. 1342016 - Fast-path for isObservableSlot(). 1388045 - Branch Pruning: Check the compile info associated with the resume point.
This commit is contained in:
parent
8a82d779d7
commit
5eb6f53b0d
8 changed files with 7759 additions and 6 deletions
File diff suppressed because it is too large
Load diff
2463
js/src/jit/CacheIRCompiler.cpp
Normal file
2463
js/src/jit/CacheIRCompiler.cpp
Normal file
File diff suppressed because it is too large
Load diff
752
js/src/jit/CacheIRCompiler.h
Normal file
752
js/src/jit/CacheIRCompiler.h
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
/* -*- 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_CacheIRCompiler_h
|
||||
#define jit_CacheIRCompiler_h
|
||||
|
||||
#include "jit/CacheIR.h"
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
// The ops below are defined in CacheIRCompiler and codegen is shared between
|
||||
// BaselineCacheIRCompiler and IonCacheIRCompiler.
|
||||
#define CACHE_IR_SHARED_OPS(_) \
|
||||
_(GuardIsObject) \
|
||||
_(GuardIsObjectOrNull) \
|
||||
_(GuardIsString) \
|
||||
_(GuardIsSymbol) \
|
||||
_(GuardIsInt32Index) \
|
||||
_(GuardType) \
|
||||
_(GuardClass) \
|
||||
_(GuardIsNativeFunction) \
|
||||
_(GuardIsProxy) \
|
||||
_(GuardIsCrossCompartmentWrapper) \
|
||||
_(GuardNotDOMProxy) \
|
||||
_(GuardSpecificInt32Immediate) \
|
||||
_(GuardMagicValue) \
|
||||
_(GuardNoUnboxedExpando) \
|
||||
_(GuardAndLoadUnboxedExpando) \
|
||||
_(GuardNoDetachedTypedObjects) \
|
||||
_(GuardNoDenseElements) \
|
||||
_(GuardAndGetIndexFromString) \
|
||||
_(LoadProto) \
|
||||
_(LoadEnclosingEnvironment) \
|
||||
_(LoadWrapperTarget) \
|
||||
_(LoadDOMExpandoValue) \
|
||||
_(LoadDOMExpandoValueIgnoreGeneration)\
|
||||
_(LoadUndefinedResult) \
|
||||
_(LoadBooleanResult) \
|
||||
_(LoadInt32ArrayLengthResult) \
|
||||
_(LoadUnboxedArrayLengthResult) \
|
||||
_(LoadArgumentsObjectLengthResult) \
|
||||
_(LoadFunctionLengthResult) \
|
||||
_(LoadStringLengthResult) \
|
||||
_(LoadStringCharResult) \
|
||||
_(LoadArgumentsObjectArgResult) \
|
||||
_(LoadDenseElementResult) \
|
||||
_(LoadDenseElementHoleResult) \
|
||||
_(LoadDenseElementExistsResult) \
|
||||
_(LoadDenseElementHoleExistsResult) \
|
||||
_(LoadUnboxedArrayElementResult) \
|
||||
_(LoadTypedElementResult) \
|
||||
_(LoadObjectResult) \
|
||||
_(LoadTypeOfObjectResult) \
|
||||
_(CallPrintString) \
|
||||
_(Breakpoint) \
|
||||
_(MegamorphicLoadSlotByValueResult) \
|
||||
_(MegamorphicHasOwnResult) \
|
||||
_(WrapResult)
|
||||
|
||||
// Represents a Value on the Baseline frame's expression stack. Slot 0 is the
|
||||
// value on top of the stack (the most recently pushed value), slot 1 is the
|
||||
// value pushed before that, etc.
|
||||
class BaselineFrameSlot
|
||||
{
|
||||
uint32_t slot_;
|
||||
|
||||
public:
|
||||
explicit BaselineFrameSlot(uint32_t slot) : slot_(slot) {}
|
||||
uint32_t slot() const { return slot_; }
|
||||
|
||||
bool operator==(const BaselineFrameSlot& other) const { return slot_ == other.slot_; }
|
||||
bool operator!=(const BaselineFrameSlot& other) const { return slot_ != other.slot_; }
|
||||
};
|
||||
|
||||
// OperandLocation represents the location of an OperandId. The operand is
|
||||
// either in a register or on the stack, and is either boxed or unboxed.
|
||||
class OperandLocation
|
||||
{
|
||||
public:
|
||||
enum Kind {
|
||||
Uninitialized = 0,
|
||||
PayloadReg,
|
||||
DoubleReg,
|
||||
ValueReg,
|
||||
PayloadStack,
|
||||
ValueStack,
|
||||
BaselineFrame,
|
||||
Constant,
|
||||
};
|
||||
|
||||
private:
|
||||
Kind kind_;
|
||||
|
||||
union Data {
|
||||
struct {
|
||||
Register reg;
|
||||
JSValueType type;
|
||||
} payloadReg;
|
||||
FloatRegister doubleReg;
|
||||
ValueOperand valueReg;
|
||||
struct {
|
||||
uint32_t stackPushed;
|
||||
JSValueType type;
|
||||
} payloadStack;
|
||||
uint32_t valueStackPushed;
|
||||
BaselineFrameSlot baselineFrameSlot;
|
||||
Value constant;
|
||||
|
||||
Data() : valueStackPushed(0) {}
|
||||
};
|
||||
Data data_;
|
||||
|
||||
public:
|
||||
OperandLocation() : kind_(Uninitialized) {}
|
||||
|
||||
Kind kind() const { return kind_; }
|
||||
|
||||
void setUninitialized() {
|
||||
kind_ = Uninitialized;
|
||||
}
|
||||
|
||||
ValueOperand valueReg() const {
|
||||
MOZ_ASSERT(kind_ == ValueReg);
|
||||
return data_.valueReg;
|
||||
}
|
||||
Register payloadReg() const {
|
||||
MOZ_ASSERT(kind_ == PayloadReg);
|
||||
return data_.payloadReg.reg;
|
||||
}
|
||||
FloatRegister doubleReg() const {
|
||||
MOZ_ASSERT(kind_ == DoubleReg);
|
||||
return data_.doubleReg;
|
||||
}
|
||||
uint32_t payloadStack() const {
|
||||
MOZ_ASSERT(kind_ == PayloadStack);
|
||||
return data_.payloadStack.stackPushed;
|
||||
}
|
||||
uint32_t valueStack() const {
|
||||
MOZ_ASSERT(kind_ == ValueStack);
|
||||
return data_.valueStackPushed;
|
||||
}
|
||||
JSValueType payloadType() const {
|
||||
if (kind_ == PayloadReg)
|
||||
return data_.payloadReg.type;
|
||||
MOZ_ASSERT(kind_ == PayloadStack);
|
||||
return data_.payloadStack.type;
|
||||
}
|
||||
Value constant() const {
|
||||
MOZ_ASSERT(kind_ == Constant);
|
||||
return data_.constant;
|
||||
}
|
||||
|
||||
BaselineFrameSlot baselineFrameSlot() const {
|
||||
MOZ_ASSERT(kind_ == BaselineFrame);
|
||||
return data_.baselineFrameSlot;
|
||||
}
|
||||
|
||||
void setPayloadReg(Register reg, JSValueType type) {
|
||||
kind_ = PayloadReg;
|
||||
data_.payloadReg.reg = reg;
|
||||
data_.payloadReg.type = type;
|
||||
}
|
||||
void setDoubleReg(FloatRegister reg) {
|
||||
kind_ = DoubleReg;
|
||||
data_.doubleReg = reg;
|
||||
}
|
||||
void setValueReg(ValueOperand reg) {
|
||||
kind_ = ValueReg;
|
||||
data_.valueReg = reg;
|
||||
}
|
||||
void setPayloadStack(uint32_t stackPushed, JSValueType type) {
|
||||
kind_ = PayloadStack;
|
||||
data_.payloadStack.stackPushed = stackPushed;
|
||||
data_.payloadStack.type = type;
|
||||
}
|
||||
void setValueStack(uint32_t stackPushed) {
|
||||
kind_ = ValueStack;
|
||||
data_.valueStackPushed = stackPushed;
|
||||
}
|
||||
void setConstant(const Value& v) {
|
||||
kind_ = Constant;
|
||||
data_.constant = v;
|
||||
}
|
||||
|
||||
void setBaselineFrame(BaselineFrameSlot slot) {
|
||||
kind_ = BaselineFrame;
|
||||
data_.baselineFrameSlot = slot;
|
||||
}
|
||||
|
||||
bool isInRegister() const { return kind_ == PayloadReg || kind_ == ValueReg; }
|
||||
bool isOnStack() const { return kind_ == PayloadStack || kind_ == ValueStack; }
|
||||
|
||||
size_t stackPushed() const {
|
||||
if (kind_ == PayloadStack)
|
||||
return data_.payloadStack.stackPushed;
|
||||
MOZ_ASSERT(kind_ == ValueStack);
|
||||
return data_.valueStackPushed;
|
||||
}
|
||||
size_t stackSizeInBytes() const {
|
||||
if (kind_ == PayloadStack)
|
||||
return sizeof(uintptr_t);
|
||||
MOZ_ASSERT(kind_ == ValueStack);
|
||||
return sizeof(js::Value);
|
||||
}
|
||||
void adjustStackPushed(int32_t diff) {
|
||||
if (kind_ == PayloadStack) {
|
||||
data_.payloadStack.stackPushed += diff;
|
||||
return;
|
||||
}
|
||||
MOZ_ASSERT(kind_ == ValueStack);
|
||||
data_.valueStackPushed += diff;
|
||||
}
|
||||
|
||||
bool aliasesReg(Register reg) const {
|
||||
if (kind_ == PayloadReg)
|
||||
return payloadReg() == reg;
|
||||
if (kind_ == ValueReg)
|
||||
return valueReg().aliases(reg);
|
||||
return false;
|
||||
}
|
||||
bool aliasesReg(ValueOperand reg) const {
|
||||
#if defined(JS_NUNBOX32)
|
||||
return aliasesReg(reg.typeReg()) || aliasesReg(reg.payloadReg());
|
||||
#else
|
||||
return aliasesReg(reg.valueReg());
|
||||
#endif
|
||||
}
|
||||
|
||||
bool aliasesReg(const OperandLocation& other) const;
|
||||
|
||||
bool operator==(const OperandLocation& other) const;
|
||||
bool operator!=(const OperandLocation& other) const { return !operator==(other); }
|
||||
};
|
||||
|
||||
struct SpilledRegister
|
||||
{
|
||||
Register reg;
|
||||
uint32_t stackPushed;
|
||||
|
||||
SpilledRegister(Register reg, uint32_t stackPushed)
|
||||
: reg(reg), stackPushed(stackPushed)
|
||||
{}
|
||||
bool operator==(const SpilledRegister& other) const {
|
||||
return reg == other.reg && stackPushed == other.stackPushed;
|
||||
}
|
||||
bool operator!=(const SpilledRegister& other) const { return !(*this == other); }
|
||||
};
|
||||
|
||||
using SpilledRegisterVector = Vector<SpilledRegister, 2, SystemAllocPolicy>;
|
||||
|
||||
// Class to track and allocate registers while emitting IC code.
|
||||
class MOZ_RAII CacheRegisterAllocator
|
||||
{
|
||||
// The original location of the inputs to the cache.
|
||||
Vector<OperandLocation, 4, SystemAllocPolicy> origInputLocations_;
|
||||
|
||||
// The current location of each operand.
|
||||
Vector<OperandLocation, 8, SystemAllocPolicy> operandLocations_;
|
||||
|
||||
// Free lists for value- and payload-slots on stack
|
||||
Vector<uint32_t, 2, SystemAllocPolicy> freeValueSlots_;
|
||||
Vector<uint32_t, 2, SystemAllocPolicy> freePayloadSlots_;
|
||||
|
||||
// The registers allocated while emitting the current CacheIR op.
|
||||
// This prevents us from allocating a register and then immediately
|
||||
// clobbering it for something else, while we're still holding on to it.
|
||||
LiveGeneralRegisterSet currentOpRegs_;
|
||||
|
||||
const AllocatableGeneralRegisterSet allocatableRegs_;
|
||||
|
||||
// Registers that are currently unused and available.
|
||||
AllocatableGeneralRegisterSet availableRegs_;
|
||||
|
||||
// Registers that are available, but before use they must be saved and
|
||||
// then restored when returning from the stub.
|
||||
AllocatableGeneralRegisterSet availableRegsAfterSpill_;
|
||||
|
||||
// Registers we took from availableRegsAfterSpill_ and spilled to the stack.
|
||||
SpilledRegisterVector spilledRegs_;
|
||||
|
||||
// The number of bytes pushed on the native stack.
|
||||
uint32_t stackPushed_;
|
||||
|
||||
// The index of the CacheIR instruction we're currently emitting.
|
||||
uint32_t currentInstruction_;
|
||||
|
||||
const CacheIRWriter& writer_;
|
||||
|
||||
CacheRegisterAllocator(const CacheRegisterAllocator&) = delete;
|
||||
CacheRegisterAllocator& operator=(const CacheRegisterAllocator&) = delete;
|
||||
|
||||
void freeDeadOperandLocations(MacroAssembler& masm);
|
||||
|
||||
void spillOperandToStack(MacroAssembler& masm, OperandLocation* loc);
|
||||
void spillOperandToStackOrRegister(MacroAssembler& masm, OperandLocation* loc);
|
||||
|
||||
void popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest);
|
||||
void popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest);
|
||||
|
||||
public:
|
||||
friend class AutoScratchRegister;
|
||||
friend class AutoScratchRegisterExcluding;
|
||||
|
||||
explicit CacheRegisterAllocator(const CacheIRWriter& writer)
|
||||
: allocatableRegs_(GeneralRegisterSet::All()),
|
||||
stackPushed_(0),
|
||||
currentInstruction_(0),
|
||||
writer_(writer)
|
||||
{}
|
||||
|
||||
[[nodiscard]] bool init();
|
||||
|
||||
void initAvailableRegs(const AllocatableGeneralRegisterSet& available) {
|
||||
availableRegs_ = available;
|
||||
}
|
||||
|
||||
void initAvailableRegsAfterSpill();
|
||||
|
||||
void fixupAliasedInputs(MacroAssembler& masm);
|
||||
|
||||
OperandLocation operandLocation(size_t i) const {
|
||||
return operandLocations_[i];
|
||||
}
|
||||
void setOperandLocation(size_t i, const OperandLocation& loc) {
|
||||
operandLocations_[i] = loc;
|
||||
}
|
||||
|
||||
OperandLocation origInputLocation(size_t i) const {
|
||||
return origInputLocations_[i];
|
||||
}
|
||||
void initInputLocation(size_t i, ValueOperand reg) {
|
||||
origInputLocations_[i].setValueReg(reg);
|
||||
operandLocations_[i].setValueReg(reg);
|
||||
}
|
||||
void initInputLocation(size_t i, Register reg, JSValueType type) {
|
||||
origInputLocations_[i].setPayloadReg(reg, type);
|
||||
operandLocations_[i].setPayloadReg(reg, type);
|
||||
}
|
||||
|
||||
void initInputLocation(size_t i, FloatRegister reg) {
|
||||
origInputLocations_[i].setDoubleReg(reg);
|
||||
operandLocations_[i].setDoubleReg(reg);
|
||||
}
|
||||
void initInputLocation(size_t i, const Value& v) {
|
||||
origInputLocations_[i].setConstant(v);
|
||||
operandLocations_[i].setConstant(v);
|
||||
}
|
||||
|
||||
void initInputLocation(size_t i, BaselineFrameSlot slot) {
|
||||
origInputLocations_[i].setBaselineFrame(slot);
|
||||
operandLocations_[i].setBaselineFrame(slot);
|
||||
}
|
||||
|
||||
void initInputLocation(size_t i, const TypedOrValueRegister& reg);
|
||||
void initInputLocation(size_t i, const ConstantOrRegister& value);
|
||||
|
||||
const SpilledRegisterVector& spilledRegs() const { return spilledRegs_; }
|
||||
|
||||
[[nodiscard]] bool setSpilledRegs(const SpilledRegisterVector& regs) {
|
||||
spilledRegs_.clear();
|
||||
return spilledRegs_.appendAll(regs);
|
||||
}
|
||||
|
||||
void nextOp() {
|
||||
currentOpRegs_.clear();
|
||||
currentInstruction_++;
|
||||
}
|
||||
|
||||
uint32_t stackPushed() const {
|
||||
return stackPushed_;
|
||||
}
|
||||
|
||||
void setStackPushed(uint32_t pushed) {
|
||||
stackPushed_ = pushed;
|
||||
}
|
||||
|
||||
bool isAllocatable(Register reg) const {
|
||||
return allocatableRegs_.has(reg);
|
||||
}
|
||||
|
||||
// Allocates a new register.
|
||||
Register allocateRegister(MacroAssembler& masm);
|
||||
ValueOperand allocateValueRegister(MacroAssembler& masm);
|
||||
|
||||
void allocateFixedRegister(MacroAssembler& masm, Register reg);
|
||||
void allocateFixedValueRegister(MacroAssembler& masm, ValueOperand reg);
|
||||
|
||||
// Releases a register so it can be reused later.
|
||||
void releaseRegister(Register reg) {
|
||||
MOZ_ASSERT(currentOpRegs_.has(reg));
|
||||
availableRegs_.add(reg);
|
||||
currentOpRegs_.take(reg);
|
||||
}
|
||||
void releaseValueRegister(ValueOperand reg) {
|
||||
#ifdef JS_NUNBOX32
|
||||
releaseRegister(reg.payloadReg());
|
||||
releaseRegister(reg.typeReg());
|
||||
#else
|
||||
releaseRegister(reg.valueReg());
|
||||
#endif
|
||||
}
|
||||
|
||||
// Removes spilled values from the native stack. This should only be
|
||||
// called after all registers have been allocated.
|
||||
void discardStack(MacroAssembler& masm);
|
||||
|
||||
Address addressOf(MacroAssembler& masm, BaselineFrameSlot slot) const;
|
||||
|
||||
// Returns the register for the given operand. If the operand is currently
|
||||
// not in a register, it will load it into one.
|
||||
ValueOperand useValueRegister(MacroAssembler& masm, ValOperandId val);
|
||||
ValueOperand useFixedValueRegister(MacroAssembler& masm, ValOperandId valId, ValueOperand reg);
|
||||
Register useRegister(MacroAssembler& masm, TypedOperandId typedId);
|
||||
|
||||
ConstantOrRegister useConstantOrRegister(MacroAssembler& masm, ValOperandId val);
|
||||
|
||||
// Allocates an output register for the given operand.
|
||||
Register defineRegister(MacroAssembler& masm, TypedOperandId typedId);
|
||||
ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val);
|
||||
|
||||
// Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN.
|
||||
JSValueType knownType(ValOperandId val) const;
|
||||
|
||||
// Emits code to restore registers and stack to the state at the start of
|
||||
// the stub.
|
||||
void restoreInputState(MacroAssembler& masm, bool discardStack = true);
|
||||
|
||||
// Returns the set of registers storing the IC input operands.
|
||||
GeneralRegisterSet inputRegisterSet() const;
|
||||
|
||||
void saveIonLiveRegisters(MacroAssembler& masm, LiveRegisterSet liveRegs,
|
||||
Register scratch, IonScript* ionScript);
|
||||
void restoreIonLiveRegisters(MacroAssembler& masm, LiveRegisterSet liveRegs);
|
||||
};
|
||||
|
||||
// RAII class to allocate a scratch register and release it when we're done
|
||||
// with it.
|
||||
class MOZ_RAII AutoScratchRegister
|
||||
{
|
||||
CacheRegisterAllocator& alloc_;
|
||||
Register reg_;
|
||||
|
||||
AutoScratchRegister(const AutoScratchRegister&) = delete;
|
||||
void operator=(const AutoScratchRegister&) = delete;
|
||||
|
||||
public:
|
||||
AutoScratchRegister(CacheRegisterAllocator& alloc, MacroAssembler& masm,
|
||||
Register reg = InvalidReg)
|
||||
: alloc_(alloc)
|
||||
{
|
||||
if (reg != InvalidReg) {
|
||||
alloc.allocateFixedRegister(masm, reg);
|
||||
reg_ = reg;
|
||||
} else {
|
||||
reg_ = alloc.allocateRegister(masm);
|
||||
}
|
||||
MOZ_ASSERT(alloc_.currentOpRegs_.has(reg_));
|
||||
}
|
||||
~AutoScratchRegister() {
|
||||
alloc_.releaseRegister(reg_);
|
||||
}
|
||||
|
||||
Register get() const { return reg_; }
|
||||
operator Register() const { return reg_; }
|
||||
};
|
||||
|
||||
// Like AutoScratchRegister, but lets the caller specify a register that should
|
||||
// not be allocated here.
|
||||
class MOZ_RAII AutoScratchRegisterExcluding
|
||||
{
|
||||
CacheRegisterAllocator& alloc_;
|
||||
Register reg_;
|
||||
|
||||
public:
|
||||
AutoScratchRegisterExcluding(CacheRegisterAllocator& alloc, MacroAssembler& masm,
|
||||
Register excluding)
|
||||
: alloc_(alloc)
|
||||
{
|
||||
MOZ_ASSERT(excluding != InvalidReg);
|
||||
|
||||
reg_ = alloc.allocateRegister(masm);
|
||||
|
||||
if (reg_ == excluding) {
|
||||
// We need a different register, so try again.
|
||||
reg_ = alloc.allocateRegister(masm);
|
||||
MOZ_ASSERT(reg_ != excluding);
|
||||
alloc_.releaseRegister(excluding);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(alloc_.currentOpRegs_.has(reg_));
|
||||
}
|
||||
~AutoScratchRegisterExcluding() {
|
||||
alloc_.releaseRegister(reg_);
|
||||
}
|
||||
operator Register() const { return reg_; }
|
||||
};
|
||||
|
||||
// The FailurePath class stores everything we need to generate a failure path
|
||||
// at the end of the IC code. The failure path restores the input registers, if
|
||||
// needed, and jumps to the next stub.
|
||||
class FailurePath
|
||||
{
|
||||
Vector<OperandLocation, 4, SystemAllocPolicy> inputs_;
|
||||
SpilledRegisterVector spilledRegs_;
|
||||
NonAssertingLabel label_;
|
||||
uint32_t stackPushed_;
|
||||
|
||||
public:
|
||||
FailurePath() = default;
|
||||
|
||||
FailurePath(FailurePath&& other)
|
||||
: inputs_(Move(other.inputs_)),
|
||||
spilledRegs_(Move(other.spilledRegs_)),
|
||||
label_(other.label_),
|
||||
stackPushed_(other.stackPushed_)
|
||||
{}
|
||||
|
||||
Label* label() { return &label_; }
|
||||
|
||||
void setStackPushed(uint32_t i) { stackPushed_ = i; }
|
||||
uint32_t stackPushed() const { return stackPushed_; }
|
||||
|
||||
[[nodiscard]] bool appendInput(const OperandLocation& loc) {
|
||||
return inputs_.append(loc);
|
||||
}
|
||||
OperandLocation input(size_t i) const {
|
||||
return inputs_[i];
|
||||
}
|
||||
|
||||
const SpilledRegisterVector& spilledRegs() const { return spilledRegs_; }
|
||||
|
||||
[[nodiscard]] bool setSpilledRegs(const SpilledRegisterVector& regs) {
|
||||
MOZ_ASSERT(spilledRegs_.empty());
|
||||
return spilledRegs_.appendAll(regs);
|
||||
}
|
||||
|
||||
// If canShareFailurePath(other) returns true, the same machine code will
|
||||
// be emitted for two failure paths, so we can share them.
|
||||
bool canShareFailurePath(const FailurePath& other) const;
|
||||
};
|
||||
|
||||
class AutoOutputRegister;
|
||||
|
||||
// Base class for BaselineCacheIRCompiler and IonCacheIRCompiler.
|
||||
class MOZ_RAII CacheIRCompiler
|
||||
{
|
||||
protected:
|
||||
friend class AutoOutputRegister;
|
||||
|
||||
enum class Mode { Baseline, Ion };
|
||||
|
||||
JSContext* cx_;
|
||||
CacheIRReader reader;
|
||||
const CacheIRWriter& writer_;
|
||||
MacroAssembler masm;
|
||||
|
||||
CacheRegisterAllocator allocator;
|
||||
Vector<FailurePath, 4, SystemAllocPolicy> failurePaths;
|
||||
|
||||
// Float registers that are live. Registers not in this set can be
|
||||
// clobbered and don't need to be saved before performing a VM call.
|
||||
// Doing this for non-float registers is a bit more complicated because
|
||||
// the IC register allocator allocates GPRs.
|
||||
LiveFloatRegisterSet liveFloatRegs_;
|
||||
|
||||
Maybe<TypedOrValueRegister> outputUnchecked_;
|
||||
Mode mode_;
|
||||
|
||||
// Whether this IC may read double values from uint32 arrays.
|
||||
Maybe<bool> allowDoubleResult_;
|
||||
|
||||
CacheIRCompiler(JSContext* cx, const CacheIRWriter& writer, Mode mode)
|
||||
: cx_(cx),
|
||||
reader(writer),
|
||||
writer_(writer),
|
||||
allocator(writer_),
|
||||
liveFloatRegs_(FloatRegisterSet::All()),
|
||||
mode_(mode)
|
||||
{
|
||||
MOZ_ASSERT(!writer.failed());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool addFailurePath(FailurePath** failure);
|
||||
[[nodiscard]] bool emitFailurePath(size_t i);
|
||||
|
||||
// Returns the set of volatile float registers that are live. These
|
||||
// registers need to be saved when making non-GC calls with callWithABI.
|
||||
FloatRegisterSet liveVolatileFloatRegs() const {
|
||||
return FloatRegisterSet::Intersect(liveFloatRegs_.set(), FloatRegisterSet::Volatile());
|
||||
}
|
||||
|
||||
void emitLoadTypedObjectResultShared(const Address& fieldAddr, Register scratch,
|
||||
TypedThingLayout layout, uint32_t typeDescr,
|
||||
const AutoOutputRegister& output);
|
||||
|
||||
void emitStoreTypedObjectReferenceProp(ValueOperand val, ReferenceTypeDescr::Type type,
|
||||
const Address& dest, Register scratch);
|
||||
|
||||
private:
|
||||
void emitPostBarrierShared(Register obj, const ConstantOrRegister& val, Register scratch,
|
||||
Register maybeIndex);
|
||||
|
||||
void emitPostBarrierShared(Register obj, ValueOperand val, Register scratch,
|
||||
Register maybeIndex) {
|
||||
emitPostBarrierShared(obj, ConstantOrRegister(val), scratch, maybeIndex);
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
void emitPostBarrierSlot(Register obj, const T& val, Register scratch) {
|
||||
emitPostBarrierShared(obj, val, scratch, InvalidReg);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void emitPostBarrierElement(Register obj, const T& val, Register scratch, Register index) {
|
||||
MOZ_ASSERT(index != InvalidReg);
|
||||
emitPostBarrierShared(obj, val, scratch, index);
|
||||
}
|
||||
|
||||
#define DEFINE_SHARED_OP(op) [[nodiscard]] bool emit##op();
|
||||
CACHE_IR_SHARED_OPS(DEFINE_SHARED_OP)
|
||||
#undef DEFINE_SHARED_OP
|
||||
};
|
||||
|
||||
// Ensures the IC's output register is available for writing.
|
||||
class MOZ_RAII AutoOutputRegister
|
||||
{
|
||||
TypedOrValueRegister output_;
|
||||
CacheRegisterAllocator& alloc_;
|
||||
|
||||
AutoOutputRegister(const AutoOutputRegister&) = delete;
|
||||
void operator=(const AutoOutputRegister&) = delete;
|
||||
|
||||
public:
|
||||
explicit AutoOutputRegister(CacheIRCompiler& compiler);
|
||||
~AutoOutputRegister();
|
||||
|
||||
Register maybeReg() const {
|
||||
if (output_.hasValue())
|
||||
return output_.valueReg().scratchReg();
|
||||
if (!output_.typedReg().isFloat())
|
||||
return output_.typedReg().gpr();
|
||||
return InvalidReg;
|
||||
}
|
||||
|
||||
bool hasValue() const { return output_.hasValue(); }
|
||||
ValueOperand valueReg() const { return output_.valueReg(); }
|
||||
AnyRegister typedReg() const { return output_.typedReg(); }
|
||||
|
||||
JSValueType type() const {
|
||||
MOZ_ASSERT(!hasValue());
|
||||
return ValueTypeFromMIRType(output_.type());
|
||||
}
|
||||
|
||||
operator TypedOrValueRegister() const { return output_; }
|
||||
};
|
||||
|
||||
// Like AutoScratchRegister, but reuse a register of |output| if possible.
|
||||
class MOZ_RAII AutoScratchRegisterMaybeOutput
|
||||
{
|
||||
mozilla::Maybe<AutoScratchRegister> scratch_;
|
||||
Register scratchReg_;
|
||||
|
||||
AutoScratchRegisterMaybeOutput(const AutoScratchRegisterMaybeOutput&) = delete;
|
||||
void operator=(const AutoScratchRegisterMaybeOutput&) = delete;
|
||||
|
||||
public:
|
||||
AutoScratchRegisterMaybeOutput(CacheRegisterAllocator& alloc, MacroAssembler& masm,
|
||||
const AutoOutputRegister& output)
|
||||
{
|
||||
scratchReg_ = output.maybeReg();
|
||||
if (scratchReg_ == InvalidReg) {
|
||||
scratch_.emplace(alloc, masm);
|
||||
scratchReg_ = scratch_.ref();
|
||||
}
|
||||
}
|
||||
|
||||
operator Register() const { return scratchReg_; }
|
||||
};
|
||||
|
||||
// See the 'Sharing Baseline stub code' comment in CacheIR.h for a description
|
||||
// of this class.
|
||||
class CacheIRStubInfo
|
||||
{
|
||||
// These fields don't require 8 bits, but GCC complains if these fields are
|
||||
// smaller than the size of the enums.
|
||||
CacheKind kind_ : 8;
|
||||
ICStubEngine engine_ : 8;
|
||||
bool makesGCCalls_ : 1;
|
||||
uint8_t stubDataOffset_;
|
||||
|
||||
const uint8_t* code_;
|
||||
uint32_t length_;
|
||||
const uint8_t* fieldTypes_;
|
||||
|
||||
CacheIRStubInfo(CacheKind kind, ICStubEngine engine, bool makesGCCalls,
|
||||
uint32_t stubDataOffset, const uint8_t* code, uint32_t codeLength,
|
||||
const uint8_t* fieldTypes)
|
||||
: kind_(kind),
|
||||
engine_(engine),
|
||||
makesGCCalls_(makesGCCalls),
|
||||
stubDataOffset_(stubDataOffset),
|
||||
code_(code),
|
||||
length_(codeLength),
|
||||
fieldTypes_(fieldTypes)
|
||||
{
|
||||
MOZ_ASSERT(kind_ == kind, "Kind must fit in bitfield");
|
||||
MOZ_ASSERT(engine_ == engine, "Engine must fit in bitfield");
|
||||
MOZ_ASSERT(stubDataOffset_ == stubDataOffset, "stubDataOffset must fit in uint8_t");
|
||||
}
|
||||
|
||||
CacheIRStubInfo(const CacheIRStubInfo&) = delete;
|
||||
CacheIRStubInfo& operator=(const CacheIRStubInfo&) = delete;
|
||||
|
||||
public:
|
||||
CacheKind kind() const { return kind_; }
|
||||
ICStubEngine engine() const { return engine_; }
|
||||
bool makesGCCalls() const { return makesGCCalls_; }
|
||||
|
||||
const uint8_t* code() const { return code_; }
|
||||
uint32_t codeLength() const { return length_; }
|
||||
uint32_t stubDataOffset() const { return stubDataOffset_; }
|
||||
|
||||
size_t stubDataSize() const;
|
||||
|
||||
StubField::Type fieldType(uint32_t i) const { return (StubField::Type)fieldTypes_[i]; }
|
||||
|
||||
static CacheIRStubInfo* New(CacheKind kind, ICStubEngine engine, bool canMakeCalls,
|
||||
uint32_t stubDataOffset, const CacheIRWriter& writer);
|
||||
|
||||
template <class Stub, class T>
|
||||
js::GCPtr<T>& getStubField(Stub* stub, uint32_t field) const;
|
||||
|
||||
template <class T>
|
||||
js::GCPtr<T>& getStubField(ICStub* stub, uint32_t field) const {
|
||||
return getStubField<ICStub, T>(stub, field);
|
||||
}
|
||||
|
||||
void copyStubData(ICStub* src, ICStub* dest) const;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void TraceCacheIRStub(JSTracer* trc, T* stub, const CacheIRStubInfo* stubInfo);
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
#endif /* jit_CacheIRCompiler_h */
|
||||
|
|
@ -7725,8 +7725,7 @@ CodeGenerator::visitCharCodeAt(LCharCodeAt* lir)
|
|||
|
||||
OutOfLineCode* ool = oolCallVM(CharCodeAtInfo, lir, ArgList(str, index), StoreRegisterTo(output));
|
||||
|
||||
masm.branchIfRope(str, ool->entry());
|
||||
masm.loadStringChar(str, index, output);
|
||||
masm.loadStringChar(str, index, output, ool->entry());
|
||||
|
||||
masm.bind(ool->rejoin());
|
||||
}
|
||||
|
|
|
|||
2185
js/src/jit/IonCacheIRCompiler.cpp
Normal file
2185
js/src/jit/IonCacheIRCompiler.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -409,6 +409,14 @@ MacroAssembler::branchIfRopeOrExternal(Register str, Register temp, Label* label
|
|||
branch32(Assembler::Equal, temp, Imm32(JSString::EXTERNAL_FLAGS), label);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchIfNotRope(Register str, Label* label)
|
||||
{
|
||||
Address flags(str, JSString::offsetOfFlags());
|
||||
static_assert(JSString::ROPE_FLAGS == 0, "Rope type flags must be 0");
|
||||
branchTest32(Assembler::NonZero, flags, Imm32(JSString::TYPE_FLAGS_MASK), label);
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::branchLatin1String(Register string, Label* label)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1310,19 +1310,40 @@ MacroAssembler::loadStringChars(Register str, Register dest)
|
|||
}
|
||||
|
||||
void
|
||||
MacroAssembler::loadStringChar(Register str, Register index, Register output)
|
||||
MacroAssembler::loadStringChar(Register str, Register index, Register output, Label* fail)
|
||||
{
|
||||
MOZ_ASSERT(str != output);
|
||||
MOZ_ASSERT(index != output);
|
||||
|
||||
loadStringChars(str, output);
|
||||
movePtr(str, output);
|
||||
|
||||
// This follows JSString::getChar.
|
||||
Label notRope;
|
||||
branchIfNotRope(str, ¬Rope);
|
||||
|
||||
// Load leftChild.
|
||||
loadPtr(Address(str, JSRope::offsetOfLeft()), output);
|
||||
|
||||
// Check if the index is contained in the leftChild.
|
||||
// Todo: Handle index in the rightChild.
|
||||
branch32(Assembler::BelowOrEqual, Address(output, JSString::offsetOfLength()), index, fail);
|
||||
|
||||
// If the left side is another rope, give up.
|
||||
branchIfRope(output, fail);
|
||||
|
||||
bind(¬Rope);
|
||||
|
||||
Label isLatin1, done;
|
||||
branchLatin1String(str, &isLatin1);
|
||||
// We have to check the left/right side for ropes,
|
||||
// because a TwoByte rope might have a Latin1 child.
|
||||
branchLatin1String(output, &isLatin1);
|
||||
|
||||
loadStringChars(output, output);
|
||||
load16ZeroExtend(BaseIndex(output, index, TimesTwo), output);
|
||||
jump(&done);
|
||||
|
||||
bind(&isLatin1);
|
||||
loadStringChars(output, output);
|
||||
load8ZeroExtend(BaseIndex(output, index, TimesOne), output);
|
||||
|
||||
bind(&done);
|
||||
|
|
|
|||
|
|
@ -1096,6 +1096,8 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
inline void branchIfRope(Register str, Label* label);
|
||||
inline void branchIfRopeOrExternal(Register str, Register temp, Label* label);
|
||||
|
||||
inline void branchIfNotRope(Register str, Label* label);
|
||||
|
||||
inline void branchLatin1String(Register string, Label* label);
|
||||
inline void branchTwoByteString(Register string, Label* label);
|
||||
|
||||
|
|
@ -1445,7 +1447,7 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
}
|
||||
|
||||
void loadStringChars(Register str, Register dest);
|
||||
void loadStringChar(Register str, Register index, Register output);
|
||||
void loadStringChar(Register str, Register index, Register output, Label* fail);
|
||||
|
||||
void loadJSContext(Register dest) {
|
||||
movePtr(ImmPtr(GetJitContext()->runtime->getJSContext()), dest);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue