ObjectFuse.h (10891B)
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 #ifndef vm_ObjectFuse_h 8 #define vm_ObjectFuse_h 9 10 #include "mozilla/MathAlgorithms.h" 11 #include "mozilla/MemoryReporting.h" 12 13 #include "gc/Barrier.h" 14 #include "jit/InvalidationScriptSet.h" 15 #include "jit/JitOptions.h" 16 #include "js/SweepingAPI.h" 17 #include "vm/PropertyInfo.h" 18 19 // [SMDOC] ObjectFuse 20 // 21 // ObjectFuse contains extra data associated with a single JSObject that the 22 // JITs can use to optimize operations on this object. 23 // 24 // An object's ObjectFuse is allocated lazily the first time it's needed by the 25 // JITs and freed when the object dies. 26 // 27 // ObjectFuse is currently used to track which properties are constant (unlikely 28 // to be mutated) so that IC stubs can guard on this and return the constant 29 // property value. In Warp, the guard becomes an invalidation dependency and the 30 // property value is a constant in the MIR graph, enabling additional compiler 31 // optimizations. ObjectFuse is currently used for the global object, the 32 // global lexical environment, and certain builtin constructors and prototypes. 33 // 34 // Each ObjectFuse has a generation counter. When the generation is bumped, IC 35 // guards will fail and dependent Ion scripts that are affected by the operation 36 // are invalidated. The generation changes when: 37 // 38 // * Removing a tracked property. 39 // * Shadowing a tracked global object property on the lexical environment. 40 // * Shadowing a tracked property on a different prototype object (related to 41 // shape teleporting). 42 // * Mutating the prototype of a prototype object (also related to shape 43 // teleporting). 44 // * Swapping the object with a different object. 45 // 46 // The property state information should only be accessed by the JITs after 47 // checking the generation still matches. 48 49 namespace js { 50 51 class NativeObject; 52 53 // A generation counter that becomes invalid (we no longer optimize based on it) 54 // when it reaches a maximum value (currently UINT32_MAX). 55 class SaturatedGenerationCounter { 56 uint32_t value_ = 0; 57 static constexpr uint32_t InvalidValue = UINT32_MAX; 58 59 public: 60 bool isValid() const { return value_ != InvalidValue; } 61 bool check(uint32_t v) const { 62 MOZ_RELEASE_ASSERT(v != InvalidValue); 63 return value_ == v; 64 } 65 void bump() { 66 if (isValid()) { 67 value_++; 68 } 69 } 70 uint32_t value() const { 71 MOZ_RELEASE_ASSERT(isValid()); 72 return value_; 73 } 74 uint32_t valueMaybeInvalid() const { return value_; } 75 }; 76 77 class ObjectFuse { 78 // State of a single property. This is encoded in two bits in 79 // |propertyStateBits_|. 80 // 81 // Note that Untracked and Constant are different states mainly to ensure 82 // global variables (property x for |var x = y;| in the global scope) can be 83 // marked Constant. In this case the property is initially defined with value 84 // |undefined| before bytecode assigns the actual value. 85 enum class PropertyState { 86 // Initial state. The JIT hasn't optimized this property as a constant. 87 Untracked = 0, 88 89 // This property is assumed to be constant. JIT code may depend on this 90 // assumption. 91 Constant = 1, 92 93 // This property is no longer tracked as a constant because it was mutated 94 // after being marked Constant. 95 // 96 // Note: IC guards rely on the fact that this value is the only enum value 97 // value that has the upper bit set. See getConstantPropertyGuardData. 98 NotConstant = 2, 99 }; 100 static constexpr size_t NumPropsPerWord = 16; 101 static constexpr size_t NumBitsPerProp = 2; 102 static constexpr size_t PropBitsMask = BitMask(NumBitsPerProp); 103 static_assert(NumPropsPerWord * NumBitsPerProp == 104 CHAR_BIT * sizeof(uint32_t)); 105 106 // Bit vector with two bits per property. Words are allocated lazily when a 107 // property is marked Constant/NotConstant. 108 UniquePtr<uint32_t[], JS::FreePolicy> propertyStateBits_; 109 110 // Length of the propertiesBits_ array in words. 111 uint32_t propertyStateLength_ = 0; 112 113 // This field is set to 1 when a property is marked NotConstant and when the 114 // generation counter is bumped. IC code can use a fast path based on this 115 // field. 116 uint32_t invalidatedConstantProperty_ = 0; 117 118 // Generation counter of this ObjectFuse. JIT guards should only access the 119 // property state bits when the generation still matches. 120 SaturatedGenerationCounter generation_{}; 121 122 // This maps a uint32_t propertySlot to the Ion compilations that depend on 123 // this property being a constant. 124 using DepMap = GCHashMap<uint32_t, js::jit::DependentIonScriptSet, 125 DefaultHasher<uint32_t>, SystemAllocPolicy>; 126 DepMap dependencies_; 127 128 [[nodiscard]] bool ensurePropertyStateLength(uint32_t length); 129 130 void invalidateDependentIonScriptsForProperty(JSContext* cx, 131 PropertyInfo prop, 132 const char* reason); 133 void invalidateAllDependentIonScripts(JSContext* cx, const char* reason); 134 135 static constexpr uint32_t propertyStateShift(uint32_t propSlot) { 136 return (propSlot % NumPropsPerWord) * NumBitsPerProp; 137 } 138 PropertyState getPropertyState(uint32_t propSlot) const { 139 uint32_t index = propSlot / NumPropsPerWord; 140 if (index >= propertyStateLength_) { 141 return PropertyState::Untracked; 142 } 143 uint32_t shift = propertyStateShift(propSlot); 144 uint32_t bits = (propertyStateBits_[index] >> shift) & PropBitsMask; 145 MOZ_ASSERT(bits <= uint32_t(PropertyState::NotConstant)); 146 return PropertyState(bits); 147 } 148 PropertyState getPropertyState(PropertyInfo prop) const { 149 return getPropertyState(prop.slot()); 150 } 151 void setPropertyState(PropertyInfo prop, PropertyState state) { 152 uint32_t slot = prop.slot(); 153 uint32_t index = slot / NumPropsPerWord; 154 MOZ_ASSERT(index < propertyStateLength_); 155 uint32_t shift = propertyStateShift(slot); 156 propertyStateBits_[index] &= ~(PropBitsMask << shift); 157 propertyStateBits_[index] |= uint32_t(state) << shift; 158 } 159 160 bool isUntrackedProperty(PropertyInfo prop) const { 161 return getPropertyState(prop) == PropertyState::Untracked; 162 } 163 bool isConstantProperty(PropertyInfo prop) const { 164 return getPropertyState(prop) == PropertyState::Constant; 165 } 166 167 [[nodiscard]] bool markPropertyConstant(PropertyInfo prop); 168 169 void bumpGeneration() { 170 invalidatedConstantProperty_ = 1; 171 generation_.bump(); 172 } 173 174 public: 175 uint32_t generationMaybeInvalid() const { 176 return generation_.valueMaybeInvalid(); 177 } 178 bool hasInvalidatedConstantProperty() const { 179 return invalidatedConstantProperty_; 180 } 181 182 bool tryOptimizeConstantProperty(PropertyInfo prop); 183 184 // Data needed for guards in IC code. We use a bitmask to check the 185 // PropertyState's upper bit isn't set. 186 struct GuardData { 187 uint32_t generation; 188 uint32_t propIndex; 189 uint32_t propMask; 190 }; 191 GuardData getConstantPropertyGuardData(PropertyInfo prop) const { 192 MOZ_ASSERT(isConstantProperty(prop)); 193 194 GuardData data; 195 data.generation = generation_.value(); 196 data.propIndex = prop.slot() / NumPropsPerWord; 197 static_assert(size_t(PropertyState::NotConstant) == 2); 198 data.propMask = uint32_t(0b10) << propertyStateShift(prop.slot()); 199 200 // Make sure propertySlotFromIndexAndMask will return the original slot 201 // number. 202 MOZ_ASSERT(propertySlotFromIndexAndMask(data.propIndex, data.propMask) == 203 prop.slot()); 204 205 return data; 206 } 207 208 // The inverse of getConstantPropertyGuardData: it computes the property slot 209 // from the index and mask pair stored in an IC stub. 210 static uint32_t propertySlotFromIndexAndMask(uint32_t propIndex, 211 uint32_t propMask) { 212 MOZ_ASSERT(mozilla::CountPopulation32(propMask) == 1); 213 uint32_t slot = propIndex * NumPropsPerWord; 214 slot += mozilla::CountTrailingZeroes(propMask) / NumBitsPerProp; 215 return slot; 216 } 217 218 // We can only optimize SetProp operations for non-constant properties. 219 bool canOptimizeSetSlot(PropertyInfo prop) const { 220 return getPropertyState(prop) == PropertyState::NotConstant; 221 } 222 223 void handlePropertyValueChange(JSContext* cx, PropertyInfo prop); 224 void handlePropertyRemove(JSContext* cx, PropertyInfo prop, 225 bool* wasTrackedProp); 226 void handleTeleportingShadowedProperty(JSContext* cx, PropertyInfo prop); 227 void handleTeleportingProtoMutation(JSContext* cx); 228 void handleShadowedGlobalProperty(JSContext* cx, PropertyInfo prop); 229 void handleObjectSwap(JSContext* cx); 230 231 bool addDependency(uint32_t propSlot, const jit::IonScriptKey& ionScript); 232 233 bool checkPropertyIsConstant(uint32_t generation, uint32_t propSlot) const { 234 if (!generation_.check(generation)) { 235 return false; 236 } 237 PropertyState state = getPropertyState(propSlot); 238 if (state == PropertyState::NotConstant) { 239 MOZ_ASSERT(invalidatedConstantProperty_); 240 return false; 241 } 242 MOZ_ASSERT(state == PropertyState::Constant); 243 return true; 244 } 245 246 const char* getPropertyStateString(PropertyInfo prop) const; 247 248 size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; 249 250 // We should sweep ObjectFuseMap entries based on the key (the object) but 251 // never based on the ObjectFuse. We do need to trace weak pointers in the 252 // DependentIonScriptSets. 253 bool needsSweep(JSTracer* trc) const { return false; } 254 bool traceWeak(JSTracer* trc) { 255 dependencies_.traceWeak(trc); 256 return true; 257 } 258 259 static constexpr size_t offsetOfInvalidatedConstantProperty() { 260 return offsetof(ObjectFuse, invalidatedConstantProperty_); 261 } 262 static constexpr size_t offsetOfGeneration() { 263 return offsetof(ObjectFuse, generation_); 264 } 265 static constexpr size_t offsetOfPropertyStateBits() { 266 return offsetof(ObjectFuse, propertyStateBits_); 267 } 268 }; 269 270 class ObjectFuseMap { 271 using Map = 272 GCHashMap<WeakHeapPtr<JSObject*>, UniquePtr<ObjectFuse>, 273 StableCellHasher<WeakHeapPtr<JSObject*>>, SystemAllocPolicy>; 274 JS::WeakCache<Map> objectFuses_; 275 276 public: 277 explicit ObjectFuseMap(JSRuntime* rt) : objectFuses_(rt) {} 278 279 ObjectFuse* getOrCreate(JSContext* cx, NativeObject* obj); 280 ObjectFuse* get(NativeObject* obj); 281 282 size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; 283 }; 284 285 // ObjectFuses can have some performance overhead due to the Watchtower code for 286 // property changes, so we only use them if we can take advantage of object 287 // fuses with IC stubs. Note that we don't check this for the |addObjectFuse| 288 // testing function. 289 inline bool ShouldUseObjectFuses() { 290 return jit::IsBaselineInterpreterEnabled(); 291 } 292 293 } // namespace js 294 295 #endif // vm_ObjectFuse_h