Skip to content

ESP32-C3: Implement software atomic operations for missing A extension #1479

@luoliwoshang

Description

@luoliwoshang

Problem

ESP32-C3 doesn't support RISC-V A extension (atomic operations). When code uses atomic operations, linking fails with undefined symbol errors:

llgo run -a -target esp32c3 .

ld.lld: error: undefined symbol: __atomic_store_4
>>> referenced by write
>>>               .../write.o:(github.com/goplus/llgo/_demo/embed/esp32c3/write.main)

Root Cause

  1. ESP32-C3 only supports RV32IMC instruction set, no A extension (atomic operations)
  2. PR fix(esp32c3): correct -march to rv32imc to prevent illegal instruction crashes #1443 fixed -march=rv32imac-march=rv32imc and excluded atomic.c compilation
  3. However, when Go code uses atomic operations (e.g., sync/atomic package or compiler-generated atomic instructions), it generates calls to __atomic_* functions
  4. Without hardware support or software implementation, linker cannot find these symbols

Proposed Solution

Reference TinyGo's approach (tinygo#2146) - implement software atomic operations by disabling interrupts:

//export __atomic_load_4
func __atomic_load_4(ptr *uint32, ordering int32) uint32 {
    mask := riscv.DisableInterrupts()
    value := *ptr
    riscv.EnableInterrupts(mask)
    return value
}

//export __atomic_store_4
func __atomic_store_4(ptr *uint32, value uint32, ordering int32) {
    mask := riscv.DisableInterrupts()
    *ptr = value
    riscv.EnableInterrupts(mask)
}

Rationale: On single-core systems (like ESP32-C3), disabling interrupts guarantees atomicity since no other code can interrupt current execution.

Functions to Implement

Common atomic operation functions:

  • __atomic_load_1/2/4/8 - Atomic load
  • __atomic_store_1/2/4/8 - Atomic store
  • __atomic_exchange_1/2/4/8 - Atomic exchange
  • __atomic_compare_exchange_1/2/4/8 - Atomic compare-and-swap (CAS)
  • __atomic_fetch_add_1/2/4/8 - Atomic add
  • __atomic_fetch_sub_1/2/4/8 - Atomic subtract
  • __atomic_fetch_and/or/xor_1/2/4/8 - Atomic bitwise operations

Simplified Approach

For llgo, we can start with the most commonly used functions (like __atomic_store_4, __atomic_load_4) using simple assignment:

// targets/device/esp/esp32c3_atomic.c

void __atomic_store_4(uint32_t *ptr, uint32_t value, int ordering) {
    *ptr = value;  // Simple assignment (safe on single-core)
}

uint32_t __atomic_load_4(uint32_t *ptr, int ordering) {
    return *ptr;
}

Add interrupt disabling logic later if stricter atomicity guarantees are needed.

Related Links

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions