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();
byte[] bytes =
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;
@@ -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
@@ -88,12 +88,14 @@ public class InlineCodegenUtil {
public static final String INLINE_FUN_THIS_0_SUFFIX = "$inline_fun";
public static final String INLINE_FUN_VAR_SUFFIX = "$iv";
public static final String DEFAULT_LAMBDA_FAKE_CALL = "$$$DEFAULT_LAMBDA_FAKE_CALL$$$";
@Nullable
public static SMAPAndMethodNode getMethodNode(
byte[] classData,
String methodName,
String methodDescriptor,
ClassId classId
@NotNull String methodName,
@NotNull String methodDescriptor,
@NotNull String classInternalName
) {
ClassReader cr = new ClassReader(classData);
MethodNode[] node = new MethodNode[1];
@@ -138,12 +140,12 @@ public class InlineCodegenUtil {
return null;
}
if (classId.equals(IntrinsicArrayConstructorsKt.getClassId())) {
if (IntrinsicArrayConstructorsKt.getClassId().asString().equals(classInternalName)) {
// Don't load source map for intrinsic array constructors
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);
}
@@ -169,7 +171,7 @@ public class InlineCodegenUtil {
}
@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();
String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName);
//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) {
int opcode = ins.getOpcode();
Integer value;
if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
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.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.psi.KtCallableReferenceExpression
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCallWithAssert
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.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
@@ -66,7 +67,11 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean, @JvmField val is
companion object {
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,
val capturedArgs: Array<Type>,
val parameterDescriptor: ValueParameterDescriptor,
val initInstuctions: List<AbstractInsnNode>,
val offset: Int
) : LambdaInfo(false, false) {
override val invokeMethod: Method
get() = TODO("not implemented")
) : LambdaInfo(parameterDescriptor.isCrossinline, false) {
override val invokeMethodDescriptor: FunctionDescriptor
get() = TODO("not implemented")
override lateinit var invokeMethod: Method
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 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
putStackValuesIntoLocals(listOf(*info.invokeMethod.argumentTypes), valueParamShift, this, desc)
if (invokeCall.lambdaInfo.invokeMethodDescriptor.getValueParameters().isEmpty()) {
if (invokeCall.lambdaInfo.invokeMethodDescriptor.valueParameters.isEmpty()) {
// 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.
visitInsn(Opcodes.NOP)
@@ -90,14 +90,11 @@ private fun MethodInliner.getLambdaIfExistsAndMarkInstructions(
return null
}
fun parameterOffsets(valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
var offset = 0
fun parameterOffsets(isStatic: Boolean, valueParameters: List<JvmMethodParameterSignature>): Array<Int> {
var nextOffset = if (isStatic) 0 else 1
return Array(valueParameters.size) { index ->
if (index == 0) {
0
}
else offset.apply {
offset += valueParameters[index - 1].asmType.size
nextOffset.also {
nextOffset += valueParameters[index].asmType.size
}
}
}
@@ -16,10 +16,17 @@
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.asSequence
import org.jetbrains.kotlin.codegen.optimization.common.insnText
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
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> {
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
return functionDescriptor.valueParameters.filter {
@@ -81,7 +95,7 @@ fun expandMaskConditionsAndUpdateVariableNodes(
val jumpInstruction = it.next?.next?.next as JumpInsnNode
Condition(
masks[it.`var` - maskStartIndex],
InlineCodegenUtil.getConstant(it.next),
getConstant(it.next),
it,
jumpInstruction,
jumpInstruction.label.previous as VarInsnNode
@@ -97,10 +111,12 @@ fun expandMaskConditionsAndUpdateVariableNodes(
else null
}.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 toDelete = arrayListOf<AbstractInsnNode>()
conditions.forEach {
val jumpInstruction = it.jumpInstruction
InsnSequence(it.maskInstruction, (if (it.expandNotDelete) jumpInstruction.next else jumpInstruction.label)).forEach {
@@ -113,51 +129,72 @@ fun expandMaskConditionsAndUpdateVariableNodes(
}
}
toDelete.forEach {
node.instructions.remove(it)
toInsert.forEach { (position, newInsn) ->
node.instructions.insert(position, newInsn)
}
node.remove(toDelete)
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) }
return defaultLambdaConditions.map {
val varAssignmentInstruction = it.varInsNode!!
var instanceInstuction = varAssignmentInstruction.previous
if (instanceInstuction is TypeInsnNode && instanceInstuction.opcode == Opcodes.CHECKCAST) {
instanceInstuction = instanceInstuction.previous
}
when (instanceInstuction) {
val (owner, argTypes) = when (instanceInstuction) {
is MethodInsnNode -> {
assert(instanceInstuction.name == "<init>") { "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}"
"Dup should follow default lambda instanceInstruction 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
)
toDelete.apply {
addAll(listOf(instanceCreation, instanceCreation.next))
addAll(InsnSequence(instanceInstuction, varAssignmentInstruction.next).toList())
}
Type.getObjectType(instanceInstuction.owner) to Type.getArgumentTypes(instanceInstuction.desc)
}
is FieldInsnNode -> DefaultLambda(
Type.getObjectType(instanceInstuction.owner),
emptyArray<Type>(),
defaultLambdas[it.varIndex]!!,
InsnSequence(instanceInstuction, varAssignmentInstruction).toList(),
it.varIndex
)
else -> throw RuntimeException("Can't extract default lambda info $it.\n Unknown instruction: $instanceInstuction")
is FieldInsnNode -> {
toDelete.addAll(InsnSequence(instanceInstuction, varAssignmentInstruction.next).toList())
Type.getObjectType(instanceInstuction.owner) to emptyArray<Type>()
}
else -> throw RuntimeException("Can't extract default lambda info $it.\n Unknown instruction: ${instanceInstuction.insnText}")
}
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)
fun DeclarationDescriptor.getMemberOwnerKind(): OwnerKind = when (this) {
is FileClassDescriptor ->
is FileClassDescriptor,
is PackageFragmentDescriptor ->
OwnerKind.PACKAGE
is PackageFragmentDescriptor,
is ClassDescriptor ->
OwnerKind.IMPLEMENTATION
else ->