Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use hashes for language service cache keys #6058

Merged
merged 5 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 20 additions & 18 deletions src/fsharp/service/service.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2188,17 +2188,17 @@ module Helpers =
&& FSharpProjectOptions.UseSameProject(o1,o2)

/// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. parsing
let AreSameForParsing((fileName1: string, source1: ISourceText, options1), (fileName2, source2, options2)) =
fileName1 = fileName2 && options1 = options2 && source1.ContentEquals(source2)
let AreSameForParsing((fileName1: string, source1Hash: int, options1), (fileName2, source2Hash, options2)) =
fileName1 = fileName2 && options1 = options2 && source1Hash = source2Hash

let AreSimilarForParsing((fileName1, _, _), (fileName2, _, _)) =
fileName1 = fileName2

/// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. checking
let AreSameForChecking3((fileName1: string, source1: ISourceText, options1: FSharpProjectOptions), (fileName2, source2, options2)) =
let AreSameForChecking3((fileName1: string, source1Hash: int, options1: FSharpProjectOptions), (fileName2, source2Hash, options2)) =
(fileName1 = fileName2)
&& FSharpProjectOptions.AreSameForChecking(options1,options2)
&& source1.ContentEquals(source2)
&& source1Hash = source2Hash

/// Determine whether two (fileName,sourceText,options) keys should be identical w.r.t. resource usage
let AreSubsumable3((fileName1:string,_,o1:FSharpProjectOptions),(fileName2:string,_,o2:FSharpProjectOptions)) =
Expand Down Expand Up @@ -2314,7 +2314,7 @@ module CompileHelpers =
System.Console.SetError error
| None -> ()


type SourceTextHash = int
type FileName = string
type FilePath = string
type ProjectPath = string
Expand Down Expand Up @@ -2460,7 +2460,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC

// Also keyed on source. This can only be out of date if the antecedent is out of date
let checkFileInProjectCache =
MruCache<ParseCacheLockToken,FileName * ISourceText * FSharpProjectOptions, FSharpParseFileResults * FSharpCheckFileResults * FileVersion * DateTime>
MruCache<ParseCacheLockToken,FileName * SourceTextHash * FSharpProjectOptions, FSharpParseFileResults * FSharpCheckFileResults * FileVersion * DateTime>
(keepStrongly=checkFileInProjectCacheSize,
areSame=AreSameForChecking3,
areSimilar=AreSubsumable3)
Expand Down Expand Up @@ -2511,13 +2511,14 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC

member bc.ParseFile(filename: string, sourceText: ISourceText, options: FSharpParsingOptions, userOpName: string) =
async {
match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, sourceText, options))) with
let hash = sourceText.GetHashCode()
match parseCacheLock.AcquireLock(fun ltok -> parseFileCache.TryGet(ltok, (filename, hash, options))) with
| Some res -> return res
| None ->
foregroundParseCount <- foregroundParseCount + 1
let parseErrors, parseTreeOpt, anyErrors = Parser.parseFile(sourceText, filename, options, userOpName)
let res = FSharpParseFileResults(parseErrors, parseTreeOpt, anyErrors, options.SourceFiles)
parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, sourceText, options), res))
parseCacheLock.AcquireLock(fun ltok -> parseFileCache.Set(ltok, (filename, hash, options), res))
return res
}

Expand All @@ -2536,9 +2537,9 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
}
)

member bc.GetCachedCheckFileResult(builder: IncrementalBuilder,filename,sourceText,options) =
member bc.GetCachedCheckFileResult(builder: IncrementalBuilder,filename,sourceText: ISourceText,options) =
// Check the cache. We can only use cached results when there is no work to do to bring the background builder up-to-date
let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename,sourceText,options)))
let cachedResults = parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCache.TryGet(ltok, (filename,sourceText.GetHashCode(),options)))

