diff --git a/kotlin-native/runtime/src/mm/cpp/SafePoint.cpp b/kotlin-native/runtime/src/mm/cpp/SafePoint.cpp index aefaf47c094..52202f17b0c 100644 --- a/kotlin-native/runtime/src/mm/cpp/SafePoint.cpp +++ b/kotlin-native/runtime/src/mm/cpp/SafePoint.cpp @@ -9,6 +9,7 @@ #include "GCScheduler.hpp" #include "KAssert.h" +#include "Logging.hpp" #include "ThreadData.hpp" #include "ThreadState.hpp" @@ -51,6 +52,7 @@ void incrementActiveCount() noexcept { ++activeCount; RuntimeAssert(activeCount >= 1, "Unexpected activeCount: %" PRId64, activeCount); if (activeCount == 1) { + RuntimeLogDebug({kTagMM}, "Enabling safe points"); auto prev = safePointAction.exchange(safePointActionImpl, std::memory_order_seq_cst); RuntimeAssert(prev == nullptr, "Action cannot have been set. Was %p", prev); } @@ -63,6 +65,7 @@ void decrementActiveCount() noexcept { if (activeCount == 0) { auto prev = safePointAction.exchange(nullptr, std::memory_order_seq_cst); RuntimeAssert(prev == safePointActionImpl, "Action must have been %p. Was %p", safePointActionImpl, prev); + RuntimeLogDebug({kTagMM}, "Disabled safe points"); } } @@ -93,3 +96,11 @@ ALWAYS_INLINE void mm::safePoint(mm::ThreadData& threadData) noexcept { slowPath(threadData); } } + +bool mm::test_support::safePointsAreActive() noexcept { + return safePointAction.load(std::memory_order_relaxed) != nullptr; +} + +void mm::test_support::setSafePointAction(void (*action)(mm::ThreadData&)) noexcept { + safePointAction.store(action, std::memory_order_seq_cst); +} diff --git a/kotlin-native/runtime/src/mm/cpp/SafePoint.hpp b/kotlin-native/runtime/src/mm/cpp/SafePoint.hpp index d8d883bc456..0eb760eb07a 100644 --- a/kotlin-native/runtime/src/mm/cpp/SafePoint.hpp +++ b/kotlin-native/runtime/src/mm/cpp/SafePoint.hpp @@ -35,4 +35,11 @@ private: void safePoint() noexcept; void safePoint(ThreadData& threadData) noexcept; +namespace test_support { + +bool safePointsAreActive() noexcept; +void setSafePointAction(void (*action)(mm::ThreadData&)) noexcept; + +} // namespace test_support + } // namespace kotlin::mm diff --git a/kotlin-native/runtime/src/mm/cpp/SafePointTest.cpp b/kotlin-native/runtime/src/mm/cpp/SafePointTest.cpp new file mode 100644 index 00000000000..205138c6136 --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/SafePointTest.cpp @@ -0,0 +1,147 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the LICENSE file. + */ + +#include "SafePoint.hpp" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "ScopedThread.hpp" +#include "TestSupport.hpp" +#include "std_support/Vector.hpp" + +using namespace kotlin; + +TEST(SafePointTest, SafePointActivator) { + ASSERT_FALSE(mm::test_support::safePointsAreActive()); + { + mm::SafePointActivator activator; + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + } + ASSERT_FALSE(mm::test_support::safePointsAreActive()); +} + +TEST(SafePointTest, SafePointActivatorNested) { + ASSERT_FALSE(mm::test_support::safePointsAreActive()); + { + mm::SafePointActivator activator; + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + { + mm::SafePointActivator activator; + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + } + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + } + ASSERT_FALSE(mm::test_support::safePointsAreActive()); +} + +TEST(SafePointTest, SafePointActivatorMove) { + ASSERT_FALSE(mm::test_support::safePointsAreActive()); + { + std::optional activator; + ASSERT_FALSE(mm::test_support::safePointsAreActive()); + { + mm::SafePointActivator innerActivator; + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + activator = std::move(innerActivator); + } + ASSERT_TRUE(mm::test_support::safePointsAreActive()); + } + ASSERT_FALSE(mm::test_support::safePointsAreActive()); +} + +TEST(SafePointTest, StressSafePointActivator) { + std::atomic initialized = 0; + std::atomic canStart = false; + std::atomic started = 0; + std::atomic canStop = false; + std::atomic stopped = 0; + std_support::vector threads; + for (int i = 0; i < kDefaultThreadCount; ++i) { + threads.emplace_back([&]() noexcept { + initialized.fetch_add(1, std::memory_order_relaxed); + while (!canStart.load(std::memory_order_relaxed)) { + std::this_thread::yield(); + } + { + mm::SafePointActivator activator; + started.fetch_add(1, std::memory_order_relaxed); + while (!canStop.load(std::memory_order_relaxed)) { + std::this_thread::yield(); + } + } + stopped.fetch_add(1, std::memory_order_relaxed); + }); + } + while (initialized.load(std::memory_order_relaxed) < threads.size()) { + std::this_thread::yield(); + } + EXPECT_FALSE(mm::test_support::safePointsAreActive()); + canStart.store(true, std::memory_order_relaxed); + while (true) { + auto count = started.load(std::memory_order_relaxed); + if (count > 0) { + EXPECT_TRUE(mm::test_support::safePointsAreActive()); + } + if (count == threads.size()) break; + std::this_thread::yield(); + } + canStop.store(true, std::memory_order_relaxed); + while (stopped.load(std::memory_order_relaxed) < threads.size()) { + std::this_thread::yield(); + } + EXPECT_FALSE(mm::test_support::safePointsAreActive()); +} + +class SafePointActionTest : public ::testing::Test { +public: + SafePointActionTest() noexcept { + EXPECT_THAT(instance_, nullptr); + instance_ = this; + } + + ~SafePointActionTest() { + EXPECT_THAT(instance_, this); + instance_ = nullptr; + } + + testing::MockFunction& mockSafePoint() noexcept { return mockSafePoint_; } + + static void action(mm::ThreadData& thread) noexcept { instance_->mockSafePoint_.Call(thread); } + +private: + static SafePointActionTest* instance_; + + testing::StrictMock> mockSafePoint_; +}; + +// static +SafePointActionTest* SafePointActionTest::instance_ = nullptr; + +TEST_F(SafePointActionTest, SafePoint) { + RunInNewThread([this](mm::ThreadData& thread) noexcept { + mm::test_support::setSafePointAction(&action); + + EXPECT_CALL(mockSafePoint(), Call(testing::Ref(thread))); + mm::safePoint(); + testing::Mock::VerifyAndClearExpectations(&mockSafePoint()); + + mm::test_support::setSafePointAction(nullptr); + }); +} + +TEST_F(SafePointActionTest, SafePointWithExplicitThread) { + RunInNewThread([this](mm::ThreadData& thread) noexcept { + mm::test_support::setSafePointAction(&action); + + EXPECT_CALL(mockSafePoint(), Call(testing::Ref(thread))); + mm::safePoint(thread); + testing::Mock::VerifyAndClearExpectations(&mockSafePoint()); + + mm::test_support::setSafePointAction(nullptr); + }); +}