Skip to content

Commit

Permalink
Multiple trie (dotnet#16098)
Browse files Browse the repository at this point in the history
* Use Immutable structures for Trie types. Create incremental Trie instances.
  • Loading branch information
nojaf authored Oct 13, 2023
1 parent 1c0ba9f commit d848547
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 225 deletions.
46 changes: 24 additions & 22 deletions src/Compiler/Driver/GraphChecking/DependencyResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,29 @@ let queryTriePartial (trie: TrieNode) (path: LongIdentifier) : TrieNode option =

visit trie path

let mapNodeToQueryResult (currentFileIndex: FileIndex) (node: TrieNode option) : QueryTrieNodeResult =
let mapNodeToQueryResult (node: TrieNode option) : QueryTrieNodeResult =
match node with
| Some finalNode ->
if
Set.isEmpty finalNode.Files
// If this node exposes files which the current index cannot see, we consider it not to have data at all.
|| Set.forall (fun idx -> idx >= currentFileIndex) finalNode.Files
then
if Set.isEmpty finalNode.Files then
QueryTrieNodeResult.NodeDoesNotExposeData
else
QueryTrieNodeResult.NodeExposesData(finalNode.Files)
| None -> QueryTrieNodeResult.NodeDoesNotExist

/// <summary>Find a path in the Trie.</summary>
let queryTrie (currentFileIndex: FileIndex) (trie: TrieNode) (path: LongIdentifier) : QueryTrieNodeResult =
queryTriePartial trie path |> mapNodeToQueryResult currentFileIndex
let queryTrie (trie: TrieNode) (path: LongIdentifier) : QueryTrieNodeResult =
queryTriePartial trie path |> mapNodeToQueryResult

/// <summary>Same as 'queryTrie' but allows passing in a path combined from two parts, avoiding list allocation.</summary>
let queryTrieDual (currentFileIndex: FileIndex) (trie: TrieNode) (path1: LongIdentifier) (path2: LongIdentifier) : QueryTrieNodeResult =
let queryTrieDual (trie: TrieNode) (path1: LongIdentifier) (path2: LongIdentifier) : QueryTrieNodeResult =
match queryTriePartial trie path1 with
| Some intermediateNode -> queryTriePartial intermediateNode path2
| None -> None
|> mapNodeToQueryResult currentFileIndex
|> mapNodeToQueryResult

/// Process namespace declaration.
let processNamespaceDeclaration (trie: TrieNode) (path: LongIdentifier) (state: FileContentQueryState) : FileContentQueryState =
let queryResult = queryTrie state.CurrentFile trie path
let queryResult = queryTrie trie path

match queryResult with
| QueryTrieNodeResult.NodeDoesNotExist -> state
Expand All @@ -53,7 +49,7 @@ let processNamespaceDeclaration (trie: TrieNode) (path: LongIdentifier) (state:
/// Process an "open" statement.
/// The statement could link to files and/or should be tracked as an open namespace.
let processOpenPath (trie: TrieNode) (path: LongIdentifier) (state: FileContentQueryState) : FileContentQueryState =
let queryResult = queryTrie state.CurrentFile trie path
let queryResult = queryTrie trie path

match queryResult with
| QueryTrieNodeResult.NodeDoesNotExist -> state
Expand Down Expand Up @@ -103,13 +99,12 @@ let rec processStateEntry (trie: TrieNode) (state: FileContentQueryState) (entry
||> Array.fold (fun state takeParts ->
let path = List.take takeParts path
// process the name was if it were a FQN
let stateAfterFullIdentifier =
processIdentifier (queryTrieDual state.CurrentFile trie [] path) state
let stateAfterFullIdentifier = processIdentifier (queryTrieDual trie [] path) state

// Process the name in combination with the existing open namespaces
(stateAfterFullIdentifier, state.OpenNamespaces)
||> Set.fold (fun acc openNS ->
let queryResult = queryTrieDual state.CurrentFile trie openNS path
let queryResult = queryTrieDual trie openNS path
processIdentifier queryResult acc))

| FileContentEntry.NestedModule (nestedContent = nestedContent) ->
Expand Down Expand Up @@ -142,7 +137,7 @@ let collectGhostDependencies (fileIndex: FileIndex) (trie: TrieNode) (result: Fi
// For each opened namespace, if none of already resolved dependencies define it, return the top-most file that defines it.
Set.toArray result.OpenedNamespaces
|> Array.choose (fun path ->
match queryTrie fileIndex trie path with
match queryTrie trie path with
| QueryTrieNodeResult.NodeExposesData _
| QueryTrieNodeResult.NodeDoesNotExist -> None
| QueryTrieNodeResult.NodeDoesNotExposeData ->
Expand Down Expand Up @@ -201,20 +196,25 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
else
let fileContent = fileContents[file.Idx]

let knownFiles = [| 0 .. (file.Idx - 1) |] |> set
// The Trie we want to use is the one that contains only files before our current index.
// As we skip implementation files (backed by a signature), we cannot just use the current file index to find the right Trie.
let trieForFile =
trie
|> Array.fold (fun acc (idx, t) -> if idx < file.Idx then t else acc) TrieNode.Empty

// File depends on all files above it that define accessible symbols at the root level (global namespace).
let filesFromRoot = trie.Files |> Set.filter (fun rootIdx -> rootIdx < file.Idx)
let filesFromRoot =
trieForFile.Files |> Set.filter (fun rootIdx -> rootIdx < file.Idx)
// Start by listing root-level dependencies.
let initialDepsResult =
(FileContentQueryState.Create file.Idx knownFiles filesFromRoot), fileContent
let initialDepsResult = (FileContentQueryState.Create filesFromRoot), fileContent
// Sequentially process all relevant entries of the file and keep updating the state and set of dependencies.
let depsResult =
initialDepsResult
// Seq is faster than List in this case.
||> Seq.fold (processStateEntry trie)
||> Seq.fold (processStateEntry trieForFile)

// Add missing links for cases where an unused open namespace did not create a link.
let ghostDependencies = collectGhostDependencies file.Idx trie depsResult
let ghostDependencies = collectGhostDependencies file.Idx trieForFile depsResult

// Add a link from implementation files to their signature files.
let signatureDependency =
Expand All @@ -237,4 +237,6 @@ let mkGraph (filePairs: FilePairMap) (files: FileInProject array) : Graph<FileIn
|> Array.Parallel.map (fun file -> file.Idx, findDependencies file)
|> readOnlyDict

let trie = trie |> Array.last |> snd

graph, trie
3 changes: 1 addition & 2 deletions src/Compiler/Driver/GraphChecking/DependencyResolution.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ module internal FSharp.Compiler.GraphChecking.DependencyResolution

/// <summary>
/// Query a TrieNode to find a certain path.
/// The result will take the current file index into account to determine if the result node contains data.
/// </summary>
/// <remarks>This code is only used directly in unit tests.</remarks>
val queryTrie: currentFileIndex: FileIndex -> trie: TrieNode -> path: LongIdentifier -> QueryTrieNodeResult
val queryTrie: trie: TrieNode -> path: LongIdentifier -> QueryTrieNodeResult

/// <summary>Process an open path (found in the ParsedInput) with a given FileContentQueryState.</summary>
/// <remarks>This code is only used directly in unit tests.</remarks>
Expand Down
Loading

0 comments on commit d848547

Please sign in to comment.