blob: bc39acfaf96790bbd557440d19f06d79ea9bc1e2 [file]
/*
* 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 "SpecialRefRegistry.hpp"
#include "GlobalData.hpp"
#include "MemoryPrivate.hpp"
#include "ObjCBackRef.hpp"
#include "StableRef.hpp"
#include "ThreadData.hpp"
#include "ThreadState.hpp"
#include "WeakRef.hpp"
using namespace kotlin;
mm::StableRef mm::SpecialRefRegistry::ThreadQueue::createStableRef(ObjHeader* object) noexcept {
return mm::StableRef(registerNode(object, 1, true).asRaw());
}
mm::WeakRef mm::SpecialRefRegistry::ThreadQueue::createWeakRef(ObjHeader* object) noexcept {
return mm::WeakRef(registerNode(object, 0, false).asRaw());
}
mm::ObjCBackRef mm::SpecialRefRegistry::ThreadQueue::createObjCBackRef(ObjHeader* object) noexcept {
return mm::ObjCBackRef(registerNode(object, 1, false).asRaw());
}
void mm::SpecialRefRegistry::ThreadQueue::deleteNodeIfLocal(Node& node) noexcept {
// This is a very weird optimization.
// * We're saving some time during root scanning and some memory by
// deleting some short-lived nodes without ever publishing them.
// * But in order to do that we have to be in a runnable state, so
// we potentially force a native state thread to go wait for the GC.
if (node.owner_ == this) {
queue_.erase(node.position_);
}
}
// static
mm::SpecialRefRegistry& mm::SpecialRefRegistry::instance() noexcept {
return GlobalData::Instance().specialRefRegistry();
}
mm::SpecialRefRegistry::Node* mm::SpecialRefRegistry::nextRoot(Node* current) noexcept {
RuntimeAssert(current != nullptr, "current cannot be null");
RuntimeAssert(current != rootsTail(), "current cannot be tail");
Node* candidate = current->nextRoot_.load(std::memory_order_relaxed);
// Not an infinite loop, `candidate` always moves forward and since insertions can only
// happen in the head, they will always happen before `candidate`.
while (true) {
RuntimeAssert(candidate != nullptr, "candidate cannot be null");
if (candidate == rootsTail())
// Reached tail, nothing to do anymore
return candidate;
if (candidate->rc_.load(std::memory_order_relaxed) > 0) {
// Keeping acquire-release for nextRoot_.
std::atomic_thread_fence(std::memory_order_acquire);
// Perfectly good node. Stop right there.
return candidate;
}
// Bad node. Let's remove it from the roots.
// Racy if someone concurrently inserts in the middle. Or iterates.
// But we don't have that here. Inserts are only in the beginning.
// Iteration also happens only here.
auto [candidatePrev, candidateNext] = eraseFromRoots(current, candidate);
// We removed candidate. But should we have?
if (candidate->rc_.load(std::memory_order_relaxed) > 0) {
RuntimeAssert(candidate->obj_ != nullptr, "candidate cannot have a null obj_");
// Ooops. Let's put it back. Okay to put into the head.
insertIntoRootsHead(*candidate);
}
// eraseFromRoots and insertIntoRootsHead are both acquire-release fences.
// This means they play nice with each other and we don't need an extra fence
// here to ensure synchronization with 0->1 rc_ change:
// * We read rc_ after eraseFromRoots.
// * retainRef writes rc_ before insertIntoRootsHead.
// So the write to rc_ in retainRef happens before the read here.
//
// Okay, properly deleted. Our new `candidate` is the next of previous candidate,
// and our `current` then is our best guess at the previous node of the `candidate`.
current = candidatePrev;
candidate = candidateNext;
// `current` has either moved forward or stayed where it is.
// `candidate` has definitely moved forward.
// `current` is only used in `eraseFromRoots` which itself ensures that no
// infinite loop can happen.
// So, this loop is also not infinite.
}
}
std::pair<mm::SpecialRefRegistry::Node*, mm::SpecialRefRegistry::Node*> mm::SpecialRefRegistry::eraseFromRoots(
Node* prev, Node* node) noexcept {
RuntimeAssert(node != rootsHead(), "node cannot be head");
RuntimeAssert(node != rootsTail(), "node cannot be tail");
Node* next = node->nextRoot_.load(std::memory_order_acquire);
RuntimeAssert(next != nullptr, "node@%p next cannot be null", node);
do {
Node* prevExpectedNext = node;
bool removed =
prev->nextRoot_.compare_exchange_strong(prevExpectedNext, next, std::memory_order_release, std::memory_order_acquire);
if (removed) {
auto* actualNext = node->nextRoot_.exchange(nullptr, std::memory_order_acq_rel);
RuntimeAssert(actualNext == next, "node@%p next expected %p actual %p", node, next, actualNext);
return {prev, next};
}
prev = prevExpectedNext;
RuntimeAssert(prev != rootsHead(), "prev cannot be head");
RuntimeAssert(prev != rootsTail(), "prev cannot be tail");
// We moved `prev` forward, nothing can insert after `prev` anymore, this
// cannot be an infinite loop, then.
} while (true);
}
void mm::SpecialRefRegistry::insertIntoRootsHead(Node& node) noexcept {
Node* next = rootsHead()->nextRoot_.load(std::memory_order_acquire);
Node* nodeExpectedNext = nullptr;
do {
RuntimeAssert(next != nullptr, "head's next cannot be null");
if (!node.nextRoot_.compare_exchange_strong(nodeExpectedNext, next, std::memory_order_release, std::memory_order_acquire)) {
// So:
// * `node` is already in the roots list
// * some other thread is inserting it in the roots list
// * GC thread may be removing it from the roots list, but
// will recheck rc afterwards and insert it back if needed
// In either case, do not touch anything anymore here.
return;
}
// CAS was successfull, so we need to update the expected value of node.nextRoot_
nodeExpectedNext = next;
} while (!rootsHead()->nextRoot_.compare_exchange_weak(next, &node, std::memory_order_release, std::memory_order_acquire));
}
std_support::list<mm::SpecialRefRegistry::Node>::iterator mm::SpecialRefRegistry::findAliveNode(
std_support::list<Node>::iterator it) noexcept {
while (it != all_.end() && it->rc_.load(std::memory_order_relaxed) == Node::disposedMarker) {
// Synchronization with `Node::dispose()`
std::atomic_thread_fence(std::memory_order_acquire);
// Removing disposed nodes.
if (it->nextRoot_.load(std::memory_order_relaxed) != nullptr) {
// Wait, it's in the roots list. Lets wait until the next GC
// for it to get cleaned up from there.
++it;
continue;
}
// If we observe both `nextRoot_ == nullptr` and `rc_ == disposedMarker`, we
// can be sure, that no mutator can change `nextRoot_` later.
// Proof:
// For the mutator to change `nextRoot_`, it would need to perform 0->1 `rc_` transition.
// But `rc_` can only be set to `disposedMarker` during `dispose()` call,
// which can only happen after any `retainRef()` call. So, it's impossible
// to observe any changes in `nextRoot_` after we observe `disposedMarker` in `nextRoot_`.
it = all_.erase(it);
}
return it;
}