If you want to go further with debug tools, you can extract the business logic of the allocator into a library and write a heap walker that can run in the debugger!
When you allocate it in the init upfront you are allocating some 50*50*X
bytes once, where X is 20 or 30 or so - the size of your pixel class (two ints plus swift instance header, which I believe is two pointers). That's a significant amount of memory but it fits within 128K you are testing it with. And you are not testing how deallocate works with this approach (so even if that was a no-op you'd probably not notice it).
On the contrary, when you are creating a class on as needed basis, 50*50 times per frame x several frames in a row – you are (should be) allocating a single pixel object, use it somehow and deallocate it immediately – the result should be much less memory pressure. The fact it crashes with time suggests deallocation doesn't happen.
If you test this scenario with my simple allocator I guess you'll get into the fatalError while true loop after a few seconds (instead of crashing).
Overall it smells like memory leak (or the allocator's deallocator is misbehaving), we just can't pinpoint where exactly it is at this point.
Edit:
Try this:
init() {
for _ in 0 ..< 1000 {
pixels = []
for index in 0..<width * height {
pixels.append(Pixel(argb: getSwiftLogoPixelDataAt(UInt32(index))))
}
}
}
Does it crash? (Obviously it should not.)
Here's how I've adapted your simple allocator code
test_alloc.swift
let startAddress = UInt(0xD02A_B000)
var memoryArea = UnsafeMutableRawPointer(bitPattern: startAddress)!
var offset = 0
@_cdecl("posix_memalign")
public func posix_memalign(
_ ptr: UnsafeMutablePointer<UnsafeMutableRawPointer?>, _ alignment: Int, _ size: Int
) -> CInt {
// 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()
}
ptr.pointee = result
return 0
}
@_cdecl("free")
public func free(_ ptr: UnsafeMutableRawPointer?) {
// noop
}
func fatalError() -> Never {
while true {
HAL_Delay(100)
BSP_LED_Toggle(RED_LED)
HAL_Delay(100)
} // or something something
}
As @tera suggested I've tested the following three things:
- the "allocate all upfront" version (with simple allocator) doesn't enter the fatalError loop.
- the "allocate as you go" version (with simple allocator) does enter the fatalError loop.
- the "allocate all upfront" version (with simple allocator) that is changed to allocate 1000 times (below) does enter the fatalError loop
init() {
for _ in 0 ..< 1000 {
pixels = []
for index in 0..<width * height {
pixels.append(Pixel(argb: getSwiftLogoPixelDataAt(UInt32(index))))
}
}
}
All of the tests are true.
What should I try to find the root cause of the issue ?
Edit:
I did some crude debugging with RTT and while I can see many calls to posix_memalign
@_cdecl("free") public func free(_ ptr: UnsafeMutableRawPointer?)
is never called
With the "no-op" deallocator it is expected. The question: does it happen with the original allocator you were trying? Note that on the out of memory condition instead of entering the "while true" loop it will return nil
which would translate into a crash.
If free
is not getting called that's the problem. A quick check – are you still passing "-no-allocations" flag? That would explain why free
is not called (although that doesn't explain why malloc/posix_memalign
is called).
No, I don't pass -no-allocations
.
Here's current compile command for swift files
swiftc -Xfrontend -disable-stack-protector -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded -parse-as-library \
-import-bridging-header Game/Bridging-Header.h \
-Xcc -fno-stack-protector -Xcc -ffreestanding -Xcc -fdata-sections -Xcc -ffunction-sections -Xcc -mcpu=cortex-m4 -Xcc -mthumb -Xcc -mfpu=fpv4-sp-d16 -Xcc -mfloat-abi=hard \
-c Game/engine.swift test_alloc.swift -o build/engine.o
I've tried removing -parse-as-library
and -disable-stack-protector
flags, but to no effect.
If you run this simplified version of your app (the logic is simplified, the LED flashing is moved to free) do you see the LED flashing?
simplified
let startAddress = UInt(0xD02A_B000)
var memoryArea = UnsafeMutableRawPointer(bitPattern: startAddress)!
var usedSize = 0
@_cdecl("posix_memalign")
public func posix_memalign(_ ptr: UnsafeMutablePointer<UnsafeMutableRawPointer?>, _ alignment: Int, _ size: Int) -> CInt {
// always align to 16 for simplicity
var size = size + 16 - 1
size -= size % 16
let result = memoryArea + usedSize
usedSize += size
if usedSize >= 5000_000 {
return -1
}
ptr.pointee = result
return 0
}
@_cdecl("free")
public func free(_ ptr: UnsafeMutableRawPointer?) {
HAL_Delay(100)
BSP_LED_Toggle(RED_LED)
HAL_Delay(100)
}
// start
class C { int x = 0 }
var c: C
for _ in 0 ..< 20 {
c = C()
}
// end
I guess not? That to double check that free
is not getting called.
PS. For completeness malloc
/ calloc
/ and realloc
need to change if you change free
. What happens on your setup if you call those? If you are allowed to call those are they returning non-nil value?
PPS. or is posix_memalign
the underlying low level call for any of those?
PPPS. I don't see how sane implementation of realloc
(that tries to maintain the existing address instead of always allocating new memory and copying to it) could be implemented in terms of posix_memalign
(while the opposite is ok). realloc
per se might not be needed for Swift runtime, but I am not sure about the other two: malloc
and calloc
. Leaving this for others to comment upon.
Having said that. What about malloc_size
? Is it available on the platform? What happens if you call it? Does Swift runtime need it? It won't work with my toy allocator implementation (as above) although it would be easy to add. Typically you'd need something like this for it to work:
size payload size payload ...
^ - the allocator returns this memory
^ - malloc_size reads the preceding bytes to know the size
Note that malloc_size
is needed for realloc
among other things.
Blockquote
If you run this simplified version of your app (the logic is simplified, the LED flashing is moved to free) do you see the LED flashing? I guess not? That to double check thatfree
is not getting called.
No, I don't see the LED flashing.
FWIW when tested in a simple console macOS application:
struct C {}
var c: C
c = C()
for _ in 0 ..< 20 {
c = C()
}
I see malloc_size
, malloc
, calloc
and free
being called, although not posix_memalign
or realloc
. But that on macOS... the situation could be very different on an embedded platform.
I did some digging into disassembly and here's what I've found
Stack Trace
STM32SMOL.elf [cores: 0]
Thread #1 [main] 1 [core: 0] (Suspended : Breakpoint)
$ss26swift_deallocClassInstance6object13allocatedSize0F9AlignMaskyBp_S2itF() at 0x8000ad0
_swift_embedded_invoke_heap_object_destroy() at 0x8000e52
$s6engine6ScreenC5width6height15backgroundColorACSi_SiAA5PixelCtcfC() at 0x8000394
$s6engine11SwiftEngineCACycfc() at 0x8000576
$s6engine16startSwiftEngineyyF() at 0x80001c4
main() at main.c:125 0x80026b4
Disassembly
$ss26swift_deallocClassInstance6object13allocatedSize0F9AlignMaskyBp_S2itF:
08000ace: ldr r1, [r0, #4]
08000ad0: cmp r1, #0
08000ad2: bmi.n 0x8000ad8 <$ss26swift_deallocClassInstance6object13allocatedSize0F9AlignMaskyBp_S2itF+10>
08000ad4: b.w 0x8001046 <free>
08000ad8: bx lr
In disassembly at address 08000ad2 register r1 has value of -1 and execution branches to 0x8000ad8 instead of calling free.
Stepping back a little. Without your version of free
what happens when you try: free(nil)
, does it compile and link?
Ditto for the malloc(1)
.
I've removed SingleCoreAllocator.swift
since, and use Support.c
instead.
Support.c
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2024 Apple Inc. and the Swift project authors.
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
#if defined(__arm__)
#include <stdint.h>
#include <stddef.h>
#define HEAP_SIZE (2 * 1024 * 1024)
char* heap = (char*)0xD02AB000;
size_t next_heap_index = 0;
void *calloc(size_t count, size_t size) {
if (next_heap_index + count * size > HEAP_SIZE) __builtin_trap();
void *p = &heap[next_heap_index];
next_heap_index += count * size;
return p;
}
int posix_memalign(void **memptr, size_t alignment, size_t size) {
*memptr = calloc(size + alignment, 1);
if (((uintptr_t)*memptr) % alignment == 0) return 0;
*(uintptr_t *)memptr += alignment - ((uintptr_t)*memptr % alignment);
return 0;
}
// void free(void *ptr) {
// while (1) {
// HAL_Delay(100);
// BSP_LED_Toggle(1);
// HAL_Delay(100);
// }
// }
void *memset(void *b, int c, size_t len) {
for (int i = 0; i < len; i++) {
((char *)b)[i] = c;
}
return b;
}
void *memcpy(void *restrict dst, const void *restrict src, size_t n) {
for (int i = 0; i < n; i++) {
((char *)dst)[i] = ((char *)src)[i];
}
return dst;
}
#endif
And here's my test swift code. It compiles, links and executes successfully. I can see green LED flashing.
public func startSwiftEngine() {
malloc(1)
free(nil)
struct CDontMangleMe {}
var c: CDontMangleMe
c = CDontMangleMe()
for _ in 0..<20 {
c = CDontMangleMe()
}
while true {
HAL_Delay(50)
Led.green.toggle()
HAL_Delay(50)
}
}
Note that I had to add the following to my Bridging Header.
#include <stddef.h>
void free(void* ptr);
void *malloc(size_t size);
Also I need posix_mealign
defined (it's in Support.c), or else linking fails
Edit: I feel like this debugging is getting quite messy, I'll create a bare project with Swift classes and post it here
Here's clean project (it's in debug branch) generated with STM32CubeMX with one Swift file added.
Src/main.c
calls into
public func startSwiftEngine()
defined in Game/game.swift
game.swift
public func startSwiftEngine() {
struct CDontMangleMe {}
var c: CDontMangleMe
c = CDontMangleMe()
for _ in 0..<20_000 {
c = CDontMangleMe()
}
while true {
HAL_Delay(1000)
BSP_LED_Toggle(0)
HAL_Delay(1000)
}
}
Here's compile command for Swift files
swiftc -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded -parse-as-library -no-allocations \
-import-bridging-header Bridging-Header.h \
-c Game/game.swift -o $(BUILD_DIR)/game.o
It has -no-allocations
since game.swift
compiles just fine with this flag.
At this stage everything works as expected, I can see green LED (0
) flashing.
Next, I change struct CDontMangleMe
to class CDontMangleMe
and also remove -no-allocations
flag. I get the following error
error
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: build/game.o: in function `$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF':
game.o:(.text+0x5c): 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/game.o: in function `$ss15swift_slowAllocySvSgSi_SitF':
game.o:(.text+0x11e): undefined reference to `posix_memalign'
collect2: error: ld returned 1 exit status
make: *** [build/Swift_STM.elf] Error 1
Now I need provide implementation for posix_memalign
.
I add your test allocator, @tera
test_alloc.swift
let startAddress = UInt(0x2001_8000)
var memoryArea = UnsafeMutableRawPointer(bitPattern: startAddress)!
var offset = 0
@_cdecl("posix_memalign")
public func posix_memalign(
_ ptr: UnsafeMutablePointer<UnsafeMutableRawPointer?>, _ alignment: Int, _ size: Int
) -> CInt {
// always align to 16 for simplicity
var size = size + 16 - 1
size = size - (size % 16)
let result = memoryArea + offset
offset += size
if offset >= 0xC000 {
fatalError()
}
ptr.pointee = result
return 0
}
@_cdecl("free")
public func free(_ ptr: UnsafeMutableRawPointer?) {
// noop
}
func fatalError() -> Never {
while true {
HAL_Delay(50)
BSP_LED_Toggle(1)
HAL_Delay(50)
} // or something something
}
And run the code. Everything works until I add a field to my class
class CDontMangleMe {
let x = UInt32.random(in: 0x00..<0xffff_ffff)
}
game.swift
public func startSwiftEngine() {
class CDontMangleMe {
let x = UInt32.random(in: 0x00..<0xffff_ffff)
}
var c: CDontMangleMe
c = CDontMangleMe()
for _ in 0..<20_000 {
c = CDontMangleMe()
}
while true {
HAL_Delay(1000)
BSP_LED_Toggle(0)
HAL_Delay(1000)
}
}
After this I can see Red LED (1
) blinking really fast, that mean's I've ran out of heap space. I can lower the number of allocations to 1000 and I don't run out of heap anymore. But 20000 is too many
Note about new addresses. What I've done is cut down space for RAM in .ld script in half. And I'm using half of the other half for Swift's heap. Something like this
|---------------------RAM---------------------|
|----other_sections----||-swift_heap-||-empty-|
Next I change free to this (below) and number of allocations to 1000. I expect to see both LEDs and program running in inf loop. But it does not happen, it enters while loop in game.swift
@_cdecl("free")
public func free(_ ptr: UnsafeMutableRawPointer?) {
BSP_LED_On(1)
BSP_LED_On(2)
while (1) {}
}
Here's pretty much everything from the empty project to last commit in debug
Thank you for reading this essay Hope this is useful!
Here's make output
terminal
> Swift_STM % make clean
rm -fR build
> Swift_STM % make stflash
mkdir build
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/main.d" -Wa,-a,-ad,-alms=build/main.lst Src/main.c -o build/main.o
Src/main.c: In function 'main':
Src/main.c:93:3: warning: implicit declaration of function '$$s4game16startSwiftEngineyyF' [-Wimplicit-function-declaration]
93 | $$s4game16startSwiftEngineyyF();
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/gpio.d" -Wa,-a,-ad,-alms=build/gpio.lst Src/gpio.c -o build/gpio.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_it.d" -Wa,-a,-ad,-alms=build/stm32f4xx_it.lst Src/stm32f4xx_it.c -o build/stm32f4xx_it.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_msp.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_msp.lst Src/stm32f4xx_hal_msp.c -o build/stm32f4xx_hal_msp.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_rcc.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_rcc.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c -o build/stm32f4xx_hal_rcc.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_rcc_ex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_rcc_ex.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c -o build/stm32f4xx_hal_rcc_ex.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_flash.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_flash.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash.c -o build/stm32f4xx_hal_flash.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_flash_ex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_flash_ex.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ex.c -o build/stm32f4xx_hal_flash_ex.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_flash_ramfunc.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_flash_ramfunc.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_flash_ramfunc.c -o build/stm32f4xx_hal_flash_ramfunc.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_gpio.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_gpio.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c -o build/stm32f4xx_hal_gpio.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_dma_ex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_dma_ex.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c -o build/stm32f4xx_hal_dma_ex.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_dma.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_dma.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c -o build/stm32f4xx_hal_dma.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_pwr.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_pwr.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr.c -o build/stm32f4xx_hal_pwr.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_pwr_ex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_pwr_ex.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_pwr_ex.c -o build/stm32f4xx_hal_pwr_ex.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_cortex.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_cortex.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c -o build/stm32f4xx_hal_cortex.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c -o build/stm32f4xx_hal.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f4xx_hal_exti.d" -Wa,-a,-ad,-alms=build/stm32f4xx_hal_exti.lst Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c -o build/stm32f4xx_hal_exti.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/stm32f429i_discovery.d" -Wa,-a,-ad,-alms=build/stm32f429i_discovery.lst Drivers/STM32F429I-Discovery/stm32f429i_discovery.c -o build/stm32f429i_discovery.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/system_stm32f4xx.d" -Wa,-a,-ad,-alms=build/system_stm32f4xx.lst Src/system_stm32f4xx.c -o build/system_stm32f4xx.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/sysmem.d" -Wa,-a,-ad,-alms=build/sysmem.lst Src/sysmem.c -o build/sysmem.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/syscalls.d" -Wa,-a,-ad,-alms=build/syscalls.lst Src/syscalls.c -o build/syscalls.o
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc -x assembler-with-cpp -c -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -DUSE_HAL_DRIVER -DSTM32F429xx -IInc -IDrivers/STM32F4xx_HAL_Driver/Inc -IDrivers/STM32F4xx_HAL_Driver/Inc/Legacy -IDrivers/STM32F429I-Discovery/ -IDrivers/CMSIS/Device/ST/STM32F4xx/Include -IDrivers/CMSIS/Include -Og -Wall -fdata-sections -ffunction-sections -g -gdwarf-2 -MMD -MP -MF"build/startup_stm32f429xx.d" startup_stm32f429xx.s -o build/startup_stm32f429xx.o
swiftc -target armv7em-none-none-eabi -Osize -wmo -enable-experimental-feature Embedded -parse-as-library \
-import-bridging-header Bridging-Header.h \
-c Game/game.swift test_alloc.swift -o build/game.o
/Users/mykhailotymchyshyn/Development/Embedded/ST/Swift_STM/Game/game.swift:5:9: warning: variable 'c' was written to, but never read
3 | let x = UInt32.random(in: 0x00..<0xffff_ffff)
4 | }
5 | var c: CDontMangleMe
| `- warning: variable 'c' was written to, but never read
6 | c = CDontMangleMe()
7 |
/Users/mykhailotymchyshyn/Development/Embedded/ST/Swift_STM/test_alloc.swift:21: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
19 | }
20 |
21 | @_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
22 | public func free(_ ptr: UnsafeMutableRawPointer?) {
23 | BSP_LED_On(1)
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-gcc build/main.o build/gpio.o build/stm32f4xx_it.o build/stm32f4xx_hal_msp.o build/stm32f4xx_hal_rcc.o build/stm32f4xx_hal_rcc_ex.o build/stm32f4xx_hal_flash.o build/stm32f4xx_hal_flash_ex.o build/stm32f4xx_hal_flash_ramfunc.o build/stm32f4xx_hal_gpio.o build/stm32f4xx_hal_dma_ex.o build/stm32f4xx_hal_dma.o build/stm32f4xx_hal_pwr.o build/stm32f4xx_hal_pwr_ex.o build/stm32f4xx_hal_cortex.o build/stm32f4xx_hal.o build/stm32f4xx_hal_exti.o build/stm32f429i_discovery.o build/system_stm32f4xx.o build/sysmem.o build/syscalls.o build/startup_stm32f429xx.o build/game.o -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=soft -specs=nano.specs -TSTM32F429ZITx_FLASH.ld -lc -lm -lnosys -Wl,-Map=build/Swift_STM.map,--cref -Wl,--gc-sections -o build/Swift_STM.elf
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: /Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/lib/thumb/v7e-m/nofp/libc_nano.a(libc_a-getentropyr.o): in function `_getentropy_r':
getentropyr.c:(.text._getentropy_r+0xe): warning: _getentropy is not implemented and will always fail
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: warning: build/game.o uses 32-bit enums yet the output is to use variable-size enums; use of enum values across objects may fail
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: warning: /Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7e-m/nofp/crtn.o: missing .note.GNU-stack section implies executable stack
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: NOTE: This behaviour is deprecated and will be removed in a future version of the linker
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.2.1/../../../../arm-none-eabi/bin/ld: warning: build/Swift_STM.elf has a LOAD segment with RWX permissions
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-size build/Swift_STM.elf
text data bss dec hex filename
8580 128 3000 11708 2dbc build/Swift_STM.elf
/Applications/ArmGNUToolchain/13.2.Rel1/arm-none-eabi/bin/arm-none-eabi-objcopy -O binary -S build/Swift_STM.elf build/Swift_STM.bin
stm32-programmer-cli -c port=SWD -d build/Swift_STM.bin 0x08000000 -v -rst
My guess it that Swift somehow calls free from some library that project links with, but I'm not sure how to test or fix this.
Yep, that was my worry. If you can successfully compile and build the app
- that has this in the Bridging header:
void *malloc(size_t);
void *calloc(size_t, size_t);
void *realloc(void*, size_t);
void free(void*);
- calls those functions (from Swift or C):
malloc(1)
calloc(1, 1)
realloc(nil, 1)
free(nil)
- and do not have to provide any (or some!) of those functions (in Swift or C)
that's a worrying sign, as something else will be used that you are not overriding.
Equally worrying, if you do override those in Swift / or C, but they are still not called (this happens to me under macOS).
Obviously -no-allocations
/ -no-locks
should not be used when you want to use reference types.
What is the result of this app (if it compiles and links)?
Bridging header:
void *malloc(size_t);
Swift:
let x = malloc(1);
if x == nil {
// blink once
} else if x == UnsafeMutableRawPointer(bitPattern: -1)! {
// blink twice
} else {
// blink three times
}
If it is three blinks – well, you already have a good working malloc/free implementation, and you just need to implement the missing posix_memalign
in terms of that existing malloc
.
It's three blinks.
So here's a posix_memalign implementation I've added.
Support.c
#include <stddef.h>
int posix_memalign(void **res, size_t align, size_t len)
{
if (align < sizeof(void *)) return my_fatal_error(21);
void *mem = aligned_alloc(align, len);
if (!mem) return my_fatal_error(22);
*res = mem;
return 0;
}
void my_fatal_error(int code) {
if (code == 21) {
BSP_LED_On(0);
} else if (code == 22) {
BSP_LED_On(1);
} else {
BSP_LED_On(0);
BSP_LED_On(1);
}
while (1) {};
}
And It's error 22 (or Red LED) when I try to allocate 20000 instances of class CDontMangleMe
. When I do just 1000 instances everything is fine, executions reaches infinite loop at the end of swift function.
Edit: I've removed test_alloc.swift and changed it for Support.c
Alright, you are getting somewhere.
You can check how much memory that malloc could give you by some:
let n = 100 // iteratively increase to see when it starts fatalError-ring.
for _ in 0 ..< n {
let p = malloc(1000);
if p == nil { fatalError() }
}
BTW, it's non-trivial to make a proper Scratch that – I missed posix_memalign
call... the result of it is used with other calls like free
or realloc
– so it must be the block itself that's returned with malloc
/calloc
/realloc
– you can't adjust the pointer. You could detect that the alignment is wrong and .... return nil
if that's the case, but that's about it, or try to call malloc
in a loop until alignment requirements are satisfied, ouch. Nasty.aligned_alloc
– that's quite handy to have.
But... you are back to square one? As you are reassigning the variable:
for _ in 0..<20_000 {
c = CDontMangleMe()
}
that means free
is still not getting called?!
malloc
can give me about 93*1024 bytes of memory. Asking for 94 is too much. That is almost much as whole RAM section is in .ld script
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
I've also tried increasing memory area of RAM to full capacity of 192K. And malloc
can give me 189*1024 in that case.
The limit is fine (a bit small, but ok).
Do I understand it right that this works for you:
for _ in 0 ..< 1000_000 {
guard let p = malloc(1000) else { myFatalError() }
free(p)
}
but not this?
var c: C?
for _ in 0..< 1000_000 {
c = CDontMangleMe()
}
This works
let n = 2000 // It's way more than I have
let minusOne = UnsafeMutableRawPointer(bitPattern: -1)!
for _ in 0..<n {
let p = malloc(1024)
if p == nil || p == minusOne { swift_fatal_Error() }
free(p)
}
and this works
class CDontMangleMe {
let x = UInt32.random(in: 0x00..<0xffff_ffff)
}
var c: CDontMangleMe
c = CDontMangleMe()
for _ in 0..<1_000 {
c = CDontMangleMe()
}
but this does not
for _ in 0..<20_000 {
c = CDontMangleMe()
}
Execution ends up here:
Swift_STM.elf [cores: 0]
Thread #1 [main] 1 [core: 0] (Suspended : Signal : SIGINT:Interrupt)
my_fatal_error() at Support.c:21 0x8000692
posix_memalign() at Support.c:7 0x80006c8
$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF() at 0x80002d4
$s4game16startSwiftEngineyyF() at 0x80001de
main() at main.c:93 0x8000792
On the side note, my board has an external 8M chip of SDRAM, I just wanted to keep everything in on-chip ram to avoid any surprises.
Yes, the crash on the allocation size is not surprising. The issue is that free is not called on the dealloc side of things (and thus the memory is getting exhausted even when it shouldn't)... I have no idea why. Anyone?
(You've confirmed above that free itself works with malloc+free test... it's just not getting called for some reason by Swift runtime).
BTW, this does look like a reference count check for immortality – I wonder if reference counting machinery is working alright on your platform. e.g. the newly created instance of C should have at least 1 reported by CFGetRetainCount()
(if you have that, or just read the value from the second word of the object memory).