Bug 1264948 part 1 - Register if the LifoAlloc is supposed to be infallible or not. r=jonco,h4writer
☠☠ backed out by 5b44d6438cf5 ☠ ☠
authorNicolas B. Pierron <nicolas.b.pierron@mozilla.com>
Fri, 10 Jun 2016 16:56:27 +0000
changeset 377675 a4e54f664175fc4e4b68a6c2d68438c0b86f00bb
parent 377674 358018e43d8da2517c3600c6172f129c1e65ada0
child 377676 cc9f6e6c7d420d367551966c78e7bd12209967a1
push id20857
push userbmo:james@hoppipolla.co.uk
push dateSun, 12 Jun 2016 16:59:39 +0000
reviewersjonco, h4writer
bugs1264948
milestone50.0a1
Bug 1264948 part 1 - Register if the LifoAlloc is supposed to be infallible or not. r=jonco,h4writer This patch adds a new flag to the LifoAlloc structure, which would be used to assert when we attempt to allocate a new chunk for the LifoAlloc. This ensure that we assert (in debug builds) if we attempt to allocate beyong the reserved space of the ballast.
js/src/ds/LifoAlloc.h
js/src/jit-test/tests/ion/bug1264948.js
js/src/jit-test/tests/ion/bug1269756.js
js/src/jit/JitAllocPolicy.h
--- a/js/src/ds/LifoAlloc.h
+++ b/js/src/ds/LifoAlloc.h
@@ -157,16 +157,19 @@ class LifoAlloc
 
     BumpChunk*  first;
     BumpChunk*  latest;
     BumpChunk*  last;
     size_t      markCount;
     size_t      defaultChunkSize_;
     size_t      curSize_;
     size_t      peakSize_;
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+    bool        fallibleScope_;
+#endif
 
     void operator=(const LifoAlloc&) = delete;
     LifoAlloc(const LifoAlloc&) = delete;
 
     // Return a BumpChunk that can perform an allocation of at least size |n|
     // and add it to the chain appropriately.
     //
     // Side effect: if retval is non-null, |first| and |latest| are initialized
