Make SingleThreadMarkAndSweep support multiple threads

This commit is contained in:
Alexander Shabalin
2021-05-21 13:59:22 +03:00
committed by Space
parent 10ae9c511b
commit 9ebba93dd9
15 changed files with 556 additions and 127 deletions
@@ -11,6 +11,11 @@
#include "Utils.hpp"
namespace kotlin {
namespace mm {
class ThreadData;
}
namespace gc {
// No-op GC is a GC that does not free memory.
@@ -23,7 +28,7 @@ public:
public:
using ObjectData = NoOpGC::ObjectData;
explicit ThreadData(NoOpGC& gc) noexcept {}
explicit ThreadData(NoOpGC& gc, mm::ThreadData& threadData) noexcept {}
~ThreadData() = default;
void SafePointFunctionEpilogue() noexcept {}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace gc {
using GC = kotlin::gc::SingleThreadMarkAndSweep;
inline constexpr bool kSupportsMultipleMutators = false;
inline constexpr bool kSupportsMultipleMutators = true;
} // namespace gc
} // namespace kotlin
@@ -12,6 +12,7 @@
#include "Runtime.h"
#include "ThreadData.hpp"
#include "ThreadRegistry.hpp"
#include "ThreadSuspension.hpp"
using namespace kotlin;
@@ -49,49 +50,76 @@ struct FinalizeTraits {
} // namespace
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointFunctionEpilogue() noexcept {
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
PerformFullGC();
}
++safePointsCounter_;
SafePointRegular(1);
}
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointLoopBody() noexcept {
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
PerformFullGC();
}
++safePointsCounter_;
SafePointRegular(1);
}
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointExceptionUnwind() noexcept {
if (gc_.GetThreshold() == 0 || safePointsCounter_ % gc_.GetThreshold() == 0) {
PerformFullGC();
}
++safePointsCounter_;
SafePointRegular(1);
}
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointAllocation(size_t size) noexcept {
size_t allocationOverhead =
gc_.GetAllocationThresholdBytes() == 0 ? allocatedBytes_ : allocatedBytes_ % gc_.GetAllocationThresholdBytes();
if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
if (threadData_.suspensionData().suspendIfRequested()) {
allocatedBytes_ = 0;
} else if (allocationOverhead + size >= gc_.GetAllocationThresholdBytes()) {
allocatedBytes_ = 0;
PerformFullGC();
}
allocatedBytes_ += size;
}
void gc::SingleThreadMarkAndSweep::ThreadData::PerformFullGC() noexcept {
gc_.PerformFullGC();
mm::ObjectFactory<gc::SingleThreadMarkAndSweep>::FinalizerQueue finalizerQueue;
{
// Switch state to native to simulate this thread being a GC thread.
// As a bonus, if we failed to suspend threads (which means some other thread asked for a GC),
// we will automatically suspend at the scope exit.
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
ThreadStateGuard guard(ThreadState::kNative);
finalizerQueue = gc_.PerformFullGC();
}
// Finalizers are run after threads are resumed, because finalizers may request GC themselves, which would
// try to suspend threads again. Also, we run finalizers in the runnable state, because they may be executing
// kotlin code.
// TODO: These will actually need to be run on a separate thread.
// TODO: Cannot use `threadData_` here, because there's no way to transform `mm::ThreadData` into `MemoryState*`.
AssertThreadState(ThreadState::kRunnable);
finalizerQueue.Finalize();
}
void gc::SingleThreadMarkAndSweep::ThreadData::OnOOM(size_t size) noexcept {
PerformFullGC();
}
void gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
RuntimeAssert(running_ == false, "Cannot have been called during another collection");
running_ = true;
void gc::SingleThreadMarkAndSweep::ThreadData::SafePointRegular(size_t weight) noexcept {
size_t counterOverhead = gc_.GetThreshold() == 0 ? safePointsCounter_ : safePointsCounter_ % gc_.GetThreshold();
if (threadData_.suspensionData().suspendIfRequested()) {
safePointsCounter_ = 0;
} else if (counterOverhead + weight >= gc_.GetThreshold()) {
safePointsCounter_ = 0;
PerformFullGC();
}
safePointsCounter_ += weight;
}
mm::ObjectFactory<gc::SingleThreadMarkAndSweep>::FinalizerQueue gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
bool didSuspend = mm::SuspendThreads();
if (!didSuspend) {
// Somebody else suspended the threads, and so ran a GC.
// TODO: This breaks if suspension is used by something apart from GC.
return {};
}
KStdVector<ObjHeader*> graySet;
for (auto& thread : mm::GlobalData::Instance().threadRegistry().Iter()) {
// TODO: Maybe it's more efficient to do by the suspending thread?
thread.Publish();
for (auto* object : mm::ThreadRootSet(thread)) {
if (!isNullOrMarker(object)) {
@@ -109,10 +137,7 @@ void gc::SingleThreadMarkAndSweep::PerformFullGC() noexcept {
gc::Mark<MarkTraits>(std::move(graySet));
auto finalizerQueue = gc::Sweep<SweepTraits>(mm::GlobalData::Instance().objectFactory());
running_ = false;
mm::ResumeThreads();
// TODO: These will actually need to be run on a separate thread.
// TODO: This probably should check for the existence of runtime itself, but unit tests initialize only memory.
RuntimeAssert(mm::ThreadRegistry::Instance().CurrentThreadData() != nullptr, "Finalizers need a Kotlin runtime");
finalizerQueue.Finalize();
return finalizerQueue;
}
@@ -8,13 +8,20 @@
#include <cstddef>
#include "ObjectFactory.hpp"
#include "Types.h"
#include "Utils.hpp"
namespace kotlin {
namespace mm {
class ThreadData;
}
namespace gc {
// Stop-the-world Mark-and-Sweep for a single mutator
// Stop-the-world Mark-and-Sweep that runs on mutator threads. Can support targets that do not have threads.
// TODO: Rename it away from SingleThreadMarkAndSweep, but keep it STMS.
class SingleThreadMarkAndSweep : private Pinned {
public:
class ObjectData {
@@ -36,7 +43,7 @@ public:
public:
using ObjectData = SingleThreadMarkAndSweep::ObjectData;
explicit ThreadData(SingleThreadMarkAndSweep& gc) noexcept : gc_(gc) {}
explicit ThreadData(SingleThreadMarkAndSweep& gc, mm::ThreadData& threadData) noexcept : gc_(gc), threadData_(threadData) {}
~ThreadData() = default;
void SafePointFunctionEpilogue() noexcept;
@@ -49,7 +56,10 @@ public:
void OnOOM(size_t size) noexcept;
private:
void SafePointRegular(size_t weight) noexcept;
SingleThreadMarkAndSweep& gc_;
mm::ThreadData& threadData_;
size_t allocatedBytes_ = 0;
size_t safePointsCounter_ = 0;
};
@@ -67,9 +77,7 @@ public:
bool GetAutoTune() noexcept { return autoTune_; }
private:
void PerformFullGC() noexcept;
bool running_ = false;
mm::ObjectFactory<SingleThreadMarkAndSweep>::FinalizerQueue PerformFullGC() noexcept;
size_t threshold_ = 1000;
size_t allocationThresholdBytes_ = 10000;
@@ -5,6 +5,11 @@
#include "SingleThreadMarkAndSweep.hpp"
#include <condition_variable>
#include <future>
#include <mutex>
#include <thread>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -58,6 +63,10 @@ public:
mm::AllocateObject(&threadData, typeHolder.typeInfo(), &location_);
}
GlobalObjectHolder(mm::ThreadData& threadData, ObjHeader* object) : location_(object) {
mm::GlobalsRegistry::Instance().RegisterStorageForGlobal(&threadData, &location_);
}
ObjHeader* header() { return location_; }
test_support::Object<Payload>& operator*() { return test_support::Object<Payload>::FromObjHeader(location_); }
@@ -126,6 +135,7 @@ class StackObjectHolder : private Pinned {
public:
explicit StackObjectHolder(mm::ThreadData& threadData) { mm::AllocateObject(&threadData, typeHolder.typeInfo(), holder_.slot()); }
explicit StackObjectHolder(test_support::Object<Payload>& object) : holder_(object.header()) {}
explicit StackObjectHolder(ObjHeader* object) : holder_(object) {}
ObjHeader* header() { return holder_.obj(); }
@@ -629,3 +639,404 @@ TEST_F(SingleThreadMarkAndSweepTest, SameObjectInRootSet) {
EXPECT_THAT(GetColor(object.header()), Color::kWhite);
});
}
namespace {
class Mutator : private Pinned {
public:
Mutator() : thread_(&Mutator::RunLoop, this) {}
~Mutator() {
{
std::unique_lock guard(queueMutex_);
shutdownRequested_ = true;
}
queueCV_.notify_one();
thread_.join();
RuntimeAssert(queue_.empty(), "The queue must be empty, has size=%zu", queue_.size());
RuntimeAssert(memory_ == nullptr, "Memory must have been deinitialized");
RuntimeAssert(stackRoots_.empty(), "Stack roots must be empty, has size=%zu", stackRoots_.size());
RuntimeAssert(globalRoots_.empty(), "Global roots must be empty, has size=%zu", globalRoots_.size());
}
template <typename F>
[[nodiscard]] std::future<void> Execute(F&& f) {
std::packaged_task<void()> task([this, f = std::forward<F>(f)]() { f(*memory_->memoryState()->GetThreadData(), *this); });
auto future = task.get_future();
{
std::unique_lock guard(queueMutex_);
queue_.push_back(std::move(task));
}
queueCV_.notify_one();
return future;
}
StackObjectHolder& AddStackRoot() {
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddStackRoot can only be called in the mutator thread");
auto holder = make_unique<StackObjectHolder>(*memory_->memoryState()->GetThreadData());
auto& holderRef = *holder;
stackRoots_.push_back(std::move(holder));
return holderRef;
}
StackObjectHolder& AddStackRoot(ObjHeader* object) {
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddStackRoot can only be called in the mutator thread");
auto holder = make_unique<StackObjectHolder>(object);
auto& holderRef = *holder;
stackRoots_.push_back(std::move(holder));
return holderRef;
}
GlobalObjectHolder& AddGlobalRoot() {
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddGlobalRoot can only be called in the mutator thread");
auto holder = make_unique<GlobalObjectHolder>(*memory_->memoryState()->GetThreadData());
auto& holderRef = *holder;
globalRoots_.push_back(std::move(holder));
return holderRef;
}
GlobalObjectHolder& AddGlobalRoot(ObjHeader* object) {
RuntimeAssert(std::this_thread::get_id() == thread_.get_id(), "AddGlobalRoot can only be called in the mutator thread");
auto holder = make_unique<GlobalObjectHolder>(*memory_->memoryState()->GetThreadData(), object);
auto& holderRef = *holder;
globalRoots_.push_back(std::move(holder));
return holderRef;
}
KStdVector<ObjHeader*> Alive() { return ::Alive(*memory_->memoryState()->GetThreadData()); }
private:
void RunLoop() {
memory_ = make_unique<ScopedMemoryInit>();
AssertThreadState(memory_->memoryState(), ThreadState::kRunnable);
while (true) {
std::packaged_task<void()> task;
{
std::unique_lock guard(queueMutex_);
queueCV_.wait(guard, [this]() { return !queue_.empty() || shutdownRequested_; });
if (shutdownRequested_) {
globalRoots_.clear();
stackRoots_.clear();
memory_.reset();
return;
}
task = std::move(queue_.front());
queue_.pop_front();
}
task();
}
}
KStdUniquePtr<ScopedMemoryInit> memory_;
// TODO: Consider full runtime init instead, and interact with initialized worker
std::condition_variable queueCV_;
std::mutex queueMutex_;
KStdDeque<std::packaged_task<void()>> queue_;
bool shutdownRequested_ = false;
std::thread thread_;
KStdVector<KStdUniquePtr<GlobalObjectHolder>> globalRoots_;
KStdVector<KStdUniquePtr<StackObjectHolder>> stackRoots_;
};
} // namespace
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsCollect) {
KStdVector<Mutator> mutators(kDefaultThreadCount);
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
auto& global = mutator.AddGlobalRoot();
auto& local = mutator.AddStackRoot();
auto& reachable = AllocateObject(threadData);
AllocateObject(threadData);
local->field1 = reachable.header();
globals[i] = global.header();
locals[i] = local.header();
reachables[i] = reachable.header();
};
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i]
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
.wait();
}
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (int i = 1; i < kDefaultThreadCount; ++i) {
gcFutures[i] =
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().SafePointFunctionEpilogue(); });
}
for (auto& future : gcFutures) {
future.wait();
}
KStdVector<ObjHeader*> expectedAlive;
for (auto& global : globals) {
expectedAlive.push_back(global);
}
for (auto& local : locals) {
expectedAlive.push_back(local);
}
for (auto& reachable : reachables) {
expectedAlive.push_back(reachable);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
}
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsAllCollect) {
KStdVector<Mutator> mutators(kDefaultThreadCount);
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
auto& global = mutator.AddGlobalRoot();
auto& local = mutator.AddStackRoot();
auto& reachable = AllocateObject(threadData);
AllocateObject(threadData);
local->field1 = reachable.header();
globals[i] = global.header();
locals[i] = local.header();
reachables[i] = reachable.header();
};
for (int i = 0; i < kDefaultThreadCount; ++i) {
mutators[i]
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
.wait();
}
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
// TODO: Maybe check that only one GC is performed.
for (int i = 0; i < kDefaultThreadCount; ++i) {
gcFutures[i] = mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
}
for (auto& future : gcFutures) {
future.wait();
}
KStdVector<ObjHeader*> expectedAlive;
for (auto& global : globals) {
expectedAlive.push_back(global);
}
for (auto& local : locals) {
expectedAlive.push_back(local);
}
for (auto& reachable : reachables) {
expectedAlive.push_back(reachable);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
}
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsAddToRootSetAfterCollectionRequested) {
KStdVector<Mutator> mutators(kDefaultThreadCount);
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
KStdVector<ObjHeader*> reachables(kDefaultThreadCount);
auto allocateInHeap = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
auto& global = AllocateObject(threadData);
auto& local = AllocateObject(threadData);
auto& reachable = AllocateObject(threadData);
AllocateObject(threadData);
local->field1 = reachable.header();
globals[i] = global.header();
locals[i] = local.header();
reachables[i] = reachable.header();
};
auto expandRootSet = [&globals, &locals](mm::ThreadData& threadData, Mutator& mutator, int i) {
mutator.AddGlobalRoot(globals[i]);
mutator.AddStackRoot(locals[i]);
};
mutators[0]
.Execute([expandRootSet, allocateInHeap](mm::ThreadData& threadData, Mutator& mutator) {
allocateInHeap(threadData, mutator, 0);
expandRootSet(threadData, mutator, 0);
})
.wait();
// Allocate everything in heap before scheduling the GC.
for (int i = 1; i < kDefaultThreadCount; ++i) {
mutators[i]
.Execute([allocateInHeap, i](mm::ThreadData& threadData, Mutator& mutator) { allocateInHeap(threadData, mutator, i); })
.wait();
}
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (int i = 1; i < kDefaultThreadCount; ++i) {
gcFutures[i] = mutators[i].Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) {
expandRootSet(threadData, mutator, i);
threadData.gc().SafePointFunctionEpilogue();
});
}
for (auto& future : gcFutures) {
future.wait();
}
KStdVector<ObjHeader*> expectedAlive;
for (auto& global : globals) {
expectedAlive.push_back(global);
}
for (auto& local : locals) {
expectedAlive.push_back(local);
}
for (auto& reachable : reachables) {
expectedAlive.push_back(reachable);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
}
TEST_F(SingleThreadMarkAndSweepTest, CrossThreadReference) {
KStdVector<Mutator> mutators(kDefaultThreadCount);
KStdVector<ObjHeader*> globals(kDefaultThreadCount);
KStdVector<ObjHeader*> locals(kDefaultThreadCount);
KStdVector<ObjHeader*> reachables(2 * kDefaultThreadCount);
auto expandRootSet = [&globals, &locals, &reachables](mm::ThreadData& threadData, Mutator& mutator, int i) {
auto& global = mutator.AddGlobalRoot();
auto& local = mutator.AddStackRoot();
auto& reachable1 = AllocateObject(threadData);
auto& reachable2 = AllocateObject(threadData);
globals[i] = global.header();
locals[i] = local.header();
reachables[2 * i] = reachable1.header();
reachables[2 * i + 1] = reachable2.header();
// Expected to be run consequtively, so `reachables` for `j < i` are set.
if (i != 0) {
global->field1 = reachables[2 * (i - 1)];
local->field1 = reachables[2 * (i - 1) + 1];
}
};
for (int i = 0; i < kDefaultThreadCount; ++i) {
// `expandRootSet` is expected to be run consequtively for each thread, so `.wait()` is required below.
mutators[i]
.Execute([i, expandRootSet](mm::ThreadData& threadData, Mutator& mutator) { expandRootSet(threadData, mutator, i); })
.wait();
}
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
gcFutures[0] = mutators[0].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().PerformFullGC(); });
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (int i = 1; i < kDefaultThreadCount; ++i) {
gcFutures[i] =
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) { threadData.gc().SafePointFunctionEpilogue(); });
}
for (auto& future : gcFutures) {
future.wait();
}
KStdVector<ObjHeader*> expectedAlive;
for (auto& global : globals) {
expectedAlive.push_back(global);
}
for (auto& local : locals) {
expectedAlive.push_back(local);
}
// The last two are in fact unreachable. Their absence allows us to check that GC was in fact performed.
reachables.pop_back();
reachables.pop_back();
for (auto& reachable : reachables) {
expectedAlive.push_back(reachable);
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAreArray(expectedAlive));
}
}
TEST_F(SingleThreadMarkAndSweepTest, MultipleMutatorsWeaks) {
KStdVector<Mutator> mutators(kDefaultThreadCount);
ObjHeader* globalRoot = nullptr;
WeakCounter* weak = nullptr;
mutators[0]
.Execute([&weak, &globalRoot](mm::ThreadData& threadData, Mutator& mutator) {
auto& global = mutator.AddGlobalRoot();
auto& object = AllocateObject(threadData);
auto& objectWeak = ([&threadData, &object]() -> WeakCounter& {
ObjHolder holder;
return InstallWeakCounter(threadData, object.header(), holder.slot());
})();
global->field1 = objectWeak.header();
weak = &objectWeak;
globalRoot = global.header();
})
.wait();
// Make sure all mutators are initialized.
for (int i = 1; i < kDefaultThreadCount; ++i) {
mutators[i].Execute([](mm::ThreadData& threadData, Mutator& mutator) {}).wait();
}
KStdVector<std::future<void>> gcFutures(kDefaultThreadCount);
gcFutures[0] = mutators[0].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().PerformFullGC();
EXPECT_THAT((*weak)->referred, nullptr);
});
// Spin until thread suspension is requested.
while (!mm::IsThreadSuspensionRequested()) {
}
for (int i = 1; i < kDefaultThreadCount; ++i) {
gcFutures[i] = mutators[i].Execute([weak](mm::ThreadData& threadData, Mutator& mutator) {
threadData.gc().SafePointFunctionEpilogue();
EXPECT_THAT((*weak)->referred, nullptr);
});
}
for (auto& future : gcFutures) {
future.wait();
}
for (auto& mutator : mutators) {
EXPECT_THAT(mutator.Alive(), testing::UnorderedElementsAre(globalRoot, weak->header()));
}
}
// TODO: Attaching new threads while GC is in progress.
@@ -79,7 +79,10 @@ void mm::ExtraObjectData::ClearWeakReferenceCounter() noexcept {
if (!HasWeakReferenceCounter()) return;
WeakReferenceCounterClear(weakReferenceCounter_);
mm::SetHeapRef(&weakReferenceCounter_, nullptr);
// Not using `mm::SetHeapRef here`, because this code is called during sweep phase by the GC thread,
// and so cannot affect marking.
// TODO: Asserts on the above?
weakReferenceCounter_ = nullptr;
}
mm::ExtraObjectData::~ExtraObjectData() {
@@ -515,6 +515,7 @@ public:
auto* heapObject = new (node.Data()) HeapObjHeader();
auto* object = &heapObject->object;
object->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
// TODO: Consider supporting TF_IMMUTABLE: mark instance as frozen upon creation.
return object;
}
@@ -528,6 +529,7 @@ public:
auto* array = &heapArray->array;
array->typeInfoOrMeta_ = const_cast<TypeInfo*>(typeInfo);
array->count_ = count;
// TODO: Consider supporting TF_IMMUTABLE: mark instance as frozen upon creation.
return array;
}
@@ -33,7 +33,7 @@ public:
threadId_(threadId),
globalsThreadQueue_(GlobalsRegistry::Instance()),
stableRefThreadQueue_(StableRefRegistry::Instance()),
gc_(GlobalData::Instance().gc()),
gc_(GlobalData::Instance().gc(), *this),
objectFactoryThreadQueue_(GlobalData::Instance().objectFactory(), gc_),
suspensionData_(ThreadState::kRunnable) {}
@@ -42,4 +42,4 @@ ALWAYS_INLINE void kotlin::AssertThreadState(MemoryState* thread, std::initializ
ThreadState kotlin::GetThreadState(MemoryState* thread) noexcept {
return thread->GetThreadData()->state();
}
}
@@ -33,7 +33,8 @@ kotlin::test_support::TypeInfoHolder theObjCObjectWrapperTypeInfoHolder{
kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
kotlin::test_support::TypeInfoHolder theOpaqueFunctionTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
kotlin::test_support::TypeInfoHolder theShortArrayTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KShort>()};
kotlin::test_support::TypeInfoHolder theStringTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>()};
kotlin::test_support::TypeInfoHolder theStringTypeInfoHolder{
kotlin::test_support::TypeInfoHolder::ArrayBuilder<KChar>().addFlag(TF_IMMUTABLE)};
kotlin::test_support::TypeInfoHolder theThrowableTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
kotlin::test_support::TypeInfoHolder theUnitTypeInfoHolder{kotlin::test_support::TypeInfoHolder::ObjectBuilder<EmptyPayload>()};
kotlin::test_support::TypeInfoHolder theWorkerBoundReferenceTypeInfoHolder{