← board

ESP32 bare-metal boot profile (no IDF)

Motivation

The bare-metal face of the ESP32 milestone: own startup, image layout, UART and allocator with no ESP-IDF dependency. Stage-1 codegen (done/feature-target-esp32) proved the instruction emitters under qemu-user with Linux-style ELF; booting a real -M esp32/-M esp32c3 machine (or a chip from flash) needs the actual SoC contract. Lower priority than the IDF profile — useful for compiler control, tiny images, and education.

Scope

Acceptance

Progress log

2026-06-18 — esp32c3/riscv32 bare-boot DONE

--esp-profile=bare (riscv32) ships. Acceptance for the C3 met:

Settled load path (the key open question): Espressif qemu accepts a raw ET_EXEC ELF via -kernel — it honors the program-header load address and sets pc to the ELF entry. No flash image / esptool merge-bin / second-stage header needed. The C3 SRAM is mapped twice (IRAM/DRAM) but qemu models it as one RWX region, so a single PT_LOAD at ESP_BARE_IRAM_BASE = 0x40380000 holds code+data+bss+stack. Startup stub sets sp = ESP_BARE_STACK_TOP = 0x403C0000. UART0 TX FIFO MMIO at 0x60000000. Static-arena heap + managed AnsiString work unchanged on bare metal. ESP-gated; make test + make cross-bootstrap stay byte-identical. Full write-up in devdocs/developer/esp32-support.md (§ Bare-metal boot).

Implementation: EspBareBoot flag (compiler.pas) → PXX_ESP_BARE define (lexer.inc); ESP base in writeELF32 (elfwriter.inc); sp-init in the riscv32 entry stub (parser.inc). .map/symtab emission for the bare path is not done (gdb works via stepi + manual fp-walk; the IDF profile already gets maps).

2026-06-18 — esp32s3/xtensa bare-boot DONE (same session)

Same image shape, Call0 ABI only. --esp-profile=bare --target=xtensa boots under qemu-system-xtensa -M esp32s3 -kernel, UART output byte-identical to the oracle (make test-esp-bare runs both chips). Image base ESP_BARE_IRAM_BASE_XT = 0x40378000 (S3 shared I/D SRAM org); same stack top 0x403C0000 and UART 0x60000000. The entry stub loads sp = a1 from an l32r literal island before main. Windowed ABI is rejected on bare (no window-exception handlers / vecbase) — Call0 sidesteps register windows entirely, so managed strings + heap + recursion all run with no vecbase/PS setup. -kernel honors the ELF load address here too (the xtensa qemu warns about a default -bios but loads our kernel). Forward-decl of EmitLoadConstXtensa added in symtab.inc so the parser entry stub can call it.

The whole bare-boot ticket is now complete for the user's two boards (esp32c3 + esp32s3). .map/symtab emission for the bare path remains the one deferred scope item (not needed for the gdb proof; the IDF profile gets maps).

Notes