C2 - Split If


Stacktrace

libjvm.so!PhaseIdealLoop::split_if_with_blocks(PhaseIdealLoop * const this, VectorSet & visited, Node_Stack & nstack) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/loopopts.cpp:1649)
libjvm.so!PhaseIdealLoop::build_and_optimize(PhaseIdealLoop * const this, LoopOptsMode mode) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/loopnode.cpp:4011)
libjvm.so!PhaseIdealLoop::PhaseIdealLoop(PhaseIdealLoop * const this, PhaseIterGVN & igvn, LoopOptsMode mode) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/loopnode.hpp:1068)
libjvm.so!PhaseIdealLoop::optimize(PhaseIterGVN & igvn, LoopOptsMode mode) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/loopnode.hpp:1146)
libjvm.so!Compile::optimize_loops(Compile * const this, PhaseIterGVN & igvn, LoopOptsMode mode) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/compile.cpp:2006)
libjvm.so!Compile::Optimize(Compile * const this) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/compile.cpp:2229)
libjvm.so!Compile::Compile(Compile * const this, ciEnv * ci_env, ciMethod * target, int osr_bci, bool subsume_loads, bool do_escape_analysis, bool eliminate_boxing, bool install_code, DirectiveSet * directive) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/compile.cpp:771)
libjvm.so!C2Compiler::compile_method(C2Compiler * const this, ciEnv * env, ciMethod * target, int entry_bci, bool install_code, DirectiveSet * directive) (/home/qingfeng.yy/jdktip/src/hotspot/share/opto/c2compiler.cpp:103)
libjvm.so!CompileBroker::invoke_compiler_on_method(CompileTask * task) (/home/qingfeng.yy/jdktip/src/hotspot/share/compiler/compileBroker.cpp:2312)
libjvm.so!CompileBroker::compiler_thread_loop() (/home/qingfeng.yy/jdktip/src/hotspot/share/compiler/compileBroker.cpp:1985)
libjvm.so!CompilerThread::thread_entry(JavaThread * thread, JavaThread * __the_thread__) (/home/qingfeng.yy/jdktip/src/hotspot/share/compiler/compilerThread.cpp:59)
libjvm.so!JavaThread::thread_main_inner(JavaThread * const this) (/home/qingfeng.yy/jdktip/src/hotspot/share/runtime/thread.cpp:1310)
libjvm.so!JavaThread::run(JavaThread * const this) (/home/qingfeng.yy/jdktip/src/hotspot/share/runtime/thread.cpp:1293)
libjvm.so!Thread::call_run(Thread * const this) (/home/qingfeng.yy/jdktip/src/hotspot/share/runtime/thread.cpp:394)
libjvm.so!thread_native_entry(Thread * thread) (/home/qingfeng.yy/jdktip/src/hotspot/os/linux/os_linux.cpp:719)
libpthread.so.0!start_thread (Unknown Source:0)
libc.so.6!clone (Unknown Source:0)

Java

public class SIF {
    static int f1(Object a) { return a.hashCode();}
    static int f2(Object a) { return a.hashCode();}

    static int test(Object a1, Object a2, boolean flag1) {
        boolean flag2;
        int f = 0;
        Object a = null;
        if (flag1) {
            flag2 = true;
            a = a1;
        } else {
            flag2 = false;
            a = a2;
        }
        if (flag2) {
            f = f1(a);
        } else {
            f = f2(a);
        }
        return f;
    }

    public static void main(String[] args) {
        Object a = new Object();
        for (int i = 0; i < 20_000; i++) {
            test(a, a, (i % 2) == 0);    
        }
    }
}

Ideal Graph

Before:

After:

我理解的split if有点像字面意思的反操作,就是吧两个if,合并成一个,就像这样:

// before
if (flag1 == 0) {
 phi = a2
} else {
 phi = a1
}

