Embedded Swift

The state machine would be very interesting. I have yet to find one that is satisfying.

A bit off-topic, but has anyone thought about porting Swift to XMOS? I'm using their microcontroller (well, somewhere between a MCU and a FPGA) for an embedded project where the control logic, running on a RPi, is written in Swift. It'd be nice to have the complete stack in Swift!

I mention it as their variant of C, XC (which I think they are sadly deprecating in favour of C) has a concurrency model that has quite a number of similarities with structured concurrency in Swift. The next generation of XMOS MCUs will support RISC-V, too.

I think it would be appropriate at this point to introduce a new category here on the forum just for Swift on embedded devices, just like there is already for Swift on servers. :slight_smile:

21 Likes

Hi! Congrats on the Embedded Swift development! I got to play a bit with it and it works great. However, I discovered that strings are unavailable in embedded Swift. Why is that? I understand why print is unavailable (since it is target-dependent), but why is this also the case for strings, and is there a viable alternative? I guess I should probably use UnsafePointer<CChar>? with the microcontroller SDK's provided printf.

1 Like

You can use StaticString.

4 Likes

Oh, makes sense now! Thanks :smile:

Swift strings are Unicode-correct, and you can't achieve that without embedding Unicode tables in your final binary. That would inflate it by at least hundreds of kilobytes, potentially megabytes if you rely on Foundation, since the latter ships its own build of ICU on top of stdlib Unicode support.

3 Likes

Makes sense, thanks!
It's great that StaticStrings exists :slight_smile:

1 Like

Is there any way to "replace" the String type with a user-defined (and likely less powerful type) than the standard library's String type?

Currently, uses of String result in a compilation error, because there's a stub definition for String that is marked as unavailable in Embedded, and automatically included when building with Embedded enabled: swift/stdlib/public/core/EmbeddedStubs.swift at a63078f279e76a64ae92f338c1875c3d372c540b · apple/swift · GitHub

1 Like

You can define your own type (that's not called String) and make it ExpressibleByStringLiteral. That's how StaticString is implemented: swift/stdlib/public/core/StaticString.swift at a63078f279e76a64ae92f338c1875c3d372c540b · apple/swift · GitHub

1 Like

I wonder why you said "(that's not called String)", what bad would happen if I define my own type called String for the platform where String is not available?

Nothing bad, it would just be confusing. "String" is really Swift.String. Defining your own type named "String" is really creating a new type MyModule.String.

1 Like

Obviously you should name it Strï̇ng, though.

10 Likes

Wasn't sure if I should make a new topic or not, but wanted to ask about the possibility of weak references with embedded Swift.

I have the following imported functions from a C module:

func allocThing() -> OpaquePointer
func deallocThing(thing: OpaquePointer)

func setThingCallback(thing: OpaquePointer, callback: @convention(c) (_ thing: OpaquePointer) -> ())

and would like to create a Swift wrapper class like so:

open class Thing {
    private let pointer: OpaquePointer
    
    init() {
        pointer = allocThing()
        setThingCallback(thing: pointer) { thing in
            // A C function pointer cannot be formed from a closure that captures context
            self.callback()
        }
    }
    
    deinit {
        deallocThing(thing: pointer)
    }
    
    func callback() {
        // Implemented by subclass
    }
}

where subclasses can override callback. This currently doesn't seem possible, since a C closure can't capture context. The solution I came up with was to store all Things in a dictionary of [OpaquePointer: weak Thing] or similar as a global somewhere, and find the matching thing in the C closure to call the swift callback(). Then I ran into "Attribute 'weak' cannot be used in embedded Swift" and couldn't think of a solution that didn't result in a retain cycle/manually releasing the class.

I see in the runtime a comment about "We need to think about supporting (or banning) weak and/or unowned references." and was wondering if there were any more thoughts on this, whether it is something that could be supported eventually or if I should look for other solutions.

1 Like

Typically that's done by having some extra context parameter that is typecasted to appropriate type. Typing in here so there could be typos:

/*
void* allocThing(void);
void deallocThing(void*);
typedef void (*Callback)(void* thing, void* context);
void setThingCallback(void* thing, void* context, Callback);
*/

setThingCallback(pointer, unsafeBitCast(self, UnsafeRawPointer.self) {
    thing, context in
    let this = unsafeBitCast(context, Thing.self)
    this.callback(thing)
}

Another approach would be to use a block in C API:

typedef void (^Block)(void* pointer);
void setThingCallback(void* thing, Block);

Blocks were backported from Obj-C to C, and are similar to Swift closures (they can capture context).

1 Like

I thought about that and dismissed it as also creating a retain cycle, but looking again I think that might work for me, thanks! Still think that weak references would be nice to have though.

1 Like

Sure. Just naming my string type "String" (along with proving the similar to Swift.String API) would allow sources that use String-† to compile on those platforms without modifications.

† - assuming they don't spell it as "Swift.String".

I don't think that's necessarily a good goal. Having a type called String with different semantics would lead to subtle bugs when you try to share code across the implementations. My hope is that we can provide the subset of String that doesn't depend on Unicode tables portably across all platforms. The main difference would then be that you need to use the .utf8 or .utf16 view to explicitly do code-unit-level Equatable and/or Hashable operations; that would be valuable to have even on desktop Swift since a lot of strings are reall just byte strings from wire protocols, and grapheme cluster awareness is unnecessary overhead. AIUI the fundamental overhead of the tables necessary for grapheme splitting aren't actually that big—more on the order of tens of kilobytes than the hundreds mentioned above—so while there will definitely be very resource-limited platforms that can't afford even that much overhead, having a full featured String that's fully compatible with desktop Swift might scale further down than we originally thought.

14 Likes

There's definitely a domain of devices where the 10-40kb of unicode tables are entirely unacceptable, but I would (lightly) assert that for most hobbyist use cases this doesn't matter.

I personally expect that the folks who want non-allocating Embedded Swift probably heavily overlap with the folks that want non-unicode strings, which is primarily going to be professional uses. (All personal speculation ofc)

I 100% agree that diverging the API/behavior with the same type would be a code portability and understandable nightmare and that String should always be String.

7 Likes

Would that be better than just using [UInt8] (or similar), for embedded systems? Most of the API is already the same, since it's inherited by their common protocols (Sequence and Collection most notably). And [UInt8] or variations thereon are already what you're using if you're interacting with C APIs.

Conversely, I'd be a bit alarmed if String got neutered or its APIs made more awkward in any way regarding Unicode compliance and safety for the sake of a small subset of users.