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.
Summary
The Xtensa backend fails instruction selection when LLVM materialises an
[N x float]constant-pool entry. It can materialise the address of ordinarydata, but there appears to be no selection rule for
XtensaISD::PCREL_WRAPPERover a
TargetConstantPoolnode.Error
Environment
channel = "esp"(espup)1.95.0-nightly (95e5bda86 2026-04-15) (1.95.0.0)95e5bda868c960c607597bc03ed9e8f0ad26226dxtensa-esp32s3-none-elfReproduction
Triggered by a branch-selected
[f32; 4]constant — the standardbranch-minimising RGB→HSV formulation, where the two arms differ only in
constant lanes:
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 namesthe 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=fatit surfaces when linking the final binary; at-C opt-level=1 -C lto=offit surfaces earlier, while compiling the librarycrate itself.
Note on minimisation: extracting the LUT above into a standalone
no_stdcrate does not reproduce — that compiles cleanly at
opt-level=1and atopt-level=s/lto=fat, with the array escaping through an out-pointer andwith the table written as a
const [[f32; 2]; 8]. So the constant pool onlyforms with more surrounding optimisation context than a small crate provides.
I can produce a reduced
.llfrom the failing build on request — say the wordand 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
u32bit patterns (const-evaluated withf32::to_bits, selected, thenf32::from_bits) so no float constant poolforms. 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.