← board

Xtensa text-assembler (EmitAsmXtensa) for ESP32

Motivation

ESP32 codegen (ir_codegen_xtensa.inc) emits Xtensa through hand calls to the xtensa_* encoders, and the ISA's sharp edges — L32R's always-negative literal offsets, CALL0 align4(PC)+4, J's imm18 at bits [23:6], RET = 80 00 00 — make that error-prone and a session-eater to debug (see [[project_esp32_stage1]]).

Same idea as the x86-64 emitter ([[project_array_of_const_and_asm_emitter]], feature-asm-text-emitter): write emit blocks as assembly text parsed and encoded by a per-target text-assembler, with runtime % holes bound inline. Readable, fewer bugs, and it doubles as the backend for eventual Xtensa inline asm … end. Xtensa is the primary target here — it makes ESP32 work cleaner. This is mostly new code on top of xtensaenc.inc, not a rewrite; ESP32 progress is chaotic, so land it incrementally.

Precedent to copy

compiler/asmtext.inc (EmitAsmX64) is the template: interleaved array of const of instruction strings + %-hole ints, one instruction per string, encoded through the typed x64_* layer. EmitAsmXtensa is the same front-end over the typed xtensa_* layer (compiler/xtensaenc.inc). Reuse the shared helpers' shape (AsmTextCharAt, AsmTextSlice, AsmTextParseInt, hole-binding loop) — ideally factor the target-agnostic bits so both emitters share them rather than copy.

Operand model (simpler than x86 — no ModRM/brackets)

Xtensa is mostly 24-bit (some 16-bit narrow) with a flat register file a0..a15 (sp = a1). Instructions are mnem dst, src, … comma-separated; loads/stores take a base register + immediate offset, not bracketed memory:

l32i  a3, a2, 8        ; a3 := [a2 + 8]
addi  a4, a4, %        ; immediate hole
beq   a3, a5, .loop    ; branch to label
j     .done

Scope (incremental)

  1. Cover the instructions ir_codegen_xtensa.inc already uses first: add sub and or xor mull mov movi addi, l32i l16ui l16si l8ui s32i s16i s8i, nop ret, branches beq bne blt bge + j. Grow on demand.
  2. Labels + branch/J relative-offset resolution (back + forward), honouring the J imm18-at-bits[23:6] and branch range/encoding rules.
  3. Emit through the existing xtensa_* encoders + byte sink — no new relocation machinery.
  4. Convert one real ir_codegen_xtensa.inc block to EmitAsmXtensa (a fixed/branchy one), leave heavily-dynamic blocks on the typed encoders. Mix freely, like the x86 emitter does.
  5. Defer (call out clearly): L32R literal-pool sugar (the jump-over-island + always-negative l32r rd, $FFFF scheme — the gnarliest piece), 16-bit narrow encodings, windowed-ABI entry/call8 sugar, --target=esp32 IDF specifics.

Landmines (PXX self-host — the emitter runs in the compiler)

Acceptance

Log