@@ -33,9 +33,11 @@ open System.Reflection
3333
3434let checking = false
3535let logging = false
36- let _ = if checkingthen dprintn" warning :Ilread .checking is on"
36+ let _ = if checkingthen 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
351353do stats.weakByteFileCount<- stats.weakByteFileCount+ 1
352354
@@ -357,30 +359,29 @@ type WeakByteFile(fileName: string) =
357359let weakBytes = new WeakReference< byte[]> ( null )
358360
359361member __.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) <> fileStampthen
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
371364interface BinaryFilewith
372- override __.GetView () =
373- let mutable tg = null
365+
366+ override this.GetView () =
374367let strongBytes =
368+ let mutable tg = null
375369if not ( weakBytes.TryGetTarget(& tg)) then
376370if FileSystem.GetLastWriteTimeShim( fileName) <> fileStampthen
377- errorR( Error( FSComp.SR.ilreadFileChanged fileName, range0))
371+ error( Error( FSComp.SR.ilreadFileChanged fileName, range0))
372+
373+ let bytes =
374+ match chunkwith
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 = ILModuleReaderCacheKeyof 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 =
39343936not noStableFileHeuristic&& try FileSystem.IsStableFileHeuristic fileNamewith _ -> 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.
39393941if opts.reduceMemoryUsage= ReduceMemoryFlag.Yes&& stableFileHeuristicApplies fileNamethen
3940- WeakByteFile( fileName) :> BinaryFile
3942+ WeakByteFile( fileName, chunk ) :> BinaryFile
39413943else
3942- let bytes = FileSystem.ReadAllBytesShim( fileName)
3944+ let bytes =
3945+ match chunkwith
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 =
39463951let file =
39473952try
39483953 MemoryMapFile.Create fileName:> BinaryFile
39493954with _ ->
3950- createByteFile opts fileName
3955+ createByteFileChunk opts fileName None
39513956let disposer =
39523957{ new IDisposablewith
39533958member __.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 =
39673972try
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
39783982let cacheResult =
39793983if keyOkthen
@@ -3999,30 +4003,29 @@ let OpenILModuleReader fileName opts =
39994003
40004004// See if tryGetMetadata gives us a BinaryFile for the metadata section alone.
40014005let 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 optsfileName
4012+ let disposer , pefileEager = tryMemoryMapWholeFile optsfullPath
40094013use _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)
40114015let mdfile =
40124016match mdfileOptwith
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)
40204023new ILModuleReader( ilModule, ilAssemblyRefs, ignore)
40214024else
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 optsfileName
4025- let ilModule , ilAssemblyRefs , _pdb = openPE( fileName , pefile, None, reduceMemoryUsage, opts.ilGlobals)
4027+ let pefile = createByteFileChunk optsfullPath None
4028+ let ilModule , ilAssemblyRefs , _pdb = openPE( fullPath , pefile, None, reduceMemoryUsage, opts.ilGlobals)
40264029new ILModuleReader( ilModule, ilAssemblyRefs, ignore)
40274030
40284031if keyOkthen
@@ -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
40444047let _disposer , pefile =
4045- if alwaysMemoryMapFSC|| stableFileHeuristicAppliesfileName then
4046- tryMemoryMap optsfileName
4048+ if alwaysMemoryMapFSC|| stableFileHeuristicAppliesfullPath then
4049+ tryMemoryMapWholeFile optsfullPath
40474050else
4048- let pefile = createByteFile optsfileName
4051+ let pefile = createByteFileChunk optsfullPath None
40494052let disposer = { new IDisposablewith 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)
40534056let 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.