6121c396ee
TEST=pkg/vm_service/test/id_zones_test.dart, CI Issue: https://github.com/dart-lang/sdk/issues/55869 CoreLibraryReviewExempt: This CL does not include any core library API changes, only VM Service implementation changes in sdk/lib/vmservice/running_isolates.dart. Change-Id: Id63180fd08a88865064435b056fa3269835cd476 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/381400 Reviewed-by: Ben Konyi <bkonyi@google.com> Commit-Queue: Derek Xu <derekx@google.com>
314 lines
12 KiB
C++
314 lines
12 KiB
C++
// 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/service.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
namespace dart {
|
|
|
|
#ifndef PRODUCT
|
|
|
|
class ObjectIdRingTestHelper {
|
|
public:
|
|
static void SetMaxSerial(ObjectIdRing& ring, int32_t max_serial) {
|
|
ring.max_serial_ = 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) {
|
|
ObjectIdRing ring(/*capacity=*/2);
|
|
ObjectIdRingTestHelper::SetMaxSerial(ring, 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);
|
|
}
|
|
|
|
class ServiceIdZonePolicyOverrideScope : public ValueObject {
|
|
public:
|
|
explicit ServiceIdZonePolicyOverrideScope(ServiceIdZone& id_zone,
|
|
ObjectIdRing::IdPolicy new_policy)
|
|
: id_zone_(id_zone), original_policy_(id_zone.policy()) {
|
|
id_zone_.policy_ = new_policy;
|
|
}
|
|
|
|
~ServiceIdZonePolicyOverrideScope() { id_zone_.policy_ = original_policy_; }
|
|
|
|
private:
|
|
ServiceIdZone& id_zone_;
|
|
ObjectIdRing::IdPolicy original_policy_;
|
|
};
|
|
|
|
// 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, nullptr);
|
|
Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, nullptr);
|
|
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);
|
|
|
|
ServiceIdZone& default_id_zone =
|
|
thread->isolate()->EnsureDefaultServiceIdZone();
|
|
ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
|
|
|
|
{
|
|
TransitionNativeToVM to_vm(thread);
|
|
const 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 = default_id_zone.GetIdForObject(raw_obj);
|
|
EXPECT_EQ(0, raw_obj_id1);
|
|
{
|
|
ServiceIdZonePolicyOverrideScope override(
|
|
default_id_zone, ObjectIdRing::IdPolicy::kReuseId);
|
|
// Get id 0 again.
|
|
EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_obj));
|
|
}
|
|
|
|
// Add to ring a second time.
|
|
intptr_t raw_obj_id2 = default_id_zone.GetIdForObject(raw_obj);
|
|
EXPECT_EQ(1, raw_obj_id2);
|
|
{
|
|
ServiceIdZonePolicyOverrideScope override(
|
|
default_id_zone, ObjectIdRing::IdPolicy::kReuseId);
|
|
// Get id 0 again.
|
|
EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_obj));
|
|
}
|
|
|
|
const ObjectPtr raw_obj1 =
|
|
default_id_zone.GetObjectForId(raw_obj_id1, &kind);
|
|
EXPECT_EQ(ObjectIdRing::kValid, kind);
|
|
const ObjectPtr raw_obj2 =
|
|
default_id_zone.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();
|
|
const ObjectPtr raw_object_moved1 =
|
|
default_id_zone.GetObjectForId(raw_obj_id1, &kind);
|
|
EXPECT_EQ(ObjectIdRing::kValid, kind);
|
|
const ObjectPtr raw_object_moved2 =
|
|
default_id_zone.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.
|
|
|
|
{
|
|
ServiceIdZonePolicyOverrideScope override(
|
|
default_id_zone, ObjectIdRing::IdPolicy::kReuseId);
|
|
EXPECT_EQ(raw_obj_id1, default_id_zone.GetIdForObject(raw_object_moved1));
|
|
}
|
|
}
|
|
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 when major GC runs.
|
|
ISOLATE_UNIT_TEST_CASE(ObjectIdRingOldGCTest) {
|
|
ServiceIdZone& default_id_zone =
|
|
thread->isolate()->EnsureDefaultServiceIdZone();
|
|
|
|
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());
|
|
|
|
const 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 = default_id_zone.GetIdForObject(raw_obj);
|
|
EXPECT_EQ(0, raw_obj_id1);
|
|
raw_obj_id2 = default_id_zone.GetIdForObject(raw_obj);
|
|
EXPECT_EQ(1, raw_obj_id2);
|
|
|
|
const ObjectPtr raw_obj1 =
|
|
default_id_zone.GetObjectForId(raw_obj_id1, &kind);
|
|
EXPECT_EQ(ObjectIdRing::kValid, kind);
|
|
const ObjectPtr raw_obj2 =
|
|
default_id_zone.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 other reference to the old string exists, but the service id
|
|
// should keep it alive.
|
|
GCTestHelper::CollectOldSpace();
|
|
const ObjectPtr raw_object_moved1 =
|
|
default_id_zone.GetObjectForId(raw_obj_id1, &kind);
|
|
EXPECT_EQ(ObjectIdRing::kValid, kind);
|
|
EXPECT(raw_object_moved1->IsOneByteString());
|
|
const ObjectPtr raw_object_moved2 =
|
|
default_id_zone.GetObjectForId(raw_obj_id2, &kind);
|
|
EXPECT_EQ(ObjectIdRing::kValid, kind);
|
|
EXPECT(raw_object_moved2->IsOneByteString());
|
|
EXPECT_EQ(raw_object_moved1, 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) {
|
|
ObjectIdRing ring(RingServiceIdZone::kFallbackCapacityForCreateIdZone);
|
|
|
|
// 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 < RingServiceIdZone::kFallbackCapacityForCreateIdZone;
|
|
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
|