Allow stateless and constant objects compile-time init . (#3645)

Allow stateless and constant objects compile-time init.
This commit is contained in:
Nikolay Igotti
2019-12-12 16:24:43 +03:00
committed by GitHub
parent 8f9d3ccfe4
commit 607210bea3
15 changed files with 158 additions and 49 deletions
@@ -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")
}
@@ -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"
}
@@ -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 {
@@ -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<IrE
context.llvm.fileInitializers
.forEach {
if (it.initializer?.expression !is IrConst<*>?) {
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<IrE
context.llvm.fileInitializers
.forEach {
if (it.initializer?.expression !is IrConst<*>?) {
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<IrE
appendingTo(bbLocalDeinit) {
context.llvm.fileInitializers.forEach {
// Only if a subject for memory management.
if (it.type.binaryTypeIsReference() && it.storageClass == FieldStorage.THREAD_LOCAL) {
if (it.type.binaryTypeIsReference() && it.storageKind == FieldStorageKind.THREAD_LOCAL) {
val address = context.llvmDeclarations.forStaticField(it).storage
storeHeapRef(codegen.kNullObjHeaderPtr, address)
}
@@ -426,7 +435,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
context.llvm.fileInitializers
// Only if a subject for memory management.
.forEach {
if (it.type.binaryTypeIsReference() && it.storageClass != FieldStorage.THREAD_LOCAL) {
if (it.type.binaryTypeIsReference() && it.storageKind != FieldStorageKind.THREAD_LOCAL) {
val address = context.llvmDeclarations.forStaticField(it).storage
storeHeapRef(codegen.kNullObjHeaderPtr, address)
}
@@ -738,9 +747,22 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
it.acceptVoid(this)
}
}
if (declaration.kind.isSingleton && !declaration.isUnit()) {
val value = context.llvmDeclarations.forSingleton(declaration).instanceFieldRef
LLVMSetInitializer(value, if (declaration.storageKind(context) == ObjectStorageKind.PERMANENT)
context.llvm.staticData.createConstKotlinObject(declaration,
*computeFields(declaration)).llvm else codegen.kNullObjHeaderPtr)
}
}
//-------------------------------------------------------------------------//
private fun computeFields(declaration: IrClass): Array<ConstValue> {
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<IrE
val globalValue = context.llvmDeclarations.forStaticField(value.symbol.owner).storage
if (context.config.threadsAreAllowed && value.symbol.owner.isMainOnlyNonPrimitive)
functionGenerationContext.checkMainThread(currentCodeContext.exceptionHandler)
if (value.symbol.owner.storageClass == FieldStorage.SHARED)
if (value.symbol.owner.storageKind == FieldStorageKind.SHARED)
functionGenerationContext.freeze(valueToAssign, currentCodeContext.exceptionHandler)
functionGenerationContext.storeAny(valueToAssign, globalValue, false)
}
@@ -273,12 +273,10 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
} else {
"kobjref:" + qualifyInternalName(irClass)
}
val threadLocal = !(irClass.objectIsShared && context.config.threadsAreAllowed)
val threadLocal = irClass.storageKind(context) == ObjectStorageKind.THREAD_LOCAL
val instanceFieldRef = addGlobal(
symbolName, getLLVMType(irClass.defaultType), isExported = isExported, threadLocal = threadLocal)
LLVMSetInitializer(instanceFieldRef, kNullObjHeaderPtr)
val instanceShadowFieldRef =
if (threadLocal) null
else {
@@ -329,7 +327,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
val name = "kvar:" + qualifyInternalName(declaration)
val storage = addGlobal(
name, getLLVMType(declaration.type), isExported = false,
threadLocal = declaration.storageClass == FieldStorage.THREAD_LOCAL)
threadLocal = declaration.storageKind == FieldStorageKind.THREAD_LOCAL)
this.staticFields[declaration] = StaticFieldLlvmDeclarations(storage)
}
@@ -22,6 +22,8 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.isPrimitiveType
import org.jetbrains.kotlin.ir.types.isString
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
@@ -64,6 +66,7 @@ internal class InitializersLowering(val context: CommonBackendContext) : ClassLo
val initializer = declaration.initializer ?: return declaration
val startOffset = initializer.startOffset
val endOffset = initializer.endOffset
val initExpression = initializer.expression
initializers.add(IrBlockImpl(startOffset, endOffset,
context.irBuiltIns.unitType,
STATEMENT_ORIGIN_ANONYMOUS_INITIALIZER,
@@ -73,11 +76,19 @@ internal class InitializersLowering(val context: CommonBackendContext) : ClassLo
startOffset, endOffset,
irClass.thisReceiver!!.type, irClass.thisReceiver!!.symbol
),
initializer.expression,
initExpression,
context.irBuiltIns.unitType,
STATEMENT_ORIGIN_ANONYMOUS_INITIALIZER)))
)
declaration.initializer = null
// We shall keep initializer for constants for compile-time instantiation.
declaration.initializer =
if (initExpression is IrConst<*> &&
(initExpression.type.isPrimitiveType() || initExpression.type.isString())) {
IrExpressionBodyImpl(initExpression.copy())
} else {
null
}
return declaration
}
})
@@ -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?) {
@@ -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) {
+29 -1
View File
@@ -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)
}
+1
View File
@@ -65,6 +65,7 @@ public class Char private constructor() : Comparable<Char> {
@TypedIntrinsic(IntrinsicType.UNSIGNED_TO_FLOAT)
external public fun toDouble(): Double
@kotlin.native.internal.CanBePrecreated
companion object {
/**
* The minimum value of a character code unit.
+6 -2
View File
@@ -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<Byte> {
@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<Byte> {
* Represents a 16-bit signed integer.
*/
public final class Short private constructor() : Number(), Comparable<Short> {
@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<Short> {
* Represents a 32-bit signed integer.
*/
public final class Int private constructor() : Number(), Comparable<Int> {
@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<Int> {
* Represents a 64-bit signed integer.
*/
public final class Long private constructor() : Number(), Comparable<Long> {
@CanBePrecreated
companion object {
/**
* A constant holding the minimum value an instance of Long can have.
@@ -505,8 +505,9 @@ actual class HashMap<K, V> 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
@@ -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
@@ -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
@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
@@ -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()
}