[Analysis] Adding convert_from_arbitrary_fp support in computeKnownFPClass. (#208585)

fixes #208437 

From LangRef for intrinsic convert_from_arbitrary_fp ,
"Normal finite values are converted exactly. NaN values follow LLVM’s
standard NaN rules; the NaN representation is preserved... and the NaN
payload may be truncated or extended..."
-> if src has Nan -> preserve nan

"If a value exceeds the representable range of the target type (for
example, converting Float8E8M0FNU with large exponents to half), the
result is converted to infinity with the appropriate sign."
This commit is contained in:
Chirag Patel 2026-07-29 19:05:52 +05:30 committed by GitHub
parent 03de9b148a
commit dc5f2aae75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 231 additions and 0 deletions

View file

@ -5727,6 +5727,45 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
Known.knownNot(fcNan | fcInf | fcNegative);
break;
}
case Intrinsic::convert_from_arbitrary_fp: {
auto *MD = cast<MetadataAsValue>(II->getArgOperand(1))->getMetadata();
StringRef FormatStr = cast<MDString>(MD)->getString();
const fltSemantics *SrcSemantics =
APFloat::getArbitraryFPSemantics(FormatStr);
if (!SrcSemantics)
break;
const fltSemantics DstSemantics =
II->getType()->getScalarType()->getFltSemantics();
if (!APFloat::semanticsHasNaN(*SrcSemantics))
Known.knownNot(fcNan);
// fcInf can only be cleared if the source format has no Inf encoding
// and the dst max exp can accommodate src max exp.
if (!APFloat::semanticsHasInf(*SrcSemantics) &&
APFloat::semanticsMaxExponent(*SrcSemantics) <=
APFloat::semanticsMaxExponent(DstSemantics))
Known.knownNot(fcInf);
// Check and clear all neg flags for formats that do not have signed
// representation.
if (!APFloat::semanticsHasSignedRepr(*SrcSemantics))
Known.knownNot(fcNegative);
// Check if format has no zero at all (Float8E8M0FNU), or no negative
// zero.
if (!APFloat::semanticsHasZero(*SrcSemantics))
Known.knownNot(fcZero);
else if (SrcSemantics->nanEncoding == fltNanEncoding::NegativeZero)
Known.knownNot(fcNegZero);
// If src lands normally in dest, the result can never be subnormal.
if (APFloat::isRepresentableAsNormalIn(*SrcSemantics, DstSemantics))
Known.knownNot(fcSubnormal);
break;
}
default:
break;
}

View file

