Skip to content

Import: share a referenced project's CCU instead of pickling it - #20416

Open
auduchinok wants to merge 2 commits into
dotnet:mainfrom
auduchinok:il-projectReferenceHandover
Open

Import: share a referenced project's CCU instead of pickling it#20416
auduchinok wants to merge 2 commits into
dotnet:mainfrom
auduchinok:il-projectReferenceHandover

Conversation

@auduchinok

@auduchinok auduchinok commented Sep 1, 2026

Copy link
Copy Markdown
Member

A project reference pickles its signature once and every consumer unpickles its own copy: five projects
referencing one hub hold five copies of its signature TAST. The pickled tree is already in the shape
consumers need, so offer it directly through IImportedProjectCcu, and pickle only when something asks.
Depends on the shared imported assemblies change (#20296), whose keys let a consumer establish it will hold
the same ccus.

Four things make it usable:

  • Remapped against a ccu made for this view, not the project's live one, which a consumer would else hold
    whole.
  • PruneExportedSignatureInPlace brings it to the shape unpickling produces: no value definitions, no
    display-only data, no compiled-representation cache. Rescoping happens in the same traversal that builds
    the tree - ILScopeRef.Local is what internal is rooted at, so a consumer would otherwise read the
    reference's internals as its own.
  • Every non-local reference is re-pointed at the reading project's ccu of the same name, as u_ccuref does
    for a consumer reading the bytes; a name the reader lacks keeps ours. This is what lets a consumer on
    another framework take the contents.
  • What each name bound to is recorded, and a second consumer takes that copy only where it resolves all of
    them alike. Consumers that disagree get a copy each, capped at eight.

Binding runs once the batch is registered and before anything relinks: an assembly unpickled beside it
resolves the names it mentions against what is registered, and a delayed CCU is an error there rather than
a wait.

Solution Projects main This Retained
ReSharper.FSharp 10 385.94 MB 304.64 MB -81.30 MB (-21.1%)
Fantomas 8 350.42 MB 275.79 MB -74.63 MB (-21.3%)
FSharp.Compiler.Service 14 879.43 MB 818.39 MB -61.04 MB (-6.9%)
FsToolkit.ErrorHandling 8 87.77 MB 69.36 MB -18.42 MB (-21.0%)
Oxpecker 16 131.67 MB 114.24 MB -17.43 MB (-13.2%)
Prime 5 112.22 MB 97.93 MB -14.29 MB (-12.7%)
IcedTasks 7 96.97 MB 92.37 MB -4.61 MB (-4.8%)
consoleapp 1 29.38 MB 29.37 MB 0.00 MB

The numbers are smaller due to other FSharpChecker creation flags.

consoleapp has no project references. Not all of this is the handover: switching the handover alone off and
on inside one build moves the compiler's own solution by 2 MB rather than 61, the rest coming from dropping
the language version out of the import reuse key, which lets projects that differ only in it share one
imported graph.

Also fixes a check-then-act race in BackgroundCompiler that predates this: builders were cached without a
second look under the gate, so callers arriving together each built the project, and a project built twice
is two of everything it imports. Nothing here survives that.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@github-actions github-actions Bot added the AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files label Sep 1, 2026
auduchinok and others added 2 commits September 2, 2026 16:31
A project reference pickles its signature once and every consumer unpickles its own copy: five projects
referencing one hub hold five copies of its signature TAST. The pickled tree is already in the shape
consumers need, so offer it directly through IImportedProjectCcu, and pickle only when something asks.

Four things make it usable:

- Remapped against a ccu made for this view, not the project's live one, which a consumer would else hold
  whole.
- PruneExportedSignatureInPlace brings it to the shape unpickling produces: no value definitions, no
  display-only data, no compiled-representation cache.
- Every non-local reference is re-pointed at the reading project's ccu of the same name, as unpickling
  does; a name the reader lacks keeps ours. So a consumer on another framework takes it too.
- What each name bound to is recorded, and a second consumer takes that copy only where it resolves all of
  them alike.

Binding runs once the batch is registered and before anything relinks: an assembly unpickled beside it
resolves the names it mentions against what is registered, and a delayed CCU is an error there.

Depends on sharing imported assemblies (dotnet#20296). Also fixes a check-then-act race in BackgroundCompiler:
builders were cached without a second look under the gate, so callers arriving together each built the
project.

Retained memory, under editor options. Diagnostics identical with the change off and on:

  Fantomas           8 proj  350.2 -> 275.7 MB  -74.5 (-21.3%)
  ReSharper.FSharp  10 proj  384.9 -> 304.6 MB  -80.3 (-20.9%)
  FsToolkit          8 proj   87.7 ->  70.1 MB  -17.6 (-20.1%)
  Oxpecker          16 proj  131.7 -> 114.2 MB  -17.5 (-13.3%)
  Prime              5 proj  112.2 ->  97.9 MB  -14.3 (-12.7%)
  IcedTasks          7 proj   97.0 ->  92.4 MB   -4.6  (-4.7%)
  FCS solution      14 proj  820.8 -> 818.7 MB   -2.1  (-0.2%)
  consoleapp         1 proj   29.4 ->  29.4 MB    0.0

The FCS solution gains least: nearly every edge in it crosses framework import layers, so each consumer
rebuilds a copy of its own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@auduchinok
auduchinok force-pushed the il-projectReferenceHandover branch from befdc7b to d62a9b1 Compare September 2, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-Tooling-Check-Scanned-Clean Tooling check: diff analyzed, no interesting infrastructure files

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

1 participant