Small MM refactor, ignore acyclic and frozen objects in cycle collector. (#2354)

This commit is contained in:
Nikolay Igotti
2018-11-22 17:38:51 +03:00
committed by GitHub
parent 901219ed5f
commit 70efeb1433
4 changed files with 188 additions and 101 deletions
@@ -6,27 +6,66 @@
package org.jetbrains.kotlin.backend.konan.llvm
import llvm.*
import org.jetbrains.kotlin.backend.common.ir.ir2string
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.computePrimitiveBinaryTypeOrNull
import org.jetbrains.kotlin.backend.konan.descriptors.*
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.name.FqName
internal class RTTIGenerator(override val context: Context) : ContextUtils {
private val acyclicCache = mutableMapOf<IrType, Boolean>()
private val safeAcyclicFieldTypes = setOf(
context.irBuiltIns.stringClass,
context.irBuiltIns.booleanClass, context.irBuiltIns.charClass,
context.irBuiltIns.byteClass, context.irBuiltIns.shortClass, context.irBuiltIns.intClass,
context.irBuiltIns.longClass,
context.irBuiltIns.floatClass,context.irBuiltIns.doubleClass) +
context.ir.symbols.primitiveArrays.values +
context.ir.symbols.unsignedArrays.values
// TODO: extend logic here by taking into account final acyclic classes.
private fun checkAcyclicFieldType(type: IrType): Boolean = acyclicCache.getOrPut(type) {
when {
type.isInterface() -> false
type.computePrimitiveBinaryTypeOrNull() != null -> true
else -> {
val classifier = type.classifierOrNull
(classifier != null && classifier in safeAcyclicFieldTypes)
}
}
}
private fun checkAcyclicClass(classDescriptor: ClassDescriptor): Boolean = when {
classDescriptor.symbol == context.ir.symbols.array -> false
classDescriptor.isArray -> true
context.llvmDeclarations.forClass(classDescriptor).fields.all { checkAcyclicFieldType(it.type) } -> true
else -> false
}
private fun flagsFromClass(classDescriptor: ClassDescriptor): Int {
var result = 0
if (classDescriptor.isFrozen)
result = result or TF_IMMUTABLE
// TODO: maybe perform deeper analysis to find surely acyclic types.
if (!classDescriptor.isInterface && !classDescriptor.isAbstract() && !classDescriptor.isAnnotationClass) {
if (checkAcyclicClass(classDescriptor)) {
result = result or TF_ACYCLIC
}
}
return result
}
private inner class FieldTableRecord(val nameSignature: LocalHash, val fieldOffset: Int) :
private inner class FieldTableRecord(val nameSignature: LocalHash, fieldOffset: Int) :
Struct(runtime.fieldTableRecordType, nameSignature, Int32(fieldOffset))
inner class MethodTableRecord(val nameSignature: LocalHash, val methodEntryPoint: ConstPointer?) :
inner class MethodTableRecord(val nameSignature: LocalHash, methodEntryPoint: ConstPointer?) :
Struct(runtime.methodTableRecordType, nameSignature, methodEntryPoint)
private inner class TypeInfo(
@@ -379,3 +418,4 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
}
private const val TF_IMMUTABLE = 1
private const val TF_ACYCLIC = 2
+97 -79
View File
@@ -545,8 +545,8 @@ inline void IncrementRC(ContainerHeader* container) {
container->incRefCount<Atomic>();
}
template <bool Atomic>
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
template <bool Atomic, bool UseCycleCollector>
inline void DecrementRC(ContainerHeader* container) {
if (container->decRefCount<Atomic>() == 0) {
FreeContainer(container);
}
@@ -561,18 +561,21 @@ inline uint32_t freeableSize(MemoryState* state) {
template <bool Atomic>
inline void IncrementRC(ContainerHeader* container) {
container->incRefCount<Atomic>();
container->setColor(CONTAINER_TAG_GC_BLACK);
container->setColorUnlessGreen(CONTAINER_TAG_GC_BLACK);
}
template <bool Atomic>
inline void DecrementRC(ContainerHeader* container, bool useCycleCollector) {
template <bool Atomic, bool UseCycleCollector>
inline void DecrementRC(ContainerHeader* container) {
if (container->decRefCount<Atomic>() == 0) {
FreeContainer(container);
} else if (!Atomic && useCycleCollector) { // Possible root.
// Do not use cycle collector for frozen objects, as we already detected possible cycles during
// freezing.
if (container->color() != CONTAINER_TAG_GC_PURPLE) {
container->setColor(CONTAINER_TAG_GC_PURPLE);
} else if (UseCycleCollector) { // Possible root.
RuntimeAssert(!Atomic, "Cycle collector shalln't be used with shared objects yet");
// We do not use cycle collector for frozen objects, as we already detected
// possible cycles during freezing.
// Also do not use cycle collector for provable acyclic objects.
int color = container->color();
if (color != CONTAINER_TAG_GC_PURPLE && color != CONTAINER_TAG_GC_GREEN) {
container->setColorAssertIfGreen(CONTAINER_TAG_GC_PURPLE);
if (!container->buffered()) {
container->setBuffered();
auto state = memoryState;
@@ -622,26 +625,34 @@ void dumpReachable(const char* prefix, const ContainerHeaderSet* roots) {
#if USE_GC
void MarkRoots(MemoryState*);
void DeleteCorpses(MemoryState*);
void ScanRoots(MemoryState*);
void CollectRoots(MemoryState*);
void Scan(ContainerHeader* container);
#if TRACE_MEMORY
const char* colorNames[] = {"BLACK", "GRAY", "WHITE", "PURPLE", "GREEN", "ORANGE", "RED"};
#endif
template<bool useColor>
void MarkGray(ContainerHeader* start) {
ContainerHeaderDeque toVisit;
toVisit.push_back(start);
toVisit.push_front(start);
while (!toVisit.empty()) {
auto container = toVisit.front();
auto* container = toVisit.front();
MEMORY_LOG("MarkGray visit %p [%s]\n", container, colorNames[container->color()]);
toVisit.pop_front();
if (useColor) {
if (container->color() == CONTAINER_TAG_GC_GRAY) continue;
} else {
if (container->marked()) continue;
}
if (useColor) {
container->setColor(CONTAINER_TAG_GC_GRAY);
int color = container->color();
if (color == CONTAINER_TAG_GC_GRAY) continue;
// If see an acyclic object not being garbage - ignore it. We must properly traverse garbage, although.
if (color == CONTAINER_TAG_GC_GREEN && container->refCount() != 0) {
continue;
}
// Only garbage green object could be recolored here.
container->setColorEvenIfGreen(CONTAINER_TAG_GC_GRAY);
} else {
if (container->marked()) continue;
container->mark();
}
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
@@ -656,29 +667,29 @@ void MarkGray(ContainerHeader* start) {
}
}
void Scan(ContainerHeader* container);
template<bool useColor>
void ScanBlack(ContainerHeader* start) {
ContainerHeaderDeque toVisit;
toVisit.push_back(start);
toVisit.push_front(start);
while (!toVisit.empty()) {
auto container = toVisit.front();
auto* container = toVisit.front();
MEMORY_LOG("ScanBlack visit %p [%s]\n", container, colorNames[container->color()]);
toVisit.pop_front();
if (useColor) {
container->setColor(CONTAINER_TAG_GC_BLACK);
if (container->color() == CONTAINER_TAG_GC_GREEN) continue;
container->setColorAssertIfGreen(CONTAINER_TAG_GC_BLACK);
} else {
container->unMark();
}
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
auto childContainer = ref->container();
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
if (!childContainer->shareable()) {
childContainer->incRefCount<false>();
if (useColor) {
if (childContainer->color() != CONTAINER_TAG_GC_BLACK)
int color = childContainer->color();
if (color != CONTAINER_TAG_GC_BLACK)
toVisit.push_front(childContainer);
} else {
if (childContainer->marked())
@@ -703,6 +714,8 @@ void MarkRoots(MemoryState* state) {
for (auto container : *(state->toFree)) {
if (isMarkedAsRemoved(container))
continue;
// Acyclic containers cannot be in this list.
RuntimeCheck(container->color() != CONTAINER_TAG_GC_GREEN, "Must not be green");
auto color = container->color();
auto rcIsZero = container->refCount() == 0;
if (color == CONTAINER_TAG_GC_PURPLE && !rcIsZero) {
@@ -710,6 +723,7 @@ void MarkRoots(MemoryState* state) {
state->roots->push_back(container);
} else {
container->resetBuffered();
RuntimeAssert(color != CONTAINER_TAG_GC_GREEN, "Must not be green");
if (color == CONTAINER_TAG_GC_BLACK && rcIsZero) {
scheduleDestroyContainer(state, container);
}
@@ -727,27 +741,34 @@ void CollectRoots(MemoryState* state) {
// Here we might free some objects and call deallocation hooks on them,
// which in turn might call DecrementRC and trigger new GC - forbid that.
state->gcSuspendCount++;
for (auto container : *(state->roots)) {
for (auto* container : *(state->roots)) {
container->resetBuffered();
CollectWhite(state, container);
}
state->gcSuspendCount--;
}
void Scan(ContainerHeader* container) {
if (container->color() != CONTAINER_TAG_GC_GRAY) return;
if (container->refCount() != 0) {
ScanBlack<true>(container);
return;
}
container->setColor(CONTAINER_TAG_GC_WHITE);
traverseContainerReferredObjects(container, [](ObjHeader* ref) {
auto childContainer = ref->container();
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
if (!childContainer->shareable()) {
Scan(childContainer);
}
});
void Scan(ContainerHeader* start) {
ContainerHeaderDeque toVisit;
toVisit.push_front(start);
while (!toVisit.empty()) {
auto* container = toVisit.front();
toVisit.pop_front();
if (container->color() != CONTAINER_TAG_GC_GRAY) continue;
if (container->refCount() != 0) {
ScanBlack<true>(container);
continue;
}
container->setColorAssertIfGreen(CONTAINER_TAG_GC_WHITE);
traverseContainerReferredObjects(container, [&toVisit](ObjHeader* ref) {
auto* childContainer = ref->container();
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
if (!childContainer->shareable()) {
toVisit.push_front(childContainer);
}
});
}
}
void CollectWhite(MemoryState* state, ContainerHeader* start) {
@@ -755,14 +776,14 @@ void CollectWhite(MemoryState* state, ContainerHeader* start) {
toVisit.push_back(start);
while (!toVisit.empty()) {
auto container = toVisit.front();
auto* container = toVisit.front();
toVisit.pop_front();
if (container->color() != CONTAINER_TAG_GC_WHITE || container->buffered()) continue;
container->setColor(CONTAINER_TAG_GC_BLACK);
container->setColorAssertIfGreen(CONTAINER_TAG_GC_BLACK);
traverseContainerObjectFields(container, [state, &toVisit](ObjHeader** location) {
auto ref = *location;
auto* ref = *location;
if (ref == nullptr) return;
auto childContainer = ref->container();
auto* childContainer = ref->container();
RuntimeAssert(!isArena(childContainer), "A reference to local object is encountered");
if (childContainer->shareable()) {
UpdateRef(location, nullptr);
@@ -784,19 +805,16 @@ inline void AddRef(ContainerHeader* header) {
case CONTAINER_TAG_PERMANENT:
break;
case CONTAINER_TAG_NORMAL:
IncrementRC<false>(header);
break;
case CONTAINER_TAG_FROZEN:
case CONTAINER_TAG_ATOMIC:
IncrementRC<true>(header);
IncrementRC</* Atomic = */ false>(header);
break;
/* case CONTAINER_TAG_FROZEN: case CONTAINER_TAG_ATOMIC: */
default:
RuntimeAssert(false, "unknown container type");
IncrementRC</* Atomic = */ true>(header);
break;
}
}
inline void Release(ContainerHeader* header, bool useCycleCollector) {
inline void Release(ContainerHeader* header) {
// Looking at container type we may want to skip Release() totally
// (non-escaping stack objects, constant objects).
switch (header->refCount_ & CONTAINER_TAG_MASK) {
@@ -804,14 +822,11 @@ inline void Release(ContainerHeader* header, bool useCycleCollector) {
case CONTAINER_TAG_STACK:
break;
case CONTAINER_TAG_NORMAL:
DecrementRC<false>(header, useCycleCollector);
break;
case CONTAINER_TAG_FROZEN:
case CONTAINER_TAG_ATOMIC:
DecrementRC<true>(header, useCycleCollector);
DecrementRC</* Atomic = */ false, /* UseCyclicCollector = */ true>(header);
break;
/* case CONTAINER_TAG_FROZEN: case CONTAINER_TAG_ATOMIC: */
default:
RuntimeAssert(false, "unknown container type");
DecrementRC</* Atomic = */ true, /* UseCyclicCollector = */ false>(header);
break;
}
}
@@ -951,7 +966,7 @@ void FreeContainer(ContainerHeader* container) {
// And release underlying memory.
if (isFreeable(container)) {
container->setColor(CONTAINER_TAG_GC_BLACK);
container->setColorEvenIfGreen(CONTAINER_TAG_GC_BLACK);
if (!container->buffered())
scheduleDestroyContainer(state, container);
}
@@ -1091,9 +1106,7 @@ inline void AddRef(const ObjHeader* object) {
inline void ReleaseRef(const ObjHeader* object) {
MEMORY_LOG("ReleaseRef on %p in %p\n", object, object->container())
// Use cycle collector only for objects having object fields, or if container is multiobject.
auto container = object->container();
Release(container, (object->type_info()->objOffsetsCount_ > 0) || (container->objectCount() > 1));
Release(object->container());
}
void AddRefFromAssociatedObject(const ObjHeader* object) {
@@ -1533,17 +1546,22 @@ OBJ_GETTER(AdoptStablePointer, KNativePtr pointer) {
}
#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 (!child->shareable() && (visited->find(child) == visited->end())) {
result |= hasExternalRefs(child, visited);
}
});
return result;
bool hasExternalRefs(ContainerHeader* start, ContainerHeaderSet* visited) {
ContainerHeaderDeque toVisit;
toVisit.push_back(start);
while (!toVisit.empty()) {
auto* container = toVisit.front();
toVisit.pop_front();
visited->insert(container);
if (container->refCount() != 0) return true;
traverseContainerReferredObjects(container, [&toVisit, visited](ObjHeader* ref) {
auto* child = ref->container();
if (!child->shareable() && (visited->count(child) == 0)) {
toVisit.push_front(child);
}
});
}
return false;
}
#endif
@@ -1551,7 +1569,7 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
#if USE_GC
if (root != nullptr) {
auto state = memoryState;
auto container = root->container();
auto* container = root->container();
if (container->frozen())
// We assume, that frozen objects can be safely passed and are already removed
@@ -1572,12 +1590,12 @@ bool ClearSubgraphReferences(ObjHeader* root, bool checked) {
}
}
// TODO: not very effecient traversal.
// TODO: not very efficient traversal.
for (auto it = state->toFree->begin(); it != state->toFree->end(); ++it) {
auto container = *it;
if (visited.find(container) != visited.end()) {
if (visited.count(container) != 0) {
container->resetBuffered();
container->setColor(CONTAINER_TAG_GC_BLACK);
container->setColorAssertIfGreen(CONTAINER_TAG_GC_BLACK);
*it = markAsRemoved(container);
}
}
@@ -1643,7 +1661,7 @@ void freezeAcyclic(ContainerHeader* rootContainer) {
queue.pop_front();
current->unMark();
current->resetBuffered();
current->setColor(CONTAINER_TAG_GC_BLACK);
current->setColorUnlessGreen(CONTAINER_TAG_GC_BLACK);
// Note, that once object is frozen, it could be concurrently accessed, so
// color and similar attributes shall not be used.
current->freeze();
@@ -1712,7 +1730,7 @@ void freezeCyclic(ContainerHeader* rootContainer, const KStdVector<ContainerHead
// Freeze component.
for (auto* container : component) {
container->resetBuffered();
container->setColor(CONTAINER_TAG_GC_BLACK);
container->setColorUnlessGreen(CONTAINER_TAG_GC_BLACK);
// Note, that once object is frozen, it could be concurrently accessed, so
// color and similar attributes shall not be used.
container->freeze();
+37 -9
View File
@@ -30,11 +30,12 @@ typedef enum {
CONTAINER_TAG_FROZEN = 1 | 1, // shareable
// Stack container, no need to free, children cleanup still shall be there.
CONTAINER_TAG_STACK = 2,
// Those container tags shall not be refcounted.
// Permanent container, cannot refer to non-permanent containers, so no need to cleanup those.
CONTAINER_TAG_PERMANENT = 3 | 1, // shareable
// Atomic container, reference counter is atomically updated.
CONTAINER_TAG_ATOMIC = 5 | 1, // shareable
// Those container tags shall not be refcounted.
// Permanent container, cannot refer to non-permanent containers, so no need to cleanup those.
// Please check isFreeable() if changing the numeric value.
CONTAINER_TAG_PERMANENT = 7 | 1, // shareable
// Shift to get actual counter.
CONTAINER_TAG_SHIFT = 3,
// Actual value to increment/decrement container by. Tag is in lower bits.
@@ -44,19 +45,31 @@ typedef enum {
// Those bit masks are applied to objectCount_ field.
// Shift to get actual object count.
CONTAINER_TAG_GC_SHIFT = 5,
CONTAINER_TAG_GC_SHIFT = 6,
CONTAINER_TAG_GC_INCREMENT = 1 << CONTAINER_TAG_GC_SHIFT,
// Color mask of a container.
CONTAINER_TAG_GC_COLOR_MASK = (1 << 2) - 1,
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 << 2,
CONTAINER_TAG_GC_BUFFERED = 1 << 3,
CONTAINER_TAG_GC_SEEN = 1 << 4
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)
} ContainerTag;
typedef uint32_t container_size_t;
@@ -152,10 +165,23 @@ struct ContainerHeader {
return objectCount_ & CONTAINER_TAG_GC_COLOR_MASK;
}
inline void setColor(unsigned color) {
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;
}
@@ -281,6 +307,8 @@ class Container {
// Take into account typeInfo's immutability for ARC strategy.
if ((type_info->flags_ & TF_IMMUTABLE) != 0)
header_->refCount_ |= CONTAINER_TAG_FROZEN;
if ((type_info->flags_ & TF_ACYCLIC) != 0)
header_->setColorEvenIfGreen(CONTAINER_TAG_GC_GREEN);
}
};
+11 -10
View File
@@ -45,20 +45,21 @@ struct FieldTableRecord {
// Type for runtime representation of Konan object.
// Keep in sync with runtimeTypeMap in RTTIGenerator.
enum Konan_RuntimeType {
RT_INVALID = 0,
RT_OBJECT = 1,
RT_INT8 = 2,
RT_INT16 = 3,
RT_INT32 = 4,
RT_INT64 = 5,
RT_FLOAT32 = 6,
RT_FLOAT64 = 7,
RT_INVALID = 0,
RT_OBJECT = 1,
RT_INT8 = 2,
RT_INT16 = 3,
RT_INT32 = 4,
RT_INT64 = 5,
RT_FLOAT32 = 6,
RT_FLOAT64 = 7,
RT_NATIVE_PTR = 8,
RT_BOOLEAN = 9
RT_BOOLEAN = 9
};
enum Konan_TypeFlags {
TF_IMMUTABLE = 1 << 0
TF_IMMUTABLE = 1 << 0,
TF_ACYCLIC = 1 << 1
};
enum Konan_MetaFlags {