[llvm] [llubi] Upstream existing floating-point intrinsics (PR #196034)

Zhige Chen via llvm-commits llvm-commits at lists.llvm.org
Wed May 6 02:45:53 PDT 2026


https://github.com/nofe1248 created https://github.com/llvm/llvm-project/pull/196034

This PR upstreams existing floating-point intrinsics in the out-of-tree version of llubi. Including FP vector reduction, FP min/max operations, etc.

>From 4a5053621b086687fa87a7834ff6169019d61881 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 6 May 2026 17:23:46 +0800
Subject: [PATCH 1/2] [llubi] Floating-point intrinsics

---
 llvm/test/tools/llubi/intr_fp_fma.ll          |  30 ++
 llvm/test/tools/llubi/intr_fp_fptoi_sat.ll    |  32 ++
 llvm/test/tools/llubi/intr_fp_is_fpclass.ll   |  35 ++
 llvm/test/tools/llubi/intr_fp_minmax.ll       | 122 +++++++
 llvm/test/tools/llubi/intr_fp_unary.ll        |  28 ++
 .../test/tools/llubi/intr_fp_vector_reduce.ll |  78 +++++
 llvm/tools/llubi/lib/Context.h                |   3 +
 llvm/tools/llubi/lib/Interpreter.cpp          | 310 +++++++++++++++++-
 llvm/tools/llubi/llubi.cpp                    |   6 +
 9 files changed, 636 insertions(+), 8 deletions(-)
 create mode 100644 llvm/test/tools/llubi/intr_fp_fma.ll
 create mode 100644 llvm/test/tools/llubi/intr_fp_fptoi_sat.ll
 create mode 100644 llvm/test/tools/llubi/intr_fp_is_fpclass.ll
 create mode 100644 llvm/test/tools/llubi/intr_fp_minmax.ll
 create mode 100644 llvm/test/tools/llubi/intr_fp_unary.ll
 create mode 100644 llvm/test/tools/llubi/intr_fp_vector_reduce.ll

diff --git a/llvm/test/tools/llubi/intr_fp_fma.ll b/llvm/test/tools/llubi/intr_fp_fma.ll
new file mode 100644
index 0000000000000..cd52f1ac7d0d4
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_fma.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %fma = call float @llvm.fma.f32(float 2.0, float 3.0, float 4.0)
+  %fma_poison_lhs = call float @llvm.fma.f32(float poison, float 3.0, float 4.0)
+  %fma_poison_rhs = call float @llvm.fma.f32(float 2.0, float poison, float 4.0)
+  %fma_poison_addend = call float @llvm.fma.f32(float 2.0, float 3.0, float poison)
+  %fma_vec = call <4 x float> @llvm.fma.v4f32(<4 x float> <float 2.0, float poison, float 2.0, float 2.0>, <4 x float> <float 3.0, float 3.0, float 5.0, float 3.0>, <4 x float> <float 4.0, float 4.0, float 7.0, float poison>)
+
+  %fmuladd = call float @llvm.fmuladd.f32(float 2.0, float 3.0, float 4.0)
+  %fmuladd_poison_lhs = call float @llvm.fmuladd.f32(float poison, float 3.0, float 4.0)
+  %fmuladd_poison_rhs = call float @llvm.fmuladd.f32(float 2.0, float poison, float 4.0)
+  %fmuladd_poison_addend = call float @llvm.fmuladd.f32(float 2.0, float 3.0, float poison)
+  %fmuladd_vec = call <4 x float> @llvm.fmuladd.v4f32(<4 x float> <float 2.0, float poison, float 2.0, float 2.0>, <4 x float> <float 3.0, float 3.0, float 5.0, float 3.0>, <4 x float> <float 4.0, float 4.0, float 7.0, float poison>)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %fma = call float @llvm.fma.f32(float 2.000000e+00, float 3.000000e+00, float 4.000000e+00) => float 1.000000e+01
+; CHECK-NEXT:   %fma_poison_lhs = call float @llvm.fma.f32(float poison, float 3.000000e+00, float 4.000000e+00) => poison
+; CHECK-NEXT:   %fma_poison_rhs = call float @llvm.fma.f32(float 2.000000e+00, float poison, float 4.000000e+00) => poison
+; CHECK-NEXT:   %fma_poison_addend = call float @llvm.fma.f32(float 2.000000e+00, float 3.000000e+00, float poison) => poison
+; CHECK-NEXT:   %fma_vec = call <4 x float> @llvm.fma.v4f32(<4 x float> <float 2.000000e+00, float poison, float 2.000000e+00, float 2.000000e+00>, <4 x float> <float 3.000000e+00, float 3.000000e+00, float 5.000000e+00, float 3.000000e+00>, <4 x float> <float 4.000000e+00, float 4.000000e+00, float 7.000000e+00, float poison>) => { float 1.000000e+01, poison, float 1.700000e+01, poison }
+; CHECK-NEXT:   %fmuladd = call float @llvm.fmuladd.f32(float 2.000000e+00, float 3.000000e+00, float 4.000000e+00) => float 1.000000e+01
+; CHECK-NEXT:   %fmuladd_poison_lhs = call float @llvm.fmuladd.f32(float poison, float 3.000000e+00, float 4.000000e+00) => poison
+; CHECK-NEXT:   %fmuladd_poison_rhs = call float @llvm.fmuladd.f32(float 2.000000e+00, float poison, float 4.000000e+00) => poison
+; CHECK-NEXT:   %fmuladd_poison_addend = call float @llvm.fmuladd.f32(float 2.000000e+00, float 3.000000e+00, float poison) => poison
+; CHECK-NEXT:   %fmuladd_vec = call <4 x float> @llvm.fmuladd.v4f32(<4 x float> <float 2.000000e+00, float poison, float 2.000000e+00, float 2.000000e+00>, <4 x float> <float 3.000000e+00, float 3.000000e+00, float 5.000000e+00, float 3.000000e+00>, <4 x float> <float 4.000000e+00, float 4.000000e+00, float 7.000000e+00, float poison>) => { float 1.000000e+01, poison, float 1.700000e+01, poison }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_fp_fptoi_sat.ll b/llvm/test/tools/llubi/intr_fp_fptoi_sat.ll
new file mode 100644
index 0000000000000..fa93a5b1338d4
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_fptoi_sat.ll
@@ -0,0 +1,32 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %spos = call i8 @llvm.fptosi.sat.i8.f32(float 127.75)
+  %sneg = call i8 @llvm.fptosi.sat.i8.f32(float -128.75)
+  %slarge = call i8 @llvm.fptosi.sat.i8.f32(float 1.0e+10)
+  %slarge_neg = call i8 @llvm.fptosi.sat.i8.f32(float -1.0e+10)
+  %spoison = call i8 @llvm.fptosi.sat.i8.f32(float poison)
+  %svec = call <4 x i8> @llvm.fptosi.sat.v4i8.v4f32(<4 x float> <float 1.25, float poison, float 1.0e+10, float -1.0e+10>)
+
+  %upos = call i8 @llvm.fptoui.sat.i8.f32(float 255.75)
+  %uneg = call i8 @llvm.fptoui.sat.i8.f32(float -1.25)
+  %ularge = call i8 @llvm.fptoui.sat.i8.f32(float 1.0e+10)
+  %upoison = call i8 @llvm.fptoui.sat.i8.f32(float poison)
+  %uvec = call <4 x i8> @llvm.fptoui.sat.v4i8.v4f32(<4 x float> <float 1.25, float poison, float 1.0e+10, float -1.25>)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %spos = call i8 @llvm.fptosi.sat.i8.f32(float 1.277500e+02) => i8 127
+; CHECK-NEXT:   %sneg = call i8 @llvm.fptosi.sat.i8.f32(float -1.287500e+02) => i8 -128
+; CHECK-NEXT:   %slarge = call i8 @llvm.fptosi.sat.i8.f32(float 1.000000e+10) => i8 127
+; CHECK-NEXT:   %slarge_neg = call i8 @llvm.fptosi.sat.i8.f32(float -1.000000e+10) => i8 -128
+; CHECK-NEXT:   %spoison = call i8 @llvm.fptosi.sat.i8.f32(float poison) => poison
+; CHECK-NEXT:   %svec = call <4 x i8> @llvm.fptosi.sat.v4i8.v4f32(<4 x float> <float 1.250000e+00, float poison, float 1.000000e+10, float -1.000000e+10>) => { i8 1, poison, i8 127, i8 -128 }
+; CHECK-NEXT:   %upos = call i8 @llvm.fptoui.sat.i8.f32(float 2.557500e+02) => i8 -1
+; CHECK-NEXT:   %uneg = call i8 @llvm.fptoui.sat.i8.f32(float -1.250000e+00) => i8 0
+; CHECK-NEXT:   %ularge = call i8 @llvm.fptoui.sat.i8.f32(float 1.000000e+10) => i8 -1
+; CHECK-NEXT:   %upoison = call i8 @llvm.fptoui.sat.i8.f32(float poison) => poison
+; CHECK-NEXT:   %uvec = call <4 x i8> @llvm.fptoui.sat.v4i8.v4f32(<4 x float> <float 1.250000e+00, float poison, float 1.000000e+10, float -1.250000e+00>) => { i8 1, poison, i8 -1, i8 0 }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_fp_is_fpclass.ll b/llvm/test/tools/llubi/intr_fp_is_fpclass.ll
new file mode 100644
index 0000000000000..c781fbeae281e
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_is_fpclass.ll
@@ -0,0 +1,35 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %is_snan = call i1 @llvm.is.fpclass.f32(float 0x7FF0000020000000, i32 1)
+  %is_qnan = call i1 @llvm.is.fpclass.f32(float 0x7FF8000020000000, i32 2)
+  %is_nan = call i1 @llvm.is.fpclass.f32(float 0x7FF8000020000000, i32 3)
+  %is_neg_inf = call i1 @llvm.is.fpclass.f32(float 0xFFF0000000000000, i32 4)
+  %is_neg_normal = call i1 @llvm.is.fpclass.f32(float -1.0, i32 8)
+  %is_neg_subnormal = call i1 @llvm.is.fpclass.f32(float -1.434929627468612680625899e-42, i32 16)
+  %is_neg_zero = call i1 @llvm.is.fpclass.f32(float -0.0, i32 32)
+  %is_pos_zero = call i1 @llvm.is.fpclass.f32(float 0.0, i32 64)
+  %is_pos_subnormal = call i1 @llvm.is.fpclass.f32(float 1.434929627468612680625899e-42, i32 128)
+  %is_pos_normal = call i1 @llvm.is.fpclass.f32(float 1.0, i32 256)
+  %is_pos_inf = call i1 @llvm.is.fpclass.f32(float 0x7FF0000000000000, i32 512)
+  %is_poison = call i1 @llvm.is.fpclass.f32(float poison, i32 256)
+  %is_vec = call <4 x i1> @llvm.is.fpclass.v4f32(<4 x float> <float 0x7FF8000020000000, float poison, float 1.0, float -0.0>, i32 291)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %is_snan = call i1 @llvm.is.fpclass.f32(float 0x7FF0000020000000, i32 1) => T
+; CHECK-NEXT:   %is_qnan = call i1 @llvm.is.fpclass.f32(float 0x7FF8000020000000, i32 2) => T
+; CHECK-NEXT:   %is_nan = call i1 @llvm.is.fpclass.f32(float 0x7FF8000020000000, i32 3) => T
+; CHECK-NEXT:   %is_neg_inf = call i1 @llvm.is.fpclass.f32(float 0xFFF0000000000000, i32 4) => T
+; CHECK-NEXT:   %is_neg_normal = call i1 @llvm.is.fpclass.f32(float -1.000000e+00, i32 8) => T
+; CHECK-NEXT:   %is_neg_subnormal = call i1 @llvm.is.fpclass.f32(float 0xB740000000000000, i32 16) => T
+; CHECK-NEXT:   %is_neg_zero = call i1 @llvm.is.fpclass.f32(float -0.000000e+00, i32 32) => T
+; CHECK-NEXT:   %is_pos_zero = call i1 @llvm.is.fpclass.f32(float 0.000000e+00, i32 64) => T
+; CHECK-NEXT:   %is_pos_subnormal = call i1 @llvm.is.fpclass.f32(float 0x3740000000000000, i32 128) => T
+; CHECK-NEXT:   %is_pos_normal = call i1 @llvm.is.fpclass.f32(float 1.000000e+00, i32 256) => T
+; CHECK-NEXT:   %is_pos_inf = call i1 @llvm.is.fpclass.f32(float 0x7FF0000000000000, i32 512) => T
+; CHECK-NEXT:   %is_poison = call i1 @llvm.is.fpclass.f32(float poison, i32 256) => poison
+; CHECK-NEXT:   %is_vec = call <4 x i1> @llvm.is.fpclass.v4f32(<4 x float> <float 0x7FF8000020000000, float poison, float 1.000000e+00, float -0.000000e+00>, i32 291) => { T, poison, T, T }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_fp_minmax.ll b/llvm/test/tools/llubi/intr_fp_minmax.ll
new file mode 100644
index 0000000000000..7195c5bc5ca67
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_minmax.ll
@@ -0,0 +1,122 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %maxnum = call float @llvm.maxnum.f32(float 1.0, float 2.0)
+  %maxnum_neg_zero = call float @llvm.maxnum.f32(float -0.0, float 0.0)
+  %maxnum_qnan_lhs = call float @llvm.maxnum.f32(float 0x7FF8000020000000, float 2.0)
+  %maxnum_qnan_rhs = call float @llvm.maxnum.f32(float 2.0, float 0x7FF8000020000000)
+  %maxnum_snan_lhs = call float @llvm.maxnum.f32(float 0x7FF0000020000000, float 2.0)
+  %maxnum_snan_rhs = call float @llvm.maxnum.f32(float 2.0, float 0x7FF0000020000000)
+  %maxnum_poison_lhs = call float @llvm.maxnum.f32(float poison, float 2.0)
+  %maxnum_poison_rhs = call float @llvm.maxnum.f32(float 1.0, float poison)
+  %maxnum_vec = call <4 x float> @llvm.maxnum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+
+  %minnum = call float @llvm.minnum.f32(float 1.0, float 2.0)
+  %minnum_neg_zero = call float @llvm.minnum.f32(float -0.0, float 0.0)
+  %minnum_qnan_lhs = call float @llvm.minnum.f32(float 0x7FF8000020000000, float 2.0)
+  %minnum_qnan_rhs = call float @llvm.minnum.f32(float 2.0, float 0x7FF8000020000000)
+  %minnum_snan_lhs = call float @llvm.minnum.f32(float 0x7FF0000020000000, float 2.0)
+  %minnum_snan_rhs = call float @llvm.minnum.f32(float 2.0, float 0x7FF0000020000000)
+  %minnum_poison_lhs = call float @llvm.minnum.f32(float poison, float 2.0)
+  %minnum_poison_rhs = call float @llvm.minnum.f32(float 1.0, float poison)
+  %minnum_vec = call <4 x float> @llvm.minnum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+
+  %maximum = call float @llvm.maximum.f32(float 1.0, float 2.0)
+  %maximum_neg_zero = call float @llvm.maximum.f32(float -0.0, float 0.0)
+  %maximum_qnan_lhs = call float @llvm.maximum.f32(float 0x7FF8000020000000, float 2.0)
+  %maximum_qnan_rhs = call float @llvm.maximum.f32(float 2.0, float 0x7FF8000020000000)
+  %maximum_snan_lhs = call float @llvm.maximum.f32(float 0x7FF0000020000000, float 2.0)
+  %maximum_snan_rhs = call float @llvm.maximum.f32(float 2.0, float 0x7FF0000020000000)
+  %maximum_poison_lhs = call float @llvm.maximum.f32(float poison, float 2.0)
+  %maximum_poison_rhs = call float @llvm.maximum.f32(float 1.0, float poison)
+  %maximum_vec = call <4 x float> @llvm.maximum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+
+  %minimum = call float @llvm.minimum.f32(float 1.0, float 2.0)
+  %minimum_neg_zero = call float @llvm.minimum.f32(float -0.0, float 0.0)
+  %minimum_qnan_lhs = call float @llvm.minimum.f32(float 0x7FF8000020000000, float 2.0)
+  %minimum_qnan_rhs = call float @llvm.minimum.f32(float 2.0, float 0x7FF8000020000000)
+  %minimum_snan_lhs = call float @llvm.minimum.f32(float 0x7FF0000020000000, float 2.0)
+  %minimum_snan_rhs = call float @llvm.minimum.f32(float 2.0, float 0x7FF0000020000000)
+  %minimum_poison_lhs = call float @llvm.minimum.f32(float poison, float 2.0)
+  %minimum_poison_rhs = call float @llvm.minimum.f32(float 1.0, float poison)
+  %minimum_vec = call <4 x float> @llvm.minimum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+
+  %maximumnum = call float @llvm.maximumnum.f32(float 1.0, float 2.0)
+  %maximumnum_neg_zero = call float @llvm.maximumnum.f32(float -0.0, float 0.0)
+  %maximumnum_qnan_lhs = call float @llvm.maximumnum.f32(float 0x7FF8000020000000, float 2.0)
+  %maximumnum_qnan_rhs = call float @llvm.maximumnum.f32(float 2.0, float 0x7FF8000020000000)
+  %maximumnum_snan_lhs = call float @llvm.maximumnum.f32(float 0x7FF0000020000000, float 2.0)
+  %maximumnum_snan_rhs = call float @llvm.maximumnum.f32(float 2.0, float 0x7FF0000020000000)
+  %maximumnum_poison_lhs = call float @llvm.maximumnum.f32(float poison, float 2.0)
+  %maximumnum_poison_rhs = call float @llvm.maximumnum.f32(float 1.0, float poison)
+  %maximumnum_vec = call <4 x float> @llvm.maximumnum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+
+  %minimumnum = call float @llvm.minimumnum.f32(float 1.0, float 2.0)
+  %minimumnum_neg_zero = call float @llvm.minimumnum.f32(float -0.0, float 0.0)
+  %minimumnum_qnan_lhs = call float @llvm.minimumnum.f32(float 0x7FF8000020000000, float 2.0)
+  %minimumnum_qnan_rhs = call float @llvm.minimumnum.f32(float 2.0, float 0x7FF8000020000000)
+  %minimumnum_snan_lhs = call float @llvm.minimumnum.f32(float 0x7FF0000020000000, float 2.0)
+  %minimumnum_snan_rhs = call float @llvm.minimumnum.f32(float 2.0, float 0x7FF0000020000000)
+  %minimumnum_poison_lhs = call float @llvm.minimumnum.f32(float poison, float 2.0)
+  %minimumnum_poison_rhs = call float @llvm.minimumnum.f32(float 1.0, float poison)
+  %minimumnum_vec = call <4 x float> @llvm.minimumnum.v4f32(<4 x float> <float 1.0, float poison, float 0x7FF8000020000000, float 4.0>, <4 x float> <float 2.0, float 2.0, float 2.0, float poison>)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %maxnum = call float @llvm.maxnum.f32(float 1.000000e+00, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maxnum_neg_zero = call float @llvm.maxnum.f32(float -0.000000e+00, float 0.000000e+00) => float 0.000000e+00
+; CHECK-NEXT:   %maxnum_qnan_lhs = call float @llvm.maxnum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maxnum_qnan_rhs = call float @llvm.maxnum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %maxnum_snan_lhs = call float @llvm.maxnum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 0x7F800001
+; CHECK-NEXT:   %maxnum_snan_rhs = call float @llvm.maxnum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 0xFFC00000
+; CHECK-NEXT:   %maxnum_poison_lhs = call float @llvm.maxnum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %maxnum_poison_rhs = call float @llvm.maxnum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %maxnum_vec = call <4 x float> @llvm.maxnum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 2.000000e+00, poison, float 2.000000e+00, poison }
+; CHECK-NEXT:   %minnum = call float @llvm.minnum.f32(float 1.000000e+00, float 2.000000e+00) => float 1.000000e+00
+; CHECK-NEXT:   %minnum_neg_zero = call float @llvm.minnum.f32(float -0.000000e+00, float 0.000000e+00) => float -0.000000e+00
+; CHECK-NEXT:   %minnum_qnan_lhs = call float @llvm.minnum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %minnum_qnan_rhs = call float @llvm.minnum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %minnum_snan_lhs = call float @llvm.minnum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 0x7F800001
+; CHECK-NEXT:   %minnum_snan_rhs = call float @llvm.minnum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 0xFFC00000
+; CHECK-NEXT:   %minnum_poison_lhs = call float @llvm.minnum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %minnum_poison_rhs = call float @llvm.minnum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %minnum_vec = call <4 x float> @llvm.minnum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 1.000000e+00, poison, float 2.000000e+00, poison }
+; CHECK-NEXT:   %maximum = call float @llvm.maximum.f32(float 1.000000e+00, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maximum_neg_zero = call float @llvm.maximum.f32(float -0.000000e+00, float 0.000000e+00) => float 0.000000e+00
+; CHECK-NEXT:   %maximum_qnan_lhs = call float @llvm.maximum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 0x7FC00001
+; CHECK-NEXT:   %maximum_qnan_rhs = call float @llvm.maximum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 0xFFC00000
+; CHECK-NEXT:   %maximum_snan_lhs = call float @llvm.maximum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 0x7F800001
+; CHECK-NEXT:   %maximum_snan_rhs = call float @llvm.maximum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 0x7F800001
+; CHECK-NEXT:   %maximum_poison_lhs = call float @llvm.maximum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %maximum_poison_rhs = call float @llvm.maximum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %maximum_vec = call <4 x float> @llvm.maximum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 2.000000e+00, poison, float 0xFFC00001, poison }
+; CHECK-NEXT:   %minimum = call float @llvm.minimum.f32(float 1.000000e+00, float 2.000000e+00) => float 1.000000e+00
+; CHECK-NEXT:   %minimum_neg_zero = call float @llvm.minimum.f32(float -0.000000e+00, float 0.000000e+00) => float -0.000000e+00
+; CHECK-NEXT:   %minimum_qnan_lhs = call float @llvm.minimum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 0xFFC00000
+; CHECK-NEXT:   %minimum_qnan_rhs = call float @llvm.minimum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 0xFFC00001
+; CHECK-NEXT:   %minimum_snan_lhs = call float @llvm.minimum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 0x7F800001
+; CHECK-NEXT:   %minimum_snan_rhs = call float @llvm.minimum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 0x7FC00001
+; CHECK-NEXT:   %minimum_poison_lhs = call float @llvm.minimum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %minimum_poison_rhs = call float @llvm.minimum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %minimum_vec = call <4 x float> @llvm.minimum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 1.000000e+00, poison, float NaN, poison }
+; CHECK-NEXT:   %maximumnum = call float @llvm.maximumnum.f32(float 1.000000e+00, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maximumnum_neg_zero = call float @llvm.maximumnum.f32(float -0.000000e+00, float 0.000000e+00) => float 0.000000e+00
+; CHECK-NEXT:   %maximumnum_qnan_lhs = call float @llvm.maximumnum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maximumnum_qnan_rhs = call float @llvm.maximumnum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %maximumnum_snan_lhs = call float @llvm.maximumnum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %maximumnum_snan_rhs = call float @llvm.maximumnum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %maximumnum_poison_lhs = call float @llvm.maximumnum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %maximumnum_poison_rhs = call float @llvm.maximumnum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %maximumnum_vec = call <4 x float> @llvm.maximumnum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 2.000000e+00, poison, float 2.000000e+00, poison }
+; CHECK-NEXT:   %minimumnum = call float @llvm.minimumnum.f32(float 1.000000e+00, float 2.000000e+00) => float 1.000000e+00
+; CHECK-NEXT:   %minimumnum_neg_zero = call float @llvm.minimumnum.f32(float -0.000000e+00, float 0.000000e+00) => float -0.000000e+00
+; CHECK-NEXT:   %minimumnum_qnan_lhs = call float @llvm.minimumnum.f32(float 0x7FF8000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %minimumnum_qnan_rhs = call float @llvm.minimumnum.f32(float 2.000000e+00, float 0x7FF8000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %minimumnum_snan_lhs = call float @llvm.minimumnum.f32(float 0x7FF0000020000000, float 2.000000e+00) => float 2.000000e+00
+; CHECK-NEXT:   %minimumnum_snan_rhs = call float @llvm.minimumnum.f32(float 2.000000e+00, float 0x7FF0000020000000) => float 2.000000e+00
+; CHECK-NEXT:   %minimumnum_poison_lhs = call float @llvm.minimumnum.f32(float poison, float 2.000000e+00) => poison
+; CHECK-NEXT:   %minimumnum_poison_rhs = call float @llvm.minimumnum.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %minimumnum_vec = call <4 x float> @llvm.minimumnum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 0x7FF8000020000000, float 4.000000e+00>, <4 x float> <float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float poison>) => { float 1.000000e+00, poison, float 2.000000e+00, poison }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_fp_unary.ll b/llvm/test/tools/llubi/intr_fp_unary.ll
new file mode 100644
index 0000000000000..269664bf14c34
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_unary.ll
@@ -0,0 +1,28 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %fabs = call float @llvm.fabs.f32(float -1.5)
+  %fabs_neg_zero = call float @llvm.fabs.f32(float -0.0)
+  %fabs_poison = call float @llvm.fabs.f32(float poison)
+  %fabs_vec = call <4 x float> @llvm.fabs.v4f32(<4 x float> <float -1.0, float poison, float 2.0, float -0.0>)
+
+  %copysign = call float @llvm.copysign.f32(float 1.0, float -2.0)
+  %copysign_neg_zero = call float @llvm.copysign.f32(float 1.0, float -0.0)
+  %copysign_poison_mag = call float @llvm.copysign.f32(float poison, float -2.0)
+  %copysign_poison_sign = call float @llvm.copysign.f32(float 1.0, float poison)
+  %copysign_vec = call <4 x float> @llvm.copysign.v4f32(<4 x float> <float 1.0, float poison, float 3.0, float -0.0>, <4 x float> <float -2.0, float -2.0, float 2.0, float 2.0>)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %fabs = call float @llvm.fabs.f32(float -1.500000e+00) => float 1.500000e+00
+; CHECK-NEXT:   %fabs_neg_zero = call float @llvm.fabs.f32(float -0.000000e+00) => float 0.000000e+00
+; CHECK-NEXT:   %fabs_poison = call float @llvm.fabs.f32(float poison) => poison
+; CHECK-NEXT:   %fabs_vec = call <4 x float> @llvm.fabs.v4f32(<4 x float> <float -1.000000e+00, float poison, float 2.000000e+00, float -0.000000e+00>) => { float 1.000000e+00, poison, float 2.000000e+00, float 0.000000e+00 }
+; CHECK-NEXT:   %copysign = call float @llvm.copysign.f32(float 1.000000e+00, float -2.000000e+00) => float -1.000000e+00
+; CHECK-NEXT:   %copysign_neg_zero = call float @llvm.copysign.f32(float 1.000000e+00, float -0.000000e+00) => float -1.000000e+00
+; CHECK-NEXT:   %copysign_poison_mag = call float @llvm.copysign.f32(float poison, float -2.000000e+00) => poison
+; CHECK-NEXT:   %copysign_poison_sign = call float @llvm.copysign.f32(float 1.000000e+00, float poison) => poison
+; CHECK-NEXT:   %copysign_vec = call <4 x float> @llvm.copysign.v4f32(<4 x float> <float 1.000000e+00, float poison, float 3.000000e+00, float -0.000000e+00>, <4 x float> <float -2.000000e+00, float -2.000000e+00, float 2.000000e+00, float 2.000000e+00>) => { float -1.000000e+00, poison, float 3.000000e+00, float 0.000000e+00 }
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/intr_fp_vector_reduce.ll b/llvm/test/tools/llubi/intr_fp_vector_reduce.ll
new file mode 100644
index 0000000000000..57050438c0e2b
--- /dev/null
+++ b/llvm/test/tools/llubi/intr_fp_vector_reduce.ll
@@ -0,0 +1,78 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --vscale=4 --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+  %fadd = call float @llvm.vector.reduce.fadd.v4f32(float 1.0, <4 x float> <float 2.0, float 3.0, float 4.0, float 5.0>)
+  %fadd_poison_acc = call float @llvm.vector.reduce.fadd.v4f32(float poison, <4 x float> <float 2.0, float 3.0, float 4.0, float 5.0>)
+  %fadd_poison_vec = call float @llvm.vector.reduce.fadd.v4f32(float 1.0, <4 x float> <float 2.0, float poison, float 4.0, float 5.0>)
+
+  %fmul = call float @llvm.vector.reduce.fmul.v4f32(float 2.0, <4 x float> <float 2.0, float 3.0, float 4.0, float 5.0>)
+  %fmul_poison_acc = call float @llvm.vector.reduce.fmul.v4f32(float poison, <4 x float> <float 2.0, float 3.0, float 4.0, float 5.0>)
+  %fmul_poison_vec = call float @llvm.vector.reduce.fmul.v4f32(float 2.0, <4 x float> <float 2.0, float poison, float 4.0, float 5.0>)
+
+  %fmax = call float @llvm.vector.reduce.fmax.v4f32(<4 x float> <float 1.0, float 2.0, float 4.0, float 5.0>)
+  %fmax_poison = call float @llvm.vector.reduce.fmax.v4f32(<4 x float> <float 1.0, float poison, float 4.0, float 5.0>)
+
+  %fmin = call float @llvm.vector.reduce.fmin.v4f32(<4 x float> <float 1.0, float 2.0, float 4.0, float 5.0>)
+  %fmin_poison = call float @llvm.vector.reduce.fmin.v4f32(<4 x float> <float 1.0, float poison, float 4.0, float 5.0>)
+
+  %fmaximum = call float @llvm.vector.reduce.fmaximum.v4f32(<4 x float> <float 1.0, float 2.0, float 4.0, float 5.0>)
+  %fmaximum_poison = call float @llvm.vector.reduce.fmaximum.v4f32(<4 x float> <float 1.0, float poison, float 4.0, float 5.0>)
+
+  %fminimum = call float @llvm.vector.reduce.fminimum.v4f32(<4 x float> <float 1.0, float 2.0, float 4.0, float 5.0>)
+  %fminimum_poison = call float @llvm.vector.reduce.fminimum.v4f32(<4 x float> <float 1.0, float poison, float 4.0, float 5.0>)
+
+  %sv_poison = insertelement <vscale x 4 x float> splat (float 2.0), float poison, i64 1
+  %sv_fadd = call float @llvm.vector.reduce.fadd.nxv4f32(float 1.0, <vscale x 4 x float> splat (float 2.0))
+  %sv_fadd_poison_acc = call float @llvm.vector.reduce.fadd.nxv4f32(float poison, <vscale x 4 x float> splat (float 2.0))
+  %sv_fadd_poison_vec = call float @llvm.vector.reduce.fadd.nxv4f32(float 1.0, <vscale x 4 x float> %sv_poison)
+
+  %sv_fmul = call float @llvm.vector.reduce.fmul.nxv4f32(float 2.0, <vscale x 4 x float> splat (float 2.0))
+  %sv_fmul_poison_acc = call float @llvm.vector.reduce.fmul.nxv4f32(float poison, <vscale x 4 x float> splat (float 2.0))
+  %sv_fmul_poison_vec = call float @llvm.vector.reduce.fmul.nxv4f32(float 2.0, <vscale x 4 x float> %sv_poison)
+
+  %sv_fmax = call float @llvm.vector.reduce.fmax.nxv4f32(<vscale x 4 x float> splat (float 2.0))
+  %sv_fmax_poison = call float @llvm.vector.reduce.fmax.nxv4f32(<vscale x 4 x float> %sv_poison)
+
+  %sv_fmin = call float @llvm.vector.reduce.fmin.nxv4f32(<vscale x 4 x float> splat (float 2.0))
+  %sv_fmin_poison = call float @llvm.vector.reduce.fmin.nxv4f32(<vscale x 4 x float> %sv_poison)
+
+  %sv_fmaximum = call float @llvm.vector.reduce.fmaximum.nxv4f32(<vscale x 4 x float> splat (float 2.0))
+  %sv_fmaximum_poison = call float @llvm.vector.reduce.fmaximum.nxv4f32(<vscale x 4 x float> %sv_poison)
+
+  %sv_fminimum = call float @llvm.vector.reduce.fminimum.nxv4f32(<vscale x 4 x float> splat (float 2.0))
+  %sv_fminimum_poison = call float @llvm.vector.reduce.fminimum.nxv4f32(<vscale x 4 x float> %sv_poison)
+  ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT:   %fadd = call float @llvm.vector.reduce.fadd.v4f32(float 1.000000e+00, <4 x float> <float 2.000000e+00, float 3.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 1.500000e+01
+; CHECK-NEXT:   %fadd_poison_acc = call float @llvm.vector.reduce.fadd.v4f32(float poison, <4 x float> <float 2.000000e+00, float 3.000000e+00, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fadd_poison_vec = call float @llvm.vector.reduce.fadd.v4f32(float 1.000000e+00, <4 x float> <float 2.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fmul = call float @llvm.vector.reduce.fmul.v4f32(float 2.000000e+00, <4 x float> <float 2.000000e+00, float 3.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 2.400000e+02
+; CHECK-NEXT:   %fmul_poison_acc = call float @llvm.vector.reduce.fmul.v4f32(float poison, <4 x float> <float 2.000000e+00, float 3.000000e+00, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fmul_poison_vec = call float @llvm.vector.reduce.fmul.v4f32(float 2.000000e+00, <4 x float> <float 2.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fmax = call float @llvm.vector.reduce.fmax.v4f32(<4 x float> <float 1.000000e+00, float 2.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 5.000000e+00
+; CHECK-NEXT:   %fmax_poison = call float @llvm.vector.reduce.fmax.v4f32(<4 x float> <float 1.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fmin = call float @llvm.vector.reduce.fmin.v4f32(<4 x float> <float 1.000000e+00, float 2.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 1.000000e+00
+; CHECK-NEXT:   %fmin_poison = call float @llvm.vector.reduce.fmin.v4f32(<4 x float> <float 1.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fmaximum = call float @llvm.vector.reduce.fmaximum.v4f32(<4 x float> <float 1.000000e+00, float 2.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 5.000000e+00
+; CHECK-NEXT:   %fmaximum_poison = call float @llvm.vector.reduce.fmaximum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %fminimum = call float @llvm.vector.reduce.fminimum.v4f32(<4 x float> <float 1.000000e+00, float 2.000000e+00, float 4.000000e+00, float 5.000000e+00>) => float 1.000000e+00
+; CHECK-NEXT:   %fminimum_poison = call float @llvm.vector.reduce.fminimum.v4f32(<4 x float> <float 1.000000e+00, float poison, float 4.000000e+00, float 5.000000e+00>) => poison
+; CHECK-NEXT:   %sv_poison = insertelement <vscale x 4 x float> splat (float 2.000000e+00), float poison, i64 1 => { float 2.000000e+00, poison, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00, float 2.000000e+00 }
+; CHECK-NEXT:   %sv_fadd = call float @llvm.vector.reduce.fadd.nxv4f32(float 1.000000e+00, <vscale x 4 x float> splat (float 2.000000e+00)) => float 3.300000e+01
+; CHECK-NEXT:   %sv_fadd_poison_acc = call float @llvm.vector.reduce.fadd.nxv4f32(float poison, <vscale x 4 x float> splat (float 2.000000e+00)) => poison
+; CHECK-NEXT:   %sv_fadd_poison_vec = call float @llvm.vector.reduce.fadd.nxv4f32(float 1.000000e+00, <vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   %sv_fmul = call float @llvm.vector.reduce.fmul.nxv4f32(float 2.000000e+00, <vscale x 4 x float> splat (float 2.000000e+00)) => float 1.310720e+05
+; CHECK-NEXT:   %sv_fmul_poison_acc = call float @llvm.vector.reduce.fmul.nxv4f32(float poison, <vscale x 4 x float> splat (float 2.000000e+00)) => poison
+; CHECK-NEXT:   %sv_fmul_poison_vec = call float @llvm.vector.reduce.fmul.nxv4f32(float 2.000000e+00, <vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   %sv_fmax = call float @llvm.vector.reduce.fmax.nxv4f32(<vscale x 4 x float> splat (float 2.000000e+00)) => float 2.000000e+00
+; CHECK-NEXT:   %sv_fmax_poison = call float @llvm.vector.reduce.fmax.nxv4f32(<vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   %sv_fmin = call float @llvm.vector.reduce.fmin.nxv4f32(<vscale x 4 x float> splat (float 2.000000e+00)) => float 2.000000e+00
+; CHECK-NEXT:   %sv_fmin_poison = call float @llvm.vector.reduce.fmin.nxv4f32(<vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   %sv_fmaximum = call float @llvm.vector.reduce.fmaximum.nxv4f32(<vscale x 4 x float> splat (float 2.000000e+00)) => float 2.000000e+00
+; CHECK-NEXT:   %sv_fmaximum_poison = call float @llvm.vector.reduce.fmaximum.nxv4f32(<vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   %sv_fminimum = call float @llvm.vector.reduce.fminimum.nxv4f32(<vscale x 4 x float> splat (float 2.000000e+00)) => float 2.000000e+00
+; CHECK-NEXT:   %sv_fminimum_poison = call float @llvm.vector.reduce.fminimum.nxv4f32(<vscale x 4 x float> %sv_poison) => poison
+; CHECK-NEXT:   ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 177778aa1f691..af5849caeb8d6 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -208,6 +208,7 @@ class Context {
   bool Deterministic = false;
   UndefValueBehavior UndefBehavior = UndefValueBehavior::NonDeterministic;
   NaNPropagationBehavior NaNBehavior = NaNPropagationBehavior::NonDeterministic;
+  bool fusedMultiplyAdd = false;
 
   std::mt19937_64 Rng;
 
@@ -260,6 +261,7 @@ class Context {
   void setVScale(uint32_t VS) { VScale = VS; }
   void setMaxSteps(uint32_t MS) { MaxSteps = MS; }
   void setMaxStackDepth(uint32_t Depth) { MaxStackDepth = Depth; }
+  void setFusedMultiplyAdd(bool F) { fusedMultiplyAdd = F; }
   uint64_t getMemoryLimit() const { return MaxMem; }
   uint32_t getVScale() const { return VScale; }
   uint32_t getMaxSteps() const { return MaxSteps; }
@@ -269,6 +271,7 @@ class Context {
   bool mayUseNonDeterminism() const { return !Deterministic; }
   UndefValueBehavior getEffectiveUndefValueBehavior() const;
   NaNPropagationBehavior getEffectiveNaNPropagationBehavior() const;
+  bool fuseMultiplyAdd() const { return fusedMultiplyAdd; }
   void setUndefValueBehavior(UndefValueBehavior UB) { UndefBehavior = UB; }
   void setNaNPropagationBehavior(NaNPropagationBehavior NaNBehav) {
     NaNBehavior = NaNBehav;
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 212e1626c1b91..acde46b5b3c01 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -165,7 +165,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   }
 
   APFloat handleDenormal(APFloat Val, DenormalMode::DenormalModeKind Mode,
-                         bool IsInput = false) {
+                         bool IsInput) {
     if (!Val.isDenormal())
       return Val;
     if (IsInput) {
@@ -394,6 +394,31 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                        });
   }
 
+  AnyValue
+  visitFPUnOpWithResult(Type *RetTy, const FastMathFlags &FMF,
+                        const AnyValue &Operand,
+                        function_ref<APFloat(const APFloat &)> ScalarFn) {
+    return computeUnOp(
+        RetTy, Operand, [&](const AnyValue &OperandInner) -> AnyValue {
+          if (OperandInner.isPoison())
+            return AnyValue::poison();
+
+          // We don't flush denormals here since the only
+          // floating-point unary operation is fneg. And fneg is
+          // specified as a bitwise operation which only flips
+          // the sign bit of the input.
+
+          AnyValue ValidatedOperand =
+              handleFMFFlags(OperandInner, FMF, /*IsInput=*/true);
+          if (ValidatedOperand.isPoison())
+            return ValidatedOperand;
+
+          APFloat Result = ScalarFn(ValidatedOperand.asFloat());
+
+          return handleFMFFlags(Result, FMF, /*IsInput=*/false);
+        });
+  }
+
   AnyValue computeBinOp(
       Type *Ty, const AnyValue &LHS, const AnyValue &RHS,
       function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
@@ -448,14 +473,16 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         return ValidatedRHS;
 
       // Flush input denormals
-      APFloat FLHS = handleDenormal(ValidatedLHS.asFloat(), DenormMode.Input);
-      APFloat FRHS = handleDenormal(ValidatedRHS.asFloat(), DenormMode.Input);
+      APFloat FLHS = handleDenormal(ValidatedLHS.asFloat(), DenormMode.Input,
+                                    /*IsInput=*/true);
+      APFloat FRHS = handleDenormal(ValidatedRHS.asFloat(), DenormMode.Input,
+                                    /*IsInput=*/true);
 
       APFloat RawResult = ScalarFn(FLHS, FRHS);
 
       // Flush output denormals and handle fast-math flags.
       AnyValue FResult = handleFMFFlags(
-          handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/true), FMF,
+          handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/false), FMF,
           /*IsInput=*/false);
 
       if (FResult.isPoison())
@@ -510,6 +537,53 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
                        AnyValue(std::move(OverflowVec))};
   }
 
+  AnyValue visitFPBinOpWithResult(
+      Type *RetTy, const FastMathFlags &FMF, const AnyValue &LHS,
+      const AnyValue &RHS,
+      function_ref<APFloat(const APFloat &, const APFloat &)> ScalarFn) {
+    DenormalMode DenormMode = getCurrentDenormalMode(RetTy);
+
+    if (!Ctx.isDefaultFPEnv())
+      reportImmediateUB() << "Non-constrained floating-point operation assumes "
+                             "default floating-point environment";
+
+    return computeBinOp(
+        RetTy, LHS, RHS,
+        [&](const AnyValue &LHSInner, const AnyValue &RHSInner) -> AnyValue {
+          if (LHSInner.isPoison() || RHSInner.isPoison())
+            return AnyValue::poison();
+
+          AnyValue ValidatedLHS =
+              handleFMFFlags(LHSInner, FMF, /*IsInput=*/true);
+          AnyValue ValidatedRHS =
+              handleFMFFlags(RHSInner, FMF, /*IsInput=*/true);
+          if (ValidatedLHS.isPoison())
+            return ValidatedLHS;
+          if (ValidatedRHS.isPoison())
+            return ValidatedRHS;
+
+          // Flush input denormals
+          APFloat FLHS = handleDenormal(ValidatedLHS.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
+          APFloat FRHS = handleDenormal(ValidatedRHS.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
+
+          APFloat RawResult = ScalarFn(FLHS, FRHS);
+
+          // Flush output denormals and handle fast-math flags.
+          AnyValue FResult = handleFMFFlags(
+              handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/false),
+              FMF,
+              /*IsInput=*/false);
+
+          if (FResult.isPoison())
+            return FResult;
+
+          APFloat Result = FResult.asFloat();
+          return applyNaNPropagation(Result, {&FLHS, &FRHS});
+        });
+  }
+
   AnyValue
   computeTriOp(Type *Ty, const AnyValue &Op1, const AnyValue &Op2,
                const AnyValue &Op3,
@@ -569,6 +643,61 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         });
   }
 
+  AnyValue visitFPTriOpWithResult(
+      Type *RetTy, const FastMathFlags &FMF, const AnyValue &Op1,
+      const AnyValue &Op2, const AnyValue &Op3,
+      function_ref<APFloat(const APFloat &, const APFloat &, const APFloat &)>
+          ScalarFn) {
+    DenormalMode DenormMode = getCurrentDenormalMode(RetTy);
+
+    if (!Ctx.isDefaultFPEnv())
+      reportImmediateUB() << "Non-constrained floating-point operation assumes "
+                             "default floating-point environment";
+
+    return computeTriOp(
+        RetTy, Op1, Op2, Op3,
+        [&](const AnyValue &Op1Inner, const AnyValue &Op2Inner,
+            const AnyValue &Op3Inner) -> AnyValue {
+          if (Op1Inner.isPoison() || Op2Inner.isPoison() || Op3Inner.isPoison())
+            return AnyValue::poison();
+
+          AnyValue ValidatedOp1 =
+              handleFMFFlags(Op1Inner, FMF, /*IsInput=*/true);
+          AnyValue ValidatedOp2 =
+              handleFMFFlags(Op2Inner, FMF, /*IsInput=*/true);
+          AnyValue ValidatedOp3 =
+              handleFMFFlags(Op3Inner, FMF, /*IsInput=*/true);
+          if (ValidatedOp1.isPoison())
+            return ValidatedOp1;
+          if (ValidatedOp2.isPoison())
+            return ValidatedOp2;
+          if (ValidatedOp3.isPoison())
+            return ValidatedOp3;
+
+          // Flush input denormals
+          APFloat FOp1 = handleDenormal(ValidatedOp1.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
+          APFloat FOp2 = handleDenormal(ValidatedOp2.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
+          APFloat FOp3 = handleDenormal(ValidatedOp3.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
+
+          APFloat RawResult = ScalarFn(FOp1, FOp2, FOp3);
+
+          // Flush output denormals and handle fast-math flags.
+          AnyValue FResult = handleFMFFlags(
+              handleDenormal(RawResult, DenormMode.Output, /*IsInput=*/false),
+              FMF,
+              /*IsInput=*/false);
+
+          if (FResult.isPoison())
+            return FResult;
+
+          APFloat Result = FResult.asFloat();
+          return applyNaNPropagation(Result, {&FOp1, &FOp2, &FOp3});
+        });
+  }
+
   void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
     if (!Handler.onBBJump(Terminator, *DestBB)) {
       setFailed();
@@ -696,6 +825,14 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
         I.getOperand(0)->getType()->getScalarType()->getFltSemantics());
   }
 
+  DenormalMode getCurrentDenormalMode(Type *Ty) {
+    if (Ty->isFPOrFPVectorTy()) {
+      return CurrentFrame->Func.getDenormalMode(
+          Ty->getScalarType()->getFltSemantics());
+    }
+    return DenormalMode::getDefault();
+  }
+
   // Helper function to convert BooleanKind to bool. Report an immediate UB if
   // a poison is found.
   bool getBooleanNonPoison(BooleanKind Boolean) {
@@ -816,6 +953,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
   AnyValue callIntrinsic(CallBase &CB, ArrayRef<AnyValue> Args) {
     Intrinsic::ID IID = CB.getIntrinsicID();
     Type *RetTy = CB.getType();
+    const FastMathFlags FMF =
+        isa<FPMathOperator>(CB) ? CB.getFastMathFlags() : FastMathFlags();
 
     switch (IID) {
     case Intrinsic::assume:
@@ -1304,6 +1443,159 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       }
       return std::move(Res);
     }
+    case Intrinsic::vector_reduce_fadd:
+    case Intrinsic::vector_reduce_fmul:
+    case Intrinsic::vector_reduce_fmax:
+    case Intrinsic::vector_reduce_fmin:
+    case Intrinsic::vector_reduce_fmaximum:
+    case Intrinsic::vector_reduce_fminimum: {
+      const auto DenormMode = getCurrentDenormalMode(RetTy);
+      const bool HasStart =
+          IID == Intrinsic::vector_reduce_fadd ||
+          IID == Intrinsic::vector_reduce_fmul;
+      const AnyValue &Vector = HasStart ? Args[1] : Args[0];
+      std::optional<APFloat> Res;
+      if (HasStart) {
+        if (Args[0].isPoison())
+          return AnyValue::poison();
+        const AnyValue ValidatedStart =
+            handleFMFFlags(Args[0], FMF, /*IsInput=*/true);
+        if (ValidatedStart.isPoison())
+          return AnyValue::poison();
+        Res = handleDenormal(ValidatedStart.asFloat(), DenormMode.Input,
+                             /*IsInput=*/true);
+      }
+      for (const auto &V : Vector.asAggregate()) {
+        if (V.isPoison()) {
+          Res.reset();
+          break;
+        }
+        const AnyValue ValidatedOp =
+            handleFMFFlags(V, FMF, /*IsInput=*/true);
+        if (ValidatedOp.isPoison()) {
+          Res.reset();
+          break;
+        }
+        APFloat Op = handleDenormal(ValidatedOp.asFloat(), DenormMode.Input,
+                                    /*IsInput=*/true);
+        if (!Res) {
+          Res = std::move(Op);
+          continue;
+        }
+        switch (IID) {
+        case Intrinsic::vector_reduce_fadd:
+          *Res = *Res + Op;
+          break;
+        case Intrinsic::vector_reduce_fmul:
+          *Res = *Res * Op;
+          break;
+        case Intrinsic::vector_reduce_fmax:
+          *Res = maxnum(*Res, Op);
+          break;
+        case Intrinsic::vector_reduce_fmin:
+          *Res = minnum(*Res, Op);
+          break;
+        case Intrinsic::vector_reduce_fmaximum:
+          *Res = maximum(*Res, Op);
+          break;
+        case Intrinsic::vector_reduce_fminimum:
+          *Res = minimum(*Res, Op);
+          break;
+        default:
+          llvm_unreachable("Unexpected intrinsic ID");
+        }
+      }
+      return Res.has_value()
+                 ? handleDenormal(handleFMFFlags(std::move(*Res), FMF,
+                                                 /*IsInput=*/false)
+                                      .asFloat(),
+                                  DenormMode.Output,
+                                  /*IsInput=*/false)
+                 : AnyValue::poison();
+    }
+    case Intrinsic::fabs: {
+      return visitFPUnOpWithResult(
+          RetTy, FMF, Args[0],
+          [](const APFloat &Operand) -> APFloat { return abs(Operand); });
+    }
+    case Intrinsic::fma: {
+      return visitFPTriOpWithResult(
+          RetTy, FMF, Args[0], Args[1], Args[2],
+          [](const APFloat &Op1, const APFloat &Op2,
+             const APFloat &Op3) -> APFloat {
+            auto Res = Op1;
+            Res.fusedMultiplyAdd(Op2, Op3, RoundingMode::NearestTiesToEven);
+            return Res;
+          });
+    }
+    case Intrinsic::fmuladd: {
+      return visitFPTriOpWithResult(
+          RetTy, FMF, Args[0], Args[1], Args[2],
+          [&](const APFloat &Op1, const APFloat &Op2,
+              const APFloat &Op3) -> APFloat {
+            if (Ctx.fuseMultiplyAdd()) {
+              auto Res = Op1;
+              Res.fusedMultiplyAdd(Op2, Op3, RoundingMode::NearestTiesToEven);
+              return Res;
+            }
+            return Op1 * Op2 + Op3;
+          });
+    }
+    case Intrinsic::is_fpclass: {
+      if (Args[1].isPoison())
+        return AnyValue::poison();
+      const FPClassTest Mask =
+          static_cast<FPClassTest>(Args[1].asInteger().getZExtValue());
+      return computeUnOp(RetTy, Args[0], [&](const AnyValue &Op) -> AnyValue {
+        if (Op.isPoison())
+          return AnyValue::poison();
+        return AnyValue::boolean(
+            static_cast<bool>(Op.asFloat().classify() & Mask));
+      });
+    }
+    case Intrinsic::copysign:
+    case Intrinsic::maxnum:
+    case Intrinsic::minnum:
+    case Intrinsic::maximum:
+    case Intrinsic::minimum:
+    case Intrinsic::maximumnum:
+    case Intrinsic::minimumnum: {
+      return visitFPBinOpWithResult(
+          RetTy, FMF, Args[0], Args[1],
+          [IID](const APFloat &LHS, const APFloat &RHS) -> APFloat {
+            switch (IID) {
+            case Intrinsic::copysign:
+              return APFloat::copySign(LHS, RHS);
+            case Intrinsic::maxnum:
+              return maxnum(LHS, RHS);
+            case Intrinsic::minnum:
+              return minnum(LHS, RHS);
+            case Intrinsic::maximum:
+              return maximum(LHS, RHS);
+            case Intrinsic::minimum:
+              return minimum(LHS, RHS);
+            case Intrinsic::maximumnum:
+              return maximumnum(LHS, RHS);
+            case Intrinsic::minimumnum:
+              return minimumnum(LHS, RHS);
+            default:
+              llvm_unreachable("Unexpected intrinsic ID");
+            }
+          });
+    }
+    case Intrinsic::fptosi_sat:
+    case Intrinsic::fptoui_sat: {
+      const auto BitWidth = RetTy->getScalarSizeInBits();
+      return computeUnOp(RetTy, Args[0], [&](const AnyValue &Op) -> AnyValue {
+        if (Op.isPoison())
+          return AnyValue::poison();
+        const APFloat &Operand = Op.asFloat();
+        APSInt V(BitWidth, IID == Intrinsic::fptoui_sat);
+        [[maybe_unused]] bool IsExact;
+        Operand.convertToInteger(V, APFloat::rmTowardZero, &IsExact);
+        return V;
+      });
+    }
     default:
       Handler.onUnrecognizedInstruction(CB);
       setFailed();
@@ -1776,8 +2068,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
       if (ValidatedOperand.isPoison())
         return ValidatedOperand;
 
-      APFloat FOperand =
-          handleDenormal(ValidatedOperand.asFloat(), DenormMode.Input);
+      APFloat FOperand = handleDenormal(ValidatedOperand.asFloat(),
+                                        DenormMode.Input, /*IsInput=*/true);
       APFloat SourceNaN = FOperand;
 
       bool LosesInfo;
@@ -1931,8 +2223,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           ValidateRes.isPoison())
         return ValidateRes;
 
-      APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
-      APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
+      APFloat FLHS =
+          handleDenormal(LHS.asFloat(), DenormMode.Input, /*IsInput=*/true);
+      APFloat FRHS =
+          handleDenormal(RHS.asFloat(), DenormMode.Input, /*IsInput=*/true);
 
       return AnyValue::boolean(FCmpInst::compare(FLHS, FRHS, I.getPredicate()));
     });
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 396ace887d0fc..d92e4de691e14 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -84,6 +84,11 @@ static cl::opt<bool>
                   cl::desc("Disable interpreter-introduced non-determinism."),
                   cl::init(false), cl::cat(InterpreterCategory));
 
+static cl::opt<bool>
+    FuseMultiplyAdd("fuse-multiply-add",
+                  cl::desc("Fuse floating-point multiply add operation"),
+                  cl::init(false), cl::cat(InterpreterCategory));
+
 cl::opt<ubi::UndefValueBehavior> UndefBehavior(
     "", cl::desc("Choose undef value behavior:"),
     cl::values(clEnumVal(ubi::UndefValueBehavior::NonDeterministic,
@@ -234,6 +239,7 @@ int main(int argc, char **argv) {
   Ctx.setVScale(VScale);
   Ctx.setMaxSteps(MaxSteps);
   Ctx.setMaxStackDepth(MaxStackDepth);
+  Ctx.setFusedMultiplyAdd(FuseMultiplyAdd);
   Ctx.setDeterministic(Deterministic);
   Ctx.setUndefValueBehavior(UndefBehavior);
   Ctx.setNaNPropagationBehavior(NaNPropagationBehavior);

>From be86eca689b44a0e71a1fffb9628463cb6d9d0c2 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 6 May 2026 17:43:42 +0800
Subject: [PATCH 2/2] [llubi] Fix copysign and NaN propagation in FP vector
 reduction

---
 llvm/tools/llubi/lib/Interpreter.cpp | 54 ++++++++++++++++++++--------
 1 file changed, 39 insertions(+), 15 deletions(-)

diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index acde46b5b3c01..7df6ff9acd202 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1450,9 +1450,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
     case Intrinsic::vector_reduce_fmaximum:
     case Intrinsic::vector_reduce_fminimum: {
       const auto DenormMode = getCurrentDenormalMode(RetTy);
-      const bool HasStart =
-          IID == Intrinsic::vector_reduce_fadd ||
-          IID == Intrinsic::vector_reduce_fmul;
+      const bool HasStart = IID == Intrinsic::vector_reduce_fadd ||
+                            IID == Intrinsic::vector_reduce_fmul;
       const AnyValue &Vector = HasStart ? Args[1] : Args[0];
       std::optional<APFloat> Res;
       if (HasStart) {
@@ -1470,8 +1469,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           Res.reset();
           break;
         }
-        const AnyValue ValidatedOp =
-            handleFMFFlags(V, FMF, /*IsInput=*/true);
+        const AnyValue ValidatedOp = handleFMFFlags(V, FMF, /*IsInput=*/true);
         if (ValidatedOp.isPoison()) {
           Res.reset();
           break;
@@ -1505,13 +1503,24 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           llvm_unreachable("Unexpected intrinsic ID");
         }
       }
-      return Res.has_value()
-                 ? handleDenormal(handleFMFFlags(std::move(*Res), FMF,
-                                                 /*IsInput=*/false)
-                                      .asFloat(),
-                                  DenormMode.Output,
-                                  /*IsInput=*/false)
-                 : AnyValue::poison();
+      if (Res.has_value()) {
+        const AnyValue ValidatedRes =
+            handleFMFFlags(*Res, FMF, /*IsInput=*/false);
+        if (ValidatedRes.isPoison()) {
+          Res.reset();
+        }
+        const APFloat FRes =
+            handleDenormal(ValidatedRes.asFloat(), DenormMode.Output,
+                           /*IsInput=*/false);
+        std::vector<AnyValue> RawInputVec = Vector.asAggregate();
+        std::vector<const APFloat *> InputVec;
+        InputVec.reserve(RawInputVec.size());
+        transform(
+            RawInputVec, std::back_inserter(InputVec),
+            [](const AnyValue &V) -> const APFloat * { return &V.asFloat(); });
+        return applyNaNPropagation(FRes, InputVec);
+      }
+      return AnyValue::poison();
     }
     case Intrinsic::fabs: {
       return visitFPUnOpWithResult(
@@ -1553,7 +1562,24 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
             static_cast<bool>(Op.asFloat().classify() & Mask));
       });
     }
-    case Intrinsic::copysign:
+    case Intrinsic::copysign: {
+      return computeBinOp(
+          RetTy, Args[0], Args[1],
+          [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+            if (LHS.isPoison() || RHS.isPoison())
+              return AnyValue::poison();
+            const AnyValue ValidatedLHS =
+                handleFMFFlags(LHS, FMF, /*IsInput=*/true);
+            const AnyValue ValidatedRHS =
+                handleFMFFlags(RHS, FMF, /*IsInput=*/true);
+            if (ValidatedLHS.isPoison() || ValidatedRHS.isPoison())
+              return AnyValue::poison();
+
+            return handleFMFFlags(APFloat::copySign(ValidatedLHS.asFloat(),
+                                                    ValidatedRHS.asFloat()),
+                                  FMF, /*IsInput=*/false);
+          });
+    }
     case Intrinsic::maxnum:
     case Intrinsic::minnum:
     case Intrinsic::maximum:
@@ -1564,8 +1590,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
           RetTy, FMF, Args[0], Args[1],
           [IID](const APFloat &LHS, const APFloat &RHS) -> APFloat {
             switch (IID) {
-            case Intrinsic::copysign:
-              return APFloat::copySign(LHS, RHS);
             case Intrinsic::maxnum:
               return maxnum(LHS, RHS);
             case Intrinsic::minnum:



More information about the llvm-commits mailing list