All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.cli.js.klib.compilerPipeline.kt Maven / Gradle / Ivy

There is a newer version: 2.1.0-RC
Show newest version
/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package org.jetbrains.kotlin.cli.js.klib

import org.jetbrains.kotlin.KtSourceFile
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cli.common.*
import org.jetbrains.kotlin.cli.common.fir.FirDiagnosticsCompilerResultsReporter
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.constant.EvaluatedConstTracker
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.impl.BaseDiagnosticsCollector
import org.jetbrains.kotlin.diagnostics.impl.PendingDiagnosticsCollectorWithSuppress
import org.jetbrains.kotlin.fir.BinaryModuleData
import org.jetbrains.kotlin.fir.DependencyListForCliModule
import org.jetbrains.kotlin.fir.FirSession
import org.jetbrains.kotlin.fir.backend.*
import org.jetbrains.kotlin.fir.backend.js.FirJsKotlinMangler
import org.jetbrains.kotlin.fir.descriptors.FirModuleDescriptor
import org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar
import org.jetbrains.kotlin.fir.pipeline.*
import org.jetbrains.kotlin.fir.serialization.FirKLibSerializerExtension
import org.jetbrains.kotlin.fir.serialization.serializeSingleFirFile
import org.jetbrains.kotlin.fir.session.KlibIcData
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerIr
import org.jetbrains.kotlin.ir.types.IrTypeSystemContextImpl
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.js.config.WasmTarget
import org.jetbrains.kotlin.js.resolve.JsPlatformAnalyzerServices
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.library.unresolvedDependencies
import org.jetbrains.kotlin.metadata.ProtoBuf
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.platform.js.JsPlatforms
import org.jetbrains.kotlin.platform.wasm.WasmPlatforms
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.utils.metadataVersion
import org.jetbrains.kotlin.wasm.resolve.WasmJsPlatformAnalyzerServices
import org.jetbrains.kotlin.wasm.resolve.WasmWasiPlatformAnalyzerServices
import java.io.File
import java.nio.file.Paths

inline fun  compileModuleToAnalyzedFir(
    moduleStructure: ModulesStructure,
    files: List,
    libraries: List,
    friendLibraries: List,
    incrementalDataProvider: IncrementalDataProvider?,
    lookupTracker: LookupTracker?,
    noinline isCommonSource: (F) -> Boolean,
    noinline fileBelongsToModule: (F, String) -> Boolean,
    buildResolveAndCheckFir: (FirSession, List) -> ModuleCompilerAnalyzedOutput,
    useWasmPlatform: Boolean,
): List {
    // FIR
    val extensionRegistrars = FirExtensionRegistrar.getInstances(moduleStructure.project)

    val mainModuleName = moduleStructure.compilerConfiguration.get(CommonConfigurationKeys.MODULE_NAME)!!
    val escapedMainModuleName = Name.special("<$mainModuleName>")
    val platform = if (useWasmPlatform) WasmPlatforms.Default else JsPlatforms.defaultJsPlatform
    val platformAnalyzerServices = if (useWasmPlatform) {
        when (moduleStructure.compilerConfiguration.get(JSConfigurationKeys.WASM_TARGET, WasmTarget.JS)) {
            WasmTarget.JS -> WasmJsPlatformAnalyzerServices
            WasmTarget.WASI -> WasmWasiPlatformAnalyzerServices
        }
    } else JsPlatformAnalyzerServices

    val binaryModuleData = BinaryModuleData.initialize(escapedMainModuleName, platform, platformAnalyzerServices)
    val dependencyList = DependencyListForCliModule.build(binaryModuleData) {
        dependencies(libraries.map { Paths.get(it).toAbsolutePath() })
        friendDependencies(friendLibraries.map { Paths.get(it).toAbsolutePath() })
        // TODO: !!! dependencies module data?
    }

    val resolvedLibraries = moduleStructure.allDependencies

    val sessionsWithSources = if (useWasmPlatform) {
        prepareWasmSessions(
            files, moduleStructure.compilerConfiguration, escapedMainModuleName,
            resolvedLibraries, dependencyList, extensionRegistrars,
            isCommonSource = isCommonSource,
            fileBelongsToModule = fileBelongsToModule,
            lookupTracker,
            icData = incrementalDataProvider?.let(::KlibIcData),
        )
    } else {
        prepareJsSessions(
            files, moduleStructure.compilerConfiguration, escapedMainModuleName,
            resolvedLibraries, dependencyList, extensionRegistrars,
            isCommonSource = isCommonSource,
            fileBelongsToModule = fileBelongsToModule,
            lookupTracker,
            icData = incrementalDataProvider?.let(::KlibIcData),
        )
    }

    val outputs = sessionsWithSources.map {
        buildResolveAndCheckFir(it.session, it.files)
    }

    return outputs
}

open class AnalyzedFirOutput(val output: List) {
    protected open fun checkSyntaxErrors(messageCollector: MessageCollector) = false

