blob: 4ea669ab2353edabe22b834b6ed225709ad6f86a [file] [log] [blame]
/*
* 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.
* <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.
*
* @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.
*/
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.
*/
ExceptionDispatchBlock(ExceptionHandler handler, int deoptBci) {
super(handler.getHandlerBCI());
this.endBci = startBci;
this.deoptBci = deoptBci;
this.handler = handler;
}
/**
* 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.
*/
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 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.
*/
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.
*/
public BciBlock getLoopHeader(int index) {
return loopHeaders[index];
}
/**
* 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.
*/
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 {@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;
}
}