-
Notifications
You must be signed in to change notification settings - Fork 417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lambda inlining optimization #353
base: master
Are you sure you want to change the base?
Changes from 1 commit
274fd19
043be3a
c098ad8
7922f11
bb66672
573995e
e4ce1e0
70e5966
aabc5bd
f4a22fa
7b2146e
3573c19
025571c
845d044
13b38d5
fb9fe7d
cf74760
2090514
fb5eedb
6ae5fc3
e90c5c9
33aa83d
1d49b49
cb60c13
3d3bf8f
8c354ab
813be34
f155107
7d82d2d
047719c
5cc6748
1f5c85b
54233a5
ef37241
beeadd5
343df22
71213fe
42493c6
8cfd505
cff6565
5644a9d
48c52e5
badf492
61e1e98
00664b2
fca2cde
f8f1155
bcab91e
2710ff3
49bd302
55b4f0e
7613999
ee3e3ab
f1ba773
7d602c9
4d18370
2648338
b431cb7
11f21f1
028e8c4
0cc53cd
a440b45
7e652b3
607bf86
f69fd14
95b23ab
c5bca4a
23d4efe
7cdac26
03ae5c6
ad8b5ff
26d9850
a5ebb5e
71b63a4
7da8e01
751cc74
f4f84e4
9d080b2
b8fbcd0
3a866fc
a910309
0cf13a7
1ba33ba
ceea1b6
93d3bb6
ac7f42d
370704b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ | |
import proguard.optimize.LineNumberTrimmer; | ||
import proguard.optimize.Optimizer; | ||
import proguard.optimize.gson.GsonOptimizer; | ||
import proguard.optimize.inline.LambdaInliner; | ||
import proguard.optimize.peephole.LineNumberLinearizer; | ||
import proguard.pass.PassRunner; | ||
import proguard.preverify.PreverificationClearer; | ||
|
@@ -195,6 +196,11 @@ public void execute() throws Exception | |
new ListParser(new NameParser()).parse(configuration.optimizations) : | ||
new ConstantMatcher(true); | ||
|
||
if (configuration.lambdaInlining) | ||
{ | ||
inlineLambdas(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should probably happen inside the optimize method instead, and run once per optimization pass |
||
} | ||
|
||
if (configuration.optimize && | ||
filter.matches(Optimizer.LIBRARY_GSON)) | ||
{ | ||
|
@@ -462,6 +468,11 @@ private void optimizeGson() throws Exception | |
passRunner.run(new GsonOptimizer(configuration), appView); | ||
} | ||
|
||
private void inlineLambdas() throws Exception | ||
TimothyGeerkensGuardsquare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
LambdaInliner lambdaInliner = new LambdaInliner(); | ||
passRunner.run(lambdaInliner, appView); | ||
} | ||
|
||
/** | ||
* Performs the optimization step. | ||
|
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package proguard.optimize.inline; | ||
|
||
import proguard.classfile.Clazz; | ||
import proguard.classfile.Method; | ||
import proguard.classfile.ProgramClass; | ||
import proguard.classfile.attribute.CodeAttribute; | ||
import proguard.classfile.constant.AnyMethodrefConstant; | ||
import proguard.classfile.constant.visitor.ConstantVisitor; | ||
import proguard.classfile.editor.CodeAttributeEditor; | ||
import proguard.classfile.editor.ConstantPoolEditor; | ||
import proguard.classfile.instruction.ConstantInstruction; | ||
import proguard.classfile.instruction.Instruction; | ||
import proguard.classfile.instruction.visitor.InstructionVisitor; | ||
|
||
public class CallReplacer implements InstructionVisitor { | ||
TimothyGeerkensGuardsquare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private final CodeAttributeEditor codeAttributeEditor; | ||
private final Clazz methodOwnerClazz; | ||
private final Method oldMethod; | ||
private final Method newMethod; | ||
|
||
public CallReplacer(CodeAttributeEditor codeAttributeEditor, Clazz methodOwnerClazz, Method oldMethod, Method newMethod) { | ||
this.codeAttributeEditor = codeAttributeEditor; | ||
this.methodOwnerClazz = methodOwnerClazz; | ||
this.oldMethod = oldMethod; | ||
this.newMethod = newMethod; | ||
} | ||
|
||
@Override | ||
public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} | ||
|
||
@Override | ||
public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { | ||
if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { | ||
ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) clazz); | ||
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { | ||
@Override | ||
public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { | ||
if (anyMethodrefConstant.referencedMethod != null && anyMethodrefConstant.referencedMethod.equals(oldMethod)) { | ||
int newMethodIndex = constantPoolEditor.addMethodrefConstant(methodOwnerClazz, newMethod); | ||
codeAttributeEditor.reset(codeAttribute.u4codeLength); | ||
codeAttributeEditor.replaceInstruction(offset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, newMethodIndex)); | ||
codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package proguard.optimize.inline; | ||
|
||
public class CannotInlineException extends RuntimeException { | ||
public CannotInlineException(String reason) { | ||
super(reason); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package proguard.optimize.inline; | ||
|
||
import proguard.classfile.Clazz; | ||
import proguard.classfile.Method; | ||
import proguard.classfile.attribute.CodeAttribute; | ||
import proguard.classfile.constant.AnyMethodrefConstant; | ||
import proguard.classfile.constant.NameAndTypeConstant; | ||
import proguard.classfile.constant.visitor.ConstantVisitor; | ||
import proguard.classfile.editor.CodeAttributeEditor; | ||
import proguard.classfile.instruction.ConstantInstruction; | ||
import proguard.classfile.instruction.Instruction; | ||
import proguard.classfile.instruction.InstructionFactory; | ||
import proguard.classfile.instruction.VariableInstruction; | ||
import proguard.classfile.instruction.visitor.InstructionVisitor; | ||
import proguard.classfile.visitor.ClassPrinter; | ||
|
||
import java.util.*; | ||
|
||
public class CastRemover implements InstructionVisitor { | ||
TimothyGeerkensGuardsquare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private final CodeAttributeEditor codeAttributeEditor; | ||
private List<Integer> keepList; | ||
private int currentIndex; | ||
|
||
public CastRemover(CodeAttributeEditor codeAttributeEditor, List<Integer> keepList) { | ||
this.codeAttributeEditor = codeAttributeEditor; | ||
this.currentIndex = 0; | ||
this.keepList = keepList; | ||
} | ||
|
||
public CastRemover(CodeAttributeEditor codeAttributeEditor) { | ||
this(codeAttributeEditor, new ArrayList<>()); | ||
} | ||
|
||
@Override | ||
public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { | ||
TimothyGeerkensGuardsquare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Set<String> castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); | ||
if (constantInstruction.opcode == Instruction.OP_CHECKCAST) { | ||
System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); | ||
codeAttributeEditor.deleteInstruction(offset); | ||
} else if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { | ||
if (getInvokedMethodName(clazz, constantInstruction).equals("valueOf")) { | ||
if (!keepList.contains(currentIndex)) { | ||
System.out.print("Removing "); InstructionFactory.create(codeAttribute.code, offset).accept(clazz, method, codeAttribute, offset, new ClassPrinter()); | ||
codeAttributeEditor.deleteInstruction(offset); | ||
} | ||
|
||
currentIndex++; | ||
} | ||
} else if (constantInstruction.opcode == Instruction.OP_INVOKEVIRTUAL) { | ||
System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); | ||
if (castingMethodNames.contains(getInvokedMethodName(clazz, constantInstruction))) { | ||
codeAttributeEditor.deleteInstruction(offset); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { | ||
} | ||
|
||
private String getInvokedMethodName(Clazz clazz, ConstantInstruction constantInstruction) { | ||
final String[] invokedMethodName = new String[1]; | ||
clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { | ||
@Override public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { | ||
clazz.constantPoolEntryAccept(anyMethodrefConstant.u2nameAndTypeIndex, new ConstantVisitor() { | ||
@Override | ||
public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) { | ||
invokedMethodName[0] = nameAndTypeConstant.getName(clazz); | ||
} | ||
}); | ||
} | ||
}); | ||
return invokedMethodName[0]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package proguard.optimize.inline; | ||
|
||
import proguard.classfile.*; | ||
import proguard.classfile.editor.AttributeAdder; | ||
import proguard.classfile.editor.ClassBuilder; | ||
import proguard.classfile.editor.ClassEditor; | ||
import proguard.obfuscate.NameFactory; | ||
import proguard.obfuscate.SimpleNameFactory; | ||
import proguard.obfuscate.UniqueMemberNameFactory; | ||
import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; | ||
|
||
import java.util.function.Function; | ||
|
||
public class DescriptorModifier { | ||
private final ClassBuilder classBuilder; | ||
private final NameFactory nameFactory; | ||
private final Clazz clazz; | ||
|
||
DescriptorModifier(Clazz clazz) { | ||
classBuilder = new ClassBuilder((ProgramClass) clazz); | ||
nameFactory = new UniqueMemberNameFactory(new SimpleNameFactory(), clazz); | ||
this.clazz = clazz; | ||
} | ||
|
||
public ProgramMethod modify(ProgramMethod sourceMethod, Function<String, String> modifier) { | ||
return modify(sourceMethod, modifier, false); | ||
} | ||
|
||
public ProgramMethod modify(ProgramMethod sourceMethod, Function<String, String> modifier, boolean removeOriginal) { | ||
ProgramMethod newMethod = classBuilder.addAndReturnMethod( | ||
sourceMethod.u2accessFlags, | ||
nameFactory.nextName(), | ||
modifier.apply(sourceMethod.getDescriptor(clazz)) | ||
); | ||
|
||
sourceMethod.attributesAccept((ProgramClass) clazz, | ||
new AttributeAdder((ProgramClass) clazz, newMethod, true)); | ||
|
||
if (removeOriginal) | ||
removeOriginal(sourceMethod); | ||
|
||
newMethod.accept(clazz, new ProgramMemberOptimizationInfoSetter()); | ||
return newMethod; | ||
} | ||
|
||
private void removeOriginal(ProgramMethod method) { | ||
ClassEditor classEditor = new ClassEditor((ProgramClass) clazz); | ||
classEditor.removeMethod(method); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package proguard.optimize.inline; | ||
|
||
import proguard.classfile.instruction.Instruction; | ||
|
||
import java.util.Objects; | ||
|
||
public final class InstructionAtOffset { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it not more safe to use the CodeAttribute and Instruction for this? Using existing data types, classes,... helps to make use of the existing infrastructure which makes a lot of stuff easier. The infrastructure helps by performing updates on the data structures and performing validity checks. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that if we only store a CodeAttribute and an Instruction we can't get the correct offset of the instruction reliably. |
||
private final Instruction instruction; | ||
private final int offset; | ||
|
||
InstructionAtOffset(Instruction instruction, int offset) { | ||
this.instruction = instruction; | ||
this.offset = offset; | ||
} | ||
|
||
public Instruction instruction() { | ||
return instruction; | ||
} | ||
|
||
public int offset() { | ||
return offset; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object obj) { | ||
if (obj == this) return true; | ||
if (obj == null || obj.getClass() != this.getClass()) return false; | ||
InstructionAtOffset that = (InstructionAtOffset) obj; | ||
return Objects.equals(this.instruction, that.instruction) && | ||
this.offset == that.offset; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash(instruction, offset); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "InstructionAtOffset[" + | ||
"instruction=" + instruction + ", " + | ||
"offset=" + offset + ']'; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package proguard.optimize.inline; | ||
|
||
import proguard.classfile.*; | ||
import proguard.classfile.constant.ClassConstant; | ||
import proguard.classfile.constant.FieldrefConstant; | ||
import proguard.classfile.constant.visitor.ConstantVisitor; | ||
import proguard.classfile.visitor.AllMethodVisitor; | ||
import proguard.classfile.visitor.MemberVisitor; | ||
|
||
public class LambdaImplementationVisitor implements ConstantVisitor, MemberVisitor { | ||
private final InvokeMethodVisitor invokeMethodVisitor; | ||
private Clazz lambdaImplementationClass; | ||
public LambdaImplementationVisitor(InvokeMethodVisitor invokeMethodHandler) { | ||
this.invokeMethodVisitor = invokeMethodHandler; | ||
} | ||
|
||
@Override | ||
public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { | ||
lambdaImplementationClass = fieldrefConstant.referencedClass; | ||
fieldrefConstant.referencedClass.interfaceConstantsAccept(this); | ||
} | ||
|
||
@Override | ||
public void visitClassConstant(Clazz clazz, ClassConstant referencedClassConstant) { | ||
referencedClassConstant.referencedClass.methodsAccept(this); | ||
} | ||
|
||
@Override | ||
public void visitProgramMethod(ProgramClass interfaceClass, ProgramMethod programMethod) | ||
{ | ||
getLambdaImplementation(interfaceClass, programMethod); | ||
} | ||
|
||
@Override | ||
public void visitLibraryMethod(LibraryClass interfaceClass, LibraryMethod libraryMethod) { | ||
getLambdaImplementation(interfaceClass, libraryMethod); | ||
} | ||
|
||
private void getLambdaImplementation(Clazz interfaceClazz, Method method) { | ||
String descriptor = method.getDescriptor(interfaceClazz); | ||
lambdaImplementationClass.methodAccept("invoke", descriptor, new MemberVisitor() { | ||
@Override | ||
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { | ||
TimothyGeerkensGuardsquare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
System.out.println("Descriptor " + programMethod.getDescriptor(programClass)); | ||
String descriptor = programMethod.getDescriptor(programClass); | ||
lambdaImplementationClass.accept(new AllMethodVisitor(new MemberVisitor() { | ||
@Override | ||
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { | ||
if (programMethod.getName(programClass).equals("invoke") && programMethod.u2accessFlags == (AccessConstants.PUBLIC | AccessConstants.FINAL)) { | ||
lambdaImplementationClass.methodAccept("invoke", programMethod.getDescriptor(programClass), new MemberVisitor() { | ||
@Override | ||
public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { | ||
invokeMethodVisitor.visitInvokeMethod(programClass, programMethod, interfaceClazz, descriptor); | ||
} | ||
}); | ||
} | ||
} | ||
})); | ||
} | ||
}); | ||
} | ||
|
||
public interface InvokeMethodVisitor { | ||
void visitInvokeMethod(ProgramClass programClass, ProgramMethod programMethod, Clazz interfaceClass, String bridgeDescriptor); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether we would like to have this enabled by default but rather have this as an opt-in functionality. For opt-in I believe it should be false here.