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:
win7-7 2025-12-22 11:30:26 +02:00 committed by wuggy
commit 5eb6f53b0d
8 changed files with 7759 additions and 6 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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

View file

@ -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());
}

File diff suppressed because it is too large Load diff

View file

@ -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)
{

View file

@ -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, &notRope);
// 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(&notRope);
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);

View file

@ -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);