IR: optimize f(); return in tailrec fun f(): Unit
It's equivalent to `return f()`.
This commit is contained in:
+31
-24
@@ -16,15 +16,15 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.common
|
package org.jetbrains.kotlin.backend.common
|
||||||
|
|
||||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
|
||||||
import org.jetbrains.kotlin.ir.IrElement
|
import org.jetbrains.kotlin.ir.IrElement
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.types.IrSimpleType
|
|
||||||
import org.jetbrains.kotlin.ir.types.classOrNull
|
import org.jetbrains.kotlin.ir.types.classOrNull
|
||||||
|
import org.jetbrains.kotlin.ir.types.IdSignatureValues
|
||||||
import org.jetbrains.kotlin.ir.types.isUnit
|
import org.jetbrains.kotlin.ir.types.isUnit
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentClassOrNull
|
||||||
import org.jetbrains.kotlin.ir.util.usesDefaultArguments
|
import org.jetbrains.kotlin.ir.util.usesDefaultArguments
|
||||||
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
|
||||||
|
|
||||||
@@ -42,8 +42,9 @@ fun collectTailRecursionCalls(irFunction: IrFunction): Set<IrCall> {
|
|||||||
return emptySet()
|
return emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = mutableSetOf<IrCall>()
|
val isUnitReturn = irFunction.returnType.isUnit()
|
||||||
|
|
||||||
|
val result = mutableSetOf<IrCall>()
|
||||||
val visitor = object : IrElementVisitor<Unit, ElementKind> {
|
val visitor = object : IrElementVisitor<Unit, ElementKind> {
|
||||||
|
|
||||||
override fun visitElement(element: IrElement, data: ElementKind) {
|
override fun visitElement(element: IrElement, data: ElementKind) {
|
||||||
@@ -73,18 +74,39 @@ fun collectTailRecursionCalls(irFunction: IrFunction): Set<IrCall> {
|
|||||||
expression.value.accept(this, valueKind)
|
expression.value.accept(this, valueKind)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitContainerExpression(expression: IrContainerExpression, data: ElementKind) {
|
override fun visitExpressionBody(body: IrExpressionBody, data: ElementKind) =
|
||||||
|
body.acceptChildren(this, data)
|
||||||
|
|
||||||
|
override fun visitBlockBody(body: IrBlockBody, data: ElementKind) =
|
||||||
|
visitStatementContainer(body, data)
|
||||||
|
|
||||||
|
override fun visitContainerExpression(expression: IrContainerExpression, data: ElementKind) =
|
||||||
|
visitStatementContainer(expression, data)
|
||||||
|
|
||||||
|
private fun visitStatementContainer(expression: IrStatementContainer, data: ElementKind) {
|
||||||
expression.statements.forEachIndexed { index, irStatement ->
|
expression.statements.forEachIndexed { index, irStatement ->
|
||||||
val statementKind = if (index == expression.statements.lastIndex) {
|
val statementKind = when {
|
||||||
// The last statement defines the result of the container expression, so it has the same kind.
|
// The last statement defines the result of the container expression, so it has the same kind.
|
||||||
data
|
index == expression.statements.lastIndex -> data
|
||||||
} else {
|
// In a Unit-returning function, any statement directly followed by a `return` is a tail statement.
|
||||||
ElementKind.NOT_SURE
|
isUnitReturn && expression.statements[index + 1].let {
|
||||||
|
it is IrReturn && it.returnTargetSymbol == irFunction.symbol && it.value.isUnitRead()
|
||||||
|
} -> ElementKind.TAIL_STATEMENT
|
||||||
|
else -> ElementKind.NOT_SURE
|
||||||
}
|
}
|
||||||
irStatement.accept(this, statementKind)
|
irStatement.accept(this, statementKind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun IrExpression.isUnitRead(): Boolean =
|
||||||
|
when (this) {
|
||||||
|
is IrGetObjectValue -> symbol
|
||||||
|
// On the JVM, if SingletonReferencesLowering has already finished, a `Unit` reference
|
||||||
|
// is now an IrGetField to the INSTANCE field.
|
||||||
|
is IrGetField -> symbol.owner.parentClassOrNull?.symbol
|
||||||
|
else -> null
|
||||||
|
}?.signature == IdSignatureValues.unit
|
||||||
|
|
||||||
override fun visitWhen(expression: IrWhen, data: ElementKind) {
|
override fun visitWhen(expression: IrWhen, data: ElementKind) {
|
||||||
expression.branches.forEach {
|
expression.branches.forEach {
|
||||||
it.condition.accept(this, ElementKind.NOT_SURE)
|
it.condition.accept(this, ElementKind.NOT_SURE)
|
||||||
@@ -131,25 +153,10 @@ fun collectTailRecursionCalls(irFunction: IrFunction): Set<IrCall> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.add(expression)
|
result.add(expression)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
val body = irFunction.body
|
|
||||||
if (body !is IrBlockBody) {
|
|
||||||
return emptySet() // TODO: should an assert be here instead?
|
|
||||||
}
|
|
||||||
|
|
||||||
body.statements.forEachIndexed { index, irStatement ->
|
|
||||||
val kind = if (index == body.statements.lastIndex && irFunction.returnType.isUnit()) {
|
|
||||||
ElementKind.TAIL_STATEMENT
|
|
||||||
} else {
|
|
||||||
ElementKind.NOT_SURE
|
|
||||||
}
|
|
||||||
irStatement.accept(visitor, kind)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irFunction.body?.accept(visitor, ElementKind.TAIL_STATEMENT)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-8
@@ -52,18 +52,25 @@ open class TailrecLowering(val context: BackendContext) : BodyLoweringPass {
|
|||||||
|
|
||||||
override fun visitFunction(declaration: IrFunction) {
|
override fun visitFunction(declaration: IrFunction) {
|
||||||
declaration.acceptChildrenVoid(this)
|
declaration.acceptChildrenVoid(this)
|
||||||
lowerTailRecursionCalls(context, declaration, useProperComputationOrderOfTailrecDefaultParameters())
|
lowerTailRecursionCalls(declaration)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
lowerTailRecursionCalls(context, container, useProperComputationOrderOfTailrecDefaultParameters())
|
lowerTailRecursionCalls(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun lowerTailRecursionCalls(function: IrFunction) =
|
||||||
|
lowerTailRecursionCalls(context, function, useProperComputationOrderOfTailrecDefaultParameters())
|
||||||
|
|
||||||
open fun useProperComputationOrderOfTailrecDefaultParameters() = true
|
open fun useProperComputationOrderOfTailrecDefaultParameters() = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun lowerTailRecursionCalls(context: BackendContext, irFunction: IrFunction, properComputationOrderOfTailrecDefaultParameters: Boolean) {
|
private fun lowerTailRecursionCalls(
|
||||||
|
context: BackendContext,
|
||||||
|
irFunction: IrFunction,
|
||||||
|
properComputationOrderOfTailrecDefaultParameters: Boolean
|
||||||
|
) {
|
||||||
val tailRecursionCalls = collectTailRecursionCalls(irFunction)
|
val tailRecursionCalls = collectTailRecursionCalls(irFunction)
|
||||||
if (tailRecursionCalls.isEmpty()) {
|
if (tailRecursionCalls.isEmpty()) {
|
||||||
return
|
return
|
||||||
@@ -78,8 +85,8 @@ private fun lowerTailRecursionCalls(context: BackendContext, irFunction: IrFunct
|
|||||||
oldBody.statements.clear()
|
oldBody.statements.clear()
|
||||||
oldBody.statements += builder.irBlockBody {
|
oldBody.statements += builder.irBlockBody {
|
||||||
// Define variables containing current values of parameters:
|
// Define variables containing current values of parameters:
|
||||||
val parameterToVariable = parameters.associate {
|
val parameterToVariable = parameters.associateWith {
|
||||||
it to createTmpVariable(irGet(it), nameHint = it.symbol.suggestVariableName(), isMutable = true)
|
createTmpVariable(irGet(it), nameHint = it.symbol.suggestVariableName(), isMutable = true)
|
||||||
}
|
}
|
||||||
// (these variables are to be updated on any tail call).
|
// (these variables are to be updated on any tail call).
|
||||||
|
|
||||||
@@ -89,9 +96,8 @@ private fun lowerTailRecursionCalls(context: BackendContext, irFunction: IrFunct
|
|||||||
|
|
||||||
body = irBlock(startOffset, endOffset, resultType = context.irBuiltIns.unitType) {
|
body = irBlock(startOffset, endOffset, resultType = context.irBuiltIns.unitType) {
|
||||||
// Read variables containing current values of parameters:
|
// Read variables containing current values of parameters:
|
||||||
val parameterToNew = parameters.associate {
|
val parameterToNew = parameters.associateWith {
|
||||||
val variable = parameterToVariable[it]!!
|
createTmpVariable(irGet(parameterToVariable[it]!!), nameHint = it.symbol.suggestVariableName())
|
||||||
it to createTmpVariable(irGet(variable), nameHint = it.symbol.suggestVariableName())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val transformer = BodyTransformer(
|
val transformer = BodyTransformer(
|
||||||
|
|||||||
+5
-5
@@ -7,14 +7,14 @@
|
|||||||
// IGNORE_BACKEND: JS
|
// IGNORE_BACKEND: JS
|
||||||
|
|
||||||
tailrec fun test(x : Int) : Unit {
|
tailrec fun test(x : Int) : Unit {
|
||||||
if (x == 1) {
|
if (x > 800000) {
|
||||||
test(x - 1)
|
test(x - 1)
|
||||||
} else if (x == 2) {
|
} else if (x > 600000) {
|
||||||
test(x - 1)
|
test(x - 1)
|
||||||
return
|
return
|
||||||
} else if (x == 3) {
|
} else if (x > 400000) {
|
||||||
<!NON_TAIL_RECURSIVE_CALL!>test<!>(x - 1)
|
<!NON_TAIL_RECURSIVE_CALL!>test<!>(1)
|
||||||
if (x == 3) {
|
if (x > 200000) {
|
||||||
test(x - 1)
|
test(x - 1)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user