From c84a70c654c286ee15fca2c46ce6edb98b465d92 Mon Sep 17 00:00:00 2001 From: Vladimir Sukharev Date: Tue, 9 Aug 2022 18:07:02 +0000 Subject: [PATCH] Always inline primitive unbox functions in IR level Merge-request: KT-MR-6708 Merged-by: Vladimir Sukharev --- .../backend/konan/KonanLoweringPhases.kt | 9 +++ .../kotlin/backend/konan/ToplevelPhases.kt | 2 + .../konan/lower/UnboxInlineLowering.kt | 73 +++++++++++++++++++ .../backend.native/tests/build.gradle | 53 ++++++++++++++ .../filecheck/kt53261/kt53261_inline_unbox.kt | 34 +++++++++ .../kt53261/kt53261_noinline_CPointer.kt | 9 +++ .../kt53261/kt53261_noinline_NativePointed.kt | 9 +++ .../kt53261_noinline_NonNullNativePtr.kt | 9 +++ .../kt53261/kt53261_noinline_StableRef.kt | 9 +++ .../kt53261/kt53261_noinline_UByteArray.kt | 9 +++ .../kt53261/kt53261_noinline_UIntArray.kt | 9 +++ .../kt53261/kt53261_noinline_ULongArray.kt | 9 +++ .../kt53261/kt53261_noinline_UShortArray.kt | 9 +++ .../kt53261/kt53261_noinline_value_unbox.kt | 17 +++++ 14 files changed, 260 insertions(+) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnboxInlineLowering.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_inline_unbox.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_CPointer.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NativePointed.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NonNullNativePtr.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_StableRef.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UByteArray.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UIntArray.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_ULongArray.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UShortArray.kt create mode 100644 kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_value_unbox.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt index 082a534c87d..fdd8cdcf00b 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLoweringPhases.kt @@ -10,8 +10,10 @@ import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineLamb import org.jetbrains.kotlin.backend.common.lower.loops.ForLoopsLowering import org.jetbrains.kotlin.backend.common.lower.optimizations.FoldConstantLowering import org.jetbrains.kotlin.backend.common.lower.optimizations.PropertyAccessorInlineLowering +import org.jetbrains.kotlin.backend.konan.lower.UnboxInlineLowering import org.jetbrains.kotlin.backend.common.phaser.* import org.jetbrains.kotlin.backend.konan.ir.FunctionsWithoutBoundCheckGenerator +import org.jetbrains.kotlin.backend.konan.llvm.redundantCoercionsCleaningPhase import org.jetbrains.kotlin.backend.konan.lower.* import org.jetbrains.kotlin.backend.konan.lower.InitializersLowering import org.jetbrains.kotlin.backend.konan.optimizations.KonanBCEForLoopBodyTransformer @@ -408,6 +410,13 @@ internal val autoboxPhase = makeKonanFileLoweringPhase( prerequisite = setOf(bridgesPhase, coroutinesPhase) ) +internal val unboxInlinePhase = makeKonanModuleLoweringPhase( + ::UnboxInlineLowering, + name = "UnboxInline", + description = "Unbox functions inline lowering", + prerequisite = setOf(autoboxPhase, redundantCoercionsCleaningPhase) +) + internal val expressionBodyTransformPhase = makeKonanFileLoweringPhase( ::ExpressionBodyTransformer, name = "ExpressionBodyTransformer", diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 8669f7014e1..f801a7b3a17 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -583,6 +583,7 @@ internal val bitcodePhase = NamedCompilerPhase( // from dependencies can be changed during lowerings. inlineClassPropertyAccessorsPhase then redundantCoercionsCleaningPhase then + unboxInlinePhase then createLLVMDeclarationsPhase then ghaPhase then RTTIPhase then @@ -699,6 +700,7 @@ internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { // Inline accessors only in optimized builds due to separate compilation and possibility to get broken // debug information. disable(propertyAccessorInlinePhase) + disable(unboxInlinePhase) disable(inlineClassPropertyAccessorsPhase) disable(dcePhase) disable(removeRedundantCallsToFileInitializersPhase) diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnboxInlineLowering.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnboxInlineLowering.kt new file mode 100644 index 00000000000..f327907d502 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/UnboxInlineLowering.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.backend.konan.lower + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION +import org.jetbrains.kotlin.backend.konan.getUnboxFunction +import org.jetbrains.kotlin.ir.builders.irGetField +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.types.getClass +import org.jetbrains.kotlin.ir.types.isNullable +import org.jetbrains.kotlin.ir.util.statements +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +/** + * In case the body of is exactly RETURN(GET_FIELD(IrExpression, backing_field)), it is inlined. + * So, the snippets `CALL 'public final fun (IrExpression)` are transformed to 'GET_FIELD(IrExpression, backing_field)'. + */ +internal class UnboxInlineLowering( + private val context: CommonBackendContext, +) : BodyLoweringPass { + + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid(AccessorInliner(context)) + } +} + +private class AccessorInliner(commonBackendContext: CommonBackendContext) : IrElementTransformerVoid() { + + private val context = commonBackendContext as Context + private val anyType = context.irBuiltIns.anyType + + private fun IrFunction.isEasyInlineableUnbox(): Boolean = + origin == DECLARATION_ORIGIN_INLINE_CLASS_SPECIAL_FUNCTION && + name.asString().endsWith("-unbox>") && + !returnType.isNullable() + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + + return if (expression.symbol.owner.isEasyInlineableUnbox()) + tryInlineUnbox(expression) ?: expression + else expression + } + + private fun tryInlineUnbox(call: IrCall): IrExpression? { + val returnClass = call.type.getClass()!! + val singleStatement = context.getUnboxFunction(returnClass).body?.statements?.singleOrNull() + return if (singleStatement is IrReturn) { + val retVal = singleStatement.value + if (retVal is IrGetField) { + // Boxed primitive types (Int, Short,..) have `value` field + // Inline unsigned classes (UInt, UShort,..) have `data` field + val field = retVal.symbol.owner + context.createIrBuilder(call.symbol, call.startOffset, call.endOffset).irGetField(call.getValueArgument(0), field) + } else { + context.log { "Cannot inline unbox function ${call.symbol} with body `IrReturn(expression)`, where `expression` is not IrGetField(...)" } + null + } + } else { + context.log { "Cannot inline unbox function ${call.symbol} with body which is not IrReturn(IrGetField(...))" } + null + } + } +} diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle index d435344b21d..3738b72fba9 100644 --- a/kotlin-native/backend.native/tests/build.gradle +++ b/kotlin-native/backend.native/tests/build.gradle @@ -6225,6 +6225,59 @@ fileCheckTest("filecheck_kt49847_generic_receiver") { enabled = project.globalTestArgs.contains('-opt') } +fileCheckTest("filecheck_kt53261_inline_unbox") { + annotatedSource = project.file('filecheck/kt53261/kt53261_inline_unbox.kt') + enabled = project.globalTestArgs.contains('-opt') + if (target.architecture == Architecture.ARM64) { + checkPrefix = "CHECK-ARM64" + } +} + +fileCheckTest("filecheck_kt53261_noinline_value_unbox") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_value_unbox.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_CPointer.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_CPointer.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_NativePointed.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_NativePointed.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_NonNullNativePtr.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_NonNullNativePtr.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_StableRef") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_StableRef.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_UByteArray.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_UByteArray.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_UIntArray.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_UIntArray.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_ULongArray.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_ULongArray.kt') + enabled = project.globalTestArgs.contains('-opt') +} + +fileCheckTest("filecheck_kt53261_noinline_UShortArray.kt") { + annotatedSource = project.file('filecheck/kt53261/kt53261_noinline_UShortArray.kt') + enabled = project.globalTestArgs.contains('-opt') +} + fileCheckTest("filecheck_intrinsics") { annotatedSource = project.file('filecheck/intrinsics.kt') } diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_inline_unbox.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_inline_unbox.kt new file mode 100644 index 00000000000..2bee9b3d99c --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_inline_unbox.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK-NOT: {{call|invoke}} i64 @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} i64 @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} i32 @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} i32 @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} signext i16 @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} zeroext i16 @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} signext i8 @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} zeroext i8 @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} zeroext i16 @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} zeroext i1 @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} double @"kfun:kotlin# +// CHECK-NOT: {{call|invoke}} float @"kfun:kotlin# + +// CHECK-NOT: {{call|invoke}} i8* @"kfun:kotlin.native.internal# +// CHECK-NOT: {{call|invoke}} i32 @"kfun:kotlin.native.concurrent# +// CHECK-NOT: {{call|invoke}} i32 @"kfun:kotlin.native.concurrent# +// CHECK-NOT: {{call|invoke}} <4 x float> @"kfun:kotlin.native# +// CHECK-NOT: {{call|invoke}} %struct.ObjHeader* @"kfun:kotlin# + +// On ARM64, generated functions IntToNSNumber and UIntToNSNumber may contain non-converted invocations of unbox functions. +// CHECK-ARM64: IntToNSNumber diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_CPointer.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_CPointer.kt new file mode 100644 index 00000000000..942b81b8f86 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_CPointer.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} i8* @"kfun:kotlinx.cinterop# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NativePointed.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NativePointed.kt new file mode 100644 index 00000000000..ea8d69d6f25 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NativePointed.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} i8* @"kfun:kotlinx.cinterop# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NonNullNativePtr.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NonNullNativePtr.kt new file mode 100644 index 00000000000..ab068b70ec4 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_NonNullNativePtr.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} i8* @"kfun:kotlin.native.internal# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_StableRef.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_StableRef.kt new file mode 100644 index 00000000000..a85f3539f13 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_StableRef.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} i8* @"kfun:kotlinx.cinterop# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UByteArray.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UByteArray.kt new file mode 100644 index 00000000000..5130c48a817 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UByteArray.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} %struct.ObjHeader* @"kfun:kotlin# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UIntArray.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UIntArray.kt new file mode 100644 index 00000000000..3ed4130c6e2 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UIntArray.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} %struct.ObjHeader* @"kfun:kotlin# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_ULongArray.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_ULongArray.kt new file mode 100644 index 00000000000..ccb1eb02019 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_ULongArray.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} %struct.ObjHeader* @"kfun:kotlin# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UShortArray.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UShortArray.kt new file mode 100644 index 00000000000..d456731f57e --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_UShortArray.kt @@ -0,0 +1,9 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +fun main(arr: Array) { + println(arr[0].toInt() + 1) +} +// CHECK: {{call|invoke}} %struct.ObjHeader* @"kfun:kotlin# diff --git a/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_value_unbox.kt b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_value_unbox.kt new file mode 100644 index 00000000000..2773d4396e8 --- /dev/null +++ b/kotlin-native/backend.native/tests/filecheck/kt53261/kt53261_noinline_value_unbox.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +value class C(val x: Any) +val c = C(42) + +fun main() { + println(c.x) +} +// CHECK: {{call|invoke}} %struct.ObjHeader* @"kfun:# + +// Note: is called from IR of generated methods like +// FUN BRIDGE_METHOD(target=FUN GENERATED_SINGLE_FIELD_VALUE_CLASS_MEMBER name:equals +// FUN BRIDGE_METHOD(target=FUN GENERATED_SINGLE_FIELD_VALUE_CLASS_MEMBER name:hashCode +// FUN BRIDGE_METHOD(target=FUN GENERATED_SINGLE_FIELD_VALUE_CLASS_MEMBER name:toString