Xcode Build System: Compiling
Understand how Xcode Build System decides what to compile and when. This article breaks down complicated terms and processes into simple explanations, perfect for anyone curious about what happens behind the scenes.
This article is a part of the Xcode Build System: Everything Everywhere All at Once main article.
After preparing for compilation, the Xcode Build System is now organizing a Swift module for the upcoming compilation phase.
Planning Swift module
SwiftDriver (LongNights normal arm64 com.apple.xcode.tools.swift.compiler)
cd LongNights
builtin-SwiftDriver
~/Xcode.app/.../usr/bin/swiftc
-module-name LongNights
-Onone
-whole-module-optimization
-enforce-exclusivity\=checked
@~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNights.SwiftFileList
-DDEBUG
-sdk
~/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.0.sdk
-target arm64-apple-ios17.0-simulator
-enable-bare-slash-regex
-g
-module-cache-path
~/Xcode/DerivedData/ModuleCache.noindex
-Xfrontend
-serialize-debugging-options
-profile-coverage-mapping
-profile-generate
-enable-testing
-index-store-path
~/Xcode/DerivedData/.../Index.noindex/DataStore
-swift-version 5
-I
~/Xcode/DerivedData/.../Debug-iphonesimulator
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator
-emit-localized-strings
-emit-localized-strings-path
~/Xcode/.../LongNights.build/Objects-normal/arm64
-c
-num-threads 12
-Xcc -ivfsstatcache
-Xcc
~/Xcode/DerivedData/SDKStatCaches.noindex/iphonesimulator17.0-21A326-9f995cea1212cde75317d7d3cd2045e2.sdkstatcache
-output-file-map
~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNights-OutputFileMap.json
-use-frontend-parseable-output
-save-temps
-no-color-diagnostics
-serialize-diagnostics
-emit-dependencies
-emit-module
-emit-module-path
~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNights.swiftmodule
-validate-clang-modules-once
-clang-build-session-file
~/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation
-Xcc -I
~/Xcode/.../LongNights.build/swift-overrides.hmap
-emit-const-values
-Xfrontend
-const-gather-protocols-file
-Xfrontend
~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNights_const_extract_protocols.json
-Xcc -iquote
-Xcc
~/Xcode/.../LongNights.build/LongNights-generated-files.hmap
-Xcc -I
~/Xcode/.../LongNights.build/LongNights-own-target-headers.hmap
-Xcc -I
~/Xcode/.../LongNights.build/LongNights-all-non-framework-target-headers.hmap
-Xcc -ivfsoverlay
-Xcc
~/Xcode/.../LongNights.build/all-product-headers.yaml
-Xcc -iquote
-Xcc
~/Xcode/.../LongNights.build/LongNights-project-headers.hmap
-Xcc -I
~/Xcode/DerivedData/.../Debug-iphonesimulator/include
-Xcc -I
~/Xcode/.../LongNights.build/DerivedSources-normal/arm64
-Xcc -I
~/Xcode/.../LongNights.build/DerivedSources/arm64
-Xcc -I
~/Xcode/.../LongNights.build/DerivedSources
-Xcc -DDEBUG\=1
-emit-objc-header
-emit-objc-header-path
~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNights-Swift.h
-working-directory
~/LongNights
-no-emit-module-separately-wmo
The SwiftDriver is a component within the Swift toolchain responsible for coordinating the overall compilation process. It acts as a driver for the Swift compiler (swiftc
), managing various tasks involved in compiling Swift source code.
The output of the builtin-SwiftDriver
command itself, which invokes the Swift compiler (swiftc
), typically includes information about the compilation process. This information is often extensive and may include details such as:
- The actual command-line invocation of the Swift compiler, including the compiler executable path, options, and flags.
- A list of Swift source files that are part of the current compilation task.
- Information about the progress of the compilation, such as the stages being executed.
- Paths where the compiled artifacts (object files, modules, etc.) are being generated.
- Any diagnostic messages, warnings, or errors encountered during compilation.
- Optionally, performance-related information or metrics may be included, especially if profiling or optimization features are enabled.
- Depending on the specific build settings and options, additional details related to the build process may be present.
Here you can see that all the files(.hmap, .json, .SwiftFileList) that were generated before, now act as inputs.
The Swift compiler command is provided with various options and parameters.
-module-name LongNights
: Specifies the module name for the Swift module.-Onone
: Disables optimizations.-whole-module-optimization
: Enables whole module optimization.-enforce-exclusivity\=checked
: Enables checking of exclusivity violations.-enable-bare-slash-regex
: Enables bare slash regex.-g
: Generates debug information.~/LongNights.build/Objects-normal/arm64/LongNights.SwiftFileList
: Specifies the Swift file list.-target arm64-apple-ios17.0-simulator
: Sets the target architecture.-Xfrontend -serialize-debugging-options
: Instructs the frontend to serialize debugging options.-profile-coverage-mapping -profile-generate
: Enables code coverage profiling and generation.-swift-version 5
: Specifies the Swift language version.-num-threads 12
: Specifies the number of threads to use for compilation.-output-file-map ...
: Specifies the output file map path.-no-emit-module-separately-wmo
: Disables emitting modules separately.-disable-cmo
flag is used in Swift compilation to disable the "cross-module optimization" (CMO) feature. Cross-module optimization is a Swift compiler optimization that allows the compiler to optimize across the boundaries of Swift modules. When enabled, the compiler can perform additional optimizations that involve multiple modules, potentially resulting in better performance.
Let’s explore all the options provided in this command in more detail.
-Xfrontend
flag is used to pass additional options directly to the Swift frontend, which is the part of the Swift compiler responsible for parsing, type-checking, and generating intermediate representations of the code. The -Xfrontend
option allows you to customize the behavior of the frontend with specific options that are not part of the standard Swift command-line interface.
-Xcc:
-X
flags in Swift are used to pass additional arguments to the compiler.-Xcc
specifically indicates that the following argument is intended for the C compiler.
In Swift compilation, the -emit
flags are used to control the generation of specific output files or data during the compilation process. Here are a few commonly used -emit
flags:
-emit-objc-header:
- This flag instructs the Swift compiler (
swiftc
) to generate an Objective-C bridging header. The bridging header is used when you have Swift code in your project that needs to be interoperable with Objective-C code. The generated header file (-emit-objc-header-path
) contains declarations for Swift entities that you want to expose to Objective-C.
-emit-module:
- This flag tells the compiler to emit a binary module file (with a
.swiftmodule
extension). Module files contain information about the Swift module, which can be used for separate compilation and optimization. We’ll talk about it later.
-emit-module-path:
- Specifies the path where the emitted module file should be saved.
-emit-const-values:
- This flag instructs the compiler to emit constant values (for example, enum cases) into a separate file.
These flags give you control over what kind of output the Swift compiler produces during the compilation process. Depending on your needs, you might use these flags to generate specific files or data for further processing or to support interoperability with other languages or tools.
As you may already notice there is an important option: -whole-module-optimization.
If we turn it off we see that there appears one task calling “Emmiting module”.
It means that in order to produce .swiftmodule file it requires more information of all the swift files compilations.
Whole Module Optimization (WMO) is an advanced compiler optimization technique used by the Swift compiler to generate highly optimized code for an entire Swift module. It works by analyzing and optimizing the entire module as a single unit, rather than compiling individual source files separately. Here’s how it works:
When you enable WMO, the Swift compiler first performs a deep analysis of the entire module. It considers all the code within the module, including functions, types, and dependencies between them. This analysis involves understanding the interactions between different parts of the code.
With knowledge of the entire module, the compiler can perform more aggressive and global optimizations. It can identify opportunities for optimization that would be missed when compiling individual source files independently.
One of the key optimizations enabled by WMO is inlining. Inlining involves replacing a function call with the actual code of the function wherever it’s used. This eliminates the overhead of function calls and can lead to significant performance improvements. WMO can perform inlining across module boundaries, which is not possible when compiling files separately.
WMO allows the compiler to analyze and optimize code across different source files within the module. This means that it can make optimizations that involve interactions between code defined in different files. For example, it can optimize code that passes complex data structures or objects between functions in different files.
It can optimize data structures, eliminate redundant computations, and perform other high-level transformations.
WMO can perform more effective dead code elimination. It can detect code that is never used within the entire module and remove it from the final binary, reducing the size of the executable.
After the global analysis and optimizations, the Swift compiler generates highly optimized machine code for the entire module. This optimized code is what gets included in your final application or framework.
The main benefit of WMO is improved runtime performance. The highly optimized code generated by WMO can lead to faster execution of your Swift code, making your application or framework more responsive and efficient.
Compiling swift files
cd LongNights
builtin-swiftTaskExecution
~/Xcode.app/.../usr/bin/swift-frontend
-frontend -c
~/LongNights/Filters/FilterSolutions.swift
~/LongNights/ContentView.swift
~/LongNights/LongNightsApp.swift
~/Xcode/.../LongNights.build/DerivedSources/GeneratedAssetSymbols.swift
-supplementary-output-file-map
~/Xcode/.../LongNights.build/Objects-normal/arm64/supplementaryOutputs-49
-emit-localized-strings
-emit-localized-strings-path
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64
-target arm64-apple-ios17.0-simulator
-Xllvm -aarch64-use-tbi
-enable-objc-interop
-sdk
~/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.0.sdk
-I
~/Xcode/DerivedData/.../Debug-iphonesimulator
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator/PackageFrameworks
-F
~/Xcode/DerivedData/.../Debug-iphonesimulator
-no-color-diagnostics
-enable-testing
-g
-module-cache-path
~/Xcode/DerivedData/ModuleCache.noindex
-profile-generate
-profile-coverage-mapping
-swift-version 5
-enforce-exclusivity\=checked
-Onone
-D DEBUG
-serialize-debugging-options
-const-gather-protocols-file
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/LongNights_const_extract_protocols.json
-enable-bare-slash-regex
-empty-abi-descriptor
-validate-clang-modules-once
-clang-build-session-file
~/Xcode/DerivedData/ModuleCache.noindex/Session.modulevalidation
-Xcc
-working-directory
-resource-dir
~/Xcode.app/.../usr/lib/swift
-enable-anonymous-context-mangled-names
-Xcc -ivfsstatcache
-Xcc
~/Xcode/DerivedData/SDKStatCaches.noindex/iphonesimulator17.0-21A326-9f995cea1212cde75317d7d3cd2045e2.sdkstatcache
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/swift-overrides.hmap
-Xcc -iquote
-Xcc
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/LongNights-generated-files.hmap
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/LongNights-own-target-headers.hmap
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/LongNights-all-non-framework-target-headers.hmap
-Xcc -ivfsoverlay
-Xcc
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/all-product-headers.yaml
-Xcc -iquote
-Xcc
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/LongNights-project-headers.hmap
-Xcc -I
~/Xcode/DerivedData/LongNights-exbpdzmtpimctvbptilfftsijotl/Build/Products/Debug-iphonesimulator/include
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/DerivedSources-normal/arm64
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/DerivedSources/arm64
-Xcc -I
~/Xcode/.../LongNights.build/Debug-iphonesimulator/LongNights.build/DerivedSources
-Xcc
-DDEBUG\=1
-module-name LongNights
-frontend-parseable-output
-disable-clang-spi
-target-sdk-version 17.0
-target-sdk-name iphonesimulator17.0
-external-plugin-path
~/Xcode.app/.../iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.0.sdk/usr/lib/swift/host/plugins\#~/Xcode.app/.../usr/bin/swift-plugin-server
-external-plugin-path
~/Xcode.app/.../iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator17.0.sdk/usr/local/lib/swift/host/plugins\#~/Xcode.app/.../usr/bin/swift-plugin-server
-external-plugin-path
~/Xcode.app/.../iPhoneOS.platform/Developer/usr/lib/swift/host/plugins\#~/Xcode.app/.../usr/bin/swift-plugin-server
-external-plugin-path
~/Xcode.app/.../iPhoneOS.platform/Developer/usr/local/lib/swift/host/plugins\#~/Xcode.app/.../usr/bin/swift-plugin-server
-plugin-path
~/Xcode.app/.../usr/lib/swift/host/plugins
-plugin-path
~/Xcode.app/.../usr/local/lib/swift/host/plugins
-num-threads 12
-o
~/Xcode/.../LongNights.build/Objects-normal/arm64/FilterSolutions.o
-o
~/Xcode/.../LongNights.build/Objects-normal/arm64/ContentView.o
-o
~/Xcode/.../LongNights.build/Objects-normal/arm64/LongNightsApp.o
-o
~/Xcode/.../LongNights.build/Objects-normal/arm64/GeneratedAssetSymbols.o
-index-unit-output-path
~/LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/FilterSolutions.o
-index-unit-output-path
~/LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/ContentView.o
-index-unit-output-path
~/LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/LongNightsApp.o
-index-unit-output-path
~/LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/GeneratedAssetSymbols.o
-index-store-path
~/Xcode/DerivedData/LongNights-exbpdzmtpimctvbptilfftsijotl/Index.noindex/DataStore
-index-system-modules
Here we see that all swift files (including asset symbols):
- FilterSolutions.swift, ContentView.swift, LongNightsApp.swift
- GeneratedAssetSymbols.swift
The output of this command include compiled object files (.o
files). These object files are intermediate files generated during the compilation process and are used later in the linking phase to produce the final executable.
Additionally, the compilation process may produce diagnostic messages, warnings, and errors, which would be printed to the console during the execution of this command.
Let’s consider options of this command:
-Xllvm
flag in Swift compiler commands is used to pass LLVM options directly to the LLVM backend during the compilation process.
When you use the -Xllvm
flag, you can provide specific LLVM options to fine-tune the code generation or perform other low-level optimizations.
-aarch64-use-tbi
is an LLVM option that enables the use of the Top Byte Ignore (TBI) feature on the AArch64 (ARM64) architecture. TBI is a feature in ARM architectures that ignores the top byte of addresses, allowing more efficient use of the address space.-disable-clang-spi
flag is used with the Swift compiler (swiftc
) and is related to Swift's interaction with Clang, the C and C++ compiler front-end often used in conjunction with Swift when developing mixed-language projects.
SPI stands for “Swift Private Interfaces.” SPIs are a way for Swift to interact with certain implementation details of a library or framework, even though those details are not part of the library’s public API.
In other words, it disables the Swift compiler’s ability to access these private interfaces. This can be useful in scenarios where you want to ensure that your Swift code doesn’t rely on or access any internal implementation details of C or C++ libraries.
Using this flag can help maintain code stability and compatibility because it prevents Swift code from relying on features that may change or be removed in future library updates, promoting better code encapsulation and reducing the risk of breaking changes when working with C or C++ libraries from Swift. However, it can also limit your ability to interact with certain library features that are not part of the public API.
-enable-anonymous-context-mangled-names
it is related to how Swift generates mangled names for entities within the code. Mangled names are used to uniquely identify entities such as functions, methods, types, and variables in the compiled binary, especially when dealing with overloads, generic types, and nested types.
When you enable this flag, you are instructing the Swift compiler to include information about the lexical context (e.g., the containing type or scope) in the mangled names for certain entities.
Including the lexical context in mangled names can make it easier to understand error messages and debugging output. When dealing with generic types or nested types, it can be more informative to have mangled names that reflect the context in which these entities are defined.
It helps avoid name clashes, especially when you have multiple entities with the same name within different generic contexts or nested scopes. By including the context in the mangled name, Swift ensures that these entities have distinct names in the binary.
As I mentioned the compiling process depends on the whole-module-optimization option (Build Settings). In the case when this option is turned off the compiling task of swift files runs in parallel.
Unlock downstream dependents
This task using builtin-Swift-Compilation-Requirements
.
It’s checking if it’s safe to proceed with compiling downstream dependents.
Swift Merge Generated Headers
cd ~/LongNights
builtin-swiftHeaderTool
-arch arm64
~/Xcode/DerivedData/.../Intermediates.noindex/.../LongNights.build/Objects-normal/arm64/LongNights-Swift.h
-o
~/Xcode/DerivedData/.../Intermediates.noindex/.../LongNights.build/DerivedSources/LongNights-Swift.h
This command is essentially telling Xcode’s built-in tool to generate or update the Swift header file for the project targeting the arm64
architecture. This generated header file will be used to bridge between Swift and Objective-C code within the project, allowing each language to access the other's APIs as needed.
Copy .abi.json file
cd /Users/U780052/Desktop/LongNights
builtin-copy
-exclude .DS_Store
-exclude CVS
-exclude .svn
-exclude .git
-exclude .hg
-resolve-src-symlinks
-rename
~/Xcode/DerivedData/.../Build/Intermediates.noindex/.../LongNights.build/Objects-normal/arm64/LongNights.abi.json
~/Xcode/DerivedData/.../Build/Products/Debug-iphonesimulator/LongNights.swiftmodule/arm64-apple-ios-simulator.abi.json
This command is copying a JSON file containing ABI (Application Binary Interface) information related to the project from a directory of built products to an intermediate directory. The ABI file is essential for the Swift compiler and runtime to ensure compatibility and correct behavior of the binary code.
ABI details are crucial in ensuring that the compiled code correctly interacts with other compiled code (e.g., libraries, runtime, etc.), especially when there are updates or changes to the Swift language or the project itself.
-resolve-src-symlinks
: This option indicates that if the source file is a symbolic link, the command should resolve it to the real file before copying.
Copy .swiftmodule file
cd ~/LongNights
builtin-copy
-exclude .DS_Store
-exclude CVS
-exclude .svn
-exclude .git
-exclude .hg
-resolve-src-symlinks
-rename
~/Xcode/DerivedData/.../Build/Intermediates.noindex/LongNights.build/Debug-iphonesimulator/LongNights.build/Objects-normal/arm64/LongNights.swiftmodule
~/Xcode/DerivedData/.../Build/Products/Debug-iphonesimulator/LongNights.swiftmodule/arm64-apple-ios-simulator.swiftmodule
The .swiftmodule
file contains the necessary metadata about the module, allowing other modules or the main app to use the compiled module without needing access to its source code.
The .swiftmodule
file acts as an interface for the module. It includes information about the public types, functions, variables, and other members defined in the module. When you import a module in Swift, the compiler reads the .swiftmodule
file to understand what is available from that module and ensure that your code interacts correctly with it.
During the compilation of a program that uses the module, the Swift compiler uses the .swiftmodule
file for type checking and to generate code. It ensures that the types and function signatures match what is expected and generates the appropriate calls to the module's code.
Swift’s powerful generics system can be optimized using information in .swiftmodule
files. The compiler can use the metadata about generic types and functions to generate specialized versions of the generics for the actual types they are used with in your program, which can greatly improve performance.
In larger projects, or projects that depend on several modules, .swiftmodule
files are essential for incremental builds. The compiler can skip recompiling a module if the .swiftmodule
file indicates that there have been no changes. This significantly speeds up the build process, especially during development when changes are being made frequently.
The Swift compiler can use the information in .swiftmodule
files to perform optimizations across module boundaries. For example, if a public function in a module isn't actually used by any external code, the compiler might be able to omit that function from the final binary, even if the function is technically "public" within the module's scope.
This article is a part of the Xcode Build System: Everything Everywhere All at Once main article, where you can read more about Linking.
Buy a Coffee for salgara: https://ko-fi.com/salgara