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

Proof of concept: reusing typecheck results #18354

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 2 additions & 0 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
@@ -2781,6 +2781,8 @@ type ILTypeDef

member _.MetadataIndex = metadataIndex

member _.Flags = additionalFlags
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is expected, basic APIs will likely have to increase their surface to make them accessible to the new pickling code.


member x.With
(
?name,
6 changes: 6 additions & 0 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
@@ -949,6 +949,8 @@ type internal ILSecurityDecl = ILSecurityDecl of ILSecurityAction * byte[]
/// below to construct/destruct these.
[<NoComparison; NoEquality; Struct>]
type internal ILSecurityDecls =
new: array: ILSecurityDecl[] -> ILSecurityDecls

member AsList: unit -> ILSecurityDecl list

/// Represents the efficiency-oriented storage of ILSecurityDecls in another item.
@@ -1207,6 +1209,8 @@ type ILMethodDef =
/// name and arity.
[<NoEquality; NoComparison; Class; Sealed>]
type ILMethodDefs =
new: f: (unit -> ILMethodDef array) -> ILMethodDefs

inherit DelayInitArrayMap<ILMethodDef, string, ILMethodDef list>

interface IEnumerable<ILMethodDef>
@@ -1311,6 +1315,7 @@ type ILFieldDef =
/// a form to allow efficient looking up fields by name.
[<NoEquality; NoComparison; Sealed>]
type ILFieldDefs =

member internal AsList: unit -> ILFieldDef list

member internal LookupByName: string -> ILFieldDef list
@@ -1613,6 +1618,7 @@ type ILTypeDef =
member Encoding: ILDefaultPInvokeEncoding
member IsKnownToBeAttribute: bool
member CanContainExtensionMethods: bool
member Flags: ILTypeDefAdditionalFlags

member internal WithAccess: ILTypeDefAccess -> ILTypeDef
member internal WithNestedAccess: ILMemberAccess -> ILTypeDef
13 changes: 13 additions & 0 deletions src/Compiler/Driver/CompilerConfig.fs
Original file line number Diff line number Diff line change
@@ -445,6 +445,11 @@ type TypeCheckingMode =
| Sequential
| Graph

[<RequireQualifiedAccess>]
type ReuseTcResults =
| On
| Off

[<RequireQualifiedAccess>]
type TypeCheckingConfig =
{
@@ -652,6 +657,8 @@ type TcConfigBuilder =

mutable parallelReferenceResolution: ParallelReferenceResolution

mutable reuseTcResults: ReuseTcResults

mutable captureIdentifiersWhenParsing: bool

mutable typeCheckingConfig: TypeCheckingConfig
@@ -661,6 +668,8 @@ type TcConfigBuilder =
mutable realsig: bool

mutable compilationMode: TcGlobals.CompilationMode

mutable cmdLineArgs: string array
}

// Directories to start probing in
@@ -859,6 +868,7 @@ type TcConfigBuilder =
xmlDocInfoLoader = None
exiter = QuitProcessExiter
parallelReferenceResolution = ParallelReferenceResolution.Off
reuseTcResults = ReuseTcResults.Off
captureIdentifiersWhenParsing = false
typeCheckingConfig =
{
@@ -873,6 +883,7 @@ type TcConfigBuilder =
realsig = false
strictIndentation = None
compilationMode = TcGlobals.CompilationMode.Unset
cmdLineArgs = [||]
}

member tcConfigB.FxResolver =
@@ -1413,11 +1424,13 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) =
member _.xmlDocInfoLoader = data.xmlDocInfoLoader
member _.exiter = data.exiter
member _.parallelReferenceResolution = data.parallelReferenceResolution
member _.reuseTcResults = data.reuseTcResults
member _.captureIdentifiersWhenParsing = data.captureIdentifiersWhenParsing
member _.typeCheckingConfig = data.typeCheckingConfig
member _.dumpSignatureData = data.dumpSignatureData
member _.realsig = data.realsig
member _.compilationMode = data.compilationMode
member _.cmdLineArgs = data.cmdLineArgs

static member Create(builder, validate) =
use _ = UseBuildPhase BuildPhase.Parameter
13 changes: 13 additions & 0 deletions src/Compiler/Driver/CompilerConfig.fsi
Original file line number Diff line number Diff line change
@@ -208,6 +208,11 @@ type ParallelReferenceResolution =
| On
| Off

[<RequireQualifiedAccess>]
type ReuseTcResults =
| On
| Off

/// Determines the algorithm used for type-checking.
[<RequireQualifiedAccess>]
type TypeCheckingMode =
@@ -519,6 +524,8 @@ type TcConfigBuilder =

mutable parallelReferenceResolution: ParallelReferenceResolution

mutable reuseTcResults: ReuseTcResults

mutable captureIdentifiersWhenParsing: bool

mutable typeCheckingConfig: TypeCheckingConfig
@@ -528,6 +535,8 @@ type TcConfigBuilder =
mutable realsig: bool

