Skip to content

[WIP] Auto-generate IlLink.Substitutions.xml to Remove F# Metadata Resources #18592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented May 22, 2025

Thanks for assigning this issue to me. I'm starting to work on it and will keep this PR's description up to date as I form a plan and make progress.

Original issue description:

Background

F# assemblies contain embedded resources for signature and optimization data that can significantly increase assembly size. These resource names are templates that get combined with the actual assembly name. To reduce the size of trimmed F# assemblies, we need to automatically remove these metadata resources during IL linking:
(the asterisk is not there, this is where the project name goes)

The full set of F#-compiler-generated prefixes currently is:
FSharpSignatureData.*
FSharpSignatureDataB.*
FSharpSignatureCompressedData.*
FSharpSignatureCompressedDataB.*

FSharpOptimizationData.*
FSharpOptimizationDataB.*
FSharpOptimizationCompressedData.*
FSharpOptimizationCompressedDataB.*

FSharpOptimizationInfo.*
FSharpSignatureInfo.*

Files to Modify

1. Create new MSBuild task: src/FSharp.Build/GenerateILLinkSubstitutions.fs

Create a new task that follows the pattern of existing embedded resource generators. Study how it creates TaskItem objects for embedded resources.

The task should:

  • Inherit from Microsoft.Build.Utilities.Task
  • Take the assembly name as input
  • Generate XML content with actual resource names (not patterns with asterisks)
  • Create the content in memory and add it as an EmbeddedResource item

Example of what the generated XML should contain (using actual assembly name):

<linker>
  <assembly fullname="MyProject">
    <resource name="FSharpOptimizationData.MyProject" action="remove" />
    <resource name="FSharpSignatureData.MyProject" action="remove" />
    <resource name="FSharpOptimizationCompressedData.MyProject" action="remove" />
    <resource name="FSharpSignatureCompressedData.MyProject" action="remove" />
    <resource name="FSharpOptimizationDataB.MyProject" action="remove" />
    <resource name="FSharpSignatureDataB.MyProject" action="remove" />
    <resource name="FSharpOptimizationCompressedDataB.MyProject" action="remove" />
    <resource name="FSharpSignatureCompressedDataB.MyProject" action="remove" />
    <resource name="FSharpOptimizationInfo.MyProject" action="remove" />
    <resource name="FSharpSignatureInfo.MyProject" action="remove" />
  </assembly>
</linker>

2. Update src/FSharp.Build/FSharp.Build.fsproj

Add the new task file to the compilation list: 3

<Compile Include="GenerateILLinkSubstitutions.fs" />

3. Add target to src/FSharp.Build/Microsoft.FSharp.NetSdk.targets

Create a new target that runs during every compilation. Looking at the existing props file structure, add:

<UsingTask TaskName="GenerateILLinkSubstitutions" AssemblyFile="$(FSharpBuildTasksAssembly)" />

<Target Name="GenerateFSharpILLinkSubstitutions" BeforeTargets="CoreCompile">
  <GenerateILLinkSubstitutions 
    AssemblyName="$(AssemblyName)"
    IntermediateOutputPath="$(IntermediateOutputPath)" />
</Target>

Implementation Steps

  1. Study the existing embedded resource pattern: Look at how it generates embedded resources programmatically.

  2. Understand resource name construction: The patterns are templates. You need to append the actual assembly name to create the full resource names (e.g., "FSharpOptimizationData." + assemblyName).

  3. Create the MSBuild task:

    • Follow the structure of existing tasks in src/FSharp.Build/
    • Generate XML content in memory
    • Create TaskItem with the XML content as an embedded resource
    • Set appropriate metadata like LogicalName="ILLink.Substitutions.xml"
    • Use action="remove" for all resource entries
  4. Wire into the build system:

    • Add the target to run during normal compilation
    • Use BeforeTargets="CoreCompile" to ensure it runs at the right time
    • Pass the assembly name from MSBuild properties
  5. ** Create tests for the behavior.

