diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractAccessorForFunctionDescriptor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractAccessorForFunctionDescriptor.kt index fe9ab459771..4bef750b3f1 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractAccessorForFunctionDescriptor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractAccessorForFunctionDescriptor.kt @@ -43,5 +43,5 @@ open class AbstractAccessorForFunctionDescriptor( } protected fun copyValueParameters(descriptor: FunctionDescriptor): List = - descriptor.valueParameters.map { it.copy(this, it.name) } + descriptor.valueParameters.map { it.copy(this, it.name, it.index) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClosureCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClosureCodegen.java index 2d83cb5d85e..a6459c81aba 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClosureCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClosureCodegen.java @@ -68,13 +68,13 @@ import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin. import static org.jetbrains.org.objectweb.asm.Opcodes.*; public class ClosureCodegen extends MemberCodegen { - private final FunctionDescriptor funDescriptor; + protected final FunctionDescriptor funDescriptor; private final ClassDescriptor classDescriptor; private final SamType samType; private final KotlinType superClassType; private final List superInterfaceTypes; private final FunctionDescriptor functionReferenceTarget; - private final FunctionGenerationStrategy strategy; + protected final FunctionGenerationStrategy strategy; private final CalculatedClosure closure; private final Type asmType; private final int visibilityFlag; @@ -170,6 +170,32 @@ public class ClosureCodegen extends MemberCodegen { @Override protected void generateBody() { + generateBridges(); + generateClosureBody(); + + this.constructor = generateConstructor(); + + if (isConst(closure)) { + generateConstInstance(asmType, asmType); + } + + genClosureFields(closure, v, typeMapper); + } + + protected void generateClosureBody() { + functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOrigin(element, funDescriptor), funDescriptor, strategy); + + + if (functionReferenceTarget != null) { + generateFunctionReferenceMethods(functionReferenceTarget); + } + + functionCodegen.generateDefaultIfNeeded( + context.intoFunction(funDescriptor), funDescriptor, context.getContextKind(), DefaultParameterValueLoader.DEFAULT, null + ); + } + + private void generateBridges() { FunctionDescriptor erasedInterfaceFunction; if (samType == null) { erasedInterfaceFunction = getErasedInvokeFunction(funDescriptor); @@ -183,8 +209,6 @@ public class ClosureCodegen extends MemberCodegen { typeMapper.mapAsmMethod(funDescriptor) ); - functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOrigin(element, funDescriptor), funDescriptor, strategy); - //TODO: rewrite cause ugly hack if (samType != null) { SimpleFunctionDescriptorImpl descriptorForBridges = SimpleFunctionDescriptorImpl @@ -200,22 +224,6 @@ public class ClosureCodegen extends MemberCodegen { DescriptorUtilsKt.setSingleOverridden(descriptorForBridges, erasedInterfaceFunction); functionCodegen.generateBridges(descriptorForBridges); } - - if (functionReferenceTarget != null) { - generateFunctionReferenceMethods(functionReferenceTarget); - } - - functionCodegen.generateDefaultIfNeeded( - context.intoFunction(funDescriptor), funDescriptor, context.getContextKind(), DefaultParameterValueLoader.DEFAULT, null - ); - - this.constructor = generateConstructor(); - - if (isConst(closure)) { - generateConstInstance(asmType, asmType); - } - - genClosureFields(closure, v, typeMapper); } @Override diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 13e485de88f..0879af22682 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -37,6 +37,8 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.codegen.binding.CalculatedClosure; import org.jetbrains.kotlin.codegen.binding.CodegenBinding; import org.jetbrains.kotlin.codegen.context.*; +import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegen; +import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension; import org.jetbrains.kotlin.codegen.inline.*; import org.jetbrains.kotlin.codegen.intrinsics.*; @@ -47,6 +49,7 @@ import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; import org.jetbrains.kotlin.codegen.when.SwitchCodegen; import org.jetbrains.kotlin.codegen.when.SwitchCodegenUtil; +import org.jetbrains.kotlin.coroutines.CoroutineUtilKt; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor; @@ -102,6 +105,7 @@ import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isInt; import static org.jetbrains.kotlin.codegen.AsmUtil.*; import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.*; import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.*; +import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.addInlineMarker; import static org.jetbrains.kotlin.resolve.BindingContext.*; import static org.jetbrains.kotlin.resolve.BindingContextUtils.*; import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry; @@ -314,6 +318,10 @@ public class ExpressionCodegen extends KtVisitor impleme public void gen(KtElement expr, Type type) { StackValue value = Type.VOID_TYPE.equals(type) ? genStatement(expr) : gen(expr); + putStackValue(expr, type, value); + } + + private void putStackValue(KtElement expr, Type type, StackValue value) { // for repl store the result of the last line into special field if (value.type != Type.VOID_TYPE && state.getReplSpecific().getShouldGenerateScriptResultValue()) { ScriptContext context = getScriptContext(); @@ -1438,7 +1446,8 @@ public class ExpressionCodegen extends KtVisitor impleme declaration.getContainingFile() ); - ClosureCodegen closureCodegen = new ClosureCodegen( + ClosureCodegen coroutineCodegen = CoroutineCodegen.create(this, descriptor, declaration, cv); + ClosureCodegen closureCodegen = coroutineCodegen != null ? coroutineCodegen : new ClosureCodegen( state, declaration, samType, context.intoClosure(descriptor, this, typeMapper), functionReferenceTarget, strategy, parentCodegen, cv ); @@ -1613,6 +1622,12 @@ public class ExpressionCodegen extends KtVisitor impleme if (!iterator.hasNext()) { answer = result; + StackValue handleResultValue = !(possiblyLabeledStatement instanceof KtReturnExpression) + ? genControllerHandleResultCallIfNeeded(possiblyLabeledStatement, result) + : null; + if (handleResultValue != null) { + answer = handleResultValue; + } } else { result.put(Type.VOID_TYPE, v); @@ -1635,6 +1650,31 @@ public class ExpressionCodegen extends KtVisitor impleme }); } + @Nullable + private StackValue genControllerHandleResultCallIfNeeded(@NotNull KtExpression callOwner, @Nullable StackValue valueToReturn) { + ResolvedCall resolvedCall = bindingContext.get(RETURN_HANDLE_RESULT_RESOLVED_CALL, callOwner); + + if (resolvedCall != null) { + assert resolvedCall.getValueArgumentsByIndex() != null : "Arguments were not resolved for call element: " + callOwner.getText(); + KtExpression argumentExpression = + resolvedCall.getValueArgumentsByIndex().get(0).getArguments().get(0).getArgumentExpression(); + if (KotlinBuiltIns.isUnit(resolvedCall.getResultingDescriptor().getValueParameters().get(0).getType())) { + tempVariables.put(argumentExpression, StackValue.unit()); + } + else { + tempVariables.put(argumentExpression, valueToReturn); + } + + // second argument for handleResult is always Continuation ('this'-object in current implementation) + tempVariables.put( + resolvedCall.getValueArgumentsByIndex().get(1).getArguments().get(0).getArgumentExpression(), + StackValue.thisOrOuter(this, context.getThisDescriptor(), false, false)); + + return invokeFunction(resolvedCall, StackValue.none()); + } + return null; + } + @NotNull private Type getVariableType(@NotNull VariableDescriptor variableDescriptor) { Type sharedVarType = typeMapper.getSharedVarType(variableDescriptor); @@ -1910,8 +1950,14 @@ public class ExpressionCodegen extends KtVisitor impleme } Type returnType = isNonLocalReturn ? nonLocalReturn.returnType : ExpressionCodegen.this.returnType; - if (returnedExpression != null) { - gen(returnedExpression, returnType); + StackValue valueToReturn = returnedExpression != null ? gen(returnedExpression) : null; + StackValue handleResultValue = genControllerHandleResultCallIfNeeded(expression, valueToReturn); + + if (handleResultValue != null) { + handleResultValue.put(Type.VOID_TYPE, v); + } + else if (returnedExpression != null && valueToReturn != null) { + putStackValue(returnedExpression, returnType, valueToReturn); } Label afterReturnLabel = new Label(); @@ -2491,6 +2537,11 @@ public class ExpressionCodegen extends KtVisitor impleme @NotNull CallGenerator callGenerator, @NotNull ArgumentGenerator argumentGenerator ) { + boolean isCallToSuspendFunction = isCallToSuspendFunction(resolvedCall); + if (isCallToSuspendFunction) { + // Inline markers are used to spill the stack before coroutine suspension + addInlineMarker(v, true); + } boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor; if (!isConstructor) { // otherwise already receiver = StackValue.receiver(resolvedCall, receiver, this, callableMethod); @@ -2523,8 +2574,18 @@ public class ExpressionCodegen extends KtVisitor impleme } } + if (isCallToSuspendFunction) { + v.invokestatic( + CoroutineCodegenUtilKt.SUSPENSION_POINT_MARKER_OWNER, + CoroutineCodegenUtilKt.SUSPENSION_POINT_MARKER_NAME, "()V", false); + } + callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this); + if (isCallToSuspendFunction) { + addInlineMarker(v, false); + } + KotlinType returnType = resolvedCall.getResultingDescriptor().getReturnType(); if (returnType != null && KotlinBuiltIns.isNothing(returnType)) { v.aconst(null); @@ -2532,6 +2593,11 @@ public class ExpressionCodegen extends KtVisitor impleme } } + private static boolean isCallToSuspendFunction(@NotNull ResolvedCall resolvedCall) { + CallableDescriptor descriptor = resolvedCall.getResultingDescriptor(); + return descriptor instanceof FunctionDescriptor && ((FunctionDescriptor) descriptor).isSuspend(); + } + @NotNull private CallGenerator getOrCreateCallGenerator( @NotNull CallableDescriptor descriptor, @@ -2634,7 +2700,7 @@ public class ExpressionCodegen extends KtVisitor impleme } } else if (receiverValue instanceof ExtensionReceiver) { - return generateReceiver(((ExtensionReceiver) receiverValue).getDeclarationDescriptor()); + return generateExtensionReceiver(((ExtensionReceiver) receiverValue).getDeclarationDescriptor()); } else if (receiverValue instanceof ExpressionReceiver) { return gen(((ExpressionReceiver) receiverValue).getExpression()); @@ -2645,7 +2711,19 @@ public class ExpressionCodegen extends KtVisitor impleme } @NotNull - private StackValue generateReceiver(@NotNull CallableDescriptor descriptor) { + private StackValue generateExtensionReceiver(@NotNull CallableDescriptor descriptor) { + KotlinType coroutineControllerType = CoroutineUtilKt.getControllerTypeIfCoroutine(descriptor); + if (coroutineControllerType != null) { + ClassDescriptor thisDescriptor = context.getThisDescriptor(); + + return StackValue.field( + FieldInfo.createForHiddenField( + typeMapper.mapClass(thisDescriptor), + typeMapper.mapType(coroutineControllerType), + CoroutineCodegenUtilKt.COROUTINE_CONTROLLER_FIELD_NAME), + StackValue.thisOrOuter(this, thisDescriptor, /* isSuper =*/ false, /* castReceiver */ false)); + } + return context.generateReceiver(descriptor, state, false); } @@ -3736,7 +3814,7 @@ public class ExpressionCodegen extends KtVisitor impleme return StackValue.thisOrOuter(this, (ClassDescriptor) descriptor, false, true); } if (descriptor instanceof CallableDescriptor) { - return generateReceiver((CallableDescriptor) descriptor); + return generateExtensionReceiver((CallableDescriptor) descriptor); } throw new UnsupportedOperationException("Neither this nor receiver: " + descriptor); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmRuntimeTypes.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmRuntimeTypes.java index ef764501228..381b9815106 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmRuntimeTypes.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmRuntimeTypes.java @@ -17,20 +17,21 @@ package org.jetbrains.kotlin.codegen; import org.jetbrains.annotations.NotNull; -import org.jetbrains.kotlin.builtins.DefaultBuiltIns; +import org.jetbrains.kotlin.coroutines.CoroutineUtilKt; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.annotations.Annotations; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; -import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl; import org.jetbrains.kotlin.descriptors.impl.MutablePackageFragmentDescriptor; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; import org.jetbrains.kotlin.name.FqName; import org.jetbrains.kotlin.name.Name; +import org.jetbrains.kotlin.resolve.DescriptorUtils; import org.jetbrains.kotlin.resolve.FunctionTypeResolveUtilsKt; -import org.jetbrains.kotlin.resolve.TargetPlatformKt; import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; -import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform; -import org.jetbrains.kotlin.storage.LockBasedStorageManager; import org.jetbrains.kotlin.types.KotlinType; +import org.jetbrains.kotlin.types.TypeConstructorSubstitution; +import org.jetbrains.kotlin.types.TypeProjectionImpl; +import org.jetbrains.kotlin.types.Variance; import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils; import java.util.*; @@ -42,13 +43,9 @@ public class JvmRuntimeTypes { private final List mutablePropertyReferences; private final ClassDescriptor localVariableReference; private final ClassDescriptor mutableLocalVariableReference; + private final KotlinType defaultContinuationSupertype; - public JvmRuntimeTypes() { - ModuleDescriptorImpl module = TargetPlatformKt.createModule( - JvmPlatform.INSTANCE, - Name.special(""), - LockBasedStorageManager.NO_LOCKS, - DefaultBuiltIns.getInstance()); + public JvmRuntimeTypes(@NotNull ModuleDescriptor module) { PackageFragmentDescriptor kotlinJvmInternal = new MutablePackageFragmentDescriptor(module, new FqName("kotlin.jvm.internal")); this.lambda = createClass(kotlinJvmInternal, "Lambda"); @@ -62,6 +59,27 @@ public class JvmRuntimeTypes { propertyReferences.add(createClass(kotlinJvmInternal, "PropertyReference" + i)); mutablePropertyReferences.add(createClass(kotlinJvmInternal, "MutablePropertyReference" + i)); } + + defaultContinuationSupertype = createNullableAnyContinuation(module); + } + + /** + * @param module + * @return Continuation type + */ + @NotNull + private static KotlinType createNullableAnyContinuation(@NotNull ModuleDescriptor module) { + ClassDescriptor classDescriptor = + DescriptorUtilsKt.resolveTopLevelClass( + module, DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME, NoLookupLocation.FROM_BACKEND); + + assert classDescriptor != null : DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME + " was not found in built-ins"; + + //noinspection ConstantConditions + return TypeConstructorSubstitution + .createByParametersMap(Collections.singletonMap(classDescriptor.getDeclaredTypeParameters().get(0), + new TypeProjectionImpl(module.getBuiltIns().getNullableAnyType()))) + .buildSubstitutor().substitute(classDescriptor.getDefaultType(), Variance.INVARIANT); } @NotNull @@ -91,6 +109,13 @@ public class JvmRuntimeTypes { descriptor.getReturnType() ); + KotlinType coroutineControllerType = CoroutineUtilKt.getControllerTypeIfCoroutine(descriptor); + + if (coroutineControllerType != null) { + return Arrays.asList( + lambda.getDefaultType(), functionType, /*coroutineType,*/ defaultContinuationSupertype); + } + return Arrays.asList(lambda.getDefaultType(), functionType); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt new file mode 100644 index 00000000000..7eeeb70ae36 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineCodegen.kt @@ -0,0 +1,189 @@ +/* + * Copyright 2010-2016 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.codegen.coroutines + +import org.jetbrains.kotlin.codegen.* +import org.jetbrains.kotlin.codegen.binding.CodegenBinding +import org.jetbrains.kotlin.codegen.context.ClosureContext +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.coroutines.controllerTypeIfCoroutine +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtDeclarationWithBody +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.psi.KtFunction +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe +import org.jetbrains.kotlin.resolve.descriptorUtil.setSingleOverridden +import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type + + +class CoroutineCodegen( + state: GenerationState, + element: KtElement, + private val closureContext: ClosureContext, + strategy: FunctionGenerationStrategy, + parentCodegen: MemberCodegen<*>, + classBuilder: ClassBuilder, + private val continuationSuperType: KotlinType, + private val controllerType: KotlinType +) : ClosureCodegen(state, element, null, closureContext, null, strategy, parentCodegen, classBuilder) { + + override fun generateClosureBody() { + v.newField( + JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_PRIVATE, + COROUTINE_CONTROLLER_FIELD_NAME, + typeMapper.mapType(controllerType).descriptor, null, null) + + v.newField( + JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_PRIVATE, + COROUTINE_LABEL_FIELD_NAME, + Type.INT_TYPE.descriptor, null, null) + + val classDescriptor = closureContext.contextDescriptor + + functionCodegen.generateMethod(JvmDeclarationOrigin.NO_ORIGIN, funDescriptor, + object : FunctionGenerationStrategy.CodegenBased(state) { + override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { + AsmUtil.genAssignInstanceFieldFromParam( + FieldInfo.createForHiddenField( + typeMapper.mapClass(classDescriptor), + typeMapper.mapType(controllerType), COROUTINE_CONTROLLER_FIELD_NAME), + 1, codegen.v) + + with(codegen.v) { + load(0, AsmTypes.OBJECT_TYPE) + iconst(0) + putfield(v.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor) + load(0, AsmTypes.OBJECT_TYPE) + areturn(AsmTypes.OBJECT_TYPE) + } + } + }) + + val resumeFunctionDescriptor = + createSynthesizedImplementationByName( + "resume", interfaceSupertype = continuationSuperType, implementationClass = classDescriptor) + + val resumeWithExceptionFunctionDescriptor = + createSynthesizedImplementationByName( + "resumeWithException", interfaceSupertype = continuationSuperType, implementationClass = classDescriptor) + + // private fun resume(result, throwable) + val combinedResumeFunctionDescriptor = + resumeFunctionDescriptor.newCopyBuilder() + .setVisibility(Visibilities.PRIVATE) + .setName(Name.identifier("doResume")) + .setCopyOverrides(false) + .setValueParameters( + listOf( + resumeFunctionDescriptor.valueParameters[0].copy( + resumeFunctionDescriptor, Name.identifier("value"), 0), + resumeWithExceptionFunctionDescriptor.valueParameters[0].copy( + resumeFunctionDescriptor, Name.identifier("exception"), 1)) + ).build()!! + + generatedDelegationToCombinedResume(resumeFunctionDescriptor, combinedResumeFunctionDescriptor, isSuccess = true) + generatedDelegationToCombinedResume(resumeWithExceptionFunctionDescriptor, combinedResumeFunctionDescriptor, isSuccess = false) + + functionCodegen.generateMethod(OtherOrigin(element), combinedResumeFunctionDescriptor, + object : FunctionGenerationStrategy.FunctionDefault(state, element as KtDeclarationWithBody) { + override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { + codegen.v.visitAnnotation(CONTINUATION_METHOD_ANNOTATION_DESC, true).visitEnd() + super.doGenerateBody(codegen, signature) + } + }) + } + + + private fun createSynthesizedImplementationByName( + name: String, + interfaceSupertype: KotlinType, + implementationClass: ClassDescriptor + ): SimpleFunctionDescriptor { + val inSuperType = interfaceSupertype.memberScope.getContributedFunctions(Name.identifier(name), NoLookupLocation.FROM_BACKEND).single() + val result = inSuperType.newCopyBuilder().setOwner(implementationClass).setModality(Modality.FINAL).build()!! + result.setSingleOverridden(inSuperType) + + return result + } + + private fun generatedDelegationToCombinedResume( + from: SimpleFunctionDescriptor, + combinedResume: SimpleFunctionDescriptor, + isSuccess: Boolean + ) { + functionCodegen.generateMethod(OtherOrigin(element), from, + object : FunctionGenerationStrategy.CodegenBased(state) { + override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { + with(codegen.v) { + load(0, AsmTypes.OBJECT_TYPE) + if (isSuccess) { + load(1, AsmTypes.OBJECT_TYPE) + aconst(null) + } + else { + aconst(null) + load(1, AsmTypes.OBJECT_TYPE) + } + + val delegateTo = typeMapper.mapAsmMethod(combinedResume) + invokevirtual(className, delegateTo.name, delegateTo.descriptor, false) + areturn(Type.VOID_TYPE) + } + } + }) + } + + + companion object { + @JvmStatic fun create( + expressionCodegen: ExpressionCodegen, + originalCoroutineLambdaDescriptor: FunctionDescriptor, + declaration: KtElement, + classBuilder: ClassBuilder + ): ClosureCodegen? { + val classDescriptor = expressionCodegen.bindingContext[CodegenBinding.CLASS_FOR_CALLABLE, originalCoroutineLambdaDescriptor] ?: return null + declaration as? KtFunction ?: return null + + val continuationSupertype = + classDescriptor.typeConstructor.supertypes.firstOrNull { + it.constructor.declarationDescriptor?.fqNameUnsafe == + DescriptorUtils.CONTINUATION_INTERFACE_FQ_NAME.toUnsafe() + } ?: return null + + val descriptorWithContinuationReturnType = + originalCoroutineLambdaDescriptor.newCopyBuilder().setReturnType(continuationSupertype).build()!! + + val state = expressionCodegen.state + return CoroutineCodegen( + state, + declaration, + expressionCodegen.context.intoClosure(descriptorWithContinuationReturnType, expressionCodegen, state.typeMapper), + FunctionGenerationStrategy.FunctionDefault(state, declaration), expressionCodegen.parentCodegen, classBuilder, + continuationSupertype, + originalCoroutineLambdaDescriptor.controllerTypeIfCoroutine!!) + } + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt new file mode 100644 index 00000000000..5a37733bde9 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformationClassBuilder.kt @@ -0,0 +1,268 @@ +/* + * Copyright 2010-2016 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.codegen.coroutines + +import org.jetbrains.kotlin.codegen.* +import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer +import org.jetbrains.kotlin.codegen.optimization.SKIP_MANDATORY_TRANSFORMATIONS_ANNOTATION_DESC +import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter +import org.jetbrains.kotlin.codegen.optimization.common.asSequence +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer +import org.jetbrains.kotlin.resolve.jvm.AsmTypes +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.Label +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.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.commons.Method +import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue + +class CoroutineTransformationClassBuilder(private val delegate: ClassBuilder) : DelegatingClassBuilder() { + override fun getDelegate() = delegate + + override fun newMethod( + origin: JvmDeclarationOrigin, + access: Int, name: String, + desc: String, signature: + String?, + exceptions: Array? + ) = CoroutineTransformerMethodVisitor( + delegate.newMethod(origin, access, name, desc, signature, exceptions), + access, name, desc, signature, exceptions, this) +} + +class CoroutineTransformerClassBuilderFactory(delegate: ClassBuilderFactory) : DelegatingClassBuilderFactory(delegate) { + override fun newClassBuilder(origin: JvmDeclarationOrigin) = CoroutineTransformationClassBuilder(delegate.newClassBuilder(origin)) +} + +class CoroutineTransformerMethodVisitor( + delegate: MethodVisitor, + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array?, + private val classBuilder: ClassBuilder +) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { + override fun performTransformations(methodNode: MethodNode) { + if (methodNode.visibleAnnotations?.none { it.desc == CONTINUATION_METHOD_ANNOTATION_DESC } != false) return + methodNode.visibleAnnotations.removeAll { it.desc == CONTINUATION_METHOD_ANNOTATION_DESC } + + MandatoryMethodTransformer().transform("fake", methodNode) + methodNode.visibleAnnotations.add(AnnotationNode(SKIP_MANDATORY_TRANSFORMATIONS_ANNOTATION_DESC)) + + val suspensionPoints = collectSuspensionPoints(methodNode) + if (suspensionPoints.isEmpty()) return + + spillVariables(suspensionPoints, methodNode) + + val suspensionPointLabels = suspensionPoints.map { + transformCallAndReturnContinuationLabel(it, methodNode) + } + + methodNode.instructions.apply { + val startLabel = LabelNode() + val defaultLabel = LabelNode() + // tableswitch(this.label) + insertBefore(first, + insnListOf( + VarInsnNode(Opcodes.ALOAD, 0), + FieldInsnNode( + Opcodes.GETFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor), + TableSwitchInsnNode(0, + suspensionPoints.map { it.id }.max()!!, + defaultLabel, + *(arrayOf(startLabel) + suspensionPointLabels)), + startLabel)) + + + insert(last, withInstructionAdapter { + visitLabel(defaultLabel.label) + AsmUtil.genThrow(this, "java/lang/IllegalStateException", "call to 'resume' before 'invoke' with coroutine") + areturn(Type.VOID_TYPE) + }) + } + + } + + private fun collectSuspensionPoints(methodNode: MethodNode): List { + val suspensionPoints = mutableListOf() + + for (methodInsn in methodNode.instructions.asSequence().filterIsInstance()) { + if (methodInsn.owner != SUSPENSION_POINT_MARKER_OWNER) continue + + when (methodInsn.name) { + SUSPENSION_POINT_MARKER_NAME -> { + assert(methodInsn.next is MethodInsnNode) { + "Expected method call instruction after suspension point, but ${methodInsn.next} found" + } + + suspensionPoints.add(SuspensionPoint(suspensionPoints.size + 1, methodInsn.next as MethodInsnNode)) + } + + else -> error("Unexpected suspension point marker kind '${methodInsn.name}'") + } + } + + // Drop markers + suspensionPoints.forEach { methodNode.instructions.remove(it.suspensionCall.previous) } + + return suspensionPoints + } + + private fun spillVariables(suspensionPoints: List, methodNode: MethodNode) { + val instructions = methodNode.instructions + val frames = MethodTransformer.analyze("fake", methodNode, OptimizationBasicInterpreter()) + fun AbstractInsnNode.index() = instructions.indexOf(this) + + // We postpone these actions because they change instruction indices that we use when obtaining frames + val postponedActions = mutableListOf<() -> Unit>() + val maxVarsCountByType = mutableMapOf() + + for (suspension in suspensionPoints) { + val call = suspension.suspensionCall + assert(frames[call.next.index()].stackSize == (if (Type.getReturnType(call.desc).sort == Type.VOID) 0 else 1)) { + "Stack should be spilled before suspension call" + } + + val frame = frames[call.index()] + val localsCount = frame.locals + val varsCountByType = mutableMapOf() + // 0 - this + // 1 - continuation argument + // 2 - continuation exception + val variablesToSpill = + (3 until localsCount).map { Pair(it, frame.getLocal(it)) }.filter { it.second != BasicValue.UNINITIALIZED_VALUE } + + for ((index, basicValue) in variablesToSpill) { + val type = basicValue.type + val normalizedType = type.normalize() + + val indexBySort = varsCountByType[normalizedType]?.plus(1) ?: 0 + varsCountByType[normalizedType] = indexBySort + + val fieldName = normalizedType.fieldNameForVar(indexBySort) + + postponedActions.add { + with(instructions) { + // store variable before suspension call + insertBefore(call, VarInsnNode(Opcodes.ALOAD, 0)) + insertBefore(call, VarInsnNode(type.getOpcode(Opcodes.ILOAD), index)) + insertBefore(call, coercionInsns(type, normalizedType)) + insertBefore(call, FieldInsnNode(Opcodes.PUTFIELD, classBuilder.thisName, fieldName, normalizedType.descriptor)) + + // restore variable after suspension call + val nextInsnAfterCall = call.next + insertBefore(nextInsnAfterCall, VarInsnNode(Opcodes.ALOAD, 0)) + insertBefore(nextInsnAfterCall, + FieldInsnNode(Opcodes.GETFIELD, classBuilder.thisName, fieldName, normalizedType.descriptor)) + insertBefore(nextInsnAfterCall, coercionInsns(normalizedType, type)) + insertBefore(nextInsnAfterCall, VarInsnNode(type.getOpcode(Opcodes.ISTORE), index)) + } + } + } + + varsCountByType.forEach { + maxVarsCountByType[it.key] = Math.max(maxVarsCountByType[it.key] ?: 0, it.value) + } + } + + postponedActions.forEach(Function0::invoke) + + maxVarsCountByType.forEach { entry -> + val (type, maxIndex) = entry + for (index in 0..maxIndex) { + classBuilder.newField( + JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_PRIVATE or Opcodes.ACC_VOLATILE, + type.fieldNameForVar(index), type.descriptor, null, null) + } + } + } + + private fun transformCallAndReturnContinuationLabel(suspension: SuspensionPoint, methodNode: MethodNode): LabelNode { + val call = suspension.suspensionCall + val method = Method(call.name, call.desc) + val newParameters = method.argumentTypes + CONTINUATION_INTERFACE_ASM_TYPE + + call.desc = Method(method.name, Type.VOID_TYPE, newParameters).descriptor + + val continuationLabel = LabelNode() + + with(methodNode.instructions) { + // Save state + insertBefore(call, + insnListOf( + VarInsnNode(Opcodes.ALOAD, 0), + *withInstructionAdapter { iconst(suspension.id) }.toArray(), + FieldInsnNode( + Opcodes.PUTFIELD, classBuilder.thisName, COROUTINE_LABEL_FIELD_NAME, Type.INT_TYPE.descriptor))) + + // Pass continuation + insertBefore(call, VarInsnNode(Opcodes.ALOAD, 0)) + + val nextInsnAfterCall = call.next + + // Exit + insertBefore(nextInsnAfterCall, InsnNode(Opcodes.RETURN)) + + // Mark place for continuation + insertBefore(nextInsnAfterCall, continuationLabel) + + // Check if resumeWithException has been called + insertBefore(nextInsnAfterCall, withInstructionAdapter { + load(2, AsmTypes.OBJECT_TYPE) + dup() + val noExceptionLabel = Label() + ifnull(noExceptionLabel) + athrow() + + mark(noExceptionLabel) + pop() + }) + + // Load continuation argument just like suspending function returns it + insertBefore(nextInsnAfterCall, VarInsnNode(Opcodes.ALOAD, 1)) + insertBefore(nextInsnAfterCall, coercionInsns(AsmTypes.OBJECT_TYPE, method.returnType)) + } + + return continuationLabel + } +} + +private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index + +private fun coercionInsns(from: Type, to: Type) = withInstructionAdapter { StackValue.coerce(from, to, this) } + +private fun withInstructionAdapter(block: InstructionAdapter.() -> Unit): InsnList { + val tmpMethodNode = MethodNode() + + InstructionAdapter(tmpMethodNode).apply(block) + + return tmpMethodNode.instructions +} + +private fun Type.normalize() = + when (sort) { + Type.ARRAY, Type.OBJECT -> AsmTypes.OBJECT_TYPE + else -> this + } + +private class SuspensionPoint(val id: Int, val suspensionCall: MethodInsnNode) + +private fun insnListOf(vararg insns: AbstractInsnNode) = InsnList().apply { insns.forEach { add(it) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt new file mode 100644 index 00000000000..45465337bbc --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2016 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.codegen.coroutines + +import org.jetbrains.kotlin.coroutines.CONTINUATION_INTERFACE_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.JvmClassName +import org.jetbrains.org.objectweb.asm.Type + +val CONTINUATION_INTERFACE_ASM_TYPE = Type.getObjectType(JvmClassName.byFqNameWithoutInnerClasses(CONTINUATION_INTERFACE_FQ_NAME).internalName) + +// These classes do not actually exist at runtime +val CONTINUATION_METHOD_ANNOTATION_DESC = "Lkotlin/ContinuationMethod;" + +const val SUSPENSION_POINT_MARKER_OWNER = "kotlin/Markers" +const val SUSPENSION_POINT_MARKER_NAME = "suspensionPoint" + +const val COROUTINE_CONTROLLER_FIELD_NAME = "controller" +const val COROUTINE_LABEL_FIELD_NAME = "label" diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt index 6695595b03e..00466a41ff2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/MandatoryMethodTransforker.kt @@ -20,11 +20,16 @@ import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransfor import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.org.objectweb.asm.tree.MethodNode +val SKIP_MANDATORY_TRANSFORMATIONS_ANNOTATION_DESC = "Lkotlin/SkipMandatoryTransformations;" + class MandatoryMethodTransformer : MethodTransformer() { private val labelNormalization = LabelNormalizationMethodTransformer() private val fixStack = FixStackMethodTransformer() override fun transform(internalClassName: String, methodNode: MethodNode) { + // Mandatory transformations have already been applied + if (methodNode.visibleAnnotations?.removeAll { it.desc == SKIP_MANDATORY_TRANSFORMATIONS_ANNOTATION_DESC } == true) return + labelNormalization.transform(internalClassName, methodNode) fixStack.transform(internalClassName, methodNode) } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt index 5f6b041c60c..ec4ce267914 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/Util.kt @@ -42,6 +42,8 @@ class InsnSequence(val from: AbstractInsnNode, val to: AbstractInsnNode?) : Sequ } } +fun InsnList.asSequence() = InsnSequence(this) + fun MethodNode.prepareForEmitting() { tryCatchBlocks = tryCatchBlocks.filter { tcb -> InsnSequence(tcb.start, tcb.end).any { insn -> diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/MethodTransformer.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/MethodTransformer.java index 45f4acddc5c..1952be2fca3 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/MethodTransformer.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/transformer/MethodTransformer.java @@ -36,7 +36,7 @@ public abstract class MethodTransformer { } @NotNull - protected static Frame[] analyze( + public static Frame[] analyze( @NotNull String internalClassName, @NotNull MethodNode node, @NotNull Interpreter interpreter diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 10d95a1018c..8abae97bf47 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.`when`.MappingsClassesForWhenByEnum import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.context.CodegenContext import org.jetbrains.kotlin.codegen.context.RootContext +import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerClassBuilderFactory import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension import org.jetbrains.kotlin.codegen.inline.InlineCache import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods @@ -134,7 +135,7 @@ class GenerationState @JvmOverloads constructor( val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics) val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this) val reflectionTypes: ReflectionTypes = ReflectionTypes(module) - val jvmRuntimeTypes: JvmRuntimeTypes = JvmRuntimeTypes() + val jvmRuntimeTypes: JvmRuntimeTypes = JvmRuntimeTypes(module) val factory: ClassFileFactory private lateinit var duplicateSignatureFactory: BuilderFactoryForDuplicateSignatureDiagnostics @@ -160,6 +161,7 @@ class GenerationState @JvmOverloads constructor( this.interceptedBuilderFactory = builderFactory .wrapWith( { OptimizationClassBuilderFactory(it, configuration.get(JVMConfigurationKeys.DISABLE_OPTIMIZATION, false)) }, + ::CoroutineTransformerClassBuilderFactory, { BuilderFactoryForDuplicateSignatureDiagnostics( it, this.bindingContext, diagnostics, fileClassesProvider, incrementalCacheForThisTarget, this.moduleName ).apply { duplicateSignatureFactory = this } }, diff --git a/compiler/testData/codegen/box/coroutines/generate.kt b/compiler/testData/codegen/box/coroutines/generate.kt new file mode 100644 index 00000000000..772d114b80f --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/generate.kt @@ -0,0 +1,54 @@ +// WITH_RUNTIME +// FULL_JDK + +fun box(): String { + val x = gen().joinToString() + if (x != "1, 2") return "fail1: $x" + + val y = gen().joinToString() + if (y != "-1") return "fail2: $y" + return "OK" +} + +var was = false + +fun gen() = generate { + if (was) { + yield(-1) + return@generate + } + for (i in 1..2) { + yield(i) + } + was = true +} + +// LIBRARY CODE +fun generate(coroutine c: GeneratorController.() -> Continuation): Sequence = object : Sequence { + override fun iterator(): Iterator { + val iterator = GeneratorController() + iterator.setNextStep(c(iterator)) + return iterator + } +} + +class GeneratorController() : AbstractIterator() { + private lateinit var nextStep: Continuation + + override fun computeNext() { + nextStep.resume(Unit) + } + + fun setNextStep(step: Continuation) { + this.nextStep = step + } + + suspend fun yield(value: T, c: Continuation) { + setNext(value) + setNextStep(c) + } + + fun handleResult(result: Unit, c: Continuation) { + done() + } +} diff --git a/compiler/testData/codegen/box/coroutines/innerSuspensionCalls.kt b/compiler/testData/codegen/box/coroutines/innerSuspensionCalls.kt new file mode 100644 index 00000000000..acacbf92695 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/innerSuspensionCalls.kt @@ -0,0 +1,35 @@ +class Controller { + var i = 0 + suspend fun suspendHere(x: Continuation) { + x.resume((i++).toString()) + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + result += "-" + result += suspendHere() + + if (result == "-0") { + builder { + result += "+" + result += suspendHere() + result += suspendHere() + result += "#" + } + + result += suspendHere() + result += "&" + } + } + + if (result != "-0+01#1&") return "fail: $result" + + return "OK" +} diff --git a/compiler/testData/codegen/box/coroutines/returnByLabel.kt b/compiler/testData/codegen/box/coroutines/returnByLabel.kt new file mode 100644 index 00000000000..0ac5d8cfbbc --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/returnByLabel.kt @@ -0,0 +1,30 @@ +class Controller { + var res = 0 + suspend fun suspendHere(x: Continuation) { + x.resume("OK") + } + + operator fun handleResult(x: Int, y: Continuation) { + res = x + } +} + +fun builder(coroutine c: Controller.() -> Continuation): Int { + val controller = Controller() + c(controller).resume(Unit) + + return controller.res +} + +fun box(): String { + var result = "" + + val handledResult = builder { + result = suspendHere() + return@builder 56 + } + + if (handledResult != 56) return "fail 1: $handledResult" + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/simple.kt b/compiler/testData/codegen/box/coroutines/simple.kt new file mode 100644 index 00000000000..05dc60e0c70 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/simple.kt @@ -0,0 +1,19 @@ +class Controller { + suspend fun suspendHere(x: Continuation) { + x.resume("OK") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/simpleException.kt b/compiler/testData/codegen/box/coroutines/simpleException.kt new file mode 100644 index 00000000000..d84b75ac988 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/simpleException.kt @@ -0,0 +1,24 @@ +class Controller { + suspend fun suspendHere(x: Continuation) { + x.resumeWithException(RuntimeException("OK")) + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + try { + suspendHere() + result = "fail" + } catch (e: RuntimeException) { + result = "OK" + } + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt b/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt new file mode 100644 index 00000000000..647b98aaaf3 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt @@ -0,0 +1,30 @@ +class Controller { + var res = 0 + suspend fun suspendHere(x: Continuation) { + x.resume("OK") + } + + operator fun handleResult(x: Int, y: Continuation) { + res = x + } +} + +fun builder(coroutine c: Controller.() -> Continuation): Int { + val controller = Controller() + c(controller).resume(Unit) + + return controller.res +} + +fun box(): String { + var result = "" + + val handledResult = builder { + result = suspendHere() + 56 + } + + if (handledResult != 56) return "fail 1: $handledResult" + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/suspendInCycle.kt b/compiler/testData/codegen/box/coroutines/suspendInCycle.kt new file mode 100644 index 00000000000..b3d3b99b6c9 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/suspendInCycle.kt @@ -0,0 +1,34 @@ +class Controller { + var i = 0 + suspend fun suspendHere(x: Continuation) { + x.resume(i++) + } + suspend fun suspendThere(x: Continuation) { + x.resume("?") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +fun box(): String { + var result = "" + + builder { + result += "-" + for (i in 0..5) { + if (i % 2 == 0) { + result += suspendHere().toString() + } + else if (i == 3) { + result += suspendThere() + } + } + result += "+" + } + + if (result != "-01?2+") return "fail: $result" + + return "OK" +} diff --git a/compiler/testData/codegen/java8/box/async.kt b/compiler/testData/codegen/java8/box/async.kt new file mode 100644 index 00000000000..24fd446c764 --- /dev/null +++ b/compiler/testData/codegen/java8/box/async.kt @@ -0,0 +1,81 @@ +// WITH_RUNTIME +// FULL_JDK + +import java.util.concurrent.CompletableFuture + +fun foo(): CompletableFuture = CompletableFuture.supplyAsync { "foo" } +fun bar(v: String): CompletableFuture = CompletableFuture.supplyAsync { "bar with $v" } +fun exception(v: String): CompletableFuture = CompletableFuture.supplyAsync { throw RuntimeException(v) } + +fun foobar(x: String, y: String) = x + y + +fun box(): String { + var result = "" + fun log(x: String) { + synchronized(result) { + if (result.isNotEmpty()) result += "\n" + result += x + } + } + + val future = async { + log("start") + val x = await(foo()) + log("got '$x'") + val y = foobar("123 ", await(bar(x))) + log("got '$y' after '$x'") + y + } + + future.whenComplete { value, t -> + log("completed with '$value'") + } + + future.join() + java.lang.Thread.sleep(1000) + + val readResult = synchronized(result) { + result + } + + val expectedResult = + """ + |start + |got 'foo' + |got '123 bar with foo' after 'foo' + |completed with '123 bar with foo'""".trimMargin().trim('\n', ' ') + + if (expectedResult != readResult) return readResult + + return "OK" +} + +// LIBRARY CODE + +fun async(coroutine c: FutureController.() -> Continuation): CompletableFuture { + val controller = FutureController() + c(controller).resume(Unit) + return controller.future +} + +class FutureController { + val future = CompletableFuture() + + + suspend fun await(f: CompletableFuture, machine: Continuation) { + f.whenComplete { value, throwable -> + if (throwable == null) + machine.resume(value) + else + machine.resumeWithException(throwable) + } + } + + fun handleResult(value: T, c: Continuation) { + future.complete(value) + } + + fun handleException(t: Throwable, c: Continuation) { + future.completeExceptionally(t) + } +} diff --git a/compiler/testData/codegen/java8/box/asyncException.kt b/compiler/testData/codegen/java8/box/asyncException.kt new file mode 100644 index 00000000000..5c6787df7e4 --- /dev/null +++ b/compiler/testData/codegen/java8/box/asyncException.kt @@ -0,0 +1,61 @@ +// WITH_RUNTIME +// FULL_JDK + +import java.util.concurrent.CompletableFuture + +fun exception(v: String): CompletableFuture = CompletableFuture.supplyAsync { throw RuntimeException(v) } + +fun foobar(x: String, y: String) = x + y + +fun box(): String { + var result = "" + + val future = async() { + try { + await(exception("OK")) + } catch (e: Exception) { + result = e.cause?.message!! + } + "56" + } + + future.join() + + if (future.get() != "56") return "fail: ${future.get()}" + + java.lang.Thread.sleep(1000) + + return result +} + +fun async(coroutine c: FutureController.() -> Continuation): CompletableFuture { + val controller = FutureController() + c(controller).resume(Unit) + return controller.future +} + +class FutureController { + val future = CompletableFuture() + + + suspend fun await(f: CompletableFuture, machine: Continuation) { + f.whenComplete { value, throwable -> + try { + if (throwable == null) + machine.resume(value) + else + machine.resumeWithException(throwable) + } catch (e: Exception) { + future.completeExceptionally(e) + } + } + } + + fun handleResult(value: T, c: Continuation) { + future.complete(value) + } + + fun handleException(t: Throwable, c: Continuation) { + future.completeExceptionally(t) + } +} diff --git a/compiler/testData/compileKotlinAgainstKotlin/coroutinesBinary.kt b/compiler/testData/compileKotlinAgainstKotlin/coroutinesBinary.kt new file mode 100644 index 00000000000..82e9cac886b --- /dev/null +++ b/compiler/testData/compileKotlinAgainstKotlin/coroutinesBinary.kt @@ -0,0 +1,24 @@ +// FILE: A.kt +package a +class Controller { + suspend fun suspendHere(x: Continuation) { + x.resume("OK") + } +} + +fun builder(coroutine c: Controller.() -> Continuation) { + c(Controller()).resume(Unit) +} + +// FILE: B.kt +import a.builder + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + } + + return result +} diff --git a/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.kt b/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.kt new file mode 100644 index 00000000000..4914f145f3a --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.kt @@ -0,0 +1,18 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER -ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE -UNUSED_VALUE -UNUSED_VARIABLE +class GenericController { + operator fun handleResult(x: T, c: Continuation) { } + suspend fun await(f: V, machine: Continuation) {} +} + +fun genericBuilder(coroutine c: GenericController.() -> Continuation): T = null!! + +fun foo() { + var result = "" + genericBuilder { + try { + await("") + } catch(e: Exception) { + result = "fail" + } + } +} diff --git a/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.txt b/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.txt new file mode 100644 index 00000000000..42d02b66f14 --- /dev/null +++ b/compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.txt @@ -0,0 +1,13 @@ +package + +public fun foo(): kotlin.Unit +public fun genericBuilder(/*0*/ coroutine c: GenericController.() -> kotlin.coroutines.Continuation): T + +public final class GenericController { + public constructor GenericController() + public final suspend fun await(/*0*/ f: V, /*1*/ machine: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public final operator fun handleResult(/*0*/ x: T, /*1*/ c: kotlin.coroutines.Continuation): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String +} diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java index 9b2e7149d6e..0b2cb9b7655 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java @@ -35,6 +35,18 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/box"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("async.kt") + public void testAsync() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/async.kt"); + doTest(fileName); + } + + @TestMetadata("asyncException.kt") + public void testAsyncException() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/asyncException.kt"); + doTest(fileName); + } + @TestMetadata("defaultMethodCallFromInterface.kt") public void testDefaultMethodCallFromInterface() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/defaultMethodCallFromInterface.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java index 2786846a07b..32e95d1f884 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestGenerated.java @@ -3879,6 +3879,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest { doTest(fileName); } + @TestMetadata("tryCatchLambda.kt") + public void testTryCatchLambda() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/tryCatchLambda.kt"); + doTest(fileName); + } + @TestMetadata("wrongHandleResult.kt") public void testWrongHandleResult() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/coroutines/wrongHandleResult.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 20c60db384f..5d1fa6e1019 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -4103,6 +4103,57 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/coroutines") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Coroutines extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInCoroutines() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("generate.kt") + public void testGenerate() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/generate.kt"); + doTest(fileName); + } + + @TestMetadata("innerSuspensionCalls.kt") + public void testInnerSuspensionCalls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/innerSuspensionCalls.kt"); + doTest(fileName); + } + + @TestMetadata("returnByLabel.kt") + public void testReturnByLabel() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/returnByLabel.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/simple.kt"); + doTest(fileName); + } + + @TestMetadata("simpleException.kt") + public void testSimpleException() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/simpleException.kt"); + doTest(fileName); + } + + @TestMetadata("simpleWithHandleResult.kt") + public void testSimpleWithHandleResult() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt"); + doTest(fileName); + } + + @TestMetadata("suspendInCycle.kt") + public void testSuspendInCycle() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/suspendInCycle.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/dataClasses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java index 4b8d40055e9..176ec7d93c1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java @@ -77,6 +77,12 @@ public class CompileKotlinAgainstKotlinTestGenerated extends AbstractCompileKotl doTest(fileName); } + @TestMetadata("coroutinesBinary.kt") + public void testCoroutinesBinary() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/compileKotlinAgainstKotlin/coroutinesBinary.kt"); + doTest(fileName); + } + @TestMetadata("defaultConstructor.kt") public void testDefaultConstructor() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/compileKotlinAgainstKotlin/defaultConstructor.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/ValueParameterDescriptor.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/ValueParameterDescriptor.kt index ec2dd46c99c..6f35f39077c 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/ValueParameterDescriptor.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/ValueParameterDescriptor.kt @@ -42,7 +42,7 @@ interface ValueParameterDescriptor : VariableDescriptor, ParameterDescriptor { override fun substitute(substitutor: TypeSubstitutor): ValueParameterDescriptor - fun copy(newOwner: CallableDescriptor, newName: Name): ValueParameterDescriptor + fun copy(newOwner: CallableDescriptor, newName: Name, newIndex: Int): ValueParameterDescriptor /** * Parameter p1 overrides p2 iff diff --git a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ValueParameterDescriptorImpl.kt b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ValueParameterDescriptorImpl.kt index b59a427c00e..ea27d0f16bf 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ValueParameterDescriptorImpl.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/descriptors/impl/ValueParameterDescriptorImpl.kt @@ -58,10 +58,9 @@ class ValueParameterDescriptorImpl( override fun isVar() = false override fun getCompileTimeInitializer() = null - - override fun copy(newOwner: CallableDescriptor, newName: Name): ValueParameterDescriptor { + override fun copy(newOwner: CallableDescriptor, newName: Name, newIndex: Int): ValueParameterDescriptor { return ValueParameterDescriptorImpl( - newOwner, null, index, annotations, newName, type, declaresDefaultValue(), + newOwner, null, newIndex, annotations, newName, type, declaresDefaultValue(), isCrossinline, isNoinline, isCoroutine, varargElementType, SourceElement.NO_SOURCE ) } diff --git a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt index 4de4f0254a8..063d9adc1b2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt +++ b/idea/src/org/jetbrains/kotlin/idea/quickfix/ChangeMemberFunctionSignatureFix.kt @@ -259,7 +259,7 @@ class ChangeMemberFunctionSignatureFix private constructor( override fun choose(parameter: ValueParameterDescriptor, superParameter: ValueParameterDescriptor): ValueParameterDescriptor? { // TODO: support for generic functions if (KotlinTypeChecker.DEFAULT.equalTypes(parameter.type, superParameter.type)) { - return superParameter.copy(parameter.containingDeclaration, parameter.name) + return superParameter.copy(parameter.containingDeclaration, parameter.name, parameter.index) } else { return null