| // Copyright (c) 2013, 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/object_id_ring.h" | 
 | #include "platform/assert.h" | 
 | #include "vm/dart_api_impl.h" | 
 | #include "vm/dart_api_state.h" | 
 | #include "vm/globals.h" | 
 | #include "vm/symbols.h" | 
 | #include "vm/unit_test.h" | 
 |  | 
 | namespace dart { | 
 |  | 
 | #ifndef PRODUCT | 
 |  | 
 | class ObjectIdRingTestHelper { | 
 |  public: | 
 |   static void SetCapacityAndMaxSerial(ObjectIdRing* ring, | 
 |                                       int32_t capacity, | 
 |                                       int32_t max_serial) { | 
 |     ring->SetCapacityAndMaxSerial(capacity, max_serial); | 
 |   } | 
 |  | 
 |   static void ExpectIdIsValid(ObjectIdRing* ring, intptr_t id) { | 
 |     EXPECT(ring->IsValidId(id)); | 
 |   } | 
 |  | 
 |   static void ExpectIdIsInvalid(ObjectIdRing* ring, intptr_t id) { | 
 |     EXPECT(!ring->IsValidId(id)); | 
 |   } | 
 |  | 
 |   static void ExpectIndexId(ObjectIdRing* ring, intptr_t index, intptr_t id) { | 
 |     EXPECT_EQ(id, ring->IdOfIndex(index)); | 
 |   } | 
 |  | 
 |   static void ExpectInvalidIndex(ObjectIdRing* ring, intptr_t index) { | 
 |     EXPECT_EQ(-1, ring->IdOfIndex(index)); | 
 |   } | 
 |  | 
 |   static ObjectPtr MakeString(const char* s) { | 
 |     return Symbols::New(Thread::Current(), s); | 
 |   } | 
 |  | 
 |   static void ExpectString(ObjectPtr obj, const char* s) { | 
 |     String& str = String::Handle(); | 
 |     str ^= obj; | 
 |     EXPECT(str.Equals(s)); | 
 |   } | 
 | }; | 
 |  | 
 | // Test that serial number wrapping works. | 
 | ISOLATE_UNIT_TEST_CASE(ObjectIdRingSerialWrapTest) { | 
 |   Isolate* isolate = Isolate::Current(); | 
 |   ObjectIdRing* ring = isolate->EnsureObjectIdRing(); | 
 |   ObjectIdRingTestHelper::SetCapacityAndMaxSerial(ring, 2, 4); | 
 |   intptr_t id; | 
 |   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("0")); | 
 |   EXPECT_EQ(0, id); | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); | 
 |   ObjectIdRingTestHelper::ExpectInvalidIndex(ring, 1); | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("1")); | 
 |   EXPECT_EQ(1, id); | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); | 
 |   // Test that id 1 gives us the "1" string. | 
 |   ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "1"); | 
 |   EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); | 
 |   // We have wrapped, index 0 is being reused. | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("2")); | 
 |   EXPECT_EQ(2, id); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 0); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); | 
 |   // Index 0 has id 2. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 2); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 2); | 
 |   // Index 1 has id 1. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("3")); | 
 |   EXPECT_EQ(3, id); | 
 |   // Index 0 has id 2. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 2); | 
 |   // Index 1 has id 3. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 3); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 0); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 2); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3); | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("4")); | 
 |   EXPECT_EQ(0, id); | 
 |   // Index 0 has id 0. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); | 
 |   // Index 1 has id 3. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 3); | 
 |   ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "4"); | 
 |   EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3); | 
 |   id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("5")); | 
 |   EXPECT_EQ(1, id); | 
 |   // Index 0 has id 0. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0); | 
 |   // Index 1 has id 1. | 
 |   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1); | 
 |   ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "5"); | 
 |   EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0); | 
 |   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2); | 
 |   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3); | 
 | } | 
 |  | 
 | // Test that the ring table is updated when the scavenger moves an object. | 
 | TEST_CASE(ObjectIdRingScavengeMoveTest) { | 
 |   const char* kScriptChars = | 
 |       "main() {\n" | 
 |       "  return [1, 2, 3];\n" | 
 |       "}\n"; | 
 |   Dart_Handle lib = TestCase::LoadTestScript(kScriptChars, NULL); | 
 |   Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); | 
 |   Dart_Handle moved_handle; | 
 |   intptr_t list_length = 0; | 
 |   EXPECT_VALID(result); | 
 |   EXPECT(!Dart_IsNull(result)); | 
 |   EXPECT(Dart_IsList(result)); | 
 |   EXPECT_VALID(Dart_ListLength(result, &list_length)); | 
 |   EXPECT_EQ(3, list_length); | 
 |  | 
 |   Isolate* isolate = thread->isolate(); | 
 |   ObjectIdRing* ring = isolate->EnsureObjectIdRing(); | 
 |   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; | 
 |  | 
 |   { | 
 |     TransitionNativeToVM to_vm(thread); | 
 |     ObjectPtr raw_obj = Api::UnwrapHandle(result); | 
 |     // Located in new heap. | 
 |     EXPECT(raw_obj->IsNewObject()); | 
 |     EXPECT_NE(Object::null(), raw_obj); | 
 |     intptr_t raw_obj_id1 = ring->GetIdForObject(raw_obj); | 
 |     EXPECT_EQ(0, raw_obj_id1); | 
 |     // Get id 0 again. | 
 |     EXPECT_EQ(raw_obj_id1, | 
 |               ring->GetIdForObject(raw_obj, ObjectIdRing::kReuseId)); | 
 |     // Add to ring a second time. | 
 |     intptr_t raw_obj_id2 = ring->GetIdForObject(raw_obj); | 
 |     EXPECT_EQ(1, raw_obj_id2); | 
 |     // Get id 0 again. | 
 |     EXPECT_EQ(raw_obj_id1, | 
 |               ring->GetIdForObject(raw_obj, ObjectIdRing::kReuseId)); | 
 |     ObjectPtr raw_obj1 = ring->GetObjectForId(raw_obj_id1, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     ObjectPtr raw_obj2 = ring->GetObjectForId(raw_obj_id2, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     EXPECT_NE(Object::null(), raw_obj1); | 
 |     EXPECT_NE(Object::null(), raw_obj2); | 
 |     EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), | 
 |               UntaggedObject::ToAddr(raw_obj1)); | 
 |     EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), | 
 |               UntaggedObject::ToAddr(raw_obj2)); | 
 |     // Force a scavenge. | 
 |     GCTestHelper::CollectNewSpace(); | 
 |     ObjectPtr raw_object_moved1 = ring->GetObjectForId(raw_obj_id1, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     ObjectPtr raw_object_moved2 = ring->GetObjectForId(raw_obj_id2, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     EXPECT_NE(Object::null(), raw_object_moved1); | 
 |     EXPECT_NE(Object::null(), raw_object_moved2); | 
 |     EXPECT_EQ(UntaggedObject::ToAddr(raw_object_moved1), | 
 |               UntaggedObject::ToAddr(raw_object_moved2)); | 
 |     // Test that objects have moved. | 
 |     EXPECT_NE(UntaggedObject::ToAddr(raw_obj1), | 
 |               UntaggedObject::ToAddr(raw_object_moved1)); | 
 |     EXPECT_NE(UntaggedObject::ToAddr(raw_obj2), | 
 |               UntaggedObject::ToAddr(raw_object_moved2)); | 
 |     // Test that we still point at the same list. | 
 |     moved_handle = Api::NewHandle(thread, raw_object_moved1); | 
 |     // Test id reuse. | 
 |     EXPECT_EQ(raw_obj_id1, | 
 |               ring->GetIdForObject(raw_object_moved1, ObjectIdRing::kReuseId)); | 
 |   } | 
 |   EXPECT_VALID(moved_handle); | 
 |   EXPECT(!Dart_IsNull(moved_handle)); | 
 |   EXPECT(Dart_IsList(moved_handle)); | 
 |   EXPECT_VALID(Dart_ListLength(moved_handle, &list_length)); | 
 |   EXPECT_EQ(3, list_length); | 
 | } | 
 |  | 
 | // Test that the ring table is updated with nulls when the old GC collects. | 
 | ISOLATE_UNIT_TEST_CASE(ObjectIdRingOldGCTest) { | 
 |   Isolate* isolate = thread->isolate(); | 
 |   ObjectIdRing* ring = isolate->EnsureObjectIdRing(); | 
 |  | 
 |   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; | 
 |   intptr_t raw_obj_id1 = -1; | 
 |   intptr_t raw_obj_id2 = -1; | 
 |   { | 
 |     HandleScope handle_scope(thread); | 
 |     const String& str = String::Handle(String::New("old", Heap::kOld)); | 
 |     EXPECT(!str.IsNull()); | 
 |     EXPECT_EQ(3, str.Length()); | 
 |  | 
 |     ObjectPtr raw_obj = Object::RawCast(str.ptr()); | 
 |     // Verify that it is located in old heap. | 
 |     EXPECT(raw_obj->IsOldObject()); | 
 |     EXPECT_NE(Object::null(), raw_obj); | 
 |     raw_obj_id1 = ring->GetIdForObject(raw_obj); | 
 |     EXPECT_EQ(0, raw_obj_id1); | 
 |     raw_obj_id2 = ring->GetIdForObject(raw_obj); | 
 |     EXPECT_EQ(1, raw_obj_id2); | 
 |     ObjectPtr raw_obj1 = ring->GetObjectForId(raw_obj_id1, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     ObjectPtr raw_obj2 = ring->GetObjectForId(raw_obj_id2, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     EXPECT_NE(Object::null(), raw_obj1); | 
 |     EXPECT_NE(Object::null(), raw_obj2); | 
 |     EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), | 
 |               UntaggedObject::ToAddr(raw_obj1)); | 
 |     EXPECT_EQ(UntaggedObject::ToAddr(raw_obj), | 
 |               UntaggedObject::ToAddr(raw_obj2)); | 
 |     // Exit scope. Freeing String handle. | 
 |   } | 
 |   // Force a GC. No reference exist to the old string anymore. It should be | 
 |   // collected and the object id ring will now return the null object for | 
 |   // those ids. | 
 |   GCTestHelper::CollectOldSpace(); | 
 |   ObjectPtr raw_object_moved1 = ring->GetObjectForId(raw_obj_id1, &kind); | 
 |   EXPECT_EQ(ObjectIdRing::kCollected, kind); | 
 |   EXPECT_EQ(Object::null(), raw_object_moved1); | 
 |   ObjectPtr raw_object_moved2 = ring->GetObjectForId(raw_obj_id2, &kind); | 
 |   EXPECT_EQ(ObjectIdRing::kCollected, kind); | 
 |   EXPECT_EQ(Object::null(), raw_object_moved2); | 
 | } | 
 |  | 
 | // Test that the ring table correctly reports an entry as expired when it is | 
 | // overridden by new entries. | 
 | ISOLATE_UNIT_TEST_CASE(ObjectIdRingExpiredEntryTest) { | 
 |   Isolate* isolate = Isolate::Current(); | 
 |   ObjectIdRing* ring = isolate->EnsureObjectIdRing(); | 
 |  | 
 |   // Insert an object and check we can look it up. | 
 |   String& obj = String::Handle(String::New("I will expire")); | 
 |   intptr_t obj_id = ring->GetIdForObject(obj.ptr()); | 
 |   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; | 
 |   ObjectPtr obj_lookup = ring->GetObjectForId(obj_id, &kind); | 
 |   EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |   EXPECT_EQ(obj.ptr(), obj_lookup); | 
 |  | 
 |   // Insert as many new objects as the ring size to bump out our first entry. | 
 |   Object& new_obj = Object::Handle(); | 
 |   for (intptr_t i = 0; i < ObjectIdRing::kDefaultCapacity; i++) { | 
 |     new_obj = String::New("Bump"); | 
 |     intptr_t new_obj_id = ring->GetIdForObject(new_obj.ptr()); | 
 |     ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid; | 
 |     ObjectPtr new_obj_lookup = ring->GetObjectForId(new_obj_id, &kind); | 
 |     EXPECT_EQ(ObjectIdRing::kValid, kind); | 
 |     EXPECT_EQ(new_obj.ptr(), new_obj_lookup); | 
 |   } | 
 |  | 
 |   // Check our first entry reports it has expired. | 
 |   obj_lookup = ring->GetObjectForId(obj_id, &kind); | 
 |   EXPECT_EQ(ObjectIdRing::kExpired, kind); | 
 |   EXPECT_NE(obj.ptr(), obj_lookup); | 
 |   EXPECT_EQ(Object::null(), obj_lookup); | 
 | } | 
 |  | 
 | #endif  // !PRODUCT | 
 |  | 
 | }  // namespace dart |