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}
diff --git a/BUILD.gn b/BUILD.gn
index ff0517f..1ed2de1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -3920,6 +3920,20 @@
]
}
+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("cppgc") {
+ deps = [ ":cppgc_base" ]
+
+ configs = [ ":internal_config" ]
+}
+
###############################################################################
# Executables
#
@@ -4257,6 +4277,15 @@
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 @@
public_configs = [ ":external_config" ]
}
+
+ group("cppgc_for_testing") {
+ testonly = true
+
+ public_deps = [ ":cppgc_base" ]
+
+ public_configs = [ ":external_config" ]
+ }
}
v8_executable("d8") {
diff --git a/include/cppgc/README.md b/include/cppgc/README.md
new file mode 100644
index 0000000..3a2db6d
--- /dev/null
+++ b/include/cppgc/README.md
@@ -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*.
\ No newline at end of file
diff --git a/include/cppgc/finalizer-trait.h b/include/cppgc/finalizer-trait.h
new file mode 100644
index 0000000..474848b
--- /dev/null
+++ b/include/cppgc/finalizer-trait.h
@@ -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_
diff --git a/src/heap/cppgc/cppgc.cc b/src/heap/cppgc/cppgc.cc
new file mode 100644
index 0000000..dcec740
--- /dev/null
+++ b/src/heap/cppgc/cppgc.cc
@@ -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
diff --git a/test/unittests/BUILD.gn b/test/unittests/BUILD.gn
index 16a4740..7e43244 100644
--- a/test/unittests/BUILD.gn
+++ b/test/unittests/BUILD.gn
@@ -20,6 +20,43 @@
}
}
+# 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 @@
#}],
deps = [
+ ":cppgc_unittests_sources",
":unittests_sources",
"../..:v8_for_testing",
"../..:v8_libbase",
diff --git a/test/unittests/heap/cppgc/finalizer-trait_unittest.cc b/test/unittests/heap/cppgc/finalizer-trait_unittest.cc
new file mode 100644
index 0000000..91a255e
--- /dev/null
+++ b/test/unittests/heap/cppgc/finalizer-trait_unittest.cc
@@ -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
diff --git a/test/unittests/heap/cppgc/run-all-unittests.cc b/test/unittests/heap/cppgc/run-all-unittests.cc
new file mode 100644
index 0000000..cdc862e
--- /dev/null
+++ b/test/unittests/heap/cppgc/run-all-unittests.cc
@@ -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();
+}