# Evolving how Swift is built: Driver as a library, explicitly-built modules, and beyond

**URL:** <https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102>\
**Category:** Compiler\
**Created:** [October 28, 2022, 8:45pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102 "2022-10-28T20:45:28Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![ArtemC](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/artemc/32/27369_2.png) [@ArtemC](https://forums.swift.org/u/ArtemC)\
**Post date:** [October 28, 2022, 8:45pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/1 "2022-10-28T20:45:29Z")

</div>

Modules are the core unit of code compilation and distribution in Swift. The task of taking a collection of Swift source files and orchestrating construction of a module is the prerogative of [swift-driver](https://github.com/apple/swift-driver). This post summarizes our recent work in Swift’s compilation model around modules and discusses some planned changes to enhance this process to achieve better efficiency and transparency.

## Swift Driver as a Library

Within the last year, Swift transitioned to the new [swift-driver](https://github.com/apple/swift-driver) as the default compiler driver, with the C++-based legacy driver now being phased out wherever possible. (If you find yourself still using the `-disallow-use-new-driver` option, please consider removing it. And if you cannot, please let us know; the option to use the legacy driver will soon be deprecated).

A major design goal of the new driver, since day 1, has been a [Library-based architecture that allows better integration with build tools](https://github.com/apple/swift-driver/blob/main/README.md?plain=1#L8). Deploying a compiler driver, itself written in Swift, with this architecture, has made it possible to integrate the driver library directly into SwiftPM and Xcode - giving the higher-level build system broader visibility into compilation sub-tasks required to build a given module. Being able to centrally schedule all such subtasks individually, across all compiled targets, yielded significant improvements to build performance. (More details in this session from WWDC22: [Demystify parallelization in Xcode builds](https://developer.apple.com/videos/play/wwdc2022/110364/))

## Module Dependency Resolution and Explicit Builds

As [outlined](https://forums.swift.org/t/explicit-module-builds-the-new-swift-driver-and-swiftpm/36990) by @Douglas_Gregor a while back, building Swift code with _explicitly-built_ modules is a goal to transition the compiler away from _implicit_ dependency discovery and compilation. To re-state the problem: today, with _implicit_ module loading, encountering an `import` statement in program code causes the Swift compiler to perform a filesystem search for the imported module. It may mean either:

1. Discovering and loading an existing binary `.swiftmodule` .
2. Discovering a `.swiftinterface` for the required module and launching a compiler sub-invocation in a separate thread to build the textual interface into a binary module.
3. Querying Swift’s built-in Clang instance to perform module lookup and find a pre-built binary Clang module ( `.pcm`).
4. Similarly to (2), have Swift’s built-in Clang instance launch a module build for a discovered `.modulemap` into a `.pcm` that can be loaded by the Swift compiler.

Notably, when loading/building dependencies, this process nests and repeats itself for all transitive module dependencies as well.

This approach results in several classes of problems that we are now in a position to tackle:

1. **Brittle build artifact sharing and work duplication:** With many Swift compilations simultaneously in-flight, they are likely to have common dependencies, each attempting to both locate _and_ build the same dependency modules. To deal with this, both Swift and Clang utilize filesystem synchronization mechanisms to attempt to avoid races with multiple compilers writing the same or similar modules into the module cache simlutaneously. Filesystem locking mechanisms can be brittle and are not sufficient to ensure maximal re-use of common shared artifacts. They also introduce a performance penalty of having compilers waiting on an arbitrary number of file-locks placed by other in-flight compilers. Moreover, all such compilations with shared module dependencies are each re-doing the work searching the filesystem for module dependencies.
2. **Difficult to debug failure modes:** The resulting module cache state is prone to cache invalidation issues and races in the file-system, which are failure modes that are exceedingly difficult to reason about. Moreover, other dependency build failures may occur in deeply-nested compiler sub-invocations: understanding a dependency compilation error can mean having to reason about state of many levels of compiler sub-invocations from the original compiler frontend invocation.
3. **Opaque to build systems/clients:** Dependency discovery and build operations are invisible to compilation clients (either the Swift Driver itself, or any other higher-level Build System). Meaning they do not participate in global task scheduling and failures during dependency build actions manifest from arbitrary parent compilation tasks that encounter them first.

#### Dependency Scanning and Explicit Module Builds

Explicit module loading moves compilation of textual module dependencies (both Swift and Clang) into separate compiler invocations. This is achieved by having a separate new stage of compilation: Dependency Scanning, which is invoked early during build planning by the driver. The dependency scanner is a component of the Swift compiler (invoked either via `swift-frontend -scan-dependencies...` or via API calls to `libSwiftScan`) which resolves `import`s to their underlying modules discovered on the file-system, which may fall into one of the four kinds of dependencies outlined above. The output of the scan is a build graph that contains the complete set of dependencies, direct and transitive, required to build the current module, as well as recipes on how to build them, for textual modules. Swift Driver takes this graph and produces a set of compiler invocations required to build all these dependencies into modules (either `swift-frontend -compile-module-from-interface` or `swift-frontend -emit-pcm`), which are then used as explicit inputs to compilation tasks. This results in the following improvements which directly address the shortcomings of _implicit_ module loading outlined above:

- None of the compilation tasks of a given target search the filesystem for module dependencies.
  - This is now done only once in the dependency scanner, ahead of time, saving redundant work from having to be repeated by each compiler invocation.

- None of the compilation tasks of a given target spawn compilation sub-invocations in a separate thread to build a textual module dependency (either Swift or Clang) into a binary module dependency.
  - Defining away the potential errors caused by filesystem-locking and module cache consistency and inheritance of compilation state from parent invocations.

- Dependency build actions are explicit compilation tasks, with a standalone compilation command and input/output dependencies to all other compilation tasks. Failures during dependency build actions are unambiguously-attributable and reproducible.
- Dependency build actions are exposed to the build system and can be scheduled in-concert with all other compilation tasks.

Moreover, further removing the Swift compiler frontend from the filesystem state makes it more amenable to potential fine-grained [build-caching technologies](https://discourse.llvm.org/t/rfc-add-an-llvm-cas-library-and-experiment-with-fine-grained-caching-for-builds/59864).

### Explicit Modules Today

As per the previous [update](https://forums.swift.org/t/explicit-module-builds-the-new-swift-driver-and-swiftpm/36990/17), SwiftDriver contains an initial implementation of Explicit Module Builds:

- An individual target can be built with Explicit Modules using: `swiftc -explicit-module-build...`

- SwiftPM can use it for explicit package builds with `swift build --``use-integrated-swift-driver --experimental-explicit-module-build...`

- The Driver’s dependency scanner integration is being used in Swift Package Manager to gain build-time insight into direct target dependencies in order to [diagnose](https://github.com/apple/swift-package-manager/pull/3562) usage of targets not explicitly-specified in the package manifest.

### Ongoing Work

We are continuing our efforts to bring Explicit Modules up-to-par with today’s status quo in terms of both _functionality_ and _build performance_. Our goal is to eventually fully replace/remove Swift compiler’s module-searching and ad-hoc dependency-building functionality. In the meantime, areas of focus span

- Working on ensuring correctness and maximizing re-use in the dependency scanner cache across different scanning invocations with the same scanner instance.
- Working towards eliminating the use of auto-linking directives when building with Explicit Modules: instead of inserting an auto-link directive, the driver can utilize its complete knowledge of the build graph to generate a complete linker invocation.

In the end state, the Swift compiler frontend will have only two module loaders remaining: explicitly-provided binary Swift module loader and explicitly-provided binary Clang module loader. With the rest subsumed by the dependency scanner and SwiftDriver’s dependency build planning.

## Module Loading Future is Swift

The Swift compiler has been gaining in components written in Swift: the new Driver, [libSwift](https://forums.swift.org/t/libswift-is-enabled/49687) as a means to write SIL Optimizations in Swift, and now the new [Swift Parser](https://forums.swift.org/t/a-new-swift-parser-for-swiftsyntax/59813). When _explicit_ module loading becomes the default, the dependency scanner will become an integral early step in the compilation pipeline, invoked by the driver. Today, this involves either launching a `swift-frontend` task or querying `libSwiftScan` requiring the following functionality and logic that live in the compiler today:

1. Knowledge of all (user-specified and default) search paths and how to traverse them on the filesystem looking for modules
2. Ability to deserialize a binary `.swiftmodule` in order to get at its list of module dependencies.
3. Ability to parse a `.swiftinterface` in order to extract its compilation flags and collect its `import` statements.
4. Direct API access to the Clang dependency scanner to lookup Clang modules.

Because parsing Swift source-code will soon be done purely in Swift, the dependency scanner becomes the next natural candidate for itself being re-imagined in Swift. Today, the logic of _implicit_ module loaders and dependency scanning is greatly intertwined. Without the need to do implicit module loading, the dependency scanner stands to benefit from being greatly simplified, affording an opportunity to do so in Swift along the way. Aside from this and other obvious benefits that using a language like Swift entails, this will, crucially, allow Swift’s Parser to reason about `#if canImport()` statements without involving the compiler and allow Swift Driver to perform build planning without involving compiler logic, further simplifying the compilation flow.

The new dependency scanner will then be required to address the above-outlined functionality:

1. The Swift Driver has knowledge of user-provide search paths which it would convey to the scanner. The scanner will then need to abstract the file-system using something akin to what today is provided by [STSC](https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCBasic/FileSystem.swift), or what is being built up in [swift-system](https://github.com/apple/swift-system/blob/main/Sources/System/FilePath/FilePath.swift#L44).
2. An implementation of LLVM Bitcode deserialization already exists in Swift in [STSC](https://github.com/apple/swift-tools-support-core/blob/main/Sources/TSCUtility/Bitstream.swift), and is already used in Swift Driver to read out incremental build information. This same implementation can be extended to read out a list of a binary module’s dependencies.
3. The new Swift Parser makes it simple to be able to parse a `.swiftinterface` in order to extract interface-specific information (command-line flags, version, etc.) and gather all `import` statements.
4. The Clang dependency scanner vends a [stable C API](https://github.com/apple/llvm-project/blob/next/clang/include/clang-c/Dependencies.h) that Swift clients can use directly.

---

<div class="post-metadata">

**Author:** ![hovox](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hovox/32/691_2.png) [@hovox](https://forums.swift.org/u/hovox)\
**Post date:** [October 31, 2022, 11:38am UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/2 "2022-10-31T11:38:17Z")

</div>

Hi,

In our project disabling swift driver makes incremental builds 3 times faster, so with `SWIFT_USE_INTEGRATED_DRIVER = NO` we have 55 seconds build time just by changing something in swift code (even some private variable value). And with `SWIFT_USE_INTEGRATED_DRIVER = YES` we have 162 seconds build time.

Is it expected behavior ? Do you want us share more data in order to fix this issue (of course if this is an unknown issue).

---

<div class="post-metadata">

**Author:** ![ArtemC](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/artemc/32/27369_2.png) [@ArtemC](https://forums.swift.org/u/ArtemC)\
**Post date:** [October 31, 2022, 3:49pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/4 "2022-10-31T15:49:49Z")

</div>

We have had a couple of similar reports but getting more data would always be useful. Could you please file a feedback about this issue and attach a build timing summary of builds with this build setting enabled and disabled?

A build timing summary can be obtained by running `xcodebuild` with these additional flags: `-showBuildTimingSummary -resultBundlePath ./Build.xcresult`. Thank you!

---

<div class="post-metadata">

**Author:** ![hovox](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hovox/32/691_2.png) [@hovox](https://forums.swift.org/u/hovox)\
**Post date:** [October 31, 2022, 4:26pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/5 "2022-10-31T16:26:44Z")

</div>

Sure, we already have a report FB10821586. Do you want me to share `Build.xcresult` file or just timing summary ?

---

<div class="post-metadata">

**Author:** ![ArtemC](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/artemc/32/27369_2.png) [@ArtemC](https://forums.swift.org/u/ArtemC)\
**Post date:** [October 31, 2022, 4:27pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/6 "2022-10-31T16:27:33Z")

</div>

> [@hovox](#):
>
> FB10821586

If you could add the `.xcresult` file to this, that would be great.

---

<div class="post-metadata">

**Author:** ![Jon\_Shier](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jon_shier/32/921_2.png) [@Jon\_Shier](https://forums.swift.org/u/Jon_Shier)\
**Post date:** [October 31, 2022, 7:10pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/7 "2022-10-31T19:10:37Z")

</div>

> [@hovox](#):
>
> SWIFT\_USE\_INTEGRATED\_DRIVER

Are you setting this as a custom Xcode build setting, or as an environment variable somewhere? I'm trying to test whether my build issues may also be related.

In prior testing it was clear that the Swift driver has slower startup than the old, which is unfortunate.

---

<div class="post-metadata">

**Author:** ![ainopara](https://avatars.discourse-cdn.com/v4/letter/a/958977/32.png) [@ainopara](https://forums.swift.org/u/ainopara)\
**Post date:** [November 1, 2022, 2:02am UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/8 "2022-11-01T02:02:46Z")

</div>

Is there a way to make libSwiftDriver accept environment variable SWIFT\_EXEC, so that XCRemoteCache can be used to further speed up builds when swift-driver is enabled?

Related Issue: [Support Xcode 14.0+ Swift driver · Issue #153 · spotify/XCRemoteCache · GitHub](https://github.com/spotify/XCRemoteCache/issues/153)

---

<div class="post-metadata">

**Author:** ![hovox](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hovox/32/691_2.png) [@hovox](https://forums.swift.org/u/hovox)\
**Post date:** [November 1, 2022, 2:42am UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/9 "2022-11-01T02:42:01Z")

</div>

I added it as a custom build setting in Xcode

---

<div class="post-metadata">

**Author:** ![hovox](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hovox/32/691_2.png) [@hovox](https://forums.swift.org/u/hovox)\
**Post date:** [November 1, 2022, 11:53am UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/10 "2022-11-01T11:53:22Z")

</div>

Hi @ArtemC ,

I have attached build logs to the FB10821586, but I have exported Xcode build logs instead of doing build with xcodebuild command, I assume that the result should be the same.  
Please let me know if you need additional data.  
Thanks.

---

<div class="post-metadata">

**Author:** ![liamnichols](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/liamnichols/32/6570_2.png) [@liamnichols](https://forums.swift.org/u/liamnichols)\
**Post date:** [November 8, 2022, 11:23pm UTC](https://forums.swift.org/t/evolving-how-swift-is-built-driver-as-a-library-explicitly-built-modules-and-beyond/61102/11 "2022-11-08T23:23:16Z")

</div>

Cross posting my message from a different thread, but it looks like I'm facing a similar issue:

> [@Swift 5.7 / Xcode 14 incremental build time 10x slower than Swift 5.6 / Xcode 13](https://forums.swift.org/t/swift-5-7-xcode-14-incremental-build-time-10x-slower-than-swift-5-6-xcode-13/60568/19):
>
> I'm not entirely sure if we're seeing the same issue described here, or something else but after moving to Xcode 14 there is some kind of bug that is adding a ~52s increase in an incremental build where I'm just making a relatively isolated one line change in a view controller. In Xcode 13.4.1, the same incremental build takes ~23s and in Xcode 14.1 we're up to 75s which suggests that this one step is the issue. Furthermore, with the help of @Jon_Shier, I found that setting the SWIFT\_USE\_INTEGR…

Reported as FB11768802 with as much info as I can, but I unfortunately can't isolate the issue in my project and provide an example. Let me know if there is anything else that we can do to help dig deeper.
