菜单

模块加载与序列化

相关源文件

本文档解释了 Swift 如何处理模块加载和序列化,这是 Swift 编译器基础设施的关键部分。它描述了模块在内存中的表示方式、序列化到磁盘以及在需要时如何重新加载。本页面涵盖了序列化模块格式(.swiftmodule)和模块接口(.swiftinterface),以及用于优化编译时间的缓存机制。

有关 Clang 导入器的具体信息,请参阅 Clang 导入器

模块系统架构

Swift 编译器将代码组织成模块,模块是代码分发和命名空间的基本单元。一个模块由一个或多个文件单元组成,这些文件单元可以是 Swift 源文件、序列化模块或其他导入的内容。

来源: include/swift/AST/Module.h227-786 include/swift/AST/FileUnit.h29-38 include/swift/AST/ModuleLoader.h50-67

关键模块组件

Swift 模块系统由几个关键类组成。

  • ModuleDecl:代表一个完整的模块,包含一个或多个文件单元。
  • FileUnit:表示模块作用域声明容器的抽象基类。
    • SourceFile:包含 Swift 源代码的文件。
    • SerializedASTFile:一个二进制 .swiftmodule 文件。
    • ClangModule:一个导入的 Clang 模块。
    • BuiltinUnit:特殊的 Swift 内建模块。
  • ModuleLoader:加载模块的抽象基类。
    • SerializedModuleLoader:加载序列化的 .swiftmodule 文件。
    • ModuleInterfaceLoader:加载 .swiftinterface 文件并从中构建模块。
    • ClangImporter:导入 C、C++ 和 Objective-C 声明。
    • SourceLoader:将 Swift 源文件直接作为模块加载。

来源: lib/AST/Module.cpp749-802 include/swift/AST/FileUnit.h35-110 include/swift/AST/ModuleLoader.h50-67

模块加载过程

当导入一个模块时,Swift 编译器会尝试通过各种加载器来定位和加载它,遵循特定的优先级顺序。

来源: lib/Serialization/SerializedModuleLoader.cpp200-260 lib/Frontend/ModuleInterfaceLoader.cpp505-580

模块搜索路径

模块加载器在各种搜索路径中查找模块,这些路径通过编译器选项指定。

  1. 导入搜索路径(-I 路径)
  2. 框架搜索路径(-F 路径)
  3. 系统库路径(SDK 路径)
  4. 运行时库导入路径

在查找模块时,加载器会在此类路径中查找

  • .swiftmodule 文件或目录
  • .swiftinterface 文件
  • Clang 模块映射文件

来源: lib/Serialization/SerializedModuleLoader.cpp143-264

序列化模块格式

Swift 模块被序列化为 .swiftmodule 文件,其中包含模块声明和类型的压缩位流表示。

来源: lib/Serialization/ModuleFile.h52-65 lib/Serialization/ModuleFileSharedCore.h31-43

模块依赖

序列化模块会跟踪其依赖项,以确保在依赖项更改时重新构建模块。依赖项包括:

  • 其他 Swift 模块
  • Clang 模块
  • 桥接头文件
  • 源文件

每个依赖项都记录了检测更改所需的信息,例如:

  • 文件路径
  • 修改时间
  • 文件大小
  • 内容哈希(对于预构建模块)

来源: lib/Serialization/ModuleFile.h94-124 include/swift/Serialization/SerializationOptions.h70-97

模块接口加载

模块接口(.swiftinterface 文件)是模块的文本表示形式,可以编译成二进制 .swiftmodule 文件。这是 Swift ABI 稳定性故事的关键部分,因为它允许模块在需要时从源代码重新构建。

来源: lib/Frontend/ModuleInterfaceLoader.cpp531-677 lib/Frontend/ModuleInterfaceBuilder.cpp98-180

模块缓存策略

