Remove optimizations performed during JS inlining. Implement several optimizations as a separate pass

This commit is contained in:
Alexey Andreev
2016-03-15 16:43:01 +03:00
parent 3301f1f614
commit 3ceea68859
15 changed files with 837 additions and 106 deletions
@@ -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
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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
@@ -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"
}