tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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