[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:
Troels Bjerre Lund
2023-10-16 13:52:20 +02:00
committed by Space Team
parent 0333985445
commit 58a99da54f
3 changed files with 67 additions and 2 deletions
@@ -68,6 +68,8 @@ object BinaryOptions : BinaryOptionRegistry() {
val disableAllocatorOverheadEstimate by booleanOption()
val enableSafepointSignposts by booleanOption()
val packFields by booleanOption()
}
open class BinaryOption<T : Any>(
@@ -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")
@@ -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) {