Skip to content

Commit cc8bc2b

Browse files
auduchinokKevinRansom
authored andcommitted
Assembly reader shim (#4915)
* Add assembly reader shim * Add test * Extract IModuleReader interface * Update naming per PR review
1 parent aeeed79 commit cc8bc2b

File tree

5 files changed

+107
-33
lines changed

5 files changed

+107
-33
lines changed

fcs/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<FSharpSourcesRoot>$(MSBuildProjectDirectory)\..\..\src</FSharpSourcesRoot>
44
</PropertyGroup>
@@ -26,6 +26,9 @@
2626
<Compile Include="$(FSharpSourcesRoot)\..\tests\service\Common.fs">
2727
<Link>Common.fs</Link>
2828
</Compile>
29+
<Compile Include="$(FSharpSourcesRoot)\..\tests\service\AssemblyReaderShim.fs">
30+
<Link>AssemblyReaderShim.fs</Link>
31+
</Compile>
2932
<Compile Include="$(FSharpSourcesRoot)\..\tests\service\EditorTests.fs">
3033
<Link>EditorTests.fs</Link>
3134
</Compile>

src/absil/ilread.fs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3945,11 +3945,20 @@ type ILReaderOptions =
39453945
metadataOnly: MetadataOnlyFlag
39463946
tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot }
39473947

3948+
3949+
type ILModuleReader =
3950+
abstract ILModuleDef : ILModuleDef
3951+
abstract ILAssemblyRefs : ILAssemblyRef list
3952+
3953+
/// ILModuleReader objects only need to be explicitly disposed if memory mapping is used, i.e. reduceMemoryUsage = false
3954+
inherit System.IDisposable
3955+
3956+
39483957
[<Sealed>]
3949-
type ILModuleReader(ilModule: ILModuleDef, ilAssemblyRefs: Lazy<ILAssemblyRef list>, dispose: unit -> unit) =
3950-
member x.ILModuleDef = ilModule
3951-
member x.ILAssemblyRefs = ilAssemblyRefs.Force()
3952-
interface IDisposable with
3958+
type ILModuleReaderImpl(ilModule: ILModuleDef, ilAssemblyRefs: Lazy<ILAssemblyRef list>, dispose: unit -> unit) =
3959+
interface ILModuleReader with
3960+
member x.ILModuleDef = ilModule
3961+
member x.ILAssemblyRefs = ilAssemblyRefs.Force()
39533962
member x.Dispose() = dispose()
39543963

39553964
// ++GLOBAL MUTABLE STATE (concurrency safe via locking)
@@ -3990,7 +3999,7 @@ let tryMemoryMapWholeFile opts fileName =
39903999
let OpenILModuleReaderFromBytes fileName bytes opts =
39914000
let pefile = ByteFile(fileName, bytes) :> BinaryFile
39924001
let ilModule, ilAssemblyRefs, pdb = openPE (fileName, pefile, opts.pdbDirPath, (opts.reduceMemoryUsage = ReduceMemoryFlag.Yes), opts.ilGlobals, true)
3993-
new ILModuleReader(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb))
4002+
new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb)) :> ILModuleReader
39944003

39954004
let OpenILModuleReader fileName opts =
39964005
// Pseudo-normalize the paths.
@@ -4046,18 +4055,18 @@ let OpenILModuleReader fileName opts =
40464055
createByteFileChunk opts fullPath (Some (metadataPhysLoc, metadataSize))
40474056

40484057
let ilModule, ilAssemblyRefs = openPEMetadataOnly (fullPath, peinfo, pectxtEager, pevEager, mdfile, reduceMemoryUsage, opts.ilGlobals)
4049-
new ILModuleReader(ilModule, ilAssemblyRefs, ignore)
4058+
new ILModuleReaderImpl(ilModule, ilAssemblyRefs, ignore)
40504059
else
40514060
// If we are not doing metadata-only, then just go ahead and read all the bytes and hold them either strongly or weakly
40524061
// depending on the heuristic
40534062
let pefile = createByteFileChunk opts fullPath None
40544063
let ilModule, ilAssemblyRefs, _pdb = openPE (fullPath, pefile, None, reduceMemoryUsage, opts.ilGlobals, false)
4055-
new ILModuleReader(ilModule, ilAssemblyRefs, ignore)
4064+
new ILModuleReaderImpl(ilModule, ilAssemblyRefs, ignore)
40564065

40574066
if keyOk then
40584067
ilModuleReaderCacheLock.AcquireLock (fun ltok -> ilModuleReaderCache.Put(ltok, key, ilModuleReader))
40594068

4060-
ilModuleReader
4069+
ilModuleReader :> ILModuleReader
40614070

40624071
else
40634072
// This case is primarily used in fsc.exe.
@@ -4079,13 +4088,25 @@ let OpenILModuleReader fileName opts =
40794088
disposer, pefile
40804089

