Serialize library level into .swiftmodule binary format#88345
Conversation
|
@swift-ci please test |
There was a problem hiding this comment.
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_LEVELrecord to the.swiftmoduleoptions_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
.swiftinterfaceflags 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.
| 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); | ||
| } |
There was a problem hiding this comment.
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).
| /// Discriminator for library level (LibraryLevel enum). | ||
| unsigned LibraryLevel : 2; | ||
|
|
||
| // Explicitly pad out to the next word boundary if neccessary. |
There was a problem hiding this comment.
Typo in comment: "neccessary" should be "necessary".
| // Explicitly pad out to the next word boundary if neccessary. | |
| // Explicitly pad out to the next word boundary if necessary. |
|
@swift-ci please smoke test |
| // 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 |
There was a problem hiding this comment.
Could you also add a test for -library-level ipi being printed in .swiftinterface files when module interfaces are emitted from compiler?
| static_cast<uint8_t>(M->getCXXStdlibKind())); | ||
| } | ||
|
|
||
| if (M->getASTContext().LangOpts.LibraryLevel != LibraryLevel::Other) { |
There was a problem hiding this comment.
Can we serialize LibraryLevel::Other as well? so that the content in the binary formats are more aligned.
There was a problem hiding this comment.
To clarify, the idea is to keep the serialized contents as intact as possible and we could tweak the result from the loader side.
There was a problem hiding this comment.
Do we have Other in textual interface as well? Doesn't seem so...
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
This if check shouldn't be necessary if we serialize Other library level, I presume?
930efe8 to
c85f167
Compare
|
@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
c85f167 to
a09cf04
Compare
|
@swift-ci please smoke test |
|
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👍. |
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