    fun reportCompilationErrors(
        moduleStructure: ModulesStructure,
        diagnosticsReporter: BaseDiagnosticsCollector,
        messageCollector: MessageCollector,
    ): Boolean {
        if (checkSyntaxErrors(messageCollector) || diagnosticsReporter.hasErrors) {
            val renderName = moduleStructure.compilerConfiguration.getBoolean(CLIConfigurationKeys.RENDER_DIAGNOSTIC_INTERNAL_NAME)
            FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(diagnosticsReporter, messageCollector, renderName)
            return true
        }

        return false
    }
}

class AnalyzedFirWithPsiOutput(
    output: List,
    private val compiledFiles: List
) : AnalyzedFirOutput(output) {
    override fun checkSyntaxErrors(messageCollector: MessageCollector): Boolean {
        return compiledFiles.fold(false) { errorsFound, file ->
            AnalyzerWithCompilerReport.reportSyntaxErrors(file, messageCollector).isHasErrors or errorsFound
        }
    }
}

fun compileModuleToAnalyzedFirWithPsi(
    moduleStructure: ModulesStructure,
    ktFiles: List,
    libraries: List,
    friendLibraries: List,
    diagnosticsReporter: BaseDiagnosticsCollector,
    incrementalDataProvider: IncrementalDataProvider?,
    lookupTracker: LookupTracker?,
    useWasmPlatform: Boolean,
): AnalyzedFirWithPsiOutput {
    val output = compileModuleToAnalyzedFir(
        moduleStructure,
        ktFiles,
        libraries,
        friendLibraries,
        incrementalDataProvider,
        lookupTracker,
        isCommonSource = isCommonSourceForPsi,
        fileBelongsToModule = fileBelongsToModuleForPsi,
        buildResolveAndCheckFir = { session, files ->
            buildResolveAndCheckFirFromKtFiles(session, files, diagnosticsReporter)
        },
        useWasmPlatform = useWasmPlatform,
    )
    return AnalyzedFirWithPsiOutput(output, ktFiles)
}

fun compileModulesToAnalyzedFirWithLightTree(
    moduleStructure: ModulesStructure,
    groupedSources: GroupedKtSources,
    ktSourceFiles: List,
    libraries: List,
    friendLibraries: List,
    diagnosticsReporter: BaseDiagnosticsCollector,
    incrementalDataProvider: IncrementalDataProvider?,
    lookupTracker: LookupTracker?,
    useWasmPlatform: Boolean,
): AnalyzedFirOutput {
    val output = compileModuleToAnalyzedFir(
        moduleStructure,
        ktSourceFiles,
        libraries,
        friendLibraries,
        incrementalDataProvider,
        lookupTracker,
        isCommonSource = { groupedSources.isCommonSourceForLt(it) },
        fileBelongsToModule = { file, it -> groupedSources.fileBelongsToModuleForLt(file, it) },
        buildResolveAndCheckFir = { session, files ->
            buildResolveAndCheckFirViaLightTree(session, files, diagnosticsReporter, null)
        },
        useWasmPlatform = useWasmPlatform,
    )
    return AnalyzedFirOutput(output)
}

fun transformFirToIr(
    moduleStructure: ModulesStructure,
    firOutputs: List,
    diagnosticsReporter: PendingDiagnosticsCollectorWithSuppress,
): Fir2IrActualizedResult {
    val fir2IrExtensions = Fir2IrExtensions.Default

    var builtInsModule: KotlinBuiltIns? = null
    val dependencies = mutableListOf()

    val librariesDescriptors = moduleStructure.allDependencies.map { resolvedLibrary ->
        val storageManager = LockBasedStorageManager("ModulesStructure")

        val moduleDescriptor = JsFactories.DefaultDeserializedDescriptorFactory.createDescriptorOptionalBuiltIns(
            resolvedLibrary,
            moduleStructure.compilerConfiguration.languageVersionSettings,
            storageManager,
            builtInsModule,
            packageAccessHandler = null,
            lookupTracker = LookupTracker.DO_NOTHING
        )
        dependencies += moduleDescriptor
        moduleDescriptor.setDependencies(ArrayList(dependencies))

        val isBuiltIns = resolvedLibrary.unresolvedDependencies.isEmpty()
        if (isBuiltIns) builtInsModule = moduleDescriptor.builtIns

        moduleDescriptor
    }

    val firResult = FirResult(firOutputs)
    return firResult.convertToIrAndActualize(
        fir2IrExtensions,
        Fir2IrConfiguration(
            languageVersionSettings = moduleStructure.compilerConfiguration.languageVersionSettings,
            diagnosticReporter = diagnosticsReporter,
            linkViaSignatures = false,
            evaluatedConstTracker = moduleStructure.compilerConfiguration
                .putIfAbsent(CommonConfigurationKeys.EVALUATED_CONST_TRACKER, EvaluatedConstTracker.create()),
            inlineConstTracker = null,
            expectActualTracker = moduleStructure.compilerConfiguration[CommonConfigurationKeys.EXPECT_ACTUAL_TRACKER],
            allowNonCachedDeclarations = false,
        ),
        IrGenerationExtension.getInstances(moduleStructure.project),
        signatureComposer = DescriptorSignatureComposerStub(JsManglerDesc),
        irMangler = JsManglerIr,
        firMangler = FirJsKotlinMangler(),
        visibilityConverter = Fir2IrVisibilityConverter.Default,
        kotlinBuiltIns = builtInsModule ?: DefaultBuiltIns.Instance,
        actualizerTypeContextProvider = ::IrTypeSystemContextImpl
    ) {
        (this.irModuleFragment.descriptor as? FirModuleDescriptor)?.let { it.allDependencyModules = librariesDescriptors }
    }
}

private class Fir2KlibSerializer(
    moduleStructure: ModulesStructure,
    private val firOutputs: List,
    private val fir2IrActualizedResult: Fir2IrActualizedResult
) {
    private val firFilesAndSessionsBySourceFile = buildMap {
        for (output in firOutputs) {
            output.fir.forEach {
                put(it.sourceFile!!, Triple(it, output.session, output.scopeSession))
            }
        }
    }

    private val actualizedExpectDeclarations by lazy {
        fir2IrActualizedResult.irActualizedResult.extractFirDeclarations()
    }

    private val metadataVersion = moduleStructure.compilerConfiguration.metadataVersion()

    private val languageVersionSettings = moduleStructure.compilerConfiguration.languageVersionSettings

    val sourceFiles: List = firFilesAndSessionsBySourceFile.keys.toList()

    fun serializeSingleFirFile(file: KtSourceFile): ProtoBuf.PackageFragment {
        val (firFile, session, scopeSession) = firFilesAndSessionsBySourceFile[file]
            ?: error("cannot find FIR file by source file ${file.name} (${file.path})")

        return serializeSingleFirFile(
            firFile,
            session,
            scopeSession,
            actualizedExpectDeclarations,
            FirKLibSerializerExtension(
                session, metadataVersion,
                ConstValueProviderImpl(fir2IrActualizedResult.components),
                allowErrorTypes = false, exportKDoc = false,
                fir2IrActualizedResult.components.annotationsFromPluginRegistrar.createMetadataAnnotationsProvider()
            ),
            languageVersionSettings,
        )
    }
}

fun serializeFirKlib(
    moduleStructure: ModulesStructure,
    firOutputs: List,
    fir2IrActualizedResult: Fir2IrActualizedResult,
    outputKlibPath: String,
    messageCollector: MessageCollector,
    diagnosticsReporter: BaseDiagnosticsCollector,
    jsOutputName: String?,
    useWasmPlatform: Boolean,
) {
    val fir2KlibSerializer = Fir2KlibSerializer(moduleStructure, firOutputs, fir2IrActualizedResult)
    val icData = moduleStructure.compilerConfiguration.incrementalDataProvider?.getSerializedData(fir2KlibSerializer.sourceFiles)

    serializeModuleIntoKlib(
        moduleStructure.compilerConfiguration[CommonConfigurationKeys.MODULE_NAME]!!,
        moduleStructure.compilerConfiguration,
        moduleStructure.compilerConfiguration.get(IrMessageLogger.IR_MESSAGE_LOGGER) ?: IrMessageLogger.None,
        fir2KlibSerializer.sourceFiles,
        klibPath = outputKlibPath,
        moduleStructure.allDependencies,
        fir2IrActualizedResult.irModuleFragment,
        expectDescriptorToSymbol = mutableMapOf(),
        cleanFiles = icData ?: emptyList(),
        nopack = true,
        perFile = false,
        containsErrorCode = messageCollector.hasErrors() || diagnosticsReporter.hasErrors,
        abiVersion = KotlinAbiVersion.CURRENT, // TODO get from test file data
        jsOutputName = jsOutputName,
        serializeSingleFile = fir2KlibSerializer::serializeSingleFirFile,
        builtInsPlatform = if (useWasmPlatform) BuiltInsPlatform.WASM else BuiltInsPlatform.JS,
    )
}

fun shouldGoToNextIcRound(
    moduleStructure: ModulesStructure,
    firOutputs: List,
    fir2IrActualizedResult: Fir2IrActualizedResult
): Boolean {
    val nextRoundChecker = moduleStructure.compilerConfiguration.get(JSConfigurationKeys.INCREMENTAL_NEXT_ROUND_CHECKER) ?: return false

    val fir2KlibSerializer = Fir2KlibSerializer(moduleStructure, firOutputs, fir2IrActualizedResult)

    for (ktFile in fir2KlibSerializer.sourceFiles) {
        val packageFragment = fir2KlibSerializer.serializeSingleFirFile(ktFile)

        // to minimize a number of IC rounds, we should inspect all proto for changes first,
        // then go to a next round if needed, with all new dirty files
        nextRoundChecker.checkProtoChanges(File(ktFile.path!!), packageFragment.toByteArray())
    }

    return nextRoundChecker.shouldGoToNextRound()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy