Skip to content
Merged
41 changes: 41 additions & 0 deletions src/fsharp/CompilerImports.fs
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,47 @@ type RawFSharpAssemblyDataBackedByFileOnDisk (ilModule: ILModuleDef, ilAssemblyR
let attrs = GetCustomAttributesOfILModule ilModule
List.exists (IsMatchingSignatureDataVersionAttr ilg (parseILVersion Internal.Utilities.FSharpEnvironment.FSharpBinaryMetadataFormatRevision)) attrs

[<Sealed>]
type RawFSharpAssemblyData (ilModule: ILModuleDef, ilAssemblyRefs) =

interface IRawFSharpAssemblyData with

member _.GetAutoOpenAttributes ilg = GetAutoOpenAttributes ilg ilModule

member _.GetInternalsVisibleToAttributes ilg = GetInternalsVisibleToAttributes ilg ilModule

member _.TryGetILModuleDef() = Some ilModule

member _.GetRawFSharpSignatureData(_, _, _) =
let resources = ilModule.Resources.AsList
[ for iresource in resources do
if IsSignatureDataResource iresource then
let ccuName = GetSignatureDataResourceName iresource
yield (ccuName, fun () -> iresource.GetBytes()) ]

member _.GetRawFSharpOptimizationData(_, _, _) =
ilModule.Resources.AsList
|> List.choose (fun r -> if IsOptimizationDataResource r then Some(GetOptimizationDataResourceName r, (fun () -> r.GetBytes())) else None)

member _.GetRawTypeForwarders() =
match ilModule.Manifest with
| Some manifest -> manifest.ExportedTypes
| None -> mkILExportedTypes []

member _.ShortAssemblyName = GetNameOfILModule ilModule

member _.ILScopeRef = MakeScopeRefForILModule ilModule

member _.ILAssemblyRefs = ilAssemblyRefs

member _.HasAnyFSharpSignatureDataAttribute =
let attrs = GetCustomAttributesOfILModule ilModule
List.exists IsSignatureDataVersionAttr attrs

member _.HasMatchingFSharpSignatureDataAttribute ilg =
let attrs = GetCustomAttributesOfILModule ilModule
List.exists (IsMatchingSignatureDataVersionAttr ilg (parseILVersion Internal.Utilities.FSharpEnvironment.FSharpBinaryMetadataFormatRevision)) attrs

//----------------------------------------------------------------------------
// TcImports
//--------------------------------------------------------------------------
Expand Down
7 changes: 7 additions & 0 deletions src/fsharp/CompilerImports.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ type TcAssemblyResolutions =

static member GetAssemblyResolutionInformation: ctok: CompilationThreadToken * tcConfig: TcConfig -> AssemblyResolution list * UnresolvedAssemblyReference list

[<Sealed>]
type RawFSharpAssemblyData =

new : ilModule: ILModuleDef * ilAssemblyRefs: ILAssemblyRef list -> RawFSharpAssemblyData

interface IRawFSharpAssemblyData

/// Represents a table of imported assemblies with their resolutions.
/// Is a disposable object, but it is recommended not to explicitly call Dispose unless you absolutely know nothing will be using its contents after the disposal.
/// Otherwise, simply allow the GC to collect this and it will properly call Dispose from the finalizer.
Expand Down
29 changes: 29 additions & 0 deletions src/fsharp/absil/ilread.fs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ open FSharp.Compiler.ErrorLogger
open FSharp.Compiler.IO
open FSharp.Compiler.Text.Range
open System.Reflection
open System.Reflection.PortableExecutable
open FSharp.NativeInterop

#nowarn "9"

Expand Down Expand Up @@ -131,6 +133,27 @@ type ByteFile(fileName: string, bytes: byte[]) =
member _.FileName = fileName
interface BinaryFile with
override bf.GetView() = view

type PEFile(fileName: string, peReader: PEReader) as this =

// We store a weak byte memory reference so we do not constantly create a lot of byte memory objects.
// We could just have a single ByteMemory stored in the PEFile, but we need to dispose of the stream via the finalizer; we cannot have a cicular reference.
let mutable weakMemory = new WeakReference<ByteMemory>(Unchecked.defaultof<_>)

member _.FileName = fileName

override _.Finalize() =
peReader.Dispose()

interface BinaryFile with
override _.GetView() =
match weakMemory.TryGetTarget() with
| true, m -> m.AsReadOnly()
| _ ->
let block = peReader.GetEntireImage() // it's ok to call this everytime we do GetView as it is cached in the PEReader.
let m = ByteMemory.FromUnsafePointer(block.Pointer |> NativePtr.toNativeInt, block.Length, this)
weakMemory <- WeakReference<ByteMemory>(m)
m.AsReadOnly()

/// Same as ByteFile but holds the bytes weakly. The bytes will be re-read from the backing file when a view is requested.
/// This is the default implementation used by F# Compiler Services when accessing "stable" binaries. It is not used
Expand Down Expand Up @@ -3888,6 +3911,12 @@ let OpenILModuleReaderFromBytes fileName assemblyContents options =
let ilModule, ilAssemblyRefs, pdb = openPE (fileName, pefile, options.pdbDirPath, (options.reduceMemoryUsage = ReduceMemoryFlag.Yes), true)
new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) :> ILModuleReader

let OpenILModuleReaderFromStream fileName (peStream: Stream) options =
let peReader = new System.Reflection.PortableExecutable.PEReader(peStream, PEStreamOptions.PrefetchEntireImage)
let pefile = PEFile(fileName, peReader) :> BinaryFile
let ilModule, ilAssemblyRefs, pdb = openPE (fileName, pefile, options.pdbDirPath, (options.reduceMemoryUsage = ReduceMemoryFlag.Yes), true)
new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) :> ILModuleReader

let ClearAllILModuleReaderCache() =
ilModuleReaderCache1.Clear(ILModuleReaderCache1LockToken())
ilModuleReaderCache2.Clear()
Expand Down
8 changes: 8 additions & 0 deletions src/fsharp/absil/ilread.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
/// you need.
module FSharp.Compiler.AbstractIL.ILBinaryReader

open System.IO
open FSharp.Compiler.AbstractIL.IL

/// Used to implement a Binary file over native memory, used by Roslyn integration
Expand Down Expand Up @@ -71,13 +72,20 @@ type public ILModuleReader =
/// Open a binary reader, except first copy the entire contents of the binary into
/// memory, close the file and ensure any subsequent reads happen from the in-memory store.
/// PDB files may not be read with this option.
/// Binary reader is internally cached.
val internal OpenILModuleReader: string -> ILReaderOptions -> ILModuleReader

val internal ClearAllILModuleReaderCache : unit -> unit

/// Open a binary reader based on the given bytes.
/// This binary reader is not internally cached.
val internal OpenILModuleReaderFromBytes: fileName:string -> assemblyContents: byte[] -> options: ILReaderOptions -> ILModuleReader

/// Open a binary reader based on the given stream.
/// This binary reader is not internally cached.
/// The binary reader will own the given stream and the stream will be disposed when there are no references to the binary reader.
val internal OpenILModuleReaderFromStream: fileName:string -> peStream: Stream -> options: ILReaderOptions -> ILModuleReader

type internal Statistics =
{ mutable rawMemoryFileCount : int
mutable memoryMapFileOpenedCount : int
Expand Down
41 changes: 36 additions & 5 deletions src/fsharp/service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,46 @@ open FSharp.Compiler.Text.Position
open FSharp.Compiler.Text.Range
open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeOps
open System.Reflection.PortableExecutable

open Internal.Utilities
open Internal.Utilities.Collections
open FSharp.Compiler.AbstractIL.ILBinaryReader

type FSharpUnresolvedReferencesSet = FSharpUnresolvedReferencesSet of UnresolvedAssemblyReference list

[<RequireQualifiedAccess;NoComparison>]
type FSharpReferencedProject =
| FSharpReference of projectFileName: string * options: FSharpProjectOptions
| PEReference of projectFileName: string * stamp: DateTime * reader: ILModuleReader

member this.FileName =
match this with
| FSharpReference(projectFileName=projectFileName)
| PEReference(projectFileName=projectFileName) -> projectFileName

static member CreateFSharp(projectFileName, options) =
FSharpReference(projectFileName, options)

static member CreatePortableExecutable(projectFileName, stamp, stream) =
let ilReaderOptions: ILReaderOptions =
{
pdbDirPath = None
reduceMemoryUsage = ReduceMemoryFlag.Yes
metadataOnly = MetadataOnlyFlag.Yes
tryGetMetadataSnapshot = fun _ -> None
}
let ilReader = ILBinaryReader.OpenILModuleReaderFromStream projectFileName stream ilReaderOptions
PEReference(projectFileName, stamp, ilReader)

// NOTE: may be better just to move to optional arguments here
type FSharpProjectOptions =
and FSharpProjectOptions =
{
ProjectFileName: string
ProjectId: string option
SourceFiles: string[]
OtherOptions: string[]
ReferencedProjects: (string * FSharpProjectOptions)[]
ReferencedProjects: FSharpReferencedProject[]
IsIncompleteTypeCheckEnvironment : bool
UseScriptResolutionRules : bool
LoadTime : System.DateTime
Expand All @@ -89,9 +114,15 @@ type FSharpProjectOptions =
options1.UnresolvedReferences = options2.UnresolvedReferences &&
options1.OriginalLoadReferences = options2.OriginalLoadReferences &&
options1.ReferencedProjects.Length = options2.ReferencedProjects.Length &&
Array.forall2 (fun (n1,a) (n2,b) ->
n1 = n2 &&
FSharpProjectOptions.AreSameForChecking(a,b)) options1.ReferencedProjects options2.ReferencedProjects &&
(options1.ReferencedProjects, options2.ReferencedProjects)
||> Array.forall2 (fun r1 r2 ->
match r1, r2 with
| FSharpReferencedProject.FSharpReference(n1,a), FSharpReferencedProject.FSharpReference(n2,b) ->
n1 = n2 && FSharpProjectOptions.AreSameForChecking(a,b)
| FSharpReferencedProject.PEReference(n1, stamp1, _), FSharpReferencedProject.PEReference(n2, stamp2, _) ->
n1 = n2 && stamp1 = stamp2
| _ ->
false) &&
options1.LoadTime = options2.LoadTime

member po.ProjectDirectory = System.IO.Path.GetDirectoryName(po.ProjectFileName)
Expand Down
18 changes: 17 additions & 1 deletion src/fsharp/service/FSharpCheckerResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
namespace FSharp.Compiler.CodeAnalysis

open System
open System.IO
open System.Threading
open Internal.Utilities.Library
open FSharp.Compiler.AbstractIL.IL
open FSharp.Compiler.AbstractIL.ILBinaryReader
open FSharp.Compiler.AccessibilityLogic
open FSharp.Compiler.CodeAnalysis
open FSharp.Compiler.CheckDeclarations
Expand Down Expand Up @@ -46,7 +48,7 @@ type public FSharpProjectOptions =

/// The command line arguments for the other projects referenced by this project, indexed by the
/// exact text used in the "-r:" reference in FSharpProjectOptions.
ReferencedProjects: (string * FSharpProjectOptions)[]
ReferencedProjects: FSharpReferencedProject[]

/// When true, the typechecking environment is known a priori to be incomplete, for
/// example when a .fs file is opened outside of a project. In this case, the number of error
Expand Down Expand Up @@ -82,6 +84,20 @@ type public FSharpProjectOptions =
/// Compute the project directory.
member internal ProjectDirectory: string

and [<NoComparison>] public FSharpReferencedProject =
internal
Copy link
Member

Choose a reason for hiding this comment

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

Could you please make it public instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't want the internal structure to be public. But, we should be able to create different kinds of FSharpReferencedProject instances.

| FSharpReference of projectFileName: string * options: FSharpProjectOptions
| PEReference of projectFileName: string * stamp: DateTime * reader: ILModuleReader

member FileName : string

/// Creates a reference for an F# project. The physical data for it is stored/cached inside of the compiler service.
static member CreateFSharp : projectFileName: string * options: FSharpProjectOptions -> FSharpReferencedProject

/// Creates a reference for any portable executable, including F#. The stream is owned by this reference.
/// The stream will be automatically disposed when there are no references to FSharpReferencedProject and is GC collected.
static member CreatePortableExecutable : projectFileName: string * stamp: DateTime * stream: Stream -> FSharpReferencedProject

/// Represents the use of an F# symbol from F# source code
[<Sealed>]
type public FSharpSymbolUse =
Expand Down
50 changes: 32 additions & 18 deletions src/fsharp/service/service.fs
Original file line number Diff line number Diff line change
Expand Up @@ -243,24 +243,38 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
cancellable {
Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "CreateOneIncrementalBuilder", options.ProjectFileName)
let projectReferences =
[ for (nm,opts) in options.ReferencedProjects do

// Don't use cross-project references for FSharp.Core, since various bits of code require a concrete FSharp.Core to exist on-disk.
// The only solutions that have these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The only ramification
// of this is that you need to build FSharp.Core to get intellisense in those projects.

if (try Path.GetFileNameWithoutExtension(nm) with _ -> "") <> GetFSharpCoreLibraryName() then

yield
{ new IProjectReference with
member x.EvaluateRawContents(ctok) =
cancellable {
Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
return! self.GetAssemblyData(opts, ctok, userOpName + ".CheckReferencedProject("+nm+")")
}
member x.TryGetLogicalTimeStamp(cache) =
self.TryGetLogicalTimeStampForProject(cache, opts)
member x.FileName = nm } ]
[ for r in options.ReferencedProjects do

match r with
| FSharpReferencedProject.FSharpReference(nm,opts) ->
// Don't use cross-project references for FSharp.Core, since various bits of code require a concrete FSharp.Core to exist on-disk.
// The only solutions that have these cross-project references to FSharp.Core are VisualFSharp.sln and FSharp.sln. The only ramification
// of this is that you need to build FSharp.Core to get intellisense in those projects.

if (try Path.GetFileNameWithoutExtension(nm) with _ -> "") <> GetFSharpCoreLibraryName() then

yield
{ new IProjectReference with
member x.EvaluateRawContents(ctok) =
cancellable {
Trace.TraceInformation("FCS: {0}.{1} ({2})", userOpName, "GetAssemblyData", nm)
return! self.GetAssemblyData(opts, ctok, userOpName + ".CheckReferencedProject("+nm+")")
}
member x.TryGetLogicalTimeStamp(cache) =
self.TryGetLogicalTimeStampForProject(cache, opts)
member x.FileName = nm }

| FSharpReferencedProject.PEReference(nm,stamp,ilReader) ->
yield
{ new IProjectReference with
member x.EvaluateRawContents(_) =
cancellable {
let ilModuleDef, ilAsmRefs = ilReader.ILModuleDef, ilReader.ILAssemblyRefs
return RawFSharpAssemblyData(ilModuleDef, ilAsmRefs) :> IRawFSharpAssemblyData |> Some
}
member x.TryGetLogicalTimeStamp(_) = stamp |> Some
member x.FileName = nm }
]

let loadClosure = scriptClosureCache.TryGet(AnyCallerThread, options)

Expand Down
Loading