From: kasperl@chromium.org Date: Thu, 30 Oct 2008 09:15:58 +0000 (+0000) Subject: Make sure that allocations through CALL_HEAP_FUNCTION X-Git-Tag: upstream/4.7.83~25075 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=a2be3b6f84044f60d8a4128fcc57e90af411145a;p=platform%2Fupstream%2Fv8.git Make sure that allocations through CALL_HEAP_FUNCTION 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 --- diff --git a/src/assembler.cc b/src/assembler.cc index a2930f1..1fd0dc3 100644 --- a/src/assembler.cc +++ b/src/assembler.cc @@ -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()); } diff --git a/src/assembler.h b/src/assembler.h index 137c37c..81c8056 100644 --- a/src/assembler.h +++ b/src/assembler.h @@ -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(); diff --git a/src/codegen-arm.cc b/src/codegen-arm.cc index 256dec5..35522b1 100644 --- a/src/codegen-arm.cc +++ b/src/codegen-arm.cc @@ -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); diff --git a/src/codegen-ia32.cc b/src/codegen-ia32.cc index 79dbb0b..eaac989 100644 --- a/src/codegen-ia32.cc +++ b/src/codegen-ia32.cc @@ -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); diff --git a/src/codegen.h b/src/codegen.h index f2ac05c..ca11c34 100644 --- a/src/codegen.h +++ b/src/codegen.h @@ -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); diff --git a/src/heap-inl.h b/src/heap-inl.h index bfa2b65..b54ef3a 100644 --- a/src/heap-inl.h +++ b/src/heap-inl.h @@ -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(); \ - } \ - } else { \ - if (__object__->IsOutOfMemoryFailure()) { \ - v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION"); \ - } \ - return Handle(); \ - } \ - } \ - return Handle(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::cast(__object__)), \ + Handle()) + + +#define CALL_HEAP_FUNCTION_VOID(FUNCTION_CALL) \ + CALL_AND_RETRY(FUNCTION_CALL, , ) #ifdef DEBUG diff --git a/src/heap.cc b/src/heap.cc index dc0e100..bb30deb 100644 --- a/src/heap.cc +++ b/src/heap.cc @@ -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(HeapObject::cast(result)->address()), reinterpret_cast(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(HeapObject::cast(clone)->address()), - reinterpret_cast(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(clone_address), + reinterpret_cast(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(HeapObject::cast(clone)->address()), + reinterpret_cast(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(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(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; diff --git a/src/heap.h b/src/heap.h index d120f5a..958b4eb 100644 --- a/src/heap.h +++ b/src/heap.h @@ -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
(&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); + } }; diff --git a/src/runtime.cc b/src/runtime.cc index 821de5e..cb5c538 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -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(); } } diff --git a/src/serialize.cc b/src/serialize.cc index 1fd94ab..cd7a323 100644 --- a/src/serialize.cc +++ b/src/serialize.cc @@ -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. diff --git a/src/spaces.cc b/src/spaces.cc index 34d27a7..63add7c 100644 --- a/src/spaces.cc +++ b/src/spaces.cc @@ -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()); } diff --git a/src/v8-counters.h b/src/v8-counters.h index 6eb7426..0505d20 100644 --- a/src/v8-counters.h +++ b/src/v8-counters.h @@ -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) \ diff --git a/test/cctest/SConscript b/test/cctest/SConscript index bd0e327..2ac13c4 100644 --- a/test/cctest/SConscript +++ b/test/cctest/SConscript @@ -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 index 0000000..5516353 --- /dev/null +++ b/test/cctest/test-alloc.cc @@ -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 Test() { + CALL_HEAP_FUNCTION(AllocateAfterFailures(), Object); +} + + +TEST(Stress) { + v8::Persistent env = v8::Context::New(); + v8::HandleScope scope; + env->Enter(); + Handle o = Test(); + CHECK(o->IsSmi() && Smi::cast(*o)->value() == 42); + env->Exit(); +} diff --git a/tools/visual_studio/v8_cctest.vcproj b/tools/visual_studio/v8_cctest.vcproj index 5c14b9b..d62b887 100644 --- a/tools/visual_studio/v8_cctest.vcproj +++ b/tools/visual_studio/v8_cctest.vcproj @@ -144,6 +144,10 @@ > + +