final ArrayDeque<ControlFlowNode> agenda = new ArrayDeque<>();
agenda.addLast(entryPoint);
while (!agenda.isEmpty()) {
final ControlFlowNode node = agenda.pollFirst();
//
// If the node is a loop header...
//
if (scope.contains(node) &&
node.getDominanceFrontier().contains(node) &&
(node != entryPoint || !excludeEntryPoint)) {
final Set<ControlFlowNode> loopContents = findLoopContents(scope, node);
//
// If the first or last expression is a loop condition...
//
final BasicBlock basicBlock = (BasicBlock) node.getUserData();
final StrongBox<Expression> condition = new StrongBox<>();
final StrongBox<Label> trueLabel = new StrongBox<>();
final StrongBox<Label> falseLabel = new StrongBox<>();
final ControlFlowNode lastInLoop = lastOrDefault(loopContents);
final BasicBlock lastBlock = (BasicBlock) lastInLoop.getUserData();
//
// Check for an infinite loop.
//
if (loopContents.size() == 1 &&
matchSimpleBreak(basicBlock, trueLabel) &&
trueLabel.get() == first(basicBlock.getBody())) {
final Loop emptyLoop = new Loop();
emptyLoop.setBody(new Block());
final BasicBlock block = new BasicBlock();
final List<Node> blockBody = block.getBody();
blockBody.add(basicBlock.getBody().get(0));
blockBody.add(emptyLoop);
result.add(block);
scope.remove(lastInLoop);
continue;
}
//
// Check for a conditional loop.
//
for (int pass = 0; pass < 2; pass++) {
final boolean isPostCondition = pass == 1;
final boolean foundCondition = isPostCondition ? matchLastAndBreak(lastBlock, AstCode.IfTrue, trueLabel, condition, falseLabel)
: matchSingleAndBreak(basicBlock, AstCode.IfTrue, trueLabel, condition, falseLabel);
//
// It has to be just IfTrue; any preceding code would introduce a goto.
//
if (!foundCondition) {
continue;
}
final ControlFlowNode trueTarget = labelsToNodes.get(trueLabel.get());
final ControlFlowNode falseTarget = labelsToNodes.get(falseLabel.get());
//
// If one point inside the loop and the other outside...
//
if ((!loopContents.contains(falseTarget) || loopContents.contains(trueTarget)) &&
(!loopContents.contains(trueTarget) || loopContents.contains(falseTarget))) {
continue;
}
final boolean flipped = loopContents.contains(falseTarget) || falseTarget == node;
//
// If false means enter the loop, negate the condition.
//
if (flipped) {
final Label temp = trueLabel.get();
trueLabel.set(falseLabel.get());
falseLabel.set(temp);
condition.set(AstOptimizer.simplifyLogicalNot(new Expression(AstCode.LogicalNot, null, condition.get().getOffset(), condition.get())));
}
final boolean canWriteConditionalLoop;
if (isPostCondition) {
final Expression continueGoto;
if (flipped) {
continueGoto = (Expression) last(lastBlock.getBody());
}
else {
continueGoto = (Expression) lastBlock.getBody().get(lastBlock.getBody().size() - 2);
}
canWriteConditionalLoop = countJumps(loopContents, trueLabel.get(), continueGoto) == 0;
}
else {
canWriteConditionalLoop = true;
}
if (canWriteConditionalLoop) {
removeOrThrow(loopContents, node);
removeOrThrow(scope, node);
final ControlFlowNode postLoopTarget = labelsToNodes.get(falseLabel.get());
if (postLoopTarget != null) {
//
// Pull more nodes into the loop.
//
final Set<ControlFlowNode> postLoopContents = findDominatedNodes(scope, postLoopTarget);
final LinkedHashSet<ControlFlowNode> pullIn = new LinkedHashSet<>(scope);
pullIn.removeAll(postLoopContents);
for (final ControlFlowNode n : pullIn) {
if (node.dominates(n)) {
loopContents.add(n);
}
}
}
//
// Use loop to implement the IfTrue.
//
final BasicBlock block;
final List<Node> basicBlockBody;
final Label loopLabel;
if (isPostCondition) {
block = new BasicBlock();
basicBlockBody = block.getBody();
removeTail(lastBlock.getBody(), AstCode.IfTrue, AstCode.Goto);
if (lastBlock.getBody().size() > 1) {
lastBlock.getBody().add(new Expression(AstCode.Goto, trueLabel.get(), Expression.MYSTERY_OFFSET));
loopLabel = new Label("Loop_" + _nextLabelIndex++);
}
else {
scope.remove(lastInLoop);
loopContents.remove(lastInLoop);
loopLabel = (Label) lastBlock.getBody().get(0);
}
basicBlockBody.add(loopLabel);
}
else {
block = basicBlock;
basicBlockBody = block.getBody();
removeTail(basicBlockBody, AstCode.IfTrue, AstCode.Goto);
}
final Loop loop = new Loop();
final Block bodyBlock = new Block();
loop.setCondition(condition.get());
loop.setBody(bodyBlock);
if (isPostCondition) {
loop.setLoopType(LoopType.PostCondition);
bodyBlock.getBody().add(basicBlock);
}
bodyBlock.setEntryGoto(new Expression(AstCode.Goto, trueLabel.get(), Expression.MYSTERY_OFFSET));
bodyBlock.getBody().addAll(findLoops(loopContents, node, isPostCondition));
basicBlockBody.add(loop);
if (isPostCondition) {
basicBlockBody.add(new Expression(AstCode.Goto, falseLabel.get(), Expression.MYSTERY_OFFSET));
}
else {
basicBlockBody.add(new Expression(AstCode.Goto, falseLabel.get(), Expression.MYSTERY_OFFSET));
}
result.add(block);
scope.removeAll(loopContents);
break;
}
}
//
// Fallback method: while (true) { ... }
//
if (scope.contains(node)) {
final BasicBlock block = new BasicBlock();
final List<Node> blockBody = block.getBody();
final Loop loop = new Loop();
final Block bodyBlock = new Block();
loop.setBody(bodyBlock);
final LoopExitInfo exitInfo = findLoopExitInfo(loopContents);
if (exitInfo.exitLabel != null) {
final ControlFlowNode postLoopTarget = labelsToNodes.get(exitInfo.exitLabel);
if (postLoopTarget.getIncoming().size() == 1) {
//
// See if our only exit comes from an inner switch's default label. If so, pull it in
// to the loop if there are no other references.
//
final ControlFlowNode predecessor = firstOrDefault(postLoopTarget.getPredecessors());
if (predecessor != null && loopContents.contains(predecessor)) {
final BasicBlock b = (BasicBlock) predecessor.getUserData();
if (matchLast(b, AstCode.Switch, switchLabels, condition) &&
!ArrayUtilities.isNullOrEmpty(switchLabels.get()) &&
exitInfo.exitLabel == switchLabels.get()[0]) {
final Set<ControlFlowNode> defaultContents = findDominatedNodes(scope, postLoopTarget);
for (final ControlFlowNode n : defaultContents) {
if (scope.contains(n) && node.dominates(n)) {
loopContents.add(n);
}
}
}
}
}
if (!loopContents.contains(postLoopTarget)) {
//
// Pull more nodes into the loop.
//
final Set<ControlFlowNode> postLoopContents = findDominatedNodes(scope, postLoopTarget);
final LinkedHashSet<ControlFlowNode> pullIn = new LinkedHashSet<>(scope);
pullIn.removeAll(postLoopContents);
for (final ControlFlowNode n : pullIn) {
if (n.getBlockIndex() < postLoopTarget.getBlockIndex() && scope.contains(n) && node.dominates(n)) {
loopContents.add(n);
}
}
}
}
else if (exitInfo.additionalNodes.size() == 1) {
final ControlFlowNode postLoopTarget = first(exitInfo.additionalNodes);
final BasicBlock postLoopBlock = (BasicBlock) postLoopTarget.getUserData();
final Node postLoopBlockHead = firstOrDefault(postLoopBlock.getBody());
//
// See if our only exit comes from an inner switch's default label. If so, pull it in
// to the loop if there are no other references.
//
final ControlFlowNode predecessor = single(postLoopTarget.getPredecessors());
if (postLoopBlockHead instanceof Label &&
loopContents.contains(predecessor)) {
final BasicBlock b = (BasicBlock) predecessor.getUserData();
if (matchLast(b, AstCode.Switch, switchLabels, condition) &&
!ArrayUtilities.isNullOrEmpty(switchLabels.get()) &&
postLoopBlockHead == switchLabels.get()[0]) {