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:
@@ -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>()
|
||||
|
||||
+42
-13
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+17
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user