Revert Remove unboxed objects.

This commit is contained in:
Fedor 2020-03-12 20:43:27 +03:00
parent 51dcdf812a
commit c9e0d090cd
48 changed files with 3401 additions and 76 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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), &notNull);
moveValue(NullValue(), output.valueReg());
jump(&done);
bind(&notNull);
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, &notInt32);
int32ValueToDouble(reg, ScratchDoubleReg);
storeDouble(ScratchDoubleReg, address);
jump(&end);
bind(&notInt32);
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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

946
js/src/vm/UnboxedObject.cpp Normal file
View File

@ -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, &notObject);
Register valueObject = masm.extractObject(valueAddress, scratch1);
masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier);
masm.bind(&notObject);
}
}
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) ? &notObject : &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(&notObject);
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)));
}

319
js/src/vm/UnboxedObject.h Normal file
View File

@ -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 */