← board

Decide: how does the compiler learn WHICH ESP chip, and what does it derive?

The problem: the chip is currently IMPLIED by the ISA

There is no chip axis. --target=riscv32 means "ESP32-C3" by convention, and the convention is written down in three unrelated places:

That works while there is one chip per ISA. It breaks the moment a second riscv32 part appears, and it breaks in three independent ways at once: ISA extensions, core count, and memory map.

Today's axes

axis flag values
ISA / backend --target= riscv32, xtensa, x86_64, …
platform --platform= esp | posix (derived from target, overridable)
profile --esp-profile=bare bare metal vs IDF
xtensa ABI --xtensa-abi= windowed | call0

What IDF calls things (borrow, do not invent)

idf.py set-target <x> and CONFIG_IDF_TARGET_<X> use one lowercase token, no separator: esp32, esp32s2, esp32s3, esp32c2, esp32c3, esp32c6, esp32h2, esp32p4. Every ESP developer already types these, and an IDF wrapper can pass $IDF_TARGET through untranslated.

Borrowing verbatim also survives Espressif renaming their own scheme — an invented short form (--esp=c3) assumes the esp32 prefix is a constant, and esp32p4 already shows the prefix is a brand rather than a family descriptor. A future 64-bit part need not be esp32* at all.

The fork: WHERE does the chip live?

(A) A separate axis — --esp-chip=esp32c3, alongside platform/profile, defaulting per ISA to today's assumption.

(B) SoC targets in the existing namespace — --target=esp32c6 (recommended).

The half that matters more than the name: a CAPABILITY TABLE

Whichever spelling wins, codegen must consult capabilities, not chip names. One table, and every decision site asks it:

chip ISA cores atomic primitive
esp32c3 RV32IMC 1 interrupt mask (no A)
esp32c2 RV32IMC 1 interrupt mask (no A)
esp32c6 / esp32h2 RV32IMAC 1 AMO / LR-SC
esp32p4 RV32IMAFC 2 AMO / LR-SC
esp32s2 Xtensa LX7 1 S32C1I
esp32 / esp32s3 Xtensa LX6/LX7 2 S32C1I

The alternative is if chip = esp32c3 … else if esp32c6 … spreading across the backends, the memory map and the peripheral bases — N mechanisms for one concept, and far harder to unpick later than to set up now (devdocs/dev/normalise-dont-special-case.md).

Capabilities the table owes its callers on day one: SocCoreCount, SocHasAtomicISA, SocIramBase / SocStackTop (already hardcoded per ISA today), SocUartBase (already assumed identical "on both").

What it immediately unblocks

Atomics stops being a special case and falls out of the table:

Note the single-core row is not "atomics unnecessary": FreeRTOS preempts tasks on one core, so a bare n := n + 1 still tears. Single-core means the CHEAP primitive suffices, not that none is needed.

Recommendation

(B), borrowing IDF's spelling verbatim, plus the capability table — with the generic riscv32 / xtensa targets kept as today's defaults so nothing existing moves. The table is the part worth insisting on; the flag spelling is reversible, a chip-name conditional sprayed through five files is not.

Gate (for whoever implements the decision)

--target=esp32c3 and --target=riscv32 producing byte-identical output; likewise esp32s3 vs xtensa; the capability table consulted (not chip names) at every site that currently hardcodes a C3/S3 fact; self-host byte-identical.

DECIDED 2026-08-11 (user): option (B) — IDF spelling, SoC targets, capability table

"good. let's file that. … maybe IDF has a naming scheme that we can borrow here. but then it's by like --target=esp32c6 etc." … "yes, so this is decided right" — user

So, confirmed:

  1. Chip names are IDF's, verbatimesp32, esp32s2, esp32s3, esp32c2, esp32c3, esp32c6, esp32h2, esp32p4. We do not invent a short form; idf.py set-target and $IDF_TARGET pass through untranslated, and nothing assumes the esp32 prefix is permanent.
  2. They live in the --target= namespace, not a second --esp-chip= axis. A SoC target implies arch + platform=esp + its capability row, so an ISA/chip contradiction cannot be expressed.
  3. --target=riscv32 and --target=xtensa stay, meaning exactly what they mean today (C3 caps and S3 caps respectively) — riscv32 is dual-role with hosted linux under qemu-user, which the cross-test infrastructure depends on. No existing command line changes behaviour.
  4. Codegen consults CAPABILITIES, never chip names. This is the load-bearing half: the flag spelling is reversible, a chip-name conditional spread across the backends, the memory map and the peripheral bases is not.

Implementation order

The capability table first, as the single home for facts that are currently hardcoded per ISA (ESP_BARE_IRAM_BASE / _XT, the UART base, the implied core count), then the SoC targets that select a row, then the consumers.

bug-a-riscv32-and-xtensa-have-no-atomic-codegen is the first consumer and is partly independent: the xtensa half needs no chip gate (S32C1I is on every LX6/LX7 part) and can land before or after this. The riscv32 half is the one that needs the table, because the answer differs within the ISA.

Implemented 2026-08-11 — the table and the targets (first slice)

Landed, in the decided order:

Equivalence is asserted in the Makefile, not just measured once: the same source built as riscv32 and as esp32c3 must cmp equal, likewise xtensa/esp32s3 under --esp-profile=bare. If those ever diverge, the table has started disagreeing with the constants it replaced.

One measurement correction worth recording: a first pass "verified" the xtensa equivalence with a loop that left STALE outputs in place when a compile failed, so cmp compared the previous iteration's riscv files and reported success. Hosted xtensa does not compile this program at all — pre-existing, "external (dynamic) symbols not yet supported", and it fails identically for both spellings. The real check requires both outputs to exist.

Still to do

SocCoreCount and SocHasAtomicISA have no consumer yet — they exist for [[bug-a-riscv32-and-xtensa-have-no-atomic-codegen]], which is the next slice and whose xtensa half needs no chip gate. Facts still hardcoded per ISA elsewhere (the shared ESP_BARE_STACK_TOP, the UART base named only in a comment) should migrate to the table as they acquire a second value.