Skip to content

Commit 1e7ee8d

Browse files
committed
C#: Loop unrolling for foreach statements
1 parent 1bfef70 commit 1e7ee8d

File tree

13 files changed

+546
-174
lines changed

13 files changed

+546
-174
lines changed

csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowElement.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,19 @@ class ControlFlowElement extends ExprOrStmtParent, @control_flow_element {
3232
* several `ControlFlow::Node`s, for example to represent the continuation
3333
* flow in a `try/catch/finally` construction.
3434
*/
35-
Node getAControlFlowNode() { result.getElement() = this }
35+
Nodes::ElementNode getAControlFlowNode() { result.getElement() = this }
3636

3737
/**
3838
* Gets a first control flow node executed within this element.
3939
*/
40-
Node getAControlFlowEntryNode() {
40+
Nodes::ElementNode getAControlFlowEntryNode() {
4141
result = Internal::getAControlFlowEntryNode(this).getAControlFlowNode()
4242
}
4343

4444
/**
4545
* Gets a potential last control flow node executed within this element.
4646
*/
47-
Node getAControlFlowExitNode() {
47+
Nodes::ElementNode getAControlFlowExitNode() {
4848
result = Internal::getAControlFlowExitNode(this).getAControlFlowNode()
4949
}
5050

csharp/ql/src/semmle/code/csharp/controlflow/ControlFlowGraph.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,8 @@ module ControlFlow {
318318
class ExceptionHandlerSplit = ExceptionHandlerSplitting::ExceptionHandlerSplitImpl;
319319

320320
class BooleanSplit = BooleanSplitting::BooleanSplitImpl;
321+
322+
class LoopUnrollingSplit = LoopUnrollingSplitting::LoopUnrollingSplitImpl;
321323
}
322324

323325
class BasicBlock = BBs::BasicBlock;

csharp/ql/src/semmle/code/csharp/controlflow/Guards.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1020,7 +1020,7 @@ module Internal {
10201020
}
10211021

10221022
/** Holds if pre-basic-block `bb` only is reached when guard `g` has abstract value `v`. */
1023-
private predicate preControls(Guard g, PreBasicBlocks::PreBasicBlock bb, AbstractValue v) {
1023+
predicate preControls(Guard g, PreBasicBlocks::PreBasicBlock bb, AbstractValue v) {
10241024
exists(AbstractValue v0, Guard g0 | preControlsDirect(g0, bb, v0) |
10251025
preImpliesSteps(g0, v0, g, v)
10261026
)

csharp/ql/src/semmle/code/csharp/controlflow/internal/Splitting.qll

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ private module Cached {
2929
TInitializerSplitKind() or
3030
TFinallySplitKind() or
3131
TExceptionHandlerSplitKind() or
32-
TBooleanSplitKind(BooleanSplitting::BooleanSplitSubKind kind) { kind.startsSplit(_) }
32+
TBooleanSplitKind(BooleanSplitting::BooleanSplitSubKind kind) { kind.startsSplit(_) } or
33+
TLoopUnrollingSplitKind(LoopUnrollingSplitting::UnrollableLoopStmt loop)
3334

3435
cached
3536
newtype TSplit =
@@ -39,7 +40,8 @@ private module Cached {
3940
TBooleanSplit(BooleanSplitting::BooleanSplitSubKind kind, boolean branch) {
4041
kind.startsSplit(_) and
4142
(branch = true or branch = false)
42-
}
43+
} or
44+
TLoopUnrollingSplit(LoopUnrollingSplitting::UnrollableLoopStmt loop)
4345

4446
cached
4547
newtype TSplits =
@@ -1023,6 +1025,165 @@ module BooleanSplitting {
10231025
}
10241026
}
10251027

1028+
module LoopUnrollingSplitting {
1029+
private import semmle.code.csharp.controlflow.Guards as Guards
1030+
private import PreBasicBlocks
1031+
private import PreSsa
1032+
1033+
/** Holds if `ce` is guarded by a (non-)empty check, as specified by `v`. */
1034+
private predicate emptinessGuarded(
1035+
Guards::Guard g, Guards::CollectionExpr ce, Guards::AbstractValues::EmptyCollectionValue v
1036+
) {
1037+
exists(PreBasicBlock bb | Guards::Internal::preControls(g, bb, v) |
1038+
PreSsa::adjacentReadPairSameVar(g, ce) and
1039+
bb.getAnElement() = ce
1040+
)
1041+
}
1042+
1043+
/**
1044+
* A loop where the body is guaranteed to be executed at least once, and
1045+
* can therefore be unrolled in the control flow graph.
1046+
*/
1047+
abstract class UnrollableLoopStmt extends LoopStmt {
1048+
/** Holds if the step `pred --c--> succ` should start loop unrolling. */
1049+
abstract predicate startUnroll(ControlFlowElement pred, ControlFlowElement succ, Completion c);
1050+
1051+
/** Holds if the step `pred --c--> succ` should stop loop unrolling. */
1052+
abstract predicate stopUnroll(ControlFlowElement pred, ControlFlowElement succ, Completion c);
1053+
1054+
/**
1055+
* Holds if any step `pred --c--> _` should be pruned from the unrolled loop
1056+
* (the loop condition evaluating to `false`).
1057+
*/
1058+
abstract predicate pruneLoopCondition(ControlFlowElement pred, ConditionalCompletion c);
1059+
1060+
/** Gets a descendant that belongs to the body of this loop. */
1061+
ControlFlowElement getABodyDescendant() {
1062+
result = this.getBody()
1063+
or
1064+
exists(ControlFlowElement mid |
1065+
mid = this.getABodyDescendant() and
1066+
result = getAChild(mid, mid.getEnclosingCallable())
1067+
)
1068+
}
1069+
}
1070+
1071+
private class UnrollableForeachStmt extends UnrollableLoopStmt, ForeachStmt {
1072+
UnrollableForeachStmt() {
1073+
exists(Guards::AbstractValues::EmptyCollectionValue v | v.isNonEmpty() |
1074+
emptinessGuarded(_, this.getIterableExpr(), v)
1075+
or
1076+
this.getIterableExpr() = v.getAnExpr()
1077+
)
1078+
}
1079+
1080+
override predicate startUnroll(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
1081+
pred = last(this.getIterableExpr(), c) and
1082+
succ = this
1083+
}
1084+
1085+
override predicate stopUnroll(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
1086+
pred = last(this.getBody(), c) and
1087+
succ = succ(pred, c) and
1088+
not succ = this.getABodyDescendant()
1089+
}
1090+
1091+
override predicate pruneLoopCondition(ControlFlowElement pred, ConditionalCompletion c) {
1092+
pred = this and
1093+
c.(EmptinessCompletion).isEmpty()
1094+
}
1095+
}
1096+
1097+
/**
1098+
* A split for loops where the body is guaranteed to be executed at least once, and
1099+
* can therefore be unrolled in the control flow graph. For example, in
1100+
*
1101+
* ```
1102+
* void M(string[] args)
1103+
* {
1104+
* if (args.Length == 0)
1105+
* return;
1106+
* foreach (var arg in args)
1107+
* System.Console.WriteLine(args);
1108+
* }
1109+
* ```
1110+
*
1111+
* the `foreach` loop is guaranteed to be executed at least once, as a result of the
1112+
* `args.Length == 0` check.
1113+
*/
1114+
class LoopUnrollingSplitImpl extends SplitImpl, TLoopUnrollingSplit {
1115+
UnrollableLoopStmt loop;
1116+
1117+
LoopUnrollingSplitImpl() { this = TLoopUnrollingSplit(loop) }
1118+
1119+
override string toString() {
1120+
result = "unroll (line " + loop.getLocation().getStartLine() + ")"
1121+
}
1122+
}
1123+
1124+
private int getListOrder(UnrollableLoopStmt loop) {
1125+
exists(Callable c, int r | c = loop.getEnclosingCallable() |
1126+
result = r + BooleanSplitting::getNextListOrder() - 1 and
1127+
loop = rank[r](UnrollableLoopStmt loop0 |
1128+
loop0.getEnclosingCallable() = c
1129+
|
1130+
loop0 order by loop0.getLocation().getStartLine(), loop0.getLocation().getStartColumn()
1131+
)
1132+
)
1133+
}
1134+
1135+
int getNextListOrder() {
1136+
result = max(int i | i = getListOrder(_) + 1 or i = BooleanSplitting::getNextListOrder())
1137+
}
1138+
1139+
private class LoopUnrollingSplitKind extends SplitKind, TLoopUnrollingSplitKind {
1140+
private UnrollableLoopStmt loop;
1141+
1142+
LoopUnrollingSplitKind() { this = TLoopUnrollingSplitKind(loop) }
1143+
1144+
override int getListOrder() { result = getListOrder(loop) }
1145+
1146+
override string toString() { result = "Unroll" }
1147+
}
1148+
1149+
private class LoopUnrollingSplitInternal extends SplitInternal, LoopUnrollingSplitImpl {
1150+
override LoopUnrollingSplitKind getKind() { result = TLoopUnrollingSplitKind(loop) }
1151+
1152+
override predicate hasEntry(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
1153+
loop.startUnroll(pred, succ, c)
1154+
}
1155+
1156+
override predicate hasEntry(Callable pred, ControlFlowElement succ) { none() }
1157+
1158+
/**
1159+
* Holds if this split applies to control flow element `pred`, where `pred`
1160+
* is a valid predecessor.
1161+
*/
1162+
private predicate appliesToPredecessor(ControlFlowElement pred) {
1163+
this.appliesTo(pred) and
1164+
(exists(succ(pred, _)) or exists(succExit(pred, _)))
1165+
}
1166+
1167+
override predicate hasExit(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
1168+
this.appliesToPredecessor(pred) and
1169+
loop.stopUnroll(pred, succ, c)
1170+
}
1171+
1172+
override Callable hasExit(ControlFlowElement pred, Completion c) {
1173+
this.appliesToPredecessor(pred) and
1174+
result = succExit(pred, c) and
1175+
not loop.pruneLoopCondition(pred, c)
1176+
}
1177+
1178+
override predicate hasSuccessor(ControlFlowElement pred, ControlFlowElement succ, Completion c) {
1179+
this.appliesToPredecessor(pred) and
1180+
succ = succ(pred, c) and
1181+
not loop.pruneLoopCondition(pred, c) and
1182+
not loop.stopUnroll(pred, succ, c)
1183+
}
1184+
}
1185+
}
1186+
10261187
/**
10271188
* A set of control flow node splits. The set is represented by a list of splits,
10281189
* ordered by ascending rank.

csharp/ql/src/semmle/code/csharp/dataflow/internal/BaseSSA.qll

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,13 @@ module BaseSsa {
99
private import ControlFlow
1010
private import AssignableDefinitions
1111

12+
pragma[noinline]
13+
Callable getAnAssigningCallable(LocalScopeVariable v) {
14+
result = any(AssignableDefinition def | def.getTarget() = v).getEnclosingCallable()
15+
}
16+
1217
private class SimpleLocalScopeVariable extends LocalScopeVariable {
13-
SimpleLocalScopeVariable() {
14-
not exists(AssignableDefinition def1, AssignableDefinition def2 |
15-
def1.getTarget() = this and
16-
def2.getTarget() = this and
17-
def1.getEnclosingCallable() != def2.getEnclosingCallable()
18-
)
19-
}
18+
SimpleLocalScopeVariable() { not getAnAssigningCallable(this) != getAnAssigningCallable(this) }
2019
}
2120

2221
/**

csharp/ql/test/library-tests/controlflow/graph/BasicBlock.expected

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -290,38 +290,39 @@
290290
| LoopUnrolling.cs:10:13:10:19 | return ...; | LoopUnrolling.cs:10:13:10:19 | return ...; | 1 |
291291
| LoopUnrolling.cs:11:9:12:35 | foreach (... ... in ...) ... | LoopUnrolling.cs:11:9:12:35 | foreach (... ... in ...) ... | 1 |
292292
| LoopUnrolling.cs:11:21:11:23 | String arg | LoopUnrolling.cs:12:13:12:34 | call to method WriteLine | 4 |
293-
| LoopUnrolling.cs:11:28:11:31 | access to parameter args | LoopUnrolling.cs:11:28:11:31 | access to parameter args | 1 |
294-
| LoopUnrolling.cs:15:10:15:11 | enter M2 | LoopUnrolling.cs:18:26:18:27 | access to local variable xs | 10 |
293+
| LoopUnrolling.cs:11:28:11:31 | access to parameter args | LoopUnrolling.cs:12:13:12:34 | [unroll (line 11)] call to method WriteLine | 6 |
294+
| LoopUnrolling.cs:15:10:15:11 | enter M2 | LoopUnrolling.cs:19:13:19:32 | [unroll (line 18)] call to method WriteLine | 15 |
295295
| LoopUnrolling.cs:15:10:15:11 | exit M2 | LoopUnrolling.cs:15:10:15:11 | exit M2 | 1 |
296296
| LoopUnrolling.cs:18:9:19:33 | foreach (... ... in ...) ... | LoopUnrolling.cs:18:9:19:33 | foreach (... ... in ...) ... | 1 |
297297
| LoopUnrolling.cs:18:21:18:21 | String x | LoopUnrolling.cs:19:13:19:32 | call to method WriteLine | 4 |
298298
| LoopUnrolling.cs:22:10:22:11 | enter M3 | LoopUnrolling.cs:24:29:24:32 | access to parameter args | 3 |
299299
| LoopUnrolling.cs:22:10:22:11 | exit M3 | LoopUnrolling.cs:22:10:22:11 | exit M3 | 1 |
300300
| LoopUnrolling.cs:24:9:26:40 | foreach (... ... in ...) ... | LoopUnrolling.cs:24:9:26:40 | foreach (... ... in ...) ... | 1 |
301-
| LoopUnrolling.cs:24:22:24:24 | Char arg | LoopUnrolling.cs:25:34:25:37 | access to parameter args | 2 |
301+
| LoopUnrolling.cs:24:22:24:24 | Char arg | LoopUnrolling.cs:26:17:26:39 | [unroll (line 25)] call to method WriteLine | 7 |
302302
| LoopUnrolling.cs:25:13:26:40 | foreach (... ... in ...) ... | LoopUnrolling.cs:25:13:26:40 | foreach (... ... in ...) ... | 1 |
303303
| LoopUnrolling.cs:25:26:25:29 | Char arg0 | LoopUnrolling.cs:26:17:26:39 | call to method WriteLine | 4 |
304304
| LoopUnrolling.cs:29:10:29:11 | enter M4 | LoopUnrolling.cs:32:26:32:27 | access to local variable xs | 7 |
305305
| LoopUnrolling.cs:29:10:29:11 | exit M4 | LoopUnrolling.cs:29:10:29:11 | exit M4 | 1 |
306306
| LoopUnrolling.cs:32:9:33:33 | foreach (... ... in ...) ... | LoopUnrolling.cs:32:9:33:33 | foreach (... ... in ...) ... | 1 |
307307
| LoopUnrolling.cs:32:21:32:21 | String x | LoopUnrolling.cs:33:13:33:32 | call to method WriteLine | 4 |
308-
| LoopUnrolling.cs:36:10:36:11 | enter M5 | LoopUnrolling.cs:40:26:40:27 | access to local variable xs | 17 |
308+
| LoopUnrolling.cs:36:10:36:11 | enter M5 | LoopUnrolling.cs:42:17:42:40 | [unroll (line 40), unroll (line 41)] call to method WriteLine | 27 |
309309
| LoopUnrolling.cs:36:10:36:11 | exit M5 | LoopUnrolling.cs:36:10:36:11 | exit M5 | 1 |
310310
| LoopUnrolling.cs:40:9:42:41 | foreach (... ... in ...) ... | LoopUnrolling.cs:40:9:42:41 | foreach (... ... in ...) ... | 1 |
311-
| LoopUnrolling.cs:40:21:40:21 | String x | LoopUnrolling.cs:41:30:41:31 | access to local variable ys | 2 |
311+
| LoopUnrolling.cs:40:21:40:21 | String x | LoopUnrolling.cs:42:17:42:40 | [unroll (line 41)] call to method WriteLine | 9 |
312+
| LoopUnrolling.cs:41:13:42:41 | [unroll (line 40)] foreach (... ... in ...) ... | LoopUnrolling.cs:41:13:42:41 | [unroll (line 40)] foreach (... ... in ...) ... | 1 |
312313
| LoopUnrolling.cs:41:13:42:41 | foreach (... ... in ...) ... | LoopUnrolling.cs:41:13:42:41 | foreach (... ... in ...) ... | 1 |
313314
| LoopUnrolling.cs:41:25:41:25 | String y | LoopUnrolling.cs:42:17:42:40 | call to method WriteLine | 6 |
314-
| LoopUnrolling.cs:45:10:45:11 | enter M6 | LoopUnrolling.cs:48:9:52:9 | foreach (... ... in ...) ... | 11 |
315-
| LoopUnrolling.cs:45:10:45:11 | exit M6 | LoopUnrolling.cs:45:10:45:11 | exit M6 | 1 |
316-
| LoopUnrolling.cs:48:21:48:21 | String x | LoopUnrolling.cs:49:9:52:9 | {...} | 2 |
317-
| LoopUnrolling.cs:50:13:50:17 | Label: | LoopUnrolling.cs:51:13:51:23 | goto ...; | 5 |
318-
| LoopUnrolling.cs:55:10:55:11 | enter M7 | LoopUnrolling.cs:58:9:64:9 | foreach (... ... in ...) ... | 11 |
315+
| LoopUnrolling.cs:41:25:41:25 | [unroll (line 40)] String y | LoopUnrolling.cs:42:17:42:40 | [unroll (line 40)] call to method WriteLine | 6 |
316+
| LoopUnrolling.cs:45:10:45:11 | enter M6 | LoopUnrolling.cs:49:9:52:9 | [unroll (line 48)] {...} | 13 |
317+
| LoopUnrolling.cs:50:13:50:17 | [unroll (line 48)] Label: | LoopUnrolling.cs:51:13:51:23 | [unroll (line 48)] goto ...; | 5 |
318+
| LoopUnrolling.cs:55:10:55:11 | enter M7 | LoopUnrolling.cs:60:17:60:17 | [unroll (line 58)] access to parameter b | 15 |
319319
| LoopUnrolling.cs:55:10:55:11 | exit M7 | LoopUnrolling.cs:55:10:55:11 | exit M7 | 1 |
320-
| LoopUnrolling.cs:58:21:58:21 | String x | LoopUnrolling.cs:60:17:60:17 | access to parameter b | 4 |
321-
| LoopUnrolling.cs:58:21:58:21 | [b (line 55): false] String x | LoopUnrolling.cs:60:17:60:17 | [b (line 55): false] access to parameter b | 4 |
322-
| LoopUnrolling.cs:58:21:58:21 | [b (line 55): true] String x | LoopUnrolling.cs:60:17:60:17 | [b (line 55): true] access to parameter b | 4 |
323-
| LoopUnrolling.cs:61:17:61:37 | [b (line 55): true] ...; | LoopUnrolling.cs:58:9:64:9 | [b (line 55): true] foreach (... ... in ...) ... | 9 |
324-
| LoopUnrolling.cs:62:13:63:37 | [b (line 55): false] if (...) ... | LoopUnrolling.cs:58:9:64:9 | [b (line 55): false] foreach (... ... in ...) ... | 3 |
320+
| LoopUnrolling.cs:58:9:64:9 | [b (line 55): false] foreach (... ... in ...) ... | LoopUnrolling.cs:58:9:64:9 | [b (line 55): false] foreach (... ... in ...) ... | 1 |
321+
| LoopUnrolling.cs:58:9:64:9 | [b (line 55): true] foreach (... ... in ...) ... | LoopUnrolling.cs:58:9:64:9 | [b (line 55): true] foreach (... ... in ...) ... | 1 |
322+
| LoopUnrolling.cs:58:21:58:21 | [b (line 55): false] String x | LoopUnrolling.cs:62:17:62:17 | [b (line 55): false] access to parameter b | 6 |
323+
| LoopUnrolling.cs:58:21:58:21 | [b (line 55): true] String x | LoopUnrolling.cs:63:17:63:36 | [b (line 55): true] call to method WriteLine | 12 |
324+
| LoopUnrolling.cs:61:17:61:37 | [b (line 55): true, unroll (line 58)] ...; | LoopUnrolling.cs:63:17:63:36 | [b (line 55): true, unroll (line 58)] call to method WriteLine | 8 |
325+
| LoopUnrolling.cs:62:13:63:37 | [b (line 55): false, unroll (line 58)] if (...) ... | LoopUnrolling.cs:62:17:62:17 | [b (line 55): false, unroll (line 58)] access to parameter b | 2 |
325326
| LoopUnrolling.cs:67:10:67:11 | enter M7 | LoopUnrolling.cs:69:14:69:23 | call to method Any | 6 |
326327
| LoopUnrolling.cs:67:10:67:11 | exit M7 | LoopUnrolling.cs:67:10:67:11 | exit M7 | 1 |
327328
| LoopUnrolling.cs:70:13:70:19 | return ...; | LoopUnrolling.cs:70:13:70:19 | return ...; | 1 |

0 commit comments

Comments
 (0)