Skip to content

Commit 2352770

Browse files
authored
[Experimental] [WIP] Transparent Compiler (#15179)
1 parent 1da7005 commit 2352770

File tree

94 files changed

+10694
-2128
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

94 files changed

+10694
-2128
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
<MicroBuildCoreSentinelVersion>1.0.0</MicroBuildCoreSentinelVersion>
169169
<MicroBuildPluginsSwixBuildVersion>1.1.33</MicroBuildPluginsSwixBuildVersion>
170170
<!-- other packages -->
171-
<BenchmarkDotNetVersion>0.13.2</BenchmarkDotNetVersion>
171+
<BenchmarkDotNetVersion>0.13.10</BenchmarkDotNetVersion>
172172
<FsCheckVersion>2.16.5</FsCheckVersion>
173173
<FSharpDataTypeProvidersVersion>4.3.0.0</FSharpDataTypeProvidersVersion>
174174
<MicrosoftCompositionVersion>1.0.31</MicrosoftCompositionVersion>

src/Compiler/Driver/GraphChecking/Graph.fs

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,26 +27,43 @@ module internal Graph =
2727
|> Array.map (fun (KeyValue(k, v)) -> k, v)
2828
|> readOnlyDict
2929

30-
let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
31-
/// Find transitive dependencies of a single node.
32-
let transitiveDeps (node: 'Node) =
33-
let visited = HashSet<'Node>()
30+
let nodes (graph: Graph<'Node>) : Set<'Node> =
31+
graph.Values |> Seq.collect id |> Seq.append graph.Keys |> Set
32+
33+
/// Find transitive dependencies of a single node.
34+
let transitiveDeps (node: 'Node) (graph: Graph<'Node>) =
35+
let visited = HashSet<'Node>()
3436

35-
let rec dfs (node: 'Node) =
36-
graph[node]
37-
// Add direct dependencies.
38-
// Use HashSet.Add return value semantics to filter out those that were added previously.
39-
|> Array.filter visited.Add
40-
|> Array.iter dfs
37+
let rec dfs (node: 'Node) =
38+
graph[node]
39+
// Add direct dependencies.
40+
// Use HashSet.Add return value semantics to filter out those that were added previously.
41+
|> Array.filter visited.Add
42+
|> Array.iter dfs
4143

42-
dfs node
43-
visited |> Seq.toArray
44+
dfs node
45+
visited |> Seq.toArray
4446

47+
let transitive<'Node when 'Node: equality> (graph: Graph<'Node>) : Graph<'Node> =
4548
graph.Keys
4649
|> Seq.toArray
47-
|> Array.Parallel.map (fun node -> node, transitiveDeps node)
50+
|> Array.Parallel.map (fun node -> node, graph |> transitiveDeps node)
4851
|> readOnlyDict
4952

53+
// TODO: optimize
54+
/// Get subgraph of the given graph that contains only nodes that are reachable from the given node.
55+
let subGraphFor node graph =
56+
let allDeps = graph |> transitiveDeps node
57+
let relevant n = n = node || allDeps |> Array.contains n
58+
59+
graph
60+
|> Seq.choose (fun (KeyValue(src, deps)) ->
61+
if relevant src then
62+
Some(src, deps |> Array.filter relevant)
63+
else
64+
None)
65+
|> make
66+
5067
/// Create a reverse of the graph
5168
let reverse (originalGraph: Graph<'Node>) : Graph<'Node> =
5269
originalGraph
@@ -69,7 +86,7 @@ module internal Graph =
6986
let print (graph: Graph<'Node>) : unit =
7087
printCustom graph (fun node -> node.ToString())
7188

72-
let serialiseToMermaid path (graph: Graph<FileIndex * string>) =
89+
let serialiseToMermaid (graph: Graph<FileIndex * string>) =
7390
let sb = StringBuilder()
7491
let appendLine (line: string) = sb.AppendLine(line) |> ignore
7592

@@ -84,8 +101,10 @@ module internal Graph =
84101
appendLine $" %i{idx} --> %i{depIdx}"
85102

86103
appendLine "```"
104+
sb.ToString()
87105

106+
let writeMermaidToFile path (graph: Graph<FileIndex * string>) =
88107
use out =
89108
FileSystem.OpenFileForWriteShim(path, fileMode = System.IO.FileMode.Create)
90109

91-
out.WriteAllText(sb.ToString())
110+
graph |> serialiseToMermaid |> out.WriteAllText

src/Compiler/Driver/GraphChecking/Graph.fsi

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ module internal Graph =
1010
/// Build the graph.
1111
val make: nodeDeps: seq<'Node * 'Node array> -> Graph<'Node> when 'Node: equality
1212
val map<'T, 'U when 'U: equality> : f: ('T -> 'U) -> graph: Graph<'T> -> Graph<'U>
13+
/// Get all nodes of the graph.
14+
val nodes: graph: Graph<'Node> -> Set<'Node>
1315
/// Create a transitive closure of the graph in O(n^2) time (but parallelize it).
1416
/// The resulting graph contains edge A -> C iff the input graph contains a (directed) non-zero length path from A to C.
1517
val transitive<'Node when 'Node: equality> : graph: Graph<'Node> -> Graph<'Node>
18+
/// Get a sub-graph of the graph containing only the nodes reachable from the given node.
19+
val subGraphFor: node: 'Node -> graph: Graph<'Node> -> Graph<'Node> when 'Node: equality
1620
/// Create a reverse of the graph.
1721
val reverse<'Node when 'Node: equality> : originalGraph: Graph<'Node> -> Graph<'Node>
1822
/// Print the contents of the graph to the standard output.
1923
val print: graph: Graph<'Node> -> unit
24+
/// Create a simple Mermaid graph
25+
val serialiseToMermaid: graph: Graph<FileIndex * string> -> string
2026
/// Create a simple Mermaid graph and save it under the path specified.
21-
val serialiseToMermaid: path: string -> graph: Graph<FileIndex * string> -> unit
27+
val writeMermaidToFile: path: string -> graph: Graph<FileIndex * string> -> unit

src/Compiler/Driver/GraphChecking/GraphProcessing.fs

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
module internal FSharp.Compiler.GraphChecking.GraphProcessing
22

33
open System.Threading
4+
open FSharp.Compiler.GraphChecking
5+
open System.Threading.Tasks
6+
open System
47

58
/// Information about the node in a graph, describing its relation with other nodes.
69
type NodeInfo<'Item> =
@@ -32,6 +35,9 @@ type ProcessedNode<'Item, 'Result> =
3235
Result: 'Result
3336
}
3437

38+
type GraphProcessingException(msg, ex: System.Exception) =
39+
inherit exn(msg, ex)
40+
3541
let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
3642
(graph: Graph<'Item>)
3743
(work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result)
@@ -150,7 +156,7 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
150156
// If we stopped early due to an exception, reraise it.
151157
match getExn () with
152158
| None -> ()
153-
| Some(item, ex) -> raise (System.Exception($"Encountered exception when processing item '{item}'", ex))
159+
| Some(item, ex) -> raise (GraphProcessingException($"Encountered exception when processing item '{item}'", ex))
154160

155161
// All calculations succeeded - extract the results and sort in input order.
156162
nodes.Values
@@ -162,3 +168,131 @@ let processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison>
162168
node.Info.Item, result)
163169
|> Seq.sortBy fst
164170
|> Seq.toArray
171+
172+
let processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison>
173+
(graph: Graph<'Item>)
174+
(work: ('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>)
175+
: Async<('Item * 'Result)[]> =
176+
async {
177+
let transitiveDeps = graph |> Graph.transitive
178+
let dependants = graph |> Graph.reverse
179+
// Cancellation source used to signal either an exception in one of the items or end of processing.
180+
let! parentCt = Async.CancellationToken
181+
use localCts = new CancellationTokenSource()
182+
183+
let completionSignal = TaskCompletionSource()
184+
185+
use _ = parentCt.Register(fun () -> completionSignal.TrySetCanceled() |> ignore)
186+
187+
use cts = CancellationTokenSource.CreateLinkedTokenSource(parentCt, localCts.Token)
188+
189+
let makeNode (item: 'Item) : GraphNode<'Item, 'Result> =
190+
let info =
191+
let exists = graph.ContainsKey item
192+
193+
if
194+
not exists
195+
|| not (transitiveDeps.ContainsKey item)
196+
|| not (dependants.ContainsKey item)
197+
then
198+
printfn $"Unexpected inconsistent state of the graph for item '{item}'"
199+
200+
{
201+
Item = item
202+
Deps = graph[item]
203+
TransitiveDeps = transitiveDeps[item]
204+
Dependants = dependants[item]
205+
}
206+
207+
{
208+
Info = info
209+
Result = None
210+
ProcessedDepsCount = IncrementableInt(0)
211+
}
212+
213+
let nodes = graph.Keys |> Seq.map (fun item -> item, makeNode item) |> readOnlyDict
214+
215+
let lookupMany items =
216+
items |> Array.map (fun item -> nodes[item])
217+
218+
let leaves =
219+
nodes.Values |> Seq.filter (fun n -> n.Info.Deps.Length = 0) |> Seq.toArray
220+
221+
let getItemPublicNode item =
222+
let node = nodes[item]
223+
224+
{
225+
ProcessedNode.Info = node.Info
226+
ProcessedNode.Result =
227+
node.Result
228+
|> Option.defaultWith (fun () -> failwith $"Results for item '{node.Info.Item}' are not yet available")
229+
}
230+
231+
let processedCount = IncrementableInt(0)
232+
233+
let raiseExn (item, ex: exn) =
234+
localCts.Cancel()
235+
236+
match ex with
237+
| :? OperationCanceledException -> completionSignal.TrySetCanceled()
238+
| _ ->
239+
completionSignal.TrySetException(
240+
GraphProcessingException($"[*] Encountered exception when processing item '{item}': {ex.Message}", ex)
241+
)
242+
|> ignore
243+
244+
let incrementProcessedNodesCount () =
245+
if processedCount.Increment() = nodes.Count then
246+
completionSignal.TrySetResult() |> ignore
247+
248+
let rec queueNode node =
249+
Async.Start(
250+
async {
251+
let! res = processNode node |> Async.Catch
252+
253+
match res with
254+
| Choice1Of2() -> ()
255+
| Choice2Of2 ex -> raiseExn (node.Info.Item, ex)
256+
},
257+
cts.Token
258+
)
259+
260+
and processNode (node: GraphNode<'Item, 'Result>) : Async<unit> =
261+
async {
262+
263+
let info = node.Info
264+
265+
let! singleRes = work getItemPublicNode info
266+
node.Result <- Some singleRes
267+
268+
let unblockedDependants =
269+
node.Info.Dependants
270+
|> lookupMany
271+
// For every dependant, increment its number of processed dependencies,
272+
// and filter dependants which now have all dependencies processed (but didn't before).
273+
|> Array.filter (fun dependant ->
274+
let pdc = dependant.ProcessedDepsCount.Increment()
275+
// Note: We cannot read 'dependant.ProcessedDepsCount' again to avoid returning the same item multiple times.
276+
pdc = dependant.Info.Deps.Length)
277+
278+
unblockedDependants |> Array.iter queueNode
279+
incrementProcessedNodesCount ()
280+
}
281+
282+
leaves |> Array.iter queueNode
283+
284+
// Wait for end of processing, an exception, or an external cancellation request.
285+
do! completionSignal.Task |> Async.AwaitTask
286+
287+
// All calculations succeeded - extract the results and sort in input order.
288+
return
289+
nodes.Values
290+
|> Seq.map (fun node ->
291+
let result =
292+
node.Result
293+
|> Option.defaultWith (fun () -> failwith $"Unexpected lack of result for item '{node.Info.Item}'")
294+
295+
node.Info.Item, result)
296+
|> Seq.sortBy fst
297+
|> Seq.toArray
298+
}

src/Compiler/Driver/GraphChecking/GraphProcessing.fsi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ type ProcessedNode<'Item, 'Result> =
1515
{ Info: NodeInfo<'Item>
1616
Result: 'Result }
1717

18+
type GraphProcessingException =
19+
inherit exn
20+
new: msg: string * ex: System.Exception -> GraphProcessingException
21+
1822
/// <summary>
1923
/// A generic method to generate results for a graph of work items in parallel.
2024
/// Processes leaves first, and after each node has been processed, schedules any now unblocked dependants.
@@ -33,3 +37,8 @@ val processGraph<'Item, 'Result when 'Item: equality and 'Item: comparison> :
3337
work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> 'Result) ->
3438
parentCt: CancellationToken ->
3539
('Item * 'Result)[]
40+
41+
val processGraphAsync<'Item, 'Result when 'Item: equality and 'Item: comparison> :
42+
graph: Graph<'Item> ->
43+
work: (('Item -> ProcessedNode<'Item, 'Result>) -> NodeInfo<'Item> -> Async<'Result>) ->
44+
Async<('Item * 'Result)[]>

0 commit comments

Comments
 (0)