mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 934450 - Allow objects to have copy on write elements, r=billm,jandem.
This commit is contained in:
parent
c7d05c2620
commit
4228ccca2e
@ -1693,7 +1693,7 @@ class ValueOperations
|
||||
JS::Symbol *toSymbol() const { return value()->toSymbol(); }
|
||||
JSObject &toObject() const { return value()->toObject(); }
|
||||
JSObject *toObjectOrNull() const { return value()->toObjectOrNull(); }
|
||||
void *toGCThing() const { return value()->toGCThing(); }
|
||||
gc::Cell *toGCThing() const { return value()->toGCThing(); }
|
||||
uint64_t asRawBits() const { return value()->asRawBits(); }
|
||||
|
||||
JSValueType extractNonDoubleType() const { return value()->extractNonDoubleType(); }
|
||||
|
@ -3856,7 +3856,7 @@ EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp
|
||||
}
|
||||
|
||||
bool
|
||||
ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
|
||||
ParseNode::getConstantValue(ExclusiveContext *cx, AllowConstantObjects allowObjects, MutableHandleValue vp)
|
||||
{
|
||||
switch (getKind()) {
|
||||
case PNK_NUMBER:
|
||||
@ -3883,6 +3883,11 @@ ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
|
||||
unsigned count;
|
||||
ParseNode *pn;
|
||||
|
||||
if (allowObjects == DontAllowObjects)
|
||||
return false;
|
||||
if (allowObjects == DontAllowNestedObjects)
|
||||
allowObjects = DontAllowObjects;
|
||||
|
||||
if (getKind() == PNK_CALLSITEOBJ) {
|
||||
count = pn_count - 1;
|
||||
pn = pn_head->pn_next;
|
||||
@ -3899,7 +3904,7 @@ ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
|
||||
unsigned idx = 0;
|
||||
RootedId id(cx);
|
||||
for (; pn; idx++, pn = pn->pn_next) {
|
||||
if (!pn->getConstantValue(cx, &value))
|
||||
if (!pn->getConstantValue(cx, allowObjects, &value))
|
||||
return false;
|
||||
id = INT_TO_JSID(idx);
|
||||
if (!JSObject::defineGeneric(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
|
||||
@ -3915,6 +3920,11 @@ ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
|
||||
JS_ASSERT(isOp(JSOP_NEWINIT));
|
||||
JS_ASSERT(!(pn_xflags & PNX_NONCONST));
|
||||
|
||||
if (allowObjects == DontAllowObjects)
|
||||
return false;
|
||||
if (allowObjects == DontAllowNestedObjects)
|
||||
allowObjects = DontAllowObjects;
|
||||
|
||||
gc::AllocKind kind = GuessObjectGCKind(pn_count);
|
||||
RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, kind, MaybeSingletonObject));
|
||||
if (!obj)
|
||||
@ -3922,7 +3932,7 @@ ParseNode::getConstantValue(ExclusiveContext *cx, MutableHandleValue vp)
|
||||
|
||||
RootedValue value(cx), idvalue(cx);
|
||||
for (ParseNode *pn = pn_head; pn; pn = pn->pn_next) {
|
||||
if (!pn->pn_right->getConstantValue(cx, &value))
|
||||
if (!pn->pn_right->getConstantValue(cx, allowObjects, &value))
|
||||
return false;
|
||||
|
||||
ParseNode *pnid = pn->pn_left;
|
||||
@ -3976,7 +3986,7 @@ static bool
|
||||
EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
{
|
||||
RootedValue value(cx);
|
||||
if (!pn->getConstantValue(cx, &value))
|
||||
if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value))
|
||||
return false;
|
||||
|
||||
RootedObject obj(cx, &value.toObject());
|
||||
@ -3994,7 +4004,7 @@ static bool
|
||||
EmitCallSiteObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
{
|
||||
RootedValue value(cx);
|
||||
if (!pn->getConstantValue(cx, &value))
|
||||
if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value))
|
||||
return false;
|
||||
|
||||
JS_ASSERT(value.isObject());
|
||||
@ -6726,10 +6736,43 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn)
|
||||
break;
|
||||
|
||||
case PNK_ARRAY:
|
||||
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext())
|
||||
ok = EmitSingletonInitialiser(cx, bce, pn);
|
||||
else
|
||||
ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
|
||||
if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head) {
|
||||
if (bce->checkSingletonContext()) {
|
||||
// Bake in the object entirely if it will only be created once.
|
||||
ok = EmitSingletonInitialiser(cx, bce, pn);
|
||||
break;
|
||||
}
|
||||
|
||||
// If the array consists entirely of primitive values, make a
|
||||
// template object with copy on write elements that can be reused
|
||||
// every time the initializer executes.
|
||||
RootedValue value(cx);
|
||||
if (bce->emitterMode != BytecodeEmitter::SelfHosting &&
|
||||
bce->script->compileAndGo() &&
|
||||
pn->pn_count != 0 &&
|
||||
pn->getConstantValue(cx, ParseNode::DontAllowNestedObjects, &value))
|
||||
{
|
||||
// Note: the type of the template object might not yet reflect
|
||||
// that the object has copy on write elements. When the
|
||||
// interpreter or JIT compiler fetches the template, it should
|
||||
// use types::GetOrFixupCopyOnWriteObject to make sure the type
|
||||
// for the template is accurate. We don't do this here as we
|
||||
// want to use types::InitObject, which requires a finished
|
||||
// script.
|
||||
JSObject *obj = &value.toObject();
|
||||
if (!ObjectElements::MakeElementsCopyOnWrite(cx, obj))
|
||||
return false;
|
||||
|
||||
ObjectBox *objbox = bce->parser->newObjectBox(obj);
|
||||
if (!objbox)
|
||||
return false;
|
||||
|
||||
ok = EmitObjectOp(cx, objbox, JSOP_NEWARRAY_COPYONWRITE, bce);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count);
|
||||
break;
|
||||
|
||||
case PNK_ARRAYCOMP:
|
||||
|
@ -831,7 +831,13 @@ class ParseNode
|
||||
#endif
|
||||
;
|
||||
|
||||
bool getConstantValue(ExclusiveContext *cx, MutableHandleValue vp);
|
||||
enum AllowConstantObjects {
|
||||
DontAllowObjects = 0,
|
||||
DontAllowNestedObjects,
|
||||
AllowObjects
|
||||
};
|
||||
|
||||
bool getConstantValue(ExclusiveContext *cx, AllowConstantObjects allowObjects, MutableHandleValue vp);
|
||||
inline bool isConstant();
|
||||
|
||||
template <class NodeType>
|
||||
@ -1274,7 +1280,7 @@ struct CallSiteNode : public ListNode {
|
||||
}
|
||||
|
||||
bool getRawArrayValue(ExclusiveContext *cx, MutableHandleValue vp) {
|
||||
return pn_head->getConstantValue(cx, vp);
|
||||
return pn_head->getConstantValue(cx, AllowObjects, vp);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1007,14 +1007,34 @@ class HeapSlotArray
|
||||
{
|
||||
HeapSlot *array;
|
||||
|
||||
// Whether writes may be performed to the slots in this array. This helps
|
||||
// to control how object elements which may be copy on write are used.
|
||||
#ifdef DEBUG
|
||||
bool allowWrite_;
|
||||
#endif
|
||||
|
||||
public:
|
||||
explicit HeapSlotArray(HeapSlot *array) : array(array) {}
|
||||
explicit HeapSlotArray(HeapSlot *array, bool allowWrite)
|
||||
: array(array)
|
||||
#ifdef DEBUG
|
||||
, allowWrite_(allowWrite)
|
||||
#endif
|
||||
{}
|
||||
|
||||
operator const Value *() const { return Valueify(array); }
|
||||
operator HeapSlot *() const { return array; }
|
||||
operator HeapSlot *() const { JS_ASSERT(allowWrite()); return array; }
|
||||
|
||||
HeapSlotArray operator +(int offset) const { return HeapSlotArray(array + offset); }
|
||||
HeapSlotArray operator +(uint32_t offset) const { return HeapSlotArray(array + offset); }
|
||||
HeapSlotArray operator +(int offset) const { return HeapSlotArray(array + offset, allowWrite()); }
|
||||
HeapSlotArray operator +(uint32_t offset) const { return HeapSlotArray(array + offset, allowWrite()); }
|
||||
|
||||
private:
|
||||
bool allowWrite() const {
|
||||
#ifdef DEBUG
|
||||
return allowWrite_;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -849,7 +849,7 @@ ForkJoinNursery::copySlotsToTospace(JSObject *dst, JSObject *src, AllocKind dstK
|
||||
size_t
|
||||
ForkJoinNursery::copyElementsToTospace(JSObject *dst, JSObject *src, AllocKind dstKind)
|
||||
{
|
||||
if (src->hasEmptyElements())
|
||||
if (src->hasEmptyElements() || src->denseElementsAreCopyOnWrite())
|
||||
return 0;
|
||||
|
||||
ObjectElements *srcHeader = src->getElementsHeader();
|
||||
|
@ -1753,13 +1753,24 @@ GCMarker::processMarkStackTop(SliceBudget &budget)
|
||||
|
||||
unsigned nslots = obj->slotSpan();
|
||||
|
||||
if (!obj->hasEmptyElements()) {
|
||||
vp = obj->getDenseElements();
|
||||
do {
|
||||
if (obj->hasEmptyElements())
|
||||
break;
|
||||
|
||||
if (obj->denseElementsAreCopyOnWrite()) {
|
||||
JSObject *owner = obj->getElementsHeader()->ownerObject();
|
||||
if (owner != obj) {
|
||||
PushMarkStack(this, owner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vp = obj->getDenseElementsAllowCopyOnWrite();
|
||||
end = vp + obj->getDenseInitializedLength();
|
||||
if (!nslots)
|
||||
goto scan_value_array;
|
||||
pushValueArray(obj, vp, end);
|
||||
}
|
||||
} while (false);
|
||||
|
||||
vp = obj->fixedSlots();
|
||||
if (obj->slots) {
|
||||
|
@ -523,7 +523,9 @@ js::Nursery::traceObject(MinorCollectionTracer *trc, JSObject *obj)
|
||||
if (!obj->isNative())
|
||||
return;
|
||||
|
||||
if (!obj->hasEmptyElements())
|
||||
// Note: the contents of copy on write elements pointers are filled in
|
||||
// during parsing and cannot contain nursery pointers.
|
||||
if (!obj->hasEmptyElements() && !obj->denseElementsAreCopyOnWrite())
|
||||
markSlots(trc, obj->getDenseElements(), obj->getDenseInitializedLength());
|
||||
|
||||
HeapSlot *fixedStart, *fixedEnd, *dynStart, *dynEnd;
|
||||
@ -669,7 +671,7 @@ js::Nursery::moveSlotsToTenured(JSObject *dst, JSObject *src, AllocKind dstKind)
|
||||
size_t
|
||||
js::Nursery::moveElementsToTenured(JSObject *dst, JSObject *src, AllocKind dstKind)
|
||||
{
|
||||
if (src->hasEmptyElements())
|
||||
if (src->hasEmptyElements() || src->denseElementsAreCopyOnWrite())
|
||||
return 0;
|
||||
|
||||
Zone *zone = src->zone();
|
||||
|
@ -14,7 +14,7 @@ Debugger(global).onDebuggerStatement = function (frame) {
|
||||
};
|
||||
|
||||
global.log = '';
|
||||
global.eval("function f(n){var a=[1,2,3]} debugger;");
|
||||
global.eval("function f(n){var a=[1,2,n]} debugger;");
|
||||
global.f(3);
|
||||
// Should hit each item in the array.
|
||||
assertEq(global.log, "18 21 23 25 19 ");
|
||||
|
@ -3,7 +3,7 @@ load(libdir + "parallelarray-helpers.js");
|
||||
function buildSimple() {
|
||||
assertParallelModesCommute(["seq", "par"], function(m) {
|
||||
return Array.buildPar(256, function(i) {
|
||||
let obj = [0, 1, 2];
|
||||
let obj = [i, 1, 2];
|
||||
obj[0] += 1;
|
||||
obj[1] += 1;
|
||||
obj[2] += 1;
|
||||
|
@ -1649,6 +1649,32 @@ BaselineCompiler::emit_JSOP_NEWARRAY()
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef JSObject *(*NewArrayCopyOnWriteFn)(JSContext *, HandleObject, gc::InitialHeap);
|
||||
const VMFunction jit::NewArrayCopyOnWriteInfo =
|
||||
FunctionInfo<NewArrayCopyOnWriteFn>(js::NewDenseCopyOnWriteArray);
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_NEWARRAY_COPYONWRITE()
|
||||
{
|
||||
RootedScript scriptRoot(cx, script);
|
||||
JSObject *obj = types::GetOrFixupCopyOnWriteObject(cx, scriptRoot, pc);
|
||||
if (!obj)
|
||||
return false;
|
||||
|
||||
prepareVMCall();
|
||||
|
||||
pushArg(Imm32(gc::DefaultHeap));
|
||||
pushArg(ImmGCPtr(obj));
|
||||
|
||||
if (!callVM(NewArrayCopyOnWriteInfo))
|
||||
return false;
|
||||
|
||||
// Box and push return value.
|
||||
masm.tagValue(JSVAL_TYPE_OBJECT, ReturnReg, R0);
|
||||
frame.push(R0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCompiler::emit_JSOP_INITELEM_ARRAY()
|
||||
{
|
||||
|
@ -92,6 +92,7 @@ namespace jit {
|
||||
_(JSOP_BITNOT) \
|
||||
_(JSOP_NEG) \
|
||||
_(JSOP_NEWARRAY) \
|
||||
_(JSOP_NEWARRAY_COPYONWRITE) \
|
||||
_(JSOP_INITELEM_ARRAY) \
|
||||
_(JSOP_NEWOBJECT) \
|
||||
_(JSOP_NEWINIT) \
|
||||
@ -273,6 +274,8 @@ class BaselineCompiler : public BaselineCompilerSpecific
|
||||
Address getScopeCoordinateAddress(Register reg);
|
||||
};
|
||||
|
||||
extern const VMFunction NewArrayCopyOnWriteInfo;
|
||||
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
|
@ -5256,6 +5256,20 @@ ICSetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
BaseIndex element(scratchReg, key, TimesEight);
|
||||
masm.branchTestMagic(Assembler::Equal, element, &failure);
|
||||
|
||||
// Perform a single test to see if we either need to convert double
|
||||
// elements or clone the copy on write elements in the object.
|
||||
Label noSpecialHandling;
|
||||
Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, elementsFlags,
|
||||
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS |
|
||||
ObjectElements::COPY_ON_WRITE),
|
||||
&noSpecialHandling);
|
||||
|
||||
// Fail if we need to clone copy on write elements.
|
||||
masm.branchTest32(Assembler::NonZero, elementsFlags,
|
||||
Imm32(ObjectElements::COPY_ON_WRITE),
|
||||
&failure);
|
||||
|
||||
// Failure is not possible now. Free up registers.
|
||||
regs.add(R0);
|
||||
regs.add(R1);
|
||||
@ -5263,21 +5277,17 @@ ICSetElem_Dense::Compiler::generateStubCode(MacroAssembler &masm)
|
||||
regs.takeUnchecked(key);
|
||||
Address valueAddr(BaselineStackReg, ICStackValueOffset);
|
||||
|
||||
// Convert int32 values to double if convertDoubleElements is set. In this
|
||||
// case the heap typeset is guaranteed to contain both int32 and double, so
|
||||
// it's okay to store a double.
|
||||
Label dontConvertDoubles;
|
||||
Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, elementsFlags,
|
||||
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
|
||||
&dontConvertDoubles);
|
||||
// Note that double arrays are only created by IonMonkey, so if we have no
|
||||
// floating-point support Ion is disabled and there should be no double arrays.
|
||||
// We need to convert int32 values being stored into doubles. In this case
|
||||
// the heap typeset is guaranteed to contain both int32 and double, so it's
|
||||
// okay to store a double. Note that double arrays are only created by
|
||||
// IonMonkey, so if we have no floating-point support Ion is disabled and
|
||||
// there should be no double arrays.
|
||||
if (cx->runtime()->jitSupportsFloatingPoint)
|
||||
masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &dontConvertDoubles);
|
||||
masm.convertInt32ValueToDouble(valueAddr, regs.getAny(), &noSpecialHandling);
|
||||
else
|
||||
masm.assumeUnreachable("There shouldn't be double arrays when there is no FP support.");
|
||||
masm.bind(&dontConvertDoubles);
|
||||
|
||||
masm.bind(&noSpecialHandling);
|
||||
|
||||
// Don't overwrite R0 becuase |obj| might overlap with it, and it's needed
|
||||
// for post-write barrier later.
|
||||
@ -5429,6 +5439,12 @@ ICSetElemDenseAddCompiler::generateStubCode(MacroAssembler &masm)
|
||||
Address capacity(scratchReg, ObjectElements::offsetOfCapacity());
|
||||
masm.branch32(Assembler::BelowOrEqual, capacity, key, &failure);
|
||||
|
||||
// Check for copy on write elements.
|
||||
Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::NonZero, elementsFlags,
|
||||
Imm32(ObjectElements::COPY_ON_WRITE),
|
||||
&failure);
|
||||
|
||||
// Failure is not possible now. Free up registers.
|
||||
regs.add(R0);
|
||||
regs.add(R1);
|
||||
@ -5451,7 +5467,6 @@ ICSetElemDenseAddCompiler::generateStubCode(MacroAssembler &masm)
|
||||
// case the heap typeset is guaranteed to contain both int32 and double, so
|
||||
// it's okay to store a double.
|
||||
Label dontConvertDoubles;
|
||||
Address elementsFlags(scratchReg, ObjectElements::offsetOfFlags());
|
||||
masm.branchTest32(Assembler::Zero, elementsFlags,
|
||||
Imm32(ObjectElements::CONVERT_DOUBLE_ELEMENTS),
|
||||
&dontConvertDoubles);
|
||||
|
@ -22,6 +22,7 @@
|
||||
#ifdef JSGC_GENERATIONAL
|
||||
# include "gc/Nursery.h"
|
||||
#endif
|
||||
#include "jit/BaselineCompiler.h"
|
||||
#include "jit/IonCaches.h"
|
||||
#include "jit/IonLinker.h"
|
||||
#include "jit/IonOptimizationLevels.h"
|
||||
@ -1868,6 +1869,30 @@ CodeGenerator::visitMaybeToDoubleElement(LMaybeToDoubleElement *lir)
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*CopyElementsForWriteFn)(ThreadSafeContext *, JSObject *);
|
||||
static const VMFunction CopyElementsForWriteInfo =
|
||||
FunctionInfo<CopyElementsForWriteFn>(JSObject::CopyElementsForWrite);
|
||||
|
||||
bool
|
||||
CodeGenerator::visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite *lir)
|
||||
{
|
||||
Register object = ToRegister(lir->object());
|
||||
Register temp = ToRegister(lir->temp());
|
||||
|
||||
OutOfLineCode *ool = oolCallVM(CopyElementsForWriteInfo, lir,
|
||||
(ArgList(), object), StoreNothing());
|
||||
if (!ool)
|
||||
return false;
|
||||
|
||||
masm.loadPtr(Address(object, JSObject::offsetOfElements()), temp);
|
||||
masm.branchTest32(Assembler::NonZero,
|
||||
Address(temp, ObjectElements::offsetOfFlags()),
|
||||
Imm32(ObjectElements::COPY_ON_WRITE),
|
||||
ool->entry());
|
||||
masm.bind(ool->rejoin());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CodeGenerator::visitFunctionEnvironment(LFunctionEnvironment *lir)
|
||||
{
|
||||
@ -3566,6 +3591,27 @@ CodeGenerator::visitOutOfLineNewArray(OutOfLineNewArray *ool)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
CodeGenerator::visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite *lir)
|
||||
{
|
||||
Register objReg = ToRegister(lir->output());
|
||||
Register tempReg = ToRegister(lir->temp());
|
||||
JSObject *templateObject = lir->mir()->templateObject();
|
||||
gc::InitialHeap initialHeap = lir->mir()->initialHeap();
|
||||
|
||||
// If we have a template object, we can inline call object creation.
|
||||
OutOfLineCode *ool = oolCallVM(NewArrayCopyOnWriteInfo, lir,
|
||||
(ArgList(), ImmGCPtr(templateObject), Imm32(initialHeap)),
|
||||
StoreRegisterTo(objReg));
|
||||
if (!ool)
|
||||
return false;
|
||||
|
||||
masm.createGCObject(objReg, tempReg, templateObject, initialHeap, ool->entry());
|
||||
|
||||
masm.bind(ool->rejoin());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Out-of-line object allocation for JSOP_NEWOBJECT.
|
||||
class OutOfLineNewObject : public OutOfLineCodeBase<CodeGenerator>
|
||||
{
|
||||
|
@ -115,6 +115,7 @@ class CodeGenerator : public CodeGeneratorSpecific
|
||||
bool visitElements(LElements *lir);
|
||||
bool visitConvertElementsToDoubles(LConvertElementsToDoubles *lir);
|
||||
bool visitMaybeToDoubleElement(LMaybeToDoubleElement *lir);
|
||||
bool visitMaybeCopyElementsForWrite(LMaybeCopyElementsForWrite *lir);
|
||||
bool visitGuardObjectIdentity(LGuardObjectIdentity *guard);
|
||||
bool visitGuardShapePolymorphic(LGuardShapePolymorphic *lir);
|
||||
bool visitTypeBarrierV(LTypeBarrierV *lir);
|
||||
@ -144,6 +145,7 @@ class CodeGenerator : public CodeGeneratorSpecific
|
||||
bool visitNewArrayCallVM(LNewArray *lir);
|
||||
bool visitNewArray(LNewArray *lir);
|
||||
bool visitOutOfLineNewArray(OutOfLineNewArray *ool);
|
||||
bool visitNewArrayCopyOnWrite(LNewArrayCopyOnWrite *lir);
|
||||
bool visitNewObjectVMCall(LNewObject *lir);
|
||||
bool visitNewObject(LNewObject *lir);
|
||||
bool visitOutOfLineNewObject(OutOfLineNewObject *ool);
|
||||
|
@ -2268,6 +2268,16 @@ TryEliminateTypeBarrier(MTypeBarrier *barrier, bool *eliminated)
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline MDefinition *
|
||||
PassthroughOperand(MDefinition *def)
|
||||
{
|
||||
if (def->isConvertElementsToDoubles())
|
||||
return def->toConvertElementsToDoubles()->elements();
|
||||
if (def->isMaybeCopyElementsForWrite())
|
||||
return def->toMaybeCopyElementsForWrite()->object();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Eliminate checks which are redundant given each other or other instructions.
|
||||
//
|
||||
// A type barrier is considered redundant if all missing types have been tested
|
||||
@ -2324,11 +2334,12 @@ jit::EliminateRedundantChecks(MIRGraph &graph)
|
||||
} else if (iter->isTypeBarrier()) {
|
||||
if (!TryEliminateTypeBarrier(iter->toTypeBarrier(), &eliminated))
|
||||
return false;
|
||||
} else if (iter->isConvertElementsToDoubles()) {
|
||||
// Now that code motion passes have finished, replace any
|
||||
// ConvertElementsToDoubles with the actual elements.
|
||||
MConvertElementsToDoubles *ins = iter->toConvertElementsToDoubles();
|
||||
ins->replaceAllUsesWith(ins->elements());
|
||||
} else {
|
||||
// Now that code motion passes have finished, replace
|
||||
// instructions which pass through one of their operands
|
||||
// (and perform additional checks) with that operand.
|
||||
if (MDefinition *passthrough = PassthroughOperand(*iter))
|
||||
iter->replaceAllUsesWith(passthrough);
|
||||
}
|
||||
|
||||
if (eliminated)
|
||||
|
@ -1547,6 +1547,9 @@ IonBuilder::inspectOpcode(JSOp op)
|
||||
case JSOP_NEWARRAY:
|
||||
return jsop_newarray(GET_UINT24(pc));
|
||||
|
||||
case JSOP_NEWARRAY_COPYONWRITE:
|
||||
return jsop_newarray_copyonwrite();
|
||||
|
||||
case JSOP_NEWOBJECT:
|
||||
return jsop_newobject();
|
||||
|
||||
@ -5536,6 +5539,28 @@ IonBuilder::jsop_newarray(uint32_t count)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_newarray_copyonwrite()
|
||||
{
|
||||
JSObject *templateObject = types::GetCopyOnWriteObject(script(), pc);
|
||||
|
||||
// The baseline compiler should have ensured the template object has a type
|
||||
// with the copy on write flag set already. During the arguments usage
|
||||
// analysis the baseline compiler hasn't run yet, however, though in this
|
||||
// case the template object's type doesn't matter.
|
||||
JS_ASSERT_IF(info().executionMode() != ArgumentsUsageAnalysis,
|
||||
templateObject->type()->hasAnyFlags(types::OBJECT_FLAG_COPY_ON_WRITE));
|
||||
|
||||
MNewArrayCopyOnWrite *ins =
|
||||
MNewArrayCopyOnWrite::New(alloc(), constraints(), templateObject,
|
||||
templateObject->type()->initialHeap(constraints()));
|
||||
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_newobject()
|
||||
{
|
||||
@ -7931,6 +7956,9 @@ IonBuilder::setElemTryCache(bool *emitted, MDefinition *object,
|
||||
// another value.
|
||||
bool guardHoles = ElementAccessHasExtraIndexedProperty(constraints(), object);
|
||||
|
||||
// Make sure the object being written to doesn't have copy on write elements.
|
||||
object = addMaybeCopyElementsForWrite(object);
|
||||
|
||||
if (NeedsPostBarrier(info(), value))
|
||||
current->add(MPostWriteBarrier::New(alloc(), object, value));
|
||||
|
||||
@ -7966,6 +7994,9 @@ IonBuilder::jsop_setelem_dense(types::TemporaryTypeSet::DoubleConversion convers
|
||||
current->add(idInt32);
|
||||
id = idInt32;
|
||||
|
||||
// Copy the elements vector if necessary.
|
||||
obj = addMaybeCopyElementsForWrite(obj);
|
||||
|
||||
// Get the elements vector.
|
||||
MElements *elements = MElements::New(alloc(), obj);
|
||||
current->add(elements);
|
||||
@ -10258,6 +10289,16 @@ IonBuilder::addConvertElementsToDoubles(MDefinition *elements)
|
||||
return convert;
|
||||
}
|
||||
|
||||
MDefinition *
|
||||
IonBuilder::addMaybeCopyElementsForWrite(MDefinition *object)
|
||||
{
|
||||
if (!ElementAccessMightBeCopyOnWrite(constraints(), object))
|
||||
return object;
|
||||
MInstruction *copy = MMaybeCopyElementsForWrite::New(alloc(), object);
|
||||
current->add(copy);
|
||||
return copy;
|
||||
}
|
||||
|
||||
MInstruction *
|
||||
IonBuilder::addBoundsCheck(MDefinition *index, MDefinition *length)
|
||||
{
|
||||
|
@ -375,6 +375,7 @@ class IonBuilder : public MIRGenerator
|
||||
MDefinition *walkScopeChain(unsigned hops);
|
||||
|
||||
MInstruction *addConvertElementsToDoubles(MDefinition *elements);
|
||||
MDefinition *addMaybeCopyElementsForWrite(MDefinition *object);
|
||||
MInstruction *addBoundsCheck(MDefinition *index, MDefinition *length);
|
||||
MInstruction *addShapeGuard(MDefinition *obj, Shape *const shape, BailoutKind bailoutKind);
|
||||
|
||||
@ -618,6 +619,7 @@ class IonBuilder : public MIRGenerator
|
||||
bool jsop_delprop(PropertyName *name);
|
||||
bool jsop_delelem();
|
||||
bool jsop_newarray(uint32_t count);
|
||||
bool jsop_newarray_copyonwrite();
|
||||
bool jsop_newobject();
|
||||
bool jsop_initelem();
|
||||
bool jsop_initelem_array();
|
||||
|
@ -609,6 +609,12 @@ MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateOb
|
||||
gc::AllocKind allocKind = templateObj->tenuredGetAllocKind();
|
||||
JS_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST);
|
||||
|
||||
// Arrays with copy on write elements do not need fixed space for an
|
||||
// elements header. The template object, which owns the original elements,
|
||||
// might have another allocation kind.
|
||||
if (templateObj->denseElementsAreCopyOnWrite())
|
||||
allocKind = gc::FINALIZE_OBJECT0_BACKGROUND;
|
||||
|
||||
allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail);
|
||||
initGCThing(obj, temp, templateObj, initFixedSlots);
|
||||
}
|
||||
@ -844,7 +850,7 @@ MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
|
||||
{
|
||||
// Fast initialization of an empty object returned by allocateObject().
|
||||
|
||||
JS_ASSERT(!templateObj->hasDynamicElements());
|
||||
JS_ASSERT_IF(!templateObj->denseElementsAreCopyOnWrite(), !templateObj->hasDynamicElements());
|
||||
|
||||
storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape()));
|
||||
storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType()));
|
||||
@ -853,7 +859,10 @@ MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj,
|
||||
else
|
||||
storePtr(ImmPtr(nullptr), Address(obj, JSObject::offsetOfSlots()));
|
||||
|
||||
if (templateObj->is<ArrayObject>()) {
|
||||
if (templateObj->denseElementsAreCopyOnWrite()) {
|
||||
storePtr(ImmPtr((const Value *) templateObj->getDenseElements()),
|
||||
Address(obj, JSObject::offsetOfElements()));
|
||||
} else if (templateObj->is<ArrayObject>()) {
|
||||
Register temp = slots;
|
||||
JS_ASSERT(!templateObj->getDenseInitializedLength());
|
||||
|
||||
|
@ -437,6 +437,24 @@ class LNewArray : public LInstructionHelper<1, 0, 1>
|
||||
}
|
||||
};
|
||||
|
||||
class LNewArrayCopyOnWrite : public LInstructionHelper<1, 0, 1>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(NewArrayCopyOnWrite)
|
||||
|
||||
explicit LNewArrayCopyOnWrite(const LDefinition &temp) {
|
||||
setTemp(0, temp);
|
||||
}
|
||||
|
||||
const LDefinition *temp() {
|
||||
return getTemp(0);
|
||||
}
|
||||
|
||||
MNewArrayCopyOnWrite *mir() const {
|
||||
return mir_->toNewArrayCopyOnWrite();
|
||||
}
|
||||
};
|
||||
|
||||
class LNewObject : public LInstructionHelper<1, 0, 1>
|
||||
{
|
||||
public:
|
||||
@ -3759,6 +3777,26 @@ class LMaybeToDoubleElement : public LInstructionHelper<BOX_PIECES, 2, 1>
|
||||
}
|
||||
};
|
||||
|
||||
// If necessary, copy the elements in an object so they may be written to.
|
||||
class LMaybeCopyElementsForWrite : public LInstructionHelper<0, 1, 1>
|
||||
{
|
||||
public:
|
||||
LIR_HEADER(MaybeCopyElementsForWrite)
|
||||
|
||||
explicit LMaybeCopyElementsForWrite(const LAllocation &obj, const LDefinition &temp) {
|
||||
setOperand(0, obj);
|
||||
setTemp(0, temp);
|
||||
}
|
||||
|
||||
const LAllocation *object() {
|
||||
return getOperand(0);
|
||||
}
|
||||
|
||||
const LDefinition *temp() {
|
||||
return getTemp(0);
|
||||
}
|
||||
};
|
||||
|
||||
// Load the initialized length from an elements header.
|
||||
class LInitializedLength : public LInstructionHelper<1, 1, 0>
|
||||
{
|
||||
|
@ -31,6 +31,7 @@
|
||||
_(TableSwitchV) \
|
||||
_(Goto) \
|
||||
_(NewArray) \
|
||||
_(NewArrayCopyOnWrite) \
|
||||
_(ArraySplice) \
|
||||
_(NewObject) \
|
||||
_(NewDeclEnvObject) \
|
||||
@ -174,6 +175,7 @@
|
||||
_(Elements) \
|
||||
_(ConvertElementsToDoubles) \
|
||||
_(MaybeToDoubleElement) \
|
||||
_(MaybeCopyElementsForWrite) \
|
||||
_(LoadSlotV) \
|
||||
_(LoadSlotT) \
|
||||
_(StoreSlotV) \
|
||||
|
@ -155,6 +155,13 @@ LIRGenerator::visitNewArray(MNewArray *ins)
|
||||
return define(lir, ins) && assignSafepoint(lir, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitNewArrayCopyOnWrite(MNewArrayCopyOnWrite *ins)
|
||||
{
|
||||
LNewArrayCopyOnWrite *lir = new(alloc()) LNewArrayCopyOnWrite(temp());
|
||||
return define(lir, ins) && assignSafepoint(lir, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitNewObject(MNewObject *ins)
|
||||
{
|
||||
@ -2165,6 +2172,13 @@ LIRGenerator::visitMaybeToDoubleElement(MMaybeToDoubleElement *ins)
|
||||
return defineBox(lir, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite *ins)
|
||||
{
|
||||
LInstruction *check = new(alloc()) LMaybeCopyElementsForWrite(useRegister(ins->object()), temp());
|
||||
return add(check, ins) && assignSafepoint(check, ins);
|
||||
}
|
||||
|
||||
bool
|
||||
LIRGenerator::visitLoadSlot(MLoadSlot *ins)
|
||||
{
|
||||
|
@ -70,6 +70,7 @@ class LIRGenerator : public LIRGeneratorSpecific
|
||||
bool visitGoto(MGoto *ins);
|
||||
bool visitTableSwitch(MTableSwitch *tableswitch);
|
||||
bool visitNewArray(MNewArray *ins);
|
||||
bool visitNewArrayCopyOnWrite(MNewArrayCopyOnWrite *ins);
|
||||
bool visitNewObject(MNewObject *ins);
|
||||
bool visitNewDeclEnvObject(MNewDeclEnvObject *ins);
|
||||
bool visitNewCallObject(MNewCallObject *ins);
|
||||
@ -166,6 +167,7 @@ class LIRGenerator : public LIRGeneratorSpecific
|
||||
bool visitConstantElements(MConstantElements *ins);
|
||||
bool visitConvertElementsToDoubles(MConvertElementsToDoubles *ins);
|
||||
bool visitMaybeToDoubleElement(MMaybeToDoubleElement *ins);
|
||||
bool visitMaybeCopyElementsForWrite(MMaybeCopyElementsForWrite *ins);
|
||||
bool visitLoadSlot(MLoadSlot *ins);
|
||||
bool visitFunctionEnvironment(MFunctionEnvironment *ins);
|
||||
bool visitForkJoinContext(MForkJoinContext *ins);
|
||||
|
@ -408,7 +408,8 @@ IonBuilder::inlineArrayPopShift(CallInfo &callInfo, MArrayPopShift::Mode mode)
|
||||
types::OBJECT_FLAG_LENGTH_OVERFLOW |
|
||||
types::OBJECT_FLAG_ITERATED;
|
||||
|
||||
types::TemporaryTypeSet *thisTypes = callInfo.thisArg()->resultTypeSet();
|
||||
MDefinition *obj = callInfo.thisArg();
|
||||
types::TemporaryTypeSet *thisTypes = obj->resultTypeSet();
|
||||
if (!thisTypes || thisTypes->getKnownClass() != &ArrayObject::class_)
|
||||
return InliningStatus_NotInlined;
|
||||
if (thisTypes->hasObjectFlags(constraints(), unhandledFlags))
|
||||
@ -419,17 +420,18 @@ IonBuilder::inlineArrayPopShift(CallInfo &callInfo, MArrayPopShift::Mode mode)
|
||||
|
||||
callInfo.setImplicitlyUsedUnchecked();
|
||||
|
||||
obj = addMaybeCopyElementsForWrite(obj);
|
||||
|
||||
types::TemporaryTypeSet *returnTypes = getInlineReturnTypeSet();
|
||||
bool needsHoleCheck = thisTypes->hasObjectFlags(constraints(), types::OBJECT_FLAG_NON_PACKED);
|
||||
bool maybeUndefined = returnTypes->hasType(types::Type::UndefinedType());
|
||||
|
||||
BarrierKind barrier = PropertyReadNeedsTypeBarrier(analysisContext, constraints(),
|
||||
callInfo.thisArg(), nullptr, returnTypes);
|
||||
obj, nullptr, returnTypes);
|
||||
if (barrier != BarrierKind::NoBarrier)
|
||||
returnType = MIRType_Value;
|
||||
|
||||
MArrayPopShift *ins = MArrayPopShift::New(alloc(), callInfo.thisArg(), mode,
|
||||
needsHoleCheck, maybeUndefined);
|
||||
MArrayPopShift *ins = MArrayPopShift::New(alloc(), obj, mode, needsHoleCheck, maybeUndefined);
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
ins->setResultType(returnType);
|
||||
@ -550,10 +552,12 @@ IonBuilder::inlineArrayPush(CallInfo &callInfo)
|
||||
value = valueDouble;
|
||||
}
|
||||
|
||||
if (NeedsPostBarrier(info(), value))
|
||||
current->add(MPostWriteBarrier::New(alloc(), callInfo.thisArg(), value));
|
||||
obj = addMaybeCopyElementsForWrite(obj);
|
||||
|
||||
MArrayPush *ins = MArrayPush::New(alloc(), callInfo.thisArg(), value);
|
||||
if (NeedsPostBarrier(info(), value))
|
||||
current->add(MPostWriteBarrier::New(alloc(), obj, value));
|
||||
|
||||
MArrayPush *ins = MArrayPush::New(alloc(), obj, value);
|
||||
current->add(ins);
|
||||
current->push(ins);
|
||||
|
||||
|
@ -3447,6 +3447,13 @@ jit::ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefiniti
|
||||
return types && !types->hasObjectFlags(constraints, types::OBJECT_FLAG_NON_PACKED);
|
||||
}
|
||||
|
||||
bool
|
||||
jit::ElementAccessMightBeCopyOnWrite(types::CompilerConstraintList *constraints, MDefinition *obj)
|
||||
{
|
||||
types::TemporaryTypeSet *types = obj->resultTypeSet();
|
||||
return !types || types->hasObjectFlags(constraints, types::OBJECT_FLAG_COPY_ON_WRITE);
|
||||
}
|
||||
|
||||
bool
|
||||
jit::ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj)
|
||||
|
@ -1926,6 +1926,45 @@ class MNewArray : public MUnaryInstruction
|
||||
}
|
||||
};
|
||||
|
||||
class MNewArrayCopyOnWrite : public MNullaryInstruction
|
||||
{
|
||||
CompilerRootObject templateObject_;
|
||||
gc::InitialHeap initialHeap_;
|
||||
|
||||
MNewArrayCopyOnWrite(types::CompilerConstraintList *constraints, JSObject *templateObject,
|
||||
gc::InitialHeap initialHeap)
|
||||
: templateObject_(templateObject),
|
||||
initialHeap_(initialHeap)
|
||||
{
|
||||
JS_ASSERT(!templateObject->hasSingletonType());
|
||||
setResultType(MIRType_Object);
|
||||
setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject));
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(NewArrayCopyOnWrite)
|
||||
|
||||
static MNewArrayCopyOnWrite *New(TempAllocator &alloc,
|
||||
types::CompilerConstraintList *constraints,
|
||||
JSObject *templateObject,
|
||||
gc::InitialHeap initialHeap)
|
||||
{
|
||||
return new(alloc) MNewArrayCopyOnWrite(constraints, templateObject, initialHeap);
|
||||
}
|
||||
|
||||
JSObject *templateObject() const {
|
||||
return templateObject_;
|
||||
}
|
||||
|
||||
gc::InitialHeap initialHeap() const {
|
||||
return initialHeap_;
|
||||
}
|
||||
|
||||
virtual AliasSet getAliasSet() const {
|
||||
return AliasSet::None();
|
||||
}
|
||||
};
|
||||
|
||||
class MNewObject : public MUnaryInstruction
|
||||
{
|
||||
gc::InitialHeap initialHeap_;
|
||||
@ -6451,6 +6490,49 @@ class MMaybeToDoubleElement
|
||||
}
|
||||
};
|
||||
|
||||
// Passes through an object, after ensuring its elements are not copy on write.
|
||||
class MMaybeCopyElementsForWrite
|
||||
: public MUnaryInstruction,
|
||||
public SingleObjectPolicy
|
||||
{
|
||||
explicit MMaybeCopyElementsForWrite(MDefinition *object)
|
||||
: MUnaryInstruction(object)
|
||||
{
|
||||
setGuard();
|
||||
setMovable();
|
||||
setResultType(MIRType_Object);
|
||||
setResultTypeSet(object->resultTypeSet());
|
||||
}
|
||||
|
||||
public:
|
||||
INSTRUCTION_HEADER(MaybeCopyElementsForWrite)
|
||||
|
||||
static MMaybeCopyElementsForWrite *New(TempAllocator &alloc, MDefinition *object) {
|
||||
return new(alloc) MMaybeCopyElementsForWrite(object);
|
||||
}
|
||||
|
||||
MDefinition *object() const {
|
||||
return getOperand(0);
|
||||
}
|
||||
bool congruentTo(const MDefinition *ins) const {
|
||||
return congruentIfOperandsEqual(ins);
|
||||
}
|
||||
AliasSet getAliasSet() const {
|
||||
// This instruction can read and write to the elements' contents,
|
||||
// in the same manner as MConvertElementsToDoubles. As with that
|
||||
// instruction, this is safe to consolidate and freely reorder this
|
||||
// instruction, though this must precede any loads of the object's
|
||||
// elements pointer or writes to the object's elements. The latter
|
||||
// property is ensured by chaining this with the object definition
|
||||
// itself, in the same manner as MBoundsCheck.
|
||||
return AliasSet::None();
|
||||
}
|
||||
|
||||
TypePolicy *typePolicy() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
// Load the initialized length from an elements header.
|
||||
class MInitializedLength
|
||||
: public MUnaryInstruction
|
||||
@ -11343,6 +11425,7 @@ bool ElementAccessIsDenseNative(MDefinition *obj, MDefinition *id);
|
||||
bool ElementAccessIsTypedArray(MDefinition *obj, MDefinition *id,
|
||||
Scalar::Type *arrayType);
|
||||
bool ElementAccessIsPacked(types::CompilerConstraintList *constraints, MDefinition *obj);
|
||||
bool ElementAccessMightBeCopyOnWrite(types::CompilerConstraintList *constraints, MDefinition *obj);
|
||||
bool ElementAccessHasExtraIndexedProperty(types::CompilerConstraintList *constraints,
|
||||
MDefinition *obj);
|
||||
MIRType DenseNativeElementType(types::CompilerConstraintList *constraints, MDefinition *obj);
|
||||
|
@ -93,6 +93,7 @@ namespace jit {
|
||||
_(TruncateToInt32) \
|
||||
_(ToString) \
|
||||
_(NewArray) \
|
||||
_(NewArrayCopyOnWrite) \
|
||||
_(NewObject) \
|
||||
_(NewDeclEnvObject) \
|
||||
_(NewCallObject) \
|
||||
@ -121,6 +122,7 @@ namespace jit {
|
||||
_(ConstantElements) \
|
||||
_(ConvertElementsToDoubles) \
|
||||
_(MaybeToDoubleElement) \
|
||||
_(MaybeCopyElementsForWrite) \
|
||||
_(LoadSlot) \
|
||||
_(StoreSlot) \
|
||||
_(FunctionEnvironment) \
|
||||
|
@ -187,6 +187,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor
|
||||
SAFE_OP(MaybeToDoubleElement)
|
||||
CUSTOM_OP(ToString)
|
||||
CUSTOM_OP(NewArray)
|
||||
UNSAFE_OP(NewArrayCopyOnWrite)
|
||||
CUSTOM_OP(NewObject)
|
||||
CUSTOM_OP(NewCallObject)
|
||||
CUSTOM_OP(NewRunOnceCallObject)
|
||||
@ -336,6 +337,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor
|
||||
|
||||
// It looks like this could easily be made safe:
|
||||
UNSAFE_OP(ConvertElementsToDoubles)
|
||||
UNSAFE_OP(MaybeCopyElementsForWrite)
|
||||
};
|
||||
|
||||
static void
|
||||
|
@ -336,6 +336,8 @@ DeleteArrayElement(JSContext *cx, HandleObject obj, double index, bool *succeede
|
||||
if (index <= UINT32_MAX) {
|
||||
uint32_t idx = uint32_t(index);
|
||||
if (idx < obj->getDenseInitializedLength()) {
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
obj->markDenseElementsNotPacked(cx);
|
||||
obj->setDenseElement(idx, MagicValue(JS_ELEMENTS_HOLE));
|
||||
if (!js_SuppressDeletedElement(cx, obj, idx))
|
||||
@ -472,6 +474,9 @@ js::ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cxArg,
|
||||
MOZ_ASSERT(cxArg->isThreadLocal(arr));
|
||||
MOZ_ASSERT(id == NameToId(cxArg->names().length));
|
||||
|
||||
if (!arr->maybeCopyElementsForWrite(cxArg))
|
||||
return false;
|
||||
|
||||
/* Steps 1-2 are irrelevant in our implementation. */
|
||||
|
||||
/* Steps 3-5. */
|
||||
@ -547,6 +552,9 @@ js::ArraySetLength(typename ExecutionModeTraits<mode>::ContextType cxArg,
|
||||
// fix this inefficiency by moving indexed storage to be entirely
|
||||
// separate from non-indexed storage.
|
||||
if (!arr->isIndexed()) {
|
||||
if (!arr->maybeCopyElementsForWrite(cxArg))
|
||||
return false;
|
||||
|
||||
uint32_t oldCapacity = arr->getDenseCapacity();
|
||||
uint32_t oldInitializedLength = arr->getDenseInitializedLength();
|
||||
MOZ_ASSERT(oldCapacity >= oldInitializedLength);
|
||||
@ -2139,8 +2147,11 @@ js::array_pop(JSContext *cx, unsigned argc, Value *vp)
|
||||
// Don't do anything if this isn't an array, as any deletion above has no
|
||||
// effect on any elements after the "last" one indicated by the "length"
|
||||
// property.
|
||||
if (obj->is<ArrayObject>() && obj->getDenseInitializedLength() > index)
|
||||
if (obj->is<ArrayObject>() && obj->getDenseInitializedLength() > index) {
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
obj->setDenseInitializedLength(index);
|
||||
}
|
||||
|
||||
/* Steps 4a, 5d. */
|
||||
return SetLengthProperty(cx, obj, index);
|
||||
@ -2200,6 +2211,9 @@ js::array_shift(JSContext *cx, unsigned argc, Value *vp)
|
||||
if (args.rval().isMagic(JS_ELEMENTS_HOLE))
|
||||
args.rval().setUndefined();
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
obj->moveDenseElements(0, 1, obj->getDenseInitializedLength() - 1);
|
||||
obj->setDenseInitializedLength(obj->getDenseInitializedLength() - 1);
|
||||
|
||||
@ -2458,6 +2472,9 @@ js::array_splice_impl(JSContext *cx, unsigned argc, Value *vp, bool returnValueI
|
||||
uint32_t finalLength = len - actualDeleteCount + itemCount;
|
||||
|
||||
if (CanOptimizeForDenseStorage(obj, 0, len, cx)) {
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
/* Steps 12(a)-(b). */
|
||||
obj->moveDenseElements(targetIndex, sourceIndex, len - sourceIndex);
|
||||
|
||||
@ -2539,6 +2556,8 @@ js::array_splice_impl(JSContext *cx, unsigned argc, Value *vp, bool returnValueI
|
||||
}
|
||||
|
||||
if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) {
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
obj->moveDenseElements(actualStart + itemCount,
|
||||
actualStart + actualDeleteCount,
|
||||
len - (actualStart + actualDeleteCount));
|
||||
@ -3343,12 +3362,7 @@ js::NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject
|
||||
allocKind = GetBackgroundAllocKind(allocKind);
|
||||
|
||||
RootedTypeObject type(cx, templateObject->type());
|
||||
if (!type)
|
||||
return nullptr;
|
||||
|
||||
RootedShape shape(cx, templateObject->lastProperty());
|
||||
if (!shape)
|
||||
return nullptr;
|
||||
|
||||
gc::InitialHeap heap = GetInitialHeap(GenericObject, &ArrayObject::class_);
|
||||
Rooted<ArrayObject *> arr(cx, JSObject::createArray(cx, allocKind, heap, shape, type, length));
|
||||
@ -3363,6 +3377,32 @@ js::NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject
|
||||
return arr;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
js::NewDenseCopyOnWriteArray(JSContext *cx, HandleObject templateObject, gc::InitialHeap heap)
|
||||
{
|
||||
RootedTypeObject type(cx, templateObject->type());
|
||||
RootedShape shape(cx, templateObject->lastProperty());
|
||||
|
||||
JS_ASSERT(!gc::IsInsideNursery(templateObject));
|
||||
HeapSlot *elements = templateObject->getDenseElementsAllowCopyOnWrite();
|
||||
|
||||
JSObject *metadata = nullptr;
|
||||
if (!NewObjectMetadata(cx, &metadata))
|
||||
return nullptr;
|
||||
if (metadata) {
|
||||
shape = Shape::setObjectMetadata(cx, metadata, templateObject->getTaggedProto(), shape);
|
||||
if (!shape)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rooted<ArrayObject *> arr(cx, JSObject::createArray(cx, heap, shape, type, elements));
|
||||
if (!arr)
|
||||
return nullptr;
|
||||
|
||||
probes::CreateObject(cx, arr);
|
||||
return arr;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
bool
|
||||
js_ArrayInfo(JSContext *cx, unsigned argc, Value *vp)
|
||||
|
@ -81,6 +81,10 @@ NewDenseCopiedArray(JSContext *cx, uint32_t length, const Value *values, JSObjec
|
||||
extern ArrayObject *
|
||||
NewDenseAllocatedArrayWithTemplate(JSContext *cx, uint32_t length, JSObject *templateObject);
|
||||
|
||||
/* Create a dense array with the same copy-on-write elements as another object. */
|
||||
extern JSObject *
|
||||
NewDenseCopyOnWriteArray(JSContext *cx, HandleObject templateObject, gc::InitialHeap heap);
|
||||
|
||||
/*
|
||||
* Determines whether a write to the given element on |obj| should fail because
|
||||
* |obj| is an Array with a non-writable length, and writing that element would
|
||||
|
@ -3465,6 +3465,50 @@ types::FillBytecodeTypeMap(JSScript *script, uint32_t *bytecodeMap)
|
||||
JS_ASSERT(added == script->nTypeSets());
|
||||
}
|
||||
|
||||
JSObject *
|
||||
types::GetOrFixupCopyOnWriteObject(JSContext *cx, HandleScript script, jsbytecode *pc)
|
||||
{
|
||||
// Make sure that the template object for script/pc has a type indicating
|
||||
// that the object and its copies have copy on write elements.
|
||||
RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
|
||||
JS_ASSERT(obj->is<ArrayObject>());
|
||||
JS_ASSERT(obj->denseElementsAreCopyOnWrite());
|
||||
|
||||
if (obj->type()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE))
|
||||
return obj;
|
||||
|
||||
RootedTypeObject type(cx, TypeScript::InitObject(cx, script, pc, JSProto_Array));
|
||||
if (!type)
|
||||
return nullptr;
|
||||
|
||||
type->addFlags(OBJECT_FLAG_COPY_ON_WRITE);
|
||||
|
||||
// Update type information in the initializer object type.
|
||||
JS_ASSERT(obj->slotSpan() == 0);
|
||||
for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
|
||||
const Value &v = obj->getDenseElement(i);
|
||||
AddTypePropertyId(cx, type, JSID_VOID, v);
|
||||
}
|
||||
|
||||
obj->setType(type);
|
||||
return obj;
|
||||
}
|
||||
|
||||
JSObject *
|
||||
types::GetCopyOnWriteObject(JSScript *script, jsbytecode *pc)
|
||||
{
|
||||
// GetOrFixupCopyOnWriteObject should already have been called for
|
||||
// script/pc, ensuring that the template object has a type with the
|
||||
// COPY_ON_WRITE flag. We don't assert this here, due to a corner case
|
||||
// where this property doesn't hold. See jsop_newarray_copyonwrite in
|
||||
// IonBuilder.
|
||||
JSObject *obj = script->getObject(GET_UINT32_INDEX(pc));
|
||||
JS_ASSERT(obj->is<ArrayObject>());
|
||||
JS_ASSERT(obj->denseElementsAreCopyOnWrite());
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void
|
||||
types::TypeMonitorResult(JSContext *cx, JSScript *script, jsbytecode *pc, const js::Value &rval)
|
||||
{
|
||||
|
@ -484,14 +484,17 @@ enum MOZ_ENUM_TYPE(uint32_t) {
|
||||
*/
|
||||
OBJECT_FLAG_PRE_TENURE = 0x00400000,
|
||||
|
||||
/* Whether objects with this type might have copy on write elements. */
|
||||
OBJECT_FLAG_COPY_ON_WRITE = 0x00800000,
|
||||
|
||||
/*
|
||||
* Whether all properties of this object are considered unknown.
|
||||
* If set, all other flags in DYNAMIC_MASK will also be set.
|
||||
*/
|
||||
OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x00800000,
|
||||
OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x01000000,
|
||||
|
||||
/* Flags which indicate dynamic properties of represented objects. */
|
||||
OBJECT_FLAG_DYNAMIC_MASK = 0x00ff0000,
|
||||
OBJECT_FLAG_DYNAMIC_MASK = 0x01ff0000,
|
||||
|
||||
/* Mask for objects created with unknown properties. */
|
||||
OBJECT_FLAG_UNKNOWN_MASK =
|
||||
@ -1280,6 +1283,12 @@ class TypeScript
|
||||
void
|
||||
FillBytecodeTypeMap(JSScript *script, uint32_t *bytecodeMap);
|
||||
|
||||
JSObject *
|
||||
GetOrFixupCopyOnWriteObject(JSContext *cx, HandleScript script, jsbytecode *pc);
|
||||
|
||||
JSObject *
|
||||
GetCopyOnWriteObject(JSScript *script, jsbytecode *pc);
|
||||
|
||||
class RecompileInfo;
|
||||
|
||||
// Allocate a CompilerOutput for a finished compilation and generate the type
|
||||
|
112
js/src/jsobj.cpp
112
js/src/jsobj.cpp
@ -1365,8 +1365,11 @@ JSObject::sealOrFreeze(JSContext *cx, HandleObject obj, ImmutabilityType it)
|
||||
// arrays with non-writable length. We don't need to do anything special
|
||||
// for that, because capacity was zeroed out by preventExtensions. (See
|
||||
// the assertion before the if-else above.)
|
||||
if (it == FREEZE && obj->is<ArrayObject>())
|
||||
if (it == FREEZE && obj->is<ArrayObject>()) {
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
obj->getElementsHeader()->setNonwritableArrayLength();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -2315,15 +2318,48 @@ js::XDRObjectLiteral(XDRState<XDR_DECODE> *xdr, MutableHandleObject obj);
|
||||
JSObject *
|
||||
js::CloneObjectLiteral(JSContext *cx, HandleObject parent, HandleObject srcObj)
|
||||
{
|
||||
Rooted<TypeObject*> typeObj(cx);
|
||||
typeObj = cx->getNewType(&JSObject::class_, TaggedProto(cx->global()->getOrCreateObjectPrototype(cx)));
|
||||
if (srcObj->getClass() == &JSObject::class_) {
|
||||
AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots()));
|
||||
JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind());
|
||||
|
||||
JS_ASSERT(srcObj->getClass() == &JSObject::class_);
|
||||
AllocKind kind = GetBackgroundAllocKind(GuessObjectGCKind(srcObj->numFixedSlots()));
|
||||
JS_ASSERT_IF(srcObj->isTenured(), kind == srcObj->tenuredGetAllocKind());
|
||||
JSObject *proto = cx->global()->getOrCreateObjectPrototype(cx);
|
||||
if (!proto)
|
||||
return nullptr;
|
||||
Rooted<TypeObject*> typeObj(cx, cx->getNewType(&JSObject::class_, TaggedProto(proto)));
|
||||
if (!typeObj)
|
||||
return nullptr;
|
||||
|
||||
RootedShape shape(cx, srcObj->lastProperty());
|
||||
return NewReshapedObject(cx, typeObj, parent, kind, shape);
|
||||
RootedShape shape(cx, srcObj->lastProperty());
|
||||
return NewReshapedObject(cx, typeObj, parent, kind, shape);
|
||||
}
|
||||
|
||||
JS_ASSERT(srcObj->is<ArrayObject>());
|
||||
JS_ASSERT(srcObj->denseElementsAreCopyOnWrite());
|
||||
JS_ASSERT(srcObj->getElementsHeader()->ownerObject() == srcObj);
|
||||
|
||||
size_t length = srcObj->as<ArrayObject>().length();
|
||||
RootedObject res(cx, NewDenseAllocatedArray(cx, length, nullptr, MaybeSingletonObject));
|
||||
if (!res)
|
||||
return nullptr;
|
||||
|
||||
RootedId id(cx);
|
||||
RootedValue value(cx);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
// The only markable values in copy on write arrays are atoms, which
|
||||
// can be freely copied between compartments.
|
||||
value = srcObj->getDenseElement(i);
|
||||
JS_ASSERT_IF(value.isMarkable(),
|
||||
cx->runtime()->isAtomsZone(value.toGCThing()->tenuredZone()));
|
||||
|
||||
id = INT_TO_JSID(i);
|
||||
if (!JSObject::defineGeneric(cx, res, id, value, nullptr, nullptr, JSPROP_ENUMERATE))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ObjectElements::MakeElementsCopyOnWrite(cx, res))
|
||||
return nullptr;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
struct JSObject::TradeGutsReserved {
|
||||
@ -3063,6 +3099,9 @@ JSObject::shrinkSlots(ThreadSafeContext *cx, HandleObject obj, uint32_t oldCount
|
||||
/* static */ bool
|
||||
JSObject::sparsifyDenseElement(ExclusiveContext *cx, HandleObject obj, uint32_t index)
|
||||
{
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
RootedValue value(cx, obj->getDenseElement(index));
|
||||
JS_ASSERT(!value.isMagic(JS_ELEMENTS_HOLE));
|
||||
|
||||
@ -3083,6 +3122,9 @@ JSObject::sparsifyDenseElement(ExclusiveContext *cx, HandleObject obj, uint32_t
|
||||
/* static */ bool
|
||||
JSObject::sparsifyDenseElements(js::ExclusiveContext *cx, HandleObject obj)
|
||||
{
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
uint32_t initialized = obj->getDenseInitializedLength();
|
||||
|
||||
/* Create new properties with the value of non-hole dense elements. */
|
||||
@ -3201,6 +3243,9 @@ JSObject::maybeDensifySparseElements(js::ExclusiveContext *cx, HandleObject obj)
|
||||
* properties into dense elements.
|
||||
*/
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return ED_FAILED;
|
||||
|
||||
if (newInitializedLength > obj->getDenseCapacity()) {
|
||||
if (!obj->growElements(cx, newInitializedLength))
|
||||
return ED_FAILED;
|
||||
@ -3382,6 +3427,8 @@ JSObject::growElements(ThreadSafeContext *cx, uint32_t reqCapacity)
|
||||
{
|
||||
JS_ASSERT(nonProxyIsExtensible());
|
||||
JS_ASSERT(canHaveNonEmptyElements());
|
||||
if (denseElementsAreCopyOnWrite())
|
||||
MOZ_CRASH();
|
||||
|
||||
uint32_t oldCapacity = getDenseCapacity();
|
||||
JS_ASSERT(oldCapacity < reqCapacity);
|
||||
@ -3444,6 +3491,8 @@ JSObject::shrinkElements(ThreadSafeContext *cx, uint32_t reqCapacity)
|
||||
{
|
||||
JS_ASSERT(cx->isThreadLocal(this));
|
||||
JS_ASSERT(canHaveNonEmptyElements());
|
||||
if (denseElementsAreCopyOnWrite())
|
||||
MOZ_CRASH();
|
||||
|
||||
if (!hasDynamicElements())
|
||||
return;
|
||||
@ -3471,6 +3520,38 @@ JSObject::shrinkElements(ThreadSafeContext *cx, uint32_t reqCapacity)
|
||||
elements = newheader->elements();
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
JSObject::CopyElementsForWrite(ThreadSafeContext *cx, JSObject *obj)
|
||||
{
|
||||
JS_ASSERT(obj->denseElementsAreCopyOnWrite());
|
||||
|
||||
// The original owner of a COW elements array should never be modified.
|
||||
JS_ASSERT(obj->getElementsHeader()->ownerObject() != obj);
|
||||
|
||||
uint32_t initlen = obj->getDenseInitializedLength();
|
||||
uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER;
|
||||
uint32_t newAllocated = goodAllocated(allocated);
|
||||
|
||||
uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
if (newCapacity >= NELEMENTS_LIMIT)
|
||||
return false;
|
||||
|
||||
ObjectElements *newheader = AllocateElements(cx, obj, newAllocated);
|
||||
if (!newheader)
|
||||
return false;
|
||||
js_memcpy(newheader, obj->getElementsHeader(),
|
||||
(ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
|
||||
|
||||
newheader->capacity = newCapacity;
|
||||
newheader->clearCopyOnWrite();
|
||||
obj->elements = newheader->elements();
|
||||
|
||||
Debug_SetSlotRangeToCrashOnTouch(obj->elements + initlen, newCapacity - initlen);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::SetClassAndProto(JSContext *cx, HandleObject obj,
|
||||
const Class *clasp, Handle<js::TaggedProto> proto,
|
||||
@ -3967,6 +4048,9 @@ CallAddPropertyHookDense(typename ExecutionModeTraits<mode>::ExclusiveContextTyp
|
||||
if (!cx->shouldBeJSContext())
|
||||
return false;
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
/* Make a local copy of value so addProperty can mutate its inout parameter. */
|
||||
RootedValue value(cx, nominal);
|
||||
|
||||
@ -4104,6 +4188,9 @@ DefinePropertyOrElement(typename ExecutionModeTraits<mode>::ExclusiveContextType
|
||||
if (mode == ParallelExecution)
|
||||
return false;
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
ExclusiveContext *ncx = cx->asExclusiveContext();
|
||||
uint32_t index = JSID_TO_INT(id);
|
||||
JSObject::removeDenseElementForSparseIndex(ncx, obj, index);
|
||||
@ -5384,6 +5471,9 @@ baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cxArg))
|
||||
return false;
|
||||
|
||||
if (mode == ParallelExecution)
|
||||
return obj->setDenseElementIfHasType(index, vp);
|
||||
|
||||
@ -5540,6 +5630,9 @@ baseops::DeleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succe
|
||||
if (!succeeded)
|
||||
return true;
|
||||
|
||||
if (!obj->maybeCopyElementsForWrite(cx))
|
||||
return false;
|
||||
|
||||
obj->setDenseElementHole(cx, JSID_TO_INT(id));
|
||||
return js_SuppressDeletedProperty(cx, obj, id);
|
||||
}
|
||||
@ -6253,7 +6346,8 @@ JSObject::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::Objects
|
||||
|
||||
if (hasDynamicElements()) {
|
||||
js::ObjectElements *elements = getElementsHeader();
|
||||
sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements);
|
||||
if (!elements->isCopyOnWrite() || elements->ownerObject() == this)
|
||||
sizes->mallocHeapElementsNonAsmJS += mallocSizeOf(elements);
|
||||
}
|
||||
|
||||
// Other things may be measured in the future if DMD indicates it is worthwhile.
|
||||
|
@ -246,6 +246,20 @@ class JSObject : public js::ObjectImpl
|
||||
js::HandleTypeObject type,
|
||||
uint32_t length);
|
||||
|
||||
/* Make an array object with the specified initial state and elements. */
|
||||
static inline js::ArrayObject *createArray(js::ExclusiveContext *cx,
|
||||
js::gc::InitialHeap heap,
|
||||
js::HandleShape shape,
|
||||
js::HandleTypeObject type,
|
||||
js::HeapSlot *elements);
|
||||
|
||||
private:
|
||||
// Helper for the above two methods.
|
||||
static inline JSObject *
|
||||
createArrayInternal(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
|
||||
js::HandleShape shape, js::HandleTypeObject type);
|
||||
public:
|
||||
|
||||
/*
|
||||
* Remove the last property of an object, provided that it is safe to do so
|
||||
* (the shape and previous shape do not carry conflicting information about
|
||||
@ -390,6 +404,7 @@ class JSObject : public js::ObjectImpl
|
||||
|
||||
void prepareElementRangeForOverwrite(size_t start, size_t end) {
|
||||
JS_ASSERT(end <= getDenseInitializedLength());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
for (size_t i = start; i < end; i++)
|
||||
elements[i].js::HeapSlot::~HeapSlot();
|
||||
}
|
||||
@ -597,6 +612,7 @@ class JSObject : public js::ObjectImpl
|
||||
|
||||
/* Accessors for elements. */
|
||||
bool ensureElements(js::ThreadSafeContext *cx, uint32_t capacity) {
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
if (capacity > getDenseCapacity())
|
||||
return growElements(cx, capacity);
|
||||
return true;
|
||||
@ -617,6 +633,14 @@ class JSObject : public js::ObjectImpl
|
||||
return getElementsHeader()->capacity;
|
||||
}
|
||||
|
||||
static bool CopyElementsForWrite(js::ThreadSafeContext *cx, JSObject *obj);
|
||||
|
||||
bool maybeCopyElementsForWrite(js::ThreadSafeContext *cx) {
|
||||
if (denseElementsAreCopyOnWrite())
|
||||
return CopyElementsForWrite(cx, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline void ensureDenseInitializedLengthNoPackedCheck(js::ThreadSafeContext *cx,
|
||||
uint32_t index, uint32_t extra);
|
||||
@ -625,6 +649,7 @@ class JSObject : public js::ObjectImpl
|
||||
void setDenseInitializedLength(uint32_t length) {
|
||||
JS_ASSERT(isNative());
|
||||
JS_ASSERT(length <= getDenseCapacity());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
prepareElementRangeForOverwrite(length, getElementsHeader()->initializedLength);
|
||||
getElementsHeader()->initializedLength = length;
|
||||
}
|
||||
@ -635,11 +660,13 @@ class JSObject : public js::ObjectImpl
|
||||
uint32_t index, uint32_t extra);
|
||||
void setDenseElement(uint32_t index, const js::Value &val) {
|
||||
JS_ASSERT(isNative() && index < getDenseInitializedLength());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
elements[index].set(this, js::HeapSlot::Element, index, val);
|
||||
}
|
||||
|
||||
void initDenseElement(uint32_t index, const js::Value &val) {
|
||||
JS_ASSERT(isNative() && index < getDenseInitializedLength());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
elements[index].init(this, js::HeapSlot::Element, index, val);
|
||||
}
|
||||
|
||||
@ -663,6 +690,7 @@ class JSObject : public js::ObjectImpl
|
||||
|
||||
void copyDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count) {
|
||||
JS_ASSERT(dstStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
JSRuntime *rt = runtimeFromMainThread();
|
||||
if (JS::IsIncrementalBarrierNeeded(rt)) {
|
||||
JS::Zone *zone = this->zone();
|
||||
@ -676,6 +704,7 @@ class JSObject : public js::ObjectImpl
|
||||
|
||||
void initDenseElements(uint32_t dstStart, const js::Value *src, uint32_t count) {
|
||||
JS_ASSERT(dstStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
memcpy(&elements[dstStart], src, count * sizeof(js::HeapSlot));
|
||||
DenseRangeWriteBarrierPost(runtimeFromMainThread(), this, dstStart, count);
|
||||
}
|
||||
@ -685,6 +714,7 @@ class JSObject : public js::ObjectImpl
|
||||
void moveDenseElements(uint32_t dstStart, uint32_t srcStart, uint32_t count) {
|
||||
JS_ASSERT(dstStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(srcStart + count <= getDenseInitializedLength());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
|
||||
/*
|
||||
* Using memmove here would skip write barriers. Also, we need to consider
|
||||
@ -723,6 +753,7 @@ class JSObject : public js::ObjectImpl
|
||||
|
||||
JS_ASSERT(dstStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(srcStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
|
||||
memmove(elements + dstStart, elements + srcStart, count * sizeof(js::Value));
|
||||
DenseRangeWriteBarrierPost(runtimeFromMainThread(), this, dstStart, count);
|
||||
@ -736,6 +767,11 @@ class JSObject : public js::ObjectImpl
|
||||
inline void setShouldConvertDoubleElements();
|
||||
inline void clearShouldConvertDoubleElements();
|
||||
|
||||
bool denseElementsAreCopyOnWrite() {
|
||||
JS_ASSERT(isNative());
|
||||
return getElementsHeader()->isCopyOnWrite();
|
||||
}
|
||||
|
||||
/* Packed information for this object's elements. */
|
||||
inline bool writeToIndexWouldMarkNotPacked(uint32_t index);
|
||||
inline void markDenseElementsNotPacked(js::ExclusiveContext *cx);
|
||||
|
@ -213,6 +213,7 @@ JSObject::ensureDenseInitializedLengthNoPackedCheck(js::ThreadSafeContext *cx, u
|
||||
uint32_t extra)
|
||||
{
|
||||
JS_ASSERT(cx->isThreadLocal(this));
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
|
||||
/*
|
||||
* Ensure that the array's contents have been initialized up to index, and
|
||||
@ -255,6 +256,7 @@ JSObject::extendDenseElements(js::ThreadSafeContext *cx,
|
||||
uint32_t requiredCapacity, uint32_t extra)
|
||||
{
|
||||
JS_ASSERT(cx->isThreadLocal(this));
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
|
||||
/*
|
||||
* Don't grow elements for non-extensible objects or watched objects. Dense
|
||||
@ -294,6 +296,9 @@ JSObject::ensureDenseElementsNoPackedCheck(js::ThreadSafeContext *cx, uint32_t i
|
||||
{
|
||||
JS_ASSERT(isNative());
|
||||
|
||||
if (!maybeCopyElementsForWrite(cx))
|
||||
return ED_FAILED;
|
||||
|
||||
uint32_t currentCapacity = getDenseCapacity();
|
||||
|
||||
uint32_t requiredCapacity;
|
||||
@ -359,6 +364,7 @@ JSObject::initDenseElementsUnbarriered(uint32_t dstStart, const js::Value *src,
|
||||
* things do not require a barrier.
|
||||
*/
|
||||
JS_ASSERT(dstStart + count <= getDenseCapacity());
|
||||
JS_ASSERT(!denseElementsAreCopyOnWrite());
|
||||
#if defined(DEBUG) && defined(JSGC_GENERATIONAL)
|
||||
/*
|
||||
* This asserts a global invariant: parallel code does not
|
||||
@ -550,31 +556,42 @@ JSObject::create(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::Initi
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ inline js::ArrayObject *
|
||||
JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
|
||||
js::HandleShape shape, js::HandleTypeObject type,
|
||||
uint32_t length)
|
||||
/* static */ inline JSObject *
|
||||
JSObject::createArrayInternal(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
|
||||
js::HandleShape shape, js::HandleTypeObject type)
|
||||
{
|
||||
// Create a new array and initialize everything except for its elements.
|
||||
JS_ASSERT(shape && type);
|
||||
JS_ASSERT(type->clasp() == shape->getObjectClass());
|
||||
JS_ASSERT(type->clasp() == &js::ArrayObject::class_);
|
||||
JS_ASSERT_IF(type->clasp()->finalize, heap == js::gc::TenuredHeap);
|
||||
|
||||
/*
|
||||
* Arrays use their fixed slots to store elements, and must have enough
|
||||
* space for the elements header and also be marked as having no space for
|
||||
* named properties stored in those fixed slots.
|
||||
*/
|
||||
// Arrays can use their fixed slots to store elements, so can't have shapes
|
||||
// which allow named properties to be stored in the fixed slots.
|
||||
JS_ASSERT(shape->numFixedSlots() == 0);
|
||||
|
||||
size_t nDynamicSlots = dynamicSlotsCount(0, shape->slotSpan(), type->clasp());
|
||||
JSObject *obj = js::NewGCObject<js::CanGC>(cx, kind, nDynamicSlots, heap);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
uint32_t capacity = js::gc::GetGCKindSlots(kind) - js::ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
obj->shape_.init(shape);
|
||||
obj->type_.init(type);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ inline js::ArrayObject *
|
||||
JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::InitialHeap heap,
|
||||
js::HandleShape shape, js::HandleTypeObject type,
|
||||
uint32_t length)
|
||||
{
|
||||
JSObject *obj = createArrayInternal(cx, kind, heap, shape, type);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
uint32_t capacity = js::gc::GetGCKindSlots(kind) - js::ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
obj->setFixedElements();
|
||||
new (obj->getElementsHeader()) js::ObjectElements(capacity, length);
|
||||
|
||||
@ -587,6 +604,31 @@ JSObject::createArray(js::ExclusiveContext *cx, js::gc::AllocKind kind, js::gc::
|
||||
return &obj->as<js::ArrayObject>();
|
||||
}
|
||||
|
||||
/* static */ inline js::ArrayObject *
|
||||
JSObject::createArray(js::ExclusiveContext *cx, js::gc::InitialHeap heap,
|
||||
js::HandleShape shape, js::HandleTypeObject type,
|
||||
js::HeapSlot *elements)
|
||||
{
|
||||
// Use the smallest allocation kind for the array, as it can't have any
|
||||
// fixed slots (see assert in the above function) and will not be using its
|
||||
// fixed elements.
|
||||
js::gc::AllocKind kind = js::gc::FINALIZE_OBJECT0_BACKGROUND;
|
||||
|
||||
JSObject *obj = createArrayInternal(cx, kind, heap, shape, type);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
obj->elements = elements;
|
||||
|
||||
size_t span = shape->slotSpan();
|
||||
if (span)
|
||||
obj->initializeSlotRange(0, span);
|
||||
|
||||
js::gc::TraceCreateObject(obj);
|
||||
|
||||
return &obj->as<js::ArrayObject>();
|
||||
}
|
||||
|
||||
inline void
|
||||
JSObject::finish(js::FreeOp *fop)
|
||||
{
|
||||
@ -595,7 +637,8 @@ JSObject::finish(js::FreeOp *fop)
|
||||
|
||||
if (hasDynamicElements()) {
|
||||
js::ObjectElements *elements = getElementsHeader();
|
||||
fop->free_(elements);
|
||||
if (!elements->isCopyOnWrite() || elements->ownerObject() == this)
|
||||
fop->free_(elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1555,7 +1555,8 @@ ExpressionDecompiler::decompilePC(jsbytecode *pc)
|
||||
case JSOP_NEWARRAY:
|
||||
return write("[]");
|
||||
case JSOP_REGEXP:
|
||||
case JSOP_OBJECT: {
|
||||
case JSOP_OBJECT:
|
||||
case JSOP_NEWARRAY_COPYONWRITE: {
|
||||
JSObject *obj = (op == JSOP_REGEXP)
|
||||
? script->getRegExp(GET_UINT32_INDEX(pc))
|
||||
: script->getObject(GET_UINT32_INDEX(pc));
|
||||
|
@ -1596,7 +1596,6 @@ CASE(JSOP_UNUSED51)
|
||||
CASE(JSOP_UNUSED52)
|
||||
CASE(JSOP_UNUSED57)
|
||||
CASE(JSOP_UNUSED83)
|
||||
CASE(JSOP_UNUSED102)
|
||||
CASE(JSOP_UNUSED103)
|
||||
CASE(JSOP_UNUSED104)
|
||||
CASE(JSOP_UNUSED105)
|
||||
@ -3063,6 +3062,22 @@ CASE(JSOP_NEWARRAY)
|
||||
}
|
||||
END_CASE(JSOP_NEWARRAY)
|
||||
|
||||
CASE(JSOP_NEWARRAY_COPYONWRITE)
|
||||
{
|
||||
RootedObject &baseobj = rootObject0;
|
||||
baseobj = types::GetOrFixupCopyOnWriteObject(cx, script, REGS.pc);
|
||||
if (!baseobj)
|
||||
goto error;
|
||||
|
||||
RootedObject &obj = rootObject1;
|
||||
obj = NewDenseCopyOnWriteArray(cx, baseobj, gc::DefaultHeap);
|
||||
if (!obj)
|
||||
goto error;
|
||||
|
||||
PUSH_OBJECT(*obj);
|
||||
}
|
||||
END_CASE(JSOP_NEWARRAY_COPYONWRITE)
|
||||
|
||||
CASE(JSOP_NEWOBJECT)
|
||||
{
|
||||
RootedObject &baseobj = rootObject0;
|
||||
|
@ -97,6 +97,8 @@ ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr)
|
||||
ObjectElements *header = ObjectElements::fromElements(elementsHeapPtr);
|
||||
JS_ASSERT(!header->shouldConvertDoubleElements());
|
||||
|
||||
// Note: the elements can be mutated in place even for copy on write
|
||||
// arrays. See comment on ObjectElements.
|
||||
Value *vp = (Value *) elementsPtr;
|
||||
for (size_t i = 0; i < header->initializedLength; i++) {
|
||||
if (vp[i].isInt32())
|
||||
@ -107,6 +109,26 @@ ObjectElements::ConvertElementsToDoubles(JSContext *cx, uintptr_t elementsPtr)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext *cx, JSObject *obj)
|
||||
{
|
||||
// Make sure there is enough room for the owner object pointer at the end
|
||||
// of the elements.
|
||||
JS_STATIC_ASSERT(sizeof(HeapSlot) >= sizeof(HeapPtrObject));
|
||||
if (!obj->ensureElements(cx, obj->getDenseInitializedLength() + 1))
|
||||
return false;
|
||||
|
||||
ObjectElements *header = obj->getElementsHeader();
|
||||
|
||||
// Note: this method doesn't update type information to indicate that the
|
||||
// elements might be copy on write. Handling this is left to the caller.
|
||||
JS_ASSERT(!header->isCopyOnWrite());
|
||||
header->flags |= COPY_ON_WRITE;
|
||||
|
||||
header->ownerObject().init(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
void
|
||||
js::ObjectImpl::checkShapeConsistency()
|
||||
@ -294,7 +316,21 @@ js::ObjectImpl::markChildren(JSTracer *trc)
|
||||
|
||||
if (shape_->isNative()) {
|
||||
MarkObjectSlots(trc, obj, 0, obj->slotSpan());
|
||||
gc::MarkArraySlots(trc, obj->getDenseInitializedLength(), obj->getDenseElements(), "objectElements");
|
||||
|
||||
do {
|
||||
if (obj->denseElementsAreCopyOnWrite()) {
|
||||
HeapPtrObject &owner = getElementsHeader()->ownerObject();
|
||||
if (owner != this) {
|
||||
MarkObject(trc, &owner, "objectElementsOwner");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
gc::MarkArraySlots(trc,
|
||||
obj->getDenseInitializedLength(),
|
||||
obj->getDenseElementsAllowCopyOnWrite(),
|
||||
"objectElements");
|
||||
} while (false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,11 +169,21 @@ class ObjectElements
|
||||
{
|
||||
public:
|
||||
enum Flags {
|
||||
// Integers written to these elements must be converted to doubles.
|
||||
CONVERT_DOUBLE_ELEMENTS = 0x1,
|
||||
|
||||
// Present only if these elements correspond to an array with
|
||||
// non-writable length; never present for non-arrays.
|
||||
NONWRITABLE_ARRAY_LENGTH = 0x2
|
||||
NONWRITABLE_ARRAY_LENGTH = 0x2,
|
||||
|
||||
// These elements are shared with another object and must be copied
|
||||
// before they can be changed. A pointer to the original owner of the
|
||||
// elements, which is immutable, is stored immediately after the
|
||||
// elements data. There is one case where elements can be written to
|
||||
// before being copied: when setting the CONVERT_DOUBLE_ELEMENTS flag
|
||||
// the shared elements may change (from ints to doubles) without
|
||||
// making a copy first.
|
||||
COPY_ON_WRITE = 0x4
|
||||
};
|
||||
|
||||
private:
|
||||
@ -210,17 +220,27 @@ class ObjectElements
|
||||
return flags & CONVERT_DOUBLE_ELEMENTS;
|
||||
}
|
||||
void setShouldConvertDoubleElements() {
|
||||
// Note: allow isCopyOnWrite() here, see comment above.
|
||||
flags |= CONVERT_DOUBLE_ELEMENTS;
|
||||
}
|
||||
void clearShouldConvertDoubleElements() {
|
||||
JS_ASSERT(!isCopyOnWrite());
|
||||
flags &= ~CONVERT_DOUBLE_ELEMENTS;
|
||||
}
|
||||
bool hasNonwritableArrayLength() const {
|
||||
return flags & NONWRITABLE_ARRAY_LENGTH;
|
||||
}
|
||||
void setNonwritableArrayLength() {
|
||||
JS_ASSERT(!isCopyOnWrite());
|
||||
flags |= NONWRITABLE_ARRAY_LENGTH;
|
||||
}
|
||||
bool isCopyOnWrite() const {
|
||||
return flags & COPY_ON_WRITE;
|
||||
}
|
||||
void clearCopyOnWrite() {
|
||||
JS_ASSERT(isCopyOnWrite());
|
||||
flags &= ~COPY_ON_WRITE;
|
||||
}
|
||||
|
||||
public:
|
||||
MOZ_CONSTEXPR ObjectElements(uint32_t capacity, uint32_t length)
|
||||
@ -230,10 +250,18 @@ class ObjectElements
|
||||
HeapSlot *elements() {
|
||||
return reinterpret_cast<HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
|
||||
}
|
||||
const HeapSlot *elements() const {
|
||||
return reinterpret_cast<const HeapSlot*>(uintptr_t(this) + sizeof(ObjectElements));
|
||||
}
|
||||
static ObjectElements * fromElements(HeapSlot *elems) {
|
||||
return reinterpret_cast<ObjectElements*>(uintptr_t(elems) - sizeof(ObjectElements));
|
||||
}
|
||||
|
||||
HeapPtrObject &ownerObject() const {
|
||||
JS_ASSERT(isCopyOnWrite());
|
||||
return *(HeapPtrObject *)(&elements()[initializedLength]);
|
||||
}
|
||||
|
||||
static int offsetOfFlags() {
|
||||
return int(offsetof(ObjectElements, flags)) - int(sizeof(ObjectElements));
|
||||
}
|
||||
@ -248,6 +276,7 @@ class ObjectElements
|
||||
}
|
||||
|
||||
static bool ConvertElementsToDoubles(JSContext *cx, uintptr_t elements);
|
||||
static bool MakeElementsCopyOnWrite(ExclusiveContext *cx, JSObject *obj);
|
||||
|
||||
// This is enough slots to store an object of this class. See the static
|
||||
// assertion below.
|
||||
@ -406,7 +435,12 @@ class ObjectImpl : public gc::BarrieredCell<ObjectImpl>
|
||||
|
||||
HeapSlotArray getDenseElements() {
|
||||
JS_ASSERT(isNative());
|
||||
return HeapSlotArray(elements);
|
||||
return HeapSlotArray(elements, !getElementsHeader()->isCopyOnWrite());
|
||||
}
|
||||
HeapSlotArray getDenseElementsAllowCopyOnWrite() {
|
||||
// Backdoor allowing direct access to copy on write elements.
|
||||
JS_ASSERT(isNative());
|
||||
return HeapSlotArray(elements, true);
|
||||
}
|
||||
const Value &getDenseElement(uint32_t idx) {
|
||||
JS_ASSERT(isNative());
|
||||
|
@ -882,7 +882,6 @@
|
||||
* Stack: obj, id, val => obj
|
||||
*/ \
|
||||
macro(JSOP_INITELEM_SETTER, 100, "initelem_setter", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \
|
||||
\
|
||||
/*
|
||||
* Pushes the call site object specified by objectIndex onto the stack. Defines the raw
|
||||
* property specified by objectIndex + 1 on the call site object and freezes both the call site
|
||||
@ -894,7 +893,17 @@
|
||||
*/ \
|
||||
macro(JSOP_CALLSITEOBJ, 101, "callsiteobj", NULL, 5, 0, 1, JOF_OBJECT) \
|
||||
\
|
||||
macro(JSOP_UNUSED102, 102, "unused102", NULL, 1, 0, 0, JOF_BYTE) \
|
||||
/*
|
||||
* Pushes a newly created array onto the stack, whose elements are the same
|
||||
* as that of a template object's copy on write elements.
|
||||
*
|
||||
* Category: Literals
|
||||
* Type: Array
|
||||
* Operands: uint32_t objectIndex
|
||||
* Stack: => obj
|
||||
*/ \
|
||||
macro(JSOP_NEWARRAY_COPYONWRITE, 102, "newarray_copyonwrite", NULL, 5, 0, 1, JOF_OBJECT) \
|
||||
\
|
||||
macro(JSOP_UNUSED103, 103, "unused103", NULL, 1, 0, 0, JOF_BYTE) \
|
||||
macro(JSOP_UNUSED104, 104, "unused104", NULL, 1, 0, 0, JOF_BYTE) \
|
||||
macro(JSOP_UNUSED105, 105, "unused105", NULL, 1, 0, 0, JOF_BYTE) \
|
||||
|
@ -28,7 +28,7 @@ namespace js {
|
||||
*
|
||||
* https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode
|
||||
*/
|
||||
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 180);
|
||||
static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - 181);
|
||||
|
||||
class XDRBuffer {
|
||||
public:
|
||||
|
Loading…
Reference in New Issue
Block a user