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:
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.
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
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.
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.
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 which I later extern define in C file, something like this
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
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
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 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.
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)
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:
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
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)
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.