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,
CoroutineCodegenUtilKt.<FunctionDescriptor>unwrapInitialDescriptorForSuspendFunction(functionDescriptor),
function,
v.getThisName()
v.getThisName(),
state.getConstructorCallNormalizationMode()
);
}
else {
@@ -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
)
@@ -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)
@@ -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 {
@@ -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
) {
// <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:
@@ -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
@@ -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
);
}
@@ -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
);
}
}
@@ -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<String>?
) : 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")
)
@@ -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()
}
}
}
@@ -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) }