backend: implement simple IR validator

This commit is contained in:
Svyatoslav Scherbina
2017-03-09 12:29:26 +07:00
committed by SvyatoslavScherbina
parent d4fc79da43
commit 95935921ba
9 changed files with 418 additions and 27 deletions
@@ -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 <T> visitConst(expression: IrConst<T>) {
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)
}
}
@@ -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<DeclarationKey>()
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)
}
}
@@ -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)
}
@@ -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
@@ -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 {
@@ -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) {
@@ -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)
}
}
@@ -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()
}
}
@@ -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.
)
}