[Mlir-commits] [mlir] [MLIR][XeVM] Add xevm.extf op as the inverse of xevm.truncf (PR #203124)
Artem Kroviakov
llvmlistbot at llvm.org
Thu Jun 11 05:28:14 PDT 2026
================
@@ -1307,6 +1307,129 @@ class TruncfToOCLPattern : public OpConversionPattern<TruncfOp> {
}
};
+class ExtfToOCLPattern : public OpConversionPattern<ExtfOp> {
+ using OpConversionPattern::OpConversionPattern;
+ LogicalResult
+ matchAndRewrite(ExtfOp op, ExtfOp::Adaptor adaptor,
+ ConversionPatternRewriter &rewriter) const override {
+ // `xevm.extf` is the inverse of `xevm.truncf`. Supported source and result
+ // types are restricted for now, mirroring the truncf lowering.
+ auto srcEtype = op.getSrcEtype().getEtype();
+ auto dstEtype = op.getDstEtype().getEtype();
+ // Scalar case is not supported until usage case become clear.
+ auto vecSrcTy = dyn_cast<VectorType>(op.getSrc().getType());
+ if (!vecSrcTy)
+ return rewriter.notifyMatchFailure(op, "Scalar src is not supported.");
+ auto vecDstTy = dyn_cast<VectorType>(op.getDst().getType());
+ if (!vecDstTy)
+ return rewriter.notifyMatchFailure(op, "Scalar dst is not supported.");
+ Value src = op.getSrc();
+ auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
+ /*other=*/LLVM::ModRefInfo::NoModRef,
+ /*argMem=*/LLVM::ModRefInfo::NoModRef,
+ /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
+ /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
+ /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
+ /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
+ auto funcAttrs = convergentNoUnwindWillReturnAttrs;
+ funcAttrs.memEffectsAttr = memAttr;
+
+ // Handle the case where src type is fp4 (e2m1) first.
+ if (srcEtype == ExtfSrcElemTypes::E2M1) {
+ // 16 fp4 values are packed into vector<8xi8>, the result is a
+ // vector<16xf16> or vector<16xbf16>.
+ // Use:
+ // uint16 __builtin_IB_shfl_idx4_lut(int lut_index)
+ // uint8 __builtin_IB_shfl_idx4_to_fp16_8_packed(uint16 lut,
+ // char8 source)
+ // The lookup table selects the target format:
+ // 7 = e2m1 -> f16, 5 = e2m1 -> bf16.
+ if (vecSrcTy.getNumElements() != 8 || vecDstTy.getNumElements() != 16)
+ return rewriter.notifyMatchFailure(
+ op, "fp4 src expects a vector<8xi8> src and a 16 element dst");
+ constexpr int kLutE2M1ToF16 = 7;
+ constexpr int kLutE2M1ToBF16 = 5;
+ int lutIndex =
+ (dstEtype == ExtfDstElemTypes::F16) ? kLutE2M1ToF16 : kLutE2M1ToBF16;
+ Value lutIdx = LLVM::ConstantOp::create(rewriter, op.getLoc(),
+ rewriter.getI32Type(), lutIndex);
+ Type lutTy = VectorType::get(16, rewriter.getI32Type());
+ Value lut =
+ createDeviceFunctionCall(rewriter, "__builtin_IB_shfl_idx4_lut",
+ lutTy, {lutIdx.getType()}, {lutIdx}, {},
+ funcAttrs, op.getOperation())
+ ->getResult(0);
+ Type packedResTy = VectorType::get(8, rewriter.getI32Type());
+ SmallVector<Type> convArgTypes{lut.getType(), src.getType()};
+ SmallVector<Value> convArgs{lut, src};
+ Value result =
+ createDeviceFunctionCall(
+ rewriter, "__builtin_IB_shfl_idx4_to_fp16_8_packed", packedResTy,
+ convArgTypes, convArgs, {}, funcAttrs, op.getOperation())
+ ->getResult(0);
+ // The builtin returns the f16/bf16 bits packed as i32, bitcast to the
+ // f16/bf16 dst type.
+ result = LLVM::BitcastOp::create(rewriter, op.getLoc(), vecDstTy, result);
+ rewriter.replaceOp(op, result);
+ return success();
+ }
+
+ // Handle the case where src type is fp8 (bf8/hf8).
+ // Only 16 input elements are supported, see TruncfToOCLPattern for details.
+ if (vecSrcTy.getNumElements() != 16)
+ return rewriter.notifyMatchFailure(
+ op, "Only vector src of 16 elements is supported");
+
+ // Step 1: Extend fp8 (bf8/hf8) to F16.
+ // bf8 -> half: half16 __builtin_IB_bf8tohf_16(char16)
+ // hf8 -> half: half16 __builtin_IB_hf8tohf_16(char16)
+ std::string fnName = (srcEtype == ExtfSrcElemTypes::BF8)
+ ? "__builtin_IB_bf8tohf_16"
+ : "__builtin_IB_hf8tohf_16";
+ Type f16Ty = VectorType::get(vecSrcTy.getShape(), rewriter.getF16Type());
+ SmallVector<Type> argTypes{src.getType()};
+ SmallVector<Value> args{src};
+ Value result =
+ createDeviceFunctionCall(rewriter, fnName, f16Ty, argTypes, args, {},
+ funcAttrs, op.getOperation())
+ ->getResult(0);
+
+ // When the destination is F16, we are done.
+ if (dstEtype == ExtfDstElemTypes::F16) {
+ rewriter.replaceOp(op, result);
+ return success();
+ }
+
+ // BF16 destination needs some postprocessing.
+ // First extend F16 to F32 and then truncate to BF16.
+ // Step 2: Extend to F32.
+ // Use float16 convert_float16(half16)
+ std::string convFnName = "convert_float16";
+ SmallVector<Type> convArgTypes{result.getType()};
+ SmallVector<Value> convArgs{result};
+ convFnName = mangle(convFnName, convArgTypes);
+ Type f32Ty = VectorType::get(vecSrcTy.getShape(), rewriter.getF32Type());
+ result =
+ createDeviceFunctionCall(rewriter, convFnName, f32Ty, convArgTypes,
+ convArgs, {}, funcAttrs, op.getOperation())
+ ->getResult(0);
+ // Step 3: Truncate F32 to BF16.
+ // Use short16 __builtin_IB_ftobf_16(float16)
+ std::string ftobfFnName = "__builtin_IB_ftobf_16";
----------------
akroviakov wrote:
Constexpr since no mangling?
https://github.com/llvm/llvm-project/pull/203124
More information about the Mlir-commits
mailing list