Skip to content

Commit bc566a6

Browse files
magarciaEPFLlrytz
authored andcommitted
BCodeOpt buidling block: DanglingExcHandlers
This bytecode transformer removes those exception-entries (corresponding to try-catch-blocks) that are redundant. Details in the documentation for scala.tools.nsc.backend.bcode.DanglingExcHandlers
1 parent 72c1a52 commit bc566a6

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 java.util.HashSet;
9+
import java.util.Iterator;
10+
11+
import scala.tools.asm.Opcodes;
12+
import scala.tools.asm.tree.*;
13+
14+
/**
15+
* Removes redundant exception-entries,
16+
* ie those protecting a range whose only executable instructions are nop.
17+
* Any LineNumberNode or LabelNode in the range are left in place,
18+
* will be removed by follow-up `LabelsCleanup`
19+
*
20+
* Just the exception-entry is removed. The bytecode for the exception handler itself
21+
* is left in place, given that it *could* be reachable via normal-control flow
22+
* (although some JIT compilers will bailout compilation if that's the case).
23+
* In case the exception handler code turns out to be unreachable,
24+
* dead-code-elimination will take care of it.
25+
*
26+
* The bytecode for a finally-clause that protects an empty block is never removed by this transform.
27+
* In more detail, a finally-clause results in both an EH-version (reachable via exceptional control flow)
28+
* and another non-EH version (reachable via normal-control-flow).
29+
* None of those code blocks is removed by this transform,
30+
* they will be removed (if found to be unreachable) by dead-code elimination.
31+
*
32+
* See also Improving the Precision and Correctness of Exception Analysis in Soot,
33+
* http://www.sable.mcgill.ca/publications/techreports/#report2003-3
34+
*
35+
* Sidenote: on CLR, the "EH-version" of a finally-clause protecting an empty-block
36+
* can't be removed, because exceptions are handled specially there.
37+
* For example, code running within a finally block is never interrupted by Abort exceptions.
38+
*
39+
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
40+
* @version 1.0
41+
*
42+
*/
43+
public class DanglingExcHandlers {
44+
45+
/** after transform() has run, this field records whether
46+
* at least one pass of this transformer modified something. */
47+
public boolean changed = false;
48+
49+
public void transform(final MethodNode mn) {
50+
51+
changed = false;
52+
53+
if (mn.tryCatchBlocks == null) { return; }
54+
55+
Iterator<TryCatchBlockNode> tryIter = mn.tryCatchBlocks.iterator();
56+
while (tryIter.hasNext()) {
57+
TryCatchBlockNode tcb = tryIter.next();
58+
59+
assert mn.instructions.contains(tcb.start);
60+
assert mn.instructions.contains(tcb.end);
61+
62+
if (containsJustNopsOrGotos(tcb.start, tcb.end)) {
63+
changed = true;
64+
tryIter.remove();
65+
}
66+
}
67+
68+
}
69+
70+
/**
71+
* Any LineNumberNode or LabelNode or FrameNode will be skipped.
72+
*/
73+
public boolean containsJustNopsOrGotos(final LabelNode start, final LabelNode end) {
74+
assert start != null;
75+
assert end != null;
76+
77+
AbstractInsnNode current = start;
78+
while (current != end) {
79+
boolean skip = (current.getOpcode() == -1);
80+
boolean isNOP = (current.getOpcode() == Opcodes.NOP);
81+
boolean isGoto = (current.getOpcode() == Opcodes.GOTO);
82+
if (!skip && !isNOP && !isGoto) {
83+
return false;
84+
}
85+
current = current.getNext();
86+
}
87+
88+
return true;
89+
}
90+
91+
}
92+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import scala.tools.nsc.backend.bcode.DanglingExcHandlers
2+
import scala.tools.partest.BytecodeTest
3+
import scala.tools.asm
4+
import scala.collection.JavaConverters._
5+
6+
import scala.tools.asm.Opcodes
7+
8+
object Test extends BytecodeTest {
9+
10+
def show: Unit = {
11+
val b = before()
12+
assert(b.tryCatchBlocks.size() > 0)
13+
val t = transformed(b)
14+
val isa = wrapped(b)
15+
val isb = wrapped(t)
16+
// redundant exception-entry has been removed
17+
assert(t.tryCatchBlocks.size() == 0)
18+
assert(isa == isb)
19+
}
20+
21+
def wrapped(m: asm.tree.MethodNode) = instructions.fromMethod(m)
22+
23+
def mkMethodNode = {
24+
new asm.tree.MethodNode(
25+
Opcodes.ACC_PUBLIC,
26+
"m",
27+
"()V",
28+
null, null
29+
)
30+
}
31+
32+
def before(): asm.tree.MethodNode = {
33+
val m = mkMethodNode
34+
val L1 = new asm.Label
35+
val L2 = new asm.Label
36+
val L3 = new asm.Label
37+
// L1:
38+
m.visitLabel(L1)
39+
// nop
40+
m.visitInsn(Opcodes.NOP)
41+
// L2: --- normal control-flow
42+
m.visitLabel(L2)
43+
// return
44+
m.visitInsn(Opcodes.RETURN)
45+
// L3: --- exceptional control-flow
46+
m.visitLabel(L3)
47+
// return
48+
m.visitInsn(Opcodes.RETURN)
49+
50+
m.visitTryCatchBlock(L1, L2, L3, null)
51+
52+
m
53+
}
54+
55+
def transformed(input: asm.tree.MethodNode): asm.tree.MethodNode = {
56+
val tr = new DanglingExcHandlers
57+
do { tr.transform(input) } while (tr.changed)
58+
59+
input
60+
}
61+
62+
}

0 commit comments

Comments
 (0)