Emscripten Swift

Hey,
I am trying to compile a Swift package for wasm as a library for the godot engine, which exposes its api through a C header. To include that header I had to create an empty C language target, containing an ugh.c file with no code in it. Otherwise Swift refuses to see this .target() as a module and I don't get the headers, for whatever reason.

Everything works perfectly fine on macOS, I can access the dylib within godot, create a godot_string instance and print "Hello world!", from Swift.

I also want this to work with wasm, unfortunately, it fails to compile with: No available targets are compatible with triple "wasm32-emscripten-wasi" which I assume is because of the C language target? :slight_smile:
How can I compile to a wasm library?

1 Like

This triple (as is Emscripten in general) is not supported, you have to build wasm32-unknown-wasi.

I have tried, it's the same error message.

> swift build -c release --triple wasm32-unknown-wasi
Building for production...
error: unable to create target: 'No available targets are compatible with triple "wasm32-unknown-wasi"'
1 error generated.
[0/2] Compiling GodotApi ugh.c
>

But if that's true and that target is not working then (most likely) it's no use trying at all; godot uses emscripten and the Rust community spent a while to even get it working with the language properly, it's very annoying and you need to build the engine and library with the same version too, because nothing is exactly stable yet.

If I were to get rid of the standard library completely and so the large String dependencies, I assume it would be much easier to just build for whatever platform I want?
Godot already has it's own class/type system, including strings, so it makes sense to write a tiny Swift std module that just reimplements everything with godot, so for example string literals would create godot strings :slight_smile:

There is absolutely no point in bringing in Swift types increasing binary size, if they are not going to be used.

Also, I only need the header, the types, the function pointers. not the "ugh.c" file. Can I maybe take the generated Swift interface to avoid the empty C dependency (which is probably why it can't compile)

Are you using a SwiftWasm toolchain or a toolchain from swift.org? The latter doesn't support Wasm either.

I don't think so. There's a significant amount of work to be done for each platform in the runtime, the toolchain itself, and also its build system. "Getting rid of the standard library" won't help, but actually could complicate things, since you need numerics, pointers etc on any platform. Reimplementing those from scratch you'll end up with something very close to stdlib APIs anyway, unless you're ready to redesign those on your own.

That's not the reason for why it can't be compiled. C targets are required to have at least a single source file (even an empty one) by the build system.

I tried both and the error is not different.

I am aware of that, and I've read the source code of uSwift and some of the official standard library, it's not that bad. godot already has most of the complicated parts implemented and exposed through the api, so for types like String I would just wrap godot_string

This can be done with the standard library still present, but then I have an unnecessary String dependency

You're most likely not specifying a full path to the toolchain. The SwiftWasm toolchain does support wasm32-unknown-wasi triple, in fact that's one of its few differences from the upstream toolchain. If you've installed it on macOS, you can target Wasm32/WASI like this:

~/Library/Developer/Toolchains/swift-wasm-5.7.1-RELEASE.xctoolchain/usr/bin/swift \
  build -c release --triple wasm32-unknown-wasi
1 Like

Oh, I thought changing the TOOLCHAINS environment variable would work, I probably misspelled something, thanks :slight_smile:

1 Like

Well, unfortunately (as expected) godot does not understand the library. I need to build using the exact same version of emscripten.
I'm using -emit-bc and trying to compile with emcc. It's complaining about missing symbols such as: wasm-ld: error: build/libswift.bc: undefined symbol: $sytWV
As far as I know that's because I don't have the runtime.

Can I force swiftc to -emit-bc for wasm32-unknown-emscripten without recompiling the toolchain :slight_smile: I'm using the wasi target and I assume that could cause issues.
Why is this still a limitation when I don't have the standard library, I don't depend on anything :/

$sytWV is the value witness table for an empty tuple (you can find this out from swift-demangle), normally provided by the Swift runtime. For better or worse—and in your case “for worse”—Swift is a very dynamic language with a runtime you can’t just ignore except in cases where all generics, existentials, class allocations, and capturing closures are optimized away. Even without the standard library, the compiler wants the runtime to be present.

1 Like

FWIW, I saw this link error recently in relation to empty enums:

// -parse-stdlib in flags
// enum Test { case x } // OK
enum Test {} // Undefined symbol: "_$sytWV"

Here are a few others I encountered in a bigger app that doesn't use standard library:

error build: Undefined symbol: _$sytWV
error build: Undefined symbol: _swift_allocateGenericValueMetadata
error build: Undefined symbol: _swift_beginAccess
error build: Undefined symbol: _swift_checkMetadataState
error build: Undefined symbol: _swift_endAccess
error build: Undefined symbol: _swift_getAssociatedConformanceWitness
error build: Undefined symbol: _swift_getAssociatedTypeWitness
error build: Undefined symbol: _swift_getEnumTagSinglePayloadGeneric
error build: Undefined symbol: _swift_getGenericMetadata
error build: Undefined symbol: _swift_getTupleTypeLayout
error build: Undefined symbol: _swift_getTupleTypeMetadata
error build: Undefined symbol: _swift_getTypeByMangledNameInContext
error build: Undefined symbol: _swift_getTypeByMangledNameInContextInMetadataState
error build: Undefined symbol: _swift_getWitnessTable
error build: Undefined symbol: _swift_initStructMetadata
error build: Undefined symbol: _swift_once
error build: Undefined symbol: _swift_retain
error build: Undefined symbol: _swift_storeEnumTagSinglePayloadGeneric

No idea how to remove them other than by using standard library.

Edit:

Is it possible to link swift runtime without linking standard library?

1 Like

I can live without classes but I do like generics, I will have to implement it at some point but first I would like to at least get it to work at all.
It would be interesting to implement the runtime in Swift.

Oh! It makes sense, I have defined typealias Void = () :laughing:

Looking at uSwift, it seems like you can compile a runtime and link it statically :slight_smile:

Yeah, the runtime doesn’t have to be pre-linked into the standard library, that’s just convenient for Swift’s supported platforms.

If you really want to go down this route, you may be interested in my hobby project porting Swift to Mac OS 9. It is a very large amount of work, but you can have an alternate implementation of the Swift runtime that only supports a subset of functionality of the real runtime, and most of it (but not all) can be written in Swift if you’re careful.

I’m not sure I recommend this for a production project, though, over working with the existing uSwift and SwiftWasm projects to add emscripten-wasm as a supported variant.

2 Likes

I could try using uSwift, but I'm still trying to build the unknown target toolchain.
It compiles, but fails some tests and that seems to prevent it from finishing the toolchain. I can't find a way to tell build-toolchain to skip tests.

I'm not even sure the failing tests are related to swift, there's a lot of Python and lldb before the error happens. Or maybe it finished some tests and now is trying to build lldb. There's a lot of C++ in the output which is not a language I know very well :slight_smile: