From 6fb5e43f6253778de81d3596d3d0e731cea430d9 Mon Sep 17 00:00:00 2001 From: Ilya Matveev Date: Mon, 7 Dec 2020 19:15:19 +0700 Subject: [PATCH] [runtime] Introduce thread states --- .../runtime/src/legacymm/cpp/Memory.cpp | 8 ++ .../runtime/src/main/cpp/CompilerExport.cpp | 2 + kotlin-native/runtime/src/main/cpp/Memory.h | 5 ++ .../runtime/src/mm/cpp/ThreadData.hpp | 12 ++- .../runtime/src/mm/cpp/ThreadState.cpp | 67 +++++++++++++++++ .../runtime/src/mm/cpp/ThreadState.hpp | 36 +++++++++ .../runtime/src/mm/cpp/ThreadStateTest.cpp | 75 +++++++++++++++++++ .../src/test_support/cpp/TestLauncher.cpp | 3 + 8 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 kotlin-native/runtime/src/mm/cpp/ThreadState.cpp create mode 100644 kotlin-native/runtime/src/mm/cpp/ThreadState.hpp create mode 100644 kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp diff --git a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp index 54e7f287e7e..d826cd0218c 100644 --- a/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp +++ b/kotlin-native/runtime/src/legacymm/cpp/Memory.cpp @@ -3670,4 +3670,12 @@ void CheckGlobalsAccessible() { ThrowIncorrectDereferenceException(); } +ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative() { + // no-op, used by the new MM only. +} + +ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable() { + // no-op, used by the new MM only. +} + } // extern "C" diff --git a/kotlin-native/runtime/src/main/cpp/CompilerExport.cpp b/kotlin-native/runtime/src/main/cpp/CompilerExport.cpp index 8f9ce315cc5..10a5ba0b53b 100644 --- a/kotlin-native/runtime/src/main/cpp/CompilerExport.cpp +++ b/kotlin-native/runtime/src/main/cpp/CompilerExport.cpp @@ -42,4 +42,6 @@ void EnsureDeclarationsEmitted() { ensureUsed(FreezeSubgraph); ensureUsed(FreezeSubgraph); ensureUsed(CheckGlobalsAccessible); + ensureUsed(Kotlin_mm_switchThreadStateNative); + ensureUsed(Kotlin_mm_switchThreadStateRunnable); } diff --git a/kotlin-native/runtime/src/main/cpp/Memory.h b/kotlin-native/runtime/src/main/cpp/Memory.h index 94b7a94b85d..5f0b0e1eb85 100644 --- a/kotlin-native/runtime/src/main/cpp/Memory.h +++ b/kotlin-native/runtime/src/main/cpp/Memory.h @@ -268,6 +268,11 @@ void AdoptReferenceFromSharedVariable(ObjHeader* object); void CheckGlobalsAccessible(); +// Sets state of the current thread to NATIVE (used by the new MM). +ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative(); +// Sets state of the current thread to RUNNABLE (used by the new MM). +ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable(); + #ifdef __cplusplus } #endif diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp index e7a788e5099..b5f18dc1853 100644 --- a/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp +++ b/kotlin-native/runtime/src/mm/cpp/ThreadData.hpp @@ -6,12 +6,14 @@ #ifndef RUNTIME_MM_THREAD_DATA_H #define RUNTIME_MM_THREAD_DATA_H +#include #include #include "GlobalsRegistry.hpp" #include "StableRefRegistry.hpp" #include "ThreadLocalStorage.hpp" #include "Utils.hpp" +#include "ThreadState.hpp" namespace kotlin { namespace mm { @@ -21,7 +23,10 @@ namespace mm { class ThreadData final : private Pinned { public: ThreadData(pthread_t threadId) noexcept : - threadId_(threadId), globalsThreadQueue_(GlobalsRegistry::Instance()), stableRefThreadQueue_(StableRefRegistry::Instance()) {} + threadId_(threadId), + globalsThreadQueue_(GlobalsRegistry::Instance()), + stableRefThreadQueue_(StableRefRegistry::Instance()), + state_(ThreadState::kRunnable) {} ~ThreadData() = default; @@ -33,11 +38,16 @@ public: StableRefRegistry::ThreadQueue& stableRefThreadQueue() noexcept { return stableRefThreadQueue_; } + ThreadState state() noexcept { return state_; } + + ThreadState setState(ThreadState state) noexcept { return state_.exchange(state); } + private: const pthread_t threadId_; GlobalsRegistry::ThreadQueue globalsThreadQueue_; ThreadLocalStorage tls_; StableRefRegistry::ThreadQueue stableRefThreadQueue_; + std::atomic state_; }; } // namespace mm diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadState.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadState.cpp new file mode 100644 index 00000000000..57b7c2410dc --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/ThreadState.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2020 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 "ThreadData.hpp" +#include "ThreadState.hpp" + +using namespace kotlin; + +namespace { + +ALWAYS_INLINE bool isStateSwitchAllowed(mm::ThreadState oldState, mm::ThreadState newState) noexcept { + return oldState != newState; +} + +const char* stateToString(mm::ThreadState state) noexcept { + switch (state) { + case mm::ThreadState::kRunnable: + return "RUNNABLE"; + case mm::ThreadState::kNative: + return "NATIVE"; + } +} + +std::string unexpectedStateMessage(mm::ThreadState expected, mm::ThreadState actual) noexcept { + return std::string("Unexpected thread state. Expected: ") + stateToString(expected) + + ". Actual: " + stateToString(actual); +} + +std::string illegalStateSwitchMessage(mm::ThreadState oldState, mm::ThreadState newState) noexcept { + return std::string("Illegal thread state switch. Old state: ") + stateToString(oldState) + + ". New state: " + stateToString(newState); +} + +} // namespace + +// Switches the state of the current thread to `newState` and returns the previous state. +ALWAYS_INLINE mm::ThreadState mm::SwitchThreadState(ThreadData* threadData, ThreadState newState) noexcept { + auto oldState = threadData->setState(newState); + // TODO(perf): Mesaure the impact of this assert in debug and opt modes. + RuntimeAssert(isStateSwitchAllowed(oldState, newState), + illegalStateSwitchMessage(oldState, newState).c_str()); + return oldState; +} + +ALWAYS_INLINE void mm::AssertThreadState(ThreadData* threadData, ThreadState expected) noexcept { + auto actual = threadData->state(); + RuntimeAssert(actual == expected, unexpectedStateMessage(expected, actual).c_str()); +} + +mm::ThreadStateGuard::ThreadStateGuard(ThreadData* threadData, ThreadState state) noexcept : threadData_(threadData) { + oldState_ = SwitchThreadState(threadData, state); +} + +mm::ThreadStateGuard::~ThreadStateGuard() noexcept { + SwitchThreadState(threadData_, oldState_); +} + +extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateNative() { + mm::SwitchThreadState(mm::ThreadRegistry::Instance().CurrentThreadData(), mm::ThreadState::kNative); +} + +extern "C" ALWAYS_INLINE RUNTIME_NOTHROW void Kotlin_mm_switchThreadStateRunnable() { + mm::SwitchThreadState(mm::ThreadRegistry::Instance().CurrentThreadData(), mm::ThreadState::kRunnable); +} + diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadState.hpp b/kotlin-native/runtime/src/mm/cpp/ThreadState.hpp new file mode 100644 index 00000000000..65442a6db48 --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/ThreadState.hpp @@ -0,0 +1,36 @@ +/* + * Copyright 2010-2020 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. + */ + +#ifndef RUNTIME_MM_THREAD_STATE_H +#define RUNTIME_MM_THREAD_STATE_H + +#include +#include + +namespace kotlin { +namespace mm { + +enum class ThreadState { + kRunnable, kNative +}; + +// Switches the state of the current thread to `newState` and returns the previous thread state. +ALWAYS_INLINE ThreadState SwitchThreadState(ThreadData* threadData, ThreadState newState) noexcept; + +ALWAYS_INLINE void AssertThreadState(ThreadData* threadData, ThreadState expected) noexcept; + +class ThreadStateGuard final : private Pinned { +public: + explicit ThreadStateGuard(ThreadData* threadData, ThreadState state) noexcept; + ~ThreadStateGuard() noexcept; +private: + ThreadData* threadData_; + ThreadState oldState_; +}; + +} // namespace mm +} // namespace kotlin + +#endif // RUNTIME_MM_THREAD_STATE_H \ No newline at end of file diff --git a/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp b/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp new file mode 100644 index 00000000000..75f0ef5ed69 --- /dev/null +++ b/kotlin-native/runtime/src/mm/cpp/ThreadStateTest.cpp @@ -0,0 +1,75 @@ +/* + * Copyright 2010-2020 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 + +#include "gtest/gtest.h" + +#include "ThreadData.hpp" +#include "ThreadRegistry.hpp" +#include "ThreadState.hpp" + +using namespace kotlin; + +TEST(ThreadStateTest, StateSwitch) { + std::thread t([]() { + mm::ThreadRegistry::Instance().RegisterCurrentThread(); + + auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData(); + auto initialState = threadData->state(); + EXPECT_EQ(mm::ThreadState::kRunnable, initialState); + + mm::ThreadState oldState = mm::SwitchThreadState(threadData, mm::ThreadState::kNative); + EXPECT_EQ(initialState, oldState); + EXPECT_EQ(mm::ThreadState::kNative, threadData->state()); + + // Check functions exported for the compiler too. + Kotlin_mm_switchThreadStateRunnable(); + EXPECT_EQ(mm::ThreadState::kRunnable, threadData->state()); + + Kotlin_mm_switchThreadStateNative(); + EXPECT_EQ(mm::ThreadState::kNative, threadData->state()); + }); + t.join(); +} + +TEST(ThreadStateTest, StateGuard) { + std::thread t([]() { + mm::ThreadRegistry::Instance().RegisterCurrentThread(); + auto* threadData = mm::ThreadRegistry::Instance().CurrentThreadData(); + auto initialState = threadData->state(); + EXPECT_EQ(mm::ThreadState::kRunnable, initialState); + { + mm::ThreadStateGuard guard(threadData, mm::ThreadState::kNative); + EXPECT_EQ(mm::ThreadState::kNative, threadData->state()); + } + EXPECT_EQ(initialState, threadData->state()); + }); + t.join(); +} + +TEST(ThreadStateDeathTest, StateAsserts) { + std::thread t([]() { + auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get(); + EXPECT_DEATH(mm::AssertThreadState(threadData, mm::ThreadState::kNative), + "runtime assert: Unexpected thread state. Expected: NATIVE. Actual: RUNNABLE"); + }); + t.join(); +} + +TEST(ThreadStateDeathTest, IncorrectStateSwitch) { + std::thread t([]() { + auto* threadData = mm::ThreadRegistry::Instance().RegisterCurrentThread()->Get(); + EXPECT_DEATH(mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kRunnable), + "runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE"); + EXPECT_DEATH(Kotlin_mm_switchThreadStateRunnable(), + "runtime assert: Illegal thread state switch. Old state: RUNNABLE. New state: RUNNABLE"); + + mm::SwitchThreadState(threadData, kotlin::mm::ThreadState::kNative); + EXPECT_DEATH(Kotlin_mm_switchThreadStateNative(), + "runtime assert: Illegal thread state switch. Old state: NATIVE. New state: NATIVE"); + }); + t.join(); +} diff --git a/kotlin-native/runtime/src/test_support/cpp/TestLauncher.cpp b/kotlin-native/runtime/src/test_support/cpp/TestLauncher.cpp index fa2c38e9536..0981e3e6e71 100644 --- a/kotlin-native/runtime/src/test_support/cpp/TestLauncher.cpp +++ b/kotlin-native/runtime/src/test_support/cpp/TestLauncher.cpp @@ -8,5 +8,8 @@ int main(int argc, char** argv) { testing::InitGoogleMock(&argc, argv); + // Use the `threadsafe` style to mitigate possible issues with multithreaded death tests. + // See more about death test styles: https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#how-it-works + testing::FLAGS_gtest_death_test_style="threadsafe"; return RUN_ALL_TESTS(); }