Make sure that allocations through CALL_HEAP_FUNCTION
authorkasperl@chromium.org <kasperl@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 30 Oct 2008 09:15:58 +0000 (09:15 +0000)
committerkasperl@chromium.org <kasperl@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 30 Oct 2008 09:15:58 +0000 (09:15 +0000)
and runtime calls from JavaScript will always succeed
eventually if we have enough memory.
Review URL: http://codereview.chromium.org/8700

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@646 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

15 files changed:
src/assembler.cc
src/assembler.h
src/codegen-arm.cc
src/codegen-ia32.cc
src/codegen.h
src/heap-inl.h
src/heap.cc
src/heap.h
src/runtime.cc
src/serialize.cc
src/spaces.cc
src/v8-counters.h
test/cctest/SConscript
test/cctest/test-alloc.cc [new file with mode: 0644]
tools/visual_studio/v8_cctest.vcproj

index a2930f1..1fd0dc3 100644 (file)
@@ -564,6 +564,10 @@ ExternalReference ExternalReference::new_space_allocation_top_address() {
   return ExternalReference(Heap::NewSpaceAllocationTopAddress());
 }
 
+ExternalReference ExternalReference::heap_always_allocate_scope_depth() {
+  return ExternalReference(Heap::always_allocate_scope_depth_address());
+}
+
 ExternalReference ExternalReference::new_space_allocation_limit_address() {
   return ExternalReference(Heap::NewSpaceAllocationLimitAddress());
 }
index 137c37c..81c8056 100644 (file)
@@ -429,6 +429,7 @@ class ExternalReference BASE_EMBEDDED {
 
   // Static variable Heap::NewSpaceStart()
   static ExternalReference new_space_start();
+  static ExternalReference heap_always_allocate_scope_depth();
 
   // Used for fast allocation in generated code.
   static ExternalReference new_space_allocation_top_address();
index 256dec5..35522b1 100644 (file)
@@ -3812,7 +3812,8 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
                               Label* throw_normal_exception,
                               Label* throw_out_of_memory_exception,
                               StackFrame::Type frame_type,
-                              bool do_gc) {
+                              bool do_gc,
+                              bool always_allocate) {
   // r0: result parameter for PerformGC, if any
   // r4: number of arguments including receiver  (C callee-saved)
   // r5: pointer to builtin function  (C callee-saved)
@@ -3823,6 +3824,15 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
     __ Call(FUNCTION_ADDR(Runtime::PerformGC), RelocInfo::RUNTIME_ENTRY);
   }
 
+  ExternalReference scope_depth =
+      ExternalReference::heap_always_allocate_scope_depth();
+  if (always_allocate) {
+    __ mov(r0, Operand(scope_depth));
+    __ ldr(r1, MemOperand(r0));
+    __ add(r1, r1, Operand(1));
+    __ str(r1, MemOperand(r0));
+  }
+
   // Call C built-in.
   // r0 = argc, r1 = argv
   __ mov(r0, Operand(r4));
@@ -3843,7 +3853,15 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
 #else /* !defined(__arm__) */
   __ mov(pc, Operand(r5));
 #endif /* !defined(__arm__) */
-  // result is in r0 or r0:r1 - do not destroy these registers!
+
+  if (always_allocate) {
+    // It's okay to clobber r2 and r3 here. Don't mess with r0 and r1
+    // though (contain the result).
+    __ mov(r2, Operand(scope_depth));
+    __ ldr(r3, MemOperand(r2));
+    __ sub(r3, r3, Operand(1));
+    __ str(r3, MemOperand(r2));
+  }
 
   // check for failure result
   Label failure_returned;
@@ -3929,14 +3947,16 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
   GenerateCore(masm, &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
-               FLAG_gc_greedy);
+               FLAG_gc_greedy,
+               false);
 
   // Do space-specific GC and retry runtime call.
   GenerateCore(masm,
                &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
-               true);
+               true,
+               false);
 
   // Do full GC and retry runtime call one final time.
   Failure* failure = Failure::InternalError();
@@ -3945,6 +3965,7 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
                &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
+               true,
                true);
 
   __ bind(&throw_out_of_memory_exception);
index 79dbb0b..eaac989 100644 (file)
@@ -4809,7 +4809,8 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
                               Label* throw_normal_exception,
                               Label* throw_out_of_memory_exception,
                               StackFrame::Type frame_type,
-                              bool do_gc) {
+                              bool do_gc,
+                              bool always_allocate_scope) {
   // eax: result parameter for PerformGC, if any
   // ebx: pointer to C function  (C callee-saved)
   // ebp: frame pointer  (restored after C call)
@@ -4822,12 +4823,22 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
     __ call(FUNCTION_ADDR(Runtime::PerformGC), RelocInfo::RUNTIME_ENTRY);
   }
 
+  ExternalReference scope_depth =
+      ExternalReference::heap_always_allocate_scope_depth();
+  if (always_allocate_scope) {
+    __ inc(Operand::StaticVariable(scope_depth));
+  }
+
   // Call C function.
   __ mov(Operand(esp, 0 * kPointerSize), edi);  // argc.
   __ mov(Operand(esp, 1 * kPointerSize), esi);  // argv.
   __ call(Operand(ebx));
   // Result is in eax or edx:eax - do not destroy these registers!
 
+  if (always_allocate_scope) {
+    __ dec(Operand::StaticVariable(scope_depth));
+  }
+
   // Check for failure result.
   Label failure_returned;
   ASSERT(((kFailureTag + 1) & kFailureTagMask) == 0);
@@ -4963,14 +4974,16 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
   GenerateCore(masm, &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
-               FLAG_gc_greedy);
+               FLAG_gc_greedy,
+               false);
 
   // Do space-specific GC and retry runtime call.
   GenerateCore(masm,
                &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
-               true);
+               true,
+               false);
 
   // Do full GC and retry runtime call one final time.
   Failure* failure = Failure::InternalError();
@@ -4979,6 +4992,7 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
                &throw_normal_exception,
                &throw_out_of_memory_exception,
                frame_type,
+               true,
                true);
 
   __ bind(&throw_out_of_memory_exception);
index f2ac05c..ca11c34 100644 (file)
@@ -196,7 +196,8 @@ class CEntryStub : public CodeStub {
                     Label* throw_normal_exception,
                     Label* throw_out_of_memory_exception,
                     StackFrame::Type frame_type,
-                    bool do_gc);
+                    bool do_gc,
+                    bool always_allocate_scope);
   void GenerateThrowTOS(MacroAssembler* masm);
   void GenerateThrowOutOfMemory(MacroAssembler* masm);
 