if (flag1 == 0){
    f1(phi);
} else {
    f2(phi);
}
// after
phi = cmove(a1,a2);
if (flag1==0){
    f1(phi);
}else {
    f2(phi);
}
void PhaseIdealLoop::split_if_with_blocks_post(Node *n) {
  // Cloning Cmp through Phi's involves the split-if transform.
  // FastLock is not used by an If
  if (n->is_Cmp() && !n->is_FastLock()) {
    Node *n_ctrl = get_ctrl(n);
    // Determine if the Node has inputs from some local Phi.
    // Returns the block to clone thru.
    Node *n_blk = has_local_phi_input(n);
    if (n_blk != n_ctrl) {
      return;
    }

    if (!can_split_if(n_ctrl)) {
      return;
    }

    if (n->outcnt() != 1) {
      return; // Multiple bool's from 1 compare?
    }
    Node *bol = n->unique_out();
    assert(bol->is_Bool(), "expect a bool here");
    if (bol->outcnt() != 1) {
      return;// Multiple branches from 1 compare?
    }
    Node *iff = bol->unique_out();

    // Check some safety conditions
    if (iff->is_If()) {        // Classic split-if?
      if (iff->in(0) != n_ctrl) {
        return; // Compare must be in same blk as if
      }
    } else if (iff->is_CMove()) { // Trying to split-up a CMOVE
      // Can't split CMove with different control edge.
      if (iff->in(0) != NULL && iff->in(0) != n_ctrl ) {
        return;
      }
      if (get_ctrl(iff->in(2)) == n_ctrl ||
          get_ctrl(iff->in(3)) == n_ctrl) {
        return;                 // Inputs not yet split-up
      }
      if (get_loop(n_ctrl) != get_loop(get_ctrl(iff))) {
        return;                 // Loop-invar test gates loop-varying CMOVE
      }
    } else {
      return;  // some other kind of node, such as an Allocate
    }

    // When is split-if profitable?  Every 'win' on means some control flow
    // goes dead, so it's almost always a win.
    int policy = 0;
    // Split compare 'n' through the merge point if it is profitable
    Node *phi = split_thru_phi( n, n_ctrl, policy);
    if (!phi) {
      return;
    }

    // Found a Phi to split thru!
    // Replace 'n' with the new phi
    _igvn.replace_node(n, phi);

    // Now split the bool up thru the phi
    Node *bolphi = split_thru_phi(bol, n_ctrl, -1);
    guarantee(bolphi != NULL, "null boolean phi node");

    _igvn.replace_node(bol, bolphi);
    assert(iff->in(1) == bolphi, "");

    if (bolphi->Value(&_igvn)->singleton()) {
      return;
    }

    // Conditional-move?  Must split up now
    if (!iff->is_If()) {
      Node *cmovphi = split_thru_phi(iff, n_ctrl, -1);
      _igvn.replace_node(iff, cmovphi);
      return;
    }

    // Now split the IF
    do_split_if(iff);
    return;
  }

  // Two identical ifs back to back can be merged
  if (identical_backtoback_ifs(n) && can_split_if(n->in(0))) {
    Node *n_ctrl = n->in(0);
    PhiNode* bolphi = PhiNode::make_blank(n_ctrl, n->in(1));
    IfNode* dom_if = idom(n_ctrl)->as_If();
    Node* proj_true = dom_if->proj_out(1);
    Node* proj_false = dom_if->proj_out(0);
    Node* con_true = _igvn.makecon(TypeInt::ONE);
    Node* con_false = _igvn.makecon(TypeInt::ZERO);

    for (uint i = 1; i < n_ctrl->req(); i++) {
      if (is_dominator(proj_true, n_ctrl->in(i))) {
        bolphi->init_req(i, con_true);
      } else {
        assert(is_dominator(proj_false, n_ctrl->in(i)), "bad if");
        bolphi->init_req(i, con_false);
      }
    }
    register_new_node(bolphi, n_ctrl);
    _igvn.replace_input_of(n, 1, bolphi);

    // Now split the IF
    do_split_if(n);
    return;
  }

  // Check for an IF ready to split; one that has its
  // condition codes input coming from a Phi at the block start.
  int n_op = n->Opcode();

  // Check for an IF being dominated by another IF same test
  if (n_op == Op_If ||
      n_op == Op_RangeCheck) {
    Node *bol = n->in(1);
    uint max = bol->outcnt();
    // Check for same test used more than once?
    if (max > 1 && bol->is_Bool()) {
      // Search up IDOMs to see if this IF is dominated.
      Node *cutoff = get_ctrl(bol);

      // Now search up IDOMs till cutoff, looking for a dominating test
      Node *prevdom = n;
      Node *dom = idom(prevdom);
      while (dom != cutoff) {
        if (dom->req() > 1 && dom->in(1) == bol && prevdom->in(0) == dom) {
          // It's invalid to move control dependent data nodes in the inner
          // strip-mined loop, because:
          //  1) break validation of LoopNode::verify_strip_mined()
          //  2) move code with side-effect in strip-mined loop
          // Move to the exit of outer strip-mined loop in that case.
          Node* out_le = is_inner_of_stripmined_loop(dom);
          if (out_le != NULL) {
            prevdom = out_le;
          }
          // Replace the dominated test with an obvious true or false.
          // Place it on the IGVN worklist for later cleanup.
          C->set_major_progress();
          dominated_by(prevdom, n, false, true);
#ifndef PRODUCT
          if( VerifyLoopOptimizations ) verify();
#endif
          return;
        }
        prevdom = dom;
        dom = idom(prevdom);
      }
    }
  }

  try_sink_out_of_loop(n);

  try_move_store_after_loop(n);

  // Check for Opaque2's who's loop has disappeared - who's input is in the
  // same loop nest as their output.  Remove 'em, they are no longer useful.
  if( n_op == Op_Opaque2 &&
      n->in(1) != NULL &&
      get_loop(get_ctrl(n)) == get_loop(get_ctrl(n->in(1))) ) {
    _igvn.replace_node( n, n->in(1) );
  }
}

