Bug 934450 - Allow objects to have copy on write elements, r=billm,jandem.

This commit is contained in:
Brian Hackett 2014-08-19 22:25:37 -08:00
parent c7d05c2620
commit 4228ccca2e
40 changed files with 844 additions and 88 deletions

View File

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

View File

@ -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:

View File

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

View File

@ -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
}
};
/*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@
_(TableSwitchV) \
_(Goto) \
_(NewArray) \
_(NewArrayCopyOnWrite) \
_(ArraySplice) \
_(NewObject) \
_(NewDeclEnvObject) \
@ -174,6 +175,7 @@
_(Elements) \
_(ConvertElementsToDoubles) \
_(MaybeToDoubleElement) \
_(MaybeCopyElementsForWrite) \
_(LoadSlotV) \
_(LoadSlotT) \
_(StoreSlotV) \

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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: