From a8ab778b40df1175dcc5982a73a29af7e5bf2161 Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Mon, 26 Jan 2015 12:16:26 -0700 Subject: [PATCH] Bug 1116855 - Add default-disabled unboxed objects for use by interpreted constructors, r=jandem. --- js/src/builtin/TypedObject.h | 11 +- js/src/gc/Marking.cpp | 57 +- js/src/gc/Nursery.cpp | 8 +- .../basic/unboxed-object-clear-new-script.js | 49 ++ .../basic/unboxed-object-convert-to-native.js | 47 ++ js/src/jit/BaselineIC.cpp | 34 +- js/src/jit/IonBuilder.cpp | 4 + js/src/jsapi.h | 8 + js/src/jsgc.h | 16 + js/src/jsinfer.cpp | 396 ++++++---- js/src/jsinfer.h | 136 ++-- js/src/jsinferinlines.h | 14 +- js/src/jsobj.cpp | 26 +- js/src/jsobj.h | 15 +- js/src/moz.build | 1 + js/src/shell/js.cpp | 5 +- js/src/vm/NativeObject.cpp | 14 + js/src/vm/NativeObject.h | 7 + js/src/vm/Shape.cpp | 1 - js/src/vm/Shape.h | 6 +- js/src/vm/UnboxedObject.cpp | 689 ++++++++++++++++++ js/src/vm/UnboxedObject.h | 215 ++++++ 22 files changed, 1499 insertions(+), 260 deletions(-) create mode 100644 js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js create mode 100644 js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js create mode 100644 js/src/vm/UnboxedObject.cpp create mode 100644 js/src/vm/UnboxedObject.h diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 747d1698961d..1fdd6a96bd25 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -727,20 +727,13 @@ class InlineTypedObject : public TypedObject uint8_t data_[1]; public: - static const size_t MaximumSize = - sizeof(NativeObject) - sizeof(TypedObject) + NativeObject::MAX_FIXED_SLOTS * sizeof(Value); + static const size_t MaximumSize = JSObject::MAX_BYTE_SIZE - sizeof(TypedObject); static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) { size_t nbytes = descr->size(); MOZ_ASSERT(nbytes <= MaximumSize); - if (nbytes <= sizeof(NativeObject) - sizeof(TypedObject)) - return gc::FINALIZE_OBJECT0; - nbytes -= sizeof(NativeObject) - sizeof(TypedObject); - - size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value); - MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value)); - return gc::GetGCObjectKind(dataSlots); + return gc::GetGCObjectKindForBytes(nbytes + sizeof(TypedObject)); } uint8_t *inlineTypedMem() const { diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index a1c18d099c59..0a15bb897503 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -19,6 +19,7 @@ #include "vm/Shape.h" #include "vm/Symbol.h" #include "vm/TypedArrayObject.h" +#include "vm/UnboxedObject.h" #include "jscompartmentinlines.h" #include "jsinferinlines.h" @@ -1438,6 +1439,9 @@ ScanTypeObject(GCMarker *gcmarker, types::TypeObject *type) if (type->newScript()) type->newScript()->trace(gcmarker); + if (type->maybeUnboxedLayout()) + type->unboxedLayout().trace(gcmarker); + if (TypeDescr *descr = type->maybeTypeDescr()) PushMarkStack(gcmarker, descr); @@ -1464,6 +1468,9 @@ gc::MarkChildren(JSTracer *trc, types::TypeObject *type) if (type->newScript()) type->newScript()->trace(trc); + if (type->maybeUnboxedLayout()) + type->unboxedLayout().trace(trc); + if (JSObject *descr = type->maybeTypeDescr()) { MarkObjectUnbarriered(trc, &descr, "type_descr"); type->setTypeDescr(&descr->as()); @@ -1700,6 +1707,9 @@ GCMarker::processMarkStackTop(SliceBudget &budget) HeapSlot *vp, *end; JSObject *obj; + const int32_t *unboxedTraceList; + uint8_t *unboxedMemory; + uintptr_t addr = stack.pop(); uintptr_t tag = addr & StackTagMask; addr &= ~StackTagMask; @@ -1745,28 +1755,23 @@ GCMarker::processMarkStackTop(SliceBudget &budget) } return; - scan_typed_obj: + scan_unboxed: { - TypeDescr *descr = &obj->as().typeDescr(); - if (!descr->hasTraceList()) - return; - const int32_t *list = descr->traceList(); - uint8_t *memory = obj->as().inlineTypedMem(); - while (*list != -1) { - JSString *str = *reinterpret_cast(memory + *list); + while (*unboxedTraceList != -1) { + JSString *str = *reinterpret_cast(unboxedMemory + *unboxedTraceList); markAndScanString(obj, str); - list++; + unboxedTraceList++; } - list++; - while (*list != -1) { - JSObject *obj2 = *reinterpret_cast(memory + *list); + unboxedTraceList++; + while (*unboxedTraceList != -1) { + JSObject *obj2 = *reinterpret_cast(unboxedMemory + *unboxedTraceList); if (obj2 && markObject(obj, obj2)) pushObject(obj2); - list++; + unboxedTraceList++; } - list++; - while (*list != -1) { - const Value &v = *reinterpret_cast(memory + *list); + unboxedTraceList++; + while (*unboxedTraceList != -1) { + const Value &v = *reinterpret_cast(unboxedMemory + *unboxedTraceList); if (v.isString()) { markAndScanString(obj, v.toString()); } else if (v.isObject()) { @@ -1776,7 +1781,7 @@ GCMarker::processMarkStackTop(SliceBudget &budget) } else if (v.isSymbol()) { markAndScanSymbol(obj, v.toSymbol()); } - list++; + unboxedTraceList++; } return; } @@ -1806,8 +1811,22 @@ GCMarker::processMarkStackTop(SliceBudget &budget) MOZ_ASSERT_IF(!(clasp->trace == JS_GlobalObjectTraceHook && (!obj->compartment()->options().getTrace() || !obj->isOwnGlobal())), clasp->flags & JSCLASS_IMPLEMENTS_BARRIERS); - if (clasp->trace == InlineTypedObject::obj_trace) - goto scan_typed_obj; + if (clasp->trace == InlineTypedObject::obj_trace) { + TypeDescr *descr = &obj->as().typeDescr(); + if (!descr->hasTraceList()) + return; + unboxedTraceList = descr->traceList(); + unboxedMemory = obj->as().inlineTypedMem(); + goto scan_unboxed; + } + if (clasp == &UnboxedPlainObject::class_) { + const UnboxedLayout &layout = obj->as().layout(); + unboxedTraceList = layout.traceList(); + if (!unboxedTraceList) + return; + unboxedMemory = obj->as().data(); + goto scan_unboxed; + } clasp->trace(this, obj); } diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 5628e064452d..c1a35ef249a1 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -420,6 +420,12 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj) // Proxies have finalizers and are not nursery allocated. MOZ_ASSERT(!IsProxy(obj)); + // Unboxed plain objects are sized according to the data they store. + if (obj->is()) { + size_t nbytes = obj->as().layout().size(); + return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes); + } + // Inlined typed objects are followed by their data, so make sure we copy // it all over to the new object. if (obj->is()) { @@ -435,7 +441,7 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj) if (obj->is()) return FINALIZE_OBJECT0; - // The only non-native objects in existence are proxies and typed objects. + // All nursery allocatable non-native objects are handled above. MOZ_ASSERT(obj->isNative()); AllocKind kind = GetGCObjectFixedSlotsKind(obj->as().numFixedSlots()); diff --git a/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js b/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js new file mode 100644 index 000000000000..f55456222d14 --- /dev/null +++ b/js/src/jit-test/tests/basic/unboxed-object-clear-new-script.js @@ -0,0 +1,49 @@ + +function Foo(a, b) { + this.a = a; + this.b = b; +} + +function invalidate_foo() { + var a = []; + var counter = 0; + for (var i = 0; i < 50; i++) + a.push(new Foo(i, i + 1)); + Object.defineProperty(Foo.prototype, "a", {configurable: true, set: function() { counter++; }}); + for (var i = 0; i < 50; i++) + a.push(new Foo(i, i + 1)); + delete Foo.prototype.a; + var total = 0; + for (var i = 0; i < a.length; i++) { + assertEq('a' in a[i], i < 50); + total += a[i].b; + } + assertEq(total, 2550); + assertEq(counter, 50); +} +invalidate_foo(); + +function Bar(a, b, fn) { + this.a = a; + if (b == 30) + Object.defineProperty(Bar.prototype, "b", {configurable: true, set: fn}); + this.b = b; +} + +function invalidate_bar() { + var a = []; + var counter = 0; + function fn() { counter++; } + for (var i = 0; i < 50; i++) + a.push(new Bar(i, i + 1, fn)); + delete Bar.prototype.b; + var total = 0; + for (var i = 0; i < a.length; i++) { + assertEq('a' in a[i], true); + assertEq('b' in a[i], i < 29); + total += a[i].a; + } + assertEq(total, 1225); + assertEq(counter, 21); +} +invalidate_bar(); diff --git a/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js b/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js new file mode 100644 index 000000000000..691fe166c333 --- /dev/null +++ b/js/src/jit-test/tests/basic/unboxed-object-convert-to-native.js @@ -0,0 +1,47 @@ + +// Test various ways of converting an unboxed object to native. + +function Foo(a, b) { + this.a = a; + this.b = b; +} + +var proxyObj = { + get: function(recipient, name) { + return recipient[name] + 2; + } +}; + +function f() { + var a = []; + for (var i = 0; i < 50; i++) + a.push(new Foo(i, i + 1)); + + var prop = "a"; + + i = 0; + for (; i < 5; i++) + a[i].c = i; + for (; i < 10; i++) + Object.defineProperty(a[i], 'c', {value: i}); + for (; i < 15; i++) + a[i] = new Proxy(a[i], proxyObj); + for (; i < 20; i++) + a[i].a = 3.5; + for (; i < 25; i++) + delete a[i].b; + for (; i < 30; i++) + a[prop] = 4; + + var total = 0; + for (i = 0; i < a.length; i++) { + if ('a' in a[i]) + total += a[i].a; + if ('b' in a[i]) + total += a[i].b; + if ('c' in a[i]) + total += a[i].c; + } + assertEq(total, 2382.5); +} +f(); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 1b7cb82a181b..c66aca5bdcb7 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -9083,23 +9083,27 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsb // as a constructor, for later use during Ion compilation. RootedPlainObject templateObject(cx); if (constructing) { - templateObject = CreateThisForFunction(cx, fun, MaybeSingletonObject); - if (!templateObject) + JSObject *thisObject = CreateThisForFunction(cx, fun, MaybeSingletonObject); + if (!thisObject) return false; - // If we are calling a constructor for which the new script - // properties analysis has not been performed yet, don't attach a - // stub. After the analysis is performed, CreateThisForFunction may - // start returning objects with a different type, and the Ion - // compiler might get confused. - if (templateObject->type()->newScript() && - !templateObject->type()->newScript()->analyzed()) - { - // Clear the object just created from the preliminary objects - // on the TypeNewScript, as it will not be used or filled in by - // running code. - templateObject->type()->newScript()->unregisterNewObject(templateObject); - return true; + if (thisObject->is()) { + templateObject = &thisObject->as(); + + // If we are calling a constructor for which the new script + // properties analysis has not been performed yet, don't attach a + // stub. After the analysis is performed, CreateThisForFunction may + // start returning objects with a different type, and the Ion + // compiler might get confused. + if (templateObject->type()->newScript() && + !templateObject->type()->newScript()->analyzed()) + { + // Clear the object just created from the preliminary objects + // on the TypeNewScript, as it will not be used or filled in by + // running code. + templateObject->type()->newScript()->unregisterNewObject(templateObject); + return true; + } } } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 7ab585caffad..cce1880a9016 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -5381,6 +5381,10 @@ IonBuilder::createThisScriptedSingleton(JSFunction *target, MDefinition *callee) if (!templateObject->hasTenuredProto() || templateObject->getProto() != proto) return nullptr; + types::TypeObjectKey *templateObjectType = types::TypeObjectKey::get(templateObject->type()); + if (templateObjectType->hasFlags(constraints(), types::OBJECT_FLAG_NEW_SCRIPT_CLEARED)) + return nullptr; + types::StackTypeSet *thisTypes = types::TypeScript::ThisTypes(target->nonLazyScript()); if (!thisTypes || !thisTypes->hasType(types::Type::ObjectType(templateObject))) return nullptr; diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 54e3c5c62eec..89080ef10eeb 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1497,6 +1497,7 @@ class JS_PUBLIC_API(RuntimeOptions) { ion_(false), asmJS_(false), nativeRegExp_(false), + unboxedObjects_(false), werror_(false), strictMode_(false), extraWarnings_(false), @@ -1540,6 +1541,12 @@ class JS_PUBLIC_API(RuntimeOptions) { return *this; } + bool unboxedObjects() const { return unboxedObjects_; } + RuntimeOptions &setUnboxedObjects(bool flag) { + unboxedObjects_ = flag; + return *this; + } + bool werror() const { return werror_; } RuntimeOptions &setWerror(bool flag) { werror_ = flag; @@ -1585,6 +1592,7 @@ class JS_PUBLIC_API(RuntimeOptions) { bool ion_ : 1; bool asmJS_ : 1; bool nativeRegExp_ : 1; + bool unboxedObjects_ : 1; bool werror_ : 1; bool strictMode_ : 1; bool extraWarnings_ : 1; diff --git a/js/src/jsgc.h b/js/src/jsgc.h index f1064dbefc3f..19540e9e1474 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -192,6 +192,22 @@ GetGCObjectFixedSlotsKind(size_t numFixedSlots) return slotsToThingKind[numFixedSlots]; } +// Get the best kind to use when allocating an object that needs a specific +// number of bytes. +static inline AllocKind +GetGCObjectKindForBytes(size_t nbytes) +{ + MOZ_ASSERT(nbytes <= JSObject::MAX_BYTE_SIZE); + + if (nbytes <= sizeof(NativeObject)) + return FINALIZE_OBJECT0; + nbytes -= sizeof(NativeObject); + + size_t dataSlots = AlignBytes(nbytes, sizeof(Value)) / sizeof(Value); + MOZ_ASSERT(nbytes <= dataSlots * sizeof(Value)); + return GetGCObjectKind(dataSlots); +} + static inline AllocKind GetBackgroundAllocKind(AllocKind kind) { diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 160e1b210753..1c026523a2c0 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -30,6 +30,7 @@ #include "vm/HelperThreads.h" #include "vm/Opcodes.h" #include "vm/Shape.h" +#include "vm/UnboxedObject.h" #include "jsatominlines.h" #include "jsgcinlines.h" @@ -3274,6 +3275,41 @@ TypeObject::markUnknown(ExclusiveContext *cx) } } +TypeNewScript * +TypeObject::anyNewScript() +{ + if (newScript()) + return newScript(); + if (maybeUnboxedLayout()) + return unboxedLayout().newScript(); + return nullptr; +} + +void +TypeObject::detachNewScript(bool writeBarrier) +{ + // Clear the TypeNewScript from this TypeObject and, if it has been + // analyzed, remove it from the newTypeObjects table so that it will not be + // produced by calling 'new' on the associated function anymore. + // The TypeNewScript is not actually destroyed. + TypeNewScript *newScript = anyNewScript(); + MOZ_ASSERT(newScript); + + if (newScript->analyzed()) { + NewTypeObjectTable &newTypeObjects = newScript->function()->compartment()->newTypeObjects; + NewTypeObjectTable::Ptr p = + newTypeObjects.lookup(NewTypeObjectTable::Lookup(nullptr, proto(), newScript->function())); + MOZ_ASSERT(p->object == this); + + newTypeObjects.remove(p); + } + + if (this->newScript()) + setAddendum(Addendum_None, nullptr, writeBarrier); + else + unboxedLayout().setNewScript(nullptr, writeBarrier); +} + void TypeObject::maybeClearNewScriptOnOOM() { @@ -3282,53 +3318,55 @@ TypeObject::maybeClearNewScriptOnOOM() if (!isMarked()) return; - if (!newScript()) + TypeNewScript *newScript = anyNewScript(); + if (!newScript) return; - for (unsigned i = 0; i < getPropertyCount(); i++) { - Property *prop = getProperty(i); - if (!prop) - continue; - if (prop->types.definiteProperty()) - prop->types.setNonDataPropertyIgnoringConstraints(); - } + addFlags(OBJECT_FLAG_NEW_SCRIPT_CLEARED); - // This method is called during GC sweeping, so there is no write barrier - // that needs to be triggered. - js_delete(newScript()); - addendum_ = nullptr; + // This method is called during GC sweeping, so don't trigger pre barriers. + detachNewScript(/* writeBarrier = */ false); + + js_delete(newScript); } void TypeObject::clearNewScript(ExclusiveContext *cx) { - if (!newScript()) + TypeNewScript *newScript = anyNewScript(); + if (!newScript) return; - TypeNewScript *newScript = this->newScript(); - setNewScript(nullptr); + // Invalidate any Ion code constructing objects of this type. + setFlags(cx, OBJECT_FLAG_NEW_SCRIPT_CLEARED); + + // Mark the constructing function as having its 'new' script cleared, so we + // will not try to construct another one later. + if (!newScript->function()->setNewScriptCleared(cx)) + cx->recoverFromOutOfMemory(); + + detachNewScript(/* writeBarrier = */ true); AutoEnterAnalysis enter(cx); - /* - * Any definite properties we added due to analysis of the new script when - * the type object was created are now invalid: objects with the same type - * can be created by using 'new' on a different script or through some - * other mechanism (e.g. Object.create). Rather than clear out the definite - * bits on the object's properties, just mark such properties as having - * been deleted/reconfigured, which will have the same effect on JITs - * wanting to use the definite bits to optimize property accesses. - */ - for (unsigned i = 0; i < getPropertyCount(); i++) { - Property *prop = getProperty(i); - if (!prop) - continue; - if (prop->types.definiteProperty()) - prop->types.setNonDataProperty(cx); - } - if (cx->isJSContext()) { - newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this); + bool found = newScript->rollbackPartiallyInitializedObjects(cx->asJSContext(), this); + + // If we managed to rollback any partially initialized objects, then + // any definite properties we added due to analysis of the new script + // are now invalid, so remove them. If there weren't any partially + // initialized objects then we don't need to change type information, + // as no more objects of this type will be created and the 'new' script + // analysis was still valid when older objects were created. + if (found) { + for (unsigned i = 0; i < getPropertyCount(); i++) { + Property *prop = getProperty(i); + if (!prop) + continue; + if (prop->types.definiteProperty()) + prop->types.setNonDataProperty(cx); + } + } } else { // Threads with an ExclusiveContext are not allowed to run scripts. MOZ_ASSERT(!cx->perThreadData->runtimeIfOnOwnerThread() || @@ -3770,6 +3808,63 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext *cx, HandleFunction fun, return true; } +///////////////////////////////////////////////////////////////////// +// PreliminaryObjectArray +///////////////////////////////////////////////////////////////////// + +void +PreliminaryObjectArray::registerNewObject(JSObject *res) +{ + // The preliminary object pointers are weak, and won't be swept properly + // during nursery collections, so the preliminary objects need to be + // initially tenured. + MOZ_ASSERT(!IsInsideNursery(res)); + + for (size_t i = 0; i < COUNT; i++) { + if (!objects[i]) { + objects[i] = res; + return; + } + } + + MOZ_CRASH("There should be room for registering the new object"); +} + +void +PreliminaryObjectArray::unregisterNewObject(JSObject *res) +{ + for (size_t i = 0; i < COUNT; i++) { + if (objects[i] == res) { + objects[i] = nullptr; + return; + } + } + + MOZ_CRASH("The object should be one of the preliminary objects"); +} + +bool +PreliminaryObjectArray::full() const +{ + for (size_t i = 0; i < COUNT; i++) { + if (!objects[i]) + return false; + } + return true; +} + +void +PreliminaryObjectArray::sweep() +{ + // All objects in the array are weak, so clear any that are about to be + // destroyed. + for (size_t i = 0; i < COUNT; i++) { + JSObject **ptr = &objects[i]; + if (*ptr && IsObjectAboutToBeFinalized(ptr)) + *ptr = nullptr; + } +} + ///////////////////////////////////////////////////////////////////// // TypeNewScript ///////////////////////////////////////////////////////////////////// @@ -3781,6 +3876,7 @@ TypeNewScript::make(JSContext *cx, TypeObject *type, JSFunction *fun) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); MOZ_ASSERT(!type->newScript()); + MOZ_ASSERT(!type->maybeUnboxedLayout()); if (type->unknownProperties()) return; @@ -3789,14 +3885,12 @@ TypeNewScript::make(JSContext *cx, TypeObject *type, JSFunction *fun) if (!newScript) return; - newScript->fun = fun; + newScript->function_ = fun; - PlainObject **preliminaryObjects = - type->zone()->pod_calloc(PRELIMINARY_OBJECT_COUNT); - if (!preliminaryObjects) + newScript->preliminaryObjects = type->zone()->new_(); + if (!newScript->preliminaryObjects) return; - newScript->preliminaryObjects = preliminaryObjects; type->setNewScript(newScript.forget()); gc::TraceTypeNewScript(type); @@ -3816,40 +3910,19 @@ TypeNewScript::registerNewObject(PlainObject *res) { MOZ_ASSERT(!analyzed()); - // The preliminary object pointers are weak, and won't be swept properly - // during nursery collections, so the preliminary objects need to be - // initially tenured. - MOZ_ASSERT(!IsInsideNursery(res)); - // New script objects must have the maximum number of fixed slots, so that // we can adjust their shape later to match the number of fixed slots used // by the template object we eventually create. MOZ_ASSERT(res->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS); - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - if (!preliminaryObjects[i]) { - preliminaryObjects[i] = res; - return; - } - } - - MOZ_CRASH("There should be room for registering the new object"); + preliminaryObjects->registerNewObject(res); } void TypeNewScript::unregisterNewObject(PlainObject *res) { MOZ_ASSERT(!analyzed()); - - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - if (preliminaryObjects[i] == res) { - preliminaryObjects[i] = nullptr; - return; - } - } - - // The object should be one of the preliminary objects. - MOZ_CRASH(); + preliminaryObjects->unregisterNewObject(res); } // Return whether shape consists entirely of plain data properties. @@ -3950,14 +4023,10 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b return true; } - if (!force) { - // Don't perform the analyses until sufficient preliminary objects have - // been allocated. - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - if (!preliminaryObjects[i]) - return true; - } - } + // Don't perform the analyses until sufficient preliminary objects have + // been allocated. + if (!force && !preliminaryObjects->full()) + return true; AutoEnterAnalysis enter(cx); @@ -3968,10 +4037,11 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b // the preliminary objects. Shape *prefixShape = nullptr; size_t maxSlotSpan = 0; - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - PlainObject *obj = preliminaryObjects[i]; - if (!obj) + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject *objBase = preliminaryObjects->get(i); + if (!objBase) continue; + PlainObject *obj = &objBase->as(); // For now, we require all preliminary objects to have only simple // lineages of plain data properties. @@ -4007,10 +4077,11 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b // template object. Also recompute the prefix shape, as it reflects the // old number of fixed slots. Shape *newPrefixShape = nullptr; - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - PlainObject *obj = preliminaryObjects[i]; - if (!obj) + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject *objBase = preliminaryObjects->get(i); + if (!objBase) continue; + PlainObject *obj = &objBase->as(); if (!ChangeObjectFixedSlotCount(cx, obj, kind)) return false; if (newPrefixShape) { @@ -4032,7 +4103,7 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b Vector initializerVector(cx); RootedPlainObject templateRoot(cx, templateObject()); - if (!jit::AnalyzeNewScriptDefiniteProperties(cx, fun, type, templateRoot, &initializerVector)) + if (!jit::AnalyzeNewScriptDefiniteProperties(cx, function(), type, templateRoot, &initializerVector)) return false; if (!type->newScript()) @@ -4077,9 +4148,28 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b PodCopy(initializerList, initializerVector.begin(), initializerVector.length()); } - js_free(preliminaryObjects); + // Try to use an unboxed representation for the type. + if (!TryConvertToUnboxedLayout(cx, templateObject()->lastProperty(), type, preliminaryObjects)) + return false; + + js_delete(preliminaryObjects); preliminaryObjects = nullptr; + if (type->maybeUnboxedLayout()) { + // An unboxed layout was constructed for the type, and this has already + // been hooked into it. + MOZ_ASSERT(type->unboxedLayout().newScript() == this); + destroyNewScript.type = nullptr; + + // Clear out the template object. This is not used for TypeNewScripts + // with an unboxed layout, and additionally this template is now a + // mutant object with a non-native class and native shape, and must be + // collected by the next GC. + templateObject_ = nullptr; + + return true; + } + if (prefixShape->slotSpan() == templateObject()->slotSpan()) { // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis @@ -4112,11 +4202,11 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b return false; NewTypeObjectTable &table = cx->compartment()->newTypeObjects; - NewTypeObjectTable::Lookup lookup(type->clasp(), type->proto(), fun); + NewTypeObjectTable::Lookup lookup(nullptr, type->proto(), function()); MOZ_ASSERT(table.lookup(lookup)->object == type); table.remove(lookup); - table.putNew(lookup, NewTypeObjectEntry(initialType, fun)); + table.putNew(lookup, NewTypeObjectEntry(initialType, function())); templateObject()->setType(initialType); @@ -4135,7 +4225,7 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b return true; } -void +bool TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type) { // If we cleared this new script while in the middle of initializing an @@ -4144,17 +4234,19 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *ty // We can't detect the possibility of this statically while remaining // robust, but the new script keeps track of where each property is // initialized so we can walk the stack and fix up any such objects. + // Return whether any objects were modified. if (!initializerList) - return; + return false; - RootedFunction function(cx, fun); + bool found = false; + + RootedFunction function(cx, this->function()); Vector pcOffsets(cx); for (ScriptFrameIter iter(cx); !iter.done(); ++iter) { pcOffsets.append(iter.script()->pcToOffset(iter.pc())); - // This frame has no this. - if (!iter.isConstructing() || iter.matchCallee(cx, function)) + if (!iter.isConstructing() || !iter.matchCallee(cx, function)) continue; Value thisv = iter.thisv(cx); @@ -4165,6 +4257,12 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *ty continue; } + if (thisv.toObject().is() && + !thisv.toObject().as().convertToNative(cx)) + { + CrashAtUnhandlableOOM("rollbackPartiallyInitializedObjects"); + } + // Found a matching frame. RootedPlainObject obj(cx, &thisv.toObject().as()); @@ -4217,15 +4315,19 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *ty } } - if (!finished) + if (!finished) { (void) NativeObject::rollbackProperties(cx, obj, numProperties); + found = true; + } } + + return found; } void TypeNewScript::trace(JSTracer *trc) { - MarkObject(trc, &fun, "TypeNewScript_function"); + MarkObject(trc, &function_, "TypeNewScript_function"); if (templateObject_) MarkObject(trc, &templateObject_, "TypeNewScript_templateObject"); @@ -4240,15 +4342,8 @@ TypeNewScript::trace(JSTracer *trc) void TypeNewScript::sweep() { - // preliminaryObjects only holds weak pointers, so clear any objects that - // are about to be destroyed. - if (preliminaryObjects) { - for (size_t i = 0; i < PRELIMINARY_OBJECT_COUNT; i++) { - PlainObject **ptr = &preliminaryObjects[i]; - if (*ptr && IsObjectAboutToBeFinalized(ptr)) - *ptr = nullptr; - } - } + if (preliminaryObjects) + preliminaryObjects->sweep(); } ///////////////////////////////////////////////////////////////////// @@ -4362,7 +4457,7 @@ NewTypeObjectEntry::hash(const Lookup &lookup) NewTypeObjectEntry::match(const NewTypeObjectEntry &key, const Lookup &lookup) { return key.object->proto() == lookup.matchProto && - key.object->clasp() == lookup.clasp && + (!lookup.clasp || key.object->clasp() == lookup.clasp) && key.associated == lookup.associated; } @@ -4431,7 +4526,8 @@ class NewTypeObjectsSetRef : public BufferableRef NewTypeObjectTable::Ptr p = set->lookup(NewTypeObjectTable::Lookup(clasp, TaggedProto(prior), TaggedProto(proto), associated)); - MOZ_ASSERT(p); // newTypeObjects set must still contain original entry. + if (!p) + return; set->rekeyAs(NewTypeObjectTable::Lookup(clasp, TaggedProto(prior), TaggedProto(proto), associated), NewTypeObjectTable::Lookup(clasp, TaggedProto(proto), associated), *p); @@ -4465,13 +4561,20 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSObject *as MOZ_ASSERT_IF(associated, associated->is() || associated->is()); MOZ_ASSERT_IF(proto.isObject(), isInsideCurrentCompartment(proto.toObject())); + // A null lookup clasp is used for 'new' type objects with an associated + // function. The type starts out as a plain object but might mutate into an + // unboxed plain object. + MOZ_ASSERT(!clasp == (associated && associated->is())); + NewTypeObjectTable &newTypeObjects = compartment()->newTypeObjects; if (!newTypeObjects.initialized() && !newTypeObjects.init()) return nullptr; - // Canonicalize new functions to use the original one associated with its script. if (associated && associated->is()) { + MOZ_ASSERT(!clasp); + + // Canonicalize new functions to use the original one associated with its script. JSFunction *fun = &associated->as(); if (fun->hasScript()) associated = fun->nonLazyScript()->functionNonDelazifying(); @@ -4479,13 +4582,23 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSObject *as associated = fun->lazyScript()->functionNonDelazifying(); else associated = nullptr; + + // If we have previously cleared the 'new' script information for this + // function, don't try to construct another one. + if (associated && associated->wasNewScriptCleared()) + associated = nullptr; + + if (!associated) + clasp = &PlainObject::class_; } NewTypeObjectTable::AddPtr p = newTypeObjects.lookupForAdd(NewTypeObjectTable::Lookup(clasp, proto, associated)); if (p) { TypeObject *type = p->object; - MOZ_ASSERT(type->clasp() == clasp); + MOZ_ASSERT_IF(clasp, type->clasp() == clasp); + MOZ_ASSERT_IF(!clasp, type->clasp() == &PlainObject::class_ || + type->clasp() == &UnboxedPlainObject::class_); MOZ_ASSERT(type->proto() == proto); return type; } @@ -4496,11 +4609,13 @@ ExclusiveContext::getNewType(const Class *clasp, TaggedProto proto, JSObject *as return nullptr; TypeObjectFlags initialFlags = 0; - if (!proto.isObject() || proto.toObject()->lastProperty()->hasObjectFlag(BaseShape::NEW_TYPE_UNKNOWN)) + if (!proto.isObject() || proto.toObject()->isNewTypeUnknown()) initialFlags = OBJECT_FLAG_DYNAMIC_MASK; Rooted protoRoot(this, proto); - TypeObject *type = compartment()->types.newTypeObject(this, clasp, protoRoot, initialFlags); + TypeObject *type = compartment()->types.newTypeObject(this, + clasp ? clasp : &PlainObject::class_, + protoRoot, initialFlags); if (!type) return nullptr; @@ -4727,6 +4842,9 @@ TypeObject::maybeSweep(AutoClearTypeInferenceStateOnOOM *oom) Maybe fallbackOOM; EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); + if (maybeUnboxedLayout() && unboxedLayout().newScript()) + unboxedLayout().newScript()->sweep(); + if (newScript()) newScript()->sweep(); @@ -4942,53 +5060,16 @@ JSCompartment::fixupNewTypeObjectTable(NewTypeObjectTable &table) needRekey = true; } if (needRekey) { - NewTypeObjectTable::Lookup lookup(entry.object->clasp(), - proto, - entry.associated); + const Class *clasp = entry.object->clasp(); + if (entry.associated && entry.associated->is()) + clasp = nullptr; + NewTypeObjectTable::Lookup lookup(clasp, proto, entry.associated); e.rekeyFront(lookup, entry); } } } } -void -TypeNewScript::fixupAfterMovingGC() -{ - if (fun && IsForwarded(fun.get())) - fun = Forwarded(fun.get()); - /* preliminaryObjects are handled by sweep(). */ - if (templateObject_ && IsForwarded(templateObject_.get())) - templateObject_ = Forwarded(templateObject_.get()); - if (initializedShape_ && IsForwarded(initializedShape_.get())) - initializedShape_ = Forwarded(initializedShape_.get()); -} - -void -TypeObject::fixupAfterMovingGC() -{ - if (proto().isObject() && IsForwarded(proto_.get())) - proto_ = Forwarded(proto_.get()); - if (singleton_ && !lazy() && IsForwarded(singleton_.get())) - singleton_ = Forwarded(singleton_.get()); - if (addendum_) { - switch (addendumKind()) { - case Addendum_NewScript: - newScript()->fixupAfterMovingGC(); - break; - case Addendum_TypeDescr: - if (IsForwarded(&typeDescr())) - addendum_ = Forwarded(&typeDescr()); - break; - case Addendum_InterpretedFunction: - if (IsForwarded(maybeInterpretedFunction())) - addendum_ = Forwarded(maybeInterpretedFunction()); - break; - default: - MOZ_CRASH(); - } - } -} - #ifdef JSGC_HASH_TABLE_CHECKS void @@ -5016,8 +5097,11 @@ JSCompartment::checkTypeObjectTableAfterMovingGC(NewTypeObjectTable &table) CheckGCThingAfterMovingGC(proto.toObject()); CheckGCThingAfterMovingGC(entry.associated); - NewTypeObjectTable::Lookup - lookup(entry.object->clasp(), proto, entry.associated); + const Class *clasp = entry.object->clasp(); + if (entry.associated && entry.associated->is()) + clasp = nullptr; + + NewTypeObjectTable::Lookup lookup(clasp, proto, entry.associated); NewTypeObjectTable::Ptr ptr = table.lookup(lookup); MOZ_ASSERT(ptr.found() && &*ptr == &e.front()); } @@ -5126,8 +5210,12 @@ TypeCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, size_t TypeObject::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { - TypeNewScript *newScript = newScriptDontCheckGeneration(); - return newScript ? newScript->sizeOfIncludingThis(mallocSizeOf) : 0; + size_t n = 0; + if (TypeNewScript *newScript = newScriptDontCheckGeneration()) + n += newScript->sizeOfIncludingThis(mallocSizeOf); + if (UnboxedLayout *layout = maybeUnboxedLayoutDontCheckGeneration()) + n += layout->sizeOfIncludingThis(mallocSizeOf); + return n; } TypeZone::TypeZone(Zone *zone) @@ -5291,19 +5379,21 @@ TypeScript::printTypes(JSContext *cx, HandleScript script) const #endif /* DEBUG */ void -TypeObject::setAddendum(AddendumKind kind, void *addendum) +TypeObject::setAddendum(AddendumKind kind, void *addendum, bool writeBarrier /* = true */) { MOZ_ASSERT(!needsSweep()); MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT)); - MOZ_ASSERT(addendumKind() == 0 || addendumKind() == kind); - // Manually trigger barriers if we are clearing a TypeNewScript. Other - // kinds of addendums are immutable. - if (newScript()) { - MOZ_ASSERT(kind == Addendum_NewScript); - TypeNewScript::writeBarrierPre(newScript()); + if (writeBarrier) { + // Manually trigger barriers if we are clearing a TypeNewScript. Other + // kinds of addendums are immutable. + if (newScript()) + TypeNewScript::writeBarrierPre(newScript()); + else + MOZ_ASSERT(addendumKind() == Addendum_None || addendumKind() == kind); } + flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK; flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT; addendum_ = addendum; } diff --git a/js/src/jsinfer.h b/js/src/jsinfer.h index a8a639b0523a..4b1ef64b70a7 100644 --- a/js/src/jsinfer.h +++ b/js/src/jsinfer.h @@ -27,6 +27,7 @@ namespace js { class TypeDescr; +class UnboxedLayout; class TaggedProto { @@ -406,23 +407,26 @@ enum : uint32_t { /* Whether objects with this type might have copy on write elements. */ OBJECT_FLAG_COPY_ON_WRITE = 0x01000000, + /* Whether this type has had its 'new' script cleared in the past. */ + OBJECT_FLAG_NEW_SCRIPT_CLEARED = 0x02000000, + /* * 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 = 0x02000000, + OBJECT_FLAG_UNKNOWN_PROPERTIES = 0x04000000, /* Flags which indicate dynamic properties of represented objects. */ - OBJECT_FLAG_DYNAMIC_MASK = 0x03ff0000, + OBJECT_FLAG_DYNAMIC_MASK = 0x07ff0000, // Mask/shift for the kind of addendum attached to this type object. - OBJECT_FLAG_ADDENDUM_MASK = 0x0c000000, - OBJECT_FLAG_ADDENDUM_SHIFT = 26, + OBJECT_FLAG_ADDENDUM_MASK = 0x38000000, + OBJECT_FLAG_ADDENDUM_SHIFT = 27, // Mask/shift for this type object's generation. If out of sync with the // TypeZone's generation, this TypeObject hasn't been swept yet. - OBJECT_FLAG_GENERATION_MASK = 0x10000000, - OBJECT_FLAG_GENERATION_SHIFT = 28, + OBJECT_FLAG_GENERATION_MASK = 0x40000000, + OBJECT_FLAG_GENERATION_SHIFT = 30, }; typedef uint32_t TypeObjectFlags; @@ -635,7 +639,6 @@ class HeapTypeSet : public ConstraintTypeSet public: /* Mark this type set as representing a non-data property. */ inline void setNonDataProperty(ExclusiveContext *cx); - inline void setNonDataPropertyIgnoringConstraints(); // Variant for use during GC. /* Mark this type set as representing a non-writable property. */ inline void setNonWritableProperty(ExclusiveContext *cx); @@ -794,6 +797,38 @@ struct Property static jsid getKey(Property *p) { return p->id; } }; +// For types where only a small number of objects have been allocated, this +// structure keeps track of all objects with the type in existence. Once +// COUNT objects have been allocated, this structure is cleared and the objects +// are analyzed, to perform the new script properties analyses or determine if +// an unboxed representation can be used. +class PreliminaryObjectArray +{ + public: + static const uint32_t COUNT = 20; + + private: + // All objects with the type which have been allocated. The pointers in + // this array are weak. + JSObject *objects[COUNT]; + + public: + PreliminaryObjectArray() { + mozilla::PodZero(this); + } + + void registerNewObject(JSObject *res); + void unregisterNewObject(JSObject *res); + + JSObject *get(size_t i) const { + MOZ_ASSERT(i < COUNT); + return objects[i]; + } + + bool full() const; + void sweep(); +}; + // New script properties analyses overview. // // When constructing objects using 'new' on a script, we attempt to determine @@ -851,21 +886,18 @@ class TypeNewScript // Scripted function which this information was computed for. // If instances of the associated type object are created without calling // 'new' on this function, the new script information is cleared. - HeapPtrFunction fun; + HeapPtrFunction function_; - // If fewer than PRELIMINARY_OBJECT_COUNT instances of the type are - // created, this array holds pointers to each of those objects. When the - // threshold has been reached, the definite and acquired properties - // analyses are performed and this array is cleared. The pointers in this - // array are weak. - static const uint32_t PRELIMINARY_OBJECT_COUNT = 20; - PlainObject **preliminaryObjects; + // Any preliminary objects with the type. The analyses are not performed + // until this array is cleared. + PreliminaryObjectArray *preliminaryObjects; // After the new script properties analyses have been performed, a template // object to use for newly constructed objects. The shape of this object // reflects all definite properties the object will have, and the - // allocation kind to use. Note that this is actually a PlainObject, but is - // JSObject here to avoid cyclic include dependencies. + // allocation kind to use. This is null if the new objects have an unboxed + // layout, in which case the UnboxedLayout provides the initial structure + // of the object. HeapPtrPlainObject templateObject_; // Order in which definite properties become initialized. We need this in @@ -892,22 +924,14 @@ class TypeNewScript public: TypeNewScript() { mozilla::PodZero(this); } ~TypeNewScript() { - js_free(preliminaryObjects); + js_delete(preliminaryObjects); js_free(initializerList); } static inline void writeBarrierPre(TypeNewScript *newScript); bool analyzed() const { - if (preliminaryObjects) { - MOZ_ASSERT(!templateObject()); - MOZ_ASSERT(!initializerList); - MOZ_ASSERT(!initializedShape()); - MOZ_ASSERT(!initializedType()); - return false; - } - MOZ_ASSERT(templateObject()); - return true; + return preliminaryObjects == nullptr; } PlainObject *templateObject() const { @@ -922,15 +946,18 @@ class TypeNewScript return initializedType_; } + JSFunction *function() const { + return function_; + } + void trace(JSTracer *trc); void sweep(); - void fixupAfterMovingGC(); void registerNewObject(PlainObject *res); void unregisterNewObject(PlainObject *res); bool maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, bool force = false); - void rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type); + bool rollbackPartiallyInitializedObjects(JSContext *cx, TypeObject *type); static void make(JSContext *cx, TypeObject *type, JSFunction *fun); @@ -981,7 +1008,6 @@ struct TypeObject : public gc::TenuredCell } void setClasp(const Class *clasp) { - MOZ_ASSERT(singleton()); clasp_ = clasp; } @@ -1029,6 +1055,11 @@ struct TypeObject : public gc::TenuredCell // function, the addendum stores a TypeNewScript. Addendum_NewScript, + // When objects with this type have an unboxed representation, the + // addendum stores an UnboxedLayout (which might have a TypeNewScript + // as well, if the type is also constructed using 'new'). + Addendum_UnboxedLayout, + // When used by typed objects, the addendum stores a TypeDescr. Addendum_TypeDescr }; @@ -1037,7 +1068,7 @@ struct TypeObject : public gc::TenuredCell // format is indicated by the object's addendum kind. void *addendum_; - void setAddendum(AddendumKind kind, void *addendum); + void setAddendum(AddendumKind kind, void *addendum, bool writeBarrier = true); AddendumKind addendumKind() const { return (AddendumKind) @@ -1045,11 +1076,20 @@ struct TypeObject : public gc::TenuredCell } TypeNewScript *newScriptDontCheckGeneration() const { - return addendumKind() == Addendum_NewScript - ? reinterpret_cast(addendum_) - : nullptr; + if (addendumKind() == Addendum_NewScript) + return reinterpret_cast(addendum_); + return nullptr; } + UnboxedLayout *maybeUnboxedLayoutDontCheckGeneration() const { + if (addendumKind() == Addendum_UnboxedLayout) + return reinterpret_cast(addendum_); + return nullptr; + } + + TypeNewScript *anyNewScript(); + void detachNewScript(bool writeBarrier); + public: TypeObjectFlags flags() { @@ -1076,6 +1116,20 @@ struct TypeObject : public gc::TenuredCell setAddendum(Addendum_NewScript, newScript); } + UnboxedLayout *maybeUnboxedLayout() { + maybeSweep(nullptr); + return maybeUnboxedLayoutDontCheckGeneration(); + } + + UnboxedLayout &unboxedLayout() { + MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout); + return *maybeUnboxedLayout(); + } + + void setUnboxedLayout(UnboxedLayout *layout) { + setAddendum(Addendum_UnboxedLayout, layout); + } + TypeDescr *maybeTypeDescr() { // Note: there is no need to sweep when accessing the type descriptor // of an object, as it is strongly held and immutable. @@ -1115,8 +1169,8 @@ struct TypeObject : public gc::TenuredCell * values that can be read out of that property in actual JS objects. * In native objects, property types account for plain data properties * (those with a slot and no getter or setter hook) and dense elements. - * In typed objects, property types account for object and value properties - * and elements in the object. + * In typed objects and unboxed objects, property types account for object + * and value properties and elements in the object. * * For accesses on these properties, the correspondence is as follows: * @@ -1139,9 +1193,10 @@ struct TypeObject : public gc::TenuredCell * 2. Array lengths are special cased by the compiler and VM and are not * reflected in property types. * - * 3. In typed objects, the initial values of properties (null pointers and - * undefined values) are not reflected in the property types. These - * values are always possible when reading the property. + * 3. In typed objects (but not unboxed objects), the initial values of + * properties (null pointers and undefined values) are not reflected in + * the property types. These values are always possible when reading the + * property. * * We establish these by using write barriers on calls to setProperty and * defineProperty which are on native properties, and on any jitcode which @@ -1239,11 +1294,10 @@ struct TypeObject : public gc::TenuredCell flags_ |= generation << OBJECT_FLAG_GENERATION_SHIFT; } - void fixupAfterMovingGC(); - size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; inline void finalize(FreeOp *fop); + void fixupAfterMovingGC() {} static inline ThingRootKind rootKind() { return THING_ROOT_TYPE_OBJECT; } diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index f2a3b9569abe..9c0163790bfd 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -22,6 +22,7 @@ #include "vm/SharedTypedArrayObject.h" #include "vm/StringObject.h" #include "vm/TypedArrayObject.h" +#include "vm/UnboxedObject.h" #include "jscntxtinlines.h" @@ -1081,19 +1082,13 @@ HeapTypeSet::newPropertyState(ExclusiveContext *cxArg) } } -inline void -HeapTypeSet::setNonDataPropertyIgnoringConstraints() -{ - flags |= TYPE_FLAG_NON_DATA_PROPERTY; -} - inline void HeapTypeSet::setNonDataProperty(ExclusiveContext *cx) { if (flags & TYPE_FLAG_NON_DATA_PROPERTY) return; - setNonDataPropertyIgnoringConstraints(); + flags |= TYPE_FLAG_NON_DATA_PROPERTY; newPropertyState(cx); } @@ -1200,6 +1195,7 @@ inline void TypeObject::finalize(FreeOp *fop) { fop->delete_(newScriptDontCheckGeneration()); + fop->delete_(maybeUnboxedLayoutDontCheckGeneration()); } inline uint32_t @@ -1294,10 +1290,10 @@ TypeObject::getProperty(unsigned i) inline void TypeNewScript::writeBarrierPre(TypeNewScript *newScript) { - if (!newScript->fun->runtimeFromAnyThread()->needsIncrementalBarrier()) + if (!newScript->function()->runtimeFromAnyThread()->needsIncrementalBarrier()) return; - JS::Zone *zone = newScript->fun->zoneFromAnyThread(); + JS::Zone *zone = newScript->function()->zoneFromAnyThread(); if (zone->needsIncrementalBarrier()) newScript->trace(zone->barrierTracer()); } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index c76e1437900b..d75161157bb7 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -901,6 +901,9 @@ js::StandardDefineProperty(JSContext *cx, HandleObject obj, HandleId id, const P return DefinePropertyOnArray(cx, arr, id, desc, throwError, rval); } + if (obj->is() && !obj->as().convertToNative(cx)) + return false; + if (obj->getOps()->lookupGeneric) { if (obj->is()) { Rooted pd(cx); @@ -963,6 +966,9 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props) return true; } + if (obj->is() && !obj->as().convertToNative(cx)) + return false; + if (obj->getOps()->lookupGeneric) { if (obj->is()) { Rooted pd(cx); @@ -1491,10 +1497,13 @@ js::CreateThis(JSContext *cx, const Class *newclasp, HandleObject callee) return NewObjectWithClassProto(cx, newclasp, proto, parent, kind); } -static inline PlainObject * +static inline JSObject * CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *parent, NewObjectKind newKind) { + if (type->maybeUnboxedLayout() && newKind != SingletonObject) + return UnboxedPlainObject::create(cx, type, newKind); + if (types::TypeNewScript *newScript = type->newScript()) { if (newScript->analyzed()) { // The definite properties analysis has been performed for this @@ -1540,14 +1549,14 @@ CreateThisForFunctionWithType(JSContext *cx, HandleTypeObject type, JSObject *pa return NewObjectWithType(cx, type, parent, allocKind, newKind); } -PlainObject * +JSObject * js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject *proto, NewObjectKind newKind /* = GenericObject */) { - RootedPlainObject res(cx); + RootedObject res(cx); if (proto) { - RootedTypeObject type(cx, cx->getNewType(&PlainObject::class_, TaggedProto(proto), + RootedTypeObject type(cx, cx->getNewType(nullptr, TaggedProto(proto), &callee->as())); if (!type) return nullptr; @@ -1559,7 +1568,7 @@ js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject if (regenerate) { // The script was analyzed successfully and may have changed // the new type table, so refetch the type. - type = cx->getNewType(&PlainObject::class_, TaggedProto(proto), + type = cx->getNewType(nullptr, TaggedProto(proto), &callee->as()); MOZ_ASSERT(type && type->newScript()); } @@ -1581,7 +1590,7 @@ js::CreateThisForFunctionWithProto(JSContext *cx, HandleObject callee, JSObject return res; } -PlainObject * +JSObject * js::CreateThisForFunction(JSContext *cx, HandleObject callee, NewObjectKind newKind) { RootedValue protov(cx); @@ -1592,10 +1601,10 @@ js::CreateThisForFunction(JSContext *cx, HandleObject callee, NewObjectKind newK proto = &protov.toObject(); else proto = nullptr; - PlainObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); + JSObject *obj = CreateThisForFunctionWithProto(cx, callee, proto, newKind); if (obj && newKind == SingletonObject) { - RootedPlainObject nobj(cx, obj); + RootedPlainObject nobj(cx, &obj->as()); /* Reshape the singleton before passing it as the 'this' value. */ NativeObject::clear(cx, nobj); @@ -3796,6 +3805,7 @@ JSObject::dump() if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown"); if (obj->hasUncacheableProto()) fprintf(stderr, " has_uncacheable_proto"); if (obj->hadElementsAccess()) fprintf(stderr, " had_elements_access"); + if (obj->wasNewScriptCleared()) fprintf(stderr, " new_script_cleared"); if (obj->isNative()) { NativeObject *nobj = &obj->as(); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 05e1e941c2bf..2dee3116cd9a 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -426,6 +426,14 @@ class JSObject : public js::gc::Cell } static bool setNewTypeUnknown(JSContext *cx, const js::Class *clasp, JS::HandleObject obj); + // Mark an object as having its 'new' script information cleared. + bool wasNewScriptCleared() const { + return lastProperty()->hasObjectFlag(js::BaseShape::NEW_SCRIPT_CLEARED); + } + bool setNewScriptCleared(js::ExclusiveContext *cx) { + return setFlag(cx, js::BaseShape::NEW_SCRIPT_CLEARED); + } + /* Set a new prototype for an object with a singleton type. */ bool splicePrototype(JSContext *cx, const js::Class *clasp, js::Handle proto); @@ -599,6 +607,9 @@ class JSObject : public js::gc::Cell static size_t offsetOfType() { return offsetof(JSObject, type_); } js::HeapPtrTypeObject *addressOfType() { return &type_; } + // Maximum size in bytes of a JSObject. + static const size_t MAX_BYTE_SIZE = 4 * sizeof(void *) + 16 * sizeof(JS::Value); + private: JSObject() = delete; JSObject(const JSObject &other) = delete; @@ -1154,12 +1165,12 @@ GetInitialHeap(NewObjectKind newKind, const Class *clasp) // Specialized call for constructing |this| with a known function callee, // and a known prototype. -extern PlainObject * +extern JSObject * CreateThisForFunctionWithProto(JSContext *cx, js::HandleObject callee, JSObject *proto, NewObjectKind newKind = GenericObject); // Specialized call for constructing |this| with a known function callee. -extern PlainObject * +extern JSObject * CreateThisForFunction(JSContext *cx, js::HandleObject callee, NewObjectKind newKind); // Generic call for constructing |this|. diff --git a/js/src/moz.build b/js/src/moz.build index a91fd6d01626..e5951b116791 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -271,6 +271,7 @@ UNIFIED_SOURCES += [ 'vm/Symbol.cpp', 'vm/TypedArrayObject.cpp', 'vm/UbiNode.cpp', + 'vm/UnboxedObject.cpp', 'vm/Unicode.cpp', 'vm/Value.cpp', 'vm/WeakMapPtr.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index aa34f408c384..6600f07521f5 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -5529,11 +5529,13 @@ SetRuntimeOptions(JSRuntime *rt, const OptionParser &op) bool enableIon = !op.getBoolOption("no-ion"); bool enableAsmJS = !op.getBoolOption("no-asmjs"); bool enableNativeRegExp = !op.getBoolOption("no-native-regexp"); + bool enableUnboxedObjects = op.getBoolOption("unboxed-objects"); JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline) .setIon(enableIon) .setAsmJS(enableAsmJS) - .setNativeRegExp(enableNativeRegExp); + .setNativeRegExp(enableNativeRegExp) + .setUnboxedObjects(enableUnboxedObjects); if (const char *str = op.getStringOption("ion-scalar-replacement")) { if (strcmp(str, "on") == 0) @@ -5875,6 +5877,7 @@ main(int argc, char **argv, char **envp) || !op.addBoolOption('\0', "no-ion", "Disable IonMonkey") || !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation") || !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation") + || !op.addBoolOption('\0', "unboxed-objects", "Allow creating unboxed objects") || !op.addStringOption('\0', "ion-scalar-replacement", "on/off", "Scalar Replacement (default: on, off to disable)") || !op.addStringOption('\0', "ion-gvn", "[mode]", diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index f24102bc8746..dacf11f6efa1 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -367,6 +367,20 @@ NativeObject::setLastPropertyShrinkFixedSlots(Shape *shape) shape_ = shape; } +void +NativeObject::setLastPropertyMakeNonNative(Shape *shape) +{ + MOZ_ASSERT(!inDictionaryMode()); + MOZ_ASSERT(!shape->getObjectClass()->isNative()); + MOZ_ASSERT(shape->compartment() == compartment()); + MOZ_ASSERT(shape->slotSpan() == 0); + MOZ_ASSERT(shape->numFixedSlots() == 0); + MOZ_ASSERT(!hasDynamicElements()); + MOZ_ASSERT(!hasDynamicSlots()); + + shape_ = shape; +} + /* static */ bool NativeObject::setSlotSpan(ExclusiveContext *cx, HandleNativeObject obj, uint32_t span) { diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index b7f65eb05b3e..4179e22264bb 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -373,6 +373,8 @@ class NativeObject : public JSObject static_assert(MAX_FIXED_SLOTS <= Shape::FIXED_SLOTS_MAX, "verify numFixedSlots() bitfield is big enough"); + static_assert(sizeof(NativeObject) + MAX_FIXED_SLOTS * sizeof(Value) == JSObject::MAX_BYTE_SIZE, + "inconsistent maximum object size"); } public: @@ -410,6 +412,11 @@ class NativeObject : public JSObject // the new properties. void setLastPropertyShrinkFixedSlots(Shape *shape); + // As for setLastProperty(), but changes the class associated with the + // object to a non-native one. This leaves the object with a type and shape + // that are (temporarily) inconsistent. + void setLastPropertyMakeNonNative(Shape *shape); + protected: #ifdef DEBUG void checkShapeConsistency(); diff --git a/js/src/vm/Shape.cpp b/js/src/vm/Shape.cpp index 5caf797d9287..c98f86810f94 100644 --- a/js/src/vm/Shape.cpp +++ b/js/src/vm/Shape.cpp @@ -1078,7 +1078,6 @@ NativeObject::rollbackProperties(ExclusiveContext *cx, HandleNativeObject obj, u uint32_t slot = obj->lastProperty()->slot(); if (slot < slotSpan) break; - MOZ_ASSERT(obj->getSlot(slot).isUndefined()); } if (!obj->removeProperty(cx, obj->lastProperty()->propid())) return false; diff --git a/js/src/vm/Shape.h b/js/src/vm/Shape.h index 032e017020fc..e196f5db85cd 100644 --- a/js/src/vm/Shape.h +++ b/js/src/vm/Shape.h @@ -406,7 +406,11 @@ class BaseShape : public gc::TenuredCell QUALIFIED_VAROBJ = 0x2000, UNQUALIFIED_VAROBJ = 0x4000, - OBJECT_FLAG_MASK = 0x7ff8 + // For a function used as an interpreted constructor, whether a 'new' + // type had constructor information cleared. + NEW_SCRIPT_CLEARED = 0x8000, + + OBJECT_FLAG_MASK = 0xfff8 }; private: diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp new file mode 100644 index 000000000000..ee6a03751d80 --- /dev/null +++ b/js/src/vm/UnboxedObject.cpp @@ -0,0 +1,689 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "vm/UnboxedObject.h" + +#include "jsinferinlines.h" +#include "jsobjinlines.h" + +#include "vm/Shape-inl.h" + +using mozilla::ArrayLength; +using mozilla::DebugOnly; +using mozilla::PodCopy; + +using namespace js; + +///////////////////////////////////////////////////////////////////// +// UnboxedLayout +///////////////////////////////////////////////////////////////////// + +void +UnboxedLayout::trace(JSTracer *trc) +{ + for (size_t i = 0; i < properties_.length(); i++) + MarkStringUnbarriered(trc, &properties_[i].name, "unboxed_layout_name"); + + if (newScript()) + newScript()->trace(trc); +} + +size_t +UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + return mallocSizeOf(this) + + properties_.sizeOfExcludingThis(mallocSizeOf) + + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0) + + mallocSizeOf(traceList()); +} + +void +UnboxedLayout::setNewScript(types::TypeNewScript *newScript, bool writeBarrier /* = true */) +{ + if (newScript_ && writeBarrier) + types::TypeNewScript::writeBarrierPre(newScript_); + newScript_ = newScript; +} + +///////////////////////////////////////////////////////////////////// +// UnboxedPlainObject +///////////////////////////////////////////////////////////////////// + +bool +UnboxedPlainObject::setValue(JSContext *cx, const UnboxedLayout::Property &property, const Value &v) +{ + uint8_t *p = &data_[property.offset]; + + switch (property.type) { + case JSVAL_TYPE_BOOLEAN: + if (v.isBoolean()) { + *p = v.toBoolean(); + return true; + } + return false; + + case JSVAL_TYPE_INT32: + if (v.isInt32()) { + *reinterpret_cast(p) = v.toInt32(); + return true; + } + return false; + + case JSVAL_TYPE_DOUBLE: + if (v.isNumber()) { + *reinterpret_cast(p) = v.toNumber(); + return true; + } + return false; + + case JSVAL_TYPE_STRING: + if (v.isString()) { + *reinterpret_cast(p) = v.toString(); + return true; + } + return false; + + case JSVAL_TYPE_OBJECT: + if (v.isObjectOrNull()) { + // Update property types when writing object properties. Types for + // other properties were captured when the unboxed layout was + // created. + types::AddTypePropertyId(cx, this, NameToId(property.name), v); + + *reinterpret_cast(p) = v.toObjectOrNull(); + return true; + } + return false; + + default: + MOZ_CRASH("Invalid type for unboxed value"); + } +} + +Value +UnboxedPlainObject::getValue(const UnboxedLayout::Property &property) +{ + uint8_t *p = &data_[property.offset]; + + switch (property.type) { + case JSVAL_TYPE_BOOLEAN: + return BooleanValue(*p != 0); + + case JSVAL_TYPE_INT32: + return Int32Value(*reinterpret_cast(p)); + + case JSVAL_TYPE_DOUBLE: + return DoubleValue(*reinterpret_cast(p)); + + case JSVAL_TYPE_STRING: + return StringValue(*reinterpret_cast(p)); + + case JSVAL_TYPE_OBJECT: + return ObjectOrNullValue(*reinterpret_cast(p)); + + default: + MOZ_CRASH("Invalid type for unboxed value"); + } +} + +void +UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj) +{ + const UnboxedLayout &layout = obj->as().layout(); + const int32_t *list = layout.traceList(); + if (!list) + return; + + uint8_t *data = obj->as().data(); + while (*list != -1) { + HeapPtrString *heap = reinterpret_cast(data + *list); + MarkString(trc, heap, "unboxed_string"); + list++; + } + list++; + while (*list != -1) { + HeapPtrObject *heap = reinterpret_cast(data + *list); + if (*heap) + MarkObject(trc, heap, "unboxed_object"); + list++; + } + + // Unboxed objects don't have Values to trace. + MOZ_ASSERT(*(list + 1) == -1); +} + +bool +UnboxedPlainObject::convertToNative(JSContext *cx) +{ + // Immediately clear any new script on this object's type, + // as rollbackPartiallyInitializedObjects() will be confused by the type + // changes we make in this function. + type()->clearNewScript(cx); + + // clearNewScript() can reentrantly invoke this method. + if (!is()) + return true; + + Rooted obj(cx, this); + Rooted proto(cx, getTaggedProto()); + + size_t nfixed = gc::GetGCKindSlots(obj->layout().getAllocKind()); + + AutoValueVector values(cx); + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, + getMetadata(), getParent(), nfixed, + lastProperty()->getObjectFlags())); + if (!shape) + return false; + + for (size_t i = 0; i < obj->layout().properties().length(); i++) { + const UnboxedLayout::Property &property = obj->layout().properties()[i]; + + if (!values.append(obj->getValue(property))) + return false; + + StackShape unrootedChild(shape->base()->unowned(), NameToId(property.name), i, + JSPROP_ENUMERATE, 0); + RootedGeneric child(cx, &unrootedChild); + shape = cx->compartment()->propertyTree.getChild(cx, shape, *child); + if (!shape) + return false; + } + + if (!SetClassAndProto(cx, obj, &PlainObject::class_, proto)) + return false; + + // Any failures after this point will leave the object as a mutant, and we + // can't recover. + + RootedPlainObject nobj(cx, &obj->as()); + if (!nobj->setLastProperty(cx, nobj, shape)) + CrashAtUnhandlableOOM("UnboxedPlainObject::convertToNative"); + + for (size_t i = 0; i < values.length(); i++) + nobj->initSlot(i, values[i]); + + return true; +} + +/* static */ +UnboxedPlainObject * +UnboxedPlainObject::create(JSContext *cx, HandleTypeObject type, NewObjectKind newKind) +{ + MOZ_ASSERT(type->clasp() == &class_); + gc::AllocKind allocKind = type->unboxedLayout().getAllocKind(); + + UnboxedPlainObject *res = NewObjectWithType(cx, type, cx->global(), + allocKind, newKind); + if (!res) + return nullptr; + + // Initialize reference fields of the object. All fields in the object will + // be overwritten shortly, but references need to be safe for the GC. + const int32_t *list = res->layout().traceList(); + if (list) { + uint8_t *data = res->data(); + while (*list != -1) { + HeapPtrString *heap = reinterpret_cast(data + *list); + heap->init(cx->names().empty); + list++; + } + list++; + while (*list != -1) { + HeapPtrObject *heap = reinterpret_cast(data + *list); + heap->init(nullptr); + list++; + } + // Unboxed objects don't have Values to initialize. + MOZ_ASSERT(*(list + 1) == -1); + } + + return res; +} + +/* static */ bool +UnboxedPlainObject::obj_lookupGeneric(JSContext *cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp) +{ + if (obj->as().layout().lookup(id)) { + MarkNonNativePropertyFound(propp); + objp.set(obj); + return true; + } + + RootedObject proto(cx, obj->getProto()); + if (!proto) { + objp.set(nullptr); + propp.set(nullptr); + return true; + } + + return LookupProperty(cx, proto, id, objp, propp); +} + +/* static */ bool +UnboxedPlainObject::obj_lookupProperty(JSContext *cx, HandleObject obj, + HandlePropertyName name, + MutableHandleObject objp, + MutableHandleShape propp) +{ + RootedId id(cx, NameToId(name)); + return obj_lookupGeneric(cx, obj, id, objp, propp); +} + +/* static */ bool +UnboxedPlainObject::obj_lookupElement(JSContext *cx, HandleObject obj, + uint32_t index, MutableHandleObject objp, + MutableHandleShape propp) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + return obj_lookupGeneric(cx, obj, id, objp, propp); +} + +/* static */ bool +UnboxedPlainObject::obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + if (!obj->as().convertToNative(cx)) + return false; + + return DefineProperty(cx, obj, id, v, getter, setter, attrs); +} + +/* static */ bool +UnboxedPlainObject::obj_defineProperty(JSContext *cx, HandleObject obj, + HandlePropertyName name, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + Rooted id(cx, NameToId(name)); + return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); +} + +/* static */ bool +UnboxedPlainObject::obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs) +{ + AutoRooterGetterSetter gsRoot(cx, attrs, &getter, &setter); + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + return obj_defineGeneric(cx, obj, id, v, getter, setter, attrs); +} + +/* static */ bool +UnboxedPlainObject::obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, + HandleId id, MutableHandleValue vp) +{ + const UnboxedLayout &layout = obj->as().layout(); + + if (const UnboxedLayout::Property *property = layout.lookup(id)) { + vp.set(obj->as().getValue(*property)); + return true; + } + + RootedObject proto(cx, obj->getProto()); + if (!proto) { + vp.setUndefined(); + return true; + } + + return GetProperty(cx, proto, receiver, id, vp); +} + +/* static */ bool +UnboxedPlainObject::obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver, + HandlePropertyName name, MutableHandleValue vp) +{ + RootedId id(cx, NameToId(name)); + return obj_getGeneric(cx, obj, receiver, id, vp); +} + +/* static */ bool +UnboxedPlainObject::obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver, + uint32_t index, MutableHandleValue vp) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + return obj_getGeneric(cx, obj, receiver, id, vp); +} + +/* static */ bool +UnboxedPlainObject::obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id, + MutableHandleValue vp, bool strict) +{ + const UnboxedLayout &layout = obj->as().layout(); + + if (const UnboxedLayout::Property *property = layout.lookup(id)) { + if (obj->as().setValue(cx, *property, vp)) + return true; + + if (!obj->as().convertToNative(cx)) + return false; + return SetProperty(cx, obj, obj, id, vp, strict); + } + + RootedObject proto(cx, obj->getProto()); + if (!proto) { + if (!obj->as().convertToNative(cx)) + return false; + return SetProperty(cx, obj, obj, id, vp, strict); + } + + return SetProperty(cx, proto, obj, id, vp, strict); +} + +/* static */ bool +UnboxedPlainObject::obj_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, + MutableHandleValue vp, bool strict) +{ + RootedId id(cx, NameToId(name)); + return obj_setGeneric(cx, obj, id, vp, strict); +} + +/* static */ bool +UnboxedPlainObject::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index, + MutableHandleValue vp, bool strict) +{ + RootedId id(cx); + if (!IndexToId(cx, index, &id)) + return false; + return obj_setGeneric(cx, obj, id, vp, strict); +} + +/* static */ bool +UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, + MutableHandle desc) +{ + const UnboxedLayout &layout = obj->as().layout(); + + if (const UnboxedLayout::Property *property = layout.lookup(id)) { + desc.value().set(obj->as().getValue(*property)); + desc.setAttributes(JSPROP_ENUMERATE); + desc.object().set(obj); + return true; + } + + desc.object().set(nullptr); + return true; +} + +/* static */ bool +UnboxedPlainObject::obj_setGenericAttributes(JSContext *cx, HandleObject obj, + HandleId id, unsigned *attrsp) +{ + if (!obj->as().convertToNative(cx)) + return false; + return SetPropertyAttributes(cx, obj, id, attrsp); +} + +/* static */ bool +UnboxedPlainObject::obj_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded) +{ + if (!obj->as().convertToNative(cx)) + return false; + return DeleteProperty(cx, obj, id, succeeded); +} + +/* static */ bool +UnboxedPlainObject::obj_watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable) +{ + if (!obj->as().convertToNative(cx)) + return false; + return WatchProperty(cx, obj, id, callable); +} + +/* static */ bool +UnboxedPlainObject::obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties) +{ + const UnboxedLayout::PropertyVector &unboxed = obj->as().layout().properties(); + for (size_t i = 0; i < unboxed.length(); i++) { + if (!properties.append(NameToId(unboxed[i].name))) + return false; + } + return true; +} + +const Class UnboxedPlainObject::class_ = { + "Object", + Class::NON_NATIVE | JSCLASS_IMPLEMENTS_BARRIERS, + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* convert */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + UnboxedPlainObject::trace, + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + { + UnboxedPlainObject::obj_lookupGeneric, + UnboxedPlainObject::obj_lookupProperty, + UnboxedPlainObject::obj_lookupElement, + UnboxedPlainObject::obj_defineGeneric, + UnboxedPlainObject::obj_defineProperty, + UnboxedPlainObject::obj_defineElement, + UnboxedPlainObject::obj_getGeneric, + UnboxedPlainObject::obj_getProperty, + UnboxedPlainObject::obj_getElement, + UnboxedPlainObject::obj_setGeneric, + UnboxedPlainObject::obj_setProperty, + UnboxedPlainObject::obj_setElement, + UnboxedPlainObject::obj_getOwnPropertyDescriptor, + UnboxedPlainObject::obj_setGenericAttributes, + UnboxedPlainObject::obj_deleteGeneric, + UnboxedPlainObject::obj_watch, + nullptr, /* No unwatch needed, as watch() converts the object to native */ + nullptr, /* getElements */ + UnboxedPlainObject::obj_enumerate, + nullptr, /* thisObject */ + } +}; + +///////////////////////////////////////////////////////////////////// +// API +///////////////////////////////////////////////////////////////////// + +static bool +UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype) +{ + if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32) + return true; + if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL) + return true; + return false; +} + +bool +js::TryConvertToUnboxedLayout(JSContext *cx, Shape *templateShape, + types::TypeObject *type, types::PreliminaryObjectArray *objects) +{ + if (!cx->runtime()->options().unboxedObjects()) + return true; + + if (templateShape->slotSpan() == 0) + return true; + + UnboxedLayout::PropertyVector properties; + if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan())) + return false; + + size_t objectCount = 0; + for (size_t i = 0; i < types::PreliminaryObjectArray::COUNT; i++) { + JSObject *obj = objects->get(i); + if (!obj) + continue; + + objectCount++; + + // All preliminary objects must have been created with the largest + // allocation kind possible, which will allow their unboxed data to be + // filled in inline. + MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) == + NativeObject::MAX_FIXED_SLOTS); + + if (obj->as().lastProperty() != templateShape || + obj->as().hasDynamicElements()) + { + // Only use an unboxed representation if all created objects match + // the template shape exactly. + return true; + } + + for (size_t i = 0; i < templateShape->slotSpan(); i++) { + Value val = obj->as().getSlot(i); + + JSValueType &existing = properties[i].type; + JSValueType type = val.isDouble() ? JSVAL_TYPE_DOUBLE : val.extractNonDoubleType(); + + if (existing == JSVAL_TYPE_MAGIC || existing == type || UnboxedTypeIncludes(type, existing)) + existing = type; + else if (!UnboxedTypeIncludes(existing, type)) + return true; + } + } + + if (objectCount <= 1) { + // If only one of the objects has been created, it is more likely to + // have new properties added later. + return true; + } + + for (size_t i = 0; i < templateShape->slotSpan(); i++) { + // We can't use an unboxed representation if e.g. all the objects have + // a null value for one of the properties, as we can't decide what type + // it is supposed to have. + if (UnboxedTypeSize(properties[i].type) == 0) + return true; + } + + // Fill in the names for all the object's properties. + for (Shape::Range r(templateShape); !r.empty(); r.popFront()) { + size_t slot = r.front().slot(); + MOZ_ASSERT(!properties[slot].name); + properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName(); + } + + // Fill in all the unboxed object's property offsets, ordering fields from the + // largest down to avoid alignment issues. + uint32_t offset = 0; + + static const size_t typeSizes[] = { 8, 4, 1 }; + + Vector objectOffsets, stringOffsets; + + DebugOnly addedProperties = 0; + for (size_t i = 0; i < ArrayLength(typeSizes); i++) { + size_t size = typeSizes[i]; + for (size_t j = 0; j < templateShape->slotSpan(); j++) { + JSValueType type = properties[j].type; + if (UnboxedTypeSize(type) == size) { + if (type == JSVAL_TYPE_OBJECT) { + if (!objectOffsets.append(offset)) + return false; + } else if (type == JSVAL_TYPE_STRING) { + if (!stringOffsets.append(offset)) + return false; + } + addedProperties++; + properties[j].offset = offset; + offset += size; + } + } + } + MOZ_ASSERT(addedProperties == templateShape->slotSpan()); + + // The entire object must be allocatable inline. + if (sizeof(JSObject) + offset > JSObject::MAX_BYTE_SIZE) + return true; + + UnboxedLayout *layout = type->zone()->new_(properties, offset); + if (!layout) + return false; + + // Construct the layout's trace list. + if (!objectOffsets.empty() || !stringOffsets.empty()) { + Vector entries; + if (!entries.appendAll(stringOffsets) || + !entries.append(-1) || + !entries.appendAll(objectOffsets) || + !entries.append(-1) || + !entries.append(-1)) + { + return false; + } + int32_t *traceList = type->zone()->pod_malloc(entries.length()); + if (!traceList) + return false; + PodCopy(traceList, entries.begin(), entries.length()); + layout->setTraceList(traceList); + } + + // We've determined that all the preliminary objects can use the new layout + // just constructed, so convert the existing type to be an + // UnboxedPlainObject rather than a PlainObject, and update the preliminary + // objects to use the new layout. Do the fallible stuff first before + // modifying any objects. + + // Get an empty shape which we can use for the preliminary objects. + Shape *newShape = EmptyShape::getInitialShape(cx, &UnboxedPlainObject::class_, + type->proto(), + templateShape->getObjectMetadata(), + templateShape->getObjectParent(), + templateShape->getObjectFlags()); + if (!newShape) { + cx->clearPendingException(); + return false; + } + + // Accumulate a list of all the properties in each preliminary object, and + // update their shapes. + Vector values; + if (!values.reserve(objectCount * templateShape->slotSpan())) + return false; + for (size_t i = 0; i < types::PreliminaryObjectArray::COUNT; i++) { + if (!objects->get(i)) + continue; + + RootedNativeObject obj(cx, &objects->get(i)->as()); + for (size_t j = 0; j < templateShape->slotSpan(); j++) + values.infallibleAppend(obj->getSlot(j)); + + // Clear the object to remove any dynamically allocated information. + NativeObject::clear(cx, obj); + + obj->setLastPropertyMakeNonNative(newShape); + } + + if (types::TypeNewScript *newScript = type->newScript()) + layout->setNewScript(newScript); + + type->setClasp(&UnboxedPlainObject::class_); + type->setUnboxedLayout(layout); + + size_t valueCursor = 0; + for (size_t i = 0; i < types::PreliminaryObjectArray::COUNT; i++) { + if (!objects->get(i)) + continue; + UnboxedPlainObject *obj = &objects->get(i)->as(); + memset(obj->data(), 0, layout->size()); + for (size_t j = 0; j < templateShape->slotSpan(); j++) { + Value v = values[valueCursor++]; + JS_ALWAYS_TRUE(obj->setValue(cx, properties[j], v)); + } + } + + MOZ_ASSERT(valueCursor == values.length()); + return true; +} diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h new file mode 100644 index 000000000000..ac0778ebe7d5 --- /dev/null +++ b/js/src/vm/UnboxedObject.h @@ -0,0 +1,215 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef vm_UnboxedObject_h +#define vm_UnboxedObject_h + +#include "jsgc.h" +#include "jsinfer.h" +#include "jsobj.h" + +namespace js { + +// Memory required for an unboxed value of a given type. Returns zero for types +// which can't be used for unboxed objects. +static inline size_t +UnboxedTypeSize(JSValueType type) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: return 1; + case JSVAL_TYPE_INT32: return 4; + case JSVAL_TYPE_DOUBLE: return 8; + case JSVAL_TYPE_STRING: return sizeof(void *); + case JSVAL_TYPE_OBJECT: return sizeof(void *); + default: return 0; + } +} + +// Class describing the layout of an UnboxedPlainObject. +class UnboxedLayout +{ + public: + struct Property { + PropertyName *name; + uint32_t offset; + JSValueType type; + + Property() + : name(nullptr), offset(0), type(JSVAL_TYPE_MAGIC) + {} + }; + + typedef Vector PropertyVector; + + private: + // All properties on objects with this layout, in enumeration order. + PropertyVector properties_; + + // Byte size of the data for objects with this layout. + size_t size_; + + // Any 'new' script information associated with this layout. + types::TypeNewScript *newScript_; + + // List for use in tracing objects with this layout. This has the same + // structure as the trace list on a TypeDescr. + int32_t *traceList_; + + public: + UnboxedLayout(const PropertyVector &properties, size_t size) + : size_(size), newScript_(nullptr), traceList_(nullptr) + { + properties_.appendAll(properties); + } + + ~UnboxedLayout() { + js_delete(newScript_); + js_free(traceList_); + } + + const PropertyVector &properties() const { + return properties_; + } + + types::TypeNewScript *newScript() const { + return newScript_; + } + + void setNewScript(types::TypeNewScript *newScript, bool writeBarrier = true); + + const int32_t *traceList() const { + return traceList_; + } + + void setTraceList(int32_t *traceList) { + traceList_ = traceList; + } + + const Property *lookup(JSAtom *atom) const { + for (size_t i = 0; i < properties_.length(); i++) { + if (properties_[i].name == atom) + return &properties_[i]; + } + return nullptr; + } + + const Property *lookup(jsid id) const { + if (JSID_IS_STRING(id)) + return lookup(JSID_TO_ATOM(id)); + return nullptr; + } + + size_t size() const { + return size_; + } + + inline gc::AllocKind getAllocKind() const; + + void trace(JSTracer *trc); + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); +}; + +// Class for a plain object using an unboxed representation. The physical +// layout of these objects is identical to that of an InlineTypedObject, though +// these objects use an UnboxedLayout instead of a TypeDescr to keep track of +// how their properties are stored. +class UnboxedPlainObject : public JSObject +{ + // Start of the inline data, which immediately follows the shape and type. + uint8_t data_[1]; + + public: + static const Class class_; + + static bool obj_lookupGeneric(JSContext *cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp); + + static bool obj_lookupProperty(JSContext *cx, HandleObject obj, + HandlePropertyName name, + MutableHandleObject objp, + MutableHandleShape propp); + + static bool obj_lookupElement(JSContext *cx, HandleObject obj, + uint32_t index, MutableHandleObject objp, + MutableHandleShape propp); + + static bool obj_defineGeneric(JSContext *cx, HandleObject obj, HandleId id, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs); + + static bool obj_defineProperty(JSContext *cx, HandleObject obj, + HandlePropertyName name, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs); + + static bool obj_defineElement(JSContext *cx, HandleObject obj, uint32_t index, HandleValue v, + PropertyOp getter, StrictPropertyOp setter, unsigned attrs); + + static bool obj_getGeneric(JSContext *cx, HandleObject obj, HandleObject receiver, + HandleId id, MutableHandleValue vp); + + static bool obj_getProperty(JSContext *cx, HandleObject obj, HandleObject receiver, + HandlePropertyName name, MutableHandleValue vp); + + static bool obj_getElement(JSContext *cx, HandleObject obj, HandleObject receiver, + uint32_t index, MutableHandleValue vp); + + static bool obj_setGeneric(JSContext *cx, HandleObject obj, HandleId id, + MutableHandleValue vp, bool strict); + static bool obj_setProperty(JSContext *cx, HandleObject obj, HandlePropertyName name, + MutableHandleValue vp, bool strict); + static bool obj_setElement(JSContext *cx, HandleObject obj, uint32_t index, + MutableHandleValue vp, bool strict); + + static bool obj_getOwnPropertyDescriptor(JSContext *cx, HandleObject obj, HandleId id, + MutableHandle desc); + + static bool obj_setGenericAttributes(JSContext *cx, HandleObject obj, + HandleId id, unsigned *attrsp); + + static bool obj_deleteGeneric(JSContext *cx, HandleObject obj, HandleId id, bool *succeeded); + + static bool obj_enumerate(JSContext *cx, HandleObject obj, AutoIdVector &properties); + static bool obj_watch(JSContext *cx, HandleObject obj, HandleId id, HandleObject callable); + + const UnboxedLayout &layout() const { + return type()->unboxedLayout(); + } + + uint8_t *data() { + return &data_[0]; + } + + bool setValue(JSContext *cx, const UnboxedLayout::Property &property, const Value &v); + Value getValue(const UnboxedLayout::Property &property); + + bool convertToNative(JSContext *cx); + + static UnboxedPlainObject *create(JSContext *cx, HandleTypeObject type, NewObjectKind newKind); + + static void trace(JSTracer *trc, JSObject *object); + + static size_t offsetOfData() { + return offsetof(UnboxedPlainObject, data_[0]); + } +}; + +// Try to construct an UnboxedLayout for each of the preliminary objects, +// provided they all match the template shape. If successful, converts the +// preliminary objects and their type to the new unboxed representation. +bool +TryConvertToUnboxedLayout(JSContext *cx, Shape *templateShape, + types::TypeObject *type, types::PreliminaryObjectArray *objects); + +inline gc::AllocKind +UnboxedLayout::getAllocKind() const +{ + return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size()); +} + +} // namespace js + +#endif /* vm_UnboxedObject_h */