apple/swift
Design decisions
A grab-bag of "why does it work this way?" notes. Each item points at the canonical document where the decision was made.
SIL exists
Most modern compilers go AST → LLVM IR. Swift inserts SIL between them. The reasoning, taken from docs/SIL/SIL.rst:
- LLVM IR cannot represent ownership, generics, or dynamic dispatch faithfully. Lowering directly would require encoding all of those in a way LLVM tools couldn't reason about.
- SIL preserves type information, ownership, and Swift's calling-conventions. The optimizer can therefore do Swift-specific work (specialization, devirtualization, ARC reduction) before LLVM ever sees the program.
- SIL is round-trip-able as text -- which makes it scriptable and testable.
test/SILOptimizer/has thousands of.siltest fixtures that exercise individual passes.
The trade-off: SIL is another IR to maintain. The optimizer is large, the verifier is large, and the format has its own tooling. For Swift the trade-off has consistently paid off.
Two demanglers
The demangler exists in two compiled instances: lib/Demangling/ (linked into the compiler and tools) and stdlib/public/Demangling/ (compiled into the runtime so that swift_demangle can run inside any Swift program).
The reasoning: the runtime cannot link against the LLVM toolchain libraries (those are far too large), and tools cannot use the runtime's hand-rolled implementation. The compromise is a single set of shared C++ source files included from both build targets.
Resilience and .swiftinterface
ABI-stable libraries (Apple SDK frameworks, the standard library on Apple platforms) have to evolve without recompiling clients. Two choices made this work:
- Resilience (
-enable-library-evolution) makes most types accessed through value-witness tables -- the layout is private and can change. The fields-in-registers fast path is reserved for@frozentypes. .swiftinterfaceis a textual public-API surface. A client can re-typecheck it with a newer compiler without re-shipping the binary.lib/Frontend/ModuleInterfaceLoader.cpphandles the on-the-fly typecheck.
docs/LibraryEvolution.rst is the design doc. docs/StableBitcode.md and docs/Serialization.md cover the file formats.
Generic specialization is opt-in by @inlinable
Generic functions are by default not specialized for clients of an ABI-stable library. To get specialization, the function must be @inlinable (its SIL body is shipped in the .swiftmodule). For the standard library this means most generics are inlinable; for non-stdlib libraries you opt in case-by-case.
The request evaluator instead of a multi-pass type checker
Older C++ compilers run several passes over the AST in fixed order. Swift's type checker is request-driven: each query (interface type, default arg, generic signature, conformance witness, ...) is a memoized request. The evaluator handles dependency tracking for incremental builds. See docs/RequestEvaluator.md.
OSSA SIL
The shift from "SIL with implicit ARC" to "SIL with explicit ownership annotations" (OSSA) was a multi-year transition. The motivation: many SIL passes correctness-critically depend on knowing whether a value is owned or borrowed. Encoding that in the IR makes the optimizer's job tractable. docs/OwnershipManifesto.md is the design doc.
Distributed actors compose with regular actors
Distributed actors are a layering on top of regular actors rather than a separate concept: a distributed actor is also an actor and benefits from the same isolation rules. The runtime difference is only at the call site -- a remote call goes through a thunk and the user's DistributedActorSystem rather than executing locally. See Distributed actors.
C++ interop bridges in both directions
Swift sees C++ types as if they were Swift types (after import). C++ can see Swift types via PrintAsClang. The choice to support both directions (rather than only Swift-imports-C++) means a Swift library can ship a C++ client API; the alternative (C++ wrapping Swift via Objective-C) was rejected as too restrictive. See docs/CppInteroperability/.
Macros run in a separate process
A Swift macro is user code that runs at compile time. Running it inside the compiler would create trust and crash-isolation issues. The chosen design hosts macros in swift-plugin-server, a separate process. The compiler talks to the plugin via a stable protocol; a faulty macro can crash the plugin without crashing the compile. The price is some IPC latency.
Embedded Swift is a subset, not a separate language
Embedded mode disables features rather than introducing new ones. This keeps the language unitary -- existing code can compile in embedded mode if it avoids the disabled features. The chosen alternative (a separate "Swift Lite" language) was rejected for fragmenting the ecosystem. See docs/EmbeddedSwift/.
A new build (Runtimes/) instead of refactoring stdlib/
stdlib/CMakeLists.txt accumulated 15 years of platform-specific assumptions. Refactoring it in place was deemed too risky. Instead, Runtimes/ is a parallel CMake build that pulls in the same sources but with explicit, opt-in configuration. As Runtimes/ reaches parity, it will replace stdlib/'s build. See Runtimes.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.