From 53a65f818f2afe392a48b0629bb5f6ce0a7193f4 Mon Sep 17 00:00:00 2001 From: Roman Artemev Date: Tue, 16 Nov 2021 13:52:22 +0300 Subject: [PATCH] [KLIB] Support partially linked klibs in Ir linker Now it's possible to compile against klib even if in there are references to declarations from other klibs which are no longer existed - add switch flag to turn on/off that mode - pass switch flag from JS CLI to JS Linker --- .../backend/common/overrides/FakeOverrides.kt | 2 +- .../common/serialization/KotlinIrLinker.kt | 127 ++++++- .../UnlinkedDeclarationsProcessor.kt | 317 ++++++++++++++++++ .../jetbrains/kotlin/ir/backend/js/klib.kt | 8 +- .../js/lower/serialization/ir/JsIrLinker.kt | 5 +- 5 files changed, 444 insertions(+), 15 deletions(-) create mode 100644 compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/UnlinkedDeclarationsProcessor.kt diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/overrides/FakeOverrides.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/overrides/FakeOverrides.kt index c7fc5e30d20..88a95036827 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/overrides/FakeOverrides.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/overrides/FakeOverrides.kt @@ -81,7 +81,7 @@ class FakeOverrideBuilder( // TODO: The declaration table is needed for the signaturer. // private val fakeOverrideDeclarationTable = FakeOverrideDeclarationTable(mangler, signatureSerializerFactory) - private val fakeOverrideCandidates = mutableMapOf() + val fakeOverrideCandidates = mutableMapOf() fun enqueueClass(clazz: IrClass, signature: IdSignature, compatibilityMode: CompatibilityMode) { fakeOverrideDeclarationTable.assumeDeclarationSignature(clazz, signature) fakeOverrideCandidates[clazz] = compatibilityMode diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/KotlinIrLinker.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/KotlinIrLinker.kt index c1c71d29121..dd56ea25d1c 100644 --- a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/KotlinIrLinker.kt +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/KotlinIrLinker.kt @@ -14,12 +14,17 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.ir.IrBuiltIns import org.jetbrains.kotlin.ir.builders.TranslationPluginContext -import org.jetbrains.kotlin.ir.declarations.IrDeclaration -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.linkage.IrDeserializer import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.types.IrErrorType +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.IrTypeProjection import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.library.KotlinAbiVersion import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.uniqueName import org.jetbrains.kotlin.name.Name @@ -31,6 +36,7 @@ abstract class KotlinIrLinker( val builtIns: IrBuiltIns, val symbolTable: SymbolTable, private val exportedDependencies: List, + private val allowUnboundSymbols: Boolean = false ) : IrDeserializer, FileLocalAwareLinker { // Kotlin-MPP related data. Consider some refactoring @@ -57,12 +63,26 @@ abstract class KotlinIrLinker( idSignature: IdSignature, moduleDeserializer: IrModuleDeserializer ): IrModuleDeserializer { - throw SignatureIdNotFoundInModuleWithDependencies( - idSignature = idSignature, - problemModuleDeserializer = moduleDeserializer, - allModuleDeserializers = deserializersForModules.values, - userVisibleIrModulesSupport = userVisibleIrModulesSupport - ).raiseIssue(messageLogger) + if (allowUnboundSymbols) { + return object : IrModuleDeserializer(null, KotlinAbiVersion.CURRENT) { + override fun contains(idSig: IdSignature): Boolean = false + + override fun deserializeIrSymbol(idSig: IdSignature, symbolKind: BinarySymbolData.SymbolKind): IrSymbol { + return referenceDeserializedSymbol(symbolTable, null, symbolKind, idSig) + } + + override val moduleFragment: IrModuleFragment + get() = TODO("Not yet implemented") + override val moduleDependencies: Collection get() = emptyList() + } + } else { + throw SignatureIdNotFoundInModuleWithDependencies( + idSignature = idSignature, + problemModuleDeserializer = moduleDeserializer, + allModuleDeserializers = deserializersForModules.values, + userVisibleIrModulesSupport = userVisibleIrModulesSupport + ).raiseIssue(messageLogger) + } } fun resolveModuleDeserializer(module: ModuleDescriptor, idSignature: IdSignature?): IrModuleDeserializer { @@ -145,7 +165,9 @@ abstract class KotlinIrLinker( ?: tryResolveCustomDeclaration(symbol) ?: return null } catch (e: IrSymbolTypeMismatchException) { - throw SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger) + if (!allowUnboundSymbols) { + throw SymbolTypeMismatch(e, deserializersForModules.values, userVisibleIrModulesSupport).raiseIssue(messageLogger) + } } } @@ -192,11 +214,96 @@ abstract class KotlinIrLinker( deserializersForModules.values.forEach { it.init() } } + + private fun markUnlinkedClassifiers(): Set { + if (!allowUnboundSymbols) return emptySet() + val entries = fakeOverrideBuilder.fakeOverrideCandidates + val result = mutableSetOf() + + fun IrType.isUnlinked(visited: MutableSet): Boolean { + val simpleType = this as? IrSimpleType ?: return this !is IrErrorType + + val classifier = simpleType.classifier + + if (!classifier.isBound) { + return true + } + + if (classifier in result) { + return true + } + + if (!visited.add(classifier)) return false + + val superTypes = when (val decl = classifier.owner) { + is IrClass -> decl.superTypes + is IrTypeParameter -> decl.superTypes + else -> emptyList() + } + + if (superTypes.any { it.isUnlinked(visited) }) { + result.add(classifier) + return true + } + + for (ta in simpleType.arguments) { + if (ta is IrTypeProjection) { + val projected = ta.type + if (projected.isUnlinked(visited)) { + result.add(classifier) + return true + } + } + } + + return false + } + + fun IrClass.isUnlinked(visited: MutableSet): Boolean { + if (symbol in result) return true + for (s in superTypes) { + if (s.isUnlinked(visited)) { + result.add(symbol) + return true + } + } + return false + } + + val toRemove = mutableListOf() + + for (e in entries) { + val klass = e.key + if (klass.isUnlinked(mutableSetOf())) { + toRemove.add(klass) + } + } + + toRemove.forEach { entries.remove(it) } + + return result + } + + private fun applyToModules(transformer: IrElementTransformerVoid) { + deserializersForModules.values.forEach { it.moduleFragment.transformChildrenVoid(transformer) } + } + override fun postProcess() { finalizeExpectActualLinker() + + val unlinkedClassifiers = markUnlinkedClassifiers() + fakeOverrideBuilder.provideFakeOverrides() triedToDeserializeDeclarationForSymbol.clear() + if (allowUnboundSymbols) { + val t = UnlinkedDeclarationsProcessor(builtIns, unlinkedClassifiers, messageLogger) + t.addLinkageErrorIntoUnlinkedClasses() + + applyToModules(t.signatureTransformer()) + applyToModules(t.usageTransformer()) + } + // TODO: fix IrPluginContext to make it not produce additional external reference // symbolTable.noUnboundLeft("unbound after fake overrides:") } diff --git a/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/UnlinkedDeclarationsProcessor.kt b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/UnlinkedDeclarationsProcessor.kt new file mode 100644 index 00000000000..92610499350 --- /dev/null +++ b/compiler/ir/serialization.common/src/org/jetbrains/kotlin/backend/common/serialization/UnlinkedDeclarationsProcessor.kt @@ -0,0 +1,317 @@ +/* + * 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.common.serialization + +import org.jetbrains.kotlin.ir.IrBuiltIns +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.IrStatement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl +import org.jetbrains.kotlin.ir.symbols.* +import org.jetbrains.kotlin.ir.symbols.impl.IrAnonymousInitializerSymbolImpl +import org.jetbrains.kotlin.ir.types.IrErrorType +import org.jetbrains.kotlin.ir.types.IrSimpleType +import org.jetbrains.kotlin.ir.types.IrType +import org.jetbrains.kotlin.ir.types.IrTypeProjection +import org.jetbrains.kotlin.ir.types.impl.IrErrorTypeImpl +import org.jetbrains.kotlin.ir.util.IrMessageLogger +import org.jetbrains.kotlin.ir.util.fileOrNull +import org.jetbrains.kotlin.ir.util.fqNameForIrSerialization +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.types.Variance + +internal class UnlinkedDeclarationsProcessor( + private val builtIns: IrBuiltIns, + private val unlinkedClassifiers: Set, + private val messageLogger: IrMessageLogger +) { + + companion object { + val errorOrigin = object : IrStatementOriginImpl("LINKAGE ERROR") {} + } + + fun addLinkageErrorIntoUnlinkedClasses() { + for (u in unlinkedClassifiers) { + if (u is IrClassSymbol) { + val klass = u.owner + val anonInitializer = klass.declarations.firstOrNull { it is IrAnonymousInitializer } as IrAnonymousInitializer? ?: run { + builtIns.irFactory.createAnonymousInitializer( + klass.startOffset, + klass.endOffset, + IrDeclarationOrigin.DEFINED, + IrAnonymousInitializerSymbolImpl() + ).also { + it.body = builtIns.irFactory.createBlockBody(klass.startOffset, klass.endOffset) + it.parent = klass + klass.declarations.add(it) + } + } + anonInitializer.body.statements.clear() + + klass.reportWarning("Class", klass.fqNameForIrSerialization.asString()) + + anonInitializer.body.statements.add(klass.throwLinkageError(klass.symbol.signature?.render())) + + klass.superTypes = klass.superTypes.filter { !it.isUnlinked() } + } + } + } + + private val errorType = IrErrorTypeImpl(null, emptyList(), Variance.INVARIANT) + + private fun IrDeclaration.location(): IrMessageLogger.Location? = fileOrNull?.location(startOffset) + + private fun IrFile.location(offset: Int): IrMessageLogger.Location { + val module = module.name + val fileEntry = fileEntry + val fileName = fileEntry.name + val lineNumber = fileEntry.getLineNumber(offset) + 1 // since humans count from 1, not 0 + val columnNumber = fileEntry.getColumnNumber(offset) + 1 + // unsure whether should module name be added here + return IrMessageLogger.Location("$module @ $fileName", lineNumber, columnNumber) + } + + private fun IrDeclaration.reportWarning(kind: String, fqn: String) { + reportWarning("$kind declaration $fqn contains unlinked symbols", location()) + } + + private fun reportWarning(message: String, location: IrMessageLogger.Location?) { + messageLogger.report(IrMessageLogger.Severity.WARNING, message, location) + } + + fun signatureTransformer(): IrElementTransformerVoid = SignatureTransformer() + + private inner class SignatureTransformer : IrElementTransformerVoid() { + + override fun visitFunction(declaration: IrFunction): IrStatement { + var linked = true + fun IrValueParameter.fixType() { + if (type.isUnlinked()) { + linked = false + type = errorType + defaultValue = null + } + varargElementType?.let { + if (it.isUnlinked()) { + varargElementType = errorType + } + } + } + declaration.dispatchReceiverParameter?.fixType() + declaration.extensionReceiverParameter?.fixType() + declaration.valueParameters.forEach { it.fixType() } + if (declaration.returnType.isUnlinked()) { + linked = false + declaration.returnType = errorType + } + declaration.typeParameters.forEach { + if (it.superTypes.any { s -> s.isUnlinked() }) { + linked = false + it.superTypes = listOf(errorType) + } + } + if (linked) { + declaration.transformChildrenVoid() + } else { + declaration.reportWarning("Function", declaration.fqNameForIrSerialization.asString()) + + declaration.body?.let { body -> + val bb = (body as IrBlockBody) + bb.statements.clear() + bb.statements.add(declaration.throwLinkageError("Unlinked type in function signature")) + } + } + return declaration + } + + override fun visitField(declaration: IrField): IrStatement { + if (declaration.type.isUnlinked()) { + + val fqn = declaration.correspondingPropertySymbol?.owner?.fqNameWhenAvailable ?: declaration.fqNameForIrSerialization + val kind = if (declaration.correspondingPropertySymbol != null) "Property" else "Field" + declaration.reportWarning(kind, fqn.asString()) + + declaration.type = errorType + declaration.initializer = null + } else { + declaration.transformChildrenVoid() + } + return declaration + } + } + + private fun IrSymbol.isUnlinked(): Boolean { + if (!isBound) return true + when (this) { + is IrClassifierSymbol -> this.isUnlinked() + is IrPropertySymbol -> { + owner.getter?.let { if (it.symbol.isUnlinked()) return true } + owner.setter?.let { if (it.symbol.isUnlinked()) return true } + owner.backingField?.let { return it.symbol.isUnlinked() } + } + is IrFunctionSymbol -> return isUnlinked() + } + return false + } + + private fun IrClassifierSymbol.isUnlinked(): Boolean = !isBound || this in unlinkedClassifiers + + private fun IrType.isUnlinked(): Boolean { + val simpleType = this as? IrSimpleType ?: return false + + if (simpleType.classifier.isUnlinked()) return true + + return simpleType.arguments.any { it is IrTypeProjection && it.type.isUnlinked() } + } + + private fun IrFieldSymbol.isUnlinked(): Boolean { + return owner.type is IrErrorType + } + + private fun IrFunctionSymbol.isUnlinked(): Boolean { + val function = owner + if (function.returnType is IrErrorType) return true + if (function.dispatchReceiverParameter?.type is IrErrorType) return true + if (function.extensionReceiverParameter?.type is IrErrorType) return true + if (function.valueParameters.any { it.type is IrErrorType }) return true + if (function.typeParameters.any { tp -> tp.superTypes.any { st -> st is IrErrorType } }) return true + return false + } + + private fun IrElement.throwLinkageError(message: String?): IrCall { + return IrCallImpl(startOffset, endOffset, builtIns.nothingType, builtIns.linkageErrorSymbol, 0, 1, errorOrigin).also { call -> + val messageLiteral = "Linkage error of symbol: ${message ?: ""}" + call.putValueArgument(0, IrConstImpl.string(startOffset, endOffset, builtIns.stringType, messageLiteral)) + } + } + + fun usageTransformer(): IrElementTransformerVoid = UsageTransformer() + + private inner class UsageTransformer : IrElementTransformerVoid() { + + private var currentFile: IrFile? = null + + override fun visitFile(declaration: IrFile): IrFile { + currentFile = declaration + return super.visitFile(declaration).also { currentFile = null } + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression { + expression.transformChildrenVoid() + + val checkType = expression.typeOperandClassifier + + if (!checkType.isUnlinked()) return expression + + reportWarning( + "TypeOperator contains unlinked symbol ${checkType.signature?.render() ?: ""}", + currentFile?.location(expression.startOffset) + ) + + val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType) + + composite.statements.add(expression.argument) + composite.statements.add(expression.throwLinkageError(expression.typeOperandClassifier.signature?.render())) + + return composite + } + + override fun visitExpression(expression: IrExpression): IrExpression { + if (expression.type.isUnlinked() || expression.type is IrErrorType) { + reportWarning("Expression type contains unlinked symbol", currentFile?.location(expression.startOffset)) + return expression.throwLinkageError("Unlinked type of expression") + } + return super.visitExpression(expression) + } + + override fun visitMemberAccess(expression: IrMemberAccessExpression<*>): IrExpression { + expression.transformChildrenVoid() + val symbol = expression.symbol + + if (!symbol.isUnlinked()) return expression + + reportWarning( + "Accessing declaration contains unlinked symbol ${symbol.signature?.render() ?: ""}", + currentFile?.location(expression.startOffset) + ) + + val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin) + + expression.dispatchReceiver?.let { composite.statements.add(it) } + expression.extensionReceiver?.let { composite.statements.add(it) } + + for (i in 0 until expression.valueArgumentsCount) { + val arg = expression.getValueArgument(i) + arg?.let { composite.statements.add(it) } + } + + val message = + if (!symbol.isBound) "Unlinked symbol: ${symbol.signature?.render()}" else "Unlinked type in signature of ${symbol.signature?.render()}" + composite.statements.add(expression.throwLinkageError(message)) + + return composite + } + + override fun visitFieldAccess(expression: IrFieldAccessExpression): IrExpression { + expression.transformChildrenVoid() + val symbol = expression.symbol + if (!symbol.isUnlinked()) { + return expression + } + + reportWarning( + "Accessing field contains unlinked symbol ${symbol.signature?.render() ?: ""}", + currentFile?.location(expression.startOffset) + ) + + val composite = IrCompositeImpl(expression.startOffset, expression.endOffset, builtIns.nothingType, expression.origin) + expression.receiver?.let { composite.statements.add(it) } + if (expression is IrSetField) { + composite.statements.add(expression.value) + } + val message = if (!symbol.isBound) "Unlinked symbol: ${symbol.signature?.render()}" else "Field type is unlinked" + composite.statements.add(expression.throwLinkageError(message)) + return composite + } + + override fun visitClassReference(expression: IrClassReference): IrExpression { + if (expression.symbol.isUnlinked()) { + val signRender = expression.symbol.signature?.render() + reportWarning( + "Accessing class contains unlinked symbol ${signRender ?: ""}", + currentFile?.location(expression.startOffset) + ) + return expression.throwLinkageError(signRender) + } + return expression + } + + override fun visitClass(declaration: IrClass): IrStatement { + declaration.transformChildrenVoid() + + fun IrOverridableDeclaration.filterOverriddens() { + overriddenSymbols = overriddenSymbols.filter { it.isBound } + } + + for (member in declaration.declarations) { + when (member) { + is IrSimpleFunction -> member.filterOverriddens() + is IrProperty -> { + member.filterOverriddens() + member.getter?.filterOverriddens() + member.setter?.filterOverriddens() + } + } + } + + return declaration + } + } +} \ No newline at end of file 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 593338965a0..5b1549594c4 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 @@ -412,6 +412,8 @@ fun getIrModuleInfoForKlib( val typeTranslator = TypeTranslatorImpl(symbolTable, configuration.languageVersionSettings, moduleDescriptor) val irBuiltIns = IrBuiltInsOverDescriptors(moduleDescriptor.builtIns, typeTranslator, symbolTable) + val allowUnboundSymbols = configuration[JSConfigurationKeys.PARTIAL_LINKAGE] ?: false + val irLinker = JsIrLinker( null, @@ -421,7 +423,8 @@ fun getIrModuleInfoForKlib( null, null, loweredIcData, - friendModules + friendModules, + allowUnboundSymbols ) val deserializedModuleFragmentsToLib = deserializeDependencies(sortedDependencies, irLinker, mainModuleLib, filesToLoad, mapping) @@ -479,7 +482,8 @@ fun getIrModuleInfoForSourceFiles( feContext, null, loweredIcData, - friendModules + friendModules, + allowUnboundSymbols ) val deserializedModuleFragmentsToLib = deserializeDependencies(allSortedDependencies, irLinker, null,null, mapping) val deserializedModuleFragments = deserializedModuleFragmentsToLib.keys.toList() diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrLinker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrLinker.kt index 809f3b48b2d..8af88be9abe 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrLinker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/lower/serialization/ir/JsIrLinker.kt @@ -30,8 +30,9 @@ class JsIrLinker( override val translationPluginContext: TranslationPluginContext?, private val icData: ICData? = null, private val loweredIcData: Map = emptyMap(), - friendModules: Map> = emptyMap() -) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, emptyList()) { + friendModules: Map> = emptyMap(), + allowUnboundSymbols: Boolean = false +) : KotlinIrLinker(currentModule, messageLogger, builtIns, symbolTable, emptyList(), allowUnboundSymbols) { override val fakeOverrideBuilder = FakeOverrideBuilder(this, symbolTable, JsManglerIr, IrTypeSystemContextImpl(builtIns), friendModules)