Source code
Revision control
Copy as Markdown
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
#include "vm/ObjectFuse.h"
#include "gc/Barrier.h"
#include "gc/StableCellHasher.h"
#include "jit/InvalidationScriptSet.h"
#include "js/SweepingAPI.h"
#include "vm/JSScript.h"
#include "vm/NativeObject.h"
#include "vm/PropertyInfo.h"
#include "gc/StableCellHasher-inl.h"
#include "vm/JSScript-inl.h"
using namespace js;
bool ObjectFuse::ensurePropertyStateLength(uint32_t length) {
MOZ_ASSERT(length > 0);
if (length <= propertyStateLength_) {
MOZ_ASSERT(propertyStateBits_);
return true;
}
if (!propertyStateBits_) {
propertyStateBits_.reset(js_pod_malloc<uint32_t>(length));
if (!propertyStateBits_) {
return false;
}
} else {
uint32_t* bitsNew = js_pod_realloc<uint32_t>(propertyStateBits_.get(),
propertyStateLength_, length);
if (!bitsNew) {
return false;
}
(void)propertyStateBits_.release();
propertyStateBits_.reset(bitsNew);
}
// Initialize the new words to 0.
static_assert(uint32_t(PropertyState::Untracked) == 0);
std::uninitialized_fill_n(propertyStateBits_.get() + propertyStateLength_,
length - propertyStateLength_, 0);
propertyStateLength_ = length;
return true;
}
bool ObjectFuse::addDependency(uint32_t propSlot,
const jit::IonScriptKey& ionScript) {
MOZ_ASSERT(getPropertyState(propSlot) == PropertyState::Constant);
auto p = dependencies_.lookupForAdd(propSlot);
if (!p) {
if (!dependencies_.add(p, propSlot, jit::DependentIonScriptSet())) {
return false;
}
}
return p->value().addToSet(ionScript);
}
void ObjectFuse::invalidateDependentIonScriptsForProperty(JSContext* cx,
PropertyInfo prop,
const char* reason) {
if (auto p = dependencies_.lookup(prop.slot())) {
p->value().invalidateAndClear(cx, reason);
dependencies_.remove(p);
}
}
void ObjectFuse::invalidateAllDependentIonScripts(JSContext* cx,
const char* reason) {
for (auto r = dependencies_.all(); !r.empty(); r.popFront()) {
r.front().value().invalidateAndClear(cx, reason);
}
dependencies_.clear();
}
bool ObjectFuse::markPropertyConstant(PropertyInfo prop) {
MOZ_ASSERT(getPropertyState(prop) == PropertyState::Untracked);
uint32_t index = prop.slot() / NumPropsPerWord;
if (!ensurePropertyStateLength(index + 1)) {
return false;
}
setPropertyState(prop, PropertyState::Constant);
return true;
}
bool ObjectFuse::tryOptimizeConstantProperty(PropertyInfo prop) {
if (MOZ_UNLIKELY(!generation_.isValid())) {
return false;
}
PropertyState state = getPropertyState(prop);
switch (state) {
case PropertyState::Untracked:
if (!markPropertyConstant(prop)) {
return false;
}
MOZ_ASSERT(isConstantProperty(prop));
return true;
case PropertyState::Constant:
return true;
case PropertyState::NotConstant:
return false;
}
MOZ_CRASH("Unreachable");
}
void ObjectFuse::handlePropertyValueChange(JSContext* cx, PropertyInfo prop) {
// Custom data properties aren't optimized with object fuses.
if (!prop.hasSlot()) {
return;
}
PropertyState state = getPropertyState(prop);
switch (state) {
case PropertyState::Untracked:
// Mark the property as Constant. IC code for SetProp operations relies on
// properties getting marked NotConstant after a few sets, because we can
// only optimize stores to NotConstant properties. We can ignore OOM here.
(void)markPropertyConstant(prop);
break;
case PropertyState::Constant:
invalidatedConstantProperty_ = 1;
setPropertyState(prop, PropertyState::NotConstant);
invalidateDependentIonScriptsForProperty(cx, prop,
"changed constant property");
break;
case PropertyState::NotConstant:
break;
}
}
void ObjectFuse::handlePropertyRemove(JSContext* cx, PropertyInfo prop) {
if (!prop.hasSlot() || isUntrackedProperty(prop)) {
return;
}
bumpGeneration();
invalidateDependentIonScriptsForProperty(cx, prop, "removed property");
// Ensure a new property with this slot number will have the correct initial
// state.
setPropertyState(prop, PropertyState::Untracked);
}
void ObjectFuse::handleTeleportingShadowedProperty(JSContext* cx,
PropertyInfo prop) {
if (!prop.hasSlot() || isUntrackedProperty(prop)) {
return;
}
bumpGeneration();
invalidateDependentIonScriptsForProperty(cx, prop,
"teleporting shadowed property");
}
void ObjectFuse::handleTeleportingProtoMutation(JSContext* cx) {
bumpGeneration();
invalidateAllDependentIonScripts(cx, "proto mutation");
}
void ObjectFuse::handleObjectSwap(JSContext* cx) {
bumpGeneration();
// Reset state for all properties.
propertyStateLength_ = 0;
propertyStateBits_.reset();
invalidateAllDependentIonScripts(cx, "object swap");
}
void ObjectFuse::handleShadowedGlobalProperty(JSContext* cx,
PropertyInfo prop) {
if (isUntrackedProperty(prop)) {
return;
}
bumpGeneration();
invalidateDependentIonScriptsForProperty(cx, prop,
"shadowed global property");
}
const char* ObjectFuse::getPropertyStateString(PropertyInfo prop) const {
PropertyState state = getPropertyState(prop);
switch (state) {
case PropertyState::Untracked:
return "Untracked";
case PropertyState::Constant:
return "Constant";
case PropertyState::NotConstant:
return "NotConstant";
}
MOZ_CRASH("Unreachable");
}
size_t ObjectFuse::sizeOfIncludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t result = mallocSizeOf(this);
if (propertyStateBits_) {
result += mallocSizeOf(propertyStateBits_.get());
}
result += dependencies_.shallowSizeOfExcludingThis(mallocSizeOf);
for (auto r = dependencies_.all(); !r.empty(); r.popFront()) {
result += r.front().value().sizeOfExcludingThis(mallocSizeOf);
}
return result;
}
ObjectFuse* ObjectFuseMap::getOrCreate(JSContext* cx, NativeObject* obj) {
MOZ_ASSERT(obj->hasObjectFuse());
auto p = objectFuses_.lookupForAdd(obj);
if (!p) {
auto fuse = MakeUnique<ObjectFuse>();
if (!fuse || !objectFuses_.add(p, obj, std::move(fuse))) {
ReportOutOfMemory(cx);
return nullptr;
}
}
return p->value().get();
}
ObjectFuse* ObjectFuseMap::get(NativeObject* obj) {
MOZ_ASSERT(obj->hasObjectFuse());
auto p = objectFuses_.lookup(obj);
return p ? p->value().get() : nullptr;
}
size_t ObjectFuseMap::sizeOfExcludingThis(
mozilla::MallocSizeOf mallocSizeOf) const {
size_t result = objectFuses_.sizeOfExcludingThis(mallocSizeOf);
for (auto r = objectFuses_.all(); !r.empty(); r.popFront()) {
result += r.front().value()->sizeOfIncludingThis(mallocSizeOf);
}
return result;
}