Add proper DefaultLambda initialization,

patch lambda related instructions in default method

Remove default lambda linked instructions from default method
This commit is contained in:
Mikhael Bogdanov
2017-05-10 13:14:15 +02:00
parent ec066a06d8
commit daf6768181
7 changed files with 125 additions and 56 deletions
@@ -316,7 +316,7 @@ public class InlineCodegen extends CallGenerator {
ClassId classId = IntrinsicArrayConstructorsKt.getClassId(); ClassId classId = IntrinsicArrayConstructorsKt.getClassId();
byte[] bytes = byte[] bytes =
InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), classId, IntrinsicArrayConstructorsKt::getBytecode); InlineCacheKt.getOrPut(state.getInlineCache().getClassBytes(), classId, IntrinsicArrayConstructorsKt::getBytecode);
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), classId); return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), classId.asString());
} }
assert callableDescriptor instanceof DeserializedCallableMemberDescriptor : "Not a deserialized function or proper: " + callableDescriptor; assert callableDescriptor instanceof DeserializedCallableMemberDescriptor : "Not a deserialized function or proper: " + callableDescriptor;
@@ -339,7 +339,7 @@ public class InlineCodegen extends CallGenerator {
} }
}); });
return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId); return InlineCodegenUtil.getMethodNode(bytes, asmMethod.getName(), asmMethod.getDescriptor(), containerId.asString());
} }
@NotNull @NotNull
@@ -88,12 +88,14 @@ public class InlineCodegenUtil {
public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun"; public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun";
public static final String INLINE_FUN_VAR_SUFFIX = "$iv"; public static final String INLINE_FUN_VAR_SUFFIX = "$iv";
public static final String DEFAULT_LAMBDA_FAKE_CALL = "$$$DEFAULT_LAMBDA_FAKE_CALL$$$";
@Nullable @Nullable
public static SMAPAndMethodNode getMethodNode( public static SMAPAndMethodNode getMethodNode(
byte[] classData, byte[] classData,
String methodName, @NotNull String methodName,
String methodDescriptor, @NotNull String methodDescriptor,
ClassId classId @NotNull String classInternalName
) { ) {
ClassReader cr = new ClassReader(classData); ClassReader cr = new ClassReader(classData);
MethodNode[] node = new MethodNode[1]; MethodNode[] node = new MethodNode[1];
@@ -138,12 +140,12 @@ public class InlineCodegenUtil {
return null; return null;
} }
if (classId.equals(IntrinsicArrayConstructorsKt.getClassId())) { if (IntrinsicArrayConstructorsKt.getClassId().asString().equals(classInternalName)) {
// Don't load source map for intrinsic array constructors // Don't load source map for intrinsic array constructors
debugInfo[0] = null; debugInfo[0] = null;
} }
SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.asString(), lines[0], lines[1]); SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classInternalName, lines[0], lines[1]);
return new SMAPAndMethodNode(node[0], smap); return new SMAPAndMethodNode(node[0], smap);
} }
@@ -169,7 +171,7 @@ public class InlineCodegenUtil {
} }
@Nullable @Nullable
private static VirtualFile findVirtualFileImprecise(@NotNull GenerationState state, @NotNull String internalClassName) { public static VirtualFile findVirtualFileImprecise(@NotNull GenerationState state, @NotNull String internalClassName) {
FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName(); FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName); String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName);
//TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
@@ -444,7 +446,6 @@ public class InlineCodegenUtil {
public static int getConstant(@NotNull AbstractInsnNode ins) { public static int getConstant(@NotNull AbstractInsnNode ins) {
int opcode = ins.getOpcode(); int opcode = ins.getOpcode();
Integer value;
if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) { if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
return opcode - Opcodes.ICONST_0; return opcode - Opcodes.ICONST_0;
} }
@@ -25,15 +25,16 @@ import org.jetbrains.kotlin.codegen.binding.MutableClosure
import org.jetbrains.kotlin.codegen.context.EnclosedValueDescriptor import org.jetbrains.kotlin.codegen.context.EnclosedValueDescriptor
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method 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.FieldInsnNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode import org.jetbrains.org.objectweb.asm.tree.MethodNode
@@ -66,7 +67,11 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean, @JvmField val is
companion object { companion object {
fun LambdaInfo.getCapturedParamInfo(descriptor: EnclosedValueDescriptor): CapturedParamDesc { fun LambdaInfo.getCapturedParamInfo(descriptor: EnclosedValueDescriptor): CapturedParamDesc {
return CapturedParamDesc(lambdaClassType, descriptor.fieldName, descriptor.type) return capturedParamDesc(descriptor.fieldName, descriptor.type)
}
fun LambdaInfo.capturedParamDesc(fieldName: String, fieldType: Type): CapturedParamDesc {
return CapturedParamDesc(lambdaClassType, fieldName, fieldType)
} }
} }
} }
@@ -76,22 +81,52 @@ class DefaultLambda(
override val lambdaClassType: Type, override val lambdaClassType: Type,
val capturedArgs: Array<Type>, val capturedArgs: Array<Type>,
val parameterDescriptor: ValueParameterDescriptor, val parameterDescriptor: ValueParameterDescriptor,
val initInstuctions: List<AbstractInsnNode>,
val offset: Int val offset: Int
) : LambdaInfo(false, false) { ) : LambdaInfo(parameterDescriptor.isCrossinline, false) {
override val invokeMethod: Method
get() = TODO("not implemented")
override val invokeMethodDescriptor: FunctionDescriptor override lateinit var invokeMethod: Method
get() = TODO("not implemented") private set
override val capturedVars: List<CapturedParamDesc>
get() = TODO("not implemented") override val invokeMethodDescriptor: FunctionDescriptor =
parameterDescriptor.type.memberScope.getContributedFunctions(OperatorNameConventions.INVOKE, NoLookupLocation.FROM_BACKEND).single()
override lateinit var capturedVars: List<CapturedParamDesc>
private set
override fun isMyLabel(name: String): Boolean = false override fun isMyLabel(name: String): Boolean = false
override fun generateLambdaBody(codegen: ExpressionCodegen) { override fun generateLambdaBody(codegen: ExpressionCodegen) {
TODO("not implemented") val classReader = InlineCodegenUtil.buildClassReaderByInternalName(codegen.state, lambdaClassType.internalName)
val descriptor = Type.getMethodDescriptor(Type.VOID_TYPE, *capturedArgs)
val constructor = InlineCodegenUtil.getMethodNode(
classReader.b,
"<init>",
descriptor,
lambdaClassType.internalName)?.node
assert(constructor != null || capturedArgs.isEmpty()) {
"Can't find non-default constructor <init>$descriptor for default lambda $lambdaClassType"
}
capturedVars = constructor?.findCapturedFieldAssignmentInstructions()?.map {
fieldNode ->
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc))
}?.toList() ?: emptyList()
invokeMethod = Method(
OperatorNameConventions.INVOKE.asString(),
codegen.state.typeMapper.mapSignatureSkipGeneric(invokeMethodDescriptor).asmMethod.descriptor
)
node = InlineCodegenUtil.getMethodNode(
classReader.b,
invokeMethod.name,
invokeMethod.descriptor,
lambdaClassType.internalName)!!
} }
} }
@@ -189,8 +189,7 @@ class MethodInliner(
val valueParamShift = Math.max(nextLocalIndex, markerShift)//NB: don't inline cause it changes val valueParamShift = Math.max(nextLocalIndex, markerShift)//NB: don't inline cause it changes
putStackValuesIntoLocals(listOf(*info.invokeMethod.argumentTypes), valueParamShift, this, desc) putStackValuesIntoLocals(listOf(*info.invokeMethod.argumentTypes), valueParamShift, this, desc)
if (invokeCall.lambdaInfo.invokeMethodDescriptor.valueParameters.isEmpty()) {
if (invokeCall.lambdaInfo.invokeMethodDescriptor.getValueParameters().isEmpty()) {
// There won't be no parameters processing and line call can be left without actual instructions. // There won't be no parameters processing and line call can be left without actual instructions.
// Note: if function is called on the line with other instructions like 1 + foo(), 'nop' will still be generated. // Note: if function is called on the line with other instructions like 1 + foo(), 'nop' will still be generated.
visitInsn(Opcodes.NOP) visitInsn(Opcodes.NOP)
@@ -90,14 +90,11 @@ private fun MethodInliner.getLambdaIfExistsAndMarkInstructions(
return null return null
} }
fun parameterOffsets(valueParameters: List<JvmMethodParameterSignature>): Array<Int> { fun parameterOffsets(isStatic: Boolean, valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
var offset = 0 var nextOffset = if (isStatic) 0 else 1
return Array(valueParameters.size) { index -> return Array(valueParameters.size) { index ->
if (index == 0) { nextOffset.also {
0 nextOffset += valueParameters[index].asmType.size
}
else offset.apply {
offset += valueParameters[index - 1].asmType.size
} }
} }
} }
@@ -16,10 +16,17 @@
package org.jetbrains.kotlin.codegen.inline package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.backend.jvm.codegen.getMemberOwnerKind
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.DEFAULT_LAMBDA_FAKE_CALL
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil.getConstant
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.insnText
import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
@@ -39,7 +46,14 @@ private data class Condition(
fun extractDefaultLambdaOffsetAndDescriptor(jvmSignature: JvmMethodSignature, functionDescriptor: FunctionDescriptor): Map<Int, ValueParameterDescriptor> { fun extractDefaultLambdaOffsetAndDescriptor(jvmSignature: JvmMethodSignature, functionDescriptor: FunctionDescriptor): Map<Int, ValueParameterDescriptor> {
val valueParameters = jvmSignature.valueParameters val valueParameters = jvmSignature.valueParameters
val parameterOffsets = parameterOffsets(valueParameters) val containingDeclaration = functionDescriptor.containingDeclaration
val kind = containingDeclaration.getMemberOwnerKind().let {
if (DescriptorUtils.isInterface(containingDeclaration)) {
OwnerKind.DEFAULT_IMPLS
}
else it
}
val parameterOffsets = parameterOffsets(AsmUtil.isStaticMethod(kind, functionDescriptor), valueParameters)
val valueParameterOffset = valueParameters.takeWhile { it.kind != JvmMethodParameterKind.VALUE }.size val valueParameterOffset = valueParameters.takeWhile { it.kind != JvmMethodParameterKind.VALUE }.size
return functionDescriptor.valueParameters.filter { return functionDescriptor.valueParameters.filter {
@@ -81,7 +95,7 @@ fun expandMaskConditionsAndUpdateVariableNodes(
val jumpInstruction = it.next?.next?.next as JumpInsnNode val jumpInstruction = it.next?.next?.next as JumpInsnNode
Condition( Condition(
masks[it.`var` - maskStartIndex], masks[it.`var` - maskStartIndex],
InlineCodegenUtil.getConstant(it.next), getConstant(it.next),
it, it,
jumpInstruction, jumpInstruction,
jumpInstruction.label.previous as VarInsnNode jumpInstruction.label.previous as VarInsnNode
@@ -97,10 +111,12 @@ fun expandMaskConditionsAndUpdateVariableNodes(
else null else null
}.toList() }.toList()
val defaultLambdasInfo = extractDefaultLambdasInfo(conditions, defaultLambdas) val toDelete = arrayListOf<AbstractInsnNode>()
val toInsert = arrayListOf<Pair<AbstractInsnNode, AbstractInsnNode>>()
val defaultLambdasInfo = extractDefaultLambdasInfo(conditions, defaultLambdas, toDelete, toInsert)
val indexToVarNode = node.localVariables?.filter { it.index < maskStartIndex }?.associateBy { it.index } ?: emptyMap() val indexToVarNode = node.localVariables?.filter { it.index < maskStartIndex }?.associateBy { it.index } ?: emptyMap()
val toDelete = arrayListOf<AbstractInsnNode>()
conditions.forEach { conditions.forEach {
val jumpInstruction = it.jumpInstruction val jumpInstruction = it.jumpInstruction
InsnSequence(it.maskInstruction, (if (it.expandNotDelete) jumpInstruction.next else jumpInstruction.label)).forEach { InsnSequence(it.maskInstruction, (if (it.expandNotDelete) jumpInstruction.next else jumpInstruction.label)).forEach {
@@ -113,51 +129,72 @@ fun expandMaskConditionsAndUpdateVariableNodes(
} }
} }
toDelete.forEach { toInsert.forEach { (position, newInsn) ->
node.instructions.remove(it) node.instructions.insert(position, newInsn)
} }
node.remove(toDelete)
return defaultLambdasInfo return defaultLambdasInfo
} }
private fun extractDefaultLambdasInfo(conditions: List<Condition>, defaultLambdas: Map<Int, ValueParameterDescriptor>): List<DefaultLambda> { private fun extractDefaultLambdasInfo(
conditions: List<Condition>,
defaultLambdas: Map<Int, ValueParameterDescriptor>,
toDelete: MutableList<AbstractInsnNode>,
toInsert: MutableList<Pair<AbstractInsnNode, AbstractInsnNode>>
): List<DefaultLambda> {
val defaultLambdaConditions = conditions.filter { it.expandNotDelete && defaultLambdas.contains(it.varIndex) } val defaultLambdaConditions = conditions.filter { it.expandNotDelete && defaultLambdas.contains(it.varIndex) }
return defaultLambdaConditions.map { return defaultLambdaConditions.map {
val varAssignmentInstruction = it.varInsNode!! val varAssignmentInstruction = it.varInsNode!!
var instanceInstuction = varAssignmentInstruction.previous var instanceInstuction = varAssignmentInstruction.previous
if (instanceInstuction is TypeInsnNode && instanceInstuction.opcode == Opcodes.CHECKCAST) { if (instanceInstuction is TypeInsnNode && instanceInstuction.opcode == Opcodes.CHECKCAST) {
instanceInstuction = instanceInstuction.previous instanceInstuction = instanceInstuction.previous
} }
when (instanceInstuction) {
val (owner, argTypes) = when (instanceInstuction) {
is MethodInsnNode -> { is MethodInsnNode -> {
assert(instanceInstuction.name == "<init>") { "Expected constructor call for default lambda, but $instanceInstuction" } assert(instanceInstuction.name == "<init>") { "Expected constructor call for default lambda, but $instanceInstuction" }
val ownerInternalName = instanceInstuction.owner val ownerInternalName = instanceInstuction.owner
val instanceCreation = InsnSequence(it.jumpInstruction, it.jumpInstruction.label).filter { val instanceCreation = InsnSequence(it.jumpInstruction, it.jumpInstruction.label).filter {
it.opcode == Opcodes.NEW && (it as TypeInsnNode).desc == ownerInternalName it.opcode == Opcodes.NEW && (it as TypeInsnNode).desc == ownerInternalName
}.single() }.single()
assert(instanceCreation.next?.opcode == Opcodes.DUP) { assert(instanceCreation.next?.opcode == Opcodes.DUP) {
"Dup should follow default lambda instanceInstuction creation but ${instanceCreation.next}" "Dup should follow default lambda instanceInstruction creation but ${instanceCreation.next}"
} }
DefaultLambda( toDelete.apply {
Type.getObjectType(instanceInstuction.owner), addAll(listOf(instanceCreation, instanceCreation.next))
Type.getArgumentTypes(instanceInstuction.desc), addAll(InsnSequence(instanceInstuction, varAssignmentInstruction.next).toList())
defaultLambdas[it.varIndex]!!, }
listOf(instanceCreation, instanceCreation.next) + InsnSequence(instanceInstuction, varAssignmentInstruction).toList(),
it.varIndex Type.getObjectType(instanceInstuction.owner) to Type.getArgumentTypes(instanceInstuction.desc)
)
} }
is FieldInsnNode -> DefaultLambda( is FieldInsnNode -> {
Type.getObjectType(instanceInstuction.owner), toDelete.addAll(InsnSequence(instanceInstuction, varAssignmentInstruction.next).toList())
emptyArray<Type>(),
defaultLambdas[it.varIndex]!!, Type.getObjectType(instanceInstuction.owner) to emptyArray<Type>()
InsnSequence(instanceInstuction, varAssignmentInstruction).toList(), }
it.varIndex else -> throw RuntimeException("Can't extract default lambda info $it.\n Unknown instruction: ${instanceInstuction.insnText}")
)
else -> throw RuntimeException("Can't extract default lambda info $it.\n Unknown instruction: $instanceInstuction")
} }
toInsert.add(varAssignmentInstruction to defaultLambdaFakeCallStub(argTypes, it.varIndex))
DefaultLambda(owner, argTypes, defaultLambdas[it.varIndex]!!, it.varIndex)
} }
}
//marker that removes captured parameters from stack
//at inlining it would be substituted with parameters store
private fun defaultLambdaFakeCallStub(args: Array<Type>, lambdaOffset: Int): MethodInsnNode {
return MethodInsnNode(
Opcodes.INVOKESTATIC,
DEFAULT_LAMBDA_FAKE_CALL,
DEFAULT_LAMBDA_FAKE_CALL + lambdaOffset,
Type.getMethodDescriptor(Type.VOID_TYPE, *args),
false
)
} }
@@ -270,9 +270,9 @@ val IrFunction.OtherOrigin: JvmDeclarationOrigin
get() = OtherOrigin(descriptor.psiElement, this.descriptor) get() = OtherOrigin(descriptor.psiElement, this.descriptor)
fun DeclarationDescriptor.getMemberOwnerKind(): OwnerKind = when (this) { fun DeclarationDescriptor.getMemberOwnerKind(): OwnerKind = when (this) {
is FileClassDescriptor -> is FileClassDescriptor,
is PackageFragmentDescriptor ->
OwnerKind.PACKAGE OwnerKind.PACKAGE
is PackageFragmentDescriptor,
is ClassDescriptor -> is ClassDescriptor ->
OwnerKind.IMPLEMENTATION OwnerKind.IMPLEMENTATION
else -> else ->