Skip to content

Commit a7fef48

Browse files
committed
JVM IR: fix support of cyclic module dependencies
Previous episode was at 987a346 (KT-45915). Apparently, it's not enough to run psi2ir for all modules, and then backend for all modules. If there are several modules in the chunk, IR in any one of them can reference IR of any other one after psi2ir. So we would end up in a situation where we're running codegen for the first module, but the second module is completely unlowered. This would break some assumptions in the codegen, for example in KT-49575, codegen would see a reference to a top-level function from another module, and would fail because the function has no containing class (since file facades have not been generated yet!), and thus must be an intrinsic, yet no such intrinsic is known to the codegen. The solution is to run lowerings first on all modules, and then run codegen on all modules. The kind of error explained above shouldn't be possible anymore, because lowerings have to deal both with lowered and unlowered code from other files all the time, so lowerings can't assume that reference from other module is lowered either (or unlowered). The code is not very nice, but hopefully it can be improved as soon as we get rid of the old JVM backend (and maybe later with the migration to FIR too). #KT-49575 Fixed
1 parent 5012aeb commit a7fef48

File tree

7 files changed

+99
-37
lines changed

7 files changed

+99
-37
lines changed

compiler/backend/src/org/jetbrains/kotlin/codegen/CodegenFactory.kt

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,14 @@ interface CodegenFactory {
3636
// modules combined, and then backend is run on each individual module.
3737
fun getModuleChunkBackendInput(wholeBackendInput: BackendInput, sourceFiles: Collection<KtFile>): BackendInput
3838

39-
fun generateModule(state: GenerationState, input: BackendInput)
39+
fun invokeLowerings(state: GenerationState, input: BackendInput): CodegenInput
40+
41+
fun invokeCodegen(input: CodegenInput)
42+
43+
fun generateModule(state: GenerationState, input: BackendInput) {
44+
val result = invokeLowerings(state, input)
45+
invokeCodegen(result)
46+
}
4047

4148
class IrConversionInput(
4249
val project: Project,
@@ -57,8 +64,14 @@ interface CodegenFactory {
5764
}
5865
}
5966

67+
// These opaque interfaces are needed to transfer the result of psi2ir to lowerings to codegen.
68+
// Hopefully this can be refactored/simplified once the old JVM backend code is removed.
6069
interface BackendInput
6170

71+
interface CodegenInput {
72+
val state: GenerationState
73+
}
74+
6275
companion object {
6376
fun doCheckCancelled(state: GenerationState) {
6477
if (state.classBuilderMode.generateBodies) {
@@ -69,7 +82,9 @@ interface CodegenFactory {
6982
}
7083

7184
object DefaultCodegenFactory : CodegenFactory {
72-
object DummyOldBackendInput : CodegenFactory.BackendInput
85+
private object DummyOldBackendInput : CodegenFactory.BackendInput
86+
87+
private class DummyOldCodegenInput(override val state: GenerationState) : CodegenFactory.CodegenInput
7388

7489
override fun convertToIr(input: CodegenFactory.IrConversionInput): CodegenFactory.BackendInput = DummyOldBackendInput
7590

@@ -78,7 +93,7 @@ object DefaultCodegenFactory : CodegenFactory {
7893
sourceFiles: Collection<KtFile>,
7994
): CodegenFactory.BackendInput = DummyOldBackendInput
8095

81-
override fun generateModule(state: GenerationState, input: CodegenFactory.BackendInput) {
96+
override fun invokeLowerings(state: GenerationState, input: CodegenFactory.BackendInput): CodegenFactory.CodegenInput {
8297
val filesInPackages = MultiMap<FqName, KtFile>()
8398
val filesInMultifileClasses = MultiMap<FqName, KtFile>()
8499

@@ -103,6 +118,12 @@ object DefaultCodegenFactory : CodegenFactory {
103118
CodegenFactory.doCheckCancelled(state)
104119
generatePackage(state, packageFqName, filesInPackages.get(packageFqName))
105120
}
121+
122+
return DummyOldCodegenInput(state)
123+
}
124+
125+
override fun invokeCodegen(input: CodegenFactory.CodegenInput) {
126+
// Do nothing
106127
}
107128

108129
private fun generateMultifileClass(state: GenerationState, multifileClassFqName: FqName, files: Collection<KtFile>) {

compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/FirKotlinToJvmBytecodeCompiler.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,7 @@ import org.jetbrains.kotlin.resolve.PlatformDependentAnalyzerServices
6262
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatformAnalyzerServices
6363
import org.jetbrains.kotlin.resolve.multiplatform.isCommonSource
6464
import org.jetbrains.kotlin.utils.addToStdlib.runIf
65-
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
6665
import java.io.File
67-
import kotlin.collections.set
6866

6967
object FirKotlinToJvmBytecodeCompiler {
7068
fun compileModulesUsingFrontendIR(
@@ -83,11 +81,12 @@ object FirKotlinToJvmBytecodeCompiler {
8381
"ATTENTION!\n This build uses in-dev FIR: \n -Xuse-fir"
8482
)
8583

86-
val outputs = newLinkedHashMapWithExpectedSize<Module, Pair<FirResult, GenerationState>>(chunk.size)
84+
val outputs = ArrayList<Pair<FirResult, GenerationState>>(chunk.size)
8785
val targetIds = projectConfiguration.get(JVMConfigurationKeys.MODULES)?.map(::TargetId)
8886
val incrementalComponents = projectConfiguration.get(JVMConfigurationKeys.INCREMENTAL_COMPILATION_COMPONENTS)
8987
val isMultiModuleChunk = chunk.size > 1
9088

89+
// TODO: run lowerings for all modules in the chunk, then run codegen for all modules.
9190
for (module in chunk) {
9291
val moduleConfiguration = projectConfiguration.applyModuleProperties(module, buildFile)
9392
val context = CompilationContext(
@@ -105,19 +104,18 @@ object FirKotlinToJvmBytecodeCompiler {
105104
(projectEnvironment as? PsiBasedProjectEnvironment)?.project?.let { IrGenerationExtension.getInstances(it) } ?: emptyList()
106105
)
107106
val generationState = context.compileModule() ?: return false
108-
outputs[module] = generationState
107+
outputs += generationState
109108
}
110109

111110
val mainClassFqName: FqName? = runIf(chunk.size == 1 && projectConfiguration.get(JVMConfigurationKeys.OUTPUT_JAR) != null) {
112-
val firResult = outputs.values.single().first
113-
findMainClass(firResult)
111+
findMainClass(outputs.single().first)
114112
}
115113

116114
return writeOutputs(
117115
(projectEnvironment as? PsiBasedProjectEnvironment)?.project,
118116
projectConfiguration,
119117
chunk,
120-
outputs.mapValues { (_, value) -> value.second },
118+
outputs.map(Pair<FirResult, GenerationState>::second),
121119
mainClassFqName
122120
)
123121
}

compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinToJVMBytecodeCompiler.kt

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
4242
import org.jetbrains.kotlin.config.JVMConfigurationKeys
4343
import org.jetbrains.kotlin.config.languageVersionSettings
4444
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
45+
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
4546
import org.jetbrains.kotlin.ir.backend.jvm.jvmResolveLibraries
4647
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager
4748
import org.jetbrains.kotlin.modules.Module
4849
import org.jetbrains.kotlin.progress.ProgressIndicatorAndCompilationCanceledStatus
4950
import org.jetbrains.kotlin.psi.KtFile
51+
import org.jetbrains.kotlin.resolve.BindingContext
5052
import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade
51-
import org.jetbrains.kotlin.utils.newLinkedHashMapWithExpectedSize
5253
import java.io.File
53-
import kotlin.collections.set
5454

5555
object KotlinToJVMBytecodeCompiler {
5656
internal fun compileModules(
@@ -111,11 +111,12 @@ object KotlinToJVMBytecodeCompiler {
111111
findMainClass(result.bindingContext, projectConfiguration.languageVersionSettings, environment.getSourceFiles())
112112
else null
113113

114-
val outputs = newLinkedHashMapWithExpectedSize<Module, GenerationState>(chunk.size)
115-
116114
val localFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL)
117115

118116
val (codegenFactory, wholeBackendInput) = convertToIr(environment, result)
117+
val diagnosticsReporter = DiagnosticReporterFactory.createReporter()
118+
119+
val codegenInputs = ArrayList<CodegenFactory.CodegenInput>(chunk.size)
119120

120121
for (module in chunk) {
121122
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
@@ -125,13 +126,20 @@ object KotlinToJVMBytecodeCompiler {
125126
val moduleConfiguration = projectConfiguration.applyModuleProperties(module, buildFile)
126127

127128
val backendInput = codegenFactory.getModuleChunkBackendInput(wholeBackendInput, ktFiles)
128-
outputs[module] = generate(environment, moduleConfiguration, result, ktFiles, module, codegenFactory, backendInput)
129+
codegenInputs += runLowerings(
130+
environment, moduleConfiguration, result, ktFiles, module, codegenFactory, backendInput, diagnosticsReporter
131+
)
132+
}
133+
134+
val outputs = ArrayList<GenerationState>(chunk.size)
135+
136+
for (input in codegenInputs) {
137+
outputs += runCodegen(input, input.state, result.bindingContext, diagnosticsReporter, environment.configuration)
129138
}
130139

131140
return writeOutputs(environment.project, projectConfiguration, chunk, outputs, mainClassFqName)
132141
}
133142

134-
135143
internal fun configureSourceRoots(configuration: CompilerConfiguration, chunk: List<Module>, buildFile: File? = null) {
136144
for (module in chunk) {
137145
val commonSources = getBuildFilePaths(buildFile, module.getCommonSourceFiles()).toSet()
@@ -240,7 +248,12 @@ object KotlinToJVMBytecodeCompiler {
240248
result.throwIfError()
241249

242250
val (codegenFactory, backendInput) = convertToIr(environment, result)
243-
return generate(environment, environment.configuration, result, environment.getSourceFiles(), null, codegenFactory, backendInput)
251+
val diagnosticsReporter = DiagnosticReporterFactory.createReporter()
252+
val input = runLowerings(
253+
environment, environment.configuration, result, environment.getSourceFiles(), null, codegenFactory, backendInput,
254+
diagnosticsReporter
255+
)
256+
return runCodegen(input, input.state, result.bindingContext, diagnosticsReporter, environment.configuration)
244257
}
245258

246259
private fun convertToIr(environment: KotlinCoreEnvironment, result: AnalysisResult): Pair<CodegenFactory, CodegenFactory.BackendInput> {
@@ -325,17 +338,16 @@ object KotlinToJVMBytecodeCompiler {
325338
override fun toString() = "All files under: $directories"
326339
}
327340

328-
private fun generate(
341+
private fun runLowerings(
329342
environment: KotlinCoreEnvironment,
330343
configuration: CompilerConfiguration,
331344
result: AnalysisResult,
332345
sourceFiles: List<KtFile>,
333346
module: Module?,
334347
codegenFactory: CodegenFactory,
335348
backendInput: CodegenFactory.BackendInput,
336-
): GenerationState {
337-
val diagnosticsReporter = DiagnosticReporterFactory.createReporter()
338-
349+
diagnosticsReporter: BaseDiagnosticsCollector,
350+
): CodegenFactory.CodegenInput {
339351
val state = GenerationState.Builder(
340352
environment.project,
341353
ClassBuilderFactories.BINARIES,
@@ -352,30 +364,41 @@ object KotlinToJVMBytecodeCompiler {
352364

353365
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
354366

355-
val performanceManager = environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)
356-
performanceManager?.notifyGenerationStarted()
367+
environment.configuration.get(CLIConfigurationKeys.PERF_MANAGER)?.notifyGenerationStarted()
357368

358369
state.beforeCompile()
359370

360371
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
361372

362-
codegenFactory.generateModule(state, backendInput)
373+
return codegenFactory.invokeLowerings(state, backendInput)
374+
}
375+
376+
private fun runCodegen(
377+
codegenInput: CodegenFactory.CodegenInput,
378+
state: GenerationState,
379+
bindingContext: BindingContext,
380+
diagnosticsReporter: BaseDiagnosticsCollector,
381+
configuration: CompilerConfiguration,
382+
): GenerationState {
383+
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
384+
state.codegenFactory.invokeCodegen(codegenInput)
363385

364386
CodegenFactory.doCheckCancelled(state)
365387
state.factory.done()
366388

367-
performanceManager?.notifyGenerationFinished()
389+
configuration.get(CLIConfigurationKeys.PERF_MANAGER)?.notifyGenerationFinished()
368390

369391
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
370392

393+
val messageCollector = configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
371394
AnalyzerWithCompilerReport.reportDiagnostics(
372395
FilteredJvmDiagnostics(
373396
state.collectedExtraJvmDiagnostics,
374-
result.bindingContext.diagnostics
397+
bindingContext.diagnostics
375398
),
376-
environment.messageCollector
399+
messageCollector
377400
)
378-
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(diagnosticsReporter, environment.messageCollector)
401+
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(diagnosticsReporter, messageCollector)
379402

380403
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
381404
return state

compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/cliCompilerUtils.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,16 +119,16 @@ fun writeOutputs(
119119
project: Project?,
120120
projectConfiguration: CompilerConfiguration,
121121
chunk: List<Module>,
122-
outputs: Map<Module, GenerationState>,
122+
outputs: List<GenerationState>,
123123
mainClassFqName: FqName?
124124
): Boolean {
125125
try {
126-
for ((_, state) in outputs) {
126+
for (state in outputs) {
127127
ProgressIndicatorAndCompilationCanceledStatus.checkCanceled()
128128
writeOutput(state.configuration, state.factory, mainClassFqName)
129129
}
130130
} finally {
131-
outputs.values.forEach(GenerationState::destroy)
131+
outputs.forEach(GenerationState::destroy)
132132
}
133133

134134
if (projectConfiguration.getBoolean(JVMConfigurationKeys.COMPILE_JAVA)) {

compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,13 @@ open class JvmIrCodegenFactory(
6363
val notifyCodegenStart: () -> Unit
6464
) : CodegenFactory.BackendInput
6565

66+
private data class JvmIrCodegenInput(
67+
override val state: GenerationState,
68+
val context: JvmBackendContext,
69+
val module: IrModuleFragment,
70+
val notifyCodegenStart: () -> Unit,
71+
) : CodegenFactory.CodegenInput
72+
6673
override fun convertToIr(input: CodegenFactory.IrConversionInput): JvmIrBackendInput {
6774
val (mangler, symbolTable) =
6875
if (externalSymbolTable != null) externalMangler!! to externalSymbolTable
@@ -213,7 +220,7 @@ open class JvmIrCodegenFactory(
213220
)
214221
}
215222

216-
override fun generateModule(state: GenerationState, input: CodegenFactory.BackendInput) {
223+
override fun invokeLowerings(state: GenerationState, input: CodegenFactory.BackendInput): CodegenFactory.CodegenInput {
217224
val (irModuleFragment, symbolTable, customPhaseConfig, irProviders, extensions, backendExtension, notifyCodegenStart) =
218225
input as JvmIrBackendInput
219226
val irSerializer = if (
@@ -233,9 +240,13 @@ open class JvmIrCodegenFactory(
233240

234241
phases.invokeToplevel(phaseConfig, context, irModuleFragment)
235242

236-
notifyCodegenStart()
243+
return JvmIrCodegenInput(state, context, irModuleFragment, notifyCodegenStart)
244+
}
237245

238-
jvmCodegenPhases.invokeToplevel(phaseConfig, context, irModuleFragment)
246+
override fun invokeCodegen(input: CodegenFactory.CodegenInput) {
247+
val (state, context, module, notifyCodegenStart) = input as JvmIrCodegenInput
248+
notifyCodegenStart()
249+
jvmCodegenPhases.invokeToplevel(PhaseConfig(jvmCodegenPhases), context, module)
239250

240251
// TODO: split classes into groups connected by inline calls; call this after every group
241252
// and clear `JvmBackendContext.classCodegens`
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package a
22

3-
import b.Y
3+
import b.*
44

55
class A
66

77
class X(val y: Y? = null)
88
class Z : Y()
9+
10+
fun topLevelA1() {
11+
topLevelB()
12+
}
13+
14+
fun topLevelA2() {}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package b
22

3-
import a.A
4-
import a.X
3+
import a.*
54

65
class B(val a: A? = null)
76

87
open class Y(val x: X? = null)
8+
9+
fun topLevelB() {
10+
topLevelA2()
11+
}

0 commit comments

Comments
 (0)