Look at the folder https://github.com/dotnet/fsharp/tree/main/tests/AheadOfTime/Trimming to see existing trimming tests.
You will need to adjust the test setup to make it use freshly built FSharp.Build.dll as well as the adjusted props and targets.

Notes

This approach generates the substitution file as an embedded resource during compilation, ensuring F# metadata resources are removed during IL linking to reduce the size of trimmed F# assemblies.

Fixes #18591.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@T-Gro
Copy link
Member

T-Gro commented May 22, 2025

@copilot please do it

Copilot finished work on behalf of T-Gro May 22, 2025 14:31
Copy link
Contributor

✅ No release notes required

@T-Gro
Copy link
Member

T-Gro commented May 22, 2025

@copilot try now

Copilot finished work on behalf of T-Gro May 22, 2025 15:18
@T-Gro
Copy link
Member

T-Gro commented May 23, 2025

@copilot try now

Copy link
Contributor Author

Copilot AI commented May 23, 2025

@copilot try now

I've implemented the requested task to auto-generate ILLink.Substitutions.xml for removing F# metadata resources during trimming. The implementation includes:

  1. A new GenerateILLinkSubstitutions.fs task that generates the XML with resource name patterns for the current assembly
  2. Updated FSharp.Build.fsproj to include this new file
  3. Added a target in Microsoft.FSharp.NetSdk.targets that runs the task during build

Changes are in commit 3aff8db. Next I'll be working on adding tests in the AheadOfTime/Trimming folder.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • x3yvsblobprodcus370.vsblob.vsassets.io
    • Triggering command: dotnet build src/FSharp.Build/FSharp.Build.fsproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of T-Gro May 23, 2025 08:58
|]

// Generate the XML content
let sb = StringBuilder()
Copy link
Member

Choose a reason for hiding this comment

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

The size can be pre-detrmined upfront, use an overload of StringBuilder that takes initial capacity.

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've updated the code to pre-allocate the StringBuilder capacity by using the appropriate constructor overload with an initial capacity of 4096, which should be more than enough for our XML content. This change is in commit 74bce89.

Copy link
Contributor Author

Copilot AI commented May 23, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • x3yvsblobprodcus370.vsblob.vsassets.io
    • Triggering command: dotnet build src/FSharp.Build/FSharp.Build.fsproj (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of T-Gro May 23, 2025 09:29
@T-Gro
Copy link
Member

T-Gro commented Jun 3, 2025

@copilot :

The script check.ps1 reports an error, but it does not output the actual value. We need the actual value so that you can set it as the real expectation (I do realize that the value of -1 was just a placeholder).

Change the script so that it reports the actual values of sizes as well, and so that we can see them in build failures logs.

Right now the logs were just:

Publish and Execute: net9.0 - FSharpMetadataResource_Trimming_Test
Expecting -1
CheckTrim : Build failed with exit code 1
At D:\a\_work\1\s\tests\AheadOfTime\Trimming\check.ps1:52 char:1

Copy link
Contributor Author

Copilot AI commented Jun 3, 2025

@copilot :

The script check.ps1 reports an error, but it does not output the actual value. We need the actual value so that you can set it as the real expectation (I do realize that the value o...

I've updated the check.ps1 script to report the actual file sizes when the expected length is the placeholder value -1. Now when the FSharpMetadataResource_Trimming_Test runs, it will output a message like "Actual net9.0 - trimmed FSharpMetadataResource_Trimming_Test.dll length: [actual_bytes] Bytes (expected length is placeholder -1, update test with this actual value)" so you can see the actual size in the build logs and update the test accordingly. Changes are in commit 28d99ca.

Copilot finished work on behalf of T-Gro June 3, 2025 08:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: New
Development

Successfully merging this pull request may close these issues.

Auto-generate IlLink.Substitutions.xml to Remove F# Metadata Resources
2 participants