forked from scala/scala
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds support for singleton closures, and for closure state minimization. The optimizations in question are intra-program, thus get applied at level -neo:o2 or higher. (1) Some anonymous closures depend only on apply() arguments, ie they capture no values. In such cases, code is emitted that avoids repeated closure allocations by keeping (in a static field added to that effect) a singleton-instance that is reused. (2) Lack of usages of a Late-Closure-Class (eg as a result of dead-code elimination) means the closure can be elided, along with its endpoint. This may lead to further tree-shaking (via UnusedPrivateDetector). (3) Minimization of closure-fields (in particular, outer) to those actually used. Field removal requires adapting both the closure allocation site, and the Late-Closure-Class itself. For example, removing the outer-pointer also requires turning the closure's endpoint into a static method. Closure allocations remain, but with smaller GC overhead.
- Loading branch information
1 parent
b65930d
commit 3400364
Showing
5 changed files
with
830 additions
and
5 deletions.
There are no files selected for viewing
173 changes: 173 additions & 0 deletions
173
src/compiler/scala/tools/nsc/backend/bcode/UnusedPrivateDetector.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/* NSC -- new Scala compiler | ||
* Copyright 2005-2012 LAMP/EPFL | ||
* @author Martin Odersky | ||
*/ | ||
|
||
package scala.tools.nsc.backend.bcode; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
import java.util.Queue; | ||
import java.util.Iterator; | ||
import java.util.LinkedList; | ||
|
||
import scala.tools.asm.Opcodes; | ||
|
||
import scala.tools.asm.tree.FieldNode; | ||
import scala.tools.asm.tree.MethodNode; | ||
import scala.tools.asm.tree.ClassNode; | ||
import scala.tools.asm.tree.AbstractInsnNode; | ||
import scala.tools.asm.tree.MethodInsnNode; | ||
import scala.tools.asm.tree.FieldInsnNode; | ||
|
||
/** | ||
* This class walker detects usages of private members of the ClassNode to transform() | ||
* (private members includes fields, methods, or constructors; be they static or instance). | ||
* Those usages are detected by visiting the class' public and protected methods and constructors | ||
* as well as any private methods or constructors transitively reachable. | ||
* | ||
* Those private members for which no usages are found can be elided, | ||
* but that decision is left to the invoker. | ||
* | ||
* If elided, any code using, say, reflection or invokedynamic or a methodhandle constant | ||
* referring to an (otherwise unused) private member, will fail after this transformer is run. | ||
* | ||
* Private methods and fields, as e.g. needed by java.io.Serializable or java.io.Externalizable, | ||
* are elided as any other. It's recommended not to run this transformer on classes extending those interfaces. | ||
* This transformer does not check that on its own. | ||
* | ||
* It's best to run this walker after constant propagation has run | ||
* (ie after redundant usages of private members have been replaced by constants). | ||
* | ||
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ | ||
* @version 1.0 | ||
*/ | ||
public class UnusedPrivateDetector implements Opcodes { | ||
|
||
public Set<FieldNode> elidableStaticFields = new HashSet<FieldNode>(); | ||
public Set<FieldNode> elidableInstanceFields = new HashSet<FieldNode>(); | ||
public Set<MethodNode> elidableStaticMethods = new HashSet<MethodNode>(); | ||
public Set<MethodNode> elidableInstanceMethods = new HashSet<MethodNode>(); | ||
|
||
/** | ||
* @return whether any private field, methods, or constructors is available for elision. | ||
* In the affirmative case, candidates for elision can be found in the fields of this class. | ||
*/ | ||
public boolean transform(ClassNode cnode) { | ||
|
||
elidableStaticFields.clear(); | ||
elidableInstanceFields.clear(); | ||
elidableStaticMethods.clear(); | ||
elidableInstanceMethods.clear(); | ||
|
||
Queue<MethodNode> toVisit = new LinkedList<MethodNode>(); | ||
for (MethodNode m : cnode.methods) { | ||
if (isPrivate(m.access) && m.isLiftedMethod) { | ||
(isStatic(m.access) ? elidableStaticMethods : elidableInstanceMethods).add(m); | ||
} else { | ||
toVisit.add(m); | ||
} | ||
} | ||
for (FieldNode f : cnode.fields) { | ||
if (isPrivate(f.access)) { | ||
(isStatic(f.access) ? elidableStaticFields : elidableInstanceFields).add(f); | ||
} | ||
} | ||
|
||
String cname = cnode.name; | ||
|
||
while (!toVisit.isEmpty()) { | ||
|
||
if (nothingToElide()) { | ||
return false; | ||
} | ||
|
||
Iterator<AbstractInsnNode> iter = toVisit.remove().instructions.iterator(); | ||
while (iter.hasNext()) { | ||
AbstractInsnNode insn = iter.next(); | ||
switch (insn.getType()) { | ||
|
||
case AbstractInsnNode.FIELD_INSN: | ||
FieldNode inUsePrivateField = lookupPrivate((FieldInsnNode)insn, cname); | ||
if (inUsePrivateField != null) { | ||
if (isStatic(inUsePrivateField.access)) { | ||
elidableStaticFields.remove(inUsePrivateField); | ||
} else { | ||
elidableInstanceFields.remove(inUsePrivateField); | ||
} | ||
} | ||
break; | ||
|
||
case AbstractInsnNode.METHOD_INSN: | ||
MethodNode inUsePrivateMethod = lookupPrivate((MethodInsnNode)insn, cname); | ||
if (inUsePrivateMethod != null) { | ||
if (isStatic(inUsePrivateMethod.access)) { | ||
if (elidableStaticMethods.contains(inUsePrivateMethod)) { | ||
toVisit.add(inUsePrivateMethod); | ||
} | ||
elidableStaticMethods.remove(inUsePrivateMethod); | ||
} else { | ||
if (elidableInstanceMethods.contains(inUsePrivateMethod)) { | ||
toVisit.add(inUsePrivateMethod); | ||
} | ||
elidableInstanceMethods.remove(inUsePrivateMethod); | ||
} | ||
} | ||
break; | ||
|
||
default: | ||
// TODO handle LDC of MethodHandle | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return !nothingToElide(); | ||
} | ||
|
||
private boolean nothingToElide() { | ||
|
||
return ( | ||
elidableStaticFields.isEmpty() && | ||
elidableInstanceFields.isEmpty() && | ||
elidableStaticMethods.isEmpty() && | ||
elidableInstanceMethods.isEmpty() | ||
); | ||
|
||
} | ||
|
||
private boolean isPrivate(int access) { | ||
return (access & ACC_PRIVATE) != 0; | ||
} | ||
|
||
private boolean isStatic(int access) { | ||
return (access & ACC_STATIC) != 0; | ||
} | ||
|
||
private FieldNode lookupPrivate(FieldInsnNode fi, String cname) { | ||
if (fi.owner.equals(cname)) { | ||
boolean isStatic = fi.getOpcode() == GETSTATIC || fi.getOpcode() == PUTSTATIC; | ||
Set<FieldNode> candidates = (isStatic ? elidableStaticFields : elidableInstanceFields); | ||
for(FieldNode fn : candidates) { | ||
if (fn.name.equals(fi.name)) { | ||
return fn; | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
private MethodNode lookupPrivate(MethodInsnNode mi, String cname) { | ||
if (mi.owner.equals(cname)) { | ||
boolean isStatic = mi.getOpcode() == INVOKESTATIC; | ||
Set<MethodNode> candidates = (isStatic ? elidableStaticMethods : elidableInstanceMethods); | ||
for(MethodNode mn : candidates) { | ||
if (mn.name.equals(mi.name) && mn.desc.equals(mi.desc)) { | ||
return mn; | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.