From 9324cf336090deb5292282a512c1c0618a8b0774 Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Fri, 3 Feb 2023 12:23:48 +0000 Subject: [PATCH] [JS IR] Support function type interfaces in incremental cache infrastructure The patch removes logic of generating extra IrFiles (fake file) into IrModuleFragment for the function type interfaces during klib deserialization, because IC infrastructure can not process files which do not exist in klib. Instead of adding extra IrFiles during deserialization, the empty files with required packages are added into Kotlin/JS stdlib physically. These files are used as containers for function type interface declarations. Since Kotlin/WASM uses the same klib loading infrastructure as Kotlin/JS, the the empty files are added into Kotlin/WASM stdlib as well. The patch also adds a check that IrModuleFagment has files only from klib. ^KT-55720 Fixed --- .../kotlin/ir/backend/js/ic/CacheUpdater.kt | 76 ++++++++------- .../kotlin/ir/backend/js/ic/ICUtils.kt | 12 +++ .../ir/backend/js/ic/IdSignatureSource.kt | 39 +++++++- .../ir/backend/js/ic/IncrementalCache.kt | 4 +- .../backend/js/ic/IncrementalCacheArtifact.kt | 2 + .../ir/backend/js/ic/JsIrLinkerLoader.kt | 94 ++++++++++++++++--- .../backend/js/ic/KotlinSourceFileMetadata.kt | 19 ++-- .../FunctionalInterfacesUtils.kt | 6 +- .../js/FunctionTypeInterfacePackages.kt | 46 +++++++++ .../jetbrains/kotlin/ir/backend/js/klib.kt | 19 ++-- .../JsIrES6InvalidationTestGenerated.java | 5 + .../JsIrInvalidationTestGenerated.java | 5 + .../kotlin/js/test/BoxJsTestGenerated.java | 6 ++ .../js/test/fir/FirJsBoxTestGenerated.java | 6 ++ .../js/test/ir/IrBoxJsES6TestGenerated.java | 6 ++ .../js/test/ir/IrBoxJsTestGenerated.java | 6 ++ .../coroutines/suspendFunctionalInterface.kt | 30 ++++++ .../functionalInterface/lib1/l1.0.kt | 3 + .../functionalInterface/lib1/l1.11.kt | 3 + .../functionalInterface/lib1/l1.9.kt | 3 + .../functionalInterface/lib1/module.info | 17 ++++ .../functionalInterface/lib2/l2a.0.kt | 4 + .../functionalInterface/lib2/l2a.1.kt | 4 + .../functionalInterface/lib2/l2a.10.kt | 4 + .../functionalInterface/lib2/l2a.12.kt | 4 + .../functionalInterface/lib2/l2a.13.kt | 5 + .../functionalInterface/lib2/l2a.14.kt | 5 + .../functionalInterface/lib2/l2a.15.kt | 4 + .../functionalInterface/lib2/l2a.2.kt | 4 + .../functionalInterface/lib2/l2a.3.kt | 4 + .../functionalInterface/lib2/l2a.4.kt | 4 + .../functionalInterface/lib2/l2a.6.kt | 4 + .../functionalInterface/lib2/l2a.7.kt | 4 + .../functionalInterface/lib2/l2a.8.kt | 4 + .../functionalInterface/lib2/l2b.0.kt | 3 + .../functionalInterface/lib2/l2b.5.kt | 3 + .../functionalInterface/lib2/module.info | 77 +++++++++++++++ .../functionalInterface/main/m.kt | 26 +++++ .../functionalInterface/main/module.info | 5 + .../functionalInterface/project.info | 20 ++++ .../kotlin-coroutines-package.kt | 10 ++ .../function-type-interface/kotlin-package.kt | 10 ++ .../kotlin-reflect-package.kt | 10 ++ .../kotlin-coroutines-package.kt | 10 ++ .../function-type-interface/kotlin-package.kt | 10 ++ .../kotlin-reflect-package.kt | 10 ++ 46 files changed, 574 insertions(+), 81 deletions(-) create mode 100644 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/FunctionTypeInterfacePackages.kt create mode 100644 js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.0.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.11.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.9.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/module.info create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.0.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.1.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.10.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.12.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.13.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.14.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.15.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.2.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.3.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.4.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.6.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.7.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.8.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.0.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.5.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/module.info create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/main/m.kt create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/main/module.info create mode 100644 js/js.translator/testData/incremental/invalidation/functionalInterface/project.info create mode 100644 libraries/stdlib/js-ir/src/function-type-interface/kotlin-coroutines-package.kt create mode 100644 libraries/stdlib/js-ir/src/function-type-interface/kotlin-package.kt create mode 100644 libraries/stdlib/js-ir/src/function-type-interface/kotlin-reflect-package.kt create mode 100644 libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-coroutines-package.kt create mode 100644 libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-package.kt create mode 100644 libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-reflect-package.kt diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt index 5e932a2ddd3..505755314ad 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/CacheUpdater.kt @@ -9,7 +9,6 @@ import org.jetbrains.kotlin.backend.common.serialization.cityHash64 import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.ir.backend.js.* import org.jetbrains.kotlin.ir.backend.js.codegen.JsGenerationGranularity -import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsIrProgramFragment import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.util.* @@ -241,18 +240,15 @@ class CacheUpdater( } private fun KotlinSourceFileMutableMap.getExportedSignaturesAndAddMetadata( - jsIrLinker: JsIrLinker, - irModule: IrModuleFragment, + symbolProviders: List, libFile: KotlinLibraryFile, dirtySrcFiles: Set ): Map { val idSignatureToFile = hashMapOf() - val moduleDeserializer = jsIrLinker.moduleDeserializer(irModule.descriptor) val incrementalCache = getLibIncrementalCache(libFile) - for (fileDeserializer in moduleDeserializer.fileDeserializers()) { - val reachableSignatures = fileDeserializer.symbolDeserializer.signatureDeserializer.signatureToIndexMapping() - val maybeImportedSignatures = HashSet(reachableSignatures.keys) - val implementedSymbols = collectImplementedSymbol(fileDeserializer.symbolDeserializer.deserializedSymbols) + for (fileSymbolProvider in symbolProviders) { + val maybeImportedSignatures = fileSymbolProvider.getReachableSignatures().toHashSet() + val implementedSymbols = fileSymbolProvider.getImplementedSymbols() for ((signature, symbol) in implementedSymbols) { var symbolCanBeExported = maybeImportedSignatures.remove(signature) resolveFakeOverrideFunction(symbol)?.let { resolvedSignature -> @@ -262,11 +258,11 @@ class CacheUpdater( symbolCanBeExported = true } if (symbolCanBeExported) { - idSignatureToFile[signature] = IdSignatureSource(libFile, fileDeserializer.file, symbol) + idSignatureToFile[signature] = IdSignatureSource(libFile, fileSymbolProvider.irFile, symbol) } } - val libSrcFile = KotlinSourceFile(fileDeserializer.file) + val libSrcFile = KotlinSourceFile(fileSymbolProvider.irFile) if (libSrcFile in dirtySrcFiles) { val metadata = incrementalCache.fetchSourceFileFullMetadata(libSrcFile) this[libFile, libSrcFile] = DirtyFileMetadata(maybeImportedSignatures, metadata.directDependencies) @@ -295,16 +291,16 @@ class CacheUpdater( } fun rebuildDirtySourceMetadata( - jsIrLinker: JsIrLinker, - loadedFragments: Map, + loadedIr: LoadedJsIr, dirtySrcFiles: KotlinSourceFileMap, ): KotlinSourceFileMap { val idSignatureToFile = hashMapOf() val updatedMetadata = KotlinSourceFileMutableMap() - for ((lib, irModule) in loadedFragments) { + for (lib in loadedIr.loadedFragments.keys) { val libDirtySrcFiles = dirtySrcFiles[lib]?.keys ?: emptySet() - idSignatureToFile += updatedMetadata.getExportedSignaturesAndAddMetadata(jsIrLinker, irModule, lib, libDirtySrcFiles) + val symbolProviders = loadedIr.getSignatureProvidersForLib(lib) + idSignatureToFile += updatedMetadata.getExportedSignaturesAndAddMetadata(symbolProviders, lib, libDirtySrcFiles) } signatureHashCalculator.addAllSignatureSymbols(idSignatureToFile) @@ -488,20 +484,17 @@ class CacheUpdater( } fun updateStdlibIntrinsicDependencies( - linker: JsIrLinker, + loadedIr: LoadedJsIr, mainModule: IrModuleFragment, - loadedFragments: Map, dirtyFiles: Map> ) { - val stdlibDescriptor = mainModule.descriptor.builtIns.builtInsModule - val (stdlibFile, stdlibIr) = loadedFragments.entries.find { - it.value.descriptor === stdlibDescriptor - } ?: notFoundIcError("stdlib loaded fragment") - + val (stdlibFile, _) = findStdlib(mainModule, loadedIr.loadedFragments) val stdlibDirtyFiles = dirtyFiles[stdlibFile] ?: return + val stdlibSymbolProviders = loadedIr.getSignatureProvidersForLib(stdlibFile) + val updatedMetadata = KotlinSourceFileMutableMap() - val idSignatureToFile = updatedMetadata.getExportedSignaturesAndAddMetadata(linker, stdlibIr, stdlibFile, stdlibDirtyFiles) + val idSignatureToFile = updatedMetadata.getExportedSignaturesAndAddMetadata(stdlibSymbolProviders, stdlibFile, stdlibDirtyFiles) signatureHashCalculator.addAllSignatureSymbols(idSignatureToFile) @@ -550,10 +543,7 @@ class CacheUpdater( } } - fun buildAndCommitCacheArtifacts( - jsIrLinker: JsIrLinker, - loadedFragments: Map - ): Map { + fun buildAndCommitCacheArtifacts(loadedIr: LoadedJsIr): Map { removedIncrementalCaches.forEach { if (!it.cacheDir.deleteRecursively()) { icError("can not delete cache directory ${it.cacheDir.absolutePath}") @@ -562,13 +552,21 @@ class CacheUpdater( return libraryDependencies.keys.associate { library -> val libFile = KotlinLibraryFile(library) val incrementalCache = getLibIncrementalCache(libFile) - val libFragment = loadedFragments[libFile] ?: notFoundIcError("loaded fragment", libFile) - val moduleDeserializer = jsIrLinker.moduleDeserializer(libFragment.descriptor) + val providers = loadedIr.getSignatureProvidersForLib(libFile) + val signatureToIndexMapping = providers.associate { KotlinSourceFile(it.irFile) to it.getSignatureToIndexMapping() } - val signatureToIndexMapping = moduleDeserializer.fileDeserializers().associate { - KotlinSourceFile(it.file) to it.symbolDeserializer.signatureDeserializer.signatureToIndexMapping() + val cacheArtifact = incrementalCache.buildIncrementalCacheArtifact(signatureToIndexMapping) + + val libFragment = loadedIr.loadedFragments[libFile] ?: notFoundIcError("loaded fragment", libFile) + val sourceFilesFromCache = cacheArtifact.getSourceFiles() + for (irFile in libFragment.files) { + if (KotlinSourceFile(irFile) !in sourceFilesFromCache) { + // IC doesn't support cases when extra IrFiles (which don't exist in klib) are added into IrModuleFragment + icError("file ${irFile.fileEntry.name} is absent in incremental cache and klib", libFile) + } } - libFile to incrementalCache.buildIncrementalCacheArtifact(signatureToIndexMapping) + + libFile to cacheArtifact } } } @@ -642,7 +640,7 @@ class CacheUpdater( while (true) { stopwatch.startNext("Dependencies ($iterations) - updating a dependency graph") - val dirtyMetadata = updater.rebuildDirtySourceMetadata(loadedIr.linker, loadedIr.loadedFragments, lastDirtyFiles) + val dirtyMetadata = updater.rebuildDirtySourceMetadata(loadedIr, lastDirtyFiles) stopwatch.startNext("Dependencies ($iterations) - collecting files with updated exports and imports") val filesWithModifiedExportsOrImports = updater.collectFilesWithModifiedExportsAndImports(dirtyMetadata) @@ -672,15 +670,15 @@ class CacheUpdater( val compilerForIC = compilerInterfaceFactory.createCompilerForIC(mainModuleFragment, compilerConfiguration) // Load declarations referenced during `context` initialization - loadedIr.linker.loadUnboundSymbols() + loadedIr.loadUnboundSymbols() val dirtyFiles = dirtyFileExports.entries.associateTo(HashMap(dirtyFileExports.size)) { it.key to HashSet(it.value.keys) } stopwatch.startNext("Processing IR - updating intrinsics and builtins dependencies") - updater.updateStdlibIntrinsicDependencies(loadedIr.linker, mainModuleFragment, loadedIr.loadedFragments, dirtyFiles) + updater.updateStdlibIntrinsicDependencies(loadedIr, mainModuleFragment, dirtyFiles) stopwatch.startNext("Incremental cache - building artifacts") - val incrementalCachesArtifacts = updater.buildAndCommitCacheArtifacts(loadedIr.linker, loadedIr.loadedFragments) + val incrementalCachesArtifacts = updater.buildAndCommitCacheArtifacts(loadedIr) stopwatch.stop() return IrForDirtyFilesAndCompiler(incrementalCachesArtifacts, loadedIr.loadedFragments, dirtyFiles, compilerForIC) @@ -746,9 +744,9 @@ fun rebuildCacheForDirtyFiles( val modifiedFiles = mapOf(libFile to dirtySrcFiles.associateWith { emptyMetadata }) val jsIrLoader = JsIrLinkerLoader(configuration, dependencyGraph, emptyList(), irFactory) - val (jsIrLinker, irModules) = jsIrLoader.loadIr(KotlinSourceFileMap(modifiedFiles), true) + val loadedIr = jsIrLoader.loadIr(KotlinSourceFileMap(modifiedFiles), true) - val currentIrModule = irModules[libFile] ?: notFoundIcError("loaded fragment", libFile) + val currentIrModule = loadedIr.loadedFragments[libFile] ?: notFoundIcError("loaded fragment", libFile) val dirtyIrFiles = dirtyFiles?.let { val files = it.toSet() currentIrModule.files.filter { irFile -> irFile.fileEntry.name in files } @@ -764,9 +762,9 @@ fun rebuildCacheForDirtyFiles( ) // Load declarations referenced during `context` initialization - jsIrLinker.loadUnboundSymbols() + loadedIr.loadUnboundSymbols() - val fragments = compilerWithIC.compile(irModules.values, dirtyIrFiles, mainArguments).map { it() } + val fragments = compilerWithIC.compile(loadedIr.loadedFragments.values, dirtyIrFiles, mainArguments).map { it() } return currentIrModule to dirtyIrFiles.zip(fragments) } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt index 4e60e2e9424..b8cb667b7b0 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/ICUtils.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.ir.backend.js.ic +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment import org.jetbrains.kotlin.protobuf.CodedInputStream import org.jetbrains.kotlin.protobuf.CodedOutputStream import java.io.File @@ -47,6 +48,17 @@ internal inline fun buildMapUntil(to: Int, builderAction: MutableMap(to).apply { repeat(to) { builderAction(it) } } } +internal fun findStdlib( + mainFragment: IrModuleFragment, + allFragments: Map +): Pair { + val stdlibDescriptor = mainFragment.descriptor.builtIns.builtInsModule + val (stdlibFile, stdlibIr) = allFragments.entries.find { + it.value.descriptor === stdlibDescriptor + } ?: notFoundIcError("stdlib fragment") + return stdlibFile to stdlibIr +} + internal class StopwatchIC { private var lapStart: Long = 0 private var lapDescription: String? = null diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureSource.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureSource.kt index eae2c372591..d57305c3e76 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureSource.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IdSignatureSource.kt @@ -5,6 +5,7 @@ package org.jetbrains.kotlin.ir.backend.js.ic +import org.jetbrains.kotlin.backend.common.serialization.IrFileDeserializer import org.jetbrains.kotlin.descriptors.DescriptorVisibilities import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.symbols.IrSymbol @@ -57,7 +58,7 @@ internal fun resolveFakeOverrideFunction(symbol: IrSymbol): IdSignature? { } } -internal fun collectImplementedSymbol(deserializedSymbols: Map): Map { +private fun collectImplementedSymbol(deserializedSymbols: Map): Map { return HashMap(deserializedSymbols.size).apply { for ((signature, symbol) in deserializedSymbols) { put(signature, symbol) @@ -98,3 +99,39 @@ internal fun collectImplementedSymbol(deserializedSymbols: Map + abstract fun getReachableSignatures(): Set + abstract fun getImplementedSymbols(): Map + + class DeserializedFromKlib(private val fileDeserializer: IrFileDeserializer) : FileSignatureProvider(fileDeserializer.file) { + override fun getSignatureToIndexMapping(): Map { + return fileDeserializer.symbolDeserializer.signatureDeserializer.signatureToIndexMapping() + } + + override fun getReachableSignatures(): Set { + return getSignatureToIndexMapping().keys + } + + override fun getImplementedSymbols(): Map { + return collectImplementedSymbol(fileDeserializer.symbolDeserializer.deserializedSymbols) + } + } + + class GeneratedFunctionTypeInterface(file: IrFile) : FileSignatureProvider(file) { + private val allSignatures = run { + val topLevelSymbols = buildMap { + for (declaration in irFile.declarations) { + val signature = declaration.symbol.signature ?: continue + put(signature, declaration.symbol) + } + } + collectImplementedSymbol(topLevelSymbols) + } + + override fun getSignatureToIndexMapping(): Map = emptyMap() + override fun getReachableSignatures(): Set = allSignatures.keys + override fun getImplementedSymbols(): Map = allSignatures + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCache.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCache.kt index 76fbb16f6cd..801a6fdfb5c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCache.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCache.kt @@ -98,7 +98,7 @@ internal class IncrementalCache(private val library: KotlinLibraryHeader, val ca } val fileArtifacts = klibSrcFiles.map { srcFile -> - commitSourceFileMetadata(srcFile.getCacheFile(BINARY_AST_SUFFIX), srcFile, signatureToIndexMapping[srcFile] ?: emptyMap()) + commitSourceFileMetadata(srcFile, signatureToIndexMapping[srcFile] ?: emptyMap()) } return IncrementalCacheArtifact(cacheDir, removedSrcFiles.isNotEmpty(), fileArtifacts, library.jsOutputName) } @@ -200,10 +200,10 @@ internal class IncrementalCache(private val library: KotlinLibraryHeader, val ca } private fun commitSourceFileMetadata( - binaryAstFile: File, srcFile: KotlinSourceFile, signatureToIndexMapping: Map ): SourceFileCacheArtifact { + val binaryAstFile = srcFile.getCacheFile(BINARY_AST_SUFFIX) val headerCacheFile = srcFile.getCacheFile(METADATA_SUFFIX) val sourceFileMetadata = kotlinLibrarySourceFileMetadata[srcFile] ?: return SourceFileCacheArtifact.DoNotChangeMetadata(srcFile, binaryAstFile) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt index 58a88baee36..dfb0a96ce6d 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/IncrementalCacheArtifact.kt @@ -54,6 +54,8 @@ internal class IncrementalCacheArtifact( private val srcCacheActions: List, private val externalModuleName: String? ) { + fun getSourceFiles() = srcCacheActions.mapTo(HashSet(srcCacheActions.size)) { it.srcFile } + fun buildModuleArtifactAndCommitCache( moduleName: String, rebuiltFileFragments: Map, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsIrLinkerLoader.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsIrLinkerLoader.kt index 42d191f69bb..6282214b26c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsIrLinkerLoader.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/JsIrLinkerLoader.kt @@ -6,6 +6,8 @@ package org.jetbrains.kotlin.ir.backend.js.ic import org.jetbrains.kotlin.backend.common.serialization.DeserializationStrategy +import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionInterface +import org.jetbrains.kotlin.backend.common.serialization.encodings.BinarySymbolData import org.jetbrains.kotlin.backend.common.serialization.linkerissues.checkNoUnboundSymbols import org.jetbrains.kotlin.backend.common.serialization.signature.IdSignatureDescriptor import org.jetbrains.kotlin.config.CompilerConfiguration @@ -14,11 +16,13 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI +import org.jetbrains.kotlin.ir.backend.js.FunctionTypeInterfacePackages import org.jetbrains.kotlin.ir.backend.js.JsFactories import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc import org.jetbrains.kotlin.ir.declarations.IrFactory import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.descriptors.IrDescriptorBasedFunctionFactory import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.SymbolTable import org.jetbrains.kotlin.ir.util.irMessageLogger @@ -30,10 +34,43 @@ import org.jetbrains.kotlin.psi2ir.descriptors.IrBuiltInsOverDescriptors import org.jetbrains.kotlin.psi2ir.generators.TypeTranslatorImpl import org.jetbrains.kotlin.storage.LockBasedStorageManager -internal fun JsIrLinker.loadUnboundSymbols() { - ExternalDependenciesGenerator(symbolTable, listOf(this)).generateUnboundSymbolsAsDependencies() - postProcess() - checkNoUnboundSymbols(symbolTable, "at the end of IR linkage process") +internal data class LoadedJsIr( + val loadedFragments: Map, + private val linker: JsIrLinker, + private val functionTypeInterfacePackages: FunctionTypeInterfacePackages +) { + private val signatureProvidersImpl = hashMapOf>() + + private fun collectSignatureProviders(irModule: IrModuleFragment): List { + val moduleDeserializer = linker.moduleDeserializer(irModule.descriptor) + val deserializers = moduleDeserializer.fileDeserializers() + val providers = ArrayList(deserializers.size) + + for (fileDeserializer in deserializers) { + val irFile = fileDeserializer.file + if (functionTypeInterfacePackages.isFunctionTypeInterfacePackageFile(irFile)) { + providers += FileSignatureProvider.GeneratedFunctionTypeInterface(irFile) + } else { + providers += FileSignatureProvider.DeserializedFromKlib(fileDeserializer) + } + } + + return providers + } + + fun getSignatureProvidersForLib(lib: KotlinLibraryFile): List { + return signatureProvidersImpl.getOrPut(lib) { + val irFragment = loadedFragments[lib] ?: notFoundIcError("loaded fragment", lib) + collectSignatureProviders(irFragment) + } + } + + fun loadUnboundSymbols() { + signatureProvidersImpl.clear() + ExternalDependenciesGenerator(linker.symbolTable, listOf(linker)).generateUnboundSymbolsAsDependencies() + linker.postProcess() + linker.checkNoUnboundSymbols(linker.symbolTable, "at the end of IR linkage process") + } } internal class JsIrLinkerLoader( @@ -45,14 +82,34 @@ internal class JsIrLinkerLoader( private val mainLibrary = dependencyGraph.keys.lastOrNull() ?: notFoundIcError("main library") @OptIn(ObsoleteDescriptorBasedAPI::class) - private fun createLinker(loadedModules: Map): JsIrLinker { + private class LinkerContext( + val symbolTable: SymbolTable, + val typeTranslator: TypeTranslatorImpl, + val irBuiltIns: IrBuiltInsOverDescriptors, + val linker: JsIrLinker + ) { + val functionTypeInterfacePackages = FunctionTypeInterfacePackages() + + fun loadFunctionInterfacesIntoStdlib(stdlibModule: IrModuleFragment) { + irBuiltIns.functionFactory = IrDescriptorBasedFunctionFactory( + irBuiltIns, + symbolTable, + typeTranslator, + functionTypeInterfacePackages.makePackageAccessor(stdlibModule), + true + ) + } + } + + @OptIn(ObsoleteDescriptorBasedAPI::class) + private fun createLinker(loadedModules: Map): LinkerContext { val signaturer = IdSignatureDescriptor(JsManglerDesc) val symbolTable = SymbolTable(signaturer, irFactory) val moduleDescriptor = loadedModules.keys.last() val typeTranslator = TypeTranslatorImpl(symbolTable, compilerConfiguration.languageVersionSettings, moduleDescriptor) val irBuiltIns = IrBuiltInsOverDescriptors(moduleDescriptor.builtIns, typeTranslator, symbolTable) val partialLinkageEnabled = compilerConfiguration[JSConfigurationKeys.PARTIAL_LINKAGE] ?: false - return JsIrLinker( + val linker = JsIrLinker( currentModule = null, messageLogger = compilerConfiguration.irMessageLogger, builtIns = irBuiltIns, @@ -61,6 +118,7 @@ internal class JsIrLinkerLoader( translationPluginContext = null, friendModules = mapOf(mainLibrary.uniqueName to mainModuleFriends.map { it.uniqueName }) ) + return LinkerContext(symbolTable, typeTranslator, irBuiltIns, linker) } private fun loadModules(): Map { @@ -97,11 +155,9 @@ internal class JsIrLinkerLoader( .toMap() } - data class LoadedJsIr(val linker: JsIrLinker, val loadedFragments: Map) - fun loadIr(modifiedFiles: KotlinSourceFileMap, loadAllIr: Boolean = false): LoadedJsIr { val loadedModules = loadModules() - val jsIrLinker = createLinker(loadedModules) + val linkerContext = createLinker(loadedModules) val irModules = loadedModules.entries.associate { (descriptor, module) -> val libraryFile = KotlinLibraryFile(module) @@ -111,7 +167,7 @@ internal class JsIrLinkerLoader( else -> DeserializationStrategy.EXPLICITLY_EXPORTED } val modified = modifiedFiles[libraryFile] ?: emptyMap() - libraryFile to jsIrLinker.deserializeIrModuleHeader(descriptor, module, { + libraryFile to linkerContext.linker.deserializeIrModuleHeader(descriptor, module, { when (KotlinSourceFile(it)) { in modified -> modifiedStrategy else -> DeserializationStrategy.WITH_INLINE_BODIES @@ -119,15 +175,22 @@ internal class JsIrLinkerLoader( }) } - jsIrLinker.init(null, emptyList()) + val mainLibraryFile = KotlinLibraryFile(mainLibrary) + val mainFragment = irModules[mainLibraryFile] ?: notFoundIcError("main module fragment", mainLibraryFile) + val (_, stdlibFragment) = findStdlib(mainFragment, irModules) + linkerContext.loadFunctionInterfacesIntoStdlib(stdlibFragment) + + linkerContext.linker.init(null, emptyList()) if (!loadAllIr) { for ((loadingLibFile, loadingSrcFiles) in modifiedFiles) { val loadingIrModule = irModules[loadingLibFile] ?: notFoundIcError("loading fragment", loadingLibFile) - val moduleDeserializer = jsIrLinker.moduleDeserializer(loadingIrModule.descriptor) + val moduleDeserializer = linkerContext.linker.moduleDeserializer(loadingIrModule.descriptor) for (loadingSrcFileSignatures in loadingSrcFiles.values) { for (loadingSignature in loadingSrcFileSignatures.getExportedSignatures()) { - if (loadingSignature in moduleDeserializer) { + if (checkIsFunctionInterface(loadingSignature)) { + moduleDeserializer.tryDeserializeIrSymbol(loadingSignature, BinarySymbolData.SymbolKind.CLASS_SYMBOL) + } else if (loadingSignature in moduleDeserializer) { moduleDeserializer.addModuleReachableTopLevel(loadingSignature) } } @@ -135,7 +198,8 @@ internal class JsIrLinkerLoader( } } - jsIrLinker.loadUnboundSymbols() - return LoadedJsIr(jsIrLinker, irModules) + val loadedIr = LoadedJsIr(irModules, linkerContext.linker, linkerContext.functionTypeInterfacePackages) + loadedIr.loadUnboundSymbols() + return loadedIr } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/KotlinSourceFileMetadata.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/KotlinSourceFileMetadata.kt index 5f5959bbf22..85cc7867ede 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/KotlinSourceFileMetadata.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/ic/KotlinSourceFileMetadata.kt @@ -107,22 +107,21 @@ internal object KotlinSourceFileMetadataNotExist : KotlinSourceFileMetadata() { override val directDependencies = KotlinSourceFileMap>(emptyMap()) } -internal class DirtyFileExports( - override val inverseDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap() -) : KotlinSourceFileExports() { - override fun getExportedSignatures(): Set = allExportedSignatures - +internal class DirtyFileExports : KotlinSourceFileExports() { val allExportedSignatures = hashSetOf() + + override val inverseDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap() + + override fun getExportedSignatures(): Set = allExportedSignatures } internal class DirtyFileMetadata( val maybeImportedSignatures: Collection, - - val oldDirectDependencies: KotlinSourceFileMap<*>, - - override val inverseDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap(), - override val directDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap(), + val oldDirectDependencies: KotlinSourceFileMap<*> ) : KotlinSourceFileMetadata() { + override val inverseDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap() + override val directDependencies: KotlinSourceFileMutableMap> = KotlinSourceFileMutableMap() + fun addInverseDependency(lib: KotlinLibraryFile, src: KotlinSourceFile, signature: IdSignature) = when (val signatures = inverseDependencies[lib, src]) { null -> inverseDependencies[lib, src] = hashSetOf(signature) diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/FunctionalInterfacesUtils.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/FunctionalInterfacesUtils.kt index 509aacb5ad4..3e33e5f6373 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/FunctionalInterfacesUtils.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/FunctionalInterfacesUtils.kt @@ -10,12 +10,14 @@ import java.util.regex.Pattern internal val functionPattern = Pattern.compile("^K?(Suspend)?Function\\d+$") -internal val functionalPackages = listOf("kotlin", "kotlin.coroutines", "kotlin.reflect") +internal val functionTypeInterfacePackages = listOf("kotlin", "kotlin.coroutines", "kotlin.reflect") + +fun checkIsFunctionTypeInterfacePackageFqName(fqName: String) = fqName in functionTypeInterfacePackages fun checkIsFunctionInterface(idSig: IdSignature?): Boolean { val publicSig = idSig?.asPublic() return publicSig != null && - publicSig.packageFqName in functionalPackages && + checkIsFunctionTypeInterfacePackageFqName(publicSig.packageFqName) && publicSig.declarationFqName.isNotEmpty() && functionPattern.matcher(publicSig.firstNameSegment).find() } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/FunctionTypeInterfacePackages.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/FunctionTypeInterfacePackages.kt new file mode 100644 index 00000000000..a52d25873ac --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/FunctionTypeInterfacePackages.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2010-2023 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.ir.backend.js + +import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionTypeInterfacePackageFqName +import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment + +class FunctionTypeInterfacePackages { + companion object { + private const val FUNCTION_TYPE_INTERFACE_DIR = "function-type-interface" + } + + private val functionTypeInterfacePackageFiles = hashSetOf() + + // Function type interfaces are not declared in a standard library, and they are generated on flight during a klib deserialization. + // The accessor allows finding a package for storing the generated function type interfaces. + // The optimization reduces the size of a standard library klib. + // Here are some numbers: + // 255 interfaces in one package increase the size of uncompressed klib up to 3.5+MB; + // 512 interfaces in one package increase the size of uncompressed klib up to 14.5+MB! + // We need 3 packages. + fun makePackageAccessor(stdlibModule: IrModuleFragment) = { packageFragmentDescriptor: PackageFragmentDescriptor -> + val packageFqName = packageFragmentDescriptor.fqName.toString() + check(checkIsFunctionTypeInterfacePackageFqName(packageFqName)) { "unexpected function type interface package $packageFqName" } + + val fileWithRequiredPackage = "${packageFqName.replace('.', '-')}-package.kt" + val packageFile = stdlibModule.files.singleOrNull { + // Do not check by name "$FUNCTION_TYPE_INTERFACE_DIR/$fileWithRequiredPackage" because the path separator depends on OS + it.fileEntry.name.endsWith(fileWithRequiredPackage) && it.fileEntry.name.contains(FUNCTION_TYPE_INTERFACE_DIR) + } ?: error("can not find a functional interface file for $packageFqName package") + + check(packageFragmentDescriptor.fqName == packageFile.fqName) { + "unexpected package in file ${packageFile.fileEntry.name}; expected $packageFqName, got ${packageFile.fqName}" + } + + functionTypeInterfacePackageFiles += packageFile + packageFile + } + + fun isFunctionTypeInterfacePackageFile(file: IrFile) = file in functionTypeInterfacePackageFiles +} diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt index 6b5236b4706..6620da2b4b2 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt @@ -29,7 +29,6 @@ import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.config.* import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor -import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider @@ -38,7 +37,6 @@ import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.* import org.jetbrains.kotlin.ir.declarations.IrFactory import org.jetbrains.kotlin.ir.declarations.IrModuleFragment -import org.jetbrains.kotlin.ir.declarations.impl.IrFileImpl import org.jetbrains.kotlin.ir.descriptors.IrDescriptorBasedFunctionFactory import org.jetbrains.kotlin.ir.linkage.IrDeserializer import org.jetbrains.kotlin.ir.symbols.IrSymbol @@ -71,6 +69,7 @@ import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.util.DummyLogger import org.jetbrains.kotlin.util.Logger import org.jetbrains.kotlin.utils.DFS +import org.jetbrains.kotlin.utils.addToStdlib.ifTrue import java.io.File val KotlinLibrary.moduleName: String @@ -184,14 +183,6 @@ fun deserializeDependencies( } } -fun getFunctionFactoryCallback(stdlibModule: IrModuleFragment) = { packageFragmentDescriptor: PackageFragmentDescriptor -> - IrFileImpl( - NaiveSourceBasedFileEntryImpl("${packageFragmentDescriptor.fqName}-[K][Suspend]Functions"), - packageFragmentDescriptor, - stdlibModule - ).also { stdlibModule.files += it } -} - fun loadIr( depsDescriptors: ModulesStructure, irFactory: IrFactory, @@ -287,7 +278,9 @@ fun getIrModuleInfoForKlib( irBuiltIns, symbolTable, typeTranslator, - if (loadFunctionInterfacesIntoStdlib) getFunctionFactoryCallback(deserializedModuleFragments.first()) else null, + loadFunctionInterfacesIntoStdlib.ifTrue { + FunctionTypeInterfacePackages().makePackageAccessor(deserializedModuleFragments.first()) + }, true ) @@ -345,7 +338,9 @@ fun getIrModuleInfoForSourceFiles( irBuiltIns, symbolTable, psi2IrContext.typeTranslator, - if (loadFunctionInterfacesIntoStdlib) getFunctionFactoryCallback(deserializedModuleFragments.first()) else null, + loadFunctionInterfacesIntoStdlib.ifTrue { + FunctionTypeInterfacePackages().makePackageAccessor(deserializedModuleFragments.first()) + }, true ) diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationTestGenerated.java index 6a1471a049a..00dabe0b904 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrES6InvalidationTestGenerated.java @@ -170,6 +170,11 @@ public class JsIrES6InvalidationTestGenerated extends AbstractJsIrES6Invalidatio runTest("js/js.translator/testData/incremental/invalidation/functionSignature/"); } + @TestMetadata("functionalInterface") + public void testFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/incremental/invalidation/functionalInterface/"); + } + @TestMetadata("genericFunctions") public void testGenericFunctions() throws Exception { runTest("js/js.translator/testData/incremental/invalidation/genericFunctions/"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationTestGenerated.java index 28768a05d56..16568406423 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/incremental/JsIrInvalidationTestGenerated.java @@ -170,6 +170,11 @@ public class JsIrInvalidationTestGenerated extends AbstractJsIrInvalidationTest runTest("js/js.translator/testData/incremental/invalidation/functionSignature/"); } + @TestMetadata("functionalInterface") + public void testFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/incremental/invalidation/functionalInterface/"); + } + @TestMetadata("genericFunctions") public void testGenericFunctions() throws Exception { runTest("js/js.translator/testData/incremental/invalidation/genericFunctions/"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java index 487574432c1..93cd8793d4d 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/BoxJsTestGenerated.java @@ -861,6 +861,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { public void testSuspendFunctionIsAs() throws Exception { runTest("js/js.translator/testData/box/coroutines/suspendFunctionIsAs.kt"); } + + @Test + @TestMetadata("suspendFunctionalInterface.kt") + public void testSuspendFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt"); + } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java index 700a8349503..1ded5fc71b1 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/fir/FirJsBoxTestGenerated.java @@ -925,6 +925,12 @@ public class FirJsBoxTestGenerated extends AbstractFirJsBoxTest { public void testSuspendFunctionIsAs() throws Exception { runTest("js/js.translator/testData/box/coroutines/suspendFunctionIsAs.kt"); } + + @Test + @TestMetadata("suspendFunctionalInterface.kt") + public void testSuspendFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt"); + } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java index 1739dac1caa..cf492125cdf 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsES6TestGenerated.java @@ -925,6 +925,12 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { public void testSuspendFunctionIsAs() throws Exception { runTest("js/js.translator/testData/box/coroutines/suspendFunctionIsAs.kt"); } + + @Test + @TestMetadata("suspendFunctionalInterface.kt") + public void testSuspendFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt"); + } } @Nested diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java index 05adb8f8874..c0df910ec4c 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/IrBoxJsTestGenerated.java @@ -925,6 +925,12 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { public void testSuspendFunctionIsAs() throws Exception { runTest("js/js.translator/testData/box/coroutines/suspendFunctionIsAs.kt"); } + + @Test + @TestMetadata("suspendFunctionalInterface.kt") + public void testSuspendFunctionalInterface() throws Exception { + runTest("js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt"); + } } @Nested diff --git a/js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt b/js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt new file mode 100644 index 00000000000..4bea14eb0d0 --- /dev/null +++ b/js/js.translator/testData/box/coroutines/suspendFunctionalInterface.kt @@ -0,0 +1,30 @@ +// IGNORE_BACKEND: JS + +inline fun handle(s: T): String { + return "${T::class}" +} + +fun check(got: String, expected: String): String? { + if (got != expected) { + return "Failed; expected $expected, got $got" + } + return null +} + +fun box(): String { + val s0: suspend () -> Unit = {} + check(handle(s0), "class SuspendFunction0")?.let { return it } + + val s1: suspend (String) -> Unit = {} + check(handle(s1), "class SuspendFunction1")?.let { return it } + + val s7: suspend (Any, Any, Any, Any, Any, Any, Any) -> Unit = { _, _, _, _, _, _, _ -> } + check(handle(s7), "class SuspendFunction7")?.let { return it } + + val s15: suspend (Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any) -> Unit + = { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ -> } + check(handle(s15), "class SuspendFunction15")?.let { return it } + + return "OK" + +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.0.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.0.kt new file mode 100644 index 00000000000..5bb7f3ca689 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.0.kt @@ -0,0 +1,3 @@ +inline fun getTypeName(s: T): String { + return "${T::class}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.11.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.11.kt new file mode 100644 index 00000000000..f178d3cdef9 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.11.kt @@ -0,0 +1,3 @@ +inline fun getTypeName(s: T): String { + return "_" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.9.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.9.kt new file mode 100644 index 00000000000..aa702c289c4 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/l1.9.kt @@ -0,0 +1,3 @@ +inline fun getTypeName(s: T): String { + return "_${T::class}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/module.info b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/module.info new file mode 100644 index 00000000000..e96f99344a5 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib1/module.info @@ -0,0 +1,17 @@ +STEP 0: + modifications: + U : l1.0.kt -> l1.kt + added file: l1.kt +STEP 1..8: +STEP 9: + modifications: + U : l1.9.kt -> l1.kt + modified ir: l1.kt +STEP 10: +STEP 11: + modifications: + U : l1.11.kt -> l1.kt + modified ir: l1.kt +STEP 12: + updated exports: l1.kt +STEP 13..15: diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.0.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.0.kt new file mode 100644 index 00000000000..3df26a2fe60 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.0.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend () -> Unit = {} + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.1.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.1.kt new file mode 100644 index 00000000000..bff655fcb93 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.1.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend (Any) -> Unit = { _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.10.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.10.kt new file mode 100644 index 00000000000..b7c78cd98d8 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.10.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: (Int, Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.12.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.12.kt new file mode 100644 index 00000000000..2fc50104a9e --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.12.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: (Int, Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _, _ -> } + return "${s::class}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.13.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.13.kt new file mode 100644 index 00000000000..1bd8bda66ad --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.13.kt @@ -0,0 +1,5 @@ +fun getString(): String { + val s1: (Int, Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _, _ -> } + val s2: (Int, Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _, _ -> } + return "${s1::class.hashCode() == s2::class.hashCode()}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.14.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.14.kt new file mode 100644 index 00000000000..6ac8d88ab8c --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.14.kt @@ -0,0 +1,5 @@ +fun getString(): String { + val s1: (Int, Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _, _ -> } + val s2: (Int, Int, Int, Int) -> Unit = { _, _, _, _ -> } + return "${s1::class == s2::class}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.15.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.15.kt new file mode 100644 index 00000000000..eb1ebdfc5de --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.15.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend (Any, Any, Any, Any, Any) -> Unit = { _, _, _, _, _ -> } + return "${s::class}" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.2.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.2.kt new file mode 100644 index 00000000000..0463dff650b --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.2.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend (Any, Any, Any, Any, Any, Any, Any) -> Unit = { _, _, _, _, _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.3.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.3.kt new file mode 100644 index 00000000000..c56c34c953d --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.3.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: (Any, Any, Any) -> Unit = { _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.4.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.4.kt new file mode 100644 index 00000000000..ea4210cdbef --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.4.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: (Any, Any, Any, Any, Any, Any) -> Unit = { _, _, _, _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.6.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.6.kt new file mode 100644 index 00000000000..1ee4e1dd26c --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.6.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend (Any, Any, Any, Any, Any, Any) -> Unit = { _, _, _, _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.7.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.7.kt new file mode 100644 index 00000000000..134241fd2a8 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.7.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: Any = 123 + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.8.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.8.kt new file mode 100644 index 00000000000..541feff9507 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2a.8.kt @@ -0,0 +1,4 @@ +fun getString(): String { + val s: suspend (Int, Int, Int, Int, Int, Int, Int, Int) -> Unit = { _, _, _, _, _, _, _, _ -> } + return getTypeName(s) +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.0.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.0.kt new file mode 100644 index 00000000000..a38a7a5d0cb --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.0.kt @@ -0,0 +1,3 @@ +fun doTest(): String { + return getString() +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.5.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.5.kt new file mode 100644 index 00000000000..9b5453d0cce --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/l2b.5.kt @@ -0,0 +1,3 @@ +fun doTest(): String { + return getString() + "_" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/module.info b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/module.info new file mode 100644 index 00000000000..40e22cc0df4 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/lib2/module.info @@ -0,0 +1,77 @@ +STEP 0: + dependencies: lib1 + modifications: + U : l2a.0.kt -> l2a.kt + U : l2b.0.kt -> l2b.kt + added file: l2a.kt, l2b.kt +STEP 1: + dependencies: lib1 + modifications: + U : l2a.1.kt -> l2a.kt + modified ir: l2a.kt +STEP 2: + dependencies: lib1 + modifications: + U : l2a.2.kt -> l2a.kt + modified ir: l2a.kt +STEP 3: + dependencies: lib1 + modifications: + U : l2a.3.kt -> l2a.kt + modified ir: l2a.kt +STEP 4: + dependencies: lib1 + modifications: + U : l2a.4.kt -> l2a.kt + modified ir: l2a.kt +STEP 5: + dependencies: lib1 + modifications: + U : l2b.5.kt -> l2b.kt + modified ir: l2b.kt +STEP 6: + dependencies: lib1 + modifications: + U : l2a.6.kt -> l2a.kt + modified ir: l2a.kt +STEP 7: + dependencies: lib1 + modifications: + U : l2a.7.kt -> l2a.kt + modified ir: l2a.kt +STEP 8: + dependencies: lib1 + modifications: + U : l2a.8.kt -> l2a.kt + modified ir: l2a.kt +STEP 9: + dependencies: lib1 + updated imports: l2a.kt +STEP 10: + dependencies: lib1 + modifications: + U : l2a.10.kt -> l2a.kt + modified ir: l2a.kt +STEP 11: + dependencies: lib1 + updated imports: l2a.kt +STEP 12: + dependencies: lib1 + modifications: + U : l2a.12.kt -> l2a.kt + modified ir: l2a.kt +STEP 13: + dependencies: lib1 + modifications: + U : l2a.13.kt -> l2a.kt + modified ir: l2a.kt +STEP 14: + dependencies: lib1 + modifications: + U : l2a.14.kt -> l2a.kt + modified ir: l2a.kt +STEP 15: + dependencies: lib1 + modifications: + U : l2a.15.kt -> l2a.kt + modified ir: l2a.kt diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/main/m.kt b/js/js.translator/testData/incremental/invalidation/functionalInterface/main/m.kt new file mode 100644 index 00000000000..76dd80834ad --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/main/m.kt @@ -0,0 +1,26 @@ +fun box(stepId: Int): String { + val s = doTest() + when (stepId) { + 0 -> if (s == "class SuspendFunction0") return "OK" + 1 -> if (s == "class SuspendFunction1") return "OK" + 2 -> if (s == "class SuspendFunction7") return "OK" + 3 -> if (s == "class Function3") return "OK" + 4 -> if (s == "class Function6") return "OK" + 5 -> if (s == "class Function6_") return "OK" + 6 -> if (s == "class SuspendFunction6_") return "OK" + 7 -> if (s == "class Any_") return "OK" + 8 -> if (s == "class SuspendFunction8_") return "OK" + 9 -> if (s == "_class SuspendFunction8_") return "OK" + 10 -> if (s == "_class Function9_") return "OK" + 11 -> if (s == "__") return "OK" + 12 -> if (s == "class Function9_") return "OK" + 13 -> if (s == "true_") return "OK" + 14 -> if (s == "false_") return "OK" + // TODO: I would expect, that it should be SuspendFunction5_, + // but it seems a feature of Kotlin/JS runtime. + // In Kotlin/JVM it also gives different strings. + 15 -> if (s == "class Function6_") return "OK" + else -> return "Unknown" + } + return "Fail; got $s" +} diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/main/module.info b/js/js.translator/testData/incremental/invalidation/functionalInterface/main/module.info new file mode 100644 index 00000000000..69cf88630b0 --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/main/module.info @@ -0,0 +1,5 @@ +STEP 0: + dependencies: lib1, lib2 + added file: m.kt +STEP 1..15: + dependencies: lib1, lib2 diff --git a/js/js.translator/testData/incremental/invalidation/functionalInterface/project.info b/js/js.translator/testData/incremental/invalidation/functionalInterface/project.info new file mode 100644 index 00000000000..b336590ba6b --- /dev/null +++ b/js/js.translator/testData/incremental/invalidation/functionalInterface/project.info @@ -0,0 +1,20 @@ +MODULES: lib1, lib2, main + +STEP 0: + libs: lib1, lib2, main + dirty js: lib1, lib2, main +STEP 1..8: + libs: lib1, lib2, main + dirty js: lib2 +STEP 9: + libs: lib1, lib2, main + dirty js: lib1, lib2 +STEP 10: + libs: lib1, lib2, main + dirty js: lib2 +STEP 11..12: + libs: lib1, lib2, main + dirty js: lib1, lib2 +STEP 13..15: + libs: lib1, lib2, main + dirty js: lib2 diff --git a/libraries/stdlib/js-ir/src/function-type-interface/kotlin-coroutines-package.kt b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-coroutines-package.kt new file mode 100644 index 00000000000..9bfec66c836 --- /dev/null +++ b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-coroutines-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin.coroutines + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt diff --git a/libraries/stdlib/js-ir/src/function-type-interface/kotlin-package.kt b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-package.kt new file mode 100644 index 00000000000..aa8d4287ca2 --- /dev/null +++ b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt diff --git a/libraries/stdlib/js-ir/src/function-type-interface/kotlin-reflect-package.kt b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-reflect-package.kt new file mode 100644 index 00000000000..7e3eca8202b --- /dev/null +++ b/libraries/stdlib/js-ir/src/function-type-interface/kotlin-reflect-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin.reflect + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt diff --git a/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-coroutines-package.kt b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-coroutines-package.kt new file mode 100644 index 00000000000..9bfec66c836 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-coroutines-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin.coroutines + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt diff --git a/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-package.kt b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-package.kt new file mode 100644 index 00000000000..aa8d4287ca2 --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt diff --git a/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-reflect-package.kt b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-reflect-package.kt new file mode 100644 index 00000000000..7e3eca8202b --- /dev/null +++ b/libraries/stdlib/wasm/src/kotlin/function-type-interface/kotlin-reflect-package.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 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 kotlin.reflect + +// Do not remove this file. +// During klib deserialization the compiler uses this file as a place for declaring function type interfaces. +// See FunctionTypeInterfacePackages.kt