Skip to content

Serialize library level into .swiftmodule binary format#88345

Merged
sepy97 merged 1 commit into
swiftlang:mainfrom
sepy97:SerializeLibraryLevel
Apr 9, 2026
Merged

Serialize library level into .swiftmodule binary format#88345
sepy97 merged 1 commit into
swiftlang:mainfrom
sepy97:SerializeLibraryLevel

Conversation

@sepy97
Copy link
Copy Markdown
Contributor

@sepy97 sepy97 commented Apr 7, 2026

Add a LIBRARY_LEVEL record to the .swiftmodule options block so the declared -library-level value survives across compilations. Without this, imported modules have to always fell back to a path heuristic that could only distinguish API vs SPI and never returned IPI.

rdar://174255626

@sepy97 sepy97 requested a review from a team as a code owner April 7, 2026 22:07
@sepy97 sepy97 requested review from artemcm, Copilot, nkcsgexi and xymus April 7, 2026 22:07
@sepy97
Copy link
Copy Markdown
Contributor Author

sepy97 commented Apr 7, 2026

@swift-ci please test

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends Swift’s .swiftmodule binary format to serialize the module -library-level in the options block, so dependency scanning and module loading can reliably recover IPI (which the existing path heuristic cannot infer).

Changes:

  • Add a new LIBRARY_LEVEL record to the .swiftmodule options_block, serialize it, and bump the module format minor version.
  • Deserialize and plumb the stored library level through ModuleFile/ModuleDecl, and use it to report IPI in dependency scanning / module library-level computation.
  • Add ScanDependencies tests covering IPI detection from both .swiftinterface flags and serialized .swiftmodule.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/ScanDependencies/library_level_ipi.swift New test validating IPI propagation via interface flags and serialized swiftmodule scanning output.
test/ScanDependencies/Inputs/Swift/IPILib.swiftinterface New swiftinterface fixture carrying -library-level ipi in swift-module-flags.
lib/Serialization/SerializedModuleLoader.cpp Stores the deserialized library level onto the ModuleDecl.
lib/Serialization/Serialization.cpp Emits LIBRARY_LEVEL into the options block when LangOpts.LibraryLevel != Other.
lib/Serialization/ScanningLoaders.cpp Reads -library-level from interface args for IPI; uses stored value for IPI in binary scanning; keeps path heuristic for API/SPI.
lib/Serialization/ModuleFormat.h Adds the new options-block record/layout and bumps SWIFTMODULE_VERSION_MINOR.
lib/Serialization/ModuleFileSharedCore.h Stores library level in shared core bitset and exposes an accessor.
lib/Serialization/ModuleFileSharedCore.cpp Deserializes LIBRARY_LEVEL from the options block into validation/core bits.
lib/Serialization/ModuleFile.h Exposes getLibraryLevel() from ModuleFile.
lib/AST/Module.cpp Uses stored library level to allow IPI detection for non-main Swift modules.
include/swift/Serialization/Validation.h Adds library-level to ExtendedValidationInfo bits and includes LangOptions.h.
include/swift/AST/Module.h Adds ModuleDecl stored-library-level accessors.
include/swift/AST/Decl.h Adds StoredLibraryLevel bitfield storage on ModuleDecl.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +225 to +234
LibraryLevel libraryLevel = LibraryLevel::Other;
auto libLevelIt = llvm::find(ArgsRefs, "-library-level");
if (libLevelIt != ArgsRefs.end() &&
(libLevelIt + 1) != ArgsRefs.end()) {
libraryLevel = llvm::StringSwitch<LibraryLevel>(*(libLevelIt + 1))
.Case("api", LibraryLevel::API)
.Case("spi", LibraryLevel::SPI)
.Case("ipi", LibraryLevel::IPI)
.Default(LibraryLevel::Other);
}
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scanInterfaceFile only detects -library-level when it appears as a separate argument followed by a value. The frontend also accepts the -library-level=<level> spelling (used in several tests/driver invocations), which would currently be missed and cause IPI modules to fall back to the path heuristic. Consider handling both spellings (e.g., match args that start with -library-level= as well as the two-arg form, or use the existing option parsing infrastructure instead of a manual find).

Copilot uses AI. Check for mistakes.
/// Discriminator for library level (LibraryLevel enum).
unsigned LibraryLevel : 2;

// Explicitly pad out to the next word boundary if neccessary.
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in comment: "neccessary" should be "necessary".

Suggested change
// Explicitly pad out to the next word boundary if neccessary.
// Explicitly pad out to the next word boundary if necessary.

Copilot uses AI. Check for mistakes.
@sepy97
Copy link
Copy Markdown
Contributor Author

sepy97 commented Apr 7, 2026

@swift-ci please smoke test

@sepy97 sepy97 enabled auto-merge April 7, 2026 22:49
// RUN: %target-swift-frontend -emit-module %t/IPIBinaryLib.swift -module-name IPIBinaryLib \
// RUN: -o %t/IPIBinaryLib.swiftmodule -library-level ipi
// RUN: %target-swift-frontend -scan-dependencies %t/IPIBinaryClient.swift -o %t/bin_deps.json -I %t
// RUN: %validate-json %t/bin_deps.json | %FileCheck %s --check-prefix CHECK-BINARY
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also add a test for -library-level ipi being printed in .swiftinterface files when module interfaces are emitted from compiler?

Comment thread lib/Serialization/Serialization.cpp Outdated
static_cast<uint8_t>(M->getCXXStdlibKind()));
}

if (M->getASTContext().LangOpts.LibraryLevel != LibraryLevel::Other) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we serialize LibraryLevel::Other as well? so that the content in the binary formats are more aligned.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify, the idea is to keep the serialized contents as intact as possible and we could tweak the result from the loader side.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have Other in textual interface as well? Doesn't seem so...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't matter, it's 0 for Other, so I don't actually need those checks anyways

case options_block::LIBRARY_LEVEL: {
unsigned rawLevel;
options_block::LibraryLevelLayout::readRecord(scratch, rawLevel);
if (rawLevel <= static_cast<unsigned>(LibraryLevel::API))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if check shouldn't be necessary if we serialize Other library level, I presume?

@sepy97 sepy97 force-pushed the SerializeLibraryLevel branch from 930efe8 to c85f167 Compare April 8, 2026 17:26
@sepy97 sepy97 requested a review from nkcsgexi April 8, 2026 17:26
@sepy97
Copy link
Copy Markdown
Contributor Author

sepy97 commented Apr 8, 2026

@swift-ci please smoke test

Add a LIBRARY_LEVEL record to the .swiftmodule options block so the
declared -library-level value survives across compilations. Without
this, imported modules have to always fell back to a path heuristic
that could only distinguish API vs SPI and never returned IPI.

rdar://174255626
@sepy97 sepy97 force-pushed the SerializeLibraryLevel branch from c85f167 to a09cf04 Compare April 8, 2026 19:17
@sepy97
Copy link
Copy Markdown
Contributor Author

sepy97 commented Apr 8, 2026

@swift-ci please smoke test

@sepy97 sepy97 disabled auto-merge April 8, 2026 20:27
@sepy97 sepy97 enabled auto-merge April 8, 2026 20:27
@sepy97 sepy97 merged commit 2be1203 into swiftlang:main Apr 9, 2026
3 checks passed
@nkcsgexi
Copy link
Copy Markdown
Contributor

nkcsgexi commented Apr 9, 2026

Is the idea to always rely on dependency scanner as the single source of truth for being an IPI module? I like that since it would work for clang modules as well👍.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants