[llvm] [llubi] Initial support for floating-point numbers (PR #188453)
Zhige Chen via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 2 04:06:17 PDT 2026
https://github.com/nofe1248 updated https://github.com/llvm/llvm-project/pull/188453
>From 0b1c62180b78d09449f14f5a8f575be5b02147c1 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 25 Mar 2026 14:46:09 +0800
Subject: [PATCH 01/20] [llubi] Initial support for floating-point numbers
---
llvm/test/tools/llubi/fp_arith.ll | 33 +++
llvm/test/tools/llubi/fp_cast.ll | 29 +++
llvm/test/tools/llubi/fp_cmp.ll | 25 +++
llvm/test/tools/llubi/fp_denorm.ll | 15 ++
llvm/test/tools/llubi/fp_fastmath.ll | 18 ++
llvm/tools/llubi/lib/Interpreter.cpp | 296 ++++++++++++++++++++++++++-
6 files changed, 415 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/tools/llubi/fp_arith.ll
create mode 100644 llvm/test/tools/llubi/fp_cast.ll
create mode 100644 llvm/test/tools/llubi/fp_cmp.ll
create mode 100644 llvm/test/tools/llubi/fp_denorm.ll
create mode 100644 llvm/test/tools/llubi/fp_fastmath.ll
diff --git a/llvm/test/tools/llubi/fp_arith.ll b/llvm/test/tools/llubi/fp_arith.ll
new file mode 100644
index 0000000000000..2fd09ac53b1a2
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith.ll
@@ -0,0 +1,33 @@
+; 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() {
+ %add = fadd float 1.5, 2.5
+ %sub = fsub double 5.0, 1.0
+ %mul = fmul float 2.0, 3.0
+ %div = fdiv double 1.0, 2.0
+ %neg = fneg float -1.5
+
+ %vadd = fadd <2 x float> <float 1.0, float 2.0>, <float 3.0, float 4.0>
+
+ %p1 = fadd float 1.0, poison
+ %p2 = fneg float poison
+
+ %inf = fdiv float 1.0, 0.0
+ %nan = fdiv float 0.0, 0.0
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd float 1.500000e+00, 2.500000e+00 => float 4.000000e+00
+; CHECK-NEXT: %sub = fsub double 5.000000e+00, 1.000000e+00 => double 4.000000e+00
+; CHECK-NEXT: %mul = fmul float 2.000000e+00, 3.000000e+00 => float 6.000000e+00
+; CHECK-NEXT: %div = fdiv double 1.000000e+00, 2.000000e+00 => double 5.000000e-01
+; CHECK-NEXT: %neg = fneg float -1.500000e+00 => float 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x float> <float 1.000000e+00, float 2.000000e+00>, <float 3.000000e+00, float 4.000000e+00> => { float 4.000000e+00, float 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd float 1.000000e+00, poison => poison
+; CHECK-NEXT: %p2 = fneg float poison => poison
+; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_cast.ll b/llvm/test/tools/llubi/fp_cast.ll
new file mode 100644
index 0000000000000..bab205f1c5891
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_cast.ll
@@ -0,0 +1,29 @@
+; 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() {
+ %ext = fpext float 2.0 to double
+ %trunc = fptrunc double 3.5 to float
+
+ %si2fp = sitofp i32 -2 to float
+ %ui2fp = uitofp i32 255 to float
+
+ %fp2si = fptosi double -4.75 to i32
+ %fp2ui = fptoui double 4.75 to i32
+
+ %oob_ui = fptoui float -1.0 to i32
+ %nan_si = fptosi float 0x7FF8000000000000 to i32
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %ext = fpext float 2.000000e+00 to double => double 2.000000e+00
+; CHECK-NEXT: %trunc = fptrunc double 3.500000e+00 to float => float 3.500000e+00
+; CHECK-NEXT: %si2fp = sitofp i32 -2 to float => float -2.000000e+00
+; CHECK-NEXT: %ui2fp = uitofp i32 255 to float => float 2.550000e+02
+; CHECK-NEXT: %fp2si = fptosi double -4.750000e+00 to i32 => i32 -4
+; CHECK-NEXT: %fp2ui = fptoui double 4.750000e+00 to i32 => i32 4
+; CHECK-NEXT: %oob_ui = fptoui float -1.000000e+00 to i32 => poison
+; CHECK-NEXT: %nan_si = fptosi float 0x7FF8000000000000 to i32 => poison
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_cmp.ll b/llvm/test/tools/llubi/fp_cmp.ll
new file mode 100644
index 0000000000000..489a91f256e91
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_cmp.ll
@@ -0,0 +1,25 @@
+; 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() {
+ %eq = fcmp oeq float 1.0, 1.0
+ %ne = fcmp one float 1.0, 2.0
+ %gt = fcmp ogt float 2.0, 1.0
+
+ %nan = fdiv float 0.0, 0.0
+ %ord_nan = fcmp oeq float %nan, 1.0
+ %uno_nan = fcmp uno float %nan, 1.0
+ %une_nan = fcmp une float %nan, 1.0
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %eq = fcmp oeq float 1.000000e+00, 1.000000e+00 => T
+; CHECK-NEXT: %ne = fcmp one float 1.000000e+00, 2.000000e+00 => T
+; CHECK-NEXT: %gt = fcmp ogt float 2.000000e+00, 1.000000e+00 => T
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: %ord_nan = fcmp oeq float %nan, 1.000000e+00 => F
+; CHECK-NEXT: %uno_nan = fcmp uno float %nan, 1.000000e+00 => T
+; CHECK-NEXT: %une_nan = fcmp une float %nan, 1.000000e+00 => T
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_denorm.ll b/llvm/test/tools/llubi/fp_denorm.ll
new file mode 100644
index 0000000000000..982c172baf6c7
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_denorm.ll
@@ -0,0 +1,15 @@
+; 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() denormal_fpenv(positivezero|positivezero) {
+ %input_flush = fadd float 1.434929627468612680625899e-42, 0.0
+
+ %output_flush = fmul float 5.421010862427522170037264e-20, 5.421010862427522170037264e-20
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %input_flush = fadd float 0x3740000000000000, 0.000000e+00 => float 0.000000e+00
+; CHECK-NEXT: %output_flush = fmul float 0x3BF0000000000000, 0x3BF0000000000000 => float 0.000000e+00
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_fastmath.ll b/llvm/test/tools/llubi/fp_fastmath.ll
new file mode 100644
index 0000000000000..475f40d2b0457
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_fastmath.ll
@@ -0,0 +1,18 @@
+; 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() {
+ %bad_nan = fdiv nnan float 0.0, 0.0
+
+ %bad_inf = fdiv ninf float 1.0, 0.0
+
+ %safe = fadd nnan ninf float 1.0, 2.0
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %bad_nan = fdiv nnan float 0.000000e+00, 0.000000e+00 => poison
+; CHECK-NEXT: %bad_inf = fdiv ninf float 1.000000e+00, 0.000000e+00 => poison
+; CHECK-NEXT: %safe = fadd nnan ninf float 1.000000e+00, 2.000000e+00 => float 3.000000e+00
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 393391d52c89c..009f4aedeec2b 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -118,6 +118,29 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
return Res;
}
+static APFloat handleDenormal(APFloat Val,
+ DenormalMode::DenormalModeKind Mode) {
+ if (!Val.isDenormal())
+ return Val;
+ if (Mode == DenormalMode::PositiveZero)
+ return APFloat::getZero(Val.getSemantics(), false);
+ if (Mode == DenormalMode::PreserveSign)
+ return APFloat::getZero(Val.getSemantics(), Val.isNegative());
+ // Default case for IEEE, Dynamic, and Invalid
+ return Val;
+}
+
+static std::optional<APFloat> validateFMF(APFloat Val, FastMathFlags FMF) {
+ // The returned std::nullopt indicates that a poison value is produced.
+ if (FMF.noNaNs() && Val.isNaN())
+ return std::nullopt;
+ if (FMF.noInfs() && Val.isInfinity())
+ return std::nullopt;
+ // Since nsz (no signed zeros) is an optimization hint, so it doesn't requires
+ // runtime modification to maintain semantic correctness.
+ return Val;
+}
+
/// Instruction executor using the visitor pattern.
/// Unlike the Context class that manages the global state,
/// InstExecutor only maintains the state for call frames.
@@ -130,6 +153,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
bool Status;
Frame *CurrentFrame = nullptr;
AnyValue None;
+ APFloat::roundingMode CurrentRoundingMode;
void reportImmediateUB(StringRef Msg) {
// Check if we have already reported an immediate UB.
@@ -187,6 +211,33 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitFPUnOp(Instruction &I,
+ function_ref<AnyValue(const APFloat &)> ScalarFn) {
+ FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
+ DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
+ I.getType()->getScalarType()->getFltSemantics());
+
+ visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
+ if (Operand.isPoison())
+ return AnyValue::poison();
+
+ // Flush input denormals
+ APFloat FInput = handleDenormal(Operand.asFloat(), DenormMode.Input);
+
+ APFloat Result = ScalarFn(FInput).asFloat();
+
+ // Flush output denormals
+ APFloat FResult = handleDenormal(Result, DenormMode.Output);
+
+ if (auto ValidateRes = validateFMF(FResult, FMF)) {
+ return *ValidateRes;
+ }
+
+ // A poison value is produced when FMFs are violated
+ return AnyValue::poison();
+ });
+ }
+
AnyValue computeBinOp(
Type *Ty, const AnyValue &LHS, const AnyValue &RHS,
function_ref<AnyValue(const AnyValue &, const AnyValue &)> ScalarFn) {
@@ -219,6 +270,42 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitFPBinOp(
+ Instruction &I,
+ function_ref<AnyValue(const APFloat &, const APFloat &)> ScalarFn) {
+ FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
+ DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
+ I.getType()->getScalarType()->getFltSemantics());
+
+ visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+ if (LHS.isPoison() || RHS.isPoison())
+ return AnyValue::poison();
+
+ if (auto ValidateRes = validateFMF(LHS.asFloat(), FMF); !ValidateRes) {
+ return AnyValue::poison();
+ }
+ if (auto ValidateRes = validateFMF(RHS.asFloat(), FMF); !ValidateRes) {
+ return AnyValue::poison();
+ }
+
+ // Flush input denormals
+ APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
+ APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
+
+ APFloat Result = ScalarFn(FLHS, FRHS).asFloat();
+
+ // Flush output denormals
+ APFloat FResult = handleDenormal(Result, DenormMode.Output);
+
+ if (auto ValidateRes = validateFMF(FResult, FMF)) {
+ return *ValidateRes;
+ }
+
+ // A poison value is produced when FMFs are violated
+ return AnyValue::poison();
+ });
+ }
+
void jumpTo(Instruction &Terminator, BasicBlock *DestBB) {
if (!Handler.onBBJump(Terminator, *DestBB)) {
Status = false;
@@ -418,7 +505,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
- : Ctx(C), DL(Ctx.getDataLayout()), Handler(H), Status(true) {
+ : Ctx(C), DL(Ctx.getDataLayout()), Handler(H), Status(true),
+ CurrentRoundingMode(APFloat::rmNearestTiesToEven) {
CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
RetVal, Ctx.getTLIImpl());
}
@@ -767,6 +855,50 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitFAdd(BinaryOperator &I) {
+ visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ APFloat Res = LHS;
+ Res.add(RHS, CurrentRoundingMode);
+ return Res;
+ });
+ }
+
+ void visitFSub(BinaryOperator &I) {
+ visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ APFloat Res = LHS;
+ Res.subtract(RHS, CurrentRoundingMode);
+ return Res;
+ });
+ }
+
+ void visitFMul(BinaryOperator &I) {
+ visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ APFloat Res = LHS;
+ Res.multiply(RHS, CurrentRoundingMode);
+ return Res;
+ });
+ }
+
+ void visitFDiv(BinaryOperator &I) {
+ visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ APFloat Res = LHS;
+ Res.divide(RHS, CurrentRoundingMode);
+ return Res;
+ });
+ }
+
+ void visitFRem(BinaryOperator &I) {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ APFloat Res = LHS;
+ Res.mod(RHS);
+ return Res;
+ });
+ }
+
+ void visitFNeg(UnaryOperator &I) {
+ visitFPUnOp(I, [](const APFloat &Operand) -> AnyValue { return -Operand; });
+ }
+
void visitTruncInst(TruncInst &Trunc) {
visitIntUnOp(Trunc, [&](const APInt &Operand) -> AnyValue {
unsigned DestBW = Trunc.getType()->getScalarSizeInBits();
@@ -794,6 +926,77 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitFPExtInst(FPExtInst &FPExt) { visitFPConvInst(FPExt); }
+
+ void visitFPTruncInst(FPTruncInst &FPTrunc) { visitFPConvInst(FPTrunc); }
+
+ void visitFPConvInst(Instruction &I) {
+ const fltSemantics &DstSem =
+ I.getType()->getScalarType()->getFltSemantics();
+
+ visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
+ if (Operand.isPoison())
+ return AnyValue::poison();
+
+ APFloat Res = Operand.asFloat();
+ bool LosesInfo;
+ Res.convert(DstSem, CurrentRoundingMode, &LosesInfo);
+ return AnyValue(Res);
+ });
+ }
+
+ void visitFPToSIInst(FPToSIInst &FPToSI) {
+ visitFPToIntInst(FPToSI, /*IsUnsigned=*/false);
+ }
+
+ void visitFPToUIInst(FPToUIInst &FPToUI) {
+ visitFPToIntInst(FPToUI, /*IsUnsigned=*/true);
+ }
+
+ void visitFPToIntInst(Instruction &I, bool IsUnsigned) {
+ // Note: We DO NOT use CurrentRoundingMode here.
+ // Language specs require truncation towards zero for FP-to-Int conversions.
+ visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
+ if (Operand.isPoison())
+ return AnyValue::poison();
+
+ APSInt Res(I.getType()->getScalarSizeInBits(), /*isUnsigned=*/IsUnsigned);
+ bool IsExact;
+ APFloat::opStatus Status = Operand.asFloat().convertToInteger(
+ Res, APFloat::rmTowardZero, &IsExact);
+
+ if (Status == APFloat::opInvalidOp)
+ return AnyValue::poison();
+
+ return AnyValue(Res);
+ });
+ }
+
+ void visitSIToFPInst(SIToFPInst &SIToFP) {
+ visitIntToFPInst(SIToFP, /*IsSigned=*/true);
+ }
+
+ void visitUIToFPInst(UIToFPInst &UIToFP) {
+ visitIntToFPInst(UIToFP, /*IsSigned=*/false);
+ }
+
+ void visitIntToFPInst(Instruction &I, bool IsSigned) {
+ const fltSemantics &DstSem =
+ I.getType()->getScalarType()->getFltSemantics();
+
+ visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
+ if (Operand.isPoison())
+ return AnyValue::poison();
+
+ APFloat Res(DstSem);
+
+ Res.convertFromAPInt(Operand.asInteger(), /*IsSigned=*/IsSigned,
+ CurrentRoundingMode);
+
+ return AnyValue(Res);
+ });
+ }
+
void visitAnd(BinaryOperator &I) {
visitIntBinOp(I, [](const APInt &LHS, const APInt &RHS) -> AnyValue {
return LHS & RHS;
@@ -860,6 +1063,97 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
});
}
+ void visitFCmpInst(FCmpInst &I) {
+ DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
+ I.getOperand(0)->getType()->getScalarType()->getFltSemantics());
+ FastMathFlags FMF = I.getFastMathFlags();
+
+ visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
+ if (LHS.isPoison() || RHS.isPoison()) {
+ return AnyValue::poison();
+ }
+
+ if (auto ValidateRes = validateFMF(LHS.asFloat(), FMF); !ValidateRes) {
+ return AnyValue::poison();
+ }
+ if (auto ValidateRes = validateFMF(RHS.asFloat(), FMF); !ValidateRes) {
+ return AnyValue::poison();
+ }
+
+ APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
+ APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
+
+ APFloat::cmpResult CmpResult = FLHS.compare(FRHS);
+
+ bool Result = false;
+
+ switch (I.getPredicate()) {
+ case FCmpInst::FCMP_FALSE:
+ Result = false;
+ break;
+ case FCmpInst::FCMP_OEQ:
+ Result = (CmpResult == APFloat::cmpEqual);
+ break;
+ case FCmpInst::FCMP_OGT:
+ Result = (CmpResult == APFloat::cmpGreaterThan);
+ break;
+ case FCmpInst::FCMP_OGE:
+ Result = (CmpResult == APFloat::cmpEqual ||
+ CmpResult == APFloat::cmpGreaterThan);
+ break;
+ case FCmpInst::FCMP_OLT:
+ Result = (CmpResult == APFloat::cmpLessThan);
+ break;
+ case FCmpInst::FCMP_OLE:
+ Result = (CmpResult == APFloat::cmpEqual ||
+ CmpResult == APFloat::cmpLessThan);
+ break;
+ case FCmpInst::FCMP_ONE:
+ Result = (CmpResult == APFloat::cmpGreaterThan ||
+ CmpResult == APFloat::cmpLessThan);
+ break;
+ case FCmpInst::FCMP_ORD:
+ Result = (CmpResult != APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_UNO:
+ Result = (CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_UEQ:
+ Result = (CmpResult == APFloat::cmpEqual ||
+ CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_UGT:
+ Result = (CmpResult == APFloat::cmpGreaterThan ||
+ CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_UGE:
+ Result = (CmpResult == APFloat::cmpEqual ||
+ CmpResult == APFloat::cmpGreaterThan ||
+ CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_ULT:
+ Result = (CmpResult == APFloat::cmpLessThan ||
+ CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_ULE:
+ Result = (CmpResult == APFloatBase::cmpEqual ||
+ CmpResult == APFloat::cmpLessThan ||
+ CmpResult == APFloat::cmpUnordered);
+ break;
+ case FCmpInst::FCMP_UNE:
+ Result = (CmpResult != APFloatBase::cmpEqual);
+ break;
+ case FCmpInst::FCMP_TRUE:
+ Result = true;
+ break;
+ default:
+ llvm_unreachable("Invalid FCmp predicate");
+ }
+
+ return AnyValue::boolean(Result);
+ });
+ }
+
void visitSelect(SelectInst &SI) {
// TODO: handle fast-math flags.
if (SI.getCondition()->getType()->isIntegerTy(1)) {
>From de1a32af17e08b4c24bc69372272eabde531a31c Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Wed, 25 Mar 2026 16:36:27 +0800
Subject: [PATCH 02/20] [llubi] Handle fast-math flags in phi nodes and select
---
llvm/test/tools/llubi/fp_phi_select.ll | 33 +++++++
llvm/tools/llubi/lib/Interpreter.cpp | 124 ++++++++++++++-----------
2 files changed, 104 insertions(+), 53 deletions(-)
create mode 100644 llvm/test/tools/llubi/fp_phi_select.ll
diff --git a/llvm/test/tools/llubi/fp_phi_select.ll b/llvm/test/tools/llubi/fp_phi_select.ll
new file mode 100644
index 0000000000000..9c0685a4d1cbc
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_phi_select.ll
@@ -0,0 +1,33 @@
+; 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() {
+entry:
+ %nan = fdiv float 0.0, 0.0
+ %inf = fdiv float 1.0, 0.0
+
+ %safe_sel = select i1 true, float %nan, float 1.0
+ %bad_sel_nan = select nnan i1 true, float %nan, float 1.0
+ %bad_sel_inf = select ninf i1 false, float 1.0, float %inf
+
+ br label %phi.test
+
+phi.test:
+ %safe_phi = phi float [ %nan, %entry ]
+ %bad_phi_nan = phi nnan float [ %nan, %entry ]
+ %bad_phi_inf = phi ninf float [ %inf, %entry ]
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
+; CHECK-NEXT: %safe_sel = select i1 true, float %nan, float 1.000000e+00 => float NaN
+; CHECK-NEXT: %bad_sel_nan = select nnan i1 true, float %nan, float 1.000000e+00 => poison
+; CHECK-NEXT: %bad_sel_inf = select ninf i1 false, float 1.000000e+00, float %inf => poison
+; CHECK-NEXT: br label %phi.test jump to %phi.test
+; CHECK-NEXT: %safe_phi = phi float [ %nan, %entry ] => float NaN
+; CHECK-NEXT: %bad_phi_nan = phi nnan float [ %nan, %entry ] => poison
+; CHECK-NEXT: %bad_phi_inf = phi ninf float [ %inf, %entry ] => poison
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 009f4aedeec2b..e187cd348384f 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -130,12 +130,24 @@ static APFloat handleDenormal(APFloat Val,
return Val;
}
-static std::optional<APFloat> validateFMF(APFloat Val, FastMathFlags FMF) {
- // The returned std::nullopt indicates that a poison value is produced.
- if (FMF.noNaNs() && Val.isNaN())
- return std::nullopt;
- if (FMF.noInfs() && Val.isInfinity())
- return std::nullopt;
+static AnyValue handleFMFFlags(AnyValue Val, FastMathFlags FMF) {
+ if (Val.isPoison() || Val.isNone())
+ return AnyValue::poison();
+
+ if (Val.isAggregate()) {
+ std::vector<AnyValue> ResVec;
+ ResVec.reserve(Val.asAggregate().size());
+ for (const auto &A : Val.asAggregate()) {
+ ResVec.push_back(handleFMFFlags(A, FMF));
+ }
+ return AnyValue(ResVec);
+ }
+
+ const APFloat &APVal = Val.asFloat();
+ if (FMF.noNaNs() && APVal.isNaN())
+ return AnyValue::poison();
+ if (FMF.noInfs() && APVal.isInfinity())
+ return AnyValue::poison();
// Since nsz (no signed zeros) is an optimization hint, so it doesn't requires
// runtime modification to maintain semantic correctness.
return Val;
@@ -229,12 +241,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
// Flush output denormals
APFloat FResult = handleDenormal(Result, DenormMode.Output);
- if (auto ValidateRes = validateFMF(FResult, FMF)) {
- return *ValidateRes;
- }
-
- // A poison value is produced when FMFs are violated
- return AnyValue::poison();
+ return handleFMFFlags(FResult, FMF);
});
}
@@ -281,11 +288,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
- if (auto ValidateRes = validateFMF(LHS.asFloat(), FMF); !ValidateRes) {
- return AnyValue::poison();
+ if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison()) {
+ return ValidateRes;
}
- if (auto ValidateRes = validateFMF(RHS.asFloat(), FMF); !ValidateRes) {
- return AnyValue::poison();
+ if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison()) {
+ return ValidateRes;
}
// Flush input denormals
@@ -297,12 +304,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
// Flush output denormals
APFloat FResult = handleDenormal(Result, DenormMode.Output);
- if (auto ValidateRes = validateFMF(FResult, FMF)) {
- return *ValidateRes;
- }
-
- // A poison value is produced when FMFs are violated
- return AnyValue::poison();
+ return handleFMFFlags(FResult, FMF);
});
}
@@ -321,9 +323,16 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
SmallVector<std::pair<PHINode *, AnyValue>> IncomingValues;
PHINode *PHI = nullptr;
while ((PHI = dyn_cast<PHINode>(CurrentFrame->PC))) {
- Value *Incoming = PHI->getIncomingValueForBlock(From);
- // TODO: handle fast-math flags.
- IncomingValues.emplace_back(PHI, getValue(Incoming));
+ AnyValue IncomingVal = getValue(PHI->getIncomingValueForBlock(From));
+
+ // Fast-math flags validation
+ if (isa<FPMathOperator>(PHI)) {
+ FastMathFlags FMF = PHI->getFastMathFlags();
+ if (FMF.any())
+ IncomingVal = handleFMFFlags(std::move(IncomingVal), FMF);
+ }
+
+ IncomingValues.emplace_back(PHI, IncomingVal);
++CurrentFrame->PC;
}
for (auto &[K, V] : IncomingValues)
@@ -1073,11 +1082,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return AnyValue::poison();
}
- if (auto ValidateRes = validateFMF(LHS.asFloat(), FMF); !ValidateRes) {
- return AnyValue::poison();
+ if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison()) {
+ return ValidateRes;
}
- if (auto ValidateRes = validateFMF(RHS.asFloat(), FMF); !ValidateRes) {
- return AnyValue::poison();
+ if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison()) {
+ return ValidateRes;
}
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
@@ -1155,40 +1164,49 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitSelect(SelectInst &SI) {
- // TODO: handle fast-math flags.
+ AnyValue Res;
+
if (SI.getCondition()->getType()->isIntegerTy(1)) {
switch (getValue(SI.getCondition()).asBoolean()) {
case BooleanKind::True:
- setResult(SI, getValue(SI.getTrueValue()));
- return;
- case BooleanKind::False:
- setResult(SI, getValue(SI.getFalseValue()));
- return;
- case BooleanKind::Poison:
- setResult(SI, AnyValue::getPoisonValue(Ctx, SI.getType()));
- return;
- }
- }
-
- auto &Cond = getValue(SI.getCondition()).asAggregate();
- auto &TV = getValue(SI.getTrueValue()).asAggregate();
- auto &FV = getValue(SI.getFalseValue()).asAggregate();
- std::vector<AnyValue> Res;
- size_t Len = Cond.size();
- Res.reserve(Len);
- for (uint32_t I = 0; I != Len; ++I) {
- switch (Cond[I].asBoolean()) {
- case BooleanKind::True:
- Res.push_back(TV[I]);
+ Res = getValue(SI.getTrueValue());
break;
case BooleanKind::False:
- Res.push_back(FV[I]);
+ Res = getValue(SI.getFalseValue());
break;
case BooleanKind::Poison:
- Res.push_back(AnyValue::poison());
+ Res = AnyValue::getPoisonValue(Ctx, SI.getType());
break;
}
+ } else {
+ auto &Cond = getValue(SI.getCondition()).asAggregate();
+ auto &TV = getValue(SI.getTrueValue()).asAggregate();
+ auto &FV = getValue(SI.getFalseValue()).asAggregate();
+ std::vector<AnyValue> ResVec;
+ size_t Len = Cond.size();
+ ResVec.reserve(Len);
+ for (uint32_t I = 0; I != Len; ++I) {
+ switch (Cond[I].asBoolean()) {
+ case BooleanKind::True:
+ ResVec.push_back(TV[I]);
+ break;
+ case BooleanKind::False:
+ ResVec.push_back(FV[I]);
+ break;
+ case BooleanKind::Poison:
+ ResVec.push_back(AnyValue::poison());
+ break;
+ }
+ }
+ Res = AnyValue(std::move(ResVec));
}
+
+ // Handle fast-math flags
+ if (auto *FPMO = dyn_cast<FPMathOperator>(&SI)) {
+ if (FastMathFlags FMF = FPMO->getFastMathFlags(); FMF.any())
+ Res = handleFMFFlags(std::move(Res), FMF);
+ }
+
setResult(SI, std::move(Res));
}
>From 5c505b3d1b86d4a9b18d734ad1b36aa7aeef2b4f Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 13:37:11 +0800
Subject: [PATCH 03/20] [llubi] Minor fixes to floating-point numbers
---
llvm/tools/llubi/lib/Context.cpp | 5 +
llvm/tools/llubi/lib/Context.h | 2 +
llvm/tools/llubi/lib/Interpreter.cpp | 145 +++++++--------------------
3 files changed, 46 insertions(+), 106 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index 968ac4f561558..ca7105153a34c 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -499,6 +499,11 @@ uint64_t Context::getEffectiveTypeStoreSize(Type *Ty) {
return getEffectiveTypeSize(DL.getTypeStoreSize(Ty));
}
+bool Context::getRandomBool() {
+ // We use the lowest bit of the raw bits from RNG as the result:
+ return static_cast<bool>(Rng() & 1);
+}
+
void MemoryObject::markAsFreed() {
State = MemoryObjectState::Freed;
Bytes.clear();
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index d1960b270d9bd..fcd6a3ad4485a 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -262,6 +262,8 @@ class Context {
/// error occurred during execution.
bool runFunction(Function &F, ArrayRef<AnyValue> Args, AnyValue &RetVal,
EventHandler &Handler);
+
+ bool getRandomBool();
};
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index e187cd348384f..e743193630957 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -127,29 +127,8 @@ static APFloat handleDenormal(APFloat Val,
if (Mode == DenormalMode::PreserveSign)
return APFloat::getZero(Val.getSemantics(), Val.isNegative());
// Default case for IEEE, Dynamic, and Invalid
- return Val;
-}
-
-static AnyValue handleFMFFlags(AnyValue Val, FastMathFlags FMF) {
- if (Val.isPoison() || Val.isNone())
- return AnyValue::poison();
-
- if (Val.isAggregate()) {
- std::vector<AnyValue> ResVec;
- ResVec.reserve(Val.asAggregate().size());
- for (const auto &A : Val.asAggregate()) {
- ResVec.push_back(handleFMFFlags(A, FMF));
- }
- return AnyValue(ResVec);
- }
-
- const APFloat &APVal = Val.asFloat();
- if (FMF.noNaNs() && APVal.isNaN())
- return AnyValue::poison();
- if (FMF.noInfs() && APVal.isInfinity())
- return AnyValue::poison();
- // Since nsz (no signed zeros) is an optimization hint, so it doesn't requires
- // runtime modification to maintain semantic correctness.
+ // Currently we treat Dynamic the same as IEEE, since we don't support
+ // changing the mode at this point.
return Val;
}
@@ -196,6 +175,31 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
}
+ AnyValue handleFMFFlags(AnyValue Val, FastMathFlags FMF) {
+ if (Val.isPoison())
+ return AnyValue::poison();
+
+ if (Val.isAggregate()) {
+ std::vector<AnyValue> ResVec;
+ ResVec.reserve(Val.asAggregate().size());
+ for (const auto &A : Val.asAggregate())
+ ResVec.push_back(handleFMFFlags(A, FMF));
+ return AnyValue(ResVec);
+ }
+
+ const APFloat &APVal = Val.asFloat();
+ if (FMF.noNaNs() && APVal.isNaN())
+ return AnyValue::poison();
+ if (FMF.noInfs() && APVal.isInfinity())
+ return AnyValue::poison();
+ // Non-deterministically flip the sign of the input.
+ if (FMF.noSignedZeros() && APVal.isZero()) {
+ return AnyValue(
+ APFloat::getZero(APVal.getSemantics(), Ctx.getRandomBool()));
+ }
+ return Val;
+ }
+
AnyValue computeUnOp(Type *Ty, const AnyValue &Operand,
function_ref<AnyValue(const AnyValue &)> ScalarFn) {
if (Ty->isVectorTy()) {
@@ -233,15 +237,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
if (Operand.isPoison())
return AnyValue::poison();
- // Flush input denormals
- APFloat FInput = handleDenormal(Operand.asFloat(), DenormMode.Input);
-
- APFloat Result = ScalarFn(FInput).asFloat();
+ // 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.
- // Flush output denormals
- APFloat FResult = handleDenormal(Result, DenormMode.Output);
+ APFloat Result = ScalarFn(Operand.asFloat()).asFloat();
- return handleFMFFlags(FResult, FMF);
+ return handleFMFFlags(Result, FMF);
});
}
@@ -288,12 +290,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
- if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison()) {
+ if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison())
return ValidateRes;
- }
- if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison()) {
+ if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison())
return ValidateRes;
- }
// Flush input denormals
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
@@ -867,7 +867,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
void visitFAdd(BinaryOperator &I) {
visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
- Res.add(RHS, CurrentRoundingMode);
+ Res.add(RHS, APFloat::rmNearestTiesToEven);
return Res;
});
}
@@ -875,7 +875,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
void visitFSub(BinaryOperator &I) {
visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
- Res.subtract(RHS, CurrentRoundingMode);
+ Res.subtract(RHS, APFloat::rmNearestTiesToEven);
return Res;
});
}
@@ -883,7 +883,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
void visitFMul(BinaryOperator &I) {
visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
- Res.multiply(RHS, CurrentRoundingMode);
+ Res.multiply(RHS, APFloat::rmNearestTiesToEven);
return Res;
});
}
@@ -891,7 +891,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
void visitFDiv(BinaryOperator &I) {
visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
- Res.divide(RHS, CurrentRoundingMode);
+ Res.divide(RHS, APFloat::rmNearestTiesToEven);
return Res;
});
}
@@ -1092,74 +1092,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
- APFloat::cmpResult CmpResult = FLHS.compare(FRHS);
-
- bool Result = false;
-
- switch (I.getPredicate()) {
- case FCmpInst::FCMP_FALSE:
- Result = false;
- break;
- case FCmpInst::FCMP_OEQ:
- Result = (CmpResult == APFloat::cmpEqual);
- break;
- case FCmpInst::FCMP_OGT:
- Result = (CmpResult == APFloat::cmpGreaterThan);
- break;
- case FCmpInst::FCMP_OGE:
- Result = (CmpResult == APFloat::cmpEqual ||
- CmpResult == APFloat::cmpGreaterThan);
- break;
- case FCmpInst::FCMP_OLT:
- Result = (CmpResult == APFloat::cmpLessThan);
- break;
- case FCmpInst::FCMP_OLE:
- Result = (CmpResult == APFloat::cmpEqual ||
- CmpResult == APFloat::cmpLessThan);
- break;
- case FCmpInst::FCMP_ONE:
- Result = (CmpResult == APFloat::cmpGreaterThan ||
- CmpResult == APFloat::cmpLessThan);
- break;
- case FCmpInst::FCMP_ORD:
- Result = (CmpResult != APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_UNO:
- Result = (CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_UEQ:
- Result = (CmpResult == APFloat::cmpEqual ||
- CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_UGT:
- Result = (CmpResult == APFloat::cmpGreaterThan ||
- CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_UGE:
- Result = (CmpResult == APFloat::cmpEqual ||
- CmpResult == APFloat::cmpGreaterThan ||
- CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_ULT:
- Result = (CmpResult == APFloat::cmpLessThan ||
- CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_ULE:
- Result = (CmpResult == APFloatBase::cmpEqual ||
- CmpResult == APFloat::cmpLessThan ||
- CmpResult == APFloat::cmpUnordered);
- break;
- case FCmpInst::FCMP_UNE:
- Result = (CmpResult != APFloatBase::cmpEqual);
- break;
- case FCmpInst::FCMP_TRUE:
- Result = true;
- break;
- default:
- llvm_unreachable("Invalid FCmp predicate");
- }
-
- return AnyValue::boolean(Result);
+ return AnyValue::boolean(FCmpInst::compare(FLHS, FRHS, I.getPredicate()));
});
}
>From 1d1d54e6754f5980a515e4e0304dfe69f4e91408 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 14:17:14 +0800
Subject: [PATCH 04/20] [llubi] NaN payload propagation for floating-point
operations
---
llvm/tools/llubi/lib/Context.h | 1 +
llvm/tools/llubi/lib/Interpreter.cpp | 63 +++++++++++++++++++++++++---
2 files changed, 58 insertions(+), 6 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index fcd6a3ad4485a..68deef1ef5951 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -202,6 +202,7 @@ class Context {
uint32_t getMaxStackDepth() const { return MaxStackDepth; }
void setUndefValueBehavior(UndefValueBehavior UB) { UndefBehavior = UB; }
void reseed(uint32_t Seed) { Rng.seed(Seed); }
+ std::mt19937_64 &getRng() { return Rng; }
LLVMContext &getContext() const { return Ctx; }
const DataLayout &getDataLayout() const { return DL; }
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index e743193630957..2b885b45df022 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -299,12 +299,63 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
- APFloat Result = ScalarFn(FLHS, FRHS).asFloat();
-
- // Flush output denormals
- APFloat FResult = handleDenormal(Result, DenormMode.Output);
-
- return handleFMFFlags(FResult, FMF);
+ APFloat RawResult = ScalarFn(FLHS, FRHS).asFloat();
+
+ // Flush output denormals and handle fast-math flags.
+ AnyValue FResult =
+ handleFMFFlags(handleDenormal(RawResult, DenormMode.Output), FMF);
+
+ if (FResult.isPoison())
+ return FResult;
+
+ APFloat Result = FResult.asFloat();
+ // NaN payload propagation
+ if (Result.isNaN()) {
+ // We non-deterministically choose from the four cases as specified in
+ // the language reference. The sign is also non-deterministic
+ const int8_t Choice = static_cast<int8_t>(Ctx.getRng()() % 4);
+ const bool Sign = Ctx.getRandomBool();
+ if (Choice == 0) {
+ // Preferred NaN: the quiet bit is set and the payload is all-zero
+ return APFloat::getQNaN(Result.getSemantics(), Sign);
+ }
+ if (Choice == 1) {
+ // Quieting NaN propagation: the quiet bit is set and the payload is
+ // copied from any input operand that is a NaN. We implement this by
+ // directly set the quiet bit of the input NaN, and
+ // non-deterministically flip its sign bit
+ auto QuietNaN = [&](APFloat &Input) {
+ APFloat Quieted = Input.makeQuiet();
+ if (Sign)
+ Quieted.changeSign();
+ return Quieted;
+ };
+ if (FLHS.isNaN())
+ return QuietNaN(FLHS);
+ if (FRHS.isNaN())
+ return QuietNaN(FRHS);
+ }
+ if (Choice == 2) {
+ // Unchanged NaN propagation: the quiet bit and payload are copied
+ // from any input operand that is a NaN
+ auto FlipSign = [&](APFloat &Input) {
+ if (Sign)
+ Input.changeSign();
+ return Input;
+ };
+ if (FLHS.isNaN())
+ return FlipSign(FLHS);
+ if (FRHS.isNaN())
+ return FlipSign(FRHS);
+ }
+ if (Choice == 3) {
+ // Target-specific NaN: the quiet bit is set and the payload is picked
+ // from a target-specific set of "extra" possible NaN payloads. We
+ // approximate this by filling the payload with random values.
+ auto Payload = APInt(64, Ctx.getRng()());
+ return APFloat::getQNaN(Result.getSemantics(), Sign, &Payload);
+ }
+ }
});
}
>From 2878ac0a40f723db299d6acab232b805b9738af9 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 14:19:28 +0800
Subject: [PATCH 05/20] [llubi] Small fix to visitBinOp
---
llvm/tools/llubi/lib/Interpreter.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 2b885b45df022..11c38afa02a59 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -334,6 +334,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return QuietNaN(FLHS);
if (FRHS.isNaN())
return QuietNaN(FRHS);
+ return QuietNaN(Result);
}
if (Choice == 2) {
// Unchanged NaN propagation: the quiet bit and payload are copied
@@ -347,6 +348,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return FlipSign(FLHS);
if (FRHS.isNaN())
return FlipSign(FRHS);
+ return FlipSign(Result);
}
if (Choice == 3) {
// Target-specific NaN: the quiet bit is set and the payload is picked
>From c55d92cb731d9d60c794ff85abda443425af8d72 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 14:21:28 +0800
Subject: [PATCH 06/20] [llubi] Small fix to visitBinOp
---
llvm/tools/llubi/lib/Interpreter.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 11c38afa02a59..f9de9c8ca463e 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -358,6 +358,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return APFloat::getQNaN(Result.getSemantics(), Sign, &Payload);
}
}
+
+ return Result;
});
}
>From b967a7bb8c6f201e4f14f7c3b6f16c6e2fe7ef70 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 14:51:18 +0800
Subject: [PATCH 07/20] [llubi] Add new CLI option to control the behavior of
NaN propagation
---
llvm/tools/llubi/lib/Context.cpp | 10 +++++++++-
llvm/tools/llubi/lib/Context.h | 22 +++++++++++++++++++++-
llvm/tools/llubi/lib/Interpreter.cpp | 28 ++++++++++++++--------------
llvm/tools/llubi/llubi.cpp | 21 +++++++++++++++++++++
4 files changed, 65 insertions(+), 16 deletions(-)
diff --git a/llvm/tools/llubi/lib/Context.cpp b/llvm/tools/llubi/lib/Context.cpp
index ca7105153a34c..2f2788f7b50fb 100644
--- a/llvm/tools/llubi/lib/Context.cpp
+++ b/llvm/tools/llubi/lib/Context.cpp
@@ -501,7 +501,15 @@ uint64_t Context::getEffectiveTypeStoreSize(Type *Ty) {
bool Context::getRandomBool() {
// We use the lowest bit of the raw bits from RNG as the result:
- return static_cast<bool>(Rng() & 1);
+ if (UndefBehavior == UndefValueBehavior::NonDeterministic)
+ return static_cast<bool>(Rng() & 1);
+ return false;
+}
+
+uint64_t Context::getRandomUInt64() {
+ if (UndefBehavior == UndefValueBehavior::NonDeterministic)
+ return Rng();
+ return 0;
}
void MemoryObject::markAsFreed() {
diff --git a/llvm/tools/llubi/lib/Context.h b/llvm/tools/llubi/lib/Context.h
index 68deef1ef5951..28a4583dace1d 100644
--- a/llvm/tools/llubi/lib/Context.h
+++ b/llvm/tools/llubi/lib/Context.h
@@ -47,6 +47,19 @@ enum class UndefValueBehavior {
Zero, // All uses of the undef value yield zero.
};
+enum class NaNPropagationBehavior {
+ NonDeterministic, // Non-deterministically choose from the following 4 cases
+ PreferredNaN, // The quiet bit is set and the payload is all-zero
+ QuietingNaN, // The quiet bit is set and the payload is copied from any input
+ // operand that is a NaN
+ UnchangedNaN, // The quiet bit and payload are copied from any input operand
+ // that is a NaN
+ TargetSpecificNaN // The quiet bit is set and the payload is picked from a
+ // target-specific set of “extra” possible NaN payloads,
+ // currently we implement it by filling payload with random
+ // values
+};
+
class MemoryObject : public RefCountedBase<MemoryObject> {
uint64_t Address;
uint64_t Size;
@@ -149,6 +162,7 @@ class Context {
uint32_t MaxSteps = 0;
uint32_t MaxStackDepth = 256;
UndefValueBehavior UndefBehavior = UndefValueBehavior::NonDeterministic;
+ NaNPropagationBehavior NaNBehavior = NaNPropagationBehavior::NonDeterministic;
std::mt19937_64 Rng;
@@ -200,9 +214,14 @@ class Context {
uint32_t getVScale() const { return VScale; }
uint32_t getMaxSteps() const { return MaxSteps; }
uint32_t getMaxStackDepth() const { return MaxStackDepth; }
+ NaNPropagationBehavior getNaNPropagationBehavior() const {
+ return NaNBehavior;
+ }
void setUndefValueBehavior(UndefValueBehavior UB) { UndefBehavior = UB; }
+ void setNaNPropagationBehavior(NaNPropagationBehavior NaNBehav) {
+ NaNBehavior = NaNBehav;
+ }
void reseed(uint32_t Seed) { Rng.seed(Seed); }
- std::mt19937_64 &getRng() { return Rng; }
LLVMContext &getContext() const { return Ctx; }
const DataLayout &getDataLayout() const { return DL; }
@@ -265,6 +284,7 @@ class Context {
EventHandler &Handler);
bool getRandomBool();
+ uint64_t getRandomUInt64();
};
} // namespace llvm::ubi
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index f9de9c8ca463e..6433e41eb1c53 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -230,8 +230,6 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
void visitFPUnOp(Instruction &I,
function_ref<AnyValue(const APFloat &)> ScalarFn) {
FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
- DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
- I.getType()->getScalarType()->getFltSemantics());
visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
if (Operand.isPoison())
@@ -311,15 +309,17 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
APFloat Result = FResult.asFloat();
// NaN payload propagation
if (Result.isNaN()) {
- // We non-deterministically choose from the four cases as specified in
- // the language reference. The sign is also non-deterministic
- const int8_t Choice = static_cast<int8_t>(Ctx.getRng()() % 4);
+ NaNPropagationBehavior Choice = Ctx.getNaNPropagationBehavior();
+ if (Choice == NaNPropagationBehavior::NonDeterministic) {
+ uint64_t NonDetChoice = Ctx.getRandomUInt64() % 4 + 1;
+ Choice = static_cast<NaNPropagationBehavior>(NonDetChoice);
+ }
const bool Sign = Ctx.getRandomBool();
- if (Choice == 0) {
+ if (Choice == NaNPropagationBehavior::PreferredNaN) {
// Preferred NaN: the quiet bit is set and the payload is all-zero
return APFloat::getQNaN(Result.getSemantics(), Sign);
}
- if (Choice == 1) {
+ if (Choice == NaNPropagationBehavior::QuietingNaN) {
// Quieting NaN propagation: the quiet bit is set and the payload is
// copied from any input operand that is a NaN. We implement this by
// directly set the quiet bit of the input NaN, and
@@ -336,7 +336,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return QuietNaN(FRHS);
return QuietNaN(Result);
}
- if (Choice == 2) {
+ if (Choice == NaNPropagationBehavior::UnchangedNaN) {
// Unchanged NaN propagation: the quiet bit and payload are copied
// from any input operand that is a NaN
auto FlipSign = [&](APFloat &Input) {
@@ -350,11 +350,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
return FlipSign(FRHS);
return FlipSign(Result);
}
- if (Choice == 3) {
+ if (Choice == NaNPropagationBehavior::TargetSpecificNaN) {
// Target-specific NaN: the quiet bit is set and the payload is picked
// from a target-specific set of "extra" possible NaN payloads. We
// approximate this by filling the payload with random values.
- auto Payload = APInt(64, Ctx.getRng()());
+ auto Payload = APInt(64, Ctx.getRandomUInt64());
return APFloat::getQNaN(Result.getSemantics(), Sign, &Payload);
}
}
@@ -920,7 +920,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitFAdd(BinaryOperator &I) {
- visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
Res.add(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -928,7 +928,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitFSub(BinaryOperator &I) {
- visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
Res.subtract(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -936,7 +936,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitFMul(BinaryOperator &I) {
- visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
Res.multiply(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -944,7 +944,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
}
void visitFDiv(BinaryOperator &I) {
- visitFPBinOp(I, [this](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
APFloat Res = LHS;
Res.divide(RHS, APFloat::rmNearestTiesToEven);
return Res;
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index de76a7e64c27b..4200c9246e8d8 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -87,6 +87,26 @@ cl::opt<ubi::UndefValueBehavior> UndefBehavior(
clEnumVal(ubi::UndefValueBehavior::Zero,
"All uses of an uninitialized byte yield zero.")));
+cl::opt<ubi::NaNPropagationBehavior> NaNPropagationBehavior(
+ "", cl::desc("Choose NaN propagation behavior:"),
+ cl::values(
+ clEnumVal(ubi::NaNPropagationBehavior::NonDeterministic,
+ "Non-deterministically choose from 4 cases as specified by "
+ "language reference."),
+ clEnumVal(ubi::NaNPropagationBehavior::PreferredNaN,
+ "The quiet bit is set and the payload is all-zero."),
+ clEnumVal(
+ ubi::NaNPropagationBehavior::QuietingNaN,
+ "The quiet bit is set and the payload is copied from any input"
+ "operand that is a NaN."),
+ clEnumVal(ubi::NaNPropagationBehavior::UnchangedNaN,
+ "The quiet bit and payload are copied from any input operand"
+ "that is a NaN"),
+ clEnumVal(ubi::NaNPropagationBehavior::TargetSpecificNaN,
+ "The quiet bit is set and the payload is picked from a"
+ "target-specific set of “extra” possible NaN payloads."
+ "Implemented by filling payload with random values")));
+
class VerboseEventHandler : public ubi::EventHandler {
public:
bool onInstructionExecuted(Instruction &I,
@@ -178,6 +198,7 @@ int main(int argc, char **argv) {
Ctx.setMaxSteps(MaxSteps);
Ctx.setMaxStackDepth(MaxStackDepth);
Ctx.setUndefValueBehavior(UndefBehavior);
+ Ctx.setNaNPropagationBehavior(NaNPropagationBehavior);
Ctx.reseed(Seed);
if (!Ctx.initGlobalValues()) {
>From ddceccdfc39000bfc069868d2b19ea155549c87d Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 15:15:45 +0800
Subject: [PATCH 08/20] [llubi] Update test cases
---
llvm/test/tools/llubi/fp_arith.ll | 2 +-
llvm/test/tools/llubi/fp_cmp.ll | 2 +-
llvm/test/tools/llubi/fp_phi_select.ll | 6 +++---
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/llvm/test/tools/llubi/fp_arith.ll b/llvm/test/tools/llubi/fp_arith.ll
index 2fd09ac53b1a2..69b091844ce8d 100644
--- a/llvm/test/tools/llubi/fp_arith.ll
+++ b/llvm/test/tools/llubi/fp_arith.ll
@@ -28,6 +28,6 @@ define void @main() {
; CHECK-NEXT: %p1 = fadd float 1.000000e+00, poison => poison
; CHECK-NEXT: %p2 = fneg float poison => poison
; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
-; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float 0xFFC00000
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_cmp.ll b/llvm/test/tools/llubi/fp_cmp.ll
index 489a91f256e91..da6d7d550479b 100644
--- a/llvm/test/tools/llubi/fp_cmp.ll
+++ b/llvm/test/tools/llubi/fp_cmp.ll
@@ -17,7 +17,7 @@ define void @main() {
; CHECK-NEXT: %eq = fcmp oeq float 1.000000e+00, 1.000000e+00 => T
; CHECK-NEXT: %ne = fcmp one float 1.000000e+00, 2.000000e+00 => T
; CHECK-NEXT: %gt = fcmp ogt float 2.000000e+00, 1.000000e+00 => T
-; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float 0xFFC00000
; CHECK-NEXT: %ord_nan = fcmp oeq float %nan, 1.000000e+00 => F
; CHECK-NEXT: %uno_nan = fcmp uno float %nan, 1.000000e+00 => T
; CHECK-NEXT: %une_nan = fcmp une float %nan, 1.000000e+00 => T
diff --git a/llvm/test/tools/llubi/fp_phi_select.ll b/llvm/test/tools/llubi/fp_phi_select.ll
index 9c0685a4d1cbc..d3492e84ea880 100644
--- a/llvm/test/tools/llubi/fp_phi_select.ll
+++ b/llvm/test/tools/llubi/fp_phi_select.ll
@@ -20,13 +20,13 @@ phi.test:
ret void
}
; CHECK: Entering function: main
-; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float NaN
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float 0xFFC00000
; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
-; CHECK-NEXT: %safe_sel = select i1 true, float %nan, float 1.000000e+00 => float NaN
+; CHECK-NEXT: %safe_sel = select i1 true, float %nan, float 1.000000e+00 => float 0xFFC00000
; CHECK-NEXT: %bad_sel_nan = select nnan i1 true, float %nan, float 1.000000e+00 => poison
; CHECK-NEXT: %bad_sel_inf = select ninf i1 false, float 1.000000e+00, float %inf => poison
; CHECK-NEXT: br label %phi.test jump to %phi.test
-; CHECK-NEXT: %safe_phi = phi float [ %nan, %entry ] => float NaN
+; CHECK-NEXT: %safe_phi = phi float [ %nan, %entry ] => float 0xFFC00000
; CHECK-NEXT: %bad_phi_nan = phi nnan float [ %nan, %entry ] => poison
; CHECK-NEXT: %bad_phi_inf = phi ninf float [ %inf, %entry ] => poison
; CHECK-NEXT: ret void
>From 2f8b3057d0dcfc1e32e2fe5b116e0825eeb7f5eb Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhigec_cpp at outlook.com>
Date: Fri, 27 Mar 2026 15:21:04 +0800
Subject: [PATCH 09/20] [llubi] Non-deterministically flushing the output FP
numbers
---
llvm/tools/llubi/lib/Interpreter.cpp | 38 ++++++++++++++++------------
1 file changed, 22 insertions(+), 16 deletions(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 6433e41eb1c53..c3568f687174f 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -118,20 +118,6 @@ static AnyValue mulNoWrap(const APInt &LHS, const APInt &RHS, bool HasNSW,
return Res;
}
-static APFloat handleDenormal(APFloat Val,
- DenormalMode::DenormalModeKind Mode) {
- if (!Val.isDenormal())
- return Val;
- if (Mode == DenormalMode::PositiveZero)
- return APFloat::getZero(Val.getSemantics(), false);
- if (Mode == DenormalMode::PreserveSign)
- return APFloat::getZero(Val.getSemantics(), Val.isNegative());
- // Default case for IEEE, Dynamic, and Invalid
- // Currently we treat Dynamic the same as IEEE, since we don't support
- // changing the mode at this point.
- return Val;
-}
-
/// Instruction executor using the visitor pattern.
/// Unlike the Context class that manages the global state,
/// InstExecutor only maintains the state for call frames.
@@ -175,6 +161,26 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
CurrentFrame->ValueMap.insert_or_assign(&I, std::move(V));
}
+ APFloat handleDenormal(APFloat Val, DenormalMode::DenormalModeKind Mode,
+ bool NonDet = false) {
+ if (!Val.isDenormal())
+ return Val;
+ if (NonDet) {
+ // Non-deterministically choose between flushing or preserving the
+ // denormal value.
+ if (Ctx.getRandomBool())
+ return Val;
+ }
+ if (Mode == DenormalMode::PositiveZero)
+ return APFloat::getZero(Val.getSemantics(), false);
+ if (Mode == DenormalMode::PreserveSign)
+ return APFloat::getZero(Val.getSemantics(), Val.isNegative());
+ // Default case for IEEE, Dynamic, and Invalid
+ // Currently we treat Dynamic the same as IEEE, since we don't support
+ // changing the mode at this point.
+ return Val;
+ }
+
AnyValue handleFMFFlags(AnyValue Val, FastMathFlags FMF) {
if (Val.isPoison())
return AnyValue::poison();
@@ -300,8 +306,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void> {
APFloat RawResult = ScalarFn(FLHS, FRHS).asFloat();
// Flush output denormals and handle fast-math flags.
- AnyValue FResult =
- handleFMFFlags(handleDenormal(RawResult, DenormMode.Output), FMF);
+ AnyValue FResult = handleFMFFlags(
+ handleDenormal(RawResult, DenormMode.Output, true), FMF);
if (FResult.isPoison())
return FResult;
>From e5cd154a2e678bd2d80553a44f59c185e84f28f3 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 14:06:27 +0800
Subject: [PATCH 10/20] [llubi] Some FP-related fixes
---
llvm/tools/llubi/lib/Interpreter.cpp | 49 ++++++++++++++++++----------
1 file changed, 31 insertions(+), 18 deletions(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index ee711a59fc025..48ebd53d2c504 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -155,7 +155,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFPUnOp(Instruction &I,
- function_ref<AnyValue(const APFloat &)> ScalarFn) {
+ function_ref<APFloat(const APFloat &)> ScalarFn) {
FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
visitUnOp(I, [&](const AnyValue &Operand) -> AnyValue {
@@ -166,7 +166,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// operation is fneg. And fneg is specified as a bitwise operation which
// only flips the sign bit of the input.
- APFloat Result = ScalarFn(Operand.asFloat()).asFloat();
+ APFloat Result = ScalarFn(Operand.asFloat());
return handleFMFFlags(Result, FMF);
});
@@ -206,10 +206,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitFPBinOp(
Instruction &I,
- function_ref<AnyValue(const APFloat &, const APFloat &)> ScalarFn) {
+ function_ref<APFloat(const APFloat &, const APFloat &)> ScalarFn) {
FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
- DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
- I.getType()->getScalarType()->getFltSemantics());
+ DenormalMode DenormMode = getCurrentDenormalMode(I);
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
if (LHS.isPoison() || RHS.isPoison())
@@ -224,7 +223,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
- APFloat RawResult = ScalarFn(FLHS, FRHS).asFloat();
+ APFloat RawResult = ScalarFn(FLHS, FRHS);
// Flush output denormals and handle fast-math flags.
AnyValue FResult = handleFMFFlags(
@@ -281,7 +280,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
// Target-specific NaN: the quiet bit is set and the payload is picked
// from a target-specific set of "extra" possible NaN payloads. We
// approximate this by filling the payload with random values.
- auto Payload = APInt(64, Ctx.getRandomUInt64());
+ APInt Payload(64, Ctx.getRandomUInt64());
return APFloat::getQNaN(Result.getSemantics(), Sign, &Payload);
}
}
@@ -411,6 +410,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return IdxInt.sext(IndexBitWidth);
}
+ DenormalMode getCurrentDenormalMode(Instruction &I) {
+ return CurrentFrame->Func.getDenormalMode(
+ I.getType()->getScalarType()->getFltSemantics());
+ }
+
public:
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
@@ -765,7 +769,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFAdd(BinaryOperator &I) {
- visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.add(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -773,7 +777,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFSub(BinaryOperator &I) {
- visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.subtract(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -781,7 +785,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFMul(BinaryOperator &I) {
- visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.multiply(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -789,7 +793,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFDiv(BinaryOperator &I) {
- visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.divide(RHS, APFloat::rmNearestTiesToEven);
return Res;
@@ -797,7 +801,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFRem(BinaryOperator &I) {
- visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> AnyValue {
+ visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.mod(RHS);
return Res;
@@ -805,7 +809,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFNeg(UnaryOperator &I) {
- visitFPUnOp(I, [](const APFloat &Operand) -> AnyValue { return -Operand; });
+ visitFPUnOp(I, [](const APFloat &Operand) -> APFloat { return -Operand; });
}
void visitTruncInst(TruncInst &Trunc) {
@@ -847,10 +851,20 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (Operand.isPoison())
return AnyValue::poison();
- APFloat Res = Operand.asFloat();
+ FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
+ APFloat FOperand = Operand.asFloat();
+ if (auto ValidateRes = handleFMFFlags(FOperand, FMF);
+ ValidateRes.isPoison())
+ return ValidateRes;
+
bool LosesInfo;
- Res.convert(DstSem, CurrentRoundingMode, &LosesInfo);
- return AnyValue(Res);
+ FOperand.convert(DstSem, CurrentRoundingMode, &LosesInfo);
+
+ if (auto ValidateRes = handleFMFFlags(FOperand, FMF);
+ ValidateRes.isPoison())
+ return ValidateRes;
+
+ return AnyValue(FOperand);
});
}
@@ -973,8 +987,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFCmpInst(FCmpInst &I) {
- DenormalMode DenormMode = CurrentFrame->Func.getDenormalMode(
- I.getOperand(0)->getType()->getScalarType()->getFltSemantics());
+ DenormalMode DenormMode = getCurrentDenormalMode(I);
FastMathFlags FMF = I.getFastMathFlags();
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
>From 6f70afe607d965c906d49b825b34152e1d08cc91 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 14:13:45 +0800
Subject: [PATCH 11/20] [llubi] Small format fix
---
llvm/tools/llubi/lib/Interpreter.cpp | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 48ebd53d2c504..20981dad6c143 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -991,16 +991,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
FastMathFlags FMF = I.getFastMathFlags();
visitBinOp(I, [&](const AnyValue &LHS, const AnyValue &RHS) -> AnyValue {
- if (LHS.isPoison() || RHS.isPoison()) {
+ if (LHS.isPoison() || RHS.isPoison())
return AnyValue::poison();
- }
- if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison()) {
+ if (auto ValidateRes = handleFMFFlags(LHS, FMF); ValidateRes.isPoison())
return ValidateRes;
- }
- if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison()) {
+ if (auto ValidateRes = handleFMFFlags(RHS, FMF); ValidateRes.isPoison())
return ValidateRes;
- }
APFloat FLHS = handleDenormal(LHS.asFloat(), DenormMode.Input);
APFloat FRHS = handleDenormal(RHS.asFloat(), DenormMode.Input);
>From 1256652434d5b31c2ea04054d5dd70dfd2d8f3ee Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 14:42:18 +0800
Subject: [PATCH 12/20] [llubi] Small FP-related fixes
---
llvm/tools/llubi/lib/Interpreter.cpp | 60 +++++++++++++++++++++++++---
llvm/tools/llubi/llubi.cpp | 3 +-
2 files changed, 57 insertions(+), 6 deletions(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 20981dad6c143..5d62d8cd69ec5 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -68,7 +68,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
const DataLayout &DL;
std::list<Frame> CallStack;
AnyValue None;
- APFloat::roundingMode CurrentRoundingMode;
+ RoundingMode CurrentRoundingMode;
+ fp::ExceptionBehavior CurrentExceptionBehavior;
const AnyValue &getValue(Value *V) {
if (auto *C = dyn_cast<Constant>(V))
@@ -121,8 +122,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return AnyValue::poison();
// Non-deterministically flip the sign of the input.
if (FMF.noSignedZeros() && APVal.isZero()) {
- return AnyValue(
- APFloat::getZero(APVal.getSemantics(), Ctx.getRandomBool()));
+ return AnyValue(APFloat::getZero(
+ APVal.getSemantics(), APVal.isNegative() ^ Ctx.getRandomBool()));
}
return Val;
}
@@ -419,7 +420,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
InstExecutor(Context &C, EventHandler &H, Function &F,
ArrayRef<AnyValue> Args, AnyValue &RetVal)
: ExecutorBase(C, H), DL(Ctx.getDataLayout()),
- CurrentRoundingMode(APFloat::rmNearestTiesToEven) {
+ CurrentRoundingMode(APFloat::rmNearestTiesToEven),
+ CurrentExceptionBehavior(fp::ebIgnore) {
CallStack.emplace_back(F, /*CallSite=*/nullptr, /*LastFrame=*/nullptr, Args,
RetVal, Ctx.getTLIImpl());
}
@@ -769,6 +771,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFAdd(BinaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.add(RHS, APFloat::rmNearestTiesToEven);
@@ -777,6 +785,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFSub(BinaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.subtract(RHS, APFloat::rmNearestTiesToEven);
@@ -785,6 +799,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFMul(BinaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.multiply(RHS, APFloat::rmNearestTiesToEven);
@@ -793,6 +813,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFDiv(BinaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.divide(RHS, APFloat::rmNearestTiesToEven);
@@ -801,6 +827,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFRem(BinaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPBinOp(I, [](const APFloat &LHS, const APFloat &RHS) -> APFloat {
APFloat Res = LHS;
Res.mod(RHS);
@@ -809,6 +841,12 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
}
void visitFNeg(UnaryOperator &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
visitFPUnOp(I, [](const APFloat &Operand) -> APFloat { return -Operand; });
}
@@ -844,6 +882,13 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
void visitFPTruncInst(FPTruncInst &FPTrunc) { visitFPConvInst(FPTrunc); }
void visitFPConvInst(Instruction &I) {
+ if (!isDefaultFPEnvironment(CurrentExceptionBehavior,
+ CurrentRoundingMode)) {
+ Status = false;
+ reportImmediateUB("Non-constrained floating-point operation assumes "
+ "default floating-point environment");
+ }
+
const fltSemantics &DstSem =
I.getType()->getScalarType()->getFltSemantics();
@@ -852,7 +897,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return AnyValue::poison();
FastMathFlags FMF = cast<FPMathOperator>(I).getFastMathFlags();
- APFloat FOperand = Operand.asFloat();
+ DenormalMode DenormMode = getCurrentDenormalMode(I);
+
+ APFloat FOperand = handleDenormal(Operand.asFloat(), DenormMode.Input);
+
if (auto ValidateRes = handleFMFFlags(FOperand, FMF);
ValidateRes.isPoison())
return ValidateRes;
@@ -864,6 +912,8 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
ValidateRes.isPoison())
return ValidateRes;
+ FOperand = handleDenormal(std::move(FOperand), DenormMode.Output, true);
+
return AnyValue(FOperand);
});
}
diff --git a/llvm/tools/llubi/llubi.cpp b/llvm/tools/llubi/llubi.cpp
index 4200c9246e8d8..825d8e17149e6 100644
--- a/llvm/tools/llubi/llubi.cpp
+++ b/llvm/tools/llubi/llubi.cpp
@@ -105,7 +105,8 @@ cl::opt<ubi::NaNPropagationBehavior> NaNPropagationBehavior(
clEnumVal(ubi::NaNPropagationBehavior::TargetSpecificNaN,
"The quiet bit is set and the payload is picked from a"
"target-specific set of “extra” possible NaN payloads."
- "Implemented by filling payload with random values")));
+ "Implemented by filling payload with random values")),
+ cl::init(ubi::NaNPropagationBehavior::NonDeterministic));
class VerboseEventHandler : public ubi::EventHandler {
public:
>From 322880654b6d5dff023f2f3bdbaa18da876d0119 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 15:01:54 +0800
Subject: [PATCH 13/20] [llubi] Refactor out NaN propagation logic & add NaN
propagation to fpext/fptrunc
---
llvm/tools/llubi/lib/Interpreter.cpp | 176 +++++++++++++++++++--------
1 file changed, 122 insertions(+), 54 deletions(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 5d62d8cd69ec5..787fa1fc88849 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -128,6 +128,122 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return Val;
}
+ enum class NaNPayloadCastMode {
+ None,
+ FPExt,
+ FPTrunc,
+ };
+
+ static unsigned getNaNPayloadBitWidth(const fltSemantics &Sem) {
+ unsigned Precision = APFloat::semanticsPrecision(Sem);
+ return Precision > 2 ? Precision - 2 : 0;
+ }
+
+ NaNPropagationBehavior resolveNaNPropagationBehavior() {
+ NaNPropagationBehavior Choice = Ctx.getNaNPropagationBehavior();
+ if (Choice == NaNPropagationBehavior::NonDeterministic) {
+ uint64_t NonDetChoice = Ctx.getRandomUInt64() % 4 + 1;
+ Choice = static_cast<NaNPropagationBehavior>(NonDetChoice);
+ }
+ return Choice;
+ }
+
+ const APFloat &pickNaNSource(ArrayRef<const APFloat *> Inputs,
+ const APFloat &Fallback) {
+ for (const APFloat *Input : Inputs) {
+ if (Input && Input->isNaN())
+ return *Input;
+ }
+ return Fallback;
+ }
+
+ APInt getNaNPayload(const APFloat &NaNVal) {
+ unsigned PayloadBits = getNaNPayloadBitWidth(NaNVal.getSemantics());
+ if (!PayloadBits)
+ return APInt(1, 0);
+ return NaNVal.bitcastToAPInt().extractBits(PayloadBits, 0);
+ }
+
+ APInt castNaNPayload(const APInt &SrcPayload, unsigned SrcPayloadBits,
+ unsigned DstPayloadBits, NaNPayloadCastMode CastMode) {
+ if (!DstPayloadBits)
+ return APInt(1, 0);
+
+ APInt CanonicalSrc = SrcPayload.zextOrTrunc(SrcPayloadBits);
+ if (CastMode == NaNPayloadCastMode::FPExt &&
+ SrcPayloadBits < DstPayloadBits)
+ return CanonicalSrc.zext(DstPayloadBits)
+ .shl(DstPayloadBits - SrcPayloadBits);
+ if (CastMode == NaNPayloadCastMode::FPTrunc &&
+ SrcPayloadBits > DstPayloadBits)
+ return CanonicalSrc.lshr(SrcPayloadBits - DstPayloadBits)
+ .trunc(DstPayloadBits);
+ return CanonicalSrc.zextOrTrunc(DstPayloadBits);
+ }
+
+ APFloat buildNaN(const fltSemantics &Sem, bool IsNegative, bool IsSignaling,
+ const APInt &Payload) {
+ unsigned SignificandBits = APFloat::semanticsPrecision(Sem) - 1;
+ APInt Fill = APInt::getZero(SignificandBits);
+ unsigned PayloadBits = getNaNPayloadBitWidth(Sem);
+ if (PayloadBits)
+ Fill.insertBits(Payload.zextOrTrunc(PayloadBits), 0);
+
+ if (IsSignaling)
+ return APFloat::getSNaN(Sem, IsNegative, &Fill);
+ return APFloat::getQNaN(Sem, IsNegative, &Fill);
+ }
+
+ APFloat propagateInputNaN(const APFloat &InputNaN, const fltSemantics &DstSem,
+ bool QuietingMode, bool FlipSign,
+ NaNPayloadCastMode CastMode) {
+ unsigned SrcPayloadBits = getNaNPayloadBitWidth(InputNaN.getSemantics());
+ unsigned DstPayloadBits = getNaNPayloadBitWidth(DstSem);
+ APInt MappedPayload = castNaNPayload(
+ getNaNPayload(InputNaN), SrcPayloadBits, DstPayloadBits, CastMode);
+
+ bool IsSignaling = InputNaN.isSignaling();
+ bool OutputIsSignaling = !QuietingMode && IsSignaling;
+ if (OutputIsSignaling &&
+ (DstPayloadBits == 0 ||
+ (CastMode == NaNPayloadCastMode::FPTrunc && MappedPayload.isZero())))
+ OutputIsSignaling = false;
+
+ bool IsNegative = InputNaN.isNegative();
+ if (FlipSign)
+ IsNegative = !IsNegative;
+ return buildNaN(DstSem, IsNegative, OutputIsSignaling, MappedPayload);
+ }
+
+ APFloat
+ applyNaNPropagation(const APFloat &Result, ArrayRef<const APFloat *> Inputs,
+ NaNPayloadCastMode CastMode = NaNPayloadCastMode::None) {
+ if (!Result.isNaN())
+ return Result;
+
+ NaNPropagationBehavior Choice = resolveNaNPropagationBehavior();
+ const bool SignChoice = Ctx.getRandomBool();
+ switch (Choice) {
+ case NaNPropagationBehavior::PreferredNaN:
+ return APFloat::getQNaN(Result.getSemantics(), SignChoice);
+ case NaNPropagationBehavior::QuietingNaN:
+ return propagateInputNaN(pickNaNSource(Inputs, Result),
+ Result.getSemantics(), /*QuietingMode=*/true,
+ SignChoice, CastMode);
+ case NaNPropagationBehavior::UnchangedNaN:
+ return propagateInputNaN(pickNaNSource(Inputs, Result),
+ Result.getSemantics(), /*QuietingMode=*/false,
+ SignChoice, CastMode);
+ case NaNPropagationBehavior::TargetSpecificNaN: {
+ APInt Payload(64, Ctx.getRandomUInt64());
+ return APFloat::getQNaN(Result.getSemantics(), SignChoice, &Payload);
+ }
+ case NaNPropagationBehavior::NonDeterministic:
+ llvm_unreachable("NonDeterministic should be resolved earlier.");
+ }
+ llvm_unreachable("Unhandled NaN propagation behavior.");
+ }
+
AnyValue computeUnOp(Type *Ty, const AnyValue &Operand,
function_ref<AnyValue(const AnyValue &)> ScalarFn) {
if (Ty->isVectorTy()) {
@@ -234,59 +350,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return FResult;
APFloat Result = FResult.asFloat();
- // NaN payload propagation
- if (Result.isNaN()) {
- NaNPropagationBehavior Choice = Ctx.getNaNPropagationBehavior();
- if (Choice == NaNPropagationBehavior::NonDeterministic) {
- uint64_t NonDetChoice = Ctx.getRandomUInt64() % 4 + 1;
- Choice = static_cast<NaNPropagationBehavior>(NonDetChoice);
- }
- const bool Sign = Ctx.getRandomBool();
- if (Choice == NaNPropagationBehavior::PreferredNaN) {
- // Preferred NaN: the quiet bit is set and the payload is all-zero
- return APFloat::getQNaN(Result.getSemantics(), Sign);
- }
- if (Choice == NaNPropagationBehavior::QuietingNaN) {
- // Quieting NaN propagation: the quiet bit is set and the payload is
- // copied from any input operand that is a NaN. We implement this by
- // directly set the quiet bit of the input NaN, and
- // non-deterministically flip its sign bit
- auto QuietNaN = [&](APFloat &Input) {
- APFloat Quieted = Input.makeQuiet();
- if (Sign)
- Quieted.changeSign();
- return Quieted;
- };
- if (FLHS.isNaN())
- return QuietNaN(FLHS);
- if (FRHS.isNaN())
- return QuietNaN(FRHS);
- return QuietNaN(Result);
- }
- if (Choice == NaNPropagationBehavior::UnchangedNaN) {
- // Unchanged NaN propagation: the quiet bit and payload are copied
- // from any input operand that is a NaN
- auto FlipSign = [&](APFloat &Input) {
- if (Sign)
- Input.changeSign();
- return Input;
- };
- if (FLHS.isNaN())
- return FlipSign(FLHS);
- if (FRHS.isNaN())
- return FlipSign(FRHS);
- return FlipSign(Result);
- }
- if (Choice == NaNPropagationBehavior::TargetSpecificNaN) {
- // Target-specific NaN: the quiet bit is set and the payload is picked
- // from a target-specific set of "extra" possible NaN payloads. We
- // approximate this by filling the payload with random values.
- APInt Payload(64, Ctx.getRandomUInt64());
- return APFloat::getQNaN(Result.getSemantics(), Sign, &Payload);
- }
- }
-
- return Result;
+ return applyNaNPropagation(Result, {&FLHS, &FRHS});
});
}
@@ -900,6 +964,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
DenormalMode DenormMode = getCurrentDenormalMode(I);
APFloat FOperand = handleDenormal(Operand.asFloat(), DenormMode.Input);
+ APFloat SourceNaN = FOperand;
if (auto ValidateRes = handleFMFFlags(FOperand, FMF);
ValidateRes.isPoison())
@@ -914,7 +979,10 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
FOperand = handleDenormal(std::move(FOperand), DenormMode.Output, true);
- return AnyValue(FOperand);
+ NaNPayloadCastMode CastMode = isa<FPExtInst>(I)
+ ? NaNPayloadCastMode::FPExt
+ : NaNPayloadCastMode::FPTrunc;
+ return AnyValue(applyNaNPropagation(FOperand, {&SourceNaN}, CastMode));
});
}
>From 25f4828c32de9bbb95c04e9721db8302b419d3b4 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 15:39:17 +0800
Subject: [PATCH 14/20] [llubi] Add basic arithmetic tests for
half/bfloat/double/x86_fp80/fp128/ppc_fp128
---
llvm/test/tools/llubi/fp_arith.ll | 33 ---------------------
llvm/test/tools/llubi/fp_arith_bfloat.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_double.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_float.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_fp128.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_fp80.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_half.ll | 20 +++++++++++++
llvm/test/tools/llubi/fp_arith_ppc_fp128.ll | 20 +++++++++++++
8 files changed, 140 insertions(+), 33 deletions(-)
delete mode 100644 llvm/test/tools/llubi/fp_arith.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_bfloat.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_double.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_float.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_fp128.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_fp80.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_half.ll
create mode 100644 llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
diff --git a/llvm/test/tools/llubi/fp_arith.ll b/llvm/test/tools/llubi/fp_arith.ll
deleted file mode 100644
index 69b091844ce8d..0000000000000
--- a/llvm/test/tools/llubi/fp_arith.ll
+++ /dev/null
@@ -1,33 +0,0 @@
-; 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() {
- %add = fadd float 1.5, 2.5
- %sub = fsub double 5.0, 1.0
- %mul = fmul float 2.0, 3.0
- %div = fdiv double 1.0, 2.0
- %neg = fneg float -1.5
-
- %vadd = fadd <2 x float> <float 1.0, float 2.0>, <float 3.0, float 4.0>
-
- %p1 = fadd float 1.0, poison
- %p2 = fneg float poison
-
- %inf = fdiv float 1.0, 0.0
- %nan = fdiv float 0.0, 0.0
-
- ret void
-}
-; CHECK: Entering function: main
-; CHECK-NEXT: %add = fadd float 1.500000e+00, 2.500000e+00 => float 4.000000e+00
-; CHECK-NEXT: %sub = fsub double 5.000000e+00, 1.000000e+00 => double 4.000000e+00
-; CHECK-NEXT: %mul = fmul float 2.000000e+00, 3.000000e+00 => float 6.000000e+00
-; CHECK-NEXT: %div = fdiv double 1.000000e+00, 2.000000e+00 => double 5.000000e-01
-; CHECK-NEXT: %neg = fneg float -1.500000e+00 => float 1.500000e+00
-; CHECK-NEXT: %vadd = fadd <2 x float> <float 1.000000e+00, float 2.000000e+00>, <float 3.000000e+00, float 4.000000e+00> => { float 4.000000e+00, float 6.000000e+00 }
-; CHECK-NEXT: %p1 = fadd float 1.000000e+00, poison => poison
-; CHECK-NEXT: %p2 = fneg float poison => poison
-; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
-; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float 0xFFC00000
-; CHECK-NEXT: ret void
-; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_bfloat.ll b/llvm/test/tools/llubi/fp_arith_bfloat.ll
new file mode 100644
index 0000000000000..c546a65ccf21e
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_bfloat.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd bfloat 1.5, 2.5
+ %sub = fsub bfloat 5.0, 1.0
+ %mul = fmul bfloat 2.0, 3.0
+ %div = fdiv bfloat 1.0, 2.0
+ %neg = fneg bfloat -1.5
+
+ %vadd = fadd <2 x bfloat> <bfloat 1.0, bfloat 2.0>, <bfloat 3.0, bfloat 4.0>
+
+ %p1 = fadd bfloat 1.0, poison
+ %p2 = fneg bfloat poison
+
+ %inf = fdiv bfloat 1.0, 0.0
+ %nan = fdiv bfloat 0.0, 0.0
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_double.ll b/llvm/test/tools/llubi/fp_arith_double.ll
new file mode 100644
index 0000000000000..7201058392b6e
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_double.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd double 1.5, 2.5
+ %sub = fsub double 5.0, 1.0
+ %mul = fmul double 2.0, 3.0
+ %div = fdiv double 1.0, 2.0
+ %neg = fneg double -1.5
+
+ %vadd = fadd <2 x double> <double 1.0, double 2.0>, <double 3.0, double 4.0>
+
+ %p1 = fadd double 1.0, poison
+ %p2 = fneg double poison
+
+ %inf = fdiv double 1.0, 0.0
+ %nan = fdiv double 0.0, 0.0
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_float.ll b/llvm/test/tools/llubi/fp_arith_float.ll
new file mode 100644
index 0000000000000..3dff7003c1f70
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_float.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd float 1.5, 2.5
+ %sub = fsub float 5.0, 1.0
+ %mul = fmul float 2.0, 3.0
+ %div = fdiv float 1.0, 2.0
+ %neg = fneg float -1.5
+
+ %vadd = fadd <2 x float> <float 1.0, float 2.0>, <float 3.0, float 4.0>
+
+ %p1 = fadd float 1.0, poison
+ %p2 = fneg float poison
+
+ %inf = fdiv float 1.0, 0.0
+ %nan = fdiv float 0.0, 0.0
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_fp128.ll b/llvm/test/tools/llubi/fp_arith_fp128.ll
new file mode 100644
index 0000000000000..1fb3b5b060ed6
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_fp128.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd fp128 0xL00000000000000003FFF800000000000, 0xL00000000000000004000400000000000
+ %sub = fsub fp128 0xL00000000000000004001400000000000, 0xL00000000000000003FFF000000000000
+ %mul = fmul fp128 0xL00000000000000004000000000000000, 0xL00000000000000004000800000000000
+ %div = fdiv fp128 0xL00000000000000003FFF000000000000, 0xL00000000000000004000000000000000
+ %neg = fneg fp128 0xL0000000000000000BFFF800000000000
+
+ %vadd = fadd <2 x fp128> <fp128 0xL00000000000000003FFF000000000000, fp128 0xL00000000000000004000000000000000>, <fp128 0xL00000000000000004000800000000000, fp128 0xL00000000000000004001000000000000>
+
+ %p1 = fadd fp128 0xL00000000000000003FFF000000000000, poison
+ %p2 = fneg fp128 poison
+
+ %inf = fdiv fp128 0xL00000000000000003FFF000000000000, 0xL00000000000000000000000000000000
+ %nan = fdiv fp128 0xL00000000000000000000000000000000, 0xL00000000000000000000000000000000
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_fp80.ll b/llvm/test/tools/llubi/fp_arith_fp80.ll
new file mode 100644
index 0000000000000..78c58fdae0d54
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_fp80.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd x86_fp80 0xK3FFFC000000000000000, 0xK4000A000000000000000
+ %sub = fsub x86_fp80 0xK4001A000000000000000, 0xK3FFF8000000000000000
+ %mul = fmul x86_fp80 0xK40008000000000000000, 0xK4000C000000000000000
+ %div = fdiv x86_fp80 0xK3FFF8000000000000000, 0xK40008000000000000000
+ %neg = fneg x86_fp80 0xKBFFFC000000000000000
+
+ %vadd = fadd <2 x x86_fp80> <x86_fp80 0xK3FFF8000000000000000, x86_fp80 0xK40008000000000000000>, <x86_fp80 0xK4000C000000000000000, x86_fp80 0xK40018000000000000000>
+
+ %p1 = fadd x86_fp80 0xK3FFF8000000000000000, poison
+ %p2 = fneg x86_fp80 poison
+
+ %inf = fdiv x86_fp80 0xK3FFF8000000000000000, 0xK00000000000000000000
+ %nan = fdiv x86_fp80 0xK00000000000000000000, 0xK00000000000000000000
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_half.ll b/llvm/test/tools/llubi/fp_arith_half.ll
new file mode 100644
index 0000000000000..49e1dbc57498c
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_half.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd half 1.5, 2.5
+ %sub = fsub half 5.0, 1.0
+ %mul = fmul half 2.0, 3.0
+ %div = fdiv half 1.0, 2.0
+ %neg = fneg half -1.5
+
+ %vadd = fadd <2 x half> <half 1.0, half 2.0>, <half 3.0, half 4.0>
+
+ %p1 = fadd half 1.0, poison
+ %p2 = fneg half poison
+
+ %inf = fdiv half 1.0, 0.0
+ %nan = fdiv half 0.0, 0.0
+
+ ret void
+}
diff --git a/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll b/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
new file mode 100644
index 0000000000000..d2a8a5c06dd4a
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
@@ -0,0 +1,20 @@
+; 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() {
+ %add = fadd ppc_fp128 0xM3FF80000000000000000000000000000, 0xM40040000000000000000000000000000
+ %sub = fsub ppc_fp128 0xM40140000000000000000000000000000, 0xM3FF00000000000000000000000000000
+ %mul = fmul ppc_fp128 0xM40000000000000000000000000000000, 0xM40080000000000000000000000000000
+ %div = fdiv ppc_fp128 0xM3FF00000000000000000000000000000, 0xM40000000000000000000000000000000
+ %neg = fneg ppc_fp128 0xMBFF80000000000000000000000000000
+
+ %vadd = fadd <2 x ppc_fp128> <ppc_fp128 0xM3FF00000000000000000000000000000, ppc_fp128 0xM40000000000000000000000000000000>, <ppc_fp128 0xM40080000000000000000000000000000, ppc_fp128 0xM40100000000000000000000000000000>
+
+ %p1 = fadd ppc_fp128 0xM3FF00000000000000000000000000000, poison
+ %p2 = fneg ppc_fp128 poison
+
+ %inf = fdiv ppc_fp128 0xM3FF00000000000000000000000000000, 0xM00000000000000000000000000000000
+ %nan = fdiv ppc_fp128 0xM00000000000000000000000000000000, 0xM00000000000000000000000000000000
+
+ ret void
+}
>From 5777b35ea2b054dcce956fe1e8e96e17914c3cdc Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 15:40:16 +0800
Subject: [PATCH 15/20] [llubi] Update tests
---
llvm/test/tools/llubi/fp_arith_bfloat.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_double.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_float.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_fp128.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_fp80.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_half.ll | 13 +++++++++++++
llvm/test/tools/llubi/fp_arith_ppc_fp128.ll | 13 +++++++++++++
7 files changed, 91 insertions(+)
diff --git a/llvm/test/tools/llubi/fp_arith_bfloat.ll b/llvm/test/tools/llubi/fp_arith_bfloat.ll
index c546a65ccf21e..e386261261f94 100644
--- a/llvm/test/tools/llubi/fp_arith_bfloat.ll
+++ b/llvm/test/tools/llubi/fp_arith_bfloat.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd bfloat 0xR3FC0, 0xR4020 => bfloat 4.000000e+00
+; CHECK-NEXT: %sub = fsub bfloat 0xR40A0, 0xR3F80 => bfloat 4.000000e+00
+; CHECK-NEXT: %mul = fmul bfloat 0xR4000, 0xR4040 => bfloat 6.000000e+00
+; CHECK-NEXT: %div = fdiv bfloat 0xR3F80, 0xR4000 => bfloat 5.000000e-01
+; CHECK-NEXT: %neg = fneg bfloat 0xRBFC0 => bfloat 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x bfloat> <bfloat 0xR3F80, bfloat 0xR4000>, <bfloat 0xR4040, bfloat 0xR4080> => { bfloat 4.000000e+00, bfloat 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd bfloat 0xR3F80, poison => poison
+; CHECK-NEXT: %p2 = fneg bfloat poison => poison
+; CHECK-NEXT: %inf = fdiv bfloat 0xR3F80, 0xR0000 => bfloat +Inf
+; CHECK-NEXT: %nan = fdiv bfloat 0xR0000, 0xR0000 => bfloat 0xFFC0
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_double.ll b/llvm/test/tools/llubi/fp_arith_double.ll
index 7201058392b6e..755af05cbbf9d 100644
--- a/llvm/test/tools/llubi/fp_arith_double.ll
+++ b/llvm/test/tools/llubi/fp_arith_double.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd double 1.500000e+00, 2.500000e+00 => double 4.000000e+00
+; CHECK-NEXT: %sub = fsub double 5.000000e+00, 1.000000e+00 => double 4.000000e+00
+; CHECK-NEXT: %mul = fmul double 2.000000e+00, 3.000000e+00 => double 6.000000e+00
+; CHECK-NEXT: %div = fdiv double 1.000000e+00, 2.000000e+00 => double 5.000000e-01
+; CHECK-NEXT: %neg = fneg double -1.500000e+00 => double 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x double> <double 1.000000e+00, double 2.000000e+00>, <double 3.000000e+00, double 4.000000e+00> => { double 4.000000e+00, double 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd double 1.000000e+00, poison => poison
+; CHECK-NEXT: %p2 = fneg double poison => poison
+; CHECK-NEXT: %inf = fdiv double 1.000000e+00, 0.000000e+00 => double +Inf
+; CHECK-NEXT: %nan = fdiv double 0.000000e+00, 0.000000e+00 => double 0xFFF8000000000000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_float.ll b/llvm/test/tools/llubi/fp_arith_float.ll
index 3dff7003c1f70..28ee59f3d7fe2 100644
--- a/llvm/test/tools/llubi/fp_arith_float.ll
+++ b/llvm/test/tools/llubi/fp_arith_float.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd float 1.500000e+00, 2.500000e+00 => float 4.000000e+00
+; CHECK-NEXT: %sub = fsub float 5.000000e+00, 1.000000e+00 => float 4.000000e+00
+; CHECK-NEXT: %mul = fmul float 2.000000e+00, 3.000000e+00 => float 6.000000e+00
+; CHECK-NEXT: %div = fdiv float 1.000000e+00, 2.000000e+00 => float 5.000000e-01
+; CHECK-NEXT: %neg = fneg float -1.500000e+00 => float 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x float> <float 1.000000e+00, float 2.000000e+00>, <float 3.000000e+00, float 4.000000e+00> => { float 4.000000e+00, float 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd float 1.000000e+00, poison => poison
+; CHECK-NEXT: %p2 = fneg float poison => poison
+; CHECK-NEXT: %inf = fdiv float 1.000000e+00, 0.000000e+00 => float +Inf
+; CHECK-NEXT: %nan = fdiv float 0.000000e+00, 0.000000e+00 => float 0xFFC00000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_fp128.ll b/llvm/test/tools/llubi/fp_arith_fp128.ll
index 1fb3b5b060ed6..83a2e7295d879 100644
--- a/llvm/test/tools/llubi/fp_arith_fp128.ll
+++ b/llvm/test/tools/llubi/fp_arith_fp128.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd fp128 0xL00000000000000003FFF800000000000, 0xL00000000000000004000400000000000 => fp128 4.000000e+00
+; CHECK-NEXT: %sub = fsub fp128 0xL00000000000000004001400000000000, 0xL00000000000000003FFF000000000000 => fp128 4.000000e+00
+; CHECK-NEXT: %mul = fmul fp128 0xL00000000000000004000000000000000, 0xL00000000000000004000800000000000 => fp128 6.000000e+00
+; CHECK-NEXT: %div = fdiv fp128 0xL00000000000000003FFF000000000000, 0xL00000000000000004000000000000000 => fp128 5.000000e-01
+; CHECK-NEXT: %neg = fneg fp128 0xL0000000000000000BFFF800000000000 => fp128 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x fp128> <fp128 0xL00000000000000003FFF000000000000, fp128 0xL00000000000000004000000000000000>, <fp128 0xL00000000000000004000800000000000, fp128 0xL00000000000000004001000000000000> => { fp128 4.000000e+00, fp128 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd fp128 0xL00000000000000003FFF000000000000, poison => poison
+; CHECK-NEXT: %p2 = fneg fp128 poison => poison
+; CHECK-NEXT: %inf = fdiv fp128 0xL00000000000000003FFF000000000000, 0xL00000000000000000000000000000000 => fp128 +Inf
+; CHECK-NEXT: %nan = fdiv fp128 0xL00000000000000000000000000000000, 0xL00000000000000000000000000000000 => fp128 0xFFFF8000000000000000000000000000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_fp80.ll b/llvm/test/tools/llubi/fp_arith_fp80.ll
index 78c58fdae0d54..759e162773cd4 100644
--- a/llvm/test/tools/llubi/fp_arith_fp80.ll
+++ b/llvm/test/tools/llubi/fp_arith_fp80.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd x86_fp80 0xK3FFFC000000000000000, 0xK4000A000000000000000 => x86_fp80 4.000000e+00
+; CHECK-NEXT: %sub = fsub x86_fp80 0xK4001A000000000000000, 0xK3FFF8000000000000000 => x86_fp80 4.000000e+00
+; CHECK-NEXT: %mul = fmul x86_fp80 0xK40008000000000000000, 0xK4000C000000000000000 => x86_fp80 6.000000e+00
+; CHECK-NEXT: %div = fdiv x86_fp80 0xK3FFF8000000000000000, 0xK40008000000000000000 => x86_fp80 5.000000e-01
+; CHECK-NEXT: %neg = fneg x86_fp80 0xKBFFFC000000000000000 => x86_fp80 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x x86_fp80> <x86_fp80 0xK3FFF8000000000000000, x86_fp80 0xK40008000000000000000>, <x86_fp80 0xK4000C000000000000000, x86_fp80 0xK40018000000000000000> => { x86_fp80 4.000000e+00, x86_fp80 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd x86_fp80 0xK3FFF8000000000000000, poison => poison
+; CHECK-NEXT: %p2 = fneg x86_fp80 poison => poison
+; CHECK-NEXT: %inf = fdiv x86_fp80 0xK3FFF8000000000000000, 0xK00000000000000000000 => x86_fp80 +Inf
+; CHECK-NEXT: %nan = fdiv x86_fp80 0xK00000000000000000000, 0xK00000000000000000000 => x86_fp80 0xFFFFC000000000000000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_half.ll b/llvm/test/tools/llubi/fp_arith_half.ll
index 49e1dbc57498c..093ec2b1e92da 100644
--- a/llvm/test/tools/llubi/fp_arith_half.ll
+++ b/llvm/test/tools/llubi/fp_arith_half.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd half 0xH3E00, 0xH4100 => half 4.000000e+00
+; CHECK-NEXT: %sub = fsub half 0xH4500, 0xH3C00 => half 4.000000e+00
+; CHECK-NEXT: %mul = fmul half 0xH4000, 0xH4200 => half 6.000000e+00
+; CHECK-NEXT: %div = fdiv half 0xH3C00, 0xH4000 => half 5.000000e-01
+; CHECK-NEXT: %neg = fneg half 0xHBE00 => half 1.500000e+00
+; CHECK-NEXT: %vadd = fadd <2 x half> <half 0xH3C00, half 0xH4000>, <half 0xH4200, half 0xH4400> => { half 4.000000e+00, half 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd half 0xH3C00, poison => poison
+; CHECK-NEXT: %p2 = fneg half poison => poison
+; CHECK-NEXT: %inf = fdiv half 0xH3C00, 0xH0000 => half +Inf
+; CHECK-NEXT: %nan = fdiv half 0xH0000, 0xH0000 => half 0xFE00
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll b/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
index d2a8a5c06dd4a..14dabc9bc1753 100644
--- a/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
+++ b/llvm/test/tools/llubi/fp_arith_ppc_fp128.ll
@@ -18,3 +18,16 @@ define void @main() {
ret void
}
+; CHECK: Entering function: main
+; CHECK-NEXT: %add = fadd ppc_fp128 0xM3FF80000000000000000000000000000, 0xM40040000000000000000000000000000 => ppc_fp128 4.000000e+00
+; CHECK-NEXT: %sub = fsub ppc_fp128 0xM40140000000000000000000000000000, 0xM3FF00000000000000000000000000000 => ppc_fp128 0x80000000000000004010000000000000
+; CHECK-NEXT: %mul = fmul ppc_fp128 0xM40000000000000000000000000000000, 0xM40080000000000000000000000000000 => ppc_fp128 6.000000e+00
+; CHECK-NEXT: %div = fdiv ppc_fp128 0xM3FF00000000000000000000000000000, 0xM40000000000000000000000000000000 => ppc_fp128 5.000000e-01
+; CHECK-NEXT: %neg = fneg ppc_fp128 0xMBFF80000000000000000000000000000 => ppc_fp128 0x80000000000000003FF8000000000000
+; CHECK-NEXT: %vadd = fadd <2 x ppc_fp128> <ppc_fp128 0xM3FF00000000000000000000000000000, ppc_fp128 0xM40000000000000000000000000000000>, <ppc_fp128 0xM40080000000000000000000000000000, ppc_fp128 0xM40100000000000000000000000000000> => { ppc_fp128 4.000000e+00, ppc_fp128 6.000000e+00 }
+; CHECK-NEXT: %p1 = fadd ppc_fp128 0xM3FF00000000000000000000000000000, poison => poison
+; CHECK-NEXT: %p2 = fneg ppc_fp128 poison => poison
+; CHECK-NEXT: %inf = fdiv ppc_fp128 0xM3FF00000000000000000000000000000, 0xM00000000000000000000000000000000 => ppc_fp128 +Inf
+; CHECK-NEXT: %nan = fdiv ppc_fp128 0xM00000000000000000000000000000000, 0xM00000000000000000000000000000000 => ppc_fp128 0x0000000000000000FFF8000000000000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
>From 7d20161ca50dd952b244c3f5c72dfa8bd9bbf10d Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 15:55:56 +0800
Subject: [PATCH 16/20] [llubi] Add nneg handling to uitofp & related test
update
---
llvm/test/tools/llubi/fp_cast.ll | 10 ++++++++++
llvm/tools/llubi/lib/Interpreter.cpp | 5 +++++
2 files changed, 15 insertions(+)
diff --git a/llvm/test/tools/llubi/fp_cast.ll b/llvm/test/tools/llubi/fp_cast.ll
index bab205f1c5891..00e1578d72395 100644
--- a/llvm/test/tools/llubi/fp_cast.ll
+++ b/llvm/test/tools/llubi/fp_cast.ll
@@ -8,12 +8,18 @@ define void @main() {
%si2fp = sitofp i32 -2 to float
%ui2fp = uitofp i32 255 to float
+ %ui2fp_nneg_pos = uitofp nneg i32 255 to float
+ %ui2fp_nneg_neg = uitofp nneg i32 -255 to float
+
%fp2si = fptosi double -4.75 to i32
%fp2ui = fptoui double 4.75 to i32
%oob_ui = fptoui float -1.0 to i32
%nan_si = fptosi float 0x7FF8000000000000 to i32
+ %oob_si_half = sitofp i32 -100000 to half
+ %oob_ui_half = uitofp i32 100000 to half
+
ret void
}
; CHECK: Entering function: main
@@ -21,9 +27,13 @@ define void @main() {
; CHECK-NEXT: %trunc = fptrunc double 3.500000e+00 to float => float 3.500000e+00
; CHECK-NEXT: %si2fp = sitofp i32 -2 to float => float -2.000000e+00
; CHECK-NEXT: %ui2fp = uitofp i32 255 to float => float 2.550000e+02
+; CHECK-NEXT: %ui2fp_nneg_pos = uitofp nneg i32 255 to float => float 2.550000e+02
+; CHECK-NEXT: %ui2fp_nneg_neg = uitofp nneg i32 -255 to float => poison
; CHECK-NEXT: %fp2si = fptosi double -4.750000e+00 to i32 => i32 -4
; CHECK-NEXT: %fp2ui = fptoui double 4.750000e+00 to i32 => i32 4
; CHECK-NEXT: %oob_ui = fptoui float -1.000000e+00 to i32 => poison
; CHECK-NEXT: %nan_si = fptosi float 0x7FF8000000000000 to i32 => poison
+; CHECK-NEXT: %oob_si_half = sitofp i32 -100000 to half => half -Inf
+; CHECK-NEXT: %oob_ui_half = uitofp i32 100000 to half => half +Inf
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index 787fa1fc88849..a0230f48c3ef6 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -1029,6 +1029,11 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
if (Operand.isPoison())
return AnyValue::poison();
+ APInt IOperand = Operand.asInteger();
+
+ if (isa<UIToFPInst>(I) && I.hasNonNeg() && IOperand.isNegative())
+ return AnyValue::poison();
+
APFloat Res(DstSem);
Res.convertFromAPInt(Operand.asInteger(), /*IsSigned=*/IsSigned,
>From 966f4f282c31f431b3a4b6c8f9e43b5317738ebe Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 16:05:40 +0800
Subject: [PATCH 17/20] [llubi] Test update for the nsz flag
---
llvm/test/tools/llubi/fp_fastmath.ll | 5 +++++
llvm/tools/llubi/lib/Interpreter.cpp | 4 +---
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/llvm/test/tools/llubi/fp_fastmath.ll b/llvm/test/tools/llubi/fp_fastmath.ll
index 475f40d2b0457..ba1cf3fca9c42 100644
--- a/llvm/test/tools/llubi/fp_fastmath.ll
+++ b/llvm/test/tools/llubi/fp_fastmath.ll
@@ -8,11 +8,16 @@ define void @main() {
%safe = fadd nnan ninf float 1.0, 2.0
+ %nsz_1 = fneg nsz float -0.0
+ %nsz_2 = fneg nsz float -0.0
+
ret void
}
; CHECK: Entering function: main
; CHECK-NEXT: %bad_nan = fdiv nnan float 0.000000e+00, 0.000000e+00 => poison
; CHECK-NEXT: %bad_inf = fdiv ninf float 1.000000e+00, 0.000000e+00 => poison
; CHECK-NEXT: %safe = fadd nnan ninf float 1.000000e+00, 2.000000e+00 => float 3.000000e+00
+; CHECK-NEXT: %nsz_1 = fneg nsz float -0.000000e+00 => float 0.000000e+00
+; CHECK-NEXT: %nsz_2 = fneg nsz float -0.000000e+00 => float -0.000000e+00
; CHECK-NEXT: ret void
; CHECK-NEXT: Exiting function: main
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index a0230f48c3ef6..d872d73598fdc 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -120,11 +120,9 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
return AnyValue::poison();
if (FMF.noInfs() && APVal.isInfinity())
return AnyValue::poison();
- // Non-deterministically flip the sign of the input.
- if (FMF.noSignedZeros() && APVal.isZero()) {
+ if (FMF.noSignedZeros() && APVal.isZero())
return AnyValue(APFloat::getZero(
APVal.getSemantics(), APVal.isNegative() ^ Ctx.getRandomBool()));
- }
return Val;
}
>From 0b0b2a7e23973d4792049cc6471b6449895eccd0 Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 16:52:06 +0800
Subject: [PATCH 18/20] [llubi] New tests for NaN propagation
---
.../test/tools/llubi/fp_nan_platform_specific.ll | 16 ++++++++++++++++
llvm/test/tools/llubi/fp_nan_preferred.ll | 16 ++++++++++++++++
llvm/test/tools/llubi/fp_nan_quieting.ll | 16 ++++++++++++++++
llvm/test/tools/llubi/fp_nan_unchanged.ll | 16 ++++++++++++++++
4 files changed, 64 insertions(+)
create mode 100644 llvm/test/tools/llubi/fp_nan_platform_specific.ll
create mode 100644 llvm/test/tools/llubi/fp_nan_preferred.ll
create mode 100644 llvm/test/tools/llubi/fp_nan_quieting.ll
create mode 100644 llvm/test/tools/llubi/fp_nan_unchanged.ll
diff --git a/llvm/test/tools/llubi/fp_nan_platform_specific.ll b/llvm/test/tools/llubi/fp_nan_platform_specific.ll
new file mode 100644
index 0000000000000..74665faf1a224
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_nan_platform_specific.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --ubi::NaNPropagationBehavior::TargetSpecificNaN --verbose < %s 2>&1 | FileCheck %s\
+
+define void @main() {
+ %qnan_zero_payload = fadd double 1.0, 0x7FF8000000000000
+ %qnan_nonzero_payload = fadd double 1.0, 0x7FF8000000000001
+ %snan = fadd double 1.0, 0x7FF0000000000001
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %qnan_zero_payload = fadd double 1.000000e+00, 0x7FF8000000000000 => double 0x7FFD3A7C3E40F98B
+; CHECK-NEXT: %qnan_nonzero_payload = fadd double 1.000000e+00, 0x7FF8000000000001 => double 0xFFFD6903CEE3FCEE
+; CHECK-NEXT: %snan = fadd double 1.000000e+00, 0x7FF0000000000001 => double 0x7FFA062AFD5FE9EE
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_nan_preferred.ll b/llvm/test/tools/llubi/fp_nan_preferred.ll
new file mode 100644
index 0000000000000..f85784868e796
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_nan_preferred.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --ubi::NaNPropagationBehavior::PreferredNaN --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %qnan_zero_payload = fadd double 1.0, 0x7FF8000000000000
+ %qnan_nonzero_payload = fadd double 1.0, 0x7FF8000000000001
+ %snan = fadd double 1.0, 0x7FF0000000000001
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %qnan_zero_payload = fadd double 1.000000e+00, 0x7FF8000000000000 => double NaN
+; CHECK-NEXT: %qnan_nonzero_payload = fadd double 1.000000e+00, 0x7FF8000000000001 => double 0xFFF8000000000000
+; CHECK-NEXT: %snan = fadd double 1.000000e+00, 0x7FF0000000000001 => double 0xFFF8000000000000
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_nan_quieting.ll b/llvm/test/tools/llubi/fp_nan_quieting.ll
new file mode 100644
index 0000000000000..cf6db52e57a38
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_nan_quieting.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --ubi::NaNPropagationBehavior::QuietingNaN --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %qnan_zero_payload = fadd double 1.0, 0x7FF8000000000000
+ %qnan_nonzero_payload = fadd double 1.0, 0x7FF8000000000001
+ %snan = fadd double 1.0, 0x7FF0000000000001
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %qnan_zero_payload = fadd double 1.000000e+00, 0x7FF8000000000000 => double NaN
+; CHECK-NEXT: %qnan_nonzero_payload = fadd double 1.000000e+00, 0x7FF8000000000001 => double 0xFFF8000000000001
+; CHECK-NEXT: %snan = fadd double 1.000000e+00, 0x7FF0000000000001 => double 0xFFF8000000000001
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
diff --git a/llvm/test/tools/llubi/fp_nan_unchanged.ll b/llvm/test/tools/llubi/fp_nan_unchanged.ll
new file mode 100644
index 0000000000000..63edb6859ce65
--- /dev/null
+++ b/llvm/test/tools/llubi/fp_nan_unchanged.ll
@@ -0,0 +1,16 @@
+; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
+; RUN: llubi --ubi::NaNPropagationBehavior::UnchangedNaN --verbose < %s 2>&1 | FileCheck %s
+
+define void @main() {
+ %qnan_zero_payload = fadd double 1.0, 0x7FF8000000000000
+ %qnan_nonzero_payload = fadd double 1.0, 0x7FF8000000000001
+ %snan = fadd double 1.0, 0x7FF0000000000001
+
+ ret void
+}
+; CHECK: Entering function: main
+; CHECK-NEXT: %qnan_zero_payload = fadd double 1.000000e+00, 0x7FF8000000000000 => double NaN
+; CHECK-NEXT: %qnan_nonzero_payload = fadd double 1.000000e+00, 0x7FF8000000000001 => double 0xFFF8000000000001
+; CHECK-NEXT: %snan = fadd double 1.000000e+00, 0x7FF0000000000001 => double 0xFFF0000000000001
+; CHECK-NEXT: ret void
+; CHECK-NEXT: Exiting function: main
>From 8c2a6690581c61aedab5dadc52d267d789b9e1af Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 16:58:15 +0800
Subject: [PATCH 19/20] [llubi] Fix getCurrentDenormalMode()
---
llvm/tools/llubi/lib/Interpreter.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/tools/llubi/lib/Interpreter.cpp b/llvm/tools/llubi/lib/Interpreter.cpp
index d872d73598fdc..e54b1271b5bef 100644
--- a/llvm/tools/llubi/lib/Interpreter.cpp
+++ b/llvm/tools/llubi/lib/Interpreter.cpp
@@ -475,7 +475,7 @@ class InstExecutor : public InstVisitor<InstExecutor, void>,
DenormalMode getCurrentDenormalMode(Instruction &I) {
return CurrentFrame->Func.getDenormalMode(
- I.getType()->getScalarType()->getFltSemantics());
+ I.getOperand(0)->getType()->getScalarType()->getFltSemantics());
}
public:
>From 1785cf3170e7c14906e2b56e60ca360066e0d22f Mon Sep 17 00:00:00 2001
From: Zhige Chen <zhige_chen at outlook.com>
Date: Thu, 2 Apr 2026 19:05:52 +0800
Subject: [PATCH 20/20] [llubi] Fix incorrect RUN command in test
---
llvm/test/tools/llubi/fp_nan_platform_specific.ll | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/test/tools/llubi/fp_nan_platform_specific.ll b/llvm/test/tools/llubi/fp_nan_platform_specific.ll
index 74665faf1a224..dd59663b8e76d 100644
--- a/llvm/test/tools/llubi/fp_nan_platform_specific.ll
+++ b/llvm/test/tools/llubi/fp_nan_platform_specific.ll
@@ -1,5 +1,5 @@
; NOTE: Assertions have been autogenerated by utils/update_llubi_test_checks.py UTC_ARGS: --version 6
-; RUN: llubi --ubi::NaNPropagationBehavior::TargetSpecificNaN --verbose < %s 2>&1 | FileCheck %s\
+; RUN: llubi --ubi::NaNPropagationBehavior::TargetSpecificNaN --verbose < %s 2>&1 | FileCheck %s
define void @main() {
%qnan_zero_payload = fadd double 1.0, 0x7FF8000000000000
More information about the llvm-commits
mailing list