[clang] [CIR] Implement fixed-point arithmetic lowering (PR #217710)

via cfe-commits cfe-commits at lists.llvm.org
Thu Aug 20 11:19:22 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Erich Keane (erichkeane)

<details>
<summary>Changes</summary>

This should be the rest of the lowering for fixed point, so it removes the `MissingFeatures` listing. It is a faithful re-implementation of classic-lowering as best I can tell, except the create<COMPARE> ops are combined.

---

Patch is 60.47 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217710.diff


3 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (-1) 
- (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+341-50) 
- (added) clang/test/CIR/CodeGen/fixed-point-arith.cpp (+759) 


``````````diff
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 041820faa27cc..ea14bf20713b2 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -318,7 +318,6 @@ struct MissingFeatures {
   static bool scalableVectors() { return false; }
   static bool unsizedTypes() { return false; }
   static bool vectorType() { return false; }
-  static bool fixedPointType() { return false; }
 
   // Future CIR operations
   static bool callOp() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
index 73978b4e5cffe..887d392f7883f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
@@ -615,6 +615,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
   mlir::Value VisitUnaryPreInc(const UnaryOperator *e) {
     return VisitUnaryPrePostIncDec(e);
   }
+
+  mlir::Value emitFixedPointIncDec(const UnaryOperator *e, mlir::Value value, QualType type);
   mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv) {
     if (cgf.getLangOpts().OpenMP)
       cgf.cgm.errorNYI(e->getSourceRange(), "inc/dec OpenMP");
@@ -727,8 +729,7 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
         return {};
       }
     } else if (type->isFixedPointType()) {
-      cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec other fixed point");
-      return {};
+      value = emitFixedPointIncDec(e, value, type);
     } else {
       assert(type->castAs<ObjCObjectPointerType>());
       cgf.cgm.errorNYI(e->getSourceRange(), "Unary inc/dec ObjectiveC pointer");
@@ -1103,6 +1104,8 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
   mlir::Value emitXor(const BinOpInfo &ops);
   mlir::Value emitOr(const BinOpInfo &ops);
 
+  mlir::Value emitFixedPointBinOp(const BinOpInfo &ops);
+
   LValue emitCompoundAssignLValue(
       const CompoundAssignOperator *e,
       mlir::Value (ScalarExprEmitter::*f)(const BinOpInfo &),
@@ -1157,6 +1160,25 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
   HANDLEBINOP(Or)
 #undef HANDLEBINOP
 
+  cir::CmpOpKind clangCmpToCIRCmp(clang::BinaryOperatorKind clangCmp) {
+    switch (clangCmp) {
+    case BO_LT:
+      return cir::CmpOpKind::lt;
+    case BO_GT:
+      return cir::CmpOpKind::gt;
+    case BO_LE:
+      return cir::CmpOpKind::le;
+    case BO_GE:
+      return cir::CmpOpKind::ge;
+    case BO_EQ:
+      return cir::CmpOpKind::eq;
+    case BO_NE:
+      return cir::CmpOpKind::ne;
+    default:
+      llvm_unreachable("unsupported comparison kind for cir.cmp");
+    }
+  }
+
   mlir::Value emitCmp(const BinaryOperator *e) {
     ignoreResultAssign = false;
     const mlir::Location loc = cgf.getLoc(e->getExprLoc());
@@ -1164,26 +1186,6 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
     QualType lhsTy = e->getLHS()->getType();
     QualType rhsTy = e->getRHS()->getType();
 
-    auto clangCmpToCIRCmp =
-        [](clang::BinaryOperatorKind clangCmp) -> cir::CmpOpKind {
-      switch (clangCmp) {
-      case BO_LT:
-        return cir::CmpOpKind::lt;
-      case BO_GT:
-        return cir::CmpOpKind::gt;
-      case BO_LE:
-        return cir::CmpOpKind::le;
-      case BO_GE:
-        return cir::CmpOpKind::ge;
-      case BO_EQ:
-        return cir::CmpOpKind::eq;
-      case BO_NE:
-        return cir::CmpOpKind::ne;
-      default:
-        llvm_unreachable("unsupported comparison kind for cir.cmp");
-      }
-    };
-
     cir::CmpOpKind kind = clangCmpToCIRCmp(e->getOpcode());
     if (lhsTy->getAs<MemberPointerType>()) {
       assert(!cir::MissingFeatures::dataMemberType());
@@ -1209,9 +1211,7 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
                                          boInfo.lhs, boInfo.rhs);
         }
       } else if (boInfo.isFixedPointOp()) {
-        assert(!cir::MissingFeatures::fixedPointType());
-        cgf.cgm.errorNYI(loc, "fixed point comparisons");
-        result = builder.getBool(false, loc);
+        result = emitFixedPointBinOp(boInfo);
       } else {
         // integers and pointers
         if (cgf.cgm.getCodeGenOpts().StrictVTablePointers &&
@@ -2168,6 +2168,168 @@ struct FixedPointBuilder {
     return convert(src, srcSema, dstSema, /*dstIsInteger=*/false);
   }
 
+  mlir::Value createAdd(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs,
+                        const llvm::FixedPointSemantics &rhsSema) {
+    auto commonSema = getCommonBinopSemantic(lhsSema, rhsSema);
+    bool useSigned = commonSema.isSigned() || commonSema.hasUnsignedPadding();
+
+    mlir::Value wideLhs = createFixedToFixed(lhs, lhsSema, commonSema);
+    mlir::Value wideRhs = createFixedToFixed(rhs, rhsSema, commonSema);
+
+    mlir::Value result;
+    if (commonSema.isSaturated()) {
+      result = builder.emitIntrinsicCallOp(
+          loc, useSigned ? "sadd.sat" : "uadd.sat", wideLhs.getType(),
+          mlir::ValueRange{wideLhs, wideRhs});
+    } else {
+      result = builder.createAdd(loc, wideLhs, wideRhs);
+    }
+
+    return createFixedToFixed(result, commonSema,
+                              lhsSema.getCommonSemantics(rhsSema));
+  }
+
+  mlir::Value createSub(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs,
+                        const llvm::FixedPointSemantics &rhsSema) {
+    auto commonSema = getCommonBinopSemantic(lhsSema, rhsSema);
+    bool useSigned = commonSema.isSigned() || commonSema.hasUnsignedPadding();
+
+    mlir::Value wideLhs = createFixedToFixed(lhs, lhsSema, commonSema);
+    mlir::Value wideRhs = createFixedToFixed(rhs, rhsSema, commonSema);
+
+    mlir::Value result;
+    if (commonSema.isSaturated()) {
+      result = builder.emitIntrinsicCallOp(
+          loc, useSigned ? "ssub.sat" : "usub.sat", wideLhs.getType(),
+          mlir::ValueRange{wideLhs, wideRhs});
+    } else {
+      result = builder.createSub(loc, wideLhs, wideRhs);
+    }
+
+    // Subtraction can end up below 0 for padded unsigned operations, so emit
+    // an extra clamp in that case.
+    if (commonSema.isSaturated() && commonSema.hasUnsignedPadding()) {
+      mlir::Value zero = builder.getNullValue(result.getType(), loc);
+      mlir::Value ltZero =
+          builder.createCompare(loc, cir::CmpOpKind::lt, result, zero);
+      result = builder.createSelect(loc, ltZero, zero, result);
+    }
+
+    return createFixedToFixed(result, commonSema,
+                              lhsSema.getCommonSemantics(rhsSema));
+  }
+
+  mlir::Value createMul(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs,
+                        const llvm::FixedPointSemantics &rhsSema) {
+    auto commonSema = getCommonBinopSemantic(lhsSema, rhsSema);
+    bool useSigned = commonSema.isSigned() || commonSema.hasUnsignedPadding();
+
+    mlir::Value wideLhs = createFixedToFixed(lhs, lhsSema, commonSema);
+    mlir::Value wideRhs = createFixedToFixed(rhs, rhsSema, commonSema);
+
+    llvm::SmallString<13> intrinId;
+    cir::ConstantOp scale;
+
+    if (useSigned) {
+      intrinId = "smul.fix";
+      scale = builder.getSInt32(commonSema.getScale(), loc);
+    } else {
+      intrinId = "umul.fix";
+      scale = builder.getUInt32(commonSema.getScale(), loc);
+    }
+
+    if (commonSema.isSaturated())
+      intrinId += ".sat";
+
+    mlir::Value result =
+        builder.emitIntrinsicCallOp(loc, intrinId, wideLhs.getType(),
+                                    mlir::ValueRange{wideLhs, wideRhs, scale});
+
+    return createFixedToFixed(result, commonSema,
+                              lhsSema.getCommonSemantics(rhsSema));
+  }
+
+  mlir::Value createDiv(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs,
+                        const llvm::FixedPointSemantics &rhsSema) {
+    auto commonSema = getCommonBinopSemantic(lhsSema, rhsSema);
+    bool useSigned = commonSema.isSigned() || commonSema.hasUnsignedPadding();
+
+    mlir::Value wideLhs = createFixedToFixed(lhs, lhsSema, commonSema);
+    mlir::Value wideRhs = createFixedToFixed(rhs, rhsSema, commonSema);
+
+    llvm::SmallString<13> intrinId;
+    cir::ConstantOp scale;
+
+    if (useSigned) {
+      intrinId = "sdiv.fix";
+      scale = builder.getSInt32(commonSema.getScale(), loc);
+    } else {
+      intrinId = "udiv.fix";
+      scale = builder.getUInt32(commonSema.getScale(), loc);
+    }
+
+    if (commonSema.isSaturated())
+      intrinId += ".sat";
+
+    mlir::Value result =
+        builder.emitIntrinsicCallOp(loc, intrinId, wideLhs.getType(),
+                                    mlir::ValueRange{wideLhs, wideRhs, scale});
+
+    return createFixedToFixed(result, commonSema,
+                              lhsSema.getCommonSemantics(rhsSema));
+  }
+
+  mlir::Value createCmp(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs,
+                        const llvm::FixedPointSemantics &rhsSema,
+                        cir::CmpOpKind kind) {
+    auto commonSema = getCommonBinopSemantic(lhsSema, rhsSema);
+
+    mlir::Value wideLhs = createFixedToFixed(lhs, lhsSema, commonSema);
+    mlir::Value wideRhs = createFixedToFixed(rhs, rhsSema, commonSema);
+
+    return builder.createCompare(loc, kind, wideLhs, wideRhs);
+  }
+
+  mlir::Value createShl(mlir::Value lhs,
+                        const llvm::FixedPointSemantics &lhsSema,
+                        mlir::Value rhs) {
+    mlir::Value result;
+    if (lhsSema.isSaturated()) {
+      // We have to cast the RHS to the matching int type, but we have to do so
+      // through unsigned so we can ensure we get zext.
+      auto rhsIntTy = mlir::cast<cir::IntType>(rhs.getType());
+      auto rhsUnsignedTy = cir::IntType::get(builder.getContext(), rhsIntTy.getWidth(), /*isSigned=*/false);
+
+      mlir::Value rhsUnsigned =
+          builder.createCast(cir::CastKind::integral, rhs, rhsUnsignedTy);
+      mlir::Value rhsResized = builder.createCast(cir::CastKind::integral,
+                                                  rhsUnsigned, lhs.getType());
+
+      bool useSigned = lhsSema.isSigned() || lhsSema.hasUnsignedPadding();
+      result = builder.emitIntrinsicCallOp(
+          loc, useSigned ? "sshl.sat" : "ushl.sat", lhs.getType(),
+          mlir::ValueRange{lhs, rhsResized});
+    } else {
+      result = builder.createShiftLeft(loc, lhs, rhs);
+    }
+
+    return result;
+  }
+
+  mlir::Value createShr(mlir::Value lhs, mlir::Value rhs) {
+    return builder.createShiftRight(loc, lhs, rhs);
+  }
+
 private:
   mlir::Value convert(mlir::Value src, const llvm::FixedPointSemantics &srcSema,
                       const llvm::FixedPointSemantics &dstSema,
@@ -2263,12 +2425,57 @@ struct FixedPointBuilder {
     assert(accommodating && "no float type for semantics?");
     return accommodating;
   }
+  /// Get the common semantic for two semantics, with the added imposition that
+  /// saturated padded types retain the padding bit.
+  llvm::FixedPointSemantics
+  getCommonBinopSemantic(const llvm::FixedPointSemantics &lhsSema,
+                         const llvm::FixedPointSemantics &rhsSema) {
+    auto c = lhsSema.getCommonSemantics(rhsSema);
+    bool bothPadded =
+        lhsSema.hasUnsignedPadding() && rhsSema.hasUnsignedPadding();
+    return llvm::FixedPointSemantics(
+        c.getWidth() + static_cast<unsigned>(bothPadded && c.isSaturated()),
+        c.getScale(), c.isSigned(), c.isSaturated(), bothPadded);
+  }
 
   CIRGenBuilderTy &builder;
   mlir::Location loc;
 };
 } // namespace
 
+mlir::Value ScalarExprEmitter::emitFixedPointIncDec(const UnaryOperator *e,
+                                                    mlir::Value value,
+                                                    QualType type) {
+  // Fixed-point types are tricky. In some cases, it isn't possible to
+  // represent a 1 or a -1 in the type at all. Piggyback off of
+  // EmitFixedPointBinOp to avoid having to reimplement saturation.
+  BinOpInfo info;
+  info.loc = e->getSourceRange();
+  info.fpFeatures = e->getFPFeaturesInEffect(cgf.getLangOpts());
+  info.e = e;
+  info.compType = info.fullType = e->getType();
+  info.opcode = e->isIncrementOp() ? BO_Add : BO_Sub;
+  info.lhs = value;
+  info.rhs = builder.getConstInt(cgf.getLoc(info.loc), value.getType(), 1);
+  // If the type is signed, it's better to represent this as +(-1) or -(-1),
+  // since -1 is guaranteed to be representable.
+  // FIXME(cir): This is a carry-over from Classic codegen, we should probably
+  // figure out if ++ -> -(-1) and -- -> +(-1) is REALLY what we want? For now
+  // we are just doing what classic does here.
+  if (type->isSignedFixedPointType()) {
+    info.opcode = e->isIncrementOp() ? BO_Sub : BO_Add;
+    info.rhs = builder.createNeg(cgf.getLoc(info.loc), info.rhs);
+  }
+  // Now, convert from our invented integer literal to the type of the unary
+  // op. This will upscale and saturate if necessary. This value can become
+  // undef in some cases.
+  FixedPointBuilder fpbuilder(builder, cgf.getLoc(info.loc));
+  auto dstSema = cgf.getContext().getFixedPointSemantics(info.fullType);
+  info.rhs =
+      fpbuilder.createIntegerToFixed(info.rhs, /*srcIsSigned=*/true, dstSema);
+  return emitFixedPointBinOp(info);
+}
+
 mlir::Value ScalarExprEmitter::emitFixedPointConversion(mlir::Value src,
                                                         QualType srcTy,
                                                         QualType dstTy,
@@ -2340,11 +2547,8 @@ mlir::Value ScalarExprEmitter::emitMul(const BinOpInfo &ops) {
     return builder.createFMul(loc, ops.lhs, ops.rhs);
   }
 
-  if (ops.isFixedPointOp()) {
-    assert(!cir::MissingFeatures::fixedPointType());
-    cgf.cgm.errorNYI("fixed point");
-    return nullptr;
-  }
+  if (ops.isFixedPointOp())
+    return emitFixedPointBinOp(ops);
 
   return cir::MulOp::create(builder, cgf.getLoc(ops.loc),
                             cgf.convertType(ops.fullType), ops.lhs, ops.rhs);
@@ -2355,6 +2559,10 @@ mlir::Value ScalarExprEmitter::emitDiv(const BinOpInfo &ops) {
     CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(cgf, ops.fpFeatures);
     return builder.createFDiv(loc, ops.lhs, ops.rhs);
   }
+
+  if (ops.isFixedPointOp())
+    return emitFixedPointBinOp(ops);
+
   return cir::DivOp::create(builder, loc, cgf.convertType(ops.fullType),
                             ops.lhs, ops.rhs);
 }
@@ -2368,6 +2576,100 @@ mlir::Value ScalarExprEmitter::emitRem(const BinOpInfo &ops) {
                             ops.lhs, ops.rhs);
 }
 
+mlir::Value ScalarExprEmitter::emitFixedPointBinOp(const BinOpInfo &ops) {
+  // This is either a binary operation where at least one of the operands is
+  // a fixed-point type, or a unary operation where the operand is a fixed-point
+  // type. The result type of a binary operation is determined by
+  // Sema::handleFixedPointConversions().
+  QualType resultTy = ops.compType;
+  QualType lhsTy, rhsTy;
+  if (const auto *binOp = dyn_cast<BinaryOperator>(ops.e)) {
+    rhsTy = binOp->getRHS()->getType();
+    if (const auto *cao = dyn_cast<CompoundAssignOperator>(binOp)) {
+      // For compound assignment, the effective type of the LHS at this point
+      // is the computation LHS type, not the actual LHS type, and the final
+      // result type is not the type of the expression but rather the
+      // computation result type.
+      lhsTy = cao->getComputationLHSType();
+      resultTy = cao->getComputationResultType();
+    } else
+      lhsTy = binOp->getLHS()->getType();
+  } else if (const auto *unOp = dyn_cast<UnaryOperator>(ops.e)) {
+    lhsTy = unOp->getSubExpr()->getType();
+    rhsTy = unOp->getSubExpr()->getType();
+  }
+  ASTContext &ctx = cgf.getContext();
+  mlir::Value lhs = ops.lhs;
+  mlir::Value rhs = ops.rhs;
+
+  auto lhsFixedSema = ctx.getFixedPointSemantics(lhsTy);
+  auto rhsFixedSema = ctx.getFixedPointSemantics(rhsTy);
+  auto resultFixedSema = ctx.getFixedPointSemantics(resultTy);
+  auto commonFixedSema = lhsFixedSema.getCommonSemantics(rhsFixedSema);
+
+  // Perform the actual operation.
+  mlir::Value result;
+  FixedPointBuilder fpbuilder(builder, cgf.getLoc(ops.loc));
+  switch (ops.opcode) {
+  case BO_AddAssign:
+  case BO_Add:
+    result = fpbuilder.createAdd(lhs, lhsFixedSema, rhs, rhsFixedSema);
+    break;
+  case BO_SubAssign:
+  case BO_Sub:
+    result = fpbuilder.createSub(lhs, lhsFixedSema, rhs, rhsFixedSema);
+    break;
+  case BO_MulAssign:
+  case BO_Mul:
+    result = fpbuilder.createMul(lhs, lhsFixedSema, rhs, rhsFixedSema);
+    break;
+  case BO_DivAssign:
+  case BO_Div:
+    result = fpbuilder.createDiv(lhs, lhsFixedSema, rhs, rhsFixedSema);
+    break;
+  case BO_ShlAssign:
+  case BO_Shl:
+    result = fpbuilder.createShl(lhs, lhsFixedSema, rhs);
+    break;
+  case BO_ShrAssign:
+  case BO_Shr:
+    result = fpbuilder.createShr(lhs, rhs);
+    break;
+  case BO_LT:
+  case BO_GT:
+  case BO_LE:
+  case BO_GE:
+  case BO_EQ:
+  case BO_NE:
+    return fpbuilder.createCmp(lhs, lhsFixedSema, rhs, rhsFixedSema,
+                               clangCmpToCIRCmp(ops.opcode));
+  case BO_Cmp:
+  case BO_LAnd:
+  case BO_LOr:
+    llvm_unreachable("Found unimplemented fixed point binary operation");
+  case BO_PtrMemD:
+  case BO_PtrMemI:
+  case BO_Rem:
+  case BO_Xor:
+  case BO_And:
+  case BO_Or:
+  case BO_Assign:
+  case BO_RemAssign:
+  case BO_AndAssign:
+  case BO_XorAssign:
+  case BO_OrAssign:
+  case BO_Comma:
+    llvm_unreachable(
+        "Found unsupported binary operation for fixed point types.");
+  }
+
+  bool isShift = BinaryOperator::isShiftOp(ops.opcode) ||
+                 BinaryOperator::isShiftAssignOp(ops.opcode);
+  // Convert to the result type.
+  return fpbuilder.createFixedToFixed(
+      result, isShift ? lhsFixedSema : commonFixedSema, resultFixedSema);
+}
+
 mlir::Value ScalarExprEmitter::emitAdd(const BinOpInfo &ops) {
   if (mlir::isa<cir::PointerType>(ops.lhs.getType()) ||
       mlir::isa<cir::PointerType>(ops.rhs.getType()))
@@ -2411,11 +2713,8 @@ mlir::Value ScalarExprEmitter::emitAdd(const BinOpInfo &ops) {
     return builder.createFAdd(loc, ops.lhs, ops.rhs);
   }
 
-  if (ops.isFixedPointOp()) {
-    assert(!cir::MissingFeatures::fixedPointType());
-    cgf.cgm.errorNYI("fixed point");
-    return {};
-  }
+  if (ops.isFixedPointOp())
+    return emitFixedPointBinOp(ops);
 
   return builder.createAdd(loc, ops.lhs, ops.rhs);
 }
@@ -2463,11 +2762,8 @@ mlir::Value ScalarExprEmitter::emitSub(const BinOpInfo &ops) {
       return builder.createFSub(loc, ops.lhs, ops.rhs);
     }
 
-    if (ops.isFixedPointOp()) {
-      assert(!cir::MissingFeatures::fixedPointType());
-      cgf.cgm.errorNYI("fixed point");
-      return {};
-    }
+    if (ops.isFixedPointOp())
+      return emitFixedPointBinOp(ops);
 
     return builder.createSub(loc, ops.lhs, ops.rhs);
   }
