Many Private Changes Cause .swiftmodule to Change

I’ve observed an interesting behavior regarding .swiftmodule files. My understanding is that .swiftmodule is intended to be a binary representation of a module’s public interface. However, I’ve found that many changes to private elements also cause the .swiftmodule to change.

For example, the following script demonstrates that modifying the value of a private variable results in a different .swiftmodule:

#!/bin/zsh
cat >lib.swift <<EOF
class Foo {
  private let foo = "aaa"
}
EOF

xcrun swiftc -emit-module -parse-as-library -module-name=lib -o lib.swiftmodule lib.swift
sha256sum lib.swiftmodule

sed -i '' 's/aaa/bbb/g' lib.swift
xcrun swiftc -emit-module -parse-as-library -module-name=lib -o lib.swiftmodule lib.swift
sha256sum lib.swiftmodule

In a comprehensive test, I found that only changes within function bodies or computed property bodies do not affect the .swiftmodule. In contrast, changes to constants, stored properties, or lazy variables do cause a change—regardless of their access control level (public, private, etc.) or the containing type (class, struct, etc.).

This behavior has notable build time implications, as dependent modules are unnecessarily rebuilt. This occurs in both Bazel and Xcode.

I’ve experimented with several compiler flags, but they don’t appear to affect this behavior in the example above:

  • -Xfrontend -disable-reflection-metadata
  • -Xfrontend -no-serialize-debugging-options
  • -enable-library-evolution
  • -swift-version 6

Is this the expected behavior? Or should build systems rely on .swiftinterface files for inter-module dependencies compilation?

2 Likes

Some previous discussions that may be of use to you:

3 Likes

Yes, .swiftmodule files are expected to contain some private details about the declarations in a module. It's not exclusively a summary of the public interfaces of that module. The additional details can be needed for a number of different reasons (e.g. type layout).

2 Likes

Thanks. I can understand the type layout information, but why the value of a private variable is also affecting .swiftmodule? In my example above, if I just change "aaa" to "bbb", .swiftmodule file is also changed.

Private variables affect type layout, and type layout is relevant to other declarations in other files within your codebase. The .swiftmodule is the result of merging the individual .swiftmodule files of multiple runs of the compiler, which means it ends up tracking information that's necessary for compilation of your project but not necessarily for other modules to consume it.

Private declarations are also important for the debugger.

1 Like

Thanks for all the responses—very helpful!

Regarding the iOS build system, should we just accept that nearly all source changes (except those within function bodies) will trigger a rebuild of all dependent modules? Or is there any way to mitigate this?

At least when library evolution is enabled, there is no technical reason for this to be the case, since the ABI depends only on the contents of the swiftinterface file, that is, public declarations.

Even with library evolution enabled, there are some exceptions to this. There are clients that a general build system must accommodate which cannot rely on swiftinterfaces alone. For example, dependents that belong to the same package identifier and dependents that use @testable import require information that isn't captured in a .swiftinterface.

1 Like

Ah, right. @testable import also explains why .swiftmodule has so many "internal" stuff.

1 Like