/* -*- 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 "jit/CacheIR.h" #include "jit/BaselineIC.h" #include "jit/IonCaches.h" #include "jsobjinlines.h" #include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::jit; using mozilla::Maybe; GetPropIRGenerator::GetPropIRGenerator(JSContext* cx, jsbytecode* pc, HandleValue val, HandlePropertyName name, MutableHandleValue res) : cx_(cx), pc_(pc), val_(val), name_(name), res_(res), emitted_(false), preliminaryObjectAction_(PreliminaryObjectAction::None) {} static void EmitLoadSlotResult(CacheIRWriter& writer, ObjOperandId holderOp, NativeObject* holder, Shape* shape) { if (holder->isFixedSlot(shape->slot())) { writer.loadFixedSlotResult(holderOp, NativeObject::getFixedSlotOffset(shape->slot())); } else { size_t dynamicSlotOffset = holder->dynamicSlotIndex(shape->slot()) * sizeof(Value); writer.loadDynamicSlotResult(holderOp, dynamicSlotOffset); } } bool GetPropIRGenerator::tryAttachStub(Maybe<CacheIRWriter>& writer) { AutoAssertNoPendingException aanpe(cx_); JS::AutoCheckCannotGC nogc; MOZ_ASSERT(!emitted_); writer.emplace(); ValOperandId valId(writer->setInputOperandId(0)); if (val_.isObject()) { RootedObject obj(cx_, &val_.toObject()); ObjOperandId objId = writer->guardIsObject(valId); if (!emitted_ && !tryAttachObjectLength(*writer, obj, objId)) 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)) return false; return true; } if (!emitted_ && !tryAttachPrimitive(*writer, valId)) return false; return true; } static bool IsCacheableNoProperty(JSContext* cx, JSObject* obj, JSObject* holder, Shape* shape, jsid id, jsbytecode* pc) { if (shape) return false; MOZ_ASSERT(!holder); // If we're doing a name lookup, we have to throw a ReferenceError. if (*pc == JSOP_GETXPROP) return false; return CheckHasNoSuchProperty(cx, obj, JSID_TO_ATOM(id)->asPropertyName()); } enum NativeGetPropCacheability { CanAttachNone, CanAttachReadSlot, }; static NativeGetPropCacheability CanAttachNativeGetProp(JSContext* cx, HandleObject obj, HandleId id, MutableHandleNativeObject holder, MutableHandleShape shape, jsbytecode* pc, bool skipArrayLen = false) { MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id)); // The lookup needs to be universally pure, otherwise we risk calling hooks out // of turn. We don't mind doing this even when purity isn't required, because we // only miss out on shape hashification, which is only a temporary perf cost. // The limits were arbitrarily set, anyways. JSObject* baseHolder = nullptr; if (!LookupPropertyPure(cx, obj, id, &baseHolder, shape.address())) return CanAttachNone; MOZ_ASSERT(!holder); if (baseHolder) { if (!baseHolder->isNative()) return CanAttachNone; holder.set(&baseHolder->as<NativeObject>()); } if (IsCacheableGetPropReadSlotForIonOrCacheIR(obj, holder, shape) || IsCacheableNoProperty(cx, obj, holder, shape, id, pc)) { return CanAttachReadSlot; } return CanAttachNone; } static void GeneratePrototypeGuards(CacheIRWriter& writer, JSObject* obj, JSObject* holder, ObjOperandId objId) { // The guards here protect against the effects of JSObject::swap(). If the // prototype chain is directly altered, then TI will toss the jitcode, so we // don't have to worry about it, and any other change to the holder, or // adding a shadowing property will result in reshaping the holder, and thus // the failure of the shape guard. MOZ_ASSERT(obj != holder); if (obj->hasUncacheableProto()) { // If the shape does not imply the proto, emit an explicit proto guard. writer.guardProto(objId, obj->staticPrototype()); } JSObject* pobj = obj->staticPrototype(); if (!pobj) return; while (pobj != holder) { if (pobj->hasUncacheableProto()) { ObjOperandId protoId = writer.loadObject(pobj); if (pobj->isSingleton()) { // Singletons can have their group's |proto| mutated directly. writer.guardProto(protoId, pobj->staticPrototype()); } else { writer.guardGroup(protoId, pobj->group()); } } pobj = pobj->staticPrototype(); } } static void TestMatchingReceiver(CacheIRWriter& writer, JSObject* obj, Shape* shape, ObjOperandId objId, Maybe<ObjOperandId>* expandoId) { 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<UnboxedArrayObject>() || obj->is<TypedObject>()) { writer.guardGroup(objId, obj->group()); } else { Shape* shape = obj->maybeShape(); MOZ_ASSERT(shape); writer.guardShape(objId, shape); } } static void EmitReadSlotResult(CacheIRWriter& writer, JSObject* obj, JSObject* holder, Shape* shape, ObjOperandId objId) { Maybe<ObjOperandId> expandoId; TestMatchingReceiver(writer, obj, shape, objId, &expandoId); ObjOperandId holderId; if (obj != holder) { GeneratePrototypeGuards(writer, obj, holder, objId); if (holder) { // Guard on the holder's shape. holderId = writer.loadObject(holder); writer.guardShape(holderId, holder->as<NativeObject>().lastProperty()); } else { // The property does not exist. Guard on everything in the prototype // chain. This is guaranteed to see only Native objects because of // CanAttachNativeGetProp(). JSObject* proto = obj->taggedProto().toObjectOrNull(); ObjOperandId lastObjId = objId; while (proto) { ObjOperandId protoId = writer.loadProto(lastObjId); writer.guardShape(protoId, proto->as<NativeObject>().lastProperty()); proto = proto->staticPrototype(); lastObjId = protoId; } } } else if (obj->is<UnboxedPlainObject>()) { holder = obj->as<UnboxedPlainObject>().maybeExpando(); holderId = *expandoId; } else { holderId = objId; } // Slot access. if (holder) { MOZ_ASSERT(holderId.valid()); EmitLoadSlotResult(writer, holderId, &holder->as<NativeObject>(), shape); } else { MOZ_ASSERT(!holderId.valid()); writer.loadUndefinedResult(); } } bool GetPropIRGenerator::tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) { MOZ_ASSERT(!emitted_); RootedShape shape(cx_); RootedNativeObject holder(cx_); RootedId id(cx_, NameToId(name_)); NativeGetPropCacheability type = CanAttachNativeGetProp(cx_, obj, id, &holder, &shape, pc_); if (type == CanAttachNone) return true; emitted_ = true; switch (type) { case CanAttachReadSlot: if (holder) { EnsureTrackPropertyTypes(cx_, holder, NameToId(name_)); if (obj == holder) { // See the comment in StripPreliminaryObjectStubs. if (IsPreliminaryObject(obj)) preliminaryObjectAction_ = PreliminaryObjectAction::NotePreliminary; else preliminaryObjectAction_ = PreliminaryObjectAction::Unlink; } } EmitReadSlotResult(writer, obj, holder, shape, objId); break; default: MOZ_CRASH("Bad NativeGetPropCacheability"); } 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) { MOZ_ASSERT(!emitted_); if (!obj->is<TypedObject>() || !cx_->runtime()->jitSupportsFloatingPoint || cx_->compartment()->detachedTypedObjects) { return true; } TypedObject* typedObj = &obj->as<TypedObject>(); if (!typedObj->typeDescr().is<StructTypeDescr>()) return true; StructTypeDescr* structDescr = &typedObj->typeDescr().as<StructTypeDescr>(); size_t fieldIndex; if (!structDescr->fieldIndex(NameToId(name_), &fieldIndex)) return true; TypeDescr* fieldDescr = &structDescr->fieldDescr(fieldIndex); if (!fieldDescr->is<SimpleTypeDescr>()) return true; Shape* shape = typedObj->maybeShape(); TypedThingLayout layout = GetTypedThingLayout(shape->getObjectClass()); uint32_t fieldOffset = structDescr->fieldOffset(fieldIndex); uint32_t typeDescr = SimpleTypeDescrKey(&fieldDescr->as<SimpleTypeDescr>()); writer.guardNoDetachedTypedObjects(); writer.guardShape(objId, shape); writer.loadTypedObjectResult(objId, fieldOffset, layout, typeDescr); emitted_ = true; return true; } bool GetPropIRGenerator::tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) { MOZ_ASSERT(!emitted_); if (name_ != cx_->names().length) return true; if (obj->is<ArrayObject>()) { // Make sure int32 is added to the TypeSet before we attach a stub, so // the stub can return int32 values without monitoring the result. if (obj->as<ArrayObject>().length() > INT32_MAX) return true; writer.guardClass(objId, GuardClassKind::Array); writer.loadInt32ArrayLengthResult(objId); emitted_ = true; return true; } if (obj->is<UnboxedArrayObject>()) { writer.guardClass(objId, GuardClassKind::UnboxedArray); writer.loadUnboxedArrayLengthResult(objId); emitted_ = true; return true; } if (obj->is<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) { if (obj->is<MappedArgumentsObject>()) { writer.guardClass(objId, GuardClassKind::MappedArguments); } else { MOZ_ASSERT(obj->is<UnmappedArgumentsObject>()); writer.guardClass(objId, GuardClassKind::UnmappedArguments); } writer.loadArgumentsObjectLengthResult(objId); emitted_ = true; return true; } return true; } bool GetPropIRGenerator::tryAttachModuleNamespace(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId) { MOZ_ASSERT(!emitted_); if (!obj->is<ModuleNamespaceObject>()) return true; Rooted<ModuleNamespaceObject*> ns(cx_, &obj->as<ModuleNamespaceObject>()); RootedModuleEnvironmentObject env(cx_); RootedShape shape(cx_); if (!ns->bindings().lookup(NameToId(name_), env.address(), shape.address())) return true; // Don't emit a stub until the target binding has been initialized. if (env->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL)) return true; if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, env, shape->propid()); emitted_ = true; // Check for the specific namespace object. writer.guardSpecificObject(objId, ns); ObjOperandId envId = writer.loadObject(env); EmitLoadSlotResult(writer, envId, env, shape); return true; } bool GetPropIRGenerator::tryAttachPrimitive(CacheIRWriter& writer, ValOperandId valId) { MOZ_ASSERT(!emitted_); JSValueType primitiveType; RootedNativeObject proto(cx_); if (val_.isString()) { if (name_ == cx_->names().length) { // String length is special-cased, see js::GetProperty. return true; } primitiveType = JSVAL_TYPE_STRING; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_String)); } else if (val_.isNumber()) { primitiveType = JSVAL_TYPE_DOUBLE; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Number)); } else if (val_.isBoolean()) { primitiveType = JSVAL_TYPE_BOOLEAN; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Boolean)); } else if (val_.isSymbol()) { primitiveType = JSVAL_TYPE_SYMBOL; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Symbol)); } else { MOZ_ASSERT(val_.isNullOrUndefined() || val_.isMagic()); return true; } if (!proto) return true; // Instantiate this property, for use during Ion compilation. RootedId id(cx_, NameToId(name_)); if (IsIonEnabled(cx_)) EnsureTrackPropertyTypes(cx_, proto, id); // For now, only look for properties directly set on the prototype. Shape* shape = proto->lookup(cx_, id); if (!shape || !shape->hasSlot() || !shape->hasDefaultGetter()) return true; writer.guardType(valId, primitiveType); ObjOperandId protoId = writer.loadObject(proto); writer.guardShape(protoId, proto->lastProperty()); EmitLoadSlotResult(writer, protoId, proto, shape); emitted_ = true; return true; }