Support crossinline suspend lambda as parameter of inline function
Use fake continuation instead of ALOAD 0 while inlining Do not generate state machine for inner lambdas and inner objects, which capture crossinline suspend lambda. #KT-19159: Fixed
This commit is contained in:
@@ -2343,7 +2343,7 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
|
||||
@NotNull CallGenerator callGenerator,
|
||||
@NotNull ArgumentGenerator argumentGenerator
|
||||
) {
|
||||
boolean isSuspendNoInlineCall = CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall);
|
||||
boolean isSuspendNoInlineCall = CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall, this);
|
||||
boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor;
|
||||
if (!(callableMethod instanceof IntrinsicWithSpecialReceiver)) {
|
||||
putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspendNoInlineCall, isConstructor);
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.descriptors.*;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotated;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
|
||||
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
|
||||
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature;
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi;
|
||||
@@ -61,10 +62,7 @@ import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isNullableAny;
|
||||
import static org.jetbrains.kotlin.codegen.AsmUtil.*;
|
||||
@@ -642,6 +640,10 @@ public class FunctionCodegen {
|
||||
mv.visitLabel(methodEnd);
|
||||
|
||||
Type thisType = getThisTypeForFunction(functionDescriptor, context, typeMapper);
|
||||
|
||||
if (functionDescriptor instanceof AnonymousFunctionDescriptor && functionDescriptor.isSuspend()) {
|
||||
functionDescriptor = CoroutineCodegenUtilKt.getOrCreateJvmSuspendFunctionView(functionDescriptor, typeMapper.getBindingContext());
|
||||
}
|
||||
generateLocalVariableTable(
|
||||
mv, signature, functionDescriptor, thisType, methodBegin, methodEnd, context.getContextKind(), typeMapper,
|
||||
(functionFakeIndex >= 0 ? 1 : 0) + (lambdaFakeIndex >= 0 ? 1 : 0)
|
||||
|
||||
+58
-13
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.codegen.binding;
|
||||
@@ -25,6 +14,7 @@ import kotlin.collections.CollectionsKt;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.builtins.ReflectionTypes;
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor;
|
||||
import org.jetbrains.kotlin.cfg.WhenChecker;
|
||||
import org.jetbrains.kotlin.codegen.*;
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
|
||||
@@ -51,6 +41,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument;
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall;
|
||||
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
|
||||
import org.jetbrains.kotlin.resolve.constants.EnumValue;
|
||||
import org.jetbrains.kotlin.resolve.constants.NullValue;
|
||||
@@ -314,8 +305,25 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
nameStack.push(name);
|
||||
|
||||
if (CoroutineUtilKt.isSuspendLambda(functionDescriptor)) {
|
||||
SimpleFunctionDescriptor jvmSuspendFunctionView =
|
||||
CoroutineCodegenUtilKt.getOrCreateJvmSuspendFunctionView(
|
||||
(SimpleFunctionDescriptor) functionDescriptor
|
||||
);
|
||||
|
||||
bindingTrace.record(
|
||||
CodegenBinding.SUSPEND_FUNCTION_TO_JVM_VIEW,
|
||||
functionDescriptor,
|
||||
jvmSuspendFunctionView
|
||||
);
|
||||
closure.setSuspend(true);
|
||||
closure.setSuspendLambda();
|
||||
if (capturesCrossinlineSuspendLambda(functionLiteral)) {
|
||||
bindingTrace.record(
|
||||
CodegenBinding.CAPTURES_CROSSINLINE_SUSPEND_LAMBDA,
|
||||
functionDescriptor,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
super.visitLambdaExpression(lambdaExpression);
|
||||
@@ -323,6 +331,35 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
classStack.pop();
|
||||
}
|
||||
|
||||
// If inner lambda captures crossinline suspend lambda, it is unsafe to generate state machine for it.
|
||||
private boolean capturesCrossinlineSuspendLambda(@NotNull PsiElement psiNode) {
|
||||
PsiElement[] children = psiNode.getChildren();
|
||||
boolean res = false;
|
||||
for (PsiElement child : children) {
|
||||
if (child instanceof KtCallElement) {
|
||||
KtExpression callee = ((KtCallElement) child).getCalleeExpression();
|
||||
Call call = bindingContext.get(CALL, callee);
|
||||
ResolvedCall<?> resolvedCall = bindingContext.get(RESOLVED_CALL, call);
|
||||
if (resolvedCall instanceof VariableAsFunctionResolvedCall) {
|
||||
VariableAsFunctionResolvedCall variableAsFunction = (VariableAsFunctionResolvedCall) resolvedCall;
|
||||
VariableDescriptor variableDescriptor = variableAsFunction.getVariableCall().getResultingDescriptor();
|
||||
CallableDescriptor callableDescriptor = ((ResolvedCall) variableAsFunction).getResultingDescriptor();
|
||||
if (variableDescriptor instanceof ValueParameterDescriptor &&
|
||||
((ValueParameterDescriptor) variableDescriptor).isCrossinline() &&
|
||||
callableDescriptor instanceof FunctionDescriptor &&
|
||||
((FunctionDescriptor) callableDescriptor).isSuspend()) {
|
||||
FunctionDescriptor enclosingDescriptor =
|
||||
bindingContext.get(ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, resolvedCall.getCall());
|
||||
assert(enclosingDescriptor != null);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
res = res || capturesCrossinlineSuspendLambda(child);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitCallableReferenceExpression(@NotNull KtCallableReferenceExpression expression) {
|
||||
ResolvedCall<?> referencedFunction = CallUtilKt.getResolvedCall(expression.getCallableReference(), bindingContext);
|
||||
@@ -537,6 +574,14 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
|
||||
MutableClosure closure = recordClosure(classDescriptor, name);
|
||||
closure.setSuspend(true);
|
||||
|
||||
if (capturesCrossinlineSuspendLambda(function)) {
|
||||
bindingTrace.record(
|
||||
CodegenBinding.CAPTURES_CROSSINLINE_SUSPEND_LAMBDA,
|
||||
functionDescriptor,
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
super.visitNamedFunction(function);
|
||||
|
||||
if (nameForClassOrPackageMember != null) {
|
||||
|
||||
@@ -52,6 +52,9 @@ public class CodegenBinding {
|
||||
public static final WritableSlice<FunctionDescriptor, FunctionDescriptor> SUSPEND_FUNCTION_TO_JVM_VIEW =
|
||||
Slices.createSimpleSlice();
|
||||
|
||||
public static final WritableSlice<FunctionDescriptor, Boolean> CAPTURES_CROSSINLINE_SUSPEND_LAMBDA =
|
||||
Slices.createSimpleSlice();
|
||||
|
||||
public static final WritableSlice<ValueParameterDescriptor, ValueParameterDescriptor> PARAMETER_SYNONYM =
|
||||
Slices.createSimpleSlice();
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.codegen.context.PackageContext
|
||||
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
|
||||
import org.jetbrains.kotlin.codegen.inline.ReificationArgument
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.TypeIntrinsics
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
import org.jetbrains.kotlin.codegen.signature.JvmSignatureWriter
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
@@ -50,6 +51,11 @@ import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import org.jetbrains.org.objectweb.asm.commons.Method
|
||||
import org.jetbrains.org.objectweb.asm.util.Textifier
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
import java.io.StringWriter
|
||||
import java.io.PrintWriter
|
||||
import java.util.*
|
||||
|
||||
fun generateIsCheck(
|
||||
@@ -424,3 +430,13 @@ inline fun FrameMap.evaluateOnce(
|
||||
leaveTemp(asType)
|
||||
}
|
||||
}
|
||||
|
||||
// Handy debugging routine. Print all instructions from methodNode.
|
||||
fun MethodNode.textifyMethodNode(): String {
|
||||
val text = Textifier()
|
||||
val tmv = TraceMethodVisitor(text)
|
||||
this.instructions.asSequence().forEach { it.accept(tmv) }
|
||||
val sw = StringWriter()
|
||||
text.print(PrintWriter(sw))
|
||||
return "$sw"
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ class InlineLambdaContext(
|
||||
contextKind: OwnerKind,
|
||||
parentContext: CodegenContext<*>,
|
||||
closure: MutableClosure?,
|
||||
private val isCrossInline: Boolean,
|
||||
val isCrossInline: Boolean,
|
||||
private val isPropertyReference: Boolean
|
||||
) : MethodContext(functionDescriptor, contextKind, parentContext, closure, false) {
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import com.intellij.util.ArrayUtil
|
||||
import org.jetbrains.kotlin.backend.common.CodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.CAPTURES_CROSSINLINE_SUSPEND_LAMBDA
|
||||
import org.jetbrains.kotlin.codegen.context.ClosureContext
|
||||
import org.jetbrains.kotlin.codegen.context.MethodContext
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension
|
||||
@@ -127,7 +129,8 @@ class CoroutineCodegenForLambda private constructor(
|
||||
element: KtElement,
|
||||
private val closureContext: ClosureContext,
|
||||
classBuilder: ClassBuilder,
|
||||
private val originalSuspendFunctionDescriptor: FunctionDescriptor
|
||||
private val originalSuspendFunctionDescriptor: FunctionDescriptor,
|
||||
private val forInline: Boolean
|
||||
) : AbstractCoroutineCodegen(
|
||||
outerExpressionCodegen, element, closureContext, classBuilder,
|
||||
userDataForDoResume = mapOf(INITIAL_SUSPEND_DESCRIPTOR_FOR_DO_RESUME to originalSuspendFunctionDescriptor)
|
||||
@@ -300,13 +303,14 @@ class CoroutineCodegenForLambda private constructor(
|
||||
object : FunctionGenerationStrategy.FunctionDefault(state, element as KtDeclarationWithBody) {
|
||||
|
||||
override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor {
|
||||
if (forInline) return super.wrapMethodVisitor(mv, access, name, desc)
|
||||
return CoroutineTransformerMethodVisitor(
|
||||
mv, access, name, desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { v },
|
||||
element = element,
|
||||
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = v.thisName,
|
||||
isForNamedFunction = false
|
||||
mv, access, name, desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { v },
|
||||
lineNumber = CodegenUtil.getLineNumberForElement(element, false) ?: 0,
|
||||
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = v.thisName,
|
||||
isForNamedFunction = false
|
||||
)
|
||||
}
|
||||
|
||||
@@ -336,7 +340,9 @@ class CoroutineCodegenForLambda private constructor(
|
||||
originalSuspendLambdaDescriptor, expressionCodegen, expressionCodegen.state.typeMapper
|
||||
),
|
||||
classBuilder,
|
||||
originalSuspendLambdaDescriptor
|
||||
originalSuspendLambdaDescriptor,
|
||||
// Local suspend lambdas, which call crossinline suspend parameters of containing functions must be generated after inlining
|
||||
expressionCodegen.bindingContext[CAPTURES_CROSSINLINE_SUSPEND_LAMBDA, originalSuspendLambdaDescriptor] == true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+63
-39
@@ -17,7 +17,6 @@
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import com.intellij.util.containers.Stack
|
||||
import org.jetbrains.kotlin.backend.common.CodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
@@ -28,7 +27,6 @@ import org.jetbrains.kotlin.codegen.optimization.common.*
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.kotlin.utils.sure
|
||||
@@ -53,7 +51,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
obtainClassBuilderForCoroutineState: () -> ClassBuilder,
|
||||
private val isForNamedFunction: Boolean,
|
||||
private val shouldPreserveClassInitialization: Boolean,
|
||||
private val element: KtElement,
|
||||
private val lineNumber: Int,
|
||||
// It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls
|
||||
private val needDispatchReceiver: Boolean = false,
|
||||
// May differ from containingClassInternalName in case of DefaultImpls
|
||||
@@ -67,6 +65,8 @@ class CoroutineTransformerMethodVisitor(
|
||||
private var exceptionIndex = if (isForNamedFunction) -1 else 2
|
||||
|
||||
override fun performTransformations(methodNode: MethodNode) {
|
||||
removeFakeContinuationConstructorCall(methodNode)
|
||||
|
||||
val suspensionPoints = collectSuspensionPoints(methodNode)
|
||||
|
||||
// First instruction in the method node may change in case of named function
|
||||
@@ -78,11 +78,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
ReturnUnitMethodTransformer.transform(containingClassInternalName, methodNode)
|
||||
|
||||
if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) {
|
||||
continuationIndex =
|
||||
if (isStatic(methodNode.access))
|
||||
Type.getArgumentTypes(methodNode.desc).size - 1
|
||||
else
|
||||
Type.getArgumentTypes(methodNode.desc).size
|
||||
continuationIndex = getLastParameterIndex(methodNode.desc, methodNode.access)
|
||||
replaceFakeContinuationsWithRealOnes(methodNode, continuationIndex)
|
||||
|
||||
dropSuspensionMarkers(methodNode, suspensionPoints)
|
||||
@@ -125,7 +121,6 @@ class CoroutineTransformerMethodVisitor(
|
||||
val startLabel = LabelNode()
|
||||
val defaultLabel = LabelNode()
|
||||
val tableSwitchLabel = LabelNode()
|
||||
val lineNumber = CodegenUtil.getLineNumberForElement(element, false) ?: 0
|
||||
|
||||
// tableswitch(this.label)
|
||||
insertBefore(
|
||||
@@ -161,12 +156,15 @@ class CoroutineTransformerMethodVisitor(
|
||||
methodNode.removeEmptyCatchBlocks()
|
||||
}
|
||||
|
||||
private fun replaceFakeContinuationsWithRealOnes(methodNode: MethodNode, continuationIndex: Int) {
|
||||
val fakeContinuations = methodNode.instructions.asSequence().filter(::isFakeContinuationMarker)
|
||||
for (fakeContinuation in fakeContinuations) {
|
||||
methodNode.instructions.removeAll(listOf(fakeContinuation.previous.previous, fakeContinuation.previous))
|
||||
methodNode.instructions.set(fakeContinuation, VarInsnNode(Opcodes.ALOAD, continuationIndex))
|
||||
private fun removeFakeContinuationConstructorCall(methodNode: MethodNode) {
|
||||
val seq = methodNode.instructions.asSequence()
|
||||
val first = seq.firstOrNull(::isBeforeFakeContinuationConstructorCallMarker)?.previous ?: return
|
||||
val last = seq.firstOrNull(::isAfterFakeContinuationConstructorCallMarker).sure {
|
||||
"BeforeFakeContinuationConstructorCallMarker without AfterFakeContinuationConstructorCallMarker"
|
||||
}
|
||||
val toRemove = InsnSequence(first, last).toList()
|
||||
methodNode.instructions.removeAll(toRemove)
|
||||
methodNode.instructions.set(last, InsnNode(Opcodes.ACONST_NULL))
|
||||
}
|
||||
|
||||
private fun createInsnForReadingLabel() =
|
||||
@@ -285,30 +283,13 @@ class CoroutineTransformerMethodVisitor(
|
||||
|
||||
visitLabel(createStateInstance)
|
||||
|
||||
anew(objectTypeForState)
|
||||
dup()
|
||||
|
||||
val parameterTypesAndIndices =
|
||||
getParameterTypesIndicesForCoroutineConstructor(
|
||||
methodNode.desc,
|
||||
methodNode.access,
|
||||
needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
for ((type, index) in parameterTypesAndIndices) {
|
||||
load(index, type)
|
||||
}
|
||||
|
||||
invokespecial(
|
||||
classBuilderForCoroutineState.thisName,
|
||||
"<init>",
|
||||
Type.getMethodDescriptor(
|
||||
Type.VOID_TYPE,
|
||||
*getParameterTypesForCoroutineConstructor(
|
||||
methodNode.desc, needDispatchReceiver,
|
||||
internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
),
|
||||
false
|
||||
generateContinuationConstructorCall(
|
||||
objectTypeForState,
|
||||
methodNode,
|
||||
needDispatchReceiver,
|
||||
internalNameForDispatchReceiver,
|
||||
containingClassInternalName,
|
||||
classBuilderForCoroutineState
|
||||
)
|
||||
|
||||
visitVarInsn(Opcodes.ASTORE, continuationIndex)
|
||||
@@ -501,7 +482,7 @@ class CoroutineTransformerMethodVisitor(
|
||||
): LabelNode {
|
||||
val continuationLabel = LabelNode()
|
||||
val continuationLabelAfterLoadedResult = LabelNode()
|
||||
val suspendElementLineNumber = CodegenUtil.getLineNumberForElement(element, false) ?: 0
|
||||
val suspendElementLineNumber = lineNumber
|
||||
val nextLineNumberNode = suspension.suspensionCallEnd.findNextOrNull { it is LineNumberNode } as? LineNumberNode
|
||||
with(methodNode.instructions) {
|
||||
// Save state
|
||||
@@ -616,6 +597,41 @@ class CoroutineTransformerMethodVisitor(
|
||||
}
|
||||
}
|
||||
|
||||
internal fun InstructionAdapter.generateContinuationConstructorCall(
|
||||
objectTypeForState: Type?,
|
||||
methodNode: MethodNode,
|
||||
needDispatchReceiver: Boolean,
|
||||
internalNameForDispatchReceiver: String?,
|
||||
containingClassInternalName: String,
|
||||
classBuilderForCoroutineState: ClassBuilder
|
||||
) {
|
||||
anew(objectTypeForState)
|
||||
dup()
|
||||
|
||||
val parameterTypesAndIndices =
|
||||
getParameterTypesIndicesForCoroutineConstructor(
|
||||
methodNode.desc,
|
||||
methodNode.access,
|
||||
needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
for ((type, index) in parameterTypesAndIndices) {
|
||||
load(index, type)
|
||||
}
|
||||
|
||||
invokespecial(
|
||||
classBuilderForCoroutineState.thisName,
|
||||
"<init>",
|
||||
Type.getMethodDescriptor(
|
||||
Type.VOID_TYPE,
|
||||
*getParameterTypesForCoroutineConstructor(
|
||||
methodNode.desc, needDispatchReceiver,
|
||||
internalNameForDispatchReceiver ?: containingClassInternalName
|
||||
)
|
||||
),
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
private fun InstructionAdapter.generateResumeWithExceptionCheck(exceptionIndex: Int) {
|
||||
// Check if resumeWithException has been called
|
||||
load(exceptionIndex, AsmTypes.OBJECT_TYPE)
|
||||
@@ -803,3 +819,11 @@ private fun AbstractInsnNode?.isInvisibleInDebugVarInsn(methodNode: MethodNode):
|
||||
|
||||
private val SAFE_OPCODES =
|
||||
((Opcodes.DUP..Opcodes.DUP2_X2) + Opcodes.NOP + Opcodes.POP + Opcodes.POP2 + (Opcodes.IFEQ..Opcodes.GOTO)).toSet()
|
||||
|
||||
internal fun replaceFakeContinuationsWithRealOnes(methodNode: MethodNode, continuationIndex: Int) {
|
||||
val fakeContinuations = methodNode.instructions.asSequence().filter(::isFakeContinuationMarker).toList()
|
||||
for (fakeContinuation in fakeContinuations) {
|
||||
methodNode.instructions.removeAll(listOf(fakeContinuation.previous.previous, fakeContinuation.previous))
|
||||
methodNode.instructions.set(fakeContinuation, VarInsnNode(Opcodes.ALOAD, continuationIndex))
|
||||
}
|
||||
}
|
||||
+56
-10
@@ -16,9 +16,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.coroutines
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.CodegenUtil
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||
import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy
|
||||
import org.jetbrains.kotlin.codegen.TransformationMethodVisitor
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.inline.addFakeContinuationConstructorCallMarker
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.config.JVMConstructorCallNormalizationMode
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
@@ -30,6 +34,8 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||
|
||||
class SuspendFunctionGenerationStrategy(
|
||||
state: GenerationState,
|
||||
@@ -39,7 +45,6 @@ class SuspendFunctionGenerationStrategy(
|
||||
private val constructorCallNormalizationMode: JVMConstructorCallNormalizationMode
|
||||
) : FunctionGenerationStrategy.CodegenBased(state) {
|
||||
|
||||
private lateinit var transformer: CoroutineTransformerMethodVisitor
|
||||
private lateinit var codegen: ExpressionCodegen
|
||||
|
||||
private val classBuilderForCoroutineState by lazy {
|
||||
@@ -57,16 +62,22 @@ class SuspendFunctionGenerationStrategy(
|
||||
override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor {
|
||||
if (access and Opcodes.ACC_ABSTRACT != 0) return mv
|
||||
|
||||
return CoroutineTransformerMethodVisitor(
|
||||
mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState,
|
||||
isForNamedFunction = true,
|
||||
element = declaration,
|
||||
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
|
||||
internalNameForDispatchReceiver = containingClassInternalNameOrNull()
|
||||
).also {
|
||||
transformer = it
|
||||
if (state.bindingContext[CodegenBinding.CAPTURES_CROSSINLINE_SUSPEND_LAMBDA, originalSuspendDescriptor] == true) {
|
||||
return AddConstructorCallForCoroutineRegeneration(
|
||||
mv, access, name, desc, null, null, this::classBuilderForCoroutineState,
|
||||
containingClassInternalName,
|
||||
originalSuspendDescriptor.dispatchReceiverParameter != null,
|
||||
containingClassInternalNameOrNull()
|
||||
)
|
||||
}
|
||||
return CoroutineTransformerMethodVisitor(
|
||||
mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState,
|
||||
isForNamedFunction = true,
|
||||
lineNumber = CodegenUtil.getLineNumberForElement(declaration, false) ?: 0,
|
||||
shouldPreserveClassInitialization = constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null,
|
||||
internalNameForDispatchReceiver = containingClassInternalNameOrNull()
|
||||
)
|
||||
}
|
||||
|
||||
private fun containingClassInternalNameOrNull() =
|
||||
@@ -76,4 +87,39 @@ class SuspendFunctionGenerationStrategy(
|
||||
this.codegen = codegen
|
||||
codegen.returnExpression(declaration.bodyExpression ?: error("Function has no body: " + declaration.getElementTextWithContext()))
|
||||
}
|
||||
|
||||
// When we generate named suspend function for the use as inline site, we do not generate state machine.
|
||||
// So, there will be no way to remember the name of generated continuation in such case.
|
||||
// In order to keep generated continuation for named suspend function, we just generate construction call, which is going to be
|
||||
// removed during inlining.
|
||||
// The continuation itself will be regenerated and used as a container for the coroutine's locals.
|
||||
private class AddConstructorCallForCoroutineRegeneration(
|
||||
delegate: MethodVisitor,
|
||||
access: Int,
|
||||
name: String,
|
||||
desc: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?,
|
||||
obtainClassBuilderForCoroutineState: () -> ClassBuilder,
|
||||
private val containingClassInternalName: String,
|
||||
private val needDispatchReceiver: Boolean,
|
||||
private val internalNameForDispatchReceiver: String?
|
||||
) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) {
|
||||
private val classBuilderForCoroutineState: ClassBuilder by lazy(obtainClassBuilderForCoroutineState)
|
||||
override fun performTransformations(methodNode: MethodNode) {
|
||||
val objectTypeForState = Type.getObjectType(classBuilderForCoroutineState.thisName)
|
||||
methodNode.instructions.insert(withInstructionAdapter {
|
||||
addFakeContinuationConstructorCallMarker(this, true)
|
||||
generateContinuationConstructorCall(
|
||||
objectTypeForState,
|
||||
methodNode,
|
||||
needDispatchReceiver,
|
||||
internalNameForDispatchReceiver,
|
||||
containingClassInternalName,
|
||||
classBuilderForCoroutineState
|
||||
)
|
||||
addFakeContinuationConstructorCallMarker(this, false)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-5
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.codegen.coroutines
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.backend.common.*
|
||||
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
|
||||
import org.jetbrains.kotlin.codegen.ExpressionCodegen
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.inline.addFakeContinuationMarker
|
||||
@@ -175,13 +176,18 @@ private fun NewResolvedCallImpl<VariableDescriptor>.asDummyOldResolvedCall(bindi
|
||||
)
|
||||
}
|
||||
|
||||
fun ResolvedCall<*>.isSuspendNoInlineCall(): Boolean {
|
||||
val isCrossinline =
|
||||
safeAs<VariableAsFunctionResolvedCall>()?.variableCall?.resultingDescriptor?.safeAs<ValueParameterDescriptor>()?.isCrossinline
|
||||
?: false
|
||||
fun ResolvedCall<*>.isSuspendNoInlineCall(codegen: ExpressionCodegen): Boolean {
|
||||
var isCrossinline = false
|
||||
var isInlineLambda = false
|
||||
if (this is VariableAsFunctionResolvedCall) {
|
||||
variableCall.resultingDescriptor.safeAs<ValueParameterDescriptor>()?.let {
|
||||
isCrossinline = it.isCrossinline
|
||||
isInlineLambda = !isCrossinline && !it.isNoinline && codegen.context.functionDescriptor.isInline
|
||||
}
|
||||
}
|
||||
return resultingDescriptor.safeAs<FunctionDescriptor>()
|
||||
?.let {
|
||||
val inline = it.isInline || isCrossinline
|
||||
val inline = it.isInline || isCrossinline || isInlineLambda
|
||||
it.isSuspend && (!inline || it.isBuiltInSuspendCoroutineOrReturnInJvm() || it.isBuiltInSuspendCoroutineUninterceptedOrReturnInJvm())
|
||||
} == true
|
||||
}
|
||||
|
||||
+111
-5
@@ -21,15 +21,17 @@ import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.coroutines.COROUTINE_IMPL_ASM_TYPE
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
import org.jetbrains.kotlin.codegen.serialization.JvmCodegenStringTable
|
||||
import org.jetbrains.kotlin.codegen.writeKotlinMetadata
|
||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor
|
||||
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
|
||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
||||
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
|
||||
import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor
|
||||
import org.jetbrains.kotlin.protobuf.MessageLite
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
|
||||
@@ -41,7 +43,8 @@ import java.util.*
|
||||
class AnonymousObjectTransformer(
|
||||
transformationInfo: AnonymousObjectTransformationInfo,
|
||||
private val inliningContext: InliningContext,
|
||||
private val isSameModule: Boolean
|
||||
private val isSameModule: Boolean,
|
||||
private val continuationClassName: String?
|
||||
) : ObjectTransformer<AnonymousObjectTransformationInfo>(transformationInfo, inliningContext.state) {
|
||||
|
||||
private val oldObjectType = Type.getObjectType(transformationInfo.oldClassName)
|
||||
@@ -138,12 +141,39 @@ class AnonymousObjectTransformer(
|
||||
val additionalFakeParams = extractParametersMappingAndPatchConstructor(
|
||||
constructor!!, allCapturedParamBuilder, constructorParamBuilder,transformationInfo, parentRemapper
|
||||
)
|
||||
|
||||
val capturesCrossinlineSuspend = (!inliningContext.isInliningLambda || inliningContext.isContinuation) &&
|
||||
inliningContext.expressionMap.values.any { lambda ->
|
||||
lambda is PsiExpressionLambda && lambda.isCrossInline && lambda.invokeMethodDescriptor.isSuspend
|
||||
}
|
||||
|
||||
val deferringMethods = ArrayList<DeferredMethodVisitor>()
|
||||
|
||||
generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams)
|
||||
|
||||
val isLambdaAlreadyGeneratedAndNotGoingToBeInlined = transformationInfo.oldClassName.contains("\$\$special\$\$inlined")
|
||||
|
||||
val hasLambdasToInline =
|
||||
((parentRemapper is RegeneratedLambdaFieldRemapper) && parentRemapper.recapturedLambdas.isNotEmpty()) || transformationInfo.capturedLambdasToInline.isNotEmpty()
|
||||
|
||||
for (next in methodsToTransform) {
|
||||
val deferringVisitor = newMethod(classBuilder, next)
|
||||
// Generate state machine for
|
||||
// 1) doResume method of suspend lambda
|
||||
// 2) Suspend named function
|
||||
// Iff it captures crossinline suspend lambda
|
||||
val generateStateMachineForLambda =
|
||||
next.name == "doResume" && capturesCrossinlineSuspend && inliningContext.isContinuation &&
|
||||
!isLambdaAlreadyGeneratedAndNotGoingToBeInlined && hasLambdasToInline
|
||||
val continuationClassName = findFakeContinuationConstructorClassName(next)
|
||||
val generateStateMachineForNamedFunction =
|
||||
capturesCrossinlineSuspend && !inliningContext.isContinuation && continuationClassName != null
|
||||
|
||||
val deferringVisitor =
|
||||
when {
|
||||
generateStateMachineForLambda -> newStateMachineForLambda(classBuilder, next)
|
||||
generateStateMachineForNamedFunction -> newStateMachineForNamedFunction(classBuilder, next, continuationClassName!!)
|
||||
else -> newMethod(classBuilder, next)
|
||||
}
|
||||
val funResult = inlineMethodAndUpdateGlobalResult(parentRemapper, deferringVisitor, next, allCapturedParamBuilder, false)
|
||||
|
||||
val returnType = Type.getReturnType(next.desc)
|
||||
@@ -160,6 +190,21 @@ class AnonymousObjectTransformer(
|
||||
deferringMethods.forEach { method ->
|
||||
removeFinallyMarkers(method.intermediate)
|
||||
method.visitEnd()
|
||||
|
||||
// During regeneration of named suspend functions, which capture crossinline suspend lambda, we need to spill the variables
|
||||
// into continuation object.
|
||||
// In order to do this, we reuse class builder, which regenerates continuation object.
|
||||
if (capturesCrossinlineSuspend &&
|
||||
!inliningContext.isContinuation &&
|
||||
inliningContext is RegeneratedClassContext
|
||||
) {
|
||||
val continuationClassName = findFakeContinuationConstructorClassName(method.intermediate)
|
||||
if (continuationClassName != null) {
|
||||
inliningContext.continuationBuilders
|
||||
.remove(continuationClassName)
|
||||
?.let(ClassBuilder::done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SourceMapper.flushToClassBuilder(sourceMapper, classBuilder)
|
||||
@@ -177,7 +222,12 @@ class AnonymousObjectTransformer(
|
||||
|
||||
writeOuterInfo(visitor)
|
||||
|
||||
classBuilder.done()
|
||||
if (continuationClassName == transformationInfo.oldClassName) {
|
||||
assert(inliningContext.parent?.parent is RegeneratedClassContext)
|
||||
(inliningContext.parent?.parent as RegeneratedClassContext).continuationBuilders[continuationClassName] = classBuilder
|
||||
} else {
|
||||
classBuilder.done()
|
||||
}
|
||||
|
||||
return transformationResult
|
||||
}
|
||||
@@ -390,6 +440,55 @@ class AnonymousObjectTransformer(
|
||||
}
|
||||
}
|
||||
|
||||
private fun newStateMachineForLambda(builder: ClassBuilder, original: MethodNode): DeferredMethodVisitor {
|
||||
return DeferredMethodVisitor(
|
||||
MethodNode(
|
||||
original.access, original.name, original.desc, original.signature,
|
||||
ArrayUtil.toStringArray(original.exceptions)
|
||||
)
|
||||
) {
|
||||
CoroutineTransformerMethodVisitor(
|
||||
builder.newMethod(
|
||||
NO_ORIGIN, original.access, original.name, original.desc, original.signature,
|
||||
ArrayUtil.toStringArray(original.exceptions)
|
||||
), original.access, original.name, original.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { builder },
|
||||
lineNumber = 0, // <- TODO
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = builder.thisName,
|
||||
isForNamedFunction = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun newStateMachineForNamedFunction(
|
||||
builder: ClassBuilder,
|
||||
original: MethodNode,
|
||||
continuationClassName: String
|
||||
): DeferredMethodVisitor {
|
||||
assert(inliningContext is RegeneratedClassContext)
|
||||
return DeferredMethodVisitor(
|
||||
MethodNode(
|
||||
original.access, original.name, original.desc, original.signature,
|
||||
ArrayUtil.toStringArray(original.exceptions)
|
||||
)
|
||||
) {
|
||||
CoroutineTransformerMethodVisitor(
|
||||
builder.newMethod(
|
||||
NO_ORIGIN, original.access, original.name, original.desc, original.signature,
|
||||
ArrayUtil.toStringArray(original.exceptions)
|
||||
), original.access, original.name, original.desc, null, null,
|
||||
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
|
||||
lineNumber = 0, // <- TODO
|
||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||
containingClassInternalName = builder.thisName,
|
||||
isForNamedFunction = true,
|
||||
needDispatchReceiver = true,
|
||||
internalNameForDispatchReceiver = builder.thisName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractParametersMappingAndPatchConstructor(
|
||||
constructor: MethodNode,
|
||||
capturedParamBuilder: ParametersBuilder,
|
||||
@@ -535,3 +634,10 @@ class AnonymousObjectTransformer(
|
||||
private fun isFirstDeclSiteLambdaFieldRemapper(parentRemapper: FieldRemapper): Boolean =
|
||||
parentRemapper !is RegeneratedLambdaFieldRemapper && parentRemapper !is InlinedLambdaRemapper
|
||||
}
|
||||
|
||||
internal fun findFakeContinuationConstructorClassName(node: MethodNode): String? {
|
||||
val marker = node.instructions.asSequence().firstOrNull(::isBeforeFakeContinuationConstructorCallMarker) ?: return null
|
||||
val new = marker.next
|
||||
assert(new?.opcode == Opcodes.NEW)
|
||||
return (new as TypeInsnNode).desc
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.codegen.inline
|
||||
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
|
||||
@@ -41,11 +42,13 @@ class RegeneratedClassContext(
|
||||
override val callSiteInfo: InlineCallSiteInfo
|
||||
) : InliningContext(
|
||||
parent, expressionMap, state, nameGenerator, typeRemapper, lambdaInfo, true
|
||||
)
|
||||
) {
|
||||
val continuationBuilders: MutableMap<String, ClassBuilder> = hashMapOf()
|
||||
}
|
||||
|
||||
open class InliningContext(
|
||||
val parent: InliningContext?,
|
||||
private val expressionMap: Map<Int, LambdaInfo>,
|
||||
val expressionMap: Map<Int, LambdaInfo>,
|
||||
val state: GenerationState,
|
||||
val nameGenerator: NameGenerator,
|
||||
val typeRemapper: TypeRemapper,
|
||||
@@ -55,7 +58,7 @@ open class InliningContext(
|
||||
|
||||
val isInliningLambda = lambdaInfo != null
|
||||
|
||||
val internalNameToAnonymousObjectTransformationInfo = hashMapOf<String, AnonymousObjectTransformationInfo>()
|
||||
private val internalNameToAnonymousObjectTransformationInfo = hashMapOf<String, AnonymousObjectTransformationInfo>()
|
||||
|
||||
var isContinuation: Boolean = false
|
||||
|
||||
@@ -64,6 +67,14 @@ open class InliningContext(
|
||||
val root: RootInliningContext
|
||||
get() = if (isRoot) this as RootInliningContext else parent!!.root
|
||||
|
||||
fun findAnonymousObjectTransformationInfo(internalName: String, searchInParent: Boolean = true): AnonymousObjectTransformationInfo? =
|
||||
internalNameToAnonymousObjectTransformationInfo[internalName]
|
||||
?: if (searchInParent) parent?.findAnonymousObjectTransformationInfo(internalName, searchInParent) else null
|
||||
|
||||
fun recordIfNotPresent(internalName: String, info: AnonymousObjectTransformationInfo) {
|
||||
internalNameToAnonymousObjectTransformationInfo.putIfAbsent(internalName, info)
|
||||
}
|
||||
|
||||
fun subInlineLambda(lambdaInfo: LambdaInfo): InliningContext =
|
||||
subInline(
|
||||
nameGenerator.subGenerator("lambda"),
|
||||
@@ -104,8 +115,4 @@ open class InliningContext(
|
||||
get() {
|
||||
return parent!!.callSiteInfo
|
||||
}
|
||||
|
||||
fun findAnonymousObjectTransformationInfo(internalName: String): AnonymousObjectTransformationInfo? {
|
||||
return root.internalNameToAnonymousObjectTransformationInfo[internalName]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.codegen.inline
|
||||
|
||||
@@ -9,18 +9,23 @@ import org.jetbrains.kotlin.backend.jvm.codegen.IrExpressionLambda
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.ClosureCodegen
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_ASM_TYPE
|
||||
import org.jetbrains.kotlin.codegen.coroutines.replaceFakeContinuationsWithRealOnes
|
||||
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||
import org.jetbrains.kotlin.descriptors.ParameterDescriptor
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
|
||||
import org.jetbrains.kotlin.utils.SmartList
|
||||
import org.jetbrains.kotlin.utils.SmartSet
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
@@ -159,7 +164,11 @@ class MethodInliner(
|
||||
currentTypeMapping,
|
||||
inlineCallSiteInfo
|
||||
)
|
||||
val transformer = transformationInfo!!.createTransformer(childInliningContext, isSameModule)
|
||||
val transformer = transformationInfo!!.createTransformer(
|
||||
childInliningContext,
|
||||
isSameModule,
|
||||
findFakeContinuationConstructorClassName(node)
|
||||
)
|
||||
|
||||
val transformResult = transformer.doTransform(nodeRemapper)
|
||||
result.merge(transformResult)
|
||||
@@ -308,7 +317,7 @@ class MethodInliner(
|
||||
//TODO: add new inner class also for other contexts
|
||||
if (inliningContext.parent is RegeneratedClassContext) {
|
||||
inliningContext.parent.typeRemapper.addAdditionalMappings(
|
||||
transformationInfo!!.oldClassName, transformationInfo!!.newClassName
|
||||
transformationInfo!!.oldClassName, transformationInfo!!.newClassName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -319,7 +328,7 @@ class MethodInliner(
|
||||
}
|
||||
}
|
||||
else if ((!inliningContext.isInliningLambda || isDefaultLambdaWithReification(inliningContext.lambdaInfo!!)) &&
|
||||
ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) {
|
||||
ReifiedTypeInliner.isNeedClassReificationMarker(MethodInsnNode(opcode, owner, name, desc, false))) {
|
||||
//we shouldn't process here content of inlining lambda it should be reified at external level except default lambdas
|
||||
}
|
||||
else {
|
||||
@@ -443,6 +452,8 @@ class MethodInliner(
|
||||
|
||||
preprocessNodeBeforeInline(processingNode, labelOwner)
|
||||
|
||||
replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode)
|
||||
|
||||
val sources = analyzeMethodNodeBeforeInline(processingNode)
|
||||
|
||||
val toDelete = SmartSet.create<AbstractInsnNode>()
|
||||
@@ -584,6 +595,67 @@ class MethodInliner(
|
||||
return processingNode
|
||||
}
|
||||
|
||||
// Replace ALOAD 0
|
||||
// with
|
||||
// ICONST fakeContinuationMarker
|
||||
// INVOKESTATIC InlineMarker.mark
|
||||
// ACONST_NULL
|
||||
// iff this ALOAD 0 is continuation and one of the following conditions is met
|
||||
// 1) it is passed as the last parameter to suspending function
|
||||
// 2) it is ASTORE'd right after
|
||||
// 3) it is passed to invoke of lambda
|
||||
private fun replaceContinuationAccessesWithFakeContinuationsIfNeeded(processingNode: MethodNode) {
|
||||
val lambdaInfo = inliningContext.lambdaInfo ?: return
|
||||
if (!lambdaInfo.invokeMethodDescriptor.isSuspend) return
|
||||
val aload0s = processingNode.instructions.asSequence().filter { it.opcode == Opcodes.ALOAD && it.safeAs<VarInsnNode>()?.`var` == 0 }
|
||||
// Expected pattern here:
|
||||
// ALOAD 0
|
||||
// ICONST_0
|
||||
// INVOKESTATIC InlineMarker.mark
|
||||
// INVOKE* suspendingFunction(..., Continuation;)Ljava/lang/Object;
|
||||
val continuationAsParameterAload0s =
|
||||
aload0s.filter { it.next?.next?.let(::isBeforeSuspendMarker) == true && isSuspendCall(it.next?.next?.next) }
|
||||
replaceContinuationsWithFakeOnes(continuationAsParameterAload0s, processingNode)
|
||||
// Expected pattern here:
|
||||
// ALOAD 0
|
||||
// ASTORE N
|
||||
// This pattern may occur after multiple inlines
|
||||
val continuationToStoreAload0s = aload0s.filter { it.next?.opcode == Opcodes.ASTORE }
|
||||
replaceContinuationsWithFakeOnes(continuationToStoreAload0s, processingNode)
|
||||
// Expected pattern here:
|
||||
// ALOAD 0
|
||||
// INVOKEINTERFACE kotlin/jvm/functions/FunctionN.invoke (...,Ljava/lang/Object;)Ljava/lang/Object;
|
||||
val continuationAsLambdaParameterAload0s = aload0s.filter { isLambdaCall(it.next) }
|
||||
replaceContinuationsWithFakeOnes(continuationAsLambdaParameterAload0s, processingNode)
|
||||
}
|
||||
|
||||
private fun isLambdaCall(invoke: AbstractInsnNode?): Boolean {
|
||||
if (invoke?.opcode != Opcodes.INVOKEINTERFACE) return false
|
||||
invoke as MethodInsnNode
|
||||
if (!invoke.owner.startsWith("kotlin/jvm/functions/Function")) return false
|
||||
if (invoke.name != "invoke") return false
|
||||
if (Type.getReturnType(invoke.desc) != OBJECT_TYPE) return false
|
||||
return Type.getArgumentTypes(invoke.desc).let { it.isNotEmpty() && it.last() == OBJECT_TYPE }
|
||||
}
|
||||
|
||||
private fun replaceContinuationsWithFakeOnes(
|
||||
continuations: Sequence<AbstractInsnNode>,
|
||||
node: MethodNode
|
||||
) {
|
||||
for (toReplace in continuations) {
|
||||
insertNodeBefore(createFakeContinuationMethodNodeForInline(), node, toReplace)
|
||||
node.instructions.remove(toReplace)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSuspendCall(invoke: AbstractInsnNode?): Boolean {
|
||||
if (invoke !is MethodInsnNode) return false
|
||||
// We can't have suspending constructors.
|
||||
assert(invoke.opcode != Opcodes.INVOKESPECIAL)
|
||||
if (Type.getReturnType(invoke.desc) != OBJECT_TYPE) return false
|
||||
return Type.getArgumentTypes(invoke.desc).let { it.isNotEmpty() && it.last() == CONTINUATION_ASM_TYPE }
|
||||
}
|
||||
|
||||
private fun preprocessNodeBeforeInline(node: MethodNode, labelOwner: LabelOwner) {
|
||||
try {
|
||||
FixStackWithLabelNormalizationMethodTransformer().transform("fake", node)
|
||||
@@ -635,7 +707,6 @@ class MethodInliner(
|
||||
needReification: Boolean,
|
||||
capturesAnonymousObjectThatMustBeRegenerated: Boolean
|
||||
): AnonymousObjectTransformationInfo {
|
||||
val memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null
|
||||
|
||||
val info = AnonymousObjectTransformationInfo(
|
||||
anonymousType, needReification, lambdaMapping,
|
||||
@@ -647,8 +718,16 @@ class MethodInliner(
|
||||
capturesAnonymousObjectThatMustBeRegenerated
|
||||
)
|
||||
|
||||
if (memoizeAnonymousObject) {
|
||||
inliningContext.root.internalNameToAnonymousObjectTransformationInfo.put(anonymousType, info)
|
||||
val memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType)
|
||||
if (memoizeAnonymousObject == null ||
|
||||
//anonymous object could be inlined in several context without transformation (keeps same class name)
|
||||
// and on further inlining such code some of such cases would be transformed and some not,
|
||||
// so we should distinguish one classes from another more clearly
|
||||
!memoizeAnonymousObject.shouldRegenerate(isSameModule) &&
|
||||
info.shouldRegenerate(isSameModule)
|
||||
) {
|
||||
|
||||
inliningContext.recordIfNotPresent(anonymousType, info)
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.codegen.inline
|
||||
|
||||
@@ -43,7 +43,7 @@ class SamWrapperTransformationInfo(override val oldClassName: String, private va
|
||||
|
||||
override fun canRemoveAfterTransformation() = false
|
||||
|
||||
override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean) =
|
||||
override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean, continuationClassName: String?) =
|
||||
SamWrapperTransformer(this, inliningContext)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ interface TransformationInfo {
|
||||
|
||||
fun canRemoveAfterTransformation(): Boolean
|
||||
|
||||
fun createTransformer(inliningContext: InliningContext, sameModule: Boolean): ObjectTransformer<*>
|
||||
fun createTransformer(inliningContext: InliningContext, sameModule: Boolean, continuationClassName: String?): ObjectTransformer<*>
|
||||
}
|
||||
|
||||
class WhenMappingTransformationInfo(
|
||||
@@ -52,7 +52,7 @@ class WhenMappingTransformationInfo(
|
||||
|
||||
override fun canRemoveAfterTransformation(): Boolean = true
|
||||
|
||||
override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean): ObjectTransformer<*> =
|
||||
override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean, continuationClassName: String?): ObjectTransformer<*> =
|
||||
WhenMappingTransformer(this, inliningContext)
|
||||
|
||||
companion object {
|
||||
@@ -103,6 +103,10 @@ class AnonymousObjectTransformationInfo internal constructor(
|
||||
return !isStaticOrigin
|
||||
}
|
||||
|
||||
override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean): ObjectTransformer<*> =
|
||||
AnonymousObjectTransformer(this, inliningContext, sameModule)
|
||||
override fun createTransformer(
|
||||
inliningContext: InliningContext,
|
||||
sameModule: Boolean,
|
||||
continuationClassName: String?
|
||||
): ObjectTransformer<*> =
|
||||
AnonymousObjectTransformer(this, inliningContext, sameModule, continuationClassName)
|
||||
}
|
||||
|
||||
@@ -84,6 +84,8 @@ private const val INLINE_MARKER_BEFORE_SUSPEND_ID = 0
|
||||
private const val INLINE_MARKER_AFTER_SUSPEND_ID = 1
|
||||
private const val INLINE_MARKER_RETURNS_UNIT = 2
|
||||
private const val INLINE_MARKER_FAKE_CONTINUATION = 3
|
||||
private const val INLINE_MARKER_BEFORE_FAKE_CONTINUATION_CONSTRUCTOR_CALL = 4
|
||||
private const val INLINE_MARKER_AFTER_FAKE_CONTINUATION_CONSTRUCTOR_CALL = 5
|
||||
private val INTRINSIC_ARRAY_CONSTRUCTOR_TYPE = AsmUtil.asmTypeByClassId(classId)
|
||||
|
||||
internal fun getMethodNode(
|
||||
@@ -295,6 +297,13 @@ internal fun insertNodeBefore(from: MethodNode, to: MethodNode, beforeNode: Abst
|
||||
|
||||
internal fun createEmptyMethodNode() = MethodNode(API, 0, "fake", "()V", null, null)
|
||||
|
||||
internal fun createFakeContinuationMethodNodeForInline(): MethodNode {
|
||||
val methodNode = createEmptyMethodNode()
|
||||
val v = InstructionAdapter(methodNode)
|
||||
addFakeContinuationMarker(v)
|
||||
return methodNode
|
||||
}
|
||||
|
||||
internal fun firstLabelInChain(node: LabelNode): LabelNode {
|
||||
var curNode = node
|
||||
while (curNode.previous is LabelNode) {
|
||||
@@ -425,6 +434,15 @@ internal fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
|
||||
)
|
||||
}
|
||||
|
||||
internal fun addFakeContinuationConstructorCallMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
|
||||
v.iconst(if (isStartNotEnd) INLINE_MARKER_BEFORE_FAKE_CONTINUATION_CONSTRUCTOR_CALL else INLINE_MARKER_AFTER_FAKE_CONTINUATION_CONSTRUCTOR_CALL)
|
||||
v.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
|
||||
"mark",
|
||||
"(I)V", false
|
||||
)
|
||||
}
|
||||
|
||||
private fun addReturnsUnitMarker(v: InstructionAdapter) {
|
||||
v.iconst(INLINE_MARKER_RETURNS_UNIT)
|
||||
v.visitMethodInsn(
|
||||
@@ -454,6 +472,10 @@ internal fun isAfterSuspendMarker(insn: AbstractInsnNode) = isSuspendMarker(insn
|
||||
internal fun isReturnsUnitMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_RETURNS_UNIT)
|
||||
internal fun isFakeContinuationMarker(insn: AbstractInsnNode) =
|
||||
insn.previous != null && isSuspendMarker(insn.previous, INLINE_MARKER_FAKE_CONTINUATION) && insn.opcode == Opcodes.ACONST_NULL
|
||||
internal fun isBeforeFakeContinuationConstructorCallMarker(insn: AbstractInsnNode) =
|
||||
isSuspendMarker(insn, INLINE_MARKER_BEFORE_FAKE_CONTINUATION_CONSTRUCTOR_CALL)
|
||||
internal fun isAfterFakeContinuationConstructorCallMarker(insn: AbstractInsnNode) =
|
||||
isSuspendMarker(insn, INLINE_MARKER_AFTER_FAKE_CONTINUATION_CONSTRUCTOR_CALL)
|
||||
|
||||
private fun isSuspendMarker(insn: AbstractInsnNode, id: Int) =
|
||||
isInlineMarker(insn, "mark") && insn.previous.intConstant == id
|
||||
|
||||
Reference in New Issue
Block a user