Implemented workers TODO about subrgaph transfer
Implemented check that subgraph of objects can be safely transferred from one worker to another by running trial deletion on the subgraph.
This commit is contained in:
+103
-58
@@ -19,6 +19,10 @@
|
|||||||
|
|
||||||
#include <cstddef> // for offsetof
|
#include <cstddef> // for offsetof
|
||||||
|
|
||||||
|
#ifndef KONAN_NO_THREADS
|
||||||
|
#include <pthread.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "Alloc.h"
|
#include "Alloc.h"
|
||||||
#include "Assert.h"
|
#include "Assert.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
@@ -48,6 +52,14 @@ constexpr container_size_t kObjectAlignment = 8;
|
|||||||
#define MEMORY_LOG(...)
|
#define MEMORY_LOG(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
inline int atomicAdd(int* where, int what) {
|
||||||
|
#ifndef KONAN_NO_THREADS
|
||||||
|
return __sync_add_and_fetch(where, what);
|
||||||
|
#else
|
||||||
|
return *where += what;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if USE_GC
|
#if USE_GC
|
||||||
// Collection threshold default (collect after having so many elements in the
|
// Collection threshold default (collect after having so many elements in the
|
||||||
// release candidates set). Better be a prime number.
|
// release candidates set). Better be a prime number.
|
||||||
@@ -68,9 +80,11 @@ struct FrameOverlay {
|
|||||||
ArenaContainer* arena;
|
ArenaContainer* arena;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Current number of allocated containers.
|
||||||
|
int allocCount = 0;
|
||||||
|
int aliveMemoryStatesCount = 0;
|
||||||
|
|
||||||
struct MemoryState {
|
struct MemoryState {
|
||||||
// Current number of allocated containers.
|
|
||||||
int allocCount = 0;
|
|
||||||
|
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
// List of all global objects addresses.
|
// List of all global objects addresses.
|
||||||
@@ -210,7 +224,7 @@ inline void processFinalizerQueue(MemoryState* state) {
|
|||||||
runDeallocationHooks(container);
|
runDeallocationHooks(container);
|
||||||
}
|
}
|
||||||
konanFreeMemory(container);
|
konanFreeMemory(container);
|
||||||
state->allocCount--;
|
atomicAdd(&allocCount, -1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -224,7 +238,7 @@ inline void scheduleDestroyContainer(
|
|||||||
processFinalizerQueue(state);
|
processFinalizerQueue(state);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
state->allocCount--;
|
atomicAdd(&allocCount, -1);
|
||||||
konanFreeMemory(header);
|
konanFreeMemory(header);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -334,9 +348,54 @@ void MarkRoots(MemoryState*);
|
|||||||
void DeleteCorpses(MemoryState*);
|
void DeleteCorpses(MemoryState*);
|
||||||
void ScanRoots(MemoryState*);
|
void ScanRoots(MemoryState*);
|
||||||
void CollectRoots(MemoryState*);
|
void CollectRoots(MemoryState*);
|
||||||
void MarkGray(ContainerHeader* container);
|
|
||||||
|
template<bool useColor>
|
||||||
|
void MarkGray(ContainerHeader* container) {
|
||||||
|
if (useColor) {
|
||||||
|
if (container->color() == CONTAINER_TAG_GC_GRAY) return;
|
||||||
|
} else {
|
||||||
|
if (container->marked()) return;
|
||||||
|
}
|
||||||
|
if (useColor) {
|
||||||
|
container->setColor(CONTAINER_TAG_GC_GRAY);
|
||||||
|
} else {
|
||||||
|
container->mark();
|
||||||
|
}
|
||||||
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
|
auto childContainer = ref->container();
|
||||||
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
|
if (!isPermanent(childContainer)) {
|
||||||
|
childContainer->decRefCount();
|
||||||
|
MarkGray<useColor>(childContainer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Scan(ContainerHeader* container);
|
void Scan(ContainerHeader* container);
|
||||||
void ScanBlack(ContainerHeader* container);
|
|
||||||
|
template<bool useColor>
|
||||||
|
void ScanBlack(ContainerHeader* container) {
|
||||||
|
if (useColor) {
|
||||||
|
container->setColor(CONTAINER_TAG_GC_BLACK);
|
||||||
|
} else {
|
||||||
|
container->unMark();
|
||||||
|
}
|
||||||
|
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
||||||
|
auto childContainer = ref->container();
|
||||||
|
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
||||||
|
if (!isPermanent(childContainer)) {
|
||||||
|
childContainer->incRefCount();
|
||||||
|
if (useColor) {
|
||||||
|
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
||||||
|
ScanBlack<useColor>(childContainer);
|
||||||
|
} else {
|
||||||
|
if (childContainer->marked())
|
||||||
|
ScanBlack<useColor>(childContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void CollectWhite(MemoryState*, ContainerHeader* container);
|
void CollectWhite(MemoryState*, ContainerHeader* container);
|
||||||
|
|
||||||
void CollectCycles(MemoryState* state) {
|
void CollectCycles(MemoryState* state) {
|
||||||
@@ -354,7 +413,7 @@ void MarkRoots(MemoryState* state) {
|
|||||||
auto color = container->color();
|
auto color = container->color();
|
||||||
auto rcIsZero = container->refCount() == 0;
|
auto rcIsZero = container->refCount() == 0;
|
||||||
if (color == CONTAINER_TAG_GC_PURPLE && !rcIsZero) {
|
if (color == CONTAINER_TAG_GC_PURPLE && !rcIsZero) {
|
||||||
MarkGray(container);
|
MarkGray<true>(container);
|
||||||
state->roots->push_back(container);
|
state->roots->push_back(container);
|
||||||
} else {
|
} else {
|
||||||
container->resetBuffered();
|
container->resetBuffered();
|
||||||
@@ -378,23 +437,10 @@ void CollectRoots(MemoryState* state) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MarkGray(ContainerHeader* container) {
|
|
||||||
if (container->color() == CONTAINER_TAG_GC_GRAY) return;
|
|
||||||
container->setColor(CONTAINER_TAG_GC_GRAY);
|
|
||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
|
||||||
auto childContainer = ref->container();
|
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
|
||||||
if (!isPermanent(childContainer)) {
|
|
||||||
childContainer->decRefCount();
|
|
||||||
MarkGray(childContainer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Scan(ContainerHeader* container) {
|
void Scan(ContainerHeader* container) {
|
||||||
if (container->color() != CONTAINER_TAG_GC_GRAY) return;
|
if (container->color() != CONTAINER_TAG_GC_GRAY) return;
|
||||||
if (container->refCount() != 0) {
|
if (container->refCount() != 0) {
|
||||||
ScanBlack(container);
|
ScanBlack<true>(container);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
container->setColor(CONTAINER_TAG_GC_WHITE);
|
container->setColor(CONTAINER_TAG_GC_WHITE);
|
||||||
@@ -407,19 +453,6 @@ void Scan(ContainerHeader* container) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScanBlack(ContainerHeader* container) {
|
|
||||||
container->setColor(CONTAINER_TAG_GC_BLACK);
|
|
||||||
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
|
|
||||||
auto childContainer = ref->container();
|
|
||||||
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
|
|
||||||
if (!isPermanent(childContainer)) {
|
|
||||||
childContainer->incRefCount();
|
|
||||||
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
|
|
||||||
ScanBlack(childContainer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollectWhite(MemoryState* state, ContainerHeader* container) {
|
void CollectWhite(MemoryState* state, ContainerHeader* container) {
|
||||||
if (container->color() != CONTAINER_TAG_GC_WHITE
|
if (container->color() != CONTAINER_TAG_GC_WHITE
|
||||||
|| container->buffered())
|
|| container->buffered())
|
||||||
@@ -495,7 +528,7 @@ ContainerHeader* AllocContainer(size_t size) {
|
|||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
state->containers->insert(result);
|
state->containers->insert(result);
|
||||||
#endif
|
#endif
|
||||||
state->allocCount++;
|
atomicAdd(&allocCount, 1);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -678,7 +711,6 @@ MemoryState* InitMemory() {
|
|||||||
RuntimeAssert(memoryState == nullptr, "memory state must be clear");
|
RuntimeAssert(memoryState == nullptr, "memory state must be clear");
|
||||||
memoryState = konanConstructInstance<MemoryState>();
|
memoryState = konanConstructInstance<MemoryState>();
|
||||||
// TODO: initialize heap here.
|
// TODO: initialize heap here.
|
||||||
memoryState->allocCount = 0;
|
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
memoryState->globalObjects = konanConstructInstance<KRefPtrList>();
|
memoryState->globalObjects = konanConstructInstance<KRefPtrList>();
|
||||||
memoryState->containers = konanConstructInstance<ContainerHeaderSet>();
|
memoryState->containers = konanConstructInstance<ContainerHeaderSet>();
|
||||||
@@ -691,6 +723,7 @@ MemoryState* InitMemory() {
|
|||||||
initThreshold(memoryState, kGcThreshold);
|
initThreshold(memoryState, kGcThreshold);
|
||||||
memoryState->gcSuspendCount = 0;
|
memoryState->gcSuspendCount = 0;
|
||||||
#endif
|
#endif
|
||||||
|
atomicAdd(&aliveMemoryStatesCount, 1);
|
||||||
return memoryState;
|
return memoryState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -716,16 +749,19 @@ void DeinitMemory(MemoryState* memoryState) {
|
|||||||
|
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
|
|
||||||
|
bool lastMemoryState = atomicAdd(&aliveMemoryStatesCount, -1) == 0;
|
||||||
|
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
if (memoryState->allocCount > 0) {
|
if (allocCount > 0) {
|
||||||
MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n",
|
MEMORY_LOG("*** Memory leaks, leaked %d containers ***\n",
|
||||||
memoryState->allocCount);
|
allocCount);
|
||||||
dumpReachable("", memoryState->containers);
|
dumpReachable("", memoryState->containers);
|
||||||
}
|
}
|
||||||
konanDestructInstance(memoryState->containers);
|
konanDestructInstance(memoryState->containers);
|
||||||
memoryState->containers = nullptr;
|
memoryState->containers = nullptr;
|
||||||
#else
|
#else
|
||||||
RuntimeAssert(memoryState->allocCount == 0, "Memory leaks found");
|
if (lastMemoryState)
|
||||||
|
RuntimeAssert(allocCount == 0, "Memory leaks found");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
konanFreeMemory(memoryState);
|
konanFreeMemory(memoryState);
|
||||||
@@ -972,33 +1008,43 @@ OBJ_GETTER(AdoptStablePointer, KNativePtr pointer) {
|
|||||||
return ref;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if USE_GC
|
||||||
|
|
||||||
|
bool hasExternalRefs(ContainerHeader* container, ContainerHeaderSet* visited) {
|
||||||
|
visited->insert(container);
|
||||||
|
bool result = container->refCount() != 0;
|
||||||
|
traverseContainerReferredObjects(container, [&result, visited](ObjHeader* ref) {
|
||||||
|
auto child = ref->container();
|
||||||
|
if (!isPermanent(child) && (visited->find(child) == visited->end())) {
|
||||||
|
result |= hasExternalRefs(child, visited);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
||||||
#if USE_GC
|
#if USE_GC
|
||||||
if (root != nullptr) {
|
if (root != nullptr) {
|
||||||
auto state = memoryState;
|
auto state = memoryState;
|
||||||
|
|
||||||
auto container = root->container();
|
auto container = root->container();
|
||||||
ContainerHeaderList todo;
|
|
||||||
ContainerHeaderSet subgraph;
|
ContainerHeaderSet visited;
|
||||||
todo.push_back(container);
|
if (!checked) {
|
||||||
while (todo.size() > 0) {
|
hasExternalRefs(container, &visited);
|
||||||
auto header = todo.back();
|
} else {
|
||||||
todo.pop_back();
|
container->decRefCount();
|
||||||
if (subgraph.count(header) != 0)
|
MarkGray<false>(container);
|
||||||
continue;
|
auto bad = hasExternalRefs(container, &visited);
|
||||||
subgraph.insert(header);
|
ScanBlack<false>(container);
|
||||||
MEMORY_LOG("Calling removeFreeable from ClearSubgraphReferences\n");
|
container->incRefCount();
|
||||||
traverseContainerReferredObjects(header, [&todo](ObjHeader* ref) {
|
if (bad) return false;
|
||||||
auto child = ref->container();
|
|
||||||
RuntimeAssert(!isArena(child), "A reference to local object is encountered");
|
|
||||||
if (!isPermanent(child)) {
|
|
||||||
todo.push_back(child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) {
|
for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) {
|
||||||
auto container = *it;
|
auto container = *it;
|
||||||
if (subgraph.find(container) != subgraph.end()) {
|
if (visited.find(container) != visited.end()) {
|
||||||
container->resetBuffered();
|
container->resetBuffered();
|
||||||
container->setColor(CONTAINER_TAG_GC_BLACK);
|
container->setColor(CONTAINER_TAG_GC_BLACK);
|
||||||
*it = reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1);
|
*it = reinterpret_cast<ContainerHeader*>(reinterpret_cast<uintptr_t>(container) | 1);
|
||||||
@@ -1006,7 +1052,6 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_GC
|
#endif // USE_GC
|
||||||
// TODO: perform trial deletion starting from this root, if in checked mode.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,16 +43,17 @@ typedef enum {
|
|||||||
|
|
||||||
// Those bit masks are applied to objectCount_ field.
|
// Those bit masks are applied to objectCount_ field.
|
||||||
// Shift to get actual object count.
|
// Shift to get actual object count.
|
||||||
CONTAINER_TAG_GC_SHIFT = 3,
|
CONTAINER_TAG_GC_SHIFT = 4,
|
||||||
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
|
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
|
||||||
// Color of a container.
|
// Color of a container.
|
||||||
CONTAINER_TAG_GC_COLOR_MASK = ((CONTAINER_TAG_GC_INCREMENT >> 1) - 1),
|
CONTAINER_TAG_GC_COLOR_MASK = ((CONTAINER_TAG_GC_INCREMENT >> 2) - 1),
|
||||||
// Colors.
|
// Colors.
|
||||||
CONTAINER_TAG_GC_BLACK = 0,
|
CONTAINER_TAG_GC_BLACK = 0,
|
||||||
CONTAINER_TAG_GC_GRAY = 1,
|
CONTAINER_TAG_GC_GRAY = 1,
|
||||||
CONTAINER_TAG_GC_WHITE = 2,
|
CONTAINER_TAG_GC_WHITE = 2,
|
||||||
CONTAINER_TAG_GC_PURPLE = 3,
|
CONTAINER_TAG_GC_PURPLE = 3,
|
||||||
CONTAINER_TAG_GC_BUFFERED = 4
|
CONTAINER_TAG_GC_MARKED = 4,
|
||||||
|
CONTAINER_TAG_GC_BUFFERED = 8
|
||||||
} ContainerTag;
|
} ContainerTag;
|
||||||
|
|
||||||
typedef uint32_t container_offset_t;
|
typedef uint32_t container_offset_t;
|
||||||
@@ -100,6 +101,15 @@ struct ContainerHeader {
|
|||||||
inline void resetBuffered() {
|
inline void resetBuffered() {
|
||||||
objectCount_ &= ~CONTAINER_TAG_GC_BUFFERED;
|
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;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ArrayHeader;
|
struct ArrayHeader;
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ KNativePtr transfer(KRef object, KInt mode) {
|
|||||||
case CHECKED:
|
case CHECKED:
|
||||||
case UNCHECKED:
|
case UNCHECKED:
|
||||||
if (!ClearSubgraphReferences(object, mode == CHECKED)) {
|
if (!ClearSubgraphReferences(object, mode == CHECKED)) {
|
||||||
|
// Release reference to the object, as it is not being managed by ObjHolder.
|
||||||
|
UpdateRef(&object, nullptr);
|
||||||
ThrowWorkerInvalidState();
|
ThrowWorkerInvalidState();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user