Add AnalysisHandlerExtension extension point for K2Native

frontendPhase is moved out of back phases to allow frontend only mode.

AnalysisHandlerExtension allows compiler plugins to:
1. Intercept and override the default analysis.
2. Utilize the compiler infrastructure to do custom analysis.

A well know plugin on the JVM platform is KAPT.
This commit is contained in:
Ting-Yuan Huang
2021-03-19 02:07:37 -07:00
committed by TeamCityServer
parent 8e7b561b10
commit dba127a4d8
7 changed files with 67 additions and 28 deletions
@@ -180,6 +180,7 @@ class K2Native : CLICompiler<K2NativeCompilerArguments>() {
put(PRINT_DESCRIPTORS, arguments.printDescriptors) put(PRINT_DESCRIPTORS, arguments.printDescriptors)
put(PRINT_LOCATIONS, arguments.printLocations) put(PRINT_LOCATIONS, arguments.printLocations)
put(PRINT_BITCODE, arguments.printBitCode) put(PRINT_BITCODE, arguments.printBitCode)
put(PRINT_FILES, arguments.printFiles)
put(PURGE_USER_LIBS, arguments.purgeUserLibs) put(PURGE_USER_LIBS, arguments.purgeUserLibs)
@@ -199,6 +199,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() {
@Argument(value = "-Xprint-locations", deprecatedName = "--print_locations", description = "Print locations") @Argument(value = "-Xprint-locations", deprecatedName = "--print_locations", description = "Print locations")
var printLocations: Boolean = false var printLocations: Boolean = false
@Argument(value = "-Xprint-files", description = "Print files")
var printFiles: Boolean = false
@Argument(value="-Xpurge-user-libs", deprecatedName = "--purge_user_libs", description = "Don't link unused libraries even explicitly specified") @Argument(value="-Xpurge-user-libs", deprecatedName = "--purge_user_libs", description = "Don't link unused libraries even explicitly specified")
var purgeUserLibs: Boolean = false var purgeUserLibs: Boolean = false
@@ -410,6 +410,8 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
fun shouldPrintLocations() = config.configuration.getBoolean(KonanConfigKeys.PRINT_LOCATIONS) fun shouldPrintLocations() = config.configuration.getBoolean(KonanConfigKeys.PRINT_LOCATIONS)
fun shouldPrintFiles() = config.configuration.getBoolean(KonanConfigKeys.PRINT_FILES)
fun shouldProfilePhases() = config.phaseConfig.needProfiling fun shouldProfilePhases() = config.phaseConfig.needProfiling
fun shouldContainDebugInfo() = config.debug fun shouldContainDebugInfo() = config.debug
@@ -106,6 +106,8 @@ class KonanConfigKeys {
= CompilerConfigurationKey.create("print ir with descriptors") = CompilerConfigurationKey.create("print ir with descriptors")
val PRINT_LOCATIONS: CompilerConfigurationKey<Boolean> val PRINT_LOCATIONS: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("print locations") = CompilerConfigurationKey.create("print locations")
val PRINT_FILES: CompilerConfigurationKey<Boolean>
= CompilerConfigurationKey.create("print files")
val PRODUCE: CompilerConfigurationKey<CompilerOutputKind> val PRODUCE: CompilerConfigurationKey<CompilerOutputKind>
= CompilerConfigurationKey.create("compiler output kind") = CompilerConfigurationKey.create("compiler output kind")
val PURGE_USER_LIBS: CompilerConfigurationKey<Boolean> val PURGE_USER_LIBS: CompilerConfigurationKey<Boolean>
@@ -5,9 +5,12 @@
package org.jetbrains.kotlin.backend.konan package org.jetbrains.kotlin.backend.konan
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.backend.common.phaser.CompilerPhase import org.jetbrains.kotlin.backend.common.phaser.CompilerPhase
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.kotlin.utils.addToStdlib.cast
fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEnvironment) { fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEnvironment) {
@@ -25,6 +28,8 @@ fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEnvironme
if (konanConfig.infoArgsOnly) return if (konanConfig.infoArgsOnly) return
if (!context.frontendPhase()) return
try { try {
toplevelPhase.cast<CompilerPhase<Context, Unit, Unit>>().invokeToplevel(context.phaseConfig, context, Unit) toplevelPhase.cast<CompilerPhase<Context, Unit, Unit>>().invokeToplevel(context.phaseConfig, context, Unit)
} finally { } finally {
@@ -36,3 +41,29 @@ fun runTopLevelPhases(konanConfig: KonanConfig, environment: KotlinCoreEnvironme
} }
} }
// returns true if should generate code.
internal fun Context.frontendPhase(): Boolean {
lateinit var analysisResult: AnalysisResult
do {
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector,
environment.configuration.languageVersionSettings)
// Build AST and binding info.
analyzerWithCompilerReport.analyzeAndReport(environment.getSourceFiles()) {
TopDownAnalyzerFacadeForKonan.analyzeFiles(environment.getSourceFiles(), this)
}
if (analyzerWithCompilerReport.hasErrors()) {
throw KonanCompilationException()
}
analysisResult = analyzerWithCompilerReport.analysisResult
if (analysisResult is AnalysisResult.RetryWithAdditionalRoots) {
environment.addKotlinSourceRoots(analysisResult.additionalKotlinRoots)
}
} while(analysisResult is AnalysisResult.RetryWithAdditionalRoots)
moduleDescriptor = analysisResult.moduleDescriptor
bindingContext = analysisResult.bindingContext
return analysisResult.shouldGenerateCode
}
@@ -28,9 +28,11 @@ import org.jetbrains.kotlin.library.metadata.NullFlexibleTypeDeserializer
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.* import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.serialization.konan.KotlinResolvedModuleDescriptors import org.jetbrains.kotlin.serialization.konan.KotlinResolvedModuleDescriptors
import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult
internal object TopDownAnalyzerFacadeForKonan { internal object TopDownAnalyzerFacadeForKonan {
@@ -64,7 +66,7 @@ internal object TopDownAnalyzerFacadeForKonan {
additionalPackages += functionInterfacePackageFragmentProvider(projectContext.storageManager, module) additionalPackages += functionInterfacePackageFragmentProvider(projectContext.storageManager, module)
} }
return analyzeFilesWithGivenTrace(files, BindingTraceContext(), moduleContext, context, additionalPackages) return analyzeFilesWithGivenTrace(files, BindingTraceContext(), moduleContext, context, projectContext, additionalPackages)
} }
fun analyzeFilesWithGivenTrace( fun analyzeFilesWithGivenTrace(
@@ -72,15 +74,16 @@ internal object TopDownAnalyzerFacadeForKonan {
trace: BindingTrace, trace: BindingTrace,
moduleContext: ModuleContext, moduleContext: ModuleContext,
context: Context, context: Context,
projectContext: ProjectContext,
additionalPackages: List<PackageFragmentProvider> = emptyList() additionalPackages: List<PackageFragmentProvider> = emptyList()
): AnalysisResult { ): AnalysisResult {
// we print out each file we compile if frontend phase is verbose // we print out each file we compile if frontend phase is verbose
files.takeIf { files.takeIf {
frontendPhase in context.phaseConfig.verbose context.shouldPrintFiles()
} ?.forEach(::println) } ?.forEach(::println)
val analyzerForKonan = createTopDownAnalyzerProviderForKonan( val container = createTopDownAnalyzerProviderForKonan(
moduleContext, trace, moduleContext, trace,
FileBasedDeclarationProviderFactory(moduleContext.storageManager, files), FileBasedDeclarationProviderFactory(moduleContext.storageManager, files),
context.config.configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)!!, context.config.configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)!!,
@@ -89,10 +92,28 @@ internal object TopDownAnalyzerFacadeForKonan {
initContainer(context.config) initContainer(context.config)
}.apply { }.apply {
postprocessComponents(context, files) postprocessComponents(context, files)
}.get<LazyTopDownAnalyzer>() }
analyzerForKonan.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files) val analyzerForKonan = container.get<LazyTopDownAnalyzer>()
return AnalysisResult.success(trace.bindingContext, moduleContext.module) val project = context.config.project
val moduleDescriptor = moduleContext.module
val analysisHandlerExtensions = AnalysisHandlerExtension.getInstances(project)
// Mimic the behavior in the jvm frontend. The extensions have 2 chances to override the normal analysis:
// * If any of the extensions returns a non-null result, use it. Otherwise do the normal analysis.
// * `analysisCompleted` can be used to override the result, too.
var result = analysisHandlerExtensions.firstNotNullResult { extension ->
extension.doAnalysis(project, moduleDescriptor, projectContext, files, trace, container)
} ?: run {
analyzerForKonan.analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files)
AnalysisResult.success(trace.bindingContext, moduleDescriptor)
}
result = analysisHandlerExtensions.firstNotNullResult { extension ->
extension.analysisCompleted(project, moduleDescriptor, trace, files)
} ?: result
return result
} }
fun checkForErrors(files: Collection<KtFile>, bindingContext: BindingContext) { fun checkForErrors(files: Collection<KtFile>, bindingContext: BindingContext) {
@@ -75,26 +75,6 @@ internal fun konanUnitPhase(
op: Context.() -> Unit op: Context.() -> Unit
) = namedOpUnitPhase(name, description, prerequisite, op) ) = namedOpUnitPhase(name, description, prerequisite, op)
internal val frontendPhase = konanUnitPhase(
op = {
val environment = environment
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector,
environment.configuration.languageVersionSettings)
// Build AST and binding info.
analyzerWithCompilerReport.analyzeAndReport(environment.getSourceFiles()) {
TopDownAnalyzerFacadeForKonan.analyzeFiles(environment.getSourceFiles(), this)
}
if (analyzerWithCompilerReport.hasErrors()) {
throw KonanCompilationException()
}
moduleDescriptor = analyzerWithCompilerReport.analysisResult.moduleDescriptor
bindingContext = analyzerWithCompilerReport.analysisResult.bindingContext
},
name = "Frontend",
description = "Frontend builds AST"
)
/** /**
* Valid from [createSymbolTablePhase] until [destroySymbolTablePhase]. * Valid from [createSymbolTablePhase] until [destroySymbolTablePhase].
*/ */
@@ -425,8 +405,7 @@ private val backendCodegen = namedUnitPhase(
val toplevelPhase: CompilerPhase<*, Unit, Unit> = namedUnitPhase( val toplevelPhase: CompilerPhase<*, Unit, Unit> = namedUnitPhase(
name = "Compiler", name = "Compiler",
description = "The whole compilation process", description = "The whole compilation process",
lower = frontendPhase then lower = createSymbolTablePhase then
createSymbolTablePhase then
objCExportPhase then objCExportPhase then
buildCExportsPhase then buildCExportsPhase then
psiToIrPhase then psiToIrPhase then