/*
 * 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 org.graalvm.compiler.java;

import static org.graalvm.compiler.bytecode.Bytecodes.AALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.AASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.ARETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.ARRAYLENGTH;
import static org.graalvm.compiler.bytecode.Bytecodes.ATHROW;
import static org.graalvm.compiler.bytecode.Bytecodes.BALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.BASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.CALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.CASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.CHECKCAST;
import static org.graalvm.compiler.bytecode.Bytecodes.DALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.DASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.DRETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.FALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.FASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.FRETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.GETFIELD;
import static org.graalvm.compiler.bytecode.Bytecodes.GETSTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.GOTO;
import static org.graalvm.compiler.bytecode.Bytecodes.GOTO_W;
import static org.graalvm.compiler.bytecode.Bytecodes.IALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.IASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.IDIV;
import static org.graalvm.compiler.bytecode.Bytecodes.IFEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IFGE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFGT;
import static org.graalvm.compiler.bytecode.Bytecodes.IFLE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFLT;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNE;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNONNULL;
import static org.graalvm.compiler.bytecode.Bytecodes.IFNULL;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ACMPNE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPEQ;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPGT;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLE;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPLT;
import static org.graalvm.compiler.bytecode.Bytecodes.IF_ICMPNE;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEDYNAMIC;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEINTERFACE;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESPECIAL;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKESTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.INVOKEVIRTUAL;
import static org.graalvm.compiler.bytecode.Bytecodes.IREM;
import static org.graalvm.compiler.bytecode.Bytecodes.IRETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.JSR;
import static org.graalvm.compiler.bytecode.Bytecodes.JSR_W;
import static org.graalvm.compiler.bytecode.Bytecodes.LALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.LASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.LDIV;
import static org.graalvm.compiler.bytecode.Bytecodes.LOOKUPSWITCH;
import static org.graalvm.compiler.bytecode.Bytecodes.LREM;
import static org.graalvm.compiler.bytecode.Bytecodes.LRETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.PUTFIELD;
import static org.graalvm.compiler.bytecode.Bytecodes.PUTSTATIC;
import static org.graalvm.compiler.bytecode.Bytecodes.RET;
import static org.graalvm.compiler.bytecode.Bytecodes.RETURN;
import static org.graalvm.compiler.bytecode.Bytecodes.SALOAD;
import static org.graalvm.compiler.bytecode.Bytecodes.SASTORE;
import static org.graalvm.compiler.bytecode.Bytecodes.TABLESWITCH;
import static org.graalvm.compiler.core.common.GraalOptions.SupportJsrBytecodes;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import jdk.internal.vm.compiler.collections.EconomicMap;
import jdk.internal.vm.compiler.collections.Equivalence;
import org.graalvm.compiler.bytecode.Bytecode;
import org.graalvm.compiler.bytecode.BytecodeLookupSwitch;
import org.graalvm.compiler.bytecode.BytecodeStream;
import org.graalvm.compiler.bytecode.BytecodeSwitch;
import org.graalvm.compiler.bytecode.BytecodeTableSwitch;
import org.graalvm.compiler.bytecode.Bytecodes;
import org.graalvm.compiler.core.common.PermanentBailoutException;
import org.graalvm.compiler.debug.DebugContext;
import org.graalvm.compiler.options.OptionValues;

import jdk.vm.ci.code.BytecodeFrame;
import jdk.vm.ci.meta.ExceptionHandler;

Builds a mapping between bytecodes and basic blocks and builds a conservative control flow graph (CFG). It makes one linear passes over the bytecodes to build the CFG where it detects block headers and connects them.

It also creates exception dispatch blocks for exception handling. These blocks are between a bytecode that might throw an exception, and the actual exception handler entries, and are later used to create the type checks with the exception handler catch types. If a bytecode is covered by an exception handler, this bytecode ends the basic block. This guarantees that a) control flow cannot be transferred to an exception dispatch block in the middle of a block, and b) that every block has at most one exception dispatch block (which is always the last entry in the successor list).

If a bytecode is covered by multiple exception handlers, a chain of exception dispatch blocks is created so that multiple exception handler types can be checked. The chains are re-used if multiple bytecodes are covered by the same exception handlers.

Note that exception unwinds, i.e., bytecodes that can throw an exception but the exception is not handled in this method, do not end a basic block. Not modeling the exception unwind block reduces the complexity of the CFG, and there is no algorithm yet where the exception unwind block would matter.

The class also handles subroutines (jsr and ret bytecodes): subroutines are inlined by duplicating the subroutine blocks. This is limited to simple, structured subroutines with a maximum subroutine nesting of 4. Otherwise, a bailout is thrown.

Loops in the methods are detected. If a method contains an irreducible loop (a loop with more than one entry), a bailout is thrown. This simplifies the compiler later on since only structured loops need to be supported.

A data flow analysis computes the live local variables from the point of view of the interpreter. The result is used later to prune frame states, i.e., remove local variable entries that are guaranteed to be never used again (even in the case of deoptimization).

The algorithms and analysis in this class are conservative and do not use any assumptions or profiling information.

/** * Builds a mapping between bytecodes and basic blocks and builds a conservative control flow graph * (CFG). It makes one linear passes over the bytecodes to build the CFG where it detects block * headers and connects them. * <p> * It also creates exception dispatch blocks for exception handling. These blocks are between a * bytecode that might throw an exception, and the actual exception handler entries, and are later * used to create the type checks with the exception handler catch types. If a bytecode is covered * by an exception handler, this bytecode ends the basic block. This guarantees that a) control flow * cannot be transferred to an exception dispatch block in the middle of a block, and b) that every * block has at most one exception dispatch block (which is always the last entry in the successor * list). * <p> * If a bytecode is covered by multiple exception handlers, a chain of exception dispatch blocks is * created so that multiple exception handler types can be checked. The chains are re-used if * multiple bytecodes are covered by the same exception handlers. * <p> * Note that exception unwinds, i.e., bytecodes that can throw an exception but the exception is not * handled in this method, do not end a basic block. Not modeling the exception unwind block reduces * the complexity of the CFG, and there is no algorithm yet where the exception unwind block would * matter. * <p> * The class also handles subroutines (jsr and ret bytecodes): subroutines are inlined by * duplicating the subroutine blocks. This is limited to simple, structured subroutines with a * maximum subroutine nesting of 4. Otherwise, a bailout is thrown. * <p> * Loops in the methods are detected. If a method contains an irreducible loop (a loop with more * than one entry), a bailout is thrown. This simplifies the compiler later on since only structured * loops need to be supported. * <p> * A data flow analysis computes the live local variables from the point of view of the interpreter. * The result is used later to prune frame states, i.e., remove local variable entries that are * guaranteed to be never used again (even in the case of deoptimization). * <p> * The algorithms and analysis in this class are conservative and do not use any assumptions or * profiling information. */
public final class BciBlockMapping { public static class BciBlock implements Cloneable { int id; final int startBci; int endBci; private boolean isExceptionEntry; private boolean isLoopHeader; int loopId; int loopEnd; List<BciBlock> successors; private int predecessorCount; private boolean visited; private boolean active; long loops; JSRData jsrData; public static class JSRData implements Cloneable { public EconomicMap<JsrScope, BciBlock> jsrAlternatives; public JsrScope jsrScope = JsrScope.EMPTY_SCOPE; public BciBlock jsrSuccessor; public int jsrReturnBci; public BciBlock retSuccessor; public boolean endsWithRet = false; public JSRData copy() { try { return (JSRData) this.clone(); } catch (CloneNotSupportedException e) { return null; } } } BciBlock(int startBci) { this.startBci = startBci; this.successors = new ArrayList<>(); } public int getStartBci() { return startBci; } public int getEndBci() { return endBci; } public long getLoops() { return loops; } public BciBlock exceptionDispatchBlock() { if (successors.size() > 0 && successors.get(successors.size() - 1) instanceof ExceptionDispatchBlock) { return successors.get(successors.size() - 1); } return null; } public int getId() { return id; } public int getPredecessorCount() { return this.predecessorCount; } public int numNormalSuccessors() { if (exceptionDispatchBlock() != null) { return successors.size() - 1; } return successors.size(); } public BciBlock copy() { try { BciBlock block = (BciBlock) super.clone(); if (block.jsrData != null) { block.jsrData = block.jsrData.copy(); } block.successors = new ArrayList<>(successors); return block; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } @Override public String toString() { StringBuilder sb = new StringBuilder("B").append(getId()); sb.append('[').append(startBci).append("..").append(endBci); if (isLoopHeader || isExceptionEntry || this instanceof ExceptionDispatchBlock) { sb.append(' '); if (isLoopHeader) { sb.append('L'); } if (isExceptionEntry) { sb.append('!'); } else if (this instanceof ExceptionDispatchBlock) { sb.append("<!>"); } } sb.append(']'); return sb.toString(); } public int getLoopDepth() { return Long.bitCount(loops); } public boolean isLoopHeader() { return isLoopHeader; } public boolean isExceptionEntry() { return isExceptionEntry; } public BciBlock getSuccessor(int index) { return successors.get(index); }
Get the loop id of the inner most loop.
Returns:the loop id of the most inner loop or -1 if not part of any loop
/** * Get the loop id of the inner most loop. * * @return the loop id of the most inner loop or -1 if not part of any loop */
public int getLoopId() { long l = loops; if (l == 0) { return -1; } int pos = 0; for (int lMask = 1; (l & lMask) == 0; lMask = lMask << 1) { pos++; } return pos; }
Iterate over loop ids.
/** * Iterate over loop ids. */
public Iterable<Integer> loopIdIterable() { return new Iterable<Integer>() { @Override public Iterator<Integer> iterator() { return idIterator(loops); } }; } private static Iterator<Integer> idIterator(long field) { return new Iterator<Integer>() { long l = field; int pos = 0; int lMask = 1; @Override public Integer next() { for (; (l & lMask) == 0; lMask = lMask << 1) { pos++; } l &= ~lMask; return pos; } @Override public boolean hasNext() { return l != 0; } }; } public double probability() { return 1D; } public BciBlock getPostdominator() { return null; } private JSRData getOrCreateJSRData() { if (jsrData == null) { jsrData = new JSRData(); } return jsrData; } void setEndsWithRet() { getOrCreateJSRData().endsWithRet = true; } public JsrScope getJsrScope() { if (this.jsrData == null) { return JsrScope.EMPTY_SCOPE; } else { return jsrData.jsrScope; } } public boolean endsWithRet() { if (this.jsrData == null) { return false; } else { return jsrData.endsWithRet; } } void setRetSuccessor(BciBlock bciBlock) { this.getOrCreateJSRData().retSuccessor = bciBlock; } public BciBlock getRetSuccessor() { if (this.jsrData == null) { return null; } else { return jsrData.retSuccessor; } } public BciBlock getJsrSuccessor() { if (this.jsrData == null) { return null; } else { return jsrData.jsrSuccessor; } } public int getJsrReturnBci() { if (this.jsrData == null) { return -1; } else { return jsrData.jsrReturnBci; } } public EconomicMap<JsrScope, BciBlock> getJsrAlternatives() { if (this.jsrData == null) { return null; } else { return jsrData.jsrAlternatives; } } public void initJsrAlternatives() { JSRData data = this.getOrCreateJSRData(); if (data.jsrAlternatives == null) { data.jsrAlternatives = EconomicMap.create(Equivalence.DEFAULT); } } void setJsrScope(JsrScope nextScope) { this.getOrCreateJSRData().jsrScope = nextScope; } void setJsrSuccessor(BciBlock clone) { this.getOrCreateJSRData().jsrSuccessor = clone; } void setJsrReturnBci(int bci) { this.getOrCreateJSRData().jsrReturnBci = bci; } public int getSuccessorCount() { return successors.size(); } public List<BciBlock> getSuccessors() { return successors; } void setId(int i) { this.id = i; } public void addSuccessor(BciBlock sux) { successors.add(sux); sux.predecessorCount++; } public void clearSucccessors() { for (BciBlock sux : successors) { sux.predecessorCount--; } successors.clear(); } public boolean isExceptionDispatch() { return false; } } public static class ExceptionDispatchBlock extends BciBlock { public final ExceptionHandler handler; public final int deoptBci;
Constructor for a normal dispatcher.
/** * Constructor for a normal dispatcher. */
ExceptionDispatchBlock(ExceptionHandler handler, int deoptBci) { super(handler.getHandlerBCI()); this.endBci = startBci; this.deoptBci = deoptBci; this.handler = handler; }
Constructor for the method unwind dispatcher.
/** * Constructor for the method unwind dispatcher. */
ExceptionDispatchBlock(int deoptBci) { super(deoptBci); this.endBci = deoptBci; this.deoptBci = deoptBci; this.handler = null; } @Override public boolean isExceptionDispatch() { return true; } }
The blocks found in this method, in reverse postorder.
/** * The blocks found in this method, in reverse postorder. */
private BciBlock[] blocks; public final Bytecode code; public boolean hasJsrBytecodes; private final ExceptionHandler[] exceptionHandlers; private BciBlock startBlock; private BciBlock[] loopHeaders; private static final int LOOP_HEADER_MAX_CAPACITY = Long.SIZE; private static final int LOOP_HEADER_INITIAL_CAPACITY = 4; private int blocksNotYetAssignedId; private final DebugContext debug;
Creates a new BlockMap instance from code.
/** * Creates a new BlockMap instance from {@code code}. */
private BciBlockMapping(Bytecode code, DebugContext debug) { this.code = code; this.debug = debug; this.exceptionHandlers = code.getExceptionHandlers(); } public BciBlock[] getBlocks() { return this.blocks; }
Builds the block map and conservative CFG and numbers blocks.
/** * Builds the block map and conservative CFG and numbers blocks. */
public void build(BytecodeStream stream, OptionValues options) { int codeSize = code.getCodeSize(); BciBlock[] blockMap = new BciBlock[codeSize]; makeExceptionEntries(blockMap); iterateOverBytecodes(blockMap, stream); if (hasJsrBytecodes) { if (!SupportJsrBytecodes.getValue(options)) { throw new JsrNotSupportedBailout("jsr/ret parsing disabled"); } createJsrAlternatives(blockMap, blockMap[0]); } if (debug.isLogEnabled()) { this.log(blockMap, "Before BlockOrder"); } computeBlockOrder(blockMap); fixLoopBits(blockMap); assert verify(); startBlock = blockMap[0]; if (debug.isLogEnabled()) { this.log(blockMap, "Before LivenessAnalysis"); } } private boolean verify() { for (BciBlock block : blocks) { assert blocks[block.getId()] == block; for (int i = 0; i < block.getSuccessorCount(); i++) { BciBlock sux = block.getSuccessor(i); if (sux instanceof ExceptionDispatchBlock) { assert i == block.getSuccessorCount() - 1 : "Only one exception handler allowed, and it must be last in successors list"; } } } return true; } private void makeExceptionEntries(BciBlock[] blockMap) { // start basic blocks at all exception handler blocks and mark them as exception entries for (ExceptionHandler h : this.exceptionHandlers) { BciBlock xhandler = makeBlock(blockMap, h.getHandlerBCI()); xhandler.isExceptionEntry = true; } } private void iterateOverBytecodes(BciBlock[] blockMap, BytecodeStream stream) { // iterate over the bytecodes top to bottom. // mark the entrypoints of basic blocks and build lists of successors for // all bytecodes that end basic blocks (i.e. goto, ifs, switches, throw, jsr, returns, ret) BciBlock current = null; stream.setBCI(0); while (stream.currentBC() != Bytecodes.END) { int bci = stream.currentBCI(); if (current == null || blockMap[bci] != null) { BciBlock b = makeBlock(blockMap, bci); if (current != null) { addSuccessor(blockMap, current.endBci, b); } current = b; } blockMap[bci] = current; current.endBci = bci; switch (stream.currentBC()) { case IRETURN: // fall through case LRETURN: // fall through case FRETURN: // fall through case DRETURN: // fall through case ARETURN: // fall through case RETURN: { current = null; break; } case ATHROW: { current = null; ExceptionDispatchBlock handler = handleExceptions(blockMap, bci); if (handler != null) { addSuccessor(blockMap, bci, handler); } break; } case IFEQ: // fall through case IFNE: // fall through case IFLT: // fall through case IFGE: // fall through case IFGT: // fall through case IFLE: // fall through case IF_ICMPEQ: // fall through case IF_ICMPNE: // fall through case IF_ICMPLT: // fall through case IF_ICMPGE: // fall through case IF_ICMPGT: // fall through case IF_ICMPLE: // fall through case IF_ACMPEQ: // fall through case IF_ACMPNE: // fall through case IFNULL: // fall through case IFNONNULL: { current = null; addSuccessor(blockMap, bci, makeBlock(blockMap, stream.readBranchDest())); addSuccessor(blockMap, bci, makeBlock(blockMap, stream.nextBCI())); break; } case GOTO: case GOTO_W: { current = null; addSuccessor(blockMap, bci, makeBlock(blockMap, stream.readBranchDest())); break; } case TABLESWITCH: { current = null; addSwitchSuccessors(blockMap, bci, new BytecodeTableSwitch(stream, bci)); break; } case LOOKUPSWITCH: { current = null; addSwitchSuccessors(blockMap, bci, new BytecodeLookupSwitch(stream, bci)); break; } case JSR: case JSR_W: { hasJsrBytecodes = true; int target = stream.readBranchDest(); if (target == 0) { throw new JsrNotSupportedBailout("jsr target bci 0 not allowed"); } BciBlock b1 = makeBlock(blockMap, target); current.setJsrSuccessor(b1); current.setJsrReturnBci(stream.nextBCI()); current = null; addSuccessor(blockMap, bci, b1); break; } case RET: { current.setEndsWithRet(); current = null; break; } case INVOKEINTERFACE: case INVOKESPECIAL: case INVOKESTATIC: case INVOKEVIRTUAL: case INVOKEDYNAMIC: { current = null; addSuccessor(blockMap, bci, makeBlock(blockMap, stream.nextBCI())); ExceptionDispatchBlock handler = handleExceptions(blockMap, bci); if (handler != null) { addSuccessor(blockMap, bci, handler); } break; } case IDIV: case IREM: case LDIV: case LREM: case IASTORE: case LASTORE: case FASTORE: case DASTORE: case AASTORE: case BASTORE: case CASTORE: case SASTORE: case IALOAD: case LALOAD: case FALOAD: case DALOAD: case AALOAD: case BALOAD: case CALOAD: case SALOAD: case ARRAYLENGTH: case CHECKCAST: case PUTSTATIC: case GETSTATIC: case PUTFIELD: case GETFIELD: { ExceptionDispatchBlock handler = handleExceptions(blockMap, bci); if (handler != null) { current = null; addSuccessor(blockMap, bci, makeBlock(blockMap, stream.nextBCI())); addSuccessor(blockMap, bci, handler); } } } stream.next(); } } private BciBlock makeBlock(BciBlock[] blockMap, int startBci) { BciBlock oldBlock = blockMap[startBci]; if (oldBlock == null) { BciBlock newBlock = new BciBlock(startBci); blocksNotYetAssignedId++; blockMap[startBci] = newBlock; return newBlock; } else if (oldBlock.startBci != startBci) { // Backward branch into the middle of an already processed block. // Add the correct fall-through successor. BciBlock newBlock = new BciBlock(startBci); blocksNotYetAssignedId++; newBlock.endBci = oldBlock.endBci; for (BciBlock oldSuccessor : oldBlock.getSuccessors()) { newBlock.addSuccessor(oldSuccessor); } oldBlock.endBci = startBci - 1; oldBlock.clearSucccessors(); oldBlock.addSuccessor(newBlock); for (int i = startBci; i <= newBlock.endBci; i++) { blockMap[i] = newBlock; } return newBlock; } else { return oldBlock; } } private void addSwitchSuccessors(BciBlock[] blockMap, int predBci, BytecodeSwitch bswitch) { // adds distinct targets to the successor list Collection<Integer> targets = new TreeSet<>(); for (int i = 0; i < bswitch.numberOfCases(); i++) { targets.add(bswitch.targetAt(i)); } targets.add(bswitch.defaultTarget()); for (int targetBci : targets) { addSuccessor(blockMap, predBci, makeBlock(blockMap, targetBci)); } } private static void addSuccessor(BciBlock[] blockMap, int predBci, BciBlock sux) { BciBlock predecessor = blockMap[predBci]; if (sux.isExceptionEntry) { throw new PermanentBailoutException("Exception handler can be reached by both normal and exceptional control flow"); } predecessor.addSuccessor(sux); } private final ArrayList<BciBlock> jsrVisited = new ArrayList<>(); private void createJsrAlternatives(BciBlock[] blockMap, BciBlock block) { jsrVisited.add(block); JsrScope scope = block.getJsrScope(); if (block.endsWithRet()) { block.setRetSuccessor(blockMap[scope.nextReturnAddress()]); block.addSuccessor(block.getRetSuccessor()); assert block.getRetSuccessor() != block.getJsrSuccessor(); } debug.log("JSR alternatives block %s sux %s jsrSux %s retSux %s jsrScope %s", block, block.getSuccessors(), block.getJsrSuccessor(), block.getRetSuccessor(), block.getJsrScope()); if (block.getJsrSuccessor() != null || !scope.isEmpty()) { for (int i = 0; i < block.getSuccessorCount(); i++) { BciBlock successor = block.getSuccessor(i); JsrScope nextScope = scope; if (successor == block.getJsrSuccessor()) { nextScope = scope.push(block.getJsrReturnBci()); } if (successor == block.getRetSuccessor()) { nextScope = scope.pop(); } if (!successor.getJsrScope().isPrefixOf(nextScope)) { throw new JsrNotSupportedBailout("unstructured control flow (" + successor.getJsrScope() + " " + nextScope + ")"); } if (!nextScope.isEmpty()) { BciBlock clone; if (successor.getJsrAlternatives() != null && successor.getJsrAlternatives().containsKey(nextScope)) { clone = successor.getJsrAlternatives().get(nextScope); } else { successor.initJsrAlternatives(); clone = successor.copy(); blocksNotYetAssignedId++; clone.setJsrScope(nextScope); successor.getJsrAlternatives().put(nextScope, clone); } block.getSuccessors().set(i, clone); if (successor == block.getJsrSuccessor()) { block.setJsrSuccessor(clone); } if (successor == block.getRetSuccessor()) { block.setRetSuccessor(clone); } } } } for (BciBlock successor : block.getSuccessors()) { if (!jsrVisited.contains(successor)) { createJsrAlternatives(blockMap, successor); } } } private ExceptionDispatchBlock handleExceptions(BciBlock[] blockMap, int bci) { ExceptionDispatchBlock lastHandler = null; int dispatchBlocks = 0; for (int i = exceptionHandlers.length - 1; i >= 0; i--) { ExceptionHandler h = exceptionHandlers[i]; if (h.getStartBCI() <= bci && bci < h.getEndBCI()) { if (h.isCatchAll()) { // Discard all information about succeeding exception handlers, since they can // never be reached. dispatchBlocks = 0; lastHandler = null; } // We do not reuse exception dispatch blocks, because nested exception handlers // might have problems reasoning about the correct frame state. ExceptionDispatchBlock curHandler = new ExceptionDispatchBlock(h, bci); dispatchBlocks++; curHandler.addSuccessor(blockMap[h.getHandlerBCI()]); if (lastHandler != null) { curHandler.addSuccessor(lastHandler); } lastHandler = curHandler; } } blocksNotYetAssignedId += dispatchBlocks; return lastHandler; } private boolean loopChanges; private void fixLoopBits(BciBlock[] blockMap) { do { loopChanges = false; for (BciBlock b : blocks) { b.visited = false; } long loop = fixLoopBits(blockMap, blockMap[0]); if (loop != 0) { // There is a path from a loop end to the method entry that does not pass the loop // header. // Therefore, the loop is non reducible (has more than one entry). // We don't want to compile such methods because the IR only supports structured // loops. throw new PermanentBailoutException("Non-reducible loop: %016x", loop); } } while (loopChanges); } private void computeBlockOrder(BciBlock[] blockMap) { int maxBlocks = blocksNotYetAssignedId; this.blocks = new BciBlock[blocksNotYetAssignedId]; long loop = computeBlockOrder(blockMap[0]); if (loop != 0) { // There is a path from a loop end to the method entry that does not pass the loop // header. Therefore, the loop is non reducible (has more than one entry). // We don't want to compile such methods because the IR only supports structured loops. throw new PermanentBailoutException("Non-reducible loop"); } // Purge null entries for unreached blocks and sort blocks such that loop bodies are always // consecutively in the array. int blockCount = maxBlocks - blocksNotYetAssignedId + 1; BciBlock[] newBlocks = new BciBlock[blockCount]; int next = 0; for (int i = 0; i < blocks.length; ++i) { BciBlock b = blocks[i]; if (b != null) { b.setId(next); newBlocks[next++] = b; if (b.isLoopHeader) { next = handleLoopHeader(newBlocks, next, i, b); } } } assert next == newBlocks.length - 1; // Add unwind block. int deoptBci = code.getMethod().isSynchronized() ? BytecodeFrame.UNWIND_BCI : BytecodeFrame.AFTER_EXCEPTION_BCI; ExceptionDispatchBlock unwindBlock = new ExceptionDispatchBlock(deoptBci); unwindBlock.setId(newBlocks.length - 1); newBlocks[newBlocks.length - 1] = unwindBlock; blocks = newBlocks; } private int handleLoopHeader(BciBlock[] newBlocks, int nextStart, int i, BciBlock loopHeader) { int next = nextStart; int endOfLoop = nextStart - 1; for (int j = i + 1; j < blocks.length; ++j) { BciBlock other = blocks[j]; if (other != null && (other.loops & (1L << loopHeader.loopId)) != 0) { other.setId(next); endOfLoop = next; newBlocks[next++] = other; blocks[j] = null; if (other.isLoopHeader) { next = handleLoopHeader(newBlocks, next, j, other); } } } loopHeader.loopEnd = endOfLoop; return next; } public void log(BciBlock[] blockMap, String name) { if (debug.isLogEnabled()) { debug.log("%sBlockMap %s: %n%s", debug.getCurrentScopeName(), name, toString(blockMap, loopHeaders)); } } public static String toString(BciBlock[] blockMap, BciBlock[] loopHeadersMap) { StringBuilder sb = new StringBuilder(); for (BciBlock b : blockMap) { if (b == null) { continue; } sb.append("B").append(b.getId()).append("[").append(b.startBci).append("..").append(b.endBci).append("]"); if (b.isLoopHeader) { sb.append(" LoopHeader"); } if (b.isExceptionEntry) { sb.append(" ExceptionEntry"); } if (b instanceof ExceptionDispatchBlock) { sb.append(" ExceptionDispatch"); } if (!b.successors.isEmpty()) { sb.append(" Successors=["); for (BciBlock s : b.getSuccessors()) { if (sb.charAt(sb.length() - 1) != '[') { sb.append(", "); } sb.append("B").append(s.getId()); } sb.append("]"); } if (b.loops != 0L) { sb.append(" Loops=["); for (int pos : b.loopIdIterable()) { if (sb.charAt(sb.length() - 1) == '[') { sb.append(", "); } sb.append("B").append(loopHeadersMap[pos].getId()); } sb.append("]"); } sb.append(System.lineSeparator()); } return sb.toString(); } @Override public String toString() { return toString(blocks, loopHeaders); }
Get the header block for a loop index.
/** * Get the header block for a loop index. */
public BciBlock getLoopHeader(int index) { return loopHeaders[index]; }
The next available loop number.
/** * The next available loop number. */
private int nextLoop;
Mark the block as a loop header, using the next available loop number. Also checks for corner cases that we don't want to compile.
/** * Mark the block as a loop header, using the next available loop number. Also checks for corner * cases that we don't want to compile. */
private void makeLoopHeader(BciBlock block) { if (!block.isLoopHeader) { block.isLoopHeader = true; if (block.isExceptionEntry) { // Loops that are implicitly formed by an exception handler lead to all sorts of // corner cases. // Don't compile such methods for now, until we see a concrete case that allows // checking for correctness. throw new PermanentBailoutException("Loop formed by an exception handler"); } if (nextLoop >= LOOP_HEADER_MAX_CAPACITY) { // This restriction can be removed by using a fall-back to a BitSet in case we have // more than 64 loops // Don't compile such methods for now, until we see a concrete case that allows // checking for correctness. throw new PermanentBailoutException("Too many loops in method"); } assert block.loops == 0; block.loops = 1L << nextLoop; debug.log("makeLoopHeader(%s) -> %x", block, block.loops); if (loopHeaders == null) { loopHeaders = new BciBlock[LOOP_HEADER_INITIAL_CAPACITY]; } else if (nextLoop >= loopHeaders.length) { loopHeaders = Arrays.copyOf(loopHeaders, LOOP_HEADER_MAX_CAPACITY); } loopHeaders[nextLoop] = block; block.loopId = nextLoop; nextLoop++; } assert Long.bitCount(block.loops) == 1; }
Depth-first traversal of the control flow graph. The flag BciBlock.visited is used to visit every block only once. The flag BciBlock.active is used to detect cycles (backward edges).
/** * Depth-first traversal of the control flow graph. The flag {@linkplain BciBlock#visited} is * used to visit every block only once. The flag {@linkplain BciBlock#active} is used to detect * cycles (backward edges). */
private long computeBlockOrder(BciBlock block) { if (block.visited) { if (block.active) { // Reached block via backward branch. makeLoopHeader(block); // Return cached loop information for this block. return block.loops; } else if (block.isLoopHeader) { return block.loops & ~(1L << block.loopId); } else { return block.loops; } } block.visited = true; block.active = true; long loops = 0; for (BciBlock successor : block.getSuccessors()) { // Recursively process successors. loops |= computeBlockOrder(successor); if (successor.active) { // Reached block via backward branch. loops |= (1L << successor.loopId); } } block.loops = loops; debug.log("computeBlockOrder(%s) -> %x", block, block.loops); if (block.isLoopHeader) { loops &= ~(1L << block.loopId); } block.active = false; blocksNotYetAssignedId--; blocks[blocksNotYetAssignedId] = block; return loops; } private long fixLoopBits(BciBlock[] blockMap, BciBlock block) { if (block.visited) { // Return cached loop information for this block. if (block.isLoopHeader) { return block.loops & ~(1L << block.loopId); } else { return block.loops; } } block.visited = true; long loops = block.loops; for (BciBlock successor : block.getSuccessors()) { // Recursively process successors. loops |= fixLoopBits(blockMap, successor); } if (block.loops != loops) { loopChanges = true; block.loops = loops; debug.log("fixLoopBits0(%s) -> %x", block, block.loops); } if (block.isLoopHeader) { loops &= ~(1L << block.loopId); } return loops; } public static BciBlockMapping create(BytecodeStream stream, Bytecode code, OptionValues options, DebugContext debug) { BciBlockMapping map = new BciBlockMapping(code, debug); map.build(stream, options); if (debug.isDumpEnabled(DebugContext.INFO_LEVEL)) { debug.dump(DebugContext.INFO_LEVEL, map, code.getMethod().format("After block building %f %R %H.%n(%P)")); } return map; } public BciBlock[] getLoopHeaders() { return loopHeaders; } public BciBlock getStartBlock() { return startBlock; } public ExceptionDispatchBlock getUnwindBlock() { return (ExceptionDispatchBlock) blocks[blocks.length - 1]; } public int getLoopCount() { return nextLoop; } public int getBlockCount() { return blocks.length; } }