Basic start with Embedded Swift

My goal is to compile this little program and be able to upload it to an ARM microcontroller. But first things first. Compiling and linking. Please ignore the fact that I am not able to read the printed text somewhere.

@_cdecl("app_main")
func app_main() {
    print("Hello, Embedded Swift!")
}

This is the compile command I am using.

swiftc -enable-experimental-feature Embedded -wmo -target armv7-none-none-eabi src/main.swift -Osize -c -o .build/firmware.o

Next step would be linking. This is the command I came up with.

clang -target armv7-none-none-eabi .build/firmware.o -o firmware -nostdlib -static

But that is failing with the following output.

% clang -target armv7-none-none-eabi .build/firmware.o -o firmware -nostdlib -static -v
Apple clang version 17.0.0 (https://github.com/swiftlang/llvm-project.git 73a346c9caf32ccc1fcb0ec7bfda033213b08d51)
Target: armv7-none-none-eabi
Thread model: posix
InstalledDir: /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-08-07-a.xctoolchain/usr/bin
 "/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-08-07-a.xctoolchain/usr/bin/ld.lld" .build/firmware.o -Bstatic -EL -L/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-08-07-a.xctoolchain/usr/bin/../lib/clang-runtimes/armv7-none-none-eabi/lib -L/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-08-07-a.xctoolchain/usr/bin/../lib/clang-runtimes/armv7-none-none-eabi/lib -L/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2024-08-07-a.xctoolchain/usr/lib/clang/17/lib/baremetal --target2=rel -o firmware
ld.lld: error: undefined symbol: putchar
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss5print_10terminatorys12StaticStringV_ADtF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss5print_10terminatorys12StaticStringV_ADtF)

ld.lld: error: undefined symbol: posix_memalign
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss15swift_slowAllocySvSgSi_SitF)

ld.lld: error: undefined symbol: __stack_chk_fail
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss15swift_slowAllocySvSgSi_SitF)

ld.lld: error: undefined symbol: __stack_chk_guard
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss15swift_slowAllocySvSgSi_SitF)
>>> referenced 1 more times

ld.lld: error: undefined symbol: free
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss19swift_deallocObject6object13allocatedSize0E9AlignMaskyBp_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss26swift_deallocClassInstance6object13allocatedSize0F9AlignMaskyBp_S2itF)
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss17swift_slowDeallocyySv_S2itF)

ld.lld: error: undefined symbol: __aeabi_memclr
>>> referenced by firmware.o
>>>               .build/firmware.o:($ss20swift_clearSensitive3buf6nbytesySv_SitF)
clang: error: ld.lld command failed with exit code 1 (use -v to see invocation)

I feel like the Standard C Library is missing (as I opted out of it with -nostdlib) but removing it lead to a different error.

ld.lld: error: unable to find library -lc
ld.lld: error: unable to find library -lm
ld.lld: error: unable to find library -lclang_rt.builtins-armv7

Would be nice if someone can push me to the right direction since I want to learn how things function.

Thanks a lot!

To actually form a full valid firmware that can run on a microcontroller, a decent amount of support code is needed and a lot of it must be platform/HW/CPU/flashing-tool specific. And we don't yet have any official or even unofficial Swift library that would be this "basic platform support" library. And because it's pretty target specific, it's not as simple as building this library once in a general way.

What you can in the meantime, is either:

  • (1) Build some minimal runtime support by understanding your target's requirements (memory map, flash location, boot requirements, etc.)
  • (2) Use an existing framework/SDK that already supports your target and provides the libc and crt0 libraries, already has the right linker scripts, etc.

There are examples of both these approaches in swift-embedded-examples. For example stm32-neopixel is an example of approach #1, see e.g. the Support.c and startup.S files. And And pico-blink-sdk is an example of approach #2.

3 Likes

Adding to what kubamracek said, if you look at the external dependencies section in the Embedded Swift User Manual its lists out the functions that you need to provide if your device's SDK does not.

Not knowing this section existed caused me so much frustration...

I fiddled a bit more and learned that I can either stud those functions that are outputted from the linker (ld.lld: error: undefined symbol: <symbol>) or use a command line flag swiftc [...] -function-sections.

While fiddling I came across some very good resources:

This fork of apples swift-embedded-examples got actually adjusted to that I should run fine on linux.

For those who are interested. I came up with this simple Makefile that builds object files and link them together into a binary.

TARGET := armv7-none-none-eabi
SWIFTC_FLAGS := -enable-experimental-feature Embedded -target $(TARGET) -wmo -Osize -Xcc -ffreestanding -Xfrontend -function-sections
CLANG_FLAGS := -target $(TARGET) -Oz
LD_FLAGS := -target $(TARGET) -static -nostdlib -Wl,-gc-sections -Wl,-T,stm32.ld -Wl,-Map,.build/firmware.map

all: startup.o firmware.o link bin

startup.o: startup.c
	clang $(CLANG_FLAGS) -c $^ -o .build/$@ 

firmware.o: Sources/Firmware/firmware.swift
	swiftc $(SWIFTC_FLAGS) -c $^ -o .build/$@

link:
	clang $(LD_FLAGS) .build/*.o -o .build/firmware.elf

# requires `brew install binutils`
bin:
	objcopy -O binary .build/firmware.elf .build/firmware.bin

clean:
	rm -rf .build/*

Thank you kubamracek and navan for your input. I think I can preceed from here on.

3 Likes