From 8d7987af42b947fc0f6b23bde703861772979fb7 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Fri, 2 May 2025 15:44:08 -0700 Subject: [PATCH 1/3] Add documentation catalog Initial pass at migrating documentation from swiftlang/swift to a more accessible location and reformatting it into a docc catalog. I've added a number of placeholder pages for additional documentation to be written. Community members are very welcome to help contribute to this documentation. --- .spi.yml | 5 + Package.resolved | 24 ++ Package.swift | 15 + .../BuildSystemSupport/IntegrateWithBazel.md | 3 + .../BuildSystemSupport/IntegrateWithCMake.md | 3 + .../BuildSystemSupport/IntegrateWithMake.md | 3 + .../IntegrateWithSwiftPM.md | 3 + .../BuildSystemSupport/IntegrateWithXcode.md | 6 + .../Documentation.docc/Development/Status.md | 77 +++++ .../Documentation.docc/Documentation.md | 196 ++++++++++++ .../GettingStarted/InstallEmbeddedSwift.md | 3 + .../GettingStarted/UserManual.md | 278 ++++++++++++++++++ .../Documentation.docc/LanguageDetails/ABI.md | 58 ++++ .../LanguageDetails/Existentials.md | 108 +++++++ .../LanguageDetails/NonFinalGenericMethods.md | 130 ++++++++ .../SDKSupport/IntegrateWithPico.md | 112 +++++++ Sources/EmbeddedSwift/Empty.swift | 12 + 17 files changed, 1036 insertions(+) create mode 100644 .spi.yml create mode 100644 Package.resolved create mode 100644 Package.swift create mode 100644 Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithBazel.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithCMake.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithMake.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithSwiftPM.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithXcode.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/Development/Status.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/Documentation.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/GettingStarted/InstallEmbeddedSwift.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/GettingStarted/UserManual.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/ABI.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/Existentials.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/NonFinalGenericMethods.md create mode 100644 Sources/EmbeddedSwift/Documentation.docc/SDKSupport/IntegrateWithPico.md create mode 100644 Sources/EmbeddedSwift/Empty.swift diff --git a/.spi.yml b/.spi.yml new file mode 100644 index 0000000..bf52842 --- /dev/null +++ b/.spi.yml @@ -0,0 +1,5 @@ +version: 1 +builder: + configs: + - documentation_targets: + - EmbeddedSwift diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..034f2da --- /dev/null +++ b/Package.resolved @@ -0,0 +1,24 @@ +{ + "originHash" : "ffbd64644921de7b1a9cb8b17d9ecb480b14c4b6487677314b16730c1b12221b", + "pins" : [ + { + "identity" : "swift-docc-plugin", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-plugin", + "state" : { + "revision" : "85e4bb4e1cd62cec64a4b8e769dcefdf0c5b9d64", + "version" : "1.4.3" + } + }, + { + "identity" : "swift-docc-symbolkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-docc-symbolkit", + "state" : { + "revision" : "b45d1f2ed151d057b54504d653e0da5552844e34", + "version" : "1.0.0" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..4f2e8c0 --- /dev/null +++ b/Package.swift @@ -0,0 +1,15 @@ +// swift-tools-version: 6.1 + +import PackageDescription + +let package = Package( + name: "swift-embedded-examples", + products: [ + .library(name: "EmbeddedSwift", targets: ["EmbeddedSwift"]) + ], + dependencies: [ + .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + ], + targets: [ + .target(name: "EmbeddedSwift") + ]) diff --git a/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithBazel.md b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithBazel.md new file mode 100644 index 0000000..44298f6 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithBazel.md @@ -0,0 +1,3 @@ +# Integrate with Bazel + +🚧 Under construction... diff --git a/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithCMake.md b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithCMake.md new file mode 100644 index 0000000..a4b1ade --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithCMake.md @@ -0,0 +1,3 @@ +# Integrate with CMake + +🚧 Under construction... diff --git a/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithMake.md b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithMake.md new file mode 100644 index 0000000..9cd03df --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithMake.md @@ -0,0 +1,3 @@ +# Integrate with Make + +🚧 Under construction... diff --git a/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithSwiftPM.md b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithSwiftPM.md new file mode 100644 index 0000000..9c01a76 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithSwiftPM.md @@ -0,0 +1,3 @@ +# Integrate with SwiftPM + +🚧 Under construction... diff --git a/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithXcode.md b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithXcode.md new file mode 100644 index 0000000..e1b79af --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/BuildSystemSupport/IntegrateWithXcode.md @@ -0,0 +1,6 @@ +# Integrate with Xcode + +🚧 Under construction... + +> Warning: Embedded Swift integration with Xcode is still under development and +> subject to change. diff --git a/Sources/EmbeddedSwift/Documentation.docc/Development/Status.md b/Sources/EmbeddedSwift/Documentation.docc/Development/Status.md new file mode 100644 index 0000000..5662f61 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/Development/Status.md @@ -0,0 +1,77 @@ +# Status + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +## Embedded Swift Language Features + +| **Language Feature** | **Currently Supported In Embedded Swift** | +|-------------------------------------------------------|------------------------------------------------------------------------------------| +| *Anything not listed below* | Yes | +| Library Evolution (resilience) | No, intentionally unsupported long-term | +| Objective-C interoperability | No, intentionally unsupported long-term | +| Non-WMO builds | No, intentionally unsupported long-term (WMO should be used) | +| Existentials (values of protocol types) | Only class-bound existentials (for protocols derived from AnyObject) are supported | +| Any | No, currently disallowed | +| AnyObject | Yes | +| Metatypes | No, currently only allowed as unused arguments (type hints) | +| Untyped throwing | No, intentionally unsupported long-term (typed throwing should be used instead) | +| Weak references, unowned references | No | +| Non-final generic class methods | No, intentionally unsupported long-term, see <[Embedded Swift -- Non-final generic methods](NonFinalGenericMethods.md)>| +| Parameter packs (variadic generics) | No, not yet supported | + +## Embedded Standard Library Breakdown + +This status table describes which of the following standard library features can be used in Embedded Swift: + +| **Swift Standard Library Feature** | **Currently Supported In Embedded Swift?** | +|------------------------------------------------------------|-----------------------------------------------------| +| Array (dynamic heap-allocated container) | Yes | +| Array slices | Yes | +| assert, precondition, fatalError | Partial, only StaticStrings can be used as a failure message | +| Bool, Integer types, Float types | Yes | +| Codable, Encodable, Decodable | No | +| Collection + related protocols | Yes | +| Collection algorithms (sort, reverse) | Yes | +| CustomStringConvertible, CustomDebugStringConvertible | Yes, except those that require reflection (e.g. Array's .description) | +| Dictionary (dynamic heap-allocated container) | Yes | +| Floating-point conversion to string | No | +| Floating-point parsing | No | +| FixedWidthInteger + related protocols | Yes | +| Hashable, Equatable, Comparable protocols | Yes | +| InputStream, OutputStream | No | +| Integer conversion to string | Yes | +| Integer parsing | Yes | +| KeyPaths | Partial (only compile-time constant key paths to stored properties supported, only usable in MemoryLayout and UnsafePointer APIs) | +| Lazy collections | Yes | +| ManagedBuffer | Yes | +| Mirror (runtime reflection) | No, intentionally unsupported long-term | +| Objective-C bridging | No, intentionally unsupported long-term | +| Optional | Yes | +| print / debugPrint | Partial (only String, string interpolation, StaticStrings, integers, pointers and booleans, and custom types that are CustomStringConvertible) | +| Range, ClosedRange, Stride | Yes | +| Result | Yes | +| Set (dynamic heap-allocated container) | Yes | +| SIMD types | Yes | +| StaticString | Yes | +| String (dynamic) | Yes | +| String interpolations | Partial (only strings, integers, booleans, and custom types that are CustomStringConvertible can be interpolated) | +| Unicode | Yes | +| Unsafe\[Mutable\]\[Raw\]\[Buffer\]Pointer | Yes | +| VarArgs | No | + +## Non-stdlib Features + +This status table describes which of the following Swift features can be used in Embedded Swift: + +| **Swift Feature** | **Currently Supported In Embedded Swift?** | +|------------------------------------------------------------|-----------------------------------------------------| +| Synchronization module | Partial (only Atomic types, no Mutex) | +| Swift Concurrency | Partial, experimental (basics of actors and tasks work in single-threaded concurrency mode) | +| C interop | Yes | +| C++ interop | Yes | +| ObjC interop | No, intentionally unsupported long-term | +| Library Evolution | No, intentionally unsupported long-term | diff --git a/Sources/EmbeddedSwift/Documentation.docc/Documentation.md b/Sources/EmbeddedSwift/Documentation.docc/Documentation.md new file mode 100644 index 0000000..2cae1cc --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/Documentation.md @@ -0,0 +1,196 @@ +# ``EmbeddedSwift`` + +Summary + +## Overview + +Swift is a general purpose programming language suitable for both high-level application software and for low-level systems software. The existing major supported deployments of Swift are primarily targeting “large” operating systems (Linux, Windows, macOS, iOS), where storage and memory are relatively plentiful, multiple applications share code via dynamic linking, and the system can be expected to provide a number of common libraries (such as the C and C++ standard libraries). The typical size of the Swift runtime and standard library in these environments is around 5MB of binary size. + +However, lots of embedded platforms and low-level environments have constraints that make the standard Swift distribution impractical. These constraints have been described and discussed in existing prior work in this area, and there have been great past discussions in the Swift forums ([link](https://forums.swift.org/t/introduce-embedded-development-using-swift/56573)) and in last year’s video call ([link](https://forums.swift.org/t/call-for-interest-video-call-kickoff-using-swift-for-embedded-bare-metal-low-resources-programming/56911)), which shows there is a lot of interest and potential for Swift in this space. The motivation of “Embedded Swift” is to achieve a first class support for embedded platforms and unblock porting and using Swift in small environments. In particular the targets are: + +* (1) Platforms that have very limited memory + * Microcontrollers and embedded systems with limited memory + * Popular MCU board families and manufacturers (Arduino, STM32, ESP32, NXP, etc.) commonly offer boards that only have an order of 10’s or 100’s of kB of memory available. + * Firmware, and especially firmware projects that are run from SRAM, or ROM +* (2) Environments where runtime dependencies, implicit runtime calls, and heap allocations are restricted + * Low-level environments without an underlying operating system, such as bootloaders, hypervisors, firmware + * Operating system kernels, kernel extensions, and other non-userspace software components + * Userspace components that are too low-level in terms of dependencies, namely anything that the Swift runtime depends on. + * A special case here is the Swift runtime itself, which is today written in C++. The concepts described further in this document allow Swift to become the implementation language instead. + +A significant portion of the current Swift runtime and standard library supports Swift’s more dynamic features, particularly those that involve metadata. These features include: + +* Dynamic reflection facilities (such as mirrors, `as?` downcasts, and printing arbitrary values) +* Existentials +* ABI stability with support for library evolution +* Separately-compiled generics +* Dynamic code loading (plug-ins) + +On “smaller” operating systems, and in restricted environments with limited binary and memory size, the size of a full Swift standard library (with all public types and APIs present) and a Swift runtime, as well as the metadata required to support dynamic features, can be so large as to prevent the usage of Swift completely. In such environments, it may be reasonable to trade away some of the flexibility from the more dynamic features to achieve a significant reduction in code size, while still retaining the character of Swift. + +The following diagram summarizes the existing approaches to shrink down the size of the Swift runtime and standard library, and how “Embedded Swift” is tackling the problems with a new approach: + +diagram + +This document presents a vision for “Embedded Swift”, a new compilation model of Swift that can produce extremely small binaries without external dependencies, suitable for restricted environments including embedded (microcontrollers) and baremetal setups (no operating system at all), and low-level environments (firmware, kernels, device drivers, low-level components of userspace OS runtimes). + +Embedded Swift limits the use of language and standard library features that would require a larger Swift runtime, while maintaining most of Swift’s feature set. It is important that Embedded Swift not become a separate dialect of Swift. Rather, it should remain an easy-to-explain subset of Swift that admits the same code and idioms as the full Swift language, where any restrictions on the language model are flagged by the Swift compiler. The subset itself should also be useful beyond low-level environments, for example, in high-performance runtimes and kernels embedded within a larger Swift application. The rest of this document describes exactly which language features are impacted, as well as the compilation model used for restricted environments. + +## Goals + +There are several goals of this new compilation mode: + +* **Eliminate the “large codesize cost of entry”** for Swift. Namely, the size of the supporting libraries (Swift runtime and standard library) must not dominate the binary size compared to application code. +* **Simplify the code generated by the compiler** to make it easier to reason about it, and about its performance and runtime cost. In particular, complex runtime mechanisms such as runtime generic instantiation are undesirable. +* **Allow effective and intuitive dead-code stripping** on application, library and standard library code. +* **Support environments with and without a dynamic heap**. Effectively, there will be two bottom layers of Swift, and the lower one, “non-allocating” Embedded Swift, will necessarily be a more restricted compilation mode (e.g. classes will be disallowed as they fundamentally require heap allocations) and likely to be used only in very specialized use cases. “Allocating” Embedded Swift should allow classes and other language facilities that rely on the heap (e.g. indirect enums). +* **Remove or reduce the amount of implicit runtime calls**. Specifically, runtime calls supporting heavyweight runtime facilities (type metadata lookups, generic instantiation) should not exist in Embedded Swift programs. Lightweight runtime calls (e.g. refcounting) are permissible but should only happen when the application uses a language feature that needs them (e.g. refcounted types). +* **Introduce a way of producing minimal statically-linked binaries** without external dependencies, namely without the need to link with a non-dead-strippable large Swift runtime/stdlib library, and without the need to link full libc and libc++ libraries. The Swift standard library contains essential facilities for writing Swift code, and must be available to write code against, but it should “fold” into the application and/or be intuitively dead-strippable. +* **Define a language subset, not a dialect.** Any code of Embedded Swift should always compile in regular Swift and behave the same. + * **The Embedded Swift language subset should stay very close to “full” Swift**, even if it means adding alternative ABIs to the compiler to support some of them. Users should expect minimal porting effort to get code to work in Embedded Swift. + +## Embedded Swift Language Subset + +In order to achieve the goals listed above, Embedded **Swift** will impose limitations on certain language features: + +* Library Evolution will be limited in some way, and there’s no expectation of ABI stability or separate distribution of libraries in binary form. +* Objective-C interoperability will not be available. C and C++ interoperability is not affected. +* Reflection and Mirrors APIs will not be available. +* The standard library’s print() function in its current form will not be available, and an alternative will be provided instead. +* Metatypes will be restricted in some way, and code patterns where a metatype value is actually needed at runtime will be disallowed (but at a minimum using a metatype function argument as a type hint will be allowed, as well as calling class methods and initializers on concrete types). + Examples: + ```swift + func foo(t: T.Type) { ... `t` used in a downcast ... } // not OK + extension UnsafeRawPointer { + func load(as type: T.Type) -> T { ... `type` unused ... } // OK + } + MyGenericClass.classFunc() // OK + ``` +* Existentials and dynamic downcasting of existentials will be disallowed. For example: + ```swift + func foo(t: Any.Type) {} // not OK + var e: any Comparable = 42 // not OK + var a: [Any] = [1, "string", 3.5] // not OK + ``` +* The types of thrown errors will be restricted in some manner, because thrown errors are of existential type `any Error` (which is disallowed by the prior item). +* Classes will have restrictions, for example they cannot have non-final generic functions. For example: + ```swift + class MyClass { + func member() { } // OK + func genericMember { } // not OK + } + ``` + It’s an open question whether class metatypes are allowed to be used as runtime values and whether classes will allow dynamic downcasting. +* KeyPaths will be restricted, but at a minimum it will be allowed to use keypath literals to form closures returning a field from a type, and it will be allowed to use keypaths that are compile-time references to inlined stored properties (so that `MemoryLayout.offset(of: ...)` will work on those). +* String APIs requiring Unicode data tables will be unavailable by default (to avoid paying the associated codesize cost), and will require opting in. For example, string iteration, comparing two strings, hashing a string, string splitting are features needing Unicode data tables. These operations should become available on UTF8View instead with the proposal to add Equatable and Hashable conformances to String views ([link](https://forums.swift.org/t/pitch-add-equatable-and-hashable-conformance-to-string-views/60449)). + +**Non-allocating Embedded Swift** will add further restrictions on top of the ones listed above: + +* Classes cannot be instantiated, indirect enums cannot be constructed. +* Escaping closures are not allowed. +* Standard library features and API that rely on classes, indirect enums, escaping closures are not available. This includes for example dynamic containers (arrays, dictionaries, sets) and strings. + +The listed restrictions (for both “allocating” and “non-allocating” Embedded Swift) are not necessarily fundamental, and we might be able to (fully or partially) lift some of them in the future, by adding alternative compile-time implementations (as opposed to their current runtime implementations) of the language features. + +## Implementation of Embedded Compilation Mode + +The following describes the high-level points in the approach to implement Embedded Swift in the compiler: + +* **Specialization is required on all uses of generics and protocols** at compile-time, and libraries are compiled in a way that allows cross-module specialization (into clients of the libraries). + * Required specialization (also known as monomorphization in other compilers/languages) needs type parameters of generic types and functions to always be compile-time known at the caller site, and then the compiler creates a specialized instantiation of the generic type/function that is no longer generic. The result is that the compiled code does not need access to any type metadata at runtime. + * This compilation mode will not support separate compilation of generics, as that makes specialization not possible. Instead, library code providing generic types and functions will be required to provide function bodies as serialized SIL (effectively, “source code”) to clients via the mechanism described below. +* **Library code is built as always inlinable and “emitIntoClient”** to support the specialization of generics/protocols in use sites that are outside of the library. + * **This applies to the standard library, too**, and we shall distribute the standard library built this way with the toolchain. + * This effectively provides the source code of libraries to application builds. +* **The need for type metadata at runtime is completely eliminated**, by further ignoring ABI stability, disabling resilience, and disallowing reflection mirrors APIs. Classes with subclasses get a simple vtable (similar to C++ virtual classes). Classes without subclasses become final and don’t need a vtable. Witness tables (which describe a conformance of a type to a protocol) are only used at compile-time and not present at runtime. + * **Type metadata is not emitted into binaries at all.** This causes code emitted by the compiler to become dead-strippable in the intuitive way given that metadata records (concretely type metadata, protocol conformance records, witness tables) are not present in compiler outputs. + * **Runtime facilities to process metadata are removed** (runtime generic instantiation, runtime protocol conformance lookups) because there is no metadata present at runtime. + +## Enabling Embedded Swift Mode + +The exact mechanics of turning on Embedded Swift compilation mode are an open question and subject to further discussion and refinement. There are different use cases that should be covered: + +* the entire platform / system is using Embedded Swift as a platform level decision +* a single component / library is built using Embedded Swift for an environment that otherwise has other code built with other compilation modes or compilers +* for testing purposes, it’s highly desirable to be able to build a library using Embedded Swift and then exercise that library with a test harness that is built with regular Swift + +A possible solution here would be to have a top-level compiler flag, e.g. `-embedded`, but we could also make environments default to Embedded Swift mode where it makes sense to do so, based on the target triple that’s used for the compilation. Specifically, the existing “none” OS already has the meaning of “baremetal environment”, and e.g. `-target arm64-apple-none` could imply Embedded Swift mode. + +Building firmware using `-target arm64-apple-none` would highlight that we’re producing binaries that are “independent“ and not built for any specific OS. The standard library will be pre-built in the baremetal mode and available in the toolchain for common set of CPU architectures. (It does not need to be built “per OS”.) + +To support writing code that’s compiled under both regular Swift and also Embedded Swift, we should provide facilities to manage availability of APIs and conditional compilation of code. The concrete syntax for that is subject to discussion, the following snippet is presented only as a straw-man proposal: + +```swift +@available(embedded, unavailable, "not available in Embedded Swift mode") +public func notAvailableOnEmbedded() + +#if !mode(embedded) +... code not compiled under Embedded Swift mode ... +#endif + +@available(noAllocations, unavailable, "not available in no allocations mode") +public func notAvailableInNonAllocatingMode() + +#if !mode(noAllocations) +... code not compiled under no allocations mode ... +#endif +``` + +## Dependencies of Embedded Swift Programs + +The expectation is that for “non-allocating” Embedded Swift, the user should only need a working Swift toolchain, and be able to pass a (set of) .swift file(s) to the compiler and receive a .o file that is just as simple to work with (e.g. to be linked into any library, app, firmware binary, etc.) as if it was produced by Clang on source code written in C: + +``` +$ swiftc *.swift -target arm64-apple-none -no-allocations -wmo -c -o a.o +$ nm -gm a.o +... shows no dependencies beyond memset/memcpy ... +memset +memcpy +``` + +A similar situation is expected even for "allocating" Embedded Swift, except that there will be a need for a small runtime library (significantly smaller compared to the existing Swift runtime written in C++) to support object instantiation and refcounting: + +``` +$ swiftc *.swift -target arm64-apple-none -wmo -c -o a.o +$ nm -gm a.o +... only very limited dependencies ... +malloc +calloc +free +swift_allocObject +swift_initStackObject +swift_initStaticObject +swift_retain +swift_release +``` + +The malloc/calloc/free APIs are expected to be provided by the platform. The Swift runtime APIs will be provided as an implementation that’s optimized for small codesize and will be available as a static library in the toolchain for common CPU architectures. Interestingly, it’s possible to write that implementation in “non-allocating” Baremetal Swift. + +## Topics + +### GettingStarted + +- +- + +### Build System Support + +- +- +- +- +- + +### SDK Support + +- + +### Language Details + +- +- +- + +### Development + +- diff --git a/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/InstallEmbeddedSwift.md b/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/InstallEmbeddedSwift.md new file mode 100644 index 0000000..71ed3a0 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/InstallEmbeddedSwift.md @@ -0,0 +1,3 @@ +# Install Embedded Swift + +🚧 Under construction... diff --git a/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/UserManual.md b/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/UserManual.md new file mode 100644 index 0000000..de931f0 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/GettingStarted/UserManual.md @@ -0,0 +1,278 @@ +# User Manual + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +The following document explains how to use Embedded Swift's support in the Swift compiler and toolchain. + +## What Embedded Swift is, and what it isn't + +- Embedded Swift **is** a way to produce small and freestanding binaries (with no, or trivial dependencies). +- Embedded Swift **is not** a complete one-click solution to program all embedded boards and MCUs. +- Embedded Swift **is** a compilation model that's analogous to a traditional C compiler in the sense that the compiler produces an object file (.o) that can be simply linked with your existing code, and it's not going to require you to port any libraries or runtimes. +- Embedded Swift **is not** a HAL, it's not an SDK for development, it's not a set of libraries to program peripherals using high-level APIs. It's instead a compilation mode that's suitable for creating these components. + +## Using Embedded Swift + +A typical setup and build + run cycle for an embedded development board involves: + +- (1) Getting an SDK with the C compilers, headers and libraries for the target +- (2) Building the C source code, and Swift source code into object files. +- (3) Linking all the libraries, C object files, and Swift object files. +- (4) Post-processing the linked firmware into a flashable format (UF2, BIN, HEX, or bespoke formats) +- (5) Uploading the flashable binary to the board over a USB cable using some vendor-provided JTAG/SWD tool, by copying it to a fake USB Mass Storage volume presented by the board or a custom platform bootloader. +- (6) Restarting the board, observing physical effects of the firmware (LEDs light up) or UART output over USB, or presence on network, etc. + +Most of these steps are out of scope for this document, instead refer to the vendor provided documentation. This document only focuses on (2) from the list above, and it's important that you first get familiar with the details of firmware development for your board without Swift in the mix. Even if you want to build a completely pure Swift firmware, you are still going to need the vendor provided tooling for linking, post-processing, uploading, etc. + +## Building code using Embedded Swift + +A basic way to build a set of Swift source files in Embedded Swift mode, is to simply give the compiler (1) a target triple, (2) the `-enable-experimental-feature Embedded` flag, (3) the set of source files that form the input module: + +```bash +$ swiftc -target -enable-experimental-feature Embedded -wmo \ + input1.swift input2.swift ... -c -o output.o +``` + +On macOS, it's common to have Xcode installed, which comes with a toolchain that does not support Embedded Swift yet. Unless you download, install, and activate a swift.org toolchain, you'll see this error: + +```bash +$ swiftc input1.swift -enable-experimental-feature Embedded -wmo +:0: error: unable to load standard library for target 'arm64-apple-macosx15.0' +``` + +To resolve it, download and install a nightly toolchain from swift.org. Then, don't forget to activate it in your terminal by setting the `TOOLCHAINS` environment variable, for example with this command (if you installed into the `/Library` path): + +```bash +$ export TOOLCHAINS=$(plutil -extract CFBundleIdentifier raw /Library/Developer/Toolchains/swift-latest.xctoolchain/Info.plist) +``` + +## Examples + +### Building Swift firmware for an embedded target + +To build Swift firmware (for now ignoring integration with SDKs, libraries and other pre-existing C code), we can use the `-target` argument to specify the CPU architecture. The target triple also decides whether the output object file will be an ELF file, or a Mach-O. For example: + +```bash +# To build an ARMv7 Mach-O object file: +$ swiftc -target armv7-apple-none-macho -enable-experimental-feature Embedded -wmo \ + input1.swift input2.swift ... -c -o output.o + +# To build an ARMv7 ELF object file: +$ swiftc -target armv7-none-none-eabi -enable-experimental-feature Embedded -wmo \ + input1.swift input2.swift ... -c -o output.o +``` + +Additionally, you probably want to specify additional Clang and/or LLVM flags to get the compiler to produce code for the exact ISA and ABI you need for your target. + +For example, a Raspberry Pi Pico / Pico W should target the ARMv6-M architecture via the `armv6m-*` target triple, but the `-mfloat-abi=soft` Clang option should also be used, and if you want to match ABI with libraries built with the GNU toolchain, you might also need `-fshort-enums`. To pass those to Swift, use the `-Xcc` prefix: + +```bash +# To build an ELF object file for ARMv6-M with soft float ABI (floating-point arguments passed in integer registers) and "short enums": +$ swiftc -target armv6m-none-none-eabi -enable-experimental-feature Embedded -wmo \ + -Xcc -mfloat-abi=soft -Xcc -fshort-enums \ + input1.swift input2.swift ... -c -o output.o +``` + +This might not be obvious: `-Xcc` flags are typically only used to alter behavior of the Clang importer, but passing flags to Clang this way also works to specify LLVM target options like selecting a specific CPU architecture (`-march`, `-mcpu`, `-mmcu`), FPU unit availability (`-mfpu`), which registers are used to pass floating-point values (`-mfloat-abi`), and others. + +### Integrating with embedded SDKs and build systems + +For details and concrete examples of how to integrate with existing SDKs, see [Embedded Swift -- Integrating with embedded SDKs](IntegratingWithSDKs.md). + +### Building a macOS Embedded Swift program: + +It's also possible to build in Embedded Swift mode for regular non-embedded operating systems, like macOS. This is very useful for testing purposes, or if you just want to observe and experiment with Embedded Swift. A simple source code like this: + +```swift +print("Hello, embedded world!") +``` + +...can be compiled using the `-enable-experimental-feature Embedded` flag (the implicit `-target` matches the host OS): + +```bash +$ xcrun swiftc hello.swift -enable-experimental-feature Embedded -wmo +$ ./hello +Hello, embedded world! +``` + +Note that the resulting executable is still a *dynamically-linked executable*, so it's not fully standalone in the embedded sense. Namely is still uses `putchar` from Libsystem. But the singular object file that was used to build this executable was produced by the compiler in the same fashion that a real embedded build would. If we ask the compiler and linker to minimize the size of the outputs and to remove any unused code, we can observe that the binary has no other dependencies other than `putchar` and that the machine code section is very small (172 bytes in the `__text` section): + +```bash +$ xcrun swiftc hello.swift -enable-experimental-feature Embedded -wmo -Osize -Xlinker -dead_strip +$ nm -um ./hello + (undefined) external _putchar (from libSystem) +$ size -m ./hello +Segment __TEXT: 16384 + Section __text: 172 +... +``` + +## Strings + +Both StaticString and String types are available in Embedded Swift. As is the case in desktop Swift, certain operations on strings require Unicode data tables for strict Unicode compliance. In Embedded Swift these data tables are provided as a separate static library (libUnicodeDataTables.a) that users need to link in manually – if they need to use these string operations. If the library is required, linking will fail due to missing on one or more of the following symbols: + +``` +_swift_stdlib_getAge +_swift_stdlib_getBinaryProperties +_swift_stdlib_getCaseMapping +_swift_stdlib_getComposition +_swift_stdlib_getDecompositionEntry +_swift_stdlib_getGeneralCategory +_swift_stdlib_getGraphemeBreakProperty +_swift_stdlib_getMapping +_swift_stdlib_getMphIdx +_swift_stdlib_getNameAlias +_swift_stdlib_getNormData +_swift_stdlib_getNumericType +_swift_stdlib_getNumericValue +_swift_stdlib_getScalarBitArrayIdx +_swift_stdlib_getScalarName +_swift_stdlib_getScript +_swift_stdlib_getScriptExtensions +_swift_stdlib_getSpecialMapping +_swift_stdlib_getWordBreakProperty +_swift_stdlib_isLinkingConsonant +_swift_stdlib_nfd_decompositions +``` + +To resolve this, link in the `libswiftUnicodeDataTables.a` that's in Swift toolchain's resource directory (`lib/swift/`) under the target triple that you're using: + +```bash +$ swiftc -target armv6m-none-none-eabi -enable-experimental-feature Embedded -wmo -c -o output.o +$ ld ... -o binary output.o $(dirname `which swiftc`)/../lib/swift/embedded/armv6m-none-none-eabi/libswiftUnicodeDataTables.a +``` + +**Unicode data tables are required for (list not exhaustive):** + +- Comparing String objects for equality +- Sorting Strings +- Using String's hash values, and in particular using String as dictionary keys +- Using String's `.count` property +- Using Unicode-aware string processing APIs (`.split()`, iterating characters, indexing) +- Using Unicode-aware conversion String APIs (`.uppercased()`, `.lowercased()`, etc.) + +**For contrast, unicode data tables are *not required for* (list not exhaustive):** + +- Using StaticString +- Creating, concatenating, string interpolating, and printing String objects +- Using `.utf8`, `.utf16`, and `.unicodeScalars` views of strings, including their .count property, using them as dictionary keys + +Manually linking `libswiftUnicodeDataTables.a` is required for several reasons, including acknowledging that the data tables are desirable: Since they have a non-negligible size, it's useful to be aware that you are using them. + +## Conditionalizing compilation for Embedded Swift + +It's often useful to have source code be compilable under both regular Swift and Embedded Swift. The following syntax is available for that (but note that as the rest of Embedded Swift, it's experimental, subject to change and not considered source stable): + +```swift +func sayHello() { + #if hasFeature(Embedded) + print("I'm Embedded Swift") + #else + print("I'm regular Swift") + #endif +} +``` + +Additionally, you can also use an attribute (also experimental, and not source stable) to make entire functions, types and other declarations unavailable in Embedded Swift. This can be particularly useful to explicitly mark your own code (and also entire types and conformances) that relies on features unavailable in Embedded Swift, e.g. the Any type or Codable -- it is explicitly allowed to use those in unavailable contexts: + +```swift +@_unavailableInEmbedded +func useAny(_: Any) { ... } + +@_unavailableInEmbedded +extension MyStruct: Codable { + ... +} +``` + +## Embedded Swift is a subset of Swift + +Embedded Swift is a subset of the Swift language, and some features are not available in Embedded Swift, however features are available, including: Generics, protocols, enums with associated values, tuples, optionals, classes (instances are allocated on the heap and refcounted just like in regular Swift), inheritance, runtime polymorphism, arrays (heap-allocated copy-on-write just like in regular Swift) and many more. + +Features that are not available: + +- **Not available**: Runtime reflection (`Mirror` APIs). +- **Not available**: Values of protocol types ("existentials"), unless the protocol is restricted to be class-bound (derived from AnyObject). E.g. `let a: Hashable = ...` is not allowed. `Any` is also not allowed. +- **Not available**: Metatypes, e.g. `let t = SomeClass.Type` or `type(of: value)` are not allowed. +- **Not available**: Printing and stringification of arbitrary types (achieved via reflection in desktop Swift). +- **Not available yet (under development)**: Swift Concurrency. + +For a more complete list of supported features in Embedded Swift, see [Embedded Swift -- Status](EmbeddedSwiftStatus.md). + +## Libraries and modules in Embedded Swift + +Traditional library build and use model of Swift is that library code is compiled into a .swiftmodule, containing the interfaces, and a compiled library with binary code, either a .a static library or a .dylib/.so dynamic library. A client's build then uses the .swiftmodule at compile-time, and the static/dynamic library at link-time. + +The library model in Embedded Swift works slightly differently: All Swift source code of a library is promoted into being inlineable and visible to client builds (this is necessary for generic code, and beneficial for optimizations for non-generic code), and ends up serialized into the .swiftmodule, the interface of the library. Therefore, the compiled code of a library is never needed, and doesn't even need to be produced. For example: + +```bash +# Build the library, only as a .swiftmomodule. Notice that we never build the .o or .a for the library. +$ swiftc -target -enable-experimental-feature Embedded -wmo \ + a.swift b.swift -module-name MyLibrary -emit-module -emit-module-path ./MyLibrary.swiftmodule + +# Build the client, "-I ." add the current directory to the module search path list +$ swiftc -target -enable-experimental-feature Embedded -wmo \ + client.swift -I . -c -o client.o +``` + +The Embedded Swift standard library is distributed in the toolchain the same way: It's strictly a .swiftmodule without any compiled code present anywhere. All the compiling into machine code is performed as part of the client's build. This has the major benefit that the client's build can provide additional ABI and ISA defining flags, such as the above-mentioned `-mfloat-abi`, `-fshort-enums`, `-mcpu`, `-march` flags, and these flags in the client's build will apply to all the library code (including standard library code) as well. + +## Allocating and non-allocating Embedded Swift mode + +Embedded Swift does allow instantiating and using reference types (classes) which are refcounted objects allocated on the heap. A common case of needing those is for dynamic containers like arrays and sets (they use dynamically-sized heap-allocated class instances as their storage). There is only a handful of Swift language features that cause allocations: + +- creating class instances, +- escaping a closure that captures local variables, +- creating an indirect enum case with a payload referencing the enum itself +- explicitly calling allocation APIs (e.g. `UnsafeMutablePointer.allocate()`). + +Outside of those cases, Embedded Swift does not perform allocations or cause heap usage. + +Some embedded platforms don't have and/or don't want *any heap allocations whatsoever* and don't provide a heap at all. The `-no-allocations` compiler flag can be used to match that, which will cause the compiler to produce an error at compile time when creating class instances or calling allocation APIs. + +```bash +$ cat test.swift +let p = UnsafeMutablePointer.allocate(capacity: 10) +$ swiftc test.swift -enable-experimental-feature Embedded -wmo -no-allocations +test.swift:1:37: error: cannot use allocating operation in -no-allocations mode +``` + +## External dependencies + +Embedded Swift minimizes external dependencies (i.e. functions that need to be available at link-time), but they still exist. There are generally two categories of dependencies: (1) functions that the Swift standard library or Embedded Swift runtime need to call, and (2) functions/symbols that are implicitly added by LLVM and the compiler pipeline. + +For (1), external dependencies are only used based on actual usage of the program under compilation: + +- instantiating a class, or using UnsafeMutablePointer.allocate() + - dependency: `int posix_memalign(void **, size_t, size_t);` + - dependency: `void free(void *);` +- using print() + - dependency: `int putchar(int);` +- using Hashable, Set, Dictionary, or random-number generating APIs + - dependency: `void arc4random_buf(void *, size_t);` + +For (2), external dependencies are also triggered by specific code needing them, but they are somewhat lower-level patterns where it might not be obvious that such patterns should cause external dependencies: + +- **basic memory copying and zeroing functions** + - usage added for a variety of reasons (e.g. using structs on the stack) + - dependency: `void *memset(void *, int, size_t);` + - dependency: `void *memcpy(void *, const void *, size_t);` +- **stack protectors** (aka stack cookies or stack canaries) + - dependency: `void *__stack_chk_guard;` + - dependency: `void __stack_chk_fail(void);` + - stack protectors can be disabled with `-disable-stack-protector` swiftc flag +- **atomics intrinsics** + - on CPU architectures that don't have direct load-acquire/store-release support in the ISA, LLVM calls helper functions for atomic operations + - needed by refcounting in the Embedded Swift runtime (so any class usage will trigger this dependency) + - also needed when using atomics from the Synchronization module +- **multiplication/division/modulo intrinsics** + - on CPU architectures that don't have direct support for the math operations in the ISA + - dependency (on Mach-O): `__divti3` + - dependency (on Mach-O): `__modti3` + - dependency (with EABI): `__aeabi_ldivmod` + +The user and/or the platform (via basic libraries like libc or compiler builtins) is expected to provide these well-known APIs. diff --git a/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/ABI.md b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/ABI.md new file mode 100644 index 0000000..7d5bea9 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/ABI.md @@ -0,0 +1,58 @@ +# ABI + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +## ABI stability + +The ABI of code generated by Embedded Swift is not currently stable. For a concrete compiler version, it will be consistent, but do not mix code built with different compiler versions. + +Similarly, do not mix Embedded Swift code with full Swift code, as the ABIs are different. Details are described in the following sections. + +## Symbol mangling under Embedded Swift + +Since Swift 5.0, the stable ABI mangling schema uses the `$s` prefix on all Swift symbols. Because Embedded Swift's ABI differs from the stable ABI, and furthermore because it's not expected to be stable, Embedded Swift uses a `$e` mangling prefix. The logic and structure of the mangling stays the same, the only difference is the prefix. + +## Calling convention of Embedded Swift + +As of today, Embedded Swift has identical calling convention to full Swift. However, this does not need to continue in the future, and there should not be expectations that the ABI of Embedded Swift is compatible with full Swift. + +The compiler respects the ABIs and calling conventions of C and C++ when interoperating with code in those languages. Calling C/C++ functions from Embedded Swift code is supported, and similarly exporting Swift code via `@_extern`, `@_cdecl` or `@_expose` will match the right calling conventions that C/C++ expects. + +## Metadata ABI of Embedded Swift + +Embedded Swift eliminates almost all metadata compared to full Swift. However, class and existential metadata are still used, because those serve as vtables and witness tables for dynamic dispatch of methods to implement runtime polymorphism with classes and existentials. + +### Class Metadata ABI + +The layout of Embedded Swift's class metadata is *different* from full Swift: + +- The **super pointer** pointing to the class metadata record for the superclass is stored at **offset 0**. If the class is a root class, it is null. +- The **destructor pointer** is stored at **offset 1**. This function is invoked by Swift's deallocator when the class instance is destroyed. +- The **ivar destroyer** is stored at **offset 2**. This function is invoked to destroy instance members when creation of the object is cancelled (e.g. in a failable initializer). +- Lastly, the **vtable** is stored at **offset 3**: For each Swift class in the class's inheritance hierarchy, in order starting + from the root class and working down to the most derived class, the function pointers to the implementation of every method of the class in declaration order in stored. + +### Witness Tables ABI + +The layout of Embedded Swift's witness tables is *different* from full Swift: + +- The first word is always a null pointer (TODO: it can be eliminated) +- The following words are witness table entries which can be one of the following: + - A method witness: a pointer to the witness function. + - An associated conformance witness: a pointer to the witness table of the associated conformance + +Note that witness tables in Embedded Swift do not contain associated type entries. + +Witness functions are always specialized for concrete types. This also means that parameters and return values are passed directly (if possible). + +## Heap object layout in Embedded Swift + +Heap objects have the following layout in Embedded Swift: + +- The **isa pointer** (pointer to the class metadata) is stored at **offset 0**. +- The **refcount** is stored inline at **offset 1**. +- Normal stored properties follow. diff --git a/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/Existentials.md b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/Existentials.md new file mode 100644 index 0000000..d445d75 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/Existentials.md @@ -0,0 +1,108 @@ +# Existentials + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +## Background + +Existentials (also known as "any" types) in Swift are a way to express a type-erased value, where the actual type is not known statically, and at runtime it can be any type that conforms to the specified protocol. Because the possible types can vary in size, the representation of such a value is an "existential container" and the actual represented value is stored either inline (when it fits) or indirectly as a pointer to a heap allocation. There are also multiple concrete representations of the existential container that are optimized for different constraints (e.g. for class-bound existentials, the value does not make sense to ever store inline, so the size of the container is matched to hold exactly one pointer). + +Existentials are restricted in Embedded Swift in multiple ways, for multiple reasons: + +- Value existentials are not allowed. This prevents the optimization barriers and heap allocation indirections that come with those existentials in regular Swift. +- Class-bound protocols can be used as an existential. This still circumvents the often undesired behavior of existentials where they allocate (and deallocate) storage on the heap for the inner value if it cannot fit in the inline buffer, because class references are always refcounted and references are shared. +- Unbounded generic methods cannot be called through an existential. + +## Class-bound existentials + +Embedded Swift allows and supports class-bound existentials: + +```swift +procotol ClassBoundProtocol: AnyObject { // ✅, this means any type that wants to conform to ClassBoundProtocol must be a class type + func foo() +} + +class Base: ClassBoundProtocol { ... } +class Derived: Base { ... } // also conforms to ClassBoundProtocol +class Other: ClassBoundProtocol { ... } + +let existential: any ClassBoundProtocol = ... // ✅ +existential.foo() // ✅ +``` + +Note that protocols that are not class-bound cannot form existentials (in Embedded Swift): + +```swift +let existential: any Equatable = ... // ❌ + +class MyClass: Equatable { ... } +let existential: any Equatable = MyClass // ❌, not enough that the actual type is a class, the protocol itself must be class-bound +``` + +Class-bound existentials in Embedded Swift allow the "is" and "as!" / "as?" operators: + +```swift +let existential: any ClassBoundProtocol = ... +if existential is Base { ... } // ✅ +guard let concrete = existential as? Derived else { ... } // ✅ +let concrete = existential as! Derived // ✅, and will trap at runtime if a different type is inside the existential +``` + +## Restrictions on class-bound existentials + +Class-bound existentials in Embedded Swift do come with some restrictions compared to class-bound existentials in regular Swift: + +- You cannot use an existential to call a unbounded generic method from the protocol. This is described in depth in [Embedded Swift -- Non-final generic methods](NonFinalGenericMethods.md). For example: +```swift +protocol ClassBoundProtocol: AnyObject { + func foo(t: T) +} + +let ex: any ClassBoundProtocol = ... // ✅ +ex.foo(t: 42) // ❌ +``` + +- You cannot use an existential composition of a class-bound protocol with a non-class-bound protocol. For example: +```swift +let ex: any ClassBoundProtocol & OtherClassBound = ... // ✅ +let ex: any ClassBoundProtocol & Equatable = ... // ❌ +``` + +## Alternatives to existentials + +When existentials are not possible (e.g. because you need struct types in an existential), or not desirable (e.g. because the indirection on a class-bound existential causes an observation performance degradation), consider one of the following alternatives (which all have different tradeoffs and code structure implications): + +**(1) Avoid using an existential, use generics instead** + +```swift +protocol MyProtocol { + func write(t: T) +} + +func usingProtocolAsGeneric(p: some MyProtocol) { + p.write(t: 42) // ✅ +} +``` + +**(2) If you only need a different type based on compile-time configuration (e.g. mocking for unit testing), use #if and typealiases:** +```swift +#if UNIT_TESTING +typealias HWAccess = MMIOBasedHWAccess +#else +typealias HWAccess = MockHWAccess +#endif + +let v = HWAccess() +``` + +**(3) If you only have a handful of tightly-coupled types that need to participate in an existential, use an enum instead:** +```swift +enum E { + case type1(Type1) + case type2(Type2) + case type3(Type3) +} +``` diff --git a/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/NonFinalGenericMethods.md b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/NonFinalGenericMethods.md new file mode 100644 index 0000000..d689c16 --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/LanguageDetails/NonFinalGenericMethods.md @@ -0,0 +1,130 @@ +# Non-final generic methods + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +## Background + +Embedded Swift relies on monomorphization to achieve its properties like not requiring type metadata. Monomorphization is mandatory specialization of all compiled code -- all function bodies get their concrete types substituted and all generics are "compiled out". This is based on passing type information top-down, i.e. from callers to callees, and specializing callees based on the concrete type provided by the caller. + +This type information passing from the caller is crucial. If it cannot happen for some reason, then monomorphization cannot happen. This is why Embedded Swift imposes restrictions on non-final generic methods on classes. + +## Non-final generic methods on classes (for subclassing-based dispatch) + +A non-final generic method on a class where the generic type does not come from the class context itself, is disallowed in Embedded Swift. This is because conservatively, the compiler must assume there could be subclasses with the method overridden. Monomorphization of a function call then cannot know the concrete target type. For example: + +```swift +class MyClass { + func write(t: T) { /* implementation */ } +} + +let instance: MyClass = ... // could be MyClass, or a subclass +instance.write(t: 42) // ❌ +``` + +Alternatives (which all have different tradeoffs and code structure implications): + +**(1) Make the class final (disallow subclassing):** + +```swift +final class MyClass { + func write(t: T) { /* implementation */ } +} + +let instance: MyClass = ... // can only be MyClass +instance.write(t: 42) // ✅ +``` + +**(2) Make the individual method final (disallow overriding in subclasses):** + +```swift +class MyClass { + final func write(t: T) { /* implementation */ } +} + +let instance: MyClass = ... // could be MyClass, or a subclass +instance.write(t: 42) // ✅ +``` + +**(3) Make the class generic instead of the method:** + +```swift +class MyClass { + func write(t: T) { /* implementation */ } +} + +let instance: MyClass = ... // can only be MyClass +instance.write(t: 42) // ✅ +``` + +**(4) Use overloading to support a set of concrete types:** + +```swift +class MyClass { + func write(t: Int) { /* implementation */ } + func write(t: Double) { /* implementation */ } +} + +let instance: MyClass = ... // could be MyClass, or a subclass +instance.write(t: 42) // ✅ +``` + +## Non-final generic methods on classes (for existential-based dispatch) + +A similar restriction applies to using class-bound existentials for dispatch method calls. Because at compile-time the target type is not statically known, monomorphization is not possible. For example: + +```swift +protocol MyProtocol: AnyObject { + func write(t: T) +} + +// existential ("any") is a runtime type-erasing box, we cannot specialize the target +// function for T == Int.self because we don't know the concrete type of "p" +func usingProtocolAsExistential(p: any MyProtocol) { + p.write(t: 42) // ❌ +} +``` + +Alternatives: + +**(1) Avoid using an existential, use generics instead** + +```swift +protocol MyProtocol: AnyObject { + func write(t: T) +} + +func usingProtocolAsGeneric(p: some MyProtocol) { + p.write(t: 42) // ✅ +} +``` + +**(2) Use a primary associated type** + +```swift +protocol MyProtocol: AnyObject { + associatedtype T + func write(t: T) +} + +func usingProtocolAsExistential(p: any MyProtocol) { + p.write(t: 42) // ✅ +} +``` + +**(3) Use overloading to support a set of concrete types:** + +```swift +protocol MyProtocol: AnyObject { + func write(t: Int) + func write(t: Double) +} + +func usingProtocolAsExistential(p: any MyProtocol) { + p.write(t: 42) // ✅ +} +``` + diff --git a/Sources/EmbeddedSwift/Documentation.docc/SDKSupport/IntegrateWithPico.md b/Sources/EmbeddedSwift/Documentation.docc/SDKSupport/IntegrateWithPico.md new file mode 100644 index 0000000..780433a --- /dev/null +++ b/Sources/EmbeddedSwift/Documentation.docc/SDKSupport/IntegrateWithPico.md @@ -0,0 +1,112 @@ +# Integrating with Raspberry Pi Pico + +**⚠️ Embedded Swift is experimental. This document might be out of date with latest development.** + +**‼️ Use the latest downloadable 'Trunk Development' snapshot from swift.org to use Embedded Swift. Public releases of Swift do not yet support Embedded Swift.** + +For an introduction and motivation into Embedded Swift, please see "[A Vision for Embedded Swift](https://github.com/swiftlang/swift-evolution/blob/main/visions/embedded-swift.md)", a Swift Evolution document highlighting the main goals and approaches. + +The following document sketches how to integrate Swift code into some popular embedded platforms' SDKs and build systems. + +## Integrating with Raspberry Pi Pico (W) build system: + +Development for [Raspberry Pi Pico and Pico W](https://www.raspberrypi.com/products/raspberry-pi-pico/) normally uses the [Pico SDK](https://github.com/raspberrypi/pico-sdk) and the vendor provides several [sample projects in the pico-examples repository](https://github.com/raspberrypi/pico-examples). The SDK and sample project setup is described in: + +- https://www.raspberrypi.com/documentation/microcontrollers/c_sdk.html#sdk-setup +- https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf + +Before trying to use Swift with the Pico SDK, make sure your environment works and can build the provided C/C++ sample projects. + +### CMake setup with a bridging header + +The Pico SDK is using CMake as its build system, and so the simplest way to integrate with it is to also use CMake to build a Swift firmware application on top of the SDK and the libraries from it. The following describes an example set up of that on a "blinky" example (code that just blinks the built-in LED). + +Let's create a directory with a Swift source file, a bridging header, and a CMake definition file: + +``` +./SwiftPicoBlinky/Main.swift +./SwiftPicoBlinky/BridgingHeader.h +./SwiftPicoBlinky/CMakeLists.txt +``` + +In `Main.swift`, let's add basic logic to initialize the GPIO port for the Pico's built-in LED, and then turn it on and off in a loop: + +```swift +@main +struct Main { + static func main() { + let led = UInt32(PICO_DEFAULT_LED_PIN) + gpio_init(led) + gpio_set_dir(led, /*out*/true) + while true { + gpio_put(led, true) + sleep_ms(250) + gpio_put(led, false) + sleep_ms(250) + } + } +} +``` + +Notice that we're using functions and variables defined in C in the Pico SDK. For that to be possible, the Swift compiler needs to have access to the C header files that define these functions and variables. The cleanest option would be to define a modulemap, but for simplicity let's just use a bridging header to make declarations visible in Swift without a module. `BridgingHeader.h` should contain: + +```c +#pragma once + +#include "pico/stdlib.h" +``` + +Finally, we need to define the application's build rules in CMake that will be using CMake logic from the Pico SDK. The following content of `CMakeLists.txt` shows how to *manually call swiftc, the Swift compiler* instead of using the recently added CMake native support for Swift, so that we can see the full Swift compilation command. + +```cmake +cmake_minimum_required(VERSION 3.13) +include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) + +project(swift-blinky) +pico_sdk_init() +execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE) + +add_executable(swift-blinky) +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o + COMMAND + ${SWIFTC} + -target armv6m-none-none-eabi -Xcc -mfloat-abi=soft -Xcc -fshort-enums + -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library + $$\( echo '$' | tr '\;' '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \) + $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}' | tr ' ' '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \) + -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h + ${CMAKE_CURRENT_LIST_DIR}/Main.swift + -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o + DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h + ${CMAKE_CURRENT_LIST_DIR}/Main.swift +) +add_custom_target(swift-blinky-swiftcode DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o) + +target_link_libraries(swift-blinky + pico_stdlib hardware_uart hardware_gpio + ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o +) +add_dependencies(swift-blinky swift-blinky-swiftcode) +pico_add_extra_outputs(swift-blinky) +``` + +With these three files, we can now configure and build a Swift firmware for the Pico: + +```bash +$ export TOOLCHAINS=org.swift.59202401301a +$ export PICO_BOARD=pico +$ export PICO_SDK_PATH= +$ export PICO_TOOLCHAIN_PATH= +$ ls -al +-rw-r--r-- 1 kuba staff 39B Feb 2 22:08 BridgingHeader.h +-rw-r--r-- 1 kuba staff 1.3K Feb 2 22:08 CMakeLists.txt +-rw-r--r-- 1 kuba staff 262B Feb 2 22:08 Main.swift +$ mkdir build +$ cd build +$ cmake -S ../ -B . -G Ninja +$ ninja -v +``` + +This should produce several build artifacts in the `build/` subdirectory, include `swift-blinky.uf2`, which can be directly uploaded to the Pico by copying it into the fake Mass Storage Volume that the device presents when plugged over USB in BOOTSEL mode. diff --git a/Sources/EmbeddedSwift/Empty.swift b/Sources/EmbeddedSwift/Empty.swift new file mode 100644 index 0000000..24ae0ca --- /dev/null +++ b/Sources/EmbeddedSwift/Empty.swift @@ -0,0 +1,12 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors. +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// +//===----------------------------------------------------------------------===// + +// This file is included so SwiftPM considers the target to be a Swift target. From 85b39dc407ee096dc781452f65de72b96432342b Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Fri, 2 May 2025 15:48:43 -0700 Subject: [PATCH 2/3] make ci happy --- Package.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 4f2e8c0..7fc1679 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.1 +// swift-tools-version: 6.0 import PackageDescription @@ -8,7 +8,8 @@ let package = Package( .library(name: "EmbeddedSwift", targets: ["EmbeddedSwift"]) ], dependencies: [ - .package(url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + .package( + url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), ], targets: [ .target(name: "EmbeddedSwift") From 76992e86b888f02b2ef97198b255d921830e01c2 Mon Sep 17 00:00:00 2001 From: Rauhul Varma Date: Fri, 2 May 2025 16:17:29 -0700 Subject: [PATCH 3/3] make ci happy --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 7fc1679..ab3b65a 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,7 @@ let package = Package( ], dependencies: [ .package( - url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0"), + url: "https://github.com/swiftlang/swift-docc-plugin", from: "1.1.0") ], targets: [ .target(name: "EmbeddedSwift")