diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index aa127e69e81..5e5a3384db7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -33,7 +33,7 @@ internal class MacOSPlatform(distrib: Distribution, override val linkerOptimizationFlags = properties.propertyList("linkerOptimizationFlags.osx") - override val linkerKonanFlags = listOf(distrib.libCppAbi) + override val linkerKonanFlags = properties.propertyList("linkerKonanFlags.osx") override val linker = "${distrib.sysRoot}/usr/bin/ld" override fun linkCommand(objectFiles: List, executable: String, optimize: Boolean): List { diff --git a/backend.native/konan.properties b/backend.native/konan.properties index 8edf4e62b0f..c45907461ec 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -10,12 +10,13 @@ libGcc.linux = target-gcc-toolchain-3-linux-x86-64/lib/gcc/x86_64-unknown-linux- llvmLtoFlags.osx = -O3 -function-sections -exported-symbol=_main llvmLlcFlags.osx = -mtriple=x86_64-apple-macosx10.10.0 +linkerKonanFlags.osx = -lc++ linkerOptimizationFlags.osx = -dead_strip macosVersionMin.osx = 10.10.0 llvmLtoFlags.linux = -O3 -function-sections -exported-symbol=main llvmLlcFlags.linux = -march=x86-64 -linkerKonanFlags.linux = -Bstatic -lc++abi -Bdynamic -ldl -lm -lpthread +linkerKonanFlags.linux = -Bstatic -lstdc++ -Bdynamic -ldl -lm -lpthread linkerOptimizationFlags.linux = --gc-sections pluginOptimizationFlags.linux = -plugin-opt=mcpu=x86-64 -plugin-opt=O3 diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 0a46a4dc24f..9cbe91cb95c 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -41,11 +41,11 @@ abstract class KonanTest extends DefaultTask { project.javaexec { main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt' classpath = project.configurations.cli_bc - jvmArgs "-ea", + jvmArgs "-ea", "-Dkonan.home=${dist.canonicalPath}", "-Djava.library.path=${dist.canonicalPath}/konan/nativelib" args("-output", output, - source, + source, *moreArgs, *project.globalArgs) } @@ -88,7 +88,7 @@ abstract class KonanTest extends DefaultTask { class RunKonanTest extends KonanTest { void compileTest(String source, String exe) { - runCompiler(source, exe, []) + runCompiler(source, exe, []) } } @@ -98,7 +98,7 @@ class LinkKonanTest extends KonanTest { void compileTest(String source, String exe) { def libDir = project.file(lib).absolutePath def libBc = "${libDir}.bc" - + runCompiler(lib, libBc, ['-nolink', '-nostdlib']) runCompiler(source, exe, ['-library', libBc]) } @@ -246,8 +246,8 @@ task tostring3(type: RunKonanTest) { "1.17549E-38\n3.40282E+38\n-INF\nINF\n" + // Linux version prints -NAN. // "NAN\n" + - "4.94066E-324\n1.79769E+308\n-INF\nINF\n" - // "NAN\n" + "4.94066E-324\n1.79769E+308\n-INF\nINF\n" + // "NAN\n" source = "runtime/basic/tostring3.kt" } @@ -359,7 +359,7 @@ task intrinsic(type: RunKonanTest) { } /* - Disabled until we extract the classes that should be + Disabled until we extract the classes that should be always present from stdlib.kt.bc into a separate binary. task link(type: LinkKonanTest) { @@ -805,6 +805,11 @@ task memory_throw_cleanup(type: RunKonanTest) { source = "runtime/memory/throw_cleanup.kt" } +task memory_collect_cycles(type: RunKonanTest) { + goldValue = "42\n" + source = "runtime/memory/cycles0.kt" +} + task unit1(type: RunKonanTest) { goldValue = "First\nkotlin.Unit\n" source = "codegen/basics/unit1.kt" diff --git a/backend.native/tests/runtime/memory/cycles0.kt b/backend.native/tests/runtime/memory/cycles0.kt new file mode 100644 index 00000000000..dc7872d1c09 --- /dev/null +++ b/backend.native/tests/runtime/memory/cycles0.kt @@ -0,0 +1,45 @@ +data class Node(val data: Int, var next: Node?, var prev: Node?, val outer: Node?) + +fun makeCycle(len: Int, outer: Node?): Node { + val start = Node(0, null, null, outer) + var prev = start + for (i in 1 .. len - 1) { + prev = Node(i, prev, null, outer) + } + start.next = prev + return start +} + +fun makeDoubleCycle(len: Int): Node { + val start = makeCycle(len, null) + var prev = start + var cur = prev.next + while (cur != start) { + cur!!.prev = prev + prev = cur + cur = cur.next + } + start.prev = prev + return start +} + +fun createCycles(junk: Node) { + val cycle1 = makeCycle(1, junk) + val cycle2 = makeCycle(2, junk) + val cycle10 = makeCycle(10, junk) + val cycle100 = makeCycle(100, junk) + val dcycle1 = makeDoubleCycle(1) + val dcycle2 = makeDoubleCycle(2) + val dcycle10 = makeDoubleCycle(10) + val dcycle100 = makeDoubleCycle(100) + +} + +fun main(args : Array) { + // Create outer link from cyclic garbage. + val outer = Node(42, null, null, null) + createCycles(outer) + konan.internal.GC.collect() + // Ensure outer is not collected. + println(outer.data) +} diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 6ee6fd74ee8..f4bc318f890 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -3,7 +3,7 @@ #include #include // for offsetof -#include // only for memory tracing. +#include #include #include "Assert.h" @@ -11,44 +11,186 @@ #include "Memory.h" #include "Natives.h" -// Define to 1 to see all memory operations. -#define TRACE_MEMORY 0 -// Define to 1 to use in multithreaded environment. +// Define to 1 to use in the multithreaded environment. #define CONCURRENT 0 +// If garbage collection algorithm for cyclic garbage to be used. +#define USE_GC 1 +// Define to 1 to print all memory operations. +#define TRACE_MEMORY 0 +// Trace garbage collection phases. +#define TRACE_GC_PHASES 0 -ContainerHeader ObjHeader::theStaticObjectsContainer = { CONTAINER_TAG_NOCOUNT }; +ContainerHeader ObjHeader::theStaticObjectsContainer = { + CONTAINER_TAG_NOCOUNT | CONTAINER_TAG_INCREMENT +}; namespace { -// Current number of allocated containers. -int allocCount = 0; -#if TRACE_MEMORY -// List of all global objects addresses. -std::vector* globalObjects = nullptr; -// Set of all containers. -std::set* containers = nullptr; +#if USE_GC +// Collection threshold (collect after having so many elements in the +// release candidates set). +constexpr size_t kGcThreshold = 10000; #endif +#if TRACE_MEMORY || USE_GC +typedef std::unordered_set ContainerHeaderSet; +typedef std::vector ContainerHeaderList; +typedef std::vector KRefPtrList; +#endif + +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; + size_t gcThreshold; +#endif +}; + +MemoryState* memoryState = nullptr; + +#if USE_GC +bool isPermanent(const ContainerHeader* header) { + return (header->ref_count_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_NOCOUNT; +} + +// Must be vector or map 'container -> number', to keep reference counters correct. +ContainerHeaderList collectMutableReferred(ContainerHeader* header) { + ContainerHeaderList result; + ObjHeader* obj = reinterpret_cast(header + 1); + const TypeInfo* typeInfo = obj->type_info(); + // TODO: generalize iteration over all references. + // TODO: this code relies on single object per container assumption. + for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { + ObjHeader** location = reinterpret_cast( + reinterpret_cast(obj + 1) + typeInfo->objOffsets_[index]); + ObjHeader* obj = *location; + if (obj != nullptr && !isPermanent(obj->container())) { + result.push_back(obj->container()); + } + } + if (typeInfo == theArrayTypeInfo) { + ArrayHeader* array = obj->array(); + for (int index = 0; index < array->count_; index++) { + ObjHeader* obj = *ArrayAddressOfElementAt(array, index); + if (obj != nullptr && !isPermanent(obj->container())) { + result.push_back(obj->container()); + } + } + } + return result; +} + +void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet* seen) { + fprintf(stderr, "%s: %p (%08x): %d refs %s\n", + prefix, + header, header->ref_count_, header->ref_count_ >> CONTAINER_TAG_SHIFT, + (header->ref_count_ & CONTAINER_TAG_SEEN) != 0 ? "X" : "-"); + seen->insert(header); + auto children = collectMutableReferred(header); + for (auto child : children) { + if (seen->count(child) == 0) { + dumpWorker(prefix, child, seen); + } + } +} + +void dumpReachable(const char* prefix, const ContainerHeaderSet* roots) { + ContainerHeaderSet seen; + for (auto container : *roots) { + dumpWorker(prefix, container, &seen); + } +} + +void phase1(ContainerHeader* header) { + if ((header->ref_count_ & CONTAINER_TAG_SEEN) != 0) + return; + header->ref_count_ |= CONTAINER_TAG_SEEN; + auto containers = collectMutableReferred(header); + for (auto container : containers) { + container->ref_count_ -= CONTAINER_TAG_INCREMENT; + phase1(container); + } +} + +void phase2(ContainerHeader* header, ContainerHeaderSet* rootset) { + if ((header->ref_count_ & CONTAINER_TAG_SEEN) == 0) + return; + if ((header->ref_count_ >> CONTAINER_TAG_SHIFT) != 0) + rootset->insert(header); + header->ref_count_ &= ~CONTAINER_TAG_SEEN; + auto containers = collectMutableReferred(header); + for (auto container : containers) { + phase2(container, rootset); + } +} + +void phase3(ContainerHeader* header) { + if ((header->ref_count_ & CONTAINER_TAG_SEEN) != 0) { + return; + } + header->ref_count_ |= CONTAINER_TAG_SEEN; + auto containers = collectMutableReferred(header); + for (auto container : containers) { + container->ref_count_ += CONTAINER_TAG_INCREMENT; + phase3(container); + } +} + +void phase4(ContainerHeader* header, ContainerHeaderSet* toRemove) { + auto ref_count = header->ref_count_ >> CONTAINER_TAG_SHIFT; + bool seen = (ref_count > 0 && (header->ref_count_ & CONTAINER_TAG_SEEN) == 0) || + (ref_count == 0 && (header->ref_count_ & CONTAINER_TAG_SEEN) != 0); + if (seen) return; + + // Add to toRemove set. + if (ref_count == 0) + toRemove->insert(header); + + // Update seen bit. + if (ref_count == 0) + header->ref_count_ |= CONTAINER_TAG_SEEN; + else + header->ref_count_ &= ~CONTAINER_TAG_SEEN; + auto containers = collectMutableReferred(header); + for (auto container : containers) { + phase4(container, toRemove); + } +} + +#endif // USE_GC + } // namespace ContainerHeader* AllocContainer(size_t size) { ContainerHeader* result = reinterpret_cast(calloc(1, size)); #if TRACE_MEMORY - printf(">>> alloc %d -> %p\n", (int)size, result); - containers->insert(result); + fprintf(stderr, ">>> alloc %d -> %p\n", (int)size, result); + memoryState->containers->insert(result); #endif // TODO: atomic increment in concurrent case. - allocCount++; + memoryState->allocCount++; return result; } void FreeContainer(ContainerHeader* header) { + RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed"); #if TRACE_MEMORY - printf("<<< free %p\n", header); - containers->erase(header); + fprintf(stderr, "<<< free %p\n", header); + memoryState->containers->erase(header); #endif header->ref_count_ = CONTAINER_TAG_INVALID; - +#if USE_GC + 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 // ArenaContainer and ObjectContainer, which both have single element. @@ -69,10 +211,29 @@ void FreeContainer(ContainerHeader* header) { // And release underlying memory. // TODO: atomic decrement in concurrent case. - allocCount--; +#if CONCURRENT + #error "Atomic update of allocCount" +#endif + memoryState->allocCount--; free(header); } +#if USE_GC +void FreeContainerNoRef(ContainerHeader* header) { + RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed"); +#if TRACE_MEMORY + fprintf(stderr, "<<< free %p\n", header); + memoryState->containers->erase(header); +#endif + header->ref_count_ = CONTAINER_TAG_INVALID; +#if USE_GC + memoryState->toFree->erase(header); +#endif + memoryState->allocCount--; + free(header); +} +#endif + ArenaContainer::ArenaContainer(uint32_t size) { ArenaContainerHeader* header = static_cast(AllocContainer(size + sizeof(ArenaContainerHeader))); @@ -92,7 +253,7 @@ void ObjectContainer::Init(const TypeInfo* type_info) { // header->ref_count_ is zero initialized by AllocContainer(). SetMeta(GetPlace(), type_info); #if TRACE_MEMORY - printf("object at %p\n", GetPlace()); + fprintf(stderr, "object at %p\n", GetPlace()); #endif } } @@ -109,7 +270,7 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) { GetPlace()->count_ = elements; SetMeta(GetPlace()->obj(), type_info); #if TRACE_MEMORY - printf("array at %p\n", GetPlace()); + fprintf(stderr, "array at %p\n", GetPlace()); #endif } } @@ -139,16 +300,35 @@ ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, int count) { inline void AddRef(const ObjHeader* object) { #if TRACE_MEMORY - printf("AddRef on %p in %p\n", object, object->container()); + fprintf(stderr, "AddRef on %p in %p\n", object, object->container()); #endif AddRef(object->container()); +#if USE_GC + // TODO: one could remove from toFree set here, as now container is reachable + // from the rootset, so cannot be cycle collection candidate. + // memoryState->toFree->erase(object->container()); +#endif } inline void ReleaseRef(const ObjHeader* object) { #if TRACE_MEMORY - printf("ReleaseRef on %p in %p\n", object, object->container()); + fprintf(stderr, "ReleaseRef on %p in %p\n", object, object->container()); #endif +#if USE_GC + // If object is not a cycle candidate - just return. + if (Release(object->container())) { + return; + } +#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(); + } +#else // !USE_GC Release(object->container()); +#endif // USE_GC } extern "C" { @@ -162,36 +342,50 @@ void InitMemory() { == offsetof(ObjHeader , container_offset_negative_), "Layout mismatch"); + RuntimeAssert(memoryState == nullptr, "memory state must be clear"); + memoryState = new MemoryState(); // TODO: initialize heap here. - allocCount = 0; + memoryState->allocCount = 0; #if TRACE_MEMORY - globalObjects = new std::vector(); - containers = new std::set(); + memoryState->globalObjects = new KRefPtrList(); + memoryState->containers = new ContainerHeaderSet(); +#endif +#if USE_GC +#if CONCURRENT + #error "Concurrent GC is not yet implemented" +#endif + memoryState->toFree = new ContainerHeaderSet(); + memoryState->gcInProgress = false; + memoryState->gcThreshold = kGcThreshold; #endif } void DeinitMemory() { #if TRACE_MEMORY // Free all global objects, to ensure no memory leaks happens. - for (auto location: *globalObjects) { - printf("Release global in *%p: %p\n", location, *location); + for (auto location: *memoryState->globalObjects) { + fprintf(stderr, "Release global in *%p: %p\n", location, *location); UpdateGlobalRef(location, nullptr); } - delete globalObjects; - globalObjects = nullptr; + delete memoryState->globalObjects; + memoryState->globalObjects = nullptr; #endif - if (allocCount > 0) { +#if USE_GC + GarbageCollect(); +#endif // USE_GC + + if (memoryState->allocCount > 0) { #if TRACE_MEMORY - // TODO: move out of TRACE_MEMORY, once exceptions free memory. - printf("*** Memory leaks, leaked %d containers ***\n", allocCount); - for (auto container: *containers) { - printf("Unfreed container %p, count = %d\n", container, container->ref_count_); - } - delete containers; - containers = nullptr; + fprintf(stderr, "*** Memory leaks, leaked %d containers ***\n", memoryState->allocCount); + dumpReachable("", memoryState->containers); + delete memoryState->containers; + memoryState->containers = nullptr; #endif } + + delete memoryState; + memoryState = nullptr; } // Now we ignore all placement hints and always allocate heap space for new object. @@ -242,7 +436,7 @@ OBJ_GETTER(InitInstance, // TODO: locking or smth lock-free in MT case? #endif #if TRACE_MEMORY - globalObjects->push_back(location); + memoryState->globalObjects->push_back(location); #endif RETURN_OBJ_RESULT(); } catch (...) { @@ -254,7 +448,7 @@ OBJ_GETTER(InitInstance, void SetLocalRef(ObjHeader** location, const ObjHeader* object) { #if TRACE_MEMORY - printf("SetLocalRef *%p: %p\n", location, object); + fprintf(stderr, "SetLocalRef *%p: %p\n", location, object); #endif *const_cast(location) = object; if (object != nullptr) { @@ -264,7 +458,7 @@ void SetLocalRef(ObjHeader** location, const ObjHeader* object) { void SetGlobalRef(ObjHeader** location, const ObjHeader* object) { #if TRACE_MEMORY - printf("SetGlobalRef *%p: %p\n", location, object); + fprintf(stderr, "SetGlobalRef *%p: %p\n", location, object); #endif *const_cast(location) = object; if (object != nullptr) { @@ -278,16 +472,16 @@ void SetGlobalRef(ObjHeader** location, const ObjHeader* object) { void UpdateLocalRef(ObjHeader** location, const ObjHeader* object) { ObjHeader* old = *location; #if TRACE_MEMORY - printf("UpdateLocalRef *%p: %p -> %p\n", location, old, object); + fprintf(stderr, "UpdateLocalRef *%p: %p -> %p\n", location, old, object); #endif if (old != object) { + if (object != nullptr) { + AddRef(object); + } *const_cast(location) = object; if (old > reinterpret_cast(1)) { ReleaseRef(old); } - if (object != nullptr) { - AddRef(object); - } } } @@ -295,7 +489,7 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) { #if CONCURRENT ObjHeader* old = *location; #if TRACE_MEMORY - printf("UpdateGlobalRef *%p: %p -> %p\n", location, old, object); + fprintf(stderr, "UpdateGlobalRef *%p: %p -> %p\n", location, old, object); #endif if (old != object) { if (object != nullptr) { @@ -320,7 +514,7 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) { void ReleaseLocalRefs(ObjHeader** start, int count) { #if TRACE_MEMORY - printf("ReleaseLocalRefs %p .. %p\n", start, start + count); + fprintf(stderr, "ReleaseLocalRefs %p .. %p\n", start, start + count); #endif ObjHeader** current = start; while (count-- > 0) { @@ -336,7 +530,7 @@ void ReleaseLocalRefs(ObjHeader** start, int count) { void ReleaseGlobalRefs(ObjHeader** start, int count) { #if TRACE_MEMORY - printf("ReleaseGlobalRefs %p .. %p\n", start, start + count); + fprintf(stderr, "ReleaseGlobalRefs %p .. %p\n", start, start + count); #endif #if CONCURRENT ObjHeader** current = start; @@ -364,4 +558,73 @@ void ReleaseGlobalRefs(ObjHeader** start, int count) { #endif } +#if USE_GC +void GarbageCollect() { + RuntimeAssert(!memoryState->gcInProgress, "Recursive GC is disallowed"); + memoryState->gcInProgress = true; + // Traverse inner pointers in the closure of release candidates, and + // temporary decrement refs on them. Set CONTAINER_TAG_SEEN while traversing. +#if TRACE_GC_PHASES + dumpReachable("P0", memoryState->toFree); +#endif + for (auto container : *memoryState->toFree) { + phase1(container); + } +#if TRACE_GC_PHASES + dumpReachable("P1", memoryState->toFree); +#endif + + // Collect rootset from containers with non-zero reference counter. Those must + // be referenced from outside of newly released object graph. + // Clear CONTAINER_TAG_SEEN while traversing. + ContainerHeaderSet rootset; + for (auto container : *memoryState->toFree) { + phase2(container, &rootset); + } +#if TRACE_GC_PHASES + dumpReachable("P2", memoryState->toFree); +#endif + + // Increment references for all elements reachable from the rootset. + // Set CONTAINER_TAG_SEEN while traversing. + for (auto container : rootset) { +#if TRACE_MEMORY + fprintf(stderr, "rootset %p\n", container); +#endif + phase3(container); + } +#if TRACE_GC_PHASES + dumpReachable("P3", memoryState->toFree); +#endif + + // Traverse all elements, and collect those not having CONTAINER_TAG_SEEN and zero RC. + // Clear CONTAINER_TAG_SEEN while traversing on live elements, set in on dead elements. + ContainerHeaderSet toRemove; + for (auto container : *memoryState->toFree) { + phase4(container, &toRemove); + } +#if TRACE_GC_PHASES + dumpReachable("P4", memoryState->toFree); +#endif + + // Clear cycle candidates list. + memoryState->toFree->clear(); + + for (auto header : toRemove) { + RuntimeAssert((header->ref_count_ & CONTAINER_TAG_SEEN) != 0, "Must be not seen"); + FreeContainerNoRef(header); + } + + memoryState->gcInProgress = false; +} + +#endif // USE_GC + +void Kotlin_konan_internal_GC_collect(KRef) { +#if USE_GC + GarbageCollect(); +#endif +} + + } // extern "C" diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 62d8abcb629..6748e893e2d 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -26,10 +26,14 @@ typedef enum { CONTAINER_TAG_SHARED = 2, // Container is no longer valid. CONTAINER_TAG_INVALID = 3, + // Container was seen during GC. + CONTAINER_TAG_SEEN = 4, + // Shift to get actual counter.. + CONTAINER_TAG_SHIFT = 3, // Actual value to increment/decrement conatiner by. Tag is in lower bits. - CONTAINER_TAG_INCREMENT = 1 << 2, - // Mask for container type. - CONTAINER_TAG_MASK = (CONTAINER_TAG_INCREMENT - 1) + CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT, + // Mask for container type, disregard seen bit. + CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1) } ContainerTag; // Could be made 64-bit for large memory configs. @@ -127,8 +131,8 @@ inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) { void FreeContainer(ContainerHeader* header); -// Those two operations are implemented by translator when storing references -// to objects. +// TODO: those two operations can be implemented by translator when storing +// reference to an object. inline void AddRef(ContainerHeader* header) { // Looking at container type we may want to skip AddRef() totally // (non-escaping stack objects, constant objects). @@ -144,18 +148,25 @@ inline void AddRef(ContainerHeader* header) { case CONTAINER_TAG_INVALID: RuntimeAssert(false, "trying to addref invalid container"); break; + default: + RuntimeAssert(false, "unknown container type"); + break; } } -inline void Release(ContainerHeader* header) { +// Release returns 'true' iff container cannot be part of cycle (either NOCOUNT +// object or container was fully released and will be collected). +inline bool Release(ContainerHeader* header) { switch (header->ref_count_ & CONTAINER_TAG_MASK) { case CONTAINER_TAG_NORMAL: if ((header->ref_count_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) { FreeContainer(header); + return true; } break; case CONTAINER_TAG_NOCOUNT: - break; + // NOCOUNT containers aren't loop candidate. + return true; // Note that shared containers have potentially subtle race, if object holds a // reference to another object, stored in shorter living container. In this // case there's unlikely, but possible case, where one mutator takes reference, @@ -173,12 +184,18 @@ inline void Release(ContainerHeader* header) { if (__sync_sub_and_fetch( &header->ref_count_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) { FreeContainer(header); + return true; } break; case CONTAINER_TAG_INVALID: RuntimeAssert(false, "trying to release invalid container"); break; + default: + RuntimeAssert(false, "unknown container type"); + break; } + // Object with non-zero counter after release are loop candidates. + return false; } // Class representing arbitrary placement container. @@ -346,6 +363,8 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTH // Optimization: release all references in range. void ReleaseLocalRefs(ObjHeader** start, int count) RUNTIME_NOTHROW; void ReleaseGlobalRefs(ObjHeader** start, int count) RUNTIME_NOTHROW; +// Collect garbage, which cannot be found by reference counting (cycles). +void GarbageCollect() RUNTIME_NOTHROW; #ifdef __cplusplus } diff --git a/runtime/src/main/kotlin/konan/internal/GC.kt b/runtime/src/main/kotlin/konan/internal/GC.kt new file mode 100644 index 00000000000..2fc73747869 --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/GC.kt @@ -0,0 +1,8 @@ +package konan.internal + +// Garbage collector interface. +// TODO: more functions to come. +object GC { + @SymbolName("Kotlin_konan_internal_GC_collect") + external fun collect() +} \ No newline at end of file