[Question] "undefined reference to `__atomic_load_4`" errors in RPi Pico Swift project

I'm trying to convert my RPi Pico cpp project to Swift, though keeping CMake since I need SDK for this.
I almost got it compiled but there're some __atomic* linker errors:

/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o: in function `$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF':
_swiftcode.o:(.text.$ss17swift_allocObject8metadata12requiredSize0E13AlignmentMaskSpys04HeapC0VGSpys13ClassMetadataVG_S2itF+0x4e): undefined reference to `posix_memalign'
/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o: in function `$ss12swift_retain6objectBpBp_tF':
_swiftcode.o:(.text.$ss12swift_retain6objectBpBp_tF+0x16): undefined reference to `__atomic_load_4'
/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o:(.text.$ss12swift_retain6objectBpBp_tF+0x28): undefined reference to `__atomic_fetch_add_4'
/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o: in function `$ss13swift_release6objectyBp_tF':
_swiftcode.o:(.text.$ss13swift_release6objectyBp_tF+0x16): undefined reference to `__atomic_load_4'
/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o:(.text.$ss13swift_release6objectyBp_tF+0x28): undefined reference to `__atomic_fetch_sub_4'
/Applications/ArmGNUToolchain/13.3.rel1/arm-none-eabi/bin/../lib/gcc/arm-none-eabi/13.3.1/../../../../arm-none-eabi/bin/ld: _swiftcode.o: in function `$ss41swift_isUniquelyReferenced_nonNull_native6objectSbBp_tF':
_swiftcode.o:(.text.$ss41swift_isUniquelyReferenced_nonNull_native6objectSbBp_tF+0x8): undefined reference to `__atomic_load_4'
......

This is what my Swift code looks like:

let BUTTON_PINS: [UInt32] = [0, 1, 2, 3, 4, 5, 6, 7]
let LED_PINS: [UInt32: UInt32] = [0: 8, 1: 9, 2: 10, 3: 11, 4: 12, 5: 13, 6: 14, 7: 15]

let DEFAULT_LED_PIN: UInt32 = 25

let DEBOUNCE_DELAY_MS: UInt32 = 5

let INTERVAL_MS: UInt32 = 1

let KEYS: [UInt32: UInt8] = [0: 0x73, 1: 0x64, 2: 0x66, 3: 0x6c, 4: 0x3b, 5: 0x63, 6: 0x62, 7: 0xa]

var start_ms: UInt32 = 0
var remote_wakeup_enabled: Bool = false
var last_button_state: [UInt32: Bool] = [
    0: false, 1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 7: false,
]
var last_button_press_ms: [UInt32: UInt32] = [0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0]

@main
struct Main {
    static func main() {
        board_init()
        tusb_init()

        for pin in BUTTON_PINS {
            gpio_init(pin)
            gpio_set_dir(pin, false)
            gpio_pull_up(pin)
        }

        for pin in LED_PINS.values {
            gpio_init(pin)
            gpio_set_dir(pin, true)
        }

        while true {
            tud_task()
            hid_task()
        }
    }
}

@_cdecl("tud_mount_cb")
func tud_mount_cb() {}

@_cdecl("tud_umount_cb")
func tud_umount_cb() {}

@_cdecl("tud_suspend_cb")
func tud_suspend_cb(remote_wakeup_en: Bool) {
    remote_wakeup_enabled = remote_wakeup_en
}

@_cdecl("tud_resume_cb")
func tud_resume_cb() {}

func sendKeys() {
    guard tud_hid_ready() else { return }

    var report = [UInt8](repeating: 0, count: 8)
    var last_idx = 0

    for (pin, state) in last_button_state {
        if state {  // button is pressed
            let key = KEYS[pin]!
            report[last_idx] = key
            last_idx += 1
        }
        gpio_put(LED_PINS[pin]!, state)
    }
    tud_hid_report(1 /*REPORT_ID_KEYBOARD*/, report, 8)
}