@ -0,0 +1,192 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -S < %s | FileCheck %s
define float @test_convert_float8e5m2_to_f32(i8 %bits) {
; CHECK-LABEL: define nofpclass(sub) float @test_convert_float8e5m2_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0:[0-9]+]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(sub) float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E5M2") #[[ATTR2:[0-9]+]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E5M2")
ret float %fp
}
define float @test_convert_float8e5m2fnuz_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e5m2fnuz_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E5M2FNUZ") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E5M2FNUZ")
ret float %fp
}
define float @test_convert_float8e4m3_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e4m3_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E4M3") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E4M3")
ret float %fp
}
define float @test_convert_float8e4m3fn_to_f32(i8 %bits) {
; CHECK-LABEL: define nofpclass(inf sub) float @test_convert_float8e4m3fn_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(inf sub) float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E4M3FN") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E4M3FN")
ret float %fp
}
define float @test_convert_float8e4m3fnuz_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e4m3fnuz_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E4M3FNUZ") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E4M3FNUZ")
ret float %fp
}
define float @test_convert_float8e4m3b11fnuz_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e4m3b11fnuz_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E4M3B11FNUZ") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E4M3B11FNUZ")
ret float %fp
}
define float @test_convert_float8e3m4_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e3m4_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E3M4") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E3M4")
ret float %fp
}
define float @test_convert_float8e8m0fnu_to_f32(i8 %bits) {
; CHECK-LABEL: define float @test_convert_float8e8m0fnu_to_f32(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 [[BITS]], metadata !"Float8E8M0FNU") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i8(i8 %bits, metadata !"Float8E8M0FNU")
ret float %fp
}
define float @test_convert_float6e3m2fn_to_f32(i6 %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) float @test_convert_float6e3m2fn_to_f32(
; CHECK-SAME: i6 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) float @llvm.convert.from.arbitrary.fp.f32.i6(i6 [[BITS]], metadata !"Float6E3M2FN") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i6(i6 %bits, metadata !"Float6E3M2FN")
ret float %fp
}
define float @test_convert_float6e2m3fn_to_f32(i6 %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) float @test_convert_float6e2m3fn_to_f32(
; CHECK-SAME: i6 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) float @llvm.convert.from.arbitrary.fp.f32.i6(i6 [[BITS]], metadata !"Float6E2M3FN") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i6(i6 %bits, metadata !"Float6E2M3FN")
ret float %fp
}
define float @test_convert_float4e2m1fn_to_f32(i4 %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) float @test_convert_float4e2m1fn_to_f32(
; CHECK-SAME: i4 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) float @llvm.convert.from.arbitrary.fp.f32.i4(i4 [[BITS]], metadata !"Float4E2M1FN") #[[ATTR2]]
; CHECK-NEXT: ret float [[FP]]
;
%fp = call float @llvm.convert.from.arbitrary.fp.f32.i4(i4 %bits, metadata !"Float4E2M1FN")
ret float %fp
}
define double @test_convert_float4e2m1fn_to_f64(i4 %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) double @test_convert_float4e2m1fn_to_f64(
; CHECK-SAME: i4 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) double @llvm.convert.from.arbitrary.fp.f64.i4(i4 [[BITS]], metadata !"Float4E2M1FN") #[[ATTR2]]
; CHECK-NEXT: ret double [[FP]]
;
%fp = call double @llvm.convert.from.arbitrary.fp.f64.i4(i4 %bits, metadata !"Float4E2M1FN")
ret double %fp
}
define half @test_convert_float8e4m3fn_to_f16(i8 %bits) {
; CHECK-LABEL: define nofpclass(inf sub) half @test_convert_float8e4m3fn_to_f16(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(inf sub) half @llvm.convert.from.arbitrary.fp.f16.i8(i8 [[BITS]], metadata !"Float8E4M3FN") #[[ATTR2]]
; CHECK-NEXT: ret half [[FP]]
;
%fp = call half @llvm.convert.from.arbitrary.fp.f16.i8(i8 %bits, metadata !"Float8E4M3FN")
ret half %fp
}
define half @test_convert_float6e2m3fn_to_f16(i6 %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) half @test_convert_float6e2m3fn_to_f16(
; CHECK-SAME: i6 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) half @llvm.convert.from.arbitrary.fp.f16.i6(i6 [[BITS]], metadata !"Float6E2M3FN") #[[ATTR2]]
; CHECK-NEXT: ret half [[FP]]
;
%fp = call half @llvm.convert.from.arbitrary.fp.f16.i6(i6 %bits, metadata !"Float6E2M3FN")
ret half %fp
}
define <4 x half> @test_convert_float8e5m2_to_v4f16(<4 x i8> %bits) {
; CHECK-LABEL: define <4 x half> @test_convert_float8e5m2_to_v4f16(
; CHECK-SAME: <4 x i8> [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(<4 x i8> [[BITS]], metadata !"Float8E5M2") #[[ATTR2]]
; CHECK-NEXT: ret <4 x half> [[FP]]
;
%fp = call <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(<4 x i8> %bits, metadata !"Float8E5M2")
ret <4 x half> %fp
}
define <4 x double> @test_convert_float4e2m1fn_to_v4f64(<4 x i4> %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) <4 x double> @test_convert_float4e2m1fn_to_v4f64(
; CHECK-SAME: <4 x i4> [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i4(<4 x i4> [[BITS]], metadata !"Float4E2M1FN") #[[ATTR2]]
; CHECK-NEXT: ret <4 x double> [[FP]]
;
%fp = call <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i4(<4 x i4> %bits, metadata !"Float4E2M1FN")
ret <4 x double> %fp
}
define <4 x double> @test_convert_float6e3m2fn_to_v4f64(<4 x i6> %bits) {
; CHECK-LABEL: define nofpclass(nan inf sub) <4 x double> @test_convert_float6e3m2fn_to_v4f64(
; CHECK-SAME: <4 x i6> [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call nofpclass(nan inf sub) <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i6(<4 x i6> [[BITS]], metadata !"Float6E3M2FN") #[[ATTR2]]
; CHECK-NEXT: ret <4 x double> [[FP]]
;
%fp = call <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i6(<4 x i6> %bits, metadata !"Float6E3M2FN")
ret <4 x double> %fp
}
define half @test_convert_float8e8m0fnu_to_f16(i8 %bits) {
; CHECK-LABEL: define half @test_convert_float8e8m0fnu_to_f16(
; CHECK-SAME: i8 [[BITS:%.*]]) #[[ATTR0]] {
; CHECK-NEXT: [[FP:%.*]] = call half @llvm.convert.from.arbitrary.fp.f16.i8(i8 [[BITS]], metadata !"Float8E8M0FNU") #[[ATTR2]]
; CHECK-NEXT: ret half [[FP]]
;
%fp = call half @llvm.convert.from.arbitrary.fp.f16.i8(i8 %bits, metadata !"Float8E8M0FNU")
ret half %fp
}
declare float @llvm.convert.from.arbitrary.fp.f32.i4(i4, metadata)
declare float @llvm.convert.from.arbitrary.fp.f32.i6(i6, metadata)
declare float @llvm.convert.from.arbitrary.fp.f32.i8(i8, metadata)
declare double @llvm.convert.from.arbitrary.fp.f64.i4(i4, metadata)
declare half @llvm.convert.from.arbitrary.fp.f16.i8(i8, metadata)
declare half @llvm.convert.from.arbitrary.fp.f16.i6(i6, metadata)
declare <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i4(<4 x i4>, metadata)
declare <4 x half> @llvm.convert.from.arbitrary.fp.v4f16.v4i8(<4 x i8>, metadata)
declare <4 x double> @llvm.convert.from.arbitrary.fp.v4f64.v4i6(<4 x i6>, metadata)