From 95935921bad8babe144f232dcaee519f5727b782 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Thu, 9 Mar 2017 12:29:26 +0700 Subject: [PATCH] backend: implement simple IR validator --- .../backend/common/CheckIrElementVisitor.kt | 160 ++++++++++++++ .../kotlin/backend/common/IrValidator.kt | 200 ++++++++++++++++++ .../jetbrains/kotlin/backend/common/Utils.kt | 27 +++ .../jetbrains/kotlin/backend/konan/Context.kt | 2 +- .../backend/konan/KonanBackendContext.kt | 3 +- .../kotlin/backend/konan/KonanDriver.kt | 13 +- .../kotlin/backend/konan/KonanLower.kt | 2 + .../kotlin/backend/konan/Reporting.kt | 25 +-- .../org/jetbrains/kotlin/ir/util/IrUtils.kt | 13 ++ 9 files changed, 418 insertions(+), 27 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/CheckIrElementVisitor.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/IrValidator.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/Utils.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/CheckIrElementVisitor.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/CheckIrElementVisitor.kt new file mode 100644 index 00000000000..acee1561832 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/CheckIrElementVisitor.kt @@ -0,0 +1,160 @@ +package org.jetbrains.kotlin.backend.common + +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.expressions.* +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.makeNullable + +typealias ReportError = (element: IrElement, message: String) -> Unit + +class CheckIrElementVisitor(val builtIns: KotlinBuiltIns, val reportError: ReportError) : IrElementVisitorVoid { + + override fun visitElement(element: IrElement) { + // Nothing to do. + } + + private fun IrExpression.ensureTypeIs(expectedType: KotlinType) { + if (expectedType != type) { + reportError(this, "unexpected expression.type: expected $expectedType, got ${type}") + } + } + + override fun visitConst(expression: IrConst) { + super.visitConst(expression) + + val naturalType = when (expression.kind) { + IrConstKind.Null -> builtIns.nullableNothingType + IrConstKind.Boolean -> builtIns.booleanType + IrConstKind.Char -> builtIns.charType + IrConstKind.Byte -> builtIns.byteType + IrConstKind.Short -> builtIns.shortType + IrConstKind.Int -> builtIns.intType + IrConstKind.Long -> builtIns.longType + IrConstKind.String -> builtIns.stringType + IrConstKind.Float -> builtIns.floatType + IrConstKind.Double -> builtIns.doubleType + } + + expression.ensureTypeIs(naturalType) + } + + override fun visitStringConcatenation(expression: IrStringConcatenation) { + super.visitStringConcatenation(expression) + + expression.ensureTypeIs(builtIns.stringType) + } + + override fun visitGetObjectValue(expression: IrGetObjectValue) { + super.visitGetObjectValue(expression) + + expression.ensureTypeIs(expression.descriptor.defaultType) + } + + // TODO: visitGetEnumValue + + override fun visitGetValue(expression: IrGetValue) { + super.visitGetValue(expression) + + expression.ensureTypeIs(expression.descriptor.type) + } + + override fun visitSetVariable(expression: IrSetVariable) { + super.visitSetVariable(expression) + + expression.ensureTypeIs(builtIns.unitType) + } + + override fun visitGetField(expression: IrGetField) { + super.visitGetField(expression) + + expression.ensureTypeIs(expression.descriptor.type) + } + + override fun visitSetField(expression: IrSetField) { + super.visitSetField(expression) + + expression.ensureTypeIs(builtIns.unitType) + } + + override fun visitCall(expression: IrCall) { + super.visitCall(expression) + + val returnType = expression.descriptor.returnType + if (returnType == null) { + reportError(expression, "${expression.descriptor} return type is null") + } else { + expression.ensureTypeIs(returnType) + } + } + + override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall) { + super.visitDelegatingConstructorCall(expression) + + expression.ensureTypeIs(builtIns.unitType) + } + + override fun visitEnumConstructorCall(expression: IrEnumConstructorCall) { + super.visitEnumConstructorCall(expression) + + expression.ensureTypeIs(builtIns.unitType) + } + + override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall) { + super.visitInstanceInitializerCall(expression) + + expression.ensureTypeIs(builtIns.unitType) + } + + override fun visitTypeOperator(expression: IrTypeOperatorCall) { + super.visitTypeOperator(expression) + + val operator = expression.operator + val typeOperand = expression.typeOperand + + val naturalType = when (operator) { + IrTypeOperator.CAST, + IrTypeOperator.IMPLICIT_CAST, + IrTypeOperator.IMPLICIT_NOTNULL, + IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, + IrTypeOperator.IMPLICIT_INTEGER_COERCION -> typeOperand + + IrTypeOperator.SAFE_CAST -> typeOperand.makeNullable() + + IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF -> builtIns.booleanType + } + + if (operator == IrTypeOperator.IMPLICIT_COERCION_TO_UNIT && typeOperand != builtIns.unitType) { + reportError(expression, "typeOperand is $typeOperand") + } + + // TODO: check IMPLICIT_NOTNULL's argument type. + + expression.ensureTypeIs(naturalType) + } + + override fun visitLoop(loop: IrLoop) { + super.visitLoop(loop) + + loop.ensureTypeIs(builtIns.unitType) + } + + override fun visitBreakContinue(jump: IrBreakContinue) { + super.visitBreakContinue(jump) + + jump.ensureTypeIs(builtIns.nothingType) + } + + override fun visitReturn(expression: IrReturn) { + super.visitReturn(expression) + + expression.ensureTypeIs(builtIns.nothingType) + } + + override fun visitThrow(expression: IrThrow) { + super.visitThrow(expression) + + expression.ensureTypeIs(builtIns.nothingType) + } +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/IrValidator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/IrValidator.kt new file mode 100644 index 00000000000..c1d13217250 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/IrValidator.kt @@ -0,0 +1,200 @@ +package org.jetbrains.kotlin.backend.common + +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.descriptors.impl.TypeAliasConstructorDescriptor +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.* +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.resolve.descriptorUtil.module + +fun validateIrFile(context: BackendContext, irFile: IrFile) { + val visitor = IrValidator(context) + irFile.acceptVoid(visitor) +} + +fun validateIrModule(context: BackendContext, irModule: IrModuleFragment) { + val visitor = IrValidator(context) + irModule.acceptVoid(visitor) + + val moduleDeclarations = visitor.foundDeclarations + + irModule.acceptVoid(object : IrElementVisitorVoid { + lateinit var currentFile: IrFile + + override fun visitFile(declaration: IrFile) { + currentFile = declaration + declaration.acceptChildrenVoid(this) + } + + override fun visitElement(element: IrElement) { + element.acceptChildrenVoid(this) + } + + override fun visitProperty(declaration: IrProperty) { + if (declaration.descriptor.modality == Modality.ABSTRACT) { + return // Workaround TODO + } + + declaration.acceptChildrenVoid(this) + } + + override fun visitDeclarationReference(expression: IrDeclarationReference) { + expression.acceptChildrenVoid(this) + + val declarationDescriptor = expression.irDeclarationDescriptor ?: return + + val declarationModule = declarationDescriptor.module + + if (declarationModule == irModule.descriptor && !moduleDeclarations.hasTarget(expression)) { + context.reportIrValidationError( + "declaration $declarationDescriptor is missing;\n" + + "references from:\n" + + expression.render(), + + currentFile, expression) + } + } + }) +} + +private fun BackendContext.reportIrValidationError(message: String, irFile: IrFile, irElement: IrElement) { + this.reportWarning("[IR VALIDATION] $message", irFile, irElement) + // TODO: throw an exception after fixing bugs leading to invalid IR. +} + +/** + * Descriptor to be found in IR element declaring target of this reference, + * or `null` if it is not supported yet. + */ +private val IrDeclarationReference.irDeclarationDescriptor: DeclarationDescriptor? + get() { + when (this) { + is IrFieldAccessExpression -> return this.descriptor.original + + is IrMemberAccessExpression -> { + val original = this.descriptor.original + if (original is CallableMemberDescriptor && !original.kind.isReal) { + return null + } + + if (original is TypeAliasConstructorDescriptor) { + return original.underlyingConstructorDescriptor + } + + return original + } + + is IrGetValue -> { + if (this.descriptor is ParameterDescriptor) { + return null + } + + return this.descriptor + } + + else -> return this.descriptor + } + } + +private val IrDeclarationReference.declarationKind: IrDeclarationKind + get() = when (this) { + is IrClassReference, is IrGetObjectValue -> IrDeclarationKind.CLASS + is IrFieldAccessExpression -> IrDeclarationKind.FIELD + is IrGetEnumValue -> IrDeclarationKind.ENUM_ENTRY + is IrValueAccessExpression -> IrDeclarationKind.VARIABLE + is IrMemberAccessExpression -> when (this.descriptor) { + is ConstructorDescriptor -> IrDeclarationKind.CONSTRUCTOR + is PropertyDescriptor -> IrDeclarationKind.PROPERTY + is VariableDescriptorWithAccessors -> IrDeclarationKind.LOCAL_PROPERTY + else -> IrDeclarationKind.FUNCTION + } + else -> TODO() + } + +/** + * The collection of IR declarations. + */ +private class Declarations { + private data class DeclarationKey(val descriptor: DeclarationDescriptor, val kind: IrDeclarationKind) + + private val declarations = mutableSetOf() + + private fun createKey(declaration: IrDeclaration) = + DeclarationKey(declaration.descriptor, declaration.declarationKind) + + fun add(declaration: IrDeclaration) { + declarations.add(createKey(declaration)) + } + + fun addParameterOf(aCatch: IrCatch) { + val kind = IrDeclarationKind.VARIABLE + val key = DeclarationKey(aCatch.parameter, kind) + declarations.add(key) + } + + fun isAlreadyDeclared(declaration: IrDeclaration): Boolean { + return createKey(declaration) in declarations + } + + fun hasTarget(reference: IrDeclarationReference): Boolean { + val descriptor = reference.irDeclarationDescriptor ?: return false + val kind = reference.declarationKind + + return DeclarationKey(descriptor, kind) in declarations + } +} + +private class IrValidator(val context: BackendContext) : IrElementVisitorVoid { + + val foundDeclarations = Declarations() + + val builtIns = context.builtIns + lateinit var currentFile: IrFile + + override fun visitFile(declaration: IrFile) { + currentFile = declaration + super.visitFile(declaration) + } + + private fun error(element: IrElement, message: String) { + // TODO: render all element's parents. + context.reportIrValidationError( + "$message\n" + + element.render(), + currentFile, element) + } + + private val elementChecker = CheckIrElementVisitor(builtIns, this::error) + + override fun visitElement(element: IrElement) { + element.acceptVoid(elementChecker) + element.acceptChildrenVoid(this) + } + + private fun recordDeclaration(declaration: IrDeclaration) { + if (foundDeclarations.isAlreadyDeclared(declaration)) { + error(declaration, "redeclaration") + } else { + foundDeclarations.add(declaration) + } + } + + override fun visitDeclaration(declaration: IrDeclaration) { + recordDeclaration(declaration) + super.visitDeclaration(declaration) + } + + override fun visitAnonymousInitializer(declaration: IrAnonymousInitializer) { + // Do not treat anonymous initializers as declarations, because they are not unique. + super.visitDeclaration(declaration) + } + + override fun visitCatch(aCatch: IrCatch) { + foundDeclarations.addParameterOf(aCatch) + super.visitCatch(aCatch) + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/Utils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/Utils.kt new file mode 100644 index 00000000000..95495a1b4bd --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/common/Utils.kt @@ -0,0 +1,27 @@ +package org.jetbrains.kotlin.backend.common + +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.konan.KonanBackendContext +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.util.getCompilerMessageLocation + +// TODO: declare as a member of BackendContext +val BackendContext.compilerConfiguration: CompilerConfiguration + get() = when (this) { + is KonanBackendContext -> this.config.configuration + is JvmBackendContext -> this.state.configuration + else -> TODO(this.toString()) + } + +val BackendContext.messageCollector: MessageCollector + get() = this.compilerConfiguration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + +fun BackendContext.reportWarning(message: String, irFile: IrFile, irElement: IrElement) { + val location = irElement.getCompilerMessageLocation(irFile) + this.messageCollector.report(CompilerMessageSeverity.WARNING, message, location) +} \ No newline at end of file diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt index 2b783624ca7..d50e222d081 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Context.kt @@ -184,7 +184,7 @@ class ReflectionTypes(module: ModuleDescriptor) { } } -internal class Context(val config: KonanConfig) : KonanBackendContext() { +internal class Context(config: KonanConfig) : KonanBackendContext(config) { var moduleDescriptor: ModuleDescriptor? = null diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt index d28da612e4c..892abf3f030 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanBackendContext.kt @@ -2,9 +2,8 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.common.BackendContext import org.jetbrains.kotlin.backend.konan.descriptors.KonanSharedVariablesManager -import org.jetbrains.kotlin.backend.konan.KonanPlatform -abstract internal class KonanBackendContext : BackendContext { +abstract internal class KonanBackendContext(val config: KonanConfig) : BackendContext { override val builtIns = KonanPlatform.builtIns override val sharedVariablesManager by lazy { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanDriver.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanDriver.kt index 71ea110f775..46c76eba407 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanDriver.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanDriver.kt @@ -1,6 +1,8 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.backend.common.messageCollector +import org.jetbrains.kotlin.backend.common.validateIrModule import org.jetbrains.kotlin.backend.konan.ir.ModuleIndex import org.jetbrains.kotlin.backend.konan.llvm.emitLLVM import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys @@ -40,12 +42,10 @@ public fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEn if (config.kotlinSourceRoots.isEmpty()) return - val collector = config.getNotNull( - CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) - - val analyzerWithCompilerReport = AnalyzerWithCompilerReport(collector) - val context = Context(konanConfig) + + val analyzerWithCompilerReport = AnalyzerWithCompilerReport(context.messageCollector) + val phaser = PhaseManager(context) phaser.phase(KonanPhase.FRONTEND) { @@ -67,10 +67,13 @@ public fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEn environment.getSourceFiles(), bindingContext) context.irModule = module + + validateIrModule(context, module) } phaser.phase(KonanPhase.BACKEND) { phaser.phase(KonanPhase.LOWER) { KonanLower(context).lower() + validateIrModule(context, context.ir.irModule) context.ir.moduleIndexForCodegen = ModuleIndex(context.ir.irModule) } phaser.phase(KonanPhase.BITCODE) { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index fff242f3b39..9b7188069b7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -2,6 +2,7 @@ package org.jetbrains.kotlin.backend.konan import org.jetbrains.kotlin.backend.common.runOnFilePostfix import org.jetbrains.kotlin.backend.common.lower.* +import org.jetbrains.kotlin.backend.common.validateIrFile import org.jetbrains.kotlin.backend.konan.lower.* import org.jetbrains.kotlin.ir.declarations.IrFile @@ -67,6 +68,7 @@ internal class KonanLower(val context: Context) { DirectBridgesCallsLowering(context).runOnFilePostfix(irFile) } phaser.phase(KonanPhase.AUTOBOX) { + validateIrFile(context, irFile) Autoboxing(context).lower(irFile) } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt index 96b33676548..dfec8414449 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Reporting.kt @@ -1,27 +1,14 @@ package org.jetbrains.kotlin.backend.konan -import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys -import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.backend.common.BackendContext +import org.jetbrains.kotlin.backend.common.messageCollector import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity -import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.util.getCompilerMessageLocation -internal val Context.messageCollector: MessageCollector - get() = this.config.configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) - -internal fun getCompilerMessageLocation(irFile: IrFile, irElement: IrElement): CompilerMessageLocation { - val sourceRangeInfo = irFile.fileEntry.getSourceRangeInfo(irElement.startOffset, irElement.endOffset) - return CompilerMessageLocation.create( - path = sourceRangeInfo.filePath, - line = sourceRangeInfo.startLineNumber, - column = sourceRangeInfo.startColumnNumber, - lineContent = null // TODO: retrieve the line content. - ) -} - -internal fun Context.reportCompilationError(message: String, irFile: IrFile, irElement: IrElement): Nothing { - val location = getCompilerMessageLocation(irFile, irElement) +internal fun BackendContext.reportCompilationError(message: String, irFile: IrFile, irElement: IrElement): Nothing { + val location = irElement.getCompilerMessageLocation(irFile) this.messageCollector.report(CompilerMessageSeverity.ERROR, message, location) throw KonanCompilationException() -} \ No newline at end of file +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils.kt index b674ebfdeca..a0186e2fe5b 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/ir/util/IrUtils.kt @@ -1,6 +1,9 @@ package org.jetbrains.kotlin.ir.util +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.descriptors.ParameterDescriptor +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.expressions.* /** @@ -64,3 +67,13 @@ internal fun IrExpression.isNullConst() = this is IrConst<*> && this.kind == IrC fun IrCall.usesDefaultArguments(): Boolean = this.descriptor.valueParameters.any { this.getValueArgument(it) == null } + +fun IrElement.getCompilerMessageLocation(containingFile: IrFile): CompilerMessageLocation { + val sourceRangeInfo = containingFile.fileEntry.getSourceRangeInfo(this.startOffset, this.endOffset) + return CompilerMessageLocation.create( + path = sourceRangeInfo.filePath, + line = sourceRangeInfo.startLineNumber, + column = sourceRangeInfo.startColumnNumber, + lineContent = null // TODO: retrieve the line content. + ) +}