Bug 1160887 - Fix various unboxed object bugs, r=jandem,terrence.

This commit is contained in:
Brian Hackett 2015-05-06 13:04:59 -06:00
parent ef90010167
commit 41a92895ba
6 changed files with 89 additions and 42 deletions

View File

@ -1157,11 +1157,11 @@ GCMarker::processMarkStackTop(SliceBudget& budget)
case SavedValueArrayTag: {
MOZ_ASSERT(!(addr & CellMask));
NativeObject* obj = reinterpret_cast<NativeObject*>(addr);
JSObject* obj = reinterpret_cast<JSObject*>(addr);
HeapValue* vp;
HeapValue* end;
if (restoreValueArray(obj, (void**)&vp, (void**)&end))
pushValueArray(obj, vp, end);
pushValueArray(&obj->as<NativeObject>(), vp, end);
else
repush(obj);
return;
@ -1385,11 +1385,15 @@ GCMarker::saveValueRanges()
}
bool
GCMarker::restoreValueArray(NativeObject* obj, void** vpp, void** endp)
GCMarker::restoreValueArray(JSObject* objArg, void** vpp, void** endp)
{
uintptr_t start = stack.pop();
HeapSlot::Kind kind = (HeapSlot::Kind) stack.pop();
if (!objArg->isNative())
return false;
NativeObject* obj = &objArg->as<NativeObject>();
if (kind == HeapSlot::Element) {
if (!obj->is<ArrayObject>())
return false;
@ -1812,10 +1816,21 @@ StoreBuffer::WholeCellEdges::mark(TenuringTracer& mover) const
JSGCTraceKind kind = GetGCThingTraceKind(edge);
if (kind <= JSTRACE_OBJECT) {
JSObject* object = static_cast<JSObject*>(edge);
if (object->is<ArgumentsObject>())
ArgumentsObject::trace(&mover, object);
// FIXME: bug 1161664 -- call the inline path below, now that it is accessable.
object->traceChildren(&mover);
// Additionally trace the expando object attached to any unboxed plain
// objects. Baseline and Ion can write properties to the expando while
// only adding a post barrier to the owning unboxed object. Note that
// it isn't possible for a nursery unboxed object to have a tenured
// expando, so that adding a post barrier on the original object will
// capture any tenured->nursery edges in the expando as well.
if (object->is<UnboxedPlainObject>()) {
if (UnboxedExpandoObject* expando = object->as<UnboxedPlainObject>().maybeExpando())
expando->traceChildren(&mover);
}
return;
}
MOZ_ASSERT(kind == JSTRACE_JITCODE);

View File

@ -277,7 +277,7 @@ class GCMarker : public JSTracer
return stack.isEmpty();
}
bool restoreValueArray(NativeObject* obj, void** vpp, void** endp);
bool restoreValueArray(JSObject* obj, void** vpp, void** endp);
void saveValueRanges();
inline void processMarkStackTop(SliceBudget& budget);

View File

@ -19,11 +19,11 @@ function inline_notSoEmpty1(a, b, c, d) {
}
var uceFault_notSoEmpty1 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty1'));
function notSoEmpty1() {
var a = { v: i };
var b = { v: 1 + a.v };
var c = { v: 2 + b.v };
var d = { v: 3 + c.v };
var unused = { v: 4 + d.v };
var a = { v: i, notunboxed: undefined };
var b = { v: 1 + a.v, notunboxed: undefined };
var c = { v: 2 + b.v, notunboxed: undefined };
var d = { v: 3 + c.v, notunboxed: undefined };
var unused = { v: 4 + d.v, notunboxed: undefined };
var res = inline_notSoEmpty1(a, b, c, d);
if (uceFault_notSoEmpty1(i) || uceFault_notSoEmpty1(i))
assertEq(i, res.v);
@ -44,15 +44,15 @@ function notSoEmpty1() {
// Check that we can recover objects with their content.
function inline_notSoEmpty2(a, b, c, d) {
"use strict";
return { v: (a.v + b.v + c.v + d.v - 10) / 4 };
return { v: (a.v + b.v + c.v + d.v - 10) / 4, notunboxed: undefined };
}
var uceFault_notSoEmpty2 = eval(uneval(uceFault).replace('uceFault', 'uceFault_notSoEmpty2'));
function notSoEmpty2(i) {
var a = { v: i };
var b = { v: 1 + a.v };
var c = { v: 2 + b.v };
var d = { v: 3 + c.v };
var unused = { v: 4 + d.v };
var a = { v: i, notunboxed: undefined };
var b = { v: 1 + a.v, notunboxed: undefined };
var c = { v: 2 + b.v, notunboxed: undefined };
var d = { v: 3 + c.v, notunboxed: undefined };
var unused = { v: 4 + d.v, notunboxed: undefined };
var res = inline_notSoEmpty2(a, b, c, d);
if (uceFault_notSoEmpty2(i) || uceFault_notSoEmpty2(i))
assertEq(i, res.v);
@ -70,13 +70,13 @@ function notSoEmpty2(i) {
var argFault_observeArg = function (i) {
if (i > 98)
return inline_observeArg.arguments[0];
return { test : i };
return { test : i, notunboxed: undefined };
};
function inline_observeArg(obj, i) {
return argFault_observeArg(i);
}
function observeArg(i) {
var obj = { test: i };
var obj = { test: i, notunboxed: undefined };
var res = inline_observeArg(obj, i);
assertEq(res.test, i);
assertRecoveredOnBailout(obj, true);
@ -84,7 +84,7 @@ function observeArg(i) {
// Check case where one successor can have multiple times the same predecessor.
function complexPhi(i) {
var obj = { test: i };
var obj = { test: i, notunboxed: undefined };
switch (i) { // TableSwitch
case 0: obj.test = 0; break;
case 1: obj.test = 1; break;
@ -103,12 +103,12 @@ function complexPhi(i) {
function withinIf(i) {
var x = undefined;
if (i % 2 == 0) {
let obj = { foo: i };
let obj = { foo: i, notunboxed: undefined };
x = obj.foo;
assertRecoveredOnBailout(obj, true);
obj = undefined;
} else {
let obj = { bar: i };
let obj = { bar: i, notunboxed: undefined };
x = obj.bar;
assertRecoveredOnBailout(obj, true);
obj = undefined;
@ -118,7 +118,7 @@ function withinIf(i) {
// Check case where one successor can have multiple times the same predecessor.
function unknownLoad(i) {
var obj = { foo: i };
var obj = { foo: i, notunboxed: undefined };
assertEq(obj.bar, undefined);
// Unknown properties are using GetPropertyCache.
assertRecoveredOnBailout(obj, false);
@ -132,7 +132,8 @@ function dynamicSlots(i) {
p11: i + 11, p12: i + 12, p13: i + 13, p14: i + 14, p15: i + 15, p16: i + 16, p17: i + 17, p18: i + 18, p19: i + 19, p20: i + 20,
p21: i + 21, p22: i + 22, p23: i + 23, p24: i + 24, p25: i + 25, p26: i + 26, p27: i + 27, p28: i + 28, p29: i + 29, p30: i + 30,
p31: i + 31, p32: i + 32, p33: i + 33, p34: i + 34, p35: i + 35, p36: i + 36, p37: i + 37, p38: i + 38, p39: i + 39, p40: i + 40,
p41: i + 41, p42: i + 42, p43: i + 43, p44: i + 44, p45: i + 45, p46: i + 46, p47: i + 47, p48: i + 48, p49: i + 49, p50: i + 50
p41: i + 41, p42: i + 42, p43: i + 43, p44: i + 44, p45: i + 45, p46: i + 46, p47: i + 47, p48: i + 48, p49: i + 49, p50: i + 50,
notunboxed: undefined
};
// Add a function call to capture a resumepoint at the end of the call or
// inside the inlined block, such as the bailout does not rewind to the
@ -147,6 +148,7 @@ function Point(x, y)
{
this.x = x;
this.y = y;
this.notUnboxed = undefined;
}
function createThisWithTemplate(i)

View File

@ -465,6 +465,8 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
// unboxed plain object.
MOZ_ASSERT(!clasp == (associated && associated->is<JSFunction>()));
AutoEnterAnalysis enter(cx);
ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.defaultNewTable;
if (!table) {
@ -498,6 +500,9 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
clasp = &PlainObject::class_;
}
if (proto.isObject() && !proto.toObject()->setDelegate(cx))
return nullptr;
ObjectGroupCompartment::NewTable::AddPtr p =
table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, associated));
if (p) {
@ -509,11 +514,6 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
return group;
}
AutoEnterAnalysis enter(cx);
if (proto.isObject() && !proto.toObject()->setDelegate(cx))
return nullptr;
ObjectGroupFlags initialFlags = 0;
if (!proto.isObject() || proto.toObject()->isNewGroupUnknown())
initialFlags = OBJECT_FLAG_DYNAMIC_MASK;

View File

@ -71,6 +71,8 @@ static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
/* static */ bool
UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
{
gc::AutoSuppressGC suppress(cx);
using namespace jit;
if (!cx->compartment()->ensureJitCompartmentExists(cx))
@ -107,8 +109,7 @@ UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny();
LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs);
for (GeneralRegisterForwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter)
masm.Push(*iter);
masm.PushRegsInMask(savedNonVolatileRegisters);
// The scratch double register might be used by MacroAssembler methods.
if (ScratchDoubleReg.volatile_())
@ -148,12 +149,20 @@ UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
masm.jump(&allocated);
masm.bind(&postBarrier);
LiveGeneralRegisterSet liveVolatileRegisters;
liveVolatileRegisters.add(propertiesReg);
if (object.volatile_())
liveVolatileRegisters.add(object);
masm.PushRegsInMask(liveVolatileRegisters);
masm.mov(ImmPtr(cx->runtime()), scratch1);
masm.setupUnalignedABICall(2, scratch2);
masm.passABIArg(scratch1);
masm.passABIArg(object);
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
masm.PopRegsInMask(liveVolatileRegisters);
masm.bind(&allocated);
ValueOperand valueOperand;
@ -213,8 +222,7 @@ UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
// Restore non-volatile registers which were saved on entry.
if (ScratchDoubleReg.volatile_())
masm.pop(ScratchDoubleReg);
for (GeneralRegisterBackwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter)
masm.Pop(*iter);
masm.PopRegsInMask(savedNonVolatileRegisters);
masm.abiret();
@ -327,7 +335,7 @@ SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
}
static inline Value
GetUnboxedValue(uint8_t* p, JSValueType type)
GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized)
{
switch (type) {
case JSVAL_TYPE_BOOLEAN:
@ -336,8 +344,16 @@ GetUnboxedValue(uint8_t* p, JSValueType type)
case JSVAL_TYPE_INT32:
return Int32Value(*reinterpret_cast<int32_t*>(p));
case JSVAL_TYPE_DOUBLE:
return DoubleValue(*reinterpret_cast<double*>(p));
case JSVAL_TYPE_DOUBLE: {
// During unboxed plain object creation, non-GC thing properties are
// left uninitialized. This is normally fine, since the properties will
// be filled in shortly, but if they are read before that happens we
// need to make sure that doubles are canonical.
double d = *reinterpret_cast<double*>(p);
if (maybeUninitialized)
return DoubleValue(JS::CanonicalizeNaN(d));
return DoubleValue(d);
}
case JSVAL_TYPE_STRING:
return StringValue(*reinterpret_cast<JSString**>(p));
@ -363,10 +379,11 @@ UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property
}
Value
UnboxedPlainObject::getValue(const UnboxedLayout::Property& property)
UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
bool maybeUninitialized /* = false */)
{
uint8_t* p = &data_[property.offset];
return GetUnboxedValue(p, property.type);
return GetUnboxedValue(p, property.type, maybeUninitialized);
}
void
@ -411,6 +428,12 @@ UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj
if (!expando)
return nullptr;
// If the expando is tenured then the original object must also be tenured.
// Otherwise barriers triggered on the original object for writes to the
// expando (as can happen in the JIT) won't see the tenured->nursery edge.
// See WholeCellEdges::mark.
MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
// As with setValue(), we need to manually trigger post barriers on the
// whole object. If we treat the field as a HeapPtrObject and later convert
// the object to its native representation, we will end up with a corrupted
@ -578,10 +601,15 @@ UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
AutoValueVector values(cx);
for (size_t i = 0; i < layout.properties().length(); i++) {
if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i])))
// We might be reading properties off the object which have not been
// initialized yet. Make sure any double values we read here are
// canonicalized.
if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
return false;
}
JSObject::writeBarrierPre(expando);
obj->setGroup(layout.nativeGroup());
obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
@ -935,7 +963,9 @@ const Class UnboxedExpandoObject::class_ = {
const Class UnboxedPlainObject::class_ = {
js_Object_str,
Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS,
Class::NON_NATIVE |
JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_CACHED_PROTO(JSProto_Object),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
@ -1099,7 +1129,7 @@ UnboxedArrayObject::getElement(size_t index)
{
MOZ_ASSERT(index < initializedLength());
uint8_t* p = elements() + index * elementSize();
return GetUnboxedValue(p, elementType());
return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false);
}
/* static */ void

View File

@ -265,7 +265,7 @@ class UnboxedPlainObject : public JSObject
static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj);
bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v);
Value getValue(const UnboxedLayout::Property& property);
Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false);
static bool convertToNative(JSContext* cx, JSObject* obj);
static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group,