001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.compilers.opt.controlflow;
014
015import static org.jikesrvm.compilers.opt.ir.Operators.GOTO;
016import static org.jikesrvm.compilers.opt.ir.Operators.YIELDPOINT_OSR;
017
018import java.util.Enumeration;
019
020import org.jikesrvm.VM;
021import org.jikesrvm.compilers.opt.OptOptions;
022import org.jikesrvm.compilers.opt.driver.CompilerPhase;
023import org.jikesrvm.compilers.opt.ir.BasicBlock;
024import org.jikesrvm.compilers.opt.ir.Goto;
025import org.jikesrvm.compilers.opt.ir.IR;
026import org.jikesrvm.compilers.opt.ir.InlineGuard;
027import org.jikesrvm.compilers.opt.ir.Instruction;
028
029/**
030 * Static splitting based on very simple hints left by
031 * guarded inlining (off blocks marked as infrequent)
032 * and semantic knowledge of tests.
033 * The goal of this pass is to create 'common-case' traces.
034 * This is done by eliminating merge points where
035 * uncommon-case code merges back into common case code
036 * by code duplication. We rely on a later pass to
037 * eliminate redundant tests on the common-case trace.
038 * <p>
039 * We use semantic knowledge of the tests to reduce the
040 * code replicated.  The key idea is that for a guarded
041 * inlining, it is correct to take the 'off' branch even
042 * if test would select the on-branch.  Therefore we can
043 * avoid replicating the on-branch code downstream of the
044 * replicated test, at the possible cost of trapping an
045 * execution in the uncommon-case trace that might have
046 * been able to use a subset of to common-case trace.
047 */
048public class StaticSplitting extends CompilerPhase {
049
050  private static final boolean DEBUG = false;
051  private final BranchOptimizations branchOpts;
052
053  public StaticSplitting() {
054    branchOpts = new BranchOptimizations(-1, false, false);
055  }
056
057  /**
058   * Return this instance of this phase. This phase contains no
059   * per-compilation instance fields.
060   * @param ir not used
061   * @return this
062   */
063  @Override
064  public CompilerPhase newExecution(IR ir) {
065    return this;
066  }
067
068  @Override
069  public String getName() {
070    return "Static Splitting";
071  }
072
073  @Override
074  public boolean shouldPerform(OptOptions options) {
075    return options.CONTROL_STATIC_SPLITTING;
076  }
077
078  @Override
079  public boolean printingEnabled(OptOptions options, boolean before) {
080    return DEBUG;
081  }
082
083  /**
084   * Do simplistic static splitting to create hot traces
085   * with that do not have incoming edges from
086   * blocks that are statically predicted to be cold.
087   *
088   * @param ir   The IR on which to apply the phase
089   */
090  @Override
091  public void perform(IR ir) {
092    // (1) Find candidates to split
093    simpleCandidateSearch(ir);
094
095    // (2) Split them
096    boolean needCleanup = haveCandidates();
097    while (haveCandidates()) {
098      splitCandidate(nextCandidate(), ir);
099    }
100
101    // (3) If something was split optimize the CFG
102    if (needCleanup) {
103      branchOpts.perform(ir);
104    }
105  }
106
107  /**
108   * Identify candidate blocks by using a very
109   * simplistic algorithm.
110   * <ul>
111   * <li> Find all blocks that end in a test that
112   *      is statically known to be likely to
113   *      create a common case trace. For example,
114   *      blocks that end in IG_METHOD_TEST, IG_CLASS_TEST
115   *      and IG_PATCH_POINT. Note that these tests also
116   *      have the property that it is correct
117   *      (but less desirable) to execute the off branch
118   *      when the test would have selected the on branch.
119   * <li> If such a block has a control flow predecessor
120   *      that is marked as infrequent, and if the block
121   *      is relatively small, then it is almost certainly
122   *      profitable to duplicate the block and transfer
123   *      the infrequent predecessor to go to
124   *      the cloned block.  This has the effect of freeing
125   *      the common-case path from the pollution of the
126   *      infrequently executed block. Therefore we identify
127   *      the block as a splitting candidate.
128   * </ul>
129   *
130   * @param ir the governing IR
131   */
132  private void simpleCandidateSearch(IR ir) {
133    for (Enumeration<BasicBlock> e = ir.getBasicBlocks(); e.hasMoreElements();) {
134      BasicBlock cand = e.nextElement();
135      if (cand.isExceptionHandlerBasicBlock()) continue;
136      Instruction candTest = getCandidateTest(cand);
137      if (candTest == null) continue;
138      BasicBlock coldPrev = findColdPrev(cand);
139      if (coldPrev == null) continue;
140      if (tooBig(cand, ir.options.CONTROL_STATIC_SPLITTING_MAX_COST)) continue;
141      BasicBlock coldSucc = findColdSucc(candTest);
142      if (containsOSRPoint(coldSucc)) continue;
143      if (DEBUG) {
144        VM.sysWrite("Found candidate \n");
145        VM.sysWrite("\tTest is " + candTest + "\n");
146        VM.sysWrite("\tcoldPrev is " + coldPrev + "\n");
147        VM.sysWrite("\tcoldSucc is " + coldSucc + "\n");
148        cand.printExtended();
149      }
150      pushCandidate(cand, coldPrev, coldSucc, candTest);
151    }
152  }
153
154  /**
155   * Splits a node where we can safely not
156   * replicate the on-branch in the cloned node.
157   * @param ci description of the split candidate.
158   * @param ir the governing IR
159   */
160  private void splitCandidate(CandInfo ci, IR ir) {
161    BasicBlock cand = ci.candBB;
162    BasicBlock prev = ci.prevBB;
163    BasicBlock succ = ci.succBB;
164    BasicBlock clone = cand.copyWithoutLinks(ir);
165
166    // Redirect clone to always stay on cold path.
167    Instruction s = clone.lastRealInstruction();
168    while (s.isBranch()) {
169      s = s.remove();
170    }
171    clone.appendInstruction(Goto.create(GOTO, succ.makeJumpTarget()));
172
173    // inject clone in code order;
174    // force prev to go to clone instead of cand.
175    prev.redirectOuts(cand, clone, ir);
176    clone.recomputeNormalOut(ir);
177    ir.cfg.addLastInCodeOrder(clone);
178    clone.setInfrequent();
179  }
180
181  /**
182   * @param bb a basic block
183   * @return the candidate test or {@code null} if
184   * there is no single test (i.e. no test or multiple tests)
185   */
186  private Instruction getCandidateTest(BasicBlock bb) {
187    Instruction test = null;
188    for (Enumeration<Instruction> e = bb.enumerateBranchInstructions(); e.hasMoreElements();) {
189      Instruction branch = e.nextElement();
190      if (InlineGuard.conforms(branch)) {
191        if (test != null) return null; // found multiple tests!
192        test = branch;
193      } else if (branch.operator() != GOTO) {
194        return null;
195      }
196    }
197    return test;
198  }
199
200  /**
201   * @param bb a basic block
202   * @return the cold predecessor to the argument block.
203   * If there is not exactly 1, return {@code null}.
204   */
205  private BasicBlock findColdPrev(BasicBlock bb) {
206    BasicBlock cold = null;
207    for (java.util.Enumeration<BasicBlock> e = bb.getInNodes(); e.hasMoreElements();) {
208      BasicBlock p = e.nextElement();
209      if (p.getInfrequent()) {
210        if (cold != null) return null;
211        cold = p;
212      }
213    }
214    return cold;
215  }
216
217  /**
218   * @param test an instruction for a candidate test
219   * @return the off-trace successor of b
220   * (on and off relative to the argument test)
221   */
222  private BasicBlock findColdSucc(Instruction test) {
223    return test.getBranchTarget();
224  }
225
226  /**
227   * Simplistic cost estimate; since we
228   * are doing the splitting based on
229   * static hints, we are only willing to
230   * copy a very small amount of code.
231   *
232   * @param bb block to check
233   * @param maxCost maximum cost that is acceptable
234   * @return whether the block is too big
235   */
236  private boolean tooBig(BasicBlock bb, int maxCost) {
237    int cost = 0;
238    for (Enumeration<Instruction> e = bb.forwardRealInstrEnumerator(); e.hasMoreElements();) {
239      Instruction s = e.nextElement();
240      if (s.isCall()) {
241        cost += 3;
242      } else if (s.isAllocation()) {
243        cost += 6;
244      } else {
245        cost++;
246      }
247      if (cost > maxCost) return true;
248    }
249    return false;
250  }
251
252  private boolean containsOSRPoint(BasicBlock bb) {
253    for (Enumeration<Instruction> e = bb.forwardRealInstrEnumerator(); e.hasMoreElements();) {
254      Instruction s = e.nextElement();
255      if (s.operator() == YIELDPOINT_OSR) {
256        return true;
257      }
258    }
259    return false;
260  }
261
262  /*
263   * Support for remembering candidates
264   */
265  private CandInfo cands;
266
267  private static final class CandInfo {
268    final BasicBlock candBB;
269    final BasicBlock prevBB;
270    final BasicBlock succBB;
271    final Instruction test;
272    final CandInfo next;
273
274    CandInfo(BasicBlock c, BasicBlock p, BasicBlock s, Instruction t, CandInfo n) {
275      candBB = c;
276      prevBB = p;
277      succBB = s;
278      test = t;
279      next = n;
280    }
281  }
282
283  private void pushCandidate(BasicBlock cand, BasicBlock prev, BasicBlock succ, Instruction test) {
284    cands = new CandInfo(cand, prev, succ, test, cands);
285  }
286
287  private boolean haveCandidates() {
288    return cands != null;
289  }
290
291  private CandInfo nextCandidate() {
292    CandInfo res = cands;
293    cands = cands.next;
294    return res;
295  }
296}