Skip to content

Commit 9395a73

Browse files
committed
changes
1 parent 66c747e commit 9395a73

8 files changed

Lines changed: 380 additions & 126 deletions

File tree

tests/FSharp.Compiler.Service.Tests2/ASTVisit.fs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Abbreviation =
2525
type ReferenceOrAbbreviation =
2626
| Reference of Reference
2727
| Abbreviation of Abbreviation
28-
28+
2929
type private References = ReferenceOrAbbreviation seq
3030

3131
let rec visitSynModuleDecl (decl : SynModuleDecl) : References =
@@ -1082,7 +1082,8 @@ and visitSynModuleOrNamespaceSig (x : SynModuleOrNamespaceSig) : References =
10821082
yield! visitSynAttributeLists synAttributeLists
10831083
}
10841084

1085-
and extractModuleRefs (input : ParsedInput) =
1085+
and findModuleAndTypeRefs (input : ParsedInput) =
1086+
// TODO It is questionable whether we correctly distinguish between and handle module and type references - needs verification
10861087
match input with
10871088
| ParsedInput.SigFile(ParsedSigFileInput(fileName, qualifiedNameOfFile, scopedPragmas, parsedHashDirectives, synModuleOrNamespaceSigs, parsedSigFileInputTrivia)) ->
10881089
synModuleOrNamespaceSigs
@@ -1216,6 +1217,39 @@ and moduleSigDecl (x : SynModuleSigDecl) : Eit =
12161217
|> moduleSigDecls
12171218
|> combine longId
12181219
Eit.Nested idents
1220+
1221+
/// Extract partial module references from partial module or type references
1222+
let extractModuleSegments (stuff : ReferenceOrAbbreviation seq) : LongIdent[] * bool =
1223+
1224+
let refs =
1225+
stuff
1226+
|> Seq.choose (function | ReferenceOrAbbreviation.Reference r -> Some r | ReferenceOrAbbreviation.Abbreviation _ -> None)
1227+
|> Seq.toArray
1228+
let abbreviations =
1229+
stuff
1230+
|> Seq.choose (function | ReferenceOrAbbreviation.Reference _ -> None | ReferenceOrAbbreviation.Abbreviation a -> Some a)
1231+
|> Seq.toArray
1232+
1233+
let moduleRefs =
1234+
refs
1235+
|> Seq.choose (fun x ->
1236+
match x.Kind with
1237+
| ModuleOrNamespace -> x.Ident |> Some
1238+
| Type ->
1239+
// Remove the last segment as it contains the type name
1240+
match x.Ident.Length with
1241+
| 0
1242+
| 1 -> None
1243+
| n -> x.Ident.GetSlice(Some 0, n - 2 |> Some) |> Some
1244+
)
1245+
|> Seq.toArray
1246+
let containsModuleAbbreviations = abbreviations.Length > 0
1247+
1248+
moduleRefs, containsModuleAbbreviations
1249+
1250+
let findModuleRefs (ast : ParsedInput) =
1251+
let typeAndModuleRefs = findModuleAndTypeRefs ast
1252+
extractModuleSegments typeAndModuleRefs
12191253

12201254
// TODO Handle 'global' namespace correctly
12211255
/// Extract the top-level module/namespaces from the AST

tests/FSharp.Compiler.Service.Tests2/DepResolving.fs

Lines changed: 1 addition & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -75,36 +75,6 @@ let calcTransitiveGraph (graph : IReadOnlyDictionary<int, int[]>) : IDictionary<
7575
|> Seq.map (fun idx -> idx, calcTransitiveDepsInner idx)
7676
|> dict
7777

