Anonymous object transformation

This commit is contained in:
Mikhael Bogdanov
2014-04-21 13:51:26 +04:00
parent 6fb0050575
commit 9007ba9c53
8 changed files with 231 additions and 125 deletions
@@ -19,17 +19,13 @@ package org.jetbrains.jet.codegen.inline;
import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.OutputFile; import org.jetbrains.jet.OutputFile;
import org.jetbrains.jet.codegen.*; import org.jetbrains.jet.codegen.*;
import org.jetbrains.jet.codegen.state.GenerationState; import org.jetbrains.jet.codegen.state.GenerationState;
import org.jetbrains.jet.codegen.state.JetTypeMapper; import org.jetbrains.jet.codegen.state.JetTypeMapper;
import org.jetbrains.org.objectweb.asm.*; import org.jetbrains.org.objectweb.asm.*;
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.*;
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 java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@@ -42,44 +38,39 @@ public class LambdaTransformer {
protected final JetTypeMapper typeMapper; protected final JetTypeMapper typeMapper;
private final MethodNode constructor; private MethodNode constructor;
private final MethodNode invoke;
private final MethodNode bridge;
private final InliningContext inliningContext; private final InliningContext inliningContext;
private final Type oldLambdaType; private final Type oldObjectType;
private final Type newLambdaType; private final Type newLambdaType;
private int classAccess; private final ClassReader reader;
private String signature;
private String superName; private String superName;
private String[] interfaces;
private final boolean isSameModule; private final boolean isSameModule;
private Map<String, List<String>> fieldNames = new HashMap<String, List<String>>(); private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
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.isSameModule = isSameModule;
this.state = inliningContext.state; this.state = inliningContext.state;
this.typeMapper = state.getTypeMapper(); this.typeMapper = state.getTypeMapper();
this.inliningContext = inliningContext; this.inliningContext = inliningContext;
this.oldLambdaType = Type.getObjectType(lambdaInternalName); this.oldObjectType = Type.getObjectType(objectInternalName);
this.newLambdaType = newLambdaType; this.newLambdaType = newLambdaType;
//try to find just compiled classes then in dependencies //try to find just compiled classes then in dependencies
ClassReader reader;
try { try {
OutputFile outputFile = state.getFactory().get(lambdaInternalName + ".class"); OutputFile outputFile = state.getFactory().get(objectInternalName + ".class");
if (outputFile != null) { if (outputFile != null) {
reader = new ClassReader(outputFile.asByteArray()); reader = new ClassReader(outputFile.asByteArray());
} else { } else {
VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), lambdaInternalName); VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), objectInternalName);
if (file == null) { 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()); reader = new ClassReader(file.getInputStream());
} }
@@ -87,75 +78,106 @@ public class LambdaTransformer {
catch (IOException e) { catch (IOException e) {
throw new RuntimeException(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) { private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) {
builder.addThis(oldLambdaType, false); builder.addThis(oldObjectType, false);
Type[] types = Type.getArgumentTypes(invoke.desc); Type[] types = Type.getArgumentTypes(node.desc);
for (Type type : types) { for (Type type : types) {
builder.addNextParameter(type, false, null); builder.addNextParameter(type, false, null);
} }
} }
public InlineResult doTransform(ConstructorInvocation invocation, FieldRemapper parentRemapper) { @NotNull
ClassBuilder classBuilder = createClassBuilder(); public InlineResult doTransform(@NotNull ConstructorInvocation invocation, @NotNull FieldRemapper parentRemapper) {
final ClassBuilder classBuilder = createClassBuilder();
final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>();
final List<FieldNode> fieldToAdd = new ArrayList<FieldNode>();
reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
//TODO: public visibility for inline function @Override
classBuilder.defineClass(null, public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
V1_6, //TODO: public visibility for inline function
classAccess, LambdaTransformer.this.superName = superName;
newLambdaType.getInternalName(), classBuilder.defineClass(null,
signature, V1_6,
superName, access,
interfaces 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 @Override
// See AsmUtil.writeKotlinSyntheticClassAnnotation 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("<init>")){
if (constructor != null)
throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor");
ParametersBuilder builder = ParametersBuilder.newBuilder(); constructor = node;
Parameters parameters = getLambdaParameters(builder, invocation); } else {
methodsToTransform.add(node);
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);
}
} }
}); 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(); classBuilder.done();
invocation.setNewLambdaType(newLambdaType); invocation.setNewLambdaType(newLambdaType);
return result; 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) { private void generateConstructorAndFields(@NotNull ClassBuilder classBuilder, @NotNull ParametersBuilder builder, @NotNull ConstructorInvocation invocation) {
List<CapturedParamInfo> infos = builder.buildCaptured(); List<CapturedParamInfo> infos = builder.buildCaptured();
List<Pair<String, Type>> newConstructorSignature = new ArrayList<Pair<String, Type>>(); List<Pair<String, Type>> newConstructorSignature = new ArrayList<Pair<String, Type>>();
@@ -174,32 +196,43 @@ public class LambdaTransformer {
invocation.setNewConstructorDescriptor(newConstructor.getDescriptor()); invocation.setNewConstructorDescriptor(newConstructor.getDescriptor());
} }
private Parameters getLambdaParameters(ParametersBuilder builder, ConstructorInvocation invocation) { @NotNull
buildInvokeParams(builder); private Parameters getMethodParametersWithCaptured(
extractParametersMapping(constructor, builder, invocation); @NotNull ParametersBuilder capturedBuilder,
@NotNull MethodNode sourceNode
) {
ParametersBuilder builder = ParametersBuilder.newBuilder();
buildInvokeParamsFor(builder, sourceNode);
for (CapturedParamInfo param : capturedBuilder.getCapturedParams()) {
builder.addCapturedParamCopy(param);
}
return builder.buildParameters(); return builder.buildParameters();
} }
@NotNull
private ClassBuilder createClassBuilder() { private ClassBuilder createClassBuilder() {
return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, inliningContext.call.getCallElement().getContainingFile()), return new RemappingClassBuilder(state.getFactory().forLambdaInlining(newLambdaType, inliningContext.call.getCallElement().getContainingFile()),
new TypeRemapper(inliningContext.typeMapping)); 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( return builder.newMethod(
null, null,
original.access, original.access,
original.name, original.name,
original.desc, original.desc,
original.signature, 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<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline(); Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
AbstractInsnNode cur = constructor.instructions.getFirst();
List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter
CapturedParamOwner owner = new CapturedParamOwner() { CapturedParamOwner owner = new CapturedParamOwner() {
@Override @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) { while (cur != null) {
if (cur.getType() == AbstractInsnNode.FIELD_INSN) { if (cur.getType() == AbstractInsnNode.FIELD_INSN) {
FieldInsnNode fieldNode = (FieldInsnNode) cur; 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(); boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode;
VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious(); boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode;
int varIndex = previous.var; if (isPrevPrevVarNode) {
LambdaInfo lambdaInfo = indexToLambda.get(varIndex); VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious();
if (lambdaInfo != null) { if (node.var == 0) {
info.setLambda(lambdaInfo); VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
capturedLambdas.add(lambdaInfo); int varIndex = previous.var;
LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
if (lambdaInfo != null) {
info.setLambda(lambdaInfo);
capturedLambdas.add(lambdaInfo);
}
}
} }
addUniqueField(info.getOriginalFieldName());
} }
cur = cur.getNext(); cur = cur.getNext();
} }
@@ -234,9 +273,9 @@ public class LambdaTransformer {
for (LambdaInfo info : capturedLambdas) { for (LambdaInfo info : capturedLambdas) {
for (CapturedParamInfo var : info.getCapturedVars()) { for (CapturedParamInfo var : info.getCapturedVars()) {
CapturedParamInfo recapturedParamInfo = builder.addCapturedParam(var, getNewFieldName(var.getOriginalFieldName())); 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(), StackValue.field(var.getType(),
oldLambdaType, /*TODO owner type*/ oldObjectType, /*TODO owner type*/
recapturedParamInfo.getNewFieldName(), false) recapturedParamInfo.getNewFieldName(), false)
); );
recapturedParamInfo.setRemapValue(composed); recapturedParamInfo.setRemapValue(composed);
@@ -249,39 +288,6 @@ public class LambdaTransformer {
invocation.setCapturedLambdasToInline(capturedLambdasToInline); 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 = "<init>".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 @NotNull
public String getNewFieldName(@NotNull String oldName) { public String getNewFieldName(@NotNull String oldName) {
if (oldName.equals("this$0")) { if (oldName.equals("this$0")) {
@@ -58,13 +58,21 @@ public class ParametersBuilder {
return addCapturedParameter(info); return addCapturedParameter(info);
} }
@NotNull
public CapturedParamInfo addCapturedParamCopy(
@NotNull CapturedParamInfo copyFrom
) {
CapturedParamInfo info = copyFrom.newIndex(nextCaptured);
return addCapturedParameter(info);
}
@NotNull @NotNull
public CapturedParamInfo addCapturedParam( public CapturedParamInfo addCapturedParam(
@NotNull CapturedParamOwner containingLambda,
@NotNull String fieldName, @NotNull String fieldName,
@NotNull Type type, @NotNull Type type,
boolean skipped, boolean skipped,
@Nullable ParameterInfo original, @Nullable ParameterInfo original
@NotNull CapturedParamOwner containingLambda
) { ) {
CapturedParamInfo info = CapturedParamInfo info =
new CapturedParamInfo(CapturedParamDesc.createDesc(containingLambda, fieldName, type), skipped, nextCaptured, new CapturedParamInfo(CapturedParamDesc.createDesc(containingLambda, fieldName, type), skipped, nextCaptured,
@@ -105,4 +113,8 @@ public class ParametersBuilder {
public Parameters buildParameters() { public Parameters buildParameters() {
return new Parameters(buildWithStubs(), buildCapturedWithStubs()); return new Parameters(buildWithStubs(), buildCapturedWithStubs());
} }
public List<CapturedParamInfo> getCapturedParams() {
return capturedParams;
}
} }
@@ -0,0 +1,20 @@
import test.*
fun box() : String {
val o = "O"
return doWork {
val k = "K"
val s = object : A<String>() {
override fun getO(): String {
return o;
}
override fun getK(): String {
return k;
}
}
s.getO() + s.getK()
}
}
@@ -0,0 +1,12 @@
package test
abstract class A<R> {
abstract fun getO() : R
abstract fun getK() : R
}
inline fun <R> doWork(job: ()-> R) : R {
return job()
}
@@ -0,0 +1,24 @@
import test.*
fun box() : String {
val o = "O"
val result = doWork {
val k = "K"
val s = object : A<String>("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"
}
@@ -0,0 +1,12 @@
package test
abstract class A<R>(val param: R) {
abstract fun getO() : R
abstract fun getK() : R
}
inline fun <R> doWork(job: ()-> R) : R {
return job()
}
@@ -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); 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") @TestMetadata("builders")
public void testBuilders() throws Exception { public void testBuilders() throws Exception {
doTestMultiFileWithInlineCheck("compiler/testData/codegen/boxInline/builders"); doTestMultiFileWithInlineCheck("compiler/testData/codegen/boxInline/builders");
@@ -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); 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") @TestMetadata("builders")
public void testBuilders() throws Exception { public void testBuilders() throws Exception {
doBoxTestWithInlineCheck("compiler/testData/codegen/boxInline/builders"); doBoxTestWithInlineCheck("compiler/testData/codegen/boxInline/builders");