match cachedResults with
// | Some (parseResults, checkResults, _, _) when builder.AreCheckResultsBeforeFileInProjectReady(filename) ->
Expand Down Expand Up @@ -2598,7 +2599,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
tcPrior.TcState, loadClosure, tcPrior.TcErrors, reactorOps, (fun () -> builder.IsAlive), textSnapshotInfo, userOpName)
let parsingOptions = FSharpParsingOptions.FromTcConfig(tcPrior.TcConfig, Array.ofList builder.SourceFiles, options.UseScriptResolutionRules)
let checkAnswer = MakeCheckFileAnswer(fileName, tcFileResult, options, builder, Array.ofList tcPrior.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors)
bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText)
bc.RecordTypeCheckFileInProjectResults(fileName, options, parsingOptions, parseResults, fileVersion, tcPrior.TimeStamp, Some checkAnswer, sourceText.GetHashCode())
return checkAnswer
finally
let dummy = ref ()
Expand All @@ -2614,7 +2615,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
}

/// Type-check the result obtained by parsing, but only if the antecedent type checking context is available.
member bc.CheckFileInProjectAllowingStaleCachedResults(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText, options, textSnapshotInfo: obj option, userOpName) =
member bc.CheckFileInProjectAllowingStaleCachedResults(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText: ISourceText, options, textSnapshotInfo: obj option, userOpName) =
let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "CheckFileInProjectAllowingStaleCachedResults ", filename, action)
async {
try
Expand Down Expand Up @@ -2657,7 +2658,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
}

/// Type-check the result obtained by parsing. Force the evaluation of the antecedent type checking context if needed.
member bc.CheckFileInProject(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText, options, textSnapshotInfo, userOpName) =
member bc.CheckFileInProject(parseResults: FSharpParseFileResults, filename, fileVersion, sourceText: ISourceText, options, textSnapshotInfo, userOpName) =
let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "CheckFileInProject", filename, action)
async {
try
Expand Down Expand Up @@ -2685,7 +2686,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
}

/// Parses and checks the source file and returns untyped AST and check results.
member bc.ParseAndCheckFileInProject (filename:string, fileVersion, sourceText, options:FSharpProjectOptions, textSnapshotInfo, userOpName) =
member bc.ParseAndCheckFileInProject (filename:string, fileVersion, sourceText: ISourceText, options:FSharpProjectOptions, textSnapshotInfo, userOpName) =
let execWithReactorAsync action = reactor.EnqueueAndAwaitOpAsync(userOpName, "ParseAndCheckFileInProject", filename, action)
async {
try
Expand Down Expand Up @@ -2772,7 +2773,7 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
match sourceText with
| Some sourceText ->
parseCacheLock.AcquireLock (fun ltok ->
match checkFileInProjectCache.TryGet(ltok,(filename,sourceText,options)) with
match checkFileInProjectCache.TryGet(ltok,(filename,sourceText.GetHashCode(),options)) with
| Some (a,b,c,_) -> Some (a,b,c)
| None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options))))
| None -> parseCacheLock.AcquireLock (fun ltok -> checkFileInProjectCachePossiblyStale.TryGet(ltok,(filename,options)))
Expand Down Expand Up @@ -3008,14 +3009,15 @@ type FSharpChecker(legacyReferenceResolver, projectCacheSize, keepAssemblyConten

member ic.ReferenceResolver = legacyReferenceResolver

member ic.MatchBraces(filename, sourceText, options: FSharpParsingOptions, ?userOpName: string) =
member ic.MatchBraces(filename, sourceText: ISourceText, options: FSharpParsingOptions, ?userOpName: string) =
let userOpName = defaultArg userOpName "Unknown"
let hash = sourceText.GetHashCode()
async {
match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText, options)) with
match braceMatchCache.TryGet(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options)) with
| Some res -> return res
| None ->
let res = Parser.matchBraces(sourceText, filename, options, userOpName)
braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, sourceText, options), res)
braceMatchCache.Set(AssumeAnyCallerThreadWithoutEvidence(), (filename, hash, options), res)
return res
}

Expand Down
5 changes: 4 additions & 1 deletion src/utils/prim-lexing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ type ISourceText =

abstract ContentEquals : sourceText: ISourceText -> bool

abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit
abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit

[<Sealed>]
type StringText(str: string) =
Expand All @@ -50,6 +50,9 @@ type StringText(str: string) =
lazy getLines str

member __.String = str

override __.GetHashCode() = str.GetHashCode()
override __.Equals(obj: obj) = str.Equals(obj)

interface ISourceText with

Expand Down
2 changes: 1 addition & 1 deletion src/utils/prim-lexing.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type ISourceText =

abstract ContentEquals : sourceText: ISourceText -> bool

abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit
abstract CopyTo : sourceIndex: int * destination: char [] * destinationIndex: int * count: int -> unit

module SourceText =

Expand Down
97 changes: 59 additions & 38 deletions vsintegration/src/FSharp.Editor/Common/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -46,61 +46,82 @@ module private SourceText =

open System.Runtime.CompilerServices

/// Ported from Roslyn.Utilities
[<RequireQualifiedAccess>]
module Hash =
/// (From Roslyn) This is how VB Anonymous Types combine hash values for fields.
let combine (newKey: int) (currentKey: int) = (currentKey * (int 0xA5555529)) + newKey

let combineValues (values: seq<'T>) =
(0, values) ||> Seq.fold (fun hash value -> combine (value.GetHashCode()) hash)

let weakTable = ConditionalWeakTable<SourceText, ISourceText>()

let create (sourceText: SourceText) =
let sourceText =
{ new ISourceText with
{
new Object() with
override __.GetHashCode() =
let checksum = sourceText.GetChecksum()
let contentsHash = if not checksum.IsDefault then Hash.combineValues checksum else 0
let encodingHash = if not (isNull sourceText.Encoding) then sourceText.Encoding.GetHashCode() else 0

sourceText.ChecksumAlgorithm.GetHashCode()
|> Hash.combine encodingHash
|> Hash.combine contentsHash
|> Hash.combine sourceText.Length

interface ISourceText with

member __.Item with get index = sourceText.[index]
member __.Item with get index = sourceText.[index]

member __.GetLineString(lineIndex) =
sourceText.Lines.[lineIndex].ToString()
member __.GetLineString(lineIndex) =
sourceText.Lines.[lineIndex].ToString()

member __.GetLineCount() =
sourceText.Lines.Count
member __.GetLineCount() =
sourceText.Lines.Count

member __.GetLastCharacterPosition() =
if sourceText.Lines.Count > 0 then
(sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length)
else
(0, 0)
member __.GetLastCharacterPosition() =
if sourceText.Lines.Count > 0 then
(sourceText.Lines.Count, sourceText.Lines.[sourceText.Lines.Count - 1].Span.Length)
else
(0, 0)

member __.GetSubTextString(start, length) =
sourceText.GetSubText(TextSpan(start, length)).ToString()
member __.GetSubTextString(start, length) =
sourceText.GetSubText(TextSpan(start, length)).ToString()

member __.SubTextEquals(target, startIndex) =
if startIndex < 0 || startIndex >= sourceText.Length then
invalidArg "startIndex" "Out of range."
member __.SubTextEquals(target, startIndex) =
if startIndex < 0 || startIndex >= sourceText.Length then
invalidArg "startIndex" "Out of range."

if String.IsNullOrEmpty(target) then
invalidArg "target" "Is null or empty."
if String.IsNullOrEmpty(target) then
invalidArg "target" "Is null or empty."

let lastIndex = startIndex + target.Length
if lastIndex <= startIndex || lastIndex >= sourceText.Length then
invalidArg "target" "Too big."
let lastIndex = startIndex + target.Length
if lastIndex <= startIndex || lastIndex >= sourceText.Length then
invalidArg "target" "Too big."

let mutable finished = false
let mutable didEqual = true
let mutable i = 0
while not finished && i < target.Length do
if target.[i] <> sourceText.[startIndex + i] then
didEqual <- false
finished <- true // bail out early
else
i <- i + 1
let mutable finished = false
let mutable didEqual = true
let mutable i = 0
while not finished && i < target.Length do
if target.[i] <> sourceText.[startIndex + i] then
didEqual <- false
finished <- true // bail out early
else
i <- i + 1

didEqual
didEqual

member __.ContentEquals(sourceText) =
match sourceText with
| :? SourceText as sourceText -> sourceText.ContentEquals(sourceText)
| _ -> false
member __.ContentEquals(sourceText) =
match sourceText with
| :? SourceText as sourceText -> sourceText.ContentEquals(sourceText)
| _ -> false

member __.Length = sourceText.Length
member __.Length = sourceText.Length

member __.CopyTo(sourceIndex, destination, destinationIndex, count) =
sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
member __.CopyTo(sourceIndex, destination, destinationIndex, count) =
sourceText.CopyTo(sourceIndex, destination, destinationIndex, count)
}

sourceText
Expand Down