Mypal/js/src/jit/CacheIR.cpp
2021-02-04 16:48:36 +02:00

473 lines
15 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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;
}