[K/N] Remove legacy MM runtime modules ^KT-59121
This commit is contained in:
committed by
Space Team
parent
f6e9e9379d
commit
288163437d
@@ -206,20 +206,6 @@ bitcode {
|
||||
onlyIf { target.supportsLibBacktrace() }
|
||||
}
|
||||
|
||||
module("strict") {
|
||||
headersDirs.from(files("src/main/cpp"))
|
||||
sourceSets {
|
||||
main {}
|
||||
}
|
||||
}
|
||||
|
||||
module("relaxed") {
|
||||
headersDirs.from(files("src/main/cpp"))
|
||||
sourceSets {
|
||||
main {}
|
||||
}
|
||||
}
|
||||
|
||||
module("profileRuntime") {
|
||||
srcRoot.set(layout.projectDirectory.dir("src/profile_runtime"))
|
||||
sourceSets {
|
||||
@@ -243,15 +229,6 @@ bitcode {
|
||||
}
|
||||
}
|
||||
|
||||
module("legacy_memory_manager") {
|
||||
srcRoot.set(layout.projectDirectory.dir("src/legacymm"))
|
||||
headersDirs.from(files("src/main/cpp"))
|
||||
sourceSets {
|
||||
main {}
|
||||
testFixtures {}
|
||||
}
|
||||
}
|
||||
|
||||
module("experimental_memory_manager") {
|
||||
srcRoot.set(layout.projectDirectory.dir("src/mm"))
|
||||
headersDirs.from(files("src/gcScheduler/common/cpp", "src/gc/common/cpp", "src/main/cpp"))
|
||||
|
||||
@@ -1,534 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef KONAN_NO_THREADS
|
||||
#define WITH_WORKERS 1
|
||||
#endif
|
||||
|
||||
#include "Atomic.h"
|
||||
#include "KAssert.h"
|
||||
#include "Memory.h"
|
||||
#include "MemoryPrivate.hpp"
|
||||
#include "Natives.h"
|
||||
#include "Porting.h"
|
||||
#include "Types.h"
|
||||
#include "std_support/Deque.hpp"
|
||||
#include "std_support/New.hpp"
|
||||
#include "std_support/UnorderedMap.hpp"
|
||||
#include "std_support/UnorderedSet.hpp"
|
||||
#include "std_support/Vector.hpp"
|
||||
|
||||
#if WITH_WORKERS
|
||||
#include <pthread.h>
|
||||
#include "PthreadUtils.h"
|
||||
#endif
|
||||
|
||||
#if WITH_WORKERS
|
||||
|
||||
// Define to 1 to print collector traces.
|
||||
#define TRACE_COLLECTOR 0
|
||||
|
||||
#if TRACE_COLLECTOR
|
||||
#define COLLECTOR_LOG(...) konan::consolePrintf(__VA_ARGS__);
|
||||
#else
|
||||
#define COLLECTOR_LOG(...)
|
||||
#endif
|
||||
|
||||
using namespace kotlin;
|
||||
|
||||
/**
|
||||
* Theory of operations:
|
||||
*
|
||||
* Kotlin/Native runtime has concurrent cyclic garbage collection for the shared mutable objects,
|
||||
* such as `AtomicReference` and `FreezableAtomicReference` instances (further known as the atomic rootset).
|
||||
* We perform such analysis by iterating over the transitive closure of the atomic rootset, and computing
|
||||
* aggregated inner reference counter for rootset elements over this transitive closure.
|
||||
* Collector runs in its own thread and is started by an explicit request or after certain time interval since last
|
||||
* collection passes, thus its operation does not affect UI responsiveness in most cases.
|
||||
* Atomic rootset is built by maintaining the set of all atomic and freezable atomic references objects.
|
||||
* Elements whose transitive closure inner reference count matches the actual reference count are ones
|
||||
* belonging to the garbage cycles and thus can be discarded.
|
||||
* We ignore elements reachable from objects having external references (i.e. inner rc != real rc).
|
||||
* If during computations of the aggregated RC there were modifications in the reference counts of
|
||||
* elements of the atomic rootset:
|
||||
* - if it is being increased, then someone already got an external reference to this element, thus we may not
|
||||
* end up matching the inner reference count anyway
|
||||
* - if it is being decreased and object become garbage, it will be collected next time
|
||||
* If transitive closure of the atomic rootset mutates, it could only happen via changing the atomics references,
|
||||
* as all elements of this closure are frozen.
|
||||
* To handle such mutations we keep collector flag, which is cleared before analysis and set on every
|
||||
* atomic reference value update. If flag's value changes - collector restarts its analysis.
|
||||
* There are not so much of complications in this algorithm due to the delayed reference counting as if there's a
|
||||
* stack reference to the shared object - it's reflected in the reference counter (see rememberNewContainer()).
|
||||
* We release objects found by the collector on a rendezvouz callback, but not on the main thread,
|
||||
* to keep UI responsive, as taking GC lock can take time, sometimes.
|
||||
*/
|
||||
namespace {
|
||||
|
||||
class Locker {
|
||||
pthread_mutex_t* lock_;
|
||||
|
||||
public:
|
||||
Locker(pthread_mutex_t* alock): lock_(alock) {
|
||||
pthread_mutex_lock(lock_);
|
||||
}
|
||||
|
||||
~Locker() {
|
||||
pthread_mutex_unlock(lock_);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename func>
|
||||
inline void traverseObjectFields(ObjHeader* obj, func process) {
|
||||
RuntimeAssert(obj != nullptr, "Must be non null");
|
||||
const TypeInfo* typeInfo = obj->type_info();
|
||||
if (typeInfo != theArrayTypeInfo) {
|
||||
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
|
||||
ObjHeader** location = reinterpret_cast<ObjHeader**>(
|
||||
reinterpret_cast<uintptr_t>(obj) + typeInfo->objOffsets_[index]);
|
||||
process(location);
|
||||
}
|
||||
} else {
|
||||
ArrayHeader* array = obj->array();
|
||||
for (uint32_t index = 0; index < array->count_; index++) {
|
||||
process(ArrayAddressOfElementAt(array, index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isAtomicReference(ObjHeader* obj) {
|
||||
return (obj->type_info()->flags_ & TF_LEAK_DETECTOR_CANDIDATE) != 0;
|
||||
}
|
||||
|
||||
#define CHECK_CALL(call, message) RuntimeCheck((call) == 0, message)
|
||||
|
||||
class CyclicCollector {
|
||||
pthread_mutex_t lock_;
|
||||
pthread_mutex_t timestampLock_;
|
||||
pthread_cond_t cond_;
|
||||
pthread_t gcThread_;
|
||||
|
||||
int currentAliveWorkers_;
|
||||
int gcRunning_;
|
||||
int mutatedAtomics_;
|
||||
int pendingRelease_;
|
||||
bool shallRunCollector_;
|
||||
bool terminateCollector_;
|
||||
int32_t currentTick_;
|
||||
int32_t lastTick_;
|
||||
int64_t lastTimestampUs_;
|
||||
void* mainWorker_;
|
||||
std_support::unordered_set<ObjHeader*> rootset_;
|
||||
std_support::unordered_set<ObjHeader*> toRelease_;
|
||||
|
||||
public:
|
||||
CyclicCollector() {
|
||||
CHECK_CALL(pthread_mutex_init(&lock_, nullptr), "Cannot init collector mutex");
|
||||
CHECK_CALL(pthread_mutex_init(×tampLock_, nullptr), "Cannot init collector timestamp mutex");
|
||||
CHECK_CALL(pthread_cond_init(&cond_, nullptr), "Cannot init collector condition");
|
||||
CHECK_CALL(pthread_create(&gcThread_, nullptr, gcWorkerRoutine, this), "Cannot start collector thread");
|
||||
}
|
||||
|
||||
void clear() {
|
||||
Locker lock(&lock_);
|
||||
rootset_.clear();
|
||||
toRelease_.clear();
|
||||
}
|
||||
|
||||
void terminate(bool enabled) {
|
||||
{
|
||||
Locker locker(&lock_);
|
||||
terminateCollector_ = true;
|
||||
if (enabled) shallRunCollector_ = true;
|
||||
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
|
||||
}
|
||||
// TODO: improve waiting for collector termination.
|
||||
while (atomicGet(&terminateCollector_)) {}
|
||||
releasePendingUnlocked(nullptr);
|
||||
}
|
||||
|
||||
~CyclicCollector() {
|
||||
pthread_cond_destroy(&cond_);
|
||||
pthread_mutex_destroy(&lock_);
|
||||
pthread_mutex_destroy(×tampLock_);
|
||||
}
|
||||
|
||||
static void* gcWorkerRoutine(void* argument) {
|
||||
CyclicCollector* thiz = reinterpret_cast<CyclicCollector*>(argument);
|
||||
thiz->gcProcessor();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void gcProcessor() {
|
||||
{
|
||||
Locker locker(&lock_);
|
||||
std_support::deque<ObjHeader*> toVisit;
|
||||
std_support::unordered_set<ObjHeader*> visited;
|
||||
std_support::unordered_map<ObjHeader*, int> sideRefCounts;
|
||||
int restartCount = 0;
|
||||
while (!terminateCollector_) {
|
||||
CHECK_CALL(pthread_cond_wait(&cond_, &lock_), "Cannot wait collector condition");
|
||||
if (!shallRunCollector_) continue;
|
||||
atomicSet(&gcRunning_, 1);
|
||||
restartCount = 0;
|
||||
restart:
|
||||
COLLECTOR_LOG("start cycle GC\n");
|
||||
if (restartCount > 10 && !terminateCollector_) {
|
||||
COLLECTOR_LOG("wait for some time to avoid GC thrashing\n");
|
||||
uint64_t nsDelta = 1000LL * 1000LL * (restartCount - 10);
|
||||
WaitOnCondVar(&cond_, &lock_, nsDelta);
|
||||
}
|
||||
atomicSet(&mutatedAtomics_, 0);
|
||||
visited.clear();
|
||||
toVisit.clear();
|
||||
sideRefCounts.clear();
|
||||
for (auto* root: rootset_) {
|
||||
// We only care about frozen values here, as only they could become part of shared cycles.
|
||||
if (!containerFor(root)->frozen()) continue;
|
||||
COLLECTOR_LOG("process root %p\n", root);
|
||||
toVisit.push_back(root);
|
||||
sideRefCounts[root] = 0;
|
||||
}
|
||||
while (toVisit.size() > 0) {
|
||||
if (atomicGet(&mutatedAtomics_) != 0) {
|
||||
COLLECTOR_LOG("restarted during rootset visit\n")
|
||||
restartCount++;
|
||||
goto restart;
|
||||
}
|
||||
auto* obj = toVisit.front();
|
||||
toVisit.pop_front();
|
||||
COLLECTOR_LOG("visit %s%p\n", isAtomicReference(obj) ? "atomic " : "", obj);
|
||||
auto* objContainer = containerFor(obj);
|
||||
if (objContainer == nullptr) continue; // Permanent object.
|
||||
RuntimeCheck(objContainer->shareable(), "Must be shareable");
|
||||
if (visited.count(obj) == 0) {
|
||||
visited.insert(obj);
|
||||
traverseObjectFields(obj, [&toVisit, obj, &sideRefCounts](ObjHeader** location) {
|
||||
ObjHeader* ref = *location;
|
||||
if (ref != nullptr) {
|
||||
COLLECTOR_LOG("object field %p in %p\n", ref, obj)
|
||||
int increment;
|
||||
// We shall not account for edges inside the same frozen container, unless it originates
|
||||
// from an atomic reference.
|
||||
if (isAtomicReference(obj) || (containerFor(obj) != containerFor(ref))) {
|
||||
COLLECTOR_LOG("counting %p -> %p\n", obj, ref)
|
||||
increment = 1;
|
||||
} else {
|
||||
COLLECTOR_LOG("not counting %p -> %p\n", obj, ref)
|
||||
increment = 0;
|
||||
}
|
||||
sideRefCounts[ref] += increment;
|
||||
toVisit.push_back(ref);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Now find all elements with external references, and mark objects reachable from them as non suitable
|
||||
// for collection by setting their side reference count to -1.
|
||||
toVisit.clear();
|
||||
for (auto it: sideRefCounts) {
|
||||
auto* obj = it.first;
|
||||
auto* objContainer = containerFor(obj);
|
||||
if (objContainer == nullptr) continue; // Permanent object.
|
||||
int refCount;
|
||||
// If object is in aggregated container - sum up RC for all elements.
|
||||
if (objContainer->objectCount() != 1) {
|
||||
RuntimeAssert(objContainer->frozen(), "Must be frozen aggregate");
|
||||
ContainerHeader** subContainer = reinterpret_cast<ContainerHeader**>(objContainer + 1);
|
||||
refCount = 0;
|
||||
for (uint32_t i = 0; i < objContainer->objectCount(); ++i) {
|
||||
auto* componentObj = reinterpret_cast<ObjHeader*>((*subContainer) + 1);
|
||||
refCount += sideRefCounts[componentObj];
|
||||
subContainer++;
|
||||
}
|
||||
} else {
|
||||
refCount = it.second;
|
||||
}
|
||||
RuntimeAssert(refCount <= objContainer->refCount(), "Must properly count inner refs");
|
||||
if (refCount != objContainer->refCount()) {
|
||||
COLLECTOR_LOG("for %p mismatched RC: %d vs %d, adding as possible root\n", obj, refCount, objContainer->refCount())
|
||||
toVisit.push_back(it.first);
|
||||
}
|
||||
}
|
||||
visited.clear();
|
||||
while (toVisit.size() > 0) {
|
||||
auto* obj = toVisit.front();
|
||||
toVisit.pop_front();
|
||||
auto* objContainer = containerFor(obj);
|
||||
if (objContainer == nullptr) continue; // Permanent object.
|
||||
RuntimeCheck(objContainer->shareable(), "Must be shareable");
|
||||
sideRefCounts[obj] = -1;
|
||||
visited.insert(obj);
|
||||
if (atomicGet(&mutatedAtomics_) != 0) {
|
||||
COLLECTOR_LOG("restarted during reachable visit\n")
|
||||
restartCount++;
|
||||
goto restart;
|
||||
}
|
||||
traverseObjectFields(obj, [&toVisit, &visited](ObjHeader** location) {
|
||||
ObjHeader* ref = *location;
|
||||
if (ref != nullptr && (visited.count(ref) == 0)) {
|
||||
toVisit.push_back(ref);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Now release all atomic roots with matching reference counters, as only their destruction is controlled.
|
||||
for (auto it: sideRefCounts) {
|
||||
auto* obj = it.first;
|
||||
// Only do that for atomic rootset elements. For them we also do not have sum up references from
|
||||
// other elements of an aggregate, as atomic references are always in single object containers.
|
||||
if (!isAtomicReference(obj)) {
|
||||
continue;
|
||||
}
|
||||
if (atomicGet(&mutatedAtomics_) != 0) {
|
||||
COLLECTOR_LOG("restarted during matching check\n")
|
||||
restartCount++;
|
||||
goto restart;
|
||||
}
|
||||
auto* objContainer = containerFor(obj);
|
||||
if (!objContainer->frozen()) continue;
|
||||
RuntimeAssert(objContainer->objectCount() == 1, "Must be single object");
|
||||
COLLECTOR_LOG("for %p inner %d actual %d\n", obj, it.second, objContainer->refCount());
|
||||
// All references are inner. We compare the number of counted
|
||||
// inner references with the number of non-stack references and per-thread ownership value
|
||||
// (see rememberNewContainer()).
|
||||
if (it.second == objContainer->refCount()) {
|
||||
COLLECTOR_LOG("adding %p to release candidates\n", it.first);
|
||||
toRelease_.insert(it.first);
|
||||
}
|
||||
}
|
||||
if (toRelease_.size() > 0)
|
||||
atomicSet(&pendingRelease_, 1);
|
||||
atomicSet(&gcRunning_, 0);
|
||||
shallRunCollector_ = false;
|
||||
COLLECTOR_LOG("end cycle GC\n");
|
||||
}
|
||||
}
|
||||
atomicSet(&terminateCollector_, false);
|
||||
}
|
||||
|
||||
void addWorker(void* worker) {
|
||||
suggestLockRelease();
|
||||
Locker lock(&lock_);
|
||||
currentAliveWorkers_++;
|
||||
if (mainWorker_ == nullptr) mainWorker_ = worker;
|
||||
}
|
||||
|
||||
void removeWorker(void* worker, bool enabled) {
|
||||
suggestLockRelease();
|
||||
Locker lock(&lock_);
|
||||
// When exiting the worker - we shall collect the cyclic garbage here.
|
||||
if (enabled) {
|
||||
shallRunCollector_ = true;
|
||||
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
|
||||
}
|
||||
currentAliveWorkers_--;
|
||||
}
|
||||
|
||||
void addRoot(ObjHeader* obj) {
|
||||
COLLECTOR_LOG("add root %p\n", obj);
|
||||
// TODO: we can only add root when collector is not processing, which looks like a limitation,
|
||||
// instead we can add elements to the side buffer or have a separate lock for that.
|
||||
suggestLockRelease();
|
||||
Locker lock(&lock_);
|
||||
rootset_.insert(obj);
|
||||
}
|
||||
|
||||
void removeRoot(ObjHeader* obj) {
|
||||
COLLECTOR_LOG("remove root %p\n", obj);
|
||||
// Note that we can only remove root when the collector is not processing.
|
||||
suggestLockRelease();
|
||||
Locker lock(&lock_);
|
||||
toRelease_.erase(obj);
|
||||
rootset_.erase(obj);
|
||||
}
|
||||
|
||||
void mutateRoot(ObjHeader* newValue) {
|
||||
// TODO: consider optimization, when clearing value (setting to null) in atomic reference shall not lead
|
||||
// to invalidation of the collector analysis state.
|
||||
atomicSet(&mutatedAtomics_, 1);
|
||||
}
|
||||
|
||||
void suggestLockRelease() {
|
||||
atomicSet(&mutatedAtomics_, 1);
|
||||
}
|
||||
|
||||
bool checkIfShallCollect() {
|
||||
auto tick = atomicAdd(¤tTick_, 1);
|
||||
auto delta = tick - atomicGet(&lastTick_);
|
||||
if (delta > 10 || delta < 0) {
|
||||
auto currentTimestampUs = konan::getTimeMicros();
|
||||
#if KONAN_NO_64BIT_ATOMIC
|
||||
if (currentTimestampUs - *(volatile int64_t*)&lastTimestampUs_ > 10000) {
|
||||
#else
|
||||
if (currentTimestampUs - atomicGet(&lastTimestampUs_) > 10000) {
|
||||
#endif // KONAN_NO_64BIT_ATOMIC
|
||||
// Do we care if this lock is not here?
|
||||
Locker locker(×tampLock_);
|
||||
lastTick_ = currentTick_;
|
||||
lastTimestampUs_ = currentTimestampUs;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void releasePendingUnlocked(void* worker) {
|
||||
// We are not doing that on the UI thread, as taking lock is slow, unless
|
||||
// it happens on deinit of the collector or if there are no other workers.
|
||||
if ((atomicGet(&pendingRelease_) != 0) && ((worker != mainWorker_) || (currentAliveWorkers_ == 1))) {
|
||||
std_support::vector<ObjHeader*> heapRefsToRelease;
|
||||
|
||||
{
|
||||
suggestLockRelease();
|
||||
Locker locker(&lock_);
|
||||
COLLECTOR_LOG("clearing %d release candidates on %p\n", toRelease_.size(), worker);
|
||||
for (auto* it: toRelease_) {
|
||||
COLLECTOR_LOG("clear references in %p\n", it)
|
||||
traverseObjectFields(it, [&heapRefsToRelease](ObjHeader** location) {
|
||||
// Avoid using ZeroHeapRef here: it can provoke garbageCollect() which would then stuck on taking [lock_]
|
||||
// (which is already taken above).
|
||||
auto* value = *location;
|
||||
if (reinterpret_cast<uintptr_t>(value) > 1) {
|
||||
*location = nullptr;
|
||||
heapRefsToRelease.push_back(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
toRelease_.clear();
|
||||
atomicSet(&pendingRelease_, 0);
|
||||
}
|
||||
|
||||
for (auto* it: heapRefsToRelease) {
|
||||
ReleaseHeapRef(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void collectorCallaback(void* worker) {
|
||||
if (atomicGet(&gcRunning_) != 0) return;
|
||||
releasePendingUnlocked(worker);
|
||||
if (checkIfShallCollect()) {
|
||||
Locker locker(&lock_);
|
||||
shallRunCollector_ = true;
|
||||
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
|
||||
}
|
||||
}
|
||||
|
||||
void scheduleGarbageCollect() {
|
||||
if (atomicGet(&gcRunning_) != 0) return;
|
||||
Locker lock(&lock_);
|
||||
shallRunCollector_ = true;
|
||||
CHECK_CALL(pthread_cond_signal(&cond_), "Cannot signal collector");
|
||||
}
|
||||
|
||||
void localGC() {
|
||||
// We just need to take GC lock here, to avoid release of object we walk on.
|
||||
// TODO: consider optimization without taking the lock and just notifying collector via an atomic.
|
||||
suggestLockRelease();
|
||||
Locker locker(&lock_);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
CyclicCollector* cyclicCollector = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // WITH_WORKERS
|
||||
|
||||
void cyclicInit() {
|
||||
#if WITH_WORKERS
|
||||
RuntimeAssert(cyclicCollector == nullptr, "Must be not yet inited");
|
||||
cyclicCollector = new (std_support::kalloc) CyclicCollector();
|
||||
#endif
|
||||
}
|
||||
|
||||
void cyclicDeinit(bool enabled) {
|
||||
#if WITH_WORKERS
|
||||
RuntimeAssert(cyclicCollector != nullptr, "Must be inited");
|
||||
auto* local = cyclicCollector;
|
||||
local->terminate(enabled);
|
||||
cyclicCollector = nullptr;
|
||||
// Workaround data race with threads non-atomically reading and then using [cyclicCollector].
|
||||
// std_support::kdelete(local);
|
||||
// Note: memory leaks here indeed, but usually it happens once per application.
|
||||
// Make best effort to clean some memory:
|
||||
local->clear();
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicAddWorker(void* worker) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->addWorker(worker);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicRemoveWorker(void* worker, bool enabled) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->removeWorker(worker, enabled);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicCollectorCallback(void* worker) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->collectorCallaback(worker);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicScheduleGarbageCollect() {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->scheduleGarbageCollect();
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicAddAtomicRoot(ObjHeader* obj) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->addRoot(obj);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicRemoveAtomicRoot(ObjHeader* obj) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->removeRoot(obj);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicMutateAtomicRoot(ObjHeader* newValue) {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->mutateRoot(newValue);
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
|
||||
void cyclicLocalGC() {
|
||||
#if WITH_WORKERS
|
||||
auto* local = cyclicCollector;
|
||||
if (local)
|
||||
local->localGC();
|
||||
#endif // WITH_WORKERS
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#ifndef RUNTIME_CYCLIC_COLLECTOR_H
|
||||
#define RUNTIME_CYCLIC_COLLECTOR_H
|
||||
|
||||
struct ObjHeader;
|
||||
|
||||
void cyclicInit();
|
||||
void cyclicDeinit(bool enabled);
|
||||
void cyclicAddWorker(void* worker);
|
||||
void cyclicRemoveWorker(void* worker, bool enabled);
|
||||
void cyclicAddAtomicRoot(ObjHeader* obj);
|
||||
void cyclicRemoveAtomicRoot(ObjHeader* obj);
|
||||
void cyclicMutateAtomicRoot(ObjHeader* newValue);
|
||||
void cyclicCollectorCallback(void* worker);
|
||||
void cyclicLocalGC();
|
||||
void cyclicScheduleGarbageCollect();
|
||||
|
||||
#endif // RUNTIME_CYCLIC_COLLECTOR_H
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,344 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef RUNTIME_MEMORYPRIVATE_HPP
|
||||
#define RUNTIME_MEMORYPRIVATE_HPP
|
||||
|
||||
#include "Memory.h"
|
||||
|
||||
typedef enum {
|
||||
// Those bit masks are applied to refCount_ field.
|
||||
// Container is normal thread-local container.
|
||||
CONTAINER_TAG_LOCAL = 0,
|
||||
// Container is frozen, could only refer to other frozen objects.
|
||||
// Refcounter update is atomics.
|
||||
CONTAINER_TAG_FROZEN = 1 | 1, // shareable
|
||||
// Stack container, no need to free, children cleanup still shall be there.
|
||||
CONTAINER_TAG_STACK = 2,
|
||||
// Atomic container, reference counter is atomically updated.
|
||||
CONTAINER_TAG_SHARED = 3 | 1, // shareable
|
||||
// Shift to get actual counter.
|
||||
CONTAINER_TAG_SHIFT = 2,
|
||||
// Actual value to increment/decrement container by. Tag is in lower bits.
|
||||
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
|
||||
// Mask for container type.
|
||||
CONTAINER_TAG_MASK = CONTAINER_TAG_INCREMENT - 1,
|
||||
|
||||
// Shift to get actual object count, if has it.
|
||||
CONTAINER_TAG_GC_SHIFT = 7,
|
||||
CONTAINER_TAG_GC_MASK = (1 << CONTAINER_TAG_GC_SHIFT) - 1,
|
||||
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
|
||||
// Color mask of a container.
|
||||
CONTAINER_TAG_COLOR_SHIFT = 3,
|
||||
CONTAINER_TAG_GC_COLOR_MASK = (1 << CONTAINER_TAG_COLOR_SHIFT) - 1,
|
||||
// Colors.
|
||||
// In use or free.
|
||||
CONTAINER_TAG_GC_BLACK = 0,
|
||||
// Possible member of garbage cycle.
|
||||
CONTAINER_TAG_GC_GRAY = 1,
|
||||
// Member of garbage cycle.
|
||||
CONTAINER_TAG_GC_WHITE = 2,
|
||||
// Possible root of cycle.
|
||||
CONTAINER_TAG_GC_PURPLE = 3,
|
||||
// Acyclic.
|
||||
CONTAINER_TAG_GC_GREEN = 4,
|
||||
// Orange and red are currently unused.
|
||||
// Candidate cycle awaiting epoch.
|
||||
CONTAINER_TAG_GC_ORANGE = 5,
|
||||
// Candidate cycle awaiting sigma computation.
|
||||
CONTAINER_TAG_GC_RED = 6,
|
||||
// Individual state bits used during GC and freezing.
|
||||
CONTAINER_TAG_GC_MARKED = 1 << CONTAINER_TAG_COLOR_SHIFT,
|
||||
CONTAINER_TAG_GC_BUFFERED = 1 << (CONTAINER_TAG_COLOR_SHIFT + 1),
|
||||
CONTAINER_TAG_GC_SEEN = 1 << (CONTAINER_TAG_COLOR_SHIFT + 2),
|
||||
// If indeed has more that one object.
|
||||
CONTAINER_TAG_GC_HAS_OBJECT_COUNT = 1 << (CONTAINER_TAG_COLOR_SHIFT + 3)
|
||||
} ContainerTag;
|
||||
|
||||
// Header of all container objects. Contains reference counter.
|
||||
struct ContainerHeader {
|
||||
// Reference counter of container. Uses CONTAINER_TAG_SHIFT, lower bits of counter
|
||||
// for container type (for polymorphism in ::Release()).
|
||||
uint32_t refCount_;
|
||||
// Number of objects in the container.
|
||||
uint32_t objectCount_;
|
||||
|
||||
inline bool local() const {
|
||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_LOCAL;
|
||||
}
|
||||
|
||||
inline bool frozen() const {
|
||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_FROZEN;
|
||||
}
|
||||
|
||||
inline void freeze() {
|
||||
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_FROZEN;
|
||||
}
|
||||
|
||||
inline void makeShared() {
|
||||
refCount_ = (refCount_ & ~CONTAINER_TAG_MASK) | CONTAINER_TAG_SHARED;
|
||||
}
|
||||
|
||||
inline bool shared() const {
|
||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_SHARED;
|
||||
}
|
||||
|
||||
inline bool shareable() const {
|
||||
return (tag() & 1) != 0; // CONTAINER_TAG_FROZEN || CONTAINER_TAG_SHARED
|
||||
}
|
||||
|
||||
inline bool stack() const {
|
||||
return (refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
||||
}
|
||||
|
||||
inline int refCount() const {
|
||||
return (int)refCount_ >> CONTAINER_TAG_SHIFT;
|
||||
}
|
||||
|
||||
inline void setRefCount(unsigned refCount) {
|
||||
refCount_ = tag() | (refCount << CONTAINER_TAG_SHIFT);
|
||||
}
|
||||
|
||||
template <bool Atomic>
|
||||
inline void incRefCount() {
|
||||
#ifdef KONAN_NO_THREADS
|
||||
refCount_ += CONTAINER_TAG_INCREMENT;
|
||||
#else
|
||||
if (Atomic)
|
||||
__sync_add_and_fetch(&refCount_, CONTAINER_TAG_INCREMENT);
|
||||
else
|
||||
refCount_ += CONTAINER_TAG_INCREMENT;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <bool Atomic>
|
||||
inline bool tryIncRefCount() {
|
||||
if (Atomic) {
|
||||
while (true) {
|
||||
uint32_t currentRefCount_ = refCount_;
|
||||
if (((int)currentRefCount_ >> CONTAINER_TAG_SHIFT) > 0) {
|
||||
if (compareAndSet(&refCount_, currentRefCount_, currentRefCount_ + CONTAINER_TAG_INCREMENT)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Note: tricky case here is doing this during cycle collection.
|
||||
// This can actually happen due to deallocation hooks.
|
||||
// Fortunately by this point reference counts have been made precise again.
|
||||
if (refCount() > 0) {
|
||||
incRefCount</* Atomic = */ false>();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <bool Atomic>
|
||||
inline int decRefCount() {
|
||||
#ifdef KONAN_NO_THREADS
|
||||
int value = refCount_ -= CONTAINER_TAG_INCREMENT;
|
||||
#else
|
||||
int value = Atomic ?
|
||||
__sync_sub_and_fetch(&refCount_, CONTAINER_TAG_INCREMENT) : refCount_ -= CONTAINER_TAG_INCREMENT;
|
||||
#endif
|
||||
return value >> CONTAINER_TAG_SHIFT;
|
||||
}
|
||||
|
||||
inline int decRefCount() {
|
||||
#ifdef KONAN_NO_THREADS
|
||||
int value = refCount_ -= CONTAINER_TAG_INCREMENT;
|
||||
#else
|
||||
int value = shareable() ?
|
||||
__sync_sub_and_fetch(&refCount_, CONTAINER_TAG_INCREMENT) : refCount_ -= CONTAINER_TAG_INCREMENT;
|
||||
#endif
|
||||
return value >> CONTAINER_TAG_SHIFT;
|
||||
}
|
||||
|
||||
inline unsigned tag() const {
|
||||
return refCount_ & CONTAINER_TAG_MASK;
|
||||
}
|
||||
|
||||
inline unsigned objectCount() const {
|
||||
return (objectCount_ & CONTAINER_TAG_GC_HAS_OBJECT_COUNT) != 0 ?
|
||||
(objectCount_ >> CONTAINER_TAG_GC_SHIFT) : 1;
|
||||
}
|
||||
|
||||
inline void incObjectCount() {
|
||||
RuntimeAssert((objectCount_ & CONTAINER_TAG_GC_HAS_OBJECT_COUNT) != 0, "Must have object count");
|
||||
objectCount_ += CONTAINER_TAG_GC_INCREMENT;
|
||||
}
|
||||
|
||||
inline void setObjectCount(int count) {
|
||||
if (count == 1) {
|
||||
objectCount_ &= ~CONTAINER_TAG_GC_HAS_OBJECT_COUNT;
|
||||
} else {
|
||||
objectCount_ = (count << CONTAINER_TAG_GC_SHIFT) | CONTAINER_TAG_GC_HAS_OBJECT_COUNT;
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned containerSize() const {
|
||||
RuntimeAssert((objectCount_ & CONTAINER_TAG_GC_HAS_OBJECT_COUNT) == 0, "Must be single-object");
|
||||
return (objectCount_ >> CONTAINER_TAG_GC_SHIFT);
|
||||
}
|
||||
|
||||
inline void setContainerSize(unsigned size) {
|
||||
RuntimeAssert((objectCount_ & CONTAINER_TAG_GC_HAS_OBJECT_COUNT) == 0, "Must not have object count");
|
||||
objectCount_ = (objectCount_ & CONTAINER_TAG_GC_MASK) | (size << CONTAINER_TAG_GC_SHIFT);
|
||||
}
|
||||
|
||||
inline bool hasContainerSize() {
|
||||
return (objectCount_ & CONTAINER_TAG_GC_HAS_OBJECT_COUNT) == 0;
|
||||
}
|
||||
|
||||
inline unsigned color() const {
|
||||
return objectCount_ & CONTAINER_TAG_GC_COLOR_MASK;
|
||||
}
|
||||
|
||||
inline void setColorAssertIfGreen(unsigned color) {
|
||||
RuntimeAssert(this->color() != CONTAINER_TAG_GC_GREEN, "Must not be green");
|
||||
setColorEvenIfGreen(color);
|
||||
}
|
||||
|
||||
inline void setColorEvenIfGreen(unsigned color) {
|
||||
// TODO: do we need atomic color update?
|
||||
objectCount_ = (objectCount_ & ~CONTAINER_TAG_GC_COLOR_MASK) | color;
|
||||
}
|
||||
|
||||
inline void setColorUnlessGreen(unsigned color) {
|
||||
// TODO: do we need atomic color update?
|
||||
unsigned objectCount = objectCount_;
|
||||
if ((objectCount & CONTAINER_TAG_GC_COLOR_MASK) != CONTAINER_TAG_GC_GREEN)
|
||||
objectCount_ = (objectCount & ~CONTAINER_TAG_GC_COLOR_MASK) | color;
|
||||
}
|
||||
|
||||
inline bool buffered() const {
|
||||
return (objectCount_ & CONTAINER_TAG_GC_BUFFERED) != 0;
|
||||
}
|
||||
|
||||
inline void setBuffered() {
|
||||
objectCount_ |= CONTAINER_TAG_GC_BUFFERED;
|
||||
}
|
||||
|
||||
inline void resetBuffered() {
|
||||
objectCount_ &= ~CONTAINER_TAG_GC_BUFFERED;
|
||||
}
|
||||
|
||||
inline bool marked() const {
|
||||
return (objectCount_ & CONTAINER_TAG_GC_MARKED) != 0;
|
||||
}
|
||||
|
||||
inline void mark() {
|
||||
objectCount_ |= CONTAINER_TAG_GC_MARKED;
|
||||
}
|
||||
|
||||
inline void unMark() {
|
||||
objectCount_ &= ~CONTAINER_TAG_GC_MARKED;
|
||||
}
|
||||
|
||||
inline bool seen() const {
|
||||
return (objectCount_ & CONTAINER_TAG_GC_SEEN) != 0;
|
||||
}
|
||||
|
||||
inline void setSeen() {
|
||||
objectCount_ |= CONTAINER_TAG_GC_SEEN;
|
||||
}
|
||||
|
||||
inline void resetSeen() {
|
||||
objectCount_ &= ~CONTAINER_TAG_GC_SEEN;
|
||||
}
|
||||
|
||||
// Following operations only work on freed container which is in finalization queue.
|
||||
// We cannot use 'this' here, as it conflicts with aliasing analysis in clang.
|
||||
inline void setNextLink(ContainerHeader* next) {
|
||||
*reinterpret_cast<ContainerHeader**>(this + 1) = next;
|
||||
}
|
||||
|
||||
inline ContainerHeader* nextLink() {
|
||||
return *reinterpret_cast<ContainerHeader**>(this + 1);
|
||||
}
|
||||
};
|
||||
|
||||
ALWAYS_INLINE ContainerHeader* containerFor(const ObjHeader* obj);
|
||||
|
||||
// Header for the meta-object.
|
||||
struct MetaObjHeader {
|
||||
// Pointer to the type info. Must be first, to match ArrayHeader and ObjHeader layout.
|
||||
const TypeInfo* typeInfo_;
|
||||
// Container pointer.
|
||||
ContainerHeader* container_;
|
||||
|
||||
#ifdef KONAN_OBJC_INTEROP
|
||||
void* associatedObject_;
|
||||
#endif
|
||||
|
||||
// Flags for the object state.
|
||||
int32_t flags_;
|
||||
|
||||
struct {
|
||||
// Strong reference to the counter object.
|
||||
ObjHeader* counter_;
|
||||
} WeakReference;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
#define MODEL_VARIANTS(returnType, name, ...) \
|
||||
returnType name##Strict(__VA_ARGS__) RUNTIME_NOTHROW; \
|
||||
returnType name##Relaxed(__VA_ARGS__) RUNTIME_NOTHROW;
|
||||
|
||||
OBJ_GETTER(AllocInstanceStrict, const TypeInfo* type_info) RUNTIME_NOTHROW;
|
||||
OBJ_GETTER(AllocInstanceRelaxed, const TypeInfo* type_info) RUNTIME_NOTHROW;
|
||||
|
||||
OBJ_GETTER(AllocArrayInstanceStrict, const TypeInfo* type_info, int32_t elements);
|
||||
OBJ_GETTER(AllocArrayInstanceRelaxed, const TypeInfo* type_info, int32_t elements);
|
||||
|
||||
|
||||
MODEL_VARIANTS(void, SetStackRef, ObjHeader** location, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, SetHeapRef, ObjHeader** location, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, ZeroStackRef, ObjHeader** location);
|
||||
MODEL_VARIANTS(void, UpdateStackRef, ObjHeader** location, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, UpdateHeapRef, ObjHeader** location, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, UpdateHeapRefIfNull, ObjHeader** location, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, UpdateReturnRef, ObjHeader** returnSlot, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, UpdateHeapRefsInsideOneArray, const ArrayHeader* array, int fromIndex, int toIndex,
|
||||
int count);
|
||||
MODEL_VARIANTS(void, EnterFrame, ObjHeader** start, int parameters, int count);
|
||||
MODEL_VARIANTS(void, LeaveFrame, ObjHeader** start, int parameters, int count);
|
||||
MODEL_VARIANTS(void, SetCurrentFrame, ObjHeader** start);
|
||||
|
||||
void ReleaseHeapRef(const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
MODEL_VARIANTS(void, ReleaseHeapRef, const ObjHeader* object);
|
||||
MODEL_VARIANTS(void, ReleaseHeapRefNoCollect, const ObjHeader* object);
|
||||
|
||||
bool TryAddHeapRef(const ObjHeader* object);
|
||||
void ReleaseHeapRefNoCollect(const ObjHeader* object) RUNTIME_NOTHROW;
|
||||
|
||||
ForeignRefContext InitLocalForeignRef(ObjHeader* object);
|
||||
ForeignRefContext InitForeignRef(ObjHeader* object);
|
||||
void DeinitForeignRef(ObjHeader* object, ForeignRefContext context);
|
||||
bool IsForeignRefAccessible(ObjHeader* object, ForeignRefContext context);
|
||||
|
||||
// Should be used when reference is read from a possibly shared variable,
|
||||
// and there's nothing else keeping the object alive.
|
||||
void AdoptReferenceFromSharedVariable(ObjHeader* object);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RUNTIME_MEMORYPRIVATE_HPP
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* 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 "MemorySharedRefs.hpp"
|
||||
|
||||
#include "Exceptions.h"
|
||||
#include "MemoryPrivate.hpp"
|
||||
#include "Runtime.h"
|
||||
|
||||
namespace {
|
||||
|
||||
inline bool isForeignRefAccessible(ObjHeader* object, ForeignRefContext context) {
|
||||
// If runtime has not been initialized on this thread, then the object is either unowned or shared.
|
||||
// In the former case initialized runtime is required to throw exceptions
|
||||
// in the latter case -- to provide proper execution context for caller.
|
||||
// TODO: this probably can't be called in uninitialized state in the new MM.
|
||||
Kotlin_initRuntimeIfNeeded();
|
||||
|
||||
return IsForeignRefAccessible(object, context);
|
||||
}
|
||||
|
||||
RUNTIME_NORETURN inline void throwIllegalSharingException(ObjHeader* object) {
|
||||
// TODO: add some info about the context.
|
||||
// Note: retrieving 'type_info()' is supposed to be correct even for unowned object.
|
||||
ThrowIllegalObjectSharingException(object->type_info(), object);
|
||||
}
|
||||
|
||||
RUNTIME_NORETURN inline void terminateWithIllegalSharingException(ObjHeader* object) {
|
||||
#if KONAN_NO_EXCEPTIONS
|
||||
// This will terminate.
|
||||
throwIllegalSharingException(object);
|
||||
#else
|
||||
try {
|
||||
throwIllegalSharingException(object);
|
||||
} catch (...) {
|
||||
// A trick to terminate with unhandled exception. This will print a stack trace
|
||||
// and write to iOS crash log.
|
||||
std::terminate();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
template <ErrorPolicy errorPolicy>
|
||||
bool ensureRefAccessible(ObjHeader* object, ForeignRefContext context) {
|
||||
static_assert(errorPolicy != ErrorPolicy::kIgnore, "Must've been handled by specialization");
|
||||
|
||||
if (isForeignRefAccessible(object, context)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (errorPolicy) {
|
||||
case ErrorPolicy::kDefaultValue:
|
||||
return false;
|
||||
case ErrorPolicy::kThrow:
|
||||
throwIllegalSharingException(object);
|
||||
case ErrorPolicy::kTerminate:
|
||||
terminateWithIllegalSharingException(object);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
bool ensureRefAccessible<ErrorPolicy::kIgnore>(ObjHeader* object, ForeignRefContext context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void KRefSharedHolder::initLocal(ObjHeader* obj) {
|
||||
RuntimeAssert(obj != nullptr, "must not be null");
|
||||
|
||||
context_ = InitLocalForeignRef(obj);
|
||||
obj_ = obj;
|
||||
}
|
||||
|
||||
void KRefSharedHolder::init(ObjHeader* obj) {
|
||||
RuntimeAssert(obj != nullptr, "must not be null");
|
||||
|
||||
context_ = InitForeignRef(obj);
|
||||
obj_ = obj;
|
||||
}
|
||||
|
||||
template <ErrorPolicy errorPolicy>
|
||||
ObjHeader* KRefSharedHolder::ref() const {
|
||||
if (!ensureRefAccessible<errorPolicy>(obj_, context_)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AdoptReferenceFromSharedVariable(obj_);
|
||||
return obj_;
|
||||
}
|
||||
|
||||
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kDefaultValue>() const;
|
||||
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kThrow>() const;
|
||||
template ObjHeader* KRefSharedHolder::ref<ErrorPolicy::kTerminate>() const;
|
||||
|
||||
void KRefSharedHolder::dispose() {
|
||||
if (obj_ == nullptr) {
|
||||
// To handle the case when it is not initialized. See [KotlinMutableSet/Dictionary dealloc].
|
||||
return;
|
||||
}
|
||||
|
||||
DeinitForeignRef(obj_, context_);
|
||||
}
|
||||
|
||||
void BackRefFromAssociatedObject::initForPermanentObject(ObjHeader* obj) {
|
||||
initAndAddRef(obj);
|
||||
}
|
||||
|
||||
void BackRefFromAssociatedObject::initAndAddRef(ObjHeader* obj) {
|
||||
RuntimeAssert(obj != nullptr, "must not be null");
|
||||
obj_ = obj;
|
||||
|
||||
// Generally a specialized addRef below:
|
||||
context_ = InitForeignRef(obj);
|
||||
refCount = 1;
|
||||
}
|
||||
|
||||
template <ErrorPolicy errorPolicy>
|
||||
void BackRefFromAssociatedObject::addRef() {
|
||||
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
|
||||
|
||||
// Can be called both from Native state (if ObjC or Swift code adds RC)
|
||||
// and from Runnable state (Kotlin_ObjCExport_refToObjC).
|
||||
|
||||
if (atomicAdd(&refCount, 1) == 1) {
|
||||
if (obj_ == nullptr) return; // E.g. after [detach].
|
||||
|
||||
// There are no references to the associated object itself, so Kotlin object is being passed from Kotlin,
|
||||
// and it is owned therefore.
|
||||
ensureRefAccessible<errorPolicy>(obj_, context_); // TODO: consider removing explicit verification.
|
||||
|
||||
// Foreign reference has already been deinitialized (see [releaseRef]).
|
||||
// Create a new one:
|
||||
context_ = InitForeignRef(obj_);
|
||||
}
|
||||
}
|
||||
|
||||
template void BackRefFromAssociatedObject::addRef<ErrorPolicy::kThrow>();
|
||||
template void BackRefFromAssociatedObject::addRef<ErrorPolicy::kTerminate>();
|
||||
|
||||
template <ErrorPolicy errorPolicy>
|
||||
bool BackRefFromAssociatedObject::tryAddRef() {
|
||||
static_assert(errorPolicy != ErrorPolicy::kDefaultValue, "Cannot use default return value here");
|
||||
|
||||
if (obj_ == nullptr) return false; // E.g. after [detach].
|
||||
|
||||
// Suboptimal but simple:
|
||||
ensureRefAccessible<errorPolicy>(obj_, context_);
|
||||
|
||||
ObjHeader* obj = obj_;
|
||||
|
||||
if (!TryAddHeapRef(obj)) return false;
|
||||
RuntimeAssert(isForeignRefAccessible(obj_, context_), "Cannot be inaccessible because of the check above");
|
||||
// TODO: This is a very weird way to ask for "unsafe" addRef.
|
||||
addRef<ErrorPolicy::kIgnore>();
|
||||
ReleaseHeapRefNoCollect(obj); // Balance TryAddHeapRef.
|
||||
// TODO: consider optimizing for non-shared objects.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kThrow>();
|
||||
template bool BackRefFromAssociatedObject::tryAddRef<ErrorPolicy::kTerminate>();
|
||||
|
||||
void BackRefFromAssociatedObject::releaseRef() {
|
||||
ForeignRefContext context = context_;
|
||||
if (atomicAdd(&refCount, -1) == 0) {
|
||||
if (obj_ == nullptr) return; // E.g. after [detach].
|
||||
|
||||
// Note: by this moment "subsequent" addRef may have already happened and patched context_.
|
||||
// So use the value loaded before refCount update:
|
||||
DeinitForeignRef(obj_, context);
|
||||
// From this moment [context] is generally a dangling pointer.
|
||||
// This is handled in [IsForeignRefAccessible] and [addRef].
|
||||
// TODO: This probably isn't fine in new MM. Make sure it works.
|
||||
}
|
||||
}
|
||||
|
||||
void BackRefFromAssociatedObject::detach() {
|
||||
RuntimeAssert(atomicGet(&refCount) == 0, "unexpected refCount");
|
||||
obj_ = nullptr; // Handled in addRef/tryAddRef/releaseRef/ref.
|
||||
}
|
||||
|
||||
void BackRefFromAssociatedObject::dealloc() {
|
||||
RuntimeFail("New MM only");
|
||||
}
|
||||
|
||||
template <ErrorPolicy errorPolicy>
|
||||
ObjHeader* BackRefFromAssociatedObject::ref() const {
|
||||
RuntimeAssert(obj_ != nullptr, "no valid Kotlin object found");
|
||||
|
||||
if (!ensureRefAccessible<errorPolicy>(obj_, context_)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AdoptReferenceFromSharedVariable(obj_);
|
||||
return obj_;
|
||||
}
|
||||
|
||||
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kDefaultValue>() const;
|
||||
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kThrow>() const;
|
||||
template ObjHeader* BackRefFromAssociatedObject::ref<ErrorPolicy::kTerminate>() const;
|
||||
|
||||
ObjHeader* BackRefFromAssociatedObject::refPermanent() const {
|
||||
return ref<ErrorPolicy::kIgnore>();
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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 "TestSupport.hpp"
|
||||
|
||||
extern "C" void Kotlin_TestSupport_AssertClearGlobalState() {
|
||||
// Nothing to do. Supported for the new MM only.
|
||||
}
|
||||
|
||||
void kotlin::DeinitMemoryForTests(MemoryState* memoryState) {
|
||||
DeinitMemory(memoryState, false);
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "Weak.h"
|
||||
|
||||
#include "Memory.h"
|
||||
#include "Types.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// TODO: an ugly hack with fixed layout.
|
||||
struct WeakReferenceCounter {
|
||||
ObjHeader header;
|
||||
KRef referred;
|
||||
KInt lock;
|
||||
KInt cookie;
|
||||
};
|
||||
|
||||
inline WeakReferenceCounter* asWeakReferenceCounter(ObjHeader* obj) {
|
||||
return reinterpret_cast<WeakReferenceCounter*>(obj);
|
||||
}
|
||||
|
||||
#if !KONAN_NO_THREADS
|
||||
|
||||
inline void lock(int32_t* address) {
|
||||
RuntimeAssert(*address == 0 || *address == 1, "Incorrect lock state");
|
||||
while (__sync_val_compare_and_swap(address, 0, 1) == 1);
|
||||
}
|
||||
|
||||
inline void unlock(int32_t* address) {
|
||||
int old = __sync_val_compare_and_swap(address, 1, 0);
|
||||
RuntimeAssert(old == 1, "Incorrect lock state");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
OBJ_GETTER(makeWeakReferenceCounterLegacyMM, void*);
|
||||
OBJ_GETTER(makeObjCWeakReferenceImpl, void*);
|
||||
OBJ_GETTER(makePermanentWeakReferenceImpl, ObjHeader*);
|
||||
|
||||
// See Weak.kt for implementation details.
|
||||
// Retrieve link on the counter object.
|
||||
OBJ_GETTER(Konan_getWeakReferenceImpl, ObjHeader* referred) {
|
||||
if (referred->permanent()) {
|
||||
RETURN_RESULT_OF(makePermanentWeakReferenceImpl, referred);
|
||||
}
|
||||
|
||||
#if KONAN_OBJC_INTEROP
|
||||
if (IsInstanceInternal(referred, theObjCObjectWrapperTypeInfo)) {
|
||||
RETURN_RESULT_OF(makeObjCWeakReferenceImpl, referred->GetAssociatedObject());
|
||||
}
|
||||
#endif // KONAN_OBJC_INTEROP
|
||||
|
||||
ObjHeader* weakCounter = referred->GetWeakCounter();
|
||||
if (weakCounter == nullptr) {
|
||||
ObjHolder counterHolder;
|
||||
// Cast unneeded, just to emphasize we store an object reference as void*.
|
||||
ObjHeader* counter = makeWeakReferenceCounterLegacyMM(reinterpret_cast<void*>(referred), counterHolder.slot());
|
||||
weakCounter = referred->GetOrSetWeakCounter(counter);
|
||||
}
|
||||
RETURN_OBJ(weakCounter);
|
||||
}
|
||||
|
||||
OBJ_GETTER(Konan_RegularWeakReferenceImpl_get, ObjHeader* counter) {
|
||||
RuntimeFail("New MM only");
|
||||
}
|
||||
|
||||
// Materialize a weak reference to either null or the real reference.
|
||||
OBJ_GETTER(Konan_WeakReferenceCounterLegacyMM_get, ObjHeader* counter) {
|
||||
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
|
||||
#if KONAN_NO_THREADS
|
||||
RETURN_OBJ(*referredAddress);
|
||||
#else
|
||||
auto* weakCounter = asWeakReferenceCounter(counter);
|
||||
RETURN_RESULT_OF(ReadHeapRefLocked, referredAddress, &weakCounter->lock, &weakCounter->cookie);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WeakReferenceCounterClear(ObjHeader* counter) {
|
||||
ObjHeader** referredAddress = &asWeakReferenceCounter(counter)->referred;
|
||||
// Note, that we don't do UpdateRef here, as reference is weak.
|
||||
#if KONAN_NO_THREADS
|
||||
*referredAddress = nullptr;
|
||||
#else
|
||||
int32_t* lockAddress = &asWeakReferenceCounter(counter)->lock;
|
||||
// Spinlock.
|
||||
lock(lockAddress);
|
||||
*referredAddress = nullptr;
|
||||
unlock(lockAddress);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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_WEAK_H
|
||||
#define RUNTIME_WEAK_H
|
||||
|
||||
#include "Memory.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Atomically clears counter object reference.
|
||||
void WeakReferenceCounterClear(ObjHeader* counter);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
#endif // RUNTIME_WEAK_H
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 "Memory.h"
|
||||
#include "../../legacymm/cpp/MemoryPrivate.hpp" // Fine, because this module is a part of legacy MM.
|
||||
|
||||
// Note that only C++ part of the runtime goes via those functions, Kotlin uses specialized versions.
|
||||
|
||||
extern "C" {
|
||||
|
||||
const MemoryModel CurrentMemoryModel = MemoryModel::kRelaxed;
|
||||
|
||||
RUNTIME_NOTHROW OBJ_GETTER(AllocInstance, const TypeInfo* typeInfo) {
|
||||
RETURN_RESULT_OF(AllocInstanceRelaxed, typeInfo);
|
||||
}
|
||||
|
||||
OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elements) {
|
||||
RETURN_RESULT_OF(AllocArrayInstanceRelaxed, typeInfo, elements);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ReleaseHeapRef(const ObjHeader* object) {
|
||||
ReleaseHeapRefRelaxed(object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ReleaseHeapRefNoCollect(const ObjHeader* object) {
|
||||
ReleaseHeapRefNoCollectRelaxed(object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ZeroStackRef(ObjHeader** location) {
|
||||
ZeroStackRefRelaxed(location);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetStackRef(ObjHeader** location, const ObjHeader* object) {
|
||||
SetStackRefRelaxed(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetHeapRef(ObjHeader** location, const ObjHeader* object) {
|
||||
SetHeapRefRelaxed(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateHeapRef(ObjHeader** location, const ObjHeader* object) {
|
||||
UpdateHeapRefRelaxed(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) {
|
||||
UpdateReturnRefRelaxed(returnSlot, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void EnterFrame(ObjHeader** start, int parameters, int count) {
|
||||
EnterFrameRelaxed(start, parameters, count);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void LeaveFrame(ObjHeader** start, int parameters, int count) {
|
||||
LeaveFrameRelaxed(start, parameters, count);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetCurrentFrame(ObjHeader** start) {
|
||||
SetCurrentFrameRelaxed(start);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateStackRef(ObjHeader** location, const ObjHeader* object) {
|
||||
UpdateStackRefRelaxed(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex, int toIndex, int count) {
|
||||
UpdateHeapRefsInsideOneArrayRelaxed(array, fromIndex, toIndex, count);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2019 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 "Memory.h"
|
||||
#include "../../legacymm/cpp/MemoryPrivate.hpp" // Fine, because this module is a part of legacy MM.
|
||||
|
||||
// Note that only C++ part of the runtime goes via those functions, Kotlin uses specialized versions.
|
||||
|
||||
extern "C" {
|
||||
|
||||
const MemoryModel CurrentMemoryModel = MemoryModel::kStrict;
|
||||
|
||||
RUNTIME_NOTHROW OBJ_GETTER(AllocInstance, const TypeInfo* typeInfo) {
|
||||
RETURN_RESULT_OF(AllocInstanceStrict, typeInfo);
|
||||
}
|
||||
|
||||
OBJ_GETTER(AllocArrayInstance, const TypeInfo* typeInfo, int32_t elements) {
|
||||
RETURN_RESULT_OF(AllocArrayInstanceStrict, typeInfo, elements);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ReleaseHeapRef(const ObjHeader* object) {
|
||||
ReleaseHeapRefStrict(object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ReleaseHeapRefNoCollect(const ObjHeader* object) {
|
||||
ReleaseHeapRefNoCollectStrict(object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetStackRef(ObjHeader** location, const ObjHeader* object) {
|
||||
SetStackRefStrict(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetHeapRef(ObjHeader** location, const ObjHeader* object) {
|
||||
SetHeapRefStrict(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void ZeroStackRef(ObjHeader** location) {
|
||||
ZeroStackRefStrict(location);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateHeapRef(ObjHeader** location, const ObjHeader* object) {
|
||||
UpdateHeapRefStrict(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateReturnRef(ObjHeader** returnSlot, const ObjHeader* object) {
|
||||
UpdateReturnRefStrict(returnSlot, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void EnterFrame(ObjHeader** start, int parameters, int count) {
|
||||
EnterFrameStrict(start, parameters, count);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void LeaveFrame(ObjHeader** start, int parameters, int count) {
|
||||
LeaveFrameStrict(start, parameters, count);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void SetCurrentFrame(ObjHeader** start) {
|
||||
SetCurrentFrameStrict(start);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateStackRef(ObjHeader** location, const ObjHeader* object) {
|
||||
UpdateStackRefStrict(location, object);
|
||||
}
|
||||
|
||||
RUNTIME_NOTHROW void UpdateHeapRefsInsideOneArray(const ArrayHeader* array, int fromIndex, int toIndex, int count) {
|
||||
UpdateHeapRefsInsideOneArrayStrict(array, fromIndex, toIndex, count);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user