Add article on porting Embedded Swift to a new platform - #231
Conversation
Documents the Embedded Swift Platform Abstraction Layer (EmbeddedPlatform.h) and the entry points a platform must provide, with pointers to the ready-made shims in swiftlang/swift's stdlib/public/EmbeddedPlatform.
The five "Provide ..." sections were nested as H3 under "Provide memory allocation" (H2), making them look like its subsections instead of sibling entry points. Also fixes "Proivde" -> "Provide".
heckj
left a comment
There was a problem hiding this comment.
Looks great to me overall - Only real feedback was that it wasn't clear to me what a user needs to do with the implementations and how to wrap that into something broader, but it's also not something I've tried to do before - so maybe it's more obvious to folks who are wrangling Embedded and board bring ups.
|
|
||
| ## Overview | ||
|
|
||
| <doc:IntegratingWithPlatforms> and <doc:Baremetal> describe how to connect Swift code to an *existing* SDK, or how to write the startup code and register access for a *specific* board. This article is about the layer underneath both of those: the small number of low-level functions that the Embedded Swift compiler and standard library call into whenever a program uses a feature that needs help from the outside world, such as allocating memory, calling `print()`, or creating a `Mutex`. |
There was a problem hiding this comment.
When I read through this, I wasn't sure if this meant the article was about doing things that get you to an SDK (prerequisites) and there's more to building an SDK than is detailed here, or if this porting guide is effectively "how to create a beta SDK for your personal use" kind of thing.
Some of this is my own lack of understanding of what you need to use an arbitrary board - do you need an SDK, or is that more of a "hardened convenience" around these bare "gotta have" technical capabilities?
There was a problem hiding this comment.
It's meant for people bringing Embedded Swift to a new platform (e.g. microprocessor or operating system environment). Granted, it something that probably doesn't happen often, but having an article like this demonstrates that we want people to consider expanding the Embedded Swift ecosystem.
|
|
||
| ## Provide mutexes | ||
|
|
||
| `_swift_mutex_init`, `_swift_mutex_destroy`, `_swift_mutex_lock`, `_swift_mutex_unlock`, and `_swift_mutex_tryLock` are only needed if you use `Synchronization.Mutex`. The caller allocates the storage for you — at least `EMBEDDED_SWIFT_MUTEX_NUM_WORDS` pointer-sized words (8 by default; override with `-Xcc -DEMBEDDED_SWIFT_MUTEX_NUM_WORDS=<n>` if your mutex representation needs more) — and hands it to `_swift_mutex_init` along with `.checked` and/or `.recursive` flags to opt into misuse diagnostics and reentrant locking. |
There was a problem hiding this comment.
A small update, this PR moved back to having separate function definitions for recursive threads.
void _swift_mutexRecursive_init(void * EMBEDDED_SWIFT_NONNULL mutex,
swift_mutex_flags_t flags);
void _swift_mutexRecursive_destroy(void * EMBEDDED_SWIFT_NONNULL mutex);
void _swift_mutexRecursive_lock(void * EMBEDDED_SWIFT_NONNULL mutex);
void _swift_mutexRecursive_unlock(void * EMBEDDED_SWIFT_NONNULL mutex);
FWIW, Synchronization.Mutex doesn’t expose recursive locking. The recursive mutex functions are currently used internally by Concurrency, so perhaps they can be left out of this section and introduced later when discussing Concurrency support.
There was a problem hiding this comment.
Personally, I like documenting both recursive and non-recursive in the same place still, with a note that it's the non-recursive one needed for Synchronization.
There was a problem hiding this comment.
I verified against the current EmbeddedPlatform.h and confirmed _swift_mutexRecursive_init/destroy/lock/unlock are back as a separate function family with their own EMBEDDED_SWIFT_MUTEX_RECURSIVE_NUM_WORDS sizing. So I removed the incorrect .recursive flag and documented both families.
DougGregor
left a comment
There was a problem hiding this comment.
Thank you for writing this! I have a couple of "flow" comments, and we've tuned a few things in the platform abstraction layer since you started.
|
|
||
| ## Provide randomness | ||
|
|
||
| `_swift_generateRandom` feeds `SystemRandomNumberGenerator`, the default source used by `shuffle()` and friends. `_swift_generateRandomHashSeed` seeds the hashing used by `Set` and `Dictionary`; it doesn't need to be cryptographically secure, and can even return a fixed value if you want deterministic hashing. Both can typically forward to a hardware RNG or `arc4random_buf` where available. |
There was a problem hiding this comment.
_swift_generateRandom is only needed if you use SystemRandomNumberGenerator. It might be worth calling that out explicitly. I also recommend splitting this into two paragraphs: one for this entrypoint, and the other for hashing, because they're different uses.
There was a problem hiding this comment.
I split into two paragraphs: one for _swift_generateRandom with an explicit call-out that it's only needed for SystemRandomNumberGenerator, and a separate one for _swift_generateRandomHashSeed covering Set/Dictionary hashing.
|
|
||
| ## Provide mutexes | ||
|
|
||
| `_swift_mutex_init`, `_swift_mutex_destroy`, `_swift_mutex_lock`, `_swift_mutex_unlock`, and `_swift_mutex_tryLock` are only needed if you use `Synchronization.Mutex`. The caller allocates the storage for you — at least `EMBEDDED_SWIFT_MUTEX_NUM_WORDS` pointer-sized words (8 by default; override with `-Xcc -DEMBEDDED_SWIFT_MUTEX_NUM_WORDS=<n>` if your mutex representation needs more) — and hands it to `_swift_mutex_init` along with `.checked` and/or `.recursive` flags to opt into misuse diagnostics and reentrant locking. |
There was a problem hiding this comment.
Personally, I like documenting both recursive and non-recursive in the same place still, with a note that it's the non-recursive one needed for Synchronization.
|
|
||
| ## Provide exclusivity checking | ||
|
|
||
| `_swift_getExclusivityTLS` and `_swift_setExclusivityTLS` are only needed when the compiler is built with `-enforce-exclusivity=checked`. They store and retrieve a single pointer per thread of execution. On a single-threaded platform this is just a global variable; on a multi-threaded platform it needs real thread-local storage. |
There was a problem hiding this comment.
These entrypoints have been subsumed by _swift_tls_* entrypoints.
There was a problem hiding this comment.
I went with documenting both families in the same "Provide mutexes" section, as you suggested, with non-recursive first with a note that Synchronization.Mutex only needs that family, then the recursive family with a note that it's currently only used internally by Concurrency.
I replaced the exclusivity section with "Provide thread-local storage," covering the current _swift_tls_init/_swift_tls_get/_swift_tls_set entry points, SWIFT_TLS_KEY_COUNT, and _swift_thread_isMain
| ## Implement entry points in C or in Swift | ||
|
|
||
| You can implement any of these functions in C. Define a function matching the declaration in `EmbeddedPlatform.h` and link it into your firmware, the same way you would provide any other C symbol the linker asks for. |
There was a problem hiding this comment.
I feel like this section belongs toward the beginning, because it lays out how you implement the functions that are described by most of the document.
There was a problem hiding this comment.
I moved this section up, right after "Reuse the built-in shims" and before the per-feature breakdown.
| ## Reuse the built-in shims | ||
|
|
||
| Because every entry point is just a function with a specified signature, you rarely need to write all of them from scratch. The Swift repository ships several ready-made shims under [`stdlib/public/EmbeddedPlatform`](https://github.com/swiftlang/swift/tree/main/stdlib/public/EmbeddedPlatform) that you can use directly (where your toolchain provides prebuilt libraries for your target) or copy as a starting point: | ||
|
|
There was a problem hiding this comment.
I feel like this part about the built-in shims belongs way at the beginning, before we discuss how to implement your own. If you're doing Embedded Swift on a POSIX-y platform. If you're single-threaded, against the provided swiftEmbeddedPlatformPOSIX and then choose between the single- and multi-threaded versions. We can show some link lines and paths to where to find them in the toolchain, because for some folks, this is all you need.
It's only if you don't have a POSIX-y platform that you need the rest of this document.
There was a problem hiding this comment.
Ok. I moved this section to the front, right after the Overview, and reworded it to lead with the POSIX case: link swiftEmbeddedPlatformPOSIX plus a threading shim (single- or multi-threaded) and you're generally done. I also added the actual library product names (swiftEmbeddedPlatformPOSIX, etc.) and an example link-line so it's actionable without reading the rest of the article. The per-feature sections that follow are now clearly for platforms that don't fit one of these shims.
- Move the shim/implementation-mechanics sections before the per-feature deep dive, since most POSIX-y platforms only need the former. - Clarify in the overview that this article is for platforms lacking these entry points entirely, not for consuming an existing SDK. - Split the randomness section into RNG vs. hash-seed paragraphs and call out the SystemRandomNumberGenerator dependency explicitly. - Document the non-recursive and recursive mutex function families together, dropping the removed .recursive flag, and note that Synchronization.Mutex only needs the non-recursive family. - Replace the removed _swift_getExclusivityTLS/_swift_setExclusivityTLS section with a "Provide thread-local storage" section describing the current _swift_tls_* entry points and _swift_thread_isMain.
DougGregor
left a comment
There was a problem hiding this comment.
Thank you for the revisions, this looks good to me!
Summary
PortingToNewPlatforms.md, documenting the Embedded Swift Platform Abstraction Layer (EmbeddedPlatform.h) and the runtime entry points a platform must provide (allocation, console output, randomness, mutexes, exclusivity checking, exit).stdlib/public/EmbeddedPlatform(POSIX, single-threaded, multi-threaded POSIX/Darwin) that they can reuse or copy as a starting point.IntegratingWithPlatformsandBaremetal.Test plan
_swift_allocate,_swift_mutex_init,EMBEDDED_SWIFT_MUTEX_NUM_WORDS,swift_getPlatformLayerVersion, etc.) against the current upstreamswiftlang/swiftsource.<doc:STM32BaremetalGuide>cross-reference resolves under main'sExamples/layout.