Problem
Serialized modules eagerly materialize too much of their candidate-extension inventory when the module is loaded.
This defeats the intended on-demand behavior of AST deserialization and makes a user pay for unrelated conditional conformances before any query needs them.
Consider a builtin supplement containing extensions with this general shape:
extension float : IDifferentiableTrigonometricFunctions { ... }
__generic<T : IDifferentiableTrigonometricFunctions, let N : int>
extension vector<T, N> : IDifferentiableTrigonometricFunctions { ... }
__generic<T : IDifferentiableTrigonometricFunctions, let R : int, let C : int>
extension matrix<T, R, C> : IDifferentiableTrigonometricFunctions { ... }
A scalar-only program that loads this supplement currently pays AST-loading and semantic-checking costs for the vector, matrix, and other unrelated extension families.
In a controlled numeric-interface experiment based on #12136 and #12830:
- The supplement with public interface declarations but no new conformances was 4,475,995 bytes.
- Adding all proposed numeric-AD conformances and wrappers increased it to 7,421,047 bytes.
- A scalar
sin autodiff workload rose from 187.74 ms to 239.55 ms.
- Removing the vector, matrix, or cooperative-vector conformance family independently saved roughly 9–13 ms in that scalar-only workload.
Layering #12446 onto both sides reduces serialized-IR loading, but the relative regression remains.
For scalar sin AD, builtin-module loading still increases from 84.93 to 122.80 ms; serialized AST reading accounts for 21.61 ms of the difference, while linkAndOptimizeIR changes only from 15.74 to 16.74 ms.
Code path
readSerializedModuleAST() starts by reading the serialized ModuleDecl.
ModuleDecl::mapDeclToCandidateExtensions is an ordinary serialized FIDDLE() field in slang-ast-decl.h, so the module-level extension index participates in that read.
When an on-demand builtin supplement becomes visible, SharedSemanticsContext::addLoadedAutodiffModule() iterates every key in moduleDecl->mapDeclToCandidateExtensions to advance extension epochs.
If the shared aggregate view has already been built, _mergeCandidateExtensionsFromModule() additionally iterates every map entry and candidate list.
Instrumentation of lazily deserialized top-level declarations follows this path: loading scalar AD materializes extension and witness declarations belonging to unrelated vector, matrix, and cooperative-vector conformances before the program queries them.
Removing those unrelated extensions from the serialized producer directly reduces both deserialization time and scalar compilation time, so this is not merely a downstream code-generation symptom.
Proposed direction
Represent a serialized module's candidate-extension data as a lazy index rather than an eagerly traversed map of materialized declarations.
Loading the module should make its target keys discoverable without deserializing every ExtensionDecl or its witness graph.
A query for candidate extensions of a particular type should deserialize only the relevant candidate list.
Extension-epoch invalidation and the shared aggregate cache need corresponding lazy semantics.
In particular, registering a newly loaded module must not require iterating every candidate declaration merely to invalidate cached inheritance results.
This work is complementary to #12446.
That PR makes serialized IR loading demand-driven; this issue concerns the AST-side extension index and front-end conformance discovery.
Suggested validation
Add an instrumented unit test with a separately serialized module containing extensions for at least two unrelated target type families.
- Load the module and verify that neither extension body/witness graph is deserialized merely because the module became visible.
- Query conformance or candidate extensions for the first target and verify that only its candidate list is materialized.
- Query the second target and verify that its list is then materialized.
- Exercise both a semantic context whose aggregate extension view already exists and a new context created after the module was loaded.
- Confirm that inheritance and subtype caches computed before the load are invalidated correctly without requiring eager candidate materialization.
The numeric-interface corpus used to expose the problem should then show that scalar AD no longer pays for shaped conformances merely because they reside in the same supplement.
Problem
Serialized modules eagerly materialize too much of their candidate-extension inventory when the module is loaded.
This defeats the intended on-demand behavior of AST deserialization and makes a user pay for unrelated conditional conformances before any query needs them.
Consider a builtin supplement containing extensions with this general shape:
A scalar-only program that loads this supplement currently pays AST-loading and semantic-checking costs for the vector, matrix, and other unrelated extension families.
In a controlled numeric-interface experiment based on #12136 and #12830:
sinautodiff workload rose from 187.74 ms to 239.55 ms.Layering #12446 onto both sides reduces serialized-IR loading, but the relative regression remains.
For scalar
sinAD, builtin-module loading still increases from 84.93 to 122.80 ms; serialized AST reading accounts for 21.61 ms of the difference, whilelinkAndOptimizeIRchanges only from 15.74 to 16.74 ms.Code path
readSerializedModuleAST()starts by reading the serializedModuleDecl.ModuleDecl::mapDeclToCandidateExtensionsis an ordinary serializedFIDDLE()field inslang-ast-decl.h, so the module-level extension index participates in that read.When an on-demand builtin supplement becomes visible,
SharedSemanticsContext::addLoadedAutodiffModule()iterates every key inmoduleDecl->mapDeclToCandidateExtensionsto advance extension epochs.If the shared aggregate view has already been built,
_mergeCandidateExtensionsFromModule()additionally iterates every map entry and candidate list.Instrumentation of lazily deserialized top-level declarations follows this path: loading scalar AD materializes extension and witness declarations belonging to unrelated vector, matrix, and cooperative-vector conformances before the program queries them.
Removing those unrelated extensions from the serialized producer directly reduces both deserialization time and scalar compilation time, so this is not merely a downstream code-generation symptom.
Proposed direction
Represent a serialized module's candidate-extension data as a lazy index rather than an eagerly traversed map of materialized declarations.
Loading the module should make its target keys discoverable without deserializing every
ExtensionDeclor its witness graph.A query for candidate extensions of a particular type should deserialize only the relevant candidate list.
Extension-epoch invalidation and the shared aggregate cache need corresponding lazy semantics.
In particular, registering a newly loaded module must not require iterating every candidate declaration merely to invalidate cached inheritance results.
This work is complementary to #12446.
That PR makes serialized IR loading demand-driven; this issue concerns the AST-side extension index and front-end conformance discovery.
Suggested validation
Add an instrumented unit test with a separately serialized module containing extensions for at least two unrelated target type families.
The numeric-interface corpus used to expose the problem should then show that scalar AD no longer pays for shaped conformances merely because they reside in the same supplement.