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.
This commit is contained in:
Dmitry Petrov
2017-10-05 16:29:14 +03:00
parent e71090ae4c
commit a89f978597
23 changed files with 323 additions and 33 deletions
@@ -137,7 +137,8 @@ public class FunctionCodegen {
state, state,
CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor), CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor),
function, function,
v.getThisName() v.getThisName(),
state.getConstructorCallNormalizationMode()
); );
} }
else { else {
@@ -145,6 +145,8 @@ class CoroutineCodegenForLambda private constructor(
) { ) {
private val builtIns = funDescriptor.builtIns private val builtIns = funDescriptor.builtIns
private val constructorCallNormalizationMode = outerExpressionCodegen.state.constructorCallNormalizationMode
private lateinit var constructorToUseFromInvoke: Method private lateinit var constructorToUseFromInvoke: Method
private val createCoroutineDescriptor = private val createCoroutineDescriptor =
@@ -313,6 +315,7 @@ class CoroutineCodegenForLambda private constructor(
mv, access, name, desc, null, null, mv, access, name, desc, null, null,
obtainClassBuilderForCoroutineState = { v }, obtainClassBuilderForCoroutineState = { v },
element = element, element = element,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
containingClassInternalName = v.thisName, containingClassInternalName = v.thisName,
isForNamedFunction = false isForNamedFunction = false
) )
@@ -54,6 +54,7 @@ class CoroutineTransformerMethodVisitor(
private val containingClassInternalName: String, private val containingClassInternalName: String,
obtainClassBuilderForCoroutineState: () -> ClassBuilder, obtainClassBuilderForCoroutineState: () -> ClassBuilder,
private val isForNamedFunction: Boolean, private val isForNamedFunction: Boolean,
private val shouldPreserveClassInitialization: Boolean,
private val element: KtElement, private val element: KtElement,
// It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls // It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls
private val needDispatchReceiver: Boolean = false, 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) // 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) removeUnreachableSuspensionPointsAndExitPoints(methodNode, suspensionPoints)
processUninitializedStores(methodNode) UninitializedStoresProcessor(methodNode, shouldPreserveClassInitialization).run()
spillVariables(suspensionPoints, methodNode) spillVariables(suspensionPoints, methodNode)
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.ExpressionCodegen
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy
import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.KtFunction
@@ -34,7 +35,8 @@ class SuspendFunctionGenerationStrategy(
state: GenerationState, state: GenerationState,
private val originalSuspendDescriptor: FunctionDescriptor, private val originalSuspendDescriptor: FunctionDescriptor,
private val declaration: KtFunction, private val declaration: KtFunction,
private val containingClassInternalName: String private val containingClassInternalName: String,
private val constructorCallNormalizationMode: JVMConstructorCallNormalizationMode
) : FunctionGenerationStrategy.CodegenBased(state) { ) : FunctionGenerationStrategy.CodegenBased(state) {
private lateinit var transformer: CoroutineTransformerMethodVisitor private lateinit var transformer: CoroutineTransformerMethodVisitor
@@ -59,6 +61,7 @@ class SuspendFunctionGenerationStrategy(
mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState, mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState,
isForNamedFunction = true, isForNamedFunction = true,
element = declaration, element = declaration,
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null, needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
internalNameForDispatchReceiver = containingClassInternalNameOrNull() internalNameForDispatchReceiver = containingClassInternalNameOrNull()
).also { ).also {
@@ -71,11 +71,10 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.Interpreter
* - generate NEW/DUP * - generate NEW/DUP
* - restore constructor arguments * - restore constructor arguments
*/ */
internal fun processUninitializedStores(methodNode: MethodNode) { class UninitializedStoresProcessor(
UninitializedStoresProcessor(methodNode).run() private val methodNode: MethodNode,
} private val shouldPreserveClassInitialization: Boolean
) {
class UninitializedStoresProcessor(private val methodNode: MethodNode) {
// <init> method is "special", because it will invoke <init> from this class or from a base class for #0 // <init> method is "special", because it will invoke <init> from this class or from a base class for #0
// //
// <clinit> method is "special", because <clinit> for singleton objects is generated as: // <clinit> method is "special", because <clinit> for singleton objects is generated as:
@@ -108,21 +107,21 @@ class UninitializedStoresProcessor(private val methodNode: MethodNode) {
methodNode.instructions.run { methodNode.instructions.run {
removeAll(removableUsages) removeAll(removableUsages)
// Replace 'NEW C' instruction with "manual" initialization of class 'C': if (shouldPreserveClassInitialization) {
// LDC [typeName for C] // Replace 'NEW C' instruction with "manual" initialization of class 'C':
// INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class; // LDC [typeName for C]
// POP // INVOKESTATIC java/lang/Class.forName (Ljava/lang/String;)Ljava/lang/Class;
val typeNameForClass = newInsn.desc.replace('/', '.') // POP
insertBefore(newInsn, LdcInsnNode(typeNameForClass)) val typeNameForClass = newInsn.desc.replace('/', '.')
insertBefore( insertBefore(newInsn, LdcInsnNode(typeNameForClass))
newInsn, insertBefore(newInsn, MethodInsnNode(
MethodInsnNode( Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false
Opcodes.INVOKESTATIC, ))
"java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", set(newInsn, InsnNode(Opcodes.POP))
false }
) else {
) remove(newInsn)
set(newInsn, InsnNode(Opcodes.POP)) }
} }
val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size val indexOfConstructorArgumentFromTopOfStack = Type.getArgumentTypes((insn as MethodInsnNode).desc).size
@@ -20,16 +20,23 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.codegen.ClassBuilder; import org.jetbrains.kotlin.codegen.ClassBuilder;
import org.jetbrains.kotlin.codegen.DelegatingClassBuilder; import org.jetbrains.kotlin.codegen.DelegatingClassBuilder;
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
import org.jetbrains.org.objectweb.asm.MethodVisitor; import org.jetbrains.org.objectweb.asm.MethodVisitor;
public class OptimizationClassBuilder extends DelegatingClassBuilder { public class OptimizationClassBuilder extends DelegatingClassBuilder {
private final ClassBuilder delegate; private final ClassBuilder delegate;
private final boolean disableOptimization; 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.delegate = delegate;
this.disableOptimization = disableOptimization; this.disableOptimization = disableOptimization;
this.constructorCallNormalizationMode = constructorCallNormalizationMode;
} }
@NotNull @NotNull
@@ -50,7 +57,7 @@ public class OptimizationClassBuilder extends DelegatingClassBuilder {
) { ) {
return new OptimizationMethodVisitor( return new OptimizationMethodVisitor(
super.newMethod(origin, access, name, desc, signature, exceptions), super.newMethod(origin, access, name, desc, signature, exceptions),
disableOptimization, disableOptimization, constructorCallNormalizationMode,
access, name, desc, signature, exceptions access, name, desc, signature, exceptions
); );
} }
@@ -19,19 +19,28 @@ package org.jetbrains.kotlin.codegen.optimization;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.ClassBuilderFactory; import org.jetbrains.kotlin.codegen.ClassBuilderFactory;
import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory; import org.jetbrains.kotlin.codegen.DelegatingClassBuilderFactory;
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode;
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin;
public class OptimizationClassBuilderFactory extends DelegatingClassBuilderFactory { public class OptimizationClassBuilderFactory extends DelegatingClassBuilderFactory {
private final boolean disableOptimization; private final boolean disableOptimization;
private final JVMConstructorCallNormalizationMode constructorCallNormalizationMode;
public OptimizationClassBuilderFactory(ClassBuilderFactory delegate, boolean disableOptimization) { public OptimizationClassBuilderFactory(
ClassBuilderFactory delegate,
boolean disableOptimization,
JVMConstructorCallNormalizationMode constructorCallNormalizationMode
) {
super(delegate); super(delegate);
this.disableOptimization = disableOptimization; this.disableOptimization = disableOptimization;
this.constructorCallNormalizationMode = constructorCallNormalizationMode;
} }
@NotNull @NotNull
@Override @Override
public OptimizationClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) { public OptimizationClassBuilder newClassBuilder(@NotNull JvmDeclarationOrigin origin) {
return new OptimizationClassBuilder(getDelegate().newClassBuilder(origin), disableOptimization); return new OptimizationClassBuilder(
getDelegate().newClassBuilder(origin), disableOptimization, constructorCallNormalizationMode
);
} }
} }
@@ -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.common.prepareForEmitting
import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer
import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer 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.MethodVisitor
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
class OptimizationMethodVisitor( class OptimizationMethodVisitor(
delegate: MethodVisitor, delegate: MethodVisitor,
private val disableOptimization: Boolean, private val disableOptimization: Boolean,
private val constructorCallNormalizationMode: JVMConstructorCallNormalizationMode,
access: Int, access: Int,
name: String, name: String,
desc: String, desc: String,
signature: String?, signature: String?,
exceptions: Array<String>? exceptions: Array<String>?
) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { ) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) {
private val constructorCallNormalizationTransformer =
UninitializedStoresMethodTransformer(constructorCallNormalizationMode)
override fun performTransformations(methodNode: MethodNode) { override fun performTransformations(methodNode: MethodNode) {
normalizationMethodTransformer.transform("fake", methodNode) normalizationMethodTransformer.transform("fake", methodNode)
constructorCallNormalizationTransformer.transform("fake", methodNode)
if (canBeOptimized(methodNode) && !disableOptimization) { if (canBeOptimized(methodNode) && !disableOptimization) {
optimizationTransformer.transform("fake", methodNode) optimizationTransformer.transform("fake", methodNode)
} }
methodNode.prepareForEmitting() methodNode.prepareForEmitting()
} }
@@ -48,7 +56,6 @@ class OptimizationMethodVisitor(
val normalizationMethodTransformer = CompositeMethodTransformer( val normalizationMethodTransformer = CompositeMethodTransformer(
FixStackWithLabelNormalizationMethodTransformer(), FixStackWithLabelNormalizationMethodTransformer(),
UninitializedStoresMethodTransformer(),
MethodVerifier("AFTER mandatory stack transformations") MethodVerifier("AFTER mandatory stack transformations")
) )
@@ -18,10 +18,17 @@ package org.jetbrains.kotlin.codegen.optimization
import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor import org.jetbrains.kotlin.codegen.coroutines.UninitializedStoresProcessor
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode
import org.jetbrains.org.objectweb.asm.tree.MethodNode 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) { override fun transform(internalClassName: String, methodNode: MethodNode) {
UninitializedStoresProcessor(methodNode).run() if (mode.isEnabled) {
UninitializedStoresProcessor(methodNode, mode.shouldPreserveClassInitialization).run()
}
} }
} }
@@ -181,10 +181,15 @@ class GenerationState @JvmOverloads constructor(
val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals) val shouldInlineConstVals = languageVersionSettings.supportsFeature(LanguageFeature.InlineConstVals)
val constructorCallNormalizationMode = configuration.get(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE,
JVMConstructorCallNormalizationMode.DEFAULT)
init { init {
val disableOptimization = configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)
this.interceptedBuilderFactory = builderFactory this.interceptedBuilderFactory = builderFactory
.wrapWith( .wrapWith(
{ OptimizationClassBuilderFactory(it, configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)) }, { OptimizationClassBuilderFactory(it, disableOptimization, constructorCallNormalizationMode) },
{ BuilderFactoryForDuplicateSignatureDiagnostics( { BuilderFactoryForDuplicateSignatureDiagnostics(
it, this.bindingContext, diagnostics, this.moduleName, it, this.bindingContext, diagnostics, this.moduleName,
shouldGenerate = { !shouldOnlyCollectSignatures(it) } shouldGenerate = { !shouldOnlyCollectSignatures(it) }
@@ -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.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.AnalysisFlag import org.jetbrains.kotlin.config.AnalysisFlag
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode
import org.jetbrains.kotlin.config.JvmTarget import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.utils.Jsr305State import org.jetbrains.kotlin.utils.Jsr305State
import org.jetbrains.kotlin.utils.ReportLevel import org.jetbrains.kotlin.utils.ReportLevel
@@ -108,6 +109,16 @@ class K2JVMCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xno-optimize", description = "Disable optimizations") @Argument(value = "-Xno-optimize", description = "Disable optimizations")
var noOptimize: Boolean by FreezableVar(false) 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") @Argument(value = "-Xreport-perf", description = "Report detailed performance statistics")
var reportPerf: Boolean by FreezableVar(false) var reportPerf: Boolean by FreezableVar(false)
@@ -349,6 +349,16 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
configuration.put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions) configuration.put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions)
configuration.put(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL, arguments.noExceptionOnExplicitEqualsForBoxedNull); configuration.put(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL, arguments.noExceptionOnExplicitEqualsForBoxedNull);
configuration.put(JVMConfigurationKeys.DISABLE_OPTIMIZATION, arguments.noOptimize) 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.INHERIT_MULTIFILE_PARTS, arguments.inheritMultifileParts)
configuration.put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, arguments.skipRuntimeVersionCheck) configuration.put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, arguments.skipRuntimeVersionCheck)
configuration.put(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING, !arguments.useOldClassFilesReading) configuration.put(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING, !arguments.useOldClassFilesReading)
@@ -57,6 +57,8 @@ public class JVMConfigurationKeys {
CompilerConfigurationKey.create("disable not-null call receiver assertions"); CompilerConfigurationKey.create("disable not-null call receiver assertions");
public static final CompilerConfigurationKey<Boolean> DISABLE_PARAM_ASSERTIONS = public static final CompilerConfigurationKey<Boolean> DISABLE_PARAM_ASSERTIONS =
CompilerConfigurationKey.create("disable not-null parameter assertions"); CompilerConfigurationKey.create("disable not-null parameter assertions");
public static final CompilerConfigurationKey<JVMConstructorCallNormalizationMode> CONSTRUCTOR_CALL_NORMALIZATION_MODE =
CompilerConfigurationKey.create("constructor call normalization mode");
public static final CompilerConfigurationKey<Boolean> NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL = public static final CompilerConfigurationKey<Boolean> 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"); CompilerConfigurationKey.create("do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type");
public static final CompilerConfigurationKey<Boolean> DISABLE_OPTIMIZATION = public static final CompilerConfigurationKey<Boolean> DISABLE_OPTIMIZATION =
@@ -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 }
}
}
+2
View File
@@ -5,6 +5,8 @@ where advanced options include:
or all modules on the module path if <module> is ALL-MODULE-PATH or all modules on the module path if <module> is ALL-MODULE-PATH
-Xbuild-file=<path> Path to the .xml build file to compile -Xbuild-file=<path> Path to the .xml build file to compile
-Xcompile-java Reuse javac analysis and compile Java source files -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> Path to JSON file to dump Java to Kotlin declaration mappings -Xdump-declarations-to=<path> Path to JSON file to dump Java to Kotlin declaration mappings
-Xmultifile-parts-inherit Compile multifile classes as a hierarchy of parts and facade -Xmultifile-parts-inherit Compile multifile classes as a hierarchy of parts and facade
-Xmodule-path=<path> Paths where to find Java 9+ modules -Xmodule-path=<path> Paths where to find Java 9+ modules
@@ -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 != "<clinit>ik<init>j<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Foo?) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -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 != "ik<clinit><init>j<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Foo?) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -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 != "<clinit>ik<init>j<init>") return "Fail: '$result'"
return "OK"
}
// FILE: util.kt
val log = StringBuilder()
fun <T> logged(msg: String, value: T): T {
log.append(msg)
return value
}
// FILE: Foo.kt
class Foo(i: Int, j: Foo?) {
init {
log.append("<init>")
}
companion object {
init {
log.append("<clinit>")
}
}
}
@@ -49,7 +49,7 @@ fun box(): String {
} }
} }
if (logger.toString() != "A.<clinit>;args;A.<init>;") { if (logger.toString() != "args;A.<clinit>;A.<init>;") {
return "Fail: '$logger'" return "Fail: '$logger'"
} }
@@ -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.common.output.outputUtils.OutputUtilsKt;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles; import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; 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.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.*; import org.jetbrains.kotlin.config.*;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil; import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
@@ -200,6 +199,8 @@ public abstract class CodegenTestCase extends KtUsefulTestCase {
private static final List<Class<?>> FLAG_CLASSES = ImmutableList.of(CLIConfigurationKeys.class, JVMConfigurationKeys.class); private static final List<Class<?>> 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 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<String> flags) { private static void updateConfigurationWithFlags(@NotNull CompilerConfiguration configuration, @NotNull List<String> flags) {
for (String flag : flags) { for (String flag : flags) {
@@ -210,6 +211,15 @@ public abstract class CodegenTestCase extends KtUsefulTestCase {
String flagName = m.group(4); String flagName = m.group(4);
tryApplyBooleanFlag(configuration, flag, flagEnabled, flagNamespace, flagName); 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);
} }
} }
} }
@@ -4307,6 +4307,24 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName); 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") @TestMetadata("inlineFunInInnerClassConstructorCall.kt")
public void testInlineFunInInnerClassConstructorCall() throws Exception { public void testInlineFunInInnerClassConstructorCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt");
@@ -4307,6 +4307,24 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName); 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") @TestMetadata("inlineFunInInnerClassConstructorCall.kt")
public void testInlineFunInInnerClassConstructorCall() throws Exception { public void testInlineFunInInnerClassConstructorCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt");
@@ -4307,6 +4307,24 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName); 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") @TestMetadata("inlineFunInInnerClassConstructorCall.kt")
public void testInlineFunInInnerClassConstructorCall() throws Exception { public void testInlineFunInInnerClassConstructorCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt"); String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/constructorCall/inlineFunInInnerClassConstructorCall.kt");