-
Notifications
You must be signed in to change notification settings - Fork 849
WIP : Experiment [this repo only] : Fast and unsafe development time compilation out of FSharpChecker caches #19267
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
base: main
Are you sure you want to change the base?
Conversation
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
❗ Release notes requiredCaution 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:
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
|
…fsharp into feature/langserver-skill
… 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
⚡ Fast Build from Cache — Prototype
What
Skips
fscentirely for dev builds by emitting DLLs directly fromFSharpChecker's in-memory typecheck cache. Dev-loop only — no optimization, no PDB, not for shipping.How to use
Currently scoped to
FSharp.Compiler.ServiceandFSharp.Compiler.ComponentTestsbyAssemblyNameguard. All other projects build normally. Falls back to normalfscif the cache server is down.Why it's fast
A persistent diagnostics server holds a warm
FSharpCheckerwithuseTransparentCompiler=true. The TransparentCompiler 📚 caches typecheck results per-file, keyed by content hashes. When a file changes:DependencyResolution.mkGraph) determines which files actually depend on the changed one — via a trie of top-level namespaces/modules andopenresolutionAsyncMemoize📚 caches (TcIntermediate,ParseFile,DependencyGraph)FSharpChecker.CompileFromCheckedProjectthen goes straight from 📚 cachedCheckedImplFile[]→ ILX codegen →WriteILBinaryFile→ DLL, skipping parse/check/optimizeEdit file 300 of 420 → files 1–299 stay cached, and peers with no dependency edge to file 300 also stay cached.
Key pieces
eng/targets/FastBuildFromCache.targets—BeforeTargets="CoreCompile", setsSkipCompilerExecution=trueon successFSharpChecker.CompileFromCheckedProjectinservice.fsServer.fs—"compile"commandProjectRouting.fs— maps source files →.fsprojProjectManager.fs—ConcurrentDictionarykeyed by fsproj path + mtime