92eae6d126
Currently, in the following struct struct LayoutObject : GarbageCollected<>, MixinA, MixinB {}; the subobject that corresponds to the first base GarbageCollected<> always takes up some space (one word). The empty-base-optimization doesn't happen because the second base (MixinA) has the same subobject as the first base (GarbageCollected), which is the most parent class GarbageCollectedBase. The compiler can't "merge" them because it must guarantee that distinct objects of the same type have distinct addresses. The attribute [[no_unique_address]] doesn't work for base classes, unfortunately (but is a good idea for a Standard proposal). As a solution, the CL simply removes GarbageCollectedBase. Bug: chromium:1260797 Change-Id: I415b10a5fbcebce3d6ee97b8870ea9ae90f383a8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3259654 Commit-Queue: Anton Bikineev <bikineev@chromium.org> Reviewed-by: Michael Lippautz <mlippautz@chromium.org> Cr-Commit-Position: refs/heads/main@{#77693}
265 lines
8.7 KiB
C++
265 lines
8.7 KiB
C++
// Copyright 2020 the V8 project authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "include/cppgc/garbage-collected.h"
|
|
|
|
#include "include/cppgc/allocation.h"
|
|
#include "include/cppgc/type-traits.h"
|
|
#include "src/base/platform/mutex.h"
|
|
#include "src/heap/cppgc/heap-object-header.h"
|
|
#include "src/heap/cppgc/heap.h"
|
|
#include "test/unittests/heap/cppgc/tests.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace cppgc {
|
|
namespace internal {
|
|
|
|
namespace {
|
|
|
|
class GCed : public GarbageCollected<GCed> {
|
|
public:
|
|
void Trace(Visitor*) const {}
|
|
};
|
|
class NotGCed {};
|
|
class Mixin : public GarbageCollectedMixin {};
|
|
class GCedWithMixin : public GarbageCollected<GCedWithMixin>, public Mixin {};
|
|
class OtherMixin : public GarbageCollectedMixin {};
|
|
class MergedMixins : public Mixin, public OtherMixin {
|
|
public:
|
|
void Trace(cppgc::Visitor* visitor) const override {
|
|
Mixin::Trace(visitor);
|
|
OtherMixin::Trace(visitor);
|
|
}
|
|
};
|
|
class GCWithMergedMixins : public GCed, public MergedMixins {
|
|
public:
|
|
void Trace(cppgc::Visitor* visitor) const override {
|
|
MergedMixins::Trace(visitor);
|
|
}
|
|
};
|
|
|
|
class GarbageCollectedTestWithHeap
|
|
: public testing::TestSupportingAllocationOnly {};
|
|
|
|
} // namespace
|
|
|
|
TEST(GarbageCollectedTest, GarbageCollectedTrait) {
|
|
STATIC_ASSERT(!IsGarbageCollectedTypeV<int>);
|
|
STATIC_ASSERT(!IsGarbageCollectedTypeV<NotGCed>);
|
|
STATIC_ASSERT(IsGarbageCollectedTypeV<GCed>);
|
|
STATIC_ASSERT(!IsGarbageCollectedTypeV<Mixin>);
|
|
STATIC_ASSERT(IsGarbageCollectedTypeV<GCedWithMixin>);
|
|
STATIC_ASSERT(!IsGarbageCollectedTypeV<MergedMixins>);
|
|
STATIC_ASSERT(IsGarbageCollectedTypeV<GCWithMergedMixins>);
|
|
}
|
|
|
|
TEST(GarbageCollectedTest, GarbageCollectedMixinTrait) {
|
|
STATIC_ASSERT(!IsGarbageCollectedMixinTypeV<int>);
|
|
STATIC_ASSERT(!IsGarbageCollectedMixinTypeV<GCed>);
|
|
STATIC_ASSERT(!IsGarbageCollectedMixinTypeV<NotGCed>);
|
|
STATIC_ASSERT(IsGarbageCollectedMixinTypeV<Mixin>);
|
|
STATIC_ASSERT(!IsGarbageCollectedMixinTypeV<GCedWithMixin>);
|
|
STATIC_ASSERT(IsGarbageCollectedMixinTypeV<MergedMixins>);
|
|
STATIC_ASSERT(!IsGarbageCollectedMixinTypeV<GCWithMergedMixins>);
|
|
}
|
|
|
|
TEST(GarbageCollectedTest, GarbageCollectedOrMixinTrait) {
|
|
STATIC_ASSERT(!IsGarbageCollectedOrMixinTypeV<int>);
|
|
STATIC_ASSERT(IsGarbageCollectedOrMixinTypeV<GCed>);
|
|
STATIC_ASSERT(!IsGarbageCollectedOrMixinTypeV<NotGCed>);
|
|
STATIC_ASSERT(IsGarbageCollectedOrMixinTypeV<Mixin>);
|
|
STATIC_ASSERT(IsGarbageCollectedOrMixinTypeV<GCedWithMixin>);
|
|
STATIC_ASSERT(IsGarbageCollectedOrMixinTypeV<MergedMixins>);
|
|
STATIC_ASSERT(IsGarbageCollectedOrMixinTypeV<GCWithMergedMixins>);
|
|
}
|
|
|
|
TEST(GarbageCollectedTest, GarbageCollectedWithMixinTrait) {
|
|
STATIC_ASSERT(!IsGarbageCollectedWithMixinTypeV<int>);
|
|
STATIC_ASSERT(!IsGarbageCollectedWithMixinTypeV<GCed>);
|
|
STATIC_ASSERT(!IsGarbageCollectedWithMixinTypeV<NotGCed>);
|
|
STATIC_ASSERT(!IsGarbageCollectedWithMixinTypeV<Mixin>);
|
|
STATIC_ASSERT(IsGarbageCollectedWithMixinTypeV<GCedWithMixin>);
|
|
STATIC_ASSERT(!IsGarbageCollectedWithMixinTypeV<MergedMixins>);
|
|
STATIC_ASSERT(IsGarbageCollectedWithMixinTypeV<GCWithMergedMixins>);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class ForwardDeclaredType;
|
|
|
|
} // namespace
|
|
|
|
TEST(GarbageCollectedTest, CompleteTypeTrait) {
|
|
STATIC_ASSERT(IsCompleteV<GCed>);
|
|
STATIC_ASSERT(!IsCompleteV<ForwardDeclaredType>);
|
|
}
|
|
|
|
TEST_F(GarbageCollectedTestWithHeap, GetObjectStartReturnsCurrentAddress) {
|
|
GCed* gced = MakeGarbageCollected<GCed>(GetAllocationHandle());
|
|
GCedWithMixin* gced_with_mixin =
|
|
MakeGarbageCollected<GCedWithMixin>(GetAllocationHandle());
|
|
const void* base_object_payload = TraceTrait<Mixin>::GetTraceDescriptor(
|
|
static_cast<Mixin*>(gced_with_mixin))
|
|
.base_object_payload;
|
|
EXPECT_EQ(gced_with_mixin, base_object_payload);
|
|
EXPECT_NE(gced, base_object_payload);
|
|
}
|
|
|
|
namespace {
|
|
|
|
class GCedWithPostConstructionCallback final : public GCed {
|
|
public:
|
|
static size_t cb_callcount;
|
|
GCedWithPostConstructionCallback() { cb_callcount = 0; }
|
|
};
|
|
size_t GCedWithPostConstructionCallback::cb_callcount;
|
|
|
|
class MixinWithPostConstructionCallback {
|
|
public:
|
|
static size_t cb_callcount;
|
|
MixinWithPostConstructionCallback() { cb_callcount = 0; }
|
|
using MarkerForMixinWithPostConstructionCallback = int;
|
|
};
|
|
size_t MixinWithPostConstructionCallback::cb_callcount;
|
|
|
|
class GCedWithMixinWithPostConstructionCallback final
|
|
: public GCed,
|
|
public MixinWithPostConstructionCallback {};
|
|
|
|
} // namespace
|
|
} // namespace internal
|
|
|
|
template <>
|
|
struct PostConstructionCallbackTrait<
|
|
internal::GCedWithPostConstructionCallback> {
|
|
static void Call(internal::GCedWithPostConstructionCallback* object) {
|
|
EXPECT_FALSE(
|
|
internal::HeapObjectHeader::FromObject(object).IsInConstruction());
|
|
internal::GCedWithPostConstructionCallback::cb_callcount++;
|
|
}
|
|
};
|
|
|
|
template <typename T>
|
|
struct PostConstructionCallbackTrait<
|
|
T,
|
|
internal::void_t<typename T::MarkerForMixinWithPostConstructionCallback>> {
|
|
// The parameter could just be T*.
|
|
static void Call(
|
|
internal::GCedWithMixinWithPostConstructionCallback* object) {
|
|
EXPECT_FALSE(
|
|
internal::HeapObjectHeader::FromObject(object).IsInConstruction());
|
|
internal::GCedWithMixinWithPostConstructionCallback::cb_callcount++;
|
|
}
|
|
};
|
|
|
|
namespace internal {
|
|
|
|
TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallback) {
|
|
EXPECT_EQ(0u, GCedWithPostConstructionCallback::cb_callcount);
|
|
MakeGarbageCollected<GCedWithPostConstructionCallback>(GetAllocationHandle());
|
|
EXPECT_EQ(1u, GCedWithPostConstructionCallback::cb_callcount);
|
|
}
|
|
|
|
TEST_F(GarbageCollectedTestWithHeap, PostConstructionCallbackForMixin) {
|
|
EXPECT_EQ(0u, MixinWithPostConstructionCallback::cb_callcount);
|
|
MakeGarbageCollected<GCedWithMixinWithPostConstructionCallback>(
|
|
GetAllocationHandle());
|
|
EXPECT_EQ(1u, MixinWithPostConstructionCallback::cb_callcount);
|
|
}
|
|
|
|
namespace {
|
|
|
|
int GetDummyValue() {
|
|
static v8::base::Mutex mutex;
|
|
static int ret = 43;
|
|
// Global lock access to avoid reordering.
|
|
v8::base::MutexGuard guard(&mutex);
|
|
return ret;
|
|
}
|
|
|
|
class CheckObjectInConstructionBeforeInitializerList final
|
|
: public GarbageCollected<CheckObjectInConstructionBeforeInitializerList> {
|
|
public:
|
|
CheckObjectInConstructionBeforeInitializerList()
|
|
: in_construction_before_initializer_list_(
|
|
HeapObjectHeader::FromObject(this).IsInConstruction()),
|
|
unused_int_(GetDummyValue()) {
|
|
EXPECT_TRUE(in_construction_before_initializer_list_);
|
|
EXPECT_TRUE(HeapObjectHeader::FromObject(this).IsInConstruction());
|
|
}
|
|
|
|
void Trace(Visitor*) const {}
|
|
|
|
private:
|
|
bool in_construction_before_initializer_list_;
|
|
int unused_int_;
|
|
};
|
|
|
|
class CheckMixinInConstructionBeforeInitializerList
|
|
: public GarbageCollectedMixin {
|
|
public:
|
|
explicit CheckMixinInConstructionBeforeInitializerList(void* payload_start)
|
|
: in_construction_before_initializer_list_(
|
|
HeapObjectHeader::FromObject(payload_start).IsInConstruction()),
|
|
unused_int_(GetDummyValue()) {
|
|
EXPECT_TRUE(in_construction_before_initializer_list_);
|
|
EXPECT_TRUE(HeapObjectHeader::FromObject(payload_start).IsInConstruction());
|
|
}
|
|
|
|
void Trace(Visitor*) const override {}
|
|
|
|
private:
|
|
bool in_construction_before_initializer_list_;
|
|
int unused_int_;
|
|
};
|
|
|
|
class UnmanagedMixinForcingVTable {
|
|
protected:
|
|
virtual void ForceVTable() {}
|
|
};
|
|
|
|
class CheckGCedWithMixinInConstructionBeforeInitializerList
|
|
: public GarbageCollected<
|
|
CheckGCedWithMixinInConstructionBeforeInitializerList>,
|
|
public UnmanagedMixinForcingVTable,
|
|
public CheckMixinInConstructionBeforeInitializerList {
|
|
public:
|
|
CheckGCedWithMixinInConstructionBeforeInitializerList()
|
|
: CheckMixinInConstructionBeforeInitializerList(this) {
|
|
// Ensure that compiler indeed generated an inner object.
|
|
CHECK_NE(
|
|
this,
|
|
static_cast<void*>(
|
|
static_cast<CheckMixinInConstructionBeforeInitializerList*>(this)));
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
TEST_F(GarbageCollectedTestWithHeap, GarbageCollectedInConstructionDuringCtor) {
|
|
MakeGarbageCollected<CheckObjectInConstructionBeforeInitializerList>(
|
|
GetAllocationHandle());
|
|
}
|
|
|
|
TEST_F(GarbageCollectedTestWithHeap,
|
|
GarbageCollectedMixinInConstructionDuringCtor) {
|
|
MakeGarbageCollected<CheckGCedWithMixinInConstructionBeforeInitializerList>(
|
|
GetAllocationHandle());
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct MixinA : GarbageCollectedMixin {};
|
|
struct MixinB : GarbageCollectedMixin {};
|
|
struct GCed1 : GarbageCollected<GCed>, MixinA, MixinB {};
|
|
struct GCed2 : MixinA, MixinB {};
|
|
|
|
static_assert(
|
|
sizeof(GCed1) == sizeof(GCed2),
|
|
"Check that empty base optimization always works for GarbageCollected");
|
|
} // namespace
|
|
|
|
} // namespace internal
|
|
} // namespace cppgc
|