Embedded Swift

Hey Swift Community!

Developing software for embedded devices is steadily gaining in popularity, with hobbyists and professionals alike writing custom logic for microcontroller boards such as Arduino. Much of this programming is done in C or C-like languages, which lacks both the ergonomics that make modern programming more pleasant and the language safety features that make programs more correct.

Swift represents the state of the art in both these areas, without asking developers to choose between the two. This represents an exciting opportunity to expand Swift's use cases to a whole new domain: embedded programming.

There is existing work in this area, and great past discussions, both here on the forums (link) and in last year’s video call (link), which shows there is a lot of interest and potential for Swift in this space. There is also ongoing work to make Swift more applicable to system libraries and kernel development, which has some of the same challenges and requirements.

The current approaches face challenges (e.g. codesize and memory usage) and could benefit from a vision document to capture the goals and ideas necessary for bringing Swift into these environments. Here’s a prospective vision for “Embedded Swift”, a new compilation mode with first class support for embedded/low-level environments:

Embedded Swift Vision Document:

GitHub Pull Request:

Proof of Concept

We have built a custom toolchain that implements some of the ideas from the vision document, and we have successfully built sample firmwares that demonstrate that the goals of the vision can be achieved:

  • (1) A Swift firmware running on an STM32 MCU board animating colors on an LED strip. This shows that we are able to build a fully standalone firmware binary with Swift with minimal dependencies, and run it in a “bare metal” (no underlying operating system) environment. We have originally built this firmware using the existing LTO-based “hermetic seal” approach, which resulted in final firmware size of ~500 kB, most of which is non-application code (Swift runtime, very trimmed down Swift standard library, libc, libc++, libm). We then built the firmware using Embedded Swift which resulted in firmware size of under 10 kB.
Animating colors on an LED strip. Firmware size ~500 kB with existing “hermetic seal” approach. 10 kB using Embedded Swift.
  • (2) A Swift firmware animating a Swift logo on an LCD screen of a STM32 MCU board. The total size of this firmware is ~15 kB, most of which is spent on the uncompressed RGBA pixels of the logo (50x50 pixels, 4 bytes each = 10 kB).
Animating Swift logo on an LCD screen. Firmware size 15 kB using Embedded Swift (with 10 kB for the graphic asset).

Here is an excerpt of the code shown running in the video. It demonstrates the extent to which Embedded Swift programs are natural and idiomatic Swift, with uses of standard library facilities, generics, string literals, and so on:

// Generics, optionals, stdlib types, custom types, yet the following still compiles
// down to a straightforward sequence of MMIO register accesses without any other
// runtime calls. Results in a few hundred bytes of dependency-free code.
let usart1 = USART(baseAddress: UnsafeMutablePointer<USART_t>(bitPattern: 0x40011000 as UInt)!)
var uart = UART<STM32F746>(uart: usart1)
uart.configure(configuration: .init(enableRead: true, enableWrite: true, baudRate: 19200))
uart.print(message: "Hello UART!")

Next Steps

We are seeking community input on the model for Embedded Swift and its usability, and we welcome any discussion on use cases as we start building support into the Swift project.

Please send any feedback as a reply directly under this posting.

I’m looking forward to working with everyone on bringing Swift to this exciting new frontier!

166 Likes

Would this be Swift-lite or full Swift? Some of Swift's features are pretty heavy for many embedded systems. Many of these systems are plenty fast, but have very little storage and memory so it is important to keep runtime metadata down.

This is primarily why efforts like CircuitPython and DeviceScript are getting popular. Speed isn't the issue, but they can use compact byte codes that don't take up much space compared to normal executable code.

5 Likes

The idea is to expose a subset of the language and standard library that keeps code size small. So, like, there’s no runtime metadata, reflection-based features are disabled, most String manipulation is done through the UTF8View so that the standard library’s Unicode tables can be removed, etc. There are lots of details in the vision document the post links to.

The other part of the idea is that nothing works differently from normal Swift—it’s just that some features are removed or disabled. So Embedded Swift code can be compiled as normal Swift code and will work exactly the same way.

52 Likes

I'd love to see this. It looks good to me on the surface.

It sounds like all of this could be very beneficial for more than IoT and kernel extensions that isn't listed here. Such as WASM in the browser (or as an extension language) and FaaS in the cloud/edge. Right now Swift binaries/WASM are really too big to be great at spinning up on-demand on the edge. Even with memory mapping, these systems really don't like distributing large binaries and it eats away at the very limited memory you have available.

8 Likes

It the runtime really is minimal enough, I could see this going in to Golang territory too where you could have dependency free binaries (nothing more then syscalls)

1 Like

The runtime can really be minimal, and based on needs of the target program: For programs that don't use any heap allocations ("non-allocating Embedded Swift mode" as described in the document) the runtime can be virtually empty. For classes we need refcounting runtime support, but that should still be very minimal.

7 Likes

Interesting :+1:

It is not clear to me, why print needs to be removed. Isn’t it possible to provide an alternative implementation, that would be “source code compatible” but would refrain from using Any (by taking advantage of pacs+macro combo)?

Also I can’t really imagine how would a Swift without Arrays look like. Fixed-sized arrays look like a good fit here, but the only thing I can think of in current Swift is probably “allocating” the space by creating a looooong type (macro?) and then iterating via UnsafeMutableBufferPointer.

1 Like

You might not have a kernel for the syscall. I imagine print should be something that will be easy to add back though.

That is just on the most embedded version of Embedded Swift.

1 Like

Is there a real prototype of this somewhere or are the images just mockups?

I would add that "print" in embedded systems is frequently a sub call of a serial library or similar, so that's where it would be best added back in.

5 Likes

It is not clear to me, why print needs to be removed. Isn’t it possible to provide an alternative implementation, that would be “source code compatible” but would refrain from using Any (by taking advantage of pacs+macro combo)?

This is something I think we should really think about as part of the Embedded Swift movement. IMO Rust does this pretty well, with a distinction between std and core, and allowing you to substitute in pieces of std, allowing you then to access more and more abstractions on top of the pieces of std that you've replaced. Implement an allocator? Nice, you get fmt!().

14 Likes

There is a prototype, the images/videos are real, and we plan to add the functionality of the prototype under experimental flags into the compiler soon, so that it's available for early experimental use.

10 Likes

You can send the output to a particular stream. I use it for stderr. Some embedded platforms I’ve worked with had printf that would do nothing unless you provide your implementation. But the program would compile.

Yes, but fixed size Arrays are essentail for precisely those usecases where malloc is missing imo.

1 Like

It is not clear to me, why print needs to be removed.

There's no intention to remove print(), the vision document just states that we have to provide some alternative implementation (compared to what's in the stdlib today); and your suggestion is one possible option for that, yes.

10 Likes

One interesting intersection is where the artifact bundle definitions of sdks can interplay here. I could imagine that the definition could ferry along information of what type of restrictions are made. E.g. some restricted environments may not have malloc, but others may have full-on concurrency support. Targeting AVR wouldn't be able to have nearly as much available to it as say targeting ESP32-S3/H2.

This may need some alteration linguistically to support additional parameters to #if style guards (or something to that effect). A low level interface to bare metal systems is definitely useful, but it would also be interesting to consider what a more user-welcoming library space would look like too (not just the stdlib).

I see three layers of experiences that could be tackled here: platform engineering (folks bringing in new chips/boards etc), library engineering (folks building out the standard library or other under-pinning libraries for smol swift), and end use engineering (folks building products for that hardware). Each of those audiences have distinct needs, and there likely are tunings that can be focused on each of them.

To be honest - if done well this could be really exciting for many developers - not just large projects but to maker level projects too!

11 Likes
  • 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).

Sounds like this has Typed throws as a prerequisite?

#if os(Darwin), a shorthand for checking for Darwin platforms also seems relevant re. the need in Embedded Swift for # if checks against more complicated environment possibilities.

Not necessarily. There are other restrictions we could possibly place on errors that allow them to be propagated without requiring the full expressivity of general existentials. Typed throws might be a good feature in and of itself, but requiring its use in embedded Swift could be a pretty severe dialectization wedge. Our guidance for long-term stable APIs will likely still be to recommend untyped errors to allow for library evolution, and we'd want libraries that intend to target both desktop and embedded Swift to follow that guidance without having to diverge their APIs.

12 Likes

My 2 cents is that I'm perfectly willing to trade almost any language feature to squeeze onto a smaller chip... something Raspberry Pico / RP2040 size would be wicked. (ARM M0)

5 Likes

RP2040 is relatively powerful - to be honest I don't think many features really would have to be skipped.

7 Likes

That's wonderful but surprising news. Can I hear an ATtiny? (kidding, but honestly can't think of a single language feature I personally wouldn't be willing to give up to make it fit)

2 Likes