зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1116855
- Add default-disabled unboxed objects for use by interpreted constructors, r=jandem.
This commit is contained in:
Родитель
530fecb909
Коммит
a8ab778b40
|
@ -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;
|
||||
}
|
||||
|
|
136
js/src/jsinfer.h
136
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<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:
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
Загрузка…
Ссылка в новой задаче