| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| #include "vm/zone.h" | |
| #include "platform/address_sanitizer.h" | |
| #include "platform/assert.h" | |
| #include "platform/memory_sanitizer.h" | |
| #include "vm/dart.h" | |
| #include "vm/isolate.h" | |
| #include "vm/unit_test.h" | |
| namespace dart { | |
| VM_UNIT_TEST_CASE(AllocateZone) { | |
| #if defined(DEBUG) | |
| FLAG_trace_zones = true; | |
| #endif | |
| TestCase::CreateTestIsolate(); | |
| Thread* thread = Thread::Current(); | |
| EXPECT(thread->zone() == nullptr); | |
| { | |
| TransitionNativeToVM transition(thread); | |
| StackZone stack_zone(thread); | |
| EXPECT(thread->zone() != nullptr); | |
| Zone* zone = stack_zone.GetZone(); | |
| uintptr_t allocated_size = 0; | |
| // The loop is to make sure we overflow one segment and go on | |
| // to the next segment. | |
| for (int i = 0; i < 1000; i++) { | |
| uword first = zone->AllocUnsafe(2 * kWordSize); | |
| uword second = zone->AllocUnsafe(3 * kWordSize); | |
| EXPECT(first != second); | |
| allocated_size = ((2 + 3) * kWordSize); | |
| } | |
| EXPECT_LE(allocated_size, zone->SizeInBytes()); | |
| // Test for allocation of large segments. | |
| const uword kLargeSize = 1 * MB; | |
| const uword kSegmentSize = 64 * KB; | |
| ASSERT(kLargeSize > kSegmentSize); | |
| for (int i = 0; i < 10; i++) { | |
| EXPECT(zone->AllocUnsafe(kLargeSize) != 0); | |
| allocated_size += kLargeSize; | |
| } | |
| EXPECT_LE(allocated_size, zone->SizeInBytes()); | |
| // Test corner cases of kSegmentSize. | |
| uint8_t* buffer = nullptr; | |
| buffer = | |
| reinterpret_cast<uint8_t*>(zone->AllocUnsafe(kSegmentSize - kWordSize)); | |
| EXPECT(buffer != nullptr); | |
| buffer[(kSegmentSize - kWordSize) - 1] = 0; | |
| allocated_size += (kSegmentSize - kWordSize); | |
| EXPECT_LE(allocated_size, zone->SizeInBytes()); | |
| buffer = reinterpret_cast<uint8_t*>( | |
| zone->AllocUnsafe(kSegmentSize - (2 * kWordSize))); | |
| EXPECT(buffer != nullptr); | |
| buffer[(kSegmentSize - (2 * kWordSize)) - 1] = 0; | |
| allocated_size += (kSegmentSize - (2 * kWordSize)); | |
| EXPECT_LE(allocated_size, zone->SizeInBytes()); | |
| buffer = | |
| reinterpret_cast<uint8_t*>(zone->AllocUnsafe(kSegmentSize + kWordSize)); | |
| EXPECT(buffer != nullptr); | |
| buffer[(kSegmentSize + kWordSize) - 1] = 0; | |
| allocated_size += (kSegmentSize + kWordSize); | |
| EXPECT_LE(allocated_size, zone->SizeInBytes()); | |
| } | |
| EXPECT(thread->zone() == nullptr); | |
| Dart_ShutdownIsolate(); | |
| } | |
| VM_UNIT_TEST_CASE(AllocGeneric_Success) { | |
| #if defined(DEBUG) | |
| FLAG_trace_zones = true; | |
| #endif | |
| TestCase::CreateTestIsolate(); | |
| Thread* thread = Thread::Current(); | |
| EXPECT(thread->zone() == nullptr); | |
| { | |
| TransitionNativeToVM transition(thread); | |
| StackZone zone(thread); | |
| EXPECT(thread->zone() != nullptr); | |
| uintptr_t allocated_size = 0; | |
| const intptr_t kNumElements = 1000; | |
| zone.GetZone()->Alloc<uint32_t>(kNumElements); | |
| allocated_size += sizeof(uint32_t) * kNumElements; | |
| EXPECT_LE(allocated_size, zone.SizeInBytes()); | |
| } | |
| EXPECT(thread->zone() == nullptr); | |
| Dart_ShutdownIsolate(); | |
| } | |
| // This test is expected to crash. | |
| VM_UNIT_TEST_CASE_WITH_EXPECTATION(AllocGeneric_Overflow, "Crash") { | |
| #if defined(DEBUG) | |
| FLAG_trace_zones = true; | |
| #endif | |
| TestCase::CreateTestIsolate(); | |
| Thread* thread = Thread::Current(); | |
| EXPECT(thread->zone() == nullptr); | |
| { | |
| StackZone zone(thread); | |
| EXPECT(thread->zone() != nullptr); | |
| const intptr_t kNumElements = (kIntptrMax / sizeof(uint32_t)) + 1; | |
| zone.GetZone()->Alloc<uint32_t>(kNumElements); | |
| } | |
| Dart_ShutdownIsolate(); | |
| } | |
| VM_UNIT_TEST_CASE(ZoneRealloc) { | |
| TestCase::CreateTestIsolate(); | |
| Thread* thread = Thread::Current(); | |
| { | |
| TransitionNativeToVM transition(thread); | |
| StackZone stack_zone(thread); | |
| auto zone = thread->zone(); | |
| const intptr_t kOldLen = 32; | |
| const intptr_t kNewLen = 16; | |
| const intptr_t kNewLen2 = 16; | |
| auto data_old = zone->Alloc<uint8_t>(kOldLen); | |
| auto data_new = zone->Realloc<uint8_t>(data_old, kOldLen, kNewLen); | |
| RELEASE_ASSERT(data_old == data_new); | |
| auto data_new2 = zone->Realloc<uint8_t>(data_old, kNewLen, kNewLen2); | |
| RELEASE_ASSERT(data_old == data_new2); | |
| } | |
| Dart_ShutdownIsolate(); | |
| } | |
| VM_UNIT_TEST_CASE(ZoneObject) { | |
| #if defined(DEBUG) | |
| FLAG_trace_zones = true; | |
| #endif | |
| TestCase::CreateTestIsolate(); | |
| Thread* thread = Thread::Current(); | |
| EXPECT(thread->zone() == nullptr); | |
| static int marker; | |
| class SimpleZoneObject : public ZoneObject { | |
| public: | |
| SimpleZoneObject() : slot(marker++) {} | |
| virtual ~SimpleZoneObject() {} | |
| virtual int GetSlot() { return slot; } | |
| int slot; | |
| }; | |
| // Reset the marker. | |
| marker = 0; | |
| // Create a few zone allocated objects. | |
| { | |
| TransitionNativeToVM transition(thread); | |
| StackZone zone(thread); | |
| EXPECT_EQ(0UL, zone.SizeInBytes()); | |
| SimpleZoneObject* first = new SimpleZoneObject(); | |
| EXPECT(first != nullptr); | |
| SimpleZoneObject* second = zone.GetZone()->New<SimpleZoneObject>(); | |
| EXPECT(second != nullptr); | |
| EXPECT(first != second); | |
| uintptr_t expected_size = (2 * sizeof(SimpleZoneObject)); | |
| EXPECT_LE(expected_size, zone.SizeInBytes()); | |
| // Make sure the constructors were invoked. | |
| EXPECT_EQ(0, first->slot); | |
| EXPECT_EQ(1, second->slot); | |
| // Make sure we can write to the members of the zone objects. | |
| first->slot = 42; | |
| second->slot = 87; | |
| EXPECT_EQ(42, first->slot); | |
| EXPECT_EQ(87, second->slot); | |
| } | |
| EXPECT(thread->zone() == nullptr); | |
| Dart_ShutdownIsolate(); | |
| } | |
| TEST_CASE(PrintToString) { | |
| TransitionNativeToVM transition(Thread::Current()); | |
| StackZone zone(Thread::Current()); | |
| const char* result = zone.GetZone()->PrintToString("Hello %s!", "World"); | |
| EXPECT_STREQ("Hello World!", result); | |
| } | |
| #if !defined(PRODUCT) && !defined(USING_ADDRESS_SANITIZER) && \ | |
| !defined(USING_MEMORY_SANITIZER) | |
| // RSS hooks absent in PRODUCT mode. Scudo quarantine interferes RSS | |
| // measurements under the sanitizers. Slack to allow for limited pooling | |
| // in the malloc implementation. | |
| static constexpr int64_t kRssSlack = 20 * MB; | |
| #define CHECK_RSS | |
| #endif | |
| // clang-format off | |
| static const size_t kSizes[] = { | |
| 64 * KB, | |
| 64 * KB + 2 * kWordSize, | |
| 64 * KB - 2 * kWordSize, | |
| 128 * KB, | |
| 128 * KB + 2 * kWordSize, | |
| 128 * KB - 2 * kWordSize, | |
| 256 * KB, | |
| 256 * KB + 2 * kWordSize, | |
| 256 * KB - 2 * kWordSize, | |
| 512 * KB, | |
| 512 * KB + 2 * kWordSize, | |
| 512 * KB - 2 * kWordSize, | |
| }; | |
| // clang-format on | |
| TEST_CASE(StressMallocDirectly) { | |
| #if defined(CHECK_RSS) | |
| int64_t start_rss = Service::CurrentRSS(); | |
| #endif | |
| void* allocations[ARRAY_SIZE(kSizes)]; | |
| for (size_t i = 0; i < ((3u * GB) / (512u * KB)); i++) { | |
| for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) { | |
| allocations[j] = malloc(kSizes[j]); | |
| } | |
| for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) { | |
| free(allocations[j]); | |
| } | |
| } | |
| #if defined(CHECK_RSS) | |
| int64_t stop_rss = Service::CurrentRSS(); | |
| EXPECT_LT(stop_rss, start_rss + kRssSlack); | |
| #endif | |
| } | |
| ISOLATE_UNIT_TEST_CASE(StressMallocThroughZones) { | |
| #if defined(CHECK_RSS) | |
| int64_t start_rss = Service::CurrentRSS(); | |
| #endif | |
| for (size_t i = 0; i < ((3u * GB) / (512u * KB)); i++) { | |
| StackZone stack_zone(Thread::Current()); | |
| Zone* zone = stack_zone.GetZone(); | |
| for (size_t j = 0; j < ARRAY_SIZE(kSizes); j++) { | |
| zone->Alloc<uint8_t>(kSizes[j]); | |
| } | |
| } | |
| #if defined(CHECK_RSS) | |
| int64_t stop_rss = Service::CurrentRSS(); | |
| EXPECT_LT(stop_rss, start_rss + kRssSlack); | |
| #endif | |
| } | |
| #if defined(DART_COMPRESSED_POINTERS) | |
| ISOLATE_UNIT_TEST_CASE(ZonesNotLimitedByCompressedHeap) { | |
| StackZone stack_zone(Thread::Current()); | |
| Zone* zone = stack_zone.GetZone(); | |
| size_t total = 0; | |
| while (total <= (4u * GB)) { | |
| size_t chunk_size = 512u * MB; | |
| zone->AllocUnsafe(chunk_size); | |
| total += chunk_size; | |
| } | |
| } | |
| #endif // defined(DART_COMPRESSED_POINTERS) | |
| ISOLATE_UNIT_TEST_CASE(ZoneVerificationScaling) { | |
| // This ought to complete in O(n), not O(n^2). | |
| const intptr_t n = 1000000; | |
| StackZone stack_zone(thread); | |
| Zone* zone = stack_zone.GetZone(); | |
| { | |
| HANDLESCOPE(thread); | |
| for (intptr_t i = 0; i < n; i++) { | |
| const Object& a = Object::Handle(zone); | |
| DEBUG_ASSERT(!a.IsNotTemporaryScopedHandle()); | |
| USE(a); | |
| const Object& b = Object::ZoneHandle(zone); | |
| DEBUG_ASSERT(b.IsNotTemporaryScopedHandle()); | |
| USE(b); | |
| } | |
| // Leaves lots of HandleBlocks for recycling. | |
| } | |
| for (intptr_t i = 0; i < n; i++) { | |
| HANDLESCOPE(thread); | |
| const Object& a = Object::Handle(zone); | |
| DEBUG_ASSERT(!a.IsNotTemporaryScopedHandle()); | |
| USE(a); | |
| const Object& b = Object::ZoneHandle(zone); | |
| DEBUG_ASSERT(b.IsNotTemporaryScopedHandle()); | |
| USE(b); | |
| // Should not visit those recyclable blocks over and over again. | |
| } | |
| } | |
| } // namespace dart |