From cd4f116f78caa683fd3e739c699e741a5e4e1c35 Mon Sep 17 00:00:00 2001 From: SvyatoslavScherbina Date: Fri, 10 Aug 2018 09:57:15 +0300 Subject: [PATCH] Support inline classes (#1840) --- .../jvm/kotlin/kotlinx/cinterop/JvmTypes.kt | 3 + .../src/main/kotlin/kotlinx/cinterop/Types.kt | 14 +- .../kotlin/kotlinx/cinterop/NativeTypes.kt | 5 + .../cli/bc/K2NativeCompilerArguments.kt | 6 + .../kotlin/backend/konan/BinaryType.kt | 31 ++ .../jetbrains/kotlin/backend/konan/Boxing.kt | 255 +++++++++--- .../kotlin/backend/konan/CAdapterGenerator.kt | 84 ++-- .../jetbrains/kotlin/backend/konan/Context.kt | 49 ++- .../kotlin/backend/konan/InlineClasses.kt | 298 ++++++++++++++ .../kotlin/backend/konan/InteropUtils.kt | 4 +- .../kotlin/backend/konan/KonanLower.kt | 2 +- .../kotlin/backend/konan/KonanPlatform.kt | 2 + .../kotlin/backend/konan/ValueTypes.kt | 171 -------- .../konan/descriptors/DescriptorUtils.kt | 35 +- .../jetbrains/kotlin/backend/konan/ir/Ir.kt | 34 +- .../backend/konan/llvm/BinaryInterface.kt | 4 +- .../kotlin/backend/konan/llvm/DataLayout.kt | 38 +- .../kotlin/backend/konan/llvm/IrToBitcode.kt | 43 +- .../backend/konan/llvm/LlvmDeclarations.kt | 9 +- .../backend/konan/llvm/RTTIGenerator.kt | 16 +- .../objcexport/ObjCExportCodeGenerator.kt | 20 +- .../kotlin/backend/konan/lower/Autoboxing.kt | 371 +++++++++++++++--- .../konan/lower/BuiltinOperatorLowering.kt | 195 +++++---- .../backend/konan/lower/EnumWhenLowering.kt | 6 +- .../backend/konan/lower/InteropLowering.kt | 7 +- .../lower/KonanDefaultParameterInjector.kt | 57 +++ .../objcexport/ObjCExportHeaderGenerator.kt | 6 +- .../konan/objcexport/ObjCExportMapper.kt | 37 +- .../backend/konan/objcexport/objcTypes.kt | 19 +- .../backend/konan/optimizations/DFGBuilder.kt | 78 +--- .../konan/optimizations/DFGSerializer.kt | 19 +- .../backend/konan/optimizations/DataFlowIR.kt | 60 ++- .../konan/optimizations/Devirtualization.kt | 18 +- .../konan/optimizations/EscapeAnalysis.kt | 3 +- .../konan/serialization/DescriptorTable.kt | 4 +- .../org/jetbrains/kotlin/ir/util/IrUtils2.kt | 3 + backend.native/tests/build.gradle | 4 + .../function/defaultsWithInlineClasses.kt | 11 + .../tests/framework/values/values.kt | 11 + .../tests/framework/values/values.swift | 20 + .../tests/produce_dynamic/simple/hello.kt | 12 +- .../tests/produce_dynamic/simple/main.c | 2 + .../src/main/kotlin/konan/internal/Boxing.kt | 236 +---------- .../main/kotlin/konan/internal/Intrinsics.kt | 26 +- .../main/kotlin/konan/internal/NativePtr.kt | 8 +- .../main/kotlin/konan/internal/ValueTypes.kt | 26 ++ runtime/src/main/kotlin/kotlin/Boolean.kt | 2 +- runtime/src/main/kotlin/kotlin/Char.kt | 6 +- runtime/src/main/kotlin/kotlin/Primitives.kt | 12 +- 49 files changed, 1486 insertions(+), 896 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryType.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InlineClasses.kt delete mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/KonanDefaultParameterInjector.kt create mode 100644 backend.native/tests/codegen/function/defaultsWithInlineClasses.kt create mode 100644 runtime/src/main/kotlin/konan/internal/ValueTypes.kt diff --git a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt index 2d821df1d81..f7db6445456 100644 --- a/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt +++ b/Interop/Runtime/src/jvm/kotlin/kotlinx/cinterop/JvmTypes.kt @@ -20,6 +20,9 @@ import java.util.concurrent.ConcurrentHashMap import kotlin.reflect.full.companionObjectInstance typealias NativePtr = Long +internal typealias NonNullNativePtr = NativePtr +@PublishedApi internal fun NonNullNativePtr.toNativePtr() = this +internal fun NativePtr.toNonNull(): NonNullNativePtr = this val nativeNullPtr: NativePtr = 0L // TODO: the functions below should eventually be intrinsified diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt index c3aec59c927..ad30ff38329 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Types.kt @@ -24,8 +24,8 @@ package kotlinx.cinterop * * TODO: the behavior of [equals], [hashCode] and [toString] differs on Native and JVM backends. */ -abstract class NativePointed(rawPtr: NativePtr) { - var rawPtr = rawPtr +open class NativePointed internal constructor(rawPtr: NonNullNativePtr) { + var rawPtr = rawPtr.toNativePtr() internal set } @@ -40,7 +40,7 @@ val NativePointed?.rawPtr: NativePtr */ inline fun interpretPointed(ptr: NativePtr): T = interpretNullablePointed(ptr)!! -private class OpaqueNativePointed(rawPtr: NativePtr) : NativePointed(rawPtr) +private class OpaqueNativePointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull()) fun interpretOpaquePointed(ptr: NativePtr): NativePointed = interpretPointed(ptr) fun interpretNullableOpaquePointed(ptr: NativePtr): NativePointed? = interpretNullablePointed(ptr) @@ -53,7 +53,7 @@ inline fun NativePointed.reinterpret(): T = interpre /** * C data or code. */ -abstract class CPointed(rawPtr: NativePtr) : NativePointed(rawPtr) +abstract class CPointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull()) /** * Represents a reference to (possibly empty) sequence of C values. @@ -130,7 +130,11 @@ abstract class CValue : CValues() /** * C pointer. */ -class CPointer internal constructor(val rawValue: NativePtr) : CValuesRef() { +class CPointer internal constructor(@PublishedApi internal val value: NonNullNativePtr) : CValuesRef() { + + // TODO: replace by [value]. + @Suppress("NOTHING_TO_INLINE") + inline val rawValue: NativePtr get() = value.toNativePtr() override fun equals(other: Any?): Boolean { if (this === other) { diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt index 9861323de19..51a8578bb33 100644 --- a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/NativeTypes.kt @@ -18,8 +18,13 @@ package kotlinx.cinterop import konan.internal.getNativeNullPtr import konan.internal.Intrinsic +import konan.internal.reinterpret typealias NativePtr = konan.internal.NativePtr +internal typealias NonNullNativePtr = konan.internal.NonNullNativePtr + +@Suppress("NOTHING_TO_INLINE") +internal inline fun NativePtr.toNonNull() = this.reinterpret() inline val nativeNullPtr: NativePtr get() = getNativeNullPtr() diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index 6bc268cada5..bc364e0e146 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -18,6 +18,8 @@ package org.jetbrains.kotlin.cli.bc import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.Argument +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.config.LanguageFeature class K2NativeCompilerArguments : CommonCompilerArguments() { // First go the options interesting to the general public. @@ -147,5 +149,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { description = "Paths to friend modules" ) var friendModules: String? = null + + override fun configureLanguageFeatures(collector: MessageCollector) = super.configureLanguageFeatures(collector).also { + it[LanguageFeature.InlineClasses] = LanguageFeature.State.ENABLED // TODO: remove after updating to 1.3. + } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryType.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryType.kt new file mode 100644 index 00000000000..398fa37526e --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/BinaryType.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan + +sealed class BinaryType { + class Primitive(val type: PrimitiveBinaryType) : BinaryType() + class Reference(val types: Sequence, val nullable: Boolean) : BinaryType() +} + +fun BinaryType<*>.primitiveBinaryTypeOrNull(): PrimitiveBinaryType? = when (this) { + is BinaryType.Primitive -> this.type + is BinaryType.Reference -> null +} + +enum class PrimitiveBinaryType { + BOOLEAN, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, POINTER +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt index 488668faf8c..dc210707e7f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt @@ -16,51 +16,180 @@ package org.jetbrains.kotlin.backend.konan -import kotlinx.cinterop.toByte import llvm.* import org.jetbrains.kotlin.backend.konan.ir.KonanSymbols +import org.jetbrains.kotlin.backend.konan.irasdescriptors.fqNameSafe import org.jetbrains.kotlin.backend.konan.llvm.* +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.SourceElement +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.annotations.Annotations +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl +import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl +import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.util.defaultType -import org.jetbrains.kotlin.ir.util.getPropertyGetter +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.KotlinType -internal fun KonanSymbols.getTypeConversion(actualType: KotlinType, expectedType: KotlinType) = - getTypeConversion(actualType.correspondingValueType, expectedType.correspondingValueType) +internal fun KonanSymbols.getTypeConversion(actualType: IrType, expectedType: IrType): IrSimpleFunctionSymbol? = + getTypeConversionImpl(actualType.getInlinedClass(), expectedType.getInlinedClass()) -internal fun KonanSymbols.getTypeConversion(actualType: IrType, expectedType: IrType) = - getTypeConversion(actualType.correspondingValueType, expectedType.correspondingValueType) +internal fun KonanSymbols.getTypeConversion(actualType: KotlinType, expectedType: KotlinType): IrSimpleFunctionSymbol? { + // TODO: rework all usages and remove this method. + val actualInlinedClass = actualType.getInlinedClass()?.let { context.ir.get(it) } + val expectedInlinedClass = expectedType.getInlinedClass()?.let { context.ir.get(it) } -internal fun KonanSymbols.getTypeConversion( - actualValueType: ValueType?, - expectedValueType: ValueType? + return getTypeConversionImpl(actualInlinedClass, expectedInlinedClass) +} + +private fun KonanSymbols.getTypeConversionImpl( + actualInlinedClass: IrClass?, + expectedInlinedClass: IrClass? ): IrSimpleFunctionSymbol? { + if (actualInlinedClass == expectedInlinedClass) return null return when { - actualValueType == expectedValueType -> null + actualInlinedClass == null && expectedInlinedClass == null -> null + actualInlinedClass != null && expectedInlinedClass == null -> context.getBoxFunction(actualInlinedClass) + actualInlinedClass == null && expectedInlinedClass != null -> context.getUnboxFunction(expectedInlinedClass) + else -> error("actual type is ${actualInlinedClass?.fqNameSafe}, expected ${expectedInlinedClass?.fqNameSafe}") + }?.symbol +} - actualValueType == null && expectedValueType != null -> { - // This may happen in the following cases: - // 1. `actualType` is `Nothing`; - // 2. `actualType` is incompatible. +internal val Context.getBoxFunction: (IrClass) -> IrSimpleFunction by Context.lazyMapMember { inlinedClass -> + assert(inlinedClass.isUsedAsBoxClass()) - this.getUnboxFunction(expectedValueType) - } + val symbols = ir.symbols - actualValueType != null && expectedValueType == null -> { - this.boxFunctions[actualValueType]!! - } + val isNullable = inlinedClass.inlinedClassIsNullable() + val unboxedType = inlinedClass.defaultOrNullableType(isNullable) + val boxedType = symbols.any.owner.defaultOrNullableType(isNullable) - else -> throw IllegalArgumentException("actual type is $actualValueType, expected $expectedValueType") + val parameterType = unboxedType + val returnType = boxedType + + val descriptor = SimpleFunctionDescriptorImpl.create( + inlinedClass.descriptor, + Annotations.EMPTY, + Name.special(""), + CallableMemberDescriptor.Kind.DECLARATION, + SourceElement.NO_SOURCE + ) + + val parameter = ValueParameterDescriptorImpl( + descriptor, + null, + 0, + Annotations.EMPTY, + Name.identifier("value"), + parameterType.toKotlinType(), + false, + false, + false, + null, + SourceElement.NO_SOURCE + ) + + descriptor.initialize( + null, + null, + emptyList(), + listOf(parameter), + returnType.toKotlinType(), + Modality.FINAL, + Visibilities.PUBLIC + ) + + val startOffset = inlinedClass.startOffset + val endOffset = inlinedClass.endOffset + + IrFunctionImpl(startOffset, endOffset, IrDeclarationOrigin.DEFINED, descriptor).apply { + this.returnType = returnType + this.valueParameters.add(IrValueParameterImpl( + startOffset, + endOffset, + IrDeclarationOrigin.DEFINED, + parameter, + parameterType, + null + )) + + this.parent = inlinedClass } } -internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFunctionSymbol = - this.unboxFunctions[valueType] - ?: this.boxClasses[valueType]!!.getPropertyGetter("value")!! as IrSimpleFunctionSymbol +internal val Context.getUnboxFunction: (IrClass) -> IrSimpleFunction by Context.lazyMapMember { inlinedClass -> + assert(inlinedClass.isUsedAsBoxClass()) + val symbols = ir.symbols + + val isNullable = inlinedClass.inlinedClassIsNullable() + val unboxedType = inlinedClass.defaultOrNullableType(isNullable) + val boxedType = symbols.any.owner.defaultOrNullableType(isNullable) + + val parameterType = boxedType + val returnType = unboxedType + + val descriptor = SimpleFunctionDescriptorImpl.create( + inlinedClass.descriptor, + Annotations.EMPTY, + Name.special(""), + CallableMemberDescriptor.Kind.DECLARATION, + SourceElement.NO_SOURCE + ) + + val parameter = ValueParameterDescriptorImpl( + descriptor, + null, + 0, + Annotations.EMPTY, + Name.identifier("value"), + parameterType.toKotlinType(), + false, + false, + false, + null, + SourceElement.NO_SOURCE + ) + + descriptor.initialize( + null, + null, + emptyList(), + listOf(parameter), + returnType.toKotlinType(), + Modality.FINAL, + Visibilities.PUBLIC + ) + + val startOffset = inlinedClass.startOffset + val endOffset = inlinedClass.endOffset + + IrFunctionImpl(startOffset, endOffset, IrDeclarationOrigin.DEFINED, descriptor).apply { + this.returnType = returnType + + this.valueParameters.add(IrValueParameterImpl( + startOffset, + endOffset, + IrDeclarationOrigin.DEFINED, + parameter, + parameterType, + null + )) + + this.parent = inlinedClass + } +} /** * Initialize static boxing. @@ -68,13 +197,11 @@ internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFuncti */ internal fun initializeCachedBoxes(context: Context) { if (context.config.produce.isNativeBinary) { - val cachedTypes = listOf(ValueType.BOOLEAN, ValueType.BYTE, ValueType.CHAR, - ValueType.SHORT, ValueType.INT, ValueType.LONG) - cachedTypes.forEach { valueType -> - val cacheName = "${valueType.name}_CACHE" - val rangeStart = "${valueType.name}_RANGE_FROM" - val rangeEnd = "${valueType.name}_RANGE_TO" - valueType.initCache(context, cacheName, rangeStart, rangeEnd) + BoxCache.values().forEach { cache -> + val cacheName = "${cache.name}_CACHE" + val rangeStart = "${cache.name}_RANGE_FROM" + val rangeEnd = "${cache.name}_RANGE_TO" + initCache(cache, context, cacheName, rangeStart, rangeEnd) } } } @@ -82,48 +209,56 @@ internal fun initializeCachedBoxes(context: Context) { /** * Adds global that refers to the cache. */ -private fun ValueType.initCache(context: Context, cacheName: String, - rangeStartName: String, rangeEndName: String) { - val kotlinType = context.ir.symbols.boxClasses[this]!!.owner - val (start, end) = context.config.target.getBoxCacheRange(this) +private fun initCache(cache: BoxCache, context: Context, cacheName: String, + rangeStartName: String, rangeEndName: String) { + + val kotlinType = context.irBuiltIns.getKotlinClass(cache) + val staticData = context.llvm.staticData + val llvmType = staticData.getLLVMType(kotlinType.defaultType) + + val (start, end) = context.config.target.getBoxCacheRange(cache) // Constancy of these globals allows LLVM's constant propagation and DCE // to remove fast path of boxing function in case of empty range. - context.llvm.staticData.placeGlobal(rangeStartName, createConstant(start), true) + staticData.placeGlobal(rangeStartName, createConstant(llvmType, start), true) .setConstant(true) - context.llvm.staticData.placeGlobal(rangeEndName, createConstant(end), true) + staticData.placeGlobal(rangeEndName, createConstant(llvmType, end), true) .setConstant(true) - val staticData = context.llvm.staticData - val values = (start..end).map { staticData.createInitializer(kotlinType, createConstant(it)) } - val llvmBoxType = structType(context.llvm.runtime.objHeaderType, this.llvmType) + val values = (start..end).map { staticData.createInitializer(kotlinType, createConstant(llvmType, it)) } + val llvmBoxType = structType(context.llvm.runtime.objHeaderType, llvmType) staticData.placeGlobalConstArray(cacheName, llvmBoxType, values, true).llvm } -private fun ValueType.createConstant(value: Int) = - constValue(when (this) { - ValueType.BOOLEAN -> LLVMConstInt(LLVMInt1Type(), (value > 0).toByte().toLong(), 1)!! - ValueType.BYTE -> LLVMConstInt(LLVMInt8Type(), value.toByte().toLong(), 1)!! - ValueType.CHAR -> LLVMConstInt(LLVMInt16Type(), value.toChar().toLong(), 0)!! - ValueType.SHORT -> LLVMConstInt(LLVMInt16Type(), value.toShort().toLong(), 1)!! - ValueType.INT -> LLVMConstInt(LLVMInt32Type(), value.toLong(), 1)!! - ValueType.LONG -> LLVMConstInt(LLVMInt64Type(), value.toLong(), 1)!! - else -> error("Cannot box value of type $this") - }) +private fun createConstant(llvmType: LLVMTypeRef, value: Int): ConstValue = + constValue(LLVMConstInt(llvmType, value.toLong(), 1)!!) // When start is greater than end then `inRange` check is always false // and can be eliminated by LLVM. private val emptyRange = 1 to 0 // Memory usage is around 20kb. -private val defaultCacheRanges = mapOf( - ValueType.BOOLEAN to (0 to 1), - ValueType.BYTE to (-128 to 127), - ValueType.SHORT to (-128 to 127), - ValueType.CHAR to (0 to 255), - ValueType.INT to (-128 to 127), - ValueType.LONG to (-128 to 127) -) +private val BoxCache.defaultRange get() = when (this) { + BoxCache.BOOLEAN -> (0 to 1) + BoxCache.BYTE -> (-128 to 127) + BoxCache.SHORT -> (-128 to 127) + BoxCache.CHAR -> (0 to 255) + BoxCache.INT -> (-128 to 127) + BoxCache.LONG -> (-128 to 127) +} -fun KonanTarget.getBoxCacheRange(valueType: ValueType): Pair = when (this) { +private fun KonanTarget.getBoxCacheRange(cache: BoxCache): Pair = when (this) { is KonanTarget.ZEPHYR -> emptyRange - else -> defaultCacheRanges[valueType]!! + else -> cache.defaultRange +} + +internal fun IrBuiltIns.getKotlinClass(cache: BoxCache): IrClass = when (cache) { + BoxCache.BOOLEAN -> booleanClass + BoxCache.BYTE -> byteClass + BoxCache.SHORT -> shortClass + BoxCache.CHAR -> charClass + BoxCache.INT -> intClass + BoxCache.LONG -> longClass +}.owner + +enum class BoxCache { + BOOLEAN, BYTE, SHORT, CHAR, INT, LONG } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt index dd2a1d60d5d..c21d4913b07 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CAdapterGenerator.kt @@ -116,12 +116,6 @@ private val cnameAnnotation = FqName("konan.internal.CName") private fun org.jetbrains.kotlin.types.KotlinType.isGeneric() = constructor.declarationDescriptor is TypeParameterDescriptor -private val ClassDescriptor.isString - get() = fqNameSafe.asString() == "kotlin.String" - -private val ClassDescriptor.isValueType - get() = this.defaultType.correspondingValueType != null - private fun isExportedFunction(descriptor: FunctionDescriptor): Boolean { if (!descriptor.isEffectivelyPublicApi || !descriptor.kind.isReal || descriptor.isExpect) @@ -143,6 +137,8 @@ private fun isExportedClass(descriptor: ClassDescriptor): Boolean { // Do not export types with type parameters. // TODO: is it correct? if (!descriptor.declaredTypeParameters.isEmpty()) return false + // Do not export inline classes for now. TODO: add proper support. + if (descriptor.isInlined()) return false return true } @@ -372,7 +368,7 @@ private class ExportedElement(val kind: ElementKind, private fun translateArgument(name: String, clazz: ClassDescriptor, direction: Direction, builder: StringBuilder): String { return when { - clazz.isString -> + owner.isMappedToString(clazz) -> if (direction == Direction.C_TO_KOTLIN) { builder.append(" KObjHolder ${name}_holder;\n") "CreateStringFromCString($name, ${name}_holder.slot())" @@ -387,7 +383,7 @@ private class ExportedElement(val kind: ElementKind, "((${owner.translateType(clazz)}){ .pinned = CreateStablePointer(${name})})" } else -> { - assert(clazz.isValueType) { + assert(!clazz.defaultType.binaryTypeIsReference()) { println(clazz.toString()) } name @@ -733,7 +729,7 @@ internal class CAdapterGenerator( val set = mutableSetOf() defineUsedTypesImpl(scope, set) set.forEach { - if (isMappedToReference(it)) { + if (isMappedToReference(it) && !it.isInlined()) { output("typedef struct {", indent) output("${prefix}_KNativePtr pinned;", indent + 1) output("} ${translateType(it)};", indent) @@ -884,26 +880,32 @@ internal class CAdapterGenerator( "" to "set" ) - private val primitiveTypeMapping = mapOf( - ValueType.BOOLEAN to "${prefix}_KBoolean", - ValueType.BYTE to "${prefix}_KByte", - ValueType.SHORT to "${prefix}_KShort", - ValueType.INT to "${prefix}_KInt", - ValueType.LONG to "${prefix}_KLong", - ValueType.FLOAT to "${prefix}_KFloat", - ValueType.DOUBLE to "${prefix}_KDouble", - ValueType.CHAR to "${prefix}_KChar", - ValueType.C_POINTER to "void*", - ValueType.NATIVE_PTR to "void*", - ValueType.NATIVE_POINTED to "void*" - ) + private val primitiveTypeMapping = KonanPrimitiveType.values().associate { + it to when (it) { + KonanPrimitiveType.BOOLEAN -> "${prefix}_KBoolean" + KonanPrimitiveType.CHAR -> "${prefix}_KChar" + KonanPrimitiveType.BYTE -> "${prefix}_KByte" + KonanPrimitiveType.SHORT -> "${prefix}_KShort" + KonanPrimitiveType.INT -> "${prefix}_KInt" + KonanPrimitiveType.LONG -> "${prefix}_KLong" + KonanPrimitiveType.FLOAT -> "${prefix}_KFloat" + KonanPrimitiveType.DOUBLE -> "${prefix}_KDouble" + KonanPrimitiveType.NON_NULL_NATIVE_PTR -> "void*" + } + } - internal fun isMappedToString(descriptor: ClassDescriptor) = - descriptor.fqNameSafe.asString() == "kotlin.String" + internal fun isMappedToString(descriptor: ClassDescriptor): Boolean = + isMappedToString(descriptor.defaultType.computeBinaryType()) + + private fun isMappedToString(binaryType: BinaryType): Boolean = + when (binaryType) { + is BinaryType.Primitive -> false + is BinaryType.Reference -> binaryType.types.first() == context.builtIns.string + } internal fun isMappedToReference(descriptor: ClassDescriptor) = - !descriptor.isUnit() && !isMappedToString(descriptor) && - !primitiveTypeMapping.contains(descriptor.defaultType.correspondingValueType) + !isMappedToVoid(descriptor) && !isMappedToString(descriptor) && + descriptor.defaultType.binaryTypeIsReference() internal fun isMappedToVoid(descriptor: ClassDescriptor): Boolean { return descriptor.isUnit() @@ -917,17 +919,29 @@ internal class CAdapterGenerator( } } - private fun translateTypeFull(clazz: ClassDescriptor): Pair { - val fqName = clazz.fqNameSafe.asString() - val valueType = clazz.defaultType.correspondingValueType - return when { - clazz.isUnit() -> "void" to "void" - fqName == "kotlin.String" -> "const char*" to "KObjHeader*" - valueType != null && primitiveTypeMapping.contains(valueType) -> primitiveTypeMapping[valueType]!! to primitiveTypeMapping[valueType]!! - else -> "${prefix}_kref_${translateTypeFqName(clazz.fqNameSafe.asString())}" to "KObjHeader*" - } + private fun translateTypeFull(clazz: ClassDescriptor): Pair = if (isMappedToVoid(clazz)) { + "void" to "void" + } else { + translateNonVoidTypeFull(clazz.defaultType) } + private fun translateNonVoidTypeFull(type: KotlinType): Pair = type.unwrapToPrimitiveOrReference( + eachInlinedClass = { _, _ -> + // unsigned types to be handled. + }, + ifPrimitive = { primitiveType, _ -> + primitiveTypeMapping[primitiveType]!!.let { it to it } + }, + ifReference = { + val clazz = (it.computeBinaryType() as BinaryType.Reference).types.first() + if (clazz == context.builtIns.string) { + "const char*" to "KObjHeader*" + } else { + "${prefix}_kref_${translateTypeFqName(clazz.fqNameSafe.asString())}" to "KObjHeader*" + } + } + ) + fun translateType(clazz: ClassDescriptor): String = translateTypeFull(clazz).first fun translateTypeBridge(clazz: ClassDescriptor): String = translateTypeFull(clazz).second diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 3621e22f3ef..53d6e8bb681 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -59,6 +59,7 @@ import org.jetbrains.kotlin.serialization.deserialization.getName import org.jetbrains.kotlin.types.KotlinType import java.lang.System.out import kotlin.LazyThreadSafetyMode.PUBLICATION +import kotlin.reflect.KProperty internal class SpecialDeclarationsFactory(val context: Context) { private val enumSpecialDeclarationsFactory by lazy { EnumSpecialDeclarationsFactory(context) } @@ -140,14 +141,20 @@ internal class SpecialDeclarationsFactory(val context: Context) { BridgeDirection.FROM_VALUE_TYPE -> context.irBuiltIns.anyNType } - val extensionReceiverType = when (bridgeDirections.array[1]) { + val dispatchReceiver = when (bridgeDirections.array[1]) { + BridgeDirection.TO_VALUE_TYPE -> function.dispatchReceiverParameter!! + BridgeDirection.NOT_NEEDED -> function.dispatchReceiverParameter + BridgeDirection.FROM_VALUE_TYPE -> context.irBuiltIns.anyClass.owner.thisReceiver!! + } + + val extensionReceiverType = when (bridgeDirections.array[2]) { BridgeDirection.TO_VALUE_TYPE -> function.extensionReceiverParameter!!.type BridgeDirection.NOT_NEEDED -> function.extensionReceiverParameter?.type BridgeDirection.FROM_VALUE_TYPE -> context.irBuiltIns.anyNType } val valueParameterTypes = function.valueParameters.mapIndexed { index, valueParameter -> - when (bridgeDirections.array[index + 2]) { + when (bridgeDirections.array[index + 3]) { BridgeDirection.TO_VALUE_TYPE -> valueParameter.type BridgeDirection.NOT_NEEDED -> valueParameter.type BridgeDirection.FROM_VALUE_TYPE -> context.irBuiltIns.anyNType @@ -157,6 +164,7 @@ internal class SpecialDeclarationsFactory(val context: Context) { function, bridgeDirections, returnType, + dispatchReceiver, extensionReceiverType, valueParameterTypes ) @@ -171,7 +179,17 @@ internal class SpecialDeclarationsFactory(val context: Context) { this.parent = function.parent } - bridge.createDispatchReceiverParameter() + bridge.dispatchReceiverParameter = dispatchReceiver?.let { + IrValueParameterImpl( + it.startOffset, + it.endOffset, + IrDeclarationOrigin.DEFINED, + it.descriptor, + it.type, + null + ) + } + extensionReceiverType?.let { val extensionReceiverParameter = function.extensionReceiverParameter!! bridge.extensionReceiverParameter = IrValueParameterImpl( @@ -207,6 +225,7 @@ internal class SpecialDeclarationsFactory(val context: Context) { function: IrFunction, bridgeDirections: BridgeDirections, returnType: IrType, + dispatchReceiverParameter: IrValueParameter?, extensionReceiverType: IrType?, valueParameterTypes: List ): SimpleFunctionDescriptor { @@ -235,7 +254,7 @@ internal class SpecialDeclarationsFactory(val context: Context) { } bridgeDescriptor.initialize( /* receiverParameterType = */ extensionReceiverType?.toKotlinType(), - /* dispatchReceiverParameter = */ descriptor.dispatchReceiverParameter, + /* dispatchReceiverParameter = */ dispatchReceiverParameter?.descriptor as ReceiverParameterDescriptor?, /* typeParameters = */ descriptor.typeParameters, /* unsubstitutedValueParameters = */ valueParameters, /* unsubstitutedReturnType = */ returnType.toKotlinType(), @@ -262,6 +281,28 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) { } val specialDeclarationsFactory = SpecialDeclarationsFactory(this) + + class LazyMember(val initializer: Context.() -> T) { + operator fun getValue(thisRef: Context, property: KProperty<*>): T = thisRef.getValue(this) + } + + companion object { + fun lazyMember(initializer: Context.() -> T) = LazyMember(initializer) + + fun lazyMapMember(initializer: Context.(K) -> V): LazyMember<(K) -> V> = lazyMember { + val storage = mutableMapOf() + val result: (K) -> V = { + storage.getOrPut(it, { initializer(it) }) + } + result + } + } + + private val lazyValues = mutableMapOf, Any?>() + + fun getValue(member: LazyMember): T = + @Suppress("UNCHECKED_CAST") (lazyValues.getOrPut(member, { member.initializer(this) }) as T) + override val reflectionTypes: ReflectionTypes by lazy(PUBLICATION) { ReflectionTypes(moduleDescriptor, FqName("konan.internal")) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InlineClasses.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InlineClasses.kt new file mode 100644 index 00000000000..fecf90d26f1 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InlineClasses.kt @@ -0,0 +1,298 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan + +import org.jetbrains.kotlin.backend.konan.irasdescriptors.containsNull +import org.jetbrains.kotlin.backend.konan.irasdescriptors.fqNameSafe +import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.ir.declarations.IrClass +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol +import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol +import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.constructors +import org.jetbrains.kotlin.ir.util.defaultType +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.isNullable +import org.jetbrains.kotlin.types.typeUtil.makeNullable + +fun IrType.getInlinedClass(): IrClass? = IrTypeInlineClassesSupport.getInlinedClass(this) + +fun IrType.isInlined(): Boolean = IrTypeInlineClassesSupport.isInlined(this) +fun IrClass.isInlined(): Boolean = IrTypeInlineClassesSupport.isInlined(this) + +fun KotlinType.getInlinedClass(): ClassDescriptor? = KotlinTypeInlineClassesSupport.getInlinedClass(this) + +fun ClassDescriptor.isInlined(): Boolean = KotlinTypeInlineClassesSupport.isInlined(this) + +fun KotlinType.unwrapInlinedClasses() = KotlinTypeInlineClassesSupport.unwrappingInlinedClasses(this).last() + +internal inline fun KotlinType.unwrapToPrimitiveOrReference( + eachInlinedClass: (inlinedClass: ClassDescriptor, nullable: Boolean) -> Unit, + ifPrimitive: (primitiveType: KonanPrimitiveType, nullable: Boolean) -> R, + ifReference: (type: KotlinType) -> R +): R = KotlinTypeInlineClassesSupport.unwrapToPrimitiveOrReference(this, eachInlinedClass, ifPrimitive, ifReference) + +// TODO: consider renaming to `isReference`. +fun KotlinType.binaryTypeIsReference(): Boolean = this.computePrimitiveBinaryTypeOrNull() == null +fun IrType.binaryTypeIsReference(): Boolean = this.computePrimitiveBinaryTypeOrNull() == null + +fun KotlinType.computePrimitiveBinaryTypeOrNull(): PrimitiveBinaryType? = + this.computeBinaryType().primitiveBinaryTypeOrNull() + +fun KotlinType.computeBinaryType(): BinaryType = KotlinTypeInlineClassesSupport.computeBinaryType(this) + +fun IrType.computePrimitiveBinaryTypeOrNull(): PrimitiveBinaryType? = + this.computeBinaryType().primitiveBinaryTypeOrNull() + +fun IrType.computeBinaryType(): BinaryType = IrTypeInlineClassesSupport.computeBinaryType(this) + +fun IrClass.inlinedClassIsNullable(): Boolean = this.defaultType.makeNullable().getInlinedClass() == this // TODO: optimize +fun IrClass.isUsedAsBoxClass(): Boolean = IrTypeInlineClassesSupport.isUsedAsBoxClass(this) + +/** + * Most "underlying" user-visible non-reference type. + * It is visible as inlined to compiler for simplicity, and wraps internal [ValueClass]. + */ +enum class KonanPrimitiveType(val classId: ClassId) { + BOOLEAN(PrimitiveType.BOOLEAN), + CHAR(PrimitiveType.CHAR), + BYTE(PrimitiveType.BYTE), + SHORT(PrimitiveType.SHORT), + INT(PrimitiveType.INT), + LONG(PrimitiveType.LONG), + FLOAT(PrimitiveType.FLOAT), + DOUBLE(PrimitiveType.DOUBLE), + NON_NULL_NATIVE_PTR(ClassId.topLevel(KonanBuiltIns.FqNames.nonNullNativePtr.toSafe())) + + ; + + constructor(primitiveType: PrimitiveType) : this(ClassId.topLevel(primitiveType.typeFqName)) + + val fqName: FqNameUnsafe get() = this.classId.asSingleFqName().toUnsafe() + + companion object { + val byFqName = KonanPrimitiveType.values().associateBy { it.fqName } + } +} + +internal abstract class InlineClassesSupport { + protected abstract fun isNullable(type: Type): Boolean + protected abstract fun makeNullable(type: Type): Type + protected abstract fun erase(type: Type): Class + protected abstract fun computeFullErasure(type: Type): Sequence + protected abstract fun getFqName(clazz: Class): FqNameUnsafe + protected abstract fun hasInlineModifier(clazz: Class): Boolean + protected abstract fun getNativePointedSuperclass(clazz: Class): Class? + protected abstract fun getInlinedClassUnderlyingType(clazz: Class): Type + + @JvmName("classIsInlined") + fun isInlined(clazz: Class): Boolean = getInlinedClass(clazz) != null + fun isInlined(type: Type): Boolean = getInlinedClass(type) != null + + fun isUsedAsBoxClass(clazz: Class) = getInlinedClass(clazz) == clazz // To handle NativePointed subclasses. + + fun getInlinedClass(type: Type): Class? = + getInlinedClass(erase(type), isNullable(type)) + + private fun getInlinedClass(erased: Class, isNullable: Boolean): Class? { + val inlinedClass = getInlinedClass(erased) ?: return null + return if (!isNullable || representationIsNonNullReferenceOrPointer(inlinedClass)) { + inlinedClass + } else { + null + } + } + + tailrec fun representationIsNonNullReferenceOrPointer(clazz: Class): Boolean { + val valueClass = ValueClass.fqNameToValueClass[getFqName(clazz)] + if (valueClass != null) { + return valueClass == ValueClass.NON_NULL_POINTER + } + + val inlinedClass = getInlinedClass(clazz) ?: return true + + val underlyingType = getInlinedClassUnderlyingType(inlinedClass) + return if (isNullable(underlyingType)) { + false + } else { + representationIsNonNullReferenceOrPointer(erase(underlyingType)) + } + } + + @JvmName("classGetInlinedClass") + private fun getInlinedClass(clazz: Class): Class? = + if (hasInlineModifier(clazz) || getFqName(clazz) in implicitInlineClasses) { + clazz + } else { + getNativePointedSuperclass(clazz) + } + + fun unwrapInlinedClass(type: Type): Type? { + val inlinedClass = getInlinedClass(type) ?: return null + val underlyingType = getInlinedClassUnderlyingType(inlinedClass) + return if (isNullable(type)) makeNullable(underlyingType) else underlyingType + } + + inline fun unwrapToPrimitiveOrReference( + type: Type, + eachInlinedClass: (inlinedClass: Class, nullable: Boolean) -> Unit, + ifPrimitive: (primitiveType: KonanPrimitiveType, nullable: Boolean) -> R, + ifReference: (type: Type) -> R + ): R { + var currentType: Type = type + + while (true) { + val inlinedClass = getInlinedClass(currentType) + if (inlinedClass == null) { + return ifReference(currentType) + } + + KonanPrimitiveType.byFqName[getFqName(inlinedClass)]?.let { primitiveType -> + return ifPrimitive(primitiveType, isNullable(type)) + } + + eachInlinedClass(inlinedClass, isNullable(type)) + currentType = unwrapInlinedClass(currentType)!! + } + } + + // TODO: optimize. + fun unwrappingInlinedClasses(type: Type): Sequence = generateSequence(type, { unwrapInlinedClass(it) }) + + // TODO: optimize. + fun computeBinaryType(type: Type): BinaryType { + val erased = erase(type) + val valueClass = ValueClass.fqNameToValueClass[getFqName(erased)] + if (valueClass != null) { + if (isNullable(type) && valueClass != ValueClass.NON_NULL_POINTER) { + error("${valueClass.fqName} can't be nullable") + } + return valueClass.binaryType + } + + val inlinedClass = getInlinedClass(erased, isNullable(type)) ?: return createReferenceBinaryType(type) + val underlyingBinaryType = computeBinaryType(getInlinedClassUnderlyingType(inlinedClass)) + return if (isNullable(type) && underlyingBinaryType is BinaryType.Reference) { + BinaryType.Reference(underlyingBinaryType.types, true) + } else { + underlyingBinaryType + } + } + + private fun createReferenceBinaryType(type: Type): BinaryType.Reference = + BinaryType.Reference(computeFullErasure(type), true) +} + +private val implicitInlineClasses = + (KonanPrimitiveType.values().map { it.fqName } + + KonanBuiltIns.FqNames.nativePtr + + InteropBuiltIns.FqNames.cPointer).toSet() + +private enum class ValueClass(val fqName: FqNameUnsafe, val binaryType: BinaryType.Primitive) { + + BOOLEAN("konan.internal.BooleanValue", PrimitiveBinaryType.BOOLEAN), + BYTE("konan.internal.ByteValue", PrimitiveBinaryType.BYTE), + SHORT("konan.internal.ShortValue", PrimitiveBinaryType.SHORT), + INT("konan.internal.IntValue", PrimitiveBinaryType.INT), + LONG("konan.internal.LongValue", PrimitiveBinaryType.LONG), + FLOAT("konan.internal.FloatValue", PrimitiveBinaryType.FLOAT), + DOUBLE("konan.internal.DoubleValue", PrimitiveBinaryType.DOUBLE), + NON_NULL_POINTER("konan.internal.NotNullPointerValue", PrimitiveBinaryType.POINTER) + + ; + + constructor(fqName: String, primitiveBinaryType: PrimitiveBinaryType) : + this(FqNameUnsafe(fqName), primitiveBinaryType) + + constructor(fqName: FqNameUnsafe, primitiveBinaryType: PrimitiveBinaryType) : + this(fqName, BinaryType.Primitive(primitiveBinaryType)) + + companion object { + val fqNameToValueClass = ValueClass.values().associateBy { it.fqName } + } +} + +private fun IrClass.getAllSuperClassifiers(): List = listOf(this) + this.superTypes.flatMap { (it.classifierOrFail.owner as IrClass).getAllSuperClassifiers() } + +internal object KotlinTypeInlineClassesSupport : InlineClassesSupport() { + + override fun isNullable(type: KotlinType): Boolean = type.isNullable() + override fun makeNullable(type: KotlinType): KotlinType = type.makeNullable() + override tailrec fun erase(type: KotlinType): ClassDescriptor { + val descriptor = type.constructor.declarationDescriptor + return if (descriptor is ClassDescriptor) { + descriptor + } else { + erase(type.constructor.supertypes.first()) + } + } + + override fun computeFullErasure(type: KotlinType): Sequence { + val classifier = type.constructor.declarationDescriptor + return if (classifier is ClassDescriptor) sequenceOf(classifier) + else type.constructor.supertypes.asSequence().flatMap { computeFullErasure(it) } + } + + override fun getFqName(clazz: ClassDescriptor): FqNameUnsafe = clazz.fqNameUnsafe + override fun hasInlineModifier(clazz: ClassDescriptor): Boolean = clazz.isInline + + override fun getNativePointedSuperclass(clazz: ClassDescriptor): ClassDescriptor? = clazz.getAllSuperClassifiers() + .firstOrNull { it.fqNameUnsafe == InteropBuiltIns.FqNames.nativePointed } as ClassDescriptor? + + override fun getInlinedClassUnderlyingType(clazz: ClassDescriptor): KotlinType = + clazz.unsubstitutedPrimaryConstructor!!.valueParameters.single().type +} + +private object IrTypeInlineClassesSupport : InlineClassesSupport() { + + override fun isNullable(type: IrType): Boolean = type.containsNull() + + override fun makeNullable(type: IrType): IrType = type.makeNullable() + + override tailrec fun erase(type: IrType): IrClass { + val classifier = type.classifierOrFail + return when (classifier) { + is IrClassSymbol -> classifier.owner + is IrTypeParameterSymbol -> erase(classifier.owner.superTypes.first()) + else -> error(classifier) + } + } + + override fun computeFullErasure(type: IrType): Sequence { + val classifier = type.classifierOrFail + return when (classifier) { + is IrClassSymbol -> sequenceOf(classifier.owner) + is IrTypeParameterSymbol -> classifier.owner.superTypes.asSequence().flatMap { computeFullErasure(it) } + else -> error(classifier) + } + } + + override fun getFqName(clazz: IrClass): FqNameUnsafe = clazz.fqNameSafe.toUnsafe() + override fun hasInlineModifier(clazz: IrClass): Boolean = clazz.descriptor.isInline + + override fun getNativePointedSuperclass(clazz: IrClass): IrClass? = clazz.getAllSuperClassifiers() + .firstOrNull { it.fqNameSafe.toUnsafe() == InteropBuiltIns.FqNames.nativePointed } + + override fun getInlinedClassUnderlyingType(clazz: IrClass): IrType = + clazz.constructors.first { it.isPrimary }.valueParameters.single().type + +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt index b707779b631..f193fd94eb0 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/InteropUtils.kt @@ -73,9 +73,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) { val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single() - val nullableInteropValueTypes = listOf(ValueType.C_POINTER, ValueType.NATIVE_POINTED) - - private val nativePointed = packageScope.getContributedClass(nativePointedName) + val nativePointed = packageScope.getContributedClass(nativePointedName) val cPointer = this.packageScope.getContributedClass(cPointerName) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 019d7032e16..b40dc7bdbb6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -124,7 +124,7 @@ internal class KonanLower(val context: Context, val parentPhaser: PhaseManager) } phaser.phase(KonanPhase.LOWER_DEFAULT_PARAMETER_EXTENT) { DefaultArgumentStubGenerator(context).runOnFilePostfix(irFile) - DefaultParameterInjector(context).runOnFilePostfix(irFile) + KonanDefaultParameterInjector(context).runOnFilePostfix(irFile) } phaser.phase(KonanPhase.LOWER_BUILTIN_OPERATORS) { BuiltinOperatorLowering(context).lower(irFile) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt index 68492edba14..6892fbf83fc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPlatform.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.FqNameUnsafe import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.MultiTargetPlatform @@ -49,6 +50,7 @@ class KonanBuiltIns(storageManager: StorageManager) : KotlinBuiltIns(storageMana val packageName = FqName("konan.internal") val nativePtr = packageName.child(Name.identifier(nativePtrName)).toUnsafe() + val nonNullNativePtr = FqNameUnsafe("konan.internal.NonNullNativePtr") val throws = FqName("konan.Throws") } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt deleted file mode 100644 index 93f437cc030..00000000000 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ValueTypes.kt +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright 2010-2017 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.jetbrains.kotlin.backend.konan - -import org.jetbrains.kotlin.builtins.KotlinBuiltIns -import org.jetbrains.kotlin.ir.symbols.IrClassSymbol -import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol -import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol -import org.jetbrains.kotlin.ir.types.IrSimpleType -import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.classifierOrNull -import org.jetbrains.kotlin.name.FqNameUnsafe -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.types.KotlinType - -/** - * Value type is a kind of Kotlin types represented as plain value in generated code, not as an object reference. - * Such types may require autoboxing. - * - * The value type nearly corresponds to `[classFqName]` or `[classFqName]?` Kotlin type (depending on [isNullable]). - * - * @property classFqName name of the base value type class - * @property isNullable whether `null` is included into this value type - */ -enum class ValueType(val classFqName: FqNameUnsafe, val isNullable: Boolean = false) { - BOOLEAN(KotlinBuiltIns.FQ_NAMES._boolean), - CHAR(KotlinBuiltIns.FQ_NAMES._char), - - BYTE(KotlinBuiltIns.FQ_NAMES._byte), - SHORT(KotlinBuiltIns.FQ_NAMES._short), - INT(KotlinBuiltIns.FQ_NAMES._int), - LONG(KotlinBuiltIns.FQ_NAMES._long), - FLOAT(KotlinBuiltIns.FQ_NAMES._float), - DOUBLE(KotlinBuiltIns.FQ_NAMES._double), - - NATIVE_PTR(KonanBuiltIns.FqNames.nativePtr), - - NATIVE_POINTED(InteropBuiltIns.FqNames.nativePointed, isNullable = true), - C_POINTER(InteropBuiltIns.FqNames.cPointer, isNullable = true) -} - -private fun KotlinType.isConstructedFromGivenClass(fqName: FqNameUnsafe) = - KotlinBuiltIns.isConstructedFromGivenClass(this, fqName) - -/** - * @return `true` if this type must be represented as given value type in generated code. - */ -tailrec fun KotlinType.isRepresentedAs(valueType: ValueType): Boolean { - if (this.isMarkedNullable && !valueType.isNullable) { - return false - } - - if (this.isConstructedFromGivenClass(valueType.classFqName)) { - return true - } - - // Supertypes should be checked even for "final" value types (e.g. Int) - // to treat type parameter `T` with `Int` upper bound as value type. - // This behavior is observed on Kotlin JVM and used in interop implementation. - // - // However to optimize this method only first supertype is checked - // (it is supposed to be enough in all sane cases). - val firstSupertype = this.constructor.supertypes.firstOrNull() ?: return false - return firstSupertype.isRepresentedAs(valueType) -} - -/** - * @return `true` if this type without `null` value must be represented as given value type in generated code. - * - * TODO: this method can be considered as a hack; rework its usages. - */ -tailrec fun KotlinType.notNullableIsRepresentedAs(valueType: ValueType): Boolean { - if (this.isConstructedFromGivenClass(valueType.classFqName)) { - return true - } - - // See comment in [isRepresentedAs]. - val firstSupertype = this.constructor.supertypes.firstOrNull() ?: return false - return firstSupertype.notNullableIsRepresentedAs(valueType) -} - -fun KotlinType.isValueType() = ValueType.values().any { this.isRepresentedAs(it) } - -/** - * @return the [ValueType] given type represented in generated code as, - * or `null` if represented as object reference. - */ -val KotlinType.correspondingValueType: ValueType? - get() = ValueType.values().firstOrNull { - isRepresentedAs(it) - } - - -////// - -private fun IrType.isConstructedFromGivenClass(fqName: FqNameUnsafe) = - (this.classifierOrNull as? IrClassSymbol)?.descriptor?.fqNameSafe?.toUnsafe() == fqName - -private val IrClassifierSymbol.ownerSuperTypes: List - get() = when (this) { - is IrClassSymbol -> this.owner.superTypes - is IrTypeParameterSymbol -> this.owner.superTypes - else -> error(this) - } - - -/** - * @return `true` if this type must be represented as given value type in generated code. - */ -tailrec fun IrType.isRepresentedAs(valueType: ValueType): Boolean { - if (this !is IrSimpleType) { - return false - } - - if (this.hasQuestionMark && !valueType.isNullable) { - return false - } - - if (this.isConstructedFromGivenClass(valueType.classFqName)) { - return true - } - - // Supertypes should be checked even for "final" value types (e.g. Int) - // to treat type parameter `T` with `Int` upper bound as value type. - // This behavior is observed on Kotlin JVM and used in interop implementation. - // - // However to optimize this method only first supertype is checked - // (it is supposed to be enough in all sane cases). - val firstSupertype = this.classifier.ownerSuperTypes.firstOrNull() ?: return false - return firstSupertype.isRepresentedAs(valueType) -} - -/** - * @return `true` if this type without `null` value must be represented as given value type in generated code. - * - * TODO: this method can be considered as a hack; rework its usages. - */ -tailrec fun IrType.notNullableIsRepresentedAs(valueType: ValueType): Boolean { - if (this.isConstructedFromGivenClass(valueType.classFqName)) { - return true - } - - // See comment in [isRepresentedAs]. - val firstSupertype = this.classifierOrNull?.ownerSuperTypes?.firstOrNull() ?: return false - return firstSupertype.notNullableIsRepresentedAs(valueType) -} - -fun IrType.isValueType() = ValueType.values().any { this.isRepresentedAs(it) } - -/** - * @return the [ValueType] given type represented in generated code as, - * or `null` if represented as object reference. - */ -val IrType.correspondingValueType: ValueType? - get() = ValueType.values().firstOrNull { - isRepresentedAs(it) - } \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index d387b549ef2..eead21ffdc4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -16,8 +16,9 @@ package org.jetbrains.kotlin.backend.konan.descriptors +import org.jetbrains.kotlin.backend.konan.binaryTypeIsReference import org.jetbrains.kotlin.backend.konan.irasdescriptors.* -import org.jetbrains.kotlin.backend.konan.isValueType +import org.jetbrains.kotlin.backend.konan.isInlined import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction @@ -90,18 +91,13 @@ internal val FunctionDescriptor.isIntrinsic: Boolean get() = this.descriptor.annotations.hasAnnotation(intrinsicAnnotation) internal val org.jetbrains.kotlin.descriptors.DeclarationDescriptor.isFrozen: Boolean - get() = this.annotations.hasAnnotation(frozenAnnotation) + get() = this.annotations.hasAnnotation(frozenAnnotation) || + // RTTI is used for non-reference type box: + this is org.jetbrains.kotlin.descriptors.ClassDescriptor && !this.defaultType.binaryTypeIsReference() internal val DeclarationDescriptor.isFrozen: Boolean get() = this.descriptor.isFrozen -private val intrinsicTypes = setOf( - "kotlin.Boolean", "kotlin.Char", - "kotlin.Byte", "kotlin.Short", - "kotlin.Int", "kotlin.Long", - "kotlin.Float", "kotlin.Double" -) - internal val arrayTypes = setOf( "kotlin.Array", "kotlin.ByteArray", @@ -115,9 +111,6 @@ internal val arrayTypes = setOf( "konan.ImmutableBinaryBlob" ) -internal val ClassDescriptor.isIntrinsic: Boolean - get() = this.fqNameSafe.asString() in intrinsicTypes - internal val ClassDescriptor.isArray: Boolean get() = this.fqNameSafe.asString() in arrayTypes @@ -131,17 +124,19 @@ fun ClassDescriptor.isAbstract() = this.modality == Modality.SEALED || this.moda internal fun FunctionDescriptor.hasValueTypeAt(index: Int): Boolean { when (index) { - 0 -> return !isSuspend && returnType.let { (it.isValueType() || it.isUnit()) } - 1 -> return extensionReceiverParameter.let { it != null && it.type.isValueType() } - else -> return this.valueParameters[index - 2].type.isValueType() + 0 -> return !isSuspend && returnType.let { (it.isInlined() || it.isUnit()) } + 1 -> return dispatchReceiverParameter.let { it != null && it.type.isInlined() } + 2 -> return extensionReceiverParameter.let { it != null && it.type.isInlined() } + else -> return this.valueParameters[index - 3].type.isInlined() } } internal fun FunctionDescriptor.hasReferenceAt(index: Int): Boolean { when (index) { - 0 -> return isSuspend || returnType.let { !it.isValueType() && !it.isUnit() } - 1 -> return extensionReceiverParameter.let { it != null && !it.type.isValueType() } - else -> return !this.valueParameters[index - 2].type.isValueType() + 0 -> return isSuspend || returnType.let { !it.isInlined() && !it.isUnit() } + 1 -> return dispatchReceiverParameter.let { it != null && !it.type.isInlined() } + 2 -> return extensionReceiverParameter.let { it != null && !it.type.isInlined() } + else -> return !this.valueParameters[index - 3].type.isInlined() } } @@ -149,7 +144,7 @@ private fun FunctionDescriptor.needBridgeToAt(target: FunctionDescriptor, index: = hasValueTypeAt(index) xor target.hasValueTypeAt(index) internal fun FunctionDescriptor.needBridgeTo(target: FunctionDescriptor) - = (0..this.valueParameters.size + 1).any { needBridgeToAt(target, it) } + = (0..this.valueParameters.size + 2).any { needBridgeToAt(target, it) } internal val SimpleFunctionDescriptor.target: SimpleFunctionDescriptor get() = (if (modality == Modality.ABSTRACT) this else resolveFakeOverride()).original @@ -174,7 +169,7 @@ private fun FunctionDescriptor.bridgeDirectionToAt(target: FunctionDescriptor, i } internal class BridgeDirections(val array: Array) { - constructor(parametersCount: Int): this(Array(parametersCount + 2, { BridgeDirection.NOT_NEEDED })) + constructor(parametersCount: Int): this(Array(parametersCount + 3, { BridgeDirection.NOT_NEEDED })) fun allNotNeeded(): Boolean = array.all { it == BridgeDirection.NOT_NEEDED } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt index 7f21bacfc69..4ca723f9bde 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ir/Ir.kt @@ -17,12 +17,9 @@ package org.jetbrains.kotlin.backend.konan.ir import org.jetbrains.kotlin.backend.common.COROUTINE_SUSPENDED_NAME -import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.common.ir.Ir import org.jetbrains.kotlin.backend.common.ir.Symbols -import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.KONAN_FUNCTION_INTERFACES_MAX_PARAMETERS -import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.konanInternal import org.jetbrains.kotlin.backend.konan.irasdescriptors.typeWith import org.jetbrains.kotlin.backend.konan.llvm.findMainEntryPoint @@ -46,7 +43,6 @@ import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl import org.jetbrains.kotlin.ir.types.impl.IrStarProjectionImpl import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.types.KotlinType @@ -182,28 +178,16 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.builtIns.getNativeNullPtr) - val boxFunctions = ValueType.values().associate { - val boxFunctionName = "box${it.classFqName.shortName()}" - it to symbolTable.referenceSimpleFunction(context.getInternalFunctions(boxFunctionName).single()) + val boxCachePredicates = BoxCache.values().associate { + val name = "in${it.name.toLowerCase().capitalize()}BoxCache" + it to symbolTable.referenceSimpleFunction(context.getInternalFunctions(name).single()) } - val boxClasses = ValueType.values().associate { - it to symbolTable.referenceClass(context.getInternalClass("${it.classFqName.shortName()}Box")) + val boxCacheGetters = BoxCache.values().associate { + val name = "getCached${it.name.toLowerCase().capitalize()}Box" + it to symbolTable.referenceSimpleFunction(context.getInternalFunctions(name).single()) } - val valueClassToBox = ValueType.values().associate { - val valueClassId = ClassId.topLevel(it.classFqName.toSafe()) - val valueClassDescriptor = context.builtIns.builtInsModule.findClassAcrossModuleDependencies(valueClassId)!! - symbolTable.referenceClass(valueClassDescriptor) to boxClasses[it]!! - } - - val unboxFunctions = ValueType.values().mapNotNull { - val unboxFunctionName = "unbox${it.classFqName.shortName()}" - context.getInternalFunctions(unboxFunctionName).atMostOne()?.let { descriptor -> - it to symbolTable.referenceSimpleFunction(descriptor) - } - }.toMap() - val immutableBinaryBlob = symbolTable.referenceClass( builtInsPackage("konan").getContributedClassifier( Name.identifier("ImmutableBinaryBlob"), NoLookupLocation.FROM_BACKEND @@ -216,7 +200,9 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val val areEqualByValue = context.getInternalFunctions("areEqualByValue").map { symbolTable.referenceSimpleFunction(it) - } + }.associateBy { it.descriptor.valueParameters[0].type.computePrimitiveBinaryTypeOrNull()!! } + + val reinterpret = symbolTable.referenceSimpleFunction(context.getInternalFunctions("reinterpret").single()) val ieee754Equals = context.getInternalFunctions("ieee754Equals").map { symbolTable.referenceSimpleFunction(it) 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 f024dd012ca..f1269c80f5f 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 @@ -19,7 +19,7 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.LLVMTypeRef import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract import org.jetbrains.kotlin.backend.konan.irasdescriptors.* -import org.jetbrains.kotlin.backend.konan.isValueType +import org.jetbrains.kotlin.backend.konan.isInlined import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader import org.jetbrains.kotlin.backend.konan.optimizations.DataFlowIR import org.jetbrains.kotlin.descriptors.ModuleDescriptor @@ -177,7 +177,7 @@ private val FunctionDescriptor.signature: String val signatureSuffix = when { this.typeParameters.isNotEmpty() -> "Generic" - returnType.isValueType() -> "ValueType" + returnType.isInlined() -> "ValueType" !returnType.isUnitOrNullableUnit() -> typeToHashString(returnType) else -> "" } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt index c6a2145e158..6f29c02770b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt @@ -17,41 +17,33 @@ package org.jetbrains.kotlin.backend.konan.llvm import llvm.* -import org.jetbrains.kotlin.backend.konan.ValueType -import org.jetbrains.kotlin.backend.konan.isRepresentedAs +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.optimizations.DataFlowIR import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.isUnit -private val valueTypes = ValueType.values().associate { +private val primitiveToLlvm = PrimitiveBinaryType.values().associate { it to when (it) { - ValueType.BOOLEAN -> LLVMInt1Type() - ValueType.BYTE -> LLVMInt8Type() - ValueType.SHORT, ValueType.CHAR -> LLVMInt16Type() - ValueType.INT -> LLVMInt32Type() - ValueType.LONG -> LLVMInt64Type() - ValueType.FLOAT -> LLVMFloatType() - ValueType.DOUBLE -> LLVMDoubleType() + PrimitiveBinaryType.BOOLEAN -> LLVMInt1Type() + PrimitiveBinaryType.BYTE -> LLVMInt8Type() + PrimitiveBinaryType.SHORT -> LLVMInt16Type() + PrimitiveBinaryType.INT -> LLVMInt32Type() + PrimitiveBinaryType.LONG -> LLVMInt64Type() + PrimitiveBinaryType.FLOAT -> LLVMFloatType() + PrimitiveBinaryType.DOUBLE -> LLVMDoubleType() - ValueType.NATIVE_PTR, ValueType.NATIVE_POINTED, ValueType.C_POINTER -> int8TypePtr + PrimitiveBinaryType.POINTER -> int8TypePtr }!! } -internal val ValueType.llvmType - get() = valueTypes[this]!! +private fun RuntimeAware.getLlvmType(primitiveBinaryType: PrimitiveBinaryType?) = + primitiveBinaryType?.let { primitiveToLlvm[it]!! } ?: this.kObjHeaderPtr -internal fun RuntimeAware.getLLVMType(type: IrType): LLVMTypeRef { - for ((valueType, llvmType) in valueTypes) { - if (type.isRepresentedAs(valueType)) { - return llvmType - } - } - - return this.kObjHeaderPtr -} +internal fun RuntimeAware.getLLVMType(type: IrType): LLVMTypeRef = + getLlvmType(type.computePrimitiveBinaryTypeOrNull()) internal fun RuntimeAware.getLLVMType(type: DataFlowIR.Type) = - type.correspondingValueType?.let { valueTypes[it]!! } ?: this.kObjHeaderPtr + getLlvmType(type.primitiveBinaryType) internal fun RuntimeAware.getLLVMReturnType(type: IrType): LLVMTypeRef { return when { 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 a908aeba806..b0134ddc325 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 @@ -43,7 +43,6 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.konan.target.CompilerOutputKind import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe private val threadLocalAnnotationFqName = FqName("konan.ThreadLocal") @@ -160,10 +159,6 @@ internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid { super.visitClass(declaration) val descriptor = declaration - if (descriptor.isIntrinsic) { - // do not generate any code for intrinsic classes as they require special handling - return - } generator.generate(descriptor) @@ -407,7 +402,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map { + val numBits = LLVMSizeOfTypeInBits(codegen.llvmTargetData, arg0.type).toInt() + val integerType = LLVMIntType(numBits)!! + icmpEq(bitcast(integerType, arg0), bitcast(integerType, arg1)) + } + + LLVMTypeKind.LLVMIntegerTypeKind, LLVMTypeKind.LLVMPointerTypeKind -> icmpEq(arg0, arg1) + + else -> error(typeKind) + } + } } "konan.internal.ieee754Equals" -> { val arg0 = args[0] @@ -2127,7 +2132,8 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map args.single() + interop.nativePointedGetRawPointer, interop.cPointerGetRawValue, // TODO: implement through `reinterpret` + context.ir.symbols.reinterpret.descriptor -> args.single() in interop.readPrimitive -> { val pointerType = pointerType(codegen.getLLVMType(function.returnType)) @@ -2184,10 +2190,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map irBuiltIns.booleanClass + ObjCValueType.UNSIGNED_SHORT -> irBuiltIns.charClass + ObjCValueType.CHAR -> irBuiltIns.byteClass + ObjCValueType.SHORT -> irBuiltIns.shortClass + ObjCValueType.INT -> irBuiltIns.intClass + ObjCValueType.LONG_LONG -> irBuiltIns.longClass + ObjCValueType.FLOAT -> irBuiltIns.floatClass + ObjCValueType.DOUBLE -> irBuiltIns.doubleClass + }.owner + + val name = "${boxClass.name}To${objCValueType.nsNumberName}" val converter = generateFunction(codegen, kotlinToObjCFunctionType, name) { - val unboxFunction = symbols.getUnboxFunction(valueType).owner.llvmFunction + val unboxFunction = context.getUnboxFunction(boxClass).llvmFunction val kotlinValue = callFromBridge( unboxFunction, listOf(param(0)), @@ -361,8 +371,6 @@ private fun ObjCExportCodeGenerator.emitBoxConverter(objCValueType: ObjCValueTyp } LLVMSetLinkage(converter, LLVMLinkage.LLVMPrivateLinkage) - - val boxClass = symbols.boxClasses[valueType]!! setObjCExportTypeInfo(boxClass.descriptor, constPointer(converter)) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index 707075fc1b1..4fe128b362a 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -18,25 +18,32 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.atMostOne +import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.target +import org.jetbrains.kotlin.backend.konan.irasdescriptors.constructedClass import org.jetbrains.kotlin.backend.konan.irasdescriptors.containsNull import org.jetbrains.kotlin.backend.konan.irasdescriptors.isOverridable import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSuspend -import org.jetbrains.kotlin.backend.konan.irasdescriptors.makeNullableAsSpecified +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl import org.jetbrains.kotlin.ir.IrStatement -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrValueParameter +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrBlockImpl import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.IrType -import org.jetbrains.kotlin.ir.types.classifierOrFail -import org.jetbrains.kotlin.ir.types.makeNullable import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.addIfNotNull /** * Boxes and unboxes values of value types when necessary. @@ -47,6 +54,7 @@ internal class Autoboxing(val context: Context) : FileLoweringPass { override fun lower(irFile: IrFile) { irFile.transformChildrenVoid(transformer) + irFile.transformChildrenVoid(InlineClassTransformer(context)) } } @@ -60,68 +68,18 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr // TODO: should we handle the cases when expression type // is not equal to e.g. called function return type? - - /** - * @return type to use for runtime type checks instead of given one (e.g. `IntBox` instead of `Int`) - */ - private fun getRuntimeReferenceType(type: IrType): IrType { - ValueType.values().forEach { - if (type.notNullableIsRepresentedAs(it)) { - return getBoxType(it).makeNullableAsSpecified(type.containsNull()) - } - } - - return type - } - override fun IrExpression.useInTypeOperator(operator: IrTypeOperator, typeOperand: IrType): IrExpression { return if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT || operator == IrTypeOperator.IMPLICIT_INTEGER_COERCION) { this + } else if (operator == IrTypeOperator.IMPLICIT_CAST) { + this.useAs(typeOperand) } else { // Codegen expects the argument of type-checking operator to be an object reference: this.useAs(context.irBuiltIns.anyNType) } } - override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { - super.visitTypeOperator(expression).let { - // Assume that the transformer doesn't replace the entire expression for simplicity: - assert (it === expression) - } - - val newTypeOperand = getRuntimeReferenceType(expression.typeOperand) - val newTypeOperandClassifier = newTypeOperand.classifierOrFail - - return when (expression.operator) { - IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, - IrTypeOperator.IMPLICIT_INTEGER_COERCION -> expression - - IrTypeOperator.CAST, IrTypeOperator.IMPLICIT_CAST, - IrTypeOperator.IMPLICIT_NOTNULL, IrTypeOperator.SAFE_CAST -> { - - val newExpressionType = if (expression.operator == IrTypeOperator.SAFE_CAST) { - newTypeOperand.makeNullable() - } else { - newTypeOperand - } - - IrTypeOperatorCallImpl(expression.startOffset, expression.endOffset, - newExpressionType, expression.operator, newTypeOperand, newTypeOperandClassifier, - expression.argument).useAs(expression.type) - } - - IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> if (newTypeOperand == expression.typeOperand) { - // Do not create new expression if nothing changes: - expression - } else { - IrTypeOperatorCallImpl(expression.startOffset, expression.endOffset, - expression.type, expression.operator, newTypeOperand, newTypeOperandClassifier, - expression.argument) - } - } - } - private var currentFunctionDescriptor: IrFunction? = null override fun visitFunction(declaration: IrFunction): IrStatement { @@ -143,8 +101,8 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr } override fun IrExpression.useAs(type: IrType): IrExpression { - val interop = context.interopBuiltIns - if (this.isNullConst() && interop.nullableInteropValueTypes.any { type.isRepresentedAs(it) }) { + if (this.isNullConst() && type.isNullablePointer()) { + // TODO: consider using IrConst with proper type. return IrCallImpl( startOffset, endOffset, @@ -156,6 +114,7 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr val actualType = when (this) { is IrCall -> { if (this.symbol.owner.isSuspend) irBuiltIns.anyNType + else if (this.symbol == symbols.reinterpret) this.getTypeArgument(1)!! else this.callTarget.returnType } is IrGetField -> this.symbol.owner.type @@ -165,6 +124,8 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr // TODO: is it a workaround for inconsistent IR? this.typeOperand + IrTypeOperator.CAST -> context.irBuiltIns.anyNType + else -> this.type } @@ -174,6 +135,9 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr return this.adaptIfNecessary(actualType, type) } + private fun IrType.isNullablePointer(): Boolean = + this.containsNull() && this.computePrimitiveBinaryTypeOrNull() == PrimitiveBinaryType.POINTER + private val IrFunctionAccessExpression.target: IrFunction get() = when (this) { is IrCall -> this.callTarget is IrDelegatingConstructorCall -> this.symbol.owner @@ -231,6 +195,289 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr return this } - private fun getBoxType(valueType: ValueType) = symbols.boxClasses[valueType]!!.owner.defaultType + override fun visitCall(expression: IrCall): IrExpression { + return when (expression.symbol) { + symbols.reinterpret -> { + expression.transformChildrenVoid() + + // TODO: check types has the same binary representation. + val oldType = expression.getTypeArgument(0)!! + val newType = expression.getTypeArgument(1)!! + + assert(oldType.computePrimitiveBinaryTypeOrNull() == newType.computePrimitiveBinaryTypeOrNull()) + + expression.extensionReceiver = expression.extensionReceiver!!.useAs(oldType) + + expression + } + + else -> super.visitCall(expression) + } + } } + +private class InlineClassTransformer(private val context: Context) : IrBuildingTransformer(context) { + + private val symbols = context.ir.symbols + private val irBuiltIns = context.irBuiltIns + + override fun visitClass(declaration: IrClass): IrStatement { + super.visitClass(declaration) + + if (declaration.isInlined()) { + if (declaration.isUsedAsBoxClass()) { + buildBoxFunction(declaration, context.getBoxFunction(declaration)) + buildUnboxFunction(declaration, context.getUnboxFunction(declaration)) + } + + declaration.constructors.filter { !it.isPrimary }.toList().mapTo(declaration.declarations) { + context.getLoweredInlineClassConstructor(it) + } + } + + return declaration + } + + private fun IrField.isInlinedClassField(): Boolean { + val parentClass = this.parent as? IrClass + return parentClass != null && parentClass.isInlined() + } + + override fun visitGetField(expression: IrGetField): IrExpression { + super.visitGetField(expression) + + return if (expression.symbol.owner.isInlinedClassField()) { + expression.receiver!! + } else { + expression + } + } + + override fun visitSetField(expression: IrSetField): IrExpression { + super.visitSetField(expression) + + return if (expression.symbol.owner.isInlinedClassField()) { + // TODO: it is better to get rid of functions setting such fields. + val startOffset = expression.startOffset + val endOffset = expression.endOffset + IrBlockImpl(startOffset, endOffset, irBuiltIns.unitType).apply { + statements.addIfNotNull(expression.receiver) + statements += expression.value + statements += IrCallImpl(startOffset, endOffset, irBuiltIns.nothingType, symbols.ThrowNullPointerException) + statements += IrGetObjectValueImpl(startOffset, endOffset, irBuiltIns.unitType, irBuiltIns.unitClass) + } + } else { + expression + } + } + + override fun visitCall(expression: IrCall): IrExpression { + super.visitCall(expression) + + val function = expression.symbol.owner + return if (function is IrConstructor && function.constructedClass.isInlined()) { + builder.lowerConstructorCallToValue(expression, function) + } else { + expression + } + } + + override fun visitConstructor(declaration: IrConstructor): IrStatement { + val classIsInlined = declaration.constructedClass.isInlined() + + if (classIsInlined && !declaration.isPrimary) { + buildLoweredSecondaryConstructor(declaration) + } + + if (classIsInlined || !declaration.returnType.binaryTypeIsReference()) { + // TODO: fix DFG building and nullify the body instead. + (declaration.body as IrBlockBody).statements.clear() + } + + return super.visitConstructor(declaration) + } + + private fun IrBuilderWithScope.irIsNull(expression: IrExpression): IrExpression { + val binary = expression.type.computeBinaryType() + return when (binary) { + is BinaryType.Primitive -> { + assert(binary.type == PrimitiveBinaryType.POINTER) + irCall(symbols.areEqualByValue[binary.type]!!.owner).apply { + putValueArgument(0, expression) + putValueArgument(1, irNullPointer()) + } + } + is BinaryType.Reference -> irCall(context.irBuiltIns.eqeqeqFun).apply { + putValueArgument(0, expression) + putValueArgument(1, irNull()) + } + } + } + + private fun buildBoxFunction(irClass: IrClass, function: IrFunction) { + val builder = context.createIrBuilder(function.symbol) + val cache = BoxCache.values().toList().atMostOne { context.irBuiltIns.getKotlinClass(it) == irClass } + + function.body = builder.irBlockBody(function) { + val valueToBox = function.valueParameters[0] + if (valueToBox.type.containsNull()) { + +irIfThen( + condition = irIsNull(irGet(valueToBox)), + thenPart = irReturn(irNull()) + ) + } + + if (cache != null) { + +irIfThen( + condition = irCall(symbols.boxCachePredicates[cache]!!.owner).apply { + putValueArgument(0, irGet(valueToBox)) + }, + thenPart = irReturn(irCall(symbols.boxCacheGetters[cache]!!.owner).apply { + putValueArgument(0, irGet(valueToBox)) + }) + ) + } + + // Note: IR variable created below has reference type intentionally. + val box = irTemporary(irCall(symbols.createUninitializedInstance.owner).also { + it.putTypeArgument(0, irClass.defaultType) + }) + +irSetField(irGet(box), getInlineClassBackingField(irClass), irGet(valueToBox)) + +irReturn(irGet(box)) + } + + irClass.declarations += function + } + + private fun IrBuilderWithScope.irNullPointerOrReference(type: IrType): IrExpression = + if (type.binaryTypeIsReference()) { + irNull() + } else { + irNullPointer() + } + + private fun IrBuilderWithScope.irNullPointer(): IrExpression = irCall(symbols.getNativeNullPtr.owner) + + private fun buildUnboxFunction(irClass: IrClass, function: IrFunction) { + val builder = context.createIrBuilder(function.symbol) + + function.body = builder.irBlockBody(function) { + val boxParameter = function.valueParameters.single() + if (boxParameter.type.containsNull()) { + +irIfThen( + condition = irEqeqeq(irGet(boxParameter), irNull()), + thenPart = irReturn(irNullPointerOrReference(function.returnType)) + ) + } + +irReturn(irGetField(irGet(boxParameter), getInlineClassBackingField(irClass))) + } + + irClass.declarations += function + } + + private fun IrBuilderWithScope.lowerConstructorCallToValue( + expression: IrMemberAccessExpression, + callee: IrConstructor + ): IrExpression = if (callee.isPrimary) { + expression.getValueArgument(0)!! + } else { + this.at(expression).irCall(this@InlineClassTransformer.context.getLoweredInlineClassConstructor(callee)).apply { + (0 until expression.valueArgumentsCount).forEach { + putValueArgument(it, expression.getValueArgument(it)!!) + } + } + } + + private fun buildLoweredSecondaryConstructor(irConstructor: IrConstructor) { + val result = context.getLoweredInlineClassConstructor(irConstructor) + val irClass = irConstructor.parentAsClass + + result.body = context.createIrBuilder(result.symbol).irBlockBody(result) { + lateinit var thisVar: IrVariable + val parameterMapping = result.valueParameters.associateBy { + irConstructor.valueParameters[it.index].symbol + } + + (irConstructor.body as IrBlockBody).statements.forEach { statement -> + +statement.transform(object : IrElementTransformerVoid() { + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression { + expression.transformChildrenVoid() + + val value = lowerConstructorCallToValue(expression, expression.symbol.owner) + return irBlock(expression) { + thisVar = irTemporary(value) + } + } + + override fun visitGetValue(expression: IrGetValue): IrExpression { + expression.transformChildrenVoid() + if (expression.symbol == irClass.thisReceiver?.symbol) { + return irGet(thisVar) + } + + parameterMapping[expression.symbol]?.let { return irGet(it) } + return expression + } + + override fun visitReturn(expression: IrReturn): IrExpression { + expression.transformChildrenVoid() + if (expression.returnTargetSymbol == irConstructor.symbol) { + return irReturn(irBlock(expression.startOffset, expression.endOffset) { + +expression.value + +irGet(thisVar) + }) + } + + return expression + } + + }, null) + } + +irReturn(irGet(thisVar)) + } + } + + private fun getInlineClassBackingField(irClass: IrClass): IrField = + irClass.declarations.filterIsInstance().mapNotNull { it.backingField }.single() +} + +private val Context.getLoweredInlineClassConstructor: (IrConstructor) -> IrSimpleFunction by Context.lazyMapMember { irConstructor -> + require(irConstructor.constructedClass.isInlined()) + require(!irConstructor.isPrimary) + + val descriptor = SimpleFunctionDescriptorImpl.create( + irConstructor.descriptor.containingDeclaration, + irConstructor.descriptor.annotations, + Name.special(""), + CallableMemberDescriptor.Kind.SYNTHESIZED, + irConstructor.descriptor.source + ) + + val parameterDescriptors = irConstructor.descriptor.valueParameters.map { + it.copy(descriptor, it.name, it.index) + } + + descriptor.initialize( + null, + null, + emptyList(), + parameterDescriptors, + irConstructor.descriptor.returnType, + Modality.FINAL, + irConstructor.visibility + ) + + IrFunctionImpl( + irConstructor.startOffset, + irConstructor.endOffset, + IrDeclarationOrigin.DEFINED, + descriptor + ).apply { + parent = irConstructor.parent + returnType = irConstructor.returnType + irConstructor.valueParameters.mapTo(this.valueParameters) { + it.copy(this.descriptor.valueParameters[it.index]) + } + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt index 0eb6fb1776b..fe768da7688 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/BuiltinOperatorLowering.kt @@ -21,10 +21,9 @@ import org.jetbrains.kotlin.backend.common.atMostOne import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.backend.common.lower.irNot -import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.irasdescriptors.containsNull import org.jetbrains.kotlin.backend.konan.irasdescriptors.isSubtypeOf -import org.jetbrains.kotlin.backend.konan.isValueType import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.declarations.IrFile @@ -40,6 +39,8 @@ import org.jetbrains.kotlin.ir.util.irCall import org.jetbrains.kotlin.ir.builders.irGet import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.isNothing +import org.jetbrains.kotlin.ir.types.toKotlinType +import org.jetbrains.kotlin.ir.util.defaultOrNullableType import org.jetbrains.kotlin.ir.util.isNullConst import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid @@ -98,7 +99,7 @@ internal class BuiltinOperatorLowering(val context: Context) : FileLoweringPass, val lhs = expression.getValueArgument(0)!! val rhs = expression.getValueArgument(1)!! - return if (lhs.type.isValueType() && rhs.type.isValueType()) { + return if (lhs.type.isInlined() && rhs.type.isInlined()) { // Achieve the same behavior as with JVM BE: if both sides of `===` are values, then compare by value: lowerEqeq(expression) // Note: such comparisons are deprecated. @@ -107,9 +108,13 @@ internal class BuiltinOperatorLowering(val context: Context) : FileLoweringPass, } } - private fun IrBuilderWithScope.irLogicalAnd(lhs: IrExpression, rhs: IrExpression) = context.andand(lhs, rhs) - private fun IrBuilderWithScope.irIsNull(exp: IrExpression) = irEqeqeq(exp, irNull()) - private fun IrBuilderWithScope.irIsNotNull(exp: IrExpression) = irNot(irEqeqeq(exp, irNull())) + private fun IrBuilderWithScope.reinterpret(expression: IrExpression, toType: IrType) = + reinterpret(expression, expression.type, toType) + + private fun IrBuilderWithScope.reinterpret(expression: IrExpression, fromType: IrType, toType: IrType) = + irCall(symbols.reinterpret.owner, listOf(fromType, toType)).apply { + extensionReceiver = expression + } private fun lowerEqeq(expression: IrCall): IrExpression { // TODO: optimize boxing? @@ -118,79 +123,127 @@ internal class BuiltinOperatorLowering(val context: Context) : FileLoweringPass, val lhs = expression.getValueArgument(0)!! val rhs = expression.getValueArgument(1)!! - val nullableNothingType = builtIns.nullableNothingType - if (lhs.type.isSubtypeOf(nullableNothingType) && rhs.type.isSubtypeOf(nullableNothingType)) { - // Compare by reference if each part is either `Nothing` or `Nothing?`: - return irEqeqeq(lhs, rhs) + if (rhs.isNullConst()) { + return irEqeqNull(lhs) } - // Find a type-compatible `konan.internal.areEqualByValue` intrinsic: - selectIntrinsic(symbols.areEqualByValue, lhs.type, rhs.type, false)?.let { - return irCall(it).apply { - putValueArgument(0, lhs) - putValueArgument(1, rhs) - } + if (lhs.isNullConst()) { + return irEqeqNull(rhs) } - if (lhs.isNullConst() || rhs.isNullConst()) { - // or compare by reference if left or right part is `null`: - return irEqeqeq(lhs, rhs) - } - - // TODO: areEqualByValue and ieee754Equals intrinsics are specially treated by code generator - // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. - fun callEquals(lhs: IrExpression, rhs: IrExpression) = - if (expression.descriptor in ieee754EqualsDescriptors()) - // Find a type-compatible `konan.internal.ieee754Equals` intrinsic: - irCall(selectIntrinsic(symbols.ieee754Equals, lhs.type, rhs.type, true)!!).apply { - putValueArgument(0, lhs) - putValueArgument(1, rhs) - } - else - irCall(symbols.equals).apply { - dispatchReceiver = lhs - putValueArgument(0, rhs) - } - - val lhsIsNotNullable = !lhs.type.containsNull() - val rhsIsNotNullable = !rhs.type.containsNull() - - return if (expression.descriptor in ieee754EqualsDescriptors()) { - if (lhsIsNotNullable && rhsIsNotNullable) - callEquals(lhs, rhs) - else irBlock { - val lhsTemp = irTemporary(lhs) - val rhsTemp = irTemporary(rhs) - if (lhsIsNotNullable xor rhsIsNotNullable) { // Exactly one nullable. - +irLogicalAnd( - irIsNotNull(irGet(if (lhsIsNotNullable) rhsTemp else lhsTemp)), - callEquals(irGet(lhsTemp), irGet(rhsTemp)) - ) - } else { // Both are nullable. - +irIfThenElse(context.irBuiltIns.booleanType, irIsNull(irGet(lhsTemp)), - irIsNull(irGet(rhsTemp)), - irLogicalAnd( - irIsNotNull(irGet(rhsTemp)), - callEquals(irGet(lhsTemp), irGet(rhsTemp)) - ) - ) + if (expression.symbol == irBuiltins.eqeqSymbol) { + lhs.type.getInlinedClass()?.let { + if (it == rhs.type.getInlinedClass()) { + return genInlineClassEquals(expression.descriptor, rhs, lhs) } } - } else { - if (lhsIsNotNullable) - callEquals(lhs, rhs) - else { - irBlock { - val lhsTemp = irTemporary(lhs) - if (rhsIsNotNullable) - +irLogicalAnd(irIsNotNull(irGet(lhsTemp)), callEquals(irGet(lhsTemp), rhs)) - else { - val rhsTemp = irTemporary(rhs) - +irIfThenElse(irBuiltins.booleanType, irIsNull(irGet(lhsTemp)), - irIsNull(irGet(rhsTemp)), + } + + return genFloatingOrReferenceEquals(expression.descriptor, lhs, rhs) + } + } + + fun IrBuilderWithScope.genInlineClassEquals( + descriptor: FunctionDescriptor, + rhs: IrExpression, + lhs: IrExpression + ): IrExpression { + val lhsBinaryType = lhs.type.computeBinaryType() + return when (lhsBinaryType) { + is BinaryType.Primitive -> { + val areEqualByValue = symbols.areEqualByValue[lhsBinaryType.type]!!.owner + irCall(areEqualByValue).apply { + putValueArgument(0, reinterpret(lhs, areEqualByValue.valueParameters[0].type)) + putValueArgument(1, reinterpret(rhs, areEqualByValue.valueParameters[1].type)) + } + } + + is BinaryType.Reference -> { + // TODO: don't use binaryType.nullable. + val lhsRawType = irBuiltins.anyClass.owner.defaultOrNullableType(lhsBinaryType.nullable) + val rhsBinaryType = rhs.type.computeBinaryType() as BinaryType.Reference<*> + val rhsRawType = irBuiltins.anyClass.owner.defaultOrNullableType(rhsBinaryType.nullable) + + genFloatingOrReferenceEquals( + descriptor, + reinterpret(lhs, lhsRawType), + reinterpret(rhs, rhsRawType) + ) + } + } + } + + private fun IrBuilderWithScope.irEqeqNull(expression: IrExpression): IrExpression { + val type = expression.type.makeNullable() + val primitiveBinaryTypeOrNull = type.computePrimitiveBinaryTypeOrNull() + return when (primitiveBinaryTypeOrNull) { + null -> irEqeqeq(reinterpret(expression, type, irBuiltins.anyNType), irNull()) + PrimitiveBinaryType.POINTER -> irCall(symbols.areEqualByValue[PrimitiveBinaryType.POINTER]!!.owner).apply { + putValueArgument(0, reinterpret(expression, type, symbols.nativePtrType)) + putValueArgument(1, reinterpret(irNull(), type, symbols.nativePtrType)) + } + else -> error("Nullable type ${type.toKotlinType()} is $primitiveBinaryTypeOrNull") + } + } + + private fun IrBuilderWithScope.irLogicalAnd(lhs: IrExpression, rhs: IrExpression) = context.andand(lhs, rhs) + private fun IrBuilderWithScope.irIsNull(exp: IrExpression) = irEqeqeq(exp, irNull()) + private fun IrBuilderWithScope.irIsNotNull(exp: IrExpression) = irNot(irEqeqeq(exp, irNull())) + + private fun IrBuilderWithScope.genFloatingOrReferenceEquals(descriptor: FunctionDescriptor, lhs: IrExpression, rhs: IrExpression): IrExpression { + // TODO: areEqualByValue and ieee754Equals intrinsics are specially treated by code generator + // and thus can be declared synthetically in the compiler instead of explicitly in the runtime. + fun callEquals(lhs: IrExpression, rhs: IrExpression) = + if (descriptor in ieee754EqualsDescriptors()) + // Find a type-compatible `konan.internal.ieee754Equals` intrinsic: + irCall(selectIntrinsic(symbols.ieee754Equals, lhs.type, rhs.type, true)!!).apply { + putValueArgument(0, lhs) + putValueArgument(1, rhs) + } + else + irCall(symbols.equals).apply { + dispatchReceiver = lhs + putValueArgument(0, rhs) + } + + val lhsIsNotNullable = !lhs.type.containsNull() + val rhsIsNotNullable = !rhs.type.containsNull() + + return if (descriptor in ieee754EqualsDescriptors()) { + if (lhsIsNotNullable && rhsIsNotNullable) + callEquals(lhs, rhs) + else irBlock { + val lhsTemp = irTemporary(lhs) + val rhsTemp = irTemporary(rhs) + if (lhsIsNotNullable xor rhsIsNotNullable) { // Exactly one nullable. + +irLogicalAnd( + irIsNotNull(irGet(if (lhsIsNotNullable) rhsTemp else lhsTemp)), + callEquals(irGet(lhsTemp), irGet(rhsTemp)) + ) + } else { // Both are nullable. + +irIfThenElse(context.irBuiltIns.booleanType, irIsNull(irGet(lhsTemp)), + irIsNull(irGet(rhsTemp)), + irLogicalAnd( + irIsNotNull(irGet(rhsTemp)), callEquals(irGet(lhsTemp), irGet(rhsTemp)) ) - } + ) + } + } + } else { + if (lhsIsNotNullable) + callEquals(lhs, rhs) + else { + irBlock { + val lhsTemp = irTemporary(lhs) + if (rhsIsNotNullable) + +irLogicalAnd(irIsNotNull(irGet(lhsTemp)), callEquals(irGet(lhsTemp), rhs)) + else { + val rhsTemp = irTemporary(rhs) + +irIfThenElse(irBuiltins.booleanType, irIsNull(irGet(lhsTemp)), + irIsNull(irGet(rhsTemp)), + callEquals(irGet(lhsTemp), irGet(rhsTemp)) + ) } } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumWhenLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumWhenLowering.kt index 6fff0792493..4883e378e2e 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumWhenLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/EnumWhenLowering.kt @@ -7,7 +7,6 @@ import org.jetbrains.kotlin.backend.common.push import org.jetbrains.kotlin.backend.konan.Context import org.jetbrains.kotlin.backend.konan.irasdescriptors.containsNull import org.jetbrains.kotlin.backend.konan.irasdescriptors.getClass -import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrFile @@ -24,7 +23,6 @@ import org.jetbrains.kotlin.ir.util.getPropertyGetter import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.types.isNullable /** Look for when-constructs where subject is enum entry. * Replace branches that are comparisons with compile-time known enum entries @@ -110,9 +108,7 @@ internal class EnumWhenLowering(private val context: Context) : IrElementTransfo return tryLower(expression) } - private val areEqualByValue = context.ir.symbols.areEqualByValue.first { - it.owner.valueParameters[0].type.classifierOrNull == context.ir.symbols.int - } + private val areEqualByValue = context.irBuiltIns.eqeqSymbol // We are looking for branch that is a comparison of the subject and another enum entry. private fun tryLower(call: IrCall): IrExpression { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt index 1dbbd83aea3..1d694ac06c5 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/InteropLowering.kt @@ -732,10 +732,11 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB expression.transformChildrenVoid(this) builder.at(expression) val descriptor = expression.descriptor.original + val function = expression.symbol.owner - if (descriptor is ClassConstructorDescriptor) { - val type = descriptor.constructedClass.defaultType - if (type.isRepresentedAs(ValueType.C_POINTER) || type.isRepresentedAs(ValueType.NATIVE_POINTED)) { + if (function is IrConstructor) { + val inlinedClass = function.returnType.getInlinedClass() + if (inlinedClass?.descriptor == interop.cPointer || inlinedClass?.descriptor == interop.nativePointed) { throw Error("Native interop types constructors must not be called directly") } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/KonanDefaultParameterInjector.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/KonanDefaultParameterInjector.kt new file mode 100644 index 00000000000..0ed9e55a02f --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/KonanDefaultParameterInjector.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.lower.DefaultParameterInjector +import org.jetbrains.kotlin.backend.konan.* +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.util.irCall + +internal class KonanDefaultParameterInjector(private val konanContext: KonanBackendContext) + : DefaultParameterInjector(konanContext) { + + override fun nullConst(expression: IrElement, type: IrType): IrExpression { + val symbols = konanContext.ir.symbols + + val startOffset = expression.startOffset + val endOffset = expression.endOffset + + val nullConstOfEquivalentType = when (type.computePrimitiveBinaryTypeOrNull()) { + null -> IrConstImpl.constNull(startOffset, endOffset, context.irBuiltIns.nothingNType) + PrimitiveBinaryType.BOOLEAN -> IrConstImpl.boolean(startOffset, endOffset, type, false) + PrimitiveBinaryType.BYTE -> IrConstImpl.byte(startOffset, endOffset, type, 0) + PrimitiveBinaryType.SHORT -> IrConstImpl.short(startOffset, endOffset, type, 0) + PrimitiveBinaryType.INT -> IrConstImpl.int(startOffset, endOffset, type, 0) + PrimitiveBinaryType.LONG -> IrConstImpl.long(startOffset, endOffset, type, 0) + PrimitiveBinaryType.FLOAT -> IrConstImpl.float(startOffset, endOffset, type, 0.0F) + PrimitiveBinaryType.DOUBLE -> IrConstImpl.double(startOffset, endOffset, type, 0.0) + PrimitiveBinaryType.POINTER -> irCall(startOffset, endOffset, symbols.getNativeNullPtr.owner, emptyList()) + } + + return irCall( + startOffset, + endOffset, + symbols.reinterpret.owner, + listOf(nullConstOfEquivalentType.type, type) + ).apply { + extensionReceiver = nullConstOfEquivalentType + } + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt index 1accf9fe23d..6b38e236285 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -29,6 +29,7 @@ import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.resolve.scopes.MemberScope import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.isNullable import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addIfNotNull @@ -638,7 +639,7 @@ abstract class ObjCExportHeaderGenerator( internal fun mapReferenceType(kotlinType: KotlinType): ObjCReferenceType = mapReferenceTypeIgnoringNullability(kotlinType).let { - if (TypeUtils.isNullableType(kotlinType)) { + if (kotlinType.unwrapInlinedClasses().isNullable()) { ObjCNullableReferenceType(it) } else { it @@ -683,7 +684,8 @@ abstract class ObjCExportHeaderGenerator( // TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* ` - if (classDescriptor == builtIns.any || classDescriptor in hiddenTypes) { + // TODO: expose custom inline class boxes properly. + if (classDescriptor == builtIns.any || classDescriptor in hiddenTypes || classDescriptor.isInlined()) { return ObjCIdType } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt index b348bab7acd..0d5c5b8b7ac 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -22,9 +22,9 @@ import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors import org.jetbrains.kotlin.backend.konan.descriptors.isArray import org.jetbrains.kotlin.backend.konan.descriptors.isInterface +import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns -import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi +import org.jetbrains.kotlin.resolve.descriptorUtil.* import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.typeUtil.isUnit @@ -67,7 +67,7 @@ internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Bool descriptor.isEffectivelyPublicApi && !descriptor.defaultType.isObjCObjectType() && when (descriptor.kind) { ClassKind.CLASS, ClassKind.INTERFACE, ClassKind.ENUM_CLASS, ClassKind.OBJECT -> true ClassKind.ENUM_ENTRY, ClassKind.ANNOTATION_CLASS -> false - } && !descriptor.isExpect && !isSpecialMapped(descriptor) + } && !descriptor.isExpect && !isSpecialMapped(descriptor) && !descriptor.isInlined() private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean = descriptor.overriddenDescriptors.all { !shouldBeExposed(it) } @@ -110,15 +110,28 @@ internal fun ObjCExportMapper.doesThrow(method: FunctionDescriptor): Boolean = m it.overriddenDescriptors.isEmpty() && it.annotations.hasAnnotation(KonanBuiltIns.FqNames.throws) } -private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge { - val valueType = kotlinType.correspondingValueType - ?: return ReferenceBridge - - val objCValueType = ObjCValueType.values().singleOrNull { it.kotlinValueType == valueType } - ?: error("Can't produce $kotlinType to framework API") - - return ValueTypeBridge(objCValueType) -} +private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge = kotlinType.unwrapToPrimitiveOrReference( + eachInlinedClass = { _, _ -> + // unsigned types to be handled. + }, + ifPrimitive = { primitiveType, _ -> + val objCValueType = when (primitiveType) { + KonanPrimitiveType.BOOLEAN -> ObjCValueType.BOOL + KonanPrimitiveType.CHAR -> ObjCValueType.UNSIGNED_SHORT + KonanPrimitiveType.BYTE -> ObjCValueType.CHAR + KonanPrimitiveType.SHORT -> ObjCValueType.SHORT + KonanPrimitiveType.INT -> ObjCValueType.INT + KonanPrimitiveType.LONG -> ObjCValueType.LONG_LONG + KonanPrimitiveType.FLOAT -> ObjCValueType.FLOAT + KonanPrimitiveType.DOUBLE -> ObjCValueType.DOUBLE + KonanPrimitiveType.NON_NULL_NATIVE_PTR -> error("Can't produce pointer type to framework API") // TODO + } + ValueTypeBridge(objCValueType) + }, + ifReference = { + ReferenceBridge + } +) private fun ObjCExportMapper.bridgeParameter(parameter: ParameterDescriptor): MethodBridgeValueParameter = MethodBridgeValueParameter.Mapped(bridgeType(parameter.type)) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt index e1f13163f7d..e0dc309ba82 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/objcTypes.kt @@ -16,8 +16,6 @@ package org.jetbrains.kotlin.backend.konan.objcexport -import org.jetbrains.kotlin.backend.konan.ValueType - sealed class ObjCType { final override fun toString(): String = this.render() @@ -114,18 +112,17 @@ object ObjCVoidType : ObjCType() { } internal enum class ObjCValueType( - val kotlinValueType: ValueType, // It is here for simplicity. val encoding: String ) { - BOOL(ValueType.BOOLEAN, "c"), - CHAR(ValueType.BYTE, "c"), - UNSIGNED_SHORT(ValueType.CHAR, "S"), - SHORT(ValueType.SHORT, "s"), - INT(ValueType.INT, "i"), - LONG_LONG(ValueType.LONG, "q"), - FLOAT(ValueType.FLOAT, "f"), - DOUBLE(ValueType.DOUBLE, "d") + BOOL("c"), + CHAR("c"), + SHORT("s"), + UNSIGNED_SHORT("S"), + INT("i"), + LONG_LONG("q"), + FLOAT("f"), + DOUBLE("d") ; 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 e6e199b1757..89ee028b43a 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 @@ -21,15 +21,11 @@ import org.jetbrains.kotlin.backend.common.ir.ir2stringWhole import org.jetbrains.kotlin.backend.common.peek import org.jetbrains.kotlin.backend.common.pop import org.jetbrains.kotlin.backend.common.push -import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors -import org.jetbrains.kotlin.backend.konan.correspondingValueType import org.jetbrains.kotlin.backend.konan.descriptors.isInterface import org.jetbrains.kotlin.backend.konan.descriptors.target -import org.jetbrains.kotlin.backend.konan.getTypeConversion -import org.jetbrains.kotlin.backend.konan.ir.KonanIr import org.jetbrains.kotlin.backend.konan.irasdescriptors.* -import org.jetbrains.kotlin.backend.konan.isValueType import org.jetbrains.kotlin.backend.konan.llvm.functionName import org.jetbrains.kotlin.backend.konan.llvm.localHash import org.jetbrains.kotlin.descriptors.ClassKind @@ -38,6 +34,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrClassSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.constructors @@ -49,43 +46,6 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.util.OperatorNameConventions -private fun getClassWithBoxingIncluded(type: IrType, ir: KonanIr): ClassDescriptor? { - /* - * Some primitive types can be null and some can't. Those that can't must be replaced with the corresponding box. - * Int -> Int - * Int? -> IntBox - * but - * CPointer -> CPointer - * CPointer? -> CPointer - */ - return if (type.correspondingValueType == null && type.makeNotNull().correspondingValueType != null) - ir.symbols.getTypeConversion(type.makeNotNull(), type)!!.owner.returnType.getClass()!! - else - type.getClass() -} - -private fun computeErasure(type: IrType, ir: KonanIr, erasure: MutableList) { - val irClass = getClassWithBoxingIncluded(type, ir) - if (irClass != null) { - erasure += irClass - } else { - val classifier = type.classifierOrFail - if (classifier is IrTypeParameterSymbol) { - classifier.owner.superTypes.forEach { - computeErasure(it, ir, erasure) - } - } else { - TODO(classifier.descriptor.toString()) - } - } -} - -internal fun IrType.erasure(context: Context): List { - val result = mutableListOf() - computeErasure(this, context.ir, result) - return result -} - private fun IrClass.getOverridingOf(function: FunctionDescriptor) = (function as? SimpleFunctionDescriptor)?.let { it.allOverriddenDescriptors.atMostOne { it.parent == this } } @@ -516,7 +476,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag ) val throwsNode = DataFlowIR.Node.Variable( values = thrownValues.map { expressionToEdge(it) }, - type = symbolTable.mapClass(context.ir.symbols.throwable.owner), + type = symbolTable.mapClassReferenceType(context.ir.symbols.throwable.owner), kind = DataFlowIR.VariableKind.Temporary ) variables.forEach { descriptor, node -> @@ -535,7 +495,10 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag private fun expressionToEdge(expression: IrExpression) = if (expression is IrTypeOperatorCall && expression.operator.isCast()) - DataFlowIR.Edge(getNode(expression.argument), symbolTable.mapType(expression.typeOperand)) + DataFlowIR.Edge( + getNode(expression.argument), + symbolTable.mapClassReferenceType(expression.typeOperand.getClass()!!) + ) else DataFlowIR.Edge(getNode(expression), null) private fun getNode(expression: IrExpression): DataFlowIR.Node { @@ -595,7 +558,9 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag expressionToEdge(value.getValueArgument(0)!!), expressionToEdge(value.getValueArgument(1)!!)) createUninitializedInstanceSymbol -> - DataFlowIR.Node.AllocInstance(symbolTable.mapType(value.getTypeArgument(0)!!)) + DataFlowIR.Node.AllocInstance(symbolTable.mapClassReferenceType( + value.getTypeArgument(0)!!.getClass()!! + )) initInstanceSymbol -> { val thiz = expressionToEdge(value.getValueArgument(0)!!) @@ -605,7 +570,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag DataFlowIR.Node.StaticCall( symbolTable.mapFunction(callee), arguments, - symbolTable.mapClass(callee.constructedClass), + symbolTable.mapClassReferenceType(callee.constructedClass), null ) } @@ -624,7 +589,7 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag DataFlowIR.Node.NewObject( symbolTable.mapFunction(callee), arguments, - symbolTable.mapClass(callee.constructedClass), + symbolTable.mapClassReferenceType(callee.constructedClass), value ) } else { @@ -632,27 +597,22 @@ internal class ModuleDFGBuilder(val context: Context, val irModule: IrModuleFrag if (callee.isOverridable && value.superQualifier == null) { val owner = callee.containingDeclaration as ClassDescriptor val actualReceiverType = value.dispatchReceiver!!.type + val actualReceiverClassifier = actualReceiverType.classifierOrFail + val receiverType = - if (actualReceiverType.classifierOrNull is IrTypeParameterSymbol + if (actualReceiverClassifier is IrTypeParameterSymbol || !callee.isReal /* Could be a bridge. */) - symbolTable.mapClass(owner) + symbolTable.mapClassReferenceType(owner) else { val actualClassAtCallsite = - actualReceiverType.classifierOrFail.descriptor - as org.jetbrains.kotlin.descriptors.ClassDescriptor + (actualReceiverClassifier as IrClassSymbol).descriptor // assert (DescriptorUtils.isSubclass(actualClassAtCallsite, owner.descriptor)) { // "Expected an inheritor of ${owner.descriptor}, but was $actualClassAtCallsite" // } if (DescriptorUtils.isSubclass(actualClassAtCallsite, owner.descriptor)) { - symbolTable.mapType( - actualReceiverType.let { - if (it.isValueType()) // A virtual call on a value type - it must be boxed. - context.ir.symbols.getTypeConversion(it, it.makeNullable())!!.owner.returnType - else it - } - ) + symbolTable.mapClassReferenceType(actualReceiverClassifier.owner) // Box if inline class. } else { - symbolTable.mapClass(owner) + symbolTable.mapClassReferenceType(owner) } } if (owner.isInterface) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt index 166c1019ec9..da7b6b0ecf2 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/DFGSerializer.kt @@ -17,7 +17,7 @@ package org.jetbrains.kotlin.backend.konan.optimizations import org.jetbrains.kotlin.backend.konan.Context -import org.jetbrains.kotlin.backend.konan.ValueType +import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType import org.jetbrains.kotlin.config.CommonConfigurationKeys import sun.misc.Unsafe import kotlin.reflect.KClass @@ -204,15 +204,15 @@ internal object DFGSerializer { } } - class TypeBase(val isFinal: Boolean, val isAbstract: Boolean, val correspondingValueType: ValueType?, val name: String?) { + class TypeBase(val isFinal: Boolean, val isAbstract: Boolean, val primitiveBinaryType: PrimitiveBinaryType?, val name: String?) { constructor(data: ArraySlice) : this(data.readBoolean(), data.readBoolean(), - data.readNullableInt()?.let { ValueType.values()[it] }, data.readNullableString()) + data.readNullableInt()?.let { PrimitiveBinaryType.values()[it] }, data.readNullableString()) fun write(result: ArraySlice) { result.writeBoolean(isFinal) result.writeBoolean(isAbstract) - result.writeNullableInt(correspondingValueType?.ordinal) + result.writeNullableInt(primitiveBinaryType?.ordinal) result.writeNullableString(name) } } @@ -776,7 +776,8 @@ internal object DFGSerializer { fun serialize(context: Context, moduleDFG: ModuleDFG) { val symbolTable = moduleDFG.symbolTable - val typeMap = (symbolTable.classMap.values + DataFlowIR.Type.Virtual).distinct().withIndex().associateBy({ it.value }, { it.index }) + val typeList = symbolTable.classMap.values + symbolTable.primitiveMap.values + DataFlowIR.Type.Virtual + val typeMap = typeList.distinct().withIndex().associateBy({ it.value }, { it.index }) val functionSymbolMap = symbolTable.functionMap.values.distinct().withIndex().associateBy({ it.value }, { it.index }) DEBUG_OUTPUT(0) { println("TYPES: ${typeMap.size}, " + @@ -790,7 +791,7 @@ internal object DFGSerializer { .map { fun buildTypeBase(type: DataFlowIR.Type) = - TypeBase(type.isFinal, type.isAbstract, type.correspondingValueType, type.name) + TypeBase(type.isFinal, type.isAbstract, type.primitiveBinaryType, type.name) fun buildTypeIntestines(type: DataFlowIR.Type.Declared) = DeclaredType( @@ -962,14 +963,14 @@ internal object DFGSerializer { when { external != null -> DataFlowIR.Type.External(external.hash, external.base.isFinal, external.base.isAbstract, - external.base.correspondingValueType, external.base.name) + external.base.primitiveBinaryType, external.base.name) public != null -> { val symbolTableIndex = public.intestines.index if (symbolTableIndex >= 0) ++module.numberOfClasses DataFlowIR.Type.Public(public.hash, public.intestines.base.isFinal, - public.intestines.base.isAbstract, public.intestines.base.correspondingValueType, + public.intestines.base.isAbstract, public.intestines.base.primitiveBinaryType, module, symbolTableIndex, public.intestines.base.name).also { publicTypesMap.put(it.hash, it) allTypes += it @@ -981,7 +982,7 @@ internal object DFGSerializer { if (symbolTableIndex >= 0) ++module.numberOfClasses DataFlowIR.Type.Private(privateTypeIndex++, private.intestines.base.isFinal, - private.intestines.base.isAbstract, private.intestines.base.correspondingValueType, + private.intestines.base.isAbstract, private.intestines.base.primitiveBinaryType, module, symbolTableIndex, private.intestines.base.name).also { allTypes += it } 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 adfa90bd944..6051d4e2e51 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 @@ -50,13 +50,14 @@ import org.jetbrains.kotlin.resolve.constants.IntValue internal object DataFlowIR { abstract class Type(val isFinal: Boolean, val isAbstract: Boolean, - val correspondingValueType: ValueType?, val name: String?) { + val primitiveBinaryType: PrimitiveBinaryType?, + val name: String?) { // Special marker type forbidding devirtualization on its instances. object Virtual : Declared(false, true, null, null, -1, "\$VIRTUAL") class External(val hash: Long, isFinal: Boolean, isAbstract: Boolean, - correspondingValueType: ValueType?, name: String? = null) - : Type(isFinal, isAbstract, correspondingValueType, name) { + primitiveBinaryType: PrimitiveBinaryType?, name: String? = null) + : Type(isFinal, isAbstract, primitiveBinaryType, name) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is External) return false @@ -73,17 +74,17 @@ internal object DataFlowIR { } } - abstract class Declared(isFinal: Boolean, isAbstract: Boolean, correspondingValueType: ValueType?, + abstract class Declared(isFinal: Boolean, isAbstract: Boolean, primitiveBinaryType: PrimitiveBinaryType?, val module: Module?, val symbolTableIndex: Int, name: String?) - : Type(isFinal, isAbstract, correspondingValueType, name) { + : Type(isFinal, isAbstract, primitiveBinaryType, name) { val superTypes = mutableListOf() val vtable = mutableListOf() val itable = mutableMapOf() } - class Public(val hash: Long, isFinal: Boolean, isAbstract: Boolean, correspondingValueType: ValueType?, + class Public(val hash: Long, isFinal: Boolean, isAbstract: Boolean, primitiveBinaryType: PrimitiveBinaryType?, module: Module, symbolTableIndex: Int, name: String? = null) - : Declared(isFinal, isAbstract, correspondingValueType, module, symbolTableIndex, name) { + : Declared(isFinal, isAbstract, primitiveBinaryType, module, symbolTableIndex, name) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Public) return false @@ -100,9 +101,9 @@ internal object DataFlowIR { } } - class Private(val index: Int, isFinal: Boolean, isAbstract: Boolean, correspondingValueType: ValueType?, + class Private(val index: Int, isFinal: Boolean, isAbstract: Boolean, primitiveBinaryType: PrimitiveBinaryType?, module: Module, symbolTableIndex: Int, name: String? = null) - : Declared(isFinal, isAbstract, correspondingValueType, module, symbolTableIndex, name) { + : Declared(isFinal, isAbstract, primitiveBinaryType, module, symbolTableIndex, name) { override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Private) return false @@ -450,6 +451,7 @@ internal object DataFlowIR { private inline fun takeName(block: () -> String) = if (TAKE_NAMES) block() else null val classMap = mutableMapOf() + val primitiveMap = mutableMapOf() val functionMap = mutableMapOf() private val NAME_ESCAPES = Name.identifier("Escapes") @@ -490,41 +492,40 @@ internal object DataFlowIR { override fun visitClass(declaration: IrClass) { declaration.acceptChildrenVoid(this) - mapClass(declaration) + mapClassReferenceType(declaration) } }, data = null) } private fun ClassDescriptor.isFinal() = modality == Modality.FINAL && kind != ClassKind.ENUM_CLASS - fun mapClass(descriptor: ClassDescriptor): Type { + fun mapClassReferenceType(descriptor: ClassDescriptor): Type { // Do not try to devirtualize ObjC classes. if (descriptor.module.name == Name.special("") || descriptor.isObjCClass()) return Type.Virtual val isFinal = descriptor.isFinal() val isAbstract = descriptor.isAbstract() - val correspondingValueType = descriptor.defaultType.correspondingValueType val name = descriptor.fqNameSafe.asString() if (descriptor.module != irModule.descriptor) return classMap.getOrPut(descriptor) { - Type.External(name.localHash.value, isFinal, isAbstract, correspondingValueType, takeName { name }) + Type.External(name.localHash.value, isFinal, isAbstract, null, takeName { name }) } classMap[descriptor]?.let { return it } - val placeToClassTable = correspondingValueType == null + val placeToClassTable = true val symbolTableIndex = if (placeToClassTable) module.numberOfClasses++ else -1 val type = if (descriptor.isExported()) - Type.Public(name.localHash.value, isFinal, isAbstract, correspondingValueType, + Type.Public(name.localHash.value, isFinal, isAbstract, null, module, symbolTableIndex, takeName { name }) else - Type.Private(privateTypeIndex++, isFinal, isAbstract, correspondingValueType, + Type.Private(privateTypeIndex++, isFinal, isAbstract, null, module, symbolTableIndex, takeName { name }) classMap[descriptor] = type - type.superTypes += descriptor.superTypes.map { mapType(it) } + type.superTypes += descriptor.superTypes.map { mapClassReferenceType(it.getClass()!!) } if (!isAbstract) { val vtableBuilder = context.getVtableBuilder(descriptor) type.vtable += vtableBuilder.vtableEntries.map { mapFunction(it.getImplementation(context)!!) } @@ -541,7 +542,26 @@ internal object DataFlowIR { return erasure.singleOrNull { !it.isInterface } ?: context.ir.symbols.any.owner } - fun mapType(type: IrType) = mapClass(choosePrimary(type.erasure(context))) + fun mapPrimitiveBinaryType(primitiveBinaryType: PrimitiveBinaryType): Type = + primitiveMap.getOrPut(primitiveBinaryType) { + Type.Public( + primitiveBinaryType.ordinal.toLong(), + true, + false, + primitiveBinaryType, + module, + -1, + null + ) + } + + fun mapType(type: IrType): Type { + val binaryType = type.computeBinaryType() + return when (binaryType) { + is BinaryType.Primitive -> mapPrimitiveBinaryType(binaryType.type) + is BinaryType.Reference -> mapClassReferenceType(choosePrimary(binaryType.types.toList())) + } + } // TODO: use from LlvmDeclarations. private fun getFqName(descriptor: DeclarationDescriptor): FqName = @@ -602,7 +622,7 @@ internal object DataFlowIR { symbol.parameterTypes = (descriptor.allParameters.map { it.type } + (if (descriptor.isSuspend) listOf(continuationType) else emptyList())) - .map { mapClass(choosePrimary(it.erasure(context))) } + .map { mapType(it) } .toTypedArray() symbol.returnType = mapType(if (descriptor.isSuspend) context.irBuiltIns.anyType @@ -622,7 +642,7 @@ internal object DataFlowIR { functionMap[it] = symbol symbol.parameterTypes = emptyArray() - symbol.returnType = mapClass(context.ir.symbols.unit.owner) + symbol.returnType = mapClassReferenceType(context.ir.symbols.unit.owner) return symbol } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt index 1955cde781f..cd0b3364b15 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/Devirtualization.kt @@ -979,11 +979,13 @@ internal object Devirtualization { private fun devirtualize(irModule: IrModuleFragment, context: Context, moduleDFG: ModuleDFG, externalModulesDFG: ExternalModulesDFG, devirtualizedCallSites: Map) { - val nativePtrType = context.builtIns.nativePtr.defaultType - val nativePtrEqualityOperatorSymbol = context.ir.symbols.areEqualByValue.single { it.descriptor.valueParameters[0].type == nativePtrType } + val nativePtrType = context.ir.symbols.nativePtrType + val nativePtrEqualityOperatorSymbol = context.ir.symbols.areEqualByValue[PrimitiveBinaryType.POINTER]!! val optimize = context.shouldOptimize() + /* val boxFunctions = ValueType.values().associate { context.ir.symbols.boxFunctions[it]!! to it } val unboxFunctions = ValueType.values().associate { context.ir.symbols.getUnboxFunction(it) to it } + */ fun DataFlowIR.Type.resolved(): DataFlowIR.Type.Declared { if (this is DataFlowIR.Type.Declared) return this @@ -997,7 +999,11 @@ internal object Devirtualization { return this } + /* fun IrExpression.isBoxOrUnboxCall() = this is IrCall && (boxFunctions[symbol] != null || unboxFunctions[symbol] != null) + */ + + fun IrExpression.isBoxOrUnboxCall() = false fun IrBuilderWithScope.irCoerce(value: IrExpression, coercion: IrFunctionSymbol?) = if (coercion == null) @@ -1023,6 +1029,7 @@ internal object Devirtualization { , coercion.symbol) } + /* fun assertCoercionsMatch(coercion1: IrFunctionSymbol, coercion2: IrFunctionSymbol) { boxFunctions[coercion1]?.let { assert (unboxFunctions[coercion2] == it) { "Incosistent coercions: ${coercion1.descriptor}, ${coercion2.descriptor}" } @@ -1044,6 +1051,7 @@ internal object Devirtualization { assertCoercionsMatch(coercion, prevCoercion) return irGet(value) } + */ fun IrBuilderWithScope.irDevirtualizedCall(callee: IrCall, actualType: IrType, devirtualizedCallee: DataFlowIR.FunctionSymbol.Declared) = @@ -1067,7 +1075,7 @@ internal object Devirtualization { extensionReceiver: PossiblyCoercedValue?, parameters: Map) = actualCallee.bridgeTarget.let { - if (it == null) +// if (it == null) irDevirtualizedCall(callee, actualType, actualCallee).apply { this.dispatchReceiver = irGet(receiver) this.extensionReceiver = extensionReceiver?.getFullValue(this@irDevirtualizedCall) @@ -1075,6 +1083,7 @@ internal object Devirtualization { putValueArgument(it.index, parameters[it]!!.getFullValue(this@irDevirtualizedCall)) } } + /* else { val bridgeTarget = it.resolved() as DataFlowIR.FunctionSymbol.Declared val callResult = irDevirtualizedCall(callee, actualType, bridgeTarget).apply { @@ -1102,6 +1111,7 @@ internal object Devirtualization { actualCallee.returnType.resolved().correspondingValueType) irCoerce(callResult, returnCoercion) } + */ } irModule.transformChildrenVoid(object: IrElementTransformerVoidWithContext() { @@ -1114,10 +1124,12 @@ internal object Devirtualization { arg.argument else arg if (!uncastedArg.isBoxOrUnboxCall()) return expression + /* val argarg = (uncastedArg as IrCall).getArguments().single().second if (boxFunctions[expression.symbol].let { it != null && it == unboxFunctions[uncastedArg.symbol] } || unboxFunctions[expression.symbol].let { it != null && it == boxFunctions[uncastedArg.symbol] }) return argarg + */ return expression } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt index e74f6d650f6..2a61f459c02 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/EscapeAnalysis.kt @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.backend.konan.DirectedGraphMultiNode import org.jetbrains.kotlin.backend.konan.llvm.Lifetime import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.ir.util.defaultType internal object EscapeAnalysis { @@ -106,7 +107,7 @@ internal object EscapeAnalysis { } fun analyze(): Map { - val nothing = moduleDFG.symbolTable.mapClass(context.ir.symbols.nothing.owner).resolved() + val nothing = moduleDFG.symbolTable.mapClassReferenceType(context.ir.symbols.nothing.owner).resolved() return callGraph.nodes.associateBy({ it.symbol }) { val function = functions[it.symbol]!! val body = function.body diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt index f65fa274ee5..ef0fd7b33cb 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/serialization/DescriptorTable.kt @@ -17,8 +17,8 @@ package org.jetbrains.kotlin.backend.konan.serialization import org.jetbrains.kotlin.backend.konan.descriptors.isExpectMember +import org.jetbrains.kotlin.backend.konan.getInlinedClass import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo -import org.jetbrains.kotlin.backend.konan.isValueType import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.types.KotlinType @@ -222,7 +222,7 @@ private val FunctionDescriptor.signature: String val signatureSuffix = when { this.typeParameters.isNotEmpty() -> "Generic" - returnType.let { it != null && it.isValueType() } -> "ValueType" + returnType?.getInlinedClass() != null -> "ValueType" returnType.let { it != null && !KotlinBuiltIns.isUnitOrNullableUnit(it) } -> typeToHashString(returnType!!) else -> "" } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt index f763bc7152d..269f224720f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils2.kt @@ -736,3 +736,6 @@ val IrTypeArgument.typeOrNull: IrType? get() = (this as? IrTypeProjection)?.type val IrType.isSimpleTypeWithQuestionMark: Boolean get() = this is IrSimpleType && this.hasQuestionMark + +fun IrClass.defaultOrNullableType(hasQuestionMark: Boolean) = + if (hasQuestionMark) this.defaultType.makeNullable() else this.defaultType diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index 2765c7c6da0..2a86c747d9a 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -379,6 +379,10 @@ task function_defaults_with_vararg2(type: RunKonanTest) { source = "codegen/function/defaultsWithVarArg2.kt" } +task function_defaults_with_inline_classes(type: RunKonanTest) { + source = "codegen/function/defaultsWithInlineClasses.kt" +} + task sum_3const(type: RunKonanTest) { source = "codegen/function/sum_3const.kt" } diff --git a/backend.native/tests/codegen/function/defaultsWithInlineClasses.kt b/backend.native/tests/codegen/function/defaultsWithInlineClasses.kt new file mode 100644 index 00000000000..07f10c50e2b --- /dev/null +++ b/backend.native/tests/codegen/function/defaultsWithInlineClasses.kt @@ -0,0 +1,11 @@ +package codegen.function.defaultsWithInlineClasses + +import kotlin.test.* + +inline class Foo(val value: Int) +fun foo(x: Foo = Foo(42)) = x.value + +@Test fun runTest() { + assertEquals(foo(), 42) + assertEquals(foo(Foo(17)), 17) +} \ No newline at end of file diff --git a/backend.native/tests/framework/values/values.kt b/backend.native/tests/framework/values/values.kt index 1428dad11aa..7807a271511 100644 --- a/backend.native/tests/framework/values/values.kt +++ b/backend.native/tests/framework/values/values.kt @@ -193,3 +193,14 @@ fun getNamedObjectInterface(): OpenClassI = WithCompanionAndObject.Named typealias EE = Enumeration fun EE.getAnswer() : EE = Enumeration.ANSWER + +inline class IC1(val value: Int) +inline class IC2(val value: String) +inline class IC3(val value: TripleVals?) + +fun box(ic1: IC1): Any = ic1 +fun box(ic2: IC2): Any = ic2 +fun box(ic3: IC3): Any = ic3 + +fun concatenateInlineClassValues(ic1: IC1, ic1N: IC1?, ic2: IC2, ic2N: IC2?, ic3: IC3, ic3N: IC3?): String = + "${ic1.value} ${ic1N?.value} ${ic2.value} ${ic2N?.value} ${ic3.value} ${ic3N?.value}" \ No newline at end of file diff --git a/backend.native/tests/framework/values/values.swift b/backend.native/tests/framework/values/values.swift index e3d366afcc3..cfd7e6e6589 100644 --- a/backend.native/tests/framework/values/values.swift +++ b/backend.native/tests/framework/values/values.swift @@ -277,6 +277,25 @@ func testCompanionObj() throws { try assertEquals(actual: Values.getNamedObjectInterface().iFun(), expected: named.iFun(), "Named object's method") } +func testInlineClasses() throws { + let ic1: Int32 = 42 + let ic1N = Values.box(ic1: 17) + let ic2 = "foo" + let ic2N = "bar" + let ic3 = ValuesTripleVals(first: 1, second: 2, third: 3) + let ic3N = Values.box(ic3: nil) + + try assertEquals( + actual: Values.concatenateInlineClassValues(ic1: ic1, ic1N: ic1N, ic2: ic2, ic2N: ic2N, ic3: ic3, ic3N: ic3N), + expected: "42 17 foo bar TripleVals(first=1, second=2, third=3) null" + ) + + try assertEquals( + actual: Values.concatenateInlineClassValues(ic1: ic1, ic1N: nil, ic2: ic2, ic2N: nil, ic3: nil, ic3N: nil), + expected: "42 null foo null null null" + ) +} + // -------- Execution of the test -------- class ValuesTests : TestProvider { @@ -307,6 +326,7 @@ class ValuesTests : TestProvider { TestCase(name: "TestEnum", method: withAutorelease(testEnum)), TestCase(name: "TestDataClass", method: withAutorelease(testDataClass)), TestCase(name: "TestCompanionObj", method: withAutorelease(testCompanionObj)), + TestCase(name: "TestInlineClasses", method: withAutorelease(testInlineClasses)), ] } } diff --git a/backend.native/tests/produce_dynamic/simple/hello.kt b/backend.native/tests/produce_dynamic/simple/hello.kt index 6a1a9f498f9..d49378e7210 100644 --- a/backend.native/tests/produce_dynamic/simple/hello.kt +++ b/backend.native/tests/produce_dynamic/simple/hello.kt @@ -70,4 +70,14 @@ class Impl2 : Impl1() { override fun foo(arg0: String, arg1: Int, arg2: I) { println("Impl2.I: $arg0 $arg1 ${arg2::class.qualifiedName}") } -} \ No newline at end of file +} + +inline class IC1(val value: Int) +inline class IC2(val value: String) +inline class IC3(val value: Base?) + +fun useInlineClasses(ic1: IC1, ic2: IC2, ic3: IC3) { + assert(ic1.value == 42) + assert(ic2.value == "bar") + assert(ic3.value is Base) +} diff --git a/backend.native/tests/produce_dynamic/simple/main.c b/backend.native/tests/produce_dynamic/simple/main.c index cfd2a960706..b81713fac3a 100644 --- a/backend.native/tests/produce_dynamic/simple/main.c +++ b/backend.native/tests/produce_dynamic/simple/main.c @@ -40,6 +40,8 @@ int main(void) { __ kotlin.root.topLevelFunctionVoid(42, 0); printf("topLevel = %d %d\n", topLevelFunctionFromC(780, 3), __ kotlin.root.topLevelFunctionFromCShort(5, 2)); + __ kotlin.root.useInlineClasses(42, "bar", base); + __ DisposeString(string); __ DisposeStablePointer(base.pinned); __ DisposeStablePointer(child.pinned); diff --git a/runtime/src/main/kotlin/konan/internal/Boxing.kt b/runtime/src/main/kotlin/konan/internal/Boxing.kt index 66bbcb6169e..a3c77e41bbc 100644 --- a/runtime/src/main/kotlin/konan/internal/Boxing.kt +++ b/runtime/src/main/kotlin/konan/internal/Boxing.kt @@ -17,258 +17,52 @@ package konan.internal @SymbolName("getCachedBooleanBox") -external fun getCachedBooleanBox(value: Boolean): BooleanBox +external fun getCachedBooleanBox(value: Boolean): Boolean? @SymbolName("inBooleanBoxCache") external fun inBooleanBoxCache(value: Boolean): Boolean @SymbolName("getCachedByteBox") -external fun getCachedByteBox(value: Byte): ByteBox +external fun getCachedByteBox(value: Byte): Byte? @SymbolName("inByteBoxCache") external fun inByteBoxCache(value: Byte): Boolean @SymbolName("getCachedCharBox") -external fun getCachedCharBox(value: Char): CharBox +external fun getCachedCharBox(value: Char): Char? @SymbolName("inCharBoxCache") external fun inCharBoxCache(value: Char): Boolean @SymbolName("getCachedShortBox") -external fun getCachedShortBox(value: Short): ShortBox +external fun getCachedShortBox(value: Short): Short? @SymbolName("inShortBoxCache") external fun inShortBoxCache(value: Short): Boolean @SymbolName("getCachedIntBox") -external fun getCachedIntBox(idx: Int): IntBox +external fun getCachedIntBox(idx: Int): Int? @SymbolName("inIntBoxCache") external fun inIntBoxCache(value: Int): Boolean @SymbolName("getCachedLongBox") -external fun getCachedLongBox(value: Long): LongBox +external fun getCachedLongBox(value: Long): Long? @SymbolName("inLongBoxCache") external fun inLongBoxCache(value: Long): Boolean -@Frozen -class BooleanBox(val value: Boolean) : Comparable { - override fun equals(other: Any?): Boolean { - if (other !is BooleanBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Boolean): Int = value.compareTo(other) -} +// TODO: functions below are used for ObjCExport, move and rename them correspondigly. @ExportForCppRuntime("Kotlin_boxBoolean") -fun boxBoolean(value: Boolean) = if (inBooleanBoxCache(value)) { - getCachedBooleanBox(value) -} else { - BooleanBox(value) -} - -@Frozen -class CharBox(val value: Char) : Comparable { - override fun equals(other: Any?): Boolean { - if (other !is CharBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Char): Int = value.compareTo(other) -} +fun boxBoolean(value: Boolean): Boolean? = value @ExportForCppRuntime("Kotlin_boxChar") -fun boxChar(value: Char) = if (inCharBoxCache(value)) { - getCachedCharBox(value) -} else { - CharBox(value) -} - -@Frozen -class ByteBox(val value: Byte) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is ByteBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Byte): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxChar(value: Char): Char? = value @ExportForCppRuntime("Kotlin_boxByte") -fun boxByte(value: Byte) = if (inByteBoxCache(value)) { - getCachedByteBox(value) -} else { - ByteBox(value) -} - -@Frozen -class ShortBox(val value: Short) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is ShortBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Short): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxByte(value: Byte): Byte? = value @ExportForCppRuntime("Kotlin_boxShort") -fun boxShort(value: Short) = if (inShortBoxCache(value)) { - getCachedShortBox(value) -} else { - ShortBox(value) -} - -@Frozen -class IntBox(val value: Int) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is IntBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Int): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxShort(value: Short): Short? = value @ExportForCppRuntime("Kotlin_boxInt") -fun boxInt(value: Int) = if (inIntBoxCache(value)) { - getCachedIntBox(value) -} else { - IntBox(value) -} - -@Frozen -class LongBox(val value: Long) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is LongBox) { - return false - } - - return this.value == other.value - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Long): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxInt(value: Int): Int? = value @ExportForCppRuntime("Kotlin_boxLong") -fun boxLong(value: Long) = if (inLongBoxCache(value)) { - getCachedLongBox(value) -} else { - LongBox(value) -} - -@Frozen -class FloatBox(val value: Float) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is FloatBox) { - return false - } - - return this.value.equals(other.value) - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Float): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxLong(value: Long): Long? = value @ExportForCppRuntime("Kotlin_boxFloat") -fun boxFloat(value: Float) = FloatBox(value) - -@Frozen -class DoubleBox(val value: Double) : Number(), Comparable { - override fun equals(other: Any?): Boolean { - if (other !is DoubleBox) { - return false - } - - return this.value.equals(other.value) - } - - override fun hashCode() = value.hashCode() - - override fun toString() = value.toString() - - override fun compareTo(other: Double): Int = value.compareTo(other) - - override fun toByte() = value.toByte() - override fun toChar() = value.toChar() - override fun toShort() = value.toShort() - override fun toInt() = value.toInt() - override fun toLong() = value.toLong() - override fun toFloat() = value.toFloat() - override fun toDouble() = value.toDouble() -} +fun boxFloat(value: Float): Float? = value @ExportForCppRuntime("Kotlin_boxDouble") -fun boxDouble(value: Double) = DoubleBox(value) +fun boxDouble(value: Double): Double? = value diff --git a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt index a11e093b3d9..629f8e618e7 100644 --- a/runtime/src/main/kotlin/konan/internal/Intrinsics.kt +++ b/runtime/src/main/kotlin/konan/internal/Intrinsics.kt @@ -20,16 +20,20 @@ import kotlinx.cinterop.CPointer import kotlinx.cinterop.NativePointed import kotlinx.cinterop.NativePtr -@Intrinsic external fun areEqualByValue(first: Boolean, second: Boolean): Boolean -@Intrinsic external fun areEqualByValue(first: Char, second: Char): Boolean -@Intrinsic external fun areEqualByValue(first: Byte, second: Byte): Boolean -@Intrinsic external fun areEqualByValue(first: Short, second: Short): Boolean -@Intrinsic external fun areEqualByValue(first: Int, second: Int): Boolean -@Intrinsic external fun areEqualByValue(first: Long, second: Long): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Boolean, second: Boolean): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Byte, second: Byte): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Short, second: Short): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Int, second: Int): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Long, second: Long): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: NativePtr, second: NativePtr): Boolean -@Intrinsic external fun ieee754Equals(first: Float, second: Float): Boolean -@Intrinsic external fun ieee754Equals(first: Double, second: Double): Boolean +// Bitwise equality: +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Float, second: Float): Boolean +@Intrinsic @PublishedApi external internal fun areEqualByValue(first: Double, second: Double): Boolean -@Intrinsic external fun areEqualByValue(first: NativePtr, second: NativePtr): Boolean -@Intrinsic external fun areEqualByValue(first: NativePointed?, second: NativePointed?): Boolean -@Intrinsic external fun areEqualByValue(first: CPointer<*>?, second: CPointer<*>?): Boolean \ No newline at end of file +// IEEE754 equality: +@Intrinsic @PublishedApi external internal fun ieee754Equals(first: Float, second: Float): Boolean +@Intrinsic @PublishedApi external internal fun ieee754Equals(first: Double, second: Double): Boolean + +// Reinterprets this value from T to R having the same binary representation (e.g. to unwrap inline class). +@Intrinsic @PublishedApi external internal fun T.reinterpret(): R diff --git a/runtime/src/main/kotlin/konan/internal/NativePtr.kt b/runtime/src/main/kotlin/konan/internal/NativePtr.kt index 3f3ecb609c9..2a8a900617d 100644 --- a/runtime/src/main/kotlin/konan/internal/NativePtr.kt +++ b/runtime/src/main/kotlin/konan/internal/NativePtr.kt @@ -18,7 +18,7 @@ package konan.internal @Intrinsic external fun getNativeNullPtr(): NativePtr -class NativePtr private constructor() { +class NativePtr @PublishedApi internal constructor(private val value: NonNullNativePtr?) { companion object { val NULL = getNativeNullPtr() } @@ -33,3 +33,9 @@ class NativePtr private constructor() { override fun toString() = "0x${this.toLong().toString(16)}" } + +@PublishedApi +internal inline class NonNullNativePtr(val value: NotNullPointerValue) { // TODO: refactor to use this type widely. + @Suppress("NOTHING_TO_INLINE") + inline fun toNativePtr() = NativePtr(this) +} diff --git a/runtime/src/main/kotlin/konan/internal/ValueTypes.kt b/runtime/src/main/kotlin/konan/internal/ValueTypes.kt new file mode 100644 index 00000000000..559dd79e5bd --- /dev/null +++ b/runtime/src/main/kotlin/konan/internal/ValueTypes.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package konan.internal + +internal class BooleanValue +internal class ByteValue +internal class ShortValue +internal class IntValue +internal class LongValue +internal class FloatValue +internal class DoubleValue +internal class NotNullPointerValue diff --git a/runtime/src/main/kotlin/kotlin/Boolean.kt b/runtime/src/main/kotlin/kotlin/Boolean.kt index 75aeb0b50e6..79fecf662c9 100644 --- a/runtime/src/main/kotlin/kotlin/Boolean.kt +++ b/runtime/src/main/kotlin/kotlin/Boolean.kt @@ -20,7 +20,7 @@ package kotlin * Represents a value which is either `true` or `false`. On the JVM, non-nullable values of this type are * represented as values of the primitive type `boolean`. */ -public final class Boolean : Comparable { +public final class Boolean private constructor(private val value: konan.internal.BooleanValue) : Comparable { /** * Returns the inverse of this boolean. */ diff --git a/runtime/src/main/kotlin/kotlin/Char.kt b/runtime/src/main/kotlin/kotlin/Char.kt index ac558fb9890..edd16317549 100644 --- a/runtime/src/main/kotlin/kotlin/Char.kt +++ b/runtime/src/main/kotlin/kotlin/Char.kt @@ -20,7 +20,7 @@ package kotlin * Represents a 16-bit Unicode character. * On the JVM, non-nullable values of this type are represented as values of the primitive type `char`. */ -public final class Char : Comparable { +public final class Char private constructor(private val value: konan.internal.ShortValue) : Comparable { /** * Compares this value with the specified value for order. @@ -133,10 +133,10 @@ public final class Char : Comparable { } // Konan-specific. - public fun equals(other: Char): Boolean = konan.internal.areEqualByValue(this, other) + public fun equals(other: Char): Boolean = this == other public override fun equals(other: Any?): Boolean = - other is Char && konan.internal.areEqualByValue(this, other) + other is Char && this.equals(other) @SymbolName("Kotlin_Char_toString") external public override fun toString(): String diff --git a/runtime/src/main/kotlin/kotlin/Primitives.kt b/runtime/src/main/kotlin/kotlin/Primitives.kt index ae311cb22f2..2e9e1a14319 100644 --- a/runtime/src/main/kotlin/kotlin/Primitives.kt +++ b/runtime/src/main/kotlin/kotlin/Primitives.kt @@ -22,7 +22,7 @@ import konan.internal.NumberConverter * Represents a 8-bit signed integer. * On the JVM, non-nullable values of this type are represented as values of the primitive type `byte`. */ -public final class Byte : Number(), Comparable { +public final class Byte private constructor(private val value: konan.internal.ByteValue) : Number(), Comparable { companion object { /** * A constant holding the minimum value an instance of Byte can have. @@ -236,7 +236,7 @@ public final class Byte : Number(), Comparable { * Represents a 16-bit signed integer. * On the JVM, non-nullable values of this type are represented as values of the primitive type `short`. */ -public final class Short : Number(), Comparable { +public final class Short private constructor(private val value: konan.internal.ShortValue) : Number(), Comparable { companion object { /** * A constant holding the minimum value an instance of Short can have. @@ -450,7 +450,7 @@ public final class Short : Number(), Comparable { * Represents a 32-bit signed integer. * On the JVM, non-nullable values of this type are represented as values of the primitive type `int`. */ -public final class Int : Number(), Comparable { +public final class Int private constructor(private val value: konan.internal.IntValue) : Number(), Comparable { companion object { /** * A constant holding the minimum value an instance of Int can have. @@ -686,7 +686,7 @@ public final class Int : Number(), Comparable { * Represents a 64-bit signed integer. * On the JVM, non-nullable values of this type are represented as values of the primitive type `long`. */ -public final class Long : Number(), Comparable { +public final class Long private constructor(private val value: konan.internal.LongValue) : Number(), Comparable { companion object { /** * A constant holding the minimum value an instance of Long can have. @@ -922,7 +922,7 @@ public final class Long : Number(), Comparable { * Represents a single-precision 32-bit IEEE 754 floating point number. * On the JVM, non-nullable values of this type are represented as values of the primitive type `float`. */ -public final class Float : Number(), Comparable { +public final class Float private constructor(private val value: konan.internal.FloatValue) : Number(), Comparable { companion object { /** * A constant holding the smallest *positive* nonzero value of Float. @@ -1141,7 +1141,7 @@ public final class Float : Number(), Comparable { * Represents a double-precision 64-bit IEEE 754 floating point number. * On the JVM, non-nullable values of this type are represented as values of the primitive type `double`. */ -public final class Double : Number(), Comparable { +public final class Double private constructor(private val value: konan.internal.DoubleValue) : Number(), Comparable { companion object { /** * A constant holding the smallest *positive* nonzero value of Double.