cppgc: Hello world

"By my deeds I honor him. V8."

- Add basic build files for library and unittests.
- Integrate unittests also in existing V8 unittests for simplicity.

The CL also adds FinalizerTrait and unittests to allow building a
testing target that executes code.

FinalizerTrait is used to determine how managed C++ types are
finalized. The trait should not be overridable by users but needs to
be exposed on API-level to avoid including library-internal headers.

Bug: chromium:1056170
Change-Id: I64d91053410a17a7835e50547f58990625d2da28
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2108549
Reviewed-by: Hannes Payer <hpayer@chromium.org>
Reviewed-by: Omer Katz <omerkatz@chromium.org>
Reviewed-by: Michael Achenbach <machenbach@chromium.org>
Reviewed-by: Ulan Degenbaev <ulan@chromium.org>
Commit-Queue: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66834}
This commit is contained in:
Michael Lippautz 2020-03-20 15:44:46 +01:00 committed by Commit Bot
parent 92bd7818b7
commit 9d75253764
7 changed files with 327 additions and 0 deletions

View File

@ -3920,6 +3920,20 @@ v8_source_set("fuzzer_support") {
]
}
v8_source_set("cppgc_base") {
visibility = [ ":*" ]
sources = [
"include/cppgc/finalizer-trait.h",
"include/v8config.h",
"src/heap/cppgc/cppgc.cc",
]
configs = [ ":internal_config" ]
public_deps = [ ":v8_libbase" ]
}
###############################################################################
# Produce a single static library for embedders
#
@ -3966,6 +3980,12 @@ v8_static_library("wee8") {
]
}
v8_static_library("cppgc") {
deps = [ ":cppgc_base" ]
configs = [ ":internal_config" ]
}
###############################################################################
# Executables
#
@ -4257,6 +4277,15 @@ if (is_component_build) {
public_configs = [ ":external_config" ]
}
v8_component("cppgc_for_testing") {
testonly = true
public_deps = [ ":cppgc_base" ]
configs = [ ":internal_config" ]
public_configs = [ ":external_config" ]
}
} else {
group("v8") {
public_deps = [
@ -4280,6 +4309,14 @@ if (is_component_build) {
public_configs = [ ":external_config" ]
}
group("cppgc_for_testing") {
testonly = true
public_deps = [ ":cppgc_base" ]
public_configs = [ ":external_config" ]
}
}
v8_executable("d8") {

5
include/cppgc/README.md Normal file
View File

@ -0,0 +1,5 @@
# C++ Garbage Collection
This directory provides an open-source garbage collection library for C++.
The library is under construction, meaning that *all APIs in this directory are incomplete and considered unstable and should not be used*.

View File

@ -0,0 +1,96 @@
// 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.
#ifndef INCLUDE_CPPGC_FINALIZER_TRAIT_H_
#define INCLUDE_CPPGC_FINALIZER_TRAIT_H_
#include <type_traits>
namespace cppgc {
namespace internal {
using FinalizationCallback = void (*)(void*);
// Pre-C++17 custom implementation of std::void_t.
template <typename... Ts>
struct make_void {
typedef void type;
};
template <typename... Ts>
using void_t = typename make_void<Ts...>::type;
template <typename T, typename = void>
struct HasFinalizeGarbageCollectedObject : std::false_type {};
template <typename T>
struct HasFinalizeGarbageCollectedObject<
T, void_t<decltype(std::declval<T>().FinalizeGarbageCollectedObject())>>
: std::true_type {};
// The FinalizerTraitImpl specifies how to finalize objects.
template <typename T, bool isFinalized>
struct FinalizerTraitImpl;
template <typename T>
struct FinalizerTraitImpl<T, true> {
private:
// Dispatch to custom FinalizeGarbageCollectedObject().
struct Custom {
static void Call(void* obj) {
static_cast<T*>(obj)->FinalizeGarbageCollectedObject();
}
};
// Dispatch to regular destructor.
struct Destructor {
static void Call(void* obj) { static_cast<T*>(obj)->~T(); }
};
using FinalizeImpl =
std::conditional_t<HasFinalizeGarbageCollectedObject<T>::value, Custom,
Destructor>;
public:
static void Finalize(void* obj) {
static_assert(sizeof(T), "T must be fully defined");
FinalizeImpl::Call(obj);
}
};
template <typename T>
struct FinalizerTraitImpl<T, false> {
static void Finalize(void* obj) {
static_assert(sizeof(T), "T must be fully defined");
}
};
// The FinalizerTrait is used to determine if a type requires finalization and
// what finalization means.
template <typename T>
struct FinalizerTrait {
private:
// Object has a finalizer if it has
// - a custom FinalizeGarbageCollectedObject method, or
// - a destructor.
static constexpr bool kNonTrivialFinalizer =
internal::HasFinalizeGarbageCollectedObject<T>::value ||
!std::is_trivially_destructible<typename std::remove_cv<T>::type>::value;
static void Finalize(void* obj) {
internal::FinalizerTraitImpl<T, kNonTrivialFinalizer>::Finalize(obj);
}
public:
// The callback used to finalize an object of type T.
static constexpr FinalizationCallback kCallback =
kNonTrivialFinalizer ? Finalize : nullptr;
};
template <typename T>
constexpr FinalizationCallback FinalizerTrait<T>::kCallback;
} // namespace internal
} // namespace cppgc
#endif // INCLUDE_CPPGC_FINALIZER_TRAIT_H_

16
src/heap/cppgc/cppgc.cc Normal file
View File

@ -0,0 +1,16 @@
// 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/v8config.h"
namespace cppgc {
namespace internal {
V8_EXPORT void Dummy() {
// TODO(mlippautz): Placeholder to force building a library. Remove as soon as
// actual code is available.
}
} // namespace internal
} // namespace cppgc

View File

@ -20,6 +20,43 @@ if (is_fuchsia) {
}
}
# Stand-alone target for C++ GC unittests. This is used to ensure that it
# builds without V8 as well. They are also included in the regular unittests
# target for simplicity.
v8_executable("cppgc_unittests") {
testonly = true
configs = [
"../..:external_config",
"../..:internal_config_base",
]
sources = [ "heap/cppgc/run-all-unittests.cc" ]
deps = [
":cppgc_unittests_sources",
"//testing/gmock",
"//testing/gtest",
]
}
v8_source_set("cppgc_unittests_sources") {
testonly = true
sources = [ "heap/cppgc/finalizer-trait_unittest.cc" ]
configs = [
"../..:external_config",
"../..:internal_config_base",
]
deps = [
"../..:cppgc_for_testing",
"//testing/gmock",
"//testing/gtest",
]
}
v8_executable("unittests") {
testonly = true
@ -29,6 +66,7 @@ v8_executable("unittests") {
#}],
deps = [
":cppgc_unittests_sources",
":unittests_sources",
"../..:v8_for_testing",
"../..:v8_libbase",

View File

@ -0,0 +1,118 @@
// 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/finalizer-trait.h"
#include <type_traits>
#include "testing/gtest/include/gtest/gtest.h"
namespace cppgc {
namespace internal {
namespace {
// Trivially destructible types.
class TypeWithoutDestructor final {};
class TypeWithPrimitive final {
public:
int foo = 0;
};
class InvokeCounter {
public:
static size_t kCallcount;
static void Reset() { kCallcount = 0; }
static void Invoke() { kCallcount++; }
};
size_t InvokeCounter::kCallcount = 0;
// Regular C++ use cases.
class TypeWithDestructor final : public InvokeCounter {
public:
~TypeWithDestructor() { Invoke(); }
};
class TypeWithVirtualDestructorBase {
public:
virtual ~TypeWithVirtualDestructorBase() = default;
};
class TypeWithVirtualDestructorChild final
: public TypeWithVirtualDestructorBase,
public InvokeCounter {
public:
~TypeWithVirtualDestructorChild() final { Invoke(); }
};
// Manual dispatch to avoid vtables.
class TypeWithCustomFinalizationMethod final : public InvokeCounter {
public:
void FinalizeGarbageCollectedObject() { Invoke(); }
};
class TypeWithCustomFinalizationMethodAtBase {
public:
void FinalizeGarbageCollectedObject();
};
class TypeWithCustomFinalizationMethodAtBaseChild
: public TypeWithCustomFinalizationMethodAtBase,
public InvokeCounter {
public:
~TypeWithCustomFinalizationMethodAtBaseChild() { Invoke(); }
};
void TypeWithCustomFinalizationMethodAtBase::FinalizeGarbageCollectedObject() {
// The test knows that base is only inherited by a single child. In practice
// users can maintain a map of valid types in already existing storage.
static_cast<TypeWithCustomFinalizationMethodAtBaseChild*>(this)
->~TypeWithCustomFinalizationMethodAtBaseChild();
}
template <typename Type>
void ExpectFinalizerIsInvoked(Type* object) {
InvokeCounter::Reset();
EXPECT_NE(nullptr, FinalizerTrait<Type>::kCallback);
FinalizerTrait<Type>::kCallback(object);
EXPECT_EQ(1u, InvokeCounter::kCallcount);
operator delete(object);
}
} // namespace
TEST(FinalizerTrait, TypeWithoutDestructorHasNoFinalizer) {
static_assert(std::is_trivially_destructible<TypeWithoutDestructor>::value,
"trivially destructible");
EXPECT_EQ(nullptr, FinalizerTrait<TypeWithoutDestructor>::kCallback);
}
TEST(FinalizerTrait, TypeWithPrimitiveHasNoFinalizer) {
static_assert(std::is_trivially_destructible<TypeWithPrimitive>::value,
"trivially destructible");
EXPECT_EQ(nullptr, FinalizerTrait<TypeWithPrimitive>::kCallback);
}
TEST(FinalizerTrait, FinalizerForTypeWithDestructor) {
ExpectFinalizerIsInvoked(new TypeWithDestructor());
}
TEST(FinalizerTrait, FinalizerForTypeWithVirtualBaseDtor) {
TypeWithVirtualDestructorBase* base = new TypeWithVirtualDestructorChild();
ExpectFinalizerIsInvoked(base);
}
TEST(FinalizerTrait, FinalizerForCustomFinalizationMethod) {
ExpectFinalizerIsInvoked(new TypeWithCustomFinalizationMethod());
}
TEST(FinalizerTrait, FinalizerForCustomFinalizationMethodInBase) {
TypeWithCustomFinalizationMethodAtBase* base =
new TypeWithCustomFinalizationMethodAtBaseChild();
ExpectFinalizerIsInvoked(base);
}
} // namespace internal
} // namespace cppgc

View File

@ -0,0 +1,17 @@
// 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 "testing/gmock/include/gmock/gmock.h"
int main(int argc, char** argv) {
// Don't catch SEH exceptions and continue as the following tests might hang
// in an broken environment on windows.
testing::GTEST_FLAG(catch_exceptions) = false;
// Most unit-tests are multi-threaded, so enable thread-safe death-tests.
testing::FLAGS_gtest_death_test_style = "threadsafe";
testing::InitGoogleMock(&argc, argv);
return RUN_ALL_TESTS();
}