-
Notifications
You must be signed in to change notification settings - Fork 17.9k
[SelectionDAG] Add expansion for llvm.convert.from.arbitrary.fp #179318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
9b1a6b6
8c4426e
1a00323
a747ef5
dc41832
e019c5d
0fc6c6d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3495,6 +3495,254 @@ bool SelectionDAGLegalize::ExpandNode(SDNode *Node) { | |
| Results.push_back(Op); | ||
| break; | ||
| } | ||
| case ISD::CONVERT_FROM_ARBITRARY_FP: { | ||
| // Expand conversion from arbitrary FP format stored in an integer to a | ||
| // native IEEE float type using integer bit manipulation. | ||
| // | ||
| // TODO: currently only conversions from FP4, FP6 and FP8 formats from OCP | ||
| // specification are expanded. Remaining arbitrary FP types: Float8E4M3, | ||
| // Float8E3M4, Float8E5M2FNUZ, Float8E4M3FNUZ, Float8E4M3B11FNUZ, | ||
| // Float8E8M0FNU. | ||
| EVT DstVT = Node->getValueType(0); | ||
|
|
||
| // For vector types, unroll into scalar operations. | ||
| if (DstVT.isVector()) { | ||
| Results.push_back(DAG.UnrollVectorOp(Node)); | ||
| break; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you not do this? I don't see anything in the code here that shouldn't just naturally work out for vectors.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, it would require a bit of patch rewriting, let me check, what I can do. Unrolling is required in the current version due to the fact, that the expansion creates SETCC + vselect nodes for NaN/Inf/etc classification. These produce vector i1 types that are (AFAIU) not legal on AMDGPU.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, with CONVERT_FROM_ARBITRARY_FP registered in LegalizeVectorOps.cpp UnrollVectorOp is not needed. |
||
|
|
||
| SDValue IntVal = Node->getOperand(0); | ||
| const uint64_t SemEnum = Node->getConstantOperandVal(1); | ||
| const auto Sem = static_cast<APFloatBase::Semantics>(SemEnum); | ||
|
|
||
| // Supported source formats. | ||
| switch (Sem) { | ||
| case APFloatBase::S_Float8E5M2: | ||
| case APFloatBase::S_Float8E4M3FN: | ||
| case APFloatBase::S_Float6E3M2FN: | ||
| case APFloatBase::S_Float6E2M3FN: | ||
| case APFloatBase::S_Float4E2M1FN: | ||
| break; | ||
| default: | ||
| report_fatal_error("CONVERT_FROM_ARBITRARY_FP: unsupported source " | ||
| "format (semantics enum " + | ||
| Twine(SemEnum) + ")"); | ||
|
MrSidims marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| const fltSemantics &SrcSem = APFloatBase::EnumToSemantics(Sem); | ||
|
|
||
| const unsigned SrcBits = APFloat::getSizeInBits(SrcSem); | ||
| const unsigned SrcPrecision = APFloat::semanticsPrecision(SrcSem); | ||
| const unsigned SrcMant = SrcPrecision - 1; | ||
| const unsigned SrcExp = SrcBits - SrcMant - 1; | ||
| const int SrcBias = 1 - APFloat::semanticsMinExponent(SrcSem); | ||
|
|
||
| const fltNonfiniteBehavior NFBehavior = SrcSem.nonFiniteBehavior; | ||
| const fltNanEncoding NanEnc = SrcSem.nanEncoding; | ||
|
|
||
| // Destination format parameters. | ||
| const fltSemantics *DstSem; | ||
| if (DstVT == MVT::f16) | ||
|
MrSidims marked this conversation as resolved.
Outdated
|
||
| DstSem = &APFloat::IEEEhalf(); | ||
| else if (DstVT == MVT::bf16) | ||
| DstSem = &APFloat::BFloat(); | ||
| else if (DstVT == MVT::f32) | ||
| DstSem = &APFloat::IEEEsingle(); | ||
| else if (DstVT == MVT::f64) | ||
| DstSem = &APFloat::IEEEdouble(); | ||
| else | ||
| llvm_unreachable("Unsupported destination float type"); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why won't fp128 work?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will work, I was just having in mind SPIR-V capabilities (where fp128 is not supported) and this is a wrong way of doing the patch. Implicitly fixed be applying #179318 (comment) |
||
|
|
||
| const unsigned DstBits = APFloat::getSizeInBits(*DstSem); | ||
| const unsigned DstMant = APFloat::semanticsPrecision(*DstSem) - 1; | ||
| const unsigned DstExpBits = DstBits - DstMant - 1; | ||
| const int DstMinExp = APFloat::semanticsMinExponent(*DstSem); | ||
| const int DstBias = 1 - DstMinExp; | ||
| const uint64_t DstExpAllOnes = (1ULL << DstExpBits) - 1; | ||
|
|
||
| // Work in an integer type matching the destination float width. | ||
| // Use zero-extend to preserve the raw bit-pattern. | ||
| EVT IntVT = EVT::getIntegerVT(*DAG.getContext(), DstBits); | ||
| SDValue Src = DAG.getZExtOrTrunc(IntVal, dl, IntVT); | ||
|
|
||
| EVT SetCCVT = | ||
| TLI.getSetCCResultType(DAG.getDataLayout(), *DAG.getContext(), IntVT); | ||
| SDValue Zero = DAG.getConstant(0, dl, IntVT); | ||
| SDValue One = DAG.getConstant(1, dl, IntVT); | ||
|
|
||
| // Extract bit fields. | ||
| const uint64_t MantMask = (SrcMant > 0) ? ((1ULL << SrcMant) - 1) : 0; | ||
| const uint64_t ExpMask = (1ULL << SrcExp) - 1; | ||
|
|
||
| SDValue MantField = DAG.getNode(ISD::AND, dl, IntVT, Src, | ||
| DAG.getConstant(MantMask, dl, IntVT)); | ||
|
|
||
| SDValue ExpField = | ||
| DAG.getNode(ISD::AND, dl, IntVT, | ||
| DAG.getNode(ISD::SRL, dl, IntVT, Src, | ||
| DAG.getShiftAmountConstant(SrcMant, IntVT, dl)), | ||
| DAG.getConstant(ExpMask, dl, IntVT)); | ||
|
|
||
| SDValue SignBit = | ||
| DAG.getNode(ISD::SRL, dl, IntVT, Src, | ||
| DAG.getShiftAmountConstant(SrcBits - 1, IntVT, dl)); | ||
|
|
||
| // Precompute sign shifted to MSB of destination. | ||
| SDValue SignShifted = | ||
| DAG.getNode(ISD::SHL, dl, IntVT, SignBit, | ||
| DAG.getShiftAmountConstant(DstBits - 1, IntVT, dl)); | ||
|
|
||
| // Classify the input value based on compile-time format properties. | ||
| SDValue ExpAllOnes = DAG.getConstant(ExpMask, dl, IntVT); | ||
| SDValue IsExpAllOnes = | ||
| DAG.getSetCC(dl, SetCCVT, ExpField, ExpAllOnes, ISD::SETEQ); | ||
| SDValue IsExpZero = DAG.getSetCC(dl, SetCCVT, ExpField, Zero, ISD::SETEQ); | ||
| SDValue IsMantZero = DAG.getSetCC(dl, SetCCVT, MantField, Zero, ISD::SETEQ); | ||
| SDValue IsMantNonZero = | ||
| DAG.getSetCC(dl, SetCCVT, MantField, Zero, ISD::SETNE); | ||
|
|
||
| // NaN detection. | ||
| SDValue IsNaN; | ||
| if (NFBehavior == fltNonfiniteBehavior::FiniteOnly) { | ||
| // FiniteOnly formats (E2M1FN, E3M2FN, E2M3FN) never produce NaN. | ||
| IsNaN = DAG.getBoolConstant(false, dl, SetCCVT, IntVT); | ||
| } else if (NFBehavior == fltNonfiniteBehavior::IEEE754) { | ||
| // E5M2 produces NaN when exp == all-ones AND mantissa != 0. | ||
| IsNaN = DAG.getNode(ISD::AND, dl, SetCCVT, IsExpAllOnes, IsMantNonZero); | ||
| } else { | ||
| // NanOnly + AllOnes (E4M3FN): NaN when all exp and mantissa bits are 1. | ||
| assert(NanEnc == fltNanEncoding::AllOnes); | ||
| SDValue MantAllOnes = DAG.getConstant(MantMask, dl, IntVT); | ||
| SDValue IsMantAllOnes = | ||
| DAG.getSetCC(dl, SetCCVT, MantField, MantAllOnes, ISD::SETEQ); | ||
| IsNaN = DAG.getNode(ISD::AND, dl, SetCCVT, IsExpAllOnes, IsMantAllOnes); | ||
| } | ||
|
|
||
| // Inf detection. | ||
| SDValue IsInf; | ||
| if (NFBehavior == fltNonfiniteBehavior::IEEE754) { | ||
| // E5M2: Inf when exp == all-ones AND mantissa == 0. | ||
| IsInf = DAG.getNode(ISD::AND, dl, SetCCVT, IsExpAllOnes, IsMantZero); | ||
| } else { | ||
| // NanOnly and FiniteOnly formats have no Inf representation. | ||
| IsInf = DAG.getBoolConstant(false, dl, SetCCVT, IntVT); | ||
| } | ||
|
|
||
| // Zero detection. | ||
| SDValue IsZero = DAG.getNode(ISD::AND, dl, SetCCVT, IsExpZero, IsMantZero); | ||
|
|
||
| // Denorm detection: exp == 0 AND mant != 0. | ||
| SDValue IsDenorm = | ||
| DAG.getNode(ISD::AND, dl, SetCCVT, IsExpZero, IsMantNonZero); | ||
|
|
||
| // Normal value conversion. | ||
| // dst_exp = exp_field + (DstBias - SrcBias) | ||
| // dst_mant = mant << (DstMant - SrcMant) | ||
| const int BiasAdjust = DstBias - SrcBias; | ||
| SDValue NormDstExp = DAG.getNode( | ||
| ISD::ADD, dl, IntVT, ExpField, | ||
| DAG.getConstant(APInt(DstBits, BiasAdjust, true), dl, IntVT)); | ||
|
|
||
| SDValue NormDstMant; | ||
| if (DstMant > SrcMant) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Braces and temp variable to avoid ugly line breaks
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied |
||
| NormDstMant = | ||
| DAG.getNode(ISD::SHL, dl, IntVT, MantField, | ||
| DAG.getShiftAmountConstant(DstMant - SrcMant, IntVT, dl)); | ||
| else | ||
| NormDstMant = MantField; | ||
|
|
||
| // Assemble normal result. | ||
| SDValue NormExpShifted = | ||
| DAG.getNode(ISD::SHL, dl, IntVT, NormDstExp, | ||
| DAG.getShiftAmountConstant(DstMant, IntVT, dl)); | ||
| SDValue NormResult = DAG.getNode( | ||
| ISD::OR, dl, IntVT, | ||
| DAG.getNode(ISD::OR, dl, IntVT, SignShifted, NormExpShifted), | ||
| NormDstMant); | ||
|
|
||
| // Denormal value conversion. | ||
| // For a denormal source (exp_field == 0, mant != 0), normalize by finding | ||
| // the MSB position of mant using CTLZ, then compute the correct | ||
| // exponent and mantissa for the destination format. | ||
| SDValue DenormResult; | ||
| { | ||
| const unsigned IntVTBits = DstBits; | ||
| SDValue LeadingZeros = | ||
| DAG.getNode(ISD::CTLZ_ZERO_UNDEF, dl, IntVT, MantField); | ||
|
|
||
| // dst_exp_denorm = (IntVTBits + DstBias - SrcBias - SrcMant) - | ||
| // LeadingZeros | ||
| const int DenormExpConst = | ||
| (int)IntVTBits + DstBias - SrcBias - (int)SrcMant; | ||
| SDValue DenormDstExp = DAG.getNode( | ||
| ISD::SUB, dl, IntVT, | ||
| DAG.getConstant(APInt(DstBits, DenormExpConst, true), dl, IntVT), | ||
| LeadingZeros); | ||
|
|
||
| // MSB position of the mantissa (0-indexed from LSB). | ||
| SDValue MantMSB = | ||
| DAG.getNode(ISD::SUB, dl, IntVT, | ||
| DAG.getConstant(IntVTBits - 1, dl, IntVT), LeadingZeros); | ||
|
|
||
| // leading_one = 1 << MantMSB | ||
| SDValue LeadingOne = DAG.getNode(ISD::SHL, dl, IntVT, One, MantMSB); | ||
|
|
||
| // frac = mant XOR leading_one (strip the implicit 1) | ||
| SDValue Frac = DAG.getNode(ISD::XOR, dl, IntVT, MantField, LeadingOne); | ||
|
|
||
| // shift_amount = DstMant - MantMSB | ||
| // = DstMant - (IntVTBits - 1 - LeadingZeros) | ||
| // = LeadingZeros - (IntVTBits - 1 - DstMant) | ||
| const unsigned ShiftSub = IntVTBits - 1 - DstMant; // always >= 0 | ||
| SDValue ShiftAmount = DAG.getNode(ISD::SUB, dl, IntVT, LeadingZeros, | ||
| DAG.getConstant(ShiftSub, dl, IntVT)); | ||
|
|
||
| SDValue DenormDstMant = | ||
| DAG.getNode(ISD::SHL, dl, IntVT, Frac, ShiftAmount); | ||
|
|
||
| // Assemble denorm as sign | (denorm_dst_exp << DstMant) | denorm_dst_mant | ||
| SDValue DenormExpShifted = | ||
| DAG.getNode(ISD::SHL, dl, IntVT, DenormDstExp, | ||
| DAG.getShiftAmountConstant(DstMant, IntVT, dl)); | ||
| DenormResult = DAG.getNode( | ||
| ISD::OR, dl, IntVT, | ||
| DAG.getNode(ISD::OR, dl, IntVT, SignShifted, DenormExpShifted), | ||
| DenormDstMant); | ||
| } | ||
|
|
||
| // Select between normal and denorm paths. | ||
| SDValue FiniteResult = | ||
| DAG.getSelect(dl, IntVT, IsDenorm, DenormResult, NormResult); | ||
|
|
||
| // Build special-value results. | ||
| // NaN -> canonical quiet NaN: sign=0, exp=all-ones, qNaN bit set. | ||
| // Encoding: (DstExpAllOnes << DstMant) | (1 << (DstMant - 1)) | ||
| const uint64_t QNaNBit = (DstMant > 0) ? (1ULL << (DstMant - 1)) : 0; | ||
| SDValue NaNResult = | ||
| DAG.getConstant((DstExpAllOnes << DstMant) | QNaNBit, dl, IntVT); | ||
|
|
||
| // Inf -> destination Inf. | ||
| // sign | (DstExpAllOnes << DstMant) | ||
| SDValue InfResult = | ||
| DAG.getNode(ISD::OR, dl, IntVT, SignShifted, | ||
| DAG.getConstant(DstExpAllOnes << DstMant, dl, IntVT)); | ||
|
|
||
| // Zero -> signed zero. | ||
| // Sign bit only. | ||
| SDValue ZeroResult = SignShifted; | ||
|
|
||
| // Final selection goes in order: NaN takes priority, then Inf, then Zero. | ||
| SDValue Result = FiniteResult; | ||
| Result = DAG.getSelect(dl, IntVT, IsZero, ZeroResult, Result); | ||
| Result = DAG.getSelect(dl, IntVT, IsInf, InfResult, Result); | ||
| Result = DAG.getSelect(dl, IntVT, IsNaN, NaNResult, Result); | ||
|
|
||
| // Bitcast integer result to destination float type. | ||
| Result = DAG.getNode(ISD::BITCAST, dl, DstVT, Result); | ||
|
|
||
| Results.push_back(Result); | ||
| break; | ||
| } | ||
| case ISD::FCANONICALIZE: { | ||
| // This implements llvm.canonicalize.f* by multiplication with 1.0, as | ||
| // suggested in | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TargetTransformInfo::isLegalArbitraryFpConversionUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will add target specific lowering as well for AMDGPU and SPIR-V backends in follow up patches. In SPIR-V there is a public multi-vendor extension SPV_EXT_float8 that introduces fp8 <-> ieee float conversions. For AMDGPU I see quite a few entries in VOP3Instructions.td, but it requires me to do some research to see what intructions from there can be mapped to
llvm.convert.from.arbitrary.fpandllvm.convert.to.arbitrary.fp(the later will be added right after this PR is merged as it reuses some of the utility functions) intrinsics.Also I believe there are NVPTX capabilities for this and there is SPV_INTEL_float4 extension, but those will be covered by appropriate folks I guess.
Depending on where to place such rewriting. If in middle end - it would require a pass to check which target triple the module has to skip those targets which have native support. I personally don't mind such skip, but I know that some folks would object, saying that it's not LLVM way.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have mixed views about IR expansions. I simultaneously think they're a hack, and we'd be better off if we did more legalization on IR than in SelectionDAG/GISel. These cases do not require control flow, so they can follow SOP and do the DAG expansion. The downsides of the DAG expansion is that DAG combiner will always be worse than any IR optimizations.
They also add an unstructured ordering property to the "modular IR"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re the fp8 operations, I can advise on those - I did a lot of the plumbing up in MLIR
This also means we'll probably have the awkward impedance mismatch where the AMDGPU target really dislikes APIs that have the from
<N x i8>but that'll be the best form for this sort of convert.from.arbitrary intrinsic to represent vectors.(I'm also going to flag the interesting notes that many of these operations come in vector flavors - stuff like "take 16 bits, treat that as 2 x fp8, and expand that to 2 x float" - or, really, take a specified half of 32 bits". That's all stuf that can be done in the optimizer, but it's a long-term hazard.)