40814090
let ilModule, ilAssemblyRefs, pdb = openPE (fullPath, pefile, opts.pdbDirPath, reduceMemoryUsage, opts.ilGlobals, false)
4082-
let ilModuleReader = new ILModuleReader(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb))
4091+
let ilModuleReader = new ILModuleReaderImpl(ilModule, ilAssemblyRefs, (fun () -> ClosePdbReader pdb))
40834092

40844093
// Readers with PDB reader disposal logic don't go in the cache. Note the PDB reader is only used in static linking.
40854094
if keyOk && opts.pdbDirPath.IsNone then
40864095
ilModuleReaderCacheLock.AcquireLock (fun ltok -> ilModuleReaderCache.Put(ltok, key, ilModuleReader))
40874096

4088-
ilModuleReader
4097+
ilModuleReader :> ILModuleReader
4098+
4099+
[<AutoOpen>]
4100+
module Shim =
4101+
open Microsoft.FSharp.Compiler.Lib
40894102

4103+
type IAssemblyReader =
4104+
abstract GetILModuleReader: filename: string * readerOptions: ILReaderOptions -> ILModuleReader
40904105

4106+
[<Sealed>]
4107+
type DefaultAssemblyReader() =
4108+
interface IAssemblyReader with
4109+
member __.GetILModuleReader(filename, readerOptions) =
4110+
OpenILModuleReader filename readerOptions
40914111

4112+
let mutable AssemblyReader = DefaultAssemblyReader() :> IAssemblyReader

src/absil/ilread.fsi

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ type ILReaderMetadataSnapshot = (obj * nativeint * int)
3838
type ILReaderTryGetMetadataSnapshot = (* path: *) string * (* snapshotTimeStamp: *) System.DateTime -> ILReaderMetadataSnapshot option
3939

4040
[<RequireQualifiedAccess>]
41-
type internal MetadataOnlyFlag = Yes | No
41+
type MetadataOnlyFlag = Yes | No
4242

4343
[<RequireQualifiedAccess>]
44-
type internal ReduceMemoryFlag = Yes | No
44+
type ReduceMemoryFlag = Yes | No
4545

46-
type internal ILReaderOptions =
46+
type ILReaderOptions =
4747
{ pdbDirPath: string option
4848

4949
ilGlobals: ILGlobals
@@ -64,16 +64,17 @@ type internal ILReaderOptions =
6464
/// and from which we can read the metadata. Only used when metadataOnly=true.
6565
tryGetMetadataSnapshot: ILReaderTryGetMetadataSnapshot }
6666

67+
6768
/// Represents a reader of the metadata of a .NET binary. May also give some values (e.g. IL code) from the PE file
6869
/// if it was provided.
69-
[<Sealed>]
70-
type internal ILModuleReader =
71-
member ILModuleDef : ILModuleDef
72-
member ILAssemblyRefs : ILAssemblyRef list
70+
type ILModuleReader =
71+
abstract ILModuleDef: ILModuleDef
72+
abstract ILAssemblyRefs: ILAssemblyRef list
7373

7474
/// ILModuleReader objects only need to be explicitly disposed if memory mapping is used, i.e. reduceMemoryUsage = false
75-
interface System.IDisposable
76-
75+
inherit System.IDisposable
76+
77+
7778
/// Open a binary reader, except first copy the entire contents of the binary into
7879
/// memory, close the file and ensure any subsequent reads happen from the in-memory store.
7980
/// PDB files may not be read with this option.
@@ -89,4 +90,16 @@ type Statistics =
8990
mutable weakByteFileCount : int
9091
mutable byteFileCount : int }
9192

92-
val GetStatistics : unit -> Statistics
93+
val GetStatistics : unit -> Statistics
94+
95+
[<AutoOpen>]
96+
module Shim =
97+
98+
type IAssemblyReader =
99+
abstract GetILModuleReader: filename: string * readerOptions: ILReaderOptions -> ILModuleReader
100+
101+
[<Sealed>]
102+
type DefaultAssemblyReader =
103+
interface IAssemblyReader
104+
105+
val mutable AssemblyReader: IAssemblyReader

src/fsharp/CompileOps.fs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2624,14 +2624,7 @@ type TcConfigBuilder =
26242624
ri, fileNameOfPath ri, ILResourceAccess.Public
26252625

26262626

