diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index f4bc318f890..c8da8601f31 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -27,7 +27,7 @@ ContainerHeader ObjHeader::theStaticObjectsContainer = { namespace { #if USE_GC -// Collection threshold (collect after having so many elements in the +// Collection threshold default (collect after having so many elements in the // release candidates set). constexpr size_t kGcThreshold = 10000; #endif @@ -41,17 +41,23 @@ typedef std::vector KRefPtrList; struct MemoryState { // Current number of allocated containers. int allocCount = 0; + #if TRACE_MEMORY // List of all global objects addresses. KRefPtrList* globalObjects; // Set of all containers. ContainerHeaderSet* containers; #endif + #if USE_GC // Set of references to release. ContainerHeaderSet* toFree; - bool gcInProgress; + // How many GC suspend requests happened. + int gcSuspendCount; + // How many candidate elements in toFree shall trigger collection. size_t gcThreshold; + // If collection is in progress. + bool gcInProgress; #endif }; @@ -189,7 +195,8 @@ void FreeContainer(ContainerHeader* header) { #endif header->ref_count_ = CONTAINER_TAG_INVALID; #if USE_GC - memoryState->toFree->erase(header); + if (memoryState->toFree) + memoryState->toFree->erase(header); #endif // Now let's clean all object's fields in this container. // TODO: this is gross hack, relying on the fact that we now only alloc @@ -227,7 +234,8 @@ void FreeContainerNoRef(ContainerHeader* header) { #endif header->ref_count_ = CONTAINER_TAG_INVALID; #if USE_GC - memoryState->toFree->erase(header); + if (memoryState->toFree) + memoryState->toFree->erase(header); #endif memoryState->allocCount--; free(header); @@ -322,9 +330,11 @@ inline void ReleaseRef(const ObjHeader* object) { #if TRACE_MEMORY fprintf(stderr, "%p is release candidate\n", object->container()); #endif - memoryState->toFree->insert(object->container()); - if (memoryState->toFree->size() > memoryState->gcThreshold) { - GarbageCollect(); + if (memoryState->toFree != nullptr) { + memoryState->toFree->insert(object->container()); + if (memoryState->gcSuspendCount == 0 && + memoryState->toFree->size() > memoryState->gcThreshold) + GarbageCollect(); } #else // !USE_GC Release(object->container()); @@ -357,6 +367,7 @@ void InitMemory() { memoryState->toFree = new ContainerHeaderSet(); memoryState->gcInProgress = false; memoryState->gcThreshold = kGcThreshold; + memoryState->gcSuspendCount = 0; #endif } @@ -560,6 +571,7 @@ void ReleaseGlobalRefs(ObjHeader** start, int count) { #if USE_GC void GarbageCollect() { + RuntimeAssert(memoryState->toFree != nullptr, "GC must not be stopped"); RuntimeAssert(!memoryState->gcInProgress, "Recursive GC is disallowed"); memoryState->gcInProgress = true; // Traverse inner pointers in the closure of release candidates, and @@ -626,5 +638,57 @@ void Kotlin_konan_internal_GC_collect(KRef) { #endif } +void Kotlin_konan_internal_GC_suspend(KRef) { +#if USE_GC + memoryState->gcSuspendCount++; +#endif +} + +void Kotlin_konan_internal_GC_resume(KRef) { +#if USE_GC + if (memoryState->gcSuspendCount > 0) { + memoryState->gcSuspendCount--; + if (memoryState->toFree != nullptr && + memoryState->toFree->size() >= memoryState->gcThreshold) { + GarbageCollect(); + } + } +#endif +} + +void Kotlin_konan_internal_GC_stop(KRef) { +#if USE_GC + if (memoryState->toFree != nullptr) { + GarbageCollect(); + delete memoryState->toFree; + memoryState->toFree = nullptr; + } +#endif +} + +void Kotlin_konan_internal_GC_start(KRef) { +#if USE_GC + if (memoryState->toFree == nullptr) { + memoryState->toFree = new ContainerHeaderSet(); + } +#endif +} + +void Kotlin_konan_internal_GC_setThreshold(KRef, KInt value) { +#if USE_GC + if (value > 0) { + memoryState->gcThreshold = value; + } +#endif +} + +KInt Kotlin_konan_internal_GC_getThreshold(KRef) { +#if USE_GC + return memoryState->gcThreshold; +#else + return -1; +#endif +} + } // extern "C" diff --git a/runtime/src/main/kotlin/konan/internal/GC.kt b/runtime/src/main/kotlin/konan/internal/GC.kt index 2fc73747869..e2c775cca5b 100644 --- a/runtime/src/main/kotlin/konan/internal/GC.kt +++ b/runtime/src/main/kotlin/konan/internal/GC.kt @@ -1,8 +1,52 @@ package konan.internal -// Garbage collector interface. -// TODO: more functions to come. +// Cycle garbage collector interface. +// +// Konan relies upon reference counting for object management, however it could +// not collect cyclical garbage, so we perform periodic garbage collection. +// This may slow down application, so this interface provides control over how +// garbage collector activates and runs. +// Garbage collector can be in one of the following states: +// * running +// * suspended (so cycle candidates are collected, but GC is not performed until resume) +// * stopped (all cyclical garbage is hopelessly lost) +// Immediately after startup GC is in running state. +// Depending on application needs it may select to suspend GC for certain phases of +// its lifetime, and resume it later on, or just completely turn it off, if GC pauses +// are less desirable than cyclical garbage leaks. object GC { + // To force garbage collection immediately, unless collector is stopped + // with stop() operation. Even if GC is suspended, collect() still triggers collection. @SymbolName("Kotlin_konan_internal_GC_collect") external fun collect() + + // Suspend garbage collection. Release candidates are still collected, but + // GC algorithm is not executed. + @SymbolName("Kotlin_konan_internal_GC_suspend") + external fun suspend() + + // Resume garbage collection. Can potentially lead to GC immediately. + @SymbolName("Kotlin_konan_internal_GC_resume") + external fun resume() + + // Stop garbage collection. Cyclical garbage is no longer collected. + @SymbolName("Kotlin_konan_internal_GC_stop") + external fun stop() + + // Start garbage collection. Cyclical garbage produced while GC was stopped + // cannot be reclaimed, but all new garbage is collected. + @SymbolName("Kotlin_konan_internal_GC_start") + external fun start() + + // GC threshold, controlling how frequenly GC is activated, and how much time GC + // takes. Bigger values lead to longer GC pauses, but less GCs. + var threshold: Int + get() = getThreshold() + set(value) = setThreshold(value) + + @SymbolName("Kotlin_konan_internal_GC_getThreshold") + private external fun getThreshold(): Int + + @SymbolName("Kotlin_konan_internal_GC_setThreshold") + private external fun setThreshold(value: Int) } \ No newline at end of file