Skip to content

Commit bfadf92

Browse files
magarciaEPFLlrytz
authored andcommitted
BCodeOpt buidling block: UnreachableCode
This bytecode transformer removes code that is unreachable. Details in the documentation for scala.tools.nsc.backend.bcode.UnreachableCode
1 parent bc566a6 commit bfadf92

File tree

5 files changed

+212
-1
lines changed

5 files changed

+212
-1
lines changed

src/asm/scala/tools/asm/MethodWriter.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
* @author Eric Bruneton
3838
* @author Eugene Kuleshov
3939
*/
40-
class MethodWriter extends MethodVisitor {
40+
public class MethodWriter extends MethodVisitor {
4141

4242
/**
4343
* Pseudo access flag used to denote constructors.
@@ -223,11 +223,19 @@ class MethodWriter extends MethodVisitor {
223223
*/
224224
private int maxStack;
225225

226+
public int getMaxStack() {
227+
return maxStack;
228+
}
229+
226230
/**
227231
* Maximum number of local variables for this method.
228232
*/
229233
private int maxLocals;
230234

235+
public int getMaxLocals() {
236+
return maxLocals;
237+
}
238+
231239
/**
232240
* Number of local variables in the current stack map frame.
233241
*/
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* NSC -- new Scala compiler
2+
* Copyright 2005-2013 LAMP/EPFL
3+
* @author Martin Odersky
4+
*/
5+
6+
package scala.tools.nsc.backend.bcode;
7+
8+
import scala.tools.asm.tree.MethodNode;
9+
import scala.tools.asm.tree.AbstractInsnNode;
10+
import scala.tools.asm.tree.LabelNode;
11+
12+
import scala.tools.asm.tree.analysis.Analyzer;
13+
import scala.tools.asm.tree.analysis.AnalyzerException;
14+
import scala.tools.asm.tree.analysis.Frame;
15+
import scala.tools.asm.tree.analysis.BasicInterpreter;
16+
import scala.tools.asm.tree.analysis.BasicValue;
17+
18+
/**
19+
* Detects and removes unreachable code.
20+
*
21+
* Should be used last in a transformation chain, before stack map frames are computed.
22+
* The Java 6 verifier demands frames be available even for dead code.
23+
* Those frames are tricky to compute, http://asm.ow2.org/doc/developer-guide.html#deadcode
24+
* The problem is avoided altogether by not emitting unreachable code in the first place.
25+
*
26+
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
27+
* @version 1.0
28+
*
29+
*/
30+
public class UnreachableCode {
31+
32+
/** after transform() has run, this field records whether
33+
* at least one pass of this transformer modified something. */
34+
public boolean changed = false;
35+
36+
public void transform(final String owner, final MethodNode mnode) throws AnalyzerException {
37+
38+
changed = false;
39+
40+
Analyzer<BasicValue> a = new Analyzer<BasicValue>(new BasicInterpreter());
41+
a.analyze(owner, mnode);
42+
43+
Frame<BasicValue>[] frames = a.getFrames();
44+
AbstractInsnNode[] insns = mnode.instructions.toArray();
45+
46+
int i = 0;
47+
while (i < insns.length) {
48+
if (frames[i] == null &&
49+
insns[i] != null &&
50+
!(insns[i] instanceof LabelNode)) {
51+
mnode.instructions.remove(insns[i]);
52+
changed = true;
53+
}
54+
i += 1;
55+
}
56+
57+
}
58+
59+
}
60+

src/compiler/scala/tools/nsc/backend/bcode/Util.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
package scala.tools.nsc.backend.bcode;
88

9+
import scala.tools.asm.ClassWriter;
10+
import scala.tools.asm.MethodWriter;
11+
912
import scala.tools.asm.tree.AbstractInsnNode;
1013
import scala.tools.asm.tree.LabelNode;
14+
import scala.tools.asm.tree.MethodNode;
15+
1116
/**
1217
* Utilities.
1318
*
@@ -33,5 +38,28 @@ public static AbstractInsnNode insnLabelledBy(final LabelNode label) {
3338
return labelled;
3439
}
3540

41+
// ------------------------------------------------------------------------
42+
// maxLocals and maxStack
43+
// ------------------------------------------------------------------------
44+
45+
/**
46+
* In order to run Analyzer.analyze() on a method, its `maxLocals` should have been computed.
47+
*/
48+
public static boolean isReadyForAnalyzer(final MethodNode mnode) {
49+
return mnode.maxLocals != 0 || mnode.maxStack != 0;
50+
}
51+
52+
/**
53+
* In order to run Analyzer.analyze() on a method, its `maxLocals` should have been computed.
54+
*/
55+
public static void computeMaxLocalsMaxStack(final MethodNode mnode) {
56+
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
57+
String[] excs = mnode.exceptions.toArray(new String[0]);
58+
MethodWriter mw = (MethodWriter)cw.visitMethod(mnode.access, mnode.name, mnode.desc, mnode.signature, excs);
59+
mnode.accept(mw);
60+
mnode.maxLocals = mw.getMaxLocals();
61+
mnode.maxStack = mw.getMaxStack();
62+
}
63+
3664

3765
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import scala.tools.nsc.backend.bcode.UnreachableCode
2+
import scala.tools.nsc.backend.bcode.Util
3+
import scala.tools.partest.BytecodeTest
4+
import scala.tools.asm
5+
import scala.collection.JavaConverters._
6+
7+
import scala.tools.asm.Opcodes
8+
9+
object Test extends BytecodeTest {
10+
11+
def show: Unit = {
12+
val t = transformed(before())
13+
val isa = wrapped(t)
14+
val isb = wrapped(after())
15+
// unreachable code has been removed
16+
assert(isa == isb)
17+
}
18+
19+
def wrapped(m: asm.tree.MethodNode) = instructions.fromMethod(m)
20+
21+
def mkMethodNode = {
22+
new asm.tree.MethodNode(
23+
Opcodes.ACC_PUBLIC,
24+
"m",
25+
"()V",
26+
null, null
27+
)
28+
}
29+
30+
def before(): asm.tree.MethodNode = {
31+
val m = mkMethodNode
32+
m.visitInsn(Opcodes.ACONST_NULL)
33+
m.visitInsn(Opcodes.ATHROW)
34+
m.visitInsn(Opcodes.RETURN)
35+
36+
m
37+
}
38+
39+
def after(): asm.tree.MethodNode = {
40+
val m = mkMethodNode
41+
m.visitInsn(Opcodes.ACONST_NULL)
42+
m.visitInsn(Opcodes.ATHROW)
43+
44+
m
45+
}
46+
47+
def transformed(input: asm.tree.MethodNode): asm.tree.MethodNode = {
48+
val tr = new UnreachableCode
49+
Util.computeMaxLocalsMaxStack(input)
50+
do { tr.transform("C", input) } while (tr.changed)
51+
52+
input
53+
}
54+
55+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import scala.tools.nsc.backend.bcode.UnreachableCode
2+
import scala.tools.nsc.backend.bcode.Util
3+
import scala.tools.partest.BytecodeTest
4+
import scala.tools.asm
5+
import scala.collection.JavaConverters._
6+
7+
import scala.tools.asm.Opcodes
8+
9+
object Test extends BytecodeTest {
10+
11+
def show: Unit = {
12+
val t = transformed(before())
13+
val isa = wrapped(t)
14+
val isb = wrapped(after())
15+
// unreachable code has been removed
16+
assert(isa == isb)
17+
}
18+
19+
def wrapped(m: asm.tree.MethodNode) = instructions.fromMethod(m)
20+
21+
def mkMethodNode = {
22+
new asm.tree.MethodNode(
23+
Opcodes.ACC_PUBLIC,
24+
"m",
25+
"()V",
26+
null, null
27+
)
28+
}
29+
30+
def before(): asm.tree.MethodNode = {
31+
val m = mkMethodNode
32+
val L = new asm.Label
33+
m.visitJumpInsn(Opcodes.GOTO, L)
34+
m.visitInsn(Opcodes.ACONST_NULL)
35+
m.visitInsn(Opcodes.ATHROW)
36+
m.visitLabel(L)
37+
m.visitInsn(Opcodes.RETURN)
38+
39+
m
40+
}
41+
42+
def after(): asm.tree.MethodNode = {
43+
val m = mkMethodNode
44+
val L = new asm.Label
45+
m.visitJumpInsn(Opcodes.GOTO, L)
46+
m.visitLabel(L)
47+
m.visitInsn(Opcodes.RETURN)
48+
49+
m
50+
}
51+
52+
def transformed(input: asm.tree.MethodNode): asm.tree.MethodNode = {
53+
val tr = new UnreachableCode
54+
Util.computeMaxLocalsMaxStack(input)
55+
do { tr.transform("C", input) } while (tr.changed)
56+
57+
input
58+
}
59+
60+
}

0 commit comments

Comments
 (0)