gt:         AbstractBeginNode begin = BeginNode.begin(target);
context: /*
   * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.
   *
   * This code 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
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  package com.oracle.graal.java;
  
  import static com.oracle.graal.bytecode.Bytecodes.*;
  import static com.oracle.graal.compiler.common.GraalOptions.*;
  import static com.oracle.graal.compiler.common.type.StampFactory.*;
  import static com.oracle.graal.graphbuilderconf.IntrinsicContext.CompilationContext.*;
  import static com.oracle.graal.java.BytecodeParser.Options.*;
  import static com.oracle.graal.nodes.type.StampTool.*;
  import static java.lang.String.*;
  import static jdk.internal.jvmci.common.JVMCIError.*;
  import static jdk.internal.jvmci.meta.DeoptimizationAction.*;
  import static jdk.internal.jvmci.meta.DeoptimizationReason.*;
  
  import java.util.*;
  
  import jdk.internal.jvmci.code.*;
  import jdk.internal.jvmci.common.*;
  import jdk.internal.jvmci.compiler.Compiler;
  import jdk.internal.jvmci.debug.*;
  
  import com.oracle.graal.debug.*;
  import com.oracle.graal.debug.Debug.Scope;
  
  import jdk.internal.jvmci.meta.*;
  import jdk.internal.jvmci.options.*;
  
  import com.oracle.graal.bytecode.*;
  import com.oracle.graal.compiler.common.*;
  import com.oracle.graal.compiler.common.calc.*;
  import com.oracle.graal.compiler.common.type.*;
  import com.oracle.graal.graph.Graph.Mark;
  import com.oracle.graal.graph.*;
  import com.oracle.graal.graph.Node.ValueNumberable;
  import com.oracle.graal.graph.iterators.*;
  import com.oracle.graal.graphbuilderconf.*;
  import com.oracle.graal.graphbuilderconf.InlineInvokePlugin.InlineInfo;
  import com.oracle.graal.graphbuilderconf.InvocationPlugins.InvocationPluginReceiver;
  import com.oracle.graal.java.BciBlockMapping.BciBlock;
  import com.oracle.graal.java.BciBlockMapping.ExceptionDispatchBlock;
  import com.oracle.graal.nodeinfo.*;
  import com.oracle.graal.nodes.*;
  import com.oracle.graal.nodes.CallTargetNode.InvokeKind;
  import com.oracle.graal.nodes.calc.*;
  import com.oracle.graal.nodes.extended.*;
  import com.oracle.graal.nodes.java.*;
  import com.oracle.graal.nodes.spi.*;
  import com.oracle.graal.nodes.type.*;
  import com.oracle.graal.nodes.util.*;
  import com.oracle.graal.phases.*;
  
  /**
   * The {@code GraphBuilder} class parses the bytecode of a method and builds the IR graph.
   */
  public class BytecodeParser implements GraphBuilderContext {
  
      public static class Options {
          @Option(help = "The trace level for the bytecode parser used when building a graph from bytecode", type = OptionType.Debug)//
          public static final OptionValue<Integer> TraceBytecodeParserLevel = new OptionValue<>(0);
  
          @Option(help = "Inlines trivial methods during bytecode parsing.", type = OptionType.Expert)//
          public static final StableOptionValue<Boolean> InlineDuringParsing = new StableOptionValue<>(true);
  
          @Option(help = "Inlines intrinsic methods during bytecode parsing.", type = OptionType.Expert)//
          public static final StableOptionValue<Boolean> InlineIntrinsicsDuringParsing = new StableOptionValue<>(true);
  
          @Option(help = "Traces inlining performed during bytecode parsing.", type = OptionType.Debug)//
          public static final StableOptionValue<Boolean> TraceInlineDuringParsing = new StableOptionValue<>(false);
  
          @Option(help = "Traces use of plugins during bytecode parsing.", type = OptionType.Debug)//
          public static final StableOptionValue<Boolean> TraceParserPlugins = new StableOptionValue<>(false);
  
          @Option(help = "Maximum depth when inlining during bytecode parsing.", type = OptionType.Debug)//
          public static final StableOptionValue<Integer> InlineDuringParsingMaxDepth = new StableOptionValue<>(10);
  
          @Option(help = "Dump graphs after non-trivial changes during bytecode parsing.", type = OptionType.Debug)//
          public static final StableOptionValue<Boolean> DumpDuringGraphBuilding = new StableOptionValue<>(false);
  
          @Option(help = "Max number of loop explosions per method.", type = OptionType.Debug)//
          public static final OptionValue<Integer> MaximumLoopExplosionCount = new OptionValue<>(10000);
  
          @Option(help = "Do not bail out but throw an exception on failed loop explosion.", type = OptionType.Debug)//
          public static final OptionValue<Boolean> FailedLoopExplosionIsFatal = new OptionValue<>(false);
  
          @Option(help = "When creating info points hide the methods of the substitutions.", type = OptionType.Debug)//
          public static final OptionValue<Boolean> HideSubstitutionStates = new OptionValue<>(false);
      }
  
      /**
       * The minimum value to which {@link Options#TraceBytecodeParserLevel} must be set to trace the
       * bytecode instructions as they are parsed.
       */
      public static final int TRACELEVEL_INSTRUCTIONS = 1;
  
      /**
       * The minimum value to which {@link Options#TraceBytecodeParserLevel} must be set to trace the
       * frame state before each bytecode instruction as it is parsed.
       */
      public static final int TRACELEVEL_STATE = 2;
  
      /**
       * Meters the number of actual bytecodes parsed.
       */
      public static final DebugMetric BytecodesParsed = Debug.metric("BytecodesParsed");
  
      protected static final DebugMetric EXPLICIT_EXCEPTIONS = Debug.metric("ExplicitExceptions");
  
      /**
       * A scoped object for tasks to be performed after parsing an intrinsic such as processing
       * {@linkplain BytecodeFrame#isPlaceholderBci(int) placeholder} frames states.
       */
      static class IntrinsicScope implements AutoCloseable {
          FrameState stateBefore;
          final Mark mark;
          final BytecodeParser parser;
  
          /**
           * Creates a scope for root parsing an intrinsic.
           *
           * @param parser the parsing context of the intrinsic
           */
          public IntrinsicScope(BytecodeParser parser) {
              this.parser = parser;
              assert parser.parent == null;
              assert parser.bci() == 0;
              mark = null;
          }
  
          /**
           * Creates a scope for parsing an intrinsic during graph builder inlining.
           *
           * @param parser the parsing context of the (non-intrinsic) method calling the intrinsic
           * @param args the arguments to the call
           */
          public IntrinsicScope(BytecodeParser parser, Kind[] argSlotKinds, ValueNode[] args) {
              assert !parser.parsingIntrinsic();
              this.parser = parser;
              mark = parser.getGraph().getMark();
              stateBefore = parser.frameState.create(parser.bci(), parser.getNonIntrinsicAncestor(), false, argSlotKinds, args);
          }
  
          public void close() {
              IntrinsicContext intrinsic = parser.intrinsicContext;
              if (intrinsic != null && intrinsic.isPostParseInlined()) {
                  return;
              }
  
              processPlaceholderFrameStates(intrinsic);
          }
  
          /**
           * Fixes up the {@linkplain BytecodeFrame#isPlaceholderBci(int) placeholder} frame states
           * added to the graph while parsing/inlining the intrinsic for which this object exists.
           */
          private void processPlaceholderFrameStates(IntrinsicContext intrinsic) {
              FrameState stateAfterReturn = null;
              StructuredGraph graph = parser.getGraph();
              for (Node node : graph.getNewNodes(mark)) {
                  if (node instanceof FrameState) {
                      FrameState frameState = (FrameState) node;
                      if (BytecodeFrame.isPlaceholderBci(frameState.bci)) {
                          if (frameState.bci == BytecodeFrame.AFTER_BCI) {
                              FrameStateBuilder frameStateBuilder = parser.frameState;
                              if (frameState.stackSize() != 0) {
                                  assert frameState.usages().count() == 1;
                                  ValueNode returnVal = frameState.stackAt(0);
                                  assert returnVal == frameState.usages().first();
  
                                  /*
                                   * Swap the top-of-stack value with the side-effect return value
                                   * using the frame state.
                                   */
                                  Kind returnKind = parser.currentInvokeReturnType.getKind();
                                  ValueNode tos = frameStateBuilder.pop(returnKind);
                                  assert tos.getKind() == returnVal.getKind();
                                  FrameState newFrameState = frameStateBuilder.create(parser.stream.nextBCI(), parser.getNonIntrinsicAncestor(), false, new Kind[]{returnKind},
                                                  new ValueNode[]{returnVal});
                                  frameState.replaceAndDelete(newFrameState);
                                  frameStateBuilder.push(returnKind, tos);
                              } else {
                                  if (stateAfterReturn == null) {
                                      if (intrinsic != null) {
                                          assert intrinsic.isCompilationRoot();
                                          stateAfterReturn = graph.add(new FrameState(BytecodeFrame.INVALID_FRAMESTATE_BCI));
                                      } else {
                                          stateAfterReturn = frameStateBuilder.create(parser.stream.nextBCI(), null);
                                      }
                                  }
                                  frameState.replaceAndDelete(stateAfterReturn);
                              }
                          } else if (frameState.bci == BytecodeFrame.BEFORE_BCI) {
                              if (stateBefore == null) {
                                  stateBefore = graph.start().stateAfter();
                              }
                              if (stateBefore != frameState) {
                                  frameState.replaceAndDelete(stateBefore);
                              }
                          } else {
                              assert frameState.bci == BytecodeFrame.INVALID_FRAMESTATE_BCI;
                          }
                      }
                  }
              }
          }
      }
  
      private static class Target {
          FixedNode fixed;
          FrameStateBuilder state;
  
          public Target(FixedNode fixed, FrameStateBuilder state) {
              this.fixed = fixed;
              this.state = state;
          }
      }
  
      private static class ExplodedLoopContext {
          private BciBlock header;
          private int[] targetPeelIteration;
          private int peelIteration;
      }
  
      @SuppressWarnings("serial")
      public static class BytecodeParserError extends JVMCIError {
  
          public BytecodeParserError(Throwable cause) {
              super(cause);
          }
  
          public BytecodeParserError(String msg, Object... args) {
              super(msg, args);
          }
      }
  
      private final GraphBuilderPhase.Instance graphBuilderInstance;
      protected final StructuredGraph graph;
  
      private BciBlockMapping blockMap;
      private LocalLiveness liveness;
      protected final int entryBCI;
      private final BytecodeParser parent;
  
      private LineNumberTable lnt;
      private int previousLineNumber;
      private int currentLineNumber;
  
      private ValueNode methodSynchronizedObject;
  
      private ValueNode returnValue;
      private FixedWithNextNode beforeReturnNode;
      private ValueNode unwindValue;
      private FixedWithNextNode beforeUnwindNode;
  
      private FixedWithNextNode lastInstr;                 // the last instruction added
      private final boolean explodeLoops;
      private final boolean mergeExplosions;
      private final Map<FrameStateBuilder, Integer> mergeExplosionsMap;
      private Deque<ExplodedLoopContext> explodeLoopsContext;
      private int nextPeelIteration = 1;
      private boolean controlFlowSplit;
      private final InvocationPluginReceiver invocationPluginReceiver = new InvocationPluginReceiver(this);
  
      private FixedWithNextNode[] firstInstructionArray;
      private FrameStateBuilder[] entryStateArray;
      private FixedWithNextNode[][] firstInstructionMatrix;
      private FrameStateBuilder[][] entryStateMatrix;
  
      protected BytecodeParser(GraphBuilderPhase.Instance graphBuilderInstance, StructuredGraph graph, BytecodeParser parent, ResolvedJavaMethod method, int entryBCI, IntrinsicContext intrinsicContext) {
          this.graphBuilderInstance = graphBuilderInstance;
          this.graph = graph;
          this.graphBuilderConfig = graphBuilderInstance.graphBuilderConfig;
          this.optimisticOpts = graphBuilderInstance.optimisticOpts;
          this.metaAccess = graphBuilderInstance.metaAccess;
          this.stampProvider = graphBuilderInstance.stampProvider;
          this.constantReflection = graphBuilderInstance.constantReflection;
          this.stream = new BytecodeStream(method.getCode());
          this.profilingInfo = (graphBuilderConfig.getUseProfiling() ? method.getProfilingInfo() : null);
          this.constantPool = method.getConstantPool();
          this.method = method;
          this.intrinsicContext = intrinsicContext;
          this.entryBCI = entryBCI;
          this.parent = parent;
  
          assert method.getCode() != null : "method must contain bytecodes: " + method;
  
          if (graphBuilderConfig.insertNonSafepointDebugInfo() && !parsingIntrinsic()) {
              lnt = method.getLineNumberTable();
              previousLineNumber = -1;
          }
  
          LoopExplosionPlugin loopExplosionPlugin = graphBuilderConfig.getPlugins().getLoopExplosionPlugin();
          if (loopExplosionPlugin != null) {
              explodeLoops = loopExplosionPlugin.shouldExplodeLoops(method);
              if (explodeLoops) {
                  mergeExplosions = loopExplosionPlugin.shouldMergeExplosions(method);
                  mergeExplosionsMap = new HashMap<>();
              } else {
                  mergeExplosions = false;
                  mergeExplosionsMap = null;
              }
          } else {
              explodeLoops = false;
              mergeExplosions = false;
              mergeExplosionsMap = null;
          }
      }
  
      public ValueNode getReturnValue() {
          return returnValue;
      }
  
      public FixedWithNextNode getBeforeReturnNode() {
          return this.beforeReturnNode;
      }
  
      public ValueNode getUnwindValue() {
          return unwindValue;
      }
  
      public FixedWithNextNode getBeforeUnwindNode() {
          return this.beforeUnwindNode;
      }
  
      protected void buildRootMethod() {
          FrameStateBuilder startFrameState = new FrameStateBuilder(this, method, graph);
          startFrameState.initializeForMethodStart(graphBuilderConfig.eagerResolving() || intrinsicContext != null, graphBuilderConfig.getPlugins().getParameterPlugins());
  
          try (IntrinsicScope s = intrinsicContext != null ? new IntrinsicScope(this) : null) {
              build(graph.start(), startFrameState);
          }
  
          cleanupFinalGraph();
          ComputeLoopFrequenciesClosure.compute(graph);
      }
  
      protected void build(FixedWithNextNode startInstruction, FrameStateBuilder startFrameState) {
          if (PrintProfilingInformation.getValue() && profilingInfo != null) {
              TTY.println("Profiling info for " + method.format("%H.%n(%p)"));
              TTY.println(MetaUtil.indent(profilingInfo.toString(method, CodeUtil.NEW_LINE), "  "));
          }
  
          try (Indent indent = Debug.logAndIndent("build graph for %s", method)) {
  
              // compute the block map, setup exception handlers and get the entrypoint(s)
              BciBlockMapping newMapping = BciBlockMapping.create(stream, method);
              this.blockMap = newMapping;
              this.firstInstructionArray = new FixedWithNextNode[blockMap.getBlockCount()];
              this.entryStateArray = new FrameStateBuilder[blockMap.getBlockCount()];
  
              /*
               * Configure the assertion checking behavior of the FrameStateBuilder. This needs to be
               * done only when assertions are enabled, so it is wrapped in an assertion itself.
               */
              assert computeKindVerification(startFrameState);
  
              try (Scope s = Debug.scope("LivenessAnalysis")) {
                  int maxLocals = method.getMaxLocals();
                  liveness = LocalLiveness.compute(stream, blockMap.getBlocks(), maxLocals, blockMap.getLoopCount());
              } catch (Throwable e) {
                  throw Debug.handle(e);
              }
  
              lastInstr = startInstruction;
              this.setCurrentFrameState(startFrameState);
              stream.setBCI(0);
  
              BciBlock startBlock = blockMap.getStartBlock();
              if (this.parent == null) {
                  StartNode startNode = graph.start();
                  if (method.isSynchronized()) {
                      assert !parsingIntrinsic();
                      startNode.setStateAfter(createFrameState(BytecodeFrame.BEFORE_BCI, startNode));
                  } else {
                      if (!parsingIntrinsic()) {
                          if (graph.method() != null && graph.method().isJavaLangObjectInit()) {
                              /*
                               * Don't clear the receiver when Object.<init> is the compilation root.
                               * The receiver is needed as input to RegisterFinalizerNode.
                               */
                          } else {
                              frameState.clearNonLiveLocals(startBlock, liveness, true);
                          }
                          assert bci() == 0;
                          startNode.setStateAfter(createFrameState(bci(), startNode));
                      } else {
                          if (startNode.stateAfter() == null) {
                              FrameState stateAfterStart = createStateAfterStartOfReplacementGraph();
                              startNode.setStateAfter(stateAfterStart);
                          }
                      }
                  }
              }
  
              if (method.isSynchronized()) {
                  // add a monitor enter to the start block
                  methodSynchronizedObject = synchronizedObject(frameState, method);
                  frameState.clearNonLiveLocals(startBlock, liveness, true);
                  assert bci() == 0;
                  genMonitorEnter(methodSynchronizedObject, bci());
              }
  
              finishPrepare(lastInstr);
  
              if (graphBuilderConfig.insertNonSafepointDebugInfo() && !parsingIntrinsic()) {
                  genInfoPointNode(InfopointReason.METHOD_START, null);
              }
  
              currentBlock = blockMap.getStartBlock();
              setEntryState(startBlock, 0, frameState);
              if (startBlock.isLoopHeader && !explodeLoops) {
                  appendGoto(startBlock);
              } else {
                  setFirstInstruction(startBlock, 0, lastInstr);
              }
  
              int index = 0;
              BciBlock[] blocks = blockMap.getBlocks();
              while (index < blocks.length) {
                  BciBlock block = blocks[index];
                  index = iterateBlock(blocks, block);
              }
  
              if (this.mergeExplosions) {
                  Debug.dump(graph, "Before loop detection");
                  detectLoops(startInstruction);
              }
  
              if (Debug.isDumpEnabled() && DumpDuringGraphBuilding.getValue() && this.beforeReturnNode != startInstruction) {
                  Debug.dump(graph, "Bytecodes parsed: " + method.getDeclaringClass().getUnqualifiedName() + "." + method.getName());
              }
          }
      }
  
      private boolean computeKindVerification(FrameStateBuilder startFrameState) {
          if (blockMap.hasJsrBytecodes) {
              /*
               * The JSR return address is an int value, but stored using the astore bytecode. Instead
               * of weakening the kind assertion checking for all methods, we disable it completely
               * for methods that contain a JSR bytecode.
               */
              startFrameState.disableKindVerification();
          }
  
          for (NodePlugin plugin : graphBuilderConfig.getPlugins().getNodePlugins()) {
              if (plugin.canChangeStackKind(this)) {
                  /*
                   * We have a plugin that can change the kind of values, so no kind assertion
                   * checking is possible.
                   */
                  startFrameState.disableKindVerification();
              }
          }
          return true;
      }
  
      /**
       * Hook for subclasses to modify the graph start instruction or append new instructions to it.
       *
       * @param startInstr the start instruction of the graph
       */
      protected void finishPrepare(FixedWithNextNode startInstr) {
      }
  
      protected void cleanupFinalGraph() {
          GraphUtil.normalizeLoops(graph);
  
          // Remove dead parameters.
          for (ParameterNode param : graph.getNodes(ParameterNode.TYPE)) {
              if (param.hasNoUsages()) {
                  assert param.inputs().isEmpty();
                  param.safeDelete();
              }
          }
  
          // Remove redundant begin nodes.
          for (BeginNode beginNode : graph.getNodes(BeginNode.TYPE)) {
              Node predecessor = beginNode.predecessor();
              if (predecessor instanceof ControlSplitNode) {
                  // The begin node is necessary.
              } else {
                  if (beginNode.hasUsages()) {
                      reanchorGuardedNodes(beginNode);
                  }
                  GraphUtil.unlinkFixedNode(beginNode);
                  beginNode.safeDelete();
              }
          }
      }
  
      /**
       * Removes {@link GuardedNode}s from {@code beginNode}'s usages and re-attaches them to an
       * appropriate preceeding {@link GuardingNode}.
       */
      protected void reanchorGuardedNodes(BeginNode beginNode) {
          // Find the new guarding node
          GuardingNode guarding = null;
          Node pred = beginNode.predecessor();
          while (pred != null) {
              if (pred instanceof BeginNode) {
                  if (pred.predecessor() instanceof ControlSplitNode) {
                      guarding = (GuardingNode) pred;
                      break;
                  }
              } else if (pred.getNodeClass().getAllowedUsageTypes().contains(InputType.Guard)) {
                  guarding = (GuardingNode) pred;
                  break;
              }
              pred = pred.predecessor();
          }
  
          // Reset the guard for all of beginNode's usages
          for (Node usage : beginNode.usages().snapshot()) {
              GuardedNode guarded = (GuardedNode) usage;
              assert guarded.getGuard() == beginNode;
              guarded.setGuard(guarding);
          }
          assert beginNode.hasNoUsages() : beginNode;
      }
  
      /**
       * Creates the frame state after the start node of a graph for an {@link IntrinsicContext
       * intrinsic} that is the parse root (either for root compiling or for post-parse inlining).
       */
      private FrameState createStateAfterStartOfReplacementGraph() {
          assert parent == null;
          assert frameState.getMethod().equals(intrinsicContext.getIntrinsicMethod());
          assert bci() == 0;
          assert frameState.stackSize() == 0;
          FrameState stateAfterStart;
          if (intrinsicContext.isPostParseInlined()) {
              stateAfterStart = graph.add(new FrameState(BytecodeFrame.BEFORE_BCI));
          } else {
              ResolvedJavaMethod original = intrinsicContext.getOriginalMethod();
              ValueNode[] locals;
              if (original.getMaxLocals() == frameState.localsSize() || original.isNative()) {
                  locals = new ValueNode[original.getMaxLocals()];
                  for (int i = 0; i < locals.length; i++) {
                      ValueNode node = frameState.locals[i];
                      if (node == FrameState.TWO_SLOT_MARKER) {
                          node = null;
                      }
                      locals[i] = node;
                  }
              } else {
                  locals = new ValueNode[original.getMaxLocals()];
                  int parameterCount = original.getSignature().getParameterCount(!original.isStatic());
                  for (int i = 0; i < parameterCount; i++) {
                      ValueNode param = frameState.locals[i];
                      if (param == FrameState.TWO_SLOT_MARKER) {
                          param = null;
                      }
                      locals[i] = param;
                      assert param == null || param instanceof ParameterNode || param.isConstant();
                  }
              }
              ValueNode[] stack = {};
              int stackSize = 0;
              ValueNode[] locks = {};
              List<MonitorIdNode> monitorIds = Collections.emptyList();
              stateAfterStart = graph.add(new FrameState(null, original, 0, locals, stack, stackSize, locks, monitorIds, false, false));
          }
          return stateAfterStart;
      }
  
      private void detectLoops(FixedNode startInstruction) {
          NodeBitMap visited = graph.createNodeBitMap();
          NodeBitMap active = graph.createNodeBitMap();
          Deque<Node> stack = new ArrayDeque<>();
          stack.add(startInstruction);
          visited.mark(startInstruction);
          while (!stack.isEmpty()) {
              Node next = stack.peek();
              assert next.isDeleted() || visited.isMarked(next);
              if (next.isDeleted() || active.isMarked(next)) {
                  stack.pop();
                  if (!next.isDeleted()) {
                      active.clear(next);
                  }
              } else {
                  active.mark(next);
                  for (Node n : next.cfgSuccessors()) {
                      if (active.contains(n)) {
                          // Detected cycle.
                          assert n instanceof MergeNode;
                          assert next instanceof EndNode;
                          MergeNode merge = (MergeNode) n;
                          EndNode endNode = (EndNode) next;
                          merge.removeEnd(endNode);
                          FixedNode afterMerge = merge.next();
                          if (!(afterMerge instanceof EndNode) || !(((EndNode) afterMerge).merge() instanceof LoopBeginNode)) {
                              merge.setNext(null);
                              LoopBeginNode newLoopBegin = this.appendLoopBegin(merge);
                              newLoopBegin.setNext(afterMerge);
                          }
                          LoopBeginNode loopBegin = (LoopBeginNode) ((EndNode) merge.next()).merge();
                          LoopEndNode loopEnd = graph.add(new LoopEndNode(loopBegin));
                          if (parsingIntrinsic()) {
                              loopEnd.disableSafepoint();
                          }
                          endNode.replaceAndDelete(loopEnd);
                      } else if (visited.contains(n)) {
                          // Normal merge into a branch we are already exploring.
                      } else {
                          visited.mark(n);
                          stack.push(n);
                      }
                  }
              }
          }
  
          Debug.dump(graph, "After loops detected");
          insertLoopEnds(startInstruction);
      }
  
      private void insertLoopEnds(FixedNode startInstruction) {
          NodeBitMap visited = graph.createNodeBitMap();
          Deque<Node> stack = new ArrayDeque<>();
          stack.add(startInstruction);
          visited.mark(startInstruction);
          List<LoopBeginNode> loopBegins = new ArrayList<>();
          while (!stack.isEmpty()) {
              Node next = stack.pop();
              assert visited.isMarked(next);
              if (next instanceof LoopBeginNode) {
                  loopBegins.add((LoopBeginNode) next);
              }
              for (Node n : next.cfgSuccessors()) {
                  if (visited.contains(n)) {
                      // Nothing to do.
                  } else {
                      visited.mark(n);
                      stack.push(n);
                  }
              }
          }
  
          IdentityHashMap<LoopBeginNode, List<LoopBeginNode>> innerLoopsMap = new IdentityHashMap<>();
          for (int i = loopBegins.size() - 1; i >= 0; --i) {
              LoopBeginNode loopBegin = loopBegins.get(i);
              insertLoopExits(loopBegin, innerLoopsMap);
              if (DumpDuringGraphBuilding.getValue()) {
                  Debug.dump(graph, "After building loop exits for %s.", loopBegin);
              }
          }
  
          // Remove degenerated merges with only one predecessor.
          for (LoopBeginNode loopBegin : loopBegins) {
              Node pred = loopBegin.forwardEnd().predecessor();
              if (pred instanceof MergeNode) {
                  MergeNode.removeMergeIfDegenerated((MergeNode) pred);
              }
          }
      }
  
      private void insertLoopExits(LoopBeginNode loopBegin, IdentityHashMap<LoopBeginNode, List<LoopBeginNode>> innerLoopsMap) {
          NodeBitMap visited = graph.createNodeBitMap();
          Deque<Node> stack = new ArrayDeque<>();
          for (LoopEndNode loopEnd : loopBegin.loopEnds()) {
              stack.push(loopEnd);
              visited.mark(loopEnd);
          }
  
          List<ControlSplitNode> controlSplits = new ArrayList<>();
          List<LoopBeginNode> innerLoopBegins = new ArrayList<>();
  
          while (!stack.isEmpty()) {
              Node current = stack.pop();
              if (current == loopBegin) {
                  continue;
              }
              for (Node pred : current.cfgPredecessors()) {
                  if (!visited.isMarked(pred)) {
                      visited.mark(pred);
                      if (pred instanceof LoopExitNode) {
                          // Inner loop
                          LoopExitNode loopExitNode = (LoopExitNode) pred;
                          LoopBeginNode innerLoopBegin = loopExitNode.loopBegin();
                          if (!visited.isMarked(innerLoopBegin)) {
                              stack.push(innerLoopBegin);
                              visited.mark(innerLoopBegin);
                              innerLoopBegins.add(innerLoopBegin);
                          }
                      } else {
                          if (pred instanceof ControlSplitNode) {
                              ControlSplitNode controlSplitNode = (ControlSplitNode) pred;
                              controlSplits.add(controlSplitNode);
                          }
                          stack.push(pred);
                      }
                  }
              }
          }
  
          for (ControlSplitNode controlSplit : controlSplits) {
              for (Node succ : controlSplit.cfgSuccessors()) {
                  if (!visited.isMarked(succ)) {
                      LoopExitNode loopExit = graph.add(new LoopExitNode(loopBegin));
                      FixedNode next = ((FixedWithNextNode) succ).next();
                      next.replaceAtPredecessor(loopExit);
                      loopExit.setNext(next);
                  }
              }
          }
  
          for (LoopBeginNode inner : innerLoopBegins) {
              addLoopExits(loopBegin, inner, innerLoopsMap, visited);
              if (DumpDuringGraphBuilding.getValue()) {
                  Debug.dump(graph, "After adding loop exits for %s.", inner);
              }
          }
  
          innerLoopsMap.put(loopBegin, innerLoopBegins);
      }
  
      private void addLoopExits(LoopBeginNode loopBegin, LoopBeginNode inner, IdentityHashMap<LoopBeginNode, List<LoopBeginNode>> innerLoopsMap, NodeBitMap visited) {
          for (LoopExitNode exit : inner.loopExits()) {
              if (!visited.isMarked(exit)) {
                  LoopExitNode newLoopExit = graph.add(new LoopExitNode(loopBegin));
                  FixedNode next = exit.next();
                  next.replaceAtPredecessor(newLoopExit);
                  newLoopExit.setNext(next);
              }
          }
  
          for (LoopBeginNode innerInner : innerLoopsMap.get(inner)) {
              addLoopExits(loopBegin, innerInner, innerLoopsMap, visited);
          }
      }
  
      private int iterateBlock(BciBlock[] blocks, BciBlock block) {
          if (block.isLoopHeader && this.explodeLoops) {
              return iterateExplodedLoopHeader(blocks, block);
          } else {
              processBlock(this, block);
              return block.getId() + 1;
          }
      }
  
      private int iterateExplodedLoopHeader(BciBlock[] blocks, BciBlock header) {
          if (explodeLoopsContext == null) {
              explodeLoopsContext = new ArrayDeque<>();
          }
  
          ExplodedLoopContext context = new ExplodedLoopContext();
          context.header = header;
          context.peelIteration = this.getCurrentDimension();
          if (this.mergeExplosions) {
              this.addToMergeCache(getEntryState(context.header, context.peelIteration), context.peelIteration);
          }
          explodeLoopsContext.push(context);
          if (Debug.isDumpEnabled() && DumpDuringGraphBuilding.getValue()) {
              Debug.dump(graph, "before loop explosion dimension " + context.peelIteration);
          }
          peelIteration(blocks, header, context);
          explodeLoopsContext.pop();
          return header.loopEnd + 1;
      }
  
      private void addToMergeCache(FrameStateBuilder key, int dimension) {
          mergeExplosionsMap.put(key, dimension);
      }
  
      private void peelIteration(BciBlock[] blocks, BciBlock header, ExplodedLoopContext context) {
          while (true) {
              if (TraceParserPlugins.getValue()) {
                  traceWithContext("exploding loop, iteration %d", context.peelIteration);
              }
              processBlock(this, header);
              int j = header.getId() + 1;
              while (j <= header.loopEnd) {
                  BciBlock block = blocks[j];
                  j = iterateBlock(blocks, block);
              }
  
              int[] targets = context.targetPeelIteration;
              if (targets != null) {
                  // We were reaching the backedge during explosion. Explode further.
                  for (int i = 0; i < targets.length; ++i) {
                      context.peelIteration = targets[i];
                      context.targetPeelIteration = null;
                      if (Debug.isDumpEnabled() && DumpDuringGraphBuilding.getValue()) {
                          Debug.dump(graph, "next loop explosion iteration " + context.peelIteration);
                      }
                      if (i < targets.length - 1) {
                          peelIteration(blocks, header, context);
                      }
                  }
              } else {
                  // We did not reach the backedge. Exit.
                  break;
              }
          }
      }
  
      /**
       * @param type the unresolved type of the constant
       */
      protected void handleUnresolvedLoadConstant(JavaType type) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param type the unresolved type of the type check
       * @param object the object value whose type is being checked against {@code type}
       */
      protected void handleUnresolvedCheckCast(JavaType type, ValueNode object) {
          assert !graphBuilderConfig.eagerResolving();
          append(new FixedGuardNode(graph.unique(new IsNullNode(object)), Unresolved, InvalidateRecompile));
          frameState.push(Kind.Object, appendConstant(JavaConstant.NULL_POINTER));
      }
  
      /**
       * @param type the unresolved type of the type check
       * @param object the object value whose type is being checked against {@code type}
       */
      protected void handleUnresolvedInstanceOf(JavaType type, ValueNode object) {
          assert !graphBuilderConfig.eagerResolving();
          AbstractBeginNode successor = graph.add(new BeginNode());
          DeoptimizeNode deopt = graph.add(new DeoptimizeNode(InvalidateRecompile, Unresolved));
          append(new IfNode(graph.unique(new IsNullNode(object)), successor, deopt, 1));
          lastInstr = successor;
          frameState.push(Kind.Int, appendConstant(JavaConstant.INT_0));
      }
  
      /**
       * @param type the type being instantiated
       */
      protected void handleUnresolvedNewInstance(JavaType type) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param type the type of the array being instantiated
       * @param length the length of the array
       */
      protected void handleUnresolvedNewObjectArray(JavaType type, ValueNode length) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param type the type being instantiated
       * @param dims the dimensions for the multi-array
       */
      protected void handleUnresolvedNewMultiArray(JavaType type, List<ValueNode> dims) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param field the unresolved field
       * @param receiver the object containing the field or {@code null} if {@code field} is static
       */
      protected void handleUnresolvedLoadField(JavaField field, ValueNode receiver) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param field the unresolved field
       * @param value the value being stored to the field
       * @param receiver the object containing the field or {@code null} if {@code field} is static
       */
      protected void handleUnresolvedStoreField(JavaField field, ValueNode value, ValueNode receiver) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param type
       */
      protected void handleUnresolvedExceptionType(JavaType type) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      /**
       * @param javaMethod
       * @param invokeKind
       */
      protected void handleUnresolvedInvoke(JavaMethod javaMethod, InvokeKind invokeKind) {
          assert !graphBuilderConfig.eagerResolving();
          append(new DeoptimizeNode(InvalidateRecompile, Unresolved));
      }
  
      private DispatchBeginNode handleException(ValueNode exceptionObject, int bci) {
          assert bci == BytecodeFrame.BEFORE_BCI || bci == bci() : "invalid bci";
          Debug.log("Creating exception dispatch edges at %d, exception object=%s, exception seen=%s", bci, exceptionObject, (profilingInfo == null ? "" : profilingInfo.getExceptionSeen(bci)));
  
          BciBlock dispatchBlock = currentBlock.exceptionDispatchBlock();
          /*
           * The exception dispatch block is always for the last bytecode of a block, so if we are not
           * at the endBci yet, there is no exception handler for this bci and we can unwind
           * immediately.
           */
          if (bci != currentBlock.endBci || dispatchBlock == null) {
              dispatchBlock = blockMap.getUnwindBlock();
          }
  
          FrameStateBuilder dispatchState = frameState.copy();
          dispatchState.clearStack();
  
          DispatchBeginNode dispatchBegin;
          if (exceptionObject == null) {
              dispatchBegin = graph.add(new ExceptionObjectNode(metaAccess));
              dispatchState.push(Kind.Object, dispatchBegin);
              dispatchState.setRethrowException(true);
              dispatchBegin.setStateAfter(dispatchState.create(bci, dispatchBegin));
          } else {
              dispatchBegin = graph.add(new DispatchBeginNode());
              dispatchState.push(Kind.Object, exceptionObject);
              dispatchState.setRethrowException(true);
              dispatchBegin.setStateAfter(dispatchState.create(bci, dispatchBegin));
          }
          this.controlFlowSplit = true;
          FixedNode target = createTarget(dispatchBlock, dispatchState);
          FixedWithNextNode finishedDispatch = finishInstruction(dispatchBegin, dispatchState);
          finishedDispatch.setNext(target);
          return dispatchBegin;
      }
  
      protected ValueNode genLoadIndexed(ValueNode array, ValueNode index, Kind kind) {
          return LoadIndexedNode.create(array, index, kind, metaAccess, constantReflection);
      }
  
      protected void genStoreIndexed(ValueNode array, ValueNode index, Kind kind, ValueNode value) {
          add(new StoreIndexedNode(array, index, kind, value));
      }
  
      protected ValueNode genIntegerAdd(ValueNode x, ValueNode y) {
          return AddNode.create(x, y);
      }
  
      protected ValueNode genIntegerSub(ValueNode x, ValueNode y) {
          return SubNode.create(x, y);
      }
  
      protected ValueNode genIntegerMul(ValueNode x, ValueNode y) {
          return MulNode.create(x, y);
      }
  
      protected ValueNode genFloatAdd(ValueNode x, ValueNode y) {
          return AddNode.create(x, y);
      }
  
      protected ValueNode genFloatSub(ValueNode x, ValueNode y) {
          return SubNode.create(x, y);
      }
  
      protected ValueNode genFloatMul(ValueNode x, ValueNode y) {
          return MulNode.create(x, y);
      }
  
      protected ValueNode genFloatDiv(ValueNode x, ValueNode y) {
          return DivNode.create(x, y);
      }
  
      protected ValueNode genFloatRem(ValueNode x, ValueNode y) {
          return new RemNode(x, y);
      }
  
      protected ValueNode genIntegerDiv(ValueNode x, ValueNode y) {
          return new IntegerDivNode(x, y);
      }
  
      protected ValueNode genIntegerRem(ValueNode x, ValueNode y) {
          return new IntegerRemNode(x, y);
      }
  
      protected ValueNode genNegateOp(ValueNode x) {
          return (new NegateNode(x));
      }
  
      protected ValueNode genLeftShift(ValueNode x, ValueNode y) {
          return new LeftShiftNode(x, y);
      }
  
      protected ValueNode genRightShift(ValueNode x, ValueNode y) {
          return new RightShiftNode(x, y);
      }
  
      protected ValueNode genUnsignedRightShift(ValueNode x, ValueNode y) {
          return new UnsignedRightShiftNode(x, y);
      }
  
      protected ValueNode genAnd(ValueNode x, ValueNode y) {
          return AndNode.create(x, y);
      }
  
      protected ValueNode genOr(ValueNode x, ValueNode y) {
          return OrNode.create(x, y);
      }
  
      protected ValueNode genXor(ValueNode x, ValueNode y) {
          return XorNode.create(x, y);
      }
  
      protected ValueNode genNormalizeCompare(ValueNode x, ValueNode y, boolean isUnorderedLess) {
          return NormalizeCompareNode.create(x, y, isUnorderedLess, constantReflection);
      }
  
      protected ValueNode genFloatConvert(FloatConvert op, ValueNode input) {
          return FloatConvertNode.create(op, input);
      }
  
      protected ValueNode genNarrow(ValueNode input, int bitCount) {
          return NarrowNode.create(input, bitCount);
      }
  
      protected ValueNode genSignExtend(ValueNode input, int bitCount) {
          return SignExtendNode.create(input, bitCount);
      }
  
      protected ValueNode genZeroExtend(ValueNode input, int bitCount) {
          return ZeroExtendNode.create(input, bitCount);
      }
  
      protected void genGoto() {
          appendGoto(currentBlock.getSuccessor(0));
          assert currentBlock.numNormalSuccessors() == 1;
      }
  
      protected LogicNode genObjectEquals(ValueNode x, ValueNode y) {
          return ObjectEqualsNode.create(x, y, constantReflection);
      }
  
      protected LogicNode genIntegerEquals(ValueNode x, ValueNode y) {
          return IntegerEqualsNode.create(x, y, constantReflection);
      }
  
      protected LogicNode genIntegerLessThan(ValueNode x, ValueNode y) {
          return IntegerLessThanNode.create(x, y, constantReflection);
      }
  
      protected ValueNode genUnique(ValueNode x) {
          return (ValueNode) graph.unique((Node & ValueNumberable) x);
      }
  
      protected ValueNode genIfNode(LogicNode condition, FixedNode falseSuccessor, FixedNode trueSuccessor, double d) {
          return new IfNode(condition, falseSuccessor, trueSuccessor, d);
      }
  
      protected void genThrow() {
          genInfoPointNode(InfopointReason.LINE_NUMBER, null);
  
          ValueNode exception = frameState.pop(Kind.Object);
          FixedGuardNode nullCheck = append(new FixedGuardNode(graph.unique(new IsNullNode(exception)), NullCheckException, InvalidateReprofile, true));
          PiNode nonNullException = graph.unique(new PiNode(exception, exception.stamp().join(objectNonNull())));
          nonNullException.setGuard(nullCheck);
          lastInstr.setNext(handleException(nonNullException, bci()));
      }
  
      protected ValueNode createCheckCast(ResolvedJavaType type, ValueNode object, JavaTypeProfile profileForTypeCheck, boolean forStoreCheck) {
          return CheckCastNode.create(type, object, profileForTypeCheck, forStoreCheck, graph.getAssumptions());
      }
  
      protected ValueNode createInstanceOf(ResolvedJavaType type, ValueNode object, JavaTypeProfile profileForTypeCheck) {
          return InstanceOfNode.create(type, object, profileForTypeCheck);
      }
  
      protected ValueNode genConditional(ValueNode x) {
          return new ConditionalNode((LogicNode) x);
      }
  
      protected NewInstanceNode createNewInstance(ResolvedJavaType type, boolean fillContents) {
          return new NewInstanceNode(type, fillContents);
      }
  
      protected NewArrayNode createNewArray(ResolvedJavaType elementType, ValueNode length, boolean fillContents) {
          return new NewArrayNode(elementType, length, fillContents);
      }
  
      protected NewMultiArrayNode createNewMultiArray(ResolvedJavaType type, List<ValueNode> dimensions) {
          return new NewMultiArrayNode(type, dimensions.toArray(new ValueNode[0]));
      }
  
      protected ValueNode genLoadField(ValueNode receiver, ResolvedJavaField field) {
          return new LoadFieldNode(receiver, field);
      }
  
      protected ValueNode emitExplicitNullCheck(ValueNode receiver) {
          if (StampTool.isPointerNonNull(receiver.stamp())) {
              return receiver;
          }
          BytecodeExceptionNode exception = graph.add(new BytecodeExceptionNode(metaAccess, NullPointerException.class));
          AbstractBeginNode falseSucc = graph.add(new BeginNode());
          PiNode nonNullReceiver = graph.unique(new PiNode(receiver, receiver.stamp().join(objectNonNull())));
          nonNullReceiver.setGuard(falseSucc);
          append(new IfNode(graph.unique(new IsNullNode(receiver)), exception, falseSucc, 0.01));
          lastInstr = falseSucc;
  
          exception.setStateAfter(createFrameState(bci(), exception));
          exception.setNext(handleException(exception, bci()));
          return nonNullReceiver;
      }
  
      protected void emitExplicitBoundsCheck(ValueNode index, ValueNode length) {
          AbstractBeginNode trueSucc = graph.add(new BeginNode());
          BytecodeExceptionNode exception = graph.add(new BytecodeExceptionNode(metaAccess, ArrayIndexOutOfBoundsException.class, index));
          append(new IfNode(graph.unique(IntegerBelowNode.create(index, length, constantReflection)), trueSucc, exception, 0.99));
          lastInstr = trueSucc;
  
          exception.setStateAfter(createFrameState(bci(), exception));
          exception.setNext(handleException(exception, bci()));
      }
  
      protected ValueNode genArrayLength(ValueNode x) {
          return ArrayLengthNode.create(x, constantReflection);
      }
  
      protected void genStoreField(ValueNode receiver, ResolvedJavaField field, ValueNode value) {
          StoreFieldNode storeFieldNode = new StoreFieldNode(receiver, field, value);
          append(storeFieldNode);
          storeFieldNode.setStateAfter(this.createFrameState(stream.nextBCI(), storeFieldNode));
      }
  
      /**
       * Ensure that concrete classes are at least linked before generating an invoke. Interfaces may
       * never be linked so simply return true for them.
       *
       * @param target
       * @return true if the declared holder is an interface or is linked
       */
      private static boolean callTargetIsResolved(JavaMethod target) {
          if (target instanceof ResolvedJavaMethod) {
              ResolvedJavaMethod resolvedTarget = (ResolvedJavaMethod) target;
              ResolvedJavaType resolvedType = resolvedTarget.getDeclaringClass();
              return resolvedType.isInterface() || resolvedType.isLinked();
          }
          return false;
      }
  
      protected void genInvokeStatic(JavaMethod target) {
          if (callTargetIsResolved(target)) {
              ResolvedJavaMethod resolvedTarget = (ResolvedJavaMethod) target;
              ResolvedJavaType holder = resolvedTarget.getDeclaringClass();
              if (!holder.isInitialized() && ResolveClassBeforeStaticInvoke.getValue()) {
                  handleUnresolvedInvoke(target, InvokeKind.Static);
              } else {
                  ValueNode[] args = frameState.popArguments(resolvedTarget.getSignature().getParameterCount(false));
                  appendInvoke(InvokeKind.Static, resolvedTarget, args);
              }
          } else {
              handleUnresolvedInvoke(target, InvokeKind.Static);
          }
      }
  
      protected void genInvokeInterface(JavaMethod target) {
          if (callTargetIsResolved(target)) {
              ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(true));
              appendInvoke(InvokeKind.Interface, (ResolvedJavaMethod) target, args);
          } else {
              handleUnresolvedInvoke(target, InvokeKind.Interface);
          }
      }
  
      protected void genInvokeDynamic(JavaMethod target) {
          if (target instanceof ResolvedJavaMethod) {
              JavaConstant appendix = constantPool.lookupAppendix(stream.readCPI4(), Bytecodes.INVOKEDYNAMIC);
              if (appendix != null) {
                  frameState.push(Kind.Object, ConstantNode.forConstant(appendix, metaAccess, graph));
              }
              ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(false));
              appendInvoke(InvokeKind.Static, (ResolvedJavaMethod) target, args);
          } else {
              handleUnresolvedInvoke(target, InvokeKind.Static);
          }
      }
  
      protected void genInvokeVirtual(JavaMethod target) {
          if (callTargetIsResolved(target)) {
              /*
               * Special handling for runtimes that rewrite an invocation of MethodHandle.invoke(...)
               * or MethodHandle.invokeExact(...) to a static adapter. HotSpot does this - see
               * https://wikis.oracle.com/display/HotSpotInternals/Method+handles +and+invokedynamic
               */
              boolean hasReceiver = !((ResolvedJavaMethod) target).isStatic();
              JavaConstant appendix = constantPool.lookupAppendix(stream.readCPI(), Bytecodes.INVOKEVIRTUAL);
              if (appendix != null) {
                  frameState.push(Kind.Object, ConstantNode.forConstant(appendix, metaAccess, graph));
              }
              ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(hasReceiver));
              if (hasReceiver) {
                  appendInvoke(InvokeKind.Virtual, (ResolvedJavaMethod) target, args);
              } else {
                  appendInvoke(InvokeKind.Static, (ResolvedJavaMethod) target, args);
              }
          } else {
              handleUnresolvedInvoke(target, InvokeKind.Virtual);
          }
  
      }
  
      protected void genInvokeSpecial(JavaMethod target) {
          if (callTargetIsResolved(target)) {
              assert target != null;
              assert target.getSignature() != null;
              ValueNode[] args = frameState.popArguments(target.getSignature().getParameterCount(true));
              appendInvoke(InvokeKind.Special, (ResolvedJavaMethod) target, args);
          } else {
              handleUnresolvedInvoke(target, InvokeKind.Special);
          }
      }
  
      private InvokeKind currentInvokeKind;
      private JavaType currentInvokeReturnType;
      protected FrameStateBuilder frameState;
      protected BciBlock currentBlock;
      protected final BytecodeStream stream;
      protected final GraphBuilderConfiguration graphBuilderConfig;
      protected final ResolvedJavaMethod method;
      protected final ProfilingInfo profilingInfo;
      protected final OptimisticOptimizations optimisticOpts;
      protected final ConstantPool constantPool;
      protected final MetaAccessProvider metaAccess;
      private final ConstantReflectionProvider constantReflection;
      private final StampProvider stampProvider;
      protected final IntrinsicContext intrinsicContext;
  
      public InvokeKind getInvokeKind() {
          return currentInvokeKind;
      }
  
      public JavaType getInvokeReturnType() {
          return currentInvokeReturnType;
      }
  
      private boolean forceInliningEverything;
  
      public void handleReplacedInvoke(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, boolean inlineEverything) {
          boolean previous = forceInliningEverything;
          forceInliningEverything = previous || inlineEverything;
          try {
              appendInvoke(invokeKind, targetMethod, args);
          } finally {
              forceInliningEverything = previous;
          }
      }
  
      private void appendInvoke(InvokeKind initialInvokeKind, ResolvedJavaMethod initialTargetMethod, ValueNode[] args) {
          ResolvedJavaMethod targetMethod = initialTargetMethod;
          InvokeKind invokeKind = initialInvokeKind;
          if (initialInvokeKind.isIndirect()) {
              ResolvedJavaType contextType = this.frameState.getMethod().getDeclaringClass();
              ResolvedJavaMethod specialCallTarget = MethodCallTargetNode.findSpecialCallTarget(initialInvokeKind, args[0], initialTargetMethod, contextType);
              if (specialCallTarget != null) {
                  invokeKind = InvokeKind.Special;
                  targetMethod = specialCallTarget;
              }
          }
  
          Kind resultType = targetMethod.getSignature().getReturnKind();
          if (DeoptALot.getValue()) {
              append(new DeoptimizeNode(DeoptimizationAction.None, RuntimeConstraint));
              frameState.pushReturn(resultType, ConstantNode.defaultForKind(resultType, graph));
              return;
          }
  
          JavaType returnType = targetMethod.getSignature().getReturnType(method.getDeclaringClass());
          if (graphBuilderConfig.eagerResolving() || parsingIntrinsic()) {
              returnType = returnType.resolve(targetMethod.getDeclaringClass());
          }
          if (invokeKind.hasReceiver()) {
              args[0] = emitExplicitExceptions(args[0], null);
  
              if (args[0].isNullConstant()) {
                  append(new DeoptimizeNode(InvalidateRecompile, NullCheckException));
                  return;
              }
          }
  
          try {
              currentInvokeReturnType = returnType;
              currentInvokeKind = invokeKind;
              if (tryNodePluginForInvocation(args, targetMethod)) {
                  if (TraceParserPlugins.getValue()) {
                      traceWithContext("used node plugin for %s", targetMethod.format("%h.%n(%p)"));
                  }
                  return;
              }
  
              if (invokeKind.isDirect()) {
                  if (tryInvocationPlugin(args, targetMethod, resultType)) {
                      if (TraceParserPlugins.getValue()) {
                          traceWithContext("used invocation plugin for %s", targetMethod.format("%h.%n(%p)"));
                      }
                      return;
                  }
  
                  if (tryInline(args, targetMethod, returnType)) {
                      return;
                  }
              }
          } finally {
              currentInvokeReturnType = null;
              currentInvokeKind = null;
          }
  
          JavaTypeProfile profile = null;
          if (invokeKind.isIndirect() && profilingInfo != null && this.optimisticOpts.useTypeCheckHints()) {
              profile = profilingInfo.getTypeProfile(bci());
          }
          MethodCallTargetNode callTarget = graph.add(createMethodCallTarget(invokeKind, targetMethod, args, returnType, profile));
  
          // be conservative if information was not recorded (could result in endless
          // recompiles otherwise)
          Invoke invoke;
          if (lastInlineInfo == InlineInfo.DO_NOT_INLINE_NO_EXCEPTION || graphBuilderConfig.omitAllExceptionEdges() ||
                          (!StressInvokeWithExceptionNode.getValue() && optimisticOpts.useExceptionProbability() && profilingInfo != null && profilingInfo.getExceptionSeen(bci()) == TriState.FALSE)) {
              invoke = createInvoke(callTarget, resultType);
          } else {
              invoke = createInvokeWithException(callTarget, resultType);
              AbstractBeginNode beginNode = graph.add(new KillingBeginNode(LocationIdentity.any()));
              invoke.setNext(beginNode);
              lastInstr = beginNode;
          }
  
          for (InlineInvokePlugin plugin : graphBuilderConfig.getPlugins().getInlineInvokePlugins()) {
              plugin.notifyNotInlined(this, targetMethod, invoke);
          }
      }
  
      /**
       * Contains all the assertion checking logic around the application of an
       * {@link InvocationPlugin}. This class is only loaded when assertions are enabled.
       */
      class InvocationPluginAssertions {
          final InvocationPlugin plugin;
          final ValueNode[] args;
          final ResolvedJavaMethod targetMethod;
          final Kind resultType;
          final int beforeStackSize;
          final boolean needsNullCheck;
          final int nodeCount;
          final Mark mark;
  
          public InvocationPluginAssertions(InvocationPlugin plugin, ValueNode[] args, ResolvedJavaMethod targetMethod, Kind resultType) {
              guarantee(assertionsEnabled(), "%s should only be loaded and instantiated if assertions are enabled", getClass().getSimpleName());
              this.plugin = plugin;
              this.targetMethod = targetMethod;
              this.args = args;
              this.resultType = resultType;
              this.beforeStackSize = frameState.stackSize();
              this.needsNullCheck = !targetMethod.isStatic() && args[0].getKind() == Kind.Object && !StampTool.isPointerNonNull(args[0].stamp());
              this.nodeCount = graph.getNodeCount();
              this.mark = graph.getMark();
          }
  
          String error(String format, Object... a) {
              return String.format(format, a) + String.format("%n\tplugin at %s", plugin.getApplySourceLocation(metaAccess));
          }
  
          boolean check(boolean pluginResult) {
              if (pluginResult == true) {
                  int expectedStackSize = beforeStackSize + resultType.getSlotCount();
                  assert expectedStackSize == frameState.stackSize() : error("plugin manipulated the stack incorrectly: expected=%d, actual=%d", expectedStackSize, frameState.stackSize());
                  NodeIterable<Node> newNodes = graph.getNewNodes(mark);
                  assert !needsNullCheck || isPointerNonNull(args[0].stamp()) : error("plugin needs to null check the receiver of %s: receiver=%s", targetMethod.format("%H.%n(%p)"), args[0]);
                  for (Node n : newNodes) {
                      if (n instanceof StateSplit) {
                          StateSplit stateSplit = (StateSplit) n;
                          assert stateSplit.stateAfter() != null || !stateSplit.hasSideEffect() : error("%s node added by plugin for %s need to have a non-null frame state: %s",
                                          StateSplit.class.getSimpleName(), targetMethod.format("%H.%n(%p)"), stateSplit);
                      }
                  }
                  try {
                      graphBuilderConfig.getPlugins().getInvocationPlugins().checkNewNodes(BytecodeParser.this, plugin, newNodes);
                  } catch (Throwable t) {
                      throw new AssertionError(error("Error in plugin"), t);
                  }
              } else {
                  assert nodeCount == graph.getNodeCount() : error("plugin that returns false must not create new nodes");
                  assert beforeStackSize == frameState.stackSize() : error("plugin that returns false must not modify the stack");
              }
              return true;
          }
      }
  
      private boolean tryInvocationPlugin(ValueNode[] args, ResolvedJavaMethod targetMethod, Kind resultType) {
          InvocationPlugin plugin = graphBuilderConfig.getPlugins().getInvocationPlugins().lookupInvocation(targetMethod);
          if (plugin != null) {
  
              if (intrinsicContext != null && intrinsicContext.isCallToOriginal(targetMethod)) {
                  // Self recursive intrinsic means the original
                  // method should be called.
                  assert !targetMethod.hasBytecodes() : "TODO: when does this happen?";
                  return false;
              }
  
              InvocationPluginAssertions assertions = assertionsEnabled() ? new InvocationPluginAssertions(plugin, args, targetMethod, resultType) : null;
              if (plugin.execute(this, targetMethod, invocationPluginReceiver.init(targetMethod, args), args)) {
                  assert assertions.check(true);
                  return true;
              }
              assert assertions.check(false);
          }
          return false;
      }
  
      private boolean tryNodePluginForInvocation(ValueNode[] args, ResolvedJavaMethod targetMethod) {
          for (NodePlugin plugin : graphBuilderConfig.getPlugins().getNodePlugins()) {
              if (plugin.handleInvoke(this, targetMethod, args)) {
                  return true;
              }
          }
          return false;
      }
  
      InlineInfo lastInlineInfo;
  
      private boolean tryInline(ValueNode[] args, ResolvedJavaMethod targetMethod, JavaType returnType) {
          boolean canBeInlined = forceInliningEverything || parsingIntrinsic() || targetMethod.canBeInlined();
          if (!canBeInlined) {
              return false;
          }
  
          if (forceInliningEverything) {
              return inline(targetMethod, targetMethod, false, args);
          }
  
          for (InlineInvokePlugin plugin : graphBuilderConfig.getPlugins().getInlineInvokePlugins()) {
              lastInlineInfo = plugin.shouldInlineInvoke(this, targetMethod, args, returnType);
              if (lastInlineInfo != null) {
                  if (lastInlineInfo.getMethodToInline() == null) {
                      /* Do not inline, and do not ask the remaining plugins. */
                      return false;
                  } else {
                      return inline(targetMethod, lastInlineInfo.getMethodToInline(), lastInlineInfo.isIntrinsic(), args);
                  }
              }
          }
          return false;
      }
  
      public void intrinsify(ResolvedJavaMethod targetMethod, ResolvedJavaMethod substitute, ValueNode[] args) {
          boolean res = inline(targetMethod, substitute, true, args);
          assert res : "failed to inline " + substitute;
      }
  
      private boolean inline(ResolvedJavaMethod targetMethod, ResolvedJavaMethod inlinedMethod, boolean isIntrinsic, ValueNode[] args) {
          if (TraceInlineDuringParsing.getValue() || TraceParserPlugins.getValue()) {
              if (targetMethod.equals(inlinedMethod)) {
                  traceWithContext("inlining call to %s", inlinedMethod.format("%h.%n(%p)"));
              } else {
                  traceWithContext("inlining call to %s as intrinsic for %s", inlinedMethod.format("%h.%n(%p)"), targetMethod.format("%h.%n(%p)"));
              }
          }
          IntrinsicContext intrinsic = this.intrinsicContext;
          if (intrinsic != null && intrinsic.isCallToOriginal(targetMethod)) {
              if (intrinsic.isCompilationRoot()) {
                  // A root compiled intrinsic needs to deoptimize
                  // if the slow path is taken. During frame state
                  // assignment, the deopt node will get its stateBefore
                  // from the start node of the intrinsic
                  append(new DeoptimizeNode(InvalidateRecompile, RuntimeConstraint));
                  return true;
              } else {
                  // Otherwise inline the original method. Any frame state created
                  // during the inlining will exclude frame(s) in the
                  // intrinsic method (see HIRFrameStateBuilder.create(int bci)).
                  if (intrinsic.getOriginalMethod().isNative()) {
                      return false;
                  }
                  parseAndInlineCallee(intrinsic.getOriginalMethod(), args, null);
                  return true;
              }
          } else {
              if (intrinsic == null && isIntrinsic) {
                  assert !inlinedMethod.equals(targetMethod);
                  intrinsic = new IntrinsicContext(targetMethod, inlinedMethod, INLINE_DURING_PARSING);
              }
              if (inlinedMethod.hasBytecodes()) {
                  for (InlineInvokePlugin plugin : graphBuilderConfig.getPlugins().getInlineInvokePlugins()) {
                      plugin.notifyBeforeInline(inlinedMethod);
                  }
                  parseAndInlineCallee(inlinedMethod, args, intrinsic);
                  for (InlineInvokePlugin plugin : graphBuilderConfig.getPlugins().getInlineInvokePlugins()) {
                      plugin.notifyAfterInline(inlinedMethod);
                  }
              } else {
                  return false;
              }
          }
          return true;
      }
  
      /**
       * Prints a line to {@link TTY} with a prefix indicating the current parse context. The prefix
       * is of the form:
       *
       * <pre>
       * {SPACE * n} {name of method being parsed} "(" {file name} ":" {line number} ")"
       * </pre>
       *
       * where {@code n} is the current inlining depth.
       *
       * @param format a format string
       * @param args arguments to the format string
       */
  
      protected void traceWithContext(String format, Object... args) {
          StackTraceElement where = method.asStackTraceElement(bci());
          TTY.println(format("%s%s (%s:%d) %s", nSpaces(getDepth()), method.isConstructor() ? method.format("%h.%n") : method.getName(), where.getFileName(), where.getLineNumber(), format(format, args)));
      }
  
      protected BytecodeParserError asParserError(Throwable e) {
          if (e instanceof BytecodeParserError) {
              return (BytecodeParserError) e;
          }
          BytecodeParser bp = this;
          BytecodeParserError res = new BytecodeParserError(e);
          while (bp != null) {
              res.addContext("parsing " + bp.method.asStackTraceElement(bp.bci()));
              bp = bp.parent;
          }
          return res;
      }
  
      private void parseAndInlineCallee(ResolvedJavaMethod targetMethod, ValueNode[] args, IntrinsicContext calleeIntrinsicContext) {
          try (IntrinsicScope s = calleeIntrinsicContext != null && !parsingIntrinsic() ? new IntrinsicScope(this, targetMethod.getSignature().toParameterKinds(!targetMethod.isStatic()), args) : null) {
  
              BytecodeParser parser = graphBuilderInstance.createBytecodeParser(graph, this, targetMethod, Compiler.INVOCATION_ENTRY_BCI, calleeIntrinsicContext);
              FrameStateBuilder startFrameState = new FrameStateBuilder(parser, targetMethod, graph);
              if (!targetMethod.isStatic()) {
                  args[0] = nullCheckedValue(args[0]);
              }
              startFrameState.initializeFromArgumentsArray(args);
              parser.build(this.lastInstr, startFrameState);
  
              FixedWithNextNode calleeBeforeReturnNode = parser.getBeforeReturnNode();
              this.lastInstr = calleeBeforeReturnNode;
              Kind calleeReturnKind = targetMethod.getSignature().getReturnKind();
              if (calleeBeforeReturnNode != null) {
                  ValueNode calleeReturnValue = parser.getReturnValue();
                  if (calleeReturnValue != null) {
                      frameState.push(calleeReturnKind.getStackKind(), calleeReturnValue);
                  }
              }
  
              FixedWithNextNode calleeBeforeUnwindNode = parser.getBeforeUnwindNode();
              if (calleeBeforeUnwindNode != null) {
                  ValueNode calleeUnwindValue = parser.getUnwindValue();
                  assert calleeUnwindValue != null;
                  calleeBeforeUnwindNode.setNext(handleException(calleeUnwindValue, bci()));
              }
  
              // Record inlined method dependency in the graph
              graph.recordInlinedMethod(targetMethod);
          }
      }
  
      protected MethodCallTargetNode createMethodCallTarget(InvokeKind invokeKind, ResolvedJavaMethod targetMethod, ValueNode[] args, JavaType returnType, JavaTypeProfile profile) {
          return new MethodCallTargetNode(invokeKind, targetMethod, args, returnType, profile);
      }
  
      protected InvokeNode createInvoke(CallTargetNode callTarget, Kind resultType) {
          InvokeNode invoke = append(new InvokeNode(callTarget, bci()));
          frameState.pushReturn(resultType, invoke);
          invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke));
          return invoke;
      }
  
      protected InvokeWithExceptionNode createInvokeWithException(CallTargetNode callTarget, Kind resultType) {
          if (currentBlock != null && stream.nextBCI() > currentBlock.endBci) {
              /*
               * Clear non-live locals early so that the exception handler entry gets the cleared
               * state.
               */
              frameState.clearNonLiveLocals(currentBlock, liveness, false);
          }
  
          DispatchBeginNode exceptionEdge = handleException(null, bci());
          InvokeWithExceptionNode invoke = append(new InvokeWithExceptionNode(callTarget, exceptionEdge, bci()));
          frameState.pushReturn(resultType, invoke);
          invoke.setStateAfter(createFrameState(stream.nextBCI(), invoke));
          return invoke;
      }
  
      protected void genReturn(ValueNode returnVal, Kind returnKind) {
          if (parsingIntrinsic() && returnVal != null) {
              if (returnVal instanceof StateSplit) {
                  StateSplit stateSplit = (StateSplit) returnVal;
                  FrameState stateAfter = stateSplit.stateAfter();
                  if (stateSplit.hasSideEffect()) {
                      assert stateSplit != null;
                      if (stateAfter.bci == BytecodeFrame.AFTER_BCI) {
                          assert stateAfter.usages().count() == 1;
                          assert stateAfter.usages().first() == stateSplit;
                          stateAfter.replaceAtUsages(graph.add(new FrameState(BytecodeFrame.AFTER_BCI, returnVal)));
                          GraphUtil.killWithUnusedFloatingInputs(stateAfter);
                      } else {
                          /*
                           * This must be the return value from within a partial intrinsification.
                           */
                          assert !BytecodeFrame.isPlaceholderBci(stateAfter.bci);
                      }
                  } else {
                      assert stateAfter == null;
                  }
              }
          }
          if (parent == null) {
              frameState.setRethrowException(false);
              frameState.clearStack();
              beforeReturn(returnVal, returnKind);
              append(new ReturnNode(returnVal));
          } else {
              if (blockMap.getReturnCount() == 1 || !controlFlowSplit) {
                  // There is only a single return.
                  beforeReturn(returnVal, returnKind);
                  this.returnValue = returnVal;
                  this.beforeReturnNode = this.lastInstr;
                  this.lastInstr = null;
              } else {
                  frameState.setRethrowException(false);
                  frameState.clearStack();
                  if (returnVal != null) {
                      frameState.push(returnKind, returnVal);
                  }
                  assert blockMap.getReturnCount() > 1;
                  appendGoto(blockMap.getReturnBlock());
              }
          }
      }
  
      private void beforeReturn(ValueNode x, Kind kind) {
          if (graph.method() != null && graph.method().isJavaLangObjectInit()) {
              append(new RegisterFinalizerNode(frameState.loadLocal(0, Kind.Object)));
          }
          if (graphBuilderConfig.insertNonSafepointDebugInfo() && !parsingIntrinsic()) {
              genInfoPointNode(InfopointReason.METHOD_END, x);
          }
  
          synchronizedEpilogue(BytecodeFrame.AFTER_BCI, x, kind);
          if (frameState.lockDepth(false) != 0) {
              throw bailout("unbalanced monitors");
          }
      }
  
      protected void genMonitorEnter(ValueNode x, int bci) {
          MonitorIdNode monitorId = graph.add(new MonitorIdNode(frameState.lockDepth(true)));
          MonitorEnterNode monitorEnter = append(new MonitorEnterNode(x, monitorId));
          frameState.pushLock(x, monitorId);
          monitorEnter.setStateAfter(createFrameState(bci, monitorEnter));
      }
  
      protected void genMonitorExit(ValueNode x, ValueNode escapedReturnValue, int bci) {
          MonitorIdNode monitorId = frameState.peekMonitorId();
          ValueNode lockedObject = frameState.popLock();
          if (GraphUtil.originalValue(lockedObject) != GraphUtil.originalValue(x)) {
              throw bailout(String.format("unbalanced monitors: mismatch at monitorexit, %s != %s", GraphUtil.originalValue(x), GraphUtil.originalValue(lockedObject)));
          }
          MonitorExitNode monitorExit = append(new MonitorExitNode(x, monitorId, escapedReturnValue));
          monitorExit.setStateAfter(createFrameState(bci, monitorExit));
      }
  
      protected void genJsr(int dest) {
          BciBlock successor = currentBlock.getJsrSuccessor();
          assert successor.startBci == dest : successor.startBci + " != " + dest + " @" + bci();
          JsrScope scope = currentBlock.getJsrScope();
          int nextBci = getStream().nextBCI();
          if (!successor.getJsrScope().pop().equals(scope)) {
              throw new JsrNotSupportedBailout("unstructured control flow (internal limitation)");
          }
          if (successor.getJsrScope().nextReturnAddress() != nextBci) {
              throw new JsrNotSupportedBailout("unstructured control flow (internal limitation)");
          }
          ConstantNode nextBciNode = getJsrConstant(nextBci);
          frameState.push(Kind.Object, nextBciNode);
          appendGoto(successor);
      }
  
      protected void genRet(int localIndex) {
          BciBlock successor = currentBlock.getRetSuccessor();
          ValueNode local = frameState.loadLocal(localIndex, Kind.Object);
          JsrScope scope = currentBlock.getJsrScope();
          int retAddress = scope.nextReturnAddress();
          ConstantNode returnBciNode = getJsrConstant(retAddress);
          LogicNode guard = IntegerEqualsNode.create(local, returnBciNode, constantReflection);
          guard = graph.unique(guard);
          append(new FixedGuardNode(guard, JavaSubroutineMismatch, InvalidateReprofile));
          if (!successor.getJsrScope().equals(scope.pop())) {
              throw new JsrNotSupportedBailout("unstructured control flow (ret leaves more than one scope)");
          }
          appendGoto(successor);
      }
  
      private ConstantNode getJsrConstant(long bci) {
          JavaConstant nextBciConstant = new RawConstant(bci);
          Stamp nextBciStamp = StampFactory.forConstant(nextBciConstant);
          ConstantNode nextBciNode = new ConstantNode(nextBciConstant, nextBciStamp);
          return graph.unique(nextBciNode);
      }
  
      protected void genIntegerSwitch(ValueNode value, ArrayList<BciBlock> actualSuccessors, int[] keys, double[] keyProbabilities, int[] keySuccessors) {
          if (value.isConstant()) {
              JavaConstant constant = (JavaConstant) value.asConstant();
              int constantValue = constant.asInt();
              for (int i = 0; i < keys.length; ++i) {
                  if (keys[i] == constantValue) {
                      appendGoto(actualSuccessors.get(keySuccessors[i]));
                      return;
                  }
              }
              appendGoto(actualSuccessors.get(keySuccessors[keys.length]));
          } else {
              this.controlFlowSplit = true;
              double[] successorProbabilities = successorProbabilites(actualSuccessors.size(), keySuccessors, keyProbabilities);
              IntegerSwitchNode switchNode = append(new IntegerSwitchNode(value, actualSuccessors.size(), keys, keyProbabilities, keySuccessors));
              for (int i = 0; i < actualSuccessors.size(); i++) {
                  switchNode.setBlockSuccessor(i, createBlockTarget(successorProbabilities[i], actualSuccessors.get(i), frameState));
              }
          }
      }
  
      /**
       * Helper function that sums up the probabilities of all keys that lead to a specific successor.
       *
       * @return an array of size successorCount with the accumulated probability for each successor.
       */
      private static double[] successorProbabilites(int successorCount, int[] keySuccessors, double[] keyProbabilities) {
          double[] probability = new double[successorCount];
          for (int i = 0; i < keySuccessors.length; i++) {
              probability[keySuccessors[i]] += keyProbabilities[i];
          }
          return probability;
      }
  
      protected ConstantNode appendConstant(JavaConstant constant) {
          assert constant != null;
          return ConstantNode.forConstant(constant, metaAccess, graph);
      }
  
      @Override
      public <T extends ValueNode> T append(T v) {
          if (v.graph() != null) {
              return v;
          }
          T added = graph.addOrUnique(v);
          if (added == v) {
              updateLastInstruction(v);
          }
          return added;
      }
  
      public <T extends ValueNode> T recursiveAppend(T v) {
          if (v.graph() != null) {
              return v;
          }
          T added = graph.addOrUniqueWithInputs(v);
          if (added == v) {
              updateLastInstruction(v);
          }
          return added;
      }
  
      private <T extends ValueNode> void updateLastInstruction(T v) {
          if (v instanceof FixedNode) {
              FixedNode fixedNode = (FixedNode) v;
              lastInstr.setNext(fixedNode);
              if (fixedNode instanceof FixedWithNextNode) {
                  FixedWithNextNode fixedWithNextNode = (FixedWithNextNode) fixedNode;
                  assert fixedWithNextNode.next() == null : "cannot append instruction to instruction which isn't end";
                  lastInstr = fixedWithNextNode;
              } else {
                  lastInstr = null;
              }
          }
      }
  
      private Target checkLoopExit(FixedNode target, BciBlock targetBlock, FrameStateBuilder state) {
          if (currentBlock != null && !explodeLoops) {
              long exits = currentBlock.loops & ~targetBlock.loops;
              if (exits != 0) {
                  LoopExitNode firstLoopExit = null;
                  LoopExitNode lastLoopExit = null;
  
                  int pos = 0;
                  ArrayList<BciBlock> exitLoops = new ArrayList<>(Long.bitCount(exits));
                  do {
                      long lMask = 1L << pos;
                      if ((exits & lMask) != 0) {
                          exitLoops.add(blockMap.getLoopHeader(pos));
                          exits &= ~lMask;
                      }
                      pos++;
                  } while (exits != 0);
  
                  Collections.sort(exitLoops, new Comparator<BciBlock>() {
  
                      @Override
                      public int compare(BciBlock o1, BciBlock o2) {
                          return Long.bitCount(o2.loops) - Long.bitCount(o1.loops);
                      }
                  });
  
                  int bci = targetBlock.startBci;
                  if (targetBlock instanceof ExceptionDispatchBlock) {
                      bci = ((ExceptionDispatchBlock) targetBlock).deoptBci;
                  }
                  FrameStateBuilder newState = state.copy();
                  for (BciBlock loop : exitLoops) {
                      LoopBeginNode loopBegin = (LoopBeginNode) getFirstInstruction(loop, this.getCurrentDimension());
                      LoopExitNode loopExit = graph.add(new LoopExitNode(loopBegin));
                      if (lastLoopExit != null) {
                          lastLoopExit.setNext(loopExit);
                      }
                      if (firstLoopExit == null) {
                          firstLoopExit = loopExit;
                      }
                      lastLoopExit = loopExit;
                      Debug.log("Target %s Exits %s, scanning framestates...", targetBlock, loop);
                      newState.insertLoopProxies(loopExit, getEntryState(loop, this.getCurrentDimension()));
                      loopExit.setStateAfter(newState.create(bci, loopExit));
                  }
  
                  lastLoopExit.setNext(target);
                  return new Target(firstLoopExit, newState);
              }
          }
          return new Target(target, state);
      }
  
      private FrameStateBuilder getEntryState(BciBlock block, int dimension) {
          int id = block.id;
          if (dimension == 0) {
              return entryStateArray[id];
          } else {
              return getEntryStateMultiDimension(dimension, id);
          }
      }
  
      private FrameStateBuilder getEntryStateMultiDimension(int dimension, int id) {
          if (entryStateMatrix != null && dimension - 1 < entryStateMatrix.length) {
              FrameStateBuilder[] entryStateArrayEntry = entryStateMatrix[dimension - 1];
              if (entryStateArrayEntry == null) {
                  return null;
              }
              return entryStateArrayEntry[id];
          } else {
              return null;
          }
      }
  
      private void setEntryState(BciBlock block, int dimension, FrameStateBuilder entryState) {
          int id = block.id;
          if (dimension == 0) {
              this.entryStateArray[id] = entryState;
          } else {
              setEntryStateMultiDimension(dimension, entryState, id);
          }
      }
  
      private void setEntryStateMultiDimension(int dimension, FrameStateBuilder entryState, int id) {
          if (entryStateMatrix == null) {
              entryStateMatrix = new FrameStateBuilder[4][];
          }
          if (dimension - 1 < entryStateMatrix.length) {
              // We are within bounds.
          } else {
              // We are out of bounds.
              entryStateMatrix = Arrays.copyOf(entryStateMatrix, Math.max(entryStateMatrix.length * 2, dimension));
          }
          if (entryStateMatrix[dimension - 1] == null) {
              entryStateMatrix[dimension - 1] = new FrameStateBuilder[blockMap.getBlockCount()];
          }
          entryStateMatrix[dimension - 1][id] = entryState;
      }
  
      private void setFirstInstruction(BciBlock block, int dimension, FixedWithNextNode firstInstruction) {
          int id = block.id;
          if (dimension == 0) {
              this.firstInstructionArray[id] = firstInstruction;
          } else {
              setFirstInstructionMultiDimension(dimension, firstInstruction, id);
          }
      }
  
      private void setFirstInstructionMultiDimension(int dimension, FixedWithNextNode firstInstruction, int id) {
          if (firstInstructionMatrix == null) {
              firstInstructionMatrix = new FixedWithNextNode[4][];
          }
          if (dimension - 1 < firstInstructionMatrix.length) {
              // We are within bounds.
          } else {
              // We are out of bounds.
              firstInstructionMatrix = Arrays.copyOf(firstInstructionMatrix, Math.max(firstInstructionMatrix.length * 2, dimension));
          }
          if (firstInstructionMatrix[dimension - 1] == null) {
              firstInstructionMatrix[dimension - 1] = new FixedWithNextNode[blockMap.getBlockCount()];
          }
          firstInstructionMatrix[dimension - 1][id] = firstInstruction;
      }
  
      private FixedWithNextNode getFirstInstruction(BciBlock block, int dimension) {
          int id = block.id;
          if (dimension == 0) {
              return firstInstructionArray[id];
          } else {
              return getFirstInstructionMultiDimension(dimension, id);
          }
      }
  
      private FixedWithNextNode getFirstInstructionMultiDimension(int dimension, int id) {
          if (firstInstructionMatrix != null && dimension - 1 < firstInstructionMatrix.length) {
              FixedWithNextNode[] firstInstructionArrayEntry = firstInstructionMatrix[dimension - 1];
              if (firstInstructionArrayEntry == null) {
                  return null;
              }
              return firstInstructionArrayEntry[id];
          } else {
              return null;
          }
      }
  
      private FixedNode createTarget(double probability, BciBlock block, FrameStateBuilder stateAfter) {
          assert probability >= 0 && probability <= 1.01 : probability;
          if (isNeverExecutedCode(probability)) {
              return graph.add(new DeoptimizeNode(InvalidateReprofile, UnreachedCode));
          } else {
              assert block != null;
              return createTarget(block, stateAfter);
          }
      }
  
      private FixedNode createTarget(BciBlock block, FrameStateBuilder state) {
          return createTarget(block, state, false, false);
      }
  
      private FixedNode createTarget(BciBlock block, FrameStateBuilder state, boolean canReuseInstruction, boolean canReuseState) {
          assert block != null && state != null;
          assert !block.isExceptionEntry || state.stackSize() == 1;
  
          int operatingDimension = findOperatingDimension(block, state);
  
          if (getFirstInstruction(block, operatingDimension) == null) {
              /*
               * This is the first time we see this block as a branch target. Create and return a
               * placeholder that later can be replaced with a MergeNode when we see this block again.
               */
              FixedNode targetNode;
              if (canReuseInstruction && (block.getPredecessorCount() == 1 || !controlFlowSplit) && !block.isLoopHeader && (currentBlock.loops & ~block.loops) == 0) {
                  setFirstInstruction(block, operatingDimension, lastInstr);
                  lastInstr = null;
              } else {
                  setFirstInstruction(block, operatingDimension, graph.add(new BeginNode()));
              }
              targetNode = getFirstInstruction(block, operatingDimension);
              Target target = checkLoopExit(targetNode, block, state);
              FixedNode result = target.fixed;
              FrameStateBuilder currentEntryState = target.state == state ? (canReuseState ? state : state.copy()) : target.state;
              setEntryState(block, operatingDimension, currentEntryState);
              currentEntryState.clearNonLiveLocals(block, liveness, true);
  
              Debug.log("createTarget %s: first visit, result: %s", block, targetNode);
              return result;
          }
  
          // We already saw this block before, so we have to merge states.
          if (!getEntryState(block, operatingDimension).isCompatibleWith(state)) {
              throw bailout("stacks do not match; bytecodes would not verify");
          }
  
          if (getFirstInstruction(block, operatingDimension) instanceof LoopBeginNode) {
              assert this.explodeLoops || (block.isLoopHeader && currentBlock.getId() >= block.getId()) : "must be backward branch";
              /*
               * Backward loop edge. We need to create a special LoopEndNode and merge with the loop
               * begin node created before.
               */
              LoopBeginNode loopBegin = (LoopBeginNode) getFirstInstruction(block, operatingDimension);
              LoopEndNode loopEnd = graph.add(new LoopEndNode(loopBegin));
              if (parsingIntrinsic()) {
                  loopEnd.disableSafepoint();
              }
              Target target = checkLoopExit(loopEnd, block, state);
              FixedNode result = target.fixed;
              getEntryState(block, operatingDimension).merge(loopBegin, target.state);
  
              Debug.log("createTarget %s: merging backward branch to loop header %s, result: %s", block, loopBegin, result);
              return result;
          }
          assert currentBlock == null || currentBlock.getId() < block.getId() || this.mergeExplosions : "must not be backward branch";
          assert getFirstInstruction(block, operatingDimension).next() == null || this.mergeExplosions : "bytecodes already parsed for block";
  
          if (getFirstInstruction(block, operatingDimension) instanceof AbstractBeginNode && !(getFirstInstruction(block, operatingDimension) instanceof AbstractMergeNode)) {
              /*
               * This is the second time we see this block. Create the actual MergeNode and the End
               * Node for the already existing edge.
               */
              AbstractBeginNode beginNode = (AbstractBeginNode) getFirstInstruction(block, operatingDimension);
  
              // The EndNode for the already existing edge.
              EndNode end = graph.add(new EndNode());
              // The MergeNode that replaces the placeholder.
              AbstractMergeNode mergeNode = graph.add(new MergeNode());
              FixedNode next = beginNode.next();
  
              if (beginNode.predecessor() instanceof ControlSplitNode) {
                  beginNode.setNext(end);
              } else {
                  beginNode.replaceAtPredecessor(end);
                  beginNode.safeDelete();
              }
  
              mergeNode.addForwardEnd(end);
              mergeNode.setNext(next);
  
              setFirstInstruction(block, operatingDimension, mergeNode);
          }
  
          AbstractMergeNode mergeNode = (AbstractMergeNode) getFirstInstruction(block, operatingDimension);
  
          // The EndNode for the newly merged edge.
          EndNode newEnd = graph.add(new EndNode());
          Target target = checkLoopExit(newEnd, block, state);
          FixedNode result = target.fixed;
          getEntryState(block, operatingDimension).merge(mergeNode, target.state);
          mergeNode.addForwardEnd(newEnd);
  
          Debug.log("createTarget %s: merging state, result: %s", block, result);
          return result;
      }
  
      private int findOperatingDimension(BciBlock block, FrameStateBuilder state) {
          if (this.explodeLoops && this.explodeLoopsContext != null && !this.explodeLoopsContext.isEmpty()) {
              return findOperatingDimensionWithLoopExplosion(block, state);
          }
          return this.getCurrentDimension();
      }
  
      private int findOperatingDimensionWithLoopExplosion(BciBlock block, FrameStateBuilder state) {
          for (ExplodedLoopContext context : explodeLoopsContext) {
              if (context.header == block) {
  
                  if (this.mergeExplosions) {
                      state.clearNonLiveLocals(block, liveness, true);
                      Integer cachedDimension = mergeExplosionsMap.get(state);
                      if (cachedDimension != null) {
                          return cachedDimension;
                      }
                  }
  
                  // We have a hit on our current explosion context loop begin.
                  if (context.targetPeelIteration == null) {
                      context.targetPeelIteration = new int[1];
                  } else {
                      context.targetPeelIteration = Arrays.copyOf(context.targetPeelIteration, context.targetPeelIteration.length + 1);
                  }
  
                  // This is the first hit => allocate a new dimension and at the same
                  // time mark the context loop begin as hit during the current
                  // iteration.
                  if (this.mergeExplosions) {
                      this.addToMergeCache(state, nextPeelIteration);
                  }
                  context.targetPeelIteration[context.targetPeelIteration.length - 1] = nextPeelIteration++;
                  if (nextPeelIteration > MaximumLoopExplosionCount.getValue()) {
                      String message = "too many loop explosion iterations - does the explosion not terminate for method " + method + "?";
                      if (FailedLoopExplosionIsFatal.getValue()) {
                          throw new RuntimeException(message);
                      } else {
                          throw bailout(message);
                      }
                  }
  
                  // Operate on the target dimension.
                  return context.targetPeelIteration[context.targetPeelIteration.length - 1];
              } else if (block.getId() > context.header.getId() && block.getId() <= context.header.loopEnd) {
                  // We hit the range of this context.
                  return context.peelIteration;
              }
          }
  
          // No dimension found.
          return 0;
      }
  
      /**
       * Returns a block begin node with the specified state. If the specified probability is 0, the
       * block deoptimizes immediately.
       */
      private AbstractBeginNode createBlockTarget(double probability, BciBlock block, FrameStateBuilder stateAfter) {
          FixedNode target = createTarget(probability, block, stateAfter);
  