diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt index 990e77c0218..ce306653895 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanFqNames.kt @@ -26,6 +26,7 @@ object KonanFqNames { val sharedImmutable = FqName("kotlin.native.concurrent.SharedImmutable") val frozen = FqName("kotlin.native.internal.Frozen") val leakDetectorCandidate = FqName("kotlin.native.internal.LeakDetectorCandidate") + val canBePrecreated = FqName("kotlin.native.internal.CanBePrecreated") val typedIntrinsic = FqName("kotlin.native.internal.TypedIntrinsic") val objCMethod = FqName("kotlinx.cinterop.ObjCMethod") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index 2f5534ed5ad..4620be97fc2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -143,7 +143,6 @@ internal val IrClass.objectInstanceShadowFieldSymbolName: String assert (this.isExported()) assert (this.kind.isSingleton) assert (!this.isUnit()) - assert (this.objectIsShared) return "kshadowobjref:$fqNameForIrSerialization" } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index f9a6f47a5eb..24cff4e7ed3 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -18,6 +18,28 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.backend.konan.descriptors.resolveFakeOverride import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.descriptors.konan.CompiledKlibModuleOrigin +import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall +import org.jetbrains.kotlin.ir.expressions.IrGetObjectValue +import org.jetbrains.kotlin.ir.expressions.IrReturn + +private fun IrConstructor.isAnyConstructorDelegation(context: Context): Boolean { + val statements = this.body?.statements ?: return false + if (statements.size != 2) return false + val lastStatement = statements[1] + if (lastStatement !is IrReturn || + (lastStatement.value as? IrGetObjectValue)?.symbol != context.irBuiltIns.unitClass) return false + val constructorCall = statements[0] as? IrDelegatingConstructorCall ?: return false + val constructor = constructorCall.symbol.owner as? IrConstructor ?: return false + return constructor.constructedClass.isAny() + } + +// TODO: shall we memoize that property? +internal fun IrClass.hasConstStateAndNoSideEffects(context: Context): Boolean { + if (!context.shouldOptimize()) return false + if (this.hasAnnotation(KonanFqNames.canBePrecreated)) return true + val fields = context.getLayoutBuilder(this).fields + return fields.isEmpty() && this.constructors.all { it.isAnyConstructorDelegation(context) } +} internal class CodeGenerator(override val context: Context) : ContextUtils { @@ -42,7 +64,7 @@ internal class CodeGenerator(override val context: Context) : ContextUtils { fun functionEntryPointAddress(function: IrFunction) = function.entryPointAddress.llvm fun functionHash(function: IrFunction): LLVMValueRef = function.functionName.localHash.llvm - fun getObjectInstanceStorage(irClass: IrClass, shared: Boolean): LLVMValueRef { + fun getObjectInstanceStorage(irClass: IrClass, kind: ObjectStorageKind): LLVMValueRef { assert (!irClass.isUnit()) val llvmGlobal = if (!isExternal(irClass)) { context.llvmDeclarations.forSingleton(irClass).instanceFieldRef @@ -52,19 +74,21 @@ internal class CodeGenerator(override val context: Context) : ContextUtils { irClass.objectInstanceFieldSymbolName, llvmType, origin = irClass.llvmSymbolOrigin, - threadLocal = !shared + threadLocal = kind == ObjectStorageKind.THREAD_LOCAL ) } - if (shared) - context.llvm.sharedObjects += llvmGlobal - else - context.llvm.objects += llvmGlobal + when (kind) { + ObjectStorageKind.SHARED -> context.llvm.sharedObjects += llvmGlobal + ObjectStorageKind.THREAD_LOCAL -> context.llvm.objects += llvmGlobal + ObjectStorageKind.PERMANENT -> { /* Do nothing, no need to free such an instance. */ } + } + return llvmGlobal } fun getObjectInstanceShadowStorage(irClass: IrClass): LLVMValueRef { assert (!irClass.isUnit()) - assert (irClass.objectIsShared) + assert (irClass.storageKind(context) == ObjectStorageKind.SHARED) val llvmGlobal = if (!isExternal(irClass)) { context.llvmDeclarations.forSingleton(irClass).instanceShadowFieldRef!! } else { @@ -893,6 +917,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, startLocationInfo: LocationInfo?, endLocationInfo: LocationInfo? = null ): LLVMValueRef { + // TODO: could be processed the same way as other stateless objects. if (irClass.isUnit()) { return codegen.theUnitInstanceRef.llvm } @@ -910,9 +935,12 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, ) } } + val storageKind = irClass.storageKind(context) + if (storageKind == ObjectStorageKind.PERMANENT) { + return loadSlot(codegen.getObjectInstanceStorage(irClass, storageKind), false) + } - val shared = irClass.objectIsShared && context.config.threadsAreAllowed - val objectPtr = codegen.getObjectInstanceStorage(irClass, shared) + val objectPtr = codegen.getObjectInstanceStorage(irClass, storageKind) val bbInit = basicBlock("label_init", startLocationInfo, endLocationInfo) val bbExit = basicBlock("label_continue", startLocationInfo, endLocationInfo) val objectVal = loadSlot(objectPtr, false) @@ -925,7 +953,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, val defaultConstructor = irClass.constructors.single { it.valueParameters.size == 0 } val ctor = codegen.llvmFunction(defaultConstructor) val (initFunction, args) = - if (shared) { + if (storageKind == ObjectStorageKind.SHARED && context.config.threadsAreAllowed) { val shadowObjectPtr = codegen.getObjectInstanceShadowStorage(irClass) context.llvm.initSharedInstanceFunction to listOf(objectPtr, shadowObjectPtr, typeInfo, ctor) } else { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index ee8094879da..8ce0b85612a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -14,7 +14,6 @@ import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.* import org.jetbrains.kotlin.backend.konan.llvm.coverage.LLVMCoverageInstrumentation import org.jetbrains.kotlin.backend.konan.optimizations.DataFlowIR -import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.UnsignedType import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrElement @@ -33,33 +32,43 @@ import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.descriptorUtil.classId import org.jetbrains.kotlin.resolve.descriptorUtil.module -internal enum class FieldStorage { +internal enum class FieldStorageKind { MAIN_THREAD, SHARED, THREAD_LOCAL } -// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well? -val IrClass.objectIsShared get() = - !annotations.hasAnnotation(KonanFqNames.threadLocal) +internal enum class ObjectStorageKind { + PERMANENT, + THREAD_LOCAL, + SHARED +} -internal val IrField.storageClass: FieldStorage get() { +// TODO: maybe unannotated singleton objects shall be accessed from main thread only as well? +internal val IrField.storageKind: FieldStorageKind get() { // TODO: Is this correct? val annotations = correspondingPropertySymbol?.owner?.annotations ?: annotations return when { - annotations.hasAnnotation(KonanFqNames.threadLocal) -> FieldStorage.THREAD_LOCAL - !isFinal -> FieldStorage.MAIN_THREAD - annotations.hasAnnotation(KonanFqNames.sharedImmutable) -> FieldStorage.SHARED + annotations.hasAnnotation(KonanFqNames.threadLocal) -> FieldStorageKind.THREAD_LOCAL + !isFinal -> FieldStorageKind.MAIN_THREAD + annotations.hasAnnotation(KonanFqNames.sharedImmutable) -> FieldStorageKind.SHARED // TODO: simplify, once IR types are fully there. (type.classifierOrNull?.owner as? IrAnnotationContainer) - ?.annotations?.hasAnnotation(KonanFqNames.frozen) == true -> FieldStorage.SHARED - else -> FieldStorage.MAIN_THREAD + ?.annotations?.hasAnnotation(KonanFqNames.frozen) == true -> FieldStorageKind.SHARED + else -> FieldStorageKind.MAIN_THREAD } } +internal fun IrClass.storageKind(context: Context): ObjectStorageKind = when { + this.annotations.hasAnnotation(KonanFqNames.threadLocal) && + context.config.threadsAreAllowed -> ObjectStorageKind.THREAD_LOCAL + this.hasConstStateAndNoSideEffects(context) -> ObjectStorageKind.PERMANENT + else -> ObjectStorageKind.SHARED +} + val IrField.isMainOnlyNonPrimitive get() = when { descriptor.type.computePrimitiveBinaryTypeOrNull() != null -> false - else -> storageClass == FieldStorage.MAIN_THREAD + else -> storageKind == FieldStorageKind.MAIN_THREAD } internal fun verifyModule(llvmModule: LLVMModuleRef, current: String = "") { @@ -384,10 +393,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map?) { - if (it.storageClass != FieldStorage.THREAD_LOCAL) { + if (it.storageKind != FieldStorageKind.THREAD_LOCAL) { val initialization = evaluateExpression(it.initializer!!.expression) val address = context.llvmDeclarations.forStaticField(it).storage - if (it.storageClass == FieldStorage.SHARED) + if (it.storageKind == FieldStorageKind.SHARED) freeze(initialization, currentCodeContext.exceptionHandler) storeAny(initialization, address, false) } @@ -400,7 +409,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map?) { - if (it.storageClass == FieldStorage.THREAD_LOCAL) { + if (it.storageKind == FieldStorageKind.THREAD_LOCAL) { val initialization = evaluateExpression(it.initializer!!.expression) val address = context.llvmDeclarations.forStaticField(it).storage storeAny(initialization, address, false) @@ -413,7 +422,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { + val fields = context.getLayoutBuilder(declaration).fields + return Array(fields.size) { index -> + val initializer = fields[index].initializer!!.expression as IrConst<*> + constValue(evaluateConst(initializer)) + } + } override fun visitProperty(declaration: IrProperty) { declaration.getter?.acceptVoid(this) @@ -1527,7 +1549,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map && + (initExpression.type.isPrimitiveType() || initExpression.type.isString())) { + IrExpressionBodyImpl(initExpression.copy()) + } else { + null + } return declaration } }) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt index 4dedbda9ceb..5d45b11dae6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGBuilder.kt @@ -225,14 +225,15 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag } override fun visitField(declaration: IrField) { - declaration.initializer?.let { - DEBUG_OUTPUT(0) { - println("Analysing global field ${declaration.descriptor}") - println("IR: ${ir2stringWhole(declaration)}") + if (declaration.parent is IrFile) + declaration.initializer?.let { + DEBUG_OUTPUT(0) { + println("Analysing global field ${declaration.descriptor}") + println("IR: ${ir2stringWhole(declaration)}") + } + analyze(declaration, IrSetFieldImpl(it.startOffset, it.endOffset, declaration.symbol, null, + it.expression, context.irBuiltIns.unitType)) } - analyze(declaration, IrSetFieldImpl(it.startOffset, it.endOffset, declaration.symbol, null, - it.expression, context.irBuiltIns.unitType)) - } } private fun analyze(declaration: IrDeclaration, body: IrElement?) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt index 157f21e2261..fe47bc88cec 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DataFlowIR.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.backend.konan.optimizations +import org.jetbrains.kotlin.backend.common.ir.ir2stringWhole import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract import org.jetbrains.kotlin.backend.konan.descriptors.target @@ -487,7 +488,10 @@ internal object DataFlowIR { } override fun visitField(declaration: IrField) { - declaration.initializer?.let { mapFunction(declaration) } + if (declaration.parent is IrFile) + declaration.initializer?.let { + mapFunction(declaration) + } } override fun visitClass(declaration: IrClass) { diff --git a/backend.native/tests/runtime/memory/var1.kt b/backend.native/tests/runtime/memory/var1.kt index 5f114612058..774c02f8d91 100644 --- a/backend.native/tests/runtime/memory/var1.kt +++ b/backend.native/tests/runtime/memory/var1.kt @@ -16,7 +16,7 @@ fun foo(x: Any, y: Any) { y.use() } -@Test fun runTest() { +@Test fun runTest1() { var x = Integer(0) for (i in 0..1) { @@ -31,4 +31,32 @@ fun foo(x: Any, y: Any) { fun Any?.use() { var x = this +} + +@kotlin.native.internal.CanBePrecreated +object CompileTime { + + const val int = Int.MIN_VALUE + const val byte = Byte.MIN_VALUE + const val short = Short.MIN_VALUE + const val long = Long.MIN_VALUE + const val boolean = true + const val float = 1.0f + const val double = 1.0 + const val char = Char.MIN_VALUE +} + +class AClass { + companion object {} +} + +@Test fun runTest2() { + assertEquals(Int.MIN_VALUE, CompileTime.int) + assertEquals(Byte.MIN_VALUE, CompileTime.byte) + assertEquals(Short.MIN_VALUE, CompileTime.short) + assertEquals(Long.MIN_VALUE, CompileTime.long) + assertEquals(true, CompileTime.boolean) + assertEquals(1.0f, CompileTime.float) + assertEquals(1.0, CompileTime.double) + assertEquals(Char.MIN_VALUE, CompileTime.char) } \ No newline at end of file diff --git a/runtime/src/main/kotlin/kotlin/Char.kt b/runtime/src/main/kotlin/kotlin/Char.kt index 65796659f2f..d3c424e82e2 100644 --- a/runtime/src/main/kotlin/kotlin/Char.kt +++ b/runtime/src/main/kotlin/kotlin/Char.kt @@ -65,6 +65,7 @@ public class Char private constructor() : Comparable { @TypedIntrinsic(IntrinsicType.UNSIGNED_TO_FLOAT) external public fun toDouble(): Double + @kotlin.native.internal.CanBePrecreated companion object { /** * The minimum value of a character code unit. diff --git a/runtime/src/main/kotlin/kotlin/Primitives.kt b/runtime/src/main/kotlin/kotlin/Primitives.kt index e796ff8e5a9..9fd019ac754 100644 --- a/runtime/src/main/kotlin/kotlin/Primitives.kt +++ b/runtime/src/main/kotlin/kotlin/Primitives.kt @@ -7,15 +7,16 @@ package kotlin +import kotlin.native.internal.CanBePrecreated +import kotlin.native.internal.IntrinsicType import kotlin.native.internal.NumberConverter import kotlin.native.internal.TypedIntrinsic -import kotlin.native.internal.IntrinsicType /** * Represents a 8-bit signed integer. */ public final class Byte private constructor() : Number(), Comparable { - + @CanBePrecreated companion object { /** * A constant holding the minimum value an instance of Byte can have. @@ -284,6 +285,7 @@ public final class Byte private constructor() : Number(), Comparable { * Represents a 16-bit signed integer. */ public final class Short private constructor() : Number(), Comparable { + @CanBePrecreated companion object { /** * A constant holding the minimum value an instance of Short can have. @@ -551,6 +553,7 @@ public final class Short private constructor() : Number(), Comparable { * Represents a 32-bit signed integer. */ public final class Int private constructor() : Number(), Comparable { + @CanBePrecreated companion object { /** * A constant holding the minimum value an instance of Int can have. @@ -844,6 +847,7 @@ public final class Int private constructor() : Number(), Comparable { * Represents a 64-bit signed integer. */ public final class Long private constructor() : Number(), Comparable { + @CanBePrecreated companion object { /** * A constant holding the minimum value an instance of Long can have. diff --git a/runtime/src/main/kotlin/kotlin/collections/HashMap.kt b/runtime/src/main/kotlin/kotlin/collections/HashMap.kt index 4757343ccd1..7c982e0c464 100644 --- a/runtime/src/main/kotlin/kotlin/collections/HashMap.kt +++ b/runtime/src/main/kotlin/kotlin/collections/HashMap.kt @@ -505,8 +505,9 @@ actual class HashMap private constructor( internal fun valuesIterator() = ValuesItr(this) internal fun entriesIterator() = EntriesItr(this) + @kotlin.native.internal.CanBePrecreated private companion object { - const val MAGIC = 2654435769L.toInt() // golden ratio + const val MAGIC = -1640531527 // 2654435769L.toInt(), golden ratio const val INITIAL_CAPACITY = 8 const val INITIAL_MAX_PROBE_DISTANCE = 2 const val TOMBSTONE = -1 diff --git a/runtime/src/main/kotlin/kotlin/native/BitSet.kt b/runtime/src/main/kotlin/kotlin/native/BitSet.kt index 3357030bbe3..7dd03267076 100644 --- a/runtime/src/main/kotlin/kotlin/native/BitSet.kt +++ b/runtime/src/main/kotlin/kotlin/native/BitSet.kt @@ -13,6 +13,7 @@ package kotlin.native */ public class BitSet(size: Int = ELEMENT_SIZE) { + @kotlin.native.internal.CanBePrecreated companion object { // Default size of one element in the array used to store bits. private const val ELEMENT_SIZE = 64 diff --git a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt index f9e35e80c6c..fc2a7154685 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/Annotations.kt @@ -121,4 +121,13 @@ annotation class Independent * Marks a class whose instances to be added to the list of leak detector candidates. */ @Target(AnnotationTarget.CLASS) -@PublishedApi internal annotation class LeakDetectorCandidate \ No newline at end of file +@PublishedApi internal annotation class LeakDetectorCandidate + +/** + * Indicates that given top level signleton object can be created in compile time and thus + * members access doesn't need to use an init barrier and allow better optimizations for + * field access, such as constant folding. + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +public annotation class CanBePrecreated diff --git a/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt b/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt index c4f06ef7880..64612ed3cd4 100644 --- a/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt +++ b/runtime/src/main/kotlin/kotlin/native/internal/NativePtr.kt @@ -12,6 +12,7 @@ external fun getNativeNullPtr(): NativePtr class NativePtr @PublishedApi internal constructor(private val value: NonNullNativePtr?) { companion object { + // TODO: make it properly precreated, maybe use an intrinsic for that. val NULL = getNativeNullPtr() }