v8/test/unittests/heap/cppgc/garbage-collected-unittest.cc
Anton Bikineev 92eae6d126 cppgc: Force EBO to always work with GCed
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}
2021-11-03 22:23:59 +00:00

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