ModuleInterfaceLoader 使用复杂的缓存策略,尽可能避免从接口重新构建模块。

  1. 首先,检查模块缓存(由 -module-cache-path 指定)
  2. 接下来,查找 .swiftinterface 文件旁的邻近位置。
  3. 最后,检查预构建模块缓存(由 -prebuilt-module-cache-path 指定)
  4. 如果找不到有效的模块,则从接口构建它。

对于缓存的模块,加载器通过检查其依赖项来验证它们是否是最新的。

来源: lib/Frontend/ModuleInterfaceLoader.cpp65-104 lib/Frontend/ModuleInterfaceLoader.cpp605-645

模块验证

在加载序列化模块之前,需要对其进行验证,以确保它与当前编译器和目标兼容。

来源: include/swift/Serialization/Validation.h37-88 lib/Serialization/ModuleFileSharedCore.cpp310-450

验证检查

验证过程包括几项检查:

  1. 格式版本:确保模块是用兼容的编译器版本构建的。
  2. 目标三元组:验证模块是否是为兼容的目标体系结构和操作系统构建的。
  3. SDK 版本:检查模块是否是针对兼容的 SDK 构建的。
  4. 依赖项:验证所有依赖项是否都可以加载。
  5. 弹性:如果需要,确保 OSSA(Ownership SSA)兼容性。

对于每项检查,如果验证失败,则会返回适当的错误代码。

来源: lib/Serialization/SerializedModuleLoader.cpp307-344 lib/Serialization/ModuleFile.cpp275-316

特殊模块类型

转发模块

转发模块是一种特殊的 .swiftmodule,它指向另一个模块文件,通常在预构建缓存中。它们为基于哈希的预构建模块添加了基于修改时间的依赖项。

来源: lib/Frontend/ModuleInterfaceLoader.cpp65-145

预构建模块

预构建模块是预编译的 .swiftmodule 文件,通常随 SDK 一起发布。它们使用内容哈希而不是修改时间来进行依赖项跟踪,使其更具可重定位性。

来源: lib/Frontend/ModuleInterfaceLoader.cpp658-686

依赖管理

Swift 编译器会跟踪模块之间的依赖关系,以确保正确的构建并实现增量编译。

来源: include/swift/AST/ModuleLoader.h95-119 lib/AST/ModuleLoader.cpp35-54

传递性依赖

模块具有不同类型的依赖关系,这些依赖关系具有不同的可见性规则

  • 必需:模块正常运行必须加载的依赖项
  • 仅供实现使用:仅在实现细节中需要的依赖项
  • 仅限包:仅在同一包内可见的依赖项
  • 导出的:重新导出给模块客户端的依赖项

这些可见性规则会影响导入模块时传递性地加载依赖项的方式。

来源: lib/Serialization/ModuleFile.cpp318-332 lib/Serialization/SerializedModuleLoader.cpp479-526

查找机制

Swift 使用高效的查找机制来查找已加载模块中的声明。

来源: lib/AST/Module.cpp973-986 lib/Serialization/ModuleFile.cpp345-372

缓存机制

模块系统使用多种缓存机制来提高性能

  • SourceLookupCache:缓存源文件中的查找
  • 已序列化模块中的哈希表,用于高效查找声明
  • 模块缓存,以避免从接口重新构建模块

来源: lib/AST/Module.cpp139-247 lib/AST/Module.cpp920-928

结论

Swift 的模块加载和序列化系统是编译器基础设施的关键组成部分,它支持单独编译、快速增量构建和 ABI 稳定性。该系统平衡了对性能的需求以及现代编译器所需的灵活性。

该系统的关键方面包括:

  • 多种模块表示形式(源文件、序列化、接口)
  • 基于模块类型的不同加载策略
  • 复杂的缓存机制以提高性能
  • 仔细的验证以确保兼容性
  • 灵活的依赖管理

了解此系统对于任何从事 Swift 编译器或开发与 Swift 模块交互的工具的人来说都至关重要。