func hid_task() {
    guard to_ms_since_boot(get_absolute_time()) - start_ms > INTERVAL_MS else { return }

    start_ms += INTERVAL_MS
    for pin in BUTTON_PINS {
        guard to_ms_since_boot(get_absolute_time()) - last_button_press_ms[pin]! > DEBOUNCE_DELAY_MS
        else { continue }
        let state = !gpio_get(pin)
        if state != last_button_state[pin] {
            last_button_state[pin] = state
            last_button_press_ms[pin] = to_ms_since_boot(get_absolute_time())
        }
    }

    sendKeys()
}

@_cdecl("tud_hid_report_complete_cb")
func tud_hid_report_complete_cb(instance: UInt8, report: UnsafePointer<UInt8>, len: UInt16) {}

@_cdecl("tud_hid_get_report_cb")
func tud_hid_get_report_cb(
    instance: UInt8, report_id: UInt8, report_type: UnsafePointer<hid_report_type_t>,
    buffer: UnsafeMutablePointer<UInt8>, len: UInt16
) -> UInt16 {
    // nothing to do
    return 0
}

@_cdecl("tud_hid_set_report_cb")
func tud_hid_set_report_cb(
    instance: UInt8, report_id: UInt8, report_type: UnsafePointer<hid_report_type_t>,
    buffer: UnsafePointer<UInt8>, len: UInt16
) {}

And this is my CMakeLists.txt:

cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)

project(game-controller_project C CXX ASM)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
pico_sdk_init()

if(APPLE)
execute_process(COMMAND xcrun -f swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)
else()
execute_process(COMMAND which swiftc OUTPUT_VARIABLE SWIFTC OUTPUT_STRIP_TRAILING_WHITESPACE)
endif()

add_executable(game-controller
    ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c
)

add_custom_command(
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o
    COMMAND
        ${SWIFTC}
        -target armv6m-none-none-eabi -Xcc -mfloat-abi=soft -Xcc -fshort-enums
        -Xfrontend -function-sections -enable-experimental-feature Embedded -wmo -parse-as-library
        -Xcc -DCFG_TUSB_MCU=OPT_MCU_RP2040
        $$\( echo '$<TARGET_PROPERTY:game-controller,INCLUDE_DIRECTORIES>' | tr '\;' '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \)
        $$\( echo '${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES}'             | tr ' '  '\\n' | sed -e 's/\\\(.*\\\)/-Xcc -I\\1/g' \)
        -import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
        ${CMAKE_CURRENT_LIST_DIR}/Main.swift
        -c -o ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o
    DEPENDS
        ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
        ${CMAKE_CURRENT_LIST_DIR}/Main.swift
)

add_custom_target(game-controller-swiftcode DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o)

target_include_directories(game-controller PUBLIC ${CMAKE_CURRENT_LIST_DIR})

target_link_libraries(game-controller
    pico_stdlib
    pico_unique_id
    hardware_gpio
    tinyusb_device
    tinyusb_board
    ${CMAKE_CURRENT_BINARY_DIR}/_swiftcode.o
)

add_dependencies(game-controller game-controller-swiftcode)
pico_add_extra_outputs(game-controller)

Seems like the __atomic_load_4 error is because of 32bit integer but I see the example using UInt32 and it compiles without any issues...
Is there anything I can do to fix these error?

1 Like

cc @rauhul @Philippe_Hausler @kubamracek this architecture doesn't support atomic instructions, so any usage of classes (in this case Array and Dictionary) will call out to a compiler-rt builtin. Do we have good solutions around this? Do we need to perform atomic accesses for retain/release if the processor doesn't support it?

I ended up creating a silly little implementation of basic atomic instructions and posix memalign for my project (GitHub Gist)

If you add this to your cmake project it should work

2 Likes

Thanks for sharing your workaround! While it does compile this time, now it crashes(sigtrap) when creating dictionary/array...

Fixed it by using UInt16 for most of the things!(still need the gist though)

I suspect it’s the posix_memalign implementation that’s erroring out

Edit: Another issue can be stack overflow. I remember having to allocate more memory to the stack, and enabling stack guards to make my program not crash