From 58a99da54fd03c1acb34f7be33ae5556955d04bb Mon Sep 17 00:00:00 2001 From: Troels Bjerre Lund Date: Mon, 16 Oct 2023 13:52:20 +0200 Subject: [PATCH] [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. --- .../kotlin/backend/konan/BinaryOptions.kt | 2 + .../kotlin/backend/konan/KonanConfig.kt | 3 + .../backend/konan/llvm/LlvmDeclarations.kt | 64 ++++++++++++++++++- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt index 3353cefaa1e..9f5beaf0b16 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryOptions.kt @@ -68,6 +68,8 @@ object BinaryOptions : BinaryOptionRegistry() { val disableAllocatorOverheadEstimate by booleanOption() val enableSafepointSignposts by booleanOption() + + val packFields by booleanOption() } open class BinaryOption( diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index 2ebb9de42a0..313c4c7eea9 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -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") diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index c57537fac45..e1d2f6caafb 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -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): 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() @@ -189,10 +191,68 @@ private class DeclarationsGeneratorVisitor(override val generationState: NativeG super.visitClass(declaration) } + private fun packFields(declaration: IrClass): List { + 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() + 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) {