Bug 1116855 - Add default-disabled unboxed objects for use by interpreted constructors, r=jandem.

This commit is contained in:
Brian Hackett 2015-01-26 12:16:26 -07:00
Родитель 530fecb909
Коммит a8ab778b40
22 изменённых файлов: 1499 добавлений и 260 удалений

Просмотреть файл

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

Просмотреть файл

@ -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<TypeDescr>());
@ -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<InlineOpaqueTypedObject>().typeDescr();
if (!descr->hasTraceList())
return;
const int32_t *list = descr->traceList();
uint8_t *memory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
while (*list != -1) {
JSString *str = *reinterpret_cast<JSString **>(memory + *list);
while (*unboxedTraceList != -1) {
JSString *str = *reinterpret_cast<JSString **>(unboxedMemory + *unboxedTraceList);
markAndScanString(obj, str);
list++;
unboxedTraceList++;
}
list++;
while (*list != -1) {
JSObject *obj2 = *reinterpret_cast<JSObject **>(memory + *list);
unboxedTraceList++;
while (*unboxedTraceList != -1) {
JSObject *obj2 = *reinterpret_cast<JSObject **>(unboxedMemory + *unboxedTraceList);
if (obj2 && markObject(obj, obj2))
pushObject(obj2);
list++;
unboxedTraceList++;
}
list++;
while (*list != -1) {
const Value &v = *reinterpret_cast<Value *>(memory + *list);
unboxedTraceList++;
while (*unboxedTraceList != -1) {
const Value &v = *reinterpret_cast<Value *>(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<InlineOpaqueTypedObject>().typeDescr();
if (!descr->hasTraceList())
return;
unboxedTraceList = descr->traceList();
unboxedMemory = obj->as<InlineOpaqueTypedObject>().inlineTypedMem();
goto scan_unboxed;
}
if (clasp == &UnboxedPlainObject::class_) {
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
unboxedTraceList = layout.traceList();
if (!unboxedTraceList)
return;
unboxedMemory = obj->as<UnboxedPlainObject>().data();
goto scan_unboxed;
}
clasp->trace(this, obj);
}

Просмотреть файл

@ -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<UnboxedPlainObject>()) {
size_t nbytes = obj->as<UnboxedPlainObject>().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<InlineTypedObject>()) {
@ -435,7 +441,7 @@ GetObjectAllocKindForCopy(const Nursery &nursery, JSObject *obj)
if (obj->is<OutlineTypedObject>())
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<NativeObject>().numFixedSlots());

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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<PlainObject>()) {
templateObject = &thisObject->as<PlainObject>();
// 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;
}
}
}

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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<PlainObject *>(PRELIMINARY_OBJECT_COUNT);
if (!preliminaryObjects)
newScript->preliminaryObjects = type->zone()->new_<PreliminaryObjectArray>();
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<PlainObject>();
// 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<PlainObject>();
if (!ChangeObjectFixedSlotCount(cx, obj, kind))
return false;
if (newPrefixShape) {
@ -4032,7 +4103,7 @@ TypeNewScript::maybeAnalyze(JSContext *cx, TypeObject *type, bool *regenerate, b
Vector<Initializer> 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<uint32_t, 32> 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<UnboxedPlainObject>() &&
!thisv.toObject().as<UnboxedPlainObject>().convertToNative(cx))
{
CrashAtUnhandlableOOM("rollbackPartiallyInitializedObjects");
}
// Found a matching frame.
RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
@ -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<JSFunction>() || associated->is<TypeDescr>());
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<JSFunction>()));
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<JSFunction>()) {
MOZ_ASSERT(!clasp);
// Canonicalize new functions to use the original one associated with its script.
JSFunction *fun = &associated->as<JSFunction>();
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<TaggedProto> 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<AutoClearTypeInferenceStateOnOOM> 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<JSFunction>())
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<JSFunction>())
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;
}

Просмотреть файл