index bfa2b65..b54ef3a 100644 (file)
@@ -39,8 +39,12 @@ int Heap::MaxHeapObjectSize() {
 
 
 Object* Heap::AllocateRaw(int size_in_bytes,
-                          AllocationSpace space) {
+                          AllocationSpace space,
+                          AllocationSpace retry_space) {
   ASSERT(allocation_allowed_ && gc_state_ == NOT_IN_GC);
+  ASSERT(space != NEW_SPACE ||
+         retry_space == OLD_POINTER_SPACE ||
+         retry_space == OLD_DATA_SPACE);
 #ifdef DEBUG
   if (FLAG_gc_interval >= 0 &&
       !disallow_allocation_failure_ &&
@@ -50,11 +54,16 @@ Object* Heap::AllocateRaw(int size_in_bytes,
   Counters::objs_since_last_full.Increment();
   Counters::objs_since_last_young.Increment();
 #endif
+  Object* result;
   if (NEW_SPACE == space) {
-    return new_space_.AllocateRaw(size_in_bytes);
+    result = new_space_.AllocateRaw(size_in_bytes);
+    if (always_allocate() && result->IsFailure()) {
+      space = retry_space;
+    } else {
+      return result;
+    }
   }
 
-  Object* result;
   if (OLD_POINTER_SPACE == space) {
     result = old_pointer_space_->AllocateRaw(size_in_bytes);
   } else if (OLD_DATA_SPACE == space) {
@@ -132,17 +141,25 @@ void Heap::RecordWrite(Address address, int offset) {
 
 
 OldSpace* Heap::TargetSpace(HeapObject* object) {
+  InstanceType type = object->map()->instance_type();
+  AllocationSpace space = TargetSpaceId(type);
+  return (space == OLD_POINTER_SPACE)
+      ? old_pointer_space_
+      : old_data_space_;
+}
+
+
+AllocationSpace Heap::TargetSpaceId(InstanceType type) {
   // Heap numbers and sequential strings are promoted to old data space, all
   // other object types are promoted to old pointer space.  We do not use
   // object->IsHeapNumber() and object->IsSeqString() because we already
   // know that object has the heap object tag.
-  InstanceType type = object->map()->instance_type();
   ASSERT((type != CODE_TYPE) && (type != MAP_TYPE));
   bool has_pointers =
       type != HEAP_NUMBER_TYPE &&
       (type >= FIRST_NONSTRING_TYPE ||
-       String::cast(object)->representation_tag() != kSeqStringTag);
-  return has_pointers ? old_pointer_space_ : old_data_space_;
+       (type & kStringRepresentationMask) != kSeqStringTag);
+  return has_pointers ? OLD_POINTER_SPACE : OLD_DATA_SPACE;
 }
 
 
@@ -188,74 +205,59 @@ void Heap::ClearKeyedLookupCache() {
 #define GC_GREEDY_CHECK() \
   ASSERT(!FLAG_gc_greedy || v8::internal::Heap::GarbageCollectionGreedyCheck())
 
-// Do not use the identifier __object__ in a call to this macro.
-//
-// Call the function FUNCTION_CALL.  If it fails with a RetryAfterGC
-// failure, call the garbage collector and retry the function.  If the
-// garbage collector cannot reclaim the required space or the second
-// call fails with a RetryAfterGC failure, fail with out of memory.
-// If there is any other failure, return a null handle.  If either
-// call succeeds, return a handle to the functions return value.
-//
-// Note that this macro always returns or raises a fatal error.
-#define CALL_HEAP_FUNCTION(FUNCTION_CALL, TYPE)                              \
-  do {                                                                       \
-    GC_GREEDY_CHECK();                                                       \
-    Object* __object__ = FUNCTION_CALL;                                      \
-    if (__object__->IsFailure()) {                                           \
-      if (__object__->IsRetryAfterGC()) {                                    \
-        if (!Heap::CollectGarbage(                                           \
-                Failure::cast(__object__)->requested(),                      \
-                Failure::cast(__object__)->allocation_space())) {            \
-          /* TODO(1181417): Fix this. */                                     \
-          v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION");   \
-        }                                                                    \
-        __object__ = FUNCTION_CALL;                                          \
-        if (__object__->IsFailure()) {                                       \
-          if (__object__->IsRetryAfterGC()) {                                \
-            /* TODO(1181417): Fix this. */                                   \
-            v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION"); \
-          }                                                                  \
-          return Handle<TYPE>();                                             \
-        }                                                                    \
-      } else {                                                               \
-        if (__object__->IsOutOfMemoryFailure()) {                            \
-          v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION");   \
-        }                                                                    \
-        return Handle<TYPE>();                                               \
-      }                                                                      \
-    }                                                                        \
-    return Handle<TYPE>(TYPE::cast(__object__));                             \
+
+// Calls the FUNCTION_CALL function and retries it up to three times
+// to guarantee that any allocations performed during the call will
+// succeed if there's enough memory.
+
+// Warning: Do not use the identifiers __object__ or __scope__ in a
+// call to this macro.
+
+#define CALL_AND_RETRY(FUNCTION_CALL, RETURN_VALUE, RETURN_EMPTY)         \
+  do {                                                                    \
+    GC_GREEDY_CHECK();                                                    \
+    Object* __object__ = FUNCTION_CALL;                                   \
+    if (!__object__->IsFailure()) return RETURN_VALUE;                    \
+    if (__object__->IsOutOfMemoryFailure()) {                             \
+      v8::internal::V8::FatalProcessOutOfMemory("CALL_AND_RETRY_0");      \
+    }                                                                     \
+    if (!__object__->IsRetryAfterGC()) return RETURN_EMPTY;               \
+    if (!Heap::CollectGarbage(                                            \
+            Failure::cast(__object__)->requested(),                       \
+            Failure::cast(__object__)->allocation_space())) {             \
+      v8::internal::V8::FatalProcessOutOfMemory("CALL_AND_RETRY_1");      \
+      return RETURN_EMPTY;                                                \
+    }                                                                     \
+    __object__ = FUNCTION_CALL;                                           \
+    if (!__object__->IsFailure()) return RETURN_VALUE;                    \
+    if (__object__->IsOutOfMemoryFailure()) {                             \
+      v8::internal::V8::FatalProcessOutOfMemory("CALL_AND_RETRY_2");      \
+    }                                                                     \
+    if (!__object__->IsRetryAfterGC()) return RETURN_EMPTY;               \
+    Counters::gc_last_resort_from_handles.Increment();                    \
+    Heap::CollectAllGarbage();                                            \
+    {                                                                     \
+      AlwaysAllocateScope __scope__;                                      \
+      __object__ = FUNCTION_CALL;                                         \
+    }                                                                     \
+    if (!__object__->IsFailure()) return RETURN_VALUE;                    \
+    if (__object__->IsOutOfMemoryFailure()) {                             \
+      /* TODO(1181417): Fix this. */                                      \
+      v8::internal::V8::FatalProcessOutOfMemory("CALL_AND_RETRY_3");      \
+    }                                                                     \
+    ASSERT(!__object__->IsRetryAfterGC());                                \
+    return RETURN_EMPTY;                                                  \
   } while (false)
 
 
-// Don't use the following names: __object__, __failure__.
-#define CALL_HEAP_FUNCTION_VOID(FUNCTION_CALL)                      \
-  GC_GREEDY_CHECK();                                                \
-  Object* __object__ = FUNCTION_CALL;                               \
-  if (__object__->IsFailure()) {                                    \
-    if (__object__->IsRetryAfterGC()) {                             \
-      Failure* __failure__ = Failure::cast(__object__);             \
-      if (!Heap::CollectGarbage(__failure__->requested(),           \
-                                __failure__->allocation_space())) { \
-         /* TODO(1181417): Fix this. */                             \
-         V8::FatalProcessOutOfMemory("Handles");                    \
-      }                                                             \
-      __object__ = FUNCTION_CALL;                                   \
-      if (__object__->IsFailure()) {                                \
-        if (__object__->IsRetryAfterGC()) {                         \
-           /* TODO(1181417): Fix this. */                           \
-           V8::FatalProcessOutOfMemory("Handles");                  \
-        }                                                           \
-        return;                                                     \
-      }                                                             \
-    } else {                                                        \
-      if (__object__->IsOutOfMemoryFailure()) {                     \
-         V8::FatalProcessOutOfMemory("Handles");                    \
-      }                                                             \
-      UNREACHABLE();                                                \
-    }                                                               \
-  }
+#define CALL_HEAP_FUNCTION(FUNCTION_CALL, TYPE)         \
+  CALL_AND_RETRY(FUNCTION_CALL,                         \
+                 Handle<TYPE>(TYPE::cast(__object__)),  \
+                 Handle<TYPE>())
+
+
+#define CALL_HEAP_FUNCTION_VOID(FUNCTION_CALL) \
+  CALL_AND_RETRY(FUNCTION_CALL, , )
 
 
 #ifdef DEBUG
index dc0e100..bb30deb 100644 (file)
@@ -96,6 +96,8 @@ Heap::HeapState Heap::gc_state_ = NOT_IN_GC;
 int Heap::mc_count_ = 0;
 int Heap::gc_count_ = 0;
 
+int Heap::always_allocate_scope_depth_ = 0;
+
 #ifdef DEBUG
 bool Heap::allocation_allowed_ = true;
 
@@ -1025,7 +1027,7 @@ Object* Heap::AllocateHeapNumber(double value, PretenureFlag pretenure) {
   // spaces.
   STATIC_ASSERT(HeapNumber::kSize <= Page::kMaxHeapObjectSize);
   AllocationSpace space = (pretenure == TENURED) ? OLD_DATA_SPACE : NEW_SPACE;
-  Object* result = AllocateRaw(HeapNumber::kSize, space);
+  Object* result = AllocateRaw(HeapNumber::kSize, space, OLD_DATA_SPACE);
   if (result->IsFailure()) return result;
 
   HeapObject::cast(result)->set_map(heap_number_map());
@@ -1035,6 +1037,8 @@ Object* Heap::AllocateHeapNumber(double value, PretenureFlag pretenure) {
 
 
 Object* Heap::AllocateHeapNumber(double value) {
+  // Use general version, if we're forced to always allocate.
+  if (always_allocate()) return AllocateHeapNumber(value, NOT_TENURED);
   // This version of AllocateHeapNumber is optimized for
   // allocation in new space.
   STATIC_ASSERT(HeapNumber::kSize <= Page::kMaxHeapObjectSize);
@@ -1531,7 +1535,7 @@ Object* Heap::AllocateByteArray(int length) {
   AllocationSpace space =
       size > MaxHeapObjectSize() ? LO_SPACE : NEW_SPACE;
 
-  Object* result = AllocateRaw(size, space);
+  Object* result = AllocateRaw(size, space, OLD_DATA_SPACE);
 
   if (result->IsFailure()) return result;
 
@@ -1604,7 +1608,9 @@ Object* Heap::CopyCode(Code* code) {
 Object* Heap::Allocate(Map* map, AllocationSpace space) {
   ASSERT(gc_state_ == NOT_IN_GC);
   ASSERT(map->instance_type() != MAP_TYPE);
-  Object* result = AllocateRaw(map->instance_size(), space);
+  Object* result = AllocateRaw(map->instance_size(),
+                               space,
+                               TargetSpaceId(map->instance_type()));
   if (result->IsFailure()) return result;
   HeapObject::cast(result)->set_map(map);
   return result;
@@ -1664,19 +1670,19 @@ Object* Heap::AllocateArgumentsObject(Object* callee, int length) {
   // Make the clone.
   Map* map = boilerplate->map();
   int object_size = map->instance_size();
-  Object* result = new_space_.AllocateRaw(object_size);
+  Object* result = AllocateRaw(object_size, NEW_SPACE, OLD_POINTER_SPACE);
   if (result->IsFailure()) return result;
-  ASSERT(Heap::InNewSpace(result));
 
-  // Copy the content.
+  // Copy the content. The arguments boilerplate doesn't have any
+  // fields that point to new space so it's safe to skip the write
+  // barrier here.
   CopyBlock(reinterpret_cast<Object**>(HeapObject::cast(result)->address()),
             reinterpret_cast<Object**>(boilerplate->address()),
             object_size);
 
   // Set the two properties.
   JSObject::cast(result)->InObjectPropertyAtPut(arguments_callee_index,
-                                                callee,
-                                                SKIP_WRITE_BARRIER);
+                                                callee);
   JSObject::cast(result)->InObjectPropertyAtPut(arguments_length_index,
                                                 Smi::FromInt(length),
                                                 SKIP_WRITE_BARRIER);
@@ -1784,14 +1790,33 @@ Object* Heap::CopyJSObject(JSObject* source) {
   // Make the clone.
   Map* map = source->map();
   int object_size = map->instance_size();
-  Object* clone = new_space_.AllocateRaw(object_size);
-  if (clone->IsFailure()) return clone;
-  ASSERT(Heap::InNewSpace(clone));
-
-  // Copy the content.
-  CopyBlock(reinterpret_cast<Object**>(HeapObject::cast(clone)->address()),
-            reinterpret_cast<Object**>(source->address()),
-            object_size);
+  Object* clone;
+
+  // If we're forced to always allocate, we use the general allocation
+  // functions which may leave us with an object in old space.
+  if (always_allocate()) {
+    clone = AllocateRaw(object_size, NEW_SPACE, OLD_POINTER_SPACE);
+    if (clone->IsFailure()) return clone;
+    Address clone_address = HeapObject::cast(clone)->address();
+    CopyBlock(reinterpret_cast<Object**>(clone_address),
+              reinterpret_cast<Object**>(source->address()),
+              object_size);
+    // Update write barrier for all fields that lie beyond the header.
+    for (int offset = JSObject::kHeaderSize;
+         offset < object_size;
+         offset += kPointerSize) {
+      RecordWrite(clone_address, offset);
+    }
+  } else {
+    clone = new_space_.AllocateRaw(object_size);
+    if (clone->IsFailure()) return clone;
+    ASSERT(Heap::InNewSpace(clone));
+    // Since we know the clone is allocated in new space, we can copy
+    // the contents without worring about updating the write barrier.
+    CopyBlock(reinterpret_cast<Object**>(HeapObject::cast(clone)->address()),
+              reinterpret_cast<Object**>(source->address()),
+              object_size);
+  }
 
   FixedArray* elements = FixedArray::cast(source->elements());
   FixedArray* properties = FixedArray::cast(source->properties());
@@ -2013,7 +2038,7 @@ Object* Heap::AllocateSymbol(unibrow::CharacterStream* buffer,
   // Allocate string.
   AllocationSpace space =
       (size > MaxHeapObjectSize()) ? LO_SPACE : OLD_DATA_SPACE;
-  Object* result = AllocateRaw(size, space);
+  Object* result = AllocateRaw(size, space, OLD_DATA_SPACE);
   if (result->IsFailure()) return result;
 
   reinterpret_cast<HeapObject*>(result)->set_map(map);
@@ -2039,7 +2064,7 @@ Object* Heap::AllocateRawAsciiString(int length, PretenureFlag pretenure) {
 
   // Use AllocateRaw rather than Allocate because the object's size cannot be
   // determined from the map.
-  Object* result = AllocateRaw(size, space);
+  Object* result = AllocateRaw(size, space, OLD_DATA_SPACE);
   if (result->IsFailure()) return result;
 
   // Determine the map based on the string's length.
@@ -2069,7 +2094,7 @@ Object* Heap::AllocateRawTwoByteString(int length, PretenureFlag pretenure) {
 
   // Use AllocateRaw rather than Allocate because the object's size cannot be
   // determined from the map.
-  Object* result = AllocateRaw(size, space);
+  Object* result = AllocateRaw(size, space, OLD_DATA_SPACE);
   if (result->IsFailure()) return result;
 
   // Determine the map based on the string's length.
@@ -2092,7 +2117,7 @@ Object* Heap::AllocateRawTwoByteString(int length, PretenureFlag pretenure) {
 
 Object* Heap::AllocateEmptyFixedArray() {
   int size = FixedArray::SizeFor(0);
-  Object* result = AllocateRaw(size, OLD_DATA_SPACE);
+  Object* result = AllocateRaw(size, OLD_DATA_SPACE, OLD_DATA_SPACE);
   if (result->IsFailure()) return result;
   // Initialize the object.
   reinterpret_cast<Array*>(result)->set_map(fixed_array_map());
@@ -2102,6 +2127,8 @@ Object* Heap::AllocateEmptyFixedArray() {
 
 
 Object* Heap::AllocateRawFixedArray(int length) {
+  // Use the general function if we're forced to always allocate.
+  if (always_allocate()) return AllocateFixedArray(length, NOT_TENURED);
   // Allocate the raw data for a fixed array.
   int size = FixedArray::SizeFor(length);
   return (size > MaxHeapObjectSize())
@@ -2159,7 +2186,7 @@ Object* Heap::AllocateFixedArray(int length, PretenureFlag pretenure) {
   } else {
     AllocationSpace space =
         (pretenure == TENURED) ? OLD_POINTER_SPACE : NEW_SPACE;
-    result = AllocateRaw(size, space);
+    result = AllocateRaw(size, space, OLD_POINTER_SPACE);
   }
   if (result->IsFailure()) return result;
 
index d120f5a..958b4eb 100644 (file)
@@ -260,6 +260,11 @@ class Heap : public AllStatic {
   static MapSpace* map_space() { return map_space_; }
   static LargeObjectSpace* lo_space() { return lo_space_; }
 
+  static bool always_allocate() { return always_allocate_scope_depth_ != 0; }
+  static Address always_allocate_scope_depth_address() {
+    return reinterpret_cast<Address>(&always_allocate_scope_depth_);
+  }
+
   static Address* NewSpaceAllocationTopAddress() {
     return new_space_.allocation_top_address();
   }
@@ -523,7 +528,8 @@ class Heap : public AllStatic {
   // failed.
   // Please note this function does not perform a garbage collection.
   static inline Object* AllocateRaw(int size_in_bytes,
-                                    AllocationSpace space);
+                                    AllocationSpace space,
+                                    AllocationSpace retry_space);
 
   // Makes a new native code object
   // Returns Failure::RetryAfterGC(requested_bytes, space) if the allocation
@@ -631,6 +637,7 @@ class Heap : public AllStatic {
 
   // Finds out which space an object should get promoted to based on its type.
   static inline OldSpace* TargetSpace(HeapObject* object);
+  static inline AllocationSpace TargetSpaceId(InstanceType type);
 
   // Sets the stub_cache_ (only used when expanding the dictionary).
   static void set_code_stubs(Dictionary* value) { code_stubs_ = value; }
@@ -767,6 +774,8 @@ class Heap : public AllStatic {
   static int new_space_growth_limit_;
   static int scavenge_count_;
 
+  static int always_allocate_scope_depth_;
+
   static const int kMaxMapSpaceSize = 8*MB;
 
   static NewSpace new_space_;
@@ -925,6 +934,25 @@ class Heap : public AllStatic {
 
   friend class Factory;
   friend class DisallowAllocationFailure;
+  friend class AlwaysAllocateScope;
+};
+
+
+class AlwaysAllocateScope {
+ public:
+  AlwaysAllocateScope() {
+    // We shouldn't hit any nested scopes, because that requires
+    // non-handle code to call handle code. The code still works but
+    // performance will degrade, so we want to catch this situation
+    // in debug mode.
+    ASSERT(Heap::always_allocate_scope_depth_ == 0);
+    Heap::always_allocate_scope_depth_++;
+  }
+
+  ~AlwaysAllocateScope() {
+    Heap::always_allocate_scope_depth_--;
+    ASSERT(Heap::always_allocate_scope_depth_ == 0);
+  }
 };
 
 
index 821de5e..cb5c538 100644 (file)
@@ -5787,6 +5787,7 @@ void Runtime::PerformGC(Object* result) {
   } else {
     // Handle last resort GC and make sure to allow future allocations
     // to grow the heap without causing GCs (if possible).
+    Counters::gc_last_resort_from_js.Increment();
     Heap::CollectAllGarbage();
   }
 }
index 1fd94ab..cd7a323 100644 (file)
@@ -593,17 +593,21 @@ ExternalReferenceTable::ExternalReferenceTable() : refs_(64) {
       UNCLASSIFIED,
       5,
       "Heap::NewSpaceStart()");
-  Add(ExternalReference::new_space_allocation_limit_address().address(),
+  Add(ExternalReference::heap_always_allocate_scope_depth().address(),
       UNCLASSIFIED,
       6,
+      "Heap::always_allocate_scope_depth()");
+  Add(ExternalReference::new_space_allocation_limit_address().address(),
+      UNCLASSIFIED,
+      7,
       "Heap::NewSpaceAllocationLimitAddress()");
   Add(ExternalReference::new_space_allocation_top_address().address(),
       UNCLASSIFIED,
-      7,
+      8,
       "Heap::NewSpaceAllocationTopAddress()");
   Add(ExternalReference::debug_step_in_fp_address().address(),
       UNCLASSIFIED,
-      8,
+      9,
       "Debug::step_in_fp_addr()");
 }
 
@@ -1401,7 +1405,10 @@ Object* Deserializer::GetObject() {
   } else if (IsLargeFixedArray(a)) {
     o = Heap::lo_space()->AllocateRawFixedArray(size);
   } else {
-    o = Heap::AllocateRaw(size, space);
+    AllocationSpace retry_space = (space == NEW_SPACE)
+        ? Heap::TargetSpaceId(type)
+        : space;
+    o = Heap::AllocateRaw(size, space, retry_space);
   }
   ASSERT(!o->IsFailure());
   // Check that the simulation of heap allocation was correct.
index 34d27a7..63add7c 100644 (file)
@@ -1533,7 +1533,7 @@ HeapObject* OldSpace::SlowAllocateRaw(int size_in_bytes) {
   // Free list allocation failed and there is no next page.  Fail if we have
   // hit the old generation size limit that should cause a garbage
   // collection.
-  if (Heap::OldGenerationAllocationLimitReached()) {
+  if (!Heap::always_allocate() && Heap::OldGenerationAllocationLimitReached()) {
     return NULL;
   }
 
@@ -2018,7 +2018,7 @@ HeapObject* MapSpace::SlowAllocateRaw(int size_in_bytes) {
   // Free list allocation failed and there is no next page.  Fail if we have
   // hit the old generation size limit that should cause a garbage
   // collection.
-  if (Heap::OldGenerationAllocationLimitReached()) {
+  if (!Heap::always_allocate() && Heap::OldGenerationAllocationLimitReached()) {
     return NULL;
   }
 
@@ -2251,7 +2251,7 @@ Object* LargeObjectSpace::AllocateRawInternal(int requested_size,
 
   // Check if we want to force a GC before growing the old space further.
   // If so, fail the allocation.
-  if (Heap::OldGenerationAllocationLimitReached()) {
+  if (!Heap::always_allocate() && Heap::OldGenerationAllocationLimitReached()) {
     return Failure::RetryAfterGC(requested_size, identity());
   }
 
index 6eb7426..0505d20 100644 (file)
@@ -101,6 +101,8 @@ namespace v8 { namespace internal {
      V8.GCCompactorCausedByOldspaceExhaustion)                      \
   SC(gc_compactor_caused_by_weak_handles,                           \
      V8.GCCompactorCausedByWeakHandles)                             \
+  SC(gc_last_resort_from_js, V8.GCLastResortFromJS)                 \
+  SC(gc_last_resort_from_handles, V8.GCLastResortFromHandles)       \
   /* How is the generic keyed-load stub used? */                    \
   SC(keyed_load_generic_smi, V8.KeyedLoadGenericSmi)                \
   SC(keyed_load_generic_symbol, V8.KeyedLoadGenericSymbol)          \
index bd0e327..2ac13c4 100644 (file)
@@ -38,7 +38,7 @@ SOURCES = {
     'test-ast.cc', 'test-heap.cc', 'test-utils.cc', 'test-compiler.cc',
     'test-spaces.cc', 'test-mark-compact.cc', 'test-lock.cc',
     'test-conversions.cc', 'test-strings.cc', 'test-serialize.cc',
-    'test-decls.cc'
+    'test-decls.cc', 'test-alloc.cc'
   ],
   'arch:arm':  ['test-assembler-arm.cc', 'test-disasm-arm.cc'],
   'arch:ia32': ['test-assembler-ia32.cc', 'test-disasm-ia32.cc'],
diff --git a/test/cctest/test-alloc.cc b/test/cctest/test-alloc.cc
new file mode 100644 (file)
index 0000000..5516353
--- /dev/null
@@ -0,0 +1,102 @@
+// Copyright 2007-2008 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "v8.h"
+#include "top.h"
+
+#include "cctest.h"
+
+
+using namespace v8::internal;
+
+
+static Object* AllocateAfterFailures() {
+  static int attempts = 0;
+  if (++attempts < 3) return Failure::RetryAfterGC(0);
+
+  // New space.
+  NewSpace* new_space = Heap::new_space();
+  static const int kNewSpaceFillerSize = ByteArray::SizeFor(0);
+  while (new_space->Available() > kNewSpaceFillerSize) {
+    CHECK(!Heap::AllocateByteArray(0)->IsFailure());
+  }
+  CHECK(!Heap::AllocateByteArray(100)->IsFailure());
+  CHECK(!Heap::AllocateFixedArray(100, NOT_TENURED)->IsFailure());
+
+  // Make sure we can allocate through optimized allocation functions
+  // for specific kinds.
+  CHECK(!Heap::AllocateFixedArray(100)->IsFailure());
+  CHECK(!Heap::AllocateHeapNumber(0.42)->IsFailure());
+  CHECK(!Heap::AllocateArgumentsObject(Smi::FromInt(87), 10)->IsFailure());
+  Object* object = Heap::AllocateJSObject(*Top::object_function());
+  CHECK(!Heap::CopyJSObject(JSObject::cast(object))->IsFailure());
+
+  // Old data space.
+  OldSpace* old_data_space = Heap::old_data_space();
+  static const int kOldDataSpaceFillerSize = SeqAsciiString::SizeFor(0);
+  while (old_data_space->Available() > kOldDataSpaceFillerSize) {
+    CHECK(!Heap::AllocateRawAsciiString(0, TENURED)->IsFailure());
+  }
+  CHECK(!Heap::AllocateRawAsciiString(100, TENURED)->IsFailure());
+
+  // Large object space.
+  while (!Heap::OldGenerationAllocationLimitReached()) {
+    CHECK(!Heap::AllocateFixedArray(10000, TENURED)->IsFailure());
+  }
+  CHECK(!Heap::AllocateFixedArray(10000, TENURED)->IsFailure());
+
+  // Map space.
+  MapSpace* map_space = Heap::map_space();
+  static const int kMapSpaceFillerSize = Map::kSize;
+  InstanceType instance_type = JS_OBJECT_TYPE;
+  int instance_size = JSObject::kHeaderSize;
+  while (map_space->Available() > kMapSpaceFillerSize) {
+    CHECK(!Heap::AllocateMap(instance_type, instance_size)->IsFailure());
+  }
+  CHECK(!Heap::AllocateMap(instance_type, instance_size)->IsFailure());
+
+  // Test that we can allocate in old pointer space and code space.
+  CHECK(!Heap::AllocateFixedArray(100, TENURED)->IsFailure());
+  CHECK(!Heap::CopyCode(Builtins::builtin(Builtins::Illegal))->IsFailure());
+
+  // Return success.
+  return Smi::FromInt(42);
+}
+
+static Handle<Object> Test() {
+  CALL_HEAP_FUNCTION(AllocateAfterFailures(), Object);
+}
+
+
+TEST(Stress) {
+  v8::Persistent<v8::Context> env = v8::Context::New();
+  v8::HandleScope scope;
+  env->Enter();
+  Handle<Object> o = Test();
+  CHECK(o->IsSmi() && Smi::cast(*o)->value() == 42);
+  env->Exit();
+}
index 5c14b9b..d62b887 100644 (file)
                        >
                </File>
                <File
+                       RelativePath="..\..\test\cctest\test-alloc.cc"
+                       >
+               </File>
+               <File
                        RelativePath="..\..\test\cctest\test-api.cc"
                        >
                </File>