diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index a325e250a1c4..dfef4406b00c 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -622,6 +622,42 @@ obj_isPrototypeOf(JSContext *cx, unsigned argc, Value *vp) return true; } +PlainObject * +js::ObjectCreateImpl(JSContext *cx, HandleObject proto, NewObjectKind newKind, HandleTypeObject type) +{ + // Give the new object a small number of fixed slots, like we do for empty + // object literals ({}). + gc::AllocKind allocKind = GuessObjectGCKind(0); + + if (!proto) { + // Object.create(null) is common, optimize it by using an allocation + // site specific TypeObject. Because GetTypeCallerInitObject is pretty + // slow, the caller can pass in the type if it's known and we use that + // instead. + RootedTypeObject ntype(cx, type); + if (!ntype) { + ntype = GetTypeCallerInitObject(cx, JSProto_Null); + if (!ntype) + return nullptr; + } + + MOZ_ASSERT(!ntype->proto().toObjectOrNull()); + + return NewObjectWithType(cx, ntype, cx->global(), allocKind, + newKind); + } + + return NewObjectWithGivenProto(cx, proto, cx->global(), allocKind, newKind); +} + +PlainObject * +js::ObjectCreateWithTemplate(JSContext *cx, HandlePlainObject templateObj) +{ + RootedObject proto(cx, templateObj->getProto()); + RootedTypeObject type(cx, templateObj->type()); + return ObjectCreateImpl(cx, proto, GenericObject, type); +} + /* ES5 15.2.3.5: Object.create(O [, Properties]) */ bool js::obj_create(JSContext *cx, unsigned argc, Value *vp) @@ -633,8 +669,8 @@ js::obj_create(JSContext *cx, unsigned argc, Value *vp) return false; } - RootedValue v(cx, args[0]); - if (!v.isObjectOrNull()) { + if (!args[0].isObjectOrNull()) { + RootedValue v(cx, args[0]); char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); if (!bytes) return false; @@ -644,13 +680,8 @@ js::obj_create(JSContext *cx, unsigned argc, Value *vp) return false; } - RootedObject proto(cx, v.toObjectOrNull()); - - /* - * Use the callee's global as the parent of the new object to avoid dynamic - * scoping (i.e., using the caller's global). - */ - RootedObject obj(cx, NewObjectWithGivenProto(cx, proto, &args.callee().global())); + RootedObject proto(cx, args[0].toObjectOrNull()); + RootedPlainObject obj(cx, ObjectCreateImpl(cx, proto)); if (!obj) return false; diff --git a/js/src/builtin/Object.h b/js/src/builtin/Object.h index 9fe2c06e43e2..52cc8769d84f 100644 --- a/js/src/builtin/Object.h +++ b/js/src/builtin/Object.h @@ -23,6 +23,13 @@ obj_construct(JSContext *cx, unsigned argc, JS::Value *vp); bool obj_valueOf(JSContext *cx, unsigned argc, JS::Value *vp); +PlainObject * +ObjectCreateImpl(JSContext *cx, HandleObject proto, NewObjectKind newKind = GenericObject, + HandleTypeObject type = js::NullPtr()); + +PlainObject * +ObjectCreateWithTemplate(JSContext *cx, HandlePlainObject templateObj); + // Object methods exposed so they can be installed in the self-hosting global. bool obj_create(JSContext *cx, unsigned argc, JS::Value *vp); diff --git a/js/src/jit-test/tests/ion/object-create.js b/js/src/jit-test/tests/ion/object-create.js new file mode 100644 index 000000000000..dca277c3edcb --- /dev/null +++ b/js/src/jit-test/tests/ion/object-create.js @@ -0,0 +1,25 @@ +// Ensure Ion inlining of Object.create(x) tests the type of x +// matches the template object. + +var P1 = {}; +var P2 = {}; +minorgc(); + +function f1() { + for (var i=0; i<100; i++) { + var P = (i & 1) ? P1 : P2; + var o = Object.create(P); + assertEq(Object.getPrototypeOf(o), P); + } +} +f1(); + +function f2() { + var arr = [null, Array]; + for (var i=0; i<99; i++) { + var p = arr[(i / 50)|0]; + var o = Object.create(p); + assertEq(Object.getPrototypeOf(o), p); + } +} +f2(); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 204e05b13212..533c1caabb8d 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -8909,6 +8909,14 @@ GetTemplateObjectForNative(JSContext *cx, HandleScript script, jsbytecode *pc, return true; } + if (native == obj_create && args.length() == 1 && args[0].isObjectOrNull()) { + RootedObject proto(cx, args[0].toObjectOrNull()); + res.set(ObjectCreateImpl(cx, proto, TenuredObject)); + if (!res) + return false; + return true; + } + return true; } diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index e35d90ceaa2d..f3d685bf6170 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4038,9 +4038,9 @@ class OutOfLineNewObject : public OutOfLineCodeBase typedef JSObject *(*NewInitObjectFn)(JSContext *, HandlePlainObject); static const VMFunction NewInitObjectInfo = FunctionInfo(NewInitObject); -typedef JSObject *(*NewInitObjectWithClassPrototypeFn)(JSContext *, HandlePlainObject); -static const VMFunction NewInitObjectWithClassPrototypeInfo = - FunctionInfo(NewInitObjectWithClassPrototype); +typedef PlainObject *(*ObjectCreateWithTemplateFn)(JSContext *, HandlePlainObject); +static const VMFunction ObjectCreateWithTemplateInfo = + FunctionInfo(ObjectCreateWithTemplate); void CodeGenerator::visitNewObjectVMCall(LNewObject *lir) @@ -4056,10 +4056,12 @@ CodeGenerator::visitNewObjectVMCall(LNewObject *lir) // that derives its class from its prototype instead of being // JSObject::class_'d) from self-hosted code, we need a different init // function. - if (lir->mir()->templateObjectIsClassPrototype()) - callVM(NewInitObjectWithClassPrototypeInfo, lir); - else + if (lir->mir()->mode() == MNewObject::ObjectLiteral) { callVM(NewInitObjectInfo, lir); + } else { + MOZ_ASSERT(lir->mir()->mode() == MNewObject::ObjectCreate); + callVM(ObjectCreateWithTemplateInfo, lir); + } if (ReturnReg != objReg) masm.movePtr(ReturnReg, objReg); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 79df7cc016cd..7ab585caffad 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -6120,7 +6120,7 @@ IonBuilder::jsop_newobject() templateObject->hasSingletonType() ? gc::TenuredHeap : templateObject->type()->initialHeap(constraints()), - /* templateObjectIsClassPrototype = */ false); + MNewObject::ObjectLiteral); current->add(ins); current->push(ins); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 175aed9485e1..e86fc5997b75 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -755,6 +755,9 @@ class IonBuilder InliningStatus inlineRegExpExec(CallInfo &callInfo); InliningStatus inlineRegExpTest(CallInfo &callInfo); + // Object natives. + InliningStatus inlineObjectCreate(CallInfo &callInfo); + // Atomics natives. InliningStatus inlineAtomicsCompareExchange(CallInfo &callInfo); InliningStatus inlineAtomicsLoad(CallInfo &callInfo); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 6147b13e54a9..6b961e84df01 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -163,6 +163,10 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSFunction *target) if (native == regexp_test) return inlineRegExpTest(callInfo); + // Object natives. + if (native == obj_create) + return inlineObjectCreate(callInfo); + // Array intrinsics. if (native == intrinsic_UnsafePutElements) return inlineUnsafePutElements(callInfo); @@ -1601,6 +1605,50 @@ IonBuilder::inlineSubstringKernel(CallInfo &callInfo) return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineObjectCreate(CallInfo &callInfo) +{ + if (callInfo.argc() != 1 || callInfo.constructing()) + return InliningStatus_NotInlined; + + NativeObject *templateObject = inspector->getTemplateObjectForNative(pc, obj_create); + if (!templateObject) + return InliningStatus_NotInlined; + + MOZ_ASSERT(templateObject->is()); + MOZ_ASSERT(!templateObject->hasSingletonType()); + + // Ensure the argument matches the template object's prototype. + MDefinition *arg = callInfo.getArg(0); + if (JSObject *proto = templateObject->getProto()) { + if (IsInsideNursery(proto)) + return InliningStatus_NotInlined; + + types::TemporaryTypeSet *types = arg->resultTypeSet(); + if (!types || types->getSingleton() != proto) + return InliningStatus_NotInlined; + + MOZ_ASSERT(types->getKnownMIRType() == MIRType_Object); + } else { + if (arg->type() != MIRType_Null) + return InliningStatus_NotInlined; + } + + callInfo.setImplicitlyUsedUnchecked(); + + MConstant *templateConst = MConstant::NewConstraintlessObject(alloc(), templateObject); + current->add(templateConst); + MNewObject *ins = MNewObject::New(alloc(), constraints(), templateConst, + templateObject->type()->initialHeap(constraints()), + MNewObject::ObjectCreate); + current->add(ins); + current->push(ins); + if (!resumeAfter(ins)) + return InliningStatus_Error; + + return InliningStatus_Inlined; +} + IonBuilder::InliningStatus IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 9008b56a008a..0aff2c0c9e40 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -2793,17 +2793,21 @@ class MNewObject : public MUnaryInstruction, public NoTypePolicy::Data { + public: + enum Mode { ObjectLiteral, ObjectCreate }; + + private: gc::InitialHeap initialHeap_; - bool templateObjectIsClassPrototype_; + Mode mode_; MNewObject(types::CompilerConstraintList *constraints, MConstant *templateConst, - gc::InitialHeap initialHeap, bool templateObjectIsClassPrototype) + gc::InitialHeap initialHeap, Mode mode) : MUnaryInstruction(templateConst), initialHeap_(initialHeap), - templateObjectIsClassPrototype_(templateObjectIsClassPrototype) + mode_(mode) { PlainObject *obj = templateObject(); - MOZ_ASSERT_IF(templateObjectIsClassPrototype, !shouldUseVM()); + MOZ_ASSERT_IF(mode != ObjectLiteral, !shouldUseVM()); setResultType(MIRType_Object); if (!obj->hasSingletonType()) setResultTypeSet(MakeSingletonTypeSet(constraints, obj)); @@ -2821,18 +2825,17 @@ class MNewObject static MNewObject *New(TempAllocator &alloc, types::CompilerConstraintList *constraints, MConstant *templateConst, gc::InitialHeap initialHeap, - bool templateObjectIsClassPrototype) + Mode mode) { - return new(alloc) MNewObject(constraints, templateConst, initialHeap, - templateObjectIsClassPrototype); + return new(alloc) MNewObject(constraints, templateConst, initialHeap, mode); } // Returns true if the code generator should call through to the // VM rather than the fast path. bool shouldUseVM() const; - bool templateObjectIsClassPrototype() const { - return templateObjectIsClassPrototype_; + Mode mode() const { + return mode_; } PlainObject *templateObject() const { diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 571049d77ce0..88ab1c9914cd 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -1156,13 +1156,14 @@ MNewObject::writeRecoverData(CompactBufferWriter &writer) const { MOZ_ASSERT(canRecoverOnBailout()); writer.writeUnsigned(uint32_t(RInstruction::Recover_NewObject)); - writer.writeByte(templateObjectIsClassPrototype_); + MOZ_ASSERT(Mode(uint8_t(mode_)) == mode_); + writer.writeByte(uint8_t(mode_)); return true; } RNewObject::RNewObject(CompactBufferReader &reader) { - templateObjectIsClassPrototype_ = reader.readByte(); + mode_ = MNewObject::Mode(reader.readByte()); } bool @@ -1173,10 +1174,12 @@ RNewObject::recover(JSContext *cx, SnapshotIterator &iter) const JSObject *resultObject = nullptr; // See CodeGenerator::visitNewObjectVMCall - if (templateObjectIsClassPrototype_) - resultObject = NewInitObjectWithClassPrototype(cx, templateObject); - else + if (mode_ == MNewObject::ObjectLiteral) { resultObject = NewInitObject(cx, templateObject); + } else { + MOZ_ASSERT(mode_ == MNewObject::ObjectCreate); + resultObject = ObjectCreateWithTemplate(cx, templateObject); + } if (!resultObject) return false; diff --git a/js/src/jit/Recover.h b/js/src/jit/Recover.h index 2646d4fc4e48..aac42bea0bfc 100644 --- a/js/src/jit/Recover.h +++ b/js/src/jit/Recover.h @@ -613,7 +613,7 @@ class RTruncateToInt32 MOZ_FINAL : public RInstruction class RNewObject MOZ_FINAL : public RInstruction { private: - bool templateObjectIsClassPrototype_; + MNewObject::Mode mode_; public: RINSTRUCTION_HEADER_(NewObject) diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 17e283664ba8..ff3a43f75dec 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -301,27 +301,6 @@ NewInitObject(JSContext *cx, HandlePlainObject templateObject) return obj; } -JSObject * -NewInitObjectWithClassPrototype(JSContext *cx, HandlePlainObject templateObject) -{ - MOZ_ASSERT(!templateObject->hasSingletonType()); - MOZ_ASSERT(!templateObject->hasLazyType()); - - NewObjectKind newKind = templateObject->type()->shouldPreTenure() - ? TenuredObject - : GenericObject; - PlainObject *obj = NewObjectWithGivenProto(cx, - templateObject->getProto(), - cx->global(), - newKind); - if (!obj) - return nullptr; - - obj->setType(templateObject->type()); - - return obj; -} - bool ArraySpliceDense(JSContext *cx, HandleObject obj, uint32_t start, uint32_t deleteCount) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 8f0164d47d97..8a6f3791370a 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -270,6 +270,7 @@ template struct TypeToDataType { /* Unexpected return type for a VMFunct template <> struct TypeToDataType { static const DataType result = Type_Bool; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; +template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; @@ -657,7 +658,6 @@ template bool StringsEqual(JSContext *cx, HandleString left, HandleString right, bool *res); JSObject *NewInitObject(JSContext *cx, HandlePlainObject templateObject); -JSObject *NewInitObjectWithClassPrototype(JSContext *cx, HandlePlainObject templateObject); bool ArrayPopDense(JSContext *cx, HandleObject obj, MutableHandleValue rval); bool ArrayPushDense(JSContext *cx, HandleArrayObject obj, HandleValue v, uint32_t *length); diff --git a/js/src/jsinfer.cpp b/js/src/jsinfer.cpp index 35be424964b9..be878cd66fa2 100644 --- a/js/src/jsinfer.cpp +++ b/js/src/jsinfer.cpp @@ -2299,7 +2299,7 @@ TypeCompartment::addAllocationSiteTypeObject(JSContext *cx, AllocationSiteKey ke if (!res) { RootedObject proto(cx); - if (!GetBuiltinPrototype(cx, key.kind, &proto)) + if (key.kind != JSProto_Null && !GetBuiltinPrototype(cx, key.kind, &proto)) return nullptr; Rooted tagged(cx, TaggedProto(proto)); diff --git a/js/src/jsinferinlines.h b/js/src/jsinferinlines.h index faec0a5858b1..f2a3b9569abe 100644 --- a/js/src/jsinferinlines.h +++ b/js/src/jsinferinlines.h @@ -309,6 +309,7 @@ inline const Class * GetClassForProtoKey(JSProtoKey key) { switch (key) { + case JSProto_Null: case JSProto_Object: return &PlainObject::class_; case JSProto_Array: @@ -369,7 +370,7 @@ inline TypeObject * GetTypeNewObject(JSContext *cx, JSProtoKey key) { RootedObject proto(cx); - if (!GetBuiltinPrototype(cx, key, &proto)) + if (key != JSProto_Null && !GetBuiltinPrototype(cx, key, &proto)) return nullptr; return cx->getNewType(GetClassForProtoKey(key), TaggedProto(proto.get())); } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index bf38dd3da742..ea5eb7d29dde 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1422,7 +1422,8 @@ js::NewObjectWithTypeCommon(JSContext *cx, HandleTypeObject type, JSObject *pare NewObjectCache &cache = cx->runtime()->newObjectCache; NewObjectCache::EntryIndex entry = -1; - if (parent == type->proto().toObject()->getParent() && + if (type->proto().isObject() && + parent == type->proto().toObject()->getParent() && newKind == GenericObject && type->clasp()->isNative() && !cx->compartment()->hasObjectMetadataCallback()) diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index 7d0795eb0dc0..7b2ae5a08129 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -545,6 +545,15 @@ NewObjectWithGivenProto(ExclusiveContext *cx, JSObject *proto, JSObject *parent, return NewObjectWithGivenProto(cx, TaggedProto(proto), parent, newKind); } +template +inline T * +NewObjectWithGivenProto(ExclusiveContext *cx, JSObject *proto, JSObject *parent, + gc::AllocKind allocKind, NewObjectKind newKind = GenericObject) +{ + JSObject *obj = NewObjectWithGivenProto(cx, &T::class_, TaggedProto(proto), parent, newKind); + return obj ? &obj->as() : nullptr; +} + inline bool FindProto(ExclusiveContext *cx, const js::Class *clasp, MutableHandleObject proto) {