ObjectFuse.cpp (7564B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "vm/ObjectFuse.h" 8 9 #include "gc/Barrier.h" 10 #include "gc/StableCellHasher.h" 11 #include "jit/InvalidationScriptSet.h" 12 #include "js/SweepingAPI.h" 13 #include "vm/JSScript.h" 14 #include "vm/NativeObject.h" 15 #include "vm/PropertyInfo.h" 16 17 #include "gc/StableCellHasher-inl.h" 18 #include "vm/JSScript-inl.h" 19 20 using namespace js; 21 22 bool ObjectFuse::ensurePropertyStateLength(uint32_t length) { 23 MOZ_ASSERT(length > 0); 24 25 if (length <= propertyStateLength_) { 26 MOZ_ASSERT(propertyStateBits_); 27 return true; 28 } 29 30 if (!propertyStateBits_) { 31 propertyStateBits_.reset(js_pod_malloc<uint32_t>(length)); 32 if (!propertyStateBits_) { 33 return false; 34 } 35 } else { 36 uint32_t* bitsNew = js_pod_realloc<uint32_t>(propertyStateBits_.get(), 37 propertyStateLength_, length); 38 if (!bitsNew) { 39 return false; 40 } 41 (void)propertyStateBits_.release(); 42 propertyStateBits_.reset(bitsNew); 43 } 44 45 // Initialize the new words to 0. 46 static_assert(uint32_t(PropertyState::Untracked) == 0); 47 std::uninitialized_fill_n(propertyStateBits_.get() + propertyStateLength_, 48 length - propertyStateLength_, 0); 49 propertyStateLength_ = length; 50 return true; 51 } 52 53 bool ObjectFuse::addDependency(uint32_t propSlot, 54 const jit::IonScriptKey& ionScript) { 55 MOZ_ASSERT(getPropertyState(propSlot) == PropertyState::Constant); 56 57 auto p = dependencies_.lookupForAdd(propSlot); 58 if (!p) { 59 if (!dependencies_.add(p, propSlot, jit::DependentIonScriptSet())) { 60 return false; 61 } 62 } 63 return p->value().addToSet(ionScript); 64 } 65 66 void ObjectFuse::invalidateDependentIonScriptsForProperty(JSContext* cx, 67 PropertyInfo prop, 68 const char* reason) { 69 if (auto p = dependencies_.lookup(prop.slot())) { 70 p->value().invalidateAndClear(cx, reason); 71 dependencies_.remove(p); 72 } 73 } 74 75 void ObjectFuse::invalidateAllDependentIonScripts(JSContext* cx, 76 const char* reason) { 77 for (auto r = dependencies_.all(); !r.empty(); r.popFront()) { 78 r.front().value().invalidateAndClear(cx, reason); 79 } 80 dependencies_.clear(); 81 } 82 83 bool ObjectFuse::markPropertyConstant(PropertyInfo prop) { 84 MOZ_ASSERT(getPropertyState(prop) == PropertyState::Untracked); 85 uint32_t index = prop.slot() / NumPropsPerWord; 86 if (!ensurePropertyStateLength(index + 1)) { 87 return false; 88 } 89 setPropertyState(prop, PropertyState::Constant); 90 return true; 91 } 92 93 bool ObjectFuse::tryOptimizeConstantProperty(PropertyInfo prop) { 94 if (MOZ_UNLIKELY(!generation_.isValid())) { 95 return false; 96 } 97 PropertyState state = getPropertyState(prop); 98 switch (state) { 99 case PropertyState::Untracked: 100 if (!markPropertyConstant(prop)) { 101 return false; 102 } 103 MOZ_ASSERT(isConstantProperty(prop)); 104 return true; 105 case PropertyState::Constant: 106 return true; 107 case PropertyState::NotConstant: 108 return false; 109 } 110 MOZ_CRASH("Unreachable"); 111 } 112 113 void ObjectFuse::handlePropertyValueChange(JSContext* cx, PropertyInfo prop) { 114 // Custom data properties aren't optimized with object fuses. 115 if (!prop.hasSlot()) { 116 return; 117 } 118 119 PropertyState state = getPropertyState(prop); 120 switch (state) { 121 case PropertyState::Untracked: 122 // Mark the property as Constant. IC code for SetProp operations relies on 123 // properties getting marked NotConstant after a few sets, because we can 124 // only optimize stores to NotConstant properties. We can ignore OOM here. 125 (void)markPropertyConstant(prop); 126 break; 127 case PropertyState::Constant: 128 invalidatedConstantProperty_ = 1; 129 setPropertyState(prop, PropertyState::NotConstant); 130 invalidateDependentIonScriptsForProperty(cx, prop, 131 "changed constant property"); 132 break; 133 case PropertyState::NotConstant: 134 break; 135 } 136 } 137 138 void ObjectFuse::handlePropertyRemove(JSContext* cx, PropertyInfo prop, 139 bool* wasTrackedProp) { 140 if (!prop.hasSlot() || isUntrackedProperty(prop)) { 141 MOZ_ASSERT(!*wasTrackedProp); 142 return; 143 } 144 145 *wasTrackedProp = true; 146 bumpGeneration(); 147 invalidateDependentIonScriptsForProperty(cx, prop, "removed property"); 148 149 // Ensure a new property with this slot number will have the correct initial 150 // state. 151 setPropertyState(prop, PropertyState::Untracked); 152 } 153 154 void ObjectFuse::handleTeleportingShadowedProperty(JSContext* cx, 155 PropertyInfo prop) { 156 if (!prop.hasSlot() || isUntrackedProperty(prop)) { 157 return; 158 } 159 bumpGeneration(); 160 invalidateDependentIonScriptsForProperty(cx, prop, 161 "teleporting shadowed property"); 162 } 163 164 void ObjectFuse::handleTeleportingProtoMutation(JSContext* cx) { 165 bumpGeneration(); 166 invalidateAllDependentIonScripts(cx, "proto mutation"); 167 } 168 169 void ObjectFuse::handleObjectSwap(JSContext* cx) { 170 bumpGeneration(); 171 172 // Reset state for all properties. 173 propertyStateLength_ = 0; 174 propertyStateBits_.reset(); 175 176 invalidateAllDependentIonScripts(cx, "object swap"); 177 } 178 179 void ObjectFuse::handleShadowedGlobalProperty(JSContext* cx, 180 PropertyInfo prop) { 181 if (isUntrackedProperty(prop)) { 182 return; 183 } 184 bumpGeneration(); 185 invalidateDependentIonScriptsForProperty(cx, prop, 186 "shadowed global property"); 187 } 188 189 const char* ObjectFuse::getPropertyStateString(PropertyInfo prop) const { 190 PropertyState state = getPropertyState(prop); 191 switch (state) { 192 case PropertyState::Untracked: 193 return "Untracked"; 194 case PropertyState::Constant: 195 return "Constant"; 196 case PropertyState::NotConstant: 197 return "NotConstant"; 198 } 199 MOZ_CRASH("Unreachable"); 200 } 201 202 size_t ObjectFuse::sizeOfIncludingThis( 203 mozilla::MallocSizeOf mallocSizeOf) const { 204 size_t result = mallocSizeOf(this); 205 if (propertyStateBits_) { 206 result += mallocSizeOf(propertyStateBits_.get()); 207 } 208 result += dependencies_.shallowSizeOfExcludingThis(mallocSizeOf); 209 for (auto r = dependencies_.all(); !r.empty(); r.popFront()) { 210 result += r.front().value().sizeOfExcludingThis(mallocSizeOf); 211 } 212 return result; 213 } 214 215 ObjectFuse* ObjectFuseMap::getOrCreate(JSContext* cx, NativeObject* obj) { 216 MOZ_ASSERT(obj->hasObjectFuse()); 217 auto p = objectFuses_.lookupForAdd(obj); 218 if (!p) { 219 auto fuse = MakeUnique<ObjectFuse>(); 220 if (!fuse || !objectFuses_.add(p, obj, std::move(fuse))) { 221 ReportOutOfMemory(cx); 222 return nullptr; 223 } 224 } 225 return p->value().get(); 226 } 227 228 ObjectFuse* ObjectFuseMap::get(NativeObject* obj) { 229 MOZ_ASSERT(obj->hasObjectFuse()); 230 auto p = objectFuses_.lookup(obj); 231 return p ? p->value().get() : nullptr; 232 } 233 234 size_t ObjectFuseMap::sizeOfExcludingThis( 235 mozilla::MallocSizeOf mallocSizeOf) const { 236 size_t result = objectFuses_.sizeOfExcludingThis(mallocSizeOf); 237 for (auto r = objectFuses_.all(); !r.empty(); r.popFront()) { 238 result += r.front().value()->sizeOfIncludingThis(mallocSizeOf); 239 } 240 return result; 241 }