@@ -226,16 +229,19 @@ class LifoAlloc
         result = latest->tryAlloc(n);
         MOZ_ASSERT(result);
         return result;
     }
 
   public:
     explicit LifoAlloc(size_t defaultChunkSize)
       : peakSize_(0)
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+      , fallibleScope_(true)
+#endif
     {
         reset(defaultChunkSize);
     }
 
     // Steal allocated chunks from |other|.
     void steal(LifoAlloc* other) {
         MOZ_ASSERT(!other->markCount);
         MOZ_ASSERT(!latest);
@@ -265,60 +271,84 @@ class LifoAlloc
     static const unsigned HUGE_ALLOCATION = 50 * 1024 * 1024;
     void freeAllIfHugeAndUnused() {
         if (markCount == 0 && curSize_ > HUGE_ALLOCATION)
             freeAll();
     }
 
     MOZ_ALWAYS_INLINE
     void* alloc(size_t n) {
-        JS_OOM_POSSIBLY_FAIL();
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+        // Only simulate OOMs when we are not using the LifoAlloc as an
+        // infallible allocator.
+        if (fallibleScope_)
+            JS_OOM_POSSIBLY_FAIL();
+#endif
         return allocImpl(n);
     }
 
     MOZ_ALWAYS_INLINE
-    void* allocInfallibleOrAssert(size_t n) {
-        void* result = allocImpl(n);
-        MOZ_RELEASE_ASSERT(result, "[OOM] Is it really infallible?");
-        return result;
-    }
-
-    MOZ_ALWAYS_INLINE
-    void* allocInfallibleOrCrash(size_t n) {
+    void* allocInfallible(size_t n) {
         AutoEnterOOMUnsafeRegion oomUnsafe;
         if (void* result = allocImpl(n))
             return result;
         oomUnsafe.crash("LifoAlloc::allocInfallible");
         return nullptr;
     }
 
-    MOZ_ALWAYS_INLINE
-    void* allocInfallible(size_t n) {
-        return allocInfallibleOrCrash(n);
-    }
-
     // Ensures that enough space exists to satisfy N bytes worth of
     // allocation requests, not necessarily contiguous. Note that this does
     // not guarantee a successful single allocation of N bytes.
     MOZ_ALWAYS_INLINE
     MOZ_MUST_USE bool ensureUnusedApproximate(size_t n) {
+        AutoFallibleScope fallibleAllocator(this);
         size_t total = 0;
         for (BumpChunk* chunk = latest; chunk; chunk = chunk->next()) {
             total += chunk->unused();
             if (total >= n)
                 return true;
         }
         BumpChunk* latestBefore = latest;
         if (!getOrCreateChunk(n))
             return false;
         if (latestBefore)
             latest = latestBefore;
         return true;
     }
 
+    MOZ_ALWAYS_INLINE
+    void setAsInfallibleByDefault() {
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+        fallibleScope_ = false;
+#endif
+    }
+
+    class MOZ_RAII AutoFallibleScope {
+#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
+        LifoAlloc* lifoAlloc_;
+        bool prevFallibleScope_;
+        MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+      public:
+        explicit AutoFallibleScope(LifoAlloc* lifoAlloc MOZ_GUARD_OBJECT_NOTIFIER_PARAM) {
+            MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+            lifoAlloc_ = lifoAlloc;
+            prevFallibleScope_ = lifoAlloc->fallibleScope_;
+            lifoAlloc->fallibleScope_ = true;
+        }
+
+        ~AutoFallibleScope() {
+            lifoAlloc_->fallibleScope_ = prevFallibleScope_;
+        }
+#else
+      public:
+        explicit AutoFallibleScope(LifoAlloc*) {}
+#endif
+    };
+
     template <typename T>
     T* newArray(size_t count) {
         static_assert(mozilla::IsPod<T>::value,
                       "T must be POD so that constructors (and destructors, "
                       "when the LifoAlloc is freed) need not be called");
         return newArrayUninitialized<T>(count);
     }
 
@@ -494,24 +524,26 @@ class LifoAlloc
         }
     };
 };
 
 class MOZ_NON_TEMPORARY_CLASS LifoAllocScope
 {
     LifoAlloc*      lifoAlloc;
     LifoAlloc::Mark mark;
+    LifoAlloc::AutoFallibleScope fallibleScope;
     bool            shouldRelease;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
     explicit LifoAllocScope(LifoAlloc* lifoAlloc
                             MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : lifoAlloc(lifoAlloc),
         mark(lifoAlloc->mark()),
+        fallibleScope(lifoAlloc),
         shouldRelease(true)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     ~LifoAllocScope() {
         if (shouldRelease)
             lifoAlloc->release(mark);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1264948.js
@@ -0,0 +1,21 @@
+if (!('oomTest' in this))
+    quit();
+
+loadFile(`
+  T = TypedObject
+  ObjectStruct = new T.StructType({f: T.Object})
+  var o = new ObjectStruct
+  function testGC(p) {
+    for (; i < 5; i++)
+        whatever.push;
+  }
+  testGC(o)
+  function writeObject()
+    o.f = v
+    writeObject({function() { } })
+  for (var i ; i < 5 ; ++i)
+    try {} catch (StringStruct) {}
+`);
+function loadFile(lfVarx) {
+  oomTest(Function(lfVarx));
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1269756.js
@@ -0,0 +1,5 @@
+oomTest(function() {
+    m = parseModule(`while (x && NaN) prototype; let x`);
+    m.declarationInstantiation();
+    m.evaluation();
+})
--- a/js/src/jit/JitAllocPolicy.h
+++ b/js/src/jit/JitAllocPolicy.h
@@ -29,53 +29,57 @@ class TempAllocator
     // KiB. And with a ballast of 16 KiB, a chunk size of 32 KiB works well,
     // because TempAllocators with a peak allocation size of less than 16 KiB
     // (which is most of them) only have to allocate a single chunk.
     static const size_t BallastSize;            // 16 KiB
     static const size_t PreferredLifoChunkSize; // 32 KiB
 
     explicit TempAllocator(LifoAlloc* lifoAlloc)
       : lifoScope_(lifoAlloc)
-    { }
+    {
+        lifoAlloc->setAsInfallibleByDefault();
+    }
 
     void* allocateInfallible(size_t bytes)
     {
-        return lifoScope_.alloc().allocInfallibleOrAssert(bytes);
+        return lifoScope_.alloc().allocInfallible(bytes);
     }
 
-    void* allocate(size_t bytes)
+    MOZ_MUST_USE void* allocate(size_t bytes)
     {
+        LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc());
         void* p = lifoScope_.alloc().alloc(bytes);
         if (!ensureBallast())
             return nullptr;
         return p;
     }
 
     template <typename T>
-    T* allocateArray(size_t n)
+    MOZ_MUST_USE T* allocateArray(size_t n)
     {
+        LifoAlloc::AutoFallibleScope fallibleAllocator(lifoAlloc());
         size_t bytes;
         if (MOZ_UNLIKELY(!CalculateAllocSize<T>(n, &bytes)))
             return nullptr;
         T* p = static_cast<T*>(lifoScope_.alloc().alloc(bytes));
         if (MOZ_UNLIKELY(!ensureBallast()))
             return nullptr;
         return p;
     }
 
     // View this allocator as a fallible allocator.
     struct Fallible { TempAllocator& alloc; };
     Fallible fallible() { return { *this }; }
 
-    LifoAlloc* lifoAlloc()
-    {
+    LifoAlloc* lifoAlloc() {
         return &lifoScope_.alloc();
     }
 
     MOZ_MUST_USE bool ensureBallast() {
+        JS_OOM_POSSIBLY_FAIL_BOOL();
         return lifoScope_.alloc().ensureUnusedApproximate(BallastSize);
     }
 };
 
 class JitAllocPolicy
 {
     TempAllocator& alloc_;