@ -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<TypeNewScript *>(addendum_)
: nullptr;
if (addendumKind() == Addendum_NewScript)
return reinterpret_cast<TypeNewScript *>(addendum_);
return nullptr;
}
UnboxedLayout *maybeUnboxedLayoutDontCheckGeneration() const {
if (addendumKind() == Addendum_UnboxedLayout)
return reinterpret_cast<UnboxedLayout *>(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; }

Просмотреть файл

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

Просмотреть файл

@ -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<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
if (obj->getOps()->lookupGeneric) {
if (obj->is<ProxyObject>()) {
Rooted<PropertyDescriptor> pd(cx);
@ -963,6 +966,9 @@ js::DefineProperties(JSContext *cx, HandleObject obj, HandleObject props)
return true;
}
if (obj->is<UnboxedPlainObject>() && !obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
if (obj->getOps()->lookupGeneric) {
if (obj->is<ProxyObject>()) {
Rooted<PropertyDescriptor> 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<PlainObject>(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<JSFunction>()));
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<JSFunction>());
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<PlainObject>());
/* 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<NativeObject>();

Просмотреть файл

@ -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<js::TaggedProto> 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|.

Просмотреть файл

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

Просмотреть файл

@ -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]",

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

689
js/src/vm/UnboxedObject.cpp Normal file
Просмотреть файл

@ -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<int32_t*>(p) = v.toInt32();
return true;
}
return false;
case JSVAL_TYPE_DOUBLE:
if (v.isNumber()) {
*reinterpret_cast<double*>(p) = v.toNumber();
return true;
}
return false;
case JSVAL_TYPE_STRING:
if (v.isString()) {
*reinterpret_cast<HeapPtrString*>(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<HeapPtrObject*>(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<int32_t*>(p));
case JSVAL_TYPE_DOUBLE:
return DoubleValue(*reinterpret_cast<double*>(p));
case JSVAL_TYPE_STRING:
return StringValue(*reinterpret_cast<JSString**>(p));
case JSVAL_TYPE_OBJECT:
return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
default:
MOZ_CRASH("Invalid type for unboxed value");
}
}
void
UnboxedPlainObject::trace(JSTracer *trc, JSObject *obj)
{
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
const int32_t *list = layout.traceList();
if (!list)
return;
uint8_t *data = obj->as<UnboxedPlainObject>().data();
while (*list != -1) {
HeapPtrString *heap = reinterpret_cast<HeapPtrString *>(data + *list);
MarkString(trc, heap, "unboxed_string");
list++;
}
list++;
while (*list != -1) {
HeapPtrObject *heap = reinterpret_cast<HeapPtrObject *>(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<UnboxedPlainObject>())
return true;
Rooted<UnboxedPlainObject *> obj(cx, this);
Rooted<TaggedProto> 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<StackShape*> 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<PlainObject>());
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<UnboxedPlainObject>(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<HeapPtrString *>(data + *list);
heap->init(cx->names().empty);
list++;
}
list++;
while (*list != -1) {
HeapPtrObject *heap = reinterpret_cast<HeapPtrObject *>(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<UnboxedPlainObject>().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<UnboxedPlainObject>().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<jsid> 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<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property *property = layout.lookup(id)) {
vp.set(obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property *property = layout.lookup(id)) {
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, vp))
return true;
if (!obj->as<UnboxedPlainObject>().convertToNative(cx))
return false;
return SetProperty(cx, obj, obj, id, vp, strict);
}
RootedObject proto(cx, obj->getProto());
if (!proto) {
if (!obj->as<UnboxedPlainObject>().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<JSPropertyDescriptor> desc)
{
const UnboxedLayout &layout = obj->as<UnboxedPlainObject>().layout();
if (const UnboxedLayout::Property *property = layout.lookup(id)) {
desc.value().set(obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().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<UnboxedPlainObject>().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<UnboxedPlainObject>().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<UnboxedPlainObject>().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<PlainObject>().lastProperty() != templateShape ||
obj->as<PlainObject>().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<PlainObject>().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<NoGC> 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<int32_t, 8, SystemAllocPolicy> objectOffsets, stringOffsets;
DebugOnly<size_t> 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_<UnboxedLayout>(properties, offset);
if (!layout)
return false;
// Construct the layout's trace list.
if (!objectOffsets.empty() || !stringOffsets.empty()) {
Vector<int32_t, 8, SystemAllocPolicy> 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<int32_t>(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<Value, 0, SystemAllocPolicy> 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<NativeObject>());
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<UnboxedPlainObject>();
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;
}

215
js/src/vm/UnboxedObject.h Normal file
Просмотреть файл

@ -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<Property, 0, SystemAllocPolicy> 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<JSPropertyDescriptor> 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 */