Bug 1426574 - Use fallible bitmap ops for AtomizeAndCopyChars, r=jonco
authorSteve Fink <sfink@mozilla.com>
Wed, 21 Nov 2018 12:07:12 -0800
changeset 504361 e8eb60450f8c25c2985316e2896aa0574ba658d5
parent 504360 7e7bf210330b1a491e30a75917d8b9aafa790458
child 504362 76c1898f6b021986beccc1461b1800ebe9f9e251
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco
bugs1426574
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1426574 - Use fallible bitmap ops for AtomizeAndCopyChars, r=jonco
js/src/ds/Bitmap.cpp
js/src/ds/Bitmap.h
js/src/gc/AtomMarking-inl.h
js/src/gc/AtomMarking.h
js/src/vm/JSAtom.cpp
--- a/js/src/ds/Bitmap.cpp
+++ b/js/src/ds/Bitmap.cpp
@@ -3,16 +3,18 @@
  * 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 "ds/Bitmap.h"
 
 #include <algorithm>
 
+#include "js/UniquePtr.h"
+
 using namespace js;
 
 SparseBitmap::~SparseBitmap()
 {
     for (Data::Range r(data.all()); !r.empty(); r.popFront()) {
         js_delete(r.front().value());
     }
 }
@@ -22,25 +24,35 @@ SparseBitmap::sizeOfExcludingThis(mozill
 {
     size_t size = data.shallowSizeOfExcludingThis(mallocSizeOf);
     for (Data::Range r(data.all()); !r.empty(); r.popFront()) {
         size += mallocSizeOf(r.front().value());
     }
     return size;
 }
 
+SparseBitmap::BitBlock*
+SparseBitmap::createBlock(Data::AddPtr p, size_t blockId)
+{
+    MOZ_ASSERT(!p);
+    auto block = js::MakeUnique<BitBlock>();
+    if (!block || !data.add(p, blockId, block.get())) {
+        return nullptr;
+    }
+    std::fill(block->begin(), block->end(), 0);
+    return block.release();
+}
+
 SparseBitmap::BitBlock&
 SparseBitmap::createBlock(Data::AddPtr p, size_t blockId, AutoEnterOOMUnsafeRegion& oomUnsafe)
 {
-    MOZ_ASSERT(!p);
-    BitBlock* block = js_new<BitBlock>();
-    if (!block || !data.add(p, blockId, block)) {
+    BitBlock* block = createBlock(p, blockId);
+    if (!block) {
         oomUnsafe.crash("Bitmap OOM");
     }
-    std::fill(block->begin(), block->end(), 0);
     return *block;
 }
 
 bool
 SparseBitmap::getBit(size_t bit) const
 {
     size_t word = bit / JS_BITS_PER_WORD;
     size_t blockWord = blockStartWord(word);
--- a/js/src/ds/Bitmap.h
+++ b/js/src/ds/Bitmap.h
@@ -86,44 +86,65 @@ class SparseBitmap
     // are in |other|.
     static size_t wordIntersectCount(size_t blockWord, const DenseBitmap& other) {
         long count = other.numWords() - blockWord;
         return std::min<size_t>((size_t)WordsInBlock, std::max<long>(count, 0));
     }
 
     BitBlock& createBlock(Data::AddPtr p, size_t blockId, AutoEnterOOMUnsafeRegion& oomUnsafe);
 
+    BitBlock* createBlock(Data::AddPtr p, size_t blockId);
+
     MOZ_ALWAYS_INLINE BitBlock* getBlock(size_t blockId) const {
         Data::Ptr p = data.lookup(blockId);
         return p ? p->value() : nullptr;
     }
 
     MOZ_ALWAYS_INLINE BitBlock& getOrCreateBlock(size_t blockId) {
         // The lookupForAdd() needs protection against injected OOMs, as does
         // the add() within createBlock().
         AutoEnterOOMUnsafeRegion oomUnsafe;
         Data::AddPtr p = data.lookupForAdd(blockId);
         if (p) {
             return *p->value();
         }
         return createBlock(p, blockId, oomUnsafe);
     }
 
+    MOZ_ALWAYS_INLINE BitBlock* getOrCreateBlockFallible(size_t blockId) {
+        Data::AddPtr p = data.lookupForAdd(blockId);
+        if (p) {
+            return p->value();
+        }
+        return createBlock(p, blockId);
+    }
+
   public:
     ~SparseBitmap();
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
 
     MOZ_ALWAYS_INLINE void setBit(size_t bit) {
         size_t word = bit / JS_BITS_PER_WORD;
         size_t blockWord = blockStartWord(word);
         BitBlock& block = getOrCreateBlock(blockWord / WordsInBlock);
         block[word - blockWord] |= uintptr_t(1) << (bit % JS_BITS_PER_WORD);
     }
 
+    MOZ_ALWAYS_INLINE bool setBitFallible(size_t bit) {
+        size_t word = bit / JS_BITS_PER_WORD;
+        size_t blockWord = blockStartWord(word);
+        BitBlock* block = getOrCreateBlockFallible(blockWord / WordsInBlock);
+        if (!block) {
+            return false;
+        }
+        (*block)[word - blockWord] |= uintptr_t(1) << (bit % JS_BITS_PER_WORD);
+        return true;
+    }
+
     bool getBit(size_t bit) const;
 
     void bitwiseAndWith(const DenseBitmap& other);
     void bitwiseOrWith(const SparseBitmap& other);
     void bitwiseOrInto(DenseBitmap& other) const;
 
     // Currently, this API only supports a range of words that is in a single bit block.
     void bitwiseOrRangeInto(size_t wordStart, size_t numWords, uintptr_t* target) const;
--- a/js/src/gc/AtomMarking-inl.h
+++ b/js/src/gc/AtomMarking-inl.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "gc/AtomMarking.h"
 
+#include "mozilla/Assertions.h"
 #include "vm/Realm.h"
 
 #include "gc/Heap-inl.h"
 
 namespace js {
 namespace gc {
 
 inline size_t
@@ -29,51 +30,73 @@ ThingIsPermanent(JSAtom* atom)
 }
 
 inline bool
 ThingIsPermanent(JS::Symbol* symbol)
 {
     return symbol->isWellKnownSymbol();
 }
 
-template <typename T>
-MOZ_ALWAYS_INLINE void
-AtomMarkingRuntime::inlinedMarkAtom(JSContext* cx, T* thing)
+template <typename T, bool Fallible>
+MOZ_ALWAYS_INLINE bool
+AtomMarkingRuntime::inlinedMarkAtomInternal(JSContext* cx, T* thing)
 {
     static_assert(mozilla::IsSame<T, JSAtom>::value ||
                   mozilla::IsSame<T, JS::Symbol>::value,
                   "Should only be called with JSAtom* or JS::Symbol* argument");
 
     MOZ_ASSERT(thing);
     js::gc::TenuredCell* cell = &thing->asTenured();
     MOZ_ASSERT(cell->zoneFromAnyThread()->isAtomsZone());
 
     // The context's zone will be null during initialization of the runtime.
     if (!cx->zone()) {
-        return;
+        return true;
     }
     MOZ_ASSERT(!cx->zone()->isAtomsZone());
 
     if (ThingIsPermanent(thing)) {
-        return;
+        return true;
     }
 
     size_t bit = GetAtomBit(cell);
     MOZ_ASSERT(bit / JS_BITS_PER_WORD < allocatedWords);
 
-    cx->zone()->markedAtoms().setBit(bit);
+    if (Fallible) {
+        if (!cx->zone()->markedAtoms().setBitFallible(bit)) {
+            return false;
+        }
+    } else {
+        cx->zone()->markedAtoms().setBit(bit);
+    }
 
     if (!cx->helperThread()) {
         // Trigger a read barrier on the atom, in case there is an incremental
         // GC in progress. This is necessary if the atom is being marked
         // because a reference to it was obtained from another zone which is
         // not being collected by the incremental GC.
         T::readBarrier(thing);
     }
 
     // Children of the thing also need to be marked in the context's zone.
     // We don't have a JSTracer for this so manually handle the cases in which
     // an atom can reference other atoms.
     markChildren(cx, thing);
+
+    return true;
+}
+
+template <typename T>
+MOZ_ALWAYS_INLINE void
+AtomMarkingRuntime::inlinedMarkAtom(JSContext* cx, T* thing)
+{
+    MOZ_ALWAYS_TRUE((inlinedMarkAtomInternal<T, false>(cx, thing)));
+}
+
+template <typename T>
+MOZ_ALWAYS_INLINE bool
+AtomMarkingRuntime::inlinedMarkAtomFallible(JSContext* cx, T* thing)
+{
+    return inlinedMarkAtomInternal<T, true>(cx, thing);
 }
 
 } // namespace gc
 } // namespace js
--- a/js/src/gc/AtomMarking.h
+++ b/js/src/gc/AtomMarking.h
@@ -61,17 +61,20 @@ class AtomMarkingRuntime
     // uncollected zone in the runtime.
     void markAtomsUsedByUncollectedZones(JSRuntime* runtime);
 
     // Mark an atom or id as being newly reachable by the context's zone.
     template <typename T> void markAtom(JSContext* cx, T* thing);
 
     // Version of markAtom that's always inlined, for performance-sensitive
     // callers.
+    template <typename T, bool Fallible>
+    MOZ_ALWAYS_INLINE bool inlinedMarkAtomInternal(JSContext* cx, T* thing);
     template <typename T> MOZ_ALWAYS_INLINE void inlinedMarkAtom(JSContext* cx, T* thing);
+    template <typename T> MOZ_ALWAYS_INLINE bool inlinedMarkAtomFallible(JSContext* cx, T* thing);
 
     void markId(JSContext* cx, jsid id);
     void markAtomValue(JSContext* cx, const Value& value);
 
     // Mark all atoms in |source| as being reachable within |target|.
     void adoptMarkedAtoms(Zone* target, Zone* source);
 
 #ifdef DEBUG
--- a/js/src/vm/JSAtom.cpp
+++ b/js/src/vm/JSAtom.cpp
@@ -733,17 +733,20 @@ AtomizeAndCopyCharsFromLookup(JSContext*
                                                    tbchars, length,
                                                    pin,
                                                    indexValue,
                                                    lookup);
     if (!atom) {
         return nullptr;
     }
 
-    cx->atomMarking().inlinedMarkAtom(cx, atom);
+    if (MOZ_UNLIKELY(!cx->atomMarking().inlinedMarkAtomFallible(cx, atom))) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
 
     if (zonePtr &&
         MOZ_UNLIKELY(!zone->atomCache().add(*zonePtr, AtomStateEntry(atom, false))))
     {
         ReportOutOfMemory(cx);
         return nullptr;
     }