Skip to content

Conversation

@T-Gro
Copy link
Member

@T-Gro T-Gro commented Feb 10, 2026

⚡ Fast Build from Cache — Prototype

What

Skips fsc entirely for dev builds by emitting DLLs directly from FSharpChecker's in-memory typecheck cache. Dev-loop only — no optimization, no PDB, not for shipping.

How to use

dotnet build /p:FastBuildFromCache=true

Currently scoped to FSharp.Compiler.Service and FSharp.Compiler.ComponentTests by AssemblyName guard. All other projects build normally. Falls back to normal fsc if the cache server is down.

Why it's fast

A persistent diagnostics server holds a warm FSharpChecker with useTransparentCompiler=true. The TransparentCompiler 📚 caches typecheck results per-file, keyed by content hashes. When a file changes:

  • A dependency 🔀 graph built at parse time (DependencyResolution.mkGraph) determines which files actually depend on the changed one — via a trie of top-level namespaces/modules and open resolution
  • Only those files get re-typechecked; everything else is served from AsyncMemoize 📚 caches (TcIntermediate, ParseFile, DependencyGraph)
  • The new FSharpChecker.CompileFromCheckedProject then goes straight from 📚 cached CheckedImplFile[] → ILX codegen → WriteILBinaryFile → DLL, skipping parse/check/optimize

Edit file 300 of 420 → files 1–299 stay cached, and peers with no dependency edge to file 300 also stay cached.

Key pieces

What Where
MSBuild interception eng/targets/FastBuildFromCache.targetsBeforeTargets="CoreCompile", sets SkipCompilerExecution=true on success
📚 Cache→DLL API FSharpChecker.CompileFromCheckedProject in service.fs
Server compile handler Server.fs"compile" command
🔀 Project routing ProjectRouting.fs — maps source files → .fsproj
📚 Multi-project cache ProjectManager.fsConcurrentDictionary keyed by fsproj path + mtime

T-Gro added 15 commits February 8, 2026 20:27
Add internal CompilationData member on FSharpCheckProjectResults to expose
cached typecheck data (TcConfig, TcGlobals, TcImports, CcuThunk, TopAttribs,
ILAssemblyRef, CheckedImplFiles).

Add public CompileFromCheckedProject method on FSharpChecker that takes check
results and an output path, then runs the backend pipeline (sigdata encoding,
IL generation, module creation, binary writing) without re-parsing or
re-typechecking. Skips optimization and PDB generation for fast dev-loop use.
Save and restore generatedCcu.Contents.Attribs around the compilation
to prevent repeated CompileFromCheckedProject calls from appending
assembly attributes to the shared cached CCU on each invocation.

Includes regression test and surface area baseline update.
Add a 'compile' case to the handleRequest match block in Server.fs that:
- Extracts project and output paths from JSON
- Resolves project options via projectMgr
- Runs ParseAndCheckProject and checks HasCriticalErrors
- Calls CompileFromCheckedProject on success
- Returns 'OK' on success, 'ERROR: ...' on failure
Use Proto-built FSharp.Compiler.Service.dll (from artifacts/Bootstrap/fsc/)
when available, falling back to NuGet PackageReference otherwise. This allows
the diagnostics server to access CompileFromCheckedProject API from current
source before it ships in a release.
- ProjectManager: Replace single option cache with Dictionary<string, DateTime * FSharpProjectOptions>
- ProjectManager: Add path normalization via Path.GetFullPath
- ProjectManager: Invalidate accepts optional fsproj path (one or all)
- Server: Add resolveProject helper mapping source files to fsproj paths
- Server: getOptions now accepts file path parameter
- Server: All handlers (parseOnly, check, findRefs, typeHints) pass file to getOptions
- Server: checkProject accepts optional 'project' JSON field, defaults to FCS fsproj
- Server: Remove hardcoded fsproj variable
- ProjectManager.fs: Dictionary<string, DateTime * FSharpProjectOptions> cache
  with path normalization and optional Invalidate(?fsprojPath)
- ProjectRouting.fs: extracted resolveProject for testability, maps source
  files to fsproj (ComponentTests or FCS)
- Server.fs: uses ProjectRouting.resolveProject, all handlers route via file path
- checkProject accepts optional 'project' JSON field, defaults to FCS fsproj
- Tests: 10 tests covering resolveProject mapping and ProjectManager.Invalidate
- Add CacheCount, HasCachedProject, InjectTestEntry to ProjectManager for testability
- Rewrite ProjectManagerTests with behavioral assertions (cache population, selective/full invalidation, path normalization)
- Refactor ResolveProjectTests to use Theory/InlineData, eliminating copy-paste
- Shared createManager/dummyOptions helpers eliminate repeated setup
- 13 tests: 7 ProjectManager + 6 routing (all pass)
… tests

- Mark CacheCount, HasCachedProject, InjectTestEntry as internal
- Add InternalsVisibleTo for test project in server fsproj
- Add test: Invalidate normalizes path before removal
- Add test: InjectTestEntry overwrites existing entry (idempotency)
- Add test: resolveProject with out-of-repo file defaults to FCS
- Update guard condition in FastBuildFromCache.targets to also match
  AssemblyName=='FSharp.Compiler.ComponentTests'
