From bb311c7ac496950b325b14395734d6f5ede74178 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Mon, 19 Feb 2018 17:51:29 +0300 Subject: [PATCH] Supported lateinit locals + tests --- .../kotlin/backend/konan/KonanLower.kt | 7 +- .../kotlin/backend/konan/KonanPhases.kt | 4 +- .../backend/konan/lower/LateinitLowering.kt | 116 ++++++++++++++++++ backend.native/tests/build.gradle | 22 ++++ .../lateinit/localCapturedInitialized.kt | 12 ++ .../lateinit/localCapturedNotInitialized.kt | 18 +++ .../codegen/lateinit/localInitialized.kt | 9 ++ .../codegen/lateinit/localNotInitialized.kt | 16 +++ 8 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt create mode 100644 backend.native/tests/codegen/lateinit/localCapturedInitialized.kt create mode 100644 backend.native/tests/codegen/lateinit/localCapturedNotInitialized.kt create mode 100644 backend.native/tests/codegen/lateinit/localInitialized.kt create mode 100644 backend.native/tests/codegen/lateinit/localNotInitialized.kt 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 32f8409a3ad..9252da31f42 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 @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.backend.common.validateIrModule import org.jetbrains.kotlin.backend.konan.lower.* import org.jetbrains.kotlin.backend.konan.lower.DefaultArgumentStubGenerator import org.jetbrains.kotlin.backend.konan.lower.DefaultParameterInjector +import org.jetbrains.kotlin.backend.konan.lower.LateinitLowering import org.jetbrains.kotlin.backend.konan.lower.LocalDeclarationsLowering import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrModuleFragment @@ -95,6 +96,9 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_INITIALIZERS) { InitializersLowering(context).runOnFilePostfix(irFile) } + phaser.phase(KonanPhase.LOWER_LATEINIT) { + LateinitLowering(context).lower(irFile) + } phaser.phase(KonanPhase.LOWER_SHARED_VARIABLES) { SharedVariablesLowering(context).runOnFilePostfix(irFile) } @@ -117,9 +121,6 @@ internal class KonanLower(val context: Context) { DefaultArgumentStubGenerator(context).runOnFilePostfix(irFile) DefaultParameterInjector(context).runOnFilePostfix(irFile) } - phaser.phase(KonanPhase.LOWER_LATEINIT) { - LateinitLowering(context).lower(irFile) - } phaser.phase(KonanPhase.LOWER_BUILTIN_OPERATORS) { BuiltinOperatorLowering(context).runOnFilePostfix(irFile) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index a604f9e47af..95af5795891 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -41,7 +41,8 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_ENUMS("Enum classes lowering"), /* ... ... */ LOWER_DELEGATION("Delegation lowering"), /* ... ... */ LOWER_INITIALIZERS("Initializers lowering", LOWER_ENUMS), - /* ... ... */ LOWER_SHARED_VARIABLES("Shared Variable Lowering", LOWER_INITIALIZERS), + /* ... ... */ LOWER_LATEINIT("Lateinit properties lowering"), + /* ... ... */ LOWER_SHARED_VARIABLES("Shared Variable Lowering", LOWER_INITIALIZERS, LOWER_LATEINIT), /* ... ... */ LOWER_CALLABLES("Callable references Lowering", LOWER_DELEGATION), /* ... ... */ LOWER_LOCAL_FUNCTIONS("Local Function Lowering", LOWER_SHARED_VARIABLES, LOWER_CALLABLES), /* ... ... */ LOWER_INTEROP_PART2("Interop lowering, part 2", LOWER_LOCAL_FUNCTIONS), @@ -50,7 +51,6 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_FINALLY("Finally blocks lowering", LOWER_INITIALIZERS, LOWER_LOCAL_FUNCTIONS, LOWER_TAILREC), /* ... ... */ LOWER_DEFAULT_PARAMETER_EXTENT("Default Parameter Extent Lowering", LOWER_TAILREC, LOWER_ENUMS), /* ... ... */ LOWER_INNER_CLASSES("Inner classes lowering", LOWER_DEFAULT_PARAMETER_EXTENT), - /* ... ... */ LOWER_LATEINIT("Lateinit properties lowering"), /* ... ... */ LOWER_BUILTIN_OPERATORS("BuiltIn Operators Lowering", LOWER_DEFAULT_PARAMETER_EXTENT, LOWER_LATEINIT), /* ... ... */ LOWER_COROUTINES("Coroutines lowering", LOWER_LOCAL_FUNCTIONS), /* ... ... */ LOWER_TYPE_OPERATORS("Type operators lowering", LOWER_COROUTINES), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt new file mode 100644 index 00000000000..7e39e5d263e --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt @@ -0,0 +1,116 @@ +/* + * 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.lower + +import org.jetbrains.kotlin.backend.common.CommonBackendContext +import org.jetbrains.kotlin.backend.common.FileLoweringPass +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.backend.common.lower.irBlock +import org.jetbrains.kotlin.backend.common.lower.irIfThen +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.descriptors.VariableDescriptor +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.builders.* +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.declarations.IrProperty +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol +import org.jetbrains.kotlin.ir.symbols.IrSymbol +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid + +class LateinitLowering( + val context: CommonBackendContext, + private val generateParameterNameInAssertion: Boolean = false +) : FileLoweringPass { + override fun lower(irFile: IrFile) { + irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + + override fun visitGetValue(expression: IrGetValue): IrExpression { + val symbol = expression.symbol + val descriptor = symbol.descriptor as? VariableDescriptor + if (descriptor == null || !descriptor.isLateInit) return expression + + assert(!KotlinBuiltIns.isPrimitiveType(descriptor.type), { "'lateinit' modifier is not allowed on primitive types" }) + val startOffset = expression.startOffset + val endOffset = expression.endOffset + val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset) + irBuilder.run { + return irBlock(expression) { + // TODO: do data flow analysis to check if value is proved to be not-null. + +irIfThen( + irEqualsNull(irGet(symbol)), + throwUninitializedPropertyAccessException(symbol) + ) + +irGet(symbol) + } + } + } + + override fun visitProperty(declaration: IrProperty): IrStatement { + declaration.transformChildrenVoid(this) + + if (declaration.descriptor.isLateInit && declaration.descriptor.kind.isReal) + transformGetter(declaration.backingField!!.symbol, declaration.getter!!) + + return declaration + } + + private fun transformGetter(backingFieldSymbol: IrFieldSymbol, getter: IrFunction) { + val type = backingFieldSymbol.descriptor.type + assert(!KotlinBuiltIns.isPrimitiveType(type), { "'lateinit' modifier is not allowed on primitive types" }) + val startOffset = getter.startOffset + val endOffset = getter.endOffset + val irBuilder = context.createIrBuilder(getter.symbol, startOffset, endOffset) + irBuilder.run { + getter.body = irBlockBody { + val resultVar = irTemporary( + irGetField(getter.dispatchReceiverParameter?.let { irGet(it.symbol) }, backingFieldSymbol) + ) + +irIfThenElse( + context.builtIns.nothingType, + irNotEquals(irGet(resultVar.symbol), irNull()), + irReturn(irGet(resultVar.symbol)), + throwUninitializedPropertyAccessException(backingFieldSymbol) + ) + } + } + } + }) + } + + private fun IrBuilderWithScope.throwUninitializedPropertyAccessException(backingFieldSymbol: IrSymbol) = + irCall(throwErrorFunction).apply { + if (generateParameterNameInAssertion) { + putValueArgument( + 0, + IrConstImpl.string( + startOffset, + endOffset, + context.builtIns.stringType, + backingFieldSymbol.descriptor.name.asString() + ) + ) + } + } + + private val throwErrorFunction = context.ir.symbols.ThrowUninitializedPropertyAccessException + +} \ No newline at end of file diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index d62efacda65..cdd8d9b7104 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1036,6 +1036,28 @@ task lateinit_inBaseClass(type: RunKonanTest) { source = "codegen/lateinit/inBaseClass.kt" } +task lateinit_localInitialized(type: RunKonanTest) { + goldValue = "zzz\n" + source = "codegen/lateinit/localInitialized.kt" +} + +task lateinit_localNotInitialized(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. + goldValue = "OK\n" + source = "codegen/lateinit/localNotInitialized.kt" +} + +task lateinit_localCapturedInitialized(type: RunKonanTest) { + goldValue = "zzz\n" + source = "codegen/lateinit/localCapturedInitialized.kt" +} + +task lateinit_localCapturedNotInitialized(type: RunKonanTest) { + expectedFail = (project.testTarget == 'wasm32') // Uses exceptions. + goldValue = "OK\n" + source = "codegen/lateinit/localCapturedNotInitialized.kt" +} + task kclass0(type: RunKonanTest) { source = "codegen/kclass/kclass0.kt" } diff --git a/backend.native/tests/codegen/lateinit/localCapturedInitialized.kt b/backend.native/tests/codegen/lateinit/localCapturedInitialized.kt new file mode 100644 index 00000000000..51331291998 --- /dev/null +++ b/backend.native/tests/codegen/lateinit/localCapturedInitialized.kt @@ -0,0 +1,12 @@ +package codegen.lateinit.localCapturedInitialized + +import kotlin.test.* + +@Test fun runTest() { + lateinit var s: String + + fun foo() = s + + s = "zzz" + println(foo()) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/lateinit/localCapturedNotInitialized.kt b/backend.native/tests/codegen/lateinit/localCapturedNotInitialized.kt new file mode 100644 index 00000000000..80b2a6d2d5b --- /dev/null +++ b/backend.native/tests/codegen/lateinit/localCapturedNotInitialized.kt @@ -0,0 +1,18 @@ +package codegen.lateinit.localCapturedNotInitialized + +import kotlin.test.* + +@Test fun runTest() { + lateinit var s: String + + fun foo() = s + + try { + println(foo()) + } + catch (e: RuntimeException) { + println("OK") + return + } + println("Fail") +} \ No newline at end of file diff --git a/backend.native/tests/codegen/lateinit/localInitialized.kt b/backend.native/tests/codegen/lateinit/localInitialized.kt new file mode 100644 index 00000000000..e2aabe896b4 --- /dev/null +++ b/backend.native/tests/codegen/lateinit/localInitialized.kt @@ -0,0 +1,9 @@ +package codegen.lateinit.localInitialized + +import kotlin.test.* + +@Test fun runTest() { + lateinit var s: String + s = "zzz" + println(s) +} \ No newline at end of file diff --git a/backend.native/tests/codegen/lateinit/localNotInitialized.kt b/backend.native/tests/codegen/lateinit/localNotInitialized.kt new file mode 100644 index 00000000000..a5efa2f5814 --- /dev/null +++ b/backend.native/tests/codegen/lateinit/localNotInitialized.kt @@ -0,0 +1,16 @@ +package codegen.lateinit.localNotInitialized + +import kotlin.test.* + +@Test fun runTest() { + lateinit var s: String + + try { + println(s) + } + catch (e: RuntimeException) { + println("OK") + return + } + println("Fail") +} \ No newline at end of file