package org.jruby.ir.passes;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
import org.jruby.ir.instructions.CopyInstr;
import org.jruby.ir.instructions.Instr;
import org.jruby.ir.instructions.ResultInstr;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.representations.BasicBlock;
import java.util.*;
public class LocalOptimizationPass extends CompilerPass {
@Override
public String getLabel() {
return "Local Optimizations";
}
@Override
public String getShortLabel() {
return "Local Opts";
}
@Override
public Object execute(IRScope s, Object... data) {
for (BasicBlock b: s.getCFG().getBasicBlocks()) {
runLocalOptsOnBasicBlock(s, b);
}
// SSS FIXME: What is this about?
// Why 'Only after running local opts'? Figure out and document.
//
// Only after running local opts, compute various execution scope flags.
s.computeScopeFlags();
// LVA information is no longer valid after this pass
// Currently, we don't run this after LVA, but just in case ...
//
// FIXME: Grrr ... this seems broken to have to create a new object to invalidate
(new LiveVariableAnalysis()).invalidate(s);
return null;
}
private static void recordSimplification(Variable res, Operand val, Map<Operand, Operand> valueMap, Map<Variable, List<Variable>> simplificationMap) {
valueMap.put(res, val);
// For all variables used by val, record a reverse mapping to let us track
// Read-After-Write scenarios when any of these variables are modified.
List<Variable> valVars = new ArrayList<>();
val.addUsedVariables(valVars);
for (Variable v: valVars) {
List<Variable> x = simplificationMap.get(v);
if (x == null) {
x = new ArrayList<>();
simplificationMap.put(v, x);
}
x.add(res);
}
}
public static Instr optInstr(IRScope s, Instr instr, Map<Operand,Operand> valueMap, Map<Variable,List<Variable>> simplificationMap) {
// System.out.println("BEFORE: " + instr);
// Simplify instruction and record mapping between target variable and simplified value
Operand val = instr.simplifyAndGetResult(s, valueMap);
// Variable dst = (instr instanceof ResultInstr) ? ((ResultInstr) instr).getResult() : null;
// System.out.println("AFTER: " + instr + "; dst = " + dst + "; val = " + val);
if (!(instr instanceof ResultInstr)) {
return instr;
}
Instr newInstr = instr;
Variable res = ((ResultInstr) instr).getResult();
if (val == null) {
// If we didn't get a simplified value, remove existing simplifications
// for the result to get rid of RAW hazards!
valueMap.remove(res);
} else {
if (!res.equals(val)) {
recordSimplification(res, val, valueMap, simplificationMap);
}
if (!instr.hasSideEffects()) {
if (instr instanceof CopyInstr) {
if (res.equals(val) && instr.canBeDeletedFromScope(s)) {
instr.markDead();
}
} else {
newInstr = new CopyInstr(res, val);
}
}
}
// Purge all entries in valueMap that have 'res' as their simplified value
// to take care of RAW scenarios (because we aren't in SSA form yet!)
if (!res.equals(val)) {
List<Variable> simplifiedVars = simplificationMap.get(res);
if (simplifiedVars != null) {
for (Variable v: simplifiedVars) {
valueMap.remove(v);
}
simplificationMap.remove(res);
}
}
return newInstr;
}
public static void runLocalOptsOnInstrArray(IRScope s, Instr[] instrs) {
// Reset value map if this instruction is the start/end of a basic block
Map<Operand,Operand> valueMap = new HashMap<>();
Map<Variable,List<Variable>> simplificationMap = new HashMap<>();
for (int i = 0; i < instrs.length; i++) {
Instr instr = instrs[i];
Instr newInstr = optInstr(s, instr, valueMap, simplificationMap);
if (newInstr != instr) {
instrs[i] = newInstr;
}
// If the call has been optimized away in the previous step, it is no longer a hard boundary for opts!
//
// Right now, calls are considered hard boundaries for optimization and
// information cannot be propagated across them!
//
// SSS FIXME: Rather than treat all calls with a broad brush, what we need
// is to capture different attributes about a call :
// - uses closures
// - known call target
// - can modify scope,
// - etc.
//
// This information is present in instruction flags on CallBase. Use it!
Operation iop = instr.getOperation();
if (iop.startsBasicBlock() || iop.endsBasicBlock() || (iop.isCall() && !instr.isDead())) {
valueMap = new HashMap<>();
simplificationMap = new HashMap<>();
}
}
}
public static void runLocalOptsOnBasicBlock(IRScope s, BasicBlock b) {
ListIterator<Instr> instrs = b.getInstrs().listIterator();
// Reset value map if this instruction is the start/end of a basic block
Map<Operand,Operand> valueMap = new HashMap<>();
Map<Variable,List<Variable>> simplificationMap = new HashMap<>();
while (instrs.hasNext()) {
Instr instr = instrs.next();
Instr newInstr = optInstr(s, instr, valueMap, simplificationMap);
if (newInstr.isDead()) {
instrs.remove();
} else if (newInstr != instr) {
instrs.set(newInstr);
}
// If the call has been optimized away in the previous step, it is no longer a hard boundary for opts!
//
// Right now, calls are considered hard boundaries for optimization and
// information cannot be propagated across them!
//
// SSS FIXME: Rather than treat all calls with a broad brush, what we need
// is to capture different attributes about a call :
// - uses closures
// - known call target
// - can modify scope,
// - etc.
//
// This information is present in instruction flags on CallBase. Use it!
Operation iop = instr.getOperation();
if (iop.isCall() && !instr.isDead()) {
valueMap = new HashMap<>();
simplificationMap = new HashMap<>();
}
}
}
}