[llvm] [Xtensa] Don't convert a select of FP constants into an indexed const… (PR #214974)
Omid Safarzadeh via llvm-commits
llvm-commits at lists.llvm.org
Sat Aug 8 08:11:56 PDT 2026
https://github.com/Bellman281 created https://github.com/llvm/llvm-project/pull/214974
`DAGCombiner::convertSelectOfFPConstantsToLoadOffset` rewrites `cond ? fc1 : fc2` into a load from a two-element constant pool array whose address is the pool base plus a condition-dependent offset.
Xtensa cannot select that. `XtensaTargetLowering::LowerConstantPool` returns a bare `XtensaISD::PCREL_WRAPPER`, and the only two ISel patterns consuming such a wrapper over a `tconstpool` require it to be the address operand of a load:
- `llvm/lib/Target/Xtensa/XtensaInstrInfo.td:239` — `[(set AR:$t, (load (Xtensa_pcrel_wrapper tconstpool:$label)))]`
- `llvm/lib/Target/Xtensa/XtensaInstrInfo.td:1313` — `def : Pat<(f32 (load (Xtensa_pcrel_wrapper tconstpool:$in))), (WFR (L32R tconstpool:$in))>;`
That is by design: `L32R` loads the *contents* of a PC-relative literal, and Xtensa has no instruction that materializes a literal's address. Once the combine inserts an `ISD::ADD` between the wrapper and the load, nothing matches and codegen aborts.
```llvm
; llc -mtriple=xtensa -mattr=+fp repro.ll
define float @select_fp_constants(float %x) nounwind {
%cmp = fcmp olt float %x, 0.000000e+00
%sel = select i1 %cmp, float -1.000000e+00, float 0.000000e+00
ret float %sel
}
```
```
LLVM ERROR: Cannot select: i32 = XtensaISD::PCREL_WRAPPER
TargetConstantPool:i32<[2 x float] [float 0.000000e+00, float -1.000000e+00]> 0
In function: select_fp_constants
```
Equivalently in C: `float f(float x) { return x < 0.0f ? -1.0f : 0.0f; }` at `-O2`.
Fixed by overriding `TargetLowering::reduceSelectOfFPConstantLoads`, the hook that exists for exactly this (X86 is the only other user today). Besides avoiding the crash, the transform would be a pessimization on Xtensa: the branch it replaces is cheaper than the extra address arithmetic and load.
Testing: both functions in the new test abort on unpatched `main` and produce correct code with the patch. `llvm/test/CodeGen/Xtensa` is 55/55 passing with it, unchanged from before. Verified against a from-source Release build of `main` at `5e30da9ace9b6667992673bb15c6dd0ed46b7b68`.
This is the narrow fix. The underlying hole is wider — `LowerConstantPool` returns a wrapper the backend can only select when a load consumes it directly, so any other path needing a constant pool *address* would fail the same way. The general fix would be to make `LowerConstantPool` mirror `LowerJumpTable`. That is a considerably larger change and this crash does not need it; happy to follow up if reviewers would prefer it.
Found while building a `no_std` Burn model for `xtensa-esp32s3-none-elf`; the crate-side workaround is tracel-ai/burn#5331.
>From 62f9709b25812629dc1b256ddf8c5dbd688f6f8e Mon Sep 17 00:00:00 2001
From: Omid Safarzadeh <omidpoly at gmail.com>
Date: Sat, 8 Aug 2026 18:10:11 +0300
Subject: [PATCH] [Xtensa] Don't convert a select of FP constants into an
indexed constant pool load
[Xtensa] Don't convert a select of FP constants into an indexed constant pool load
---
llvm/lib/Target/Xtensa/XtensaISelLowering.h | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/llvm/lib/Target/Xtensa/XtensaISelLowering.h b/llvm/lib/Target/Xtensa/XtensaISelLowering.h
index 0d455919cb407..0a7a603aae915 100644
--- a/llvm/lib/Target/Xtensa/XtensaISelLowering.h
+++ b/llvm/lib/Target/Xtensa/XtensaISelLowering.h
@@ -51,6 +51,16 @@ class XtensaTargetLowering : public TargetLowering {
bool isFPImmLegal(const APFloat &Imm, EVT VT,
bool ForCodeSize) const override;
+ /// Xtensa reads constant pool entries with L32R, which loads the *contents*
+ /// of a PC-relative literal. There is no instruction that materializes the
+ /// address of a literal, so a constant pool base plus a variable offset
+ /// cannot be selected. Keep DAGCombiner from turning a select of two FP
+ /// constants into such an indexed constant pool load. Doing so would also be
+ /// a pessimization here: the branch it replaces is cheaper than the extra
+ /// address arithmetic and load.
+ bool reduceSelectOfFPConstantLoads(EVT CmpOpVT) const override {
+ return false;
+ }
std::pair<unsigned, const TargetRegisterClass *>
getRegForInlineAsmConstraint(const TargetRegisterInfo *TRI,
More information about the llvm-commits
mailing list