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:
@@ -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
|
||||
)
|
||||
|
||||
+2
-1
@@ -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)
|
||||
|
||||
|
||||
+4
-1
@@ -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 {
|
||||
|
||||
+19
-20
@@ -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
|
||||
|
||||
+9
-2
@@ -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
|
||||
);
|
||||
}
|
||||
|
||||
+11
-2
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+8
-1
@@ -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")
|
||||
)
|
||||
|
||||
|
||||
+9
-2
@@ -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) }
|
||||
|
||||
Reference in New Issue
Block a user