IR: optimize f(); return in tailrec fun f(): Unit

It's equivalent to `return f()`.
This commit is contained in:
pyos
2021-09-06 11:25:14 +02:00
committed by Mikhael Bogdanov
parent 83364d78f5
commit b2315a4a05
3 changed files with 50 additions and 37 deletions
@@ -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
} }
@@ -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(
@@ -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