Replace POP with ARETURN if it pops Unit and ARETURN shall return Unit

#KT-16880: Fixed
This commit is contained in:
Ilmir Usmanov
2017-11-10 15:46:11 +03:00
parent 2cdc246a27
commit c8904b1c7c
7 changed files with 354 additions and 2 deletions
@@ -2324,6 +2324,8 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this);
if (isSuspendCall) {
addReturnsUnitMarkerIfNecessary(v, resolvedCall);
addSuspendMarker(v, false);
addInlineMarker(v, false);
}
@@ -77,6 +77,8 @@ class CoroutineTransformerMethodVisitor(
FixStackMethodTransformer().transform(containingClassInternalName, methodNode)
if (isForNamedFunction) {
ReturnUnitMethodTransformer.transform(containingClassInternalName, methodNode)
if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) {
dropSuspensionMarkers(methodNode, suspensionPoints)
return
@@ -701,7 +703,7 @@ private fun allSuspensionPointsAreTailCalls(
}
}
private class IgnoringCopyOperationSourceInterpreter : SourceInterpreter() {
internal class IgnoringCopyOperationSourceInterpreter : SourceInterpreter() {
override fun copyOperation(insn: AbstractInsnNode?, value: SourceValue?) = value
}
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2017 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.codegen.coroutines
import org.jetbrains.kotlin.backend.common.pop
import org.jetbrains.kotlin.codegen.inline.isReturnsUnitMarker
import org.jetbrains.kotlin.codegen.optimization.boxing.isUnitInstance
import org.jetbrains.kotlin.codegen.optimization.common.ControlFlowGraph
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful
import org.jetbrains.kotlin.codegen.optimization.common.removeAll
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.keysToMap
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.tree.*
/*
* Replace POP with ARETURN iff
* 1) It is immediately followed by { GETSTATIC Unit.INSTANCE, ARETURN } sequences
* 2) It is poping Unit
*/
object ReturnUnitMethodTransformer : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
val unitMarks = findReturnsUnitMarks(methodNode)
if (unitMarks.isEmpty()) return
val units = findReturnUnitSequences(methodNode)
if (units.isEmpty()) {
cleanUpReturnsUnitMarkers(methodNode, unitMarks)
return
}
val pops = methodNode.instructions.asSequence().filter { it.opcode == Opcodes.POP }.toList()
val popSuccessors = findSuccessors(methodNode, pops)
val sourceInsns = findSourceInstructions(internalClassName, methodNode, pops)
val safePops = filterOutUnsafes(popSuccessors, units, sourceInsns)
// Replace POP with ARETURN for tail call optimization
safePops.forEach { methodNode.instructions.set(it, InsnNode(Opcodes.ARETURN)) }
cleanUpReturnsUnitMarkers(methodNode, unitMarks)
}
// Return list of POPs, which can be safely replaced by ARETURNs
private fun filterOutUnsafes(
popSuccessors: Map<AbstractInsnNode, Collection<AbstractInsnNode>>,
units: Collection<AbstractInsnNode>,
sourceInsns: Map<AbstractInsnNode, Collection<AbstractInsnNode>>
): Collection<AbstractInsnNode> {
return popSuccessors.filter { (pop, successors) ->
successors.all { it in units } &&
sourceInsns[pop].sure { "Sources of $pop cannot be null" }.all(::isSuspendingCallReturningUnit)
}.keys
}
// Find instructions which do something on stack, ignoring markers
// Return map {insn => list of found instructions}
private fun findSuccessors(
methodNode: MethodNode,
insns: List<AbstractInsnNode>
): Map<AbstractInsnNode, Collection<AbstractInsnNode>> {
val cfg = ControlFlowGraph.build(methodNode)
return insns.keysToMap { findSuccessors(cfg, it, methodNode) }
}
// Find all meaningful successors of [insn]
private fun findSuccessors(cfg: ControlFlowGraph, insn: AbstractInsnNode, methodNode: MethodNode): Collection<AbstractInsnNode> {
val stack = cfg.getSuccessorsIndices(insn).mapTo(ArrayList()) { methodNode.instructions[it] }
val successors = arrayListOf<AbstractInsnNode>()
while (stack.isNotEmpty()) {
val current = stack.pop()
if (current in successors || isReturnsUnitMarker(current)) continue
if (!current.isMeaningful || current is JumpInsnNode || current.opcode == Opcodes.NOP) {
cfg.getSuccessorsIndices(current).mapTo(stack) { methodNode.instructions[it] }
continue
}
// There can be multiple chains of { UnitInstance, POP } after inlining. Ignore them
if (current.isUnitInstance()) {
val newSuccessors = findSuccessors(cfg, current, methodNode)
if (newSuccessors.all { it.opcode == Opcodes.POP }) {
newSuccessors.flatMapTo(stack) { findSuccessors(cfg, it, methodNode) }
continue
}
}
successors.add(current)
}
return successors
}
private fun isSuspendingCallReturningUnit(node: AbstractInsnNode): Boolean =
node.safeAs<MethodInsnNode>()?.next?.next?.let(::isReturnsUnitMarker) == true
private fun findSourceInstructions(
internalClassName: String,
methodNode: MethodNode,
pops: Collection<AbstractInsnNode>
): Map<AbstractInsnNode, Collection<AbstractInsnNode>> {
val frames = analyze(internalClassName, methodNode, IgnoringCopyOperationSourceInterpreter())
return pops.keysToMap {
frames[methodNode.instructions.indexOf(it)].getStack(0).insns
}
}
// Find { GETSTATIC kotlin/Unit.INSTANCE, ARETURN } sequences
// Result is list of GETSTATIC kotlin/Unit.INSTANCE instructions
private fun findReturnUnitSequences(methodNode: MethodNode): Collection<AbstractInsnNode> =
methodNode.instructions.asSequence().filter { it.isUnitInstance() && it.next?.opcode == Opcodes.ARETURN }.toList()
private fun findReturnsUnitMarks(methodNode: MethodNode): Collection<AbstractInsnNode> =
methodNode.instructions.asSequence().filter(::isReturnsUnitMarker).toList()
private fun cleanUpReturnsUnitMarkers(methodNode: MethodNode, unitMarks: Collection<AbstractInsnNode>) {
unitMarks.forEach { methodNode.instructions.removeAll(listOf(it.previous, it)) }
}
}
@@ -17,6 +17,7 @@
package org.jetbrains.kotlin.codegen.inline
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.backend.common.descriptors.substitute
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.BaseExpressionCodegen
@@ -28,6 +29,7 @@ import org.jetbrains.kotlin.codegen.context.CodegenContext
import org.jetbrains.kotlin.codegen.context.CodegenContextUtil
import org.jetbrains.kotlin.codegen.context.InlineLambdaContext
import org.jetbrains.kotlin.codegen.context.MethodContext
import org.jetbrains.kotlin.codegen.coroutines.unwrapInitialDescriptorForSuspendFunction
import org.jetbrains.kotlin.codegen.intrinsics.classId
import org.jetbrains.kotlin.codegen.optimization.common.intConstant
import org.jetbrains.kotlin.codegen.state.GenerationState
@@ -43,14 +45,16 @@ import org.jetbrains.kotlin.load.kotlin.incremental.components.IncrementalCache
import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.ENUM_TYPE
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_CLASS_TYPE
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.tree.*
@@ -87,6 +91,7 @@ private const val INLINE_MARKER_FINALLY_START = "finallyStart"
private const val INLINE_MARKER_FINALLY_END = "finallyEnd"
private const val INLINE_MARKER_BEFORE_SUSPEND_ID = 0
private const val INLINE_MARKER_AFTER_SUSPEND_ID = 1
private const val INLINE_MARKET_RETURNS_UNIT = 2
private val INTRINSIC_ARRAY_CONSTRUCTOR_TYPE = AsmUtil.asmTypeByClassId(classId)
internal fun getMethodNode(
@@ -386,6 +391,26 @@ internal fun addInlineMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
)
}
internal fun addReturnsUnitMarkerIfNecessary(v: InstructionAdapter, resolvedCall: ResolvedCall<*>) {
val wrapperDescriptor = resolvedCall.candidateDescriptor.safeAs<FunctionDescriptor>() ?: return
val unsubstitutedDescriptor = wrapperDescriptor.unwrapInitialDescriptorForSuspendFunction()
val typeSubstitutor = TypeSubstitutor.create(
unsubstitutedDescriptor.typeParameters
.withIndex()
.associateBy({ it.value.typeConstructor }) {
TypeProjectionImpl(resolvedCall.typeArguments[wrapperDescriptor.typeParameters[it.index]] ?: return)
}
)
val substitutedDescriptor = unsubstitutedDescriptor.substitute(typeSubstitutor) ?: return
val returnType = substitutedDescriptor.returnType ?: return
if (KotlinBuiltIns.isUnit(returnType)) {
addReturnsUnitMarker(v)
}
}
internal fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
v.iconst(if (isStartNotEnd) INLINE_MARKER_BEFORE_SUSPEND_ID else INLINE_MARKER_AFTER_SUSPEND_ID)
v.visitMethodInsn(
@@ -395,8 +420,18 @@ internal fun addSuspendMarker(v: InstructionAdapter, isStartNotEnd: Boolean) {
)
}
private fun addReturnsUnitMarker(v: InstructionAdapter) {
v.iconst(INLINE_MARKET_RETURNS_UNIT)
v.visitMethodInsn(
Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
"mark",
"(I)V", false
)
}
internal fun isBeforeSuspendMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_BEFORE_SUSPEND_ID)
internal fun isAfterSuspendMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKER_AFTER_SUSPEND_ID)
internal fun isReturnsUnitMarker(insn: AbstractInsnNode) = isSuspendMarker(insn, INLINE_MARKET_RETURNS_UNIT)
private fun isSuspendMarker(insn: AbstractInsnNode, id: Int) =
isInlineMarker(insn, "mark") && insn.previous.intConstant == id
@@ -0,0 +1,61 @@
suspend fun empty() {}
suspend fun withoutReturn() { empty() }
suspend fun withReturn() { return empty() }
suspend fun notTailCall() { empty(); empty() }
suspend fun lambdaAsParameter(c: suspend ()->Unit) { c() }
suspend fun lambdaAsParameterNotTailCall(c: suspend ()->Unit) { c(); c() }
suspend fun lambdaAsParameterReturn(c: suspend ()->Unit) { return c() }
suspend fun returnsInt() = 42
// This should not be tail-call, since the caller should push Unit.INSTANCE on stack
suspend fun callsIntNotTailCall() { returnsInt() }
suspend fun multipleExitPoints(b: Boolean) { if (b) empty() else withoutReturn() }
suspend fun multipleExitPointsNotTailCall(b: Boolean) { if (b) empty() else returnsInt() }
fun ordinary() = 1
inline fun ordinaryInline() { ordinary() }
suspend fun multipleExitPointsWithOrdinaryInline(b: Boolean) { if (b) empty() else ordinaryInline() }
suspend fun multipleExitPointsWhen(i: Int) {
when(i) {
1 -> empty()
2 -> withReturn()
3 -> withoutReturn()
else -> lambdaAsParameter {}
}
}
suspend fun <T> generic(): T = TODO()
suspend fun useGenericReturningUnit() {
generic<Unit>()
}
class Generic<T> {
suspend fun foo(): T = TODO()
}
suspend fun useGenericClass(g: Generic<Unit>) {
g.foo()
}
suspend fun <T> genericInferType(c: () -> T): T = TODO()
suspend fun useGenericIngerType() {
genericInferType {}
}
suspend fun nullableUnit(): Unit? = null
suspend fun useNullableUnit() {
nullableUnit()
}
suspend fun useRunRunRunRunRun() {
run {
run {
run {
run {
run {
empty()
}
}
}
}
}
}
@@ -0,0 +1,115 @@
@kotlin.Metadata
public final class Generic {
public method <init>(): void
public final @org.jetbrains.annotations.Nullable method foo(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$callsIntNotTailCall$1 {
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
inner class TailSuspendUnitFunKt$callsIntNotTailCall$1
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$lambdaAsParameterNotTailCall$1 {
field L$0: java.lang.Object
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
inner class TailSuspendUnitFunKt$lambdaAsParameterNotTailCall$1
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$multipleExitPointsNotTailCall$1 {
field Z$0: boolean
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
inner class TailSuspendUnitFunKt$multipleExitPointsNotTailCall$1
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$multipleExitPointsWhen$2 {
inner class TailSuspendUnitFunKt$multipleExitPointsWhen$2
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method create(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$notTailCall$1 {
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
inner class TailSuspendUnitFunKt$notTailCall$1
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$useGenericIngerType$2 {
public final static field INSTANCE: TailSuspendUnitFunKt$useGenericIngerType$2
inner class TailSuspendUnitFunKt$useGenericIngerType$2
static method <clinit>(): void
method <init>(): void
public synthetic method invoke(): java.lang.Object
public final method invoke(): void
}
@kotlin.Metadata
final class TailSuspendUnitFunKt$useNullableUnit$1 {
synthetic field data: java.lang.Object
synthetic field exception: java.lang.Throwable
inner class TailSuspendUnitFunKt$useNullableUnit$1
method <init>(p0: kotlin.coroutines.experimental.Continuation): void
public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object
synthetic final method getLabel(): int
synthetic final method setLabel(p0: int): void
}
@kotlin.Metadata
public final class TailSuspendUnitFunKt {
inner class TailSuspendUnitFunKt$callsIntNotTailCall$1
inner class TailSuspendUnitFunKt$lambdaAsParameterNotTailCall$1
inner class TailSuspendUnitFunKt$multipleExitPointsNotTailCall$1
inner class TailSuspendUnitFunKt$multipleExitPointsWhen$2
inner class TailSuspendUnitFunKt$notTailCall$1
inner class TailSuspendUnitFunKt$useGenericIngerType$2
inner class TailSuspendUnitFunKt$useNullableUnit$1
public final static @org.jetbrains.annotations.Nullable method callsIntNotTailCall(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method empty(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method generic(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method genericInferType(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function0, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method lambdaAsParameter(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method lambdaAsParameterNotTailCall(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method lambdaAsParameterReturn(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method multipleExitPoints(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method multipleExitPointsNotTailCall(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method multipleExitPointsWhen(p0: int, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method multipleExitPointsWithOrdinaryInline(p0: boolean, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method notTailCall(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method nullableUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static method ordinary(): int
public final static method ordinaryInline(): void
public final static @org.jetbrains.annotations.Nullable method returnsInt(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method useGenericClass(@org.jetbrains.annotations.NotNull p0: Generic, @org.jetbrains.annotations.Nullable p1: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method useGenericIngerType(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method useGenericReturningUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method useNullableUnit(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method useRunRunRunRunRun(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method withReturn(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method withoutReturn(@org.jetbrains.annotations.Nullable p0: java.lang.Object): java.lang.Object
}
@@ -162,6 +162,12 @@ public class BytecodeListingTestGenerated extends AbstractBytecodeListingTest {
doTest(fileName);
}
@TestMetadata("tailSuspendUnitFun.kt")
public void testTailSuspendUnitFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeListing/tailSuspendUnitFun.kt");
doTest(fileName);
}
@TestMetadata("compiler/testData/codegen/bytecodeListing/annotations")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)