2627-
let OpenILBinary(filename, reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, shadowCopyReferences, tryGetMetadataSnapshot) =
2628-
let ilGlobals =
2629-
// ILScopeRef.Local can be used only for primary assembly (mscorlib or System.Runtime) itself
2630-
// Remaining assemblies should be opened using existing ilGlobals (so they can properly locate fundamental types)
2631-
match ilGlobalsOpt with
2632-
| None -> mkILGlobals ILScopeRef.Local
2633-
| Some g -> g
2634-
2627+
let OpenILBinary(filename, reduceMemoryUsage, ilGlobals, pdbDirPath, shadowCopyReferences, tryGetMetadataSnapshot) =
26352628
let opts : ILReaderOptions =
26362629
{ ilGlobals = ilGlobals
26372630
metadataOnly = MetadataOnlyFlag.Yes
@@ -2652,7 +2645,7 @@ let OpenILBinary(filename, reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, shadowCo
26522645
ignore shadowCopyReferences
26532646
#endif
26542647
filename
2655-
OpenILModuleReader location opts
2648+
AssemblyReader.GetILModuleReader(location, opts)
26562649

26572650
#if DEBUG
26582651
[<System.Diagnostics.DebuggerDisplayAttribute("AssemblyResolution({resolvedPath})")>]
@@ -2782,12 +2775,13 @@ type TcConfig private (data : TcConfigBuilder, validate:bool) =
27822775
do if ((primaryAssemblyExplicitFilenameOpt.IsSome || fslibExplicitFilenameOpt.IsSome) && data.framework) then
27832776
error(Error(FSComp.SR.buildExplicitCoreLibRequiresNoFramework("--noframework"), rangeStartup))
27842777

2778+
let ilGlobals = mkILGlobals ILScopeRef.Local
27852779
let clrRootValue, targetFrameworkVersionValue =
27862780
match primaryAssemblyExplicitFilenameOpt with
27872781
| Some(primaryAssemblyFilename) ->
27882782
let filename = ComputeMakePathAbsolute data.implicitIncludeDir primaryAssemblyFilename
27892783
try
2790-
use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, None, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot)
2784+
use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, ilGlobals, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot)
27912785
let ilModule = ilReader.ILModuleDef
27922786
match ilModule.ManifestOfAssembly.Version with
27932787
| Some(v1, v2, _, _) ->
@@ -2820,7 +2814,7 @@ type TcConfig private (data : TcConfigBuilder, validate:bool) =
28202814
let filename = ComputeMakePathAbsolute data.implicitIncludeDir fslibFilename
28212815
if fslibReference.ProjectReference.IsNone then
28222816
try
2823-
use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, None, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot)
2817+
use ilReader = OpenILBinary(filename, data.reduceMemoryUsage, ilGlobals, None, data.shadowCopyReferences, data.tryGetMetadataSnapshot)
28242818
()
28252819
with e ->
28262820
error(Error(FSComp.SR.buildErrorOpeningBinaryFile(filename, e.Message), rangeStartup))
@@ -4144,7 +4138,17 @@ type TcImports(tcConfigP:TcConfigProvider, initialResolutions:TcAssemblyResoluti
41444138
None
41454139
else
41464140
None
4147-
let ilILBinaryReader = OpenILBinary(filename, tcConfig.reduceMemoryUsage, ilGlobalsOpt, pdbDirPath, tcConfig.shadowCopyReferences, tcConfig.tryGetMetadataSnapshot)
4141+
4142+
let ilGlobals =
4143+
// ILScopeRef.Local can be used only for primary assembly (mscorlib or System.Runtime) itself
4144+
// Remaining assemblies should be opened using existing ilGlobals (so they can properly locate fundamental types)
4145+
match ilGlobalsOpt with
4146+
| None -> mkILGlobals ILScopeRef.Local
4147+
| Some g -> g
4148+
4149+
let ilILBinaryReader =
4150+
OpenILBinary (filename, tcConfig.reduceMemoryUsage, ilGlobals, pdbDirPath, tcConfig.shadowCopyReferences, tcConfig.tryGetMetadataSnapshot)
4151+
41484152
tcImports.AttachDisposeAction(fun _ -> (ilILBinaryReader :> IDisposable).Dispose())
41494153
ilILBinaryReader.ILModuleDef, ilILBinaryReader.ILAssemblyRefs
41504154
with e ->
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#if INTERACTIVE
2+
#r "../../debug/fcs/net45/FSharp.Compiler.Service.dll" // note, run 'build fcs debug' to generate this, this DLL has a public API so can be used from F# Interactive
3+
#r "../../packages/NUnit.3.5.0/lib/net45/nunit.framework.dll"
4+
#load "FsUnit.fs"
5+
#load "Common.fs"
6+
#else
7+
module FSharp.Compiler.Service.Tests.AssemblyReaderShim
8+
#endif
9+
10+
open FSharp.Compiler.Service.Tests.Common
11+
open FsUnit
12+
open Microsoft.FSharp.Compiler.AbstractIL.ILBinaryReader
13+
open NUnit.Framework
14+
15+
[<Test>]
16+
let ``Assembly reader shim gets requests`` () =
17+
let defaultReader = Shim.AssemblyReader
18+
let mutable gotRequest = false
19+
let reader =
20+
{ new IAssemblyReader with
21+
member x.GetILModuleReader(path, opts) =
22+
gotRequest <- true
23+
defaultReader.GetILModuleReader(path, opts)
24+
}
25+
Shim.AssemblyReader <- reader
26+
let source = """
27+
module M
28+
let x = 123
29+
"""
30+
31+
let fileName, options = Common.mkTestFileAndOptions source [| |]
32+
Common.checker.ParseAndCheckFileInProject(fileName, 0, source, options) |> Async.RunSynchronously |> ignore
33+
gotRequest |> should be True

0 commit comments

Comments
 (0)