-
Notifications
You must be signed in to change notification settings - Fork 4
/
Optimiser.fs
1220 lines (1118 loc) · 47.8 KB
/
Optimiser.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/// <summary>
/// Graph and term optimisation.
///
/// <para>
/// This module contains two submodules: <c>Graph</c>, which optimises
/// control-flow graphs, and <c>Term</c>, which optimises proof terms.
/// Both intend to reduce the proof burden passed to the backends.
/// </para>
/// </summary>
module Starling.Optimiser
open System.Collections.Generic
open Chessie.ErrorHandling
open Starling.Core.TypeSystem
open Starling.Collections
open Starling.Utils
open Starling.Utils.Config
open Starling.Semantics
open Starling.Core.Expr
open Starling.Core.ExprEquiv
open Starling.Core.Var
open Starling.Core.Symbolic
open Starling.Core.Symbolic.Traversal
open Starling.Core.Model
open Starling.Core.Command
open Starling.Core.Command.Queries
open Starling.Core.Traversal
open Starling.Core.View
open Starling.Core.GuardedView
open Starling.Core.GuardedView.Traversal
/// <summary>
/// Types for the graph transformer.
/// </summary>
[<AutoOpen>]
module Types =
open Starling.Core.Graph
/// <summary>
/// A type of unification used with the <c>Unify</c> leg of
/// <see cref="Transform"/>.
/// <summary>
type UnifyMode =
/// <summary>
/// Only unify if there are no edges between the two nodes to
/// unify.
/// </summary>
| OnlyIfNoConnections
/// <summary>
/// Always unify, but make any connecting edges into cycles.
/// </summary>
| MakeConnectionsCycles
/// <summary>
/// Always unify, and remove any connecting edges.
/// </summary>
| RemoveConnections
/// <summary>
/// A graph transformer command.
/// </summary>
type Transform =
/// <summary>
/// Remove the given node.
/// Abort transformation if the node does not exist, or has
/// dangling edges.
/// </summary>
| RmNode of node : string
/// <summary>
/// Remove all edges from <c>src</c> to <c>dest</c>.
/// Abort transformation if the nodes do not exist.
/// </summary>
| RmAllEdgesBetween of src : string * dest : string
/// <summary>
/// Remove the given in-edge leading into <c>node</c>.
/// Abort transformation if the edge doesn't make sense.
/// </summary>
| RmInEdge of edge : InEdge * dest : string
/// <summary>
/// Remove the given out-edge leading out of <c>node</c>.
/// Abort transformation if the edge doesn't make sense.
/// </summary>
| RmOutEdge of src : string * edge : OutEdge
/// <summary>
/// Remove the named edge from <c>src</c> to <c>dest</c>.
/// Abort transformation if the nodes do not exist.
/// </summary>
| RmNamedEdgeBetween of src : string * dest : string * name : string
/// <summary>
/// Adds an edge combining the in edge <c>in</c> with the out
/// edge <c>out</c>.
/// Abort transformation if anything goes wrong.
/// </summary>
| MkCombinedEdge of inE : InEdge * outE : OutEdge
/// <summary>
/// Adds an edge from <c>src</c> to <c>dest</c>.
/// Abort transformation if the nodes do not exist.
/// </summary>
| MkEdgeBetween of src : string * dest : string
* name : string * cmd : Command
/// <summary>
/// Merges the node <c>src</c> into <c>dest</c>,
/// substituting the latter for the former in all edges.
/// How the unification handles edges is specified by
/// <see cref="UnifyType"/>.
/// Abort transformation if the nodes do not exist.
/// </summary>
| Unify of src : string * dest : string * mode : UnifyMode
/// <summary>
/// A node-centric graph transformation context.
/// </summary>
type TransformContext =
{ /// <summary>
/// The current state of the graph.
/// </summary>
Graph : Graph
/// <summary>
/// The node currently being inspected.
/// If None, the node has been removed and no longer needs
/// transformation.
/// </summary>
Node : string option
/// <summary>
/// The log of transformations done, in reverse chronological
/// order.
/// </summary>
Transforms : Transform list
}
/// <summary>
/// Types of error that can happen in the term optimiser.
/// </summary>
type TermOptError =
/// <summary>
/// An error occurred during traversal.
/// </summary>
| Traversal of TraversalError<TermOptError>
/// <summary>
/// Types of error that can happen in the graph optimiser.
/// </summary>
type GraphOptError =
/// <summary>
/// An error occurred during traversal.
/// </summary>
| Traversal of TraversalError<GraphOptError>
/// <summary>
/// Utilities common to the whole optimisation system.
/// </summary>
module Utils =
/// <summary>
/// Parses an optimisation string list.
/// </summary>
/// <param name="opts">
/// A list of prefixes of optimisation names.
/// Optimisation name prefixes starting with 'no-' have this stripped
/// off, and the optimisation is negated.
/// </param>
/// <returns>
/// <para>
/// A list of tuples of (optimisation prefixes * enabled boolean)
/// If the optimisation is force enabled, the enabled boolean is true
/// otherwise it's false when force disabled.
/// </para>
/// <para>
/// As these strings are prefixes, 'graph' will switch on
/// all optimisations beginning with 'graph'.
/// </para>
/// <para>
/// The optimisation name 'all' is special.
/// It force-enables (or force-disables if 'no-all') all
/// optimisations.
/// </para>
/// </returns>
let parseOptimisationString (opts : string list) : (string * bool) list =
opts
|> List.map (fun (str : string) ->
if str.StartsWith("no-") then
(str.Remove(0, 3), false)
else
(str, true))
/// <summary>
/// Decides whether an optimisation name matches an allowed
/// optimisation prefix.
/// </summary>
/// <param name="prefixes">
/// The set of allowed optimisation prefixes.
/// </param>
/// <param name="opt">
/// The optimisation name to check.
/// </param>
/// <returns>
/// True if <paramref name="opt" /> is allowed, according to
/// <paramref name="prefixes" />.
/// </returns>
let optAllowed (prefixes : Set<string>) (opt : string) : bool =
// Check for the most obvious (and O(1)) case first.
Set.contains opt prefixes ||
Set.exists opt.StartsWith prefixes
/// <summary>
/// Applies a pair of optimisation removes and adds to an optimisation
/// set.
/// </summary>
/// <param name="opts">
/// A list of triples of optimiser name, whether it's enabled by
/// default, and function.
/// </param>
/// <param name="removes">
/// The set of optimisation names to remove. If this contains 'all',
/// no optimisations will be permitted.
/// </param>
/// <param name="adds">
/// The set of optimisation names to adds. If this contains 'all',
/// all optimisations will be permitted.
/// </param>
/// <typeparam name="A">
/// The type of items being optimised.
/// </typeparam>
/// <typeparam name="Error">
/// The type of errors coming out of the optimiser.
/// </typeparam>
/// <returns>
/// A list of optimisers to run.
/// </returns>
let mkOptimiserList
(allOpts : (string * bool * ('A -> Result<'A, 'Error>)) list)
(opts : (string * bool) seq)
: ('A -> Result<'A, 'Error>) list =
let config = config()
let optimisationSet = new HashSet<string>();
// try add or remove from prefix
let addFromPrefix prefix =
for (optName : string, _, _) in allOpts do
if optName.StartsWith(prefix) then
if config.verbose && not (optimisationSet.Contains(optName)) then
eprintfn "note: forced %s on" optName
ignore <| optimisationSet.Add(prefix)
let removeFromPrefix prefix =
for (optName, _, _) in allOpts do
if optName.StartsWith(prefix) then
if config.verbose && optimisationSet.Contains(optName) then
eprintfn "note: forced %s off" optName
ignore <| optimisationSet.Remove(prefix)
for (optName, enabledByDefault, _) in allOpts do
if enabledByDefault then
ignore <| optimisationSet.Add(optName)
for (optName, forceEnabled) in opts do
if optName = "all" then
if forceEnabled then
if config.verbose then
eprintfn "note: forced all optimisations on"
for (optName, enabledByDefault, _) in allOpts do
ignore <| optimisationSet.Add(optName)
else
if config.verbose then
eprintfn "note: forced all optimisations off"
optimisationSet.Clear()
else
if forceEnabled then
addFromPrefix optName
else
removeFromPrefix optName
List.filter (fun (name, _, _) -> optimisationSet.Contains(name)) allOpts
|> List.map (fun (_, _, f) -> f)
/// <summary>
/// Discovers which optimisers to activate, and applies them in
/// the specified order.
///
/// Each optimisation comes as a triple
/// (optimisation-name : string, enabled-as-default? : bool, optimisation: ('a -> 'a))
///
/// Enabling or disabling them based off the command-line arguments and whether they're enabled by default
/// </summary>
/// <param name="removes">
/// The set of optimisation names to remove. If this contains 'all',
/// no optimisations will be permitted.
/// </param>
/// <param name="adds">
/// The set of optimisation names to add. If this contains 'all',
/// all optimisations will be permitted.
/// </param>
/// <param name="opts">
/// A list of triples of optimiser name, whether it's enabled by
/// default, and function.
/// </param>
/// <typeparam name="A">
/// The type of the item to optimise.
/// </typeparam>
/// <typeparam name="Error">
/// The type of errors coming out of the optimiser.
/// </typeparam>
/// <returns>
/// A function that, when applied to something, optimises it with
/// the selected optimisers.
/// </returns>
let optimiseWith
(args : (string * bool) list)
(opts : (string * bool * ('A -> Result<'A, 'Error>)) list)
: ('A -> Result<'A, 'Error>) =
let fs = mkOptimiserList opts args
(* This would be much more readable if it wasn't pointfree...
...but would also cause fs to be evaluated every single time
the result of partially applying optimiseWith to removes, adds,
verbose and opts is then applied to the input! Oh, F#. *)
ok >> (flip (List.fold (flip bind)) fs)
/// <summary>
/// Graph optimisation.
/// </summary>
module Graph =
open Starling.Core.Axiom
open Starling.Core.Graph
/// <summary>
/// Safely stitches the names of two edges together.
/// </summary>
/// <param name="_arg1">
/// The first edge, heading in.
/// </param>
/// <param name="_arg2">
/// The second edge, heading out.
/// </param>
/// <returns>
/// A name for any edge replacing both above edges.
/// </returns>
let glueNames ({ Name = a } : InEdge) ({ Name = b } : OutEdge) : string =
String.concat "__" [ a ; b ]
/// <summary>
/// Selects all of the connecting out-edges between two nodes.
/// </summary>
/// <param name="x">The first node to check.</param>
/// <param name="y">The second node to check.</param>
/// <param name="graph">The graph to check.</param>
/// <returns>
/// The set of <see cref="OutEdge"/c>s connecting <paramref name="x"/>
/// to <paramref name="y"/> and <paramref name="y"/> to
/// <paramref name="x"/>.
/// </returns>
let connections (x : NodeID) (y : NodeID) (graph : Graph) : OutEdge seq =
let xView, xOut, _, xnk = graph.Contents.[x]
let yView, yOut, _, ynk = graph.Contents.[y]
let xToY = xOut |> Set.toSeq |> Seq.filter (fun { Dest = d } -> d = y)
let yToX = yOut |> Set.toSeq |> Seq.filter (fun { Dest = d } -> d = x)
Seq.append xToY yToX
/// <summary>
/// Runs a graph transformation.
/// </summary>
/// <param name="ctx">
/// The transformation context to transform.
/// </param>
/// <param name="xform">
/// The transform to perform.
/// </param>
/// <returns>
/// An Option. If None, the transformation did not apply.
/// Otherwise, the Option contains the new transformation context.
/// </returns>
let runTransform (ctx : TransformContext) (xform : Transform)
: TransformContext option =
let f =
match xform with
| RmNode node -> rmNode node
// All of these commands can be implemented the same way!
| RmInEdge ({ Src = src ; Name = name }, dest)
| RmOutEdge (src, { Dest = dest ; Name = name } )
| RmNamedEdgeBetween (src, dest, name) ->
rmEdgesBetween src dest ((=) name)
| RmAllEdgesBetween (src, dest) -> rmEdgesBetween src dest always
| MkCombinedEdge (inE, outE) ->
mkEdgeBetween inE.Src
outE.Dest
(glueNames inE outE)
(inE.Command @ outE.Command)
| MkEdgeBetween (src, dest, name, cmd) ->
mkEdgeBetween src dest name cmd
| Unify (src, dest, mode) ->
match mode with
| MakeConnectionsCycles -> unify src dest
| RemoveConnections ->
// NB: rmEdgesBetween only removes in one direction!
rmEdgesBetween src dest always
>> Option.bind (rmEdgesBetween dest src always)
>> Option.bind (unify src dest)
| OnlyIfNoConnections ->
fun graph ->
if Seq.isEmpty (connections src dest graph)
then unify src dest graph
else Some graph
let node' =
match xform with
| RmNode n when Some n = ctx.Node -> None
| _ -> ctx.Node
ctx.Graph
|> f
|> Option.map (fun g -> { Graph = g
Node = node'
Transforms = xform :: ctx.Transforms } )
/// <summary>
/// Runs a graph transformation list.
///
/// If any transformation step fails, the initial context is
/// returned, rewinding the transformations.
/// </summary>
/// <param name="xforms">
/// The set of transforms to perform.
/// </param>
/// <param name="initial">
/// A initial transformation context (to allow transformation results
/// to compose).
/// </param>
/// <returns>
/// The final transformation context.
/// If the list of transformations has not changed from
/// <paramref name="initial" />, the transformation list was rewound.
/// </returns>
let runTransforms
(xforms : Transform seq)
(initial : TransformContext)
: TransformContext =
match (foldFastTerm runTransform initial xforms) with
| Some final -> final
| None -> initial
/// <summary>
/// Decides whether two nodes have different names but the same view,
/// and are connected, but only by no-op commands.
/// </summary>
/// <param name="graph">
/// The graph on which <paramref name="x" /> and
/// <paramref name="y" /> lie.
/// </param>
/// <param name="x">
/// The name of one of the nodes to consider.
/// </param>
/// <param name="y">
/// The name of the other node to consider.
/// </param>
/// <returns>
/// <c>true</c> if, and only if, <paramref name="x" />
/// and <paramref name="y" /> have different names, have the same
/// view, and are connected, but only by no-op commands.
/// </returns>
let nopConnected (graph : Graph) (x : NodeID) (y : NodeID) : bool =
let xView, _, _, _ = graph.Contents.[x]
let yView, _, _, _ = graph.Contents.[y]
let cons = connections x y graph
// Different names?
(x <> y)
// Same view?
&& (xView = yView)
// Connected?
&& not (Seq.isEmpty cons)
// All edges from x to y and y to x are nop?
&& Seq.forall (fun { OutEdge.Command = c } -> List.forall isNop c) cons
/// <summary>
/// Plumbs a function over various properties of a graph and
/// node into the format expected by <c>onNodes</c>.
/// </summary>
/// <param name="ctx">
/// The transformation context.
/// </param>
/// <param name="f">
/// The function, which takes:
/// the name of the node;
/// the view of the node;
/// the out-edges of the node;
/// the in-edges of the node.
///
/// It should return a transformation sequence.
/// </param>
/// <returns>
/// If <paramref name="node" /> exists in the graph, the result of
/// calling <paramref name="f" /> and running the resulting
/// transformations.
/// Else, the original context.
/// </returns>
let expandNodeIn
(ctx : TransformContext)
(f : NodeID -> GraphViewExpr -> Set<OutEdge> -> Set<InEdge>
-> NodeKind -> Transform seq)
: TransformContext =
let findNode n = Option.map (mkPair n) (ctx.Graph.Contents.TryFind n)
match Option.bind findNode ctx.Node with
| Some (nN, (nV, outs, ins, nk)) ->
let xforms = f nN nV outs ins nk
runTransforms xforms ctx
| None -> ctx
/// <summary>
/// Given a node, tries to unify every other node that is
/// equivalent and connected, but only by no-op commands, into it.
/// </summary>
/// <param name="ctx">
/// The transformation context.
/// </param>
/// <returns>
/// A triple containing the list of node names removed, a graph,
/// and the input node Option.
/// The graph is equivalent to <paramref name="g" />, but with some
/// nodes merged into the named node if they are
/// equivalent and connected only by no-op commands.
/// </returns>
let collapseNops (ctx : TransformContext) : TransformContext =
let opt node nView outEdges inEdges nk =
let outNodes = outEdges
|> Set.toSeq
|> Seq.map (fun { Dest = d } -> d)
let inNodes = inEdges
|> Set.toSeq
|> Seq.map (fun { Src = s } -> s)
Seq.append outNodes inNodes
// Then, find out which ones are nop-connected.
|> Seq.filter (nopConnected ctx.Graph node)
(* If we found any nodes, then unify them.
We must also remove the edges between the nodes. *)
|> Seq.map (fun other -> Unify (other, node, RemoveConnections))
expandNodeIn ctx opt
/// <summary>
/// Decides whether a command is local.
/// </summary>
/// <param name="tVars">
/// A <c>VarMap</c> of thread-local variables.
/// </param>
/// <param name="cmd">The command to check.</param>
/// <returns>
/// A function returning True only if (but not necessarily if)
/// the given command is local (uses only local variables).
/// </returns>
let isLocalCommand (tVars : VarMap) (cmd : Command) : bool =
// TODO(CaptainHayashi): overlap with isLocalResults?
let typedVarIsThreadLocal var =
match tVars.TryFind (valueOf var) with
| Some _ -> true
| _ -> false
let isLocalArg arg =
// Forbid symbols in arguments.
match (mapTraversal (removeSymFromExpr ignore) arg) with
// TODO(CaptainHayashi): propagate if traversal error
| Bad _ -> false
| Ok (sp, _) ->
// Now check if all the variables in the argument are local.
let getVars = tliftOverExpr collectVars
match findVars getVars sp with
| Bad _ -> false
| Ok (pvars, _) ->
Seq.forall typedVarIsThreadLocal (Set.toSeq pvars)
let rec isLocalPrim prim =
match prim with
| // TODO(CaptainHayashi): too conservative?
SymC _ -> false
| Intrinsic (IAssign { AssignType = t })
| Intrinsic (BAssign { AssignType = t }) -> t = Local
| // TODO(CaptainHayashi): is this correct?
Intrinsic (Havoc v) -> typedVarIsThreadLocal v
| Stored { Args = ps } -> Seq.forall isLocalArg ps
| PrimBranch (trueBranch = t; falseBranch = f) ->
List.forall isLocalPrim t
&& maybe true (List.forall isLocalPrim) f
List.forall isLocalPrim cmd
/// Decides whether a given Command contains any `assume` command
/// in any of the sequentially composed primitives inside it
let hasAssume (c : Command) : bool =
List.exists isAssume c
/// <summary>
/// Partial active pattern matching <c>Sym</c>-less Boolean expressions.
/// </summary>
let (|NoSym|_|) (bexpr : BoolExpr<Sym<'Var>>) : BoolExpr<'Var> option =
bexpr
|> mkTypedSub normalRec
|> mapTraversal (removeSymFromBoolExpr ignore)
|> okOption
/// <summary>
/// Partial active pattern matching <c>Sym</c>-less expressions.
/// </summary>
let (|NoSymE|_|) (expr : Expr<Sym<'Var>>) : Expr<'Var> option =
expr
|> mapTraversal (removeSymFromExpr ignore)
|> okOption
/// <summary>
/// Active pattern matching on if-then-else guard multisets.
///
/// <para>
/// If-then-else guardsets contain two non-false guards, at least one
/// of which is equal to the negation of the other.
/// Neither guard can be symbolic.
/// </para>
///
/// <para>
/// The active pattern returns the guard and view of the first ITE
/// guard, then the guard and view of the second. The views are
/// <c>GView</c>s, but with a <c>BTrue</c> guard.
/// </para>
/// </summary>
let (|ITEGuards|_|) (ms: IteratedGView<Sym<Var>>)
: (BoolExpr<Var> * IteratedGView<Sym<Var>>
* BoolExpr<Var> * IteratedGView<Sym<Var>>) option =
match (Multiset.toFlatList ms) with
| [ { Func = { Cond = NoSym xc; Item = xi }; Iterator = xit }
{ Func = { Cond = NoSym yc; Item = yi }; Iterator = yit } ]
when (equivHolds id (negates xc yc)) ->
Some (xc, Multiset.singleton { Func = { Cond = BTrue; Item = xi }; Iterator = xit },
yc, Multiset.singleton { Func = { Cond = BTrue; Item = yi }; Iterator = yit })
// {| G -> P |} is trivially equivalent to {| G -> P * ¬G -> emp |}.
| [ { Func = { Cond = NoSym xc; Item = xi }; Iterator = it } ] ->
Some (xc, Multiset.singleton { Func = { Cond = BTrue; Item = xi }; Iterator = it },
mkNot xc, Multiset.empty)
| _ -> None
/// <summary>
/// Removes a node if it is an ITE-guarded view.
///
/// <para>
/// An ITE-guarded view is a view with one in-edge,
/// two guarded funcs which negate each other, and two
/// out-edges, each assuming one of the guards.
/// </para>
/// </summary>
/// <param name="ctx">
/// The transformation context.
/// </param>
/// <returns>
/// A triple containing the list of edge names removed, an
/// optimised graph, and an Option containing the node name if it was
/// not removed.
/// </returns>
let collapseITEs (ctx : TransformContext) : TransformContext =
(* This checks whether, given a pairs (x, y) of assume A and target
view V, they project onto a pair (i, j) of assume A and target
node N in the given order. *)
let iteProjectsInOrder xA xV yA yV iA iN jA jN =
let projectA =
equivHolds id (andEquiv (equiv xA iA) (equiv yA jA))
let projectP =
nodeHasView iN xV ctx.Graph && nodeHasView jN yV ctx.Graph
projectA && projectP
(* As above, but in either order:
either (x, y) maps onto (i, j), or (x, y) maps onto (j, i). *)
let iteProjects xA xV yA yV iA iN jA jN =
// Is the first one x and the second y?
iteProjectsInOrder xA xV yA yV iA iN jA jN
// Or is the first one y and the second x?
|| iteProjectsInOrder xA xV yA yV jA jN iA iN
let opt node nView outEdges inEdges nk =
match nView with
| InnerView(ITEGuards (xA, xV, yA, yV)) ->
match (Set.toList outEdges, Set.toList inEdges) with
(* Are there only two out edges, and only one in edge?
Are the out edges assumes, and are they non-symbolic? *)
| ( [ { Dest = iN; Command = Assume (NoSym iA) } as out1
{ Dest = jN; Command = Assume (NoSym jA) } as out2 ],
[ inE ] )
when iteProjects xA xV yA yV iA iN jA jN ->
seq { // Remove the existing edges first.
yield RmInEdge (inE, node)
yield RmOutEdge (node, out1)
yield RmOutEdge (node, out2)
// Then, remove the node.
yield RmNode node
// Then, add the new edges.
yield MkCombinedEdge (inE, out1)
yield MkCombinedEdge (inE, out2) }
| _ -> Seq.empty
| _ -> Seq.empty
expandNodeIn ctx opt
/// <summary>
/// Removes views where either all of the entry commands are local,
/// or all of the exit commands are local, and the view is advisory.
/// </summary>
/// <param name="locals">
/// The environment of local variables, used to determine whether
/// commands are local.
/// </param>
/// <param name="ctx">
/// The transformation context.
/// </param>
/// <returns>
/// A triple containing the list of edge names removed, an
/// optimised graph, and an Option containing the node name if it was
/// not removed.
/// </returns>
let dropLocalMidView (locals : VarMap) (ctx : TransformContext)
: TransformContext =
let opt nName nView outEdges inEdges nk =
(* TODO @mjp41: Need to check nView is not something with a real definition *)
if nk = Normal // Check it is not an Entry or Exit node.
(* Only continue if there is one edge for either the in or
out direction. *)
&& ((Set.count outEdges < 2) || (Set.count inEdges < 2))
&& ((Set.count outEdges > 0) && (Set.count inEdges > 0))
// Only continue if the node can be safely removed.
&& isAdvisory nView
(* Only continue if there are no cycles *)
&& (Set.forall (fun (e : OutEdge) -> e.Dest <> nName) outEdges)
&& (Set.forall (fun (e : InEdge) -> e.Src <> nName) inEdges)
(* Commands must be local on either the in or the out.*)
&& ((Set.forall (fun (e : OutEdge) -> isLocalCommand locals e.Command) outEdges)
|| (Set.forall (fun (e : InEdge) -> isLocalCommand locals e.Command) inEdges))
then
seq {
for inE in inEdges do
yield RmInEdge (inE, nName)
for outE in outEdges do
yield MkCombinedEdge (inE, outE)
for outE in outEdges do
yield RmOutEdge (nName, outE)
yield RmNode nName
}
else Seq.empty
expandNodeIn ctx opt
/// Drops edges with local results that are disjoint from
/// the vars in the pre/post condition views
/// i.e. given {| p |} c {| p |} drop iff Vars(p) n Vars(c) = {}
let dropLocalEdges (locals : VarMap) (ctx : TransformContext)
: TransformContext =
let opt node nView outEdges inEdges nk =
let disjoint (a : TypedVar list) (b : Set<TypedVar>) = List.forall (b.Contains >> not) a
let processEdge (e : OutEdge) =
if isLocalResults locals e.Command
then
let pViewexpr = nView
let qViewexpr = (fun (viewexpr, _, _, _) -> viewexpr) <| ctx.Graph.Contents.[e.Dest]
// strip away mandatory/advisory and just look at the internal view
// (TODO: do something with the ViewExpr annotations?)
match (pViewexpr, qViewexpr) with
| InnerView pView, InnerView qView ->
let varResultSet = Set.map iteratedGFuncVars (Multiset.toSet pView)
let varsResults = lift Set.unionMany (collect varResultSet)
let cmdVarsResults = commandResultVars e.Command
// TODO: Better equality?
match varsResults, cmdVarsResults with
| Ok (vars, _), Ok (cmdVars, _) ->
// TODO: Better equality?
if pView = qView && disjoint (Set.toList cmdVars) vars
then
seq {
yield RmOutEdge (node, e)
yield Unify (node, e.Dest, OnlyIfNoConnections)
}
else Seq.empty
| _, _ -> Seq.empty
else Seq.empty
seq { for e in outEdges do yield! (processEdge e) }
expandNodeIn ctx opt
/// <summary>
/// Decides whether a system {p}c{q}d{r} can be collapsed to {p}d{r}.
/// </summary>
let canCollapseUnobservable
(tvars : VarMap)
(graph : Graph)
(p : NodeID)
(c : Command)
(q : NodeID)
(d : Command)
(r : NodeID)
: bool =
// We can't collapse {p}c{p}d{r} or {p}c{r}d{r}.
let noCycle = p <> q && q <> r
// We can't collapse if {q} is mandatory.
let qViewexpr, _, _, _ = graph.Contents.[q]
let qAdvisory = match qViewexpr with | Advisory _ -> true | _ -> false
let cHidden =
match isObservable tvars c d with
| Pass false -> true
| _ -> false
noCycle && qAdvisory && cHidden
/// Collapses edges {p}c{q}d{r} to {p}d{r} iff c is unobservable
/// i.e. c writes to local variables overwritten by d
/// d does not read outputs of c,
/// and there are no assumes adding information
let collapseUnobservableEdges (tvars : VarMap) (ctx : TransformContext)
: TransformContext =
let opt node pViewexpr outEdges inEdges nodeKind =
let disjoint (a : Set<'a>) (b : Set<'a>) =
Set.empty = Set.filter b.Contains a
let processCEdge cEdge =
let dEdges = (fun (_, outs, _, _) -> outs) <| ctx.Graph.Contents.[cEdge.Dest]
let processDEdge dEdge =
(pViewexpr, cEdge, dEdge)
Set.map processDEdge dEdges
let processTriple (pViewexpr, (cEdge : OutEdge), (dEdge : OutEdge)) =
let c, d = cEdge.Command, dEdge.Command
if canCollapseUnobservable tvars ctx.Graph node c cEdge.Dest d dEdge.Dest
then
seq {
// Remove the {p}c{q} edge
yield RmOutEdge (node, cEdge)
// Remove the {q}d{r} edge
yield RmOutEdge (cEdge.Dest, dEdge)
// Remove q
yield RmNode cEdge.Dest
// Then, add the new edges {p}d{q}
yield MkCombinedEdge
({ Name = node; Src = dEdge.Dest; Command = d },
{ Name = dEdge.Dest; Dest = node; Command = d })
}
else Seq.empty
let triples = Set.fold (+) Set.empty <| Set.map processCEdge outEdges
seq { for triple in triples do yield! (processTriple triple) }
expandNodeIn ctx opt
/// <summary>
/// Performs a node-wise optimisation on every node in the graph.
/// </summary>
/// <param name="opt">
/// The optimisation, which must take a list of edges currently
/// removed, a graph, and a node to optimise. It must return a tuple
/// of the new list of edges removed, and optimised graph.
/// </param>
/// <param name="graph">
/// The graph to optimise.
/// </param>
/// <returns>
/// A graph equivalent to <paramref name="graph" />, but with the
/// node optimisation <paramref name="opt" /> performed as many times
/// as possible.
/// </returns>
let rec onNodes
(opt : TransformContext -> Result<TransformContext, GraphOptError>)
(graph : Graph)
: Result<Graph, GraphOptError> =
// TODO(CaptainHayashi): do a proper depth-first search instead.
let graphResult =
graph.Contents
// Pull out nodeNames for removeIdStep.
|> keys
// Then, for each of those, try merging everything else into it.
|> seqBind (fun node ctx -> opt { ctx with Node = Some node })
{ Graph = graph ; Node = None ; Transforms = [] }
(* Tail-recurse back if we did some optimisations.
This is to see if we enabled more of them. *)
bind
(fun { Graph = newGraph; Transforms = xs } ->
if (Seq.isEmpty xs)
then ok newGraph
else onNodes opt newGraph)
graphResult
/// <summary>
/// Optimises a graph.
/// </summary>
/// <param name="tvars">
/// The map of thread-local variables in action.
/// </param>
/// <param name="opts">
/// Set of optimisation toggles in action.
/// </param>
/// <param name="_arg1">
/// The graph to optimise.
/// </param>
/// <returns>
/// An optimised equivalent of <paramref name="_arg1" />.
/// </returns>
let optimiseGraph (tvars : VarMap) (opts : (string * bool) list)
: Graph -> Result<Graph, GraphOptError> =
// TODO(CaptainHayashi): make graph optiisations fail.
onNodes
(Utils.optimiseWith opts
[ ("graph-collapse-nops", true, collapseNops >> ok)
("graph-collapse-ites", true, collapseITEs >> ok)
("graph-drop-local-edges", true, dropLocalEdges tvars >> ok)
("graph-collapse-unobservable-edges", true, collapseUnobservableEdges tvars >> ok)
("graph-drop-local-midview",true, dropLocalMidView tvars >> ok)
])
/// <summary>
/// Optimises a model over graphs.
/// </summary>
/// <param name="opts">
/// Set of optimisation toggles in action.
/// </param>
/// <param name="mdl">
/// The model to optimise.
/// </param>
/// <param name="verbose">
/// Flag which, if enabled, causes non-default optimisation changes
/// to be reported to stderr.
/// </param>
/// <returns>
/// An optimised equivalent of <paramref name="mdl" />.
/// </returns>
let optimise (opts : (string * bool) list) (mdl : Model<Graph, _>)
: Result<Model<Graph, _>, GraphOptError> =
tryMapAxioms (optimiseGraph mdl.ThreadVars opts) mdl
/// <summary>
/// Term optimisation.
/// </summary>
module Term =
(*
* After elimination
*)
/// Partial pattern that matches a Boolean expression in terms of exactly one /
/// constant.
let rec (|ConstantBoolFunction|_|) (x : BoolExpr<Sym<MarkedVar>>)
: MarkedVar option =
let tx = mkTypedSub normalRec x
tx
|> findVars (tliftToBoolSrc (tliftToExprDest collectSymVars))
|> okOption |> Option.map (Seq.map valueOf) |> Option.bind onlyOne
/// Partial pattern that matches a Boolean expression in terms of exactly one /
/// constant.
let rec (|ConstantIntFunction|_|) (x : IntExpr<Sym<MarkedVar>>)
: MarkedVar option =
let tx = mkTypedSub normalRec x
tx
|> findVars (tliftToIntSrc (tliftToExprDest collectSymVars))
|> okOption |> Option.map (Seq.map valueOf) |> Option.bind onlyOne
/// Finds all instances of the pattern `x!after = f(x!before)` in an
/// Boolean expression that is either an equality or conjunction, and
/// where x is arithmetic.
let rec findArithAfters
: BoolExpr<Sym<MarkedVar>> -> (Var * IntExpr<Sym<MarkedVar>>) list =
function
| BIEq(IVar (Reg (After x)), (ConstantIntFunction (Before y) as fx))
when x = y
-> [(x, fx)]
| BIEq(ConstantIntFunction (Before y) as fx, IVar (Reg (After x)))
when x = y
-> [(x, fx)]
| BAnd xs -> concatMap findArithAfters xs
| _ -> []
/// Finds all instances of the pattern `x!after = f(x!before)` in a
/// Boolean expression that is either an equality or conjunction, and
/// where x is Boolean.
let rec findBoolAfters
: BoolExpr<Sym<MarkedVar>> -> (Var * BoolExpr<Sym<MarkedVar>>) list =
function
| BBEq(BVar (Reg (After x)), (ConstantBoolFunction (Before y) as fx))
when x = y
-> [(x, fx)]
| BBEq(ConstantBoolFunction (Before y) as fx, BVar (Reg (After x)))
when x = y
-> [(x, fx)]
| BAnd xs -> concatMap findBoolAfters xs
| _ -> []