Skip to content

Commit

Permalink
Remove the lamba factory method. Use the static field or constructor …
Browse files Browse the repository at this point in the history
…directly.
  • Loading branch information
JakeWharton committed Jan 10, 2016
1 parent 20f959c commit 30ed485
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private Dummy2() {

@Test
public void capturing_lambda_classes_contain_no_unnecessary_methods() throws ClassNotFoundException {
Set<String> expected = new HashSet<>(Arrays.asList("lambdaFactory$", "run"));
Set<String> expected = new HashSet<>(Arrays.asList("run"));

Class<?> lambdaClass = Class.forName(Capturing.class.getName() + "$$Lambda$1");

Expand All @@ -67,7 +67,7 @@ private Capturing() {

@Test
public void non_capturing_lambda_classes_contain_no_unnecessary_methods() throws ClassNotFoundException {
Set<String> expected = new HashSet<>(Arrays.asList("lambdaFactory$", "run"));
Set<String> expected = new HashSet<>(Arrays.asList("run"));

Class<?> lambdaClass = Class.forName(NonCapturing.class.getName() + "$$Lambda$1");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ public class BackportLambdaClass extends ClassVisitor {
private static final String JAVA_LANG_OBJECT = "java/lang/Object";

private String lambdaClass;
private Type constructor;
private Handle implMethod;
private Handle accessMethod;
private LambdaFactoryMethod factoryMethod;

public BackportLambdaClass(ClassVisitor next) {
super(ASM5, next);
Expand All @@ -26,10 +24,8 @@ public BackportLambdaClass(ClassVisitor next) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
lambdaClass = name;
LambdaReifier.setLambdaClass(lambdaClass);
implMethod = LambdaReifier.getLambdaImplMethod();
accessMethod = LambdaReifier.getLambdaAccessMethod();
factoryMethod = LambdaReifier.getLambdaFactoryMethod();

if (superName.equals(LambdaNaming.MAGIC_LAMBDA_IMPL)) {
superName = JAVA_LANG_OBJECT;
Expand All @@ -40,12 +36,32 @@ public void visit(int version, int access, String name, String signature, String
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (name.equals("<init>")) {
constructor = Type.getMethodType(desc);
Type constructor = Type.getMethodType(desc);
int argumentCount = constructor.getArgumentTypes().length;

String referenceName;
String referenceDesc;
if (argumentCount == 0) {
// Add the static field and static initializer block to keep a singleton instance.
makeSingleton();

referenceName = SINGLETON_FIELD_NAME;
referenceDesc = singletonFieldDesc();
} else {
// Make the constructor visible
access &= ~ACC_PRIVATE;

referenceName = "<init>";
referenceDesc = desc;
}

LambdaClassInfo info = new LambdaClassInfo(lambdaClass, referenceName, referenceDesc, argumentCount);
LambdaReifier.setClassInfo(info);
}
if (LambdaNaming.isSerializationHook(access, name, desc)) {
return null; // remove serialization hooks; we serialize lambda instances as-is
}
if (LambdaNaming.isPlatformFactoryMethod(access, name, desc, factoryMethod.getDesc())) {
if (LambdaNaming.isPlatformFactoryMethod(access, name)) {
return null; // remove the JVM's factory method which will not be unused
}
MethodVisitor next = super.visitMethod(access, name, desc, signature, exceptions);
Expand All @@ -54,17 +70,8 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
return next;
}

@Override
public void visitEnd() {
if (isStateless()) {
makeSingleton();
}
generateFactoryMethod();
super.visitEnd();
}

private void makeSingleton() {
FieldVisitor fv = super.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL,
FieldVisitor fv = super.visitField(ACC_STATIC | ACC_FINAL,
SINGLETON_FIELD_NAME, singletonFieldDesc(), null, null);
fv.visitEnd();

Expand All @@ -79,39 +86,10 @@ private void makeSingleton() {
mv.visitEnd();
}

private void generateFactoryMethod() {
MethodVisitor mv = cv.visitMethod(ACC_PUBLIC | ACC_STATIC,
factoryMethod.getName(), factoryMethod.getDesc(), null, null);
mv.visitCode();

if (isStateless()) {
mv.visitFieldInsn(GETSTATIC, lambdaClass, SINGLETON_FIELD_NAME, singletonFieldDesc());
mv.visitInsn(ARETURN);

} else {
mv.visitTypeInsn(NEW, lambdaClass);
mv.visitInsn(DUP);
int varIndex = 0;
for (Type type : constructor.getArgumentTypes()) {
mv.visitVarInsn(type.getOpcode(ILOAD), varIndex);
varIndex += type.getSize();
}
mv.visitMethodInsn(INVOKESPECIAL, lambdaClass, "<init>", constructor.getDescriptor(), false);
mv.visitInsn(ARETURN);
}

mv.visitMaxs(-1, -1); // rely on ClassWriter.COMPUTE_MAXS
mv.visitEnd();
}

private String singletonFieldDesc() {
return "L" + lambdaClass + ";";
}

private boolean isStateless() {
return constructor.getArgumentTypes().length == 0;
}


private static class RemoveMagicLambdaConstructorCall extends MethodVisitor {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import net.orfjackal.retrolambda.util.Bytecode;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;

import java.lang.reflect.Field;
import java.util.*;
Expand Down Expand Up @@ -51,7 +52,8 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
if (LambdaNaming.isDeserializationHook(access, name, desc)) {
return null; // remove serialization hooks; we serialize lambda instances as-is
}
return new InvokeDynamicInsnConverter(super.visitMethod(access, name, desc, signature, exceptions));
MethodVisitor next = super.visitMethod(access, name, desc, signature, exceptions);
return new InvokeDynamicInsnConverter(access, name, desc, signature, exceptions, next);
}

Handle getLambdaAccessMethod(Handle implMethod) {
Expand Down Expand Up @@ -96,10 +98,13 @@ public void visitEnd() {
}


private class InvokeDynamicInsnConverter extends MethodVisitor {
private class InvokeDynamicInsnConverter extends MethodNode {
private final MethodVisitor next;

public InvokeDynamicInsnConverter(MethodVisitor next) {
super(ASM5, next);
public InvokeDynamicInsnConverter(int access, String name, String desc, String signature, String[] exceptions,
MethodVisitor next) {
super(ASM5, access, name, desc, signature, exceptions);
this.next = next;
}

@Override
Expand All @@ -116,9 +121,55 @@ private void backportLambda(String invokedName, Type invokedType, Handle bsm, Ob
Handle implMethod = (Handle) bsmArgs[1];
Handle accessMethod = getLambdaAccessMethod(implMethod);

LambdaFactoryMethod factory = LambdaReifier.reifyLambdaClass(implMethod, accessMethod,
LambdaClassInfo info = LambdaReifier.reifyLambdaClass(implMethod, accessMethod,
invoker, invokedName, invokedType, bsm, bsmArgs);
super.visitMethodInsn(INVOKESTATIC, factory.getOwner(), factory.getName(), factory.getDesc(), false);

if (info.isStateless()) {
super.visitFieldInsn(GETSTATIC, info.getLambdaClass(), info.getReferenceName(),
info.getReferenceDesc());
return;
}

// Capturing method references emit 3 useless bytecodes immediately before the invokedynamic
// instruction which we are currently visiting:
// 3: aload_0
// 4: dup
// 5: invokevirtual #3 // Method java/lang/Object.getClass:()Ljava/lang/Class;
// 8: pop
// 9: invokedynamic #4, 0 // InvokeDynamic #0:call:(Ljava/lang/String;)Ljava/util/concurrent/Callable;
// Attempt to match whether these (4, 5, and 8) are the three preceding bytecodes and remove them if so.
if (instructions.size() >= 4) {
AbstractInsnNode maybeDup = instructions.get(instructions.size() - 3);
AbstractInsnNode maybeInvoke = instructions.get(instructions.size() - 2);
AbstractInsnNode maybePop = instructions.get(instructions.size() - 1);
if (maybeDup.getOpcode() == DUP
&& maybeInvoke instanceof MethodInsnNode
&& maybeInvoke.getOpcode() == INVOKEVIRTUAL
&& ((MethodInsnNode) maybeInvoke).owner.equals("java/lang/Object")
&& ((MethodInsnNode) maybeInvoke).name.equals("getClass")
&& ((MethodInsnNode) maybeInvoke).desc.equals("()Ljava/lang/Class;")
&& maybePop.getOpcode() == POP) {
instructions.remove(maybePop);
instructions.remove(maybeInvoke);
instructions.remove(maybeDup);
}
}

// At this point we know that the lambda is capturing and will require load bytecodes for its arguments.
// Since these must occur after the new/dup bytecodes, find the first load instruction and place the
// new/dup bytecode before it.
AbstractInsnNode firstLoad = instructions.get(instructions.size() - info.getArgumentCount());
instructions.insertBefore(firstLoad, new TypeInsnNode(NEW, info.getLambdaClass()));
instructions.insertBefore(firstLoad, new InsnNode(DUP));

super.visitMethodInsn(INVOKESPECIAL, info.getLambdaClass(), info.getReferenceName(),
info.getReferenceDesc(), false);
}

@Override
public void visitEnd() {
// Forward all recorded instructions to the delegate method visitor.
accept(next);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright © 2013-2014 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0

package net.orfjackal.retrolambda.lambdas;

public class LambdaClassInfo {

private final String lambdaClass;
private final String referenceName;
private final String referenceDesc;
private final int argumentCount;

public LambdaClassInfo(String lambdaClass, String referenceName, String referenceDesc, int argumentCount) {
this.lambdaClass = lambdaClass;
this.referenceName = referenceName;
this.referenceDesc = referenceDesc;
this.argumentCount = argumentCount;
}

public boolean isStateless() {
return getArgumentCount() == 0;
}

public String getLambdaClass() {
return lambdaClass;
}

public String getReferenceName() {
return referenceName;
}

public String getReferenceDesc() {
return referenceDesc;
}

public int getArgumentCount() {
return argumentCount;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ public static boolean isDeserializationHook(int access, String name, String desc
&& Flags.hasFlag(access, ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC);
}

public static boolean isPlatformFactoryMethod(int access, String name, String desc, String targetDesc) {
public static boolean isPlatformFactoryMethod(int access, String name) {
return name.equals("get$Lambda")
&& desc.equals(targetDesc)
&& Flags.hasFlag(access, ACC_PRIVATE | ACC_STATIC);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,21 @@ public class LambdaReifier {
private static final BlockingDeque<Handle> currentLambdaImplMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Handle> currentLambdaAccessMethod = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Class<?>> currentInvoker = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<Type> currentInvokedType = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<String> currentLambdaClass = new LinkedBlockingDeque<>(1);
private static final BlockingDeque<LambdaClassInfo> currentClassInfo = new LinkedBlockingDeque<>(1);

public static LambdaFactoryMethod reifyLambdaClass(Handle lambdaImplMethod, Handle lambdaAccessMethod,
Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
public static LambdaClassInfo reifyLambdaClass(Handle lambdaImplMethod, Handle lambdaAccessMethod,
Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) {
try {
setLambdaImplMethod(lambdaImplMethod);
setLambdaAccessMethod(lambdaAccessMethod);
setInvoker(invoker);
setInvokedType(invokedType);

// Causes the lambda class to be loaded. Retrolambda's Java agent
// will detect it, save it to a file and tell us (via the globals
// in this class) that what the name of the lambda class was.
callBootstrapMethod(invoker, invokedName, invokedType, bsm, bsmArgs);

return getLambdaFactoryMethod();
return currentClassInfo.getFirst();

} catch (Throwable t) {
throw new RuntimeException("Failed to backport lambda or method reference: " + lambdaImplMethod, t);
Expand All @@ -57,12 +55,8 @@ private static void setInvoker(Class<?> lambdaInvoker) {
currentInvoker.push(lambdaInvoker);
}

private static void setInvokedType(Type invokedType) {
currentInvokedType.push(invokedType);
}

public static void setLambdaClass(String lambdaClass) {
currentLambdaClass.push(lambdaClass);
public static void setClassInfo(LambdaClassInfo info) {
currentClassInfo.push(info);
}

public static boolean isLambdaClassToReify(String className) {
Expand All @@ -80,18 +74,11 @@ public static Handle getLambdaAccessMethod() {
return currentLambdaAccessMethod.getFirst();
}

public static LambdaFactoryMethod getLambdaFactoryMethod() {
String lambdaClass = currentLambdaClass.getFirst();
Type invokedType = currentInvokedType.getFirst();
return new LambdaFactoryMethod(lambdaClass, invokedType);
}

private static void resetGlobals() {
currentLambdaImplMethod.clear();
currentLambdaAccessMethod.clear();
currentInvoker.clear();
currentInvokedType.clear();
currentLambdaClass.clear();
currentClassInfo.clear();
}

private static CallSite callBootstrapMethod(Class<?> invoker, String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) throws Throwable {
Expand Down

0 comments on commit 30ed485

Please sign in to comment.