Mixing in Swift into C embedded project

I have a small software renderer written in C for my STM32F429I-DISC1 board. I utilises display drivers provided by STM. I would like to have my application code written in Swift instead, while keeping all (or most) of the C driver code.
I have ported two of the example projects to my board.
Two approaches that I've tried but I'm stuck on both:

  1. Take stm32-uart-echo as base of the project and drop in all of the C code the same way as 'startup.S' and 'Support.c' are added. But I can't really do that because then I have a different startup code and also I can't figure out how to link without using .ld script.
  2. Take my project as base and add Swift source files to it. This way I have to change target triple, and when I change it to 'armv7em-arm-none-eabi' Swift sources don't compile anymore.
    I'm not sure how to proceed and If it's even possible at this stage of Embedded Swift. Rewriting all the drivers looks way too hard.
    I just want to call my Swift code from C and my C code from Swift :pray:
    In short: I'm fighting with tooling when trying to mix C drivers and Swift code in Embedded project.
    Thank you!

As much as I love "the build everything from scratch" e.g. option 1, I do not recommend that approach as its a ton of work.

So I think option 2 is definitely the way to go. But I'm not following why you're using the armv7em-arm-none-eabi triple. Specifically the arm "vendor" part of the triple looks incorrect. Can you explain why you're using this vendor and also try using armv7em-none-none-eabi instead?

Thanks for the reply! I have tried to use that triple just because I'm compiling the C code with arm-none-eabi-gcc.
I've managed to compile Swift source into an .o object file using the target triple you've suggested, and I've also generated .h header for my Swift module where I can see my foo function.

Here's commands for generating swift module header and compiling swift source file.

# Compile swift sources
export TOOLCHAINS='org.swift.59202408051a'

swiftc -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded \
        -Xcc -D__APPLE__ -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m4 -Xcc -mthumb -Xcc -mfpu=fpv4-sp-d16 -Xcc -mfloat-abi=hard \
        -c hello.swift -o build/hello.o

# Generate swift module .h file
swiftc -frontend -typecheck \
      -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded \
       hello.swift -module-name SwiftModule \
       -cxx-interoperability-mode=default \
       -emit-clang-header-path SwiftModule-Swift.h \
       -Xcc -D__APPLE__ -Xcc -D__MACH__ -Xcc -ffreestanding -Xcc -mcpu=cortex-m4 -Xcc -mthumb -Xcc -mfpu=fpv4-sp-d16 -Xcc -mfloat-abi=hard \

But when I try to link all of the object files, the linker can't find my foo function.
I'm happy to provide more information if needed.

I'm working on a simplified version of the project with just a couple of .c files, .ld script and Makefile. Can my current approach work or do I have to figure out a way to compile and link my C code with clang and lld, and only then I can hope to link my object files together ?
Thank you!

I'd suggest that when you are defining the triple as -eabi avoid defining __APPLE__ and __MACH__ since those are used for mach-o builds (aka armv7em-apple-none-macho which can be used to avoid the c runtime requirements for some targets). One additional note on that c runtime requirement: you can build the llvm compiler-rt for that target to resolve some of those issues as well.

2 Likes

Thanks! I finally managed to link it together.

Here's the updated swiftc command

swiftc -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded -no-allocations -parse-as-library \
	-Xcc -ffreestanding -Xcc -fdata-sections -Xcc -ffunction-sections -Xcc -mcpu=cortex-m4 -Xcc -mthumb -Xcc -mfpu=fpv4-sp-d16 -Xcc -mfloat-abi=hard \
    -c hello.swift -o build/hello.o

Key changes are -no-allocations and -parse-as-library flags.
And for now I've dropped the approach with creating a module map and just look through .map file and search for function names :sweat_smile: which I later extern define in C file, something like this

extern bool $$s5hello7StrooctV9myFooFuncSbyF(void);

Unfortunately I can only use structs in my swift code for now, hope I can figure out a way to let swift compiler know about custom malloc code. Now I get errors like this when I try to create a class

/arm-none-eabi/bin/ld: build/hello.o: in function `$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF':
hello.o:(.text+0x48): undefined reference to `posix_memalign'
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: build/hello.o: in function `$ss15swift_slowAllocySvSgSi_SitF':
hello.o:(.text+0x13e): undefined reference to `posix_memalign'

But that's already a big success in my book :partying_face:

Can you suggest how can I fix dynamic allocation issue? There are custom allocators for my MCU and posix_memaling looks out of place here. Thanks

Here's the whole project on GitHub.

I think they got around this on the swift-embedded-examples by defining posix_memalign:

Good to see someone else playing with Embedded Swift!

1 Like

Thanks for the suggestion, will try to port that code at some point. For now I'll see how far I can go without using classes. This will be a good exercise !
I've done a little update to my project it's in development branch for now ( there are a few bugs I need to iron out )


I'm really excited that my idea looks at least possible.

3 Likes

Very cool to see this! :sunglasses:

Didn't get far without classes, so had to add them. Thanks for the help, Jesse!
This function looks a bit concerning :sweat_smile:

