Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* 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/TypeInference-inl.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/PodOperations.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include <algorithm>
#include <new>
#include <utility>
#include "jsapi.h"
#include "gc/HashUtil.h"
#include "jit/BaselineIC.h"
#include "jit/BaselineJIT.h"
#include "jit/CompileInfo.h"
#include "jit/Ion.h"
#include "jit/IonAnalysis.h"
#include "jit/JitRealm.h"
#include "js/MemoryMetrics.h"
#include "js/ScalarType.h" // js::Scalar::Type
#include "js/UniquePtr.h"
#include "util/DiagnosticAssertions.h"
#include "util/Poison.h"
#include "vm/FrameIter.h" // js::AllScriptFramesIter
#include "vm/HelperThreads.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/Opcodes.h"
#include "vm/PlainObject.h" // js::PlainObject
#include "vm/Printer.h"
#include "vm/Shape.h"
#include "vm/Time.h"
#include "gc/GC-inl.h"
#include "gc/Marking-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/ObjectGroup-inl.h" // JSObject::setSingleton
using namespace js;
using mozilla::DebugOnly;
using mozilla::Maybe;
using mozilla::PodArrayZero;
using mozilla::PodCopy;
using js::jit::JitScript;
#ifdef DEBUG
static inline jsid id___proto__(JSContext* cx) {
return NameToId(cx->names().proto);
}
static inline jsid id_constructor(JSContext* cx) {
return NameToId(cx->names().constructor);
}
static inline jsid id_caller(JSContext* cx) {
return NameToId(cx->names().caller);
}
const char* js::TypeIdStringImpl(jsid id) {
if (JSID_IS_VOID(id)) {
return "(index)";
}
if (JSID_IS_EMPTY(id)) {
return "(new)";
}
if (JSID_IS_SYMBOL(id)) {
return "(symbol)";
}
static char bufs[4][100];
static unsigned which = 0;
which = (which + 1) & 3;
PutEscapedString(bufs[which], 100, JSID_TO_LINEAR_STRING(id), 0);
return bufs[which];
}
#endif
/////////////////////////////////////////////////////////////////////
// Logging
/////////////////////////////////////////////////////////////////////
/* static */ const char* TypeSet::NonObjectTypeString(TypeSet::Type type) {
if (type.isPrimitive()) {
switch (type.primitive()) {
case ValueType::Undefined:
return "void";
case ValueType::Null:
return "null";
case ValueType::Boolean:
return "bool";
case ValueType::Int32:
return "int";
case ValueType::Double:
return "float";
case ValueType::String:
return "string";
case ValueType::Symbol:
return "symbol";
case ValueType::BigInt:
return "BigInt";
case ValueType::Magic:
return "lazyargs";
case ValueType::PrivateGCThing:
case ValueType::Object:
MOZ_CRASH("Bad type");
}
}
if (type.isUnknown()) {
return "unknown";
}
MOZ_ASSERT(type.isAnyObject());
return "object";
}
static UniqueChars MakeStringCopy(const char* s) {
AutoEnterOOMUnsafeRegion oomUnsafe;
char* copy = strdup(s);
if (!copy) {
oomUnsafe.crash("Could not copy string");
}
return UniqueChars(copy);
}
/* static */
UniqueChars TypeSet::TypeString(const TypeSet::Type type) {
if (type.isPrimitive() || type.isUnknown() || type.isAnyObject()) {
return MakeStringCopy(NonObjectTypeString(type));
}
char buf[100];
if (type.isSingleton()) {
JSObject* singleton = type.singletonNoBarrier();
SprintfLiteral(buf, "<%s %#" PRIxPTR ">", singleton->getClass()->name,
uintptr_t(singleton));
} else {
SprintfLiteral(buf, "[%s * %#" PRIxPTR "]",
type.groupNoBarrier()->clasp()->name,
uintptr_t(type.groupNoBarrier()));
}
return MakeStringCopy(buf);
}
/* static */
UniqueChars TypeSet::ObjectGroupString(const ObjectGroup* group) {
return TypeString(TypeSet::ObjectType(group));
}
#ifdef DEBUG
bool js::InferSpewActive(TypeSpewChannel channel) {
static bool active[SPEW_COUNT];
static bool checked = false;
if (!checked) {
checked = true;
PodArrayZero(active);
const char* env = getenv("INFERFLAGS");
if (!env) {
return false;
}
if (strstr(env, "ops")) {
active[ISpewOps] = true;
}
if (strstr(env, "result")) {
active[ISpewResult] = true;
}
if (strstr(env, "full")) {
for (unsigned i = 0; i < SPEW_COUNT; i++) {
active[i] = true;
}
}
}
return active[channel];
}
static bool InferSpewColorable() {
/* Only spew colors on xterm-color to not screw up emacs. */
static bool colorable = false;
static bool checked = false;
if (!checked) {
checked = true;
const char* env = getenv("TERM");
if (!env) {
return false;
}
if (strcmp(env, "xterm-color") == 0 || strcmp(env, "xterm-256color") == 0) {
colorable = true;
}
}
return colorable;
}
const char* js::InferSpewColorReset() {
if (!InferSpewColorable()) {
return "";
}
return "\x1b[0m";
}
const char* js::InferSpewColor(TypeConstraint* constraint) {
/* Type constraints are printed out using foreground colors. */
static const char* const colors[] = {"\x1b[31m", "\x1b[32m", "\x1b[33m",
"\x1b[34m", "\x1b[35m", "\x1b[36m",
"\x1b[37m"};
if (!InferSpewColorable()) {
return "";
}
return colors[DefaultHasher<TypeConstraint*>::hash(constraint) % 7];
}
const char* js::InferSpewColor(TypeSet* types) {
/* Type sets are printed out using bold colors. */
static const char* const colors[] = {"\x1b[1;31m", "\x1b[1;32m", "\x1b[1;33m",
"\x1b[1;34m", "\x1b[1;35m", "\x1b[1;36m",
"\x1b[1;37m"};
if (!InferSpewColorable()) {
return "";
}
return colors[DefaultHasher<TypeSet*>::hash(types) % 7];
}
void js::InferSpewImpl(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
fprintf(stderr, "[infer] ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
va_end(ap);
}
MOZ_NORETURN MOZ_COLD static void MOZ_FORMAT_PRINTF(2, 3)
TypeFailure(JSContext* cx, const char* fmt, ...) {
char msgbuf[1024]; /* Larger error messages will be truncated */
char errbuf[1024];
va_list ap;
va_start(ap, fmt);
VsprintfLiteral(errbuf, fmt, ap);
va_end(ap);
SprintfLiteral(msgbuf, "[infer failure] %s", errbuf);
/* Dump type state, even if INFERFLAGS is unset. */
PrintTypes(cx, cx->compartment(), true);
MOZ_ReportAssertionFailure(msgbuf, __FILE__, __LINE__);
MOZ_CRASH();
}
bool js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id,
const Value& value) {
/*
* Check the correctness of the type information in the object's property
* against an actual value.
*/
AutoSweepObjectGroup sweep(group);
if (!group->unknownProperties(sweep) && !value.isUndefined()) {
id = IdToTypeId(id);
/* Watch for properties which inference does not monitor. */
if (id == id___proto__(cx) || id == id_constructor(cx) ||
id == id_caller(cx)) {
return true;
}
TypeSet::Type type = TypeSet::GetValueType(value);
AutoEnterAnalysis enter(cx);
/*
* We don't track types for properties inherited from prototypes which
* haven't yet been accessed during analysis of the inheriting object.
* Don't do the property instantiation now.
*/
TypeSet* types = group->maybeGetProperty(sweep, id);
if (!types) {
return true;
}
// Type set guards might miss when an object's group changes and its
// properties become unknown.
if (value.isObject()) {
if (types->unknownObject()) {
return true;
}
for (size_t i = 0; i < types->getObjectCount(); i++) {
if (TypeSet::ObjectKey* key = types->getObject(i)) {
if (key->unknownProperties()) {
return true;
}
}
}
}
if (!types->hasType(type)) {
TypeFailure(cx, "Missing type in object %s %s: %s",
TypeSet::ObjectGroupString(group).get(), TypeIdString(id),
TypeSet::TypeString(type).get());
}
}
return true;
}
#endif
/////////////////////////////////////////////////////////////////////
// TypeSet
/////////////////////////////////////////////////////////////////////
TemporaryTypeSet::TemporaryTypeSet(LifoAlloc* alloc, Type type) {
MOZ_ASSERT(!jit::JitOptions.warpBuilder);
if (type.isUnknown()) {
flags |= TYPE_FLAG_BASE_MASK;
return;
}
if (type.isPrimitive()) {
flags = PrimitiveTypeFlag(type);
if (flags == TYPE_FLAG_DOUBLE) {
flags |= TYPE_FLAG_INT32;
}
return;
}
if (type.isAnyObject()) {
flags |= TYPE_FLAG_ANYOBJECT;
return;
}
if (type.isGroup()) {
AutoSweepObjectGroup sweep(type.group());
if (type.group()->unknownProperties(sweep)) {
flags |= TYPE_FLAG_ANYOBJECT;
return;
}
}
setBaseObjectCount(1);
objectSet = reinterpret_cast<ObjectKey**>(type.objectKey());
if (type.isGroup()) {
ObjectGroup* ngroup = type.group();
AutoSweepObjectGroup sweep(ngroup);
if (ngroup->newScript(sweep) &&
ngroup->newScript(sweep)->initializedGroup()) {
addType(ObjectType(ngroup->newScript(sweep)->initializedGroup()), alloc);
}
}
}
static TypeFlags MIRTypeToTypeFlags(jit::MIRType type) {
switch (type) {
case jit::MIRType::Undefined:
return TYPE_FLAG_UNDEFINED;
case jit::MIRType::Null:
return TYPE_FLAG_NULL;
case jit::MIRType::Boolean:
return TYPE_FLAG_BOOLEAN;
case jit::MIRType::Int32:
return TYPE_FLAG_INT32;
case jit::MIRType::Float32: // Fall through, there's no JSVAL for Float32.
case jit::MIRType::Double:
return TYPE_FLAG_DOUBLE;
case jit::MIRType::String:
return TYPE_FLAG_STRING;
case jit::MIRType::Symbol:
return TYPE_FLAG_SYMBOL;
case jit::MIRType::BigInt:
return TYPE_FLAG_BIGINT;
case jit::MIRType::Object:
return TYPE_FLAG_ANYOBJECT;
case jit::MIRType::MagicOptimizedArguments:
return TYPE_FLAG_LAZYARGS;
default:
MOZ_CRASH("Bad MIR type");
}
}
bool TypeSet::mightBeMIRType(jit::MIRType type) const {
if (unknown()) {
return true;
}
TypeFlags baseFlags = this->baseFlags();
if (baseObjectCount() != 0) {
baseFlags |= TYPE_FLAG_ANYOBJECT;
}
return baseFlags & MIRTypeToTypeFlags(type);
}
bool TypeSet::isSubset(std::initializer_list<jit::MIRType> types) const {
TypeFlags flags = 0;
for (auto type : types) {
flags |= MIRTypeToTypeFlags(type);
}
TypeFlags baseFlags = this->baseFlags();
if (baseObjectCount() != 0) {
baseFlags |= TYPE_FLAG_ANYOBJECT;
}
return (baseFlags & flags) == baseFlags;
}
bool TypeSet::objectsAreSubset(TypeSet* other) {
if (other->unknownObject()) {
return true;
}
if (unknownObject()) {
return false;
}
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key) {
continue;
}
if (!other->hasType(ObjectType(key))) {
return false;
}
}
return true;
}
bool TypeSet::isSubset(const TypeSet* other) const {
if ((baseFlags() & other->baseFlags()) != baseFlags()) {
return false;
}
if (unknownObject()) {
MOZ_ASSERT(other->unknownObject());
} else {
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key) {
continue;
}
if (!other->hasType(ObjectType(key))) {
return false;
}
}
}
return true;
}
bool TypeSet::objectsIntersect(const TypeSet* other) const {
if (unknownObject() || other->unknownObject()) {
return true;
}
for (unsigned i = 0; i < getObjectCount(); i++) {
ObjectKey* key = getObject(i);
if (!key) {
continue;
}
if (other->hasType(ObjectType(key))) {
return true;
}
}
return false;
}
bool TypeSet::enumerateTypes(TypeList* list) const {
/* If any type is possible, there's no need to worry about specifics. */
if (flags & TYPE_FLAG_UNKNOWN) {
return list->append(UnknownType());
}
/* Enqueue type set members stored as bits. */
for (TypeFlags flag = 1; flag < TYPE_FLAG_ANYOBJECT; flag <<= 1) {
if (flags & flag) {
Type type = PrimitiveTypeFromTypeFlag(flag);
if (!list->append(type)) {
return false;
}
}
}
/* If any object is possible, skip specifics. */
if (flags & TYPE_FLAG_ANYOBJECT) {
return list->append(AnyObjectType());
}
/* Enqueue specific object types. */
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key) {
if (!list->append(ObjectType(key))) {
return false;
}
}
}
return true;
}
inline bool TypeSet::addTypesToConstraint(JSContext* cx,
TypeConstraint* constraint) {
/*
* Build all types in the set into a vector before triggering the
* constraint, as doing so may modify this type set.
*/
TypeList types;
if (!enumerateTypes(&types)) {
return false;
}
for (unsigned i = 0; i < types.length(); i++) {
constraint->newType(cx, this, types[i]);
}
return true;
}
#ifdef DEBUG
static inline bool CompartmentsMatch(Compartment* a, Compartment* b) {
return !a || !b || a == b;
}
#endif
bool ConstraintTypeSet::addConstraint(JSContext* cx, TypeConstraint* constraint,
bool callExisting) {
checkMagic();
if (!constraint) {
/* OOM failure while constructing the constraint. */
return false;
}
MOZ_RELEASE_ASSERT(cx->zone()->types.activeAnalysis);
MOZ_ASSERT(
CompartmentsMatch(maybeCompartment(), constraint->maybeCompartment()));
InferSpew(ISpewOps, "addConstraint: %sT%p%s %sC%p%s %s", InferSpewColor(this),
this, InferSpewColorReset(), InferSpewColor(constraint), constraint,
InferSpewColorReset(), constraint->kind());
MOZ_ASSERT(constraint->next() == nullptr);
constraint->setNext(constraintList_);
constraintList_ = constraint;
if (callExisting) {
return addTypesToConstraint(cx, constraint);
}
return true;
}
void TypeSet::clearObjects() {
setBaseObjectCount(0);
objectSet = nullptr;
}
Compartment* TypeSet::maybeCompartment() {
if (unknownObject()) {
return nullptr;
}
unsigned objectCount = getObjectCount();
for (unsigned i = 0; i < objectCount; i++) {
ObjectKey* key = getObject(i);
if (!key) {
continue;
}
Compartment* comp = key->maybeCompartment();
if (comp) {
return comp;
}
}
return nullptr;
}
void TypeSet::addType(Type type, LifoAlloc* alloc) {
MOZ_ASSERT(CompartmentsMatch(maybeCompartment(), type.maybeCompartment()));
if (unknown()) {
return;
}
if (type.isUnknown()) {
flags |= TYPE_FLAG_BASE_MASK;
clearObjects();
MOZ_ASSERT(unknown());
return;
}
if (type.isPrimitive()) {
TypeFlags flag = PrimitiveTypeFlag(type);
if (flags & flag) {
return;
}
/* If we add float to a type set it is also considered to contain int. */
if (flag == TYPE_FLAG_DOUBLE) {
flag |= TYPE_FLAG_INT32;
}
flags |= flag;
return;
}
if (flags & TYPE_FLAG_ANYOBJECT) {
return;
}
if (type.isAnyObject()) {
goto unknownObject;
}
{
uint32_t objectCount = baseObjectCount();
ObjectKey* key = type.objectKey();
ObjectKey** pentry = TypeHashSet::Insert<ObjectKey*, ObjectKey, ObjectKey>(
*alloc, objectSet, objectCount, key);
if (!pentry) {
goto unknownObject;
}
if (*pentry) {
return;
}
*pentry = key;
setBaseObjectCount(objectCount);
// Limit the number of objects we track. There is a different limit
// depending on whether the set only contains DOM objects, which can
// have many different classes and prototypes but are still optimizable
// by IonMonkey.
if (objectCount >= TYPE_FLAG_OBJECT_COUNT_LIMIT) {
static_assert(TYPE_FLAG_DOMOBJECT_COUNT_LIMIT >=
TYPE_FLAG_OBJECT_COUNT_LIMIT);
// Examining the entire type set is only required when we first hit
// the normal object limit.
if (objectCount == TYPE_FLAG_OBJECT_COUNT_LIMIT) {
for (unsigned i = 0; i < objectCount; i++) {
const JSClass* clasp = getObjectClass(i);
if (clasp && !clasp->isDOMClass()) {
goto unknownObject;
}
}
}
// Make sure the newly added object is also a DOM object.
if (!key->clasp()->isDOMClass()) {
goto unknownObject;
}
// Limit the number of DOM objects.
if (objectCount == TYPE_FLAG_DOMOBJECT_COUNT_LIMIT) {
goto unknownObject;
}
}
}
if (type.isGroup()) {
ObjectGroup* ngroup = type.group();
MOZ_ASSERT(!ngroup->singleton());
AutoSweepObjectGroup sweep(ngroup);
if (ngroup->unknownProperties(sweep)) {
goto unknownObject;
}
// If we add a partially initialized group to a type set, add the
// corresponding fully initialized group, as an object's group may change
// from the former to the latter via the acquired properties analysis.
if (ngroup->newScript(sweep) &&
ngroup->newScript(sweep)->initializedGroup()) {
addType(ObjectType(ngroup->newScript(sweep)->initializedGroup()), alloc);
}
}
if (false) {
unknownObject:
flags |= TYPE_FLAG_ANYOBJECT;
clearObjects();
}
}
// This class is used for post barriers on type set contents. The only times
// when type sets contain nursery references is when a nursery object has its
// group dynamically changed to a singleton. In such cases the type set will
// need to be traced at the next minor GC.
//
// There is no barrier used for TemporaryTypeSets. These type sets are only
// used during Ion compilation, and if some ConstraintTypeSet contains nursery
// pointers then any number of TemporaryTypeSets might as well. Thus, if there
// are any such ConstraintTypeSets in existence, all off thread Ion
// compilations are canceled by the next minor GC.
class TypeSetRef : public gc::BufferableRef {
Zone* zone;
ConstraintTypeSet* types;
#ifdef DEBUG
uint64_t minorGCNumberAtCreation;
#endif
public:
TypeSetRef(Zone* zone, ConstraintTypeSet* types)
: zone(zone),
types(types)
#ifdef DEBUG
,
minorGCNumberAtCreation(
zone->runtimeFromMainThread()->gc.minorGCCount())
#endif
{
}
void trace(JSTracer* trc) override {
MOZ_ASSERT(trc->runtime()->gc.minorGCCount() == minorGCNumberAtCreation);
types->trace(zone, trc);
}
};
void ConstraintTypeSet::postWriteBarrier(JSContext* cx, Type type) {
if (type.isSingletonUnchecked()) {
if (gc::StoreBuffer* sb = type.singletonNoBarrier()->storeBuffer()) {
sb->putGeneric(TypeSetRef(cx->zone(), this));
sb->setShouldCancelIonCompilations();
sb->setHasTypeSetPointers();
}
}
}
void ConstraintTypeSet::addType(const AutoSweepBase& sweep, JSContext* cx,
Type type) {
checkMagic();
MOZ_RELEASE_ASSERT(cx->zone()->types.activeAnalysis);
if (hasType(type)) {
return;
}
TypeSet::addType(type, &cx->typeLifoAlloc());
if (type.isObjectUnchecked() && unknownObject()) {
type = AnyObjectType();
}
postWriteBarrier(cx, type);
InferSpew(ISpewOps, "addType: %sT%p%s %s", InferSpewColor(this), this,
InferSpewColorReset(), TypeString(type).get());
/* Propagate the type to all constraints. */
if (!cx->isHelperThreadContext()) {
TypeConstraint* constraint = constraintList(sweep);
while (constraint) {
constraint->newType(cx, this, type);
constraint = constraint->next();
}
} else {
MOZ_ASSERT(!constraintList_);
}
}
void TypeSet::print(FILE* fp) {
bool fromDebugger = !fp;
if (!fp) {
fp = stderr;
}
if (flags & TYPE_FLAG_NON_DATA_PROPERTY) {
fprintf(fp, " [non-data]");
}
if (flags & TYPE_FLAG_NON_WRITABLE_PROPERTY) {
fprintf(fp, " [non-writable]");
}
if (definiteProperty()) {
fprintf(fp, " [definite:%u]", definiteSlot());
}
if (baseFlags() == 0 && !baseObjectCount()) {
fprintf(fp, " missing");
return;
}
if (flags & TYPE_FLAG_UNKNOWN) {
fprintf(fp, " unknown");
}
if (flags & TYPE_FLAG_ANYOBJECT) {
fprintf(fp, " object");
}
if (flags & TYPE_FLAG_UNDEFINED) {
fprintf(fp, " void");
}
if (flags & TYPE_FLAG_NULL) {
fprintf(fp, " null");
}
if (flags & TYPE_FLAG_BOOLEAN) {
fprintf(fp, " bool");
}
if (flags & TYPE_FLAG_INT32) {
fprintf(fp, " int");
}
if (flags & TYPE_FLAG_DOUBLE) {
fprintf(fp, " float");
}
if (flags & TYPE_FLAG_STRING) {
fprintf(fp, " string");
}
if (flags & TYPE_FLAG_SYMBOL) {
fprintf(fp, " symbol");
}
if (flags & TYPE_FLAG_BIGINT) {
fprintf(fp, " BigInt");
}
if (flags & TYPE_FLAG_LAZYARGS) {
fprintf(fp, " lazyargs");
}
uint32_t objectCount = baseObjectCount();
if (objectCount) {
fprintf(fp, " object[%u]", objectCount);
unsigned count = getObjectCount();
for (unsigned i = 0; i < count; i++) {
ObjectKey* key = getObject(i);
if (key) {
fprintf(fp, " %s", TypeString(ObjectType(key)).get());
}
}
}
if (fromDebugger) {
fprintf(fp, "\n");
}
}
/* static */
void TypeSet::readBarrier(const TypeSet* types) {
if (types->unknownObject()) {
return;
}
for (unsigned i = 0; i < types->getObjectCount(); i++) {
if (ObjectKey* key = types->getObject(i)) {
if (key->isSingleton()) {
(void)key->singleton();
} else {
(void)key->group();
}
}
}
}
/* static */
bool TypeSet::IsTypeMarked(JSRuntime* rt, TypeSet::Type* v) {
bool rv;
if (v->isSingletonUnchecked()) {
JSObject* obj = v->singletonNoBarrier();
rv = IsMarkedUnbarriered(rt, &obj);
*v = TypeSet::ObjectType(obj);
} else if (v->isGroupUnchecked()) {
ObjectGroup* group = v->groupNoBarrier();
rv = IsMarkedUnbarriered(rt, &group);
*v = TypeSet::ObjectType(group);
} else {
rv = true;
}
return rv;
}
static inline bool IsObjectKeyAboutToBeFinalized(TypeSet::ObjectKey** keyp) {
TypeSet::ObjectKey* key = *keyp;
bool isAboutToBeFinalized;
if (key->isGroup()) {
ObjectGroup* group = key->groupNoBarrier();
isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&group);
if (!isAboutToBeFinalized) {
*keyp = TypeSet::ObjectKey::get(group);
}
} else {
MOZ_ASSERT(key->isSingleton());
JSObject* singleton = key->singletonNoBarrier();
isAboutToBeFinalized = IsAboutToBeFinalizedUnbarriered(&singleton);
if (!isAboutToBeFinalized) {
*keyp = TypeSet::ObjectKey::get(singleton);
}
}
return isAboutToBeFinalized;
}
bool TypeSet::IsTypeAboutToBeFinalized(TypeSet::Type* v) {
bool isAboutToBeFinalized;
if (v->isObjectUnchecked()) {
TypeSet::ObjectKey* key = v->objectKey();
isAboutToBeFinalized = IsObjectKeyAboutToBeFinalized(&key);
if (!isAboutToBeFinalized) {
*v = TypeSet::ObjectType(key);
}
} else {
isAboutToBeFinalized = false;
}
return isAboutToBeFinalized;
}
bool TypeSet::cloneIntoUninitialized(LifoAlloc* alloc,
TemporaryTypeSet* result) const {
unsigned objectCount = baseObjectCount();
unsigned capacity =
(objectCount >= 2) ? TypeHashSet::Capacity(objectCount) : 0;
ObjectKey** newSet;
if (capacity) {
// We allocate an extra word right before the array that stores the
// capacity, so make sure we clone that as well.
newSet = alloc->newArray<ObjectKey*>(capacity + 1);
if (!newSet) {
return false;
}
newSet++;
PodCopy(newSet - 1, objectSet - 1, capacity + 1);
}
new (result) TemporaryTypeSet(flags, capacity ? newSet : objectSet);
return true;
}
TemporaryTypeSet* TypeSet::clone(LifoAlloc* alloc) const {
TemporaryTypeSet* res = alloc->pod_malloc<TemporaryTypeSet>();
if (!res || !cloneIntoUninitialized(alloc, res)) {
return nullptr;
}
return res;
}
TemporaryTypeSet* TypeSet::cloneObjectsOnly(LifoAlloc* alloc) {
TemporaryTypeSet* res = clone(alloc);
if (!res) {
return nullptr;
}
res->flags &= ~TYPE_FLAG_BASE_MASK | TYPE_FLAG_ANYOBJECT;
return res;
}
TemporaryTypeSet* TypeSet::cloneWithoutObjects(LifoAlloc* alloc) {
TemporaryTypeSet* res = alloc->new_<TemporaryTypeSet>();
if (!res) {
return nullptr;
}
res->flags = flags & ~TYPE_FLAG_ANYOBJECT;
res->setBaseObjectCount(0);
return res;
}
/* static */
TemporaryTypeSet* TypeSet::unionSets(TypeSet* a, TypeSet* b, LifoAlloc* alloc) {
TemporaryTypeSet* res = alloc->new_<TemporaryTypeSet>(
a->baseFlags() | b->baseFlags(), static_cast<ObjectKey**>(nullptr));
if (!res) {
return nullptr;
}
if (!res->unknownObject()) {
for (size_t i = 0; i < a->getObjectCount() && !res->unknownObject(); i++) {
if (ObjectKey* key = a->getObject(i)) {
res->addType(ObjectType(key), alloc);
}
}
for (size_t i = 0; i < b->getObjectCount() && !res->unknownObject(); i++) {
if (ObjectKey* key = b->getObject(i)) {
res->addType(ObjectType(key), alloc);
}
}
}
return res;
}
/* static */
TemporaryTypeSet* TypeSet::removeSet(TemporaryTypeSet* input,
TemporaryTypeSet* removal,
LifoAlloc* alloc) {
// Only allow removal of primitives and the "AnyObject" flag.
MOZ_ASSERT(!removal->unknown());
MOZ_ASSERT_IF(!removal->unknownObject(), removal->getObjectCount() == 0);
uint32_t flags = input->baseFlags() & ~removal->baseFlags();
TemporaryTypeSet* res =
alloc->new_<TemporaryTypeSet>(flags, static_cast<ObjectKey**>(nullptr));
if (!res) {
return nullptr;
}
res->setBaseObjectCount(0);
if (removal->unknownObject() || input->unknownObject()) {
return res;
}
for (size_t i = 0; i < input->getObjectCount(); i++) {
if (!input->getObject(i)) {
continue;
}
res->addType(TypeSet::ObjectType(input->getObject(i)), alloc);
}
return res;
}
/* static */
TemporaryTypeSet* TypeSet::intersectSets(TemporaryTypeSet* a,
TemporaryTypeSet* b,
LifoAlloc* alloc) {
TemporaryTypeSet* res;
res = alloc->new_<TemporaryTypeSet>(a->baseFlags() & b->baseFlags(),
static_cast<ObjectKey**>(nullptr));
if (!res) {
return nullptr;
}
res->setBaseObjectCount(0);
if (res->unknownObject()) {
return res;
}
MOZ_ASSERT(!a->unknownObject() || !b->unknownObject());
if (a->unknownObject()) {
for (size_t i = 0; i < b->getObjectCount(); i++) {
if (b->getObject(i)) {
res->addType(ObjectType(b->getObject(i)), alloc);
}
}
return res;
}
if (b->unknownObject()) {
for (size_t i = 0; i < a->getObjectCount(); i++) {
if (a->getObject(i)) {
res->addType(ObjectType(a->getObject(i)), alloc);
}
}
return res;
}
MOZ_ASSERT(!a->unknownObject() && !b->unknownObject());
for (size_t i = 0; i < a->getObjectCount(); i++) {
for (size_t j = 0; j < b->getObjectCount(); j++) {
if (b->getObject(j) != a->getObject(i)) {
continue;
}
if (!b->getObject(j)) {
continue;
}
res->addType(ObjectType(b->getObject(j)), alloc);
break;
}
}
return res;
}
/////////////////////////////////////////////////////////////////////
// Compiler constraints
/////////////////////////////////////////////////////////////////////
// Compiler constraints overview
//
// Constraints generated during Ion compilation capture assumptions made about
// heap properties that will trigger invalidation of the resulting Ion code if
// the constraint is violated. Constraints can only be attached to type sets on
// the main thread, so to allow compilation to occur almost entirely off thread
// the generation is split into two phases.
//
// During compilation, CompilerConstraint values are constructed in a list,
// recording the heap property type set which was read from and its expected
// contents, along with the assumption made about those contents.
//
// At the end of compilation, when linking the result on the main thread, the
// list of compiler constraints are read and converted to type constraints and
// attached to the type sets. If the property type sets have changed so that the
// assumptions no longer hold then the compilation is aborted and its result
// discarded.
// Superclass of all constraints generated during Ion compilation. These may
// be allocated off thread, using the current JIT context's allocator.
class CompilerConstraint {
public:
// Property being queried by the compiler.
HeapTypeSetKey property;
// Contents of the property at the point when the query was performed. This
// may differ from the actual property types later in compilation as the
// main thread performs side effects.
TemporaryTypeSet* expected;
CompilerConstraint(LifoAlloc* alloc, const HeapTypeSetKey& property)
: property(property),
expected(property.maybeTypes() ? property.maybeTypes()->clone(alloc)
: nullptr) {}
// Generate the type constraint recording the assumption made by this
// compilation. Returns true if the assumption originally made still holds.
virtual bool generateTypeConstraint(JSContext* cx,
RecompileInfo recompileInfo) = 0;
};
class js::CompilerConstraintList {
public:
struct FrozenScript {
JSScript* script;
TemporaryTypeSet* thisTypes;
TemporaryTypeSet* argTypes;
TemporaryTypeSet* bytecodeTypes;
};
private:
// OOM during generation of some constraint.
bool failed_;
// Allocator used for constraints.
LifoAlloc* alloc_;
// Constraints generated on heap properties.
Vector<CompilerConstraint*, 0, jit::JitAllocPolicy> constraints;
// Scripts whose stack type sets were frozen for the compilation.
Vector<FrozenScript, 1, jit::JitAllocPolicy> frozenScripts;
public:
explicit CompilerConstraintList(jit::TempAllocator& alloc)
: failed_(false),
alloc_(alloc.lifoAlloc()),
constraints(alloc),
frozenScripts(alloc) {}
void add(CompilerConstraint* constraint) {
if (!constraint || !constraints.append(constraint)) {
setFailed();
}
}
void freezeScript(JSScript* script, TemporaryTypeSet* thisTypes,
TemporaryTypeSet* argTypes,
TemporaryTypeSet* bytecodeTypes) {
FrozenScript entry;
entry.script = script;
entry.thisTypes = thisTypes;
entry.argTypes = argTypes;
entry.bytecodeTypes = bytecodeTypes;
if (!frozenScripts.append(entry)) {
setFailed();
}
}
size_t length() { return constraints.length(); }
CompilerConstraint* get(size_t i) { return constraints[i]; }
size_t numFrozenScripts() { return frozenScripts.length(); }
const FrozenScript& frozenScript(size_t i) { return frozenScripts[i]; }
bool failed() { return failed_; }
void setFailed() { failed_ = true; }
LifoAlloc* alloc() const { return alloc_; }
};
CompilerConstraintList* js::NewCompilerConstraintList(
jit::TempAllocator& alloc) {
return alloc.lifoAlloc()->new_<CompilerConstraintList>(alloc);
}
/* static */
bool JitScript::FreezeTypeSets(CompilerConstraintList* constraints,
JSScript* script, TemporaryTypeSet** pThisTypes,
TemporaryTypeSet** pArgTypes,
TemporaryTypeSet** pBytecodeTypes) {
LifoAlloc* alloc = constraints->alloc();
AutoSweepJitScript sweep(script);
JitScript* jitScript = script->jitScript();
StackTypeSet* existing = jitScript->typeArray(sweep);
size_t count = jitScript->numTypeSets();
TemporaryTypeSet* types =
alloc->newArrayUninitialized<TemporaryTypeSet>(count);
if (!types) {
return false;
}
for (size_t i = 0; i < count; i++) {
if (!existing[i].cloneIntoUninitialized(alloc, &types[i])) {
return false;
}
}
size_t thisTypesIndex = jitScript->thisTypes(sweep, script) - existing;
*pThisTypes = types + thisTypesIndex;
if (script->function() && script->function()->nargs() > 0) {
size_t firstArgIndex = jitScript->argTypes(sweep, script, 0) - existing;
*pArgTypes = types + firstArgIndex;
} else {
*pArgTypes = nullptr;
}
*pBytecodeTypes = types;
constraints->freezeScript(script, *pThisTypes, *pArgTypes, *pBytecodeTypes);
return true;
}
namespace {
template <typename T>
class CompilerConstraintInstance : public CompilerConstraint {
T data;
public:
CompilerConstraintInstance<T>(LifoAlloc* alloc,
const HeapTypeSetKey& property, const T& data)
: CompilerConstraint(alloc, property), data(data) {}
bool generateTypeConstraint(JSContext* cx,
RecompileInfo recompileInfo) override;
};
// Constraint generated from a CompilerConstraint when linking the compilation.
template <typename T>
class TypeCompilerConstraint : public TypeConstraint {
// Compilation which this constraint may invalidate.
RecompileInfo compilation;
T data;
public:
TypeCompilerConstraint<T>(RecompileInfo compilation, const T& data)
: compilation(compilation), data(data) {}
const char* kind() override { return data.kind(); }
void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) override {
if (data.invalidateOnNewType(type)) {
cx->zone()->types.addPendingRecompile(cx, compilation);
}
}
void newPropertyState(JSContext* cx, TypeSet* source) override {
if (data.invalidateOnNewPropertyState(source)) {
cx->zone()->types.addPendingRecompile(cx, compilation);
}
}
void newObjectState(JSContext* cx, ObjectGroup* group) override {
// Note: Once the object has unknown properties, no more notifications
// will be sent on changes to its state, so always invalidate any
// associated compilations.
AutoSweepObjectGroup sweep(group);
if (group->unknownProperties(sweep) ||
data.invalidateOnNewObjectState(sweep, group)) {
cx->zone()->types.addPendingRecompile(cx, compilation);
}
}
bool sweep(TypeZone& zone, TypeConstraint** res) override {
if (data.shouldSweep() || compilation.shouldSweep(zone)) {
return false;
}
*res = zone.typeLifoAlloc().new_<TypeCompilerConstraint<T> >(compilation,
data);
return true;
}
Compartment* maybeCompartment() override { return data.maybeCompartment(); }
};
template <typename T>
bool CompilerConstraintInstance<T>::generateTypeConstraint(
JSContext* cx, RecompileInfo recompileInfo) {
// This should only be called in suppress-GC contexts, but the static
// analysis doesn't know this.
MOZ_ASSERT(cx->suppressGC);
JS::AutoSuppressGCAnalysis suppress;
if (property.object()->unknownProperties()) {
return false;
}
if (!property.instantiate(cx)) {
return false;
}
AutoSweepObjectGroup sweep(property.object()->maybeGroup());
if (property.object()->maybeGroup()->unknownProperties(sweep)) {
return false;
}
if (!data.constraintHolds(sweep, cx, property, expected)) {
return false;
}
return property.maybeTypes()->addConstraint(
cx,
cx->typeLifoAlloc().new_<TypeCompilerConstraint<T> >(recompileInfo, data),
/* callExisting = */ false);
}
} /* anonymous namespace */
const JSClass* TypeSet::ObjectKey::clasp() {
return isGroup() ? group()->clasp() : singleton()->getClass();
}
TaggedProto TypeSet::ObjectKey::proto() {
return isGroup() ? group()->proto() : singleton()->taggedProto();
}
ObjectGroup* TypeSet::ObjectKey::maybeGroup() {
if (isGroup()) {
return group();
}
if (!singleton()->hasLazyGroup()) {
return singleton()->group();
}
return nullptr;
}
bool TypeSet::ObjectKey::unknownProperties() {
if (ObjectGroup* group = maybeGroup()) {
AutoSweepObjectGroup sweep(group);
return group->unknownProperties(sweep);
}
return false;
}
HeapTypeSetKey TypeSet::ObjectKey::property(jsid id) {
MOZ_ASSERT(!unknownProperties());
HeapTypeSetKey property;
property.object_ = this;
property.id_ = id;
if (ObjectGroup* group = maybeGroup()) {
AutoSweepObjectGroup sweep(group);
if (!group->unknownProperties(sweep)) {
property.maybeTypes_ = group->maybeGetProperty(sweep, id);
}
}
return property;
}
void TypeSet::ObjectKey::ensureTrackedProperty(JSContext* cx, jsid id) {
// If we are accessing a lazily defined property which actually exists in
// the VM and has not been instantiated yet, instantiate it now if we are
// on the main thread and able to do so.
if (!JSID_IS_VOID(id) && !JSID_IS_EMPTY(id)) {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime()));
if (isSingleton()) {
JSObject* obj = singleton();
if (obj->isNative() && obj->as<NativeObject>().containsPure(id)) {
EnsureTrackPropertyTypes(cx, obj, id);
}
}
}
}
void js::EnsureTrackPropertyTypes(JSContext* cx, JSObject* obj, jsid id) {
if (!IsTypeInferenceEnabled()) {
return;
}
id = IdToTypeId(id);
if (obj->isSingleton()) {
AutoEnterAnalysis enter(cx);
if (obj->hasLazyGroup()) {
AutoEnterOOMUnsafeRegion oomUnsafe;
RootedObject objRoot(cx, obj);
if (!JSObject::getGroup(cx, objRoot)) {
oomUnsafe.crash(
"Could not allocate ObjectGroup in EnsureTrackPropertyTypes");
}
}
ObjectGroup* group = obj->group();
AutoSweepObjectGroup sweep(group);
if (!group->unknownProperties(sweep) &&
!group->getProperty(sweep, cx, obj, id)) {
MOZ_ASSERT(group->unknownProperties(sweep));
return;
}
}
MOZ_ASSERT(obj->group()->unknownPropertiesDontCheckGeneration() ||
TrackPropertyTypes(obj, id));
}
bool HeapTypeSetKey::instantiate(JSContext* cx) {
if (maybeTypes()) {
return true;
}
if (object()->isSingleton()) {
RootedObject obj(cx, object()->singleton());
if (!JSObject::getGroup(cx, obj)) {
cx->clearPendingException();
return false;
}
}
JSObject* obj = object()->isSingleton() ? object()->singleton() : nullptr;
AutoSweepObjectGroup sweep(object()->maybeGroup());
maybeTypes_ = object()->maybeGroup()->getProperty(sweep, cx, obj, id());
return maybeTypes_ != nullptr;
}
static bool CheckFrozenTypeSet(const AutoSweepJitScript& sweep, JSContext* cx,
TemporaryTypeSet* frozen, StackTypeSet* actual) {
// Return whether the types frozen for a script during compilation are
// still valid. Also check for any new types added to the frozen set during
// compilation, and add them to the actual stack type sets. These new types
// indicate places where the compiler relaxed its possible inputs to be
// more tolerant of potential new types.
if (!actual->isSubset(frozen)) {
return false;
}
if (!frozen->isSubset(actual)) {
TypeSet::TypeList list;
frozen->enumerateTypes(&list);
for (size_t i = 0; i < list.length(); i++) {
actual->addType(sweep, cx, list[i]);
}
}
return true;
}
namespace {
/*
* As for TypeConstraintFreeze, but describes an implicit freeze constraint
* added for stack types within a script. Applies to all compilations of the
* script, not just a single one.
*/
class TypeConstraintFreezeStack : public TypeConstraint {
JSScript* script_;
public:
explicit TypeConstraintFreezeStack(JSScript* script) : script_(script) {}
const char* kind() override { return "freezeStack"; }
void newType(JSContext* cx, TypeSet* source, TypeSet::Type type) override {
/*
* Unlike TypeConstraintFreeze, triggering this constraint once does
* not disable it on future changes to the type set.
*/
cx->zone()->types.addPendingRecompile(cx, script_);
}
bool sweep(TypeZone& zone, TypeConstraint** res) override {
if (IsAboutToBeFinalizedUnbarriered(&script_)) {
return false;
}
*res = zone.typeLifoAlloc().new_<TypeConstraintFreezeStack>(script_);
return true;
}
Compartment* maybeCompartment() override { return script_->compartment(); }
};
} /* anonymous namespace */
bool js::FinishCompilation(JSContext* cx, HandleScript script,
CompilerConstraintList* constraints,
IonCompilationId compilationId, bool* isValidOut) {
MOZ_ASSERT(*cx->zone()->types.currentCompilationId() == compilationId);
if (!IsTypeInferenceEnabled()) {
MOZ_ASSERT(!constraints->failed());
MOZ_ASSERT(constraints->length() == 0);
MOZ_ASSERT(constraints->numFrozenScripts() == 0);
*isValidOut = true;
return true;
}
if (constraints->failed()) {
return false;
}
RecompileInfo recompileInfo(script, compilationId);
bool succeeded = true;
for (size_t i = 0; i < constraints->length(); i++) {
CompilerConstraint* constraint = constraints->get(i);
if (!constraint->generateTypeConstraint(cx, recompileInfo)) {
succeeded = false;
}
}
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry =
constraints->frozenScript(i);
// It could happen that one of the compiled scripts was made a
// debuggee mid-compilation (e.g., via setting a breakpoint). If so,
// throw away the compilation.
if (entry.script->isDebuggee()) {
succeeded = false;
break;
}
JitScript* jitScript = entry.script->jitScript();
AutoSweepJitScript sweep(entry.script);
if (!CheckFrozenTypeSet(sweep, cx, entry.thisTypes,
jitScript->thisTypes(sweep, entry.script))) {
succeeded = false;
}
unsigned nargs =
entry.script->function() ? entry.script->function()->nargs() : 0;
for (size_t i = 0; i < nargs; i++) {
if (!CheckFrozenTypeSet(sweep, cx, &entry.argTypes[i],
jitScript->argTypes(sweep, entry.script, i))) {
succeeded = false;
}
}
for (size_t i = 0; i < entry.script->numBytecodeTypeSets(); i++) {
if (!CheckFrozenTypeSet(sweep, cx, &entry.bytecodeTypes[i],
&jitScript->typeArray(sweep)[i])) {
succeeded = false;
}
}
// Add this compilation to the inlinedCompilations list of each inlined
// script, so we can invalidate it on changes to stack type sets.
if (entry.script != script) {
if (!jitScript->addInlinedCompilation(sweep, recompileInfo)) {
succeeded = false;
}
}
// If necessary, add constraints to trigger invalidation on the script
// after any future changes to the stack type sets.
if (jitScript->hasFreezeConstraints(sweep)) {
continue;
}
size_t count = jitScript->numTypeSets();
StackTypeSet* array = jitScript->typeArray(sweep);
for (size_t i = 0; i < count; i++) {
if (!array[i].addConstraint(
cx,
cx->typeLifoAlloc().new_<TypeConstraintFreezeStack>(entry.script),
false)) {
succeeded = false;
}
}
if (succeeded) {
jitScript->setHasFreezeConstraints(sweep);
}
}
if (!succeeded) {
script->resetWarmUpCounterToDelayIonCompilation();
*isValidOut = false;
return true;
}
*isValidOut = true;
return true;
}
static void CheckDefinitePropertiesTypeSet(const AutoSweepJitScript& sweep,
JSContext* cx,
TemporaryTypeSet* frozen,
StackTypeSet* actual) {
// The definite properties analysis happens on the main thread, so no new
// types can have been added to actual. The analysis may have updated the
// contents of |frozen| though with new speculative types, and these need
// to be reflected in |actual| for AddClearDefiniteFunctionUsesInScript
// to work.
if (!frozen->isSubset(actual)) {
TypeSet::TypeList list;
frozen->enumerateTypes(&list);
for (size_t i = 0; i < list.length(); i++) {
actual->addType(sweep, cx, list[i]);
}
}
}
void js::FinishDefinitePropertiesAnalysis(JSContext* cx,
CompilerConstraintList* constraints) {
#ifdef DEBUG
// Assert no new types have been added to the StackTypeSets. Do this before
// calling CheckDefinitePropertiesTypeSet, as it may add new types to the
// StackTypeSets and break these invariants if a script is inlined more
// than once. See also CheckDefinitePropertiesTypeSet.
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry =
constraints->frozenScript(i);
JSScript* script = entry.script;
JitScript* jitScript = script->jitScript();
AutoSweepJitScript sweep(script);
MOZ_ASSERT(jitScript->thisTypes(sweep, script)->isSubset(entry.thisTypes));
unsigned nargs =
entry.script->function() ? entry.script->function()->nargs() : 0;
for (size_t j = 0; j < nargs; j++) {
StackTypeSet* argTypes = jitScript->argTypes(sweep, script, j);
MOZ_ASSERT(argTypes->isSubset(&entry.argTypes[j]));
}
for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
MOZ_ASSERT(
jitScript->typeArray(sweep)[j].isSubset(&entry.bytecodeTypes[j]));
}
}
#endif
for (size_t i = 0; i < constraints->numFrozenScripts(); i++) {
const CompilerConstraintList::FrozenScript& entry =
constraints->frozenScript(i);
JSScript* script = entry.script;
JitScript* jitScript = script->jitScript();
AutoSweepJitScript sweep(script);
CheckDefinitePropertiesTypeSet(sweep, cx, entry.thisTypes,
jitScript->thisTypes(sweep, script));
unsigned nargs = script->function() ? script->function()->nargs() : 0;
for (size_t j = 0; j < nargs; j++) {
StackTypeSet* argTypes = jitScript->argTypes(sweep, script, j);
CheckDefinitePropertiesTypeSet(sweep, cx, &entry.argTypes[j], argTypes);
}
for (size_t j = 0; j < script->numBytecodeTypeSets(); j++) {
CheckDefinitePropertiesTypeSet(sweep, cx, &entry.bytecodeTypes[j],
&jitScript->typeArray(sweep)[j]);
}
}
}
namespace {
// Constraint which triggers recompilation of a script if any type is added to a
// type set. */
class ConstraintDataFreeze {
public:
ConstraintDataFreeze() = default;
const char* kind() { return "freeze"; }
bool invalidateOnNewType(TypeSet::Type type) { return true; }
bool invalidateOnNewPropertyState(TypeSet* property) { return true; }
bool invalidateOnNewObjectState(const AutoSweepObjectGroup& sweep,
ObjectGroup* group) {
return false;
}
bool constraintHolds(const AutoSweepObjectGroup& sweep, JSContext* cx,
const HeapTypeSetKey& property,
TemporaryTypeSet* expected) {
return expected ? property.maybeTypes()->isSubset(expected)
: property.maybeTypes()->empty();
}
bool shouldSweep() { return false; }
Compartment* maybeCompartment() { return nullptr; }
};
} /* anonymous namespace */
void HeapTypeSetKey::freeze(CompilerConstraintList* constraints) {
LifoAlloc* alloc = constraints->alloc();
LifoAlloc::AutoFallibleScope fallibleAllocator(alloc);
using T = CompilerConstraintInstance<ConstraintDataFreeze>;
constraints->add(alloc->new_<T>(alloc, *this, ConstraintDataFreeze()));
}
static inline jit::MIRType GetMIRTypeFromTypeFlags(TypeFlags flags) {
switch (flags) {
case TYPE_FLAG_UNDEFINED:
return jit::MIRType::Undefined;
case TYPE_FLAG_NULL:
return jit::MIRType::Null;
case TYPE_FLAG_BOOLEAN:
return jit::MIRType::Boolean;
case TYPE_FLAG_INT32:
return jit::MIRType::Int32;
case (TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE):
return jit::MIRType::Double;
case TYPE_FLAG_STRING:
return jit::MIRType::String;
case TYPE_FLAG_SYMBOL:
return jit::MIRType::Symbol;
case TYPE_FLAG_BIGINT:
return jit::MIRType::BigInt;
case TYPE_FLAG_LAZYARGS:
return jit::MIRType::MagicOptimizedArguments;
case TYPE_FLAG_ANYOBJECT:
return jit::MIRType::Object;
default:
return jit::MIRType::Value;
}
}
jit::MIRType TemporaryTypeSet::getKnownMIRType() {
TypeFlags flags = baseFlags();
jit::MIRType type;
if (baseObjectCount()) {
type = flags ? jit::MIRType::Value : jit::MIRType::Object;
} else {
type = GetMIRTypeFromTypeFlags(flags);
}
/*
* If the type set is totally empty then it will be treated as unknown,
* but we still need to record the dependency as adding a new type can give
* it a definite type tag. This is not needed if there are enough types
* that the exact tag is unknown, as it will stay unknown as more types are
* added to the set.
*/
DebugOnly<bool> empty = flags == 0 && baseObjectCount() == 0;
MOZ_ASSERT_IF(empty, type == jit::MIRType::Value);
return type;
}
jit::MIRType HeapTypeSetKey::knownMIRType(CompilerConstraintList* constraints) {
TypeSet* types = maybeTypes();
if (!types || types->unknown()) {
return jit::MIRType::Value;
}
TypeFlags flags = types->baseFlags() & ~TYPE_FLAG_ANYOBJECT;
jit::MIRType type;
if (types->unknownObject() || types->getObjectCount()) {
type = flags ? jit::MIRType::Value : jit::MIRType::Object;
} else {
type = GetMIRTypeFromTypeFlags(flags);
}
if (type != jit::MIRType::Value) {
freeze(constraints);
}
/*
* If the type set is totally empty then it will be treated as unknown,
* but we still need to record the dependency as adding a new type can give
* it a definite type tag. This is not needed if there are enough types
* that the exact tag is unknown, as it will stay unknown as more types are
* added to the set.
*/
MOZ_ASSERT_IF(types->empty(), type == jit::MIRType::Value);
return type;
}
bool HeapTypeSetKey::isOwnProperty(CompilerConstraintList* constraints,
bool allowEmptyTypesForGlobal /* = false*/) {
if (maybeTypes() &&
(!maybeTypes()->empty() || maybeTypes()->nonDataProperty())) {
return true;
}
if (object()->isSingleton()) {
JSObject* obj = object()->singleton();
MOZ_ASSERT(CanHaveEmptyPropertyTypesForOwnProperty(obj) ==
obj->is<GlobalObject>());
if (!allowEmptyTypesForGlobal) {
if (CanHaveEmptyPropertyTypesForOwnProperty(obj)) {
return true;
}
}
}
freeze(constraints);
return false;
}
JSObject* TemporaryTypeSet::maybeSingleton() {
if (baseFlags() != 0 || baseObjectCount() != 1) {
return nullptr;
}
return getSingleton(0);
}
TemporaryTypeSet::ObjectKey* TemporaryTypeSet::maybeSingleObject() {
if (baseFlags() != 0 || baseObjectCount() != 1) {
return nullptr;
}
return getObject(0);
}
JSObject* HeapTypeSetKey::singleton(CompilerConstraintList* constraints) {
HeapTypeSet* types = maybeTypes();
if (!types || types->nonDataProperty() || types->baseFlags() != 0 ||
types->getObjectCount() != 1) {
return nullptr;
}
JSObject* obj = types->getSingleton(0);
if (obj) {
freeze(constraints);
}
return obj;
}
bool HeapTypeSetKey::needsBarrier(CompilerConstraintList* constraints) {
TypeSet* types = maybeTypes();
if (!types) {
return false;
}
bool result = types->unknownObject() || types->getObjectCount() > 0 ||
types->hasAnyFlag(TYPE_FLAG_PRIMITIVE_GCTHING);
if (!result) {