Web App with Embedded Swift - POC demo

I saw that PR, yeah. Cool stuff!!

Yeah, I thought the same thing. Using toolsets and similar should get us most of the way there with very little effort. The linking part IMHO is the biggest hurdle.

Out of curiosity, when linking the full wasi-libc instead of only select symbols (like dlalloc + a few utilities): Do we know if wasm-opt be able to strip it down to a similar size, or will there be a price to pay for using libc?

wasm-opt should be able to strip it down, otherwise it can also be built with -ffunction-sections. --gc-sections is the default for --wasm-ld.

After all, wasi-libc static archive (libc.a) is made of a lot of separate object files: 745 according to llvm-ar in swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi.artifactbundle. I'd expect wasm-ld not to include unused object files with no relevant symbols in the final binary at all, even without --ffunction-sections.

1 Like

FWIW JavaScriptKit 0.26.0 includes relevant fixes specifically for Embedded Swift:

  1. JSString: Equatable no longer needs Unicode tables. Now you can back enums with JSString instead of String (enum Foo: JSString) in Embedded Swift, this helps to avoid dependencies on String: Equatable.
  2. JSObject is now ExpressibleByDictionaryLiteral , which means you can write let foo: JSObject = ["bar": "baz"], also removing the dependency on Unicode tables introduced by String: Hashable.
2 Likes

I'm interested in getting a reproducer for this... do you have one? And can you file a bugreport?

So I actually really dislike all of these solution. We need to SwiftPM to not pass these intermediate object files to the link command period.

Emitting dead .o or telling the linker to ignore things is really a kludge. @kubamracek and I are very slowly getting towards a better defined linkage model, but we haven't prioritized this enough, I think.

This is really important because SwiftPM, Xcode, CMake, and Bazel all need to learn the exact same set of skills here to avoid user pain when using different build systems.


FWIW stuff like this would be great to bring up in the embedded community hour. I'd love to work through real world issue!

2 Likes

Agreed. While most of the core people working on SwiftPM are getting the replatforming onto Swift Build done, I am starting to think and chatting with people about how SwiftPM can support embedded better and all the platforms and SDKs I see coming out of that. It's great to keep these conversations going so we can come up with solutions that can stand the test of time.

I managed to package it up somewhat nicely in a simple script. (example here)

The cool thing is, I can now build the same package without any flags or changes as embedded (with wasi libc.a) and "normal" wasm-wasi build (via SDK). And I can finally use versioned dependencies and go on with life ; )

This example builds fine with the latest main-snapshot, but fails a lot when adding -Xfrontend -mergeable-symbols. I can create an issue tomorrow.

I also have a second issue with a build regression for this example (used to build fine, fails with latest main). I will create an issue for this one as well.

Thanks for the info, I would love to see some progress in this area.

I also noticed this here

but I fail to see how this fits in the entire "which object files to give to the linker" story? Is there way already a get @expose symbols from dependencies to the linker?

[48/50] Compiling ElementaryDOM Environment+ReactiveObject.swift
error: compile command failed due to signal 6 (use -v to see invocation)
Begin Error in Function: '$e11EmbeddedApp9GuessViewV10Elementary4HTMLAadEP7_render_4into4withyxn_qd__zAD17_RenderingContextVntAD14_HTMLRenderingRd__lFZTW'
Found an operand with a value that is not compatible with the operand's operand ownership kind map.
Value:   %191 = struct_extract %33 : $_RenderingContext, #_RenderingContext.attributes // user: %193
Value Ownership Kind: guaranteed
Instruction:
     %191 = struct_extract %33 : $_RenderingContext, #_RenderingContext.attributes // user: %193
     %192 = struct_element_addr %189 : $*_RenderingContext, #_RenderingContext.attributes // user: %193
->   store %191 to [init] %192 : $*_AttributeStorage // id: %193
Constraint: <Constraint Kind:owned LifetimeConstraint:LifetimeEnding>
End Error in Function: '$e11EmbeddedApp9GuessViewV10Elementary4HTMLAadEP7_render_4into4withyxn_qd__zAD17_RenderingContextVntAD14_HTMLRenderingRd__lFZTW'
Found ownership error?!

This would be great to get a bugreport and reproducer for, too. Maybe @Erik_Eckstein might be interested in this one?

I think I might have a compiler fix for the -mergeable-symbols crash, [embedded] Fix compilation crash with -num-threads and -mergeable-symbols by kubamracek · Pull Request #80416 · swiftlang/swift · GitHub.

This is largely orthogonal, in embedded we want the leaf most module to produce the final object and deadstrip everything thats not needed in the final image.

In order to do this compiler determines some set of "liveness roots" e.g. an entry point symbol, @_used symbols in its module, etc... Kuba's patch extends this logic to also consider @_used symbols in module dependencies to also be liveness roots, so they make it into the leaf module's object.

Quick update from the trenches:

FYI: I managed to set up an browser app example project that builds cleanly for "full swift" and embedded.

And, the cooler bit, I have a working prototype of ElementaryCSS (a layout/styling API for Elementary) that works for both server-side and client-side rendering.

I was able to port the Swiftle example from tailwind to ElementaryCSS, resulting in code like this:

@View
struct GuessView {
    var guess: Guess

    var content: some View {
        FlexRow(gap: 1) {
            for letter in guess.letters {
                LetterView(guess: letter)
            }
        }
    }
}

@View
struct LetterView {
    var guess: LetterGuess?

    var content: some View {
        Block(.width(10), .height(10), .display(.flex)) {
            Paragraph(.margin(.auto)) {
                guess?.letter.value ?? ""
            }
        }
        .style(
            .color(guess?.status == .unknown ? .gray200 : .white),
            .borderColor(guess == nil ? .gray700 : .gray400),
            .borderWidth(guess == nil || guess?.status == .unknown ? .px(2) : 0),
            .background(guess?.status.backgroundColor ?? .transparent)
        )
    }
}

Especially deciding on the shape of the styling and layout system is tricky for me. Nobody "loves" CSS (well, certainly not me), but there is no way around it and SwiftUIs layout system just does not map well onto it. Trying to implement SwiftUI-APIs (and expecting similar behavior) would be a constant uphill battle in the browser (either by using brittle and complicated CSS-tricks, or calculating layout yourself in Swift code - both not ideal).

So I am trying to thread the needle between offering a SwiftUI-feel for the APIs, but sticking to CSS behavior (ie: a person knowing CSS should not be confused, but a person knowing SwiftUI should be able to find a way in).

I would love some eyes on this early on, so if you have opinions: please let me hear it!

8 Likes

Amazing! :star_struck: love your work!
I’m super excited about this project.
Gonna test it tonight!

1 Like

Another newsletter from your friendly neighborhood swift tinkerer:

I managed to migrate to the 6.2 swift.org WebAssembly SDKs essentially without any issues.

Building embedded wasm files now works with plain old SwiftPM - no custom linking scripts or other tricks outside of the official SDK needed!

As an example, here is the command for the Swiftle demo (using JavaScriptKit's js plugin for wasm-opt and packaging)

# build.sh - 6.2-snapshot-2025-08-21
swift package \
--swift-sdk "$(swiftc -print-target-info | jq -r '.swiftCompilerTag')_wasm-embedded" \
--enable-experimental-prebuilts \
--allow-writing-to-package-directory \
js -c release --output $OUTDIR --use-cdn

I think this is fantastic progress!

In general, the WebAssembly/JS-interop part has never given me any issues (that I know of).

Now, the embedded part still has a few sharp edges in store for you. I stubbed my toe on at least these issues:

And, more generally, every now and then you accidentally use some API that requires unicode handling - but nobody tells you where. All you get is a "where is my _swift_stdlib_getNormData symbol?" linker error, but to my knowledge, there is no way to get a diagnostic on the line in question for this.

In other news, I refactored the reconciler part a tiny bit (ie: complete overhaul) with the goal of making it fit for transitions and animations. It came out as a completely generic node tree, which should be great for runtime performance (no double dispatch, almost no indirections, reduced allocations) - but I fear the code size consequences, we will see.

I thought I was being very clever making rendered nodes generic over a ~Copyable protocol, but associated types are not yet able to suppress Copyable. Also, noncopyable tuples don't work, and enum and collection handling of noncopyables is still rough anyway, so I guess we'll have to call that idea a "future direction" for now - sure would be cool.

In conclusion, I believe there is not much keeping us from having a viable solution for low-fuss real-life client-side web apps written in Swift. In my mind the only real "blocker" is missing first-class handling of JSON data that works in Embedded Swift. But once the much desired future of serialization & deserialization APIs enters the chat, we should be good on that front.

Other nice-to-haves would include Observation for Embedded (maybe more support for KeyPaths in general) and the dust settling around _Concurrency/@MainActor/Swift 6 language mode in Embedded Wasm.

wdyt?

8 Likes

Actually, is there something sharable about the plans/status quo about the Swift Concurrency story with Embedded Swift?

It is near impossible to write a UI library in Swift 6 language mode (strict concurrency) without @MainActor - which, to my knowledge, is not a thing in embedded swift (yet?)

edit: I just figured I forgot the import _Concurrency in my test package, @MainActor does seem to compile with the embedded wasm SDK.

The Embedded docs on swift.org mention this about the status Swift Concurrency support

Partial, experimental (basics of actors and tasks work in single-threaded concurrency mode)

What exactly is this "single-threaded concurrency mode", and can I use it to tell the compiler to chill a bit about strict concurrency errors?

It's the mode that Swift SDKs for Wasm currently adopt, both embedded and non-embedded. Essentially your concurrency thread pool always has a single thread. I suppose you can adopt new 6.2 default executor feature to tell it everything in a given module is @MainActor? (Haven't tried it myself yet, especially as I think that resolving these strict concurrency errors is useful in case you ever want to support multi-threaded executors in the future).

In a very preliminary test it seems to at least compile with the latest main-snapshot. Promising...

But on the latest 6.2 I get this whenever MainActor is involved
Assertion failed: (Ptr && "Cannot dereference a null Type!"), function operator->, file Type.h, line 241.

I think I'll wait this one out a bit longer - I gotta choose my battles ; )

Embedded Swift concurrency for Wasm is not supported in 6.2, I advise using main development snapshots in the meantime.

1 Like