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) {