IL: add ILPreNamespace, make ILPreTypeDef creation lazy - #20092
Open
auduchinok wants to merge 7 commits into
Open
IL: add ILPreNamespace, make ILPreTypeDef creation lazy#20092auduchinok wants to merge 7 commits into
auduchinok wants to merge 7 commits into
Conversation
Hold the child namespaces + their by-name lookup in a single InterruptibleLazy<struct (ILPreNamespace[] * Dictionary<..>)> instead of two separate lazies, and share one pre-computed empty instance for namespace-less levels (every nested-type container - the vast majority of ILTypeDefs). On a single-file FCS check against a project with ~486 references this cut the namespace InterruptibleLazy wrappers from ~171,600 to ~8,800 and the retained IL-namespace machinery from ~16.5 MB to ~11.3 MB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up cleanup on the ILPreNamespace work: - ImportILNamespaceLevel now buckets a level's flat entries and child pre-namespaces in one pass instead of via multisetDiscriminateAndMap - drop the now-unused readBlobHeapAsSplitTypeName/seekReadPreTypeDef and the mkILPreTypeDefEntry signature entry - TryFindPreTypeDef is a self-recursive member; simplify namespacesOf - flattening a grouped table (AsArray/AsList/the enumerator) restores the TypeDef row order, which static linking relies on to emit the types of a --standalone assembly in the reader's order - tests for laziness, lookup, hybrid levels, duplicate namespace nodes, imported entity order and metadata order of a read module Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Flattening a grouped table walks namespace by namespace, which is not the TypeDef row order when a namespace is split across the table. Doing the restoration inside ILTypeDefs cost a flag in the namespaces payload plus a sort in AllPreTypeDefs, in a memory-critical type; static linking is the only order-sensitive consumer, and one stable sort by MetadataIndex there covers it. Verified on a --standalone build: all 296 of FSharp.Core's top-level types are emitted in FSharp.Core's metadata order. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Temporary commit of the in-progress work on top of 9e331e8: - il.fs/il.fsi: one namespace-bucketing implementation (ILPreNamespaceOfEntries) reached both by the grouped reader and, via ilTypeDefsAsNamespaceLevel, by any flat table; DelayInitValue extracted into illib for the three hand-rolled lazies; fixed a race where AsArrayOfPreNamespaces could return an empty array. - import.fs: ImportILNamespaceLevel and its copy of the bucketing removed; ImportILTypeDefs walks the two arrays directly. Plus three unrelated memory tweaks (Nullness.GetFlags, shared noTypars, closure capture in ImportILTypeDef). - CompilerImports.fs: skip addConstraintSources for non-F# CCUs (also ported to its own branch off main, to be proposed and measured separately). - tests: namespace/cancellation tests updated, SurfaceArea baseline. - benchmarks: NamespaceImportBenchmarks with the retained-memory and retain-project probes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AddModuleOrNamespaceRefsToNameEnv runs over the root namespaces of every referenced assembly, and two checks in it forced each one's contents - which for an imported namespace means importing every type in it: - Entity.DemangledModuleOrNamespaceName forced the ModuleOrNamespaceType only to read its ModuleOrNamespaceKind. Only FSharpModuleWithSuffix demangles, and only a suffixed name can be of that kind, so testing the name first avoids the read. - `modref.IsModule && EntityHasWellKnownAttribute ... AutoOpenAttribute`: IsModule forces the contents while the attribute check only reads entity_attribs, so the operands are swapped. Retained memory when checking ReSharper.FSharp's FSharp.Common (486 references): 874.7 -> 780.4 MB (-10.8%). Framework-only projects are unchanged, since their root namespaces get imported anyway. Also un-skips `Type defs 02 - assembly import`: no type of the reference is imported now, closing dotnet#16166. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reader tests above them pin the API in isolation; these pin the guarantee it exists for. A synthetic reference assembly records every GetTypeDef, member, nested-type and attribute read, and each test checks a file against it and asserts what came out - so a walk introduced far from the reader (addConstraintSources did exactly that) fails here. What they pin: naming Ns1.A reads the root level and Ns1's own types and nothing else; members, nested types and the base type stay unread until something needs them; a member lookup is what forces the base type's namespace; attributes are read for the types that enter scope, so an open pays for all of a namespace's types and a qualified use does not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR groups lazy type definitions into lazy namespaces structure, allowing to make nested namespaces lazy and defer lazy type defs creation. It improves the custom module readers that we have in Rider.
In addition to that, the PR adds tests asserting the type defs are not eagerly read, which discovered several issues. This PR, #20088, and #20090 all fix these issues. The constraint fix has the most effect for the most users, since without it the type defs were effectively not lazy at all.
The following was measured with the other two PRs included.
Retained memory after ParseAndCheckProject (
retain-project, avg of 3):8c0e444deType defs actually read out of the references:
† Denominator is top-level rows only while the read counter also fires for nested type defs.
ParseAndCheckProject — warm (1 warm-up, then samples with the IL reader cache cleared each time):