← board

ESP float wiring (xtensa + riscv32 float value model)

Problem

xtensa + riscv32 have no float value model at all — even a:=1.5; b:=a+2.0 errors unsupported node in IR codegen. This is why float function returns couldn't be enabled there in [[feature-cross-float-returns]] (Linux targets done).

RESOLVED MODEL — Real = the target's native float depth

PXX defines Real as the widest float the target does natively (a clean generalization of FPC's with/without-coprocessor rule). Well-defined per target, documented:

This means the float arithmetic dispatch is:

target native (Real=) Single math Double math
x86-64/i386/aarch64/arm32 Double widen→Double (storage-only, current) HW
xtensa (ESP32 / S3) Single HW single FPU soft-double
riscv32 (ESP32-C3) Single soft-single soft-double

Notes:

UNBLOCKED 2026-06-21 — [[feature-esp-int64-arith]] is now DONE (both ESP backends)

Runtime 64-bit integer arithmetic now works on BOTH ESP backends (riscv32 2026-06-20, xtensa 2026-06-21), so the soft-float kernels run correctly: the full softfloat library is byte-identical to the x86-64 oracle on esp32c3 + esp32s3 (make test-esp-softfloat). The float value model + IR lowering (the wiring below) is the remaining work — resume from step 0.

Historical (the block, for context): the riscv32 + xtensa backends did runtime integer arithmetic in 32 bits only (the IR_BINOP handler used single registers, no lo:hi pair), so a 64-bit add/sub/mul/and silently truncated. softfloat is almost all 64-bit math (double shl 52, 53x53 mul; single 48-bit products + Int64 guard math), so it either truncated or hit a tyString misclassification error on ESP. That prerequisite is now cleared.

Also fixed en route (separate commit, cb46fbd): COM/ARC interface ARC helpers in builtinheap were compiled unconditionally and broke ALL ESP compiles (indirect IMT call) — silently regressing ESP bare-boot. Now {$ifndef PXX_ESP}-guarded.

The gating harness is ready: test/test_esp_softfloat_probe.pas calls the kernels (integer-exact results) and is run via tools/esp_run_bare.sh --chip esp32c3 against its x86-64 oracle. It compiles + passes on x86-64 today; it will compile for riscv32/xtensa once 64-bit int arithmetic exists.

PROGRESS 2026-06-21 — value model DONE on both ESP backends (pinned v31)

The float value model + IR lowering is implemented and validated on riscv32 AND xtensa (commits 00ab934 riscv, bc70ee2 xtensa, 323b598 params/returns, a56f773 Trunc+negate; pinned v31). Done:

xtensa HW single FPU — DONE 2026-06-21 (4a3c680), opt-in --xtensa-fpu

Single +,-,* lower to add.s/sub.s/mul.s (wfr/rfr core<->FP moves) on ESP32/-S3; single /, comparisons, conversions stay soft (no HW single divide / FP-compare-to-AR wired). Opt-in because FPU presence isn't inferable from the cpu flag — ESP32-S2 (LX7) has NO FPU, so soft is the safe default for every part. CPENABLE enabled at entry under the flag. FP encoders verified vs xtensa-esp32s3-elf-as; probe with --xtensa-fpu matches the x86-64 oracle on esp32s3 QEMU (add.s/mul.s confirmed in image). Default path + self-host byte-identical. NOTE: --xtensa-fpu sets CPENABLE itself; on ESP-IDF the OS also manages it per-task — fine (idempotent). The QEMU FPU-equality probe needs ESP_PXXFLAGS=--xtensa-fpu.

Round / Frac / Int — DONE 2026-06-21 (d1e0c14)

Soft kernels added: __pxx_d2i_rne/s2i_rne (round-half-to-even, matches the SSE cvtsd2si oracle), __pxx_dint/sint (trunc toward zero, result stays float), __pxx_dfrac/sfrac. Wired on both ESP backends (Round -204 like Trunc but rne; Frac/Int -205/-206 promote arg to double then dfrac/dint). The last ESP float error path is gone. LANDMINE recorded: bitwise not on Int64/LongWord miscompiles on ESP (IR_NOT is boolean) — kernels clear low bits via shr/shl.

{$FASTDOUBLES ON} — DONE 2026-06-21 (7cac337)

Opt-in directive (default OFF). On xtensa with --xtensa-fpu, Double +,-,* compute through the HW single FPU (d2s -> add.s/sub.s/mul.s -> s2d) instead of the soft-double kernels — lossy single precision, traded for speed, no source edits. No-op on riscv (both soft) and double-native targets. test_esp_fastdoubles (integer-valued doubles, exact in single) matches the x86-64 oracle on esp32s3 both with --xtensa-fpu and on the soft fallback; disasm confirms add.s/mul.s in the fast build, none in the soft build.

REMAINING (chore only)

This ticket is COMPLETE (Real native-depth model + per-target dispatch matrix + full value model / arith / conversions / params / returns / Trunc / Round / Frac / Int / negate + xtensa HW single FPU + {$FASTDOUBLES}), validated vs the x86-64 oracle on esp32c3 + esp32s3. Pinned v32. Only the make-test wiring chore (B-blocked) remains.

Dependencies

Approach (per target, after the lib lands)

  1. Make the Real resolver target-aware (parser.inc ~6513/6552): Double on double-HW targets, Single on xtensa/riscv. Small, do first.
  2. Value model: Single = 4 bytes in a core reg (riscv) or HW single reg (xtensa); Double = raw bits in a core-register pair (riscv a0:a1, xtensa a-pair), mirroring arm32's d0->r0:r1 spill.
  3. Lower the float IR nodes (ir_codegen_riscv32.inc / ir_codegen_xtensa.inc): literal, IR_LOAD_SYM / STORE (slot moves at the type's width), binops, intrinsics, conversions, then params + returns. Dispatch per the matrix:
    • riscv32: single ops -> soft-single helpers; double ops -> soft-double helpers.
    • xtensa: single ops -> hardware single FPU insns; double ops -> soft-double helpers; a single<->double convert is a soft repack (no HW double).
  4. Relax the EmitProcEpilog float guards last (symtab.inc ~3713 xtensa / ~3753 riscv32) — only once arithmetic + params are correct.
  5. Add float tests to the ESP/cross suites; QEMU output-equality vs the x86-64 oracle (ESP self-host is not a goal — device RAM too small).

Follow-on option: {$FASTDOUBLES ON} (xtensa speed/precision knob)

A compiler switch (default OFF) that, on targets where Double is soft but Single is hardware (xtensa today; any future single-FPU-no-double part), computes Double arithmetic by round-tripping through the hardware single FPU (double->single, do the op in HW, single->double) instead of calling the soft-double kernels. Lets the user trade precision for speed without editing source that happens to use Double.

Notes