Which module cache invalidation signal does Swift use for private implementation changes?
0 reputation · 24 Jul 2020, 12:42 UTC
0 reputation · 24 Jul 2020, 12:42 UTC
Swift's incremental build system caches compiled modules as .swiftmodule files to avoid recompiling unchanged dependencies. In recent Swift toolchains, the compiler evaluates file timestamps and dependency hashes to decide whether a cached module remains valid. A known area of uncertainty involves changes confined to a module's private implementation—such as adding an internal function—where the public interface hash stays identical. If that internal code is exposed through inlineable declarations, clients may embed the old implementation while the library binary expects new behavior, creating a potential correctness gap. The Swift project has not settled whether any source-level modification should force cache invalidation or whether interface-level hashes alone are sufficient, balancing build speed against safety.
Which invalidation heuristic does the Swift compiler apply when a dependency's private source changes but its public interface hash does not? Under what conditions does the module cache reuse a .swiftmodule despite modifications to non-public implementation that affect inlineable code? Is there a documented distinction between timestamp-based and hash-based staleness checks for this scenario?
34275 reputation · 24 Jul 2020, 21:43 UTC
Swift invalidates a cached .swiftmodule whenever the *module signature* changes. The signature is built from two hashes:
When the compiler starts a build it constructs a cache key that looks roughly like:
module‑cache‑key = + +
The module‑signature is a concatenation of the two hashes mentioned above. The compiler stores the compiled module under this key. On a subsequent build it recomputes the signature and compares it to the key in the cache. If they differ, the cached module is considered stale and is discarded (or a new entry is created).
@inlinable declarations. Even if the change is not in the public API, the source‑file hash is recomputed, causing the signature to change.
In practice, this means that *every* source‑file change invalidates the cache, regardless of whether the change touches the public API. The only time the cache is reused is when the set of source files and their contents are identical to the last build.
Earlier Swift releases (prior to Swift 5.7) relied primarily on file timestamps in the cache key. Since Swift 5.7, the compiler has moved to content hashes for the source‑file component, providing stability across rebuilds that only change timestamps. The public‑interface hash remains a timestamp‑based value that is recomputed when the exported API changes. Thus, the current algorithm is a hybrid: timestamps are used for the public API, while content hashes guard against private changes.
swift package init --type library
# Add a public struct and an internal function
swift build
swift -dump-module -module-cache-path .build/Debug/ModuleCache MyModule
swift build
To see the compiler’s cache decisions directly, use the -debug-module-cache flag. It prints whether a module was reused or recompiled and the reason.
swift build --build-path), the source‑file hash is unchanged, so the cache remains valid.
-whole-module-optimization off and the module is split into separate compilation units, changes only to a unit that is not used by the current target may not affect the cached module. This is an edge case and rarely occurs in normal library builds.
In current Swift toolchains, the compiler uses a combined hash of all source files (the source‑file hash) as part of the module‑cache key. Any change to a file belonging to the module forces cache invalidation, ensuring that inlineable code is regenerated and clients cannot embed stale implementations.
Do you build the module with -whole-module-optimization or any other flags that affect incremental compilation? This can influence when the compiler consults the cache.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.