//-----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation. All Rights Reserved.
//
//-----------------------------------------------------------------------------
using System.Diagnostics.Contracts;
using System.Collections.Generic;
using Bpl = Microsoft.Boogie;
using Microsoft.Boogie.GraphUtil;
namespace Microsoft.Boogie {
public class LoopUnroll {
public static List/*!*/ UnrollLoops(Block start, int unrollMaxDepth, bool soundLoopUnrolling) {
Contract.Requires(start != null);
Contract.Requires(0 <= unrollMaxDepth);
Contract.Ensures(cce.NonNullElements(Contract.Result>()));
Dictionary gd = new Dictionary();
HashSet beingVisited = new HashSet();
GraphNode gStart = GraphNode.ComputeGraphInfo(null, start, gd, beingVisited);
// Compute SCCs
StronglyConnectedComponents sccs =
new StronglyConnectedComponents(gd.Values, Preds, Succs);
Contract.Assert(sccs != null);
sccs.Compute();
Dictionary> containingSCC = new Dictionary>();
foreach (SCC scc in sccs) {
foreach (GraphNode/*!*/ n in scc) {
Contract.Assert(n != null);
containingSCC[n] = scc;
}
}
LoopUnroll lu = new LoopUnroll(unrollMaxDepth, soundLoopUnrolling, containingSCC, new List());
lu.Visit(gStart);
lu.newBlockSeqGlobal.Reverse();
return lu.newBlockSeqGlobal;
}
// This is supposed to "undo" to effect of loop unrolling
// on block labels. It essentially removes the "#num" from the end
// of lab, if there is something like this
public static string sanitizeLabel(string lab)
{
if (!lab.Contains("#"))
return lab;
// Find the last occurrance of "#"
int pos = lab.LastIndexOf('#');
return lab.Substring(0, pos);
}
private static System.Collections.IEnumerable/*/*!*/ Succs(GraphNode n) {
Contract.Requires(n != null);
Contract.Ensures(Contract.Result() != null);
List/*!*/ AllEdges = new List();
AllEdges.AddRange(n.ForwardEdges);
AllEdges.AddRange(n.BackEdges);
return AllEdges;
}
private static System.Collections.IEnumerable/**//*!*/ Preds(GraphNode n) {
Contract.Requires(n != null);
Contract.Ensures(Contract.Result() != null);
return n.Predecessors;
}
class GraphNode {
public readonly Block/*!*/ Block;
public readonly List/*!*/ Body;
[ContractInvariantMethod]
void ObjectInvariant() {
Contract.Invariant(Block != null);
Contract.Invariant(Body != null);
Contract.Invariant(cce.NonNullElements(ForwardEdges));
Contract.Invariant(cce.NonNullElements(BackEdges));
Contract.Invariant(cce.NonNullElements(Predecessors));
Contract.Invariant(isCutPoint == (BackEdges.Count != 0));
}
bool isCutPoint; // is set during ComputeGraphInfo
public bool IsCutPoint {
get {
return isCutPoint;
}
}
[Rep]
public readonly List/*!*/ ForwardEdges = new List();
[Rep]
public readonly List/*!*/ BackEdges = new List();
[Rep]
public readonly List/*!*/ Predecessors = new List();
GraphNode(Block b, List body) {
Contract.Requires(body != null);
Contract.Requires(b != null);
this.Block = b;
this.Body = body;
}
static List GetOptimizedBody(List cmds) {
Contract.Requires(cmds != null);
Contract.Ensures(Contract.Result>() != null);
int n = 0;
foreach (Cmd c in cmds) {
n++;
PredicateCmd pc = c as PredicateCmd;
if (pc != null && pc.Expr is LiteralExpr && ((LiteralExpr)pc.Expr).IsFalse) {
// return a sequence consisting of the commands seen so far
Cmd[] s = new Cmd[n];
for (int i = 0; i < n; i++) {
s[i] = cmds[i];
}
return new List(s);
}
}
return cmds;
}
public static GraphNode ComputeGraphInfo(GraphNode from, Block b, Dictionary/*!*/ gd, HashSet beingVisited) {
Contract.Requires(beingVisited != null);
Contract.Requires(b != null);
Contract.Requires(cce.NonNullDictionaryAndValues(gd));
Contract.Ensures(Contract.Result() != null);
GraphNode g;
if (gd.TryGetValue(b, out g)) {
Contract.Assume(from != null);
Contract.Assert(g != null);
if (beingVisited.Contains(b)) {
// it's a cut point
g.isCutPoint = true;
from.BackEdges.Add(g);
g.Predecessors.Add(from);
} else {
from.ForwardEdges.Add(g);
g.Predecessors.Add(from);
}
} else {
List body = GetOptimizedBody(b.Cmds);
g = new GraphNode(b, body);
gd.Add(b, g);
if (from != null) {
from.ForwardEdges.Add(g);
g.Predecessors.Add(from);
}
if (body != b.Cmds) {
// the body was optimized -- there is no way through this block
} else {
beingVisited.Add(b);
GotoCmd gcmd = b.TransferCmd as GotoCmd;
if (gcmd != null) {
Contract.Assume(gcmd.labelTargets != null);
foreach (Block/*!*/ succ in gcmd.labelTargets) {
Contract.Assert(succ != null);
ComputeGraphInfo(g, succ, gd, beingVisited);
}
}
beingVisited.Remove(b);
}
}
return g;
}
}
readonly List/*!*/ newBlockSeqGlobal;
readonly Dictionary>/*!*/ containingSCC;
readonly int c;
readonly bool soundLoopUnrolling;
readonly LoopUnroll next;
readonly LoopUnroll/*!*/ head;
Dictionary/*!*/ newBlocks = new Dictionary();
[ContractInvariantMethod]
void ObjectInvariant() {
Contract.Invariant(head != null);
Contract.Invariant(cce.NonNullElements(newBlockSeqGlobal));
Contract.Invariant(newBlocks != null && cce.NonNullElements(newBlocks.Values));
}
[NotDelayed]
private LoopUnroll(int unrollMaxDepth, bool soundLoopUnrolling, Dictionary>/*!*/ scc, List/*!*/ newBlockSeqGlobal)
: base() {
Contract.Requires(cce.NonNullElements(newBlockSeqGlobal));
Contract.Requires(cce.NonNullDictionaryAndValues(scc) && Contract.ForAll(scc.Values, v => cce.NonNullElements(v)));
Contract.Requires(0 <= unrollMaxDepth);
this.newBlockSeqGlobal = newBlockSeqGlobal;
this.c = unrollMaxDepth;
this.containingSCC = scc;
this.head = this;
if (unrollMaxDepth != 0) {
next = new LoopUnroll(unrollMaxDepth - 1, soundLoopUnrolling, scc, newBlockSeqGlobal, this);
}
}
private LoopUnroll(int unrollMaxDepth, bool soundLoopUnrolling, Dictionary> scc, List/*!*/ newBlockSeqGlobal, LoopUnroll head) {
Contract.Requires(head != null);
Contract.Requires(cce.NonNullDictionaryAndValues(scc));
Contract.Requires(cce.NonNullElements(newBlockSeqGlobal));
Contract.Requires(0 <= unrollMaxDepth);
this.newBlockSeqGlobal = newBlockSeqGlobal;
this.c = unrollMaxDepth;
this.soundLoopUnrolling = soundLoopUnrolling;
this.containingSCC = scc;
this.head = head;
if (unrollMaxDepth != 0) {
next = new LoopUnroll(unrollMaxDepth - 1, soundLoopUnrolling, scc, newBlockSeqGlobal, head);
}
}
Block Visit(GraphNode node) {
Contract.Requires(node != null);
Contract.Ensures(Contract.Result() != null);
Block orig = node.Block;
Block nw;
if (newBlocks.TryGetValue(orig, out nw)) {
Contract.Assert(nw != null);
} else {
List body;
TransferCmd tcmd;
Contract.Assert(orig.TransferCmd != null);
if (next == null && node.IsCutPoint) {
// as the body, use the assert/assume commands that make up the loop invariant
body = new List();
foreach (Cmd/*!*/ c in node.Body) {
Contract.Assert(c != null);
if (c is PredicateCmd || c is CommentCmd) {
body.Add(c);
} else {
break;
}
}
if (soundLoopUnrolling) {
body.Add(new AssertCmd(orig.tok, Bpl.Expr.False));
} else {
body.Add(new AssumeCmd(orig.tok, Bpl.Expr.False));
}
tcmd = new ReturnCmd(orig.TransferCmd.tok);
} else {
body = node.Body;
List newSuccs = new List();
foreach (GraphNode succ in node.ForwardEdges) {
Block s;
if (containingSCC[node] == containingSCC[succ]) {
s = Visit(succ);
} else {
Contract.Assert(head != null); // follows from object invariant
s = head.Visit(succ);
}
newSuccs.Add(s);
}
Contract.Assert(next != null || node.BackEdges.Count == 0); // follows from if-else test above and the GraphNode invariant
foreach (GraphNode succ in node.BackEdges) {
Contract.Assert(next != null); // since if we get here, node.BackEdges.Count != 0
Block s = next.Visit(succ);
newSuccs.Add(s);
}
if (newSuccs.Count == 0) {
tcmd = new ReturnCmd(orig.TransferCmd.tok);
} else {
tcmd = new GotoCmd(orig.TransferCmd.tok, newSuccs);
}
}
nw = new Block(orig.tok, orig.Label + "#" + this.c, body, tcmd);
newBlocks.Add(orig, nw);
newBlockSeqGlobal.Add(nw);
}
return nw;
}
}
}