Cycle collector. (#175)
This commit is contained in:
committed by
Konstantin Anisimov
parent
14695bf20e
commit
ecf66ab23b
+1
-1
@@ -33,7 +33,7 @@ internal class MacOSPlatform(distrib: Distribution,
|
|||||||
|
|
||||||
override val linkerOptimizationFlags =
|
override val linkerOptimizationFlags =
|
||||||
properties.propertyList("linkerOptimizationFlags.osx")
|
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 val linker = "${distrib.sysRoot}/usr/bin/ld"
|
||||||
|
|
||||||
override fun linkCommand(objectFiles: List<String>, executable: String, optimize: Boolean): List<String> {
|
override fun linkCommand(objectFiles: List<String>, executable: String, optimize: Boolean): List<String> {
|
||||||
|
|||||||
@@ -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
|
llvmLtoFlags.osx = -O3 -function-sections -exported-symbol=_main
|
||||||
llvmLlcFlags.osx = -mtriple=x86_64-apple-macosx10.10.0
|
llvmLlcFlags.osx = -mtriple=x86_64-apple-macosx10.10.0
|
||||||
|
linkerKonanFlags.osx = -lc++
|
||||||
linkerOptimizationFlags.osx = -dead_strip
|
linkerOptimizationFlags.osx = -dead_strip
|
||||||
macosVersionMin.osx = 10.10.0
|
macosVersionMin.osx = 10.10.0
|
||||||
|
|
||||||
llvmLtoFlags.linux = -O3 -function-sections -exported-symbol=main
|
llvmLtoFlags.linux = -O3 -function-sections -exported-symbol=main
|
||||||
llvmLlcFlags.linux = -march=x86-64
|
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
|
linkerOptimizationFlags.linux = --gc-sections
|
||||||
pluginOptimizationFlags.linux = -plugin-opt=mcpu=x86-64 -plugin-opt=O3
|
pluginOptimizationFlags.linux = -plugin-opt=mcpu=x86-64 -plugin-opt=O3
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ abstract class KonanTest extends DefaultTask {
|
|||||||
project.javaexec {
|
project.javaexec {
|
||||||
main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt'
|
main = 'org.jetbrains.kotlin.cli.bc.K2NativeKt'
|
||||||
classpath = project.configurations.cli_bc
|
classpath = project.configurations.cli_bc
|
||||||
jvmArgs "-ea",
|
jvmArgs "-ea",
|
||||||
"-Dkonan.home=${dist.canonicalPath}",
|
"-Dkonan.home=${dist.canonicalPath}",
|
||||||
"-Djava.library.path=${dist.canonicalPath}/konan/nativelib"
|
"-Djava.library.path=${dist.canonicalPath}/konan/nativelib"
|
||||||
args("-output", output,
|
args("-output", output,
|
||||||
source,
|
source,
|
||||||
*moreArgs,
|
*moreArgs,
|
||||||
*project.globalArgs)
|
*project.globalArgs)
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ abstract class KonanTest extends DefaultTask {
|
|||||||
|
|
||||||
class RunKonanTest extends KonanTest {
|
class RunKonanTest extends KonanTest {
|
||||||
void compileTest(String source, String exe) {
|
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) {
|
void compileTest(String source, String exe) {
|
||||||
def libDir = project.file(lib).absolutePath
|
def libDir = project.file(lib).absolutePath
|
||||||
def libBc = "${libDir}.bc"
|
def libBc = "${libDir}.bc"
|
||||||
|
|
||||||
runCompiler(lib, libBc, ['-nolink', '-nostdlib'])
|
runCompiler(lib, libBc, ['-nolink', '-nostdlib'])
|
||||||
runCompiler(source, exe, ['-library', libBc])
|
runCompiler(source, exe, ['-library', libBc])
|
||||||
}
|
}
|
||||||
@@ -246,8 +246,8 @@ task tostring3(type: RunKonanTest) {
|
|||||||
"1.17549E-38\n3.40282E+38\n-INF\nINF\n" +
|
"1.17549E-38\n3.40282E+38\n-INF\nINF\n" +
|
||||||
// Linux version prints -NAN.
|
// Linux version prints -NAN.
|
||||||
// "NAN\n" +
|
// "NAN\n" +
|
||||||
"4.94066E-324\n1.79769E+308\n-INF\nINF\n"
|
"4.94066E-324\n1.79769E+308\n-INF\nINF\n"
|
||||||
// "NAN\n"
|
// "NAN\n"
|
||||||
source = "runtime/basic/tostring3.kt"
|
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.
|
always present from stdlib.kt.bc into a separate binary.
|
||||||
|
|
||||||
task link(type: LinkKonanTest) {
|
task link(type: LinkKonanTest) {
|
||||||
@@ -805,6 +805,11 @@ task memory_throw_cleanup(type: RunKonanTest) {
|
|||||||
source = "runtime/memory/throw_cleanup.kt"
|
source = "runtime/memory/throw_cleanup.kt"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task memory_collect_cycles(type: RunKonanTest) {
|
||||||
|
goldValue = "42\n"
|
||||||
|
source = "runtime/memory/cycles0.kt"
|
||||||
|
}
|
||||||
|
|
||||||
task unit1(type: RunKonanTest) {
|
task unit1(type: RunKonanTest) {
|
||||||
goldValue = "First\nkotlin.Unit\n"
|
goldValue = "First\nkotlin.Unit\n"
|
||||||
source = "codegen/basics/unit1.kt"
|
source = "codegen/basics/unit1.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<String>) {
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
+311
-48
@@ -3,7 +3,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <cstddef> // for offsetof
|
#include <cstddef> // for offsetof
|
||||||
#include <set> // only for memory tracing.
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Assert.h"
|
#include "Assert.h"
|
||||||
@@ -11,44 +11,186 @@
|
|||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
#include "Natives.h"
|
#include "Natives.h"
|
||||||
|
|
||||||
// Define to 1 to see all memory operations.
|
// Define to 1 to use in the multithreaded environment.
|
||||||
#define TRACE_MEMORY 0
|
|
||||||
// Define to 1 to use in multithreaded environment.
|
|
||||||
#define CONCURRENT 0
|
#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 {
|
namespace {
|
||||||
|
|
||||||
// Current number of allocated containers.
|
#if USE_GC
|
||||||
int allocCount = 0;
|
// Collection threshold (collect after having so many elements in the
|
||||||
#if TRACE_MEMORY
|
// release candidates set).
|
||||||
// List of all global objects addresses.
|
constexpr size_t kGcThreshold = 10000;
|
||||||
std::vector<KRef*>* globalObjects = nullptr;
|
|
||||||
// Set of all containers.
|
|
||||||
std::set<ContainerHeader*>* containers = nullptr;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if TRACE_MEMORY || USE_GC
|
||||||
|
typedef std::unordered_set<ContainerHeader*> ContainerHeaderSet;
|
||||||
|
typedef std::vector<ContainerHeader*> ContainerHeaderList;
|
||||||
|
typedef std::vector<KRef*> 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<ObjHeader*>(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<ObjHeader**>(
|
||||||
|
reinterpret_cast<uintptr_t>(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
|
} // namespace
|
||||||
|
|
||||||
ContainerHeader* AllocContainer(size_t size) {
|
ContainerHeader* AllocContainer(size_t size) {
|
||||||
ContainerHeader* result = reinterpret_cast<ContainerHeader*>(calloc(1, size));
|
ContainerHeader* result = reinterpret_cast<ContainerHeader*>(calloc(1, size));
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf(">>> alloc %d -> %p\n", (int)size, result);
|
fprintf(stderr, ">>> alloc %d -> %p\n", (int)size, result);
|
||||||
containers->insert(result);
|
memoryState->containers->insert(result);
|
||||||
#endif
|
#endif
|
||||||
// TODO: atomic increment in concurrent case.
|
// TODO: atomic increment in concurrent case.
|
||||||
allocCount++;
|
memoryState->allocCount++;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FreeContainer(ContainerHeader* header) {
|
void FreeContainer(ContainerHeader* header) {
|
||||||
|
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed");
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("<<< free %p\n", header);
|
fprintf(stderr, "<<< free %p\n", header);
|
||||||
containers->erase(header);
|
memoryState->containers->erase(header);
|
||||||
#endif
|
#endif
|
||||||
header->ref_count_ = CONTAINER_TAG_INVALID;
|
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.
|
// 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
|
// TODO: this is gross hack, relying on the fact that we now only alloc
|
||||||
// ArenaContainer and ObjectContainer, which both have single element.
|
// ArenaContainer and ObjectContainer, which both have single element.
|
||||||
@@ -69,10 +211,29 @@ void FreeContainer(ContainerHeader* header) {
|
|||||||
|
|
||||||
// And release underlying memory.
|
// And release underlying memory.
|
||||||
// TODO: atomic decrement in concurrent case.
|
// TODO: atomic decrement in concurrent case.
|
||||||
allocCount--;
|
#if CONCURRENT
|
||||||
|
#error "Atomic update of allocCount"
|
||||||
|
#endif
|
||||||
|
memoryState->allocCount--;
|
||||||
free(header);
|
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) {
|
ArenaContainer::ArenaContainer(uint32_t size) {
|
||||||
ArenaContainerHeader* header =
|
ArenaContainerHeader* header =
|
||||||
static_cast<ArenaContainerHeader*>(AllocContainer(size + sizeof(ArenaContainerHeader)));
|
static_cast<ArenaContainerHeader*>(AllocContainer(size + sizeof(ArenaContainerHeader)));
|
||||||
@@ -92,7 +253,7 @@ void ObjectContainer::Init(const TypeInfo* type_info) {
|
|||||||
// header->ref_count_ is zero initialized by AllocContainer().
|
// header->ref_count_ is zero initialized by AllocContainer().
|
||||||
SetMeta(GetPlace(), type_info);
|
SetMeta(GetPlace(), type_info);
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("object at %p\n", GetPlace());
|
fprintf(stderr, "object at %p\n", GetPlace());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +270,7 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
|
|||||||
GetPlace()->count_ = elements;
|
GetPlace()->count_ = elements;
|
||||||
SetMeta(GetPlace()->obj(), type_info);
|
SetMeta(GetPlace()->obj(), type_info);
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("array at %p\n", GetPlace());
|
fprintf(stderr, "array at %p\n", GetPlace());
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,16 +300,35 @@ ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, int count) {
|
|||||||
|
|
||||||
inline void AddRef(const ObjHeader* object) {
|
inline void AddRef(const ObjHeader* object) {
|
||||||
#if TRACE_MEMORY
|
#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
|
#endif
|
||||||
AddRef(object->container());
|
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) {
|
inline void ReleaseRef(const ObjHeader* object) {
|
||||||
#if TRACE_MEMORY
|
#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
|
#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());
|
Release(object->container());
|
||||||
|
#endif // USE_GC
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -162,36 +342,50 @@ void InitMemory() {
|
|||||||
==
|
==
|
||||||
offsetof(ObjHeader , container_offset_negative_),
|
offsetof(ObjHeader , container_offset_negative_),
|
||||||
"Layout mismatch");
|
"Layout mismatch");
|
||||||
|
RuntimeAssert(memoryState == nullptr, "memory state must be clear");
|
||||||
|
memoryState = new MemoryState();
|
||||||
// TODO: initialize heap here.
|
// TODO: initialize heap here.
|
||||||
allocCount = 0;
|
memoryState->allocCount = 0;
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
globalObjects = new std::vector<KRef*>();
|
memoryState->globalObjects = new KRefPtrList();
|
||||||
containers = new std::set<ContainerHeader*>();
|
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
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeinitMemory() {
|
void DeinitMemory() {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
// Free all global objects, to ensure no memory leaks happens.
|
// Free all global objects, to ensure no memory leaks happens.
|
||||||
for (auto location: *globalObjects) {
|
for (auto location: *memoryState->globalObjects) {
|
||||||
printf("Release global in *%p: %p\n", location, *location);
|
fprintf(stderr, "Release global in *%p: %p\n", location, *location);
|
||||||
UpdateGlobalRef(location, nullptr);
|
UpdateGlobalRef(location, nullptr);
|
||||||
}
|
}
|
||||||
delete globalObjects;
|
delete memoryState->globalObjects;
|
||||||
globalObjects = nullptr;
|
memoryState->globalObjects = nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (allocCount > 0) {
|
#if USE_GC
|
||||||
|
GarbageCollect();
|
||||||
|
#endif // USE_GC
|
||||||
|
|
||||||
|
if (memoryState->allocCount > 0) {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
// TODO: move out of TRACE_MEMORY, once exceptions free memory.
|
fprintf(stderr, "*** Memory leaks, leaked %d containers ***\n", memoryState->allocCount);
|
||||||
printf("*** Memory leaks, leaked %d containers ***\n", allocCount);
|
dumpReachable("", memoryState->containers);
|
||||||
for (auto container: *containers) {
|
delete memoryState->containers;
|
||||||
printf("Unfreed container %p, count = %d\n", container, container->ref_count_);
|
memoryState->containers = nullptr;
|
||||||
}
|
|
||||||
delete containers;
|
|
||||||
containers = nullptr;
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete memoryState;
|
||||||
|
memoryState = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we ignore all placement hints and always allocate heap space for new object.
|
// 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?
|
// TODO: locking or smth lock-free in MT case?
|
||||||
#endif
|
#endif
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
globalObjects->push_back(location);
|
memoryState->globalObjects->push_back(location);
|
||||||
#endif
|
#endif
|
||||||
RETURN_OBJ_RESULT();
|
RETURN_OBJ_RESULT();
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
@@ -254,7 +448,7 @@ OBJ_GETTER(InitInstance,
|
|||||||
|
|
||||||
void SetLocalRef(ObjHeader** location, const ObjHeader* object) {
|
void SetLocalRef(ObjHeader** location, const ObjHeader* object) {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("SetLocalRef *%p: %p\n", location, object);
|
fprintf(stderr, "SetLocalRef *%p: %p\n", location, object);
|
||||||
#endif
|
#endif
|
||||||
*const_cast<const ObjHeader**>(location) = object;
|
*const_cast<const ObjHeader**>(location) = object;
|
||||||
if (object != nullptr) {
|
if (object != nullptr) {
|
||||||
@@ -264,7 +458,7 @@ void SetLocalRef(ObjHeader** location, const ObjHeader* object) {
|
|||||||
|
|
||||||
void SetGlobalRef(ObjHeader** location, const ObjHeader* object) {
|
void SetGlobalRef(ObjHeader** location, const ObjHeader* object) {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("SetGlobalRef *%p: %p\n", location, object);
|
fprintf(stderr, "SetGlobalRef *%p: %p\n", location, object);
|
||||||
#endif
|
#endif
|
||||||
*const_cast<const ObjHeader**>(location) = object;
|
*const_cast<const ObjHeader**>(location) = object;
|
||||||
if (object != nullptr) {
|
if (object != nullptr) {
|
||||||
@@ -278,16 +472,16 @@ void SetGlobalRef(ObjHeader** location, const ObjHeader* object) {
|
|||||||
void UpdateLocalRef(ObjHeader** location, const ObjHeader* object) {
|
void UpdateLocalRef(ObjHeader** location, const ObjHeader* object) {
|
||||||
ObjHeader* old = *location;
|
ObjHeader* old = *location;
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("UpdateLocalRef *%p: %p -> %p\n", location, old, object);
|
fprintf(stderr, "UpdateLocalRef *%p: %p -> %p\n", location, old, object);
|
||||||
#endif
|
#endif
|
||||||
if (old != object) {
|
if (old != object) {
|
||||||
|
if (object != nullptr) {
|
||||||
|
AddRef(object);
|
||||||
|
}
|
||||||
*const_cast<const ObjHeader**>(location) = object;
|
*const_cast<const ObjHeader**>(location) = object;
|
||||||
if (old > reinterpret_cast<ObjHeader*>(1)) {
|
if (old > reinterpret_cast<ObjHeader*>(1)) {
|
||||||
ReleaseRef(old);
|
ReleaseRef(old);
|
||||||
}
|
}
|
||||||
if (object != nullptr) {
|
|
||||||
AddRef(object);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,7 +489,7 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) {
|
|||||||
#if CONCURRENT
|
#if CONCURRENT
|
||||||
ObjHeader* old = *location;
|
ObjHeader* old = *location;
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("UpdateGlobalRef *%p: %p -> %p\n", location, old, object);
|
fprintf(stderr, "UpdateGlobalRef *%p: %p -> %p\n", location, old, object);
|
||||||
#endif
|
#endif
|
||||||
if (old != object) {
|
if (old != object) {
|
||||||
if (object != nullptr) {
|
if (object != nullptr) {
|
||||||
@@ -320,7 +514,7 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) {
|
|||||||
|
|
||||||
void ReleaseLocalRefs(ObjHeader** start, int count) {
|
void ReleaseLocalRefs(ObjHeader** start, int count) {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("ReleaseLocalRefs %p .. %p\n", start, start + count);
|
fprintf(stderr, "ReleaseLocalRefs %p .. %p\n", start, start + count);
|
||||||
#endif
|
#endif
|
||||||
ObjHeader** current = start;
|
ObjHeader** current = start;
|
||||||
while (count-- > 0) {
|
while (count-- > 0) {
|
||||||
@@ -336,7 +530,7 @@ void ReleaseLocalRefs(ObjHeader** start, int count) {
|
|||||||
|
|
||||||
void ReleaseGlobalRefs(ObjHeader** start, int count) {
|
void ReleaseGlobalRefs(ObjHeader** start, int count) {
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
printf("ReleaseGlobalRefs %p .. %p\n", start, start + count);
|
fprintf(stderr, "ReleaseGlobalRefs %p .. %p\n", start, start + count);
|
||||||
#endif
|
#endif
|
||||||
#if CONCURRENT
|
#if CONCURRENT
|
||||||
ObjHeader** current = start;
|
ObjHeader** current = start;
|
||||||
@@ -364,4 +558,73 @@ void ReleaseGlobalRefs(ObjHeader** start, int count) {
|
|||||||
#endif
|
#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"
|
} // extern "C"
|
||||||
|
|||||||
@@ -26,10 +26,14 @@ typedef enum {
|
|||||||
CONTAINER_TAG_SHARED = 2,
|
CONTAINER_TAG_SHARED = 2,
|
||||||
// Container is no longer valid.
|
// Container is no longer valid.
|
||||||
CONTAINER_TAG_INVALID = 3,
|
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.
|
// Actual value to increment/decrement conatiner by. Tag is in lower bits.
|
||||||
CONTAINER_TAG_INCREMENT = 1 << 2,
|
CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
|
||||||
// Mask for container type.
|
// Mask for container type, disregard seen bit.
|
||||||
CONTAINER_TAG_MASK = (CONTAINER_TAG_INCREMENT - 1)
|
CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1)
|
||||||
} ContainerTag;
|
} ContainerTag;
|
||||||
|
|
||||||
// Could be made 64-bit for large memory configs.
|
// Could be made 64-bit for large memory configs.
|
||||||
@@ -127,8 +131,8 @@ inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) {
|
|||||||
|
|
||||||
void FreeContainer(ContainerHeader* header);
|
void FreeContainer(ContainerHeader* header);
|
||||||
|
|
||||||
// Those two operations are implemented by translator when storing references
|
// TODO: those two operations can be implemented by translator when storing
|
||||||
// to objects.
|
// reference to an object.
|
||||||
inline void AddRef(ContainerHeader* header) {
|
inline void AddRef(ContainerHeader* header) {
|
||||||
// Looking at container type we may want to skip AddRef() totally
|
// Looking at container type we may want to skip AddRef() totally
|
||||||
// (non-escaping stack objects, constant objects).
|
// (non-escaping stack objects, constant objects).
|
||||||
@@ -144,18 +148,25 @@ inline void AddRef(ContainerHeader* header) {
|
|||||||
case CONTAINER_TAG_INVALID:
|
case CONTAINER_TAG_INVALID:
|
||||||
RuntimeAssert(false, "trying to addref invalid container");
|
RuntimeAssert(false, "trying to addref invalid container");
|
||||||
break;
|
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) {
|
switch (header->ref_count_ & CONTAINER_TAG_MASK) {
|
||||||
case CONTAINER_TAG_NORMAL:
|
case CONTAINER_TAG_NORMAL:
|
||||||
if ((header->ref_count_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) {
|
if ((header->ref_count_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) {
|
||||||
FreeContainer(header);
|
FreeContainer(header);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_NOCOUNT:
|
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
|
// Note that shared containers have potentially subtle race, if object holds a
|
||||||
// reference to another object, stored in shorter living container. In this
|
// reference to another object, stored in shorter living container. In this
|
||||||
// case there's unlikely, but possible case, where one mutator takes reference,
|
// 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(
|
if (__sync_sub_and_fetch(
|
||||||
&header->ref_count_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) {
|
&header->ref_count_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) {
|
||||||
FreeContainer(header);
|
FreeContainer(header);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CONTAINER_TAG_INVALID:
|
case CONTAINER_TAG_INVALID:
|
||||||
RuntimeAssert(false, "trying to release invalid container");
|
RuntimeAssert(false, "trying to release invalid container");
|
||||||
break;
|
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.
|
// Class representing arbitrary placement container.
|
||||||
@@ -346,6 +363,8 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) RUNTIME_NOTH
|
|||||||
// Optimization: release all references in range.
|
// Optimization: release all references in range.
|
||||||
void ReleaseLocalRefs(ObjHeader** start, int count) RUNTIME_NOTHROW;
|
void ReleaseLocalRefs(ObjHeader** start, int count) RUNTIME_NOTHROW;
|
||||||
void ReleaseGlobalRefs(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
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user