Home | History | Annotate | Download | only in cctest
      1 // Copyright 2012 the V8 project authors. All rights reserved.
      2 // Redistribution and use in source and binary forms, with or without
      3 // modification, are permitted provided that the following conditions are
      4 // met:
      5 //
      6 //     * Redistributions of source code must retain the above copyright
      7 //       notice, this list of conditions and the following disclaimer.
      8 //     * Redistributions in binary form must reproduce the above
      9 //       copyright notice, this list of conditions and the following
     10 //       disclaimer in the documentation and/or other materials provided
     11 //       with the distribution.
     12 //     * Neither the name of Google Inc. nor the names of its
     13 //       contributors may be used to endorse or promote products derived
     14 //       from this software without specific prior written permission.
     15 //
     16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 #include <stdlib.h>
     29 
     30 #ifdef __linux__
     31 #include <sys/types.h>
     32 #include <sys/stat.h>
     33 #include <fcntl.h>
     34 #include <unistd.h>
     35 #include <errno.h>
     36 #endif
     37 
     38 
     39 #include "v8.h"
     40 
     41 #include "global-handles.h"
     42 #include "snapshot.h"
     43 #include "cctest.h"
     44 
     45 using namespace v8::internal;
     46 
     47 
     48 TEST(MarkingDeque) {
     49   CcTest::InitializeVM();
     50   int mem_size = 20 * kPointerSize;
     51   byte* mem = NewArray<byte>(20*kPointerSize);
     52   Address low = reinterpret_cast<Address>(mem);
     53   Address high = low + mem_size;
     54   MarkingDeque s;
     55   s.Initialize(low, high);
     56 
     57   Address original_address = reinterpret_cast<Address>(&s);
     58   Address current_address = original_address;
     59   while (!s.IsFull()) {
     60     s.PushBlack(HeapObject::FromAddress(current_address));
     61     current_address += kPointerSize;
     62   }
     63 
     64   while (!s.IsEmpty()) {
     65     Address value = s.Pop()->address();
     66     current_address -= kPointerSize;
     67     CHECK_EQ(current_address, value);
     68   }
     69 
     70   CHECK_EQ(original_address, current_address);
     71   DeleteArray(mem);
     72 }
     73 
     74 
     75 TEST(Promotion) {
     76   CcTest::InitializeVM();
     77   Heap* heap = CcTest::heap();
     78   heap->ConfigureHeap(2*256*KB, 1*MB, 1*MB);
     79 
     80   v8::HandleScope sc(CcTest::isolate());
     81 
     82   // Allocate a fixed array in the new space.
     83   int array_length =
     84       (Page::kMaxNonCodeHeapObjectSize - FixedArray::kHeaderSize) /
     85       (4 * kPointerSize);
     86   Object* obj = heap->AllocateFixedArray(array_length)->ToObjectChecked();
     87   Handle<FixedArray> array(FixedArray::cast(obj));
     88 
     89   // Array should be in the new space.
     90   CHECK(heap->InSpace(*array, NEW_SPACE));
     91 
     92   // Call mark compact GC, so array becomes an old object.
     93   heap->CollectGarbage(OLD_POINTER_SPACE);
     94 
     95   // Array now sits in the old space
     96   CHECK(heap->InSpace(*array, OLD_POINTER_SPACE));
     97 }
     98 
     99 
    100 TEST(NoPromotion) {
    101   CcTest::InitializeVM();
    102   Heap* heap = CcTest::heap();
    103   heap->ConfigureHeap(2*256*KB, 1*MB, 1*MB);
    104 
    105   v8::HandleScope sc(CcTest::isolate());
    106 
    107   // Allocate a big fixed array in the new space.
    108   int array_length =
    109       (Page::kMaxNonCodeHeapObjectSize - FixedArray::kHeaderSize) /
    110       (2 * kPointerSize);
    111   Object* obj = heap->AllocateFixedArray(array_length)->ToObjectChecked();
    112   Handle<FixedArray> array(FixedArray::cast(obj));
    113 
    114   // Array should be in the new space.
    115   CHECK(heap->InSpace(*array, NEW_SPACE));
    116 
    117   // Simulate a full old space to make promotion fail.
    118   SimulateFullSpace(heap->old_pointer_space());
    119 
    120   // Call mark compact GC, and it should pass.
    121   heap->CollectGarbage(OLD_POINTER_SPACE);
    122 }
    123 
    124 
    125 TEST(MarkCompactCollector) {
    126   FLAG_incremental_marking = false;
    127   CcTest::InitializeVM();
    128   Isolate* isolate = CcTest::i_isolate();
    129   Heap* heap = isolate->heap();
    130 
    131   v8::HandleScope sc(CcTest::isolate());
    132   Handle<GlobalObject> global(isolate->context()->global_object());
    133 
    134   // call mark-compact when heap is empty
    135   heap->CollectGarbage(OLD_POINTER_SPACE, "trigger 1");
    136 
    137   // keep allocating garbage in new space until it fails
    138   const int ARRAY_SIZE = 100;
    139   Object* array;
    140   MaybeObject* maybe_array;
    141   do {
    142     maybe_array = heap->AllocateFixedArray(ARRAY_SIZE);
    143   } while (maybe_array->ToObject(&array));
    144   heap->CollectGarbage(NEW_SPACE, "trigger 2");
    145 
    146   array = heap->AllocateFixedArray(ARRAY_SIZE)->ToObjectChecked();
    147 
    148   // keep allocating maps until it fails
    149   Object* mapp;
    150   MaybeObject* maybe_mapp;
    151   do {
    152     maybe_mapp = heap->AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
    153   } while (maybe_mapp->ToObject(&mapp));
    154   heap->CollectGarbage(MAP_SPACE, "trigger 3");
    155   mapp = heap->AllocateMap(JS_OBJECT_TYPE,
    156                            JSObject::kHeaderSize)->ToObjectChecked();
    157 
    158   // allocate a garbage
    159   String* func_name = String::cast(
    160       heap->InternalizeUtf8String("theFunction")->ToObjectChecked());
    161   SharedFunctionInfo* function_share = SharedFunctionInfo::cast(
    162       heap->AllocateSharedFunctionInfo(func_name)->ToObjectChecked());
    163   JSFunction* function = JSFunction::cast(
    164       heap->AllocateFunction(*isolate->function_map(),
    165                              function_share,
    166                              heap->undefined_value())->ToObjectChecked());
    167   Map* initial_map =
    168       Map::cast(heap->AllocateMap(JS_OBJECT_TYPE,
    169                                   JSObject::kHeaderSize)->ToObjectChecked());
    170   function->set_initial_map(initial_map);
    171   JSReceiver::SetProperty(
    172       global, handle(func_name), handle(function), NONE, kNonStrictMode);
    173 
    174   JSObject* obj = JSObject::cast(
    175       heap->AllocateJSObject(function)->ToObjectChecked());
    176   heap->CollectGarbage(OLD_POINTER_SPACE, "trigger 4");
    177 
    178   func_name = String::cast(
    179       heap->InternalizeUtf8String("theFunction")->ToObjectChecked());
    180   CHECK(JSReceiver::HasLocalProperty(global, handle(func_name)));
    181   Object* func_value = isolate->context()->global_object()->
    182       GetProperty(func_name)->ToObjectChecked();
    183   CHECK(func_value->IsJSFunction());
    184   function = JSFunction::cast(func_value);
    185 
    186   obj = JSObject::cast(heap->AllocateJSObject(function)->ToObjectChecked());
    187   String* obj_name =
    188       String::cast(heap->InternalizeUtf8String("theObject")->ToObjectChecked());
    189   JSReceiver::SetProperty(
    190       global, handle(obj_name), handle(obj), NONE, kNonStrictMode);
    191   String* prop_name =
    192       String::cast(heap->InternalizeUtf8String("theSlot")->ToObjectChecked());
    193   Handle<Smi> twenty_three(Smi::FromInt(23), isolate);
    194   JSReceiver::SetProperty(
    195       handle(obj), handle(prop_name), twenty_three, NONE, kNonStrictMode);
    196 
    197   heap->CollectGarbage(OLD_POINTER_SPACE, "trigger 5");
    198 
    199   obj_name =
    200       String::cast(heap->InternalizeUtf8String("theObject")->ToObjectChecked());
    201   CHECK(JSReceiver::HasLocalProperty(global, handle(obj_name)));
    202   CHECK(isolate->context()->global_object()->
    203         GetProperty(obj_name)->ToObjectChecked()->IsJSObject());
    204   obj = JSObject::cast(isolate->context()->global_object()->
    205                        GetProperty(obj_name)->ToObjectChecked());
    206   prop_name =
    207       String::cast(heap->InternalizeUtf8String("theSlot")->ToObjectChecked());
    208   CHECK(obj->GetProperty(prop_name) == Smi::FromInt(23));
    209 }
    210 
    211 
    212 // TODO(1600): compaction of map space is temporary removed from GC.
    213 #if 0
    214 static Handle<Map> CreateMap(Isolate* isolate) {
    215   return isolate->factory()->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
    216 }
    217 
    218 
    219 TEST(MapCompact) {
    220   FLAG_max_map_space_pages = 16;
    221   CcTest::InitializeVM();
    222   Isolate* isolate = CcTest::i_isolate();
    223   Factory* factory = isolate->factory();
    224 
    225   {
    226     v8::HandleScope sc;
    227     // keep allocating maps while pointers are still encodable and thus
    228     // mark compact is permitted.
    229     Handle<JSObject> root = factory->NewJSObjectFromMap(CreateMap());
    230     do {
    231       Handle<Map> map = CreateMap();
    232       map->set_prototype(*root);
    233       root = factory->NewJSObjectFromMap(map);
    234     } while (CcTest::heap()->map_space()->MapPointersEncodable());
    235   }
    236   // Now, as we don't have any handles to just allocated maps, we should
    237   // be able to trigger map compaction.
    238   // To give an additional chance to fail, try to force compaction which
    239   // should be impossible right now.
    240   CcTest::heap()->CollectAllGarbage(Heap::kForceCompactionMask);
    241   // And now map pointers should be encodable again.
    242   CHECK(CcTest::heap()->map_space()->MapPointersEncodable());
    243 }
    244 #endif
    245 
    246 
    247 static int NumberOfWeakCalls = 0;
    248 static void WeakPointerCallback(v8::Isolate* isolate,
    249                                 v8::Persistent<v8::Value>* handle,
    250                                 void* id) {
    251   ASSERT(id == reinterpret_cast<void*>(1234));
    252   NumberOfWeakCalls++;
    253   handle->Reset();
    254 }
    255 
    256 
    257 TEST(ObjectGroups) {
    258   FLAG_incremental_marking = false;
    259   CcTest::InitializeVM();
    260   GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
    261   Heap* heap = CcTest::heap();
    262   NumberOfWeakCalls = 0;
    263   v8::HandleScope handle_scope(CcTest::isolate());
    264 
    265   Handle<Object> g1s1 =
    266       global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    267   Handle<Object> g1s2 =
    268       global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    269   Handle<Object> g1c1 =
    270       global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    271   global_handles->MakeWeak(g1s1.location(),
    272                            reinterpret_cast<void*>(1234),
    273                            &WeakPointerCallback);
    274   global_handles->MakeWeak(g1s2.location(),
    275                            reinterpret_cast<void*>(1234),
    276                            &WeakPointerCallback);
    277   global_handles->MakeWeak(g1c1.location(),
    278                            reinterpret_cast<void*>(1234),
    279                            &WeakPointerCallback);
    280 
    281   Handle<Object> g2s1 =
    282       global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    283   Handle<Object> g2s2 =
    284     global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    285   Handle<Object> g2c1 =
    286     global_handles->Create(heap->AllocateFixedArray(1)->ToObjectChecked());
    287   global_handles->MakeWeak(g2s1.location(),
    288                            reinterpret_cast<void*>(1234),
    289                            &WeakPointerCallback);
    290   global_handles->MakeWeak(g2s2.location(),
    291                            reinterpret_cast<void*>(1234),
    292                            &WeakPointerCallback);
    293   global_handles->MakeWeak(g2c1.location(),
    294                            reinterpret_cast<void*>(1234),
    295                            &WeakPointerCallback);
    296 
    297   Handle<Object> root = global_handles->Create(*g1s1);  // make a root.
    298 
    299   // Connect group 1 and 2, make a cycle.
    300   Handle<FixedArray>::cast(g1s2)->set(0, *g2s2);
    301   Handle<FixedArray>::cast(g2s1)->set(0, *g1s1);
    302 
    303   {
    304     Object** g1_objects[] = { g1s1.location(), g1s2.location() };
    305     Object** g1_children[] = { g1c1.location() };
    306     Object** g2_objects[] = { g2s1.location(), g2s2.location() };
    307     Object** g2_children[] = { g2c1.location() };
    308     global_handles->AddObjectGroup(g1_objects, 2, NULL);
    309     global_handles->AddImplicitReferences(
    310         Handle<HeapObject>::cast(g1s1).location(), g1_children, 1);
    311     global_handles->AddObjectGroup(g2_objects, 2, NULL);
    312     global_handles->AddImplicitReferences(
    313         Handle<HeapObject>::cast(g2s1).location(), g2_children, 1);
    314   }
    315   // Do a full GC
    316   heap->CollectGarbage(OLD_POINTER_SPACE);
    317 
    318   // All object should be alive.
    319   CHECK_EQ(0, NumberOfWeakCalls);
    320 
    321   // Weaken the root.
    322   global_handles->MakeWeak(root.location(),
    323                            reinterpret_cast<void*>(1234),
    324                            &WeakPointerCallback);
    325   // But make children strong roots---all the objects (except for children)
    326   // should be collectable now.
    327   global_handles->ClearWeakness(g1c1.location());
    328   global_handles->ClearWeakness(g2c1.location());
    329 
    330   // Groups are deleted, rebuild groups.
    331   {
    332     Object** g1_objects[] = { g1s1.location(), g1s2.location() };
    333     Object** g1_children[] = { g1c1.location() };
    334     Object** g2_objects[] = { g2s1.location(), g2s2.location() };
    335     Object** g2_children[] = { g2c1.location() };
    336     global_handles->AddObjectGroup(g1_objects, 2, NULL);
    337     global_handles->AddImplicitReferences(
    338         Handle<HeapObject>::cast(g1s1).location(), g1_children, 1);
    339     global_handles->AddObjectGroup(g2_objects, 2, NULL);
    340     global_handles->AddImplicitReferences(
    341         Handle<HeapObject>::cast(g2s1).location(), g2_children, 1);
    342   }
    343 
    344   heap->CollectGarbage(OLD_POINTER_SPACE);
    345 
    346   // All objects should be gone. 5 global handles in total.
    347   CHECK_EQ(5, NumberOfWeakCalls);
    348 
    349   // And now make children weak again and collect them.
    350   global_handles->MakeWeak(g1c1.location(),
    351                            reinterpret_cast<void*>(1234),
    352                            &WeakPointerCallback);
    353   global_handles->MakeWeak(g2c1.location(),
    354                            reinterpret_cast<void*>(1234),
    355                            &WeakPointerCallback);
    356 
    357   heap->CollectGarbage(OLD_POINTER_SPACE);
    358   CHECK_EQ(7, NumberOfWeakCalls);
    359 }
    360 
    361 
    362 class TestRetainedObjectInfo : public v8::RetainedObjectInfo {
    363  public:
    364   TestRetainedObjectInfo() : has_been_disposed_(false) {}
    365 
    366   bool has_been_disposed() { return has_been_disposed_; }
    367 
    368   virtual void Dispose() {
    369     ASSERT(!has_been_disposed_);
    370     has_been_disposed_ = true;
    371   }
    372 
    373   virtual bool IsEquivalent(v8::RetainedObjectInfo* other) {
    374     return other == this;
    375   }
    376 
    377   virtual intptr_t GetHash() { return 0; }
    378 
    379   virtual const char* GetLabel() { return "whatever"; }
    380 
    381  private:
    382   bool has_been_disposed_;
    383 };
    384 
    385 
    386 TEST(EmptyObjectGroups) {
    387   CcTest::InitializeVM();
    388   GlobalHandles* global_handles = CcTest::i_isolate()->global_handles();
    389 
    390   v8::HandleScope handle_scope(CcTest::isolate());
    391 
    392   Handle<Object> object = global_handles->Create(
    393       CcTest::heap()->AllocateFixedArray(1)->ToObjectChecked());
    394 
    395   TestRetainedObjectInfo info;
    396   global_handles->AddObjectGroup(NULL, 0, &info);
    397   ASSERT(info.has_been_disposed());
    398 
    399   global_handles->AddImplicitReferences(
    400         Handle<HeapObject>::cast(object).location(), NULL, 0);
    401 }
    402 
    403 
    404 #if defined(__has_feature)
    405 #if __has_feature(address_sanitizer)
    406 #define V8_WITH_ASAN 1
    407 #endif
    408 #endif
    409 
    410 
    411 // Here is a memory use test that uses /proc, and is therefore Linux-only.  We
    412 // do not care how much memory the simulator uses, since it is only there for
    413 // debugging purposes. Testing with ASAN doesn't make sense, either.
    414 #if defined(__linux__) && !defined(USE_SIMULATOR) && !defined(V8_WITH_ASAN)
    415 
    416 
    417 static uintptr_t ReadLong(char* buffer, intptr_t* position, int base) {
    418   char* end_address = buffer + *position;
    419   uintptr_t result = strtoul(buffer + *position, &end_address, base);
    420   CHECK(result != ULONG_MAX || errno != ERANGE);
    421   CHECK(end_address > buffer + *position);
    422   *position = end_address - buffer;
    423   return result;
    424 }
    425 
    426 
    427 // The memory use computed this way is not entirely accurate and depends on
    428 // the way malloc allocates memory.  That's why the memory use may seem to
    429 // increase even though the sum of the allocated object sizes decreases.  It
    430 // also means that the memory use depends on the kernel and stdlib.
    431 static intptr_t MemoryInUse() {
    432   intptr_t memory_use = 0;
    433 
    434   int fd = open("/proc/self/maps", O_RDONLY);
    435   if (fd < 0) return -1;
    436 
    437   const int kBufSize = 10000;
    438   char buffer[kBufSize];
    439   int length = read(fd, buffer, kBufSize);
    440   intptr_t line_start = 0;
    441   CHECK_LT(length, kBufSize);  // Make the buffer bigger.
    442   CHECK_GT(length, 0);  // We have to find some data in the file.
    443   while (line_start < length) {
    444     if (buffer[line_start] == '\n') {
    445       line_start++;
    446       continue;
    447     }
    448     intptr_t position = line_start;
    449     uintptr_t start = ReadLong(buffer, &position, 16);
    450     CHECK_EQ(buffer[position++], '-');
    451     uintptr_t end = ReadLong(buffer, &position, 16);
    452     CHECK_EQ(buffer[position++], ' ');
    453     CHECK(buffer[position] == '-' || buffer[position] == 'r');
    454     bool read_permission = (buffer[position++] == 'r');
    455     CHECK(buffer[position] == '-' || buffer[position] == 'w');
    456     bool write_permission = (buffer[position++] == 'w');
    457     CHECK(buffer[position] == '-' || buffer[position] == 'x');
    458     bool execute_permission = (buffer[position++] == 'x');
    459     CHECK(buffer[position] == '-' || buffer[position] == 'p');
    460     bool private_mapping = (buffer[position++] == 'p');
    461     CHECK_EQ(buffer[position++], ' ');
    462     uintptr_t offset = ReadLong(buffer, &position, 16);
    463     USE(offset);
    464     CHECK_EQ(buffer[position++], ' ');
    465     uintptr_t major = ReadLong(buffer, &position, 16);
    466     USE(major);
    467     CHECK_EQ(buffer[position++], ':');
    468     uintptr_t minor = ReadLong(buffer, &position, 16);
    469     USE(minor);
    470     CHECK_EQ(buffer[position++], ' ');
    471     uintptr_t inode = ReadLong(buffer, &position, 10);
    472     while (position < length && buffer[position] != '\n') position++;
    473     if ((read_permission || write_permission || execute_permission) &&
    474         private_mapping && inode == 0) {
    475       memory_use += (end - start);
    476     }
    477 
    478     line_start = position;
    479   }
    480   close(fd);
    481   return memory_use;
    482 }
    483 
    484 
    485 TEST(BootUpMemoryUse) {
    486   intptr_t initial_memory = MemoryInUse();
    487   // Avoid flakiness.
    488   FLAG_crankshaft = false;
    489   FLAG_concurrent_recompilation = false;
    490 
    491   // Only Linux has the proc filesystem and only if it is mapped.  If it's not
    492   // there we just skip the test.
    493   if (initial_memory >= 0) {
    494     CcTest::InitializeVM();
    495     intptr_t delta = MemoryInUse() - initial_memory;
    496     printf("delta: %" V8_PTR_PREFIX "d kB\n", delta / 1024);
    497     if (sizeof(initial_memory) == 8) {  // 64-bit.
    498       if (v8::internal::Snapshot::IsEnabled()) {
    499         CHECK_LE(delta, 4000 * 1024);
    500       } else {
    501         CHECK_LE(delta, 4500 * 1024);
    502       }
    503     } else {                            // 32-bit.
    504       if (v8::internal::Snapshot::IsEnabled()) {
    505         CHECK_LE(delta, 3100 * 1024);
    506       } else {
    507         CHECK_LE(delta, 3450 * 1024);
    508       }
    509     }
    510   }
    511 }
    512 
    513 
    514 intptr_t ShortLivingIsolate() {
    515   v8::Isolate* isolate = v8::Isolate::New();
    516   { v8::Isolate::Scope isolate_scope(isolate);
    517     v8::Locker lock(isolate);
    518     v8::HandleScope handle_scope(isolate);
    519     v8::Local<v8::Context> context = v8::Context::New(isolate);
    520     CHECK(!context.IsEmpty());
    521   }
    522   isolate->Dispose();
    523   return MemoryInUse();
    524 }
    525 
    526 
    527 TEST(RegressJoinThreadsOnIsolateDeinit) {
    528   intptr_t size_limit = ShortLivingIsolate() * 2;
    529   for (int i = 0; i < 10; i++) {
    530     CHECK_GT(size_limit, ShortLivingIsolate());
    531   }
    532 }
    533 
    534 #endif  // __linux__ and !USE_SIMULATOR
    535