Frame-local allocations. (#193)

This commit is contained in:
Nikolay Igotti
2017-01-24 17:16:54 +03:00
committed by GitHub
parent afeb1b2d55
commit 66293cb89f
11 changed files with 400 additions and 206 deletions
@@ -20,6 +20,7 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
var returnSlot: LLVMValueRef? = null var returnSlot: LLVMValueRef? = null
var slotsPhi: LLVMValueRef? = null var slotsPhi: LLVMValueRef? = null
var slotCount = 0 var slotCount = 0
var localAllocs = 0
fun prologue(descriptor: FunctionDescriptor) { fun prologue(descriptor: FunctionDescriptor) {
prologue(llvmFunction(descriptor), prologue(llvmFunction(descriptor),
@@ -45,16 +46,18 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
cleanupLandingpad = LLVMAppendBasicBlock(function, "cleanup_landingpad")!! cleanupLandingpad = LLVMAppendBasicBlock(function, "cleanup_landingpad")!!
positionAtEnd(entryBb!!) positionAtEnd(entryBb!!)
slotsPhi = phi(kObjHeaderPtrPtr) slotsPhi = phi(kObjHeaderPtrPtr)
slotCount = 0 // First slot can be assigned to keep pointer to frame local arena.
slotCount = 1
localAllocs = 0
} }
fun epilogue() { fun epilogue() {
appendingTo(prologueBb!!) { appendingTo(prologueBb!!) {
val slots = if (slotCount > 0) val slots = if (needSlots)
LLVMBuildArrayAlloca(builder, kObjHeaderPtr, Int32(slotCount).llvm, "")!! LLVMBuildArrayAlloca(builder, kObjHeaderPtr, Int32(slotCount).llvm, "")!!
else else
kNullObjHeaderPtrPtr kNullObjHeaderPtrPtr
if (slotCount > 0) { if (needSlots) {
// Zero-init slots. // Zero-init slots.
val slotsMem = bitcast(kInt8Ptr, slots) val slotsMem = bitcast(kInt8Ptr, slots)
val pointerSize = LLVMABISizeOfType(llvmTargetData, kObjHeaderPtr).toInt() val pointerSize = LLVMABISizeOfType(llvmTargetData, kObjHeaderPtr).toInt()
@@ -102,9 +105,14 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
slotsPhi = null slotsPhi = null
} }
fun releaseVars() { private val needSlots: Boolean
if (slotCount > 0) { get() {
call(context.llvm.releaseLocalRefsFunction, return slotCount > 1 || localAllocs > 0
}
private fun releaseVars() {
if (needSlots) {
call(context.llvm.leaveFrameFunction,
listOf(slotsPhi!!, Int32(slotCount).llvm)) listOf(slotsPhi!!, Int32(slotCount).llvm))
} }
} }
@@ -148,6 +156,36 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
return LLVMBuildAlloca(builder, type, name)!! return LLVMBuildAlloca(builder, type, name)!!
} }
} }
// Return object slot (ab)used for arena matching given allocation.
private fun arenaSlot() : LLVMValueRef {
return gep(slotsPhi!!, Int32(0).llvm)
}
fun allocInstance(typeInfo: LLVMValueRef, hint: Int) : LLVMValueRef {
if (hint == SCOPE_FRAME) {
val aux = arenaSlot()
localAllocs++
return call(context.llvm.arenaAllocInstanceFunction, listOf(typeInfo, aux))
} else {
val slot = vars.createAnonymousSlot()
return call(context.llvm.allocInstanceFunction, listOf(typeInfo, slot))
}
}
fun allocArray(
typeInfo: LLVMValueRef, hint: Int, count: LLVMValueRef) : LLVMValueRef {
if (hint == SCOPE_FRAME) {
val aux = arenaSlot()
localAllocs++
return call(context.llvm.arenaAllocArrayFunction, listOf(typeInfo, count, aux))
} else {
val slot = vars.createAnonymousSlot()
return call(context.llvm.allocArrayFunction, listOf(typeInfo, count, slot))
}
}
fun load(value: LLVMValueRef, name: String = ""): LLVMValueRef { fun load(value: LLVMValueRef, name: String = ""): LLVMValueRef {
val result = LLVMBuildLoad(builder, value, name)!! val result = LLVMBuildLoad(builder, value, name)!!
// Use loadSlot() API for that. // Use loadSlot() API for that.
@@ -24,6 +24,12 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
// Different scopes/lifetimes of an object, computed by escape analysis.
const val SCOPE_FRAME = 0
const val SCOPE_GLOBAL = 1
const val SCOPE_ARENA = 2
const val SCOPE_PERMANENT = 3
/** /**
* Provides utility methods to the implementer. * Provides utility methods to the implementer.
*/ */
@@ -263,13 +269,15 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
var globalInitIndex:Int = 0 var globalInitIndex:Int = 0
val allocInstanceFunction = importRtFunction("AllocInstance") val allocInstanceFunction = importRtFunction("AllocInstance")
val initInstanceFunction = importRtFunction("InitInstance") val arenaAllocInstanceFunction = importRtFunction("ArenaAllocInstance")
val allocArrayFunction = importRtFunction("AllocArrayInstance") val allocArrayFunction = importRtFunction("AllocArrayInstance")
val arenaAllocArrayFunction = importRtFunction("ArenaAllocArrayInstance")
val initInstanceFunction = importRtFunction("InitInstance")
val setLocalRefFunction = importRtFunction("SetLocalRef") val setLocalRefFunction = importRtFunction("SetLocalRef")
val setGlobalRefFunction = importRtFunction("SetGlobalRef") val setGlobalRefFunction = importRtFunction("SetGlobalRef")
val updateLocalRefFunction = importRtFunction("UpdateLocalRef") val updateLocalRefFunction = importRtFunction("UpdateLocalRef")
val updateGlobalRefFunction = importRtFunction("UpdateGlobalRef") val updateGlobalRefFunction = importRtFunction("UpdateGlobalRef")
val releaseLocalRefsFunction = importRtFunction("ReleaseLocalRefs") val leaveFrameFunction = importRtFunction("LeaveFrame")
val setArrayFunction = importRtFunction("Kotlin_Array_set") val setArrayFunction = importRtFunction("Kotlin_Array_set")
val copyImplArrayFunction = importRtFunction("Kotlin_Array_copyImpl") val copyImplArrayFunction = importRtFunction("Kotlin_Array_copyImpl")
val lookupFieldOffset = importRtFunction("LookupFieldOffset") val lookupFieldOffset = importRtFunction("LookupFieldOffset")
@@ -0,0 +1,36 @@
package org.jetbrains.kotlin.backend.konan.llvm
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
// Analysis we're implementing here is as following.
// We build graph with the following nodes:
// * allocation set, keeping tuple of [local, ctor call, owner function], AS
// * local store set, keeping pair [local, stored], LSS
// * field store set, keeping tuple [local, stored], FSS
// * global store set, [local, stored], GSS
// Function we're trying to compute is the following:
// for each element of AS, could it be referred by someone, whose value is
// alive on return from function, where element was allocated.
// Each element in RS is associated with few elements in AS, which it could refer to.
// TODO: exact algorithm TBD.
internal class EscapeAnalyzerVisitor(val allocHints: MutableMap<IrCall, Int>) : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitModuleFragment(module: IrModuleFragment) {
module.acceptChildrenVoid(this)
}
}
fun prepareAllocHints(irModule: IrModuleFragment, allocHints: MutableMap<IrCall, Int>) {
assert(allocHints.size == 0)
irModule.acceptVoid(EscapeAnalyzerVisitor(allocHints))
}
@@ -172,6 +172,7 @@ interface CodeContext {
internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid { internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid {
val codegen = CodeGenerator(context) val codegen = CodeGenerator(context)
val allocHints = mutableMapOf<IrCall, Int>()
//-------------------------------------------------------------------------// //-------------------------------------------------------------------------//
@@ -240,6 +241,8 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
override fun visitModuleFragment(module: IrModuleFragment) { override fun visitModuleFragment(module: IrModuleFragment) {
context.log("visitModule : ${ir2string(module)}") context.log("visitModule : ${ir2string(module)}")
prepareAllocHints(module, allocHints)
module.acceptChildrenVoid(this) module.acceptChildrenVoid(this)
appendLlvmUsed(context.llvm.usedFunctions) appendLlvmUsed(context.llvm.usedFunctions)
appendStaticInitializers(context.llvm.staticInitializers) appendStaticInitializers(context.llvm.staticInitializers)
@@ -691,10 +694,9 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
codegen.positionAtEnd(bbInit) codegen.positionAtEnd(bbInit)
val typeInfo = codegen.typeInfoValue(value.descriptor) val typeInfo = codegen.typeInfoValue(value.descriptor)
val allocHint = Int32(1).llvm
val initFunction = value.descriptor.constructors.first { it.valueParameters.size == 0 } val initFunction = value.descriptor.constructors.first { it.valueParameters.size == 0 }
val ctor = codegen.llvmFunction(initFunction) val ctor = codegen.llvmFunction(initFunction)
val args = listOf(objectPtr, typeInfo, allocHint, ctor) val args = listOf(objectPtr, typeInfo, ctor)
val newValue = call(context.llvm.initInstanceFunction, args) val newValue = call(context.llvm.initInstanceFunction, args)
val bbInitResult = codegen.currentBlock val bbInitResult = codegen.currentBlock
codegen.br(bbExit) codegen.br(bbExit)
@@ -802,9 +804,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
codegen.plus(sum, size!!) codegen.plus(sum, size!!)
} }
val typeInfo = codegen.typeInfoValue(value.type)!! val array = codegen.allocArray(codegen.typeInfoValue(value.type)!!, SCOPE_GLOBAL, finalLength)
val arrayCreationArgs = listOf(typeInfo, kImmInt32One, finalLength)
val array = call(context.llvm.allocArrayFunction, arrayCreationArgs)
elements.fold(kImmZero) { sum, (exp, size, isArray) -> elements.fold(kImmZero) { sum, (exp, size, isArray) ->
if (!isArray) { if (!isArray) {
call(context.llvm.setArrayFunction, listOf(array, sum, exp)) call(context.llvm.setArrayFunction, listOf(array, sum, exp))
@@ -832,6 +832,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
val kStringLength = KonanPlatform.builtIns.string.getter2Descriptor(kNameLength) val kStringLength = KonanPlatform.builtIns.string.getter2Descriptor(kNameLength)
val kStringBuilderToString = kStringBuilder.signature2Descriptor(kNameToString) val kStringBuilderToString = kStringBuilder.signature2Descriptor(kNameToString)
//TODO: make it lowering pass.
private fun evaluateStringConcatenation(value: IrStringConcatenation): LLVMValueRef { private fun evaluateStringConcatenation(value: IrStringConcatenation): LLVMValueRef {
data class Element(val string: LLVMValueRef, val llvmLenght: LLVMValueRef?, val length: Int) data class Element(val string: LLVMValueRef, val llvmLenght: LLVMValueRef?, val length: Int)
@@ -857,8 +858,8 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
val constructor = kStringBuilder!!.constructors val constructor = kStringBuilder!!.constructors
.firstOrNull { it -> it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type) }!! .firstOrNull { it -> it.valueParameters.size == 1 && KotlinBuiltIns.isInt(it.valueParameters[0].type) }!!
val stringBuilderObj = call(context.llvm.allocInstanceFunction, val stringBuilderObj = codegen.allocInstance(codegen.typeInfoValue(kStringBuilder), SCOPE_FRAME)
listOf(codegen.typeInfoValue(kStringBuilder.defaultType)!!, kImmOne))
call(codegen.llvmFunction(constructor), listOf(stringBuilderObj, totalLength)) call(codegen.llvmFunction(constructor), listOf(stringBuilderObj, totalLength))
stringsWithLengths.fold(stringBuilderObj) { sum, (string, _, _) -> stringsWithLengths.fold(stringBuilderObj) { sum, (string, _, _) ->
@@ -1434,11 +1435,10 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
*/ */
private fun fieldPtrOfClass(thisPtr: LLVMValueRef, value: PropertyDescriptor): LLVMValueRef { private fun fieldPtrOfClass(thisPtr: LLVMValueRef, value: PropertyDescriptor): LLVMValueRef {
val objHeaderPtr = codegen.bitcast(codegen.kObjHeaderPtr, thisPtr)
val typePtr = pointerType(codegen.classType(value.containingDeclaration as ClassDescriptor)) val typePtr = pointerType(codegen.classType(value.containingDeclaration as ClassDescriptor))
memScoped { memScoped {
val args = allocArrayOf(kImmOne) val args = allocArrayOf(kImmOne)
val objectPtr = LLVMBuildGEP(codegen.builder, objHeaderPtr, args[0].ptr, 1, "") val objectPtr = LLVMBuildGEP(codegen.builder, thisPtr, args[0].ptr, 1, "")
val typedObjPtr = codegen.bitcast(typePtr, objectPtr!!) val typedObjPtr = codegen.bitcast(typePtr, objectPtr!!)
val fieldPtr = LLVMBuildStructGEP(codegen.builder, typedObjPtr, codegen.indexInClass(value), "") val fieldPtr = LLVMBuildStructGEP(codegen.builder, typedObjPtr, codegen.indexInClass(value), "")
return fieldPtr!! return fieldPtr!!
@@ -1742,24 +1742,22 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
} }
//-------------------------------------------------------------------------// //-------------------------------------------------------------------------//
private fun hintForCall(callee: IrCall): Int {
return allocHints.getOrElse(callee) { SCOPE_GLOBAL }
}
private fun evaluateConstructorCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef { private fun evaluateConstructorCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
context.log("evaluateConstructorCall : ${ir2string(callee)}") context.log("evaluateConstructorCall : ${ir2string(callee)}")
memScoped { memScoped {
val constructedClass = (callee.descriptor as ConstructorDescriptor).constructedClass val constructedClass = (callee.descriptor as ConstructorDescriptor).constructedClass
val typeInfo = codegen.typeInfoValue(constructedClass)
val allocHint = Int32(1).llvm
val thisValue = if (constructedClass.isArray) { val thisValue = if (constructedClass.isArray) {
assert(args.size >= 1 && args[0].type == int32Type) assert(args.size >= 1 && args[0].type == int32Type)
val allocArrayInstanceArgs = listOf(typeInfo, allocHint, args[0]) codegen.allocArray(codegen.typeInfoValue(constructedClass), hintForCall(callee), args[0])
call(context.llvm.allocArrayFunction, allocArrayInstanceArgs)
} else { } else {
call(context.llvm.allocInstanceFunction, listOf(typeInfo, allocHint)) codegen.allocInstance(codegen.typeInfoValue(constructedClass), hintForCall(callee))
} }
val constructorParams: MutableList<LLVMValueRef> = mutableListOf() evaluateSimpleFunctionCall(callee.descriptor as FunctionDescriptor,
constructorParams += thisValue listOf(thisValue) + args)
constructorParams += args
evaluateSimpleFunctionCall(callee.descriptor as FunctionDescriptor, constructorParams)
return thisValue return thisValue
} }
} }
@@ -1772,8 +1770,8 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
return when (name) { return when (name) {
"konan.internal.areEqualByValue" -> { "konan.internal.areEqualByValue" -> {
val arg0 = args[0]!! val arg0 = args[0]
val arg1 = args[1]!! val arg1 = args[1]
assert (arg0.type == arg1.type) assert (arg0.type == arg1.type)
when (LLVMGetTypeKind(arg0.type)) { when (LLVMGetTypeKind(arg0.type)) {
@@ -1801,12 +1799,12 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
val descriptor = callee.descriptor val descriptor = callee.descriptor
val ib = context.irModule!!.irBuiltins val ib = context.irModule!!.irBuiltins
when (descriptor) { when (descriptor) {
ib.eqeqeq -> return codegen.icmpEq(args[0]!!, args[1]!!) ib.eqeqeq -> return codegen.icmpEq(args[0], args[1])
ib.gt0 -> return codegen.icmpGt(args[0]!!, kImmZero) ib.gt0 -> return codegen.icmpGt(args[0], kImmZero)
ib.gteq0 -> return codegen.icmpGe(args[0]!!, kImmZero) ib.gteq0 -> return codegen.icmpGe(args[0], kImmZero)
ib.lt0 -> return codegen.icmpLt(args[0]!!, kImmZero) ib.lt0 -> return codegen.icmpLt(args[0], kImmZero)
ib.lteq0 -> return codegen.icmpLe(args[0]!!, kImmZero) ib.lteq0 -> return codegen.icmpLe(args[0], kImmZero)
ib.booleanNot -> return codegen.icmpNe(args[0]!!, kTrue) ib.booleanNot -> return codegen.icmpNe(args[0], kTrue)
else -> { else -> {
TODO(descriptor.name.toString()) TODO(descriptor.name.toString())
} }
@@ -1838,7 +1836,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
if (resultPhi != null && !isNothing) if (resultPhi != null && !isNothing)
codegen.assignPhis(resultPhi to brResult) codegen.assignPhis(resultPhi to brResult)
if (bbExit != null && !isNothing) if (bbExit != null && !isNothing)
codegen.br(bbExit!!) codegen.br(bbExit)
if (bbNext != null) // Switch generation to next or exit. if (bbNext != null) // Switch generation to next or exit.
codegen.positionAtEnd(bbNext) codegen.positionAtEnd(bbNext)
else if (bbExit != null) else if (bbExit != null)
@@ -1878,7 +1876,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
val slot = codegen.gep(vtable, Int32(index).llvm) val slot = codegen.gep(vtable, Int32(index).llvm)
codegen.load(slot) codegen.load(slot)
} else { } else {
// Otherwise, call via hashtable. // Otherwise, call by hash.
// TODO: optimize by storing interface number in lower bits of 'this' pointer // TODO: optimize by storing interface number in lower bits of 'this' pointer
// when passing object as an interface. This way we can use those bits as index // when passing object as an interface. This way we can use those bits as index
// for an additional per-interface vtable. // for an additional per-interface vtable.
@@ -1914,7 +1912,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid
private fun call(function: LLVMValueRef, args: List<LLVMValueRef>): LLVMValueRef { private fun call(function: LLVMValueRef, args: List<LLVMValueRef>): LLVMValueRef {
if (codegen.isObjectReturn(function.type)) { if (codegen.isObjectReturn(function.type)) {
// If function returns an object - create slot for the returned value. // If function returns an object - create slot for the returned value.
// This allows appropriate rootset accounting by just looking on stack slots. // This allows appropriate rootset accounting by just looking at the stack slots.
val resultSlot = codegen.vars.createAnonymousSlot() val resultSlot = codegen.vars.createAnonymousSlot()
return currentCodeContext.genCall(function, args + resultSlot) return currentCodeContext.genCall(function, args + resultSlot)
} else { } else {
@@ -115,6 +115,8 @@ internal val ContextUtils.kTypeInfo: LLVMTypeRef
get() = LLVMGetTypeByName(context.llvmModule, "struct.TypeInfo")!! get() = LLVMGetTypeByName(context.llvmModule, "struct.TypeInfo")!!
internal val ContextUtils.kObjHeader: LLVMTypeRef internal val ContextUtils.kObjHeader: LLVMTypeRef
get() = LLVMGetTypeByName(context.llvmModule, "struct.ObjHeader")!! get() = LLVMGetTypeByName(context.llvmModule, "struct.ObjHeader")!!
internal val ContextUtils.kContainerHeader: LLVMTypeRef
get() = LLVMGetTypeByName(context.llvmModule, "struct.ContainerHeader")!!
internal val ContextUtils.kObjHeaderPtr: LLVMTypeRef internal val ContextUtils.kObjHeaderPtr: LLVMTypeRef
get() = pointerType(kObjHeader) get() = pointerType(kObjHeader)
internal val ContextUtils.kObjHeaderPtrPtr: LLVMTypeRef internal val ContextUtils.kObjHeaderPtrPtr: LLVMTypeRef
@@ -129,7 +131,7 @@ internal val kInt1 = LLVMInt1Type()!!
internal val kBoolean = kInt1 internal val kBoolean = kInt1
internal val kInt8Ptr = pointerType(int8Type) internal val kInt8Ptr = pointerType(int8Type)
internal val kInt8PtrPtr = pointerType(kInt8Ptr) internal val kInt8PtrPtr = pointerType(kInt8Ptr)
internal val kNullInt8Ptr = LLVMConstNull(kInt8Ptr) internal val kNullInt8Ptr = LLVMConstNull(kInt8Ptr)!!
internal val kImmInt32One = Int32(1).llvm internal val kImmInt32One = Int32(1).llvm
internal val kImmInt64One = Int64(1).llvm internal val kImmInt64One = Int64(1).llvm
internal val ContextUtils.kNullObjHeaderPtr: LLVMValueRef internal val ContextUtils.kNullObjHeaderPtr: LLVMValueRef
+2 -2
View File
@@ -8,10 +8,10 @@
OBJ_GETTER(setupArgs, int argc, char** argv) { OBJ_GETTER(setupArgs, int argc, char** argv) {
// The count is one less, because we skip argv[0] which is the binary name. // The count is one less, because we skip argv[0] which is the binary name.
AllocArrayInstance(theArrayTypeInfo, SCOPE_GLOBAL, argc - 1, OBJ_RESULT); AllocArrayInstance(theArrayTypeInfo, argc - 1, OBJ_RESULT);
ArrayHeader* array = (*OBJ_RESULT)->array(); ArrayHeader* array = (*OBJ_RESULT)->array();
for (int index = 1; index < argc; index++) { for (int index = 1; index < argc; index++) {
AllocStringInstance(SCOPE_GLOBAL, argv[index], strlen(argv[index]), AllocStringInstance(argv[index], strlen(argv[index]),
ArrayAddressOfElementAt(array, index - 1)); ArrayAddressOfElementAt(array, index - 1));
} }
RETURN_OBJ_RESULT(); RETURN_OBJ_RESULT();
+3 -4
View File
@@ -50,13 +50,12 @@ OBJ_GETTER0(GetCurrentStackTrace) {
RuntimeAssert(symbols != nullptr, "Not enough memory to retrieve the stacktrace"); RuntimeAssert(symbols != nullptr, "Not enough memory to retrieve the stacktrace");
AutoFree autoFree(symbols); AutoFree autoFree(symbols);
AllocArrayInstance(theArrayTypeInfo, SCOPE_GLOBAL, size, OBJ_RESULT); AllocArrayInstance(theArrayTypeInfo, size, OBJ_RESULT);
ArrayHeader* array = (*OBJ_RESULT)->array(); ArrayHeader* array = (*OBJ_RESULT)->array();
for (int index = 0; index < size; ++index) { for (int index = 0; index < size; ++index) {
AllocStringInstance( AllocStringInstance(symbols[index], strlen(symbols[index]),
SCOPE_GLOBAL, symbols[index], strlen(symbols[index]), ArrayAddressOfElementAt(array, index));
ArrayAddressOfElementAt(array, index));
} }
RETURN_OBJ_RESULT(); RETURN_OBJ_RESULT();
+192 -82
View File
@@ -21,11 +21,16 @@
#define TRACE_GC_PHASES 0 #define TRACE_GC_PHASES 0
ContainerHeader ObjHeader::theStaticObjectsContainer = { ContainerHeader ObjHeader::theStaticObjectsContainer = {
CONTAINER_TAG_NOCOUNT | CONTAINER_TAG_INCREMENT CONTAINER_TAG_PERMANENT | CONTAINER_TAG_INCREMENT
}; };
namespace { namespace {
// Granularity of arena container chunks.
constexpr container_size_t kContainerAlignment = 1024;
// Single object alignment.
constexpr container_size_t kObjectAlignment = 8;
#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). // release candidates set).
@@ -63,11 +68,30 @@ struct MemoryState {
MemoryState* memoryState = nullptr; MemoryState* memoryState = nullptr;
#if USE_GC // TODO: use those allocators for STL containers as well.
bool isPermanent(const ContainerHeader* header) { template <typename T>
return (header->ref_count_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_NOCOUNT; inline T* allocMemory(container_size_t size) {
return reinterpret_cast<T*>(calloc(1, size));
} }
inline void freeMemory(void* memory) {
free(memory);
}
inline bool isFreeable(const ContainerHeader* header) {
return (header->refCount_ & CONTAINER_TAG_MASK) < CONTAINER_TAG_PERMANENT;
}
inline bool isPermanent(const ContainerHeader* header) {
return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT;
}
inline container_size_t alignUp(container_size_t size, int alignment) {
return (size + alignment - 1) & ~(alignment - 1);
}
#if USE_GC
// Must be vector or map 'container -> number', to keep reference counters correct. // Must be vector or map 'container -> number', to keep reference counters correct.
ContainerHeaderList collectMutableReferred(ContainerHeader* header) { ContainerHeaderList collectMutableReferred(ContainerHeader* header) {
ContainerHeaderList result; ContainerHeaderList result;
@@ -98,8 +122,8 @@ ContainerHeaderList collectMutableReferred(ContainerHeader* header) {
void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet* seen) { void dumpWorker(const char* prefix, ContainerHeader* header, ContainerHeaderSet* seen) {
fprintf(stderr, "%s: %p (%08x): %d refs %s\n", fprintf(stderr, "%s: %p (%08x): %d refs %s\n",
prefix, prefix,
header, header->ref_count_, header->ref_count_ >> CONTAINER_TAG_SHIFT, header, header->refCount_, header->refCount_ >> CONTAINER_TAG_SHIFT,
(header->ref_count_ & CONTAINER_TAG_SEEN) != 0 ? "X" : "-"); (header->refCount_ & CONTAINER_TAG_SEEN) != 0 ? "X" : "-");
seen->insert(header); seen->insert(header);
auto children = collectMutableReferred(header); auto children = collectMutableReferred(header);
for (auto child : children) { for (auto child : children) {
@@ -117,22 +141,22 @@ void dumpReachable(const char* prefix, const ContainerHeaderSet* roots) {
} }
void phase1(ContainerHeader* header) { void phase1(ContainerHeader* header) {
if ((header->ref_count_ & CONTAINER_TAG_SEEN) != 0) if ((header->refCount_ & CONTAINER_TAG_SEEN) != 0)
return; return;
header->ref_count_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
container->ref_count_ -= CONTAINER_TAG_INCREMENT; container->refCount_ -= CONTAINER_TAG_INCREMENT;
phase1(container); phase1(container);
} }
} }
void phase2(ContainerHeader* header, ContainerHeaderSet* rootset) { void phase2(ContainerHeader* header, ContainerHeaderSet* rootset) {
if ((header->ref_count_ & CONTAINER_TAG_SEEN) == 0) if ((header->refCount_ & CONTAINER_TAG_SEEN) == 0)
return; return;
if ((header->ref_count_ >> CONTAINER_TAG_SHIFT) != 0) if ((header->refCount_ >> CONTAINER_TAG_SHIFT) != 0)
rootset->insert(header); rootset->insert(header);
header->ref_count_ &= ~CONTAINER_TAG_SEEN; header->refCount_ &= ~CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
phase2(container, rootset); phase2(container, rootset);
@@ -140,32 +164,32 @@ void phase2(ContainerHeader* header, ContainerHeaderSet* rootset) {
} }
void phase3(ContainerHeader* header) { void phase3(ContainerHeader* header) {
if ((header->ref_count_ & CONTAINER_TAG_SEEN) != 0) { if ((header->refCount_ & CONTAINER_TAG_SEEN) != 0) {
return; return;
} }
header->ref_count_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
container->ref_count_ += CONTAINER_TAG_INCREMENT; container->refCount_ += CONTAINER_TAG_INCREMENT;
phase3(container); phase3(container);
} }
} }
void phase4(ContainerHeader* header, ContainerHeaderSet* toRemove) { void phase4(ContainerHeader* header, ContainerHeaderSet* toRemove) {
auto ref_count = header->ref_count_ >> CONTAINER_TAG_SHIFT; auto refCount = header->refCount_ >> CONTAINER_TAG_SHIFT;
bool seen = (ref_count > 0 && (header->ref_count_ & CONTAINER_TAG_SEEN) == 0) || bool seen = (refCount > 0 && (header->refCount_ & CONTAINER_TAG_SEEN) == 0) ||
(ref_count == 0 && (header->ref_count_ & CONTAINER_TAG_SEEN) != 0); (refCount == 0 && (header->refCount_ & CONTAINER_TAG_SEEN) != 0);
if (seen) return; if (seen) return;
// Add to toRemove set. // Add to toRemove set.
if (ref_count == 0) if (refCount == 0)
toRemove->insert(header); toRemove->insert(header);
// Update seen bit. // Update seen bit.
if (ref_count == 0) if (refCount == 0)
header->ref_count_ |= CONTAINER_TAG_SEEN; header->refCount_ |= CONTAINER_TAG_SEEN;
else else
header->ref_count_ &= ~CONTAINER_TAG_SEEN; header->refCount_ &= ~CONTAINER_TAG_SEEN;
auto containers = collectMutableReferred(header); auto containers = collectMutableReferred(header);
for (auto container : containers) { for (auto container : containers) {
phase4(container, toRemove); phase4(container, toRemove);
@@ -174,91 +198,109 @@ void phase4(ContainerHeader* header, ContainerHeaderSet* toRemove) {
#endif // USE_GC #endif // USE_GC
// We use first slot as place to store frame-local arena container.
ArenaContainer* initedArena(ObjHeader** auxSlot) {
ObjHeader* slotValue = *auxSlot;
if (slotValue) return reinterpret_cast<ArenaContainer*>(slotValue);
ArenaContainer* arena = allocMemory<ArenaContainer>(sizeof(ArenaContainer));
arena->Init();
*auxSlot = reinterpret_cast<ObjHeader*>(arena);
return arena;
}
} // namespace } // namespace
ContainerHeader* AllocContainer(size_t size) { ContainerHeader* AllocContainer(size_t size) {
ContainerHeader* result = reinterpret_cast<ContainerHeader*>(calloc(1, size)); ContainerHeader* result = allocMemory<ContainerHeader>(size);
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, ">>> alloc %d -> %p\n", (int)size, result); fprintf(stderr, ">>> alloc %d -> %p\n", static_cast<int>(size), result);
memoryState->containers->insert(result); memoryState->containers->insert(result);
#endif #endif
// TODO: atomic increment in concurrent case. // TODO: atomic increment in concurrent case.
memoryState->allocCount++; memoryState->allocCount++;
return result; return result;
} }
// TODO: shall we do padding for alignment?
uint32_t ObjectSize(const ObjHeader* obj) {
const TypeInfo* type_info = obj->type_info();
if (type_info->instanceSize_ < 0) {
// An array.
return ArrayDataSizeBytes(obj->array()) + sizeof(ArrayHeader);
} else {
return type_info->instanceSize_ + sizeof(ObjHeader);
}
}
void FreeContainer(ContainerHeader* header) { void FreeContainer(ContainerHeader* header) {
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed"); RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed");
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "<<< free %p\n", header); if (isFreeable(header)) {
memoryState->containers->erase(header); fprintf(stderr, "<<< free %p\n", header);
memoryState->containers->erase(header);
}
#endif #endif
header->ref_count_ = CONTAINER_TAG_INVALID;
#if USE_GC #if USE_GC
if (memoryState->toFree) if (memoryState->toFree && isFreeable(header))
memoryState->toFree->erase(header); memoryState->toFree->erase(header);
#endif #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
// ArenaContainer and ObjectContainer, which both have single element.
ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1); ObjHeader* obj = reinterpret_cast<ObjHeader*>(header + 1);
const TypeInfo* typeInfo = obj->type_info();
// We use *local* versions as no other threads could see dead objects. for (int index = 0; index < header->objectCount_; index++) {
for (int index = 0; index < typeInfo->objOffsetsCount_; index++) { const TypeInfo* typeInfo = obj->type_info();
ObjHeader** location = reinterpret_cast<ObjHeader**>(
reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]); // We use *local* versions as no other threads could see dead objects.
UpdateLocalRef(location, nullptr); for (int index = 0; index < typeInfo->objOffsetsCount_; index++) {
} ObjHeader** location = reinterpret_cast<ObjHeader**>(
// Object arrays are *special*. reinterpret_cast<uintptr_t>(obj + 1) + typeInfo->objOffsets_[index]);
if (typeInfo == theArrayTypeInfo) { UpdateLocalRef(location, nullptr);
ArrayHeader* array = obj->array(); }
ReleaseLocalRefs(ArrayAddressOfElementAt(array, 0), array->count_); // Object arrays are *special*.
if (typeInfo == theArrayTypeInfo) {
ArrayHeader* array = obj->array();
ReleaseLocalRefs(ArrayAddressOfElementAt(array, 0), array->count_);
}
obj = reinterpret_cast<ObjHeader*>(reinterpret_cast<uintptr_t>(obj) + ObjectSize(obj));
} }
// And release underlying memory. // And release underlying memory.
// TODO: atomic decrement in concurrent case. if (isFreeable(header)) {
// TODO: atomic decrement in concurrent case.
#if CONCURRENT #if CONCURRENT
#error "Atomic update of allocCount" #error "Atomic update of allocCount"
#endif #endif
memoryState->allocCount--; memoryState->allocCount--;
free(header); freeMemory(header);
}
} }
#if USE_GC #if USE_GC
void FreeContainerNoRef(ContainerHeader* header) { void FreeContainerNoRef(ContainerHeader* header) {
RuntimeAssert(!isPermanent(header), "this kind of container shalln't be freed"); RuntimeAssert(isFreeable(header), "this kind of container shalln't be freed");
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "<<< free %p\n", header); fprintf(stderr, "<<< free %p\n", header);
memoryState->containers->erase(header); memoryState->containers->erase(header);
#endif #endif
header->ref_count_ = CONTAINER_TAG_INVALID;
#if USE_GC #if USE_GC
if (memoryState->toFree) if (memoryState->toFree)
memoryState->toFree->erase(header); memoryState->toFree->erase(header);
#endif #endif
memoryState->allocCount--; memoryState->allocCount--;
free(header); freeMemory(header);
} }
#endif #endif
ArenaContainer::ArenaContainer(uint32_t size) {
ArenaContainerHeader* header =
static_cast<ArenaContainerHeader*>(AllocContainer(size + sizeof(ArenaContainerHeader)));
header_ = header;
// header->ref_count_ is zero initialized by AllocContainer().
header->current_ =
reinterpret_cast<uint8_t*>(header_) + sizeof(ArenaContainerHeader);
header->end_ = header->current_ + size;
}
void ObjectContainer::Init(const TypeInfo* type_info) { void ObjectContainer::Init(const TypeInfo* type_info) {
RuntimeAssert(type_info->instanceSize_ >= 0, "Must be an object"); RuntimeAssert(type_info->instanceSize_ >= 0, "Must be an object");
uint32_t alloc_size = uint32_t alloc_size =
sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_; sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_;
header_ = AllocContainer(alloc_size); header_ = AllocContainer(alloc_size);
if (header_) { if (header_) {
// header->ref_count_ is zero initialized by AllocContainer(). // One object in this container.
header_->objectCount_ = 1;
// header->refCount_ is zero initialized by AllocContainer().
SetMeta(GetPlace(), type_info); SetMeta(GetPlace(), type_info);
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "object at %p\n", GetPlace()); fprintf(stderr, "object at %p\n", GetPlace());
@@ -274,7 +316,9 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
header_ = AllocContainer(alloc_size); header_ = AllocContainer(alloc_size);
RuntimeAssert(header_ != nullptr, "Cannot alloc memory"); RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
if (header_) { if (header_) {
// header->ref_count_ is zero initialized by AllocContainer(). // One object in this container.
header_->objectCount_ = 1;
// header->refCount_ is zero initialized by AllocContainer().
GetPlace()->count_ = elements; GetPlace()->count_ = elements;
SetMeta(GetPlace()->obj(), type_info); SetMeta(GetPlace()->obj(), type_info);
#if TRACE_MEMORY #if TRACE_MEMORY
@@ -283,25 +327,73 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
} }
} }
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) { void ArenaContainer::Init() {
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object"); allocContainer(1024);
uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader); }
ObjHeader* result = reinterpret_cast<ObjHeader*>(Place(size));
if (!result) { void ArenaContainer::Deinit() {
return nullptr; auto chunk = currentChunk_;
while (chunk != nullptr) {
auto toRemove = chunk;
// FreeContainer() doesn't release memory when CONTAINER_TAG_STACK is set.
FreeContainer(chunk->asHeader());
chunk = chunk->next;
freeMemory(toRemove);
} }
SetMeta(result, type_info); }
bool ArenaContainer::allocContainer(container_size_t minSize) {
auto size = minSize + sizeof(ContainerHeader) + sizeof(ContainerChunk);
size = alignUp(size, kContainerAlignment);
ContainerChunk* result = allocMemory<ContainerChunk>(size);
RuntimeAssert(result != nullptr, "Cannot alloc memory");
if (result == nullptr) return false;
result->next = currentChunk_;
result->asHeader()->refCount_ = (CONTAINER_TAG_STACK | CONTAINER_TAG_INCREMENT);
currentChunk_ = result;
current_ = reinterpret_cast<uint8_t*>(result->asHeader() + 1);
end_ = reinterpret_cast<uint8_t*>(result) + size;
return true;
}
void* ArenaContainer::place(container_size_t size) {
size = alignUp(size, kObjectAlignment);
// Fast path.
if (current_ + size < end_) {
void* result = current_;
current_ += size;
return result;
}
if (!allocContainer(size)) {
return nullptr;
}
void* result = current_;
current_ += size;
RuntimeAssert(current_ <= end_, "Must not overflow");
return result; return result;
} }
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, int count) { ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader);
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
if (!result) {
return nullptr;
}
currentChunk_->asHeader()->objectCount_++;
setMeta(result, type_info);
return result;
}
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array"); RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
uint32_t size = sizeof(ArrayHeader) - type_info->instanceSize_ * count; container_size_t size = sizeof(ArrayHeader) - type_info->instanceSize_ * count;
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(Place(size)); ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
if (!result) { if (!result) {
return nullptr; return nullptr;
} }
SetMeta(result->obj(), type_info); currentChunk_->asHeader()->objectCount_++;
setMeta(result->obj(), type_info);
result->count_ = count; result->count_ = count;
return result; return result;
} }
@@ -384,6 +476,8 @@ void DeinitMemory() {
#if USE_GC #if USE_GC
GarbageCollect(); GarbageCollect();
delete memoryState->toFree;
memoryState->toFree = nullptr;
#endif // USE_GC #endif // USE_GC
if (memoryState->allocCount > 0) { if (memoryState->allocCount > 0) {
@@ -399,20 +493,28 @@ void DeinitMemory() {
memoryState = nullptr; memoryState = nullptr;
} }
// Now we ignore all placement hints and always allocate heap space for new object. ObjHeader* ArenaAllocInstance(const TypeInfo* type_info, ObjHeader** auxSlot) {
OBJ_GETTER(AllocInstance, const TypeInfo* type_info, PlacementHint hint) { RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
return initedArena(auxSlot)->PlaceObject(type_info);
}
OBJ_GETTER(AllocInstance, const TypeInfo* type_info) {
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object"); RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
RETURN_OBJ(ObjectContainer(type_info).GetPlace()); RETURN_OBJ(ObjectContainer(type_info).GetPlace());
} }
OBJ_GETTER(AllocArrayInstance, ObjHeader* ArenaAllocArrayInstance(
const TypeInfo* type_info, PlacementHint hint, uint32_t elements) { const TypeInfo* type_info, uint32_t elements, ObjHeader** auxSlot) {
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
return initedArena(auxSlot)->PlaceArray(type_info, elements)->obj();
}
OBJ_GETTER(AllocArrayInstance, const TypeInfo* type_info, uint32_t elements) {
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array"); RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
RETURN_OBJ(ArrayContainer(type_info, elements).GetPlace()->obj()); RETURN_OBJ(ArrayContainer(type_info, elements).GetPlace()->obj());
} }
OBJ_GETTER(AllocStringInstance, OBJ_GETTER(AllocStringInstance, const char* data, uint32_t length) {
PlacementHint hint, const char* data, uint32_t length) {
ArrayHeader* array = ArrayContainer(theStringTypeInfo, length).GetPlace(); ArrayHeader* array = ArrayContainer(theStringTypeInfo, length).GetPlace();
memcpy( memcpy(
ByteArrayAddressOfElementAt(array, 0), ByteArrayAddressOfElementAt(array, 0),
@@ -422,8 +524,7 @@ OBJ_GETTER(AllocStringInstance,
} }
OBJ_GETTER(InitInstance, OBJ_GETTER(InitInstance,
ObjHeader** location, const TypeInfo* type_info, PlacementHint hint, ObjHeader** location, const TypeInfo* type_info, void (*ctor)(ObjHeader*)) {
void (*ctor)(ObjHeader*)) {
ObjHeader* sentinel = reinterpret_cast<ObjHeader*>(1); ObjHeader* sentinel = reinterpret_cast<ObjHeader*>(1);
ObjHeader* value; ObjHeader* value;
// Wait until other initializers. // Wait until other initializers.
@@ -438,7 +539,7 @@ OBJ_GETTER(InitInstance,
RETURN_OBJ(value); RETURN_OBJ(value);
} }
AllocInstance(type_info, hint, OBJ_RESULT); AllocInstance(type_info, OBJ_RESULT);
ObjHeader* object = *OBJ_RESULT; ObjHeader* object = *OBJ_RESULT;
UpdateGlobalRef(location, object); UpdateGlobalRef(location, object);
try { try {
@@ -523,6 +624,15 @@ void UpdateGlobalRef(ObjHeader** location, const ObjHeader* object) {
#endif #endif
} }
void LeaveFrame(ObjHeader** start, int count) {
ReleaseLocalRefs(start + 1, count - 1);
if (*start != nullptr) {
auto arena = initedArena(start);
arena->Deinit();
freeMemory(arena);
}
}
void ReleaseLocalRefs(ObjHeader** start, int count) { void ReleaseLocalRefs(ObjHeader** start, int count) {
#if TRACE_MEMORY #if TRACE_MEMORY
fprintf(stderr, "ReleaseLocalRefs %p .. %p\n", start, start + count); fprintf(stderr, "ReleaseLocalRefs %p .. %p\n", start, start + count);
@@ -623,7 +733,7 @@ void GarbageCollect() {
memoryState->toFree->clear(); memoryState->toFree->clear();
for (auto header : toRemove) { for (auto header : toRemove) {
RuntimeAssert((header->ref_count_ & CONTAINER_TAG_SEEN) != 0, "Must be not seen"); RuntimeAssert((header->refCount_ & CONTAINER_TAG_SEEN) != 0, "Must be not seen");
FreeContainerNoRef(header); FreeContainerNoRef(header);
} }
+79 -78
View File
@@ -10,7 +10,7 @@ typedef enum {
SCOPE_FRAME = 0, SCOPE_FRAME = 0,
// Allocation is generic global allocation. // Allocation is generic global allocation.
SCOPE_GLOBAL = 1, SCOPE_GLOBAL = 1,
// Allocation shall take place in current arena. // Allocation shall take place in current stack arena.
SCOPE_ARENA = 2, SCOPE_ARENA = 2,
// Allocation is permanent. // Allocation is permanent.
SCOPE_PERMANENT = 3 SCOPE_PERMANENT = 3
@@ -20,15 +20,16 @@ typedef enum {
typedef enum { typedef enum {
// Container is normal thread local container. // Container is normal thread local container.
CONTAINER_TAG_NORMAL = 0, CONTAINER_TAG_NORMAL = 0,
// Container shall not be refcounted (const data, frame locals). // Container shall be atomically refcounted.
CONTAINER_TAG_NOCOUNT = 1, CONTAINER_TAG_SHARED = 1,
// Container shall be atomically refcounted. // Those container tags shall not be refcounted.
CONTAINER_TAG_SHARED = 2, // Permanent object, cannot refer to non-permanent objects, so no need to cleanup those.
// Container is no longer valid. CONTAINER_TAG_PERMANENT = 2,
CONTAINER_TAG_INVALID = 3, // Stack objects, no need to free, children cleanup still shall be there.
CONTAINER_TAG_STACK = 3,
// Container was seen during GC. // Container was seen during GC.
CONTAINER_TAG_SEEN = 4, CONTAINER_TAG_SEEN = 4,
// Shift to get actual counter.. // Shift to get actual counter.
CONTAINER_TAG_SHIFT = 3, 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 << CONTAINER_TAG_SHIFT, CONTAINER_TAG_INCREMENT = 1 << CONTAINER_TAG_SHIFT,
@@ -36,14 +37,17 @@ typedef enum {
CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1) CONTAINER_TAG_MASK = ((CONTAINER_TAG_INCREMENT >> 1) - 1)
} ContainerTag; } ContainerTag;
// Could be made 64-bit for large memory configs.
typedef uint32_t container_offset_t; typedef uint32_t container_offset_t;
typedef uint32_t container_size_t;
// Header of all container objects. Contains reference counter. // Header of all container objects. Contains reference counter.
struct ContainerHeader { struct ContainerHeader {
// Reference counter of container. Uses two lower bits of counter for // Reference counter of container. Uses two lower bits of counter for
// container type (for polymorphism in ::Release()). // container type (for polymorphism in ::Release()).
volatile uint32_t ref_count_; volatile uint32_t refCount_;
// Number of objects in the container.
uint32_t objectCount_;
}; };
struct ArrayHeader; struct ArrayHeader;
@@ -116,37 +120,25 @@ struct ArrayHeader {
uint32_t count_; uint32_t count_;
}; };
struct ArenaContainerHeader : public ContainerHeader {
// Current allocation limit.
uint8_t* current_;
// Allocation end. Maybe consider having chunked backing storage
// at cost of smarter ::Release() polymorphic on container type.
uint8_t* end_;
};
inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) { inline uint32_t ArrayDataSizeBytes(const ArrayHeader* obj) {
// Instance size is negative. // Instance size is negative.
return -obj->type_info()->instanceSize_ * obj->count_; return -obj->type_info()->instanceSize_ * obj->count_;
} }
void FreeContainer(ContainerHeader* header);
// TODO: those two operations can be implemented by translator when storing // TODO: those two operations can be implemented by translator when storing
// reference to an object. // 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).
switch (header->ref_count_ & CONTAINER_TAG_MASK) { switch (header->refCount_ & CONTAINER_TAG_MASK) {
case CONTAINER_TAG_NORMAL: case CONTAINER_TAG_STACK:
header->ref_count_ += CONTAINER_TAG_INCREMENT; case CONTAINER_TAG_PERMANENT:
break; break;
case CONTAINER_TAG_NOCOUNT: case CONTAINER_TAG_NORMAL:
header->refCount_ += CONTAINER_TAG_INCREMENT;
break; break;
case CONTAINER_TAG_SHARED: case CONTAINER_TAG_SHARED:
__sync_fetch_and_add(&header->ref_count_, CONTAINER_TAG_INCREMENT); __sync_fetch_and_add(&header->refCount_, CONTAINER_TAG_INCREMENT);
break;
case CONTAINER_TAG_INVALID:
RuntimeAssert(false, "trying to addref invalid container");
break; break;
default: default:
RuntimeAssert(false, "unknown container type"); RuntimeAssert(false, "unknown container type");
@@ -154,19 +146,22 @@ inline void AddRef(ContainerHeader* header) {
} }
} }
// Release returns 'true' iff container cannot be part of cycle (either NOCOUNT void FreeContainer(ContainerHeader* header);
// Release() returns 'true' iff container cannot be part of cycle (either NOCOUNT
// object or container was fully released and will be collected). // object or container was fully released and will be collected).
inline bool Release(ContainerHeader* header) { inline bool Release(ContainerHeader* header) {
switch (header->ref_count_ & CONTAINER_TAG_MASK) { switch (header->refCount_ & CONTAINER_TAG_MASK) {
case CONTAINER_TAG_PERMANENT:
case CONTAINER_TAG_STACK:
// permanent/stack containers aren't loop candidates.
return true;
case CONTAINER_TAG_NORMAL: case CONTAINER_TAG_NORMAL:
if ((header->ref_count_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) { if ((header->refCount_ -= CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_NORMAL) {
FreeContainer(header); FreeContainer(header);
return true; return true;
} }
break; break;
case CONTAINER_TAG_NOCOUNT:
// 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,
@@ -182,14 +177,11 @@ inline bool Release(ContainerHeader* header) {
// probability even further. // probability even further.
case CONTAINER_TAG_SHARED: case CONTAINER_TAG_SHARED:
if (__sync_sub_and_fetch( if (__sync_sub_and_fetch(
&header->ref_count_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) { &header->refCount_, CONTAINER_TAG_INCREMENT) == CONTAINER_TAG_SHARED) {
FreeContainer(header); FreeContainer(header);
return true; return true;
} }
break; break;
case CONTAINER_TAG_INVALID:
RuntimeAssert(false, "trying to release invalid container");
break;
default: default:
RuntimeAssert(false, "unknown container type"); RuntimeAssert(false, "unknown container type");
break; break;
@@ -236,9 +228,9 @@ class ObjectContainer : public Container {
// Object container shalln't have any dtor, as it's being freed by // Object container shalln't have any dtor, as it's being freed by
// ::Release(). // ::Release().
ObjHeader* GetPlace() const { ObjHeader* GetPlace() const {
return reinterpret_cast<ObjHeader*>( return reinterpret_cast<ObjHeader*>(header_ + 1);
reinterpret_cast<uint8_t*>(header_) + sizeof(ContainerHeader));
} }
private: private:
@@ -253,41 +245,23 @@ class ArrayContainer : public Container {
} }
// Array container shalln't have any dtor, as it's being freed by ::Release(). // Array container shalln't have any dtor, as it's being freed by ::Release().
ArrayHeader* GetPlace() const { ArrayHeader* GetPlace() const {
return reinterpret_cast<ArrayHeader*>( return reinterpret_cast<ArrayHeader*>(header_ + 1);
reinterpret_cast<uint8_t*>(header_) + sizeof(ContainerHeader));
} }
private: private:
void Init(const TypeInfo* type_info, uint32_t elements); void Init(const TypeInfo* type_info, uint32_t elements);
}; };
// Class representing arena-style placement container. // Class representing arena-style placement container.
// Container is used for reference counting, // Container is used for reference counting, and it is assumed that objects
// and it is assumed that objects with related placement will share container. Only // with related placement will share container. Only
// whole container can be freed, individual objects are not taken into account. // whole container can be freed, individual objects are not taken into account.
class ArenaContainer : public Container { class ArenaContainer {
public: public:
explicit ArenaContainer(uint32_t size); void Init();
void Deinit();
~ArenaContainer() {
if (header_) {
RuntimeAssert(header_->ref_count_ == 0, "Non-zero refcount");
Dispose();
}
}
// Allocation function.
void* Place(int size) {
ArenaContainerHeader* header = reinterpret_cast<ArenaContainerHeader*>(header_);
if (header->current_ + size > header->end_) {
return nullptr;
}
void* result = header->current_;
header->current_ += size;
return result;
}
// Place individual object in this container. // Place individual object in this container.
ObjHeader* PlaceObject(const TypeInfo* type_info); ObjHeader* PlaceObject(const TypeInfo* type_info);
@@ -295,15 +269,28 @@ class ArenaContainer : public Container {
// Places an array of certain type in this container. Note that array_type_info // Places an array of certain type in this container. Note that array_type_info
// is type info for an array, not for an individual element. Also note that exactly // is type info for an array, not for an individual element. Also note that exactly
// same operation could be used to place strings. // same operation could be used to place strings.
ArrayHeader* PlaceArray(const TypeInfo* array_type_info, int count); ArrayHeader* PlaceArray(const TypeInfo* array_type_info, container_size_t count);
// Dispose whole container ignoring non-zero refcount. Use with care. private:
void Dispose() { struct ContainerChunk {
if (header_) { ContainerChunk* next;
FreeContainer(header_); // Then we have ContainerHeader here.
header_ = nullptr; ContainerHeader* asHeader() {
return reinterpret_cast<ContainerHeader*>(this + 1);
} }
};
void* place(container_size_t size);
bool allocContainer(container_size_t minSize);
void setMeta(ObjHeader* obj, const TypeInfo* type_info) {
obj->container_offset_negative_ =
reinterpret_cast<uintptr_t>(obj) - reinterpret_cast<uintptr_t>(currentChunk_->asHeader());
obj->set_type_info(type_info);
RuntimeAssert(obj->container() == currentChunk_->asHeader(), "Placement must match");
} }
ContainerChunk* currentChunk_;
uint8_t* current_;
uint8_t* end_;
}; };
#ifdef __cplusplus #ifdef __cplusplus
@@ -321,14 +308,26 @@ extern "C" {
void InitMemory(); void InitMemory();
void DeinitMemory(); void DeinitMemory();
OBJ_GETTER(AllocInstance, const TypeInfo* type_info, PlacementHint hint); //
OBJ_GETTER(AllocArrayInstance, // Object allocation.
const TypeInfo* type_info, PlacementHint hint, uint32_t elements); //
OBJ_GETTER(AllocStringInstance, // Allocation can happen in either GLOBAL, FRAME or ARENA scope. Depending on that,
PlacementHint hint, const char* data, uint32_t length); // Alloc* or ArenaAlloc* is called. Regular alloc means allocation happens in the heap,
// and each object gets its individual container. Otherwise, allocator uses aux slot in
// an implementation-defined manner, current behavior is to keep arena pointer there.
// Arena containers are not reference counted, and is explicitly freed when leaving
// its owner frame.
// Escape analysis algorithm is the provider of information for decision on exact aux slot
// selection, and comes from upper bound esteemation of object lifetime.
//
ObjHeader* ArenaAllocInstance(const TypeInfo* type_info, ObjHeader** auxSlot) RUNTIME_NOTHROW;
OBJ_GETTER(AllocInstance, const TypeInfo* type_info) RUNTIME_NOTHROW;
ObjHeader* ArenaAllocArrayInstance(
const TypeInfo* type_info, uint32_t elements, ObjHeader** auxSlot) RUNTIME_NOTHROW;
OBJ_GETTER(AllocArrayInstance, const TypeInfo* type_info, uint32_t elements) RUNTIME_NOTHROW;
OBJ_GETTER(AllocStringInstance, const char* data, uint32_t length) RUNTIME_NOTHROW;
OBJ_GETTER(InitInstance, OBJ_GETTER(InitInstance,
ObjHeader** location, const TypeInfo* type_info, PlacementHint hint, ObjHeader** location, const TypeInfo* type_info, void (*ctor)(ObjHeader*)) RUNTIME_NOTHROW;
void (*ctor)(ObjHeader*));
// //
// Object reference management. // Object reference management.
@@ -363,6 +362,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;
// Called on frame leave, if it has object slots.
void LeaveFrame(ObjHeader** start, int count) RUNTIME_NOTHROW;
// Collect garbage, which cannot be found by reference counting (cycles). // Collect garbage, which cannot be found by reference counting (cycles).
void GarbageCollect() RUNTIME_NOTHROW; void GarbageCollect() RUNTIME_NOTHROW;
+1 -1
View File
@@ -5,7 +5,7 @@
namespace { namespace {
inline template<typename R, typename Ta, typename Tb> R div(Ta a, Tb b) { template<typename R, typename Ta, typename Tb> R div(Ta a, Tb b) {
if (__builtin_expect(b == 0, false)) { if (__builtin_expect(b == 0, false)) {
ThrowArithmeticException(); ThrowArithmeticException();
} }
+3 -1
View File
@@ -2,6 +2,8 @@ package kotlin
import kotlin.collections.* import kotlin.collections.*
// TODO: make all iterator() methods inline.
/** /**
* An array of bytes. * An array of bytes.
* @constructor Creates a new array of the specified [size], with all elements initialized to zero. * @constructor Creates a new array of the specified [size], with all elements initialized to zero.
@@ -27,7 +29,7 @@ public final class ByteArray : Cloneable {
external private fun getArrayLength(): Int external private fun getArrayLength(): Int
/** Creates an iterator over the elements of the array. */ /** Creates an iterator over the elements of the array. */
public operator fun iterator(): kotlin.collections.ByteIterator { public operator fun iterator(): ByteIterator {
return ByteIteratorImpl(this) return ByteIteratorImpl(this)
} }
} }