/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.opt;
import org.jikesrvm.VM;
import org.jikesrvm.compilers.opt.ir.Goto;
import org.jikesrvm.compilers.opt.ir.InlineGuard;
import org.jikesrvm.compilers.opt.ir.OPT_BasicBlock;
import org.jikesrvm.compilers.opt.ir.OPT_BasicBlockEnumeration;
import org.jikesrvm.compilers.opt.ir.OPT_IR;
import org.jikesrvm.compilers.opt.ir.OPT_Instruction;
import org.jikesrvm.compilers.opt.ir.OPT_InstructionEnumeration;
import static org.jikesrvm.compilers.opt.ir.OPT_Operators.GOTO;
/**
* Static splitting based on very simple hints left by
* guarded inlining (off blocks marked as infrequent)
* and semantic knowledge of tests.
* The goal of this pass is to create 'common-case' traces.
* This is done by eliminating merge points where
* uncommon-case code merges back into common case code
* by code duplication. We rely on a later pass to
* eliminate redundant tests on the common-case trace.
* <p>
* We use semantic knowledge of the tests to reduce the
* code replicated. The key idea is that for a guarded
* inlining, it is correct to take the 'off' branch even
* if test would select the on-branch. Therefore we can
* avoid replicating the on-branch code downstream of the
* replicated test, at the possible cost of trapping an
* execution in the uncommon-case trace that might have
* been able to use a subset of to common-case trace.
* <p>
*/
class OPT_StaticSplitting extends OPT_CompilerPhase {
private static final boolean DEBUG = false;
private static final int MAX_COST = 10; // upper bound on instructions duplicated
private final OPT_BranchOptimizations branchOpts;
protected OPT_StaticSplitting() {
branchOpts = new OPT_BranchOptimizations(-1, false, false);
}
/**
* Return this instance of this phase. This phase contains no
* per-compilation instance fields.
* @param ir not used
* @return this
*/
public OPT_CompilerPhase newExecution(OPT_IR ir) {
return this;
}
public String getName() { return "Static Splitting"; }
public boolean shouldPerform(OPT_Options options) {
return options.STATIC_SPLITTING;
}
public boolean printingEnabled(OPT_Options options, boolean before) {
return DEBUG;
}
/**
* Do simplistic static splitting to create hot traces
* with that do not have incoming edges from
* blocks that are statically predicted to be cold.
*
* @param ir The IR on which to apply the phase
*/
public void perform(OPT_IR ir) {
// (1) Find candidates to split
simpleCandidateSearch(ir);
// (2) Split them
boolean needCleanup = haveCandidates();
while (haveCandidates()) {
splitCandidate(nextCandidate(), ir);
}
// (3) If something was split optimize the CFG
if (needCleanup) {
branchOpts.perform(ir);
}
}
/**
* Identify candidate blocks by using a very
* simplistic algorithm.
* <ul>
* <li> Find all blocks that end in a test that
* is statically known to be likely to
* create a common case trace. For example,
* blocks that end in IG_METHOD_TEST, IG_CLASS_TEST
* and IG_PATCH_POINT. Note that these tests also
* have the property that it is correct
* (but less desirable) to execute the off branch
* when the test would have selected the on branch.
* <li> If such a block has a control flow predecessor
* that is marked as infrequent, and if the block
* is relatively small, then it is almost certainly
* profitable to duplicate the block and transfer
* the infrequent predecessor to go to
* the cloned block. This has the effect of freeing
* the common-case path from the pollution of the
* infrequently executed block. Therefore we identify
* the block as a splitting candidate.
* </ul>
*/
private void simpleCandidateSearch(OPT_IR ir) {
for (OPT_BasicBlockEnumeration e = ir.getBasicBlocks(); e.hasMoreElements();) {
OPT_BasicBlock cand = e.next();
if (cand.isExceptionHandlerBasicBlock()) continue;
OPT_Instruction candTest = getCandidateTest(cand);
if (candTest == null) continue;
OPT_BasicBlock coldPrev = findColdPrev(cand);
if (coldPrev == null) continue;
if (tooBig(cand)) continue;
OPT_BasicBlock coldSucc = findColdSucc(cand, candTest);
if (DEBUG) {
VM.sysWrite("Found candidate \n");
VM.sysWrite("\tTest is " + candTest + "\n");
VM.sysWrite("\tcoldPrev is " + coldPrev + "\n");
VM.sysWrite("\tcoldSucc is " + coldSucc + "\n");
cand.printExtended();
}
pushCandidate(cand, coldPrev, coldSucc, candTest);
}
}
/**
* Split a node where we can safely not
* replicate the on-branch in the cloned node.
* @param ci description of the split candidate.
*/
private void splitCandidate(CandInfo ci, OPT_IR ir) {
OPT_BasicBlock cand = ci.candBB;
OPT_BasicBlock prev = ci.prevBB;
OPT_BasicBlock succ = ci.succBB;
OPT_BasicBlock clone = cand.copyWithoutLinks(ir);
// Redirect clone to always stay on cold path.
OPT_Instruction s = clone.lastRealInstruction();
while (s.isBranch()) {
s = s.remove();
}
clone.appendInstruction(Goto.create(GOTO, succ.makeJumpTarget()));
// inject clone in code order;
// force prev to go to clone instead of cand.
prev.redirectOuts(cand, clone, ir);
clone.recomputeNormalOut(ir);
ir.cfg.addLastInCodeOrder(clone);
clone.setInfrequent();
}
/**
* Return the candidate test in b, or <code>null</code> if
* b does not have one.
*/
private OPT_Instruction getCandidateTest(OPT_BasicBlock bb) {
OPT_Instruction test = null;
for (OPT_InstructionEnumeration e = bb.enumerateBranchInstructions(); e.hasMoreElements();) {
OPT_Instruction branch = e.next();
if (InlineGuard.conforms(branch)) {
if (test != null) return null; // found multiple tests!
test = branch;
} else if (branch.operator() != GOTO) {
return null;
}
}
return test;
}
/**
* Return the cold predecessor to the argument block.
* If there is not exactly 1, return null.
*/
private OPT_BasicBlock findColdPrev(OPT_BasicBlock bb) {
OPT_BasicBlock cold = null;
for (java.util.Enumeration<OPT_BasicBlock> e = bb.getInNodes(); e.hasMoreElements();) {
OPT_BasicBlock p = e.nextElement();
if (p.getInfrequent()) {
if (cold != null) return null;
cold = p;
}
}
return cold;
}
/**
* Return the off-trace successor of b
* (on and off relative to the argument test)
*/
private OPT_BasicBlock findColdSucc(OPT_BasicBlock bb, OPT_Instruction test) {
return test.getBranchTarget();
}
/**
* Simplistic cost estimate; since we
* are doing the splitting based on
* static hints, we are only willing to
* copy a very small amount of code.
*/
private boolean tooBig(OPT_BasicBlock bb) {
int cost = 0;
for (OPT_InstructionEnumeration e = bb.forwardRealInstrEnumerator(); e.hasMoreElements();) {
OPT_Instruction s = e.next();
if (s.isCall()) {
cost += 3;
} else if (s.isAllocation()) {
cost += 6;
} else {
cost++;
}
if (cost > MAX_COST) return true;
}
return false;
}
/*
* Support for remembering candidates
*/
private CandInfo cands;
private static class CandInfo {
OPT_BasicBlock candBB;
OPT_BasicBlock prevBB;
OPT_BasicBlock succBB;
final OPT_Instruction test;
CandInfo next;
CandInfo(OPT_BasicBlock c, OPT_BasicBlock p, OPT_BasicBlock s, OPT_Instruction t, CandInfo n) {
candBB = c;
prevBB = p;
succBB = s;
test = t;
next = n;
}
}
private void pushCandidate(OPT_BasicBlock cand, OPT_BasicBlock prev, OPT_BasicBlock succ, OPT_Instruction test) {
cands = new CandInfo(cand, prev, succ, test, cands);
}
private boolean haveCandidates() {
return cands != null;
}
private CandInfo nextCandidate() {
CandInfo res = cands;
cands = cands.next;
return res;
}
}