78-
79-
/// Extract partial module references from partial module or type references
80-
let extractModuleSegments (stuff : ReferenceOrAbbreviation seq) : LongIdent[] * bool =
81-
82-
let refs =
83-
stuff
84-
|> Seq.choose (function | ReferenceOrAbbreviation.Reference r -> Some r | ReferenceOrAbbreviation.Abbreviation _ -> None)
85-
|> Seq.toArray
86-
let abbreviations =
87-
stuff
88-
|> Seq.choose (function | ReferenceOrAbbreviation.Reference _ -> None | ReferenceOrAbbreviation.Abbreviation a -> Some a)
89-
|> Seq.toArray
90-
91-
let moduleRefs =
92-
refs
93-
|> Seq.choose (fun x ->
94-
match x.Kind with
95-
| ModuleOrNamespace -> x.Ident |> Some
96-
| Type ->
97-
// Remove the last segment as it contains the type name
98-
match x.Ident.Length with
99-
| 0
100-
| 1 -> None
101-
| n -> x.Ident.GetSlice(Some 0, n - 2 |> Some) |> Some
102-
)
103-
|> Seq.toArray
104-
let containsModuleAbbreviations = abbreviations.Length > 0
105-
106-
moduleRefs, containsModuleAbbreviations
107-
10878
/// Algorithm for automatically detecting (lack of) file dependencies based on their AST contents
10979
[<RequireQualifiedAccess>]
11080
module internal AutomatedDependencyResolving =
@@ -212,8 +182,7 @@ module internal AutomatedDependencyResolving =
212182
let nodes =
213183
nodes
214184
|> Array.Parallel.mapi (fun i {Name = name; Code = code; AST = ast} ->
215-
let typeAndModuleRefs = extractModuleRefs ast
216-
let moduleRefs, containsModuleAbbreviations = extractModuleSegments typeAndModuleRefs
185+
let moduleRefs, containsModuleAbbreviations = findModuleRefs ast
217186
let top = topModuleOrNamespaces ast
218187
{
219188
Idx = i

tests/FSharp.Compiler.Service.Tests2/FSharp.Compiler.Service.Tests2.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@
3838
</None>
3939
<Content Include="Docs.md" />
4040
<Compile Include="Program.fs" />
41+
<Compile Include="Parallel.fs" />
4142
<Compile Include="code.fs" />
43+
<Compile Include="GraphProcessing.fs" />
4244
</ItemGroup>
4345

4446
<ItemGroup>
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
module FSharp.Compiler.Service.Tests.GraphProcessing
2+
3+
open System
4+
open System.Collections.Concurrent
5+
open System.Collections.Generic
6+
open System.Threading
7+
8+
/// Used for processing
9+
type NodeInfo<'Item> =
10+
{
11+
Item : 'Item
12+
Deps : 'Item[]
13+
TransitiveDeps : 'Item[]
14+
Dependants : 'Item[]
15+
ProcessedDepsCount : int
16+
}
17+
type Node<'Item, 'State, 'Result> =
18+
{
19+
Info : NodeInfo<'Item>
20+
Result : ('State * 'Result) option
21+
}
22+
23+
// TODO Do we need to suppress some error logging if we
24+
// TODO apply the same partial results multiple times?
25+
// TODO Maybe we can enable logging only for the final fold
26+
/// <summary>
27+
/// Combine results of dependencies needed to type-check a 'higher' node in the graph
28+
/// </summary>
29+
/// <param name="deps">Direct dependencies of a node</param>
30+
/// <param name="transitiveDeps">Transitive dependencies of a node</param>
31+
/// <param name="folder">A way to fold a single result into existing state</param>
32+
let combineResults
33+
(deps : Node<'Item, 'State, 'Result>[])
34+
(transitiveDeps : Node<'Item, 'State, 'Result>[])
35+
(folder : 'State -> 'Result -> 'State)
36+
: 'State
37+
=
38+
let biggestDep =
39+
let sizeMetric node =
40+
// Could also use eg. total file size/AST size
41+
node.Info.TransitiveDeps.Length
42+
deps
43+
|> Array.maxBy sizeMetric
44+
let orFail value =
45+
value
46+
|> Option.defaultWith (fun () -> failwith "Unexpected lack of result")
47+
let firstState =
48+
biggestDep.Result
49+
|> orFail
50+
|> fst
51+
52+
// TODO Potential perf optimisation: Keep transDeps in a HashSet from the start,
53+
// avoiding reconstructing the HashSet here
54+
55+
// Add single-file results of remaining transitive deps one-by-one using folder
56+
// Note: Good to preserve order here so that folding happens in file order
57+
let included = HashSet(biggestDep.Info.TransitiveDeps)
58+
let resultsToAdd =
59+
transitiveDeps
60+
|> Array.filter (fun dep -> included.Contains dep.Info.Item = false)
61+
|> Array.map (fun dep ->
62+
dep.Result
63+
|> orFail
64+
|> snd
65+
)
66+
let state = Array.fold folder firstState resultsToAdd
67+
state
68+
69+
70+
// TODO Test this version
71+
/// Untested version that uses MailboxProcessor.
72+
/// See http://www.fssnip.net/nX/title/Limit-degree-of-parallelism-using-an-agent for implementation
73+
let processInParallelUsingMailbox
74+
(firstItems : 'Item[])
75+
(work : 'Item -> Async<'Item[]>)
76+
(parallelism : int)
77+
(notify : int -> unit)
78+
(ct : CancellationToken)
79+
: unit
80+
=
81+
let processedCountLock = Object()
82+
let mutable processedCount = 0
83+
let agent = Parallel.threadingLimitAgent 10 ct
84+
let rec processItem item =
85+
async {
86+
let! toSchedule = work item
87+
let pc = lock processedCountLock (fun () -> processedCount <- processedCount + 1; processedCount)
88+
notify pc
89+
toSchedule |> Array.iter (fun x -> agent.Post(Parallel.Start(processItem x)))
90+
}
91+
firstItems |> Array.iter (fun x -> agent.Post(Parallel.Start(processItem x)))
92+
()
93+
94+
// TODO Could replace with MailboxProcessor+Tasks/Asyncs instead of BlockingCollection + Threads
95+
// See http://www.fssnip.net/nX/title/Limit-degree-of-parallelism-using-an-agent
96+
let processInParallel
97+
(firstItems : 'Item[])
98+
(work : 'Item -> 'Item[])
99+
(parallelism : int)
100+
(stop : int -> bool)
101+
(ct : CancellationToken)
102+
: unit
103+
=
104+
let bc = new BlockingCollection<'Item>()
105+
firstItems |> Array.iter bc.Add
106+
let processedCountLock = Object()
107+
let mutable processedCount = 0
108+
let processItem item =
109+
let toSchedule = work item
110+
let processedCount = lock processedCountLock (fun () -> processedCount <- processedCount + 1; processedCount)
111+
toSchedule |> Array.iter bc.Add
112+
processedCount
113+
114+
// TODO Could avoid workers with some semaphores
115+
let workerWork () : unit =
116+
for node in bc.GetConsumingEnumerable(ct) do
117+
if not ct.IsCancellationRequested then // improve
118+
let processedCount = processItem node
119+
if stop processedCount then
120+
bc.CompleteAdding()
121+
122+
Array.Parallel.map workerWork |> ignore // use cancellation
123+
()
124+
125+
let processGraph
126+
(graph : FileGraph)
127+
(doWork : 'Item -> 'State -> 'Result * 'State)
128+
(folder : 'State -> 'Result -> 'State)
129+
(parallelism : int)
130+
: 'State
131+
=
132+
let transitiveDeps = graph |> calcTransitiveGraph
133+
let dependants = graph |> reverseGraph
134+
let nodes = graph.Keys |> Seq.map ...
135+
let leaves = nodes |> Seq.filter ...
136+
let work
137+
(node : Node<'Item, 'State, 'Result>)
138+
: Node<'Item, 'State, 'Result>[]
139+
=
140+
let inputState = combineResults node.Deps node.TransitiveDeps folder
141+
let res = doWork node.Info.Item
142+
node.Result <- res
143+
let unblocked =
144+
node.Info.Dependants
145+
|> Array.filter (fun x ->
146+
let pdc =
147+
lock x (fun () ->
148+
x.Info.ProcessedDepsCount++
149+
x.Info.PrcessedDepsCount
150+
)
151+
pdc = node.Info.Deps.Length
152+
)
153+
|> Array.map (fun x -> nodes[x])
154+
unblocked
155+
156+
processInParallel
157+
leaves
158+
work
159+
parallelism
160+
(fun processedCount -> processedCount = nodes.Length)
161+
162+
let state = combineResults nodes nodes addCheckResultsToTcState
163+
state
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
module FSharp.Compiler.Service.Tests.Parallel
2+
3+
open System.Collections.Generic
4+
open System.Threading
5+
6+
/// The agent handles two kind of messages - the 'Start' message is sent
7+
/// when the caller wants to start a new work item. The 'Finished' message
8+
/// is sent (by the agent itself) when one work item is completed.
9+
type LimitAgentMessage =
10+
| Start of Async<unit>
11+
| Finished
12+
13+
/// A function that takes the limit - the maximal number of operations it
14+
/// will run in parallel - and returns an agent that accepts new
15+
/// tasks via the 'Start' message
16+
let threadingLimitAgent limit (ct : CancellationToken) =
17+
let act (inbox : MailboxProcessor<LimitAgentMessage>) =
18+
async {
19+
// Keep number of items running & queue of items to run later
20+
// NOTE: We keep an explicit queue, so that we can e.g. start dropping
21+
// items if there are too many requests (or do something else)
22+
// NOTE: The loop is only accessed from one thread at each time
23+
// so we can just use non-thread-safe queue & mutation
24+
let queue = Queue<_>()
25+
let mutable count = 0
26+
27+
while true do
28+
let! msg = inbox.Receive()
29+
// When we receive Start, add the work to the queue
30+
// When we receive Finished, do count--
31+
match msg with
32+
| Start work -> queue.Enqueue(work)
33+
| Finished -> count <- count + 1
34+
// After something happened, we check if we can
35+
// start a next task from the queue
36+
if count < limit && queue.Count > 0 then
37+
count <- count + 1
38+
let work = queue.Dequeue()
39+
// Start it in a thread pool (on background)
40+
Async.Start(
41+
async {
42+
do! work
43+
inbox.Post(Finished)
44+
}
45+
)
46+
}
47+
MailboxProcessor.Start(act, ct)
48+
49+
let test () =
50+
// Create an agent that can run at most 2 tasks in parallel
51+
// and send 10 work items that take 1 second to the queue
52+
use cts = new CancellationTokenSource()
53+
let agent = threadingLimitAgent 2 cts.Token
54+
55+
for i in 0..10 do
56+
agent.Post(
57+
Start(
58+
async {
59+
do! Async.Sleep(1000)
60+
printfn $"Finished: %d{i}"
61+
}
62+
)
63+
)

tests/FSharp.Compiler.Service.Tests2/TestASTVisit.fs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ open A1
7878
let f = a
7979
"""
8080

81-
let stuff = extractModuleRefs parseResults
81+
let stuff = findModuleAndTypeRefs parseResults
8282
let top = topModuleOrNamespaces parseResults
8383
printfn $"%+A{top}"
8484
printfn $"%+A{stuff}"
@@ -100,7 +100,7 @@ let x = 3
100100
"""
101101

102102
let parsedA = parseSourceCode("A.fs", A)
103-
let visitedA = extractModuleRefs parsedA
103+
let visitedA = findModuleAndTypeRefs parsedA
104104
let parsedB = parseSourceCode("B.fs", B)
105105
let topB = topModuleOrNamespaces parsedB
106106
printfn $"Top B: %+A{topB}"
@@ -111,5 +111,5 @@ let x = 3
111111
let ``Test big.fs`` () =
112112
let code = System.IO.File.ReadAllText("Big.fs")
113113
let parsedA = getParseResults code
114-
let visitedA = extractModuleRefs parsedA
114+
let visitedA = findModuleAndTypeRefs parsedA
115115
printfn $"A refs: %+A{visitedA}"

tests/FSharp.Compiler.Service.Tests2/TestDepResolving.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ let TestProject (projectFile : string) =
140140
)
141141
|> Array.filter (fun x ->
142142
// true
143-
ASTVisit.extractModuleRefs x.AST
143+
ASTVisit.findModuleAndTypeRefs x.AST
144144
|> Array.forall (function | ReferenceOrAbbreviation.Reference _ -> true | ReferenceOrAbbreviation.Abbreviation _ -> false)
145145
)
146146
let N = files.Length

0 commit comments

Comments
 (0)