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 ddc5fc3e414..ae7ffae8b83 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -207,6 +207,14 @@ class GenerationState private constructor( configuration.get(JVMConfigurationKeys.STRING_CONCAT) ?: JvmStringConcat.INLINE else JvmStringConcat.INLINE + val samConversionsScheme = run { + val fromConfig = configuration.get(JVMConfigurationKeys.SAM_CONVERSIONS) ?: JvmSamConversions.DEFAULT + if (target >= fromConfig.minJvmTarget) + fromConfig + else + JvmSamConversions.DEFAULT + } + val moduleName: String = moduleName ?: JvmCodegenUtil.getModuleName(module) val classBuilderMode: ClassBuilderMode = builderFactory.classBuilderMode val bindingTrace: BindingTrace = DelegatingBindingTrace( diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index b31275c493d..cd7237ce2ad 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -362,6 +362,15 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var stringConcat: String? by NullableStringFreezableVar(JvmStringConcat.INLINE.description) + @Argument( + value = "-Xsam-conversions", + valueDescription = "{class|indy}", + description = """Select code generation scheme for SAM conversions. +-Xsam-conversions=indy Generate SAM conversions using `invokedynamic` with `LambdaMetafactory.metafactory`. Requires `-jvm-target 8` or greater. +-Xsam-conversions=class Generate SAM conversions as explicit classes""" + ) + var samConversions: String? by NullableStringFreezableVar(JvmSamConversions.CLASS.description) + @Argument( value = "-Xklib", valueDescription = "", diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt index c904fce6bf4..3cbe6cc8780 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/jvmArguments.kt @@ -59,8 +59,28 @@ fun CompilerConfiguration.setupJvmSpecificArguments(arguments: K2JVMCompilerArgu } } else { messageCollector.report( - ERROR, "Unknown -Xstring-concat mode: ${arguments.jvmTarget}\n" + - "Supported versions: ${JvmStringConcat.values().joinToString { it.description }}" + ERROR, "Unknown `-Xstring-concat` mode: ${arguments.stringConcat}\n" + + "Supported modes: ${JvmStringConcat.values().joinToString { it.description }}" + ) + } + } + + if (arguments.samConversions != null) { + val samConversions = JvmSamConversions.fromString(arguments.samConversions) + if (samConversions != null) { + put(JVMConfigurationKeys.SAM_CONVERSIONS, samConversions) + if (jvmTarget < samConversions.minJvmTarget) { + messageCollector.report( + WARNING, + "`-Xsam-conversions=${arguments.samConversions}` requires JVM target at least " + + "${samConversions.minJvmTarget.description} and is ignored." + ) + } + } else { + messageCollector.report( + ERROR, + "Unknown `-Xsam-conversions` argument: ${arguments.samConversions}\n" + + "Supported arguments: ${JvmSamConversions.values().joinToString { it.description }}" ) } } diff --git a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index b8e6870b0a6..b1c11d8f696 100644 --- a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -114,6 +114,9 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey STRING_CONCAT = CompilerConfigurationKey.create("Specifies string concatenation scheme"); + public static final CompilerConfigurationKey SAM_CONVERSIONS = + CompilerConfigurationKey.create("SAM conversions code generation scheme"); + public static final CompilerConfigurationKey> KLIB_PATHS = CompilerConfigurationKey.create("Paths to .klib libraries"); diff --git a/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmSamConversions.kt b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmSamConversions.kt new file mode 100644 index 00000000000..3ed25ea0b7f --- /dev/null +++ b/compiler/config.jvm/src/org/jetbrains/kotlin/config/JvmSamConversions.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.config + +enum class JvmSamConversions( + val description: String, + val minJvmTarget: JvmTarget +) { + CLASS("class", JvmTarget.JVM_1_6), + INDY("indy", JvmTarget.JVM_1_8), + ; + + companion object { + val DEFAULT = CLASS + + @JvmStatic + fun fromString(string: String?) = + values().find { it.description == string } + } +} \ No newline at end of file diff --git a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index 48f5ada576c..ca2c208fc61 100644 --- a/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/analysis-tests/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -18379,6 +18379,74 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT } } + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic") + @TestDataPath("$PROJECT_ROOT") + public class Invokedynamic extends AbstractFirBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInInvokedynamic() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic/sam") + @TestDataPath("$PROJECT_ROOT") + public class Sam extends AbstractFirBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInSam() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic/sam"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("boundReference.kt") + public void testBoundReference() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt"); + } + + @Test + @TestMetadata("capturedDispatchReceiver.kt") + public void testCapturedDispatchReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt"); + } + + @Test + @TestMetadata("capturedExtensionReceiver.kt") + public void testCapturedExtensionReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt"); + } + + @Test + @TestMetadata("capturingIndyFunInterface.kt") + public void testCapturingIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt"); + } + + @Test + @TestMetadata("capturingIndySam.kt") + public void testCapturingIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt"); + } + + @Test + @TestMetadata("primitiveVsWrapperInSam.kt") + public void testPrimitiveVsWrapperInSam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt"); + } + + @Test + @TestMetadata("simpleIndyFunInterface.kt") + public void testSimpleIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt"); + } + + @Test + @TestMetadata("simpleIndySam.kt") + public void testSimpleIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt"); + } + } + } + @Nested @TestMetadata("compiler/testData/codegen/box/ir") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt index 546b0615482..f98360eeb14 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/ir/builders/declarations/declarationBuilders.kt @@ -65,7 +65,11 @@ fun IrClass.addField(fieldName: Name, fieldType: IrType, fieldVisibility: Descri visibility = fieldVisibility } -fun IrClass.addField(fieldName: String, fieldType: IrType, fieldVisibility: DescriptorVisibility = DescriptorVisibilities.PRIVATE): IrField = +fun IrClass.addField( + fieldName: String, + fieldType: IrType, + fieldVisibility: DescriptorVisibility = DescriptorVisibilities.PRIVATE +): IrField = addField(Name.identifier(fieldName), fieldType, fieldVisibility) @PublishedApi @@ -141,16 +145,16 @@ inline fun IrClass.addFunction(builder: IrFunctionBuilder.() -> Unit): IrSimpleF factory.addFunction(this, builder) fun IrClass.addFunction( - name: String, - returnType: IrType, - modality: Modality = Modality.FINAL, - visibility: DescriptorVisibility = DescriptorVisibilities.PUBLIC, - isStatic: Boolean = false, - isSuspend: Boolean = false, - isFakeOverride: Boolean = false, - origin: IrDeclarationOrigin = IrDeclarationOrigin.DEFINED, - startOffset: Int = UNDEFINED_OFFSET, - endOffset: Int = UNDEFINED_OFFSET + name: String, + returnType: IrType, + modality: Modality = Modality.FINAL, + visibility: DescriptorVisibility = DescriptorVisibilities.PUBLIC, + isStatic: Boolean = false, + isSuspend: Boolean = false, + isFakeOverride: Boolean = false, + origin: IrDeclarationOrigin = IrDeclarationOrigin.DEFINED, + startOffset: Int = UNDEFINED_OFFSET, + endOffset: Int = UNDEFINED_OFFSET ): IrSimpleFunction = addFunction { this.startOffset = startOffset @@ -216,7 +220,7 @@ internal fun IrFactory.buildValueParameter(builder: IrValueParameterBuilder, par inline fun buildValueParameter(declaration: D, builder: IrValueParameterBuilder.() -> Unit): IrValueParameter - where D : IrDeclaration, D : IrDeclarationParent = + where D : IrDeclaration, D : IrDeclarationParent = IrValueParameterBuilder().run { builder() declaration.factory.buildValueParameter(this, declaration) @@ -234,8 +238,11 @@ inline fun IrFunction.addValueParameter(builder: IrValueParameterBuilder.() -> U } fun IrFunction.addValueParameter(name: String, type: IrType, origin: IrDeclarationOrigin = IrDeclarationOrigin.DEFINED): IrValueParameter = + addValueParameter(Name.identifier(name), type, origin) + +fun IrFunction.addValueParameter(name: Name, type: IrType, origin: IrDeclarationOrigin = IrDeclarationOrigin.DEFINED): IrValueParameter = addValueParameter { - this.name = Name.identifier(name) + this.name = name this.type = type this.origin = origin } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt index d1e7d1e1558..af7fa624ab6 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt @@ -46,4 +46,5 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin { object COMPANION_PROPERTY_BACKING_FIELD : IrDeclarationOriginImpl("COMPANION_PROPERTY_BACKING_FIELD") object FIELD_FOR_STATIC_CALLABLE_REFERENCE_INSTANCE : IrDeclarationOriginImpl("FIELD_FOR_STATIC_CALLABLE_REFERENCE_INSTANCE") object ABSTRACT_BRIDGE_STUB : IrDeclarationOriginImpl("ABSTRACT_BRIDGE_STUB") + object INVOVEDYNAMIC_CALL_TARGET : IrDeclarationOriginImpl("INVOVEDYNAMIC_CALL_TARGET") } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index 2e1c2c6131b..ba61122d72f 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.backend.jvm import org.jetbrains.kotlin.backend.common.ir.Symbols -import org.jetbrains.kotlin.backend.common.ir.addChild import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods import org.jetbrains.kotlin.builtins.StandardNames @@ -51,6 +50,17 @@ class JvmSymbols( private val kotlinReflectPackage: IrPackageFragment = createPackage(FqName("kotlin.reflect")) private val javaLangPackage: IrPackageFragment = createPackage(FqName("java.lang")) + // Special package for functions representing dynamic symbols referenced by 'INVOKEDYNAMIC' instruction - e.g., + // 'get(Ljava/lang/String;)Ljava/util/function/Supplier;' + // in + // INVOKEDYNAMIC get(Ljava/lang/String;)Ljava/util/function/Supplier; [ + // H_INVOKESTATIC java/lang/invoke/LambdaMetafactory.metafactory(...)Ljava/lang/invoke/CallSite; + // ... + // ] + // Such functions don't exist as methods in the actual bytecode + // (they are expected to be provided at run-time by the corresponding bootstrap method). + val kotlinJvmInternalInvokeDynamicPackage: IrPackageFragment = createPackage(FqName("kotlin.jvm.internal.invokeDynamic")) + private val generateOptimizedCallableReferenceSuperClasses = context.state.generateOptimizedCallableReferenceSuperClasses private fun createPackage(fqName: FqName): IrPackageFragment = @@ -535,6 +545,82 @@ class JvmSymbols( returnType = dst.defaultType }.symbol + val indySamConversionIntrinsic: IrSimpleFunctionSymbol = + irFactory.buildFun { + name = Name.special("") + origin = IrDeclarationOrigin.IR_BUILTINS_STUB + }.apply { + parent = kotlinJvmInternalPackage + val samType = addTypeParameter("SAM_TYPE", irBuiltIns.anyType) + addValueParameter("method", irBuiltIns.anyNType) + returnType = samType.defaultType + }.symbol + + val arrayOfAnyType = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyType) + + // Intrinsic to represent INVOKEDYNAMIC calls in IR. + // fun ``( + // dynamicCall: T, + // bootstrapMethodTag: Int, + // bootstrapMethodOwner: String, + // bootstrapMethodName: String, + // bootstrapMethodDesc: String, + // vararg bootstrapMethodArgs: Any + // ): T + // Bootstrap method handle is encoded as a bunch of constants. + // For example, + // REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:( + // Ljava/lang/invoke/MethodHandles$Lookup; + // Ljava/lang/String; + // Ljava/lang/invoke/MethodType; + // Ljava/lang/invoke/MethodType; + // Ljava/lang/invoke/MethodHandle; + // Ljava/lang/invoke/MethodType; + // )Ljava/lang/invoke/CallSite; + // is represented as + // bootstrapMethodTag: IrConst(value = H_INVOKESTATIC) + // bootstrapMethodOwner: IrConst(value = "java/lang/invoke/LambdaMetafactory") + // bootstrapMethodName: IrConst(value = "metafactory") + // bootstrapMethodDesc: IrConst(value = "(...)Ljava/lang/invoke/CallSite;") + // Bootstrap method owner is assumed to be a class (which is true for both LambdaMetafactory and StringConcatFactory). + val jvmIndyIntrinsic: IrSimpleFunctionSymbol = + irFactory.buildFun { + name = Name.special("") + origin = IrDeclarationOrigin.IR_BUILTINS_STUB + }.apply { + parent = kotlinJvmInternalPackage + val t = addTypeParameter("T", irBuiltIns.anyNType) + addValueParameter("dynamicCall", t.defaultType) + addValueParameter("bootstrapMethodTag", irBuiltIns.intType) + addValueParameter("bootstrapMethodOwner", irBuiltIns.stringType) + addValueParameter("bootstrapMethodName", irBuiltIns.stringType) + addValueParameter("bootstrapMethodDesc", irBuiltIns.stringType) + addValueParameter { + name = Name.identifier("bootstrapMethodArguments") + type = arrayOfAnyType + varargElementType = irBuiltIns.anyType + } + returnType = t.defaultType + }.symbol + + // Intrinsic used to represent MethodType objects in bootstrap method arguments (see jvmInvokeDynamicIntrinsic above). + // Type argument is a possibly substituted method owner type (e.g., 'java.lang.function.Supplier'). + // Value argument is a raw function reference to a corresponding method (e.g., 'java.lang.function.Supplier#get'). + val jvmMethodTypeIntrinsic: IrSimpleFunctionSymbol = + irFactory.buildFun { + name = Name.special("") + origin = IrDeclarationOrigin.IR_BUILTINS_STUB + }.apply { + parent = kotlinJvmInternalPackage + addTypeParameter("OwnerT", irBuiltIns.anyType) + addValueParameter("method", irBuiltIns.anyType) + returnType = irBuiltIns.anyType + }.symbol + + val flexibleNullabilityAnnotationFqName = JvmGeneratorExtensions.FLEXIBLE_NULLABILITY_ANNOTATION_FQ_NAME + val enhancedNullabilityAnnotationFqName = JvmGeneratorExtensions.ENHANCED_NULLABILITY_ANNOTATION_FQ_NAME + val rawTypeAnnotationFQN = JvmGeneratorExtensions.RAW_TYPE_ANNOTATION_FQ_NAME + private val collectionToArrayClass: IrClassSymbol = createClass(FqName("kotlin.jvm.internal.CollectionToArray")) { klass -> klass.origin = JvmLoweredDeclarationOrigin.TO_ARRAY diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 67d2bdb3462..70dafe7c487 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -418,7 +418,7 @@ class ExpressionCodegen( val callee = expression.symbol.owner require(callee.parent is IrClass) { "Unhandled intrinsic in ExpressionCodegen: ${callee.render()}" } - val callable = methodSignatureMapper.mapToCallableMethod(irFunction, expression) + val callable = methodSignatureMapper.mapToCallableMethod(expression, irFunction) val callGenerator = getOrCreateCallGenerator(expression, data, callable.signature) val isSuspensionPoint = expression.isSuspensionPoint() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt index f8b66fff13f..30b0ad3cb90 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/MethodSignatureMapper.kt @@ -366,7 +366,8 @@ class MethodSignatureMapper(private val context: JvmBackendContext) { if (valueArgumentsCount > 0) (getValueArgument(0) as? IrConst<*>)?.value as? Boolean ?: true else null } - fun mapToCallableMethod(caller: IrFunction, expression: IrCall): IrCallableMethod { + // TODO get rid of 'caller' argument + internal fun mapToCallableMethod(expression: IrCall, caller: IrFunction?): IrCallableMethod { val callee = expression.symbol.owner val calleeParent = expression.superQualifierSymbol?.owner ?: expression.dispatchReceiver?.type?.classOrNull?.owner @@ -385,17 +386,21 @@ class MethodSignatureMapper(private val context: JvmBackendContext) { } val declaration = findSuperDeclaration(callee, isSuperCall) - val signature = mapOverriddenSpecialBuiltinIfNeeded(caller, declaration, isSuperCall) - ?: mapSignatureSkipGeneric(declaration) + val signature = + if (caller != null && caller.isBridge()) { + // Do not remap special builtin methods when called from a bridge. The bridges are there to provide the + // remapped name or signature and forward to the actually declared method. + mapSignatureSkipGeneric(declaration) + } else { + mapOverriddenSpecialBuiltinIfNeeded(declaration, isSuperCall) + ?: mapSignatureSkipGeneric(declaration) + } return IrCallableMethod(owner, invokeOpcode, signature, isInterface) } // TODO: get rid of this (probably via some special lowering) - private fun mapOverriddenSpecialBuiltinIfNeeded(caller: IrFunction, callee: IrFunction, superCall: Boolean): JvmMethodSignature? { - // Do not remap special builtin methods when called from a bridge. The bridges are there to provide the - // remapped name or signature and forward to the actually declared method. - if (caller.isBridge()) return null + private fun mapOverriddenSpecialBuiltinIfNeeded(callee: IrFunction, superCall: Boolean): JvmMethodSignature? { // Do not remap calls to static replacements of inline class methods, since they have completely different signatures. if (callee.isStaticInlineClassReplacement) return null val overriddenSpecialBuiltinFunction = diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt index 0b7e992bdfd..bce051b46e4 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt @@ -120,6 +120,7 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) { symbols.throwTypeCastException.toKey()!! to ThrowException(Type.getObjectType("kotlin/TypeCastException")), symbols.throwUnsupportedOperationException.toKey()!! to ThrowException(Type.getObjectType("java/lang/UnsupportedOperationException")), symbols.throwKotlinNothingValueException.toKey()!! to ThrowKotlinNothingValueException, + symbols.jvmIndyIntrinsic.toKey()!! to JvmInvokeDynamic ) + numberConversionMethods() + unaryFunForPrimitives("plus", UnaryPlus) + diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JvmInvokeDynamic.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JvmInvokeDynamic.kt new file mode 100644 index 00000000000..f00c00d6f93 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/JvmInvokeDynamic.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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.backend.jvm.intrinsics + +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.codegen.* +import org.jetbrains.kotlin.codegen.inline.v +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.util.dump +import org.jetbrains.kotlin.ir.util.parentAsClass +import org.jetbrains.kotlin.ir.util.render +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.org.objectweb.asm.Handle +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type + +object JvmInvokeDynamic : IntrinsicMethod() { + override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue { + fun fail(message: String): Nothing = + throw AssertionError("$message; expression:\n${expression.dump()}") + + val dynamicCall = expression.getValueArgument(0) as? IrCall + ?: fail("'dynamicCall' is expected to be a call") + val dynamicCallee = dynamicCall.symbol.owner + if (dynamicCallee.parent != codegen.context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage || + dynamicCallee.origin != JvmLoweredDeclarationOrigin.INVOVEDYNAMIC_CALL_TARGET + ) + fail("Unexpected dynamicCallee: '${dynamicCallee.render()}'") + + val bootstrapMethodTag = expression.getValueArgument(1)?.getIntConst() + ?: fail("'bootstrapMethodTag' is expected to be an int const") + val bootstrapMethodOwner = expression.getValueArgument(2)?.getStringConst() + ?: fail("'bootstrapMethodOwner' is expected to be a string const") + val bootstrapMethodName = expression.getValueArgument(3)?.getStringConst() + ?: fail("'bootstrapMethodName' is expected to be a string const") + val bootstrapMethodDesc = expression.getValueArgument(4)?.getStringConst() + ?: fail("'bootstrapMethodDesc' is expected to be a string const") + val bootstrapMethodArgs = (expression.getValueArgument(5)?.safeAs() + ?: fail("'bootstrapMethodArgs' is expected to be a vararg")) + + val asmBootstrapMethodArgs = bootstrapMethodArgs.elements + .map { generateBootstrapMethodArg(it, codegen) } + .toTypedArray() + + val dynamicCalleeMethod = codegen.methodSignatureMapper.mapAsmMethod(dynamicCallee) + val bootstrapMethodHandle = Handle(bootstrapMethodTag, bootstrapMethodOwner, bootstrapMethodName, bootstrapMethodDesc, false) + + val dynamicCallGenerator = IrCallGenerator.DefaultCallGenerator + val dynamicCalleeArgumentTypes = dynamicCalleeMethod.argumentTypes + for (i in dynamicCallee.valueParameters.indices) { + val dynamicCalleeParameter = dynamicCallee.valueParameters[i] + val dynamicCalleeArgument = dynamicCall.getValueArgument(i) + ?: fail("No argument #$i in 'dynamicCall'") + val dynamicCalleeArgumentType = dynamicCalleeArgumentTypes.getOrElse(i) { + fail("No argument type #$i in dynamic callee: $dynamicCalleeMethod") + } + dynamicCallGenerator.genValueAndPut(dynamicCalleeParameter, dynamicCalleeArgument, dynamicCalleeArgumentType, codegen, data) + } + + codegen.v.invokedynamic(dynamicCalleeMethod.name, dynamicCalleeMethod.descriptor, bootstrapMethodHandle, asmBootstrapMethodArgs) + + return MaterialValue(codegen, dynamicCalleeMethod.returnType, expression.type) + } + + private fun generateBootstrapMethodArg(element: IrVarargElement, codegen: ExpressionCodegen): Any = + when (element) { + is IrRawFunctionReference -> + generateMethodHandle(element, codegen) + is IrCall -> + when (element.symbol) { + codegen.context.ir.symbols.jvmMethodTypeIntrinsic -> + generateMethodType(element, codegen) + else -> + throw AssertionError("Unexpected callee in bootstrap method argument:\n${element.dump()}") + } + is IrConst<*> -> + when (element.kind) { + IrConstKind.Byte -> (element.value as Byte).toInt() + IrConstKind.Short -> (element.value as Short).toInt() + IrConstKind.Int -> element.value as Int + IrConstKind.Long -> element.value as Long + IrConstKind.Float -> element.value as Float + IrConstKind.Double -> element.value as Double + IrConstKind.String -> element.value as String + else -> + throw AssertionError("Unexpected constant expression in bootstrap method argument:\n${element.dump()}") + } + else -> + throw AssertionError("Unexpected bootstrap method argument:\n${element.dump()}") + } + + private fun generateMethodHandle(irRawFunctionReference: IrRawFunctionReference, codegen: ExpressionCodegen): Any { + val irFun = irRawFunctionReference.symbol.owner + val irParentClass = irFun.parentAsClass + val owner = codegen.typeMapper.mapOwner(irParentClass) + val asmMethod = codegen.methodSignatureMapper.mapAsmMethod(irFun) + val handleTag = when { + irFun.dispatchReceiverParameter == null -> + Opcodes.H_INVOKESTATIC + else -> + Opcodes.H_INVOKEVIRTUAL + } + return Handle(handleTag, owner.internalName, asmMethod.name, asmMethod.descriptor, irParentClass.isJvmInterface) + } + + private fun generateMethodType(jvmMethodTypeCall: IrCall, codegen: ExpressionCodegen): Any { + val irRawFunctionReference = jvmMethodTypeCall.getValueArgument(0) as? IrRawFunctionReference + ?: throw AssertionError( + "Argument in ${jvmMethodTypeCall.symbol.owner.name} call is expected to be a raw function reference:\n" + + jvmMethodTypeCall.dump() + ) + val irFun = irRawFunctionReference.symbol.owner + // TODO substitute signature + val asmMethod = codegen.methodSignatureMapper.mapAsmMethod(irFun) + return Type.getMethodType(asmMethod.descriptor) + } + + private fun IrExpression.getIntConst() = + if (this is IrConst<*> && kind == IrConstKind.Int) + this.value as Int + else + null + + private fun IrExpression.getStringConst() = + if (this is IrConst<*> && kind == IrConstKind.String) + this.value as String + else + null + +} \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt index 88f30ab34a9..5ff5c96c21c 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/FunctionReferenceLowering.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.ir.* import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi +import org.jetbrains.kotlin.config.JvmSamConversions import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET @@ -21,10 +22,7 @@ import org.jetbrains.kotlin.ir.builders.* import org.jetbrains.kotlin.ir.builders.declarations.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* -import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl -import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.util.* @@ -80,6 +78,9 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) FunctionReferenceBuilder(expression).build() } + private val shouldUseIndySamConversions = + context.state.samConversionsScheme == JvmSamConversions.INDY + // Handle SAM conversions which wrap a function reference: // class sam$n(private val receiver: R) : Interface { override fun method(...) = receiver.target(...) } // @@ -87,20 +88,66 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext) // This is actually very common, as `Interface { something }` is a local function + a SAM-conversion // of a reference to it into an implementation. override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { - if (expression.operator == IrTypeOperator.SAM_CONVERSION) { - val invokable = expression.argument - val reference = if (invokable is IrFunctionReference) { - invokable - } else if (invokable is IrBlock && invokable.origin.isLambda && invokable.statements.last() is IrFunctionReference) { - invokable.statements.dropLast(1).forEach { it.transform(this, null) } - invokable.statements.last() as IrFunctionReference - } else { - return super.visitTypeOperator(expression) - } - reference.transformChildrenVoid() - return FunctionReferenceBuilder(reference, expression.typeOperand).build() + if (expression.operator != IrTypeOperator.SAM_CONVERSION) { + return super.visitTypeOperator(expression) + } + + val invokable = expression.argument + val reference = if (invokable is IrFunctionReference) { + invokable + } else if (invokable is IrBlock && invokable.origin.isLambda && invokable.statements.last() is IrFunctionReference) { + invokable.statements.dropLast(1).forEach { it.transform(this, null) } + invokable.statements.last() as IrFunctionReference + } else { + return super.visitTypeOperator(expression) + } + reference.transformChildrenVoid() + + return if (shouldUseIndySamConversions) { + wrapSamConversionArgumentWithIndySamConversion(expression) + } else { + FunctionReferenceBuilder(reference, expression.typeOperand).build() + } + } + + private fun wrapSamConversionArgumentWithIndySamConversion(expression: IrTypeOperatorCall): IrExpression { + return when (val argument = expression.argument) { + is IrFunctionReference -> + wrapWithIndySamConversion(expression.typeOperand, argument) + is IrBlock -> { + val last = argument.statements.last() + val functionReference = last as? IrFunctionReference + ?: throw AssertionError("Function reference expected: ${last.render()}") + argument.statements[argument.statements.size - 1] = wrapWithIndySamConversion(expression.typeOperand, functionReference) + return argument + } + else -> throw AssertionError("Block or function reference expected: ${expression.render()}") + } + } + + private val jvmIndySamConversionIntrinsic = context.ir.symbols.indySamConversionIntrinsic + + private val specialNullabilityAnnotationsFqNames = + setOf( + context.ir.symbols.flexibleNullabilityAnnotationFqName, + context.ir.symbols.enhancedNullabilityAnnotationFqName + ) + + private fun wrapWithIndySamConversion(samType: IrType, irFunRef: IrFunctionReference): IrCall { + val notNullSamType = samType.makeNotNull() + .removeAnnotations { it.type.classFqName in specialNullabilityAnnotationsFqNames } + return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol).run { + // We should produce the following expression: + // ``(method) + // where: + // - 'samType' is a substituted SAM type; + // - 'method' is a function reference to the actual method we are going to call + // (note that we need an IrFunctionReference here, so that further transformations would extract closure properly). + irCall(jvmIndySamConversionIntrinsic, notNullSamType).apply { + putTypeArgument(0, notNullSamType) + putValueArgument(0, irFunRef) + } } - return super.visitTypeOperator(expression) } private inner class FunctionReferenceBuilder(val irFunctionReference: IrFunctionReference, val samSuperType: IrType? = null) { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDeclarationsThatWouldBeInlinedLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDeclarationsThatWouldBeInlinedLowering.kt index c48cfe9f712..c3637e0a17f 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDeclarationsThatWouldBeInlinedLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/RemoveDeclarationsThatWouldBeInlinedLowering.kt @@ -15,8 +15,6 @@ import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.expressions.IrFunctionReference -import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin -import org.jetbrains.kotlin.ir.util.isLambda import org.jetbrains.kotlin.ir.visitors.* internal val removeDeclarationsThatWouldBeInlined = makeIrModulePhase( diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt index e6400d94e35..e5e20b37442 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/TypeOperatorLowering.kt @@ -11,26 +11,33 @@ import org.jetbrains.kotlin.backend.common.lower.at import org.jetbrains.kotlin.backend.common.lower.irNot import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.codegen.fileParent +import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound +import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.builders.* -import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin -import org.jetbrains.kotlin.ir.declarations.IrFile -import org.jetbrains.kotlin.ir.declarations.IrFunction +import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter +import org.jetbrains.kotlin.ir.builders.declarations.buildFun +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol import org.jetbrains.kotlin.ir.types.* +import org.jetbrains.kotlin.ir.util.dump +import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.isInlined import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.org.objectweb.asm.Handle +import org.jetbrains.org.objectweb.asm.Opcodes // After this pass runs there are only four kinds of IrTypeOperatorCalls left: // @@ -95,6 +102,175 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil builder.irAs(argument, type) } + private val indySamConversionIntrinsic = context.ir.symbols.indySamConversionIntrinsic + + private val indyIntrinsic = context.ir.symbols.jvmIndyIntrinsic + + private fun IrBuilderWithScope.jvmInvokeDynamic( + dynamicCall: IrCall, + bootstrapMethod: Handle, + bootstrapMethodArguments: List + ) = + irCall(indyIntrinsic, dynamicCall.type).apply { + putTypeArgument(0, dynamicCall.type) + putValueArgument(0, dynamicCall) + putValueArgument(1, irInt(bootstrapMethod.tag)) + putValueArgument(2, irString(bootstrapMethod.owner)) + putValueArgument(3, irString(bootstrapMethod.name)) + putValueArgument(4, irString(bootstrapMethod.desc)) + putValueArgument(5, irVararg(context.irBuiltIns.anyType, bootstrapMethodArguments)) + } + + private val methodTypeIntrinsic = context.ir.symbols.jvmMethodTypeIntrinsic + + private fun IrBuilderWithScope.jvmMethodType(ownerType: IrType, methodSymbol: IrFunctionSymbol) = + irCall(methodTypeIntrinsic, context.irBuiltIns.anyType).apply { + putTypeArgument(0, ownerType) + putValueArgument(0, irRawFunctionReferefence(context.irBuiltIns.anyType, methodSymbol)) + } + + private val lambdaMetafactoryHandle = + Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + "metafactory", + "(" + + "Ljava/lang/invoke/MethodHandles\$Lookup;" + + "Ljava/lang/String;" + + "Ljava/lang/invoke/MethodType;" + + "Ljava/lang/invoke/MethodType;" + + "Ljava/lang/invoke/MethodHandle;" + + "Ljava/lang/invoke/MethodType;" + + ")Ljava/lang/invoke/CallSite;", + false + ) + + override fun visitCall(expression: IrCall): IrExpression { + return when (expression.symbol) { + indySamConversionIntrinsic -> updateIndySamConversionIntrinsicCall(expression) + else -> super.visitCall(expression) + } + } + + /** + * @see FunctionReferenceLowering.wrapWithIndySamConversion + */ + private fun updateIndySamConversionIntrinsicCall(call: IrCall): IrCall { + fun fail(message: String): Nothing = + throw AssertionError("$message, call:\n${call.dump()}") + + // We expect: + // ``(method) + // where + // - 'samType' is a substituted SAM type; + // - 'method' is an IrFunctionReference to an actual method that should be called, + // with arguments captured by closure stored as function reference arguments. + // We replace it with JVM INVOKEDYNAMIC intrinsic. + + val startOffset = call.startOffset + val endOffset = call.endOffset + + val samType = call.getTypeArgument(0) as? IrSimpleType + ?: fail("'samType' is expected to be a simple type") + val samClassSymbol = samType.classOrNull + ?: fail("'samType' is expected to be a class type: '${samType.render()}'") + val samMethod = samClassSymbol.owner.functions.singleOrNull { it.modality == Modality.ABSTRACT } + ?: fail("'${samType.render()}' is not a SAM-type") + + val irFunRef = call.getValueArgument(0) as? IrFunctionReference + ?: fail("'method' is expected to be 'IrFunctionReference'") + val funSymbol = irFunRef.symbol + + val erasedSamType = samClassSymbol.defaultType as IrSimpleType + val dynamicCall = wrapClosureInDynamicCall(erasedSamType, samMethod, irFunRef) + + return context.createJvmIrBuilder( + funSymbol, // TODO actual symbol for outer scope + startOffset, endOffset + ).run { + val samMethodType = jvmMethodType(erasedSamType, samMethod.symbol) + val irRawFunRef = irRawFunctionReferefence(irFunRef.type, funSymbol) + val instanceMethodType = jvmMethodType(samType, samMethod.symbol) + + jvmInvokeDynamic( + dynamicCall, + lambdaMetafactoryHandle, + listOf(samMethodType, irRawFunRef, instanceMethodType) + ) + } + } + + private fun wrapClosureInDynamicCall( + erasedSamType: IrSimpleType, + samMethod: IrSimpleFunction, + irFunRef: IrFunctionReference + ): IrCall { + fun fail(message: String): Nothing = + throw AssertionError("$message, irFunRef:\n${irFunRef.dump()}") + + val dynamicCallArguments = ArrayList() + + val irDynamicCallTarget = context.irFactory.buildFun { + origin = JvmLoweredDeclarationOrigin.INVOVEDYNAMIC_CALL_TARGET + name = samMethod.name + returnType = erasedSamType + }.apply { + parent = context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage + + var syntheticParameterIndex = 0 + val targetFun = irFunRef.symbol.owner + + val targetDispatchReceiverParameter = targetFun.dispatchReceiverParameter + if (targetDispatchReceiverParameter != null) { + addValueParameter("p${syntheticParameterIndex++}", targetDispatchReceiverParameter.type) + val dispatchReceiver = irFunRef.dispatchReceiver + ?: fail("Captured dispatch receiver is not provided") + dynamicCallArguments.add(dispatchReceiver) + } + + val targetExtensionReceiverParameter = targetFun.extensionReceiverParameter + if (targetExtensionReceiverParameter != null) { + addValueParameter("p${syntheticParameterIndex++}", targetExtensionReceiverParameter.type) + val extensionReceiver = irFunRef.extensionReceiver + ?: fail("Captured extension receiver is not provided") + dynamicCallArguments.add(extensionReceiver) + } + + val samMethodValueParametersCount = samMethod.valueParameters.size + val targetFunValueParametersCount = targetFun.valueParameters.size + for (i in 0 until targetFunValueParametersCount - samMethodValueParametersCount) { + val targetFunValueParameter = targetFun.valueParameters[i] + addValueParameter("p${syntheticParameterIndex++}", targetFunValueParameter.type) + val capturedValueArgument = irFunRef.getValueArgument(i) + ?: fail("Captured value argument #$i (${targetFunValueParameter.name} not provided") + dynamicCallArguments.add(capturedValueArgument) + } + } + + if (dynamicCallArguments.size != irDynamicCallTarget.valueParameters.size) { + throw AssertionError( + "Dynamic call target value parameters (${irDynamicCallTarget.valueParameters.size}) " + + "don't match dynamic call arguments (${dynamicCallArguments.size}):\n" + + "irDynamicCallTarget:\n" + + irDynamicCallTarget.dump() + + "dynamicCallArguments:\n" + + dynamicCallArguments + .withIndex() + .joinToString(separator = "\n ", prefix = "[\n ", postfix = "\n]") { (index, irArg) -> + "#$index: ${irArg.dump()}" + } + ) + } + + return context.createJvmIrBuilder(irDynamicCallTarget.symbol) + .irCall(irDynamicCallTarget.symbol) + .apply { + for (i in dynamicCallArguments.indices) { + putValueArgument(i, dynamicCallArguments[i]) + } + } + } + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression = with(builder) { at(expression) return when (expression.operator) { diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt index f76b6a974f5..b8696213ed5 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/builders/ExpressionHelpers.kt @@ -11,6 +11,7 @@ import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.typeWith import org.jetbrains.kotlin.ir.util.isImmutable import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.util.render @@ -326,6 +327,11 @@ fun IrBuilderWithScope.irString(value: String) = fun IrBuilderWithScope.irConcat() = IrStringConcatenationImpl(startOffset, endOffset, context.irBuiltIns.stringType) +fun IrBuilderWithScope.irVararg(elementType: IrType, values: List) = + IrVarargImpl(startOffset, endOffset, context.irBuiltIns.arrayClass.typeWith(elementType), elementType, values) + +fun IrBuilderWithScope.irRawFunctionReferefence(type: IrType, symbol: IrFunctionSymbol) = + IrRawFunctionReferenceImpl(startOffset, endOffset, type, symbol) inline fun IrBuilderWithScope.irBlock( startOffset: Int = this.startOffset, diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt index fd6bd60ec17..d796be3d76c 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/util/RenderIrElement.kt @@ -645,6 +645,9 @@ class RenderIrElementVisitor(private val normalizeNames: Boolean = false) : IrEl "type=${expression.type.render()} origin=${expression.origin} " + "reflectionTarget=${renderReflectionTarget(expression)}" + override fun visitRawFunctionReference(expression: IrRawFunctionReference, data: Nothing?): String = + "RAW_FUNCTION_REFERENCE '${expression.symbol.renderReference()}' type=${expression.type.render()}" + private fun renderReflectionTarget(expression: IrFunctionReference) = if (expression.symbol == expression.reflectionTarget) "" diff --git a/compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt b/compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt new file mode 100644 index 00000000000..52fc026fe86 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt @@ -0,0 +1,23 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY + +fun interface KRunnable { + fun run() +} + +fun runIt(kr: KRunnable) { + kr.run() +} + +class C(var value: String) { + fun fn() { + value = "OK" + } +} + +fun box(): String { + val c = C("xxx") + runIt(c::fn) + return c.value +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt b/compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt new file mode 100644 index 00000000000..9b019b39a8d --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY + +fun interface KRunnable { + fun run() +} + +fun runIt(kr: KRunnable) { + kr.run() +} + +class C(var value: String) { + fun test(): String { + runIt { value = "OK" } + return value + } +} + +fun box() = C("xxx").test() \ No newline at end of file diff --git a/compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt b/compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt new file mode 100644 index 00000000000..17a5655f0d3 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY + +fun interface KRunnable { + fun run() +} + +fun runIt(kr: KRunnable) { + kr.run() +} + +class C(var value: String) + +fun C.test(): String { + runIt { value = "OK" } + return value +} + +fun box() = C("xxx").test() \ No newline at end of file diff --git a/compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt b/compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt new file mode 100644 index 00000000000..aa1a5512b2f --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt @@ -0,0 +1,20 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY + +fun interface KRunnable { + fun run() +} + +fun runIt(r: KRunnable) { + r.run() +} + +var test = "Failed" + +fun box(): String { + val ok = "OK" + runIt { test = ok } + return test +} + diff --git a/compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt b/compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt new file mode 100644 index 00000000000..e31cdd70d48 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt @@ -0,0 +1,18 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY +// FILE: capturingIndySam.kt +var test = "Failed" + +fun box(): String { + val ok = "OK" + J.run { test = ok } + return test +} + +// FILE: J.java +public class J { + public static void run(Runnable r) { + r.run(); + } +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt b/compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt new file mode 100644 index 00000000000..99f4aa59527 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt @@ -0,0 +1,46 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY +// FILE: primitiveVsWrapperInSam.kt +var test = 0 + +fun tf2(k: Int) { test = k * 10 } + +fun tf4() = 5678 + +fun box(): String { + J.accept42 { k: Int -> test = k } + if (test != 42) return "Failed 1: test=$test" + + J.accept42(::tf2) + if (test != 420) return "Failed 2: test=$test" + + val t3 = J.get { 1234 } + if (t3 != 1234) return "Failed 3: t3=$t3" + + val t4 = J.get(::tf4) + if (t4 != 5678) return "Failed 4: t4=$t4" + + return "OK" +} + +// FILE: J.java +public class J { + public static void accept42(Sam1 sam) { + sam.accept(42); + } + + public static int get(Sam2 sam) { + return sam.get(); + } +} + +// FILE: Sam1.java +public interface Sam1 { + void accept(Integer x); +} + +// FILE: Sam2.java +public interface Sam2 { + Integer get(); +} \ No newline at end of file diff --git a/compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt b/compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt new file mode 100644 index 00000000000..a6ff2988ea1 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt @@ -0,0 +1,18 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY + +fun interface KRunnable { + fun run() +} + +fun runIt(r: KRunnable) { + r.run() +} + +var test = "Failed" + +fun box(): String { + runIt { test = "OK" } + return test +} diff --git a/compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt b/compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt new file mode 100644 index 00000000000..0ced7703cc1 --- /dev/null +++ b/compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt @@ -0,0 +1,17 @@ +// TARGET_BACKEND: JVM +// JVM_TARGET: 1.8 +// SAM_CONVERSIONS: INDY +// FILE: simpleIndySam.kt +var test = "Failed" + +fun box(): String { + J.run { test = "OK" } + return test +} + +// FILE: J.java +public class J { + public static void run(Runnable r) { + r.run(); + } +} \ No newline at end of file diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java index 29dde57cd24..53fb681de35 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/BlackBoxCodegenTestGenerated.java @@ -18379,6 +18379,74 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic") + @TestDataPath("$PROJECT_ROOT") + public class Invokedynamic extends AbstractBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInInvokedynamic() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic/sam") + @TestDataPath("$PROJECT_ROOT") + public class Sam extends AbstractBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInSam() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic/sam"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); + } + + @Test + @TestMetadata("boundReference.kt") + public void testBoundReference() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt"); + } + + @Test + @TestMetadata("capturedDispatchReceiver.kt") + public void testCapturedDispatchReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt"); + } + + @Test + @TestMetadata("capturedExtensionReceiver.kt") + public void testCapturedExtensionReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt"); + } + + @Test + @TestMetadata("capturingIndyFunInterface.kt") + public void testCapturingIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt"); + } + + @Test + @TestMetadata("capturingIndySam.kt") + public void testCapturingIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt"); + } + + @Test + @TestMetadata("primitiveVsWrapperInSam.kt") + public void testPrimitiveVsWrapperInSam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt"); + } + + @Test + @TestMetadata("simpleIndyFunInterface.kt") + public void testSimpleIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt"); + } + + @Test + @TestMetadata("simpleIndySam.kt") + public void testSimpleIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt"); + } + } + } + @Nested @TestMetadata("compiler/testData/codegen/box/ir") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index 18fedd1f27f..32fec2dfbaf 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -18379,6 +18379,74 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes } } + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic") + @TestDataPath("$PROJECT_ROOT") + public class Invokedynamic extends AbstractIrBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInInvokedynamic() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Nested + @TestMetadata("compiler/testData/codegen/box/invokedynamic/sam") + @TestDataPath("$PROJECT_ROOT") + public class Sam extends AbstractIrBlackBoxCodegenTest { + @Test + public void testAllFilesPresentInSam() throws Exception { + KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/invokedynamic/sam"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); + } + + @Test + @TestMetadata("boundReference.kt") + public void testBoundReference() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/boundReference.kt"); + } + + @Test + @TestMetadata("capturedDispatchReceiver.kt") + public void testCapturedDispatchReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedDispatchReceiver.kt"); + } + + @Test + @TestMetadata("capturedExtensionReceiver.kt") + public void testCapturedExtensionReceiver() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturedExtensionReceiver.kt"); + } + + @Test + @TestMetadata("capturingIndyFunInterface.kt") + public void testCapturingIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndyFunInterface.kt"); + } + + @Test + @TestMetadata("capturingIndySam.kt") + public void testCapturingIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/capturingIndySam.kt"); + } + + @Test + @TestMetadata("primitiveVsWrapperInSam.kt") + public void testPrimitiveVsWrapperInSam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/primitiveVsWrapperInSam.kt"); + } + + @Test + @TestMetadata("simpleIndyFunInterface.kt") + public void testSimpleIndyFunInterface() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndyFunInterface.kt"); + } + + @Test + @TestMetadata("simpleIndySam.kt") + public void testSimpleIndySam() throws Exception { + runTest("compiler/testData/codegen/box/invokedynamic/sam/simpleIndySam.kt"); + } + } + } + @Nested @TestMetadata("compiler/testData/codegen/box/ir") @TestDataPath("$PROJECT_ROOT") diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt index dbb639e7e88..620dbdeada9 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.test.directives +import org.jetbrains.kotlin.config.JvmSamConversions import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.backend.handlers.BytecodeTextHandler import org.jetbrains.kotlin.test.backend.handlers.IrPrettyKotlinDumpHandler @@ -90,4 +91,6 @@ object CodegenTestDirectives : SimpleDirectivesContainer() { val TREAT_AS_ONE_FILE by directive( description = "Treat bytecode from all files as one in ${BytecodeTextHandler::class}" ) + + val SAM_CONVERSIONS by enumDirective("SAM conversion code generation scheme") } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/codegen/AbstractJvmBlackBoxCodegenTestBase.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/codegen/AbstractJvmBlackBoxCodegenTestBase.kt index a15425fbfbe..2548fd55c73 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/codegen/AbstractJvmBlackBoxCodegenTestBase.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/codegen/AbstractJvmBlackBoxCodegenTestBase.kt @@ -39,3 +39,14 @@ abstract class AbstractJvmBlackBoxCodegenTestBase