- Add Import of FastBuildFromCache.targets to FSharpTests.Directory.Build.targets
  (mirrors existing import in FSharpBuild.Directory.Build.targets)
- Fix XML comment containing '--compile' (invalid in XML comments)
- Update header/guard comments to mention ComponentTests
CODE-QUALITY:
- Fix unused outPath binding in Server.fs compile handler (let! _ =)
- Improve ProjectRouting.resolveProject to use StartsWith prefix check
  instead of fragile String.Replace, with StringComparison.Ordinal
- Add XML doc comment to resolveProject

NO-LEFTOVERS:
- Remove stale src/FastBuildFromCache.targets (old approach, superseded by eng/targets/)
- Remove leftover plan files (FAST_COMPILE_PLAN.md, REUSABLE_COMPILE_PLAN.md)
- Revert incorrect Directory.Build.targets import (wrong path)
- Add missing FSharpBuild.Directory.Build.targets import for src/ projects
- Include uncommitted shell script --compile handler and SKILL.md docs

TEST-COVERAGE:
- Add DesignTimeBuildTests for config defaults and DtbResult construction
- Add ProjectRouting tests: vsintegration path, FSharp.Core path,
  trailing slash, repoRoot-as-substring edge case
- Add ProjectManager tests: 3-project coexistence, selective invalidation

TEST-CODE-QUALITY:
- Tests use descriptive names and verify specific behaviors
- Edge case tests validate the String.Replace fix

All 24 tests pass. Server builds with 0 warnings.
…mCheckedProject internal

CODE-QUALITY: Extract normalizeAssemblyRefs as TcImports.NormalizeAssemblyRef
member to eliminate duplication between service.fs and fsc.fs. Make
CompileFromCheckedProject internal since it's a dev-loop-only API.
Add InternalsVisibleTo for FSharpDiagServer.

NO-LEFTOVERS: Remove redundant comment in ResolveProjectTests.fs.
Unstage .ralph/ tracking files.

Surface area baseline updated to reflect internal visibility change.
…, TEST-CODE-QUALITY, TEST-COVERAGE verifier feedback

- CODE-QUALITY: Break long line in Server.fs, use InvalidOperationException
  instead of failwith in CompileFromCheckedProject, add project-not-found
  validation in compile handler, add caching semantics comment
- HONEST-ASSESSMENT: Update SKILL.md to reflect ComponentTests support
- NO-LEFTOVERS: Remove unused InternalsVisibleTo from FSharpDiagServer.fsproj,
  update shell script usage/header to include --compile flag
- TEST-CODE-QUALITY: Consolidate duplicate Fact tests into Theory InlineData,
  add meaningful DesignTimeBuildTests for config overrides and edge cases
- TEST-COVERAGE: Add ResolveProjectOptions error path test, add FCS fallback
  and ComponentTests boundary routing tests, add DtbConfig edge cases
PERF:
- Replace Dictionary+lock with ConcurrentDictionary in ProjectManager
  to eliminate lock contention on concurrent cache lookups
- Use HashSet instead of ResizeArray for O(1) symbol name lookup in findRefs
- Avoid Array.append allocation when one diagnostics array is empty

TEST-COVERAGE:
- Add concurrent InjectTestEntry+Invalidate thread safety test
- Add concurrent InjectTestEntry from multiple threads test
- Add Invalidate-during-concurrent-reads test
- Add error-does-not-pollute-cache test
- Include leftover test consolidation from Fixup #2
@github-actions
Copy link
Contributor

❗ Release notes required

@T-Gro,

Caution

No release notes found for the changed paths (see table below).

Please make sure to add an entry with an informative description of the change as well as link to this pull request, issue and language suggestion if applicable. Release notes for this repository are based on Keep A Changelog format.

The following format is recommended for this repository:

* <Informative description>. ([PR #XXXXX](https://github.com/dotnet/fsharp/pull/XXXXX))

See examples in the files, listed in the table below or in th full documentation at https://fsharp.github.io/fsharp-compiler-docs/release-notes/About.html.

If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.

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

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/10.0.300.md No release notes found or release notes format is not correct

… filewatcher pre-warming, --times profiling

- NormalizeAssemblyRef on TcImports is now member internal (no public API change)
- CompileFromCheckedProject uses minimal optimizer with mandatory lowering passes
  (OptimizeImplFile + LowerLocalMutables + LowerCalls, no detuple/TLR/extra loops)
- Added ReportTime instrumentation for --times profiling of emit phases
- FastBuildFromCache.targets: added Inputs/Outputs mirroring CoreCompile for
  proper MSBuild incremental skip, touch all CoreCompile outputs after cache emit
- Diagnostics server: filewatcher pre-warming with 5s throttle on src/Compiler/
- Diagnostics server: compile handler with --compile flag, DTB caching, resource embedding
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.

1 participant