mutable compilationMode: TcGlobals.CompilationMode

mutable cmdLineArgs: string array
}

static member CreateNew:
@@ -899,6 +908,8 @@ type TcConfig =

member parallelReferenceResolution: ParallelReferenceResolution

member reuseTcResults: ReuseTcResults

member captureIdentifiersWhenParsing: bool

member typeCheckingConfig: TypeCheckingConfig
@@ -909,6 +920,8 @@ type TcConfig =

member compilationMode: TcGlobals.CompilationMode

member cmdLineArgs: string array

/// Represents a computation to return a TcConfig. Normally this is just a constant immutable TcConfig,
/// but for F# Interactive it may be based on an underlying mutable TcConfigBuilder.
[<Sealed>]
14 changes: 14 additions & 0 deletions src/Compiler/Driver/CompilerImports.fsi
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ open FSharp.Compiler.TcGlobals
open FSharp.Compiler.BuildGraph
open FSharp.Compiler.IO
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTreePickle
open FSharp.Core.CompilerServices

#if !NO_TYPEPROVIDERS
@@ -52,6 +53,19 @@ val IsReflectedDefinitionsResource: ILResource -> bool
val GetResourceNameAndSignatureDataFuncs:
ILResource list -> (string * ((unit -> ReadOnlyByteMemory) * (unit -> ReadOnlyByteMemory) option)) list

/// Pickling primitive
val PickleToResource:
inMem: bool ->
file: string ->
g: TcGlobals ->
compress: bool ->
scope: CcuThunk ->
rName: string ->
rNameB: string ->
p: ('a -> WriterState -> unit) ->
x: 'a ->
ILResource * ILResource option

/// Encode the F# interface data into a set of IL attributes and resources
val EncodeSignatureData:
tcConfig: TcConfig *
8 changes: 8 additions & 0 deletions src/Compiler/Driver/CompilerOptions.fs
Original file line number Diff line number Diff line change
@@ -1388,6 +1388,14 @@ let advancedFlagsFsc tcConfigB =
None,
Some(FSComp.SR.optsEmitDebugInfoInQuotations (formatOptionSwitch tcConfigB.emitDebugInfoInQuotations))
)

CompilerOption(
"reusetypecheckingresults",
tagNone,
OptionUnit(fun () -> tcConfigB.reuseTcResults <- ReuseTcResults.On),
None,
Some(FSComp.SR.optsReuseTcResults ())
)
]

// OptionBlock: Internal options (test use only)
88 changes: 52 additions & 36 deletions src/Compiler/Driver/ParseAndCheckInputs.fs
Original file line number Diff line number Diff line change
@@ -1478,8 +1478,19 @@ let CheckClosedInputSetFinish (declaredImpls: CheckedImplFile list, tcState) =
tcState, declaredImpls, ccuContents

let CheckMultipleInputsSequential (ctok, checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcState, inputs) =
(tcState, inputs)
||> List.mapFold (CheckOneInputEntry(ctok, checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt))
let checkOneInputEntry =
CheckOneInputEntry(ctok, checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt)

let mutable state = tcState

let results =
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is apparently a very dumb mechanism of collecting states. Instead, the state "deltas" should be collected and merged here.

inputs
|> List.map (fun input ->
let result, newState = checkOneInputEntry state input
state <- newState // Update state for the next iteration
result, newState)

results |> List.map fst, state, results |> List.map snd

open FSharp.Compiler.GraphChecking

@@ -1833,7 +1844,7 @@ let CheckMultipleInputsUsingGraphMode
TcState *
(PhasedDiagnostic -> PhasedDiagnostic) *
ParsedInput list)
: FinalFileResult list * TcState =
: FinalFileResult list * TcState * TcState list =
use cts = new CancellationTokenSource()

let sourceFiles: FileInProject array =
@@ -1931,40 +1942,44 @@ let CheckMultipleInputsUsingGraphMode
partialResult, state)
)

