Remove optimizations performed during JS inlining. Implement several optimizations as a separate pass
This commit is contained in:
+2
@@ -37,6 +37,8 @@ var JsParameter.hasDefaultValue: Boolean by MetadataProperty(default = false)
|
||||
|
||||
var JsInvocation.typeCheck: TypeCheck? by MetadataProperty(default = null)
|
||||
|
||||
var HasMetadata.synthetic: Boolean by MetadataProperty(default = false)
|
||||
|
||||
enum class TypeCheck {
|
||||
TYPEOF,
|
||||
INSTANCEOF
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.util
|
||||
package org.jetbrains.kotlin.js.inline
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import com.intellij.util.SmartList
|
||||
import org.jetbrains.kotlin.js.inline.util.IdentitySet
|
||||
import org.jetbrains.kotlin.js.inline.util.canHaveOwnSideEffect
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.*
|
||||
import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.*
|
||||
|
||||
@@ -322,8 +325,15 @@ internal class ExpressionDecomposer private constructor(
|
||||
val nameRef: JsExpression
|
||||
get() = name.makeRef()
|
||||
|
||||
fun assign(value: JsExpression): JsStatement =
|
||||
assignment(nameRef, value).makeStmt()
|
||||
init {
|
||||
variable.synthetic = true
|
||||
}
|
||||
|
||||
fun assign(value: JsExpression): JsStatement {
|
||||
val statement = JsExpressionStatement(assignment(nameRef, value))
|
||||
statement.synthetic = true
|
||||
return statement
|
||||
}
|
||||
}
|
||||
|
||||
private val List<JsNode>.indicesOfExtractable: Iterator<Int>
|
||||
|
||||
@@ -17,15 +17,13 @@
|
||||
package org.jetbrains.kotlin.js.inline
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.util.AstUtil
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.inline.clean.removeDefaultInitializers
|
||||
import org.jetbrains.kotlin.js.inline.context.InliningContext
|
||||
import org.jetbrains.kotlin.js.inline.context.NamingContext
|
||||
import org.jetbrains.kotlin.js.inline.util.*
|
||||
import org.jetbrains.kotlin.js.inline.util.rewriters.ReturnReplacingVisitor
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils.newVar
|
||||
import org.jetbrains.kotlin.js.translate.utils.jsAstUtils.*
|
||||
|
||||
class FunctionInlineMutator
|
||||
private constructor(
|
||||
@@ -33,7 +31,6 @@ private constructor(
|
||||
private val inliningContext: InliningContext
|
||||
) {
|
||||
private val invokedFunction: JsFunction
|
||||
private val isResultNeeded: Boolean
|
||||
private val namingContext: NamingContext
|
||||
private val body: JsBlock
|
||||
private var resultExpr: JsExpression? = null
|
||||
@@ -45,7 +42,6 @@ private constructor(
|
||||
val functionContext = inliningContext.functionContext
|
||||
invokedFunction = functionContext.getFunctionDefinition(call)
|
||||
body = invokedFunction.body.deepCopy()
|
||||
isResultNeeded = isResultNeeded(call)
|
||||
namingContext = inliningContext.newNamingContext()
|
||||
}
|
||||
|
||||
@@ -84,45 +80,13 @@ private constructor(
|
||||
private fun removeStatementsAfterTopReturn() {
|
||||
val statements = body.statements
|
||||
|
||||
val statementsSize = statements.size
|
||||
for (i in 0..statementsSize - 1) {
|
||||
val statement = statements.get(i)
|
||||
|
||||
if (statement is JsReturn) {
|
||||
statements.subList(i + 1, statementsSize).clear()
|
||||
break
|
||||
}
|
||||
val returnIndex = statements.indexOfFirst { it is JsReturn }
|
||||
if (returnIndex >= 0) {
|
||||
statements.subList(returnIndex + 1, statements.size).clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun processReturns() {
|
||||
if (currentStatement is JsReturn && currentStatement.expression === call) {
|
||||
inliningContext.statementContext.removeMe()
|
||||
return
|
||||
}
|
||||
|
||||
val returnCount = collectInstances(JsReturn::class.java, body).size
|
||||
if (returnCount == 0) {
|
||||
// TODO return Unit (KT-5647)
|
||||
resultExpr = JsLiteral.UNDEFINED
|
||||
return
|
||||
}
|
||||
|
||||
if (returnCount == 1) {
|
||||
val statements = body.statements
|
||||
val lastTopLevelStatement = statements[statements.lastIndex]
|
||||
|
||||
if (lastTopLevelStatement is JsReturn) {
|
||||
resultExpr = lastTopLevelStatement.expression
|
||||
statements.removeAt(statements.lastIndex)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
doReplaceReturns()
|
||||
}
|
||||
|
||||
private fun doReplaceReturns() {
|
||||
val resultReference = getResultReference()
|
||||
if (resultReference != null) {
|
||||
resultExpr = resultReference
|
||||
@@ -130,62 +94,22 @@ private constructor(
|
||||
assert(resultExpr == null || resultExpr is JsNameRef)
|
||||
|
||||
val breakName = namingContext.getFreshName(getBreakLabel())
|
||||
breakLabel = JsLabel(breakName)
|
||||
val breakLabel = JsLabel(breakName)
|
||||
breakLabel.synthetic = true
|
||||
this.breakLabel = breakLabel
|
||||
|
||||
val visitor = ReturnReplacingVisitor(resultExpr as? JsNameRef, breakName.makeRef())
|
||||
visitor.accept(body)
|
||||
|
||||
val statements = body.statements
|
||||
val last = statements.lastOrNull() as? JsBreak
|
||||
|
||||
if (last?.label?.name === breakLabel?.name) {
|
||||
statements.removeAt(statements.lastIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResultReference(): JsNameRef? {
|
||||
if (!isResultNeeded) return null
|
||||
|
||||
val existingReference = when (currentStatement) {
|
||||
is JsExpressionStatement -> {
|
||||
val expression = currentStatement.expression as? JsBinaryOperation
|
||||
expression?.getResultReference()
|
||||
}
|
||||
is JsVars -> currentStatement.getResultReference()
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (existingReference != null) return existingReference
|
||||
if (!isResultNeeded(call)) return null
|
||||
|
||||
val resultName = namingContext.getFreshName(getResultLabel())
|
||||
namingContext.newVar(resultName, null)
|
||||
return resultName.makeRef()
|
||||
}
|
||||
|
||||
private fun JsBinaryOperation.getResultReference(): JsNameRef? {
|
||||
if (operator !== JsBinaryOperator.ASG || arg2 !== call) return null
|
||||
|
||||
return arg1 as? JsNameRef
|
||||
}
|
||||
|
||||
private fun JsVars.getResultReference(): JsNameRef? {
|
||||
val vars = vars
|
||||
val variable = vars.first()
|
||||
|
||||
// var a = expr1 + call() is ok, but we don't want to reuse 'a' for result,
|
||||
// as it means to replace every 'return expr2' to 'a = expr1 + expr2'.
|
||||
// If there is more than one return, expr1 copies are undesirable.
|
||||
if (variable.initExpression !== call || vars.size > 1) return null
|
||||
|
||||
val varName = variable.name
|
||||
with (inliningContext.statementContext) {
|
||||
removeMe()
|
||||
addPrevious(newVar(varName, null))
|
||||
}
|
||||
|
||||
return varName.makeRef()
|
||||
}
|
||||
|
||||
private fun getArguments(): List<JsExpression> {
|
||||
val arguments = call.arguments
|
||||
if (isCallInvocation(call)) {
|
||||
@@ -245,7 +169,7 @@ private constructor(
|
||||
@JvmStatic
|
||||
private fun getThisReplacement(call: JsInvocation): JsExpression? {
|
||||
if (isCallInvocation(call)) {
|
||||
return call.arguments.get(0)
|
||||
return call.arguments[0]
|
||||
}
|
||||
|
||||
if (hasCallerQualifier(call)) {
|
||||
@@ -259,14 +183,5 @@ private constructor(
|
||||
val thisRefs = collectInstances(JsLiteral.JsThisRef::class.java, body)
|
||||
return !thisRefs.isEmpty()
|
||||
}
|
||||
|
||||
@JvmStatic fun canBeExpression(function: JsFunction): Boolean {
|
||||
return canBeExpression(function.body)
|
||||
}
|
||||
|
||||
private fun canBeExpression(body: JsBlock): Boolean {
|
||||
val statements = body.statements
|
||||
return statements.size == 1 && statements.get(0) is JsReturn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticSink;
|
||||
import org.jetbrains.kotlin.diagnostics.Errors;
|
||||
import org.jetbrains.kotlin.js.inline.clean.FunctionPostProcessor;
|
||||
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedFunctionDefinitionsKt;
|
||||
import org.jetbrains.kotlin.js.inline.clean.RemoveUnusedLocalFunctionDeclarationsKt;
|
||||
import org.jetbrains.kotlin.js.inline.context.FunctionContext;
|
||||
@@ -36,7 +37,6 @@ import org.jetbrains.kotlin.resolve.inline.InlineStrategy;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.canBeExpression;
|
||||
import static org.jetbrains.kotlin.js.inline.FunctionInlineMutator.getInlineableCallReplacement;
|
||||
import static org.jetbrains.kotlin.js.translate.utils.JsAstUtils.flattenStatement;
|
||||
|
||||
@@ -59,12 +59,7 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
||||
|
||||
JsInvocation call = (JsInvocation) node;
|
||||
|
||||
if (hasToBeInlined(call)) {
|
||||
JsFunction function = getFunctionContext().getFunctionDefinition(call);
|
||||
return !canBeExpression(function);
|
||||
}
|
||||
|
||||
return false;
|
||||
return hasToBeInlined(call);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,6 +103,8 @@ public class JsInliner extends JsVisitorWithContextImpl {
|
||||
RemoveUnusedLocalFunctionDeclarationsKt.removeUnusedLocalFunctionDeclarations(function);
|
||||
processedFunctions.add(function);
|
||||
|
||||
new FunctionPostProcessor(function.getBody()).apply();
|
||||
|
||||
assert inProcessFunctions.contains(function);
|
||||
inProcessFunctions.remove(function);
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.JsBlock
|
||||
|
||||
class FunctionPostProcessor(private val root: JsBlock) {
|
||||
fun apply() {
|
||||
TemporaryAssignmentElimination(root).apply()
|
||||
RedundantLabelRemoval(root).apply()
|
||||
TemporaryVariableElimination(root).apply()
|
||||
RedundantVariableDeclarationElimination(root).apply()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
internal class RedundantLabelRemoval(private val root: JsStatement) {
|
||||
private val labelUsages = mutableMapOf<JsName, Int>()
|
||||
|
||||
fun apply() {
|
||||
analyze()
|
||||
perform()
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsBreak, ctx: JsContext<*>) {
|
||||
super.endVisit(x, ctx)
|
||||
x.label?.let { useLabel(it.name!!) }
|
||||
}
|
||||
|
||||
override fun endVisit(x: JsContinue, ctx: JsContext<*>) {
|
||||
super.endVisit(x, ctx)
|
||||
x.label?.let { useLabel(it.name!!) }
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun perform() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsLabel, ctx: JsContext<JsNode>) {
|
||||
if (x.synthetic) {
|
||||
val statementReplacement = perform(x.statement, x.name)
|
||||
if (statementReplacement == null) {
|
||||
ctx.removeMe()
|
||||
}
|
||||
else if (labelUsages[x.name] ?: 0 == 0) {
|
||||
val replacement = statementReplacement
|
||||
if (replacement is JsBlock) {
|
||||
ctx.addPrevious(replacement.statements)
|
||||
ctx.removeMe()
|
||||
}
|
||||
else {
|
||||
ctx.replaceMe(replacement)
|
||||
}
|
||||
}
|
||||
else {
|
||||
x.statement = statementReplacement
|
||||
}
|
||||
}
|
||||
|
||||
super.endVisit(x, ctx)
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun perform(statement: JsStatement, name: JsName): JsStatement? = when (statement) {
|
||||
is JsBreak ->
|
||||
if (name == statement.label?.name) {
|
||||
unuseLabel(name)
|
||||
null
|
||||
}
|
||||
else {
|
||||
statement
|
||||
}
|
||||
is JsLabel -> {
|
||||
perform(statement.statement, name)?.let { statement }
|
||||
}
|
||||
is JsBlock ->
|
||||
if (perform(statement.statements, name).isEmpty()) {
|
||||
null
|
||||
}
|
||||
else {
|
||||
statement
|
||||
}
|
||||
is JsIf -> {
|
||||
val thenRemoved = perform(statement.thenStatement, name) == null
|
||||
val elseRemoved = statement.elseStatement?.let { perform(it, name) } == null
|
||||
when {
|
||||
thenRemoved && elseRemoved -> null
|
||||
elseRemoved -> {
|
||||
statement.elseStatement = null
|
||||
statement
|
||||
}
|
||||
thenRemoved -> {
|
||||
statement.thenStatement = statement.elseStatement
|
||||
statement.elseStatement = null
|
||||
statement.ifExpression = JsAstUtils.negated(statement.ifExpression)
|
||||
statement
|
||||
}
|
||||
else -> statement
|
||||
}
|
||||
}
|
||||
is JsTry -> {
|
||||
val finallyBlock = statement.finallyBlock
|
||||
val result = perform(statement.tryBlock, name)
|
||||
if (result != null) {
|
||||
statement
|
||||
}
|
||||
else if (finallyBlock != null && !finallyBlock.isEmpty) {
|
||||
finallyBlock
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
else -> statement
|
||||
}
|
||||
|
||||
private fun perform(statements: MutableList<JsStatement>, name: JsName): MutableList<JsStatement> {
|
||||
val last = statements.lastOrNull()
|
||||
val lastOptimized = last?.let { perform(it, name) }
|
||||
if (lastOptimized != last) {
|
||||
if (lastOptimized == null) {
|
||||
statements.removeAt(statements.lastIndex)
|
||||
}
|
||||
else if (lastOptimized is JsBlock) {
|
||||
statements.removeAt(statements.lastIndex)
|
||||
statements.addAll(lastOptimized.statements)
|
||||
}
|
||||
else {
|
||||
statements[statements.lastIndex] = lastOptimized
|
||||
}
|
||||
}
|
||||
return statements
|
||||
}
|
||||
|
||||
private fun useLabel(name: JsName) {
|
||||
labelUsages[name] = (labelUsages[name] ?: 0) + 1
|
||||
}
|
||||
|
||||
private fun unuseLabel(name: JsName) {
|
||||
labelUsages[name] = labelUsages[name]!! - 1
|
||||
}
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
|
||||
internal class RedundantVariableDeclarationElimination(private val root: JsStatement) {
|
||||
private val usages = mutableSetOf<JsName>()
|
||||
|
||||
fun apply() {
|
||||
analyze()
|
||||
perform()
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
|
||||
val name = x.name
|
||||
if (name != null && x.qualifier == null) {
|
||||
usages += name
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun perform() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun endVisit(x: JsVars, ctx: JsContext<*>) {
|
||||
if (x.synthetic) {
|
||||
x.vars.removeAll { it.initExpression == null && it.name !in usages }
|
||||
if (x.vars.isEmpty()) {
|
||||
ctx.removeMe()
|
||||
}
|
||||
}
|
||||
super.endVisit(x, ctx)
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
internal class TemporaryAssignmentElimination(private val root: JsBlock) {
|
||||
private val usageCount = mutableMapOf<JsName, Int>()
|
||||
private val assignmentCount = mutableMapOf<JsName, Int>()
|
||||
private val usages = mutableMapOf<JsName, Usage>()
|
||||
private val statementsToRemove = mutableSetOf<JsStatement>()
|
||||
private val mappedUsages = mutableMapOf<JsName, Usage>()
|
||||
private val syntheticNames = mutableSetOf<JsName>()
|
||||
|
||||
fun apply() {
|
||||
analyze()
|
||||
process()
|
||||
generateDeclarations()
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
||||
val returnExpr = x.expression
|
||||
if (returnExpr != null) {
|
||||
tryRecord(returnExpr, Usage.Return(x))
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val (name, value) = assignment
|
||||
assign(name)
|
||||
val usage = Usage.Assignment(x, name)
|
||||
if (x.synthetic) {
|
||||
syntheticNames += name
|
||||
}
|
||||
tryRecord(value, usage)
|
||||
accept(value)
|
||||
return false
|
||||
}
|
||||
|
||||
val mutation = JsAstUtils.decomposeAssignment(x.expression)
|
||||
if (mutation != null) {
|
||||
val (target, value) = mutation
|
||||
if (!target.canHaveSideEffect()) {
|
||||
val usage = Usage.Mutation(x, target)
|
||||
tryRecord(value, usage)
|
||||
accept(value)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
if (x.vars.size == 1) {
|
||||
val declaration = x.vars[0]
|
||||
val initExpression = declaration.initExpression
|
||||
if (initExpression != null) {
|
||||
tryRecord(initExpression, Usage.Declaration(x, declaration.name))
|
||||
}
|
||||
if (x.synthetic) {
|
||||
syntheticNames += declaration.name
|
||||
}
|
||||
}
|
||||
|
||||
x.vars.forEach { v -> v?.initExpression?.let { accept(it) } }
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
|
||||
val name = x.name
|
||||
if (name != null && x.qualifier == null) {
|
||||
use(name)
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
}.accept(root)
|
||||
|
||||
usages.keys.retainAll(syntheticNames)
|
||||
}
|
||||
|
||||
private fun getUsage(name: JsName): Usage? {
|
||||
return mappedUsages.getOrPut(name) {
|
||||
if (usageCount[name] ?: 0 != 1) return null
|
||||
|
||||
val usage = usages[name]
|
||||
return when (usage) {
|
||||
is Usage.Assignment -> {
|
||||
val result = getUsage(usage.target)
|
||||
if (result != null) {
|
||||
result.statements.addAll(usage.statements)
|
||||
result
|
||||
}
|
||||
else {
|
||||
usage
|
||||
}
|
||||
}
|
||||
is Usage.Declaration -> {
|
||||
val result = getUsage(usage.target)
|
||||
if (result != null) {
|
||||
result.statements.addAll(usage.statements)
|
||||
result
|
||||
}
|
||||
else {
|
||||
usage
|
||||
}
|
||||
}
|
||||
else -> usage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun process() {
|
||||
usages.keys.forEach { getUsage(it) }
|
||||
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val (name, value) = assignment
|
||||
val usage = getUsage(name)
|
||||
if (usage != null) {
|
||||
val replacement = when (usage) {
|
||||
is Usage.Return -> JsReturn(value).source(x.expression.source)
|
||||
is Usage.Assignment -> {
|
||||
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
|
||||
val statement = JsExpressionStatement(expr)
|
||||
statement.synthetic = usage.target in syntheticNames
|
||||
statement
|
||||
}
|
||||
is Usage.Declaration -> {
|
||||
val statement: JsStatement = if (assignmentCount[name] ?: 0 != 1) {
|
||||
usage.replaced = true
|
||||
val expr = JsAstUtils.assignment(usage.target.makeRef(), value).source(x.expression.source)
|
||||
val result = JsExpressionStatement(expr)
|
||||
result.synthetic = usage.target in syntheticNames
|
||||
result
|
||||
} else {
|
||||
val declaration = JsAstUtils.newVar(usage.target, value)
|
||||
declaration.source(x.expression.source)
|
||||
declaration.synthetic = usage.target in syntheticNames
|
||||
declaration
|
||||
}
|
||||
statement
|
||||
}
|
||||
is Usage.Mutation -> {
|
||||
JsExpressionStatement(JsAstUtils.assignment(usage.target, value).source(x.expression.source))
|
||||
}
|
||||
}
|
||||
ctx.replaceMe(replacement)
|
||||
statementsToRemove += usage.statements
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsReturn, ctx: JsContext<*>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
if (x in statementsToRemove) {
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun generateDeclarations() {
|
||||
var index = 0
|
||||
usages.values.asSequence()
|
||||
.filter { it is Usage.Declaration && it.replaced }
|
||||
.map { it as Usage.Declaration }
|
||||
.forEach { root.statements.add(index++, JsAstUtils.newVar(it.target, null)) }
|
||||
}
|
||||
|
||||
private fun tryRecord(expr: JsExpression, usage: Usage): Boolean {
|
||||
if (expr !is JsNameRef) return false
|
||||
val name = expr.name ?: return false
|
||||
|
||||
usages[name] = usage
|
||||
return true
|
||||
}
|
||||
|
||||
private fun use(name: JsName) {
|
||||
usageCount[name] = 1 + (usageCount[name] ?: 0)
|
||||
}
|
||||
|
||||
private fun assign(name: JsName) {
|
||||
assignmentCount[name] = 1 + (assignmentCount[name] ?: 0)
|
||||
}
|
||||
|
||||
private sealed class Usage(statement: JsStatement) {
|
||||
val statements = mutableSetOf(statement)
|
||||
|
||||
class Return(statement: JsStatement) : Usage(statement)
|
||||
|
||||
class Assignment(statement: JsStatement, val target: JsName) : Usage(statement)
|
||||
|
||||
class Declaration(statement: JsStatement, val target: JsName) : Usage(statement) {
|
||||
var replaced = false
|
||||
}
|
||||
|
||||
class Mutation(statement: JsStatement, val target: JsExpression) : Usage(statement)
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2010-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.js.inline.clean
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
internal class TemporaryVariableElimination(private val root: JsStatement) {
|
||||
private val definitions = mutableMapOf<JsName, Int>()
|
||||
private val usages = mutableMapOf<JsName, Int>()
|
||||
private val definedValues = mutableMapOf<JsName, JsExpression>()
|
||||
private val temporary = mutableSetOf<JsName>()
|
||||
|
||||
fun apply() {
|
||||
analyze()
|
||||
perform()
|
||||
}
|
||||
|
||||
private fun analyze() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
val lastAssignedVars = mutableListOf<JsName>()
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<*>): Boolean {
|
||||
val name = x.name
|
||||
if (name != null && x.qualifier == null) {
|
||||
val expr = definedValues[name]
|
||||
useVariable(name)
|
||||
if (lastAssignedVars.lastOrNull() == name) {
|
||||
lastAssignedVars.removeAt(lastAssignedVars.lastIndex)
|
||||
}
|
||||
else if (expr == null || expr.canHaveSideEffect()) {
|
||||
temporary -= name
|
||||
}
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsTry, ctx: JsContext<*>): Boolean {
|
||||
lastAssignedVars.clear()
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsWhile, ctx: JsContext<*>): Boolean {
|
||||
lastAssignedVars.clear()
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsInvocation, ctx: JsContext<*>): Boolean {
|
||||
x.arguments.asReversed().forEach { accept(it) }
|
||||
accept(x.qualifier)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<JsNode>): Boolean {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
val (name, value) = assignment
|
||||
assignVariable(name, value)
|
||||
accept(value)
|
||||
if (x.synthetic) {
|
||||
temporary += name
|
||||
}
|
||||
else if (value.canHaveSideEffect()) {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
lastAssignedVars += name
|
||||
return false
|
||||
}
|
||||
|
||||
var statement = x
|
||||
val result = accept(x.expression)
|
||||
if (result != x.expression) {
|
||||
statement = JsExpressionStatement(result)
|
||||
ctx.replaceMe(statement)
|
||||
}
|
||||
|
||||
if (statement.expression.canHaveSideEffect()) {
|
||||
lastAssignedVars.clear()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsBinaryOperation, ctx: JsContext<*>): Boolean {
|
||||
accept(x.arg2)
|
||||
accept(x.arg1)
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
for (v in x.vars) {
|
||||
val name = v.name
|
||||
val value = v.initExpression
|
||||
if (value != null) {
|
||||
assignVariable(name, value)
|
||||
accept(value)
|
||||
if (x.synthetic) {
|
||||
temporary += name
|
||||
}
|
||||
lastAssignedVars += name
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun perform() {
|
||||
object : JsVisitorWithContextImpl() {
|
||||
override fun visit(x: JsExpressionStatement, ctx: JsContext<*>): Boolean {
|
||||
val assignment = JsAstUtils.decomposeAssignmentToVariable(x.expression)
|
||||
if (assignment != null) {
|
||||
if (shouldConsiderTemporary(assignment.first)) {
|
||||
ctx.removeMe()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsVars, ctx: JsContext<*>): Boolean {
|
||||
val filteredVars = x.vars.filter { it.initExpression == null || !shouldConsiderTemporary(it.name) }
|
||||
x.vars.clear()
|
||||
x.vars.addAll(filteredVars)
|
||||
if (x.vars.isEmpty()) {
|
||||
ctx.removeMe()
|
||||
}
|
||||
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsNameRef, ctx: JsContext<JsNode>): Boolean {
|
||||
val name = x.name
|
||||
if (x.qualifier == null && name != null && shouldConsiderTemporary(name)) {
|
||||
val newExpr = definedValues[name]!!
|
||||
ctx.replaceMe(accept(newExpr))
|
||||
usages[name] = usages[name]!! - 1
|
||||
return false
|
||||
}
|
||||
return super.visit(x, ctx)
|
||||
}
|
||||
|
||||
override fun visit(x: JsBreak, ctx: JsContext<*>) = false
|
||||
|
||||
override fun visit(x: JsContinue, ctx: JsContext<*>) = false
|
||||
}.accept(root)
|
||||
}
|
||||
|
||||
private fun assignVariable(name: JsName, value: JsExpression) {
|
||||
definitions[name] = (definitions[name] ?: 0) + 1
|
||||
definedValues[name] = value
|
||||
}
|
||||
|
||||
private fun useVariable(name: JsName) {
|
||||
usages[name] = (usages[name] ?: 0) + 1
|
||||
}
|
||||
|
||||
private fun shouldConsiderTemporary(name: JsName): Boolean {
|
||||
if (definitions[name] ?: 0 != 1 || name !in temporary) return false
|
||||
|
||||
val expr = definedValues[name]
|
||||
return (expr != null && isTrivial(expr)) || usages[name] ?: 0 == 1
|
||||
}
|
||||
|
||||
private fun isTrivial(expr: JsExpression) = when (expr) {
|
||||
is JsNameRef -> expr.qualifier == null
|
||||
is JsLiteral.JsValueLiteral -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.js.inline.context
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
|
||||
import java.util.ArrayList
|
||||
import java.util.IdentityHashMap
|
||||
@@ -53,6 +54,7 @@ class NamingContext(
|
||||
|
||||
fun newVar(name: JsName, value: JsExpression? = null) {
|
||||
val vars = JsAstUtils.newVar(name, value)
|
||||
vars.synthetic = true
|
||||
declarations.add(vars)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.js.inline.util.rewriters
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*
|
||||
import com.google.dart.compiler.backend.js.ast.metadata.synthetic
|
||||
import org.jetbrains.kotlin.js.inline.util.canHaveSideEffect
|
||||
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
|
||||
|
||||
@@ -37,7 +38,9 @@ class ReturnReplacingVisitor(private val resultRef: JsNameRef?, private val brea
|
||||
|
||||
val returnReplacement = getReturnReplacement(x.expression)
|
||||
if (returnReplacement != null) {
|
||||
ctx.addNext(JsExpressionStatement(returnReplacement))
|
||||
val statement = JsExpressionStatement(returnReplacement)
|
||||
statement.synthetic = true
|
||||
ctx.addNext(statement)
|
||||
}
|
||||
|
||||
if (breakLabel != null) {
|
||||
@@ -48,8 +51,9 @@ class ReturnReplacingVisitor(private val resultRef: JsNameRef?, private val brea
|
||||
|
||||
private fun getReturnReplacement(returnExpression: JsExpression?): JsExpression? {
|
||||
if (returnExpression != null) {
|
||||
if (resultRef != null)
|
||||
if (resultRef != null) {
|
||||
return JsAstUtils.assignment(resultRef, returnExpression)
|
||||
}
|
||||
|
||||
if (returnExpression.canHaveSideEffect())
|
||||
return returnExpression
|
||||
|
||||
+12
@@ -35,6 +35,12 @@ public class InlineSizeReductionTestGenerated extends AbstractInlineSizeReductio
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("js/js.translator/testData/inlineSizeReduction/cases"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("inlineOrder.kt")
|
||||
public void testInlineOrder() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/inlineOrder.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("lastBreak.kt")
|
||||
public void testLastBreak() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/lastBreak.kt");
|
||||
@@ -47,6 +53,12 @@ public class InlineSizeReductionTestGenerated extends AbstractInlineSizeReductio
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("propertyAssignment.kt")
|
||||
public void testPropertyAssignment() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/propertyAssignment.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("returnInlineCall.kt")
|
||||
public void testReturnInlineCall() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/inlineSizeReduction/cases/returnInlineCall.kt");
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.js.translate.utils;
|
||||
|
||||
import com.google.dart.compiler.backend.js.ast.*;
|
||||
import com.intellij.util.SmartList;
|
||||
import kotlin.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.js.translate.context.Namer;
|
||||
@@ -299,6 +300,27 @@ public final class JsAstUtils {
|
||||
return new JsBinaryOperation(JsBinaryOperator.ASG, left, right);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<JsExpression, JsExpression> decomposeAssignment(@NotNull JsExpression expr) {
|
||||
if (!(expr instanceof JsBinaryOperation)) return null;
|
||||
|
||||
JsBinaryOperation binary = (JsBinaryOperation) expr;
|
||||
if (binary.getOperator() != JsBinaryOperator.ASG) return null;
|
||||
|
||||
return new Pair<JsExpression, JsExpression>(binary.getArg1(), binary.getArg2());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Pair<JsName, JsExpression> decomposeAssignmentToVariable(@NotNull JsExpression expr) {
|
||||
Pair<JsExpression, JsExpression> assignment = decomposeAssignment(expr);
|
||||
if (assignment == null || !(assignment.getFirst() instanceof JsNameRef)) return null;
|
||||
|
||||
JsNameRef nameRef = (JsNameRef) assignment.getFirst();
|
||||
if (nameRef.getName() == null || nameRef.getQualifier() != null) return null;
|
||||
|
||||
return new Pair<JsName, JsExpression>(nameRef.getName(), assignment.getSecond());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsBinaryOperation sum(@NotNull JsExpression left, @NotNull JsExpression right) {
|
||||
return new JsBinaryOperation(JsBinaryOperator.ADD, left, right);
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package foo
|
||||
|
||||
// CHECK_VARS_COUNT: function=test1 count=0
|
||||
// CHECK_VARS_COUNT: function=test2 count=0
|
||||
// CHECK_VARS_COUNT: function=test3 count=0
|
||||
// CHECK_VARS_COUNT: function=test4 count=0
|
||||
// CHECK_VARS_COUNT: function=test5 count=0
|
||||
|
||||
var global = ""
|
||||
var globalNum = 1
|
||||
var returnValue = 0
|
||||
|
||||
fun pure(n: Int) = n
|
||||
|
||||
fun x(n: Int) = globalNum++ * n
|
||||
|
||||
inline fun a(n: Int) = x(n)
|
||||
|
||||
fun b(n: Int) {
|
||||
returnValue = n
|
||||
}
|
||||
|
||||
fun c(first: Int, second: Int, third: Int) = first + second + third
|
||||
|
||||
inline fun d(first: Int, second: Int, third: Int) {
|
||||
returnValue = first + second + third
|
||||
}
|
||||
|
||||
fun test1(): Int {
|
||||
globalNum = 1
|
||||
return x(a(1) + a(10) + a(100))
|
||||
}
|
||||
|
||||
fun test2() {
|
||||
globalNum = 1
|
||||
b(a(1) + a(10) + a(100))
|
||||
}
|
||||
|
||||
fun test3(): Int {
|
||||
globalNum = 1
|
||||
return c(a(1), a(10), a(100))
|
||||
}
|
||||
|
||||
fun test4() {
|
||||
globalNum = 1
|
||||
d(a(1), a(10), a(100))
|
||||
}
|
||||
|
||||
fun test5(): Int {
|
||||
globalNum = 1
|
||||
return globalNum++ + a(10) + (globalNum++ * 100)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
var result = test1()
|
||||
if (result != 1284) return "fail1: $result"
|
||||
|
||||
test2()
|
||||
result = returnValue
|
||||
if (result != 321) return "fail2: $result"
|
||||
|
||||
result = test3()
|
||||
if (result != 321) return "fail3: $result"
|
||||
|
||||
test4()
|
||||
result = returnValue
|
||||
if (result != 321) return "fail4: $result"
|
||||
|
||||
result = test5()
|
||||
if (result != 321) return "fail5: $result"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package foo
|
||||
|
||||
// CHECK_CONTAINS_NO_CALLS: test
|
||||
// CHECK_VARS_COUNT: function=test count=0
|
||||
|
||||
object SumHolder {
|
||||
var sum = 0
|
||||
}
|
||||
|
||||
internal inline fun sum(x: Int, y: Int): Int {
|
||||
if (x == 0 || y == 0) return 0
|
||||
|
||||
return x + y
|
||||
}
|
||||
|
||||
internal fun test(x: Int, y: Int) {
|
||||
SumHolder.sum = sum(x, y)
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
test(1, 2)
|
||||
assertEquals(3, SumHolder.sum)
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user