Mirror of the JODE repository
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
jode/jode/src/net/sf/jode/obfuscator/modules/LocalOptimizer.java

789 lines
22 KiB

/* LocalOptimizer Copyright (C) 1999-2002 Jochen Hoenicke.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
* $Id$
*/
package net.sf.jode.obfuscator.modules;
import java.util.*;
import net.sf.jode.bytecode.*;
import net.sf.jode.obfuscator.*;
import net.sf.jode.GlobalOptions;
///#def COLLECTIONS java.util
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.ListIterator;
///#enddef
/**
* This class takes some bytecode and tries to minimize the number
* of locals used. It will also remove unnecessary stores. <br>
*
* This class can only work on verified code. There should also be no
* dead code, since the verifier doesn't check that dead code behaves
* okay. <br>
*
* This is done in two phases. First we determine which locals are
* the same, and which locals have a overlapping life time. In the
* second phase we will then redistribute the locals with a coloring
* graph algorithm. <br>
*
* The idea for the first phase is: For each read we follow the
* instruction flow backward to find the corresponding writes. We can
* also merge with another control flow that has a different read, in
* this case we merge with that read, too. <br>
*
* The tricky part is the subroutine handling. We follow the local
* that is used in a ret and find the corresponding jsr target (there
* must be only one, if the verifier should accept this class). While
* we do this we remember in the info of the ret, which locals are
* used in that subroutine. <br>
*
* When we know the jsr target<->ret correlation, we promote from the
* nextByAddr of every jsr the locals that are accessed by the
* subroutine to the corresponding ret and the others to the jsr. Also
* we will promote all reads from the jsr targets to the jsr. <br>
*
* If you think this might be to complicated, keep in mind that jsr's
* are not only left by the ret instructions, but also "spontanously"
* (by not reading the return address again). <br>
*/
public class LocalOptimizer implements Opcodes, CodeTransformer {
/**
* This class keeps track for each local variables:
* <ul>
* <li>which name and type this local has
* (if there is a local variable table),</li>
* <li>which other locals must be the same,</li>
* <li>which other locals have an intersecting life time.</li>
* </ul>
*/
class LocalInfo {
LocalInfo shadow = null;
public LocalInfo getReal() {
LocalInfo real = this;
while (real.shadow != null)
real = real.shadow;
return real;
}
String name;
String type;
Vector conflictingLocals = new Vector();
String id; // for debugging purposes only.
int size;
int newSlot = -1;
LocalInfo() {
}
void conflictsWith(LocalInfo l) {
if (shadow != null) {
getReal().conflictsWith(l);
} else {
l = l.getReal();
if (!conflictingLocals.contains(l)) {
conflictingLocals.addElement(l);
l.conflictingLocals.addElement(this);
}
}
}
void combineInto(LocalInfo l) {
if (shadow != null) {
getReal().combineInto(l);
return;
}
l = l.getReal();
if (this == l)
return;
shadow = l;
if (l.name == null) {
l.name = name;
l.type = type;
}
if (id.compareTo(l.id) < 0)
l.id = id;
}
void generateID(int blockNr, int instrNr) {
char[] space = new char[5];
space[0] = (char) ('0' + blockNr / 10);
space[1] = (char) ('0' + blockNr % 10);
space[2] = (char) ('a' + instrNr / (26*26));
space[3] = (char) ('a' + (instrNr / 26) % 26);
space[4] = (char) ('a' + instrNr % 26);
id = new String(space);
}
public String toString() {
return id;
}
}
private static class TodoQueue {
BlockInfo first = null;
BlockInfo last = null;
public void add(BlockInfo info) {
if (info.nextTodo == null && info != last) {
/* only enqueue if not already on queue */
info.nextTodo = first;
first = info;
if (first == null)
last = info;
}
}
public boolean isEmpty() {
return first == null;
}
public BlockInfo remove() {
BlockInfo result = first;
first = result.nextTodo;
result.nextTodo = null;
if (first == null)
last = null;
return result;
}
}
BasicBlocks bb;
TodoQueue changedInfos;
InstrInfo firstInfo;
LocalInfo[] paramLocals;
BlockInfo[] blockInfos;
int maxlocals;
/**
* This class contains information for each basic block.
*/
class BlockInfo {
/**
* The next Instruction in the todo list; null, if this block
* info is not on todo list, or if it is the last one.
*/
BlockInfo nextTodo;
/**
* The local infos for each Instruction. The index is the
* instruction number.
*/
LocalInfo[] instrLocals;
/**
* The LocalInfo, whose values are read from a previous block.
* Index is the slot number.
*/
LocalInfo[] ins;
/**
* The LocalInfo, written in this block.
* Index is the slot number.
*/
LocalInfo[] gens;
/**
* The predecessors for this block.
*/
Collection preds = new ArrayList();
/**
* The tryBlocks, for which this block is the catcher.
*/
Collection tryBlocks = new ArrayList();
/**
* For each slot, this contains the InstrInfo of one of the
* next Instruction, that may read from that slot, without
* prior writing.
*/
InstrInfo[] nextReads;
/**
* This only has a value for blocks countaining a ret. In
* that case this bitset contains all locals, that may be used
* between jsr and ret.
*/
BitSet usedBySub;
/**
* For each slot if get() is true, no instruction may read
* this slot, since it may contain different locals, depending
* on flow.
*/
LocalInfo[] lifeLocals;
/**
* If instruction is the destination of a jsr, this contains
* the single allowed ret instruction info, or null if there
* is no ret at all (or not yet detected).
*/
InstrInfo retInfo;
/**
* If this instruction is a ret, this contains the single
* allowed jsr target to which this ret belongs.
*/
BlockInfo jsrTargetInfo;
/**
* The underlying basic block.
*/
Block block;
public BlockInfo(int blockNr, Block block) {
this.block = block;
ins = new LocalInfo[bb.getMaxLocals()];
gens = new LocalInfo[bb.getMaxLocals()];
Instruction[] instrs = block.getInstructions();
instrLocals = new LocalInfo[instrs.length];
for (int instrNr = 0; instrNr < instrs.length; instrNr++) {
Instruction instr = instrs[instrNr];
if (instr.hasLocal()) {
int slot = instr.getLocalSlot();
LocalInfo local = new LocalInfo();
instrLocals[instrNr] = local;
LocalVariableInfo lvi = instr.getLocalInfo();
local.name = lvi.getName();
local.type = lvi.getType();
local.size = 1;
local.generateID(blockNr, instrNr);
switch (instr.getOpcode()) {
case opc_lload: case opc_dload:
local.size = 2;
/* fall through */
case opc_iload: case opc_fload: case opc_aload:
case opc_iinc:
/* this is a load instruction */
if (gens[slot] == null) {
ins[slot] = local;
gens[slot] = local;
changedInfos.add(this);
} else {
gens[slot].combineInto(local);
}
break;
case opc_ret:
/* this is a ret instruction */
usedBySub = new BitSet();
if (gens[slot] == null) {
ins[slot] = local;
gens[slot] = local;
changedInfos.add(this);
} else {
gens[slot].combineInto(local);
}
break;
case opc_lstore: case opc_dstore:
local.size = 2;
/* fall through */
case opc_istore: case opc_fstore: case opc_astore:
gens[slot] = local;
break;
default:
throw new InternalError
("Illegal opcode for SlotInstruction");
}
}
}
}
void promoteIn(int slot, LocalInfo local) {
if (gens[slot] == null) {
changedInfos.add(this);
gens[slot] = local;
ins[slot] = local;
} else {
gens[slot].combineInto(local);
}
}
void promoteInForTry(int slot, LocalInfo local) {
if (ins[slot] == null) {
gens[slot] = local;
ins[slot] = local;
changedInfos.add(this);
} else
ins[slot].combineInto(local);
if (gens[slot] != null) {
gens[slot].combineInto(local);
for (int i=0; i< instrLocals.length; i++) {
if (instrLocals[i] != null
&& block.getInstructions()[i].getLocalSlot() == slot)
instrLocals[i].combineInto(local);
}
}
}
public void promoteIns() {
for (int i=0; i < ins.length; i++) {
if (ins[i] != null) {
for (Iterator iter = preds.iterator(); iter.hasNext();) {
BlockInfo pred = (BlockInfo) iter.next();
pred.promoteIn(i, ins[i]);
}
for (Iterator iter = tryBlocks.iterator();
iter.hasNext();) {
BlockInfo pred = (BlockInfo) iter.next();
pred.promoteInForTry(i, ins[i]);
}
}
}
// if (prevInstr.getOpcode() == opc_jsr) {
// /* Prev instr is a jsr, promote reads to the
// * corresponding ret.
// */
// InstrInfo jsrInfo =
// (InstrInfo) instrInfos.get(prevInstr.getSingleSucc());
// if (jsrInfo.retInfo != null) {
// /* Now promote reads that are modified by the
// * subroutine to the ret, and those that are not
// * to the jsr instruction.
// */
// promoteReads(info, jsrInfo.retInfo.instr,
// jsrInfo.retInfo.usedBySub, false);
// promoteReads(info, prevInstr,
// jsrInfo.retInfo.usedBySub, true);
// }
// }
}
public void generateConflicts() {
LocalInfo[] active = (LocalInfo[]) ins.clone();
Instruction[] instrs = block.getInstructions();
for (int instrNr = 0; instrNr < instrs.length; instrNr++) {
Instruction instr = instrs[instrNr];
if (instr.isStore()) {
/* This is a store. It conflicts with every local, which
* is active at this point.
*/
for (int i=0; i < maxlocals; i++) {
if (i != instr.getLocalSlot()
&& active[i] != null)
instrLocals[instrNr].conflictsWith(active[i]);
if (info.nextInfo.nextReads[i] != null
&& info.nextInfo.nextReads[i].jsrTargetInfo != null) {
Instruction[] jsrs = info.nextInfo.nextReads[i]
.jsrTargetInfo.instr.getPreds();
for (int j=0; j< jsrs.length; j++) {
InstrInfo jsrInfo
= (InstrInfo) instrInfos.get(jsrs[j]);
for (int k=0; k < maxlocals; k++) {
if (!info.nextInfo.nextReads[i].usedBySub
.get(k)
&& jsrInfo.nextReads[k] != null)
info.local.conflictsWith
(jsrInfo.nextReads[k].local);
}
}
}
}
}
}
}
}
public LocalOptimizer() {
}
/**
* Merges the given vector to a new vector. Both vectors may
* be null in which case they are interpreted as empty vectors.
* The vectors will never changed, but the result may be one
* of the given vectors.
*/
Vector merge(Vector v1, Vector v2) {
if (v1 == null || v1.isEmpty())
return v2;
if (v2 == null || v2.isEmpty())
return v1;
Vector result = (Vector) v1.clone();
Enumeration enumeration = v2.elements();
while (enumeration.hasMoreElements()) {
Object elem = enumeration.nextElement();
if (!result.contains(elem))
result.addElement(elem);
}
return result;
}
public void calcLocalInfo() {
maxlocals = bb.getMaxLocals();
Block[] blocks = bb.getBlocks();
/* Initialize paramLocals */
{
String methodType = bb.getMethodInfo().getType();
int paramCount = (bb.getMethodInfo().isStatic() ? 0 : 1)
+ TypeSignature.getArgumentSize(methodType);
paramLocals = new LocalInfo[paramCount];
int slot = 0;
if (!bb.getMethodInfo().isStatic()) {
LocalInfo local = new LocalInfo();
LocalVariableInfo lvi = bb.getParamInfo(slot);
local.type = "L" + (bb.getMethodInfo().getClazzInfo()
.getName().replace('.', '/'))+";";
if (local.type.equals(lvi.getType()))
local.name = lvi.getName();
local.size = 1;
local.id = " this";
paramLocals[slot++] = local;
}
int pos = 1;
while (pos < methodType.length()
&& methodType.charAt(pos) != ')') {
int start = pos;
pos = TypeSignature.skipType(methodType, pos);
LocalInfo local = new LocalInfo();
LocalVariableInfo lvi = bb.getParamInfo(slot);
local.type = methodType.substring(start, pos);
if (local.type.equals(lvi.getType()))
local.name = lvi.getName();
local.size = TypeSignature.getTypeSize(local.type);
local.id = " parm";
paramLocals[slot] = local;
slot += local.size;
}
}
/* Initialize the InstrInfos and LocalInfos
*/
changedInfos = new TodoQueue();
blockInfos = new BlockInfo[blocks.length];
for (int i=0; i< blocks.length; i++)
blockInfos[i] = new BlockInfo(i, blocks[i]);
for (int i=0; i< blocks.length; i++) {
int[] succs = blocks[i].getSuccs();
for (int j=0; j< succs.length; j++) {
if (succs[j] >= 0)
blockInfos[succs[j]].preds.add(blockInfos[i]);
}
BasicBlocks.Handler[] handlers = blocks[i].getCatcher();
for (int j=0; j< handlers.length; j++) {
blockInfos[handlers[j].getCatcher()]
.tryBlocks.add(blockInfos[i]);
}
}
/* find out which locals are the same.
*/
while (!changedInfos.isEmpty()) {
BlockInfo info = changedInfos.remove();
info.promoteIns();
if (instr.getPreds() != null) {
for (int i = 0; i < instr.getPreds().length; i++) {
Instruction predInstr = instr.getPreds()[i];
if (instr.getPreds()[i].getOpcode() == opc_jsr) {
/* This is the target of a jsr instr.
*/
if (info.instr.getOpcode() != opc_astore) {
/* XXX Grrr, the bytecode verifier doesn't
* test if a jsr starts with astore. So
* it is possible to do something else
* before putting the ret address into a
* local. */
throw new InternalError("Non standard jsr");
}
InstrInfo retInfo = info.nextInfo.nextReads
[info.instr.getLocalSlot()];
if (retInfo != null) {
if (retInfo.instr.getOpcode() != opc_ret)
throw new InternalError
("reading return address");
info.retInfo = retInfo;
retInfo.jsrTargetInfo = info;
/* Now promote reads from the instruction
* after the jsr to the ret instruction if
* they are modified by the subroutine,
* and to the jsr instruction otherwise.
*/
Instruction nextInstr = predInstr.getNextByAddr();
InstrInfo nextInfo
= (InstrInfo) instrInfos.get(nextInstr);
promoteReads(nextInfo, retInfo.instr,
retInfo.usedBySub, false);
promoteReads(nextInfo, predInstr,
retInfo.usedBySub, true);
}
}
promoteReads(info, instr.getPreds()[i]);
}
}
}
changedInfos = null;
/* Now merge with the parameters
* The params should be the locals in firstInfo.nextReads
*/
int startBlock = bb.getStartBlock();
if (startBlock >= 0) {
LocalInfo[] ins = blockInfos[startBlock].ins;
for (int i=0; i< paramLocals.length; i++) {
if (ins[i] != null)
paramLocals[i].combineInto(ins[i]);
}
}
}
public void stripLocals() {
Block[] blocks = bb.getBlocks();
for (int i = 0; i < blocks.length; i++) {
Instruction[] instrs = blocks[i].getInstructions();
for (int j = 0; j < instrs.length; j++) {
Instruction instr = instrs[j];
if (info.local != null
&& info.local.usingBlocks.size() == 1) {
/* If this is a store, whose value is never read; it can
* be removed, i.e replaced by a pop.
*/
switch (instr.getOpcode()) {
case opc_istore:
case opc_fstore:
case opc_astore:
instrs[j] = Instruction.forOpcode(opc_pop);
break;
case opc_lstore:
case opc_dstore:
instrs[j] = Instruction.forOpcode(opc_pop2);
break;
default:
}
}
}
}
}
void distributeLocals(Vector locals) {
if (locals.size() == 0)
return;
/* Find the local with the least conflicts. */
int min = Integer.MAX_VALUE;
LocalInfo bestLocal = null;
Enumeration enumeration = locals.elements();
while (enumeration.hasMoreElements()) {
LocalInfo li = (LocalInfo) enumeration.nextElement();
int conflicts = 0;
Enumeration conflenum = li.conflictingLocals.elements();
while (conflenum.hasMoreElements()) {
if (((LocalInfo)conflenum.nextElement()).newSlot != -2)
conflicts++;
}
if (conflicts < min) {
min = conflicts;
bestLocal = li;
}
}
/* Mark the local as taken */
locals.removeElement(bestLocal);
bestLocal.newSlot = -2;
/* Now distribute the remaining locals recursively. */
distributeLocals(locals);
/* Finally find a new slot */
next_slot:
for (int slot = 0; ; slot++) {
Enumeration conflenum = bestLocal.conflictingLocals.elements();
while (conflenum.hasMoreElements()) {
LocalInfo conflLocal = (LocalInfo)conflenum.nextElement();
if (bestLocal.size == 2 && conflLocal.newSlot == slot+1) {
slot++;
continue next_slot;
}
if (conflLocal.size == 2 && conflLocal.newSlot+1 == slot)
continue next_slot;
if (conflLocal.newSlot == slot) {
if (conflLocal.size == 2)
slot++;
continue next_slot;
}
}
bestLocal.newSlot = slot;
break;
}
}
public void distributeLocals() {
/* give locals new slots. This is a graph coloring
* algorithm (the optimal solution is NP complete, but this
* should be a good approximation).
*/
/* first give the params the same slot as they had before.
*/
for (int i=0; i<paramLocals.length; i++)
if (paramLocals[i] != null)
paramLocals[i].newSlot = i;
/* Now calculate the conflict settings.
*/
for (int blockNr = 0; blockNr < blockInfos.length; blockNr++) {
blockInfos[blockNr].generateConflicts();
}
/* Now put the locals that need a color into a vector.
*/
Vector locals = new Vector();
for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) {
if (info.local != null
&& info.local.newSlot == -1
&& !locals.contains(info.local))
locals.addElement(info.local);
}
/* Now distribute slots recursive.
*/
distributeLocals(locals);
/* Update the instructions and calculate new maxlocals.
*/
maxlocals = paramLocals.length;
for (InstrInfo info = firstInfo; info != null; info = info.nextInfo) {
if (info.local != null) {
if (info.local.newSlot+info.local.size > maxlocals)
maxlocals = info.local.newSlot + info.local.size;
info.instr.setLocalSlot(info.local.newSlot);
}
}
bc.setMaxLocals(maxlocals);
}
private InstrInfo CONFLICT = new InstrInfo();
boolean promoteLifeLocals(LocalInfo[] newLife, InstrInfo nextInfo) {
if (nextInfo.lifeLocals == null) {
nextInfo.lifeLocals = (LocalInfo[]) newLife.clone();
return true;
}
boolean changed = false;
for (int i=0; i< maxlocals; i++) {
LocalInfo local = nextInfo.lifeLocals[i];
if (local == null)
/* A conflict has already happened, or this slot
* may not have been initialized. */
continue;
local = local.getReal();
LocalInfo newLocal = newLife[i];
if (newLocal != null)
newLocal = newLocal.getReal();
if (local != newLocal) {
nextInfo.lifeLocals[i] = null;
changed = true;
}
}
return changed;
}
public void dumpLocals() {
Vector locals = new Vector();
for (int blockNr=0; blockNr < blockInfos.length; blockNr++) {
BlockInfo info = blockInfos[blockNr];
GlobalOptions.err.print("ins: [");
for (int i=0; i<maxlocals; i++) {
if (info.ins[i] == null)
GlobalOptions.err.print("-----,");
else
GlobalOptions.err.print(info.ins[i].toString()+",");
}
GlobalOptions.err.println("]");
blockInfos.block.dumpCode(GlobalOptions.err);
GlobalOptions.err.print("gens: [");
for (int i=0; i<maxlocals; i++) {
if (info.ins[i] == null)
GlobalOptions.err.print("-----,");
else
GlobalOptions.err.print(info.ins[i].toString()+",");
}
GlobalOptions.err.println("]");
if (info.usedBySub != null)
GlobalOptions.err.println(" usedBySub: "+info.usedBySub);
if (info.retInfo != null)
GlobalOptions.err.println(" ret info: "
+info.retInfo.instr.getAddr());
if (info.jsrTargetInfo != null)
GlobalOptions.err.println(" jsr info: "
+info.jsrTargetInfo.instr.getAddr());
if (info.local != null && !locals.contains(info.local))
locals.addElement(info.local);
}
// Enumeration enumeration = locals.elements();
// while (enumeration.hasMoreElements()) {
// LocalInfo li = (LocalInfo) enumeration.nextElement();
// int slot = ((InstrInfo)li.usingInstrs.elementAt(0))
// .instr.getLocalSlot();
// GlobalOptions.err.print("Slot: "+slot+" conflicts:");
// Enumeration enum1 = li.conflictingLocals.elements();
// while (enum1.hasMoreElements()) {
// LocalInfo cfl = (LocalInfo)enum1.nextElement();
// GlobalOptions.err.print(cfl.getAddr()+", ");
// }
// GlobalOptions.err.println();
// GlobalOptions.err.print(li.getAddr());
// GlobalOptions.err.print(" instrs: ");
// Enumeration enum2 = li.usingInstrs.elements();
// while (enum2.hasMoreElements())
// GlobalOptions.err.print(((InstrInfo)enum2.nextElement())
// .instr.getAddr()+", ");
// GlobalOptions.err.println();
// }
// GlobalOptions.err.println("-----------");
}
public void transformCode(BasicBlocks bb) {
this.bb = bb;
calcLocalInfo();
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_LOCALS) != 0) {
GlobalOptions.err.println("Before Local Optimization: ");
dumpLocals();
}
stripLocals();
distributeLocals();
if ((GlobalOptions.debuggingFlags
& GlobalOptions.DEBUG_LOCALS) != 0) {
GlobalOptions.err.println("After Local Optimization: ");
dumpLocals();
}
firstInfo = null;
changedInfos = null;
instrInfos = null;
paramLocals = null;
}
}