Skip to content

Xtensa: "Cannot select: XtensaISD::PCREL_WRAPPER TargetConstantPool" for f32 array constants #282

Description

@Yona-Appletree

Summary

The Xtensa backend fails instruction selection when LLVM materialises an
[N x float] constant-pool entry. It can materialise the address of ordinary
data, but there appears to be no selection rule for XtensaISD::PCREL_WRAPPER
over a TargetConstantPool node.

Error

rustc-LLVM ERROR: Cannot select: 0x10bac2770: i32 = XtensaISD::PCREL_WRAPPER
  TargetConstantPool:i32<@.LCP162_3 = internal constant [2 x float]
  [float 0.000000e+00, float -1.000000e+00]> 0

Environment

Toolchain channel = "esp" (espup)
rustc 1.95.0-nightly (95e5bda86 2026-04-15) (1.95.0.0)
commit-hash 95e5bda868c960c607597bc03ed9e8f0ad26226d
LLVM 21.1.3
Target xtensa-esp32s3-none-elf
Host aarch64-apple-darwin

Reproduction

Triggered by a branch-selected [f32; 4] constant — the standard
branch-minimising RGB→HSV formulation, where the two arms differ only in
constant lanes:

fn rgb2hsv(r: f32, g: f32, b: f32) -> [f32; 3] {
    let p: [f32; 4] = if g < b {
        [b, g, -1.0, 2.0 / 3.0]
    } else {
        [g, b, 0.0, -1.0 / 3.0]   // <- lanes 2..3 are the reported [0.0, -1.0]
    };
    // ... q/chroma arithmetic follows
}

LLVM splits the select into a lane pair carried through a constant pool. The
reported entry [2 x float] [0.0, -1.0] is that pair. Note the error names
the containing function, not the constant's source line — worth mentioning
because it initially pointed our own investigation at an unrelated gradient
table that happened to contain the same two values.

Not an optimisation artefact. It fails at every optimisation level. At
-C opt-level=s -C lto=fat it surfaces when linking the final binary; at
-C opt-level=1 -C lto=off it surfaces earlier, while compiling the library
crate itself.

Note on minimisation: extracting the LUT above into a standalone no_std
crate does not reproduce — that compiles cleanly at opt-level=1 and at
opt-level=s/lto=fat, with the array escaping through an out-pointer and
with the table written as a const [[f32; 2]; 8]. So the constant pool only
forms with more surrounding optimisation context than a small crate provides.
I can produce a reduced .ll from the failing build on request — say the word
and I will attach one.

rv32 is unaffected. The identical source builds cleanly for
riscv32imac-unknown-none-elf. This is specific to the Xtensa backend.

Impact

Blocks compiling a native-f32 math/builtin library for ESP32-S3. In our case
exactly one site in ~90 float builtins triggered it — but it is invisible
until it fires, and the error points at the containing function rather than
the constant, so locating it is guesswork.

Workaround

Materialising the constants as u32 bit patterns (const-evaluated with
f32::to_bits, selected, then f32::from_bits) so no float constant pool
forms. Bit-identical and verified as such. Rewriting the values arithmetically
also avoids the pool but is not equivalent — it introduces signed-zero hazards
where the literal form has +0.0.

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