From a89f9785976f5583a38edcced1c2ac528799fcd5 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 5 Oct 2017 16:29:14 +0300 Subject: [PATCH] Provide configurable constructor call normalization Three modes: - 'disable' (default): normalize constructor calls in coroutines only (required because uninitialized objects can't be stored in fields), don't insert additional code for forced class initialization; - 'enable': normalize constructor calls, don't insert additional code for forced class initialization; - 'preserve-class-initialization': normalize constructor calls, insert additional code for forced class initialization. --- .../kotlin/codegen/FunctionCodegen.java | 3 +- .../codegen/coroutines/CoroutineCodegen.kt | 3 ++ .../CoroutineTransformerMethodVisitor.kt | 3 +- .../SuspendFunctionGenerationStrategy.kt | 5 ++- .../coroutines/processUninitializedStores.kt | 39 +++++++++--------- .../OptimizationClassBuilder.java | 11 ++++- .../OptimizationClassBuilderFactory.java | 13 +++++- .../optimization/OptimizationMethodVisitor.kt | 9 +++- .../UninitializedStoresMethodTransformer.kt | 11 ++++- .../kotlin/codegen/state/GenerationState.kt | 7 +++- .../arguments/K2JVMCompilerArguments.kt | 11 +++++ .../jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt | 10 +++++ .../kotlin/config/JVMConfigurationKeys.java | 2 + .../JVMConstructorCallNormalizationMode.kt | 36 ++++++++++++++++ compiler/testData/cli/jvm/extraHelp.out | 2 + ...onstructorCallWithDisabledNormalization.kt | 41 +++++++++++++++++++ ...ConstructorCallWithEnabledNormalization.kt | 41 +++++++++++++++++++ ...nConstructorCallWithStrictNormalization.kt | 41 +++++++++++++++++++ ...ddleOfObjectConstructionEvaluationOrder.kt | 2 +- .../kotlin/codegen/CodegenTestCase.java | 12 +++++- .../ir/IrBlackBoxCodegenTestGenerated.java | 18 ++++++++ .../codegen/BlackBoxCodegenTestGenerated.java | 18 ++++++++ .../LightAnalysisModeTestGenerated.java | 18 ++++++++ 23 files changed, 323 insertions(+), 33 deletions(-) create mode 100644 compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConstructorCallNormalizationMode.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt create mode 100644 compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 23044ae8192..dd68dfd1994 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -137,7 +137,8 @@ public class FunctionCodegen { state, CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(functionDescriptor), function, - v.getThisName() + v.getThisName(), + state.getConstructorCallNormalizationMode() ); } else { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt index c4e227f4b11..e06cabb4bcd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt @@ -145,6 +145,8 @@ class CoroutineCodegenForLambda private constructor( ) { private val builtIns = funDescriptor.builtIns + private val constructorCallNormalizationMode = outerExpressionCodegen.state.constructorCallNormalizationMode + private lateinit var constructorToUseFromInvoke: Method private val createCoroutineDescriptor = @@ -313,6 +315,7 @@ class CoroutineCodegenForLambda private constructor( mv, access, name, desc, null, null, obtainClassBuilderForCoroutineState = { v }, element = element, + shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization, containingClassInternalName = v.thisName, isForNamedFunction = false ) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 6d9f6fab627..b3f469ec073 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -54,6 +54,7 @@ class CoroutineTransformerMethodVisitor( private val containingClassInternalName: String, obtainClassBuilderForCoroutineState: () -> ClassBuilder, private val isForNamedFunction: Boolean, + private val shouldPreserveClassInitialization: Boolean, private val element: KtElement, // It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls private val needDispatchReceiver: Boolean = false, @@ -99,7 +100,7 @@ class CoroutineTransformerMethodVisitor( // If we don't do this, then relevant frames will not be analyzed, that is unexpected from point of view of next steps (e.g. variable spilling) removeUnreachableSuspensionPointsAndExitPoints(methodNode, suspensionPoints) - processUninitializedStores(methodNode) + UninitializedStoresProcessor(methodNode, shouldPreserveClassInitialization).run() spillVariables(suspensionPoints, methodNode) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt index 6cad1404b95..6e93443022d 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt @@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.ExpressionCodegen import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.psi.KtFunction @@ -34,7 +35,8 @@ class SuspendFunctionGenerationStrategy( state: GenerationState, private val originalSuspendDescriptor: FunctionDescriptor, private val declaration: KtFunction, - private val containingClassInternalName: String + private val containingClassInternalName: String, + private val constructorCallNormalizationMode: JVMConstructorCallNormalizationMode ) : FunctionGenerationStrategy.CodegenBased(state) { private lateinit var transformer: CoroutineTransformerMethodVisitor @@ -59,6 +61,7 @@ class SuspendFunctionGenerationStrategy( mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState, isForNamedFunction = true, element = declaration, + shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization, needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null, internalNameForDispatchReceiver = containingClassInternalNameOrNull() ).also { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt index 5f9b6284cb4..45f27bef086 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/processUninitializedStores.kt @@ -71,11 +71,10 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter * - generate NEW/DUP * - restore constructor arguments */ -internal fun processUninitializedStores(methodNode: MethodNode) { - UninitializedStoresProcessor(methodNode).run() -} - -class UninitializedStoresProcessor(private val methodNode: MethodNode) { +class UninitializedStoresProcessor( + private val methodNode: MethodNode, + private val shouldPreserveClassInitialization: Boolean +) { // method is "special", because it will invoke from this class or from a base class for #0 // // method is "special", because for singleton objects is generated as: @@ -108,21 +107,21 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) { methodNode.instructions.run { removeAll(removableUsages) - // Replace 'NEW C' instruction with "manual" initialization of class 'C': - // LDC [typeName for C] - // INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class; - // POP - val typeNameForClass = newInsn.desc.replace('/', '.') - insertBefore(newInsn, LdcInsnNode(typeNameForClass)) - insertBefore( - newInsn, - MethodInsnNode( - Opcodes.INVOKESTATIC, - "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", - false - ) - ) - set(newInsn, InsnNode(Opcodes.POP)) + if (shouldPreserveClassInitialization) { + // Replace 'NEW C' instruction with "manual" initialization of class 'C': + // LDC [typeName for C] + // INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class; + // POP + val typeNameForClass = newInsn.desc.replace('/', '.') + insertBefore(newInsn, LdcInsnNode(typeNameForClass)) + insertBefore(newInsn, MethodInsnNode( + Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false + )) + set(newInsn, InsnNode(Opcodes.POP)) + } + else { + remove(newInsn) + } } val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java index ac1ef64e6f9..641b695dab0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilder.java @@ -20,16 +20,23 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.codegen.ClassBuilder; import org.jetbrains.kotlin.codegen.DelegatingClassBuilder; +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.org.objectweb.asm.MethodVisitor; public class OptimizationClassBuilder extends DelegatingClassBuilder { private final ClassBuilder delegate; private final boolean disableOptimization; + private final JVMConstructorCallNormalizationMode constructorCallNormalizationMode; - public OptimizationClassBuilder(@NotNull ClassBuilder delegate, boolean disableOptimization) { + public OptimizationClassBuilder( + @NotNull ClassBuilder delegate, + boolean disableOptimization, + JVMConstructorCallNormalizationMode constructorCallNormalizationMode + ) { this.delegate = delegate; this.disableOptimization = disableOptimization; + this.constructorCallNormalizationMode = constructorCallNormalizationMode; } @NotNull @@ -50,7 +57,7 @@ public class OptimizationClassBuilder extends DelegatingClassBuilder { ) { return new OptimizationMethodVisitor( super.newMethod(origin, access, name, desc, signature, exceptions), - disableOptimization, + disableOptimization, constructorCallNormalizationMode, access, name, desc, signature, exceptions ); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java index 812944c12a7..f0ed86f9a1f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationClassBuilderFactory.java @@ -19,19 +19,28 @@ package org.jetbrains.kotlin.codegen.optimization; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.codegen.ClassBuilderFactory; import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory; +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; public class OptimizationClassBuilderFactory extends DelegatingClassBuilderFactory { private final boolean disableOptimization; + private final JVMConstructorCallNormalizationMode constructorCallNormalizationMode; - public OptimizationClassBuilderFactory(ClassBuilderFactory delegate, boolean disableOptimization) { + public OptimizationClassBuilderFactory( + ClassBuilderFactory delegate, + boolean disableOptimization, + JVMConstructorCallNormalizationMode constructorCallNormalizationMode + ) { super(delegate); this.disableOptimization = disableOptimization; + this.constructorCallNormalizationMode = constructorCallNormalizationMode; } @NotNull @Override public OptimizationClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) { - return new OptimizationClassBuilder(getDelegate().newClassBuilder(origin), disableOptimization); + return new OptimizationClassBuilder( + getDelegate().newClassBuilder(origin), disableOptimization, constructorCallNormalizationMode + ); } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index 04663015d31..5e7a5c91181 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -23,23 +23,31 @@ import org.jetbrains.kotlin.codegen.optimization.boxing.StackPeepholeOptimizatio import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.tree.MethodNode class OptimizationMethodVisitor( delegate: MethodVisitor, private val disableOptimization: Boolean, + private val constructorCallNormalizationMode: JVMConstructorCallNormalizationMode, access: Int, name: String, desc: String, signature: String?, exceptions: Array? ) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { + private val constructorCallNormalizationTransformer = + UninitializedStoresMethodTransformer(constructorCallNormalizationMode) + override fun performTransformations(methodNode: MethodNode) { normalizationMethodTransformer.transform("fake", methodNode) + constructorCallNormalizationTransformer.transform("fake", methodNode) + if (canBeOptimized(methodNode) && !disableOptimization) { optimizationTransformer.transform("fake", methodNode) } + methodNode.prepareForEmitting() } @@ -48,7 +56,6 @@ class OptimizationMethodVisitor( val normalizationMethodTransformer = CompositeMethodTransformer( FixStackWithLabelNormalizationMethodTransformer(), - UninitializedStoresMethodTransformer(), MethodVerifier("AFTER mandatory stack transformations") ) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt index 6f81188b8ec..663fb404da4 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/UninitializedStoresMethodTransformer.kt @@ -18,10 +18,17 @@ package org.jetbrains.kotlin.codegen.optimization import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode import org.jetbrains.org.objectweb.asm.tree.MethodNode -class UninitializedStoresMethodTransformer : MethodTransformer() { +class UninitializedStoresMethodTransformer( + private val mode: JVMConstructorCallNormalizationMode +) : MethodTransformer() { + override fun transform(internalClassName: String, methodNode: MethodNode) { - UninitializedStoresProcessor(methodNode).run() + if (mode.isEnabled) { + UninitializedStoresProcessor(methodNode, mode.shouldPreserveClassInitialization).run() + } } + } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 9da8888b465..5af902babf5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -181,10 +181,15 @@ class GenerationState @JvmOverloads constructor( val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) + val constructorCallNormalizationMode = configuration.get(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE, + JVMConstructorCallNormalizationMode.DEFAULT) + init { + val disableOptimization = configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false) + this.interceptedBuilderFactory = builderFactory .wrapWith( - { OptimizationClassBuilderFactory(it, configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)) }, + { OptimizationClassBuilderFactory(it, disableOptimization, constructorCallNormalizationMode) }, { BuilderFactoryForDuplicateSignatureDiagnostics( it, this.bindingContext, diagnostics, this.moduleName, shouldGenerate = { !shouldOnlyCollectSignatures(it) } diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index 4a291c984ce..6ccd752ebd5 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cli.common.arguments import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.config.AnalysisFlag +import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.utils.Jsr305State import org.jetbrains.kotlin.utils.ReportLevel @@ -108,6 +109,16 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xno-optimize", description = "Disable optimizations") var noOptimize: Boolean by FreezableVar(false) + @Argument( + value = "-Xnormalize-constructor-calls", + valueDescription = "{disable|enable|preserve-class-initialization}", + description = "Normalize constructor calls " + + "(disable: don't normalize; enable: normalize; " + + "preserve-class-initialization: normalize preserving class initialization order), " + + "default is disable" + ) + var constructorCallNormalizationMode: String? by FreezableVar(JVMConstructorCallNormalizationMode.DEFAULT.description) + @Argument(value = "-Xreport-perf", description = "Report detailed performance statistics") var reportPerf: Boolean by FreezableVar(false) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index 684782c5f82..d63353c33da 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -349,6 +349,16 @@ class K2JVMCompiler : CLICompiler() { configuration.put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions) configuration.put(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL, arguments.noExceptionOnExplicitEqualsForBoxedNull); configuration.put(JVMConfigurationKeys.DISABLE_OPTIMIZATION, arguments.noOptimize) + + val constructorCallNormalizationMode = JVMConstructorCallNormalizationMode.fromStringOrNull(arguments.constructorCallNormalizationMode) + if (constructorCallNormalizationMode == null) { + configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + .report(ERROR, "Unknown constructor call normalization mode: ${arguments.constructorCallNormalizationMode}, " + + "supported modes: ${JVMConstructorCallNormalizationMode.values().map { it.description }}") + } + configuration.put(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE, + constructorCallNormalizationMode ?: JVMConstructorCallNormalizationMode.DEFAULT) + configuration.put(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, arguments.inheritMultifileParts) configuration.put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, arguments.skipRuntimeVersionCheck) configuration.put(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING, !arguments.useOldClassFilesReading) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 614762c1e42..a059ed95b7b 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -57,6 +57,8 @@ public class JVMConfigurationKeys { CompilerConfigurationKey.create("disable not-null call receiver assertions"); public static final CompilerConfigurationKey DISABLE_PARAM_ASSERTIONS = CompilerConfigurationKey.create("disable not-null parameter assertions"); + public static final CompilerConfigurationKey CONSTRUCTOR_CALL_NORMALIZATION_MODE = + CompilerConfigurationKey.create("constructor call normalization mode"); public static final CompilerConfigurationKey NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL = CompilerConfigurationKey.create("do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type"); public static final CompilerConfigurationKey DISABLE_OPTIMIZATION = diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConstructorCallNormalizationMode.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConstructorCallNormalizationMode.kt new file mode 100644 index 00000000000..cabb0f4bce6 --- /dev/null +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConstructorCallNormalizationMode.kt @@ -0,0 +1,36 @@ +/* + * 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.config + +enum class JVMConstructorCallNormalizationMode( + val description: String, + val isEnabled: Boolean, + val shouldPreserveClassInitialization: Boolean +) { + DISABLE("disable", false, false), + ENABLE("enable", true, false), + PRESERVE_CLASS_INITIALIZATION("preserve-class-initialization", true, true) + ; + + companion object { + @JvmField + val DEFAULT = DISABLE + + @JvmStatic + fun fromStringOrNull(string: String?) = values().find { it.description == string } + } +} \ No newline at end of file diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index e2842d9644a..5266ca72e19 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -5,6 +5,8 @@ where advanced options include: or all modules on the module path if is ALL-MODULE-PATH -Xbuild-file= Path to the .xml build file to compile -Xcompile-java Reuse javac analysis and compile Java source files + -Xnormalize-constructor-calls={disable|enable|preserve-class-initialization} + Normalize constructor calls (disable: don't normalize; enable: normalize; preserve-class-initialization: normalize preserving class initialization order), default is disable -Xdump-declarations-to= Path to JSON file to dump Java to Kotlin declaration mappings -Xmultifile-parts-inherit Compile multifile classes as a hierarchy of parts and facade -Xmodule-path= Paths where to find Java 9+ modules diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt new file mode 100644 index 00000000000..79020cc9986 --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: CONSTRUCTOR_CALL_NORMALIZATION_MODE=disable +// FILE: test.kt +fun box(): String { + Foo( + logged("i", 1.let { it }), + logged("j", + Foo( + logged("k", 2.let { it }), + null + ) + ) + ) + + val result = log.toString() + if (result != "ikj") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Foo?) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt new file mode 100644 index 00000000000..64c7310ef57 --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: CONSTRUCTOR_CALL_NORMALIZATION_MODE=enable +// FILE: test.kt +fun box(): String { + Foo( + logged("i", 1.let { it }), + logged("j", + Foo( + logged("k", 2.let { it }), + null + ) + ) + ) + + val result = log.toString() + if (result != "ikj") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Foo?) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt new file mode 100644 index 00000000000..65ef307f5b8 --- /dev/null +++ b/compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt @@ -0,0 +1,41 @@ +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// KOTLIN_CONFIGURATION_FLAGS: CONSTRUCTOR_CALL_NORMALIZATION_MODE=preserve-class-initialization +// FILE: test.kt +fun box(): String { + Foo( + logged("i", 1.let { it }), + logged("j", + Foo( + logged("k", 2.let { it }), + null + ) + ) + ) + + val result = log.toString() + if (result != "ikj") return "Fail: '$result'" + + return "OK" +} + +// FILE: util.kt +val log = StringBuilder() + +fun logged(msg: String, value: T): T { + log.append(msg) + return value +} + +// FILE: Foo.kt +class Foo(i: Int, j: Foo?) { + init { + log.append("") + } + + companion object { + init { + log.append("") + } + } +} diff --git a/compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt b/compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt index 61c50d8d343..151c0e37509 100644 --- a/compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt +++ b/compiler/testData/codegen/box/coroutines/suspendInTheMiddleOfObjectConstructionEvaluationOrder.kt @@ -49,7 +49,7 @@ fun box(): String { } } - if (logger.toString() != "A.;args;A.;") { + if (logger.toString() != "args;A.;A.;") { return "Fail: '$logger'" } diff --git a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java index 9faf31f95f2..deafe4c28bb 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests-common/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -36,7 +36,6 @@ import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys; import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; -import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; @@ -200,6 +199,8 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { private static final List> FLAG_CLASSES = ImmutableList.of(CLIConfigurationKeys.class, JVMConfigurationKeys.class); private static final Pattern BOOLEAN_FLAG_PATTERN = Pattern.compile("([+-])(([a-zA-Z_0-9]*)\\.)?([a-zA-Z_0-9]*)"); + private static final Pattern CONSTRUCTOR_CALL_NORMALIZATION_MODE_FLAG_PATTERN = Pattern.compile( + "CONSTRUCTOR_CALL_NORMALIZATION_MODE=([a-zA-Z_0-9]*)"); private static void updateConfigurationWithFlags(@NotNull CompilerConfiguration configuration, @NotNull List flags) { for (String flag : flags) { @@ -210,6 +211,15 @@ public abstract class CodegenTestCase extends KtUsefulTestCase { String flagName = m.group(4); tryApplyBooleanFlag(configuration, flag, flagEnabled, flagNamespace, flagName); + continue; + } + + m = CONSTRUCTOR_CALL_NORMALIZATION_MODE_FLAG_PATTERN.matcher(flag); + if (m.matches()) { + String flagValueString = m.group(1); + JVMConstructorCallNormalizationMode mode = JVMConstructorCallNormalizationMode.fromStringOrNull(flagValueString); + assert mode != null : "Wrong CONSTRUCTOR_CALL_NORMALIZATION_MODE value: " + flagValueString; + configuration.put(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE, mode); } } } diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 9e6bbd64845..47d623809e6 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -4307,6 +4307,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("inlineFunInConstructorCallWithDisabledNormalization.kt") + public void testInlineFunInConstructorCallWithDisabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithEnabledNormalization.kt") + public void testInlineFunInConstructorCallWithEnabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithStrictNormalization.kt") + public void testInlineFunInConstructorCallWithStrictNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") public void testInlineFunInInnerClassConstructorCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index dc57a24c912..63f0e74f49e 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4307,6 +4307,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("inlineFunInConstructorCallWithDisabledNormalization.kt") + public void testInlineFunInConstructorCallWithDisabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithEnabledNormalization.kt") + public void testInlineFunInConstructorCallWithEnabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithStrictNormalization.kt") + public void testInlineFunInConstructorCallWithStrictNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") public void testInlineFunInInnerClassConstructorCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index a87331c0240..2620fb623ba 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -4307,6 +4307,24 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("inlineFunInConstructorCallWithDisabledNormalization.kt") + public void testInlineFunInConstructorCallWithDisabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithDisabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithEnabledNormalization.kt") + public void testInlineFunInConstructorCallWithEnabledNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithEnabledNormalization.kt"); + doTest(fileName); + } + + @TestMetadata("inlineFunInConstructorCallWithStrictNormalization.kt") + public void testInlineFunInConstructorCallWithStrictNormalization() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInConstructorCallWithStrictNormalization.kt"); + doTest(fileName); + } + @TestMetadata("inlineFunInInnerClassConstructorCall.kt") public void testInlineFunInInnerClassConstructorCall() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt");