@@ -33,9 +33,11 @@ open System.Reflection
3333
3434let checking = false
3535let logging = false
36- let _ = if checking then dprintn " warning : Ilread .checking is on"
36+ let _ = if checking then dprintn " warning : ILBinaryReader .checking is on"
3737let noStableFileHeuristic = try ( System.Environment.GetEnvironmentVariable( " FSharp_NoStableFileHeuristic" ) <> null ) with _ -> false
3838let alwaysMemoryMapFSC = try ( System.Environment.GetEnvironmentVariable( " FSharp_AlwaysMemoryMapCommandLineCompiler" ) <> null ) with _ -> false
39+ let stronglyHeldReaderCacheSizeDefault = 30
40+ let stronglyHeldReaderCacheSize = try ( match System.Environment.GetEnvironmentVariable( " FSharp_StronglyHeldBinaryReaderCacheSize" ) with null -> stronglyHeldReaderCacheSizeDefault | s -> int32 s) with _ -> stronglyHeldReaderCacheSizeDefault
3941
4042let singleOfBits ( x : int32 ) = System.BitConverter.ToSingle( System.BitConverter.GetBytes( x), 0 )
4143let doubleOfBits ( x : int64 ) = System.BitConverter.Int64BitsToDouble( x)
@@ -346,7 +348,7 @@ type ByteFile(fileName: string, bytes:byte[]) =
346348/// This is the default implementation used by F# Compiler Services when accessing "stable" binaries. It is not used
347349/// by Visual Studio, where tryGetMetadataSnapshot provides a RawMemoryFile backed by Roslyn data.
348350[<DebuggerDisplay( " {FileName}" ) >]
349- type WeakByteFile ( fileName : string ) =
351+ type WeakByteFile ( fileName : string , chunk : ( int * int ) option ) =
350352
351353 do stats.weakByteFileCount <- stats.weakByteFileCount + 1
352354
@@ -357,30 +359,29 @@ type WeakByteFile(fileName: string) =
357359 let weakBytes = new WeakReference< byte[]> ( null )
358360
359361 member __.FileName = fileName
360- /// Get the bytes for the file
361- member this.Get () =
362- let mutable tg = null
363- if not ( weakBytes.TryGetTarget(& tg)) then
364- if FileSystem.GetLastWriteTimeShim( fileName) <> fileStamp then
365- errorR ( Error ( FSComp.SR.ilreadFileChanged fileName, range0))
366-
367- tg <- FileSystem.ReadAllBytesShim fileName
368- weakBytes.SetTarget tg
369- tg
370362
363+ /// Get the bytes for the file
371364 interface BinaryFile with
372- override __.GetView () =
373- let mutable tg = null
365+
366+ override this.GetView () =
374367 let strongBytes =
368+ let mutable tg = null
375369 if not ( weakBytes.TryGetTarget(& tg)) then
376370 if FileSystem.GetLastWriteTimeShim( fileName) <> fileStamp then
377- errorR ( Error ( FSComp.SR.ilreadFileChanged fileName, range0))
371+ error ( Error ( FSComp.SR.ilreadFileChanged fileName, range0))
372+
373+ let bytes =
374+ match chunk with
375+ | None -> FileSystem.ReadAllBytesShim fileName
376+ | Some( start, length) -> File.ReadBinaryChunk ( fileName, start, length)
377+
378+ tg <- bytes
379+
380+ weakBytes.SetTarget bytes
378381
379- tg <- FileSystem.ReadAllBytesShim fileName
380- weakBytes.SetTarget tg
381382 tg
382- ( ByteView( strongBytes) :> BinaryView)
383383
384+ ( ByteView( strongBytes) :> BinaryView)
384385
385386
386387let seekReadByte ( mdv : BinaryView ) addr = mdv.ReadByte addr
@@ -3927,27 +3928,31 @@ type ILModuleReader(ilModule: ILModuleDef, ilAssemblyRefs: Lazy<ILAssemblyRef li
39273928
39283929// ++GLOBAL MUTABLE STATE (concurrency safe via locking)
39293930type ILModuleReaderCacheLockToken () = interface LockToken
3930- let ilModuleReaderCache = new AgedLookup< ILModuleReaderCacheLockToken, ( string * System.DateTime * ILScopeRef * bool * ReduceMemoryFlag * MetadataOnlyFlag), ILModuleReader>( 0 , areSimilar=( fun ( x , y ) -> x = y))
3931+ type ILModuleReaderCacheKey = ILModuleReaderCacheKey of string * DateTime * ILScopeRef * bool * ReduceMemoryFlag * MetadataOnlyFlag
3932+ let ilModuleReaderCache = new AgedLookup< ILModuleReaderCacheLockToken, ILModuleReaderCacheKey, ILModuleReader>( stronglyHeldReaderCacheSize, areSimilar=( fun ( x , y ) -> x = y))
39313933let ilModuleReaderCacheLock = Lock()
39323934
39333935let stableFileHeuristicApplies fileName =
39343936 not noStableFileHeuristic && try FileSystem.IsStableFileHeuristic fileName with _ -> false
39353937
3936- let createByteFile opts fileName =
3938+ let createByteFileChunk opts fileName chunk =
39373939 // If we're trying to reduce memory usage then we are willing to go back and re-read the binary, so we can use
39383940 // a weakly-held handle to an array of bytes.
39393941 if opts.reduceMemoryUsage = ReduceMemoryFlag.Yes && stableFileHeuristicApplies fileName then
3940- WeakByteFile( fileName) :> BinaryFile
3942+ WeakByteFile( fileName, chunk ) :> BinaryFile
39413943 else
3942- let bytes = FileSystem.ReadAllBytesShim( fileName)
3944+ let bytes =
3945+ match chunk with
3946+ | None -> FileSystem.ReadAllBytesShim fileName
3947+ | Some ( start, length) -> File.ReadBinaryChunk( fileName, start, length)
39433948 ByteFile( fileName, bytes) :> BinaryFile
39443949
3945- let tryMemoryMap opts fileName =
3950+ let tryMemoryMapWholeFile opts fileName =
39463951 let file =
39473952 try
39483953 MemoryMapFile.Create fileName :> BinaryFile
39493954 with _ ->
3950- createByteFile opts fileName
3955+ createByteFileChunk opts fileName None
39513956 let disposer =
39523957 { new IDisposable with
39533958 member __.Dispose () =
@@ -3963,17 +3968,16 @@ let OpenILModuleReaderFromBytes fileName bytes opts =
39633968
39643969let OpenILModuleReader fileName opts =
39653970 // Pseudo-normalize the paths.
3966- let (( _ , writeStamp , _ , _ , _ , _ ) as key ), keyOk =
3971+ let ( ILModuleReaderCacheKey ( fullPath , writeStamp , _ , _ , _ , _ ) as key ), keyOk =
39673972 try
3968- ( FileSystem.GetFullPathShim( fileName),
3969- FileSystem.GetLastWriteTimeShim( fileName),
3970- opts.ilGlobals.primaryAssemblyScopeRef,
3971- opts.pdbPath.IsSome,
3972- opts.reduceMemoryUsage,
3973- opts.metadataOnly), true
3974- with e ->
3975- System.Diagnostics.Debug.Assert( false , sprintf " Failed to compute key in OpenILModuleReader cache for '%s '. Falling back to uncached." fileName)
3976- ( " " , System.DateTime.UtcNow, ILScopeRef.Local, false , ReduceMemoryFlag.Yes, MetadataOnlyFlag.Yes), false
3973+ let fullPath = FileSystem.GetFullPathShim( fileName)
3974+ let writeTime = FileSystem.GetLastWriteTimeShim( fileName)
3975+ let key = ILModuleReaderCacheKey ( fullPath, writeTime, opts.ilGlobals.primaryAssemblyScopeRef, opts.pdbPath.IsSome, opts.reduceMemoryUsage, opts.metadataOnly)
3976+ key, true
3977+ with exn ->
3978+ System.Diagnostics.Debug.Assert( false , sprintf " Failed to compute key in OpenILModuleReader cache for '%s '. Falling back to uncached. Error = %s " fileName ( exn.ToString()))
3979+ let fakeKey = ILModuleReaderCacheKey( fileName, System.DateTime.UtcNow, ILScopeRef.Local, false , ReduceMemoryFlag.Yes, MetadataOnlyFlag.Yes)
3980+ fakeKey, false
39773981
39783982 let cacheResult =
39793983 if keyOk then
@@ -3999,30 +4003,29 @@ let OpenILModuleReader fileName opts =
39994003
40004004 // See if tryGetMetadata gives us a BinaryFile for the metadata section alone.
40014005 let mdfileOpt =
4002- match opts.tryGetMetadataSnapshot ( fileName , writeStamp) with
4003- | Some ( obj, start, len) -> Some ( RawMemoryFile( fileName , obj, start, len) :> BinaryFile)
4006+ match opts.tryGetMetadataSnapshot ( fullPath , writeStamp) with
4007+ | Some ( obj, start, len) -> Some ( RawMemoryFile( fullPath , obj, start, len) :> BinaryFile)
40044008 | None -> None
40054009
40064010 // For metadata-only, always use a temporary, short-lived PE file reader, preferably over a memory mapped file.
40074011 // Then use the metadata blob as the long-lived memory resource.
4008- let disposer , pefileEager = tryMemoryMap opts fileName
4012+ let disposer , pefileEager = tryMemoryMapWholeFile opts fullPath
40094013 use _disposer = disposer
4010- let ( metadataPhysLoc , metadataSize , peinfo , pectxtEager , pevEager , _pdb ) = openPEFileReader ( fileName , pefileEager, None)
4014+ let ( metadataPhysLoc , metadataSize , peinfo , pectxtEager , pevEager , _pdb ) = openPEFileReader ( fullPath , pefileEager, None)
40114015 let mdfile =
40124016 match mdfileOpt with
40134017 | Some mdfile -> mdfile
40144018 | None ->
40154019 // If tryGetMetadata doesn't give anything, then just read the metadata chunk out of the binary
4016- let bytes = File.ReadBinaryChunk ( fileName, metadataPhysLoc, metadataSize)
4017- ByteFile( fileName, bytes) :> BinaryFile
4020+ createByteFileChunk opts fullPath ( Some ( metadataPhysLoc, metadataSize))
40184021
4019- let ilModule , ilAssemblyRefs = openPEMetadataOnly ( fileName , peinfo, pectxtEager, pevEager, mdfile, reduceMemoryUsage, opts.ilGlobals)
4022+ let ilModule , ilAssemblyRefs = openPEMetadataOnly ( fullPath , peinfo, pectxtEager, pevEager, mdfile, reduceMemoryUsage, opts.ilGlobals)
40204023 new ILModuleReader( ilModule, ilAssemblyRefs, ignore)
40214024 else
40224025 // If we are not doing metadata-only, then just go ahead and read all the bytes and hold them either strongly or weakly
40234026 // depending on the heuristic
4024- let pefile = createByteFile opts fileName
4025- let ilModule , ilAssemblyRefs , _pdb = openPE ( fileName , pefile, None, reduceMemoryUsage, opts.ilGlobals)
4027+ let pefile = createByteFileChunk opts fullPath None
4028+ let ilModule , ilAssemblyRefs , _pdb = openPE ( fullPath , pefile, None, reduceMemoryUsage, opts.ilGlobals)
40264029 new ILModuleReader( ilModule, ilAssemblyRefs, ignore)
40274030
40284031 if keyOk then
@@ -4042,14 +4045,14 @@ let OpenILModuleReader fileName opts =
40424045 // multi-proc build. So use memory mapping, but only for stable files. Other files
40434046 // fill use an in-memory ByteFile
40444047 let _disposer , pefile =
4045- if alwaysMemoryMapFSC || stableFileHeuristicApplies fileName then
4046- tryMemoryMap opts fileName
4048+ if alwaysMemoryMapFSC || stableFileHeuristicApplies fullPath then
4049+ tryMemoryMapWholeFile opts fullPath
40474050 else
4048- let pefile = createByteFile opts fileName
4051+ let pefile = createByteFileChunk opts fullPath None
40494052 let disposer = { new IDisposable with member __.Dispose () = () }
40504053 disposer, pefile
40514054
4052- let ilModule , ilAssemblyRefs , pdb = openPE ( fileName , pefile, opts.pdbPath, reduceMemoryUsage, opts.ilGlobals)
4055+ let ilModule , ilAssemblyRefs , pdb = openPE ( fullPath , pefile, opts.pdbPath, reduceMemoryUsage, opts.ilGlobals)
40534056 let ilModuleReader = new ILModuleReader( ilModule, ilAssemblyRefs, ( fun () -> ClosePdbReader pdb))
40544057
40554058 // Readers with PDB reader disposal logic don't go in the cache. Note the PDB reader is only used in static linking.
0 commit comments