Bug 1426574 - Use fallible bitmap ops for AtomizeAndCopyChars, r=jonco a=lizzard
authorSteve Fink <sfink@mozilla.com>
Wed, 21 Nov 2018 12:07:12 -0800
changeset 498695 6fcd1d505c808bd7e14aaefecb9759cd7194bb87
parent 498694 ec33377dad653e3d1b0e4c49a26cf37f2663c168
child 498696 460fc055455eee173a13d73f7f9cf44609f7576c
push id10277
push userarchaeopteryx@coole-files.de
push dateWed, 28 Nov 2018 17:55:51 +0000
treeherdermozilla-beta@3a87f2a3619a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjonco, lizzard
bugs1426574
milestone64.0
Bug 1426574 - Use fallible bitmap ops for AtomizeAndCopyChars, r=jonco a=lizzard
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
@@ -681,17 +681,20 @@ AtomizeAndCopyChars(JSContext* cx, const
                                                    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;
     }