JVM IR: support cyclic module dependencies

The only way to make the compiler compile several modules with a
dependency loop is via the "build file", given by -Xbuild-file and used
in the JPS (IntelliJ built-in build system) plugin.

For the old frontend/backend it works like this: we _analyze_ sources of
all modules once, as if it's one big module, and then for each module,
we _generate_ (invoke backend) only sources of that module. Backend
needs to be invoked separately per-module because every module has its
own destination directory specified in the build file.

For JVM IR, this separation into just two steps, analyze and generate,
was problematic because there's psi2ir, which works like frontend, in
that it needs the global analysis result to be able to create and link
IR correctly. So, in case of JVM IR, we need to run psi2ir on the whole
module after analysis and before generation.

In this change, psi2ir is run on the whole module via
`CodegenFactory.convertToIr` (which does nothing in the old backend),
and then parts of the resulting IR module are extracted according to the
original separation of the combined module into individual modules via
`getModuleChunkBackendInput` by matching IrFile against KtFile. And
then, backend is run for each such module.

 #KT-45915 Fixed
 #KT-48668 Fixed
This commit is contained in:
Alexander Udalov
2021-09-14 01:19:10 +02:00
parent 5d6ca27c93
commit 987a346015
9 changed files with 100 additions and 13 deletions
@@ -31,6 +31,11 @@ import org.jetbrains.kotlin.resolve.BindingContext
interface CodegenFactory {
fun convertToIr(input: IrConversionInput): BackendInput
// Extracts a part of the BackendInput which corresponds only to the specified source files.
// This is needed to support cyclic module dependencies, which are allowed in JPS, where frontend and psi2ir is run on sources of all
// modules combined, and then backend is run on each individual module.
fun getModuleChunkBackendInput(wholeBackendInput: BackendInput, sourceFiles: Collection<KtFile>): BackendInput
fun generateModule(state: GenerationState, input: BackendInput)
class IrConversionInput(
@@ -68,6 +73,11 @@ object DefaultCodegenFactory : CodegenFactory {
override fun convertToIr(input: CodegenFactory.IrConversionInput): CodegenFactory.BackendInput = DummyOldBackendInput
override fun getModuleChunkBackendInput(
wholeBackendInput: CodegenFactory.BackendInput,
sourceFiles: Collection<KtFile>,
): CodegenFactory.BackendInput = DummyOldBackendInput
override fun generateModule(state: GenerationState, input: CodegenFactory.BackendInput) {
val filesInPackages = MultiMap<FqName, KtFile>()
val filesInMultifileClasses = MultiMap<FqName, KtFile>()
@@ -33,8 +33,8 @@ import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.toLogger
import org.jetbrains.kotlin.cli.jvm.config.*
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CodegenFactory
import org.jetbrains.kotlin.codegen.DefaultCodegenFactory
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -113,6 +113,8 @@ object KotlinToJVMBytecodeCompiler {
val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
val (codegenFactory, wholeBackendInput) = convertToIr(environment, result)
for (module in chunk) {
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
@@ -120,7 +122,8 @@ object KotlinToJVMBytecodeCompiler {
if (!checkKotlinPackageUsage(environment.configuration, ktFiles)) return false
val moduleConfiguration = projectConfiguration.applyModuleProperties(module, buildFile)
outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module)
val backendInput = codegenFactory.getModuleChunkBackendInput(wholeBackendInput, ktFiles)
outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module, codegenFactory, backendInput)
}
return writeOutputs(environment.project, projectConfiguration, chunk, outputs, mainClassFqName)
@@ -234,7 +237,28 @@ object KotlinToJVMBytecodeCompiler {
result.throwIfError()
return generate(environment, environment.configuration, result, environment.getSourceFiles(), null)
val (codegenFactory, backendInput) = convertToIr(environment, result)
return generate(environment, environment.configuration, result, environment.getSourceFiles(), null, codegenFactory, backendInput)
}
private fun convertToIr(environment: KotlinCoreEnvironment, result: AnalysisResult): Pair<CodegenFactory, CodegenFactory.BackendInput> {
val configuration = environment.configuration
val codegenFactory =
if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory(
configuration, configuration.get(CLIConfigurationKeys.PHASE_CONFIG)
) else DefaultCodegenFactory
val input = CodegenFactory.IrConversionInput(
environment.project,
environment.getSourceFiles(),
configuration,
result.moduleDescriptor,
result.bindingContext,
configuration.languageVersionSettings,
false,
)
val backendInput = codegenFactory.convertToIr(input)
return Pair(codegenFactory, backendInput)
}
fun analyze(environment: KotlinCoreEnvironment): AnalysisResult? {
@@ -304,9 +328,11 @@ object KotlinToJVMBytecodeCompiler {
configuration: CompilerConfiguration,
result: AnalysisResult,
sourceFiles: List<KtFile>,
module: Module?
module: Module?,
codegenFactory: CodegenFactory,
backendInput: CodegenFactory.BackendInput,
): GenerationState {
val generationState = GenerationState.Builder(
val state = GenerationState.Builder(
environment.project,
ClassBuilderFactories.BINARIES,
result.moduleDescriptor,
@@ -314,11 +340,7 @@ object KotlinToJVMBytecodeCompiler {
sourceFiles,
configuration
)
.codegenFactory(
if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory(
configuration, configuration.get(CLIConfigurationKeys.PHASE_CONFIG)
) else DefaultCodegenFactory
)
.codegenFactory(codegenFactory)
.withModule(module)
.onIndependentPartCompilationEnd(createOutputFilesFlushingCallbackIfPossible(configuration))
.build()
@@ -328,7 +350,14 @@ object KotlinToJVMBytecodeCompiler {
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
performanceManager?.notifyGenerationStarted()
KotlinCodegenFacade.compileCorrectFiles(generationState)
state.beforeCompile()
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
codegenFactory.generateModule(state, backendInput)
CodegenFactory.doCheckCancelled(state)
state.factory.done()
performanceManager?.notifyGenerationFinished()
@@ -336,13 +365,13 @@ object KotlinToJVMBytecodeCompiler {
AnalyzerWithCompilerReport.reportDiagnostics(
FilteredJvmDiagnostics(
generationState.collectedExtraJvmDiagnostics,
state.collectedExtraJvmDiagnostics,
result.bindingContext.diagnostics
),
environment.messageCollector
)
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
return generationState
return state
}
}
@@ -27,8 +27,10 @@ import org.jetbrains.kotlin.ir.builders.TranslationPluginContext
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.declarations.impl.IrModuleFragmentImpl
import org.jetbrains.kotlin.ir.linkage.IrProvider
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration
import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator
import org.jetbrains.kotlin.psi2ir.generators.DeclarationStubGeneratorImpl
@@ -168,6 +170,21 @@ open class JvmIrCodegenFactory(
return result.toList()
}
override fun getModuleChunkBackendInput(
wholeBackendInput: CodegenFactory.BackendInput,
sourceFiles: Collection<KtFile>,
): CodegenFactory.BackendInput {
wholeBackendInput as JvmIrBackendInput
val moduleChunk = sourceFiles.toSet()
val wholeModule = wholeBackendInput.irModuleFragment
return wholeBackendInput.copy(
IrModuleFragmentImpl(wholeModule.descriptor, wholeModule.irBuiltins, wholeModule.files.filter { file ->
file.getKtFile() in moduleChunk
})
)
}
override fun generateModule(state: GenerationState, input: CodegenFactory.BackendInput) {
val (irModuleFragment, symbolTable, customPhaseConfig, irProviders, extensions, backendExtension, notifyCodegenStart) =
input as JvmIrBackendInput
@@ -0,0 +1 @@
-Xbuild-file=$TESTDATA_DIR$/modulesWithDependencyCycle.xml
@@ -0,0 +1 @@
OK
@@ -0,0 +1,8 @@
<modules>
<module name="a" type="java-production" outputDir="$TEMP_DIR$/a">
<sources path="$TESTDATA_DIR$/modulesWithDependencyCycleA.kt"/>
</module>
<module name="b" type="java-production" outputDir="$TEMP_DIR$/b">
<sources path="$TESTDATA_DIR$/modulesWithDependencyCycleB.kt"/>
</module>
</modules>
@@ -0,0 +1,8 @@
package a
import b.Y
class A
class X(val y: Y? = null)
class Z : Y()
@@ -0,0 +1,8 @@
package b
import a.A
import a.X
class B(val a: A? = null)
open class Y(val x: X? = null)
@@ -656,6 +656,11 @@ public class CliTestGenerated extends AbstractCliTest {
runTest("compiler/testData/cli/jvm/mixingArgfilesAndUsualArgs.args");
}
@TestMetadata("modulesWithDependencyCycle.args")
public void testModulesWithDependencyCycle() throws Exception {
runTest("compiler/testData/cli/jvm/modulesWithDependencyCycle.args");
}
@TestMetadata("multipleTextRangesInDiagnosticsOrder.args")
public void testMultipleTextRangesInDiagnosticsOrder() throws Exception {
runTest("compiler/testData/cli/jvm/multipleTextRangesInDiagnosticsOrder.args");