[llvm] [Xtensa] Add fp16 conversion support (PR #208206)

Alexey Gerenkov via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 20 07:10:09 PDT 2026


https://github.com/gerekon updated https://github.com/llvm/llvm-project/pull/208206

>From b734f29a25071fb1c764207cd16e38511ea94284 Mon Sep 17 00:00:00 2001
From: Alexey Gerenkov <alexey at espressif.com>
Date: Wed, 8 Jul 2026 14:39:51 +0300
Subject: [PATCH] [Xtensa] Add fp16 conversion support

Close https://github.com/llvm/llvm-project/issues/207505
---
 llvm/lib/Target/Xtensa/XtensaISelLowering.cpp |  11 ++
 llvm/test/CodeGen/Xtensa/fp16.ll              | 159 ++++++++++++++++++
 2 files changed, 170 insertions(+)
 create mode 100644 llvm/test/CodeGen/Xtensa/fp16.ll

diff --git a/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp b/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp
index 0c880234af65f..09422cad222d1 100644
--- a/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp
+++ b/llvm/lib/Target/Xtensa/XtensaISelLowering.cpp
@@ -244,8 +244,19 @@ XtensaTargetLowering::XtensaTargetLowering(const TargetMachine &TM,
     setOperationAction(ISD::FP_TO_SINT, MVT::i32, Expand);
   }
 
+  for (MVT VT : MVT::fp_valuetypes()) {
+    setLoadExtAction(ISD::EXTLOAD, VT, MVT::f16, Expand);
+  }
+
+  setOperationAction(ISD::FP16_TO_FP, MVT::f64, Expand);
+  setOperationAction(ISD::FP_TO_FP16, MVT::f64, Expand);
+  setOperationAction(ISD::FP16_TO_FP, MVT::f32, Expand);
+  setOperationAction(ISD::FP_TO_FP16, MVT::f32, Expand);
+
   // Floating-point truncation and stores need to be done separately.
   setTruncStoreAction(MVT::f64, MVT::f32, Expand);
+  setTruncStoreAction(MVT::f64, MVT::f16, Expand);
+  setTruncStoreAction(MVT::f32, MVT::f16, Expand);
 
   if (Subtarget.hasS32C1I()) {
     setMaxAtomicSizeInBitsSupported(32);
diff --git a/llvm/test/CodeGen/Xtensa/fp16.ll b/llvm/test/CodeGen/Xtensa/fp16.ll
new file mode 100644
index 0000000000000..6c7508d8acfd0
--- /dev/null
+++ b/llvm/test/CodeGen/Xtensa/fp16.ll
@@ -0,0 +1,159 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2
+; RUN: llc -mtriple=xtensa -mcpu=esp32 < %s | FileCheck --check-prefix=CHECK-ESP32 %s
+; RUN: llc -mtriple=xtensa -mcpu=esp32s2 < %s | FileCheck --check-prefix=CHECK-ESP32S2 %s
+
+target datalayout = "e-m:e-p:32:32-v1:8:8-i64:64-i128:128-n32"
+target triple = "xtensa"
+
+ at x = global i16 12902
+ at y = global i16 0
+ at z = common global i16 0
+
+define void @foo() nounwind {
+; CHECK-ESP32-LABEL: foo:
+; CHECK-ESP32:       # %bb.0: # %entry
+; CHECK-ESP32-NEXT:    entry a1, 32
+; CHECK-ESP32-NEXT:    l32r a6, .LCPI0_0
+; CHECK-ESP32-NEXT:    l16ui a10, a6, 0
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI0_1
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    or a7, a10, a10
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI0_2
+; CHECK-ESP32-NEXT:    l16ui a10, a8, 0
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI0_3
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    wfr f8, a10
+; CHECK-ESP32-NEXT:    wfr f9, a7
+; CHECK-ESP32-NEXT:    add.s f8, f9, f8
+; CHECK-ESP32-NEXT:    rfr a10, f8
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI0_4
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    s16i a10, a6, 0
+; CHECK-ESP32-NEXT:    retw.n
+;
+; CHECK-ESP32S2-LABEL: foo:
+; CHECK-ESP32S2:       # %bb.0: # %entry
+; CHECK-ESP32S2-NEXT:    entry a1, 32
+; CHECK-ESP32S2-NEXT:    l32r a6, .LCPI0_0
+; CHECK-ESP32S2-NEXT:    l16ui a10, a6, 0
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI0_1
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    or a7, a10, a10
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI0_2
+; CHECK-ESP32S2-NEXT:    l16ui a10, a8, 0
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI0_3
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    or a11, a10, a10
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI0_4
+; CHECK-ESP32S2-NEXT:    or a10, a7, a7
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI0_5
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    s16i a10, a6, 0
+; CHECK-ESP32S2-NEXT:    retw.n
+entry:
+  %0 = load i16, ptr @x, align 2
+  %1 = load i16, ptr @y, align 2
+  %2 = tail call float @llvm.convert.from.fp16.f32(i16 %0)
+  %3 = tail call float @llvm.convert.from.fp16.f32(i16 %1)
+  %4 = fadd float %2, %3
+  %5 = tail call i16 @llvm.convert.to.fp16.f32(float %4)
+  store i16 %5, ptr @x, align 2
+  ret void
+}
+
+define double @test_from_fp16(i16 %in) {
+; CHECK-ESP32-LABEL: test_from_fp16:
+; CHECK-ESP32:         .cfi_startproc
+; CHECK-ESP32-NEXT:  # %bb.0:
+; CHECK-ESP32-NEXT:    entry a1, 32
+; CHECK-ESP32-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32-NEXT:    or a10, a2, a2
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI1_0
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI1_1
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    or a2, a10, a10
+; CHECK-ESP32-NEXT:    or a3, a11, a11
+; CHECK-ESP32-NEXT:    retw.n
+;
+; CHECK-ESP32S2-LABEL: test_from_fp16:
+; CHECK-ESP32S2:         .cfi_startproc
+; CHECK-ESP32S2-NEXT:  # %bb.0:
+; CHECK-ESP32S2-NEXT:    entry a1, 32
+; CHECK-ESP32S2-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI1_0
+; CHECK-ESP32S2-NEXT:    and a10, a2, a8
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI1_1
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI1_2
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    or a2, a10, a10
+; CHECK-ESP32S2-NEXT:    or a3, a11, a11
+; CHECK-ESP32S2-NEXT:    retw.n
+  %val = call double @llvm.convert.from.fp16.f64(i16 %in)
+  ret double %val
+}
+
+define i16 @test_to_fp16(double %in) {
+; CHECK-ESP32-LABEL: test_to_fp16:
+; CHECK-ESP32:         .cfi_startproc
+; CHECK-ESP32-NEXT:  # %bb.0:
+; CHECK-ESP32-NEXT:    entry a1, 32
+; CHECK-ESP32-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32-NEXT:    or a11, a3, a3
+; CHECK-ESP32-NEXT:    or a10, a2, a2
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI2_0
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    or a2, a10, a10
+; CHECK-ESP32-NEXT:    retw.n
+;
+; CHECK-ESP32S2-LABEL: test_to_fp16:
+; CHECK-ESP32S2:         .cfi_startproc
+; CHECK-ESP32S2-NEXT:  # %bb.0:
+; CHECK-ESP32S2-NEXT:    entry a1, 32
+; CHECK-ESP32S2-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32S2-NEXT:    or a11, a3, a3
+; CHECK-ESP32S2-NEXT:    or a10, a2, a2
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI2_0
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    or a2, a10, a10
+; CHECK-ESP32S2-NEXT:    retw.n
+  %val = call i16 @llvm.convert.to.fp16.f64(double %in)
+  ret i16 %val
+}
+
+; Function Attrs: nounwind
+define dso_local float @cvt(half %a) unnamed_addr #0 {
+; CHECK-ESP32-LABEL: cvt:
+; CHECK-ESP32:         .cfi_startproc
+; CHECK-ESP32-NEXT:  # %bb.0: # %start
+; CHECK-ESP32-NEXT:    entry a1, 32
+; CHECK-ESP32-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32-NEXT:    or a10, a2, a2
+; CHECK-ESP32-NEXT:    l32r a8, .LCPI3_0
+; CHECK-ESP32-NEXT:    callx8 a8
+; CHECK-ESP32-NEXT:    or a2, a10, a10
+; CHECK-ESP32-NEXT:    retw.n
+;
+; CHECK-ESP32S2-LABEL: cvt:
+; CHECK-ESP32S2:         .cfi_startproc
+; CHECK-ESP32S2-NEXT:  # %bb.0: # %start
+; CHECK-ESP32S2-NEXT:    entry a1, 32
+; CHECK-ESP32S2-NEXT:    .cfi_def_cfa_offset 32
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI3_0
+; CHECK-ESP32S2-NEXT:    and a10, a2, a8
+; CHECK-ESP32S2-NEXT:    l32r a8, .LCPI3_1
+; CHECK-ESP32S2-NEXT:    callx8 a8
+; CHECK-ESP32S2-NEXT:    or a2, a10, a10
+; CHECK-ESP32S2-NEXT:    retw.n
+start:
+  %_0 = fpext half %a to float
+  ret float %_0
+}
+
+declare float @llvm.convert.from.fp16.f32(i16) nounwind readnone
+declare double @llvm.convert.from.fp16.f64(i16) nounwind readnone
+
+declare i16 @llvm.convert.to.fp16.f32(float) nounwind readnone
+declare i16 @llvm.convert.to.fp16.f64(double) nounwind readnone



More information about the llvm-commits mailing list