UseMultipleDiagnosticLoggers (inputs, diagnosticsLogger, Some eagerFormat) (fun inputsWithLoggers ->
// Equip loggers to locally filter w.r.t. scope pragmas in each input
let inputsWithLoggers =
inputsWithLoggers
|> List.toArray
|> Array.map (fun (input, oldLogger) ->
let logger = DiagnosticsLoggerForInput(tcConfig, input, oldLogger)
input, logger)

let processFile (node: NodeToTypeCheck) (state: State) : Finisher<NodeToTypeCheck, State, PartialResult> =
match node with
| NodeToTypeCheck.ArtificialImplFile idx ->
let parsedInput, _ = inputsWithLoggers[idx]
processArtificialImplFile node parsedInput state
| NodeToTypeCheck.PhysicalFile idx ->
let parsedInput, logger = inputsWithLoggers[idx]
processFile node (parsedInput, logger) state

let state: State = tcState, priorErrors

let partialResults, (tcState, _) =
TypeCheckingGraphProcessing.processTypeCheckingGraph nodeGraph processFile state cts.Token

let partialResults =
partialResults
// Bring back the original, index-based file order.
|> List.sortBy fst
|> List.map snd

partialResults, tcState)
let results, state =
UseMultipleDiagnosticLoggers (inputs, diagnosticsLogger, Some eagerFormat) (fun inputsWithLoggers ->
// Equip loggers to locally filter w.r.t. scope pragmas in each input
let inputsWithLoggers =
inputsWithLoggers
|> List.toArray
|> Array.map (fun (input, oldLogger) ->
let logger = DiagnosticsLoggerForInput(tcConfig, input, oldLogger)
input, logger)

let processFile (node: NodeToTypeCheck) (state: State) : Finisher<NodeToTypeCheck, State, PartialResult> =
match node with
| NodeToTypeCheck.ArtificialImplFile idx ->
let parsedInput, _ = inputsWithLoggers[idx]
processArtificialImplFile node parsedInput state
| NodeToTypeCheck.PhysicalFile idx ->
let parsedInput, logger = inputsWithLoggers[idx]
processFile node (parsedInput, logger) state

let state: State = tcState, priorErrors

let partialResults, (tcState, _) =
TypeCheckingGraphProcessing.processTypeCheckingGraph nodeGraph processFile state cts.Token

let partialResults =
partialResults
// Bring back the original, index-based file order.
|> List.sortBy fst
|> List.map snd

partialResults, tcState)

// TODO: collect states here also
results, state, []
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yeah it doesn't work for graph checking yet. It should.


let CheckClosedInputSet (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tcGlobals, prefixPathOpt, tcState, eagerFormat, inputs) =
// tcEnvAtEndOfLastFile is the environment required by fsi.exe when incrementally adding definitions
let results, tcState =
let results, lastState, tcStates =
match tcConfig.typeCheckingConfig.Mode with
| TypeCheckingMode.Graph when (not tcConfig.isInteractive && not tcConfig.compilingFSharpCore) ->
CheckMultipleInputsUsingGraphMode(
@@ -1981,10 +1996,11 @@ let CheckClosedInputSet (ctok, checkForErrors, tcConfig: TcConfig, tcImports, tc
| _ -> CheckMultipleInputsSequential(ctok, checkForErrors, tcConfig, tcImports, tcGlobals, prefixPathOpt, tcState, inputs)

let (tcEnvAtEndOfLastFile, topAttrs, implFiles, _), tcState =
CheckMultipleInputsFinish(results, tcState)
CheckMultipleInputsFinish(results, lastState)

let tcState, declaredImpls, ccuContents =
CheckClosedInputSetFinish(implFiles, tcState)

tcState.Ccu.Deref.Contents <- ccuContents
tcState, topAttrs, declaredImpls, tcEnvAtEndOfLastFile

tcState, topAttrs, declaredImpls, tcEnvAtEndOfLastFile, tcStates
20 changes: 18 additions & 2 deletions src/Compiler/Driver/ParseAndCheckInputs.fsi
Original file line number Diff line number Diff line change
@@ -3,7 +3,9 @@
/// Contains logic to coordinate the parsing and checking of one or a group of files
module internal FSharp.Compiler.ParseAndCheckInputs

open System.Collections.Generic
open System.IO
open Internal.Utilities.Collections
open Internal.Utilities.Library
open FSharp.Compiler.CheckBasics
open FSharp.Compiler.CheckDeclarations
@@ -143,9 +145,23 @@ val ParseInputFiles:
/// applying the InternalsVisibleTo in referenced assemblies and opening 'Checked' if requested.
val GetInitialTcEnv: assemblyName: string * range * TcConfig * TcImports * TcGlobals -> TcEnv * OpenDeclaration list

type RootSigs = Zmap<QualifiedNameOfFile, ModuleOrNamespaceType>

type RootImpls = Zset<QualifiedNameOfFile>

val qnameOrder: IComparer<QualifiedNameOfFile>

/// Represents the incremental type checking state for a set of inputs
[<Sealed>]
type TcState =
{ tcsCcu: CcuThunk
tcsTcSigEnv: TcEnv
tcsTcImplEnv: TcEnv
tcsCreatesGeneratedProvidedTypes: bool
tcsRootSigs: RootSigs
tcsRootImpls: RootImpls
tcsCcuSig: ModuleOrNamespaceType
tcsImplicitOpenDeclarations: OpenDeclaration list }

/// The CcuThunk for the current assembly being checked
member Ccu: CcuThunk

@@ -239,7 +255,7 @@ val CheckClosedInputSet:
tcState: TcState *
eagerFormat: (PhasedDiagnostic -> PhasedDiagnostic) *
inputs: ParsedInput list ->
TcState * TopAttribs * CheckedImplFile list * TcEnv
TcState * TopAttribs * CheckedImplFile list * TcEnv * TcState list
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note, TcState list includes the first state. I just kept the first state separately here to decrease the change surface a bit for now.


/// Check a single input and finish the checking
val CheckOneInputAndFinish:
Loading