很多情况下会做split if。这里选一种,即identical_backtoidentical_backtoback_ifs,也就是紧挨着的两个if,做合并。

最开始是这样:

等identical_backtoback_ifs里面bolphi做完,就会创建新的phi节点,如下图89#Phi:

接着,do_split_if做实际merge工作

//------------------------------do_split_if------------------------------------
// Found an If getting its condition-code input from a Phi in the same block.
// Split thru the Region.
void PhaseIdealLoop::do_split_if( Node *iff ) {
  if (PrintOpto && VerifyLoopOptimizations) {
    tty->print_cr("Split-if");
  }
  if (TraceLoopOpts) {
    tty->print_cr("SplitIf");
  }

  C->set_major_progress();
  Node *region = iff->in(0);
  Node *region_dom = idom(region);

  // We are going to clone this test (and the control flow with it) up through
  // the incoming merge point.  We need to empty the current basic block.
  // Clone any instructions which must be in this block up through the merge
  // point.
  DUIterator i, j;
  bool progress = true;
  while (progress) {
    progress = false;
    for (i = region->outs(); region->has_out(i); i++) {
      Node* n = region->out(i);
      if( n == region ) continue;
      // The IF to be split is OK.
      if( n == iff ) continue;
      if( !n->is_Phi() ) {      // Found pinned memory op or such
        if (split_up(n, region, iff)) {
          i = region->refresh_out_pos(i);
          progress = true;
        }
        continue;
      }
      assert( n->in(0) == region, "" );

      // Recursively split up all users of a Phi
      for (j = n->outs(); n->has_out(j); j++) {
        Node* m = n->out(j);
        // If m is dead, throw it away, and declare progress
        if (_nodes[m->_idx] == NULL) {
          _igvn.remove_dead_node(m);
          // fall through
        }
        else if (m != iff && split_up(m, region, iff)) {
          // fall through
        } else {
          continue;
        }
        // Something unpredictable changed.
        // Tell the iterators to refresh themselves, and rerun the loop.
        i = region->refresh_out_pos(i);
        j = region->refresh_out_pos(j);
        progress = true;
      }
    }
  }

  // Now we have no instructions in the block containing the IF.
  // Split the IF.
  Node *new_iff = split_thru_region( iff, region );

  // Replace both uses of 'new_iff' with Regions merging True/False
  // paths.  This makes 'new_iff' go dead.
  Node *old_false = NULL, *old_true = NULL;
  Node *new_false = NULL, *new_true = NULL;
  for (DUIterator_Last j2min, j2 = iff->last_outs(j2min); j2 >= j2min; --j2) {
    Node *ifp = iff->last_out(j2);
    assert( ifp->Opcode() == Op_IfFalse || ifp->Opcode() == Op_IfTrue, "" );
    ifp->set_req(0, new_iff);
    Node *ifpx = split_thru_region( ifp, region );

    // Replace 'If' projection of a Region with a Region of
    // 'If' projections.
    ifpx->set_req(0, ifpx);       // A TRUE RegionNode

    // Setup dominator info
    set_idom(ifpx, region_dom, dom_depth(region_dom) + 1);

    // Check for splitting loop tails
    if( get_loop(iff)->tail() == ifp )
      get_loop(iff)->_tail = ifpx;

    // Replace in the graph with lazy-update mechanism
    new_iff->set_req(0, new_iff); // hook self so it does not go dead
    lazy_replace(ifp, ifpx);
    new_iff->set_req(0, region);

    // Record bits for later xforms
    if( ifp->Opcode() == Op_IfFalse ) {
      old_false = ifp;
      new_false = ifpx;
    } else {
      old_true = ifp;
      new_true = ifpx;
    }
  }
  _igvn.remove_dead_node(new_iff);
  // Lazy replace IDOM info with the region's dominator
  lazy_replace(iff, region_dom);
  lazy_update(region, region_dom); // idom must be update before handle_uses
  region->set_req(0, NULL);        // Break the self-cycle. Required for lazy_update to work on region

  // Now make the original merge point go dead, by handling all its uses.
  small_cache region_cache;
  // Preload some control flow in region-cache
  region_cache.lru_insert( new_false, new_false );
  region_cache.lru_insert( new_true , new_true  );
  // Now handle all uses of the splitting block
  for (DUIterator k = region->outs(); region->has_out(k); k++) {
    Node* phi = region->out(k);
    if (!phi->in(0)) {         // Dead phi?  Remove it
      _igvn.remove_dead_node(phi);
    } else if (phi == region) { // Found the self-reference
      continue;                 // No roll-back of DUIterator
    } else if (phi->is_Phi()) { // Expected common case: Phi hanging off of Region
      assert(phi->in(0) == region, "Inconsistent graph");
      // Need a per-def cache.  Phi represents a def, so make a cache
      small_cache phi_cache;

      // Inspect all Phi uses to make the Phi go dead
      for (DUIterator_Last lmin, l = phi->last_outs(lmin); l >= lmin; --l) {
        Node* use = phi->last_out(l);
        // Compute the new DEF for this USE.  New DEF depends on the path
        // taken from the original DEF to the USE.  The new DEF may be some
        // collection of PHI's merging values from different paths.  The Phis
        // inserted depend only on the location of the USE.  We use a
        // 2-element cache to handle multiple uses from the same block.
        handle_use(use, phi, &phi_cache, region_dom, new_false, new_true, old_false, old_true);
      } // End of while phi has uses
      // Remove the dead Phi
      _igvn.remove_dead_node( phi );
    } else {
      assert(phi->in(0) == region, "Inconsistent graph");
      // Random memory op guarded by Region.  Compute new DEF for USE.
      handle_use(phi, region, ®ion_cache, region_dom, new_false, new_true, old_false, old_true);
    }
    // Every path above deletes a use of the region, except for the region
    // self-cycle (which is needed by handle_use calling find_use_block
    // calling get_ctrl calling get_ctrl_no_update looking for dead
    // regions).  So roll back the DUIterator innards.
    --k;
  } // End of while merge point has phis

  _igvn.remove_dead_node(region);

#ifndef PRODUCT
  if( VerifyLoopOptimizations ) verify();
#endif
}

split_thru_region( iff, region )困惑了我很久,其中iff是43#If,region是35#Region

调用split_thru_region之前

调用split_thru_region之后

到remove_dead_node(new_iff)的时候:

我理解,这里说的就是,在合并点即35#Region处,他创建了两个新if代替(if(false){}else{} 和 if(true){}else{}),相当于拆分了43#If