diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 4df01e7ceb0..2deaa7b51e6 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -8,6 +8,7 @@ * Fix exponential (2^N) compile time in pattern matching with shared guards and partial active patterns. ([Issue #18425](https://github.com/dotnet/fsharp/issues/18425), [PR #20244](https://github.com/dotnet/fsharp/pull/20244)) * Fix incorrect Debug lowering of inline builders that compose low-level resumable state machines. ([Issue #20466](https://github.com/dotnet/fsharp/issues/20466), [PR #20469](https://github.com/dotnet/fsharp/pull/20469)) * Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759)) +* Embed F# metadata-removal rules before resource collection, preserve custom rules, and combine generated rules when linking assemblies. ([PR #20527](https://github.com/dotnet/fsharp/pull/20527)) * Fix FS0750 "This construct may only be used within computation expressions" incorrectly raised for `let!`/`use!`/`do!` appearing in the right-hand side of a plain `let` binding inside a computation expression. The right-hand side is now desugared as a nested computation of the same builder whose result is bound with `let!`, keeping its bindings correctly scoped. ([Issue #19457](https://github.com/dotnet/fsharp/issues/19457), [PR #19868](https://github.com/dotnet/fsharp/pull/19868)) * Stop leaking a `System.Diagnostics.Metrics.MeterListener` per `Cache` in DEBUG builds. Each cache created a `CacheMetrics.CacheMetricsListener` (which starts a `MeterListener` registered in the process-global metrics registry) and never disposed it, so listeners accumulated for the lifetime of the process. Because every cache hit/miss/add published to all registered listeners, the per-operation cost grew linearly with the number of leaked listeners, so repeated checks (and Debug FCS test runs) slowed down over time. The per-cache `CacheMetricsListener` and the per-instance `cacheId` tag are removed; `DebugDisplay` and tests now read the existing name-aggregated stats populated by the single `ListenToAll` listener, so no per-cache listener is created and no per-operation cost is added. ([PR #19995](https://github.com/dotnet/fsharp/pull/19995)) * Fix state machine lowering dropping the side-effectful receiver of an unused unit-typed member access (e.g. inside `task { (effectful()).UnitProp }`). ([Issue #13099](https://github.com/dotnet/fsharp/issues/13099), [PR #19885](https://github.com/dotnet/fsharp/pull/19885)) diff --git a/src/Compiler/Driver/CompilerImports.fsi b/src/Compiler/Driver/CompilerImports.fsi index 5cf0f846e25..7b8875cb989 100644 --- a/src/Compiler/Driver/CompilerImports.fsi +++ b/src/Compiler/Driver/CompilerImports.fsi @@ -45,6 +45,8 @@ val IsOptimizationDataResource: ILResource -> bool /// Determine if an IL resource attached to an F# assembly is an F# optimization data resource (data stream B) val IsOptimizationDataResourceB: ILResource -> bool +val GetNameOfILModule: ILModuleDef -> string + /// Determine if an IL resource attached to an F# assembly is an F# quotation data resource for reflected definitions val IsReflectedDefinitionsResource: ILResource -> bool diff --git a/src/Compiler/Driver/ILLinkSubstitutions.fs b/src/Compiler/Driver/ILLinkSubstitutions.fs new file mode 100644 index 00000000000..6e91416d304 --- /dev/null +++ b/src/Compiler/Driver/ILLinkSubstitutions.fs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +module internal FSharp.Compiler.ILLinkSubstitutions + +open System.Xml.Linq + +let names (assemblyName: string) = + seq { + for kind in [ "Signature"; "Optimization" ] do + for suffix in [ "Data"; "CompressedData"; "DataB"; "CompressedDataB"; "Info" ] do + yield $"FSharp{kind}{suffix}.{assemblyName}" + } + +let document (assemblyName: string) (names: seq) = + let x = XName.Get + let assembly = XElement(x "assembly", XAttribute(x "fullname", assemblyName)) + + for name in names do + assembly.Add(XElement(x "resource", XAttribute(x "name", name), XAttribute(x "action", "remove"), "")) + + XElement(x "linker", assembly) diff --git a/src/Compiler/Driver/StaticLinking.fs b/src/Compiler/Driver/StaticLinking.fs index 7379423579e..c822770f861 100644 --- a/src/Compiler/Driver/StaticLinking.fs +++ b/src/Compiler/Driver/StaticLinking.fs @@ -4,6 +4,7 @@ module internal FSharp.Compiler.StaticLinking open System +open System.Xml.Linq open Internal.Utilities.Collections open Internal.Utilities.Library open Internal.Utilities.Library.Extras @@ -23,6 +24,57 @@ open FSharp.Compiler.TypedTree open FSharp.Compiler.TypeProviders #endif +let linkMetadataResources assemblyName (resources: (string * ILResource) list) = + let xname = XName.Get + let prefixes = ILLinkSubstitutions.names "" |> Seq.toList + + let generated, retained = + resources + |> List.splitChoose (fun (owner, resource) -> + match resource.Location with + | ILResourceLocation.Local _ when resource.Name.Equals("ILLink.Substitutions.xml", StringComparison.OrdinalIgnoreCase) -> + try + use stream = resource.GetBytes().AsStream() + let xml = XElement.Load stream + + let names = + [ + for rule in xml.Descendants(xname "resource") do + if rule.IsEmpty then + rule.Value <- "" + + for name in rule.Attributes(xname "name") do + if List.exists name.Value.StartsWithOrdinal prefixes then + name.Value + ] + + match resource.CustomAttrs.AsList(), XNode.DeepEquals(xml, ILLinkSubstitutions.document owner names) with + | [], true -> Choice1Of2(resource, names) + | _ -> Choice2Of2(true, resource) + with :? System.Xml.XmlException -> + Choice2Of2(true, resource) + | _ -> Choice2Of2(false, resource)) + + let resources = List.map snd retained + + match generated with + | (resource, _) :: _ when not (List.exists fst retained) -> + let present = resources |> List.map _.Name |> Set.ofList + + let names = + generated |> List.collect snd |> List.filter present.Contains |> List.distinct + + resources + @ [ + if not names.IsEmpty then + let xml = ILLinkSubstitutions.document assemblyName names + + { resource with + Location = ILResourceLocation.Local(ByteStorage.FromByteArray(System.Text.Encoding.UTF8.GetBytes(xml.ToString()))) + } + ] + | _ -> resources + // Handles TypeForwarding for the generated IL model type TypeForwarding(tcImports: TcImports) = @@ -157,51 +209,30 @@ let StaticLinkILModules ] let savedResources = - let allResources = - [ - for ccu, m in dependentILModules do - for r in m.Resources.AsList() do - (ccu, r) - ] - // Don't save interface, optimization or resource definitions for provider-generated assemblies. - // These are "fake". - let isProvided (ccu: CcuThunk option) = + [ + for ccu, m in dependentILModules do + let provided = #if !NO_TYPEPROVIDERS - match ccu with - | Some c -> c.IsProviderGenerated - | None -> false + ccu |> Option.exists _.IsProviderGenerated #else - ignore ccu - false + ignore ccu + false #endif + for r in m.Resources.AsList() do + let order, enabled = + if IsSignatureDataResource r || IsSignatureDataResourceB r then + 0, tcConfig.GenerateSignatureData + elif IsOptimizationDataResource r || IsOptimizationDataResourceB r then + 1, tcConfig.GenerateOptimizationData + else + 2, true - // Save only the interface/optimization attributes of generated data - let intfDataResources, others = - allResources - |> List.partition (fun (_, r) -> IsSignatureDataResource r || IsSignatureDataResourceB r) - - let intfDataResources = - [ - for ccu, r in intfDataResources do - if tcConfig.GenerateSignatureData && not (isProvided ccu) then - r - ] - - let optDataResources, others = - others - |> List.partition (fun (_, r) -> IsOptimizationDataResource r || IsOptimizationDataResourceB r) - - let optDataResources = - [ - for ccu, r in optDataResources do - if tcConfig.GenerateOptimizationData && not (isProvided ccu) then - r - ] - - let otherResources = others |> List.map snd - - let result = intfDataResources @ optDataResources @ otherResources - result + if enabled && (order = 2 || not provided) then + yield order, (GetNameOfILModule m, r) + ] + |> List.groupBy fst + |> List.sortBy fst + |> List.collect (snd >> List.map snd) let moduls = ilxMainModule :: (List.map snd dependentILModules) @@ -248,7 +279,11 @@ let StaticLinkILModules ] ) TypeDefs = mkILTypeDefs (topTypeDef :: List.concat normalTypeDefs) - Resources = mkILResources (savedResources @ ilxMainModule.Resources.AsList()) + Resources = + savedResources + @ (ilxMainModule.Resources.AsList() |> List.map (fun r -> oldManifest.Name, r)) + |> linkMetadataResources oldManifest.Name + |> mkILResources NativeResources = savedNativeResources } diff --git a/src/Compiler/FSharp.Compiler.Service.fsproj b/src/Compiler/FSharp.Compiler.Service.fsproj index b274796ff49..ee9b164011b 100644 --- a/src/Compiler/FSharp.Compiler.Service.fsproj +++ b/src/Compiler/FSharp.Compiler.Service.fsproj @@ -534,6 +534,7 @@ + diff --git a/src/Compiler/Utilities/FileSystem.fs b/src/Compiler/Utilities/FileSystem.fs index 5fd055b525d..d9464a96fae 100644 --- a/src/Compiler/Utilities/FileSystem.fs +++ b/src/Compiler/Utilities/FileSystem.fs @@ -1092,9 +1092,11 @@ type ByteStorage(getByteMemory: unit -> ReadOnlyByteMemory) = static member FromByteMemory(bytes: ReadOnlyByteMemory) = ByteStorage(fun () -> bytes) static member FromByteMemoryAndCopy(bytes: ReadOnlyByteMemory, useBackingMemoryMappedFile: bool) = + let length = bytes.Length + if useBackingMemoryMappedFile then match MemoryMappedFile.TryFromByteMemory(bytes) with - | Some mmf -> ByteStorage(fun () -> ByteMemory.FromMemoryMappedFile(mmf).AsReadOnly()) + | Some mmf -> ByteStorage(fun () -> ByteMemory.FromMemoryMappedFile(mmf).Slice(0, length).AsReadOnly()) | _ -> let copiedBytes = ByteMemory.FromArray(bytes.ToArray()).AsReadOnly() ByteStorage.FromByteMemory(copiedBytes) diff --git a/src/FSharp.Build/FSharp.Build.fsproj b/src/FSharp.Build/FSharp.Build.fsproj index 90912e95fe2..a66d30ea404 100644 --- a/src/FSharp.Build/FSharp.Build.fsproj +++ b/src/FSharp.Build/FSharp.Build.fsproj @@ -46,6 +46,7 @@ + diff --git a/src/FSharp.Build/GenerateILLinkSubstitutions.fs b/src/FSharp.Build/GenerateILLinkSubstitutions.fs index 0478d36201d..7b6db4b87f6 100644 --- a/src/FSharp.Build/GenerateILLinkSubstitutions.fs +++ b/src/FSharp.Build/GenerateILLinkSubstitutions.fs @@ -2,9 +2,8 @@ namespace FSharp.Build -open System open System.IO -open System.Text +open FSharp.Compiler open Microsoft.Build.Framework open Microsoft.Build.Utilities @@ -34,58 +33,22 @@ type GenerateILLinkSubstitutions() = override this.Execute() = try - // Define the resource prefixes that need to be removed - let resourcePrefixes = - [| - // Signature variants - yield! - [| - for dataType in [| "Data"; "DataB" |] do - for compression in [| ""; "Compressed" |] do - yield $"FSharpSignature{compression}{dataType}" - |] + let xmlContent = + ILLinkSubstitutions.document this.AssemblyName (ILLinkSubstitutions.names this.AssemblyName) + |> string - // Optimization variants - yield! - [| - for dataType in [| "Data"; "DataB" |] do - for compression in [| ""; "Compressed" |] do - yield $"FSharpOptimization{compression}{dataType}" - |] - - // Info variants - yield "FSharpOptimizationInfo" - yield "FSharpSignatureInfo" - |] - - // Generate the XML content - let sb = StringBuilder(4096) // pre-allocate capacity - sb.AppendLine("") |> ignore - sb.AppendLine("") |> ignore - sb.AppendLine($" ") |> ignore - - // Add each resource entry with proper closing tag on the same line - for prefix in resourcePrefixes do - sb.AppendLine($" ") - |> ignore - - // Close assembly and linker tags - sb.AppendLine(" ") |> ignore - sb.AppendLine("") |> ignore - - let xmlContent = sb.ToString() - - // Create a file in the intermediate output path let outputFileName = Path.Combine(this.IntermediateOutputPath, "ILLink.Substitutions.xml") Directory.CreateDirectory(this.IntermediateOutputPath) |> ignore - File.WriteAllText(outputFileName, xmlContent) - // Create a TaskItem for the generated file - let item = TaskItem(outputFileName) :> ITaskItem - item.SetMetadata("LogicalName", "ILLink.Substitutions.xml") + if not (File.Exists outputFileName && File.ReadAllText(outputFileName) = xmlContent) then + File.WriteAllText(outputFileName, xmlContent) + let item = TaskItem(outputFileName.Replace("%", "%25")) :> ITaskItem + item.SetMetadata("LogicalName", "ILLink.Substitutions.xml") + item.SetMetadata("Type", "Non-Resx") + item.SetMetadata("WithCulture", "false") this.GeneratedItems <- [| item |] true with ex -> diff --git a/src/FSharp.Build/Microsoft.FSharp.NetSdk.targets b/src/FSharp.Build/Microsoft.FSharp.NetSdk.targets index d47a2757012..0a450e38698 100644 --- a/src/FSharp.Build/Microsoft.FSharp.NetSdk.targets +++ b/src/FSharp.Build/Microsoft.FSharp.NetSdk.targets @@ -210,7 +210,10 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - + diff --git a/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/CustomSubstitutions.xml b/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/CustomSubstitutions.xml new file mode 100644 index 00000000000..dd052add6c3 --- /dev/null +++ b/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/CustomSubstitutions.xml @@ -0,0 +1,3 @@ + + + diff --git a/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/SelfContained_Trimming_Test.fsproj b/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/SelfContained_Trimming_Test.fsproj index 8930dd44e5d..9bf819e945b 100644 --- a/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/SelfContained_Trimming_Test.fsproj +++ b/tests/AheadOfTime/Trimming/SelfContained_Trimming_Test/SelfContained_Trimming_Test.fsproj @@ -25,6 +25,8 @@ + + diff --git a/tests/AheadOfTime/Trimming/check.ps1 b/tests/AheadOfTime/Trimming/check.ps1 index 240ff643de3..5bf33243075 100644 --- a/tests/AheadOfTime/Trimming/check.ps1 +++ b/tests/AheadOfTime/Trimming/check.ps1 @@ -38,6 +38,15 @@ function CheckTrim($root, $tfm, $outputfile, $expected_len, $callerLineNumber) { # Checking that the trimmed outputfile binary is of expected size (needs adjustments if test is updated). $file = Get-Item (Join-Path $PSScriptRoot "${root}\bin\release\${tfm}\win-x64\publish\${outputfile}") + $metadata = [System.Reflection.Assembly]::LoadFile($file.FullName).GetManifestResourceNames() | + Where-Object { $_ -match '^FSharp(Signature|Optimization)' } + if ($metadata) { + $errors += "Metadata remains in ${outputfile}: $($metadata -join ', ')" + } + $app = [System.Reflection.Assembly]::LoadFile((Join-Path $file.DirectoryName "${root}.dll")) + if ($app.GetManifestResourceNames() -contains "custom.marker") { + $errors += "Custom resource-removal rule was not applied in ${root}" + } $file_len = $file.Length if ($expected_len -eq -1) { @@ -71,7 +80,7 @@ $allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outpu $allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174528 -callerLineNumber 71 # Check net9.0 trimmed assemblies with F# metadata resources removed -$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74 +$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7607296 -callerLineNumber 74 # Report all errors and exit with failure if any occurred if ($allErrors.Count -gt 0) { diff --git a/tests/FSharp.Compiler.ComponentTests/StaticLinking/StaticLinking.fs b/tests/FSharp.Compiler.ComponentTests/StaticLinking/StaticLinking.fs index 1378dca0a5b..358d682cf8b 100644 --- a/tests/FSharp.Compiler.ComponentTests/StaticLinking/StaticLinking.fs +++ b/tests/FSharp.Compiler.ComponentTests/StaticLinking/StaticLinking.fs @@ -26,6 +26,7 @@ module First FSharp """ module Second type Number = IntNumber of int | DoubleNumber of double + let getMyRecord () = First.getMyRecord() let getMyIntDU() = IntNumber 10 @@ -34,37 +35,44 @@ module Second |> withOptimize |> asLibrary - [] - let ``staticlinking_multiple_fs_libraries`` () = - let tripleQuote = "\"\"\"" - let expectedRecord = """{ A = "Hello, World!"; B = 1.027M; C = 1028; D = 1.029 }""".Replace("\n", ";") - let expectedIntDU = """IntNumber 10""".Replace("\n", ";") - let expectedDoubleDU = """DoubleNumber 12.0""".Replace("\n", ";") + [] + [] + [] + let ``staticlinking_multiple_fs_libraries`` chained = + let withSubstitutions name compilation = + let path = TestFramework.getTemporaryFileName() + File.WriteAllText(path, $"""""") + compilation |> withName name |> withOptions [ "--compressmetadata-"; $"--resource:{path},ILLink.Substitutions.xml" ] - FSharp ("""open System -open First -open Second - -let expectedRecord = $(expectedRecord) -let actualRecord = (sprintf "%A" (getMyRecord())).Replace("\r\n", "\n").Replace("\n", ";") -if expectedRecord <> actualRecord then - raise (new Exception $"Text failed:{Environment.NewLine}Expected: '{expectedRecord}'{Environment.NewLine}Actual: '{actualRecord}'{Environment.NewLine}") + let first = myRecordLibrary |> withSubstitutions "First" + let second = + myDiscriminatedUnionLibrary |> withSubstitutions "Second" + |> withReferences [ first.WithStaticLink(chained) ] -let expectedIntDU = $(expectedIntDU) -let actualIntDU = (sprintf "%A" (getMyIntDU())).Replace("\r\n", "\n").Replace("\n", ";") -if expectedIntDU <> actualIntDU then - raise (new Exception $"Text failed:{Environment.NewLine}Expected: '{expectedIntDU}'{Environment.NewLine}Actual: '{actualIntDU}'{Environment.NewLine}") + FSharp """open System +open Second -let expectedDoubleDU = $(expectedDoubleDU) -let actualDoubleDU = (sprintf "%A" (getMyDoubleDU())).Replace("\r\n", "\n").Replace("\n", ";") -if expectedDoubleDU <> actualDoubleDU then - raise (new Exception $"Text failed:{Environment.NewLine}Expected: '{expectedDoubleDU}'{Environment.NewLine}Actual: '{actualDoubleDU}'{Environment.NewLine}") - """.Replace("$(expectedRecord)", tripleQuote + expectedRecord + tripleQuote) - .Replace("$(expectedIntDU)", tripleQuote + expectedIntDU + tripleQuote) - .Replace("$(expectedDoubleDU)", tripleQuote + expectedDoubleDU + tripleQuote)) +let check expected value = + let actual = (sprintf "%A" value).Replace("\r\n", "\n").Replace("\n", ";") + if actual <> expected then failwithf "Expected %s, got %s" expected actual +check "{ A = \"Hello, World!\"; B = 1.027M; C = 1028; D = 1.029 }" (getMyRecord()) +check "IntNumber 10" (getMyIntDU()) +check "DoubleNumber 12.0" (getMyDoubleDU()) +let resources = Reflection.Assembly.GetExecutingAssembly().GetManifestResourceNames() +if Array.filter ((=) "ILLink.Substitutions.xml") resources |> Array.length <> 1 then + failwith "Static linking must produce one substitutions resource" +let xml = + use reader = new IO.StreamReader(Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("ILLink.Substitutions.xml")) + reader.ReadToEnd() +for name in ["First"; "Second"; "Final"] do + if not (xml.Contains("FSharpSignatureData." + name)) then failwith "A linked library lost its removal rule" + """ |> asExe |> withOptimize - |> withReferences [ myRecordLibrary.WithStaticLink(true) ] - |> withReferences [ myDiscriminatedUnionLibrary.WithStaticLink(true) ] + |> withSubstitutions "Final" + |> withReferences [ + if not chained then first.WithStaticLink(true) + second.WithStaticLink(true) + ] |> compileExeAndRun |> shouldSucceed diff --git a/tests/FSharp.Compiler.Service.Tests/ByteMemoryTests.fs b/tests/FSharp.Compiler.Service.Tests/ByteMemoryTests.fs index 8b24ccf7754..8e8084639a9 100644 --- a/tests/FSharp.Compiler.Service.Tests/ByteMemoryTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/ByteMemoryTests.fs @@ -8,8 +8,12 @@ module ByteMemoryTests = open FSharp.Compiler.IO [] - let ``ByteMemory.CreateMemoryMappedFile succeeds with byte length of zero`` () = - - let memory = ByteMemory.Empty.AsReadOnly() - let newMemory = ByteStorage.FromByteMemoryAndCopy(memory, useBackingMemoryMappedFile = true).GetByteMemory() - Assert.shouldBe 0 newMemory.Length + let ``Mapped storage preserves its payload length`` () = + for length in [0; 1; 4097] do + let bytes = Array.init length (fun i -> byte (i % 251)) + let memory = ByteMemory.FromArray(bytes).AsReadOnly() + let storage = ByteStorage.FromByteMemoryAndCopy(memory, useBackingMemoryMappedFile = true) + for _ in 1..2 do + let actual = storage.GetByteMemory() + Assert.shouldBe length actual.Length + Assert.shouldBe bytes (actual.ToArray()) diff --git a/tests/fsharp/Compiler/CodeGen/EmittedIL/StaticLinkTests.fs b/tests/fsharp/Compiler/CodeGen/EmittedIL/StaticLinkTests.fs index 2f39f559e26..14434b3eb85 100644 --- a/tests/fsharp/Compiler/CodeGen/EmittedIL/StaticLinkTests.fs +++ b/tests/fsharp/Compiler/CodeGen/EmittedIL/StaticLinkTests.fs @@ -81,8 +81,15 @@ printfn "%A" y CompilerAssert.Execute module2 - [] - let ``Static link quotes in multiple modules``() = + [] + [] + [] + let ``Static link quotes and metadata rules in multiple modules`` optimized = + let options = if optimized then [|"--optimize+"|] else [||] + let resource name = + let path = TestFramework.getTemporaryFileName() + File.WriteAllText(path, $"""""") + $"--resource:{path},ILLink.Substitutions.xml" let module1 = let source = """ @@ -105,80 +112,14 @@ type C() = [] static member F x = (C(), System.DateTime.Now) """ - Compilation.Create(source, Library, options = [| |]) - - let module2 = let source = - """ - -let a = Module1.Test.bar() -let b = sprintf "%A" (Module1.Test.run()) - -let test1 = (a=b) -type D() = - - [] - static member F x = (Module1.C(), D(), System.DateTime.Now) - - -let z2 = Quotations.Expr.TryGetReflectedDefinition(typeof.GetMethod("F")) -let s2 = (sprintf "%2000A" z2) -let test2 = (s2 = "Some Lambda (x, NewTuple (NewObject (C), PropertyGet (None, Now, [])))") - -let z3 = Quotations.Expr.TryGetReflectedDefinition(typeof.GetMethod("F")) -let s3 = (sprintf "%2000A" z3) -let test3 = (s3 = "Some Lambda (x, NewTuple (NewObject (C), NewObject (D), PropertyGet (None, Now, [])))") - -#if EXTRAS -// Add some references to System.ValueTuple, and add a test case which statically links this DLL -let test4 = struct (3,4) -let test5 = struct (z2,z3) -#endif - -if not test1 then - stdout.WriteLine "*** test1 FAILED"; - eprintf "FAILED, in-module result %s is different from out-module call %s" a b - -if not test2 then - stdout.WriteLine "*** test2 FAILED"; - eprintf "FAILED, %s is different from expected" s2 -if not test3 then - stdout.WriteLine "*** test3 FAILED"; - eprintf "FAILED, %s is different from expected" s3 - - -if test1 && test2 && test3 then () -else failwith "Test Failed" - """ - Compilation.Create(source, Exe, cmplRefs=[CompilationReference.CreateFSharp(module1, staticLink=true)]) - - CompilerAssert.Execute(module2, ignoreWarnings=true) - - [] - let ``Static link quotes in multiple modules - optimized``() = - let module1 = - let source = - """ -module Module1 - -module Test = - let inline run() = - <@ fun (output:'T[]) (input:'T[]) (length:int) -> - let start = 0 - let mutable i = start - while i < length do - output.[i] <- input.[i] - i <- i + 1 @> - - let bar() = - sprintf "%A" (run()) - -type C() = - - [] - static member F x = (C(), System.DateTime.Now) - """ - Compilation.Create(source, Library, [|"--optimize+"; "--nowarn:3366"|]) + if optimized then source.Replace("output[i]", "output.[i]").Replace("input[i]", "input.[i]") + else source + let options = + [| yield! options + if optimized then yield "--nowarn:3366" + yield resource "QuotedLibrary" |] + Compilation.Create(source, Library, options = options, name = "QuotedLibrary") let module2 = let source = @@ -222,8 +163,18 @@ if not test3 then if test1 && test2 && test3 then () else failwith "Test Failed" - """ - Compilation.Create(source, Exe, [|"--optimize+"|], TargetFramework.Current, [CompilationReference.CreateFSharp(module1, staticLink=true)]) +let resources = typeof.Assembly.GetManifestResourceNames() +if resources |> Array.filter ((=) "ILLink.Substitutions.xml") |> Array.length <> 1 then failwith "Metadata rules must compose without losing quotation resources" +let xml = + use reader = new System.IO.StreamReader(typeof.Assembly.GetManifestResourceStream("ILLink.Substitutions.xml")) + reader.ReadToEnd() +for owner in ["QuotedLibrary"; "QuotedApp"] do + let expected = + resources |> Array.filter (fun name -> name = $"FSharpSignatureData.{owner}" || name = $"FSharpSignatureCompressedData.{owner}") + if expected.Length = 0 || Array.exists (fun name -> not (xml.Contains($"