@@ -2492,11 +2788,9 @@ mlir::Value ScalarExprEmitter::emitSub(const BinOpInfo &ops) {
 
 mlir::Value ScalarExprEmitter::emitShl(const BinOpInfo &ops) {
   // TODO: This misses out on the sanitizer check below.
-  if (ops.isFixedPointOp()) {
-    assert(!cir::MissingFeatures::fixedPointType());
-    cgf.cgm.errorNYI("fixed point");
-    return {};
-  }
+  if (ops.isFixedPointOp())
+    return emitFixedPointBinOp(ops);
+
 
   // CIR accepts shift between different types, meaning nothing special
   // to be done here. OTOH, LLVM requires the LHS and RHS to be the same type:
@@ -2524,11 +2818,8 @@ mlir::Value ScalarExprEmitter::emitShl(const BinOpInfo &ops) {
 
 mlir::Value ScalarExprEmitter::emitShr(const BinOpInfo &ops) {
   // TODO: This misses out on the sanitizer check below.
-  if (ops.isFixedPointOp()) {
-    assert(!cir::MissingFeatures::fixedPointType());
-    cgf.cgm.errorNYI("fixed point");
-    return {};
-  }
+  if (ops.isFixedPointOp())
+    return emitFixedPointBinOp(ops);
 
   // CIR accepts shift between different types, meaning nothing special
   // to be done here. OTOH, LLVM requi...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/217710


More information about the cfe-commits mailing list