Revert Remove unboxed objects.
parent
51dcdf812a
commit
c9e0d090cd
|
@ -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();
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
function f() {
|
||||
var propNames = ["a","b","c","d","e","f","g","h","i","j","x","y"];
|
||||
var arr = [];
|
||||
for (var i=0; i<64; i++)
|
||||
arr.push({x:1, y:2});
|
||||
for (var i=0; i<64; i++) {
|
||||
// Make sure there are expandos with dynamic slots for each object.
|
||||
for (var j = 0; j < propNames.length; j++)
|
||||
arr[i][propNames[j]] = j;
|
||||
}
|
||||
var res = 0;
|
||||
for (var i=0; i<100000; i++) {
|
||||
var o = arr[i % 64];
|
||||
var p = propNames[i % propNames.length];
|
||||
res += o[p];
|
||||
}
|
||||
assertEq(res, 549984);
|
||||
}
|
||||
f();
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
// Use the correct receiver when non-native objects are prototypes of other objects.
|
||||
|
||||
function Thing(a, b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
function foo() {
|
||||
var array = [];
|
||||
for (var i = 0; i < 10000; i++)
|
||||
array.push(new Thing(i, i + 1));
|
||||
|
||||
var proto = new Thing(1, 2);
|
||||
var obj = Object.create(proto);
|
||||
|
||||
Object.defineProperty(Thing.prototype, "c", {set:function() { this.d = 0; }});
|
||||
obj.c = 3;
|
||||
assertEq(obj.c, undefined);
|
||||
assertEq(obj.d, 0);
|
||||
assertEq(obj.hasOwnProperty("d"), true);
|
||||
assertEq(proto.d, undefined);
|
||||
assertEq(proto.hasOwnProperty("d"), false);
|
||||
|
||||
obj.a = 3;
|
||||
assertEq(obj.a, 3);
|
||||
assertEq(proto.a, 1);
|
||||
assertEq(obj.hasOwnProperty("a"), true);
|
||||
}
|
||||
|
||||
foo();
|
|
@ -0,0 +1,24 @@
|
|||
function O() {
|
||||
this.x = 1;
|
||||
this.y = 2;
|
||||
}
|
||||
function testUnboxed() {
|
||||
var arr = [];
|
||||
for (var i=0; i<100; i++)
|
||||
arr.push(new O);
|
||||
|
||||
var o = arr[arr.length-1];
|
||||
o[0] = 0;
|
||||
o[2] = 2;
|
||||
var sym = Symbol();
|
||||
o[sym] = 1;
|
||||
o.z = 3;
|
||||
Object.defineProperty(o, '3', {value:1,enumerable:false,configurable:false,writable:false});
|
||||
o[4] = 4;
|
||||
|
||||
var props = Reflect.ownKeys(o);
|
||||
assertEq(props[props.length-1], sym);
|
||||
|
||||
assertEq(Object.getOwnPropertyNames(o).join(""), "0234xyz");
|
||||
}
|
||||
testUnboxed();
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
var a = [];
|
||||
for (var i = 0; i < 2000; i++)
|
||||
a.push({f:i});
|
||||
|
||||
function f() {
|
||||
var total = 0;
|
||||
for (var i = 0; i < a.length; i++)
|
||||
total += a[i].f;
|
||||
return total;
|
||||
}
|
||||
assertEq(f(), 1999000);
|
||||
|
||||
var sub = Object.create(a[0]);
|
||||
|
||||
assertEq(f(), 1999000);
|
|
@ -16,7 +16,7 @@ using namespace js;
|
|||
using namespace js::jit;
|
||||
|
||||
// OperandLocation represents the location of an OperandId. The operand is
|
||||
// either in a register or on the stack.
|
||||
// either in a register or on the stack, and is either boxed or unboxed.
|
||||
class OperandLocation
|
||||
{
|
||||
public:
|
||||
|
@ -814,6 +814,36 @@ BaselineCacheIRCompiler::emitGuardSpecificObject()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitGuardNoUnboxedExpando()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure))
|
||||
return false;
|
||||
|
||||
Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando());
|
||||
masm.branchPtr(Assembler::NotEqual, expandoAddr, ImmWord(0), failure->label());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitGuardAndLoadUnboxedExpando()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
Register output = allocator.defineRegister(masm, reader.objOperandId());
|
||||
|
||||
FailurePath* failure;
|
||||
if (!addFailurePath(&failure))
|
||||
return false;
|
||||
|
||||
Address expandoAddr(obj, UnboxedPlainObject::offsetOfExpando());
|
||||
masm.loadPtr(expandoAddr, output);
|
||||
masm.branchTestPtr(Assembler::Zero, output, output, failure->label());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitLoadFixedSlotResult()
|
||||
{
|
||||
|
@ -840,6 +870,26 @@ BaselineCacheIRCompiler::emitLoadDynamicSlotResult()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitLoadUnboxedPropertyResult()
|
||||
{
|
||||
Register obj = allocator.useRegister(masm, reader.objOperandId());
|
||||
AutoScratchRegister scratch(allocator, masm);
|
||||
|
||||
JSValueType fieldType = reader.valueType();
|
||||
|
||||
Address fieldOffset(stubAddress(reader.stubOffset()));
|
||||
masm.load32(fieldOffset, scratch);
|
||||
masm.loadUnboxedProperty(BaseIndex(obj, scratch, TimesOne), fieldType, R0);
|
||||
|
||||
if (fieldType == JSVAL_TYPE_OBJECT)
|
||||
emitEnterTypeMonitorIC();
|
||||
else
|
||||
emitReturnFromIC();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineCacheIRCompiler::emitGuardNoDetachedTypedObjects()
|
||||
{
|
||||
|
|
|
@ -44,8 +44,8 @@
|
|||
#include "jit/shared/Lowering-shared-inl.h"
|
||||
#include "vm/EnvironmentObject-inl.h"
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/StringObject-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using mozilla::DebugOnly;
|
||||
|
||||
|
@ -741,6 +741,11 @@ LastPropertyForSetProp(JSObject* obj)
|
|||
if (obj->isNative())
|
||||
return obj->as<NativeObject>().lastProperty();
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
return expando ? expando->lastProperty() : nullptr;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -1157,6 +1162,56 @@ TryAttachNativeOrUnboxedGetValueElemStub(JSContext* cx, HandleScript script, jsb
|
|||
|
||||
ICStub* monitorStub = stub->fallbackMonitorStub()->firstMonitorStub();
|
||||
|
||||
if (obj->is<UnboxedPlainObject>() && holder == obj) {
|
||||
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
|
||||
|
||||
// Once unboxed objects support symbol-keys, we need to change the following accordingly
|
||||
MOZ_ASSERT_IF(!keyVal.isString(), !property);
|
||||
|
||||
if (property) {
|
||||
if (!cx->runtime()->jitSupportsFloatingPoint)
|
||||
return true;
|
||||
|
||||
RootedPropertyName name(cx, JSID_TO_ATOM(id)->asPropertyName());
|
||||
ICGetElemNativeCompiler<PropertyName*> compiler(cx, ICStub::GetElem_UnboxedPropertyName,
|
||||
monitorStub, obj, holder,
|
||||
name,
|
||||
ICGetElemNativeStub::UnboxedProperty,
|
||||
needsAtomize, property->offset +
|
||||
UnboxedPlainObject::offsetOfData(),
|
||||
property->type);
|
||||
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
|
||||
if (!newStub)
|
||||
return false;
|
||||
|
||||
stub->addNewStub(newStub);
|
||||
*attached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Shape* shape = obj->as<UnboxedPlainObject>().maybeExpando()->lookup(cx, id);
|
||||
if (!shape->hasDefaultGetter() || !shape->hasSlot())
|
||||
return true;
|
||||
|
||||
bool isFixedSlot;
|
||||
uint32_t offset;
|
||||
GetFixedOrDynamicSlotOffset(shape, &isFixedSlot, &offset);
|
||||
|
||||
ICGetElemNativeStub::AccessType acctype =
|
||||
isFixedSlot ? ICGetElemNativeStub::FixedSlot
|
||||
: ICGetElemNativeStub::DynamicSlot;
|
||||
ICGetElemNativeCompiler<T> compiler(cx, getGetElemStubKind<T>(ICStub::GetElem_NativeSlotName),
|
||||
monitorStub, obj, holder, key,
|
||||
acctype, needsAtomize, offset);
|
||||
ICStub* newStub = compiler.getStub(compiler.getStubSpace(script));
|
||||
if (!newStub)
|
||||
return false;
|
||||
|
||||
stub->addNewStub(newStub);
|
||||
*attached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!holder->isNative())
|
||||
return true;
|
||||
|
||||
|
@ -1404,7 +1459,7 @@ TryAttachGetElemStub(JSContext* cx, JSScript* script, jsbytecode* pc, ICGetElem_
|
|||
}
|
||||
|
||||
// Check for NativeObject[id] and UnboxedPlainObject[id] shape-optimizable accesses.
|
||||
if (obj->isNative()) {
|
||||
if (obj->isNative() || obj->is<UnboxedPlainObject>()) {
|
||||
RootedScript rootedScript(cx, script);
|
||||
if (rhs.isString()) {
|
||||
if (!TryAttachNativeOrUnboxedGetValueElemStub<PropertyName*>(cx, rootedScript, pc, stub,
|
||||
|
@ -1816,6 +1871,14 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm)
|
|||
Register holderReg;
|
||||
if (obj_ == holder_) {
|
||||
holderReg = objReg;
|
||||
|
||||
if (obj_->is<UnboxedPlainObject>() && acctype_ != ICGetElemNativeStub::UnboxedProperty) {
|
||||
// The property will be loaded off the unboxed expando.
|
||||
masm.push(R1.scratchReg());
|
||||
popR1 = true;
|
||||
holderReg = R1.scratchReg();
|
||||
masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
|
||||
}
|
||||
} else {
|
||||
// Shape guard holder.
|
||||
if (regs.empty()) {
|
||||
|
@ -1866,6 +1929,13 @@ ICGetElemNativeCompiler<T>::generateStubCode(MacroAssembler& masm)
|
|||
if (popR1)
|
||||
masm.addToStackPtr(ImmWord(sizeof(size_t)));
|
||||
|
||||
} else if (acctype_ == ICGetElemNativeStub::UnboxedProperty) {
|
||||
masm.load32(Address(ICStubReg, ICGetElemNativeSlotStub<T>::offsetOfOffset()),
|
||||
scratchReg);
|
||||
masm.loadUnboxedProperty(BaseIndex(objReg, scratchReg, TimesOne), unboxedType_,
|
||||
TypedOrValueRegister(R0));
|
||||
if (popR1)
|
||||
masm.addToStackPtr(ImmWord(sizeof(size_t)));
|
||||
} else {
|
||||
MOZ_ASSERT(acctype_ == ICGetElemNativeStub::NativeGetter ||
|
||||
acctype_ == ICGetElemNativeStub::ScriptedGetter);
|
||||
|
@ -2618,6 +2688,18 @@ BaselineScript::noteArrayWriteHole(uint32_t pcOffset)
|
|||
// SetElem_DenseOrUnboxedArray
|
||||
//
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
EmitUnboxedPreBarrierForBaseline(MacroAssembler &masm, T address, JSValueType type)
|
||||
{
|
||||
if (type == JSVAL_TYPE_OBJECT)
|
||||
EmitPreBarrier(masm, address, MIRType::Object);
|
||||
else if (type == JSVAL_TYPE_STRING)
|
||||
EmitPreBarrier(masm, address, MIRType::String);
|
||||
else
|
||||
MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(type));
|
||||
}
|
||||
|
||||
bool
|
||||
ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
{
|
||||
|
@ -4061,7 +4143,18 @@ TryAttachSetValuePropStub(JSContext* cx, HandleScript script, jsbytecode* pc, IC
|
|||
return true;
|
||||
|
||||
if (!obj->isNative()) {
|
||||
return true;
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
if (expando) {
|
||||
shape = expando->lookup(cx, name);
|
||||
if (!shape)
|
||||
return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t chainDepth;
|
||||
|
@ -4208,6 +4301,40 @@ TryAttachSetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecode* pc,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
TryAttachUnboxedSetPropStub(JSContext* cx, HandleScript script,
|
||||
ICSetProp_Fallback* stub, HandleId id,
|
||||
HandleObject obj, HandleValue rhs, bool* attached)
|
||||
{
|
||||
MOZ_ASSERT(!*attached);
|
||||
|
||||
if (!cx->runtime()->jitSupportsFloatingPoint)
|
||||
return true;
|
||||
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return true;
|
||||
|
||||
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
|
||||
if (!property)
|
||||
return true;
|
||||
|
||||
ICSetProp_Unboxed::Compiler compiler(cx, obj->group(),
|
||||
property->offset + UnboxedPlainObject::offsetOfData(),
|
||||
property->type);
|
||||
ICUpdatedStub* newStub = compiler.getStub(compiler.getStubSpace(script));
|
||||
if (!newStub)
|
||||
return false;
|
||||
if (compiler.needsUpdateStubs() && !newStub->addUpdateStubForValue(cx, script, obj, id, rhs))
|
||||
return false;
|
||||
|
||||
stub->addNewStub(newStub);
|
||||
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
|
||||
*attached = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
TryAttachTypedObjectSetPropStub(JSContext* cx, HandleScript script,
|
||||
ICSetProp_Fallback* stub, HandleId id,
|
||||
|
@ -4291,6 +4418,12 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_
|
|||
return false;
|
||||
RootedReceiverGuard oldGuard(cx, ReceiverGuard(obj));
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
MOZ_ASSERT(!oldShape);
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
oldShape = expando->lastProperty();
|
||||
}
|
||||
|
||||
bool attached = false;
|
||||
// There are some reasons we can fail to attach a stub that are temporary.
|
||||
// We want to avoid calling noteUnoptimizableAccess() if the reason we
|
||||
|
@ -4361,6 +4494,15 @@ DoSetPropFallback(JSContext* cx, BaselineFrame* frame, ICSetProp_Fallback* stub_
|
|||
if (attached)
|
||||
return true;
|
||||
|
||||
if (!attached &&
|
||||
lhs.isObject() &&
|
||||
!TryAttachUnboxedSetPropStub(cx, script, stub, id, obj, rhs, &attached))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (attached)
|
||||
return true;
|
||||
|
||||
if (!attached &&
|
||||
lhs.isObject() &&
|
||||
!TryAttachTypedObjectSetPropStub(cx, script, stub, id, obj, rhs, &attached))
|
||||
|
@ -4445,7 +4587,20 @@ GuardGroupAndShapeMaybeUnboxedExpando(MacroAssembler& masm, JSObject* obj,
|
|||
|
||||
// Guard against shape or expando shape.
|
||||
masm.loadPtr(Address(ICStubReg, offsetOfShape), scratch);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
|
||||
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
|
||||
Label done;
|
||||
masm.push(object);
|
||||
masm.loadPtr(expandoAddress, object);
|
||||
masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
|
||||
masm.pop(object);
|
||||
masm.jump(failure);
|
||||
masm.bind(&done);
|
||||
masm.pop(object);
|
||||
} else {
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -4484,7 +4639,13 @@ ICSetProp_Native::Compiler::generateStubCode(MacroAssembler& masm)
|
|||
regs.takeUnchecked(objReg);
|
||||
|
||||
Register holderReg;
|
||||
if (isFixedSlot_) {
|
||||
if (obj_->is<UnboxedPlainObject>()) {
|
||||
// We are loading off the expando object, so use that for the holder.
|
||||
holderReg = regs.takeAny();
|
||||
masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
|
||||
if (!isFixedSlot_)
|
||||
masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
|
||||
} else if (isFixedSlot_) {
|
||||
holderReg = objReg;
|
||||
} else {
|
||||
holderReg = regs.takeAny();
|
||||
|
@ -4621,17 +4782,31 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm)
|
|||
regs.add(R0);
|
||||
regs.takeUnchecked(objReg);
|
||||
|
||||
// Write the object's new shape.
|
||||
Address shapeAddr(objReg, ShapedObject::offsetOfShape());
|
||||
EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
|
||||
masm.storePtr(scratch, shapeAddr);
|
||||
|
||||
if (isFixedSlot_) {
|
||||
holderReg = objReg;
|
||||
} else {
|
||||
if (obj_->is<UnboxedPlainObject>()) {
|
||||
holderReg = regs.takeAny();
|
||||
masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
|
||||
masm.loadPtr(Address(objReg, UnboxedPlainObject::offsetOfExpando()), holderReg);
|
||||
|
||||
// Write the expando object's new shape.
|
||||
Address shapeAddr(holderReg, ShapedObject::offsetOfShape());
|
||||
EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
|
||||
masm.storePtr(scratch, shapeAddr);
|
||||
|
||||
if (!isFixedSlot_)
|
||||
masm.loadPtr(Address(holderReg, NativeObject::offsetOfSlots()), holderReg);
|
||||
} else {
|
||||
// Write the object's new shape.
|
||||
Address shapeAddr(objReg, ShapedObject::offsetOfShape());
|
||||
EmitPreBarrier(masm, shapeAddr, MIRType::Shape);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetProp_NativeAdd::offsetOfNewShape()), scratch);
|
||||
masm.storePtr(scratch, shapeAddr);
|
||||
|
||||
if (isFixedSlot_) {
|
||||
holderReg = objReg;
|
||||
} else {
|
||||
holderReg = regs.takeAny();
|
||||
masm.loadPtr(Address(objReg, NativeObject::offsetOfSlots()), holderReg);
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the store. No write barrier required since this is a new
|
||||
|
@ -4662,6 +4837,70 @@ ICSetPropNativeAddCompiler::generateStubCode(MacroAssembler& masm)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ICSetProp_Unboxed::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
{
|
||||
MOZ_ASSERT(engine_ == Engine::Baseline);
|
||||
|
||||
Label failure;
|
||||
|
||||
// Guard input is an object.
|
||||
masm.branchTestObject(Assembler::NotEqual, R0, &failure);
|
||||
|
||||
AllocatableGeneralRegisterSet regs(availableGeneralRegs(2));
|
||||
Register scratch = regs.takeAny();
|
||||
|
||||
// Unbox and group guard.
|
||||
Register object = masm.extractObject(R0, ExtractTemp0);
|
||||
masm.loadPtr(Address(ICStubReg, ICSetProp_Unboxed::offsetOfGroup()), scratch);
|
||||
masm.branchPtr(Assembler::NotEqual, Address(object, JSObject::offsetOfGroup()), scratch,
|
||||
&failure);
|
||||
|
||||
if (needsUpdateStubs()) {
|
||||
// Stow both R0 and R1 (object and value).
|
||||
EmitStowICValues(masm, 2);
|
||||
|
||||
// Move RHS into R0 for TypeUpdate check.
|
||||
masm.moveValue(R1, R0);
|
||||
|
||||
// Call the type update stub.
|
||||
if (!callTypeUpdateIC(masm, sizeof(Value)))
|
||||
return false;
|
||||
|
||||
// Unstow R0 and R1 (object and key)
|
||||
EmitUnstowICValues(masm, 2);
|
||||
|
||||
// The TypeUpdate IC may have smashed object. Rederive it.
|
||||
masm.unboxObject(R0, object);
|
||||
|
||||
// Trigger post barriers here on the values being written. Fields which
|
||||
// objects can be written to also need update stubs.
|
||||
LiveGeneralRegisterSet saveRegs;
|
||||
saveRegs.add(R0);
|
||||
saveRegs.add(R1);
|
||||
saveRegs.addUnchecked(object);
|
||||
saveRegs.add(ICStubReg);
|
||||
emitPostWriteBarrierSlot(masm, object, R1, scratch, saveRegs);
|
||||
}
|
||||
|
||||
// Compute the address being written to.
|
||||
masm.load32(Address(ICStubReg, ICSetProp_Unboxed::offsetOfFieldOffset()), scratch);
|
||||
BaseIndex address(object, scratch, TimesOne);
|
||||
|
||||
EmitUnboxedPreBarrierForBaseline(masm, address, fieldType_);
|
||||
masm.storeUnboxedProperty(address, fieldType_,
|
||||
ConstantOrRegister(TypedOrValueRegister(R1)), &failure);
|
||||
|
||||
// The RHS has to be in R0.
|
||||
masm.moveValue(R1, R0);
|
||||
|
||||
EmitReturnFromIC(masm);
|
||||
|
||||
masm.bind(&failure);
|
||||
EmitStubGuardFailure(masm);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
ICSetProp_TypedObject::Compiler::generateStubCode(MacroAssembler& masm)
|
||||
{
|
||||
|
@ -5421,7 +5660,7 @@ TryAttachCallStub(JSContext* cx, ICCall_Fallback* stub, HandleScript script, jsb
|
|||
if (!thisObject)
|
||||
return false;
|
||||
|
||||
if (thisObject->is<PlainObject>())
|
||||
if (thisObject->is<PlainObject>() || thisObject->is<UnboxedPlainObject>())
|
||||
templateObject = thisObject;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "jit/SharedICRegisters.h"
|
||||
#include "js/GCVector.h"
|
||||
#include "vm/ArrayObject.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
@ -1822,7 +1823,8 @@ class ICSetProp_Native : public ICUpdatedStub
|
|||
virtual int32_t getKey() const {
|
||||
return static_cast<int32_t>(engine_) |
|
||||
(static_cast<int32_t>(kind) << 1) |
|
||||
(static_cast<int32_t>(isFixedSlot_) << 17);
|
||||
(static_cast<int32_t>(isFixedSlot_) << 17) |
|
||||
(static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18);
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
|
||||
|
@ -1927,6 +1929,7 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler
|
|||
return static_cast<int32_t>(engine_) |
|
||||
(static_cast<int32_t>(kind) << 1) |
|
||||
(static_cast<int32_t>(isFixedSlot_) << 17) |
|
||||
(static_cast<int32_t>(obj_->is<UnboxedPlainObject>()) << 18) |
|
||||
(static_cast<int32_t>(protoChainDepth_) << 19);
|
||||
}
|
||||
|
||||
|
@ -1951,7 +1954,10 @@ class ICSetPropNativeAddCompiler : public ICStubCompiler
|
|||
newGroup = nullptr;
|
||||
|
||||
RootedShape newShape(cx);
|
||||
newShape = obj_->as<NativeObject>().lastProperty();
|
||||
if (obj_->isNative())
|
||||
newShape = obj_->as<NativeObject>().lastProperty();
|
||||
else
|
||||
newShape = obj_->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
|
||||
|
||||
return newStub<ICSetProp_NativeAddImpl<ProtoChainDepth>>(
|
||||
space, getStubCode(), oldGroup_, shapes, newShape, newGroup, offset_);
|
||||
|
|
|
@ -104,11 +104,19 @@ AddReceiver(const ReceiverGuard& receiver,
|
|||
static bool
|
||||
GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
|
||||
{
|
||||
// We match:
|
||||
// We match either:
|
||||
//
|
||||
// GuardIsObject 0
|
||||
// GuardShape 0
|
||||
// LoadFixedSlotResult 0 or LoadDynamicSlotResult 0
|
||||
//
|
||||
// or
|
||||
//
|
||||
// GuardIsObject 0
|
||||
// GuardGroup 0
|
||||
// 1: GuardAndLoadUnboxedExpando 0
|
||||
// GuardShape 1
|
||||
// LoadFixedSlotResult 1 or LoadDynamicSlotResult 1
|
||||
|
||||
*receiver = ReceiverGuard();
|
||||
CacheIRReader reader(stub->stubInfo());
|
||||
|
@ -117,6 +125,14 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re
|
|||
if (!reader.matchOp(CacheOp::GuardIsObject, objId))
|
||||
return false;
|
||||
|
||||
if (reader.matchOp(CacheOp::GuardGroup, objId)) {
|
||||
receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
|
||||
|
||||
if (!reader.matchOp(CacheOp::GuardAndLoadUnboxedExpando, objId))
|
||||
return false;
|
||||
objId = reader.objOperandId();
|
||||
}
|
||||
|
||||
if (reader.matchOp(CacheOp::GuardShape, objId)) {
|
||||
receiver->shape = stub->stubInfo()->getStubField<Shape*>(stub, reader.stubOffset());
|
||||
return reader.matchOpEither(CacheOp::LoadFixedSlotResult, CacheOp::LoadDynamicSlotResult);
|
||||
|
@ -125,6 +141,29 @@ GetCacheIRReceiverForNativeReadSlot(ICCacheIR_Monitored* stub, ReceiverGuard* re
|
|||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
GetCacheIRReceiverForUnboxedProperty(ICCacheIR_Monitored* stub, ReceiverGuard* receiver)
|
||||
{
|
||||
// We match:
|
||||
//
|
||||
// GuardIsObject 0
|
||||
// GuardGroup 0
|
||||
// LoadUnboxedPropertyResult 0 ..
|
||||
|
||||
*receiver = ReceiverGuard();
|
||||
CacheIRReader reader(stub->stubInfo());
|
||||
|
||||
ObjOperandId objId = ObjOperandId(0);
|
||||
if (!reader.matchOp(CacheOp::GuardIsObject, objId))
|
||||
return false;
|
||||
|
||||
if (!reader.matchOp(CacheOp::GuardGroup, objId))
|
||||
return false;
|
||||
receiver->group = stub->stubInfo()->getStubField<ObjectGroup*>(stub, reader.stubOffset());
|
||||
|
||||
return reader.matchOp(CacheOp::LoadUnboxedPropertyResult, objId);
|
||||
}
|
||||
|
||||
bool
|
||||
BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receivers)
|
||||
{
|
||||
|
@ -143,7 +182,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
|
|||
while (stub->next()) {
|
||||
ReceiverGuard receiver;
|
||||
if (stub->isCacheIR_Monitored()) {
|
||||
if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver))
|
||||
if (!GetCacheIRReceiverForNativeReadSlot(stub->toCacheIR_Monitored(), &receiver) &&
|
||||
!GetCacheIRReceiverForUnboxedProperty(stub->toCacheIR_Monitored(), &receiver))
|
||||
{
|
||||
receivers.clear();
|
||||
return true;
|
||||
|
@ -151,6 +191,8 @@ BaselineInspector::maybeInfoForPropertyOp(jsbytecode* pc, ReceiverVector& receiv
|
|||
} else if (stub->isSetProp_Native()) {
|
||||
receiver = ReceiverGuard(stub->toSetProp_Native()->group(),
|
||||
stub->toSetProp_Native()->shape());
|
||||
} else if (stub->isSetProp_Unboxed()) {
|
||||
receiver = ReceiverGuard(stub->toSetProp_Unboxed()->group(), nullptr);
|
||||
} else {
|
||||
receivers.clear();
|
||||
return true;
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
#include "jit/IonCaches.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
@ -59,6 +60,10 @@ GetPropIRGenerator::tryAttachStub(Maybe<CacheIRWriter>& writer)
|
|||
return false;
|
||||
if (!emitted_ && !tryAttachNative(*writer, obj, objId))
|
||||
return false;
|
||||
if (!emitted_ && !tryAttachUnboxed(*writer, obj, objId))
|
||||
return false;
|
||||
if (!emitted_ && !tryAttachUnboxedExpando(*writer, obj, objId))
|
||||
return false;
|
||||
if (!emitted_ && !tryAttachTypedObject(*writer, obj, objId))
|
||||
return false;
|
||||
if (!emitted_ && !tryAttachModuleNamespace(*writer, obj, objId))
|
||||
|
@ -158,9 +163,19 @@ GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
|
|||
}
|
||||
|
||||
static void
|
||||
TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId)
|
||||
TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId,
|
||||
Maybe<ObjOperandId>* expandoId)
|
||||
{
|
||||
if (obj->is<TypedObject>()) {
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
writer.guardGroup(objId, obj->group());
|
||||
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
expandoId->emplace(writer.guardAndLoadUnboxedExpando(objId));
|
||||
writer.guardShape(expandoId->ref(), expando->lastProperty());
|
||||
} else {
|
||||
writer.guardNoUnboxedExpando(objId);
|
||||
}
|
||||
} else if (obj->is<TypedObject>()) {
|
||||
writer.guardGroup(objId, obj->group());
|
||||
} else {
|
||||
Shape* shape = obj->maybeShape();
|
||||
|
@ -173,7 +188,8 @@ static void
|
|||
EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
|
||||
Shape* shape, ObjOperandId objId)
|
||||
{
|
||||
TestMatchingReceiver(writer, obj, shape, objId);
|
||||
Maybe<ObjOperandId> expandoId;
|
||||
TestMatchingReceiver(writer, obj, shape, objId, &expandoId);
|
||||
|
||||
ObjOperandId holderId;
|
||||
if (obj != holder) {
|
||||
|
@ -196,6 +212,9 @@ EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder,
|
|||
lastObjId = protoId;
|
||||
}
|
||||
}
|
||||
} else if (obj->is<UnboxedPlainObject>()) {
|
||||
holder = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
holderId = *expandoId;
|
||||
} else {
|
||||
holderId = objId;
|
||||
}
|
||||
|
@ -246,6 +265,51 @@ GetPropIRGenerator::tryAttachNative(CacheIRWriter& writer, HandleObject obj, Obj
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropIRGenerator::tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
|
||||
{
|
||||
MOZ_ASSERT(!emitted_);
|
||||
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return true;
|
||||
|
||||
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(name_);
|
||||
if (!property)
|
||||
return true;
|
||||
|
||||
if (!cx_->runtime()->jitSupportsFloatingPoint)
|
||||
return true;
|
||||
|
||||
writer.guardGroup(objId, obj->group());
|
||||
writer.loadUnboxedPropertyResult(objId, property->type,
|
||||
UnboxedPlainObject::offsetOfData() + property->offset);
|
||||
emitted_ = true;
|
||||
preliminaryObjectAction_ = PreliminaryObjectAction::Unlink;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropIRGenerator::tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
|
||||
{
|
||||
MOZ_ASSERT(!emitted_);
|
||||
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return true;
|
||||
|
||||
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
if (!expando)
|
||||
return true;
|
||||
|
||||
Shape* shape = expando->lookup(cx_, NameToId(name_));
|
||||
if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
|
||||
return true;
|
||||
|
||||
emitted_ = true;
|
||||
|
||||
EmitReadSlotResult(writer, obj, obj, shape, objId);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropIRGenerator::tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
|
||||
{
|
||||
|
|
|
@ -87,10 +87,13 @@ class ObjOperandId : public OperandId
|
|||
_(GuardClass) \
|
||||
_(GuardSpecificObject) \
|
||||
_(GuardNoDetachedTypedObjects) \
|
||||
_(GuardNoUnboxedExpando) \
|
||||
_(GuardAndLoadUnboxedExpando) \
|
||||
_(LoadObject) \
|
||||
_(LoadProto) \
|
||||
_(LoadFixedSlotResult) \
|
||||
_(LoadDynamicSlotResult) \
|
||||
_(LoadUnboxedPropertyResult) \
|
||||
_(LoadTypedObjectResult) \
|
||||
_(LoadInt32ArrayLengthResult) \
|
||||
_(LoadArgumentsObjectLengthResult) \
|
||||
|
@ -271,6 +274,15 @@ class MOZ_RAII CacheIRWriter
|
|||
void guardNoDetachedTypedObjects() {
|
||||
writeOp(CacheOp::GuardNoDetachedTypedObjects);
|
||||
}
|
||||
void guardNoUnboxedExpando(ObjOperandId obj) {
|
||||
writeOpWithOperandId(CacheOp::GuardNoUnboxedExpando, obj);
|
||||
}
|
||||
ObjOperandId guardAndLoadUnboxedExpando(ObjOperandId obj) {
|
||||
ObjOperandId res(nextOperandId_++);
|
||||
writeOpWithOperandId(CacheOp::GuardAndLoadUnboxedExpando, obj);
|
||||
writeOperandId(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
ObjOperandId loadObject(JSObject* obj) {
|
||||
ObjOperandId res(nextOperandId_++);
|
||||
|
@ -296,6 +308,11 @@ class MOZ_RAII CacheIRWriter
|
|||
writeOpWithOperandId(CacheOp::LoadDynamicSlotResult, obj);
|
||||
addStubWord(offset, StubField::GCType::NoGCThing);
|
||||
}
|
||||
void loadUnboxedPropertyResult(ObjOperandId obj, JSValueType type, size_t offset) {
|
||||
writeOpWithOperandId(CacheOp::LoadUnboxedPropertyResult, obj);
|
||||
buffer_.writeByte(uint32_t(type));
|
||||
addStubWord(offset, StubField::GCType::NoGCThing);
|
||||
}
|
||||
void loadTypedObjectResult(ObjOperandId obj, uint32_t offset, TypedThingLayout layout,
|
||||
uint32_t typeDescr) {
|
||||
MOZ_ASSERT(uint32_t(layout) <= UINT8_MAX);
|
||||
|
@ -389,6 +406,9 @@ class MOZ_RAII GetPropIRGenerator
|
|||
PreliminaryObjectAction preliminaryObjectAction_;
|
||||
|
||||
MOZ_MUST_USE bool tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
|
||||
MOZ_MUST_USE bool tryAttachUnboxed(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
|
||||
MOZ_MUST_USE bool tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj,
|
||||
ObjOperandId objId);
|
||||
MOZ_MUST_USE bool tryAttachTypedObject(CacheIRWriter& writer, HandleObject obj,
|
||||
ObjOperandId objId);
|
||||
MOZ_MUST_USE bool tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj,
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include "builtin/Eval.h"
|
||||
#include "builtin/TypedObject.h"
|
||||
#include "gc/Nursery.h"
|
||||
#include "gc/StoreBuffer-inl.h"
|
||||
#include "irregexp/NativeRegExpMacroAssembler.h"
|
||||
#include "jit/AtomicOperations.h"
|
||||
#include "jit/BaselineCompiler.h"
|
||||
|
@ -3029,7 +3028,7 @@ CodeGenerator::visitStoreSlotV(LStoreSlotV* lir)
|
|||
|
||||
static void
|
||||
GuardReceiver(MacroAssembler& masm, const ReceiverGuard& guard,
|
||||
Register obj, Register scratch, Label* miss)
|
||||
Register obj, Register scratch, Label* miss, bool checkNullExpando)
|
||||
{
|
||||
if (guard.group) {
|
||||
masm.branchTestObjGroup(Assembler::NotEqual, obj, guard.group, miss);
|
||||
|
@ -3051,11 +3050,13 @@ CodeGenerator::emitGetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
|
|||
|
||||
Label next;
|
||||
masm.comment("GuardReceiver");
|
||||
GuardReceiver(masm, receiver, obj, scratch, &next);
|
||||
GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
|
||||
|
||||
if (receiver.shape) {
|
||||
masm.comment("loadTypedOrValue");
|
||||
Register target = obj;
|
||||
// If this is an unboxed expando access, GuardReceiver loaded the
|
||||
// expando object into scratch.
|
||||
Register target = receiver.group ? scratch : obj;
|
||||
|
||||
Shape* shape = mir->shape(i);
|
||||
if (shape->slot() < shape->numFixedSlots()) {
|
||||
|
@ -3121,10 +3122,12 @@ CodeGenerator::emitSetPropertyPolymorphic(LInstruction* ins, Register obj, Regis
|
|||
ReceiverGuard receiver = mir->receiver(i);
|
||||
|
||||
Label next;
|
||||
GuardReceiver(masm, receiver, obj, scratch, &next);
|
||||
GuardReceiver(masm, receiver, obj, scratch, &next, /* checkNullExpando = */ false);
|
||||
|
||||
if (receiver.shape) {
|
||||
Register target = obj;
|
||||
// If this is an unboxed expando access, GuardReceiver loaded the
|
||||
// expando object into scratch.
|
||||
Register target = receiver.group ? scratch : obj;
|
||||
|
||||
Shape* shape = mir->shape(i);
|
||||
if (shape->slot() < shape->numFixedSlots()) {
|
||||
|
@ -3290,7 +3293,7 @@ CodeGenerator::visitGuardReceiverPolymorphic(LGuardReceiverPolymorphic* lir)
|
|||
const ReceiverGuard& receiver = mir->receiver(i);
|
||||
|
||||
Label next;
|
||||
GuardReceiver(masm, receiver, obj, temp, &next);
|
||||
GuardReceiver(masm, receiver, obj, temp, &next, /* checkNullExpando = */ true);
|
||||
|
||||
if (i == mir->numReceivers() - 1) {
|
||||
bailoutFrom(&next, lir->snapshot());
|
||||
|
@ -8379,6 +8382,11 @@ CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer* lir)
|
|||
}
|
||||
}
|
||||
|
||||
typedef bool (*ConvertUnboxedObjectToNativeFn)(JSContext*, JSObject*);
|
||||
static const VMFunction ConvertUnboxedPlainObjectToNativeInfo =
|
||||
FunctionInfo<ConvertUnboxedObjectToNativeFn>(UnboxedPlainObject::convertToNative,
|
||||
"UnboxedPlainObject::convertToNative");
|
||||
|
||||
typedef bool (*ArrayPopShiftFn)(JSContext*, HandleObject, MutableHandleValue);
|
||||
static const VMFunction ArrayPopDenseInfo =
|
||||
FunctionInfo<ArrayPopShiftFn>(jit::ArrayPopDense, "ArrayPopDense");
|
||||
|
@ -8671,11 +8679,11 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir)
|
|||
masm.loadPtr(Address(niTemp, offsetof(NativeIterator, guard_array)), temp2);
|
||||
|
||||
// Compare object with the first receiver guard. The last iterator can only
|
||||
// match for native objects.
|
||||
// match for native objects and unboxed objects.
|
||||
{
|
||||
Address groupAddr(temp2, offsetof(ReceiverGuard, group));
|
||||
Address shapeAddr(temp2, offsetof(ReceiverGuard, shape));
|
||||
Label guardDone, shapeMismatch;
|
||||
Label guardDone, shapeMismatch, noExpando;
|
||||
masm.loadObjShape(obj, temp1);
|
||||
masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, &shapeMismatch);
|
||||
|
||||
|
@ -8687,6 +8695,12 @@ CodeGenerator::visitIteratorStartO(LIteratorStartO* lir)
|
|||
masm.bind(&shapeMismatch);
|
||||
masm.loadObjGroup(obj, temp1);
|
||||
masm.branchPtr(Assembler::NotEqual, groupAddr, temp1, ool->entry());
|
||||
masm.loadPtr(Address(obj, UnboxedPlainObject::offsetOfExpando()), temp1);
|
||||
masm.branchTestPtr(Assembler::Zero, temp1, temp1, &noExpando);
|
||||
branchIfNotEmptyObjectElements(temp1, ool->entry());
|
||||
masm.loadObjShape(temp1, temp1);
|
||||
masm.bind(&noExpando);
|
||||
masm.branchPtr(Assembler::NotEqual, shapeAddr, temp1, ool->entry());
|
||||
masm.bind(&guardDone);
|
||||
}
|
||||
|
||||
|
|
|
@ -10940,6 +10940,63 @@ IonBuilder::getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_
|
|||
return slot;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
IonBuilder::getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name, JSValueType* punboxedType)
|
||||
{
|
||||
if (!types || types->unknownObject() || !types->objectOrSentinel()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::NoTypeInfo);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
uint32_t offset = UINT32_MAX;
|
||||
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
TypeSet::ObjectKey* key = types->getObject(i);
|
||||
if (!key)
|
||||
continue;
|
||||
|
||||
if (key->unknownProperties()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::UnknownProperties);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (key->isSingleton()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::Singleton);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
UnboxedLayout* layout = key->group()->maybeUnboxedLayout();
|
||||
if (!layout) {
|
||||
trackOptimizationOutcome(TrackedOutcome::NotUnboxed);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
const UnboxedLayout::Property* property = layout->lookup(name);
|
||||
if (!property) {
|
||||
trackOptimizationOutcome(TrackedOutcome::StructNoField);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (layout->nativeGroup()) {
|
||||
trackOptimizationOutcome(TrackedOutcome::UnboxedConvertedToNative);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
|
||||
if (offset == UINT32_MAX) {
|
||||
offset = property->offset;
|
||||
*punboxedType = property->type;
|
||||
} else if (offset != property->offset) {
|
||||
trackOptimizationOutcome(TrackedOutcome::InconsistentFieldOffset);
|
||||
return UINT32_MAX;
|
||||
} else if (*punboxedType != property->type) {
|
||||
trackOptimizationOutcome(TrackedOutcome::InconsistentFieldType);
|
||||
return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::jsop_runonce()
|
||||
{
|
||||
|
@ -11906,6 +11963,72 @@ IonBuilder::getPropTryModuleNamespace(bool* emitted, MDefinition* obj, PropertyN
|
|||
return true;
|
||||
}
|
||||
|
||||
MInstruction*
|
||||
IonBuilder::loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
|
||||
BarrierKind barrier, TemporaryTypeSet* types)
|
||||
{
|
||||
// loadUnboxedValue is designed to load any value as if it were contained in
|
||||
// an array. Thus a property offset is converted to an index, when the
|
||||
// object is reinterpreted as an array of properties of the same size.
|
||||
size_t index = offset / UnboxedTypeSize(unboxedType);
|
||||
MInstruction* indexConstant = MConstant::New(alloc(), Int32Value(index));
|
||||
current->add(indexConstant);
|
||||
|
||||
return loadUnboxedValue(obj, UnboxedPlainObject::offsetOfData(),
|
||||
indexConstant, unboxedType, barrier, types);
|
||||
}
|
||||
|
||||
MInstruction*
|
||||
IonBuilder::loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
|
||||
MDefinition* index, JSValueType unboxedType,
|
||||
BarrierKind barrier, TemporaryTypeSet* types)
|
||||
{
|
||||
MInstruction* load;
|
||||
switch (unboxedType) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Uint8,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset);
|
||||
load->setResultType(MIRType::Boolean);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_INT32:
|
||||
load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Int32,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset);
|
||||
load->setResultType(MIRType::Int32);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_DOUBLE:
|
||||
load = MLoadUnboxedScalar::New(alloc(), elements, index, Scalar::Float64,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset,
|
||||
/* canonicalizeDoubles = */ false);
|
||||
load->setResultType(MIRType::Double);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_STRING:
|
||||
load = MLoadUnboxedString::New(alloc(), elements, index, elementsOffset);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_OBJECT: {
|
||||
MLoadUnboxedObjectOrNull::NullBehavior nullBehavior;
|
||||
if (types->hasType(TypeSet::NullType()))
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::HandleNull;
|
||||
else if (barrier != BarrierKind::NoBarrier)
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::BailOnNull;
|
||||
else
|
||||
nullBehavior = MLoadUnboxedObjectOrNull::NullNotPossible;
|
||||
load = MLoadUnboxedObjectOrNull::New(alloc(), elements, index, nullBehavior,
|
||||
elementsOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
current->add(load);
|
||||
return load;
|
||||
}
|
||||
|
||||
MDefinition*
|
||||
IonBuilder::addShapeGuardsForGetterSetter(MDefinition* obj, JSObject* holder, Shape* holderShape,
|
||||
const BaselineInspector::ReceiverVector& receivers,
|
||||
|
@ -12729,6 +12852,66 @@ IonBuilder::setPropTryDefiniteSlot(bool* emitted, MDefinition* obj,
|
|||
return true;
|
||||
}
|
||||
|
||||
MInstruction*
|
||||
IonBuilder::storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
|
||||
MDefinition* value)
|
||||
{
|
||||
size_t scaledOffsetConstant = offset / UnboxedTypeSize(unboxedType);
|
||||
MInstruction* scaledOffset = MConstant::New(alloc(), Int32Value(scaledOffsetConstant));
|
||||
current->add(scaledOffset);
|
||||
|
||||
return storeUnboxedValue(obj, obj, UnboxedPlainObject::offsetOfData(),
|
||||
scaledOffset, unboxedType, value);
|
||||
}
|
||||
|
||||
MInstruction*
|
||||
IonBuilder::storeUnboxedValue(MDefinition* obj, MDefinition* elements, int32_t elementsOffset,
|
||||
MDefinition* scaledOffset, JSValueType unboxedType,
|
||||
MDefinition* value, bool preBarrier /* = true */)
|
||||
{
|
||||
MInstruction* store;
|
||||
switch (unboxedType) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Uint8,
|
||||
MStoreUnboxedScalar::DontTruncateInput,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_INT32:
|
||||
store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Int32,
|
||||
MStoreUnboxedScalar::DontTruncateInput,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_DOUBLE:
|
||||
store = MStoreUnboxedScalar::New(alloc(), elements, scaledOffset, value, Scalar::Float64,
|
||||
MStoreUnboxedScalar::DontTruncateInput,
|
||||
DoesNotRequireMemoryBarrier, elementsOffset);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_STRING:
|
||||
store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value,
|
||||
elementsOffset, preBarrier);
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_OBJECT:
|
||||
MOZ_ASSERT(value->type() == MIRType::Object ||
|
||||
value->type() == MIRType::Null ||
|
||||
value->type() == MIRType::Value);
|
||||
MOZ_ASSERT(!value->mightBeType(MIRType::Undefined),
|
||||
"MToObjectOrNull slow path is invalid for unboxed objects");
|
||||
store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value, obj,
|
||||
elementsOffset, preBarrier);
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
current->add(store);
|
||||
return store;
|
||||
}
|
||||
|
||||
bool
|
||||
IonBuilder::setPropTryInlineAccess(bool* emitted, MDefinition* obj,
|
||||
PropertyName* name, MDefinition* value,
|
||||
|
|
|
@ -1050,6 +1050,19 @@ class IonBuilder
|
|||
ResultWithOOM<bool> testNotDefinedProperty(MDefinition* obj, jsid id);
|
||||
|
||||
uint32_t getDefiniteSlot(TemporaryTypeSet* types, PropertyName* name, uint32_t* pnfixed);
|
||||
uint32_t getUnboxedOffset(TemporaryTypeSet* types, PropertyName* name,
|
||||
JSValueType* punboxedType);
|
||||
MInstruction* loadUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
|
||||
BarrierKind barrier, TemporaryTypeSet* types);
|
||||
MInstruction* loadUnboxedValue(MDefinition* elements, size_t elementsOffset,
|
||||
MDefinition* scaledOffset, JSValueType unboxedType,
|
||||
BarrierKind barrier, TemporaryTypeSet* types);
|
||||
MInstruction* storeUnboxedProperty(MDefinition* obj, size_t offset, JSValueType unboxedType,
|
||||
MDefinition* value);
|
||||
MInstruction* storeUnboxedValue(MDefinition* obj,
|
||||
MDefinition* elements, int32_t elementsOffset,
|
||||
MDefinition* scaledOffset, JSValueType unboxedType,
|
||||
MDefinition* value, bool preBarrier = true);
|
||||
MOZ_MUST_USE bool checkPreliminaryGroups(MDefinition *obj);
|
||||
MOZ_MUST_USE bool freezePropTypeSets(TemporaryTypeSet* types,
|
||||
JSObject* foundProto, PropertyName* name);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "jit/shared/Lowering-shared-inl.h"
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/Shape-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
@ -619,7 +620,26 @@ TestMatchingReceiver(MacroAssembler& masm, IonCache::StubAttacher& attacher,
|
|||
Register object, JSObject* obj, Label* failure,
|
||||
bool alwaysCheckGroup = false)
|
||||
{
|
||||
if (obj->is<TypedObject>()) {
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
MOZ_ASSERT(failure);
|
||||
|
||||
masm.branchTestObjGroup(Assembler::NotEqual, object, obj->group(), failure);
|
||||
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
|
||||
Label success;
|
||||
masm.push(object);
|
||||
masm.loadPtr(expandoAddress, object);
|
||||
masm.branchTestObjShape(Assembler::Equal, object, expando->lastProperty(),
|
||||
&success);
|
||||
masm.pop(object);
|
||||
masm.jump(failure);
|
||||
masm.bind(&success);
|
||||
masm.pop(object);
|
||||
} else {
|
||||
masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
|
||||
}
|
||||
} else if (obj->is<TypedObject>()) {
|
||||
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
|
||||
Address(object, JSObject::offsetOfGroup()),
|
||||
ImmGCPtr(obj->group()), failure);
|
||||
|
@ -736,6 +756,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
|
|||
// jump directly. Otherwise, jump to the end of the stub, so there's a
|
||||
// common point to patch.
|
||||
bool multipleFailureJumps = (obj != holder)
|
||||
|| obj->is<UnboxedPlainObject>()
|
||||
|| (checkTDZ && output.hasValue())
|
||||
|| (failures != nullptr && failures->used());
|
||||
|
||||
|
@ -754,6 +775,7 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
|
|||
Register scratchReg = Register::FromCode(0); // Quell compiler warning.
|
||||
|
||||
if (obj != holder ||
|
||||
obj->is<UnboxedPlainObject>() ||
|
||||
!holder->as<NativeObject>().isFixedSlot(shape->slot()))
|
||||
{
|
||||
if (output.hasValue()) {
|
||||
|
@ -814,6 +836,10 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
|
|||
|
||||
holderReg = InvalidReg;
|
||||
}
|
||||
} else if (obj->is<UnboxedPlainObject>()) {
|
||||
holder = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
holderReg = scratchReg;
|
||||
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), holderReg);
|
||||
} else {
|
||||
holderReg = object;
|
||||
}
|
||||
|
@ -841,6 +867,30 @@ GenerateReadSlot(JSContext* cx, IonScript* ion, MacroAssembler& masm,
|
|||
attacher.jumpNextStub(masm);
|
||||
}
|
||||
|
||||
static void
|
||||
GenerateReadUnboxed(JSContext* cx, IonScript* ion, MacroAssembler& masm,
|
||||
IonCache::StubAttacher& attacher, JSObject* obj,
|
||||
const UnboxedLayout::Property* property,
|
||||
Register object, TypedOrValueRegister output,
|
||||
Label* failures = nullptr)
|
||||
{
|
||||
// Guard on the group of the object.
|
||||
attacher.branchNextStubOrLabel(masm, Assembler::NotEqual,
|
||||
Address(object, JSObject::offsetOfGroup()),
|
||||
ImmGCPtr(obj->group()), failures);
|
||||
|
||||
Address address(object, UnboxedPlainObject::offsetOfData() + property->offset);
|
||||
|
||||
masm.loadUnboxedProperty(address, property->type, output);
|
||||
|
||||
attacher.jumpRejoin(masm);
|
||||
|
||||
if (failures) {
|
||||
masm.bind(failures);
|
||||
attacher.jumpNextStub(masm);
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
EmitGetterCall(JSContext* cx, MacroAssembler& masm,
|
||||
IonCache::StubAttacher& attacher, JSObject* obj,
|
||||
|
@ -1447,6 +1497,67 @@ GetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip
|
|||
return linkAndAttachStub(cx, masm, attacher, ion, attachKind, outcome);
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
|
||||
{
|
||||
MOZ_ASSERT(canAttachStub());
|
||||
MOZ_ASSERT(!*emitted);
|
||||
MOZ_ASSERT(outerScript->ionScript() == ion);
|
||||
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return true;
|
||||
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
|
||||
if (!property)
|
||||
return true;
|
||||
|
||||
*emitted = true;
|
||||
|
||||
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
||||
|
||||
Label failures;
|
||||
emitIdGuard(masm, id, &failures);
|
||||
Label* maybeFailures = failures.used() ? &failures : nullptr;
|
||||
|
||||
StubAttacher attacher(*this);
|
||||
GenerateReadUnboxed(cx, ion, masm, attacher, obj, property, object(), output(), maybeFailures);
|
||||
return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed",
|
||||
JS::TrackedOutcome::ICGetPropStub_UnboxedRead);
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, void* returnAddr, bool* emitted)
|
||||
{
|
||||
MOZ_ASSERT(canAttachStub());
|
||||
MOZ_ASSERT(!*emitted);
|
||||
MOZ_ASSERT(outerScript->ionScript() == ion);
|
||||
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return true;
|
||||
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
|
||||
if (!expando)
|
||||
return true;
|
||||
|
||||
Shape* shape = expando->lookup(cx, id);
|
||||
if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
|
||||
return true;
|
||||
|
||||
*emitted = true;
|
||||
|
||||
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
||||
|
||||
Label failures;
|
||||
emitIdGuard(masm, id, &failures);
|
||||
Label* maybeFailures = failures.used() ? &failures : nullptr;
|
||||
|
||||
StubAttacher attacher(*this);
|
||||
GenerateReadSlot(cx, ion, masm, attacher, DontCheckTDZ, obj, obj,
|
||||
shape, object(), output(), maybeFailures);
|
||||
return linkAndAttachStub(cx, masm, attacher, ion, "read unboxed expando",
|
||||
JS::TrackedOutcome::ICGetPropStub_UnboxedReadExpando);
|
||||
}
|
||||
|
||||
bool
|
||||
GetPropertyIC::tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, bool* emitted)
|
||||
|
@ -2016,6 +2127,12 @@ GetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
|
|||
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, returnAddr, emitted))
|
||||
return false;
|
||||
|
||||
if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, returnAddr, emitted))
|
||||
return false;
|
||||
|
||||
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, returnAddr, emitted))
|
||||
return false;
|
||||
|
||||
if (!*emitted && !tryAttachTypedArrayLength(cx, outerScript, ion, obj, id, emitted))
|
||||
return false;
|
||||
}
|
||||
|
@ -2194,6 +2311,12 @@ GenerateSetSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
|
||||
NativeObject::slotsSizeMustNotOverflow();
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
obj = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), tempReg);
|
||||
object = tempReg;
|
||||
}
|
||||
|
||||
if (obj->as<NativeObject>().isFixedSlot(shape->slot())) {
|
||||
Address addr(object, NativeObject::getFixedSlotOffset(shape->slot()));
|
||||
|
||||
|
@ -2831,13 +2954,23 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
masm.branchTestObjGroup(Assembler::NotEqual, object, oldGroup, failures);
|
||||
if (obj->maybeShape()) {
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, oldShape, failures);
|
||||
} else {
|
||||
MOZ_ASSERT(obj->is<UnboxedPlainObject>());
|
||||
|
||||
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
|
||||
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failures);
|
||||
|
||||
masm.loadPtr(expandoAddress, tempReg);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, tempReg, oldShape, failures);
|
||||
}
|
||||
|
||||
Shape* newShape = obj->maybeShape();
|
||||
if (!newShape)
|
||||
newShape = obj->as<UnboxedPlainObject>().maybeExpando()->lastProperty();
|
||||
|
||||
// Guard that the incoming value is in the type set for the property
|
||||
// if a type barrier is required.
|
||||
if (newShape && checkTypeset)
|
||||
if (checkTypeset)
|
||||
CheckTypeSetForWrite(masm, obj, newShape->propid(), tempReg, value, failures);
|
||||
|
||||
// Guard shapes along prototype chain.
|
||||
|
@ -2858,7 +2991,9 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
}
|
||||
|
||||
// Call a stub to (re)allocate dynamic slots, if necessary.
|
||||
uint32_t newNumDynamicSlots = obj->as<NativeObject>().numDynamicSlots();
|
||||
uint32_t newNumDynamicSlots = obj->is<UnboxedPlainObject>()
|
||||
? obj->as<UnboxedPlainObject>().maybeExpando()->numDynamicSlots()
|
||||
: obj->as<NativeObject>().numDynamicSlots();
|
||||
if (NativeObject::dynamicSlotsCount(oldShape) != newNumDynamicSlots) {
|
||||
AllocatableRegisterSet regs(RegisterSet::Volatile());
|
||||
LiveRegisterSet save(regs.asLiveSet());
|
||||
|
@ -2869,6 +3004,12 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
Register temp1 = regs.takeAnyGeneral();
|
||||
Register temp2 = regs.takeAnyGeneral();
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
// Pass the expando object to the stub.
|
||||
masm.Push(object);
|
||||
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
|
||||
}
|
||||
|
||||
masm.setupUnalignedABICall(temp1);
|
||||
masm.loadJSContext(temp1);
|
||||
masm.passABIArg(temp1);
|
||||
|
@ -2885,16 +3026,27 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
masm.jump(&allocDone);
|
||||
|
||||
masm.bind(&allocFailed);
|
||||
if (obj->is<UnboxedPlainObject>())
|
||||
masm.Pop(object);
|
||||
masm.PopRegsInMask(save);
|
||||
masm.jump(failures);
|
||||
|
||||
masm.bind(&allocDone);
|
||||
masm.setFramePushed(framePushedAfterCall);
|
||||
if (obj->is<UnboxedPlainObject>())
|
||||
masm.Pop(object);
|
||||
masm.PopRegsInMask(save);
|
||||
}
|
||||
|
||||
bool popObject = false;
|
||||
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
masm.push(object);
|
||||
popObject = true;
|
||||
obj = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
masm.loadPtr(Address(object, UnboxedPlainObject::offsetOfExpando()), object);
|
||||
}
|
||||
|
||||
// Write the object or expando object's new shape.
|
||||
Address shapeAddr(object, ShapedObject::offsetOfShape());
|
||||
if (cx->zone()->needsIncrementalBarrier())
|
||||
|
@ -2902,6 +3054,8 @@ GenerateAddSlot(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& att
|
|||
masm.storePtr(ImmGCPtr(newShape), shapeAddr);
|
||||
|
||||
if (oldGroup != obj->group()) {
|
||||
MOZ_ASSERT(!obj->is<UnboxedPlainObject>());
|
||||
|
||||
// Changing object's group from a partially to fully initialized group,
|
||||
// per the acquired properties analysis. Only change the group if the
|
||||
// old group still has a newScript.
|
||||
|
@ -3144,6 +3298,141 @@ CanAttachNativeSetProp(JSContext* cx, HandleObject obj, HandleId id, const Const
|
|||
return SetPropertyIC::CanAttachNone;
|
||||
}
|
||||
|
||||
static void
|
||||
GenerateSetUnboxed(JSContext* cx, MacroAssembler& masm, IonCache::StubAttacher& attacher,
|
||||
JSObject* obj, jsid id, uint32_t unboxedOffset, JSValueType unboxedType,
|
||||
Register object, Register tempReg, const ConstantOrRegister& value,
|
||||
bool checkTypeset, Label* failures)
|
||||
{
|
||||
// Guard on the type of the object.
|
||||
masm.branchPtr(Assembler::NotEqual,
|
||||
Address(object, JSObject::offsetOfGroup()),
|
||||
ImmGCPtr(obj->group()), failures);
|
||||
|
||||
if (checkTypeset)
|
||||
CheckTypeSetForWrite(masm, obj, id, tempReg, value, failures);
|
||||
|
||||
Address address(object, UnboxedPlainObject::offsetOfData() + unboxedOffset);
|
||||
|
||||
if (cx->zone()->needsIncrementalBarrier()) {
|
||||
if (unboxedType == JSVAL_TYPE_OBJECT)
|
||||
masm.callPreBarrier(address, MIRType::Object);
|
||||
else if (unboxedType == JSVAL_TYPE_STRING)
|
||||
masm.callPreBarrier(address, MIRType::String);
|
||||
else
|
||||
MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(unboxedType));
|
||||
}
|
||||
|
||||
masm.storeUnboxedProperty(address, unboxedType, value, failures);
|
||||
|
||||
attacher.jumpRejoin(masm);
|
||||
|
||||
masm.bind(failures);
|
||||
attacher.jumpNextStub(masm);
|
||||
}
|
||||
|
||||
static bool
|
||||
CanAttachSetUnboxed(JSContext* cx, HandleObject obj, HandleId id, const ConstantOrRegister& val,
|
||||
bool needsTypeBarrier, bool* checkTypeset,
|
||||
uint32_t* unboxedOffset, JSValueType* unboxedType)
|
||||
{
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return false;
|
||||
|
||||
const UnboxedLayout::Property* property = obj->as<UnboxedPlainObject>().layout().lookup(id);
|
||||
if (property) {
|
||||
*checkTypeset = false;
|
||||
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
|
||||
return false;
|
||||
*unboxedOffset = property->offset;
|
||||
*unboxedType = property->type;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
CanAttachSetUnboxedExpando(JSContext* cx, HandleObject obj, HandleId id,
|
||||
const ConstantOrRegister& val,
|
||||
bool needsTypeBarrier, bool* checkTypeset, Shape** pshape)
|
||||
{
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return false;
|
||||
|
||||
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
|
||||
if (!expando)
|
||||
return false;
|
||||
|
||||
Shape* shape = expando->lookupPure(id);
|
||||
if (!shape || !shape->hasDefaultSetter() || !shape->hasSlot() || !shape->writable())
|
||||
return false;
|
||||
|
||||
*checkTypeset = false;
|
||||
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
|
||||
return false;
|
||||
|
||||
*pshape = shape;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
CanAttachAddUnboxedExpando(JSContext* cx, HandleObject obj, HandleShape oldShape,
|
||||
HandleId id, const ConstantOrRegister& val,
|
||||
bool needsTypeBarrier, bool* checkTypeset)
|
||||
{
|
||||
if (!obj->is<UnboxedPlainObject>())
|
||||
return false;
|
||||
|
||||
Rooted<UnboxedExpandoObject*> expando(cx, obj->as<UnboxedPlainObject>().maybeExpando());
|
||||
if (!expando || expando->inDictionaryMode())
|
||||
return false;
|
||||
|
||||
Shape* newShape = expando->lastProperty();
|
||||
if (newShape->isEmptyShape() || newShape->propid() != id || newShape->previous() != oldShape)
|
||||
return false;
|
||||
|
||||
MOZ_ASSERT(newShape->hasDefaultSetter() && newShape->hasSlot() && newShape->writable());
|
||||
|
||||
if (PrototypeChainShadowsPropertyAdd(cx, obj, id))
|
||||
return false;
|
||||
|
||||
*checkTypeset = false;
|
||||
if (needsTypeBarrier && !CanInlineSetPropTypeCheck(obj, id, val, checkTypeset))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropertyIC::tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, bool* emitted)
|
||||
{
|
||||
MOZ_ASSERT(!*emitted);
|
||||
|
||||
bool checkTypeset = false;
|
||||
uint32_t unboxedOffset;
|
||||
JSValueType unboxedType;
|
||||
if (!CanAttachSetUnboxed(cx, obj, id, value(), needsTypeBarrier(), &checkTypeset,
|
||||
&unboxedOffset, &unboxedType))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
*emitted = true;
|
||||
|
||||
MacroAssembler masm(cx, ion, outerScript, profilerLeavePc_);
|
||||
StubAttacher attacher(*this);
|
||||
|
||||
Label failures;
|
||||
emitIdGuard(masm, id, &failures);
|
||||
|
||||
GenerateSetUnboxed(cx, masm, attacher, obj, id, unboxedOffset, unboxedType,
|
||||
object(), temp(), value(), checkTypeset, &failures);
|
||||
return linkAndAttachStub(cx, masm, attacher, ion, "set_unboxed",
|
||||
JS::TrackedOutcome::ICSetPropStub_SetUnboxed);
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropertyIC::tryAttachProxy(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, bool* emitted)
|
||||
|
@ -3224,6 +3513,26 @@ SetPropertyIC::tryAttachNative(JSContext* cx, HandleScript outerScript, IonScrip
|
|||
MOZ_CRASH("Unreachable");
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropertyIC::tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, bool* emitted)
|
||||
{
|
||||
MOZ_ASSERT(!*emitted);
|
||||
|
||||
RootedShape shape(cx);
|
||||
bool checkTypeset = false;
|
||||
if (!CanAttachSetUnboxedExpando(cx, obj, id, value(), needsTypeBarrier(),
|
||||
&checkTypeset, shape.address()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!attachSetSlot(cx, outerScript, ion, obj, shape, checkTypeset))
|
||||
return false;
|
||||
*emitted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleValue idval, HandleValue value,
|
||||
|
@ -3249,6 +3558,12 @@ SetPropertyIC::tryAttachStub(JSContext* cx, HandleScript outerScript, IonScript*
|
|||
|
||||
if (!*emitted && !tryAttachNative(cx, outerScript, ion, obj, id, emitted, tryNativeAddSlot))
|
||||
return false;
|
||||
|
||||
if (!*emitted && !tryAttachUnboxed(cx, outerScript, ion, obj, id, emitted))
|
||||
return false;
|
||||
|
||||
if (!*emitted && !tryAttachUnboxedExpando(cx, outerScript, ion, obj, id, emitted))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (idval.isInt32()) {
|
||||
|
@ -3300,6 +3615,16 @@ SetPropertyIC::tryAttachAddSlot(JSContext* cx, HandleScript outerScript, IonScri
|
|||
return true;
|
||||
}
|
||||
|
||||
checkTypeset = false;
|
||||
if (CanAttachAddUnboxedExpando(cx, obj, oldShape, id, value(), needsTypeBarrier(),
|
||||
&checkTypeset))
|
||||
{
|
||||
if (!attachAddSlot(cx, outerScript, ion, obj, id, oldShape, oldGroup, checkTypeset))
|
||||
return false;
|
||||
*emitted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3321,6 +3646,11 @@ SetPropertyIC::update(JSContext* cx, HandleScript outerScript, size_t cacheIndex
|
|||
return false;
|
||||
|
||||
oldShape = obj->maybeShape();
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
MOZ_ASSERT(!oldShape);
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
oldShape = expando->lastProperty();
|
||||
}
|
||||
}
|
||||
|
||||
RootedId id(cx);
|
||||
|
|
|
@ -529,6 +529,18 @@ class GetPropertyIC : public IonCache
|
|||
HandleObject obj, HandleId id, void* returnAddr,
|
||||
bool* emitted);
|
||||
|
||||
MOZ_MUST_USE bool tryAttachUnboxed(JSContext* cx, HandleScript outerScript, IonScript* ion,
|
||||
HandleObject obj, HandleId id, void* returnAddr,
|
||||
bool* emitted);
|
||||
|
||||
MOZ_MUST_USE bool tryAttachUnboxedExpando(JSContext* cx, HandleScript outerScript,
|
||||
IonScript* ion, HandleObject obj, HandleId id,
|
||||
void* returnAddr, bool* emitted);
|
||||
|
||||
MOZ_MUST_USE bool tryAttachUnboxedArrayLength(JSContext* cx, HandleScript outerScript,
|
||||
IonScript* ion, HandleObject obj, HandleId id,
|
||||
void* returnAddr, bool* emitted);
|
||||
|
||||
MOZ_MUST_USE bool tryAttachTypedArrayLength(JSContext* cx, HandleScript outerScript,
|
||||
IonScript* ion, HandleObject obj, HandleId id,
|
||||
bool* emitted);
|
||||
|
|
|
@ -221,6 +221,9 @@ DefaultJitOptions::DefaultJitOptions()
|
|||
Warn(forcedRegisterAllocatorEnv, env);
|
||||
}
|
||||
|
||||
// Toggles whether unboxed plain objects can be created by the VM.
|
||||
SET_DEFAULT(disableUnboxedObjects, true);
|
||||
|
||||
// Test whether Atomics are allowed in asm.js code.
|
||||
SET_DEFAULT(asmJSAtomicsEnable, false);
|
||||
|
||||
|
|
|
@ -91,6 +91,9 @@ struct DefaultJitOptions
|
|||
mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold;
|
||||
mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
|
||||
|
||||
// The options below affect the rest of the VM, and not just the JIT.
|
||||
bool disableUnboxedObjects;
|
||||
|
||||
DefaultJitOptions();
|
||||
bool isSmallFunction(JSScript* script) const;
|
||||
void setEagerCompilation();
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "jit/shared/Lowering-shared-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/StringObject-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using mozilla::ArrayLength;
|
||||
using mozilla::AssertedCast;
|
||||
|
|
|
@ -4783,14 +4783,15 @@ MCreateThisWithTemplate::canRecoverOnBailout() const
|
|||
|
||||
MObjectState::MObjectState(MObjectState* state)
|
||||
: numSlots_(state->numSlots_),
|
||||
numFixedSlots_(state->numFixedSlots_)
|
||||
numFixedSlots_(state->numFixedSlots_),
|
||||
operandIndex_(state->operandIndex_)
|
||||
{
|
||||
// This instruction is only used as a summary for bailout paths.
|
||||
setResultType(MIRType::Object);
|
||||
setRecoveredOnBailout();
|
||||
}
|
||||
|
||||
MObjectState::MObjectState(JSObject* templateObject)
|
||||
MObjectState::MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex)
|
||||
{
|
||||
// This instruction is only used as a summary for bailout paths.
|
||||
setResultType(MIRType::Object);
|
||||
|
@ -4801,6 +4802,8 @@ MObjectState::MObjectState(JSObject* templateObject)
|
|||
NativeObject* nativeObject = &templateObject->as<NativeObject>();
|
||||
numSlots_ = nativeObject->slotSpan();
|
||||
numFixedSlots_ = nativeObject->numFixedSlots();
|
||||
|
||||
operandIndex_ = operandIndex;
|
||||
}
|
||||
|
||||
JSObject*
|
||||
|
@ -4860,7 +4863,7 @@ MObjectState::New(TempAllocator& alloc, MDefinition* obj)
|
|||
JSObject* templateObject = templateObjectOf(obj);
|
||||
MOZ_ASSERT(templateObject, "Unexpected object creation.");
|
||||
|
||||
MObjectState* res = new(alloc) MObjectState(templateObject);
|
||||
MObjectState* res = new(alloc) MObjectState(templateObject, nullptr);
|
||||
if (!res || !res->init(alloc, obj))
|
||||
return nullptr;
|
||||
return res;
|
||||
|
|
|
@ -375,7 +375,7 @@ class AliasSet {
|
|||
Element = 1 << 1, // A Value member of obj->elements or
|
||||
// a typed object.
|
||||
UnboxedElement = 1 << 2, // An unboxed scalar or reference member of
|
||||
// typed object.
|
||||
// typed object or unboxed object.
|
||||
DynamicSlot = 1 << 3, // A Value member of obj->slots.
|
||||
FixedSlot = 1 << 4, // A Value member of obj->fixedSlots().
|
||||
DOMProperty = 1 << 5, // A DOM property
|
||||
|
@ -3758,9 +3758,14 @@ class MObjectState
|
|||
{
|
||||
private:
|
||||
uint32_t numSlots_;
|
||||
uint32_t numFixedSlots_;
|
||||
uint32_t numFixedSlots_; // valid if isUnboxed() == false.
|
||||
OperandIndexMap* operandIndex_; // valid if isUnboxed() == true.
|
||||
|
||||
MObjectState(JSObject *templateObject);
|
||||
bool isUnboxed() const {
|
||||
return operandIndex_ != nullptr;
|
||||
}
|
||||
|
||||
MObjectState(JSObject *templateObject, OperandIndexMap* operandIndex);
|
||||
explicit MObjectState(MObjectState* state);
|
||||
|
||||
MOZ_MUST_USE bool init(TempAllocator& alloc, MDefinition* obj);
|
||||
|
@ -3820,6 +3825,18 @@ class MObjectState
|
|||
setSlot(slot + numFixedSlots(), def);
|
||||
}
|
||||
|
||||
// Interface reserved for unboxed objects.
|
||||
bool hasOffset(uint32_t offset) const {
|
||||
MOZ_ASSERT(isUnboxed());
|
||||
return offset < operandIndex_->map.length() && operandIndex_->map[offset] != 0;
|
||||
}
|
||||
MDefinition* getOffset(uint32_t offset) const {
|
||||
return getOperand(operandIndex_->map[offset]);
|
||||
}
|
||||
void setOffset(uint32_t offset, MDefinition* def) {
|
||||
replaceOperand(operandIndex_->map[offset], def);
|
||||
}
|
||||
|
||||
MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override;
|
||||
bool canRecoverOnBailout() const override {
|
||||
return true;
|
||||
|
|
|
@ -126,14 +126,20 @@ MacroAssembler::guardTypeSetMightBeIncomplete(TypeSet* types, Register obj, Regi
|
|||
{
|
||||
// Type set guards might miss when an object's group changes. In this case
|
||||
// either its old group's properties will become unknown, or it will change
|
||||
// to a native object. Jump to label if this might have happened for the
|
||||
// input object.
|
||||
// to a native object with an original unboxed group. Jump to label if this
|
||||
// might have happened for the input object.
|
||||
|
||||
if (types->unknownObject()) {
|
||||
jump(label);
|
||||
return;
|
||||
}
|
||||
|
||||
loadPtr(Address(obj, JSObject::offsetOfGroup()), scratch);
|
||||
load32(Address(scratch, ObjectGroup::offsetOfFlags()), scratch);
|
||||
and32(Imm32(OBJECT_FLAG_ADDENDUM_MASK), scratch);
|
||||
branch32(Assembler::Equal,
|
||||
scratch, Imm32(ObjectGroup::addendumOriginalUnboxedGroupValue()), label);
|
||||
|
||||
for (size_t i = 0; i < types->getObjectCount(); i++) {
|
||||
if (JSObject* singleton = types->getSingletonNoBarrier(i)) {
|
||||
movePtr(ImmGCPtr(singleton), scratch);
|
||||
|
@ -462,6 +468,243 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A
|
|||
template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest,
|
||||
bool allowDouble, Register temp, Label* fail);
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output)
|
||||
{
|
||||
switch (type) {
|
||||
case JSVAL_TYPE_INT32: {
|
||||
// Handle loading an int32 into a double reg.
|
||||
if (output.type() == MIRType::Double) {
|
||||
convertInt32ToDouble(address, output.typedReg().fpu());
|
||||
break;
|
||||
}
|
||||
MOZ_FALLTHROUGH;
|
||||
}
|
||||
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
case JSVAL_TYPE_STRING: {
|
||||
Register outReg;
|
||||
if (output.hasValue()) {
|
||||
outReg = output.valueReg().scratchReg();
|
||||
} else {
|
||||
MOZ_ASSERT(output.type() == MIRTypeFromValueType(type));
|
||||
outReg = output.typedReg().gpr();
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
load8ZeroExtend(address, outReg);
|
||||
break;
|
||||
case JSVAL_TYPE_INT32:
|
||||
load32(address, outReg);
|
||||
break;
|
||||
case JSVAL_TYPE_STRING:
|
||||
loadPtr(address, outReg);
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
|
||||
if (output.hasValue())
|
||||
tagValue(type, outReg, output.valueReg());
|
||||
break;
|
||||
}
|
||||
|
||||
case JSVAL_TYPE_OBJECT:
|
||||
if (output.hasValue()) {
|
||||
Register scratch = output.valueReg().scratchReg();
|
||||
loadPtr(address, scratch);
|
||||
|
||||
Label notNull, done;
|
||||
branchPtr(Assembler::NotEqual, scratch, ImmWord(0), ¬Null);
|
||||
|
||||
moveValue(NullValue(), output.valueReg());
|
||||
jump(&done);
|
||||
|
||||
bind(¬Null);
|
||||
tagValue(JSVAL_TYPE_OBJECT, scratch, output.valueReg());
|
||||
|
||||
bind(&done);
|
||||
} else {
|
||||
// Reading null can't be possible here, as otherwise the result
|
||||
// would be a value (either because null has been read before or
|
||||
// because there is a barrier).
|
||||
Register reg = output.typedReg().gpr();
|
||||
loadPtr(address, reg);
|
||||
#ifdef DEBUG
|
||||
Label ok;
|
||||
branchTestPtr(Assembler::NonZero, reg, reg, &ok);
|
||||
assumeUnreachable("Null not possible");
|
||||
bind(&ok);
|
||||
#endif
|
||||
}
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_DOUBLE:
|
||||
// Note: doubles in unboxed objects are not accessed through other
|
||||
// views and do not need canonicalization.
|
||||
if (output.hasValue())
|
||||
loadValue(address, output.valueReg());
|
||||
else
|
||||
loadDouble(address, output.typedReg().fpu());
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
template void
|
||||
MacroAssembler::loadUnboxedProperty(Address address, JSValueType type,
|
||||
TypedOrValueRegister output);
|
||||
|
||||
template void
|
||||
MacroAssembler::loadUnboxedProperty(BaseIndex address, JSValueType type,
|
||||
TypedOrValueRegister output);
|
||||
|
||||
static void
|
||||
StoreUnboxedFailure(MacroAssembler& masm, Label* failure)
|
||||
{
|
||||
// Storing a value to an unboxed property is a fallible operation and
|
||||
// the caller must provide a failure label if a particular unboxed store
|
||||
// might fail. Sometimes, however, a store that cannot succeed (such as
|
||||
// storing a string to an int32 property) will be marked as infallible.
|
||||
// This can only happen if the code involved is unreachable.
|
||||
if (failure)
|
||||
masm.jump(failure);
|
||||
else
|
||||
masm.assumeUnreachable("Incompatible write to unboxed property");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void
|
||||
MacroAssembler::storeUnboxedProperty(T address, JSValueType type,
|
||||
const ConstantOrRegister& value, Label* failure)
|
||||
{
|
||||
switch (type) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
if (value.constant()) {
|
||||
if (value.value().isBoolean())
|
||||
store8(Imm32(value.value().toBoolean()), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else if (value.reg().hasTyped()) {
|
||||
if (value.reg().type() == MIRType::Boolean)
|
||||
store8(value.reg().typedReg().gpr(), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else {
|
||||
if (failure)
|
||||
branchTestBoolean(Assembler::NotEqual, value.reg().valueReg(), failure);
|
||||
storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_INT32:
|
||||
if (value.constant()) {
|
||||
if (value.value().isInt32())
|
||||
store32(Imm32(value.value().toInt32()), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else if (value.reg().hasTyped()) {
|
||||
if (value.reg().type() == MIRType::Int32)
|
||||
store32(value.reg().typedReg().gpr(), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else {
|
||||
if (failure)
|
||||
branchTestInt32(Assembler::NotEqual, value.reg().valueReg(), failure);
|
||||
storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ 4);
|
||||
}
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_DOUBLE:
|
||||
if (value.constant()) {
|
||||
if (value.value().isNumber()) {
|
||||
loadConstantDouble(value.value().toNumber(), ScratchDoubleReg);
|
||||
storeDouble(ScratchDoubleReg, address);
|
||||
} else {
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
}
|
||||
} else if (value.reg().hasTyped()) {
|
||||
if (value.reg().type() == MIRType::Int32) {
|
||||
convertInt32ToDouble(value.reg().typedReg().gpr(), ScratchDoubleReg);
|
||||
storeDouble(ScratchDoubleReg, address);
|
||||
} else if (value.reg().type() == MIRType::Double) {
|
||||
storeDouble(value.reg().typedReg().fpu(), address);
|
||||
} else {
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
}
|
||||
} else {
|
||||
ValueOperand reg = value.reg().valueReg();
|
||||
Label notInt32, end;
|
||||
branchTestInt32(Assembler::NotEqual, reg, ¬Int32);
|
||||
int32ValueToDouble(reg, ScratchDoubleReg);
|
||||
storeDouble(ScratchDoubleReg, address);
|
||||
jump(&end);
|
||||
bind(¬Int32);
|
||||
if (failure)
|
||||
branchTestDouble(Assembler::NotEqual, reg, failure);
|
||||
storeValue(reg, address);
|
||||
bind(&end);
|
||||
}
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_OBJECT:
|
||||
if (value.constant()) {
|
||||
if (value.value().isObjectOrNull())
|
||||
storePtr(ImmGCPtr(value.value().toObjectOrNull()), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else if (value.reg().hasTyped()) {
|
||||
MOZ_ASSERT(value.reg().type() != MIRType::Null);
|
||||
if (value.reg().type() == MIRType::Object)
|
||||
storePtr(value.reg().typedReg().gpr(), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else {
|
||||
if (failure) {
|
||||
Label ok;
|
||||
branchTestNull(Assembler::Equal, value.reg().valueReg(), &ok);
|
||||
branchTestObject(Assembler::NotEqual, value.reg().valueReg(), failure);
|
||||
bind(&ok);
|
||||
}
|
||||
storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
|
||||
}
|
||||
break;
|
||||
|
||||
case JSVAL_TYPE_STRING:
|
||||
if (value.constant()) {
|
||||
if (value.value().isString())
|
||||
storePtr(ImmGCPtr(value.value().toString()), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else if (value.reg().hasTyped()) {
|
||||
if (value.reg().type() == MIRType::String)
|
||||
storePtr(value.reg().typedReg().gpr(), address);
|
||||
else
|
||||
StoreUnboxedFailure(*this, failure);
|
||||
} else {
|
||||
if (failure)
|
||||
branchTestString(Assembler::NotEqual, value.reg().valueReg(), failure);
|
||||
storeUnboxedPayload(value.reg().valueReg(), address, /* width = */ sizeof(uintptr_t));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
MOZ_CRASH();
|
||||
}
|
||||
}
|
||||
|
||||
template void
|
||||
MacroAssembler::storeUnboxedProperty(Address address, JSValueType type,
|
||||
const ConstantOrRegister& value, Label* failure);
|
||||
|
||||
template void
|
||||
MacroAssembler::storeUnboxedProperty(BaseIndex address, JSValueType type,
|
||||
const ConstantOrRegister& value, Label* failure);
|
||||
|
||||
// Inlined version of gc::CheckAllocatorState that checks the bare essentials
|
||||
// and bails for anything that cannot be handled with our jit allocators.
|
||||
void
|
||||
|
@ -1009,6 +1252,10 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
|
|||
nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t);
|
||||
offset += sizeof(uintptr_t);
|
||||
}
|
||||
} else if (templateObj->is<UnboxedPlainObject>()) {
|
||||
storePtr(ImmWord(0), Address(obj, UnboxedPlainObject::offsetOfExpando()));
|
||||
if (initContents)
|
||||
initUnboxedObjectContents(obj, &templateObj->as<UnboxedPlainObject>());
|
||||
} else {
|
||||
MOZ_CRASH("Unknown object");
|
||||
}
|
||||
|
@ -1029,6 +1276,29 @@ MacroAssembler::initGCThing(Register obj, Register temp, JSObject* templateObj,
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject)
|
||||
{
|
||||
const UnboxedLayout& layout = templateObject->layoutDontCheckGeneration();
|
||||
|
||||
// Initialize reference fields of the object, per UnboxedPlainObject::create.
|
||||
if (const int32_t* list = layout.traceList()) {
|
||||
while (*list != -1) {
|
||||
storePtr(ImmGCPtr(GetJitContext()->runtime->names().empty),
|
||||
Address(object, UnboxedPlainObject::offsetOfData() + *list));
|
||||
list++;
|
||||
}
|
||||
list++;
|
||||
while (*list != -1) {
|
||||
storePtr(ImmWord(0),
|
||||
Address(object, UnboxedPlainObject::offsetOfData() + *list));
|
||||
list++;
|
||||
}
|
||||
// Unboxed objects don't have Values to initialize.
|
||||
MOZ_ASSERT(*(list + 1) == -1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MacroAssembler::compareStrings(JSOp op, Register left, Register right, Register result,
|
||||
Label* fail)
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "vm/ProxyObject.h"
|
||||
#include "vm/Shape.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
using mozilla::FloatingPoint;
|
||||
|
||||
|
@ -1625,6 +1626,17 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest,
|
||||
unsigned numElems = 0);
|
||||
|
||||
// Load a property from an UnboxedPlainObject.
|
||||
template <typename T>
|
||||
void loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output);
|
||||
|
||||
// Store a property to an UnboxedPlainObject, without triggering barriers.
|
||||
// If failure is null, the value definitely has a type suitable for storing
|
||||
// in the property.
|
||||
template <typename T>
|
||||
void storeUnboxedProperty(T address, JSValueType type,
|
||||
const ConstantOrRegister& value, Label* failure);
|
||||
|
||||
Register extractString(const Address& address, Register scratch) {
|
||||
return extractObject(address, scratch);
|
||||
}
|
||||
|
@ -1701,6 +1713,8 @@ class MacroAssembler : public MacroAssemblerSpecific
|
|||
LiveRegisterSet liveRegs, Label* fail,
|
||||
TypedArrayObject* templateObj, TypedArrayLength lengthKind);
|
||||
|
||||
void initUnboxedObjectContents(Register object, UnboxedPlainObject* templateObject);
|
||||
|
||||
void newGCString(Register result, Register temp, Label* fail);
|
||||
void newGCFatInlineString(Register result, Register temp, Label* fail);
|
||||
|
||||
|
|
|
@ -15,9 +15,11 @@
|
|||
#include "jit/JitcodeMap.h"
|
||||
#include "jit/JitSpewer.h"
|
||||
#include "js/TrackedOptimizationInfo.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
#include "vm/ObjectGroup-inl.h"
|
||||
#include "vm/TypeInference-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
@ -844,6 +846,8 @@ MaybeConstructorFromType(TypeSet::Type ty)
|
|||
return nullptr;
|
||||
ObjectGroup* obj = ty.group();
|
||||
TypeNewScript* newScript = obj->newScript();
|
||||
if (!newScript && obj->maybeUnboxedLayout())
|
||||
newScript = obj->unboxedLayout().newScript();
|
||||
return newScript ? newScript->function() : nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
@ -1539,12 +1540,37 @@ RObjectState::recover(JSContext* cx, SnapshotIterator& iter) const
|
|||
RootedObject object(cx, &iter.read().toObject());
|
||||
RootedValue val(cx);
|
||||
|
||||
RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
|
||||
MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
|
||||
if (object->is<UnboxedPlainObject>()) {
|
||||
const UnboxedLayout& layout = object->as<UnboxedPlainObject>().layout();
|
||||
|
||||
for (size_t i = 0; i < numSlots(); i++) {
|
||||
val = iter.read();
|
||||
nativeObject->setSlot(i, val);
|
||||
RootedId id(cx);
|
||||
RootedValue receiver(cx, ObjectValue(*object));
|
||||
const UnboxedLayout::PropertyVector& properties = layout.properties();
|
||||
for (size_t i = 0; i < properties.length(); i++) {
|
||||
val = iter.read();
|
||||
|
||||
// This is the default placeholder value of MObjectState, when no
|
||||
// properties are defined yet.
|
||||
if (val.isUndefined())
|
||||
continue;
|
||||
|
||||
id = NameToId(properties[i].name);
|
||||
ObjectOpResult result;
|
||||
|
||||
// SetProperty can only fail due to OOM.
|
||||
if (!SetProperty(cx, object, id, val, receiver, result))
|
||||
return false;
|
||||
if (!result)
|
||||
return result.reportError(cx, object, id);
|
||||
}
|
||||
} else {
|
||||
RootedNativeObject nativeObject(cx, &object->as<NativeObject>());
|
||||
MOZ_ASSERT(nativeObject->slotSpan() == numSlots());
|
||||
|
||||
for (size_t i = 0; i < numSlots(); i++) {
|
||||
val = iter.read();
|
||||
nativeObject->setSlot(i, val);
|
||||
}
|
||||
}
|
||||
|
||||
val.setObject(*object);
|
||||
|
|
|
@ -285,6 +285,10 @@ class ObjectMemoryView : public MDefinitionVisitorDefaultNoop
|
|||
void visitGuardShape(MGuardShape* ins);
|
||||
void visitFunctionEnvironment(MFunctionEnvironment* ins);
|
||||
void visitLambda(MLambda* ins);
|
||||
|
||||
private:
|
||||
void storeOffset(MInstruction* ins, size_t offset, MDefinition* value);
|
||||
void loadOffset(MInstruction* ins, size_t offset);
|
||||
};
|
||||
|
||||
const char* ObjectMemoryView::phaseName = "Scalar Replacement of Object";
|
||||
|
@ -626,6 +630,35 @@ ObjectMemoryView::visitLambda(MLambda* ins)
|
|||
ins->setIncompleteObject();
|
||||
}
|
||||
|
||||
void
|
||||
ObjectMemoryView::storeOffset(MInstruction* ins, size_t offset, MDefinition* value)
|
||||
{
|
||||
// Clone the state and update the slot value.
|
||||
MOZ_ASSERT(state_->hasOffset(offset));
|
||||
state_ = BlockState::Copy(alloc_, state_);
|
||||
if (!state_) {
|
||||
oom_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
state_->setOffset(offset, value);
|
||||
ins->block()->insertBefore(ins, state_);
|
||||
|
||||
// Remove original instruction.
|
||||
ins->block()->discard(ins);
|
||||
}
|
||||
|
||||
void
|
||||
ObjectMemoryView::loadOffset(MInstruction* ins, size_t offset)
|
||||
{
|
||||
// Replace load by the slot value.
|
||||
MOZ_ASSERT(state_->hasOffset(offset));
|
||||
ins->replaceAllUsesWith(state_->getOffset(offset));
|
||||
|
||||
// Remove original instruction.
|
||||
ins->block()->discard(ins);
|
||||
}
|
||||
|
||||
static bool
|
||||
IndexOf(MDefinition* ins, int32_t* res)
|
||||
{
|
||||
|
|
|
@ -2244,7 +2244,8 @@ IsCacheableProtoChain(JSObject* obj, JSObject* holder, bool isDOMProxy)
|
|||
if (!isDOMProxy && !obj->isNative()) {
|
||||
if (obj == holder)
|
||||
return false;
|
||||
if (!obj->is<TypedObject>())
|
||||
if (!obj->is<UnboxedPlainObject>() &&
|
||||
!obj->is<TypedObject>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -2572,6 +2573,9 @@ CheckHasNoSuchProperty(JSContext* cx, JSObject* obj, PropertyName* name,
|
|||
} else if (curObj != obj) {
|
||||
// Non-native objects are only handled as the original receiver.
|
||||
return false;
|
||||
} else if (curObj->is<UnboxedPlainObject>()) {
|
||||
if (curObj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, NameToId(name)))
|
||||
return false;
|
||||
} else if (curObj->is<TypedObject>()) {
|
||||
if (curObj->as<TypedObject>().typeDescr().hasProperty(cx->names(), NameToId(name)))
|
||||
return false;
|
||||
|
@ -2836,15 +2840,34 @@ GuardReceiverObject(MacroAssembler& masm, ReceiverGuard guard,
|
|||
{
|
||||
Address groupAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfGroup());
|
||||
Address shapeAddress(ICStubReg, receiverGuardOffset + HeapReceiverGuard::offsetOfShape());
|
||||
Address expandoAddress(object, UnboxedPlainObject::offsetOfExpando());
|
||||
|
||||
if (guard.group) {
|
||||
masm.loadPtr(groupAddress, scratch);
|
||||
masm.branchTestObjGroup(Assembler::NotEqual, object, scratch, failure);
|
||||
|
||||
if (guard.group->clasp() == &UnboxedPlainObject::class_ && !guard.shape) {
|
||||
// Guard the unboxed object has no expando object.
|
||||
masm.branchPtr(Assembler::NotEqual, expandoAddress, ImmWord(0), failure);
|
||||
}
|
||||
}
|
||||
|
||||
if (guard.shape) {
|
||||
masm.loadPtr(shapeAddress, scratch);
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
|
||||
if (guard.group && guard.group->clasp() == &UnboxedPlainObject::class_) {
|
||||
// Guard the unboxed object has a matching expando object.
|
||||
masm.branchPtr(Assembler::Equal, expandoAddress, ImmWord(0), failure);
|
||||
Label done;
|
||||
masm.push(object);
|
||||
masm.loadPtr(expandoAddress, object);
|
||||
masm.branchTestObjShape(Assembler::Equal, object, scratch, &done);
|
||||
masm.pop(object);
|
||||
masm.jump(failure);
|
||||
masm.bind(&done);
|
||||
masm.pop(object);
|
||||
} else {
|
||||
masm.branchTestObjShape(Assembler::NotEqual, object, scratch, failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4228,7 +4251,8 @@ DoNewObject(JSContext* cx, void* payload, ICNewObject_Fallback* stub, MutableHan
|
|||
return false;
|
||||
|
||||
if (!stub->invalid() &&
|
||||
!templateObject->as<PlainObject>().hasDynamicSlots())
|
||||
(templateObject->is<UnboxedPlainObject>() ||
|
||||
!templateObject->as<PlainObject>().hasDynamicSlots()))
|
||||
{
|
||||
JitCode* code = GenerateNewObjectWithTemplateCode(cx, templateObject);
|
||||
if (!code)
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/StringObject-inl.h"
|
||||
#include "vm/TypeInference-inl.h"
|
||||
#include "gc/StoreBuffer-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::jit;
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "vm/Caches-inl.h"
|
||||
#include "vm/Interpreter-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
using namespace js::gc;
|
||||
|
|
|
@ -528,6 +528,9 @@ struct JSCompartment
|
|||
// table manages references from such typed objects to their buffers.
|
||||
js::ObjectWeakMap* lazyArrayBuffers;
|
||||
|
||||
// All unboxed layouts in the compartment.
|
||||
mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
|
||||
|
||||
// WebAssembly state for the compartment.
|
||||
js::wasm::Compartment wasm;
|
||||
|
||||
|
|
|
@ -268,7 +268,7 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls)
|
|||
if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
|
||||
return Proxy::getBuiltinClass(cx, obj, cls);
|
||||
|
||||
if (obj->is<PlainObject>())
|
||||
if (obj->is<PlainObject>() || obj->is<UnboxedPlainObject>())
|
||||
*cls = ESClass::Object;
|
||||
else if (obj->is<ArrayObject>())
|
||||
*cls = ESClass::Array;
|
||||
|
|
|
@ -42,7 +42,6 @@
|
|||
#include "frontend/BytecodeCompiler.h"
|
||||
#include "gc/Marking.h"
|
||||
#include "gc/Policy.h"
|
||||
#include "gc/StoreBuffer-inl.h"
|
||||
#include "jit/BaselineJIT.h"
|
||||
#include "js/MemoryMetrics.h"
|
||||
#include "js/Proxy.h"
|
||||
|
@ -54,6 +53,7 @@
|
|||
#include "vm/RegExpStaticsObject.h"
|
||||
#include "vm/Shape.h"
|
||||
#include "vm/TypedArrayCommon.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
#include "jsatominlines.h"
|
||||
#include "jsboolinlines.h"
|
||||
|
|
|
@ -32,6 +32,19 @@
|
|||
#include "vm/ShapedObject-inl.h"
|
||||
#include "vm/TypeInference-inl.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
// This is needed here for ensureShape() below.
|
||||
inline bool
|
||||
MaybeConvertUnboxedObjectToNative(ExclusiveContext* cx, JSObject* obj)
|
||||
{
|
||||
if (obj->is<UnboxedPlainObject>())
|
||||
return UnboxedPlainObject::convertToNative(cx->asJSContext(), obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
inline js::Shape*
|
||||
JSObject::maybeShape() const
|
||||
{
|
||||
|
@ -44,6 +57,8 @@ JSObject::maybeShape() const
|
|||
inline js::Shape*
|
||||
JSObject::ensureShape(js::ExclusiveContext* cx)
|
||||
{
|
||||
if (!js::MaybeConvertUnboxedObjectToNative(cx, this))
|
||||
return nullptr;
|
||||
js::Shape* shape = maybeShape();
|
||||
MOZ_ASSERT(shape);
|
||||
return shape;
|
||||
|
|
|
@ -355,6 +355,7 @@ UNIFIED_SOURCES += [
|
|||
'vm/UbiNode.cpp',
|
||||
'vm/UbiNodeCensus.cpp',
|
||||
'vm/UbiNodeShortestPaths.cpp',
|
||||
'vm/UnboxedObject.cpp',
|
||||
'vm/Unicode.cpp',
|
||||
'vm/Value.cpp',
|
||||
'vm/WeakMapPtr.cpp',
|
||||
|
|
|
@ -7539,6 +7539,7 @@ SetWorkerContextOptions(JSContext* cx)
|
|||
.setWasm(enableWasm)
|
||||
.setWasmAlwaysBaseline(enableWasmAlwaysBaseline)
|
||||
.setNativeRegExp(enableNativeRegExp)
|
||||
.setUnboxedArrays(enableUnboxedArrays)
|
||||
.setArrayProtoValues(enableArrayProtoValues);
|
||||
cx->setOffthreadIonCompilationEnabled(offthreadCompilation);
|
||||
cx->profilingScripts = enableCodeCoverage || enableDisassemblyDumps;
|
||||
|
@ -7708,6 +7709,7 @@ main(int argc, char** argv, char** envp)
|
|||
|| !op.addBoolOption('\0', "no-asmjs", "Disable asm.js compilation")
|
||||
|| !op.addBoolOption('\0', "no-wasm", "Disable WebAssembly compilation")
|
||||
|| !op.addBoolOption('\0', "no-native-regexp", "Disable native regexp compilation")
|
||||
|| !op.addBoolOption('\0', "no-unboxed-objects", "Disable creating unboxed plain objects")
|
||||
|| !op.addBoolOption('\0', "wasm-always-baseline", "Enable wasm baseline compiler when possible")
|
||||
|| !op.addBoolOption('\0', "wasm-check-bce", "Always generate wasm bounds check, even redundant ones.")
|
||||
|| !op.addBoolOption('\0', "no-array-proto-values", "Remove Array.prototype.values")
|
||||
|
|
|
@ -5016,6 +5016,11 @@ js::NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject)
|
|||
|
||||
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
|
||||
|
||||
if (templateObject->group()->maybeUnboxedLayout()) {
|
||||
RootedObjectGroup group(cx, templateObject->group());
|
||||
return UnboxedPlainObject::create(cx, group, newKind);
|
||||
}
|
||||
|
||||
JSObject* obj = CopyInitializerObject(cx, templateObject.as<PlainObject>(), newKind);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
|
|
@ -388,6 +388,33 @@ NativeObject::setLastPropertyMakeNonNative(Shape* shape)
|
|||
shape_ = shape;
|
||||
}
|
||||
|
||||
void
|
||||
NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape)
|
||||
{
|
||||
MOZ_ASSERT(getClass()->isNative());
|
||||
MOZ_ASSERT(shape->getObjectClass()->isNative());
|
||||
MOZ_ASSERT(!shape->inDictionary());
|
||||
|
||||
// This method is used to convert unboxed objects into native objects. In
|
||||
// this case, the shape_ field was previously used to store other data and
|
||||
// this should be treated as an initialization.
|
||||
shape_.init(shape);
|
||||
|
||||
slots_ = nullptr;
|
||||
elements_ = emptyObjectElements;
|
||||
|
||||
size_t oldSpan = shape->numFixedSlots();
|
||||
size_t newSpan = shape->slotSpan();
|
||||
|
||||
initializeSlotRange(0, oldSpan);
|
||||
|
||||
// A failure at this point will leave the object as a mutant, and we
|
||||
// can't recover.
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan))
|
||||
oomUnsafe.crash("NativeObject::setLastPropertyMakeNative");
|
||||
}
|
||||
|
||||
bool
|
||||
NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span)
|
||||
{
|
||||
|
|
|
@ -470,6 +470,11 @@ class NativeObject : public ShapedObject
|
|||
// that are (temporarily) inconsistent.
|
||||
void setLastPropertyMakeNonNative(Shape* shape);
|
||||
|
||||
// As for setLastProperty(), but changes the class associated with the
|
||||
// object to a native one. The object's type has already been changed, and
|
||||
// this brings the shape into sync with it.
|
||||
void setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape);
|
||||
|
||||
// Newly-created TypedArrays that map a SharedArrayBuffer are
|
||||
// marked as shared by giving them an ObjectElements that has the
|
||||
// ObjectElements::SHARED_MEMORY flag set.
|
||||
|
|
|
@ -108,6 +108,20 @@ ObjectGroup::maybePreliminaryObjects()
|
|||
return maybePreliminaryObjectsDontCheckGeneration();
|
||||
}
|
||||
|
||||
inline UnboxedLayout*
|
||||
ObjectGroup::maybeUnboxedLayout()
|
||||
{
|
||||
maybeSweep(nullptr);
|
||||
return maybeUnboxedLayoutDontCheckGeneration();
|
||||
}
|
||||
|
||||
inline UnboxedLayout&
|
||||
ObjectGroup::unboxedLayout()
|
||||
{
|
||||
maybeSweep(nullptr);
|
||||
return unboxedLayoutDontCheckGeneration();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif /* vm_ObjectGroup_inl_h */
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
#include "vm/ArrayObject.h"
|
||||
#include "vm/Shape.h"
|
||||
#include "vm/TaggedProto.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "vm/NativeObject-inl.h"
|
||||
#include "vm/UnboxedObject-inl.h"
|
||||
|
||||
using namespace js;
|
||||
|
||||
|
@ -55,6 +56,7 @@ ObjectGroup::finalize(FreeOp* fop)
|
|||
if (newScriptDontCheckGeneration())
|
||||
newScriptDontCheckGeneration()->clear();
|
||||
fop->delete_(newScriptDontCheckGeneration());
|
||||
fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
|
||||
if (maybePreliminaryObjectsDontCheckGeneration())
|
||||
maybePreliminaryObjectsDontCheckGeneration()->clear();
|
||||
fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
|
||||
|
@ -81,6 +83,8 @@ ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
|
|||
size_t n = 0;
|
||||
if (TypeNewScript* newScript = newScriptDontCheckGeneration())
|
||||
n += newScript->sizeOfIncludingThis(mallocSizeOf);
|
||||
if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration())
|
||||
n += layout->sizeOfIncludingThis(mallocSizeOf);
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -529,7 +533,8 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
|
|||
if (p) {
|
||||
ObjectGroup* group = p->group;
|
||||
MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
|
||||
MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_);
|
||||
MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
|
||||
group->clasp() == &UnboxedPlainObject::class_);
|
||||
MOZ_ASSERT(group->proto() == proto);
|
||||
return group;
|
||||
}
|
||||
|
@ -969,6 +974,46 @@ js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj,
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (newObj->is<UnboxedPlainObject>()) {
|
||||
const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout();
|
||||
const int32_t* traceList = layout.traceList();
|
||||
if (!traceList)
|
||||
return true;
|
||||
|
||||
uint8_t* newData = newObj->as<UnboxedPlainObject>().data();
|
||||
uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data();
|
||||
|
||||
for (; *traceList != -1; traceList++) {}
|
||||
traceList++;
|
||||
for (; *traceList != -1; traceList++) {
|
||||
JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList);
|
||||
JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList);
|
||||
|
||||
if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj))
|
||||
continue;
|
||||
|
||||
if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
|
||||
return false;
|
||||
|
||||
if (SameGroup(oldInnerObj, newInnerObj))
|
||||
continue;
|
||||
|
||||
if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
|
||||
return false;
|
||||
|
||||
if (SameGroup(oldInnerObj, newInnerObj)) {
|
||||
for (size_t i = 1; i < ncompare; i++) {
|
||||
if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
|
||||
uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data();
|
||||
JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList);
|
||||
if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) {
|
||||
if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1192,6 +1237,12 @@ ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_
|
|||
|
||||
RootedObjectGroup group(cx, p->value().group);
|
||||
|
||||
// Watch for existing groups which now use an unboxed layout.
|
||||
if (group->maybeUnboxedLayout()) {
|
||||
MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties);
|
||||
return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties);
|
||||
}
|
||||
|
||||
// Update property types according to the properties we are about to add.
|
||||
// Do this before we do anything which can GC, which might move or remove
|
||||
// this table entry.
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
namespace js {
|
||||
|
||||
class TypeDescr;
|
||||
class UnboxedLayout;
|
||||
|
||||
class PreliminaryObjectArrayWithTemplate;
|
||||
class TypeNewScript;
|
||||
|
@ -153,6 +154,16 @@ class ObjectGroup : public gc::TenuredCell
|
|||
// For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate.
|
||||
Addendum_PreliminaryObjects,
|
||||
|
||||
// When objects in this group have an unboxed representation, the
|
||||
// addendum stores an UnboxedLayout (which might have a TypeNewScript
|
||||
// as well, if the group is also constructed using 'new').
|
||||
Addendum_UnboxedLayout,
|
||||
|
||||
// If this group is used by objects that have been converted from an
|
||||
// unboxed representation and/or have the same allocation kind as such
|
||||
// objects, the addendum points to that unboxed group.
|
||||
Addendum_OriginalUnboxedGroup,
|
||||
|
||||
// When used by typed objects, the addendum stores a TypeDescr.
|
||||
Addendum_TypeDescr
|
||||
};
|
||||
|
@ -174,6 +185,7 @@ class ObjectGroup : public gc::TenuredCell
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
TypeNewScript* anyNewScript();
|
||||
void detachNewScript(bool writeBarrier, ObjectGroup* replacement);
|
||||
|
||||
ObjectGroupFlags flagsDontCheckGeneration() const {
|
||||
|
@ -213,6 +225,34 @@ class ObjectGroup : public gc::TenuredCell
|
|||
maybePreliminaryObjectsDontCheckGeneration();
|
||||
}
|
||||
|
||||
inline UnboxedLayout* maybeUnboxedLayout();
|
||||
inline UnboxedLayout& unboxedLayout();
|
||||
|
||||
UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const {
|
||||
if (addendumKind() == Addendum_UnboxedLayout)
|
||||
return reinterpret_cast<UnboxedLayout*>(addendum_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UnboxedLayout& unboxedLayoutDontCheckGeneration() const {
|
||||
MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
|
||||
return *maybeUnboxedLayoutDontCheckGeneration();
|
||||
}
|
||||
|
||||
void setUnboxedLayout(UnboxedLayout* layout) {
|
||||
setAddendum(Addendum_UnboxedLayout, layout);
|
||||
}
|
||||
|
||||
ObjectGroup* maybeOriginalUnboxedGroup() const {
|
||||
if (addendumKind() == Addendum_OriginalUnboxedGroup)
|
||||
return reinterpret_cast<ObjectGroup*>(addendum_);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void setOriginalUnboxedGroup(ObjectGroup* group) {
|
||||
setAddendum(Addendum_OriginalUnboxedGroup, group);
|
||||
}
|
||||
|
||||
TypeDescr* maybeTypeDescr() {
|
||||
// Note: there is no need to sweep when accessing the type descriptor
|
||||
// of an object, as it is strongly held and immutable.
|
||||
|
@ -273,8 +313,9 @@ class ObjectGroup : public gc::TenuredCell
|
|||
* 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.
|
||||
* and unboxed objects, property types account for object and value
|
||||
* properties and elements in the object, and expando properties in unboxed
|
||||
* objects.
|
||||
*
|
||||
* For accesses on these properties, the correspondence is as follows:
|
||||
*
|
||||
|
@ -297,9 +338,10 @@ class ObjectGroup : 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
|
||||
|
@ -413,6 +455,12 @@ class ObjectGroup : public gc::TenuredCell
|
|||
return &flags_;
|
||||
}
|
||||
|
||||
// Get the bit pattern stored in an object's addendum when it has an
|
||||
// original unboxed group.
|
||||
static inline int32_t addendumOriginalUnboxedGroupValue() {
|
||||
return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT;
|
||||
}
|
||||
|
||||
inline uint32_t basePropertyCount();
|
||||
|
||||
private:
|
||||
|
@ -463,8 +511,8 @@ class ObjectGroup : public gc::TenuredCell
|
|||
NewObjectKind newKind,
|
||||
NewArrayKind arrayKind = NewArrayKind::Normal);
|
||||
|
||||
// Create a PlainObject with the specified properties and a group specialized
|
||||
// for those properties.
|
||||
// Create a PlainObject or UnboxedPlainObject with the specified properties
|
||||
// and a group specialized for those properties.
|
||||
static JSObject* newPlainObject(ExclusiveContext* cx,
|
||||
IdValuePair* properties, size_t nproperties,
|
||||
NewObjectKind newKind);
|
||||
|
|
|
@ -15,7 +15,11 @@ ReceiverGuard::ReceiverGuard(JSObject* obj)
|
|||
: group(nullptr), shape(nullptr)
|
||||
{
|
||||
if (obj) {
|
||||
if (obj->is<TypedObject>()) {
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
group = obj->group();
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
shape = expando->lastProperty();
|
||||
} else if (obj->is<TypedObject>()) {
|
||||
group = obj->group();
|
||||
} else {
|
||||
shape = obj->maybeShape();
|
||||
|
@ -28,7 +32,9 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
|
|||
{
|
||||
if (group) {
|
||||
const Class* clasp = group->clasp();
|
||||
if (IsTypedObjectClass(clasp)) {
|
||||
if (clasp == &UnboxedPlainObject::class_) {
|
||||
// Keep both group and shape.
|
||||
} else if (IsTypedObjectClass(clasp)) {
|
||||
this->shape = nullptr;
|
||||
} else {
|
||||
this->group = nullptr;
|
||||
|
@ -39,6 +45,10 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
|
|||
/* static */ int32_t
|
||||
HeapReceiverGuard::keyBits(JSObject* obj)
|
||||
{
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
// Both the group and shape need to be guarded for unboxed plain objects.
|
||||
return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1;
|
||||
}
|
||||
if (obj->is<TypedObject>()) {
|
||||
// Only the group needs to be guarded for typed objects.
|
||||
return 2;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "vm/SharedArrayObject.h"
|
||||
#include "vm/StringObject.h"
|
||||
#include "vm/TypedArrayObject.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
#include "jscntxtinlines.h"
|
||||
|
||||
|
@ -284,6 +285,10 @@ TypeIdString(jsid id)
|
|||
*/
|
||||
struct AutoEnterAnalysis
|
||||
{
|
||||
// For use when initializing an UnboxedLayout. The UniquePtr's destructor
|
||||
// must run when GC is not suppressed.
|
||||
UniquePtr<UnboxedLayout> unboxedLayoutToCleanUp;
|
||||
|
||||
// Prevent GC activity in the middle of analysis.
|
||||
gc::AutoSuppressGC suppressGC;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "vm/Opcodes.h"
|
||||
#include "vm/Shape.h"
|
||||
#include "vm/Time.h"
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
#include "jsatominlines.h"
|
||||
#include "jsscriptinlines.h"
|
||||
|
@ -296,6 +297,9 @@ js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Val
|
|||
return true;
|
||||
}
|
||||
}
|
||||
JSObject* obj = &value.toObject();
|
||||
if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup())
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!types->hasType(type)) {
|
||||
|
@ -1944,6 +1948,33 @@ class ConstraintDataFreezeObjectForTypedArrayData
|
|||
}
|
||||
};
|
||||
|
||||
// Constraint which triggers recompilation if an unboxed object in some group
|
||||
// is converted to a native object.
|
||||
class ConstraintDataFreezeObjectForUnboxedConvertedToNative
|
||||
{
|
||||
public:
|
||||
ConstraintDataFreezeObjectForUnboxedConvertedToNative()
|
||||
{}
|
||||
|
||||
const char* kind() { return "freezeObjectForUnboxedConvertedToNative"; }
|
||||
|
||||
bool invalidateOnNewType(TypeSet::Type type) { return false; }
|
||||
bool invalidateOnNewPropertyState(TypeSet* property) { return false; }
|
||||
bool invalidateOnNewObjectState(ObjectGroup* group) {
|
||||
return group->unboxedLayout().nativeGroup() != nullptr;
|
||||
}
|
||||
|
||||
bool constraintHolds(JSContext* cx,
|
||||
const HeapTypeSetKey& property, TemporaryTypeSet* expected)
|
||||
{
|
||||
return !invalidateOnNewObjectState(property.object()->maybeGroup());
|
||||
}
|
||||
|
||||
bool shouldSweep() { return false; }
|
||||
|
||||
JSCompartment* maybeCompartment() { return nullptr; }
|
||||
};
|
||||
|
||||
} /* anonymous namespace */
|
||||
|
||||
void
|
||||
|
@ -2478,6 +2509,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
|
|||
bool
|
||||
js::ClassCanHaveExtraProperties(const Class* clasp)
|
||||
{
|
||||
if (clasp == &UnboxedPlainObject::class_)
|
||||
return false;
|
||||
return clasp->getResolve()
|
||||
|| clasp->getOpsLookupProperty()
|
||||
|| clasp->getOpsGetProperty()
|
||||
|
@ -2768,6 +2801,15 @@ js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, j
|
|||
// from acquiring the fully initialized group.
|
||||
if (group->newScript() && group->newScript()->initializedGroup())
|
||||
AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type);
|
||||
|
||||
// Maintain equivalent type information for unboxed object groups and their
|
||||
// corresponding native group. Since type sets might contain the unboxed
|
||||
// group but not the native group, this ensures optimizations based on the
|
||||
// unboxed group are valid for the native group.
|
||||
if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
|
||||
AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type);
|
||||
if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup())
|
||||
AddTypePropertyId(cx, unboxedGroup, nullptr, id, type);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2839,6 +2881,12 @@ ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags)
|
|||
// acquired properties analysis.
|
||||
if (newScript() && newScript()->initializedGroup())
|
||||
newScript()->initializedGroup()->setFlags(cx, flags);
|
||||
|
||||
// Propagate flag changes between unboxed and corresponding native groups.
|
||||
if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
|
||||
maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags);
|
||||
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
|
||||
unboxedGroup->setFlags(cx, flags);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2871,6 +2919,23 @@ ObjectGroup::markUnknown(ExclusiveContext* cx)
|
|||
prop->types.setNonDataProperty(cx);
|
||||
}
|
||||
}
|
||||
|
||||
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
|
||||
MarkObjectGroupUnknownProperties(cx, unboxedGroup);
|
||||
if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup())
|
||||
MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup());
|
||||
if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup())
|
||||
MarkObjectGroupUnknownProperties(cx, unboxedGroup);
|
||||
}
|
||||
|
||||
TypeNewScript*
|
||||
ObjectGroup::anyNewScript()
|
||||
{
|
||||
if (newScript())
|
||||
return newScript();
|
||||
if (maybeUnboxedLayout())
|
||||
return unboxedLayout().newScript();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2880,7 +2945,7 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
|
|||
// analyzed, remove it from the newObjectGroups table so that it will not be
|
||||
// produced by calling 'new' on the associated function anymore.
|
||||
// The TypeNewScript is not actually destroyed.
|
||||
TypeNewScript* newScript = this->newScript();
|
||||
TypeNewScript* newScript = anyNewScript();
|
||||
MOZ_ASSERT(newScript);
|
||||
|
||||
if (newScript->analyzed()) {
|
||||
|
@ -2899,7 +2964,10 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement)
|
|||
MOZ_ASSERT(!replacement);
|
||||
}
|
||||
|
||||
setAddendum(Addendum_None, nullptr, writeBarrier);
|
||||
if (this->newScript())
|
||||
setAddendum(Addendum_None, nullptr, writeBarrier);
|
||||
else
|
||||
unboxedLayout().setNewScript(nullptr, writeBarrier);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -2910,7 +2978,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
|
|||
if (!isMarked())
|
||||
return;
|
||||
|
||||
TypeNewScript* newScript = this->newScript();
|
||||
TypeNewScript* newScript = anyNewScript();
|
||||
if (!newScript)
|
||||
return;
|
||||
|
||||
|
@ -2925,7 +2993,7 @@ ObjectGroup::maybeClearNewScriptOnOOM()
|
|||
void
|
||||
ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/)
|
||||
{
|
||||
TypeNewScript* newScript = this->newScript();
|
||||
TypeNewScript* newScript = anyNewScript();
|
||||
if (!newScript)
|
||||
return;
|
||||
|
||||
|
@ -3390,6 +3458,22 @@ PreliminaryObjectArray::sweep()
|
|||
for (size_t i = 0; i < COUNT; i++) {
|
||||
JSObject** ptr = &objects[i];
|
||||
if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) {
|
||||
// Before we clear this reference, change the object's group to the
|
||||
// Object.prototype group. This is done to ensure JSObject::finalize
|
||||
// sees a NativeObject Class even if we change the current group's
|
||||
// Class to one of the unboxed object classes in the meantime. If
|
||||
// the compartment's global is dead, we don't do anything as the
|
||||
// group's Class is not going to change in that case.
|
||||
JSObject* obj = *ptr;
|
||||
GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal();
|
||||
if (global && !obj->isSingleton()) {
|
||||
JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object);
|
||||
obj->setGroup(objectProto->groupRaw());
|
||||
MOZ_ASSERT(obj->is<NativeObject>());
|
||||
MOZ_ASSERT(obj->getClass() == objectProto->getClass());
|
||||
MOZ_ASSERT(!obj->getClass()->hasFinalize());
|
||||
}
|
||||
|
||||
*ptr = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -3489,11 +3573,16 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro
|
|||
}
|
||||
}
|
||||
|
||||
// Since the preliminary objects still reflect the template object's
|
||||
// properties, and all objects in the future will be created with those
|
||||
// properties, the properties can be marked as definitive for objects in
|
||||
// the group.
|
||||
group->addDefiniteProperties(cx, shape());
|
||||
if (group->maybeUnboxedLayout())
|
||||
return;
|
||||
|
||||
if (shape()) {
|
||||
// We weren't able to use an unboxed layout, but since the preliminary
|
||||
// objects still reflect the template object's properties, and all
|
||||
// objects in the future will be created with those properties, the
|
||||
// properties can be marked as definite for objects in the group.
|
||||
group->addDefiniteProperties(cx, shape());
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
@ -3507,6 +3596,7 @@ TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun)
|
|||
{
|
||||
MOZ_ASSERT(cx->zone()->types.activeAnalysis);
|
||||
MOZ_ASSERT(!group->newScript());
|
||||
MOZ_ASSERT(!group->maybeUnboxedLayout());
|
||||
|
||||
// rollbackPartiallyInitializedObjects expects function_ to be
|
||||
// canonicalized.
|
||||
|
@ -3814,6 +3904,27 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate,
|
|||
js_delete(preliminaryObjects);
|
||||
preliminaryObjects = nullptr;
|
||||
|
||||
if (group->maybeUnboxedLayout()) {
|
||||
// An unboxed layout was constructed for the group, and this has already
|
||||
// been hooked into it.
|
||||
MOZ_ASSERT(group->unboxedLayout().newScript() == this);
|
||||
destroyNewScript.group = nullptr;
|
||||
|
||||
// Clear out the template object, which is not used for TypeNewScripts
|
||||
// with an unboxed layout. Currently it is a mutant object with a
|
||||
// non-native group and native shape, so make it safe for GC by changing
|
||||
// its group to the default for its prototype.
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_,
|
||||
group->proto());
|
||||
if (!plainGroup)
|
||||
oomUnsafe.crash("TypeNewScript::maybeAnalyze");
|
||||
templateObject_->setGroup(plainGroup);
|
||||
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
|
||||
|
@ -3927,6 +4038,12 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* g
|
|||
continue;
|
||||
}
|
||||
|
||||
if (thisv.toObject().is<UnboxedPlainObject>()) {
|
||||
AutoEnterOOMUnsafeRegion oomUnsafe;
|
||||
if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject()))
|
||||
oomUnsafe.crash("rollbackPartiallyInitializedObjects");
|
||||
}
|
||||
|
||||
// Found a matching frame.
|
||||
RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>());
|
||||
|
||||
|
@ -4120,6 +4237,12 @@ ConstraintTypeSet::sweep(Zone* zone, AutoClearTypeInferenceStateOnOOM& oom)
|
|||
// Object sets containing objects with unknown properties might
|
||||
// not be complete. Mark the type set as unknown, which it will
|
||||
// be treated as during Ion compilation.
|
||||
//
|
||||
// Note that we don't have to do this when the type set might
|
||||
// be missing the native group corresponding to an unboxed
|
||||
// object group. In this case, the native group points to the
|
||||
// unboxed object group via its addendum, so as long as objects
|
||||
// with either group exist, neither group will be finalized.
|
||||
flags |= TYPE_FLAG_ANYOBJECT;
|
||||
clearObjects();
|
||||
objectCount = 0;
|
||||
|
@ -4203,6 +4326,21 @@ ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom)
|
|||
Maybe<AutoClearTypeInferenceStateOnOOM> fallbackOOM;
|
||||
EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM);
|
||||
|
||||
if (maybeUnboxedLayout()) {
|
||||
// Remove unboxed layouts that are about to be finalized from the
|
||||
// compartment wide list while we are still on the main thread.
|
||||
ObjectGroup* group = this;
|
||||
if (IsAboutToBeFinalizedUnbarriered(&group))
|
||||
unboxedLayout().detachFromCompartment();
|
||||
|
||||
if (unboxedLayout().newScript())
|
||||
unboxedLayout().newScript()->sweep();
|
||||
|
||||
// Discard constructor code to avoid holding onto ExecutablePools.
|
||||
if (zone()->isGCCompacting())
|
||||
unboxedLayout().setConstructorCode(nullptr);
|
||||
}
|
||||
|
||||
if (maybePreliminaryObjects())
|
||||
maybePreliminaryObjects()->sweep();
|
||||
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/* -*- 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_inl_h
|
||||
#define vm_UnboxedObject_inl_h
|
||||
|
||||
#include "vm/UnboxedObject.h"
|
||||
|
||||
#include "gc/StoreBuffer-inl.h"
|
||||
#include "vm/ArrayObject-inl.h"
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
static inline Value
|
||||
GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized)
|
||||
{
|
||||
switch (type) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
return BooleanValue(*p != 0);
|
||||
|
||||
case JSVAL_TYPE_INT32:
|
||||
return Int32Value(*reinterpret_cast<int32_t*>(p));
|
||||
|
||||
case JSVAL_TYPE_DOUBLE: {
|
||||
// During unboxed plain object creation, non-GC thing properties are
|
||||
// left uninitialized. This is normally fine, since the properties will
|
||||
// be filled in shortly, but if they are read before that happens we
|
||||
// need to make sure that doubles are canonical.
|
||||
double d = *reinterpret_cast<double*>(p);
|
||||
if (maybeUninitialized)
|
||||
return DoubleValue(JS::CanonicalizeNaN(d));
|
||||
return DoubleValue(d);
|
||||
}
|
||||
|
||||
case JSVAL_TYPE_STRING:
|
||||
return StringValue(*reinterpret_cast<JSString**>(p));
|
||||
|
||||
case JSVAL_TYPE_OBJECT:
|
||||
return ObjectOrNullValue(*reinterpret_cast<JSObject**>(p));
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Invalid type for unboxed value");
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
SetUnboxedValueNoTypeChange(JSObject* unboxedObject,
|
||||
uint8_t* p, JSValueType type, const Value& v,
|
||||
bool preBarrier)
|
||||
{
|
||||
switch (type) {
|
||||
case JSVAL_TYPE_BOOLEAN:
|
||||
*p = v.toBoolean();
|
||||
return;
|
||||
|
||||
case JSVAL_TYPE_INT32:
|
||||
*reinterpret_cast<int32_t*>(p) = v.toInt32();
|
||||
return;
|
||||
|
||||
case JSVAL_TYPE_DOUBLE:
|
||||
*reinterpret_cast<double*>(p) = v.toNumber();
|
||||
return;
|
||||
|
||||
case JSVAL_TYPE_STRING: {
|
||||
MOZ_ASSERT(!IsInsideNursery(v.toString()));
|
||||
JSString** np = reinterpret_cast<JSString**>(p);
|
||||
if (preBarrier)
|
||||
JSString::writeBarrierPre(*np);
|
||||
*np = v.toString();
|
||||
return;
|
||||
}
|
||||
|
||||
case JSVAL_TYPE_OBJECT: {
|
||||
JSObject** np = reinterpret_cast<JSObject**>(p);
|
||||
|
||||
// Manually trigger post barriers on the whole object. If we treat
|
||||
// the pointer as a HeapPtrObject we will get confused later if the
|
||||
// object is converted to its native representation.
|
||||
JSObject* obj = v.toObjectOrNull();
|
||||
if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) {
|
||||
JSRuntime* rt = unboxedObject->runtimeFromMainThread();
|
||||
rt->gc.storeBuffer.putWholeCell(unboxedObject);
|
||||
}
|
||||
|
||||
if (preBarrier)
|
||||
JSObject::writeBarrierPre(*np);
|
||||
*np = obj;
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Invalid type for unboxed value");
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id,
|
||||
uint8_t* p, JSValueType type, const Value& v, bool preBarrier)
|
||||
{
|
||||
switch (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()) {
|
||||
MOZ_ASSERT(!IsInsideNursery(v.toString()));
|
||||
JSString** np = reinterpret_cast<JSString**>(p);
|
||||
if (preBarrier)
|
||||
JSString::writeBarrierPre(*np);
|
||||
*np = v.toString();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
case JSVAL_TYPE_OBJECT:
|
||||
if (v.isObjectOrNull()) {
|
||||
JSObject** np = reinterpret_cast<JSObject**>(p);
|
||||
|
||||
// Update property types when writing object properties. Types for
|
||||
// other properties were captured when the unboxed layout was
|
||||
// created.
|
||||
AddTypePropertyId(cx, unboxedObject, id, v);
|
||||
|
||||
// As above, trigger post barriers on the whole object.
|
||||
JSObject* obj = v.toObjectOrNull();
|
||||
if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) {
|
||||
JSRuntime* rt = unboxedObject->runtimeFromMainThread();
|
||||
rt->gc.storeBuffer.putWholeCell(unboxedObject);
|
||||
}
|
||||
|
||||
if (preBarrier)
|
||||
JSObject::writeBarrierPre(*np);
|
||||
*np = obj;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
default:
|
||||
MOZ_CRASH("Invalid type for unboxed value");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// UnboxedPlainObject
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
inline const UnboxedLayout&
|
||||
UnboxedPlainObject::layout() const
|
||||
{
|
||||
return group()->unboxedLayout();
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
#endif // vm_UnboxedObject_inl_h
|
|
@ -0,0 +1,946 @@
|
|||
/* -*- 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-inl.h"
|
||||
|
||||
#include "jit/BaselineIC.h"
|
||||
#include "jit/ExecutableAllocator.h"
|
||||
#include "jit/JitCommon.h"
|
||||
#include "jit/Linker.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
||||
#include "gc/Nursery-inl.h"
|
||||
#include "jit/MacroAssembler-inl.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++)
|
||||
TraceManuallyBarrieredEdge(trc, &properties_[i].name, "unboxed_layout_name");
|
||||
|
||||
if (newScript())
|
||||
newScript()->trace(trc);
|
||||
|
||||
TraceNullableEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup");
|
||||
TraceNullableEdge(trc, &nativeShape_, "unboxed_layout_nativeShape");
|
||||
TraceNullableEdge(trc, &allocationScript_, "unboxed_layout_allocationScript");
|
||||
TraceNullableEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup");
|
||||
TraceNullableEdge(trc, &constructorCode_, "unboxed_layout_constructorCode");
|
||||
}
|
||||
|
||||
size_t
|
||||
UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
|
||||
{
|
||||
return mallocSizeOf(this)
|
||||
+ properties_.sizeOfExcludingThis(mallocSizeOf)
|
||||
+ (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0)
|
||||
+ mallocSizeOf(traceList());
|
||||
}
|
||||
|
||||
void
|
||||
UnboxedLayout::setNewScript(TypeNewScript* newScript, bool writeBarrier /* = true */)
|
||||
{
|
||||
if (newScript_ && writeBarrier)
|
||||
TypeNewScript::writeBarrierPre(newScript_);
|
||||
newScript_ = newScript;
|
||||
}
|
||||
|
||||
// Constructor code returns a 0x1 value to indicate the constructor code should
|
||||
// be cleared.
|
||||
static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1;
|
||||
|
||||
/* static */ bool
|
||||
UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group)
|
||||
{
|
||||
gc::AutoSuppressGC suppress(cx);
|
||||
|
||||
using namespace jit;
|
||||
|
||||
if (!cx->compartment()->ensureJitCompartmentExists(cx))
|
||||
return false;
|
||||
|
||||
UnboxedLayout& layout = group->unboxedLayout();
|
||||
MOZ_ASSERT(!layout.constructorCode());
|
||||
|
||||
UnboxedPlainObject* templateObject = UnboxedPlainObject::create(cx, group, TenuredObject);
|
||||
if (!templateObject)
|
||||
return false;
|
||||
|
||||
JitContext jitContext(cx, nullptr);
|
||||
|
||||
MacroAssembler masm;
|
||||
|
||||
Register propertiesReg, newKindReg;
|
||||
#ifdef JS_CODEGEN_X86
|
||||
propertiesReg = eax;
|
||||
newKindReg = ecx;
|
||||
masm.loadPtr(Address(masm.getStackPointer(), sizeof(void*)), propertiesReg);
|
||||
masm.loadPtr(Address(masm.getStackPointer(), 2 * sizeof(void*)), newKindReg);
|
||||
#else
|
||||
propertiesReg = IntArgReg0;
|
||||
newKindReg = IntArgReg1;
|
||||
#endif
|
||||
|
||||
#ifdef JS_CODEGEN_ARM64
|
||||
// ARM64 communicates stack address via sp, but uses a pseudo-sp for addressing.
|
||||
masm.initStackPtr();
|
||||
#endif
|
||||
|
||||
MOZ_ASSERT(propertiesReg.volatile_());
|
||||
MOZ_ASSERT(newKindReg.volatile_());
|
||||
|
||||
AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All());
|
||||
regs.take(propertiesReg);
|
||||
regs.take(newKindReg);
|
||||
Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny();
|
||||
|
||||
LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs);
|
||||
masm.PushRegsInMask(savedNonVolatileRegisters);
|
||||
|
||||
// The scratch double register might be used by MacroAssembler methods.
|
||||
if (ScratchDoubleReg.volatile_())
|
||||
masm.push(ScratchDoubleReg);
|
||||
|
||||
Label failure, tenuredObject, allocated;
|
||||
masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject);
|
||||
masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()),
|
||||
Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject);
|
||||
|
||||
// Allocate an object in the nursery
|
||||
masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure,
|
||||
/* initFixedSlots = */ false);
|
||||
|
||||
masm.jump(&allocated);
|
||||
masm.bind(&tenuredObject);
|
||||
|
||||
// Allocate an object in the tenured heap.
|
||||
masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure,
|
||||
/* initFixedSlots = */ false);
|
||||
|
||||
// If any of the properties being stored are in the nursery, add a store
|
||||
// buffer entry for the new object.
|
||||
Label postBarrier;
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
const UnboxedLayout::Property& property = layout.properties()[i];
|
||||
if (property.type == JSVAL_TYPE_OBJECT) {
|
||||
Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
|
||||
Label notObject;
|
||||
masm.branchTestObject(Assembler::NotEqual, valueAddress, ¬Object);
|
||||
Register valueObject = masm.extractObject(valueAddress, scratch1);
|
||||
masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier);
|
||||
masm.bind(¬Object);
|
||||
}
|
||||
}
|
||||
|
||||
masm.jump(&allocated);
|
||||
masm.bind(&postBarrier);
|
||||
|
||||
LiveGeneralRegisterSet liveVolatileRegisters;
|
||||
liveVolatileRegisters.add(propertiesReg);
|
||||
if (object.volatile_())
|
||||
liveVolatileRegisters.add(object);
|
||||
masm.PushRegsInMask(liveVolatileRegisters);
|
||||
|
||||
masm.mov(ImmPtr(cx->runtime()), scratch1);
|
||||
masm.setupUnalignedABICall(scratch2);
|
||||
masm.passABIArg(scratch1);
|
||||
masm.passABIArg(object);
|
||||
masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier));
|
||||
|
||||
masm.PopRegsInMask(liveVolatileRegisters);
|
||||
|
||||
masm.bind(&allocated);
|
||||
|
||||
ValueOperand valueOperand;
|
||||
#ifdef JS_NUNBOX32
|
||||
valueOperand = ValueOperand(scratch1, scratch2);
|
||||
#else
|
||||
valueOperand = ValueOperand(scratch1);
|
||||
#endif
|
||||
|
||||
Label failureStoreOther, failureStoreObject;
|
||||
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
const UnboxedLayout::Property& property = layout.properties()[i];
|
||||
Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value));
|
||||
Address targetAddress(object, UnboxedPlainObject::offsetOfData() + property.offset);
|
||||
|
||||
masm.loadValue(valueAddress, valueOperand);
|
||||
|
||||
if (property.type == JSVAL_TYPE_OBJECT) {
|
||||
HeapTypeSet* types = group->maybeGetProperty(IdToTypeId(NameToId(property.name)));
|
||||
|
||||
Label notObject;
|
||||
masm.branchTestObject(Assembler::NotEqual, valueOperand,
|
||||
types->mightBeMIRType(MIRType::Null) ? ¬Object : &failureStoreObject);
|
||||
|
||||
Register payloadReg = masm.extractObject(valueOperand, scratch1);
|
||||
|
||||
if (!types->hasType(TypeSet::AnyObjectType())) {
|
||||
Register scratch = (payloadReg == scratch1) ? scratch2 : scratch1;
|
||||
masm.guardObjectType(payloadReg, types, scratch, &failureStoreObject);
|
||||
}
|
||||
|
||||
masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT,
|
||||
TypedOrValueRegister(MIRType::Object,
|
||||
AnyRegister(payloadReg)), nullptr);
|
||||
|
||||
if (notObject.used()) {
|
||||
Label done;
|
||||
masm.jump(&done);
|
||||
masm.bind(¬Object);
|
||||
masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
|
||||
masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, NullValue(), nullptr);
|
||||
masm.bind(&done);
|
||||
}
|
||||
} else {
|
||||
masm.storeUnboxedProperty(targetAddress, property.type,
|
||||
ConstantOrRegister(valueOperand), &failureStoreOther);
|
||||
}
|
||||
}
|
||||
|
||||
Label done;
|
||||
masm.bind(&done);
|
||||
|
||||
if (object != ReturnReg)
|
||||
masm.movePtr(object, ReturnReg);
|
||||
|
||||
// Restore non-volatile registers which were saved on entry.
|
||||
if (ScratchDoubleReg.volatile_())
|
||||
masm.pop(ScratchDoubleReg);
|
||||
masm.PopRegsInMask(savedNonVolatileRegisters);
|
||||
|
||||
masm.abiret();
|
||||
|
||||
masm.bind(&failureStoreOther);
|
||||
|
||||
// There was a failure while storing a value which cannot be stored at all
|
||||
// in the unboxed object. Initialize the object so it is safe for GC and
|
||||
// return null.
|
||||
masm.initUnboxedObjectContents(object, templateObject);
|
||||
|
||||
masm.bind(&failure);
|
||||
|
||||
masm.movePtr(ImmWord(0), object);
|
||||
masm.jump(&done);
|
||||
|
||||
masm.bind(&failureStoreObject);
|
||||
|
||||
// There was a failure while storing a value to an object slot of the
|
||||
// unboxed object. If the value is storable, the failure occurred due to
|
||||
// incomplete type information in the object, so return a token to trigger
|
||||
// regeneration of the jitcode after a new object is created in the VM.
|
||||
{
|
||||
Label isObject;
|
||||
masm.branchTestObject(Assembler::Equal, valueOperand, &isObject);
|
||||
masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther);
|
||||
masm.bind(&isObject);
|
||||
}
|
||||
|
||||
// Initialize the object so it is safe for GC.
|
||||
masm.initUnboxedObjectContents(object, templateObject);
|
||||
|
||||
masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object);
|
||||
masm.jump(&done);
|
||||
|
||||
Linker linker(masm);
|
||||
AutoFlushICache afc("UnboxedObject");
|
||||
JitCode* code = linker.newCode<NoGC>(cx, OTHER_CODE);
|
||||
if (!code)
|
||||
return false;
|
||||
|
||||
layout.setConstructorCode(code);
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
UnboxedLayout::detachFromCompartment()
|
||||
{
|
||||
if (isInList())
|
||||
remove();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// UnboxedPlainObject
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool
|
||||
UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property,
|
||||
const Value& v)
|
||||
{
|
||||
uint8_t* p = &data_[property.offset];
|
||||
return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v,
|
||||
/* preBarrier = */ true);
|
||||
}
|
||||
|
||||
Value
|
||||
UnboxedPlainObject::getValue(const UnboxedLayout::Property& property,
|
||||
bool maybeUninitialized /* = false */)
|
||||
{
|
||||
uint8_t* p = &data_[property.offset];
|
||||
return GetUnboxedValue(p, property.type, maybeUninitialized);
|
||||
}
|
||||
|
||||
void
|
||||
UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj)
|
||||
{
|
||||
if (obj->as<UnboxedPlainObject>().expando_) {
|
||||
TraceManuallyBarrieredEdge(trc,
|
||||
reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_),
|
||||
"unboxed_expando");
|
||||
}
|
||||
|
||||
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration();
|
||||
const int32_t* list = layout.traceList();
|
||||
if (!list)
|
||||
return;
|
||||
|
||||
uint8_t* data = obj->as<UnboxedPlainObject>().data();
|
||||
while (*list != -1) {
|
||||
GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
|
||||
TraceEdge(trc, heap, "unboxed_string");
|
||||
list++;
|
||||
}
|
||||
list++;
|
||||
while (*list != -1) {
|
||||
GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
|
||||
TraceNullableEdge(trc, heap, "unboxed_object");
|
||||
list++;
|
||||
}
|
||||
|
||||
// Unboxed objects don't have Values to trace.
|
||||
MOZ_ASSERT(*(list + 1) == -1);
|
||||
}
|
||||
|
||||
/* static */ UnboxedExpandoObject*
|
||||
UnboxedPlainObject::ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj)
|
||||
{
|
||||
if (obj->expando_)
|
||||
return obj->expando_;
|
||||
|
||||
UnboxedExpandoObject* expando =
|
||||
NewObjectWithGivenProto<UnboxedExpandoObject>(cx, nullptr, gc::AllocKind::OBJECT4);
|
||||
if (!expando)
|
||||
return nullptr;
|
||||
|
||||
// Don't track property types for expando objects. This allows Baseline
|
||||
// and Ion AddSlot ICs to guard on the unboxed group without guarding on
|
||||
// the expando group.
|
||||
MarkObjectGroupUnknownProperties(cx, expando->group());
|
||||
|
||||
// If the expando is tenured then the original object must also be tenured.
|
||||
// Otherwise barriers triggered on the original object for writes to the
|
||||
// expando (as can happen in the JIT) won't see the tenured->nursery edge.
|
||||
// See WholeCellEdges::mark.
|
||||
MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj));
|
||||
|
||||
// As with setValue(), we need to manually trigger post barriers on the
|
||||
// whole object. If we treat the field as a GCPtrObject and later
|
||||
// convert the object to its native representation, we will end up with a
|
||||
// corrupted store buffer entry.
|
||||
if (IsInsideNursery(expando) && !IsInsideNursery(obj))
|
||||
cx->runtime()->gc.storeBuffer.putWholeCell(obj);
|
||||
|
||||
obj->expando_ = expando;
|
||||
return expando;
|
||||
}
|
||||
|
||||
bool
|
||||
UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const
|
||||
{
|
||||
if (layout().lookup(id))
|
||||
return true;
|
||||
|
||||
if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup)
|
||||
{
|
||||
HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id);
|
||||
TypeSet::TypeList types;
|
||||
if (!typeProperty->enumerateTypes(&types)) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
for (size_t j = 0; j < types.length(); j++)
|
||||
AddTypePropertyId(cx, newGroup, nullptr, id, types[j]);
|
||||
return true;
|
||||
}
|
||||
|
||||
static PlainObject*
|
||||
MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout)
|
||||
{
|
||||
PlainObject* obj = NewObjectWithGroup<PlainObject>(cx, group, layout.getAllocKind(),
|
||||
TenuredObject);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
const UnboxedLayout::Property& property = layout.properties()[i];
|
||||
if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE))
|
||||
return nullptr;
|
||||
MOZ_ASSERT(obj->slotSpan() == i + 1);
|
||||
MOZ_ASSERT(!obj->inDictionaryMode());
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
|
||||
{
|
||||
AutoEnterAnalysis enter(cx);
|
||||
|
||||
UnboxedLayout& layout = group->unboxedLayout();
|
||||
Rooted<TaggedProto> proto(cx, group->proto());
|
||||
|
||||
MOZ_ASSERT(!layout.nativeGroup());
|
||||
|
||||
RootedObjectGroup replacementGroup(cx);
|
||||
|
||||
// Immediately clear any new script on the group. This is done by replacing
|
||||
// the existing new script with one for a replacement default new group.
|
||||
// This is done so that the size of the replacment group's objects is the
|
||||
// same as that for the unboxed group, so that we do not see polymorphic
|
||||
// slot accesses later on for sites that see converted objects from this
|
||||
// group and objects that were allocated using the replacement new group.
|
||||
if (layout.newScript()) {
|
||||
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
|
||||
if (!replacementGroup)
|
||||
return false;
|
||||
|
||||
PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout);
|
||||
if (!templateObject)
|
||||
return false;
|
||||
|
||||
TypeNewScript* replacementNewScript =
|
||||
TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject);
|
||||
if (!replacementNewScript)
|
||||
return false;
|
||||
|
||||
replacementGroup->setNewScript(replacementNewScript);
|
||||
gc::TraceTypeNewScript(replacementGroup);
|
||||
|
||||
group->clearNewScript(cx, replacementGroup);
|
||||
}
|
||||
|
||||
// Similarly, if this group is keyed to an allocation site, replace its
|
||||
// entry with a new group that has no unboxed layout.
|
||||
if (layout.allocationScript()) {
|
||||
RootedScript script(cx, layout.allocationScript());
|
||||
jsbytecode* pc = layout.allocationPc();
|
||||
|
||||
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
|
||||
if (!replacementGroup)
|
||||
return false;
|
||||
|
||||
PlainObject* templateObject = &script->getObject(pc)->as<PlainObject>();
|
||||
replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty());
|
||||
|
||||
cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object,
|
||||
replacementGroup);
|
||||
|
||||
// Clear any baseline information at this opcode which might use the old group.
|
||||
if (script->hasBaselineScript()) {
|
||||
jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc));
|
||||
jit::ICFallbackStub* fallback = entry.fallbackStub();
|
||||
for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++)
|
||||
iter.unlink(cx);
|
||||
if (fallback->isNewObject_Fallback())
|
||||
fallback->toNewObject_Fallback()->setTemplateObject(nullptr);
|
||||
else if (fallback->isNewArray_Fallback())
|
||||
fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup);
|
||||
}
|
||||
}
|
||||
|
||||
size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind());
|
||||
|
||||
RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0));
|
||||
if (!shape)
|
||||
return false;
|
||||
|
||||
// Add shapes for each property, if this is for a plain object.
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
const UnboxedLayout::Property& property = layout.properties()[i];
|
||||
|
||||
Rooted<StackShape> child(cx, StackShape(shape->base()->unowned(), NameToId(property.name),
|
||||
i, JSPROP_ENUMERATE, 0));
|
||||
shape = cx->zone()->propertyTree.getChild(cx, shape, child);
|
||||
if (!shape)
|
||||
return false;
|
||||
}
|
||||
|
||||
ObjectGroup* nativeGroup =
|
||||
ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto,
|
||||
group->flags() & OBJECT_FLAG_DYNAMIC_MASK);
|
||||
if (!nativeGroup)
|
||||
return false;
|
||||
|
||||
// No sense propagating if we don't know what we started with.
|
||||
if (!group->unknownProperties()) {
|
||||
// Propagate all property types from the old group to the new group.
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
const UnboxedLayout::Property& property = layout.properties()[i];
|
||||
jsid id = NameToId(property.name);
|
||||
if (!PropagatePropertyTypes(cx, id, group, nativeGroup))
|
||||
return false;
|
||||
|
||||
// If we are OOM we may not be able to propagate properties.
|
||||
if (nativeGroup->unknownProperties())
|
||||
break;
|
||||
|
||||
HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id);
|
||||
if (nativeProperty && nativeProperty->canSetDefinite(i))
|
||||
nativeProperty->setDefinite(i);
|
||||
}
|
||||
} else {
|
||||
// If we skip, though, the new group had better agree.
|
||||
MOZ_ASSERT(nativeGroup->unknownProperties());
|
||||
}
|
||||
|
||||
layout.nativeGroup_ = nativeGroup;
|
||||
layout.nativeShape_ = shape;
|
||||
layout.replacementGroup_ = replacementGroup;
|
||||
|
||||
nativeGroup->setOriginalUnboxedGroup(group);
|
||||
|
||||
group->markStateChange(cx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj)
|
||||
{
|
||||
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
|
||||
UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando();
|
||||
|
||||
if (!layout.nativeGroup()) {
|
||||
if (!UnboxedLayout::makeNativeGroup(cx, obj->group()))
|
||||
return false;
|
||||
|
||||
// makeNativeGroup can reentrantly invoke this method.
|
||||
if (obj->is<PlainObject>())
|
||||
return true;
|
||||
}
|
||||
|
||||
AutoValueVector values(cx);
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
// We might be reading properties off the object which have not been
|
||||
// initialized yet. Make sure any double values we read here are
|
||||
// canonicalized.
|
||||
if (!values.append(obj->as<UnboxedPlainObject>().getValue(layout.properties()[i], true)))
|
||||
return false;
|
||||
}
|
||||
|
||||
// We are eliminating the expando edge with the conversion, so trigger a
|
||||
// pre barrier.
|
||||
JSObject::writeBarrierPre(expando);
|
||||
|
||||
// Additionally trigger a post barrier on the expando itself. Whole cell
|
||||
// store buffer entries can be added on the original unboxed object for
|
||||
// writes to the expando (see WholeCellEdges::trace), so after conversion
|
||||
// we need to make sure the expando itself will still be traced.
|
||||
if (expando && !IsInsideNursery(expando))
|
||||
cx->runtime()->gc.storeBuffer.putWholeCell(expando);
|
||||
|
||||
obj->setGroup(layout.nativeGroup());
|
||||
obj->as<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape());
|
||||
|
||||
for (size_t i = 0; i < values.length(); i++)
|
||||
obj->as<PlainObject>().initSlotUnchecked(i, values[i]);
|
||||
|
||||
if (expando) {
|
||||
// Add properties from the expando object to the object, in order.
|
||||
// Suppress GC here, so that callers don't need to worry about this
|
||||
// method collecting. The stuff below can only fail due to OOM, in
|
||||
// which case the object will not have been completely filled back in.
|
||||
gc::AutoSuppressGC suppress(cx);
|
||||
|
||||
Vector<jsid> ids(cx);
|
||||
for (Shape::Range<NoGC> r(expando->lastProperty()); !r.empty(); r.popFront()) {
|
||||
if (!ids.append(r.front().propid()))
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) {
|
||||
if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
|
||||
if (!ids.append(INT_TO_JSID(i)))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
::Reverse(ids.begin(), ids.end());
|
||||
|
||||
RootedPlainObject nobj(cx, &obj->as<PlainObject>());
|
||||
Rooted<UnboxedExpandoObject*> nexpando(cx, expando);
|
||||
RootedId id(cx);
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
for (size_t i = 0; i < ids.length(); i++) {
|
||||
id = ids[i];
|
||||
if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc))
|
||||
return false;
|
||||
ObjectOpResult result;
|
||||
if (!DefineProperty(cx, nobj, id, desc, result))
|
||||
return false;
|
||||
MOZ_ASSERT(result.ok());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
UnboxedPlainObject*
|
||||
UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind)
|
||||
{
|
||||
AutoSetNewObjectMetadata metadata(cx);
|
||||
|
||||
MOZ_ASSERT(group->clasp() == &class_);
|
||||
gc::AllocKind allocKind = group->unboxedLayout().getAllocKind();
|
||||
|
||||
UnboxedPlainObject* res =
|
||||
NewObjectWithGroup<UnboxedPlainObject>(cx, group, allocKind, newKind);
|
||||
if (!res)
|
||||
return nullptr;
|
||||
|
||||
// Overwrite the dummy shape which was written to the object's expando field.
|
||||
res->initExpando();
|
||||
|
||||
// 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) {
|
||||
GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list);
|
||||
heap->init(cx->names().empty);
|
||||
list++;
|
||||
}
|
||||
list++;
|
||||
while (*list != -1) {
|
||||
GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(data + *list);
|
||||
heap->init(nullptr);
|
||||
list++;
|
||||
}
|
||||
// Unboxed objects don't have Values to initialize.
|
||||
MOZ_ASSERT(*(list + 1) == -1);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/* static */ JSObject*
|
||||
UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
|
||||
NewObjectKind newKind, IdValuePair* properties)
|
||||
{
|
||||
MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject);
|
||||
|
||||
UnboxedLayout& layout = group->unboxedLayout();
|
||||
|
||||
if (layout.constructorCode()) {
|
||||
MOZ_ASSERT(cx->isJSContext());
|
||||
|
||||
typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind);
|
||||
ConstructorCodeSignature function =
|
||||
reinterpret_cast<ConstructorCodeSignature>(layout.constructorCode()->raw());
|
||||
|
||||
JSObject* obj;
|
||||
{
|
||||
JS::AutoSuppressGCAnalysis nogc;
|
||||
obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind));
|
||||
}
|
||||
if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
|
||||
return obj;
|
||||
|
||||
if (obj == reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN))
|
||||
layout.setConstructorCode(nullptr);
|
||||
}
|
||||
|
||||
UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind);
|
||||
if (!obj)
|
||||
return nullptr;
|
||||
|
||||
for (size_t i = 0; i < layout.properties().length(); i++) {
|
||||
if (!obj->setValue(cx, layout.properties()[i], properties[i].value))
|
||||
return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind);
|
||||
}
|
||||
|
||||
#ifndef JS_CODEGEN_NONE
|
||||
if (cx->isJSContext() &&
|
||||
!group->unknownProperties() &&
|
||||
!layout.constructorCode() &&
|
||||
cx->asJSContext()->runtime()->jitSupportsFloatingPoint &&
|
||||
jit::CanLikelyAllocateMoreExecutableMemory())
|
||||
{
|
||||
if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group))
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj,
|
||||
HandleId id, MutableHandleObject objp,
|
||||
MutableHandleShape propp)
|
||||
{
|
||||
if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
|
||||
MarkNonNativePropertyFound<CanGC>(propp);
|
||||
objp.set(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
RootedObject proto(cx, obj->staticPrototype());
|
||||
if (!proto) {
|
||||
objp.set(nullptr);
|
||||
propp.set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
return LookupProperty(cx, proto, id, objp, propp);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||
Handle<PropertyDescriptor> desc,
|
||||
ObjectOpResult& result)
|
||||
{
|
||||
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
|
||||
|
||||
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
|
||||
if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
|
||||
// This define is equivalent to setting an existing property.
|
||||
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, desc.value()))
|
||||
return result.succeed();
|
||||
}
|
||||
|
||||
// Trying to incompatibly redefine an existing property requires the
|
||||
// object to be converted to a native object.
|
||||
if (!convertToNative(cx, obj))
|
||||
return false;
|
||||
|
||||
return DefineProperty(cx, obj, id, desc, result);
|
||||
}
|
||||
|
||||
// Define the property on the expando object.
|
||||
Rooted<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>()));
|
||||
if (!expando)
|
||||
return false;
|
||||
|
||||
// Update property types on the unboxed object as well.
|
||||
AddTypePropertyId(cx, obj, id, desc.value());
|
||||
|
||||
return DefineProperty(cx, expando, id, desc, result);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
|
||||
{
|
||||
if (obj->as<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) {
|
||||
*foundp = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
RootedObject proto(cx, obj->staticPrototype());
|
||||
if (!proto) {
|
||||
*foundp = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return HasProperty(cx, proto, id, foundp);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue 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;
|
||||
}
|
||||
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
if (expando->containsShapeOrElement(cx, id)) {
|
||||
RootedObject nexpando(cx, expando);
|
||||
return GetProperty(cx, nexpando, receiver, id, vp);
|
||||
}
|
||||
}
|
||||
|
||||
RootedObject proto(cx, obj->staticPrototype());
|
||||
if (!proto) {
|
||||
vp.setUndefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
return GetProperty(cx, proto, receiver, id, vp);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
|
||||
HandleValue receiver, ObjectOpResult& result)
|
||||
{
|
||||
const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout();
|
||||
|
||||
if (const UnboxedLayout::Property* property = layout.lookup(id)) {
|
||||
if (receiver.isObject() && obj == &receiver.toObject()) {
|
||||
if (obj->as<UnboxedPlainObject>().setValue(cx, *property, v))
|
||||
return result.succeed();
|
||||
|
||||
if (!convertToNative(cx, obj))
|
||||
return false;
|
||||
return SetProperty(cx, obj, id, v, receiver, result);
|
||||
}
|
||||
|
||||
return SetPropertyByDefining(cx, id, v, receiver, result);
|
||||
}
|
||||
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
if (expando->containsShapeOrElement(cx, id)) {
|
||||
// Update property types on the unboxed object as well.
|
||||
AddTypePropertyId(cx, obj, id, v);
|
||||
|
||||
RootedObject nexpando(cx, expando);
|
||||
return SetProperty(cx, nexpando, id, v, receiver, result);
|
||||
}
|
||||
}
|
||||
|
||||
return SetPropertyOnProto(cx, obj, id, v, receiver, result);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
|
||||
MutableHandle<PropertyDescriptor> 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;
|
||||
}
|
||||
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) {
|
||||
if (expando->containsShapeOrElement(cx, id)) {
|
||||
RootedObject nexpando(cx, expando);
|
||||
if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc))
|
||||
return false;
|
||||
if (desc.object() == nexpando)
|
||||
desc.object().set(obj);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
desc.object().set(nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||
ObjectOpResult& result)
|
||||
{
|
||||
if (!convertToNative(cx, obj))
|
||||
return false;
|
||||
return DeleteProperty(cx, obj, id, result);
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
UnboxedPlainObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
|
||||
bool enumerableOnly)
|
||||
{
|
||||
// Ignore expando properties here, they are special-cased by the property
|
||||
// enumeration code.
|
||||
|
||||
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 UnboxedExpandoObject::class_ = {
|
||||
"UnboxedExpandoObject",
|
||||
0
|
||||
};
|
||||
|
||||
static const ClassOps UnboxedPlainObjectClassOps = {
|
||||
nullptr, /* addProperty */
|
||||
nullptr, /* delProperty */
|
||||
nullptr, /* getProperty */
|
||||
nullptr, /* setProperty */
|
||||
nullptr, /* enumerate */
|
||||
nullptr, /* resolve */
|
||||
nullptr, /* mayResolve */
|
||||
nullptr, /* finalize */
|
||||
nullptr, /* call */
|
||||
nullptr, /* hasInstance */
|
||||
nullptr, /* construct */
|
||||
UnboxedPlainObject::trace,
|
||||
};
|
||||
|
||||
static const ObjectOps UnboxedPlainObjectObjectOps = {
|
||||
UnboxedPlainObject::obj_lookupProperty,
|
||||
UnboxedPlainObject::obj_defineProperty,
|
||||
UnboxedPlainObject::obj_hasProperty,
|
||||
UnboxedPlainObject::obj_getProperty,
|
||||
UnboxedPlainObject::obj_setProperty,
|
||||
UnboxedPlainObject::obj_getOwnPropertyDescriptor,
|
||||
UnboxedPlainObject::obj_deleteProperty,
|
||||
nullptr, /* getElements */
|
||||
UnboxedPlainObject::obj_enumerate,
|
||||
nullptr /* funToString */
|
||||
};
|
||||
|
||||
const Class UnboxedPlainObject::class_ = {
|
||||
js_Object_str,
|
||||
Class::NON_NATIVE |
|
||||
JSCLASS_HAS_CACHED_PROTO(JSProto_Object) |
|
||||
JSCLASS_DELAY_METADATA_BUILDER,
|
||||
&UnboxedPlainObjectClassOps,
|
||||
JS_NULL_CLASS_SPEC,
|
||||
JS_NULL_CLASS_EXT,
|
||||
&UnboxedPlainObjectObjectOps
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
static inline Value
|
||||
NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
|
||||
{
|
||||
return values[(*valueCursor)++];
|
||||
}
|
||||
|
||||
void
|
||||
UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx,
|
||||
Handle<GCVector<Value>> values, size_t* valueCursor)
|
||||
{
|
||||
initExpando();
|
||||
memset(data(), 0, layout().size());
|
||||
for (size_t i = 0; i < layout().properties().length(); i++)
|
||||
JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor)));
|
||||
}
|
|
@ -0,0 +1,319 @@
|
|||
/* -*- 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 "jsobj.h"
|
||||
|
||||
#include "vm/Runtime.h"
|
||||
#include "vm/TypeInference.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;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool
|
||||
UnboxedTypeNeedsPreBarrier(JSValueType type)
|
||||
{
|
||||
return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
UnboxedTypeNeedsPostBarrier(JSValueType type)
|
||||
{
|
||||
return type == JSVAL_TYPE_OBJECT;
|
||||
}
|
||||
|
||||
// Class tracking information specific to unboxed objects.
|
||||
class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
|
||||
{
|
||||
public:
|
||||
struct Property {
|
||||
PropertyName* name;
|
||||
uint32_t offset;
|
||||
JSValueType type;
|
||||
|
||||
Property()
|
||||
: name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC)
|
||||
{}
|
||||
};
|
||||
|
||||
typedef Vector<Property, 0, SystemAllocPolicy> PropertyVector;
|
||||
|
||||
private:
|
||||
// If objects in this group have ever been converted to native objects,
|
||||
// these store the corresponding native group and initial shape for such
|
||||
// objects. Type information for this object is reflected in nativeGroup.
|
||||
GCPtrObjectGroup nativeGroup_;
|
||||
GCPtrShape nativeShape_;
|
||||
|
||||
// Any script/pc which the associated group is created for.
|
||||
GCPtrScript allocationScript_;
|
||||
jsbytecode* allocationPc_;
|
||||
|
||||
// If nativeGroup is set and this object originally had a TypeNewScript or
|
||||
// was keyed to an allocation site, this points to the group which replaced
|
||||
// this one. This link is only needed to keep the replacement group from
|
||||
// being GC'ed. If it were GC'ed and a new one regenerated later, that new
|
||||
// group might have a different allocation kind from this group.
|
||||
GCPtrObjectGroup replacementGroup_;
|
||||
|
||||
// The following members are only used for unboxed plain objects.
|
||||
|
||||
// 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.
|
||||
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_;
|
||||
|
||||
// If this layout has been used to construct script or JSON constant
|
||||
// objects, this code might be filled in to more quickly fill in objects
|
||||
// from an array of values.
|
||||
GCPtrJitCode constructorCode_;
|
||||
|
||||
public:
|
||||
UnboxedLayout()
|
||||
: nativeGroup_(nullptr), nativeShape_(nullptr),
|
||||
allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
|
||||
size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr)
|
||||
{}
|
||||
|
||||
bool initProperties(const PropertyVector& properties, size_t size) {
|
||||
size_ = size;
|
||||
return properties_.appendAll(properties);
|
||||
}
|
||||
|
||||
~UnboxedLayout() {
|
||||
if (newScript_)
|
||||
newScript_->clear();
|
||||
js_delete(newScript_);
|
||||
js_free(traceList_);
|
||||
|
||||
nativeGroup_.init(nullptr);
|
||||
nativeShape_.init(nullptr);
|
||||
replacementGroup_.init(nullptr);
|
||||
constructorCode_.init(nullptr);
|
||||
}
|
||||
|
||||
void detachFromCompartment();
|
||||
|
||||
const PropertyVector& properties() const {
|
||||
return properties_;
|
||||
}
|
||||
|
||||
TypeNewScript* newScript() const {
|
||||
return newScript_;
|
||||
}
|
||||
|
||||
void setNewScript(TypeNewScript* newScript, bool writeBarrier = true);
|
||||
|
||||
JSScript* allocationScript() const {
|
||||
return allocationScript_;
|
||||
}
|
||||
|
||||
jsbytecode* allocationPc() const {
|
||||
return allocationPc_;
|
||||
}
|
||||
|
||||
void setAllocationSite(JSScript* script, jsbytecode* pc) {
|
||||
allocationScript_ = script;
|
||||
allocationPc_ = pc;
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
ObjectGroup* nativeGroup() const {
|
||||
return nativeGroup_;
|
||||
}
|
||||
|
||||
Shape* nativeShape() const {
|
||||
return nativeShape_;
|
||||
}
|
||||
|
||||
jit::JitCode* constructorCode() const {
|
||||
return constructorCode_;
|
||||
}
|
||||
|
||||
void setConstructorCode(jit::JitCode* code) {
|
||||
constructorCode_ = code;
|
||||
}
|
||||
|
||||
inline gc::AllocKind getAllocKind() const;
|
||||
|
||||
void trace(JSTracer* trc);
|
||||
|
||||
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
|
||||
|
||||
static bool makeNativeGroup(JSContext* cx, ObjectGroup* group);
|
||||
static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group);
|
||||
};
|
||||
|
||||
// Class for expando objects holding extra properties given to an unboxed plain
|
||||
// object. These objects behave identically to normal native plain objects, and
|
||||
// have a separate Class to distinguish them for memory usage reporting.
|
||||
class UnboxedExpandoObject : public NativeObject
|
||||
{
|
||||
public:
|
||||
static const Class class_;
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
// Optional object which stores extra properties on this object. This is
|
||||
// not automatically barriered to avoid problems if the object is converted
|
||||
// to a native. See ensureExpando().
|
||||
UnboxedExpandoObject* expando_;
|
||||
|
||||
// Start of the inline data, which immediately follows the group and extra properties.
|
||||
uint8_t data_[1];
|
||||
|
||||
public:
|
||||
static const Class class_;
|
||||
|
||||
static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
|
||||
HandleId id, MutableHandleObject objp,
|
||||
MutableHandleShape propp);
|
||||
|
||||
static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||
Handle<PropertyDescriptor> desc,
|
||||
ObjectOpResult& result);
|
||||
|
||||
static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp);
|
||||
|
||||
static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver,
|
||||
HandleId id, MutableHandleValue vp);
|
||||
|
||||
static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v,
|
||||
HandleValue receiver, ObjectOpResult& result);
|
||||
|
||||
static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id,
|
||||
MutableHandle<PropertyDescriptor> desc);
|
||||
|
||||
static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id,
|
||||
ObjectOpResult& result);
|
||||
|
||||
static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
|
||||
bool enumerableOnly);
|
||||
static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable);
|
||||
|
||||
inline const UnboxedLayout& layout() const;
|
||||
|
||||
const UnboxedLayout& layoutDontCheckGeneration() const {
|
||||
return group()->unboxedLayoutDontCheckGeneration();
|
||||
}
|
||||
|
||||
uint8_t* data() {
|
||||
return &data_[0];
|
||||
}
|
||||
|
||||
UnboxedExpandoObject* maybeExpando() const {
|
||||
return expando_;
|
||||
}
|
||||
|
||||
void initExpando() {
|
||||
expando_ = nullptr;
|
||||
}
|
||||
|
||||
// For use during GC.
|
||||
JSObject** addressOfExpando() {
|
||||
return reinterpret_cast<JSObject**>(&expando_);
|
||||
}
|
||||
|
||||
bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const;
|
||||
|
||||
static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> obj);
|
||||
|
||||
bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v);
|
||||
Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false);
|
||||
|
||||
static bool convertToNative(JSContext* cx, JSObject* obj);
|
||||
static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group,
|
||||
NewObjectKind newKind);
|
||||
static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group,
|
||||
NewObjectKind newKind, IdValuePair* properties);
|
||||
|
||||
void fillAfterConvert(ExclusiveContext* cx,
|
||||
Handle<GCVector<Value>> values, size_t* valueCursor);
|
||||
|
||||
static void trace(JSTracer* trc, JSObject* object);
|
||||
|
||||
static size_t offsetOfExpando() {
|
||||
return offsetof(UnboxedPlainObject, expando_);
|
||||
}
|
||||
|
||||
static size_t offsetOfData() {
|
||||
return offsetof(UnboxedPlainObject, data_[0]);
|
||||
}
|
||||
};
|
||||
|
||||
inline gc::AllocKind
|
||||
UnboxedLayout::getAllocKind() const
|
||||
{
|
||||
MOZ_ASSERT(size());
|
||||
return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
|
||||
}
|
||||
|
||||
} // namespace js
|
||||
|
||||
namespace JS {
|
||||
|
||||
template <>
|
||||
struct DeletePolicy<js::UnboxedLayout> : public js::GCManagedDeletePolicy<js::UnboxedLayout>
|
||||
{};
|
||||
|
||||
} /* namespace JS */
|
||||
|
||||
#endif /* vm_UnboxedObject_h */
|
Loading…
Reference in New Issue