JS: simplification in process
This commit is contained in:
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.js.coroutine
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
import org.jetbrains.kotlin.js.backend.ast.metadata.coroutineMetadata
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
import org.jetbrains.kotlin.js.backend.ast.metadata.isInlineableCoroutineBody
|
||||||
import org.jetbrains.kotlin.js.inline.clean.LabeledBlockToDoWhileTransformation
|
|
||||||
import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions
|
import org.jetbrains.kotlin.js.translate.declaration.transformCoroutineMetadataToSpecialFunctions
|
||||||
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ private constructor(
|
|||||||
var resultExpr: JsNameRef? = null
|
var resultExpr: JsNameRef? = null
|
||||||
private var resultName: JsName? = null
|
private var resultName: JsName? = null
|
||||||
var breakLabel: JsLabel? = null
|
var breakLabel: JsLabel? = null
|
||||||
private val currentStatement = inliningContext.statementContext.currentNode
|
private val currentStatement = inliningContext.currentStatement
|
||||||
|
|
||||||
init {
|
init {
|
||||||
invokedFunction = uncoverClosure(function.deepCopy())
|
invokedFunction = uncoverClosure(function.deepCopy())
|
||||||
@@ -112,7 +112,7 @@ private constructor(
|
|||||||
val breakName = JsScope.declareTemporaryName(getBreakLabel())
|
val breakName = JsScope.declareTemporaryName(getBreakLabel())
|
||||||
this.breakLabel = JsLabel(breakName).apply { synthetic = true }
|
this.breakLabel = JsLabel(breakName).apply { synthetic = true }
|
||||||
|
|
||||||
val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef(), invokedFunction, call.isSuspend)
|
val visitor = ReturnReplacingVisitor(resultExpr, breakName.makeRef(), invokedFunction, call.isSuspend)
|
||||||
visitor.accept(body)
|
visitor.accept(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ import java.io.StringReader
|
|||||||
*/
|
*/
|
||||||
private val JS_IDENTIFIER_START = "\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\\$_"
|
private val JS_IDENTIFIER_START = "\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\\$_"
|
||||||
private val JS_IDENTIFIER_PART = "$JS_IDENTIFIER_START\\p{Pc}\\p{Mc}\\p{Mn}\\d"
|
private val JS_IDENTIFIER_PART = "$JS_IDENTIFIER_START\\p{Pc}\\p{Mc}\\p{Mn}\\d"
|
||||||
private val JS_IDENTIFIER="[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*"
|
private val JS_IDENTIFIER = "[$JS_IDENTIFIER_START][$JS_IDENTIFIER_PART]*"
|
||||||
private val DEFINE_MODULE_PATTERN = ("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern()
|
private val DEFINE_MODULE_PATTERN =
|
||||||
|
("($JS_IDENTIFIER)\\.defineModule\\(\\s*(['\"])([^'\"]+)\\2\\s*,\\s*(\\w+)\\s*\\)").toRegex().toPattern()
|
||||||
private val DEFINE_MODULE_FIND_PATTERN = ".defineModule("
|
private val DEFINE_MODULE_FIND_PATTERN = ".defineModule("
|
||||||
|
|
||||||
private val specialFunctions = enumValues<SpecialFunction>().joinToString("|") { it.suggestedName }
|
private val specialFunctions = enumValues<SpecialFunction>().joinToString("|") { it.suggestedName }
|
||||||
@@ -55,9 +56,9 @@ private val specialFunctionsByName = enumValues<SpecialFunction>().associateBy {
|
|||||||
private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($JS_IDENTIFIER)\\.($specialFunctions)\\s*;").toPattern()
|
private val SPECIAL_FUNCTION_PATTERN = Regex("var\\s+($JS_IDENTIFIER)\\s*=\\s*($JS_IDENTIFIER)\\.($specialFunctions)\\s*;").toPattern()
|
||||||
|
|
||||||
class FunctionReader(
|
class FunctionReader(
|
||||||
private val reporter: JsConfig.Reporter,
|
private val reporter: JsConfig.Reporter,
|
||||||
private val config: JsConfig,
|
private val config: JsConfig,
|
||||||
private val currentModuleName: JsName
|
private val currentModuleName: JsName
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* fileContent: .js file content, that contains this module definition.
|
* fileContent: .js file content, that contains this module definition.
|
||||||
@@ -70,20 +71,20 @@ class FunctionReader(
|
|||||||
* The default variable is Kotlin, but it can be renamed by minifier.
|
* The default variable is Kotlin, but it can be renamed by minifier.
|
||||||
*/
|
*/
|
||||||
class ModuleInfo(
|
class ModuleInfo(
|
||||||
val filePath: String,
|
val filePath: String,
|
||||||
val fileContent: String,
|
val fileContent: String,
|
||||||
val moduleVariable: String,
|
val moduleVariable: String,
|
||||||
val kotlinVariable: String,
|
val kotlinVariable: String,
|
||||||
val specialFunctions: Map<String, SpecialFunction>,
|
val specialFunctions: Map<String, SpecialFunction>,
|
||||||
offsetToSourceMappingProvider: () -> OffsetToSourceMapping,
|
offsetToSourceMappingProvider: () -> OffsetToSourceMapping,
|
||||||
val sourceMap: SourceMap?,
|
val sourceMap: SourceMap?,
|
||||||
val outputDir: File?
|
val outputDir: File?
|
||||||
) {
|
) {
|
||||||
val offsetToSourceMapping by lazy(offsetToSourceMappingProvider)
|
val offsetToSourceMapping by lazy(offsetToSourceMappingProvider)
|
||||||
|
|
||||||
val wrapFunctionRegex = specialFunctions.entries
|
val wrapFunctionRegex = specialFunctions.entries
|
||||||
.filter { (_, v) -> v == SpecialFunction.WRAP_FUNCTION } // TODO This is a hack! Investigate duplicates!
|
.filter { (_, v) -> v == SpecialFunction.WRAP_FUNCTION } // TODO This is a hack! Investigate duplicates!
|
||||||
.map { Regex("\\s*${it.key}\\s*\\(\\s*").toPattern() }
|
.map { Regex("\\s*${it.key}\\s*\\(\\s*").toPattern() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val moduleNameToInfo by lazy {
|
private val moduleNameToInfo by lazy {
|
||||||
@@ -125,14 +126,14 @@ class FunctionReader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val moduleInfo = ModuleInfo(
|
val moduleInfo = ModuleInfo(
|
||||||
filePath = path,
|
filePath = path,
|
||||||
fileContent = content,
|
fileContent = content,
|
||||||
moduleVariable = moduleVariable,
|
moduleVariable = moduleVariable,
|
||||||
kotlinVariable = kotlinVariable,
|
kotlinVariable = kotlinVariable,
|
||||||
specialFunctions = specialFunctions,
|
specialFunctions = specialFunctions,
|
||||||
offsetToSourceMappingProvider = { OffsetToSourceMapping(content) },
|
offsetToSourceMappingProvider = { OffsetToSourceMapping(content) },
|
||||||
sourceMap = sourceMap,
|
sourceMap = sourceMap,
|
||||||
outputDir = file?.parentFile
|
outputDir = file?.parentFile
|
||||||
)
|
)
|
||||||
|
|
||||||
result.put(moduleName, moduleInfo)
|
result.put(moduleName, moduleInfo)
|
||||||
@@ -164,37 +165,60 @@ class FunctionReader(
|
|||||||
override fun toString() = text.substring(offset)
|
override fun toString() = text.substring(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
private object NotFoundMarker : Any()
|
object NotFoundMarker
|
||||||
|
|
||||||
private val functionCache = object : SLRUCache<CallableDescriptor, Any>(50, 50) {
|
private val functionCache = object : SLRUCache<CallableDescriptor, Any>(50, 50) {
|
||||||
// LibraryInlineFunctionDefinition | NotFoundMarker
|
|
||||||
override fun createValue(key: CallableDescriptor): Any =
|
override fun createValue(key: CallableDescriptor): Any =
|
||||||
readFunction(key) ?: NotFoundMarker
|
readFunction(key) ?: NotFoundMarker
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? {
|
operator fun get(descriptor: CallableDescriptor, fragment: JsProgramFragment): FunctionWithWrapper? {
|
||||||
val existed = functionCache.get(descriptor)
|
return functionCache.get(descriptor).let {
|
||||||
return if (existed === NotFoundMarker) null else existed as LibraryInlineFunctionDefinition
|
if (it === NotFoundMarker) null else {
|
||||||
|
val (fn, info) = it as Pair<*, *>
|
||||||
|
renameModules(descriptor, fn as FunctionWithWrapper, info as ModuleInfo, fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readFunction(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? {
|
private fun renameModules(
|
||||||
|
descriptor: CallableDescriptor,
|
||||||
|
fn: FunctionWithWrapper,
|
||||||
|
info: ModuleInfo,
|
||||||
|
fragment: JsProgramFragment
|
||||||
|
): FunctionWithWrapper {
|
||||||
|
val tag = Namer.getFunctionTag(descriptor, config)
|
||||||
|
val moduleReference = fragment.inlineModuleMap[tag]?.deepCopy() ?: currentModuleName.makeRef()
|
||||||
|
val allDefinedNames = collectDefinedNamesInAllScopes(fn.function)
|
||||||
|
val replacements = hashMapOf(
|
||||||
|
info.moduleVariable to moduleReference,
|
||||||
|
info.kotlinVariable to Namer.kotlinObject()
|
||||||
|
)
|
||||||
|
replaceExternalNames(fn.function, replacements, allDefinedNames)
|
||||||
|
val wrapperStatements = fn.wrapperBody?.statements?.filter { it !is JsReturn }
|
||||||
|
wrapperStatements?.forEach { replaceExternalNames(it, replacements, allDefinedNames) }
|
||||||
|
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readFunction(descriptor: CallableDescriptor): Pair<FunctionWithWrapper, ModuleInfo>? {
|
||||||
val moduleName = getModuleName(descriptor)
|
val moduleName = getModuleName(descriptor)
|
||||||
|
|
||||||
if (moduleName !in moduleNameToInfo.keys()) return null
|
if (moduleName !in moduleNameToInfo.keys()) return null
|
||||||
|
|
||||||
for (info in moduleNameToInfo[moduleName]) {
|
for (info in moduleNameToInfo[moduleName]) {
|
||||||
val function = readFunctionFromSource(descriptor, info)
|
val function = readFunctionFromSource(descriptor, info)
|
||||||
if (function != null) return function
|
if (function != null) return function to info
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move renamings to a proper place
|
// TODO move renamings to a proper place
|
||||||
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): LibraryInlineFunctionDefinition? {
|
private fun readFunctionFromSource(descriptor: CallableDescriptor, info: ModuleInfo): FunctionWithWrapper? {
|
||||||
val source = info.fileContent
|
val source = info.fileContent
|
||||||
var tag = Namer.getFunctionTag(descriptor, config)
|
var tag = Namer.getFunctionTag(descriptor, config)
|
||||||
val tagForModule = tag
|
// val tagForModule = tag
|
||||||
var index = source.indexOf(tag)
|
var index = source.indexOf(tag)
|
||||||
|
|
||||||
// Hack for compatibility with old versions of stdlib
|
// Hack for compatibility with old versions of stdlib
|
||||||
@@ -226,16 +250,14 @@ class FunctionReader(
|
|||||||
val position = info.offsetToSourceMapping[offset]
|
val position = info.offsetToSourceMapping[offset]
|
||||||
val jsScope = JsRootScope(JsProgram())
|
val jsScope = JsRootScope(JsProgram())
|
||||||
val functionExpr = try {
|
val functionExpr = try {
|
||||||
parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?:
|
parseFunction(source, info.filePath, position, offset, ThrowExceptionOnErrorReporter, jsScope) ?: return null
|
||||||
return null
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
throw Error("Exception while reading function '$tag' from ${info.filePath}", t)
|
throw Error("Exception while reading function '$tag' from ${info.filePath}", t)
|
||||||
}
|
}
|
||||||
functionExpr.fixForwardNameReferences()
|
functionExpr.fixForwardNameReferences()
|
||||||
val (function, wrapper) = if (isWrapped) {
|
val (function, wrapper) = if (isWrapped) {
|
||||||
InlineMetadata.decomposeWrapper(functionExpr) ?: return null
|
InlineMetadata.decomposeWrapper(functionExpr) ?: return null
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
FunctionWithWrapper(functionExpr, null)
|
FunctionWithWrapper(functionExpr, null)
|
||||||
}
|
}
|
||||||
// val moduleReference = moduleNameMap[tagForModule]?.deepCopy() ?: currentModuleName.makeRef()
|
// val moduleReference = moduleNameMap[tagForModule]?.deepCopy() ?: currentModuleName.makeRef()
|
||||||
@@ -262,8 +284,8 @@ class FunctionReader(
|
|||||||
markSpecialFunctions(function, allDefinedNames, info, jsScope)
|
markSpecialFunctions(function, allDefinedNames, info, jsScope)
|
||||||
|
|
||||||
val namesWithoutSideEffects = wrapperStatements.orEmpty().asSequence()
|
val namesWithoutSideEffects = wrapperStatements.orEmpty().asSequence()
|
||||||
.flatMap { collectDefinedNames(it).asSequence() }
|
.flatMap { collectDefinedNames(it).asSequence() }
|
||||||
.toSet()
|
.toSet()
|
||||||
function.accept(object : RecursiveJsVisitor() {
|
function.accept(object : RecursiveJsVisitor() {
|
||||||
override fun visitNameRef(nameRef: JsNameRef) {
|
override fun visitNameRef(nameRef: JsNameRef) {
|
||||||
if (nameRef.name in namesWithoutSideEffects && nameRef.qualifier == null) {
|
if (nameRef.name in namesWithoutSideEffects && nameRef.qualifier == null) {
|
||||||
@@ -279,7 +301,7 @@ class FunctionReader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return LibraryInlineFunctionDefinition(tagForModule, FunctionWithWrapper(function, wrapper), info)
|
return FunctionWithWrapper(function, wrapper)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set<JsName>, info: ModuleInfo, scope: JsScope) {
|
private fun markSpecialFunctions(function: JsFunction, allDefinedNames: Set<JsName>, info: ModuleInfo, scope: JsScope) {
|
||||||
@@ -365,7 +387,7 @@ private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) {
|
|||||||
inlineFuns.add(paramsJs[i + offset].name)
|
inlineFuns.add(paramsJs[i + offset].name)
|
||||||
}
|
}
|
||||||
|
|
||||||
val visitor = object: JsVisitorWithContextImpl() {
|
val visitor = object : JsVisitorWithContextImpl() {
|
||||||
override fun endVisit(x: JsInvocation, ctx: JsContext<*>) {
|
override fun endVisit(x: JsInvocation, ctx: JsContext<*>) {
|
||||||
val qualifier: JsExpression? = if (isCallInvocation(x)) {
|
val qualifier: JsExpression? = if (isCallInvocation(x)) {
|
||||||
(x.qualifier as? JsNameRef)?.qualifier
|
(x.qualifier as? JsNameRef)?.qualifier
|
||||||
@@ -385,7 +407,7 @@ private fun JsFunction.markInlineArguments(descriptor: CallableDescriptor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceExternalNames(node: JsNode, replacements: Map<String, JsExpression>, definedNames: Set<JsName>) {
|
private fun replaceExternalNames(node: JsNode, replacements: Map<String, JsExpression>, definedNames: Set<JsName>) {
|
||||||
val visitor = object: JsVisitorWithContextImpl() {
|
val visitor = object : JsVisitorWithContextImpl() {
|
||||||
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
||||||
if (x.qualifier != null || x.name in definedNames) return
|
if (x.qualifier != null || x.name in definedNames) return
|
||||||
|
|
||||||
@@ -407,5 +429,5 @@ private class ShallowSubSequence(private val underlying: CharSequence, private v
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
|
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence =
|
||||||
ShallowSubSequence(underlying, start + startIndex, start + endIndex)
|
ShallowSubSequence(underlying, start + startIndex, start + endIndex)
|
||||||
}
|
}
|
||||||
@@ -5,66 +5,8 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.inline
|
package org.jetbrains.kotlin.js.inline
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsProgramFragment
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
||||||
|
|
||||||
sealed class InlineFunctionDefinition {
|
class InlineFunctionDefinition(
|
||||||
abstract val functionWithWrapper: FunctionWithWrapper
|
val fn: FunctionWithWrapper,
|
||||||
|
val tag: String?)
|
||||||
abstract val tag: String?
|
|
||||||
|
|
||||||
open fun process() {}
|
|
||||||
|
|
||||||
// imports / nameBindings
|
|
||||||
// modules
|
|
||||||
// renameFor scope?
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Current module, new fragment. Expressed through `defineInlineFunction`. Should be self-contained
|
|
||||||
open class PublicInlineFunctionDefinition(
|
|
||||||
override val tag: String,
|
|
||||||
override val functionWithWrapper: FunctionWithWrapper,
|
|
||||||
val fragment: JsProgramFragment,
|
|
||||||
val scope: ProgramFragmentInliningScope
|
|
||||||
) : InlineFunctionDefinition() {
|
|
||||||
override fun process() {
|
|
||||||
scope.process()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current module, from Binary AST
|
|
||||||
class BinaryInlineFunctionDefinition(
|
|
||||||
override val tag: String,
|
|
||||||
override val functionWithWrapper: FunctionWithWrapper,
|
|
||||||
val fragment: JsProgramFragment
|
|
||||||
): InlineFunctionDefinition() {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current module, new fragment, used within scope only. Private functions, lambdas.
|
|
||||||
class LocalInlineFunctionDefinition(
|
|
||||||
override val functionWithWrapper: FunctionWithWrapper,
|
|
||||||
val scope: InliningScope
|
|
||||||
) : InlineFunctionDefinition() {
|
|
||||||
|
|
||||||
override val tag = null
|
|
||||||
|
|
||||||
override fun process() {
|
|
||||||
// TODO this is incorrect!
|
|
||||||
scope.process()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialized from a binary dependency (<module>.js file)
|
|
||||||
open class LibraryInlineFunctionDefinition(
|
|
||||||
override val tag: String,
|
|
||||||
override val functionWithWrapper: FunctionWithWrapper,
|
|
||||||
val moduleInfo: FunctionReader.ModuleInfo
|
|
||||||
): InlineFunctionDefinition() {
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -47,10 +47,10 @@ class InlineSuspendFunctionSplitter(
|
|||||||
val statementContext = lastStatementLevelContext
|
val statementContext = lastStatementLevelContext
|
||||||
|
|
||||||
// This function will be exported to JS
|
// This function will be exported to JS
|
||||||
val function = scope.importFunctionDefinition(PublicInlineFunctionDefinition(inlineMetadata.tag.value, inlineMetadata.function, scope.fragment, scope))
|
val function = scope.importFunctionDefinition(InlineFunctionDefinition(inlineMetadata.function, inlineMetadata.tag.value))
|
||||||
|
|
||||||
// Original function should be not be transformed into a state machine
|
// Original function should be not be transformed into a state machine
|
||||||
f.function.setName(null)
|
f.function.name = null
|
||||||
f.function.coroutineMetadata = null
|
f.function.coroutineMetadata = null
|
||||||
f.function.isInlineableCoroutineBody = true
|
f.function.isInlineableCoroutineBody = true
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,9 @@ import org.jetbrains.kotlin.js.backend.ast.JsInvocation
|
|||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
|
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.psiElement
|
import org.jetbrains.kotlin.js.backend.ast.metadata.psiElement
|
||||||
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
|
|
||||||
import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations
|
|
||||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
||||||
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
||||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||||
import org.jetbrains.kotlin.js.inline.util.refreshLabelNames
|
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@@ -66,13 +63,24 @@ class InlinerCycleReporter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Return true iff the definition should be visited by the inliner
|
fun <T> withInlining(call: JsInvocation, body: () -> T): T {
|
||||||
fun shouldProcess(definition: FunctionWithWrapper, call: JsInvocation): Boolean {
|
|
||||||
|
|
||||||
currentNamedFunction?.let {
|
currentNamedFunction?.let {
|
||||||
inlineCallInfos.add(JsCallInfo(call, it))
|
inlineCallInfos.add(JsCallInfo(call, it))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val result = body()
|
||||||
|
|
||||||
|
if (!inlineCallInfos.isEmpty()) {
|
||||||
|
if (inlineCallInfos.last.call == call) {
|
||||||
|
inlineCallInfos.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true iff the definition should be visited by the inliner
|
||||||
|
fun shouldProcess(definition: FunctionWithWrapper, call: JsInvocation?): Boolean {
|
||||||
if (definition.function in inProcessFunctions) {
|
if (definition.function in inProcessFunctions) {
|
||||||
reportInlineCycle(call, definition.function)
|
reportInlineCycle(call, definition.function)
|
||||||
} else if (definition.function !in processedFunctions) {
|
} else if (definition.function !in processedFunctions) {
|
||||||
@@ -82,16 +90,8 @@ class InlinerCycleReporter(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endVisit(x: JsInvocation) {
|
private fun reportInlineCycle(call: JsInvocation?, calledFunction: JsFunction) {
|
||||||
if (!inlineCallInfos.isEmpty()) {
|
call?.inlineStrategy = InlineStrategy.NOT_INLINE
|
||||||
if (inlineCallInfos.last.call == x) {
|
|
||||||
inlineCallInfos.removeLast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reportInlineCycle(call: JsInvocation, calledFunction: JsFunction) {
|
|
||||||
call.inlineStrategy = InlineStrategy.NOT_INLINE
|
|
||||||
val it = inlineCallInfos.descendingIterator()
|
val it = inlineCallInfos.descendingIterator()
|
||||||
|
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
|
|||||||
@@ -6,76 +6,71 @@
|
|||||||
package org.jetbrains.kotlin.js.inline
|
package org.jetbrains.kotlin.js.inline
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.forcedReturnVariable
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
|
import org.jetbrains.kotlin.js.backend.ast.metadata.inlineStrategy
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
|
||||||
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
|
|
||||||
import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations
|
|
||||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
||||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
import org.jetbrains.kotlin.js.inline.util.FunctionWithWrapper
|
||||||
import org.jetbrains.kotlin.js.inline.util.refreshLabelNames
|
import org.jetbrains.kotlin.js.inline.util.extractFunction
|
||||||
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
import org.jetbrains.kotlin.js.translate.expression.InlineMetadata
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
|
|
||||||
// TODO stateless?
|
// TODO stateless?
|
||||||
class InlinerImpl(
|
class InlinerImpl(
|
||||||
val cycleReporter: InlinerCycleReporter,
|
val jsInliner: JsInliner,
|
||||||
val functionContext: FunctionContext,
|
|
||||||
// TODO other way around? Need to find a correct inliner by function declaration.
|
|
||||||
val scope: InliningScope
|
val scope: InliningScope
|
||||||
) : JsVisitorWithContextImpl() {
|
) : JsVisitorWithContextImpl() {
|
||||||
|
|
||||||
override fun visit(function: JsFunction, context: JsContext<*>): Boolean {
|
override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean {
|
||||||
functionContext.functionsByFunctionNodes[function]?.let { (function, wrapper) ->
|
val assignment = JsAstUtils.decomposeAssignment(x)
|
||||||
visit(function, wrapper)
|
if (assignment != null) {
|
||||||
return false
|
val (left, right) = assignment
|
||||||
|
if (left is JsNameRef) {
|
||||||
|
val name = left.name
|
||||||
|
if (name != null) {
|
||||||
|
extractFunction(right)?.let { function ->
|
||||||
|
jsInliner.process(InlineFunctionDefinition(function, null), null, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
visit(function, null)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visit(x: JsBlock, ctx: JsContext<*>): Boolean {
|
|
||||||
// TODO Seems like a very roundabout way. Probably should reuse same approach as in CoroutineTransformer and ...Splitter
|
|
||||||
// TODO That approach might be missing inline properties. Check!
|
|
||||||
functionContext.functionsByWrapperNodes[x]?.let { (function, wrapper) ->
|
|
||||||
visit(function, wrapper)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return super.visit(x, ctx)
|
return super.visit(x, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun visit(function: JsFunction, wrapperBody: JsBlock?) {
|
override fun visit(x: JsVars.JsVar, ctx: JsContext<*>): Boolean {
|
||||||
cycleReporter.startFunction(function)
|
val initializer = x.initExpression
|
||||||
|
val name = x.name
|
||||||
// TODO Different visitors?
|
if (initializer != null && name != null) {
|
||||||
if (wrapperBody != null && scope is ProgramFragmentInliningScope) {
|
extractFunction(initializer)?.let { function ->
|
||||||
PublicInlineFunctionInliningScope(scope.fragment, cycleReporter, functionContext, function, wrapperBody).process()
|
jsInliner.process(InlineFunctionDefinition(function, null), null, scope)
|
||||||
} else {
|
}
|
||||||
// TODO this is still not super-clear
|
|
||||||
accept(function.body)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup
|
return super.visit(x, ctx)
|
||||||
refreshLabelNames(function.body, function.scope)
|
|
||||||
removeUnusedLocalFunctionDeclarations(function)
|
|
||||||
FunctionPostProcessor(function).apply()
|
|
||||||
|
|
||||||
cycleReporter.endFunction(function)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun visit(x: JsInvocation, ctx: JsContext<*>): Boolean {
|
||||||
|
InlineMetadata.decompose(x)?.let {
|
||||||
|
jsInliner.process(InlineFunctionDefinition(it.function, it.tag.value), x, scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.visit(x, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun endVisit(call: JsInvocation, ctx: JsContext<JsNode>) {
|
override fun endVisit(call: JsInvocation, ctx: JsContext<JsNode>) {
|
||||||
if (hasToBeInlined(call)) {
|
if (hasToBeInlined(call)) {
|
||||||
|
val (inlineableBody, resultExpression) = jsInliner.inline(scope, call, lastStatementLevelContext.currentNode)
|
||||||
|
|
||||||
val definition = functionContext.getFunctionDefinition(call, scope)
|
lastStatementLevelContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody))
|
||||||
|
|
||||||
if (cycleReporter.shouldProcess(definition.functionWithWrapper, call)) {
|
// Assumes, that resultExpression == null, when result is not needed.
|
||||||
definition.process()
|
// @see FunctionInlineMutator.isResultNeeded()
|
||||||
|
if (resultExpression == null) {
|
||||||
|
lastStatementLevelContext.removeMe()
|
||||||
|
} else {
|
||||||
|
ctx.replaceMe(resultExpression)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline(call, definition, ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleReporter.endVisit(call)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO This could be extracted into a separate pass.
|
// TODO This could be extracted into a separate pass.
|
||||||
@@ -94,62 +89,9 @@ class InlinerImpl(
|
|||||||
super.doAcceptStatementList(statements)
|
super.doAcceptStatementList(statements)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO a lot of code... Probably a bad sign
|
|
||||||
private fun inline(call: JsInvocation, definition: InlineFunctionDefinition, context: JsContext<JsNode>) {
|
|
||||||
|
|
||||||
// ---------------
|
|
||||||
// This should be isolated
|
|
||||||
|
|
||||||
val function = scope.importFunctionDefinition(definition)
|
|
||||||
// TODO This should be done inside the importer
|
|
||||||
function.body = transformSpecialFunctionsToCoroutineMetadata(function.body)
|
|
||||||
|
|
||||||
// -------------------
|
|
||||||
|
|
||||||
val statementContext = lastStatementLevelContext
|
|
||||||
|
|
||||||
val (inlineableBody, resultExpression) =
|
|
||||||
FunctionInlineMutator.getInlineableCallReplacement(call, function, InliningContext(statementContext))
|
|
||||||
|
|
||||||
// body of inline function can contain call to lambdas that need to be inlined
|
|
||||||
val inlineableBodyWithLambdasInlined = accept(inlineableBody)
|
|
||||||
assert(inlineableBody === inlineableBodyWithLambdasInlined)
|
|
||||||
|
|
||||||
patchReturnsFromSecondaryConstructor(inlineableBody)
|
|
||||||
|
|
||||||
statementContext.addPrevious(JsAstUtils.flattenStatement(inlineableBody))
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Assumes, that resultExpression == null, when result is not needed.
|
|
||||||
* @see FunctionInlineMutator.isResultNeeded()
|
|
||||||
*/
|
|
||||||
if (resultExpression == null) {
|
|
||||||
statementContext.removeMe()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO Why accept? Seems unnecessary... Some inline call in the qualifier? Shouldn't this be done along with the lambdas then?
|
|
||||||
accept(resultExpression)?.let {
|
|
||||||
it.synthetic = true
|
|
||||||
context.replaceMe(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun patchReturnsFromSecondaryConstructor(inlineableBody: JsStatement) {
|
|
||||||
// Support non-local return from secondary constructor
|
|
||||||
// Returns from secondary constructors should return `$this` object.
|
|
||||||
// TODO This seems brittle
|
|
||||||
cycleReporter.currentNamedFunction?.forcedReturnVariable?.let { returnVariable ->
|
|
||||||
inlineableBody.accept(object : RecursiveJsVisitor() {
|
|
||||||
override fun visitReturn(x: JsReturn) {
|
|
||||||
x.expression = returnVariable.makeRef()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hasToBeInlined(call: JsInvocation): Boolean {
|
private fun hasToBeInlined(call: JsInvocation): Boolean {
|
||||||
val strategy = call.inlineStrategy
|
val strategy = call.inlineStrategy
|
||||||
return if (strategy == null || !strategy.isInline) false else functionContext.hasFunctionDefinition(call, scope)
|
return if (strategy == null || !strategy.isInline) false else jsInliner.functionContext.hasFunctionDefinition(call, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,75 +12,134 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.staticRef
|
|||||||
import org.jetbrains.kotlin.js.inline.clean.removeUnusedFunctionDefinitions
|
import org.jetbrains.kotlin.js.inline.clean.removeUnusedFunctionDefinitions
|
||||||
import org.jetbrains.kotlin.js.inline.clean.removeUnusedImports
|
import org.jetbrains.kotlin.js.inline.clean.removeUnusedImports
|
||||||
import org.jetbrains.kotlin.js.inline.clean.simplifyWrappedFunctions
|
import org.jetbrains.kotlin.js.inline.clean.simplifyWrappedFunctions
|
||||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
|
||||||
import org.jetbrains.kotlin.js.inline.util.*
|
import org.jetbrains.kotlin.js.inline.util.*
|
||||||
|
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
// Handles interpreting an inline function in terms of the current context.
|
// Handles interpreting an inline function in terms of the current context.
|
||||||
// Either an program fragment, or a public inline function
|
// Either an program fragment, or a public inline function
|
||||||
sealed class InliningScope {
|
sealed class InliningScope {
|
||||||
|
|
||||||
private val cache = mutableMapOf<String, Map<JsName, JsNameRef>>()
|
abstract val fragment: JsProgramFragment
|
||||||
|
|
||||||
protected fun computeIfAbsent(tag: String?, fn: () -> Map<JsName, JsNameRef>): Map<JsName, JsNameRef> {
|
abstract fun addInlinedDeclaration(tag: String?, declaration: JsStatement)
|
||||||
if (tag == null) return fn()
|
|
||||||
|
|
||||||
return cache.computeIfAbsent(tag) { fn() }
|
abstract fun hasImport(tag: String): JsName?
|
||||||
|
|
||||||
|
abstract fun addImport(tag: String, vars: JsVars)
|
||||||
|
|
||||||
|
open fun preprocess(statement: JsStatement) {}
|
||||||
|
|
||||||
|
abstract fun update()
|
||||||
|
|
||||||
|
private val publicFunctionCache = mutableMapOf<String, Map<JsName, JsNameRef>>()
|
||||||
|
|
||||||
|
private val localFunctionCache = mutableMapOf<JsFunction, Map<JsName, JsNameRef>>()
|
||||||
|
|
||||||
|
private fun computeIfAbsent(tag: String?, function: JsFunction, fn: () -> Map<JsName, JsNameRef>): Map<JsName, JsNameRef> {
|
||||||
|
if (tag == null) return localFunctionCache.computeIfAbsent(function) { fn() }
|
||||||
|
|
||||||
|
return publicFunctionCache.computeIfAbsent(tag) { fn() }
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction
|
fun importFunctionDefinition(definition: InlineFunctionDefinition): JsFunction {
|
||||||
|
// Apparently we should avoid this trick when we implement fair support for crossinline
|
||||||
|
// That's because crossinline lambdas inline into the declaration block and specialize those.
|
||||||
|
val replacements = computeIfAbsent(definition.tag, definition.fn.function) {
|
||||||
|
val newReplacements = HashMap<JsName, JsNameRef>()
|
||||||
|
|
||||||
abstract fun process()
|
val copiedStatements = ArrayList<JsStatement>()
|
||||||
|
val importStatements = mutableMapOf<JsVars, String>()
|
||||||
|
|
||||||
abstract val fragment: JsProgramFragment
|
definition.fn.wrapperBody?.let {
|
||||||
|
it.statements.asSequence()
|
||||||
|
.filterNot { it is JsReturn }
|
||||||
|
.map { it.deepCopy() }
|
||||||
|
.forEach { statement ->
|
||||||
|
preprocess(statement)
|
||||||
|
|
||||||
|
if (statement is JsVars) {
|
||||||
|
val tag = getImportTag(statement)
|
||||||
|
if (tag != null) {
|
||||||
|
val name = statement.vars[0].name
|
||||||
|
val existingName = name.localAlias ?: hasImport(tag) ?: JsScope.declareTemporaryName(name.ident).also {
|
||||||
|
it.copyMetadataFrom(name)
|
||||||
|
importStatements[statement] = tag
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name !== existingName) {
|
||||||
|
val replacement = JsAstUtils.pureFqn(existingName, null)
|
||||||
|
newReplacements[name] = replacement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedStatements.add(statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedStatements.asSequence()
|
||||||
|
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() }
|
||||||
|
.filter { name -> !newReplacements.containsKey(name) }
|
||||||
|
.forEach { name ->
|
||||||
|
val alias = JsScope.declareTemporaryName(name.ident)
|
||||||
|
alias.copyMetadataFrom(name)
|
||||||
|
val replacement = JsAstUtils.pureFqn(alias, null)
|
||||||
|
newReplacements[name] = replacement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply renaming and restore the static ref links
|
||||||
|
JsBlock(copiedStatements).let {
|
||||||
|
replaceNames(it, newReplacements)
|
||||||
|
|
||||||
|
// Restore the staticRef links
|
||||||
|
for ((key, value) in collectNamedFunctions(it)) {
|
||||||
|
if (key.staticRef is JsFunction) {
|
||||||
|
key.staticRef = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copiedStatements.forEach {
|
||||||
|
if (it is JsVars && it in importStatements) {
|
||||||
|
addImport(importStatements[it]!!, it)
|
||||||
|
} else {
|
||||||
|
addInlinedDeclaration(definition.tag, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newReplacements
|
||||||
|
}
|
||||||
|
|
||||||
|
val paramMap = definition.fn.function.parameters.associate {
|
||||||
|
val alias = JsScope.declareTemporaryName(it.name.ident)
|
||||||
|
alias.copyMetadataFrom(it.name)
|
||||||
|
it.name to JsAstUtils.pureFqn(alias, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = definition.fn.function.deepCopy()
|
||||||
|
|
||||||
|
replaceNames(result, replacements)
|
||||||
|
replaceNames(result, paramMap)
|
||||||
|
|
||||||
|
result.body = transformSpecialFunctionsToCoroutineMetadata(result.body)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProgramFragmentInliningScope(
|
class ProgramFragmentInliningScope(
|
||||||
override val fragment: JsProgramFragment,
|
override val fragment: JsProgramFragment
|
||||||
val functionContext: FunctionContext,
|
|
||||||
val rootInliner: JsInliner
|
|
||||||
) : InliningScope() {
|
) : InliningScope() {
|
||||||
|
|
||||||
private val existingModules = fragment.importedModules.mapTo(IdentitySet()) { it.internalName }
|
private val existingModules = fragment.importedModules.associateTo(mutableMapOf()) { it.key to it }
|
||||||
|
|
||||||
private val existingImports = fragment.nameBindings.associateTo(mutableMapOf()) { it.key to it.name }
|
private val existingBindings = fragment.nameBindings.associateTo(mutableMapOf()) { it.key to it.name }
|
||||||
|
|
||||||
private val existingNameBindings = fragment.nameBindings.associateTo(IdentityHashMap()) { it.name to it.key }
|
|
||||||
|
|
||||||
private val additionalDeclarations = mutableListOf<JsStatement>()
|
private val additionalDeclarations = mutableListOf<JsStatement>()
|
||||||
|
|
||||||
private var processed = false
|
override fun update() {
|
||||||
|
|
||||||
override fun process() {
|
|
||||||
if (!processed) {
|
|
||||||
// TODO is this even needed?
|
|
||||||
processed = true
|
|
||||||
|
|
||||||
val inliner = InlinerImpl(rootInliner.cycleReporter, functionContext, this)
|
|
||||||
|
|
||||||
// TODO any way and/or need to visit everything inside the fragment?
|
|
||||||
inliner.acceptStatement(fragment.declarationBlock)
|
|
||||||
|
|
||||||
// TODO Atm it's placed after inliner in order not to perform the body inlining twice. Is that OK?
|
|
||||||
// Ideally it could be moved to the coroutine transformers. The info regarding which inline function wrappers have been imported
|
|
||||||
// on top level should be persisted for that sake. Also it going to be needed in order to avoid duplicate code.
|
|
||||||
InlineSuspendFunctionSplitter(this).accept(fragment.declarationBlock)
|
|
||||||
|
|
||||||
// Mostly for the sake of post-processor
|
|
||||||
// TODO are inline function marked with @Test possible?
|
|
||||||
if (fragment.tests != null) {
|
|
||||||
inliner.acceptStatement(fragment.tests)
|
|
||||||
}
|
|
||||||
// TODO wrap in a function in order to do the post-processing
|
|
||||||
inliner.acceptStatement(fragment.initializerBlock)
|
|
||||||
|
|
||||||
updateProgramFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProgramFragment() {
|
|
||||||
// TODO fix the order
|
// TODO fix the order
|
||||||
// TODO this probably will be replaced with a special tag -> block map for the imported stuff, so that we can merge same imports.
|
// TODO this probably will be replaced with a special tag -> block map for the imported stuff, so that we can merge same imports.
|
||||||
// TODO in that case this method will become obsolete
|
// TODO in that case this method will become obsolete
|
||||||
@@ -110,125 +169,25 @@ class ProgramFragmentInliningScope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addInlinedModule(module: JsImportedModule) {
|
override fun hasImport(tag: String): JsName? = existingBindings[tag]
|
||||||
// if (moduleName !in existingModules) {
|
|
||||||
// fragment.importedModules.add(moduleMap[moduleName]!!.let {
|
override fun addImport(tag: String, vars: JsVars) {
|
||||||
// // Copy so that the Merger.kt doesn't operate on the same instance in different fragments.
|
val name = vars.vars[0].name
|
||||||
// JsImportedModule(it.externalName, it.internalName, it.plainReference)
|
val expr = vars.vars[0].initExpression
|
||||||
// })
|
fragment.imports[tag] = expr
|
||||||
// }
|
fragment.nameBindings.add(JsNameBinding(tag, name))
|
||||||
|
existingBindings[tag] = name
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addImport(tag: String, e: JsExpression) {
|
override fun addInlinedDeclaration(tag: String?, declaration: JsStatement) {
|
||||||
fragment.imports[tag] = e
|
if (tag != null) {
|
||||||
}
|
fragment.inlinedFunctionWrappers.computeIfAbsent(tag) { JsGlobalBlock() }.statements.add(declaration)
|
||||||
|
} else {
|
||||||
private fun addNameBinding(binding: JsNameBinding) {
|
additionalDeclarations.add(declaration)
|
||||||
fragment.nameBindings.add(binding)
|
|
||||||
existingNameBindings[binding.name] = binding.key
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction {
|
|
||||||
|
|
||||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
|
||||||
// That's because crossinline lambdas inline into the declaration block and specialize those.
|
|
||||||
val replacements = computeIfAbsent(f.tag) {
|
|
||||||
val newReplacements = HashMap<JsName, JsNameRef>()
|
|
||||||
|
|
||||||
val copiedStatements = ArrayList<JsStatement>()
|
|
||||||
val importStatements = ArrayList<Pair<String, JsVars.JsVar>>()
|
|
||||||
|
|
||||||
f.functionWithWrapper.wrapperBody?.let {
|
|
||||||
it.statements.asSequence()
|
|
||||||
.filterNot { it is JsReturn }
|
|
||||||
.map { it.deepCopy() }
|
|
||||||
.forEach { statement ->
|
|
||||||
replaceExpressionsWithLocalAliases(statement)
|
|
||||||
|
|
||||||
if (statement is JsVars) {
|
|
||||||
val tag = getImportTag(statement)
|
|
||||||
if (tag != null) {
|
|
||||||
// TODO handle JsVars with multiple vars?
|
|
||||||
val name = statement.vars[0].name
|
|
||||||
var existingName: JsName? = name.localAlias
|
|
||||||
if (existingName == null) {
|
|
||||||
existingName = existingImports.computeIfAbsent(tag) {
|
|
||||||
importStatements.add(tag to statement.vars[0])
|
|
||||||
val alias = JsScope.declareTemporaryName(name.ident)
|
|
||||||
alias.copyMetadataFrom(name)
|
|
||||||
newReplacements[name] = JsAstUtils.pureFqn(alias, null)
|
|
||||||
alias
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name !== existingName) {
|
|
||||||
val replacement = JsAstUtils.pureFqn(existingName, null)
|
|
||||||
newReplacements[name] = replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copiedStatements.add(statement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(importStatements.asSequence().map { JsVars(it.second) } + copiedStatements.asSequence())
|
|
||||||
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() }
|
|
||||||
.filter { name -> !newReplacements.containsKey(name) }
|
|
||||||
.forEach { name ->
|
|
||||||
val alias = JsScope.declareTemporaryName(name.ident)
|
|
||||||
alias.copyMetadataFrom(name)
|
|
||||||
val replacement = JsAstUtils.pureFqn(alias, null)
|
|
||||||
newReplacements[name] = replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for ((tag, statement) in importStatements) {
|
|
||||||
val renamed = replaceNames(statement, newReplacements)
|
|
||||||
// TODO shouldn't this be done at `existingImports.computeIfAbsent` moment?
|
|
||||||
addImport(tag, renamed.initExpression)
|
|
||||||
|
|
||||||
addNameBinding(JsNameBinding(tag, renamed.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (f.tag != null) {
|
|
||||||
fragment.inlinedFunctionWrappers[f.tag!!] = JsGlobalBlock().also {
|
|
||||||
copiedStatements.mapTo(it.statements) { replaceNames(it, newReplacements) }
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO Handle it better?
|
|
||||||
for (statement in copiedStatements) {
|
|
||||||
additionalDeclarations.add(replaceNames(statement, newReplacements))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO shouldn't this be moved to renamer?
|
|
||||||
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
|
|
||||||
if (key.staticRef is JsFunction) {
|
|
||||||
key.staticRef = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newReplacements
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val paramMap = f.functionWithWrapper.function.parameters.associate {
|
|
||||||
val alias = JsScope.declareTemporaryName(it.name.ident)
|
|
||||||
alias.copyMetadataFrom(it.name)
|
|
||||||
it.name to JsAstUtils.pureFqn(alias, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = f.functionWithWrapper.function.deepCopy()
|
|
||||||
|
|
||||||
replaceNames(result, replacements)
|
|
||||||
replaceNames(result, paramMap)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun replaceExpressionsWithLocalAliases(statement: JsStatement) {
|
override fun preprocess(statement: JsStatement) {
|
||||||
object : JsVisitorWithContextImpl() {
|
object : JsVisitorWithContextImpl() {
|
||||||
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
override fun endVisit(x: JsNameRef, ctx: JsContext<JsNode>) {
|
||||||
replaceIfNecessary(x, ctx)
|
replaceIfNecessary(x, ctx)
|
||||||
@@ -241,97 +200,44 @@ class ProgramFragmentInliningScope(
|
|||||||
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
|
private fun replaceIfNecessary(expression: JsExpression, ctx: JsContext<JsNode>) {
|
||||||
val alias = expression.localAlias
|
val alias = expression.localAlias
|
||||||
if (alias != null) {
|
if (alias != null) {
|
||||||
// TODO wrong!
|
ctx.replaceMe(addInlinedModule(alias).makeRef())
|
||||||
ctx.replaceMe(alias.internalName.makeRef())
|
|
||||||
addInlinedModule(alias)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}.accept(statement)
|
}.accept(statement)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun addInlinedModule(module: JsImportedModule): JsName {
|
||||||
|
return existingModules.computeIfAbsent(module.key) {
|
||||||
|
// Copy so that the Merger.kt doesn't operate on the same instance in different fragments.
|
||||||
|
// TODO What about nameBindings?
|
||||||
|
JsImportedModule(module.externalName, module.internalName, module.plainReference).also {
|
||||||
|
fragment.importedModules.add(it)
|
||||||
|
}
|
||||||
|
}.internalName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PublicInlineFunctionInliningScope(
|
class PublicInlineFunctionInliningScope(
|
||||||
override val fragment: JsProgramFragment,
|
|
||||||
cycleReporter: InlinerCycleReporter,
|
|
||||||
functionContext: FunctionContext,
|
|
||||||
val function: JsFunction,
|
val function: JsFunction,
|
||||||
val wrapperBody: JsBlock
|
val wrapperBody: JsBlock,
|
||||||
|
override val fragment: JsProgramFragment
|
||||||
) : InliningScope() {
|
) : InliningScope() {
|
||||||
|
|
||||||
val additionalStatements = mutableListOf<JsStatement>()
|
val additionalStatements = mutableListOf<JsStatement>()
|
||||||
|
|
||||||
val innerInliner = InlinerImpl(
|
override fun addInlinedDeclaration(tag: String?, declaration: JsStatement) {
|
||||||
cycleReporter,
|
additionalStatements.add(declaration)
|
||||||
functionContext,
|
}
|
||||||
this
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun process() {
|
override fun hasImport(tag: String): JsName? {
|
||||||
for (statement in wrapperBody.statements) {
|
return null // TODO
|
||||||
if (statement !is JsReturn) {
|
}
|
||||||
innerInliner.acceptStatement(statement)
|
|
||||||
} else {
|
|
||||||
innerInliner.accept((statement.expression as JsFunction).body)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO keep order
|
override fun addImport(tag: String, vars: JsVars) {
|
||||||
|
additionalStatements.add(vars) // TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun update() {
|
||||||
wrapperBody.statements.addAll(0, additionalStatements)
|
wrapperBody.statements.addAll(0, additionalStatements)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addPrevious(statement: JsStatement) {
|
|
||||||
// TODO Is this correct?
|
|
||||||
additionalStatements.add(innerInliner.accept(statement))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun importFunctionDefinition(f: InlineFunctionDefinition): JsFunction {
|
|
||||||
// TODO Decrypt the comment below
|
|
||||||
// Apparently we should avoid this trick when we implement fair support for crossinline
|
|
||||||
val replacements = computeIfAbsent(f.tag) {
|
|
||||||
val newReplacements = HashMap<JsName, JsNameRef>()
|
|
||||||
|
|
||||||
// TODO Why don't we collect existing imports?
|
|
||||||
|
|
||||||
val copiedStatements = f.functionWithWrapper.wrapperBody!!.statements.asSequence()
|
|
||||||
.filterNot { it is JsReturn }
|
|
||||||
.map { it.deepCopy() }.toList()
|
|
||||||
|
|
||||||
val definedNames = copiedStatements.asSequence()
|
|
||||||
.flatMap { node -> collectDefinedNamesInAllScopes(node).asSequence() }
|
|
||||||
.filter { name -> !newReplacements.containsKey(name) }
|
|
||||||
.toSet()
|
|
||||||
for (name in definedNames) {
|
|
||||||
val alias = JsScope.declareTemporaryName(name.ident)
|
|
||||||
alias.copyMetadataFrom(name)
|
|
||||||
val replacement = JsAstUtils.pureFqn(alias, null)
|
|
||||||
newReplacements[name] = replacement
|
|
||||||
}
|
|
||||||
|
|
||||||
for (statement in copiedStatements) {
|
|
||||||
addPrevious(replaceNames(statement, newReplacements))
|
|
||||||
}
|
|
||||||
|
|
||||||
for ((key, value) in collectNamedFunctions(JsBlock(copiedStatements))) {
|
|
||||||
if (key.staticRef is JsFunction) {
|
|
||||||
key.staticRef = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newReplacements
|
|
||||||
}
|
|
||||||
|
|
||||||
val paramMap = f.functionWithWrapper.function.parameters.associate {
|
|
||||||
val alias = JsScope.declareTemporaryName(it.name.ident)
|
|
||||||
alias.copyMetadataFrom(it.name)
|
|
||||||
it.name to JsAstUtils.pureFqn(alias, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
val result = f.functionWithWrapper.function.deepCopy()
|
|
||||||
|
|
||||||
replaceNames(result, replacements)
|
|
||||||
replaceNames(result, paramMap)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,15 @@ package org.jetbrains.kotlin.js.inline
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.metadata.forcedReturnVariable
|
||||||
|
import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
import org.jetbrains.kotlin.js.config.JsConfig
|
||||||
|
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor
|
||||||
|
import org.jetbrains.kotlin.js.inline.clean.removeUnusedLocalFunctionDeclarations
|
||||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
import org.jetbrains.kotlin.js.inline.context.FunctionContext
|
||||||
|
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||||
|
import org.jetbrains.kotlin.js.inline.util.refreshLabelNames
|
||||||
|
import org.jetbrains.kotlin.js.translate.declaration.transformSpecialFunctionsToCoroutineMetadata
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
||||||
|
|
||||||
@@ -35,7 +42,92 @@ class JsInliner(
|
|||||||
|
|
||||||
fun process() {
|
fun process() {
|
||||||
for (fragment in translationResult.newFragments) {
|
for (fragment in translationResult.newFragments) {
|
||||||
functionContext.scopeForFragment(fragment).process()
|
process(fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun process(fragment: JsProgramFragment) {
|
||||||
|
val fragmentScope = functionContext.scopeForFragment(fragment) ?: return
|
||||||
|
|
||||||
|
// TODO any way and/or need to visit everything inside the fragment?
|
||||||
|
fragmentScope.process(fragment.declarationBlock)
|
||||||
|
|
||||||
|
// TODO Atm it's placed after inliner in order not to perform the body inlining twice. Is that OK?
|
||||||
|
// Ideally it could be moved to the coroutine transformers. The info regarding which inline function wrappers have been imported
|
||||||
|
// on top level should be persisted for that sake. Also it going to be needed in order to avoid duplicate code.
|
||||||
|
InlineSuspendFunctionSplitter(fragmentScope).accept(fragment.declarationBlock)
|
||||||
|
|
||||||
|
// Mostly for the sake of post-processor
|
||||||
|
// TODO are inline function marked with @Test possible?
|
||||||
|
fragment.tests?.let { fragmentScope.process(it) }
|
||||||
|
|
||||||
|
// TODO wrap in a function in order to do the post-processing
|
||||||
|
fragmentScope.process(fragment.initializerBlock)
|
||||||
|
|
||||||
|
fragmentScope.update()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun process(inlineFn: InlineFunctionDefinition, call: JsInvocation?, containingScope: InliningScope) {
|
||||||
|
if (cycleReporter.shouldProcess(inlineFn.fn, call)) {
|
||||||
|
val (function, wrapperBody) = inlineFn.fn
|
||||||
|
|
||||||
|
cycleReporter.startFunction(function)
|
||||||
|
|
||||||
|
if (wrapperBody != null) {
|
||||||
|
val scope = PublicInlineFunctionInliningScope(function, wrapperBody, containingScope.fragment)
|
||||||
|
scope.process(wrapperBody)
|
||||||
|
scope.update()
|
||||||
|
} else {
|
||||||
|
containingScope.process(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
refreshLabelNames(function.body, function.scope)
|
||||||
|
removeUnusedLocalFunctionDeclarations(function)
|
||||||
|
FunctionPostProcessor(function).apply()
|
||||||
|
|
||||||
|
cycleReporter.endFunction(function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun InliningScope.process(node: JsNode) {
|
||||||
|
InlinerImpl(this@JsInliner, this).accept(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO a lot of code... Probably a bad sign
|
||||||
|
fun inline(scope: InliningScope, call: JsInvocation, currentStatement: JsStatement?): InlineableResult {
|
||||||
|
val definition = functionContext.getFunctionDefinition(call, scope)
|
||||||
|
|
||||||
|
return cycleReporter.withInlining(call) {
|
||||||
|
|
||||||
|
val function = scope.importFunctionDefinition(definition)
|
||||||
|
|
||||||
|
val inliningContext = InliningContext(currentStatement)
|
||||||
|
|
||||||
|
val (inlineableBody, resultExpression) = FunctionInlineMutator.getInlineableCallReplacement(call, function, inliningContext)
|
||||||
|
|
||||||
|
// body of inline function can contain call to lambdas that need to be inlined
|
||||||
|
scope.process(inlineableBody)
|
||||||
|
|
||||||
|
patchReturnsFromSecondaryConstructor(inlineableBody)
|
||||||
|
|
||||||
|
// TODO shouldn't we process the resultExpression qualifier along with the lambda inlining?
|
||||||
|
resultExpression?.synthetic = true
|
||||||
|
|
||||||
|
InlineableResult(JsBlock(inliningContext.previousStatements + inlineableBody), resultExpression)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun patchReturnsFromSecondaryConstructor(inlineableBody: JsStatement) {
|
||||||
|
// Support non-local return from secondary constructor
|
||||||
|
// Returns from secondary constructors should return `$this` object.
|
||||||
|
// TODO This seems brittle
|
||||||
|
cycleReporter.currentNamedFunction?.forcedReturnVariable?.let { returnVariable ->
|
||||||
|
inlineableBody.accept(object : RecursiveJsVisitor() {
|
||||||
|
override fun visitReturn(x: JsReturn) {
|
||||||
|
x.expression = returnVariable.makeRef()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,15 +16,12 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.inline.context
|
package org.jetbrains.kotlin.js.inline.context
|
||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.*
|
import org.jetbrains.kotlin.js.backend.ast.*
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.isCallableReference
|
import org.jetbrains.kotlin.js.backend.ast.metadata.isCallableReference
|
||||||
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
import org.jetbrains.kotlin.js.backend.ast.metadata.descriptor
|
||||||
import org.jetbrains.kotlin.js.config.JsConfig
|
|
||||||
import org.jetbrains.kotlin.js.inline.*
|
import org.jetbrains.kotlin.js.inline.*
|
||||||
import org.jetbrains.kotlin.js.inline.util.*
|
import org.jetbrains.kotlin.js.inline.util.*
|
||||||
import org.jetbrains.kotlin.js.translate.context.Namer
|
import org.jetbrains.kotlin.js.translate.context.Namer
|
||||||
import org.jetbrains.kotlin.js.translate.general.AstGenerationResult
|
|
||||||
import java.util.HashMap
|
import java.util.HashMap
|
||||||
|
|
||||||
class FunctionContext(
|
class FunctionContext(
|
||||||
@@ -52,8 +49,6 @@ class FunctionContext(
|
|||||||
return getFunctionDefinitionImpl(call, scope) != null
|
return getFunctionDefinitionImpl(call, scope) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
val functionsByWrapperNodes = HashMap<JsBlock, FunctionWithWrapper>()
|
|
||||||
|
|
||||||
val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>()
|
val functionsByFunctionNodes = HashMap<JsFunction, FunctionWithWrapper>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,17 +83,13 @@ class FunctionContext(
|
|||||||
*/
|
*/
|
||||||
private fun getFunctionDefinitionImpl(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? {
|
private fun getFunctionDefinitionImpl(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? {
|
||||||
// Ensure we have the local function information
|
// Ensure we have the local function information
|
||||||
|
// TODO is this necessary?
|
||||||
loadFragment(scope.fragment)
|
loadFragment(scope.fragment)
|
||||||
|
|
||||||
val descriptor = call.descriptor
|
return lookUpFunctionDirect(call) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(call, scope.fragment)
|
||||||
if (descriptor != null) {
|
|
||||||
return lookUpFunctionDirect(descriptor) ?: lookUpFunctionIndirect(call, scope) ?: lookUpFunctionExternal(descriptor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lookUpFunctionIndirect(call, scope)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lookUpFunctionIndirect(call: JsInvocation, scope: InliningScope): LocalInlineFunctionDefinition? {
|
private fun lookUpFunctionIndirect(call: JsInvocation, scope: InliningScope): InlineFunctionDefinition? {
|
||||||
/** remove ending `()` */
|
/** remove ending `()` */
|
||||||
val callQualifier: JsExpression = if (isCallInvocation(call)) {
|
val callQualifier: JsExpression = if (isCallInvocation(call)) {
|
||||||
(call.qualifier as JsNameRef).qualifier!!
|
(call.qualifier as JsNameRef).qualifier!!
|
||||||
@@ -121,21 +112,23 @@ class FunctionContext(
|
|||||||
is JsFunction -> functionsByFunctionNodes[qualifier] ?: FunctionWithWrapper(qualifier, null)
|
is JsFunction -> functionsByFunctionNodes[qualifier] ?: FunctionWithWrapper(qualifier, null)
|
||||||
else -> null
|
else -> null
|
||||||
}?.let {
|
}?.let {
|
||||||
LocalInlineFunctionDefinition(it, scope)
|
InlineFunctionDefinition(it, null).also { definition ->
|
||||||
|
if (scope.fragment in newFragments) {
|
||||||
|
inliner.process(definition, call, scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun functionTag(call: JsInvocation): String? {
|
|
||||||
return call.descriptor?.let { Namer.getFunctionTag(it, inliner.config) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val newFragmentSet = inliner.translationResult.newFragments.toIdentitySet()
|
|
||||||
|
|
||||||
private val inliningScopeCache = mutableMapOf<JsProgramFragment, ProgramFragmentInliningScope>()
|
private val inliningScopeCache = mutableMapOf<JsProgramFragment, ProgramFragmentInliningScope>()
|
||||||
|
|
||||||
fun scopeForFragment(fragment: JsProgramFragment) = inliningScopeCache.computeIfAbsent(fragment) {
|
private val newFragments = inliner.translationResult.newFragments.toIdentitySet()
|
||||||
ProgramFragmentInliningScope(fragment, this, inliner)
|
|
||||||
}
|
fun scopeForFragment(fragment: JsProgramFragment) = if (fragment in newFragments) {
|
||||||
|
inliningScopeCache.computeIfAbsent(fragment) {
|
||||||
|
ProgramFragmentInliningScope(fragment)
|
||||||
|
}
|
||||||
|
} else null
|
||||||
|
|
||||||
private fun loadFragment(fragment: JsProgramFragment) {
|
private fun loadFragment(fragment: JsProgramFragment) {
|
||||||
fragmentInfo.computeIfAbsent(fragment) {
|
fragmentInfo.computeIfAbsent(fragment) {
|
||||||
@@ -145,36 +138,38 @@ class FunctionContext(
|
|||||||
).also { (functions, accessors) ->
|
).also { (functions, accessors) ->
|
||||||
(functions.values.asSequence() + accessors.values.asSequence()).forEach { f ->
|
(functions.values.asSequence() + accessors.values.asSequence()).forEach { f ->
|
||||||
functionsByFunctionNodes[f.function] = f
|
functionsByFunctionNodes[f.function] = f
|
||||||
if (f.wrapperBody != null) {
|
|
||||||
functionsByWrapperNodes[f.wrapperBody] = f
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fragmentByTag(tag: String): JsProgramFragment? {
|
fun fragmentByTag(tag: String): JsProgramFragment? {
|
||||||
return inliner.translationResult.inlineFunctionTagMap[tag]?.let { unit ->
|
return inliner.translationResult.inlineFunctionTagMap[tag]?.let { unit ->
|
||||||
inliner.translationResult.translate(unit).fragment.also { loadFragment(it) }
|
inliner.translationResult.translate(unit).fragment.also { loadFragment(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lookUpFunctionDirect(descriptor: CallableDescriptor): InlineFunctionDefinition? =
|
private fun lookUpFunctionDirect(call: JsInvocation): InlineFunctionDefinition? =
|
||||||
Namer.getFunctionTag(descriptor, inliner.config).let { tag ->
|
call.descriptor?.let { descriptor ->
|
||||||
fragmentByTag(tag)?.let { fragment ->
|
Namer.getFunctionTag(descriptor, inliner.config).let { tag ->
|
||||||
lookUpStaticFunctionByTag(tag, fragment)?.let {
|
fragmentByTag(tag)?.let { definitionFragment ->
|
||||||
if (fragment !in newFragmentSet) {
|
return lookUpStaticFunctionByTag(tag, definitionFragment)?.let { fn ->
|
||||||
BinaryInlineFunctionDefinition(tag, it, fragment)
|
InlineFunctionDefinition(fn, tag).also { definition ->
|
||||||
} else {
|
scopeForFragment(definitionFragment)?.let { definitionScope ->
|
||||||
// TODO This is a wrong scope =(
|
inliner.process(definition, call, definitionScope)
|
||||||
PublicInlineFunctionDefinition(tag, it, fragment, scopeForFragment(fragment))
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun lookUpFunctionExternal(call: JsInvocation, fragment: JsProgramFragment): InlineFunctionDefinition? =
|
||||||
private fun lookUpFunctionExternal(descriptor: CallableDescriptor): LibraryInlineFunctionDefinition? = functionReader[descriptor]
|
call.descriptor?.let { descriptor ->
|
||||||
|
functionReader[descriptor, fragment]?.let {
|
||||||
|
InlineFunctionDefinition(it, Namer.getFunctionTag(descriptor, inliner.config))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? {
|
private fun tryExtractCallableReference(invocation: JsInvocation): FunctionWithWrapper? {
|
||||||
if (invocation.isCallableReference) {
|
if (invocation.isCallableReference) {
|
||||||
|
|||||||
@@ -16,9 +16,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.js.inline.context
|
package org.jetbrains.kotlin.js.inline.context
|
||||||
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsContext
|
|
||||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||||
|
|
||||||
class InliningContext(val statementContext: JsContext<JsStatement>) {
|
class InliningContext(val currentStatement: JsStatement?) {
|
||||||
fun newNamingContext() = NamingContext(statementContext)
|
val previousStatements = mutableListOf<JsStatement>()
|
||||||
|
|
||||||
|
fun newNamingContext() = NamingContext(previousStatements)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ import org.jetbrains.kotlin.js.backend.ast.metadata.synthetic
|
|||||||
import org.jetbrains.kotlin.js.inline.util.replaceNames
|
import org.jetbrains.kotlin.js.inline.util.replaceNames
|
||||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||||
|
|
||||||
class NamingContext(private val statementContext: JsContext<JsStatement>) {
|
class NamingContext(private val previousStatements: MutableList<JsStatement>) {
|
||||||
private val renamings = mutableMapOf<JsName, JsNameRef>()
|
private val renamings = mutableMapOf<JsName, JsNameRef>()
|
||||||
private val declarations = mutableListOf<JsVars>()
|
private val declarations = mutableListOf<JsVars>()
|
||||||
private var addedDeclarations = false
|
private var addedDeclarations = false
|
||||||
|
|
||||||
fun applyRenameTo(target: JsNode): JsNode {
|
fun applyRenameTo(target: JsNode): JsNode {
|
||||||
if (!addedDeclarations) {
|
if (!addedDeclarations) {
|
||||||
statementContext.addPrevious(declarations)
|
previousStatements.addAll(declarations)
|
||||||
addedDeclarations = true
|
addedDeclarations = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user