JS: simplification in process

This commit is contained in:
Anton Bannykh
2018-12-12 19:02:48 +03:00
parent 2cbdc7ecb0
commit 75668826d3
12 changed files with 403 additions and 504 deletions
@@ -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
} }