diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java index e2c1de2b67c..bb1bf8736cb 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/LambdaTransformer.java @@ -19,17 +19,13 @@ package org.jetbrains.jet.codegen.inline; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.OutputFile; import org.jetbrains.jet.codegen.*; import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.JetTypeMapper; import org.jetbrains.org.objectweb.asm.*; 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; -import org.jetbrains.org.objectweb.asm.tree.VarInsnNode; +import org.jetbrains.org.objectweb.asm.tree.*; import java.io.IOException; import java.util.*; @@ -42,44 +38,39 @@ public class LambdaTransformer { protected final JetTypeMapper typeMapper; - private final MethodNode constructor; - - private final MethodNode invoke; - - private final MethodNode bridge; + private MethodNode constructor; private final InliningContext inliningContext; - private final Type oldLambdaType; + private final Type oldObjectType; private final Type newLambdaType; - private int classAccess; - private String signature; + private final ClassReader reader; + private String superName; - private String[] interfaces; + private final boolean isSameModule; - private Map> fieldNames = new HashMap>(); + private final Map> fieldNames = new HashMap>(); - public LambdaTransformer(String lambdaInternalName, InliningContext inliningContext, boolean isSameModule, Type newLambdaType) { + public LambdaTransformer(@NotNull String objectInternalName, @NotNull InliningContext inliningContext, boolean isSameModule, @NotNull Type newLambdaType) { this.isSameModule = isSameModule; this.state = inliningContext.state; this.typeMapper = state.getTypeMapper(); this.inliningContext = inliningContext; - this.oldLambdaType = Type.getObjectType(lambdaInternalName); + this.oldObjectType = Type.getObjectType(objectInternalName); this.newLambdaType = newLambdaType; //try to find just compiled classes then in dependencies - ClassReader reader; try { - OutputFile outputFile = state.getFactory().get(lambdaInternalName + ".class"); + OutputFile outputFile = state.getFactory().get(objectInternalName + ".class"); if (outputFile != null) { reader = new ClassReader(outputFile.asByteArray()); } else { - VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), lambdaInternalName); + VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), objectInternalName); if (file == null) { - throw new RuntimeException("Couldn't find virtual file for " + lambdaInternalName); + throw new RuntimeException("Couldn't find virtual file for " + objectInternalName); } reader = new ClassReader(file.getInputStream()); } @@ -87,75 +78,106 @@ public class LambdaTransformer { catch (IOException e) { throw new RuntimeException(e); } - - //TODO rewrite to one step - constructor = getMethodNode(reader, true, false); - invoke = getMethodNode(reader, false, false); - bridge = getMethodNode(reader, false, true); } - private void buildInvokeParams(ParametersBuilder builder) { - builder.addThis(oldLambdaType, false); + private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) { + builder.addThis(oldObjectType, false); - Type[] types = Type.getArgumentTypes(invoke.desc); + Type[] types = Type.getArgumentTypes(node.desc); for (Type type : types) { builder.addNextParameter(type, false, null); } } - public InlineResult doTransform(ConstructorInvocation invocation, FieldRemapper parentRemapper) { - ClassBuilder classBuilder = createClassBuilder(); + @NotNull + public InlineResult doTransform(@NotNull ConstructorInvocation invocation, @NotNull FieldRemapper parentRemapper) { + final ClassBuilder classBuilder = createClassBuilder(); + final List methodsToTransform = new ArrayList(); + final List fieldToAdd = new ArrayList(); + reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) { - //TODO: public visibility for inline function - classBuilder.defineClass(null, - V1_6, - classAccess, - newLambdaType.getInternalName(), - signature, - superName, - interfaces - ); + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + //TODO: public visibility for inline function + LambdaTransformer.this.superName = superName; + classBuilder.defineClass(null, + V1_6, + access, + newLambdaType.getInternalName(), + signature, + superName, + interfaces + ); + } - // TODO: load synthetic class kind from the transformed class and write the same kind to the copy of that class here - // See AsmUtil.writeKotlinSyntheticClassAnnotation + @Override + public MethodVisitor visitMethod( + int access, String name, String desc, String signature, String[] exceptions + ) { + MethodNode node = new MethodNode(access, name, desc, signature, exceptions); + if (name.equals("")){ + if (constructor != null) + throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor"); - ParametersBuilder builder = ParametersBuilder.newBuilder(); - Parameters parameters = getLambdaParameters(builder, invocation); - - MethodVisitor invokeVisitor = newMethod(classBuilder, invoke); - - RegeneratedLambdaFieldRemapper remapper = - new RegeneratedLambdaFieldRemapper(oldLambdaType.getInternalName(), newLambdaType.getInternalName(), - parameters, invocation.getCapturedLambdasToInline(), - parentRemapper); - - MethodInliner inliner = new MethodInliner(invoke, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), - remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName()); - InlineResult result = inliner.doInline(invokeVisitor, new LocalVarRemapper(parameters, 0), false); - invokeVisitor.visitMaxs(-1, -1); - - generateConstructorAndFields(classBuilder, builder, invocation); - - if (bridge != null) { - MethodVisitor invokeBridge = newMethod(classBuilder, bridge); - bridge.accept(new MethodVisitor(InlineCodegenUtil.API, invokeBridge) { - @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - if (owner.equals(oldLambdaType.getInternalName())) { - super.visitMethodInsn(opcode, newLambdaType.getInternalName(), name, desc, itf); - } else { - super.visitMethodInsn(opcode, owner, name, desc, itf); - } + constructor = node; + } else { + methodsToTransform.add(node); } - }); + return node; + } + + @Override + public FieldVisitor visitField( + int access, String name, String desc, String signature, Object value + ) { + addUniqueField(name); + FieldNode fieldNode = new FieldNode(access, name, desc, signature, value); + fieldToAdd.add(fieldNode); + return fieldNode; + } + }, ClassReader.SKIP_FRAMES); + + ParametersBuilder capturedBuilder = ParametersBuilder.newBuilder(); + extractParametersMappingAndPatchConstructor(constructor, capturedBuilder, invocation); + + InlineResult result = InlineResult.create(); + for (MethodNode next : methodsToTransform) { + MethodVisitor visitor = newMethod(classBuilder, next); + InlineResult funResult = inlineMethod(invocation, parentRemapper, visitor, next, capturedBuilder); + result.addAllClassesToRemove(funResult); } + generateConstructorAndFields(classBuilder, capturedBuilder, invocation); + classBuilder.done(); invocation.setNewLambdaType(newLambdaType); return result; } + @NotNull + private InlineResult inlineMethod( + @NotNull ConstructorInvocation invocation, + @NotNull FieldRemapper parentRemapper, + @NotNull MethodVisitor resultVisitor, + @NotNull MethodNode sourceNode, + @NotNull ParametersBuilder capturedBuilder + ) { + + Parameters parameters = getMethodParametersWithCaptured(capturedBuilder, sourceNode); + + RegeneratedLambdaFieldRemapper remapper = + new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(), + parameters, invocation.getCapturedLambdasToInline(), + parentRemapper); + + MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")), + remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName()); + InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false); + resultVisitor.visitMaxs(-1, -1); + return result; + } + private void generateConstructorAndFields(@NotNull ClassBuilder classBuilder, @NotNull ParametersBuilder builder, @NotNull ConstructorInvocation invocation) { List infos = builder.buildCaptured(); List> newConstructorSignature = new ArrayList>(); @@ -174,32 +196,43 @@ public class LambdaTransformer { invocation.setNewConstructorDescriptor(newConstructor.getDescriptor()); } - private Parameters getLambdaParameters(ParametersBuilder builder, ConstructorInvocation invocation) { - buildInvokeParams(builder); - extractParametersMapping(constructor, builder, invocation); + @NotNull + private Parameters getMethodParametersWithCaptured( + @NotNull ParametersBuilder capturedBuilder, + @NotNull MethodNode sourceNode + ) { + ParametersBuilder builder = ParametersBuilder.newBuilder(); + buildInvokeParamsFor(builder, sourceNode); + for (CapturedParamInfo param : capturedBuilder.getCapturedParams()) { + builder.addCapturedParamCopy(param); + } return builder.buildParameters(); } + @NotNull private ClassBuilder createClassBuilder() { return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, inliningContext.call.getCallElement().getContainingFile()), new TypeRemapper(inliningContext.typeMapping)); } - private static MethodVisitor newMethod(ClassBuilder builder, MethodNode original) { + @NotNull + private static MethodVisitor newMethod(@NotNull ClassBuilder builder, @NotNull MethodNode original) { return builder.newMethod( null, original.access, original.name, original.desc, original.signature, - null //TODO: change signature to list + original.exceptions.toArray(new String [original.exceptions.size()]) ); } - private void extractParametersMapping(MethodNode constructor, ParametersBuilder builder, final ConstructorInvocation invocation) { + private void extractParametersMappingAndPatchConstructor( + @NotNull MethodNode constructor, + @NotNull ParametersBuilder builder, + @NotNull final ConstructorInvocation invocation + ) { Map indexToLambda = invocation.getLambdasToInline(); - - AbstractInsnNode cur = constructor.instructions.getFirst(); List capturedLambdas = new ArrayList(); //captured var of inlined parameter CapturedParamOwner owner = new CapturedParamOwner() { @Override @@ -208,21 +241,27 @@ public class LambdaTransformer { } }; + AbstractInsnNode cur = constructor.instructions.getFirst(); + //load captured parameters (NB: there is also could be object fields) while (cur != null) { if (cur.getType() == AbstractInsnNode.FIELD_INSN) { FieldInsnNode fieldNode = (FieldInsnNode) cur; - CapturedParamInfo info = builder.addCapturedParam(fieldNode.name, Type.getType(fieldNode.desc), false, null, owner); + CapturedParamInfo info = builder.addCapturedParam(owner, fieldNode.name, Type.getType(fieldNode.desc), false, null); - assert fieldNode.getPrevious() instanceof VarInsnNode : "Previous instruction should be VarInsnNode but was " + fieldNode.getPrevious(); - VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); - int varIndex = previous.var; - LambdaInfo lambdaInfo = indexToLambda.get(varIndex); - if (lambdaInfo != null) { - info.setLambda(lambdaInfo); - capturedLambdas.add(lambdaInfo); + boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode; + boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode; + if (isPrevPrevVarNode) { + VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious(); + if (node.var == 0) { + VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); + int varIndex = previous.var; + LambdaInfo lambdaInfo = indexToLambda.get(varIndex); + if (lambdaInfo != null) { + info.setLambda(lambdaInfo); + capturedLambdas.add(lambdaInfo); + } + } } - - addUniqueField(info.getOriginalFieldName()); } cur = cur.getNext(); } @@ -234,9 +273,9 @@ public class LambdaTransformer { for (LambdaInfo info : capturedLambdas) { for (CapturedParamInfo var : info.getCapturedVars()) { CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(var, getNewFieldName(var.getOriginalFieldName())); - StackValue composed = StackValue.composed(StackValue.local(0, oldLambdaType), + StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType), StackValue.field(var.getType(), - oldLambdaType, /*TODO owner type*/ + oldObjectType, /*TODO owner type*/ recapturedParamInfo.getNewFieldName(), false) ); recapturedParamInfo.setRemapValue(composed); @@ -249,39 +288,6 @@ public class LambdaTransformer { invocation.setCapturedLambdasToInline(capturedLambdasToInline); } - @Nullable - public MethodNode getMethodNode(@NotNull ClassReader reader, final boolean findConstructor, final boolean findBridge) { - final MethodNode[] methodNode = new MethodNode[1]; - reader.accept(new ClassVisitor(InlineCodegenUtil.API) { - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - super.visit(version, access, name, signature, superName, interfaces); - LambdaTransformer.this.classAccess = access; - LambdaTransformer.this.signature = signature; - LambdaTransformer.this.superName = superName; - LambdaTransformer.this.interfaces = interfaces; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - boolean isConstructorMethod = "".equals(name); - boolean isBridge = (access & Opcodes.ACC_BRIDGE) != 0; - if (findConstructor && isConstructorMethod || (!findConstructor && !isConstructorMethod && (isBridge == findBridge))) { - assert methodNode[0] == null : "Wrong lambda/sam structure: " + methodNode[0].name + " conflicts with " + name; - return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); - } - return null; - } - }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - - if (methodNode[0] == null && !findBridge) { - throw new RuntimeException("Couldn't find operation method of lambda/sam class " + oldLambdaType.getInternalName() + ": findConstructor = " + findConstructor); - } - - return methodNode[0]; - } - @NotNull public String getNewFieldName(@NotNull String oldName) { if (oldName.equals("this$0")) { diff --git a/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java b/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java index 5d95bf35dd5..ef5b57ece72 100644 --- a/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java +++ b/compiler/backend/src/org/jetbrains/jet/codegen/inline/ParametersBuilder.java @@ -58,13 +58,21 @@ public class ParametersBuilder { return addCapturedParameter(info); } + @NotNull + public CapturedParamInfo addCapturedParamCopy( + @NotNull CapturedParamInfo copyFrom + ) { + CapturedParamInfo info = copyFrom.newIndex(nextCaptured); + return addCapturedParameter(info); + } + @NotNull public CapturedParamInfo addCapturedParam( + @NotNull CapturedParamOwner containingLambda, @NotNull String fieldName, @NotNull Type type, boolean skipped, - @Nullable ParameterInfo original, - @NotNull CapturedParamOwner containingLambda + @Nullable ParameterInfo original ) { CapturedParamInfo info = new CapturedParamInfo(CapturedParamDesc.createDesc(containingLambda, fieldName, type), skipped, nextCaptured, @@ -105,4 +113,8 @@ public class ParametersBuilder { public Parameters buildParameters() { return new Parameters(buildWithStubs(), buildCapturedWithStubs()); } + + public List getCapturedParams() { + return capturedParams; + } } diff --git a/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/1.kt b/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/1.kt new file mode 100644 index 00000000000..d99a9080e83 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/1.kt @@ -0,0 +1,20 @@ +import test.* + +fun box() : String { + val o = "O" + return doWork { + val k = "K" + val s = object : A() { + override fun getO(): String { + return o; + } + + override fun getK(): String { + return k; + } + } + + s.getO() + s.getK() + } +} + diff --git a/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/2.kt b/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/2.kt new file mode 100644 index 00000000000..dbc06a72b72 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObjectOnCallSite/2.kt @@ -0,0 +1,12 @@ +package test + +abstract class A { + abstract fun getO() : R + + abstract fun getK() : R +} + + +inline fun doWork(job: ()-> R) : R { + return job() +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/1.kt b/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/1.kt new file mode 100644 index 00000000000..843531fdac5 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/1.kt @@ -0,0 +1,24 @@ +import test.* + +fun box() : String { + val o = "O" + val result = doWork { + val k = "K" + val s = object : A("11") { + override fun getO(): String { + return o; + } + + override fun getK(): String { + return k; + } + } + + s.getO() + s.getK() + s.param + } + + if (result != "OK11") return "fail $result" + + return "OK" +} + diff --git a/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/2.kt b/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/2.kt new file mode 100644 index 00000000000..ad8abcf438d --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObjectSuperParams/2.kt @@ -0,0 +1,12 @@ +package test + +abstract class A(val param: R) { + abstract fun getO() : R + + abstract fun getK() : R +} + + +inline fun doWork(job: ()-> R) : R { + return job() +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java index 0ae908044c2..0d42e145e1d 100644 --- a/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/codegen/generated/BlackBoxInlineCodegenTestGenerated.java @@ -36,6 +36,16 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxCodegenT JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("compiler/testData/codegen/boxInline"), Pattern.compile("^([^\\.]+)$"), false); } + @TestMetadata("anonymousObjectOnCallSite") + public void testAnonymousObjectOnCallSite() throws Exception { + doTestMultiFileWithInlineCheck("compiler/testData/codegen/boxInline/anonymousObjectOnCallSite"); + } + + @TestMetadata("anonymousObjectSuperParams") + public void testAnonymousObjectSuperParams() throws Exception { + doTestMultiFileWithInlineCheck("compiler/testData/codegen/boxInline/anonymousObjectSuperParams"); + } + @TestMetadata("builders") public void testBuilders() throws Exception { doTestMultiFileWithInlineCheck("compiler/testData/codegen/boxInline/builders"); diff --git a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java index 1454fbcf2fa..39adac7a05a 100644 --- a/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/jet/jvm/compiler/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -36,6 +36,16 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.TestsPackage", new File("compiler/testData/codegen/boxInline"), Pattern.compile("^([^\\.]+)$"), false); } + @TestMetadata("anonymousObjectOnCallSite") + public void testAnonymousObjectOnCallSite() throws Exception { + doBoxTestWithInlineCheck("compiler/testData/codegen/boxInline/anonymousObjectOnCallSite"); + } + + @TestMetadata("anonymousObjectSuperParams") + public void testAnonymousObjectSuperParams() throws Exception { + doBoxTestWithInlineCheck("compiler/testData/codegen/boxInline/anonymousObjectSuperParams"); + } + @TestMetadata("builders") public void testBuilders() throws Exception { doBoxTestWithInlineCheck("compiler/testData/codegen/boxInline/builders");