[K/N] Pack instance fields better
This change reorders declared fields such that LLVM can pack each class space optimally, minimizing the padding required. The guarantee is that an objects body will take up space equal to the sum of the sizes of its fields (inherited and declared) rounded up to a multiple of 8 bytes. This also implies that no object will contain more than 7 bytes of padding.
This commit is contained in:
committed by
Space Team
parent
0333985445
commit
58a99da54f
+2
@@ -68,6 +68,8 @@ object BinaryOptions : BinaryOptionRegistry() {
|
||||
val disableAllocatorOverheadEstimate by booleanOption()
|
||||
|
||||
val enableSafepointSignposts by booleanOption()
|
||||
|
||||
val packFields by booleanOption()
|
||||
}
|
||||
|
||||
open class BinaryOption<T : Any>(
|
||||
|
||||
+3
@@ -113,6 +113,9 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
val disableAllocatorOverheadEstimate: Boolean by lazy {
|
||||
configuration.get(BinaryOptions.disableAllocatorOverheadEstimate) ?: false
|
||||
}
|
||||
val packFields: Boolean by lazy {
|
||||
configuration.get(BinaryOptions.packFields) ?: true
|
||||
}
|
||||
val workerExceptionHandling: WorkerExceptionHandling get() = configuration.get(KonanConfigKeys.WORKER_EXCEPTION_HANDLING)?.also {
|
||||
if (it != WorkerExceptionHandling.USE_HOOK) {
|
||||
configuration.report(CompilerMessageSeverity.STRONG_WARNING, "Legacy exception handling in workers is deprecated")
|
||||
|
||||
+62
-2
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.library.KotlinLibrary
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.min
|
||||
|
||||
internal fun createLlvmDeclarations(generationState: NativeGenerationState, irModule: IrModuleFragment): LlvmDeclarations {
|
||||
val generator = DeclarationsGeneratorVisitor(generationState)
|
||||
@@ -87,7 +88,8 @@ internal data class ClassBodyAndAlignmentInfo(
|
||||
|
||||
private fun ContextUtils.createClassBody(name: String, fields: List<ClassLayoutBuilder.FieldInfo>): ClassBodyAndAlignmentInfo {
|
||||
val classType = LLVMStructCreateNamed(LLVMGetModuleContext(llvm.module), name)!!
|
||||
val packed = fields.any { LLVMABIAlignmentOfType(runtime.targetData, it.type.toLLVMType(llvm)) != it.alignment }
|
||||
val packed = context.config.packFields ||
|
||||
fields.any { LLVMABIAlignmentOfType(runtime.targetData, it.type.toLLVMType(llvm)) != it.alignment }
|
||||
val alignment = maxOf(runtime.objectAlignment, fields.maxOfOrNull { it.alignment } ?: 0)
|
||||
val indices = mutableMapOf<IrFieldSymbol, Int>()
|
||||
|
||||
@@ -189,10 +191,68 @@ private class DeclarationsGeneratorVisitor(override val generationState: NativeG
|
||||
super.visitClass(declaration)
|
||||
}
|
||||
|
||||
private fun packFields(declaration: IrClass): List<ClassLayoutBuilder.FieldInfo> {
|
||||
val fields = context.getLayoutBuilder(declaration).getFields(llvm)
|
||||
// The NoReorderFields annotation is internal, and only occurs on final classes with no inherited fields.
|
||||
if (declaration.hasAnnotation(KonanFqNames.noReorderFields)) {
|
||||
return fields
|
||||
}
|
||||
|
||||
// offsetN indicates at what offset, if any, an N-byte appropriately aligned block can be packed.
|
||||
// If no such block exists that does not overlap with a better aligned free block, offsetN will be -1.
|
||||
var offset1 = -1L
|
||||
var offset2 = -1L
|
||||
var offset4 = -1L
|
||||
var offset8 = 0L // == size of allocated fields rounded up to multiple of 8 bytes
|
||||
|
||||
// Allocates a block of the given size. Sizes smaller than 8 bytes will be rounded up to a power of 2 and
|
||||
// aligned according to that size. Other sizes will be rounded up to a multiple of 8 and will be 8 byte aligned.
|
||||
fun nextOffset(size: Long): Long = when (size) {
|
||||
/* Algorithm:
|
||||
N -> {
|
||||
if we have a saved block of size N, return it and forget about it
|
||||
else split a block of size 2N in two, return the first, and save the second for later.
|
||||
*/
|
||||
1L -> {
|
||||
if (offset1 != -1L) offset1.also { offset1 = -1 }
|
||||
else nextOffset(2).also { offset1 = it + 1 }
|
||||
}
|
||||
2L -> {
|
||||
if (offset2 != -1L) offset2.also { offset2 = -1 }
|
||||
else nextOffset(4).also { offset2 = it + 2 }
|
||||
}
|
||||
4L -> {
|
||||
if (offset4 != -1L) offset4.also { offset4 = -1 }
|
||||
else nextOffset(8).also { offset4 = it + 4 }
|
||||
}
|
||||
/* Base case:
|
||||
else -> the size is big enough that it needs one or more 8-byte blocks by itself.
|
||||
*/
|
||||
else -> offset8.also { offset8 += alignTo(size, 8) }
|
||||
}
|
||||
|
||||
class IndexedField(val offset: Long, val field: ClassLayoutBuilder.FieldInfo)
|
||||
|
||||
val packedFields = mutableListOf<IndexedField>()
|
||||
for (field in fields) {
|
||||
val size = LLVMStoreSizeOfType(llvm.runtime.targetData, field.type.toLLVMType(llvm))
|
||||
check(size == 1L || size == 2L || size == 4L || size % 8 == 0L)
|
||||
check(min(size, 8L) % field.alignment == 0L)
|
||||
val offset = nextOffset(size)
|
||||
packedFields.add(IndexedField(offset, field))
|
||||
}
|
||||
packedFields.sortBy { it.offset }
|
||||
return packedFields.map { it.field }
|
||||
}
|
||||
|
||||
private fun createClassDeclarations(declaration: IrClass): ClassLlvmDeclarations {
|
||||
val internalName = qualifyInternalName(declaration)
|
||||
|
||||
val fields = context.getLayoutBuilder(declaration).getFields(llvm)
|
||||
val fields =
|
||||
if (context.config.packFields)
|
||||
packFields(declaration)
|
||||
else
|
||||
context.getLayoutBuilder(declaration).getFields(llvm)
|
||||
val (bodyType, alignment, fieldIndices) = createClassBody("kclassbody:$internalName", fields)
|
||||
|
||||
require(alignment == runtime.objectAlignment) {
|
||||
|
||||
Reference in New Issue
Block a user