From 980414674a10c73d3db6b832736ac9d724b4c980 Mon Sep 17 00:00:00 2001 From: Mikhael Bogdanov Date: Wed, 10 May 2017 13:02:58 +0200 Subject: [PATCH] Add default lambda parsing --- .../kotlin/codegen/inline/InlineCodegen.java | 7 +- .../kotlin/codegen/inline/LambdaInfo.kt | 9 +- .../codegen/inline/MethodInlinerUtil.kt | 76 ++------ .../codegen/inline/defaultMethodUtil.kt | 163 ++++++++++++++++++ 4 files changed, 188 insertions(+), 67 deletions(-) create mode 100644 compiler/backend/src/org/jetbrains/kotlin/codegen/inline/defaultMethodUtil.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index 07574828894..8d688fe96dc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -87,7 +87,7 @@ public class InlineCodegen extends CallGenerator { private final boolean isSameModule; private final ParametersBuilder invocationParamBuilder = ParametersBuilder.newBuilder(); - private final Map expressionMap = new HashMap<>(); + private final Map expressionMap = new LinkedHashMap<>(); private final ReifiedTypeInliner reifiedTypeInliner; @@ -413,7 +413,10 @@ public class InlineCodegen extends CallGenerator { defaultSourceMapper.setCallSiteMarker(new CallSiteMarker(codegen.getLastLineNumber())); MethodNode node = nodeAndSmap.getNode(); if (callDefault) { - MethodInlinerUtilKt.expandMaskConditionsAndUpdateVariableNodes(node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex); + List defaultLambdas = DefaultMethodUtilKt.expandMaskConditionsAndUpdateVariableNodes( + node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex, + DefaultMethodUtilKt.extractDefaultLambdaOffsetAndDescriptor(jvmSignature, functionDescriptor) + ); } ReifiedTypeParametersUsages reificationResult = reifiedTypeInliner.reifyInstructions(node); generateClosuresBodies(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt index 8b8f9d6e436..c8ec89ff5d2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.Method +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode import org.jetbrains.org.objectweb.asm.tree.MethodNode @@ -71,7 +72,13 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean, @JvmField val is } -class DefaultLambda(override val lambdaClassType: Type, lambdaArgs: Array) : LambdaInfo(false, false) { +class DefaultLambda( + override val lambdaClassType: Type, + val capturedArgs: Array, + val parameterDescriptor: ValueParameterDescriptor, + val initInstuctions: List, + val offset: Int +) : LambdaInfo(false, false) { override val invokeMethod: Method get() = TODO("not implemented") diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInlinerUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInlinerUtil.kt index 93632d0abd9..385fcf47cd8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInlinerUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInlinerUtil.kt @@ -16,12 +16,13 @@ package org.jetbrains.kotlin.codegen.inline -import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence -import org.jetbrains.kotlin.codegen.optimization.common.asSequence import org.jetbrains.kotlin.codegen.optimization.fixStack.top +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature import org.jetbrains.kotlin.utils.SmartSet import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode +import org.jetbrains.org.objectweb.asm.tree.InsnList +import org.jetbrains.org.objectweb.asm.tree.VarInsnNode import org.jetbrains.org.objectweb.asm.tree.analysis.Frame import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue @@ -89,67 +90,14 @@ private fun MethodInliner.getLambdaIfExistsAndMarkInstructions( fun SourceValue.singleOrNullInsn() = insns.singleOrNull() -fun expandMaskConditionsAndUpdateVariableNodes(node: MethodNode, maskStartIndex: Int, masks: List, methodHandlerIndex: Int) { - class Condition(mask: Int, constant: Int, val maskInstruction: VarInsnNode, val jumpInstruction: JumpInsnNode, val varIndex: Int) { - val expandNotDelete = mask and constant != 0 - } - fun isMaskIndex(varIndex: Int): Boolean { - return maskStartIndex <= varIndex && varIndex < maskStartIndex + masks.size - } - - val maskProcessingHeader = node.instructions.asSequence().takeWhile { - if (it is VarInsnNode) { - if (isMaskIndex(it.`var`)) { - /*if slot for default mask is updated than we occurred in actual function body*/ - return@takeWhile it.opcode == Opcodes.ILOAD - } - else if (isMethodHandleIndex(methodHandlerIndex, it)) { - return@takeWhile it.opcode == Opcodes.ALOAD - } +fun parameterOffsets(valueParameters: List): Array { + var offset = 0 + return Array(valueParameters.size) { index -> + if (index == 0) { + 0 } - true - } - - val conditions = maskProcessingHeader.filterIsInstance().mapNotNull { - if (isMaskIndex(it.`var`) && - it.next?.next?.opcode == Opcodes.IAND && - it.next.next.next?.opcode == Opcodes.IFEQ) { - val jumpInstruction = it.next?.next?.next as JumpInsnNode - Condition( - masks[it.`var` - maskStartIndex], - InlineCodegenUtil.getConstant(it.next), - it, - jumpInstruction, - (jumpInstruction.label.previous as VarInsnNode).`var` - ) - } - else if (isMethodHandleIndex(methodHandlerIndex, it) && - it.next?.opcode == Opcodes.IFNULL && - it.next.next?.opcode == Opcodes.NEW) { - //Always delete method handle for now - //This logic should be updated when method handles would be supported - Condition(0, 0, it,it.next as JumpInsnNode, -1) - } - else null - } - - val indexToVarNode = node.localVariables?.filter { it.index < maskStartIndex }?.associateBy { it.index } ?: emptyMap() - val toDelete = arrayListOf() - conditions.forEach { - val jumpInstruction = it.jumpInstruction - InsnSequence(it.maskInstruction, (if (it.expandNotDelete) jumpInstruction.next else jumpInstruction.label)).forEach { - toDelete.add(it) - } - if (it.expandNotDelete) { - indexToVarNode[it.varIndex]?.let { varNode -> - varNode.start = it.jumpInstruction.label - } + else offset.apply { + offset += valueParameters[index - 1].asmType.size } } - - toDelete.forEach { - node.instructions.remove(it) - } -} - -private fun isMethodHandleIndex(methodHandlerIndex: Int, it: VarInsnNode) = methodHandlerIndex == it.`var` \ No newline at end of file +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/defaultMethodUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/defaultMethodUtil.kt new file mode 100644 index 00000000000..0758272b5bb --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/defaultMethodUtil.kt @@ -0,0 +1,163 @@ +/* + * 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.inline + +import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence +import org.jetbrains.kotlin.codegen.optimization.common.asSequence +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor +import org.jetbrains.kotlin.resolve.inline.InlineUtil +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.tree.* + +private data class Condition( + val mask: Int, val constant: Int, + val maskInstruction: VarInsnNode, + val jumpInstruction: JumpInsnNode, + val varInsNode: VarInsnNode? +) { + val expandNotDelete = mask and constant != 0 + val varIndex = varInsNode?.`var` ?: 0 +} + +fun extractDefaultLambdaOffsetAndDescriptor(jvmSignature: JvmMethodSignature, functionDescriptor: FunctionDescriptor): Map { + val valueParameters = jvmSignature.valueParameters + val parameterOffsets = parameterOffsets(valueParameters) + val valueParameterOffset = valueParameters.takeWhile { it.kind != JvmMethodParameterKind.VALUE }.size + + return functionDescriptor.valueParameters.filter { + InlineUtil.isInlineLambdaParameter(it) && it.declaresDefaultValue() + }.associateBy { + parameterOffsets[valueParameterOffset + it.index] + } +} + + +fun expandMaskConditionsAndUpdateVariableNodes( + node: MethodNode, + maskStartIndex: Int, + masks: List, + methodHandlerIndex: Int, + defaultLambdas: Map +): List { + fun isMaskIndex(varIndex: Int): Boolean { + return maskStartIndex <= varIndex && varIndex < maskStartIndex + masks.size + } + + val maskProcessingHeader = node.instructions.asSequence().takeWhile { + if (it is VarInsnNode) { + if (isMaskIndex(it.`var`)) { + /*if slot for default mask is updated than we occurred in actual function body*/ + return@takeWhile it.opcode == Opcodes.ILOAD + } + else if (methodHandlerIndex == it.`var`) { + return@takeWhile it.opcode == Opcodes.ALOAD + } + } + true + } + + val conditions = maskProcessingHeader.filterIsInstance().mapNotNull { + if (isMaskIndex(it.`var`) && + it.next?.next?.opcode == Opcodes.IAND && + it.next.next.next?.opcode == Opcodes.IFEQ) { + val jumpInstruction = it.next?.next?.next as JumpInsnNode + Condition( + masks[it.`var` - maskStartIndex], + InlineCodegenUtil.getConstant(it.next), + it, + jumpInstruction, + jumpInstruction.label.previous as VarInsnNode + ) + } + else if (methodHandlerIndex == it.`var` && + it.next?.opcode == Opcodes.IFNULL && + it.next.next?.opcode == Opcodes.NEW) { + //Always delete method handle for now + //This logic should be updated when method handles would be supported + Condition(0, 0, it, it.next as JumpInsnNode, null) + } + else null + }.toList() + + val defaultLambdasInfo = extractDefaultLambdasInfo(conditions, defaultLambdas) + + val indexToVarNode = node.localVariables?.filter { it.index < maskStartIndex }?.associateBy { it.index } ?: emptyMap() + val toDelete = arrayListOf() + conditions.forEach { + val jumpInstruction = it.jumpInstruction + InsnSequence(it.maskInstruction, (if (it.expandNotDelete) jumpInstruction.next else jumpInstruction.label)).forEach { + toDelete.add(it) + } + if (it.expandNotDelete) { + indexToVarNode[it.varIndex]?.let { varNode -> + varNode.start = it.jumpInstruction.label + } + } + } + + toDelete.forEach { + node.instructions.remove(it) + } + + return defaultLambdasInfo +} + + +private fun extractDefaultLambdasInfo(conditions: List, defaultLambdas: Map): List { + val defaultLambdaConditions = conditions.filter { it.expandNotDelete && defaultLambdas.contains(it.varIndex) } + + return defaultLambdaConditions.map { + val varAssignmentInstruction = it.varInsNode!! + var instanceInstuction = varAssignmentInstruction.previous + if (instanceInstuction is TypeInsnNode && instanceInstuction.opcode == Opcodes.CHECKCAST) { + instanceInstuction = instanceInstuction.previous + } + when (instanceInstuction) { + is MethodInsnNode -> { + assert(instanceInstuction.name == "") { "Expected constructor call for default lambda, but $instanceInstuction" } + val ownerInternalName = instanceInstuction.owner + val instanceCreation = InsnSequence(it.jumpInstruction, it.jumpInstruction.label).filter { + it.opcode == Opcodes.NEW && (it as TypeInsnNode).desc == ownerInternalName + }.single() + assert(instanceCreation.next?.opcode == Opcodes.DUP) { + "Dup should follow default lambda instanceInstuction creation but ${instanceCreation.next}" + } + + DefaultLambda( + Type.getObjectType(instanceInstuction.owner), + Type.getArgumentTypes(instanceInstuction.desc), + defaultLambdas[it.varIndex]!!, + listOf(instanceCreation, instanceCreation.next) + InsnSequence(instanceInstuction, varAssignmentInstruction).toList(), + it.varIndex + ) + } + + is FieldInsnNode -> DefaultLambda( + Type.getObjectType(instanceInstuction.owner), + emptyArray(), + defaultLambdas[it.varIndex]!!, + InsnSequence(instanceInstuction, varAssignmentInstruction).toList(), + it.varIndex + ) + else -> throw RuntimeException("Can't extract default lambda info $it.\n Unknown instruction: $instanceInstuction") + } + } +} \ No newline at end of file