void free(void *ptr) {
  __builtin_trap();
  // never free
}

Does it mean that instances I create will never be deallocated ?

Couple of new questions I have are:

  1. Looks like C struct layout is different from Swift's. Can I make them the same, and if yes should I do that ?
  2. I do my screen buffer allocation like this
static uint32_t colorBuffer[SCREEN_WIDHT][SCREEN_HEIGHT] __attribute__((section(".sdram")));

I especially need the __attribute__((section(".sdram"))) to place my buffer in SDRAM section. Allocating memory in C code and using it in Swift works, and will probably be enough, but I'm curious if it's possible to do allocation in specific memory regions in Swift.

1 Like

I think there's two realistic options: (1) Use some existing libc implementation that has a working allocator -- popular choices these days seem to be picolibc, newlibc, llvm libc. (2) Write your own implementation of malloc/posix_memalign/free, either in C or in Swift. For (2) I'm not sure if there's any sample/reference written in Swift? Maybe @Philippe_Hausler knows.

So in an experiment for Embedded Swift I did write a heap allocator - this is a trimmed down version that should work (please make sure to test it well - caveat emptor)

With some not too outlandish optimizations it could be modified to support multi-core systems with locking and such (and even per-core free list locality to avoid the locking). This could also be modified to support chips with MMU's as well... I leave those tasks as an exercise for the reader ;)

It does require two defined variables for MALLOC_START and MALLOC_END - defining those reserves that memory region for all the tables and such for the malloc implementation. It does presume that the CPU is in a state that it can use atomics (due to the shared instance, and relies on that for the initialization of the initial free-list)

1 Like

I have another, this time quite baffling, issue.

Here's my Swift file, struct Point is the interesting place. I'm not even using this struct, just creating an instance of it.

public func startSwiftEngine() {
    let engine = SwiftEngine()

    engine.onCreate()

    while true {
        engine.onUpdate()
    }
}

struct Point {
    // If this empty array is here, code works. But if I comment it out it crashes.
    //let unusedArr: [Int] = []
    let x: Int
    let y: Int
}

final class SwiftRenderer {
    private let screenWidth: UInt
    private let screenHeight: UInt

    init(screenWidth: UInt, screenHeight: UInt) {
        self.screenWidth = screenWidth
        self.screenHeight = screenHeight
    }

    func renderLogo(at point: Point) {
        for x in 0..<screenWidth {
            for y in 0..<screenHeight {
                let pixel = getSwiftLogoPixelDataAt(UInt32(y % 50), UInt32(x % 50))
                screen_write_pixel(
                    UInt32(x),
                    UInt32(y),
                    pixel
                )
            }
        }
        screen_flush()
    }
}

final class SwiftEngine {
    private let renderer = SwiftRenderer(screenWidth: 240, screenHeight: 320)

    init() {}

    func onCreate() {
    }

    func onUpdate() {
        let point = Point(x: 0, y: 0)
        renderer.renderLogo(at: point)
    }
}

If I comment out unusedArr from struct, I get crash with the following stack trace:

STM32SMOL.elf [cores: 0]	
	Thread #1 [main] 1 [core: 0] (Suspended : Signal : SIGTRAP:Trace/breakpoint trap)	
		HardFault_Handler() at stm32f4xx_it.c:86 0x8002270	
		<signal handler called>() at 0xfffffff9	
		$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF() at 0x8000290	
		$s6engine11SwiftEngineCACycfc() at 0x8000234	
		$s6engine11SwiftEngineCACycfc() at 0x8000234	
		$s6engine16startSwiftEngineyyF() at 0x80001c2	
		main() at main.c:120 0x8001e58	

I've tried changing alignment of the .bss section, where I've put swift's heap, to 8.
Also I've tried to use SingleCoreAllocator.swift but the issue persists.
Can you help me out again, please ? If my description is not informative enough, what should I add ? Thank you!

hmm the heap should not go in the .bss segment; that should be initialized to zeroed memory on boot (it is used for variables and their initial values).

Typically linker scripts that define those locations look something like this:


SECTIONS {
    . = KERNEL_LOAD_ADDR;
    .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
    .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
    . = ALIGN(4096);
    PROVIDE(_data = .);
    .data : { *(.data .data.* .gnu.linkonce.d*) }
    .bss (NOLOAD) : {
        . = ALIGN(16);
        __bss_start = .;
        *(.bss .bss.*)
        *(COMMON)
        __bss_end = .;
    }
    . = ALIGN(4096);
    id_pg_dir = .;
    .data.id_pg_dir : { . += ID_MAP_TABLE_SIZE; }
    high_pg_dir = .;
    .data.high_pg_dir : { . += HIGH_MAP_TABLE_SIZE; }
    
    _end = .;

   /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
}
__bss_size = (__bss_end - __bss_start)>>3;

and early on in the initialization routines (boot.S type stuff) there is a call something along these lines:

    ldr     x0, =__bss_start
    ldr     x1, =__bss_end
    sub     x1, x1, x0
    bl      bzero

