diff options
Diffstat (limited to 'js/src/jit/CacheIR.cpp')
-rw-r--r-- | js/src/jit/CacheIR.cpp | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp new file mode 100644 index 0000000000..f1061af701 --- /dev/null +++ b/js/src/jit/CacheIR.cpp @@ -0,0 +1,473 @@ +/* -*- 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; +} |