[JS IR BE] Support main function
* Implement IR-based main function detector
This commit is contained in:
@@ -198,7 +198,8 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
|||||||
return ExitCode.COMPILATION_ERROR
|
return ExitCode.COMPILATION_ERROR
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle main call parameters
|
// TODO: Handle non-empty main call arguments
|
||||||
|
val mainCallArguments = if (K2JsArgumentConstants.NO_CALL == arguments.main) null else emptyList<String>()
|
||||||
|
|
||||||
val dependencies = libraries.flatMap { listOfNotNull(loadIrLibrary(it, messageCollector)) }
|
val dependencies = libraries.flatMap { listOfNotNull(loadIrLibrary(it, messageCollector)) }
|
||||||
.distinctBy { it.moduleName }
|
.distinctBy { it.moduleName }
|
||||||
@@ -214,7 +215,8 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
|
|||||||
sourcesFiles,
|
sourcesFiles,
|
||||||
configuration,
|
configuration,
|
||||||
immediateDependencies = dependencies,
|
immediateDependencies = dependencies,
|
||||||
allDependencies = dependencies
|
allDependencies = dependencies,
|
||||||
|
mainArguments = mainCallArguments
|
||||||
)
|
)
|
||||||
|
|
||||||
outputFile.writeText(compiledModule)
|
outputFile.writeText(compiledModule)
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ class JsIrBackendContext(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val coroutineGetContextJs = symbolTable.referenceSimpleFunction(getJsInternalFunction(GET_COROUTINE_CONTEXT_NAME))
|
val coroutineGetContextJs = symbolTable.referenceSimpleFunction(getJsInternalFunction(GET_COROUTINE_CONTEXT_NAME))
|
||||||
|
val coroutineEmptyContinuation = symbolTable.referenceField(getProperty(FqName.fromSegments(listOf("kotlin", "coroutines", "js", "internal", "EmptyContinuation"))))
|
||||||
|
|
||||||
val coroutineContextProperty: PropertyDescriptor
|
val coroutineContextProperty: PropertyDescriptor
|
||||||
get() {
|
get() {
|
||||||
@@ -283,12 +284,18 @@ class JsIrBackendContext(
|
|||||||
private fun findFunctions(memberScope: MemberScope, name: Name): List<SimpleFunctionDescriptor> =
|
private fun findFunctions(memberScope: MemberScope, name: Name): List<SimpleFunctionDescriptor> =
|
||||||
memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList()
|
memberScope.getContributedFunctions(name, NoLookupLocation.FROM_BACKEND).toList()
|
||||||
|
|
||||||
|
private fun findProperty(memberScope: MemberScope, name: Name): List<PropertyDescriptor> =
|
||||||
|
memberScope.getContributedVariables(name, NoLookupLocation.FROM_BACKEND).toList()
|
||||||
|
|
||||||
internal fun getJsInternalClass(name: String): ClassDescriptor =
|
internal fun getJsInternalClass(name: String): ClassDescriptor =
|
||||||
findClass(internalPackage.memberScope, Name.identifier(name))
|
findClass(internalPackage.memberScope, Name.identifier(name))
|
||||||
|
|
||||||
internal fun getClass(fqName: FqName): ClassDescriptor =
|
internal fun getClass(fqName: FqName): ClassDescriptor =
|
||||||
findClass(module.getPackage(fqName.parent()).memberScope, fqName.shortName())
|
findClass(module.getPackage(fqName.parent()).memberScope, fqName.shortName())
|
||||||
|
|
||||||
|
internal fun getProperty(fqName: FqName): PropertyDescriptor =
|
||||||
|
findProperty(module.getPackage(fqName.parent()).memberScope, fqName.shortName()).single()
|
||||||
|
|
||||||
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
|
private fun getIrClass(fqName: FqName): IrClassSymbol = symbolTable.referenceClass(getClass(fqName))
|
||||||
|
|
||||||
internal fun getJsInternalFunction(name: String): SimpleFunctionDescriptor =
|
internal fun getJsInternalFunction(name: String): SimpleFunctionDescriptor =
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
|
|||||||
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
|
||||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||||
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
|
||||||
|
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
|
||||||
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator
|
||||||
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
@@ -20,13 +21,16 @@ fun compile(
|
|||||||
configuration: CompilerConfiguration,
|
configuration: CompilerConfiguration,
|
||||||
phaseConfig: PhaseConfig = PhaseConfig(jsPhases),
|
phaseConfig: PhaseConfig = PhaseConfig(jsPhases),
|
||||||
immediateDependencies: List<KlibModuleRef>,
|
immediateDependencies: List<KlibModuleRef>,
|
||||||
allDependencies: List<KlibModuleRef>
|
allDependencies: List<KlibModuleRef>,
|
||||||
|
mainArguments: List<String>?
|
||||||
): String {
|
): String {
|
||||||
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
|
val (moduleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer) =
|
||||||
loadIr(project, files, configuration, immediateDependencies, allDependencies)
|
loadIr(project, files, configuration, immediateDependencies, allDependencies)
|
||||||
|
|
||||||
val moduleDescriptor = moduleFragment.descriptor
|
val moduleDescriptor = moduleFragment.descriptor
|
||||||
|
|
||||||
|
val mainFunction = JsMainFunctionDetector.getMainFunctionOrNull(moduleFragment)
|
||||||
|
|
||||||
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration)
|
val context = JsIrBackendContext(moduleDescriptor, irBuiltIns, symbolTable, moduleFragment, configuration)
|
||||||
|
|
||||||
// Load declarations referenced during `context` initialization
|
// Load declarations referenced during `context` initialization
|
||||||
@@ -55,6 +59,7 @@ fun compile(
|
|||||||
|
|
||||||
jsPhases.invokeToplevel(phaseConfig, context, moduleFragment)
|
jsPhases.invokeToplevel(phaseConfig, context, moduleFragment)
|
||||||
|
|
||||||
val jsProgram = moduleFragment.accept(IrModuleToJsTransformer(context), null)
|
val jsProgram =
|
||||||
|
moduleFragment.accept(IrModuleToJsTransformer(context, mainFunction, mainArguments), null)
|
||||||
return jsProgram.toString()
|
return jsProgram.toString()
|
||||||
}
|
}
|
||||||
+26
-1
@@ -6,6 +6,7 @@
|
|||||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||||
|
|
||||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||||
@@ -18,7 +19,9 @@ import org.jetbrains.kotlin.utils.DFS
|
|||||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
class IrModuleToJsTransformer(
|
class IrModuleToJsTransformer(
|
||||||
private val backendContext: JsIrBackendContext
|
private val backendContext: JsIrBackendContext,
|
||||||
|
private val mainFunction: IrSimpleFunction?,
|
||||||
|
private val mainArguments: List<String>?
|
||||||
) : BaseIrElementToJsNodeTransformer<JsNode, Nothing?> {
|
) : BaseIrElementToJsNodeTransformer<JsNode, Nothing?> {
|
||||||
|
|
||||||
val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
|
val moduleName = backendContext.configuration[CommonConfigurationKeys.MODULE_NAME]!!
|
||||||
@@ -166,6 +169,7 @@ class IrModuleToJsTransformer(
|
|||||||
statements += importStatements
|
statements += importStatements
|
||||||
statements += moduleBody
|
statements += moduleBody
|
||||||
statements += exportStatements
|
statements += exportStatements
|
||||||
|
statements += generateCallToMain(rootContext)
|
||||||
statements += JsReturn(internalModuleName.makeRef())
|
statements += JsReturn(internalModuleName.makeRef())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,6 +185,27 @@ class IrModuleToJsTransformer(
|
|||||||
return program
|
return program
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateMainArguments(mainFunction: IrSimpleFunction, rootContext: JsGenerationContext): List<JsExpression> {
|
||||||
|
val mainArguments = this.mainArguments!!
|
||||||
|
val mainArgumentsArray =
|
||||||
|
if (mainFunction.valueParameters.isNotEmpty()) JsArrayLiteral(mainArguments.map { JsStringLiteral(it) }) else null
|
||||||
|
|
||||||
|
val continuation = if (mainFunction.isSuspend) {
|
||||||
|
val emptyContinuationField = backendContext.coroutineEmptyContinuation.owner
|
||||||
|
rootContext.getNameForField(emptyContinuationField).makeRef()
|
||||||
|
} else null
|
||||||
|
|
||||||
|
return listOfNotNull(mainArgumentsArray, continuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateCallToMain(rootContext: JsGenerationContext): List<JsStatement> {
|
||||||
|
if (mainArguments == null) return emptyList() // in case `NO_MAIN` and `main(..)` exists
|
||||||
|
return mainFunction?.let {
|
||||||
|
val jsName = rootContext.getNameForStaticFunction(it)
|
||||||
|
listOf(JsInvocation(jsName.makeRef(), generateMainArguments(it, rootContext)).makeStmt())
|
||||||
|
} ?: emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
private fun generateImportStatements(
|
private fun generateImportStatements(
|
||||||
getNameForExternalDeclaration: (IrDeclarationWithName) -> JsName,
|
getNameForExternalDeclaration: (IrDeclarationWithName) -> JsName,
|
||||||
declareFreshGlobal: (String) -> JsName
|
declareFreshGlobal: (String) -> JsName
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 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.ir.backend.js.utils
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
|
|
||||||
|
object JsMainFunctionDetector {
|
||||||
|
|
||||||
|
|
||||||
|
private fun IrSimpleFunction.isSuitableForMainParametersSize(allowEmptyParameters: Boolean): Boolean =
|
||||||
|
when (valueParameters.size) {
|
||||||
|
1 -> true
|
||||||
|
0 -> allowEmptyParameters
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrSimpleFunction.isMain(allowEmptyParameters: Boolean): Boolean {
|
||||||
|
if (typeParameters.isNotEmpty()) return false
|
||||||
|
if (!isSuitableForMainParametersSize(allowEmptyParameters)) return false
|
||||||
|
if (!returnType.isUnit()) return false
|
||||||
|
if (name.asString() != "main") return false
|
||||||
|
|
||||||
|
if (valueParameters.size == 1) {
|
||||||
|
val parameter = valueParameters.single()
|
||||||
|
|
||||||
|
if (!parameter.type.isArray()) return false
|
||||||
|
|
||||||
|
val type = parameter.type as IrSimpleType
|
||||||
|
|
||||||
|
if (type.arguments.size != 1) return false
|
||||||
|
|
||||||
|
val argument = type.arguments.single() as? IrTypeProjection ?: return false
|
||||||
|
|
||||||
|
if (argument.variance == Variance.IN_VARIANCE) return false
|
||||||
|
|
||||||
|
return argument.type.isString()
|
||||||
|
} else {
|
||||||
|
require(allowEmptyParameters)
|
||||||
|
require(valueParameters.isEmpty())
|
||||||
|
|
||||||
|
val file = parent as IrFile
|
||||||
|
|
||||||
|
return !file.declarations.filterIsInstance<IrSimpleFunction>().any { it.isMain(allowEmptyParameters = false) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getMainFunctionOrNull(module: IrModuleFragment): IrSimpleFunction? {
|
||||||
|
|
||||||
|
var resultPair: Pair<String, IrSimpleFunction>? = null
|
||||||
|
|
||||||
|
module.files.forEach { f ->
|
||||||
|
val fqn = f.fqName.asString()
|
||||||
|
|
||||||
|
f.declarations.filterIsInstance<IrSimpleFunction>().singleOrNull { it.isMain(allowEmptyParameters = true) }?.let {
|
||||||
|
val result = resultPair
|
||||||
|
if (result == null) {
|
||||||
|
resultPair = Pair(fqn, it)
|
||||||
|
} else {
|
||||||
|
if (fqn < result.first) {
|
||||||
|
resultPair = Pair(fqn, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultPair?.second
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,7 +96,8 @@ abstract class BasicIrBoxTest(
|
|||||||
configuration = config.configuration,
|
configuration = config.configuration,
|
||||||
phaseConfig = config.configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jsPhases),
|
phaseConfig = config.configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jsPhases),
|
||||||
immediateDependencies = dependencies,
|
immediateDependencies = dependencies,
|
||||||
allDependencies = allDependencies
|
allDependencies = allDependencies,
|
||||||
|
mainArguments = mainCallParameters.run { if (shouldBeGenerated()) arguments() else null }
|
||||||
)
|
)
|
||||||
|
|
||||||
val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)
|
val wrappedCode = wrapWithModuleEmulationMarkers(jsCode, moduleId = config.moduleId, moduleKind = config.moduleKind)
|
||||||
|
|||||||
+10
-1
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1281
|
// EXPECTED_REACHABLE_NODES: 1281
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// CALL_MAIN
|
// CALL_MAIN
|
||||||
|
|
||||||
// FILE: ok.kt
|
// FILE: ok.kt
|
||||||
@@ -19,6 +18,16 @@ fun main(args: Array<String>) {
|
|||||||
ok = "fail: b.b"
|
ok = "fail: b.b"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FILE: 0.kt
|
||||||
|
|
||||||
|
package b
|
||||||
|
|
||||||
|
import ok.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
ok = "fail: b"
|
||||||
|
}
|
||||||
|
|
||||||
// FILE: 2.kt
|
// FILE: 2.kt
|
||||||
|
|
||||||
package a
|
package a
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1281
|
// EXPECTED_REACHABLE_NODES: 1281
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// CALL_MAIN
|
// CALL_MAIN
|
||||||
|
|
||||||
var ok: String = "fail"
|
var ok: String = "fail"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1281
|
// EXPECTED_REACHABLE_NODES: 1281
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// CALL_MAIN
|
// CALL_MAIN
|
||||||
|
|
||||||
var ok: String = "fail"
|
var ok: String = "fail"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1296
|
// EXPECTED_REACHABLE_NODES: 1296
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// CALL_MAIN
|
// CALL_MAIN
|
||||||
|
|
||||||
import kotlin.coroutines.*
|
import kotlin.coroutines.*
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// EXPECTED_REACHABLE_NODES: 1296
|
// EXPECTED_REACHABLE_NODES: 1296
|
||||||
// IGNORE_BACKEND: JS_IR
|
|
||||||
// CALL_MAIN
|
// CALL_MAIN
|
||||||
|
|
||||||
import kotlin.coroutines.*
|
import kotlin.coroutines.*
|
||||||
|
|||||||
Reference in New Issue
Block a user