but the malloc region should be defined something more so like this:

#define MALLOC_START (0x40000000ul)
#define MALLOC_END   (0xFC000000ul)

Where the physical address of the malloc start and end are well beyond any memory map tables or bss locations. I would guess that your linker script (smol/STM32F429ZITx_FLASH.ld at development · mkbrwr/smol · GitHub) should be defining the malloc region somewhere inside of the range _ssdram..< _esdram

Thanks a lot!
I've managed to make something similar to stm32-lcd-logo

I've defined

let MALLOC_START = UInt(0xD02A_B000)
let MALLOC_END   = UInt(0xD080_0000)

while my .sdram section is defined like this

SDRAM (rw) : ORIGIN = 0xD0000000, LENGTH = 8M

if my understanding is correct, Swift's heap goes into .sdram after my screen buffers, which end at 0xD02A_B000

I'm thinking of creating a separate section just for Swfit's heap in my liker script once I feel more comfortable with changing the script.

One additional thing I had to do is to disable stack protection with -Xfrontend -disable-stack-protector, -Xcc -fno-stack-protector for Swift and -fno-stack-protector for C.

Unfortunately code still crashes with heavy use of classes e.g. when I change Point and Pixel from struct to class code runs for a couple of seconds and then crashes with the following stack trace

STM32SMOL.elf [cores: 0]	
	Thread #1 [main] 1 [core: 0] (Suspended : Signal : SIGTRAP:Trace/breakpoint trap)	
		HardFault_Handler() at stm32f4xx_it.c:86 0x8002e60	
		<signal handler called>() at 0xfffffff9	
		$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF() at 0x8000dec	
		$s6engine14posix_memalignys5Int32VSpySvSgG_S2itF() at 0x8000aae	
		$s6engine9SwiftLogoCyAA5PixelCSgSicig() at 0x80002fc	

I'd try some primitive allocator to get this off the ground

e.g.
var memoryArea = UnsafeMutableRawPointer(bitPattern: 0xD02A_B000)!
var offset = 0

func malloc(_ size: Int) -> UnsafeMutableRawPointer! {
    // always align to 16 for simplicity
    var size = size + 16 - 1
    size = size - (size % 16)
    let result = memoryArea + offset
    offset += size
    if offset >= 5000_000 {
        fatalError()
    }
    return result
}
func free(_ v: UnsafeMutableRawPointer?) {
    // noop
}
func fatalError() -> Never {
    while true {} // or something something
}

Currently I'm using SingleCoreAllocator.swift provided by Philippe_Hausler

When I have struct Point and struct Pixel app works just fine. I assume that's because heap is not being used.
When using class instead of struct for Point and Pixel, app runs for a few seconds but then crashes. And the smaller the distance between MALLOC_START and MALLOC_END in SingleCoreAllocator.swift the quicker the app crashes. This leads me to believe that allocation works but something else is broken.

There's also this warning

SingleCoreAllocator.swift:250:2: warning: symbol name 'free' is reserved for the Swift runtime and cannot be directly referenced without causing unpredictable behavior; this will become an error
248 | }
249 |
250 | @_cdecl("free")
    |  `- warning: symbol name 'free' is reserved for the Swift runtime and cannot be directly referenced without causing unpredictable behavior; this will become an error
251 | public func free(_ ptr: UnsafeMutableRawPointer?) {
252 |     SingleCoreAllocator.shared.deallocate(ptr)

The simplest explanation would be that we exhaust the heap... Can we rule that out?

2 Likes

I'd second that. Looks like a memory leak, perhaps happening in the allocator itself.
How much space is getting allocated in total (disregarding deallocation) over the course of those few seconds? Several megabytes?

And the reason I suggested to use a much simpler allocator is just to test and to rule out the potential issue in a more complicated allocator.

Big thanks for the help !
Can this subscript cause a memory leak?

class SwiftLogo: Sprite {
    let width = 50
    let height = 50

    subscript(index: Int) -> Pixel? {
        guard index < width * height else { return nil }
        return Pixel(argb: getSwiftLogoPixelDataAt(UInt32(index)))
    }
}

getSwiftLogoPixelDataAt(UInt32(index)) is a C function

I'm asking this because when I changed this class to

class SwiftLogo: Sprite {
    let width = 50
    let height = 50

    var pixels: [Pixel] = []

    init() {
        for index in 0..<width * height {
            pixels.append(Pixel(argb: getSwiftLogoPixelDataAt(UInt32(index))))
        }
    }

    subscript(index: Int) -> Pixel? {
        guard index < width * height else { return nil }
        return pixels[index]
    }
}

it does not crash anymore. It was running just fine for a couple of minutes.

I'm testing with 128K of heap

private let MALLOC_START = UInt(0xD02A_B000)
private let MALLOC_END = UInt(0xD02C_B000)

and can see that my class Point instances init and deinit

About how much memory was allocated, I can't tell. I need a better debugging setup than the one that I have rn :sweat_smile: