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

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.

>From 22e2de1539a142b5d06859b786ae9140de4d9e1d Mon Sep 17 00:00:00 2001
From: erichkeane <[email protected]>
Date: Wed, 19 Aug 2026 07:14:29 -0700
Subject: [PATCH] [CIR] Implement fixed-point arithmetic lowering

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.
---
 clang/include/clang/CIR/MissingFeatures.h    |   1 -
 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp   | 391 ++++++++--
 clang/test/CIR/CodeGen/fixed-point-arith.cpp | 759 +++++++++++++++++++
 3 files changed, 1100 insertions(+), 51 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/fixed-point-arith.cpp

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 requires the LHS and RHS to be the same type:
diff --git a/clang/test/CIR/CodeGen/fixed-point-arith.cpp 
b/clang/test/CIR/CodeGen/fixed-point-arith.cpp
new file mode 100644
index 0000000000000..ee28fe3bae8c6
--- /dev/null
+++ b/clang/test/CIR/CodeGen/fixed-point-arith.cpp
@@ -0,0 +1,759 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -ffixed-point -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -ffixed-point -fclangir 
-emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -ffixed-point -emit-llvm 
%s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM,OGCG
+
+extern "C" {
+// CIR-LABEL: cir.func {{.*}}@add(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[RESULT:.*]] = cir.add %[[LOAD_A]], %[[WIDEN_F]] : !s32i
+// CIR-NEXT: cir.store %[[RESULT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @add(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[ADD:.*]] = add i32 %[[LOAD_A]], %[[EXT_F]]
+_Accum add(_Accum a, _Fract f) {
+  return a + f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@add2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_A:.*]] = cir.cast integral %[[LOAD_A]] : !s32i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_VAL:.*]] = cir.const #cir.int<15> : !cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_A:.*]] = cir.shift(left, %[[WIDEN_A]] : !cir.int<s, 47>, 
%[[SHIFT_VAL]] : !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[ADD_A_F:.*]] = cir.add %[[SHIFT_A]], %[[WIDEN_F]] : 
!cir.int<s, 47>
+// CIR-NEXT: %[[TRUNC_RES:.*]] = cir.cast integral %[[ADD_A_F]] : !cir.int<s, 
47> -> !s16i
+// CIR-NEXT: %[[WIDEN_RES:.*]] = cir.cast integral %[[TRUNC_RES]] : !s16i -> 
!s32i
+// CIR-NEXT: cir.store %[[WIDEN_RES]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @add2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_A:.*]] = sext i32 %[[LOAD_A]] to i47
+// LLVM-NEXT: %[[SHIFT_A:.*]] = shl i47 %[[EXT_A]], 15
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i47
+// LLVM-NEXT: %[[ADD:.*]] = add i47 %[[SHIFT_A]], %[[EXT_F]]
+// LLVM-NEXT: %[[TRUNC:.*]] = trunc i47 %[[ADD]] to i16
+// LLVM-NEXT: %[[EXT:.*]] = sext i16 %[[TRUNC]] to i32
+_Accum add2(int a, _Fract f) {
+  return a + f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@sub(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[DIFF:.*]] = cir.sub %[[LOAD_A]], %[[WIDEN_F]] : !s32i
+// CIR-NEXT: cir.store %[[DIFF]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @sub(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[SUB:.*]] = sub i32 %[[LOAD_A]], %[[EXT_F]]
+_Accum sub(_Accum a, _Fract f) {
+  return a - f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@sub2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[WIDEN_A47:.*]] = cir.cast integral %[[LOAD_A]] : !s32i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[WIDEN_F47:.*]] = cir.cast integral %[[WIDEN_F]] : !s32i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_F:.*]] = cir.shift(left, %[[WIDEN_F47]] : !cir.int<s, 
47>, %[[SHIFT_NUM]] : !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[DIFF:.*]] = cir.sub %[[WIDEN_A47]], %[[SHIFT_F]] : !cir.int<s, 
47>
+// CIR-NEXT: %[[TRUNC_DIFF:.*]] = cir.cast integral %[[DIFF]] : !cir.int<s, 
47> -> !s32i
+// CIR-NEXT: cir.store %[[TRUNC_DIFF]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @sub2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F32:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[EXT_A:.*]] = sext i32 %[[LOAD_A]] to i47
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i32 %[[EXT_F32]] to i47
+// LLVM-NEXT: %[[SHIFT:.*]] = shl i47 %[[EXT_F]], 15
+// LLVM-NEXT: %[[SUB:.*]] = sub i47 %[[EXT_A]], %[[SHIFT]]
+// LLVM-NEXT: %[[TRUNC:.*]] = trunc i47 %[[SUB]] to i32
+_Accum sub2(_Accum a, short f) {
+  return a - f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@mul(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[RES:.*]] = cir.call_llvm_intrinsic "smul.fix" %[[LOAD_A]], 
%[[WIDEN_F]], %[[SCALE]] : (!s32i, !s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[RES]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @mul(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[MUL:.*]] = call i32 @llvm.smul.fix.i32(i32 %[[LOAD_A]], i32 
%[[EXT_F]], i32 15)
+_Accum mul(_Accum a, _Fract f) {
+  return a * f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@mul2(
+// CIR-NEXT: %[[S:.*]] = cir.alloca "s" align(2) init : !cir.ptr<!u16i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_S:.*]] = cir.load align(2) %[[S]] : !cir.ptr<!u16i>, !u16i
+// CIR-NEXT: %[[WIDEN_S:.*]] = cir.cast integral %[[LOAD_S]] : !u16i -> !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_S47:.*]] = cir.cast integral %[[WIDEN_S]] : !s32i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[WIDEN_S47]] : !cir.int<s, 47>, 
%[[SHIFT_NUM]] : !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[WIDEN_F47:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[RES:.*]] = cir.call_llvm_intrinsic "smul.fix" %[[SHIFT]], 
%[[WIDEN_F47]], %[[SCALE]] : (!cir.int<s, 47>, !cir.int<s, 47>, !s32i) -> 
!cir.int<s, 47>
+// CIR-NEXT: %[[TRUNC_RES:.*]] = cir.cast integral %[[RES]] : !cir.int<s, 47> 
-> !s16i
+// CIR-NEXT: %[[WIDEN_RES:.*]] = cir.cast integral %[[TRUNC_RES]] : !s16i -> 
!s32i
+// CIR-NEXT: cir.store %[[WIDEN_RES]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @mul2(i16 
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[S:.*]] = alloca i16, align 2
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_S:.*]] = load i16, ptr %[[S]], align 2
+// LLVM-NEXT: %[[EXT_S:.*]] = zext i16 %[[LOAD_S]] to i32
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_S2:.*]] = sext i32 %[[EXT_S]] to i47
+// LLVM-NEXT: %[[SHIFT_S:.*]] = shl i47 %[[EXT_S2]], 15
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i47
+// LLVM-NEXT: %[[MUL:.*]] = call i47 @llvm.smul.fix.i47(i47 %[[SHIFT_S]], i47 
%[[EXT_F]], i32 15)
+// LLVM-NEXT: %[[TRUNC_RES:.*]] = trunc i47 %[[MUL]] to i16
+// LLVM-NEXT: %[[EXT_RES:.*]] = sext i16 %[[TRUNC_RES]] to i32
+_Accum mul2(unsigned short s, _Fract f) {
+  return s * f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@div(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[DIV:.*]] = cir.call_llvm_intrinsic "sdiv.fix" %[[LOAD_A]], 
%[[WIDEN_F]], %[[SCALE]] : (!s32i, !s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[DIV]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @div(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[DIV:.*]] = call i32 @llvm.sdiv.fix.i32(i32 %[[LOAD_A]], i32 
%[[EXT_F]], i32 15)
+_Accum div(_Accum a, _Fract f) {
+  return a / f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@div2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(4) init : !cir.ptr<!cir.float>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[FLOAT_A:.*]] = cir.cast int_to_float %[[LOAD_A]] : !s32i -> 
!cir.float
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.fp<3.05175781E-5> : !cir.float
+// CIR-NEXT: %[[SCALE_A:.*]] = cir.fmul %[[FLOAT_A]], %[[SCALE]] : !cir.float
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(4) %[[F]] : !cir.ptr<!cir.float>, 
!cir.float
+// CIR-NEXT: %[[DIV:.*]] = cir.fdiv %[[SCALE_A]], %[[LOAD_F]] : !cir.float
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.fp<3.276800e+04> : !cir.float
+// CIR-NEXT: %[[RES:.*]] = cir.fmul %[[DIV]], %[[SCALE]] : !cir.float
+// CIR-NEXT: %[[RES_TO_INT:.*]] = cir.cast float_to_int %[[RES]] : !cir.float 
-> !s32i
+// CIR-NEXT: cir.store %[[RES_TO_INT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @div2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca float, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[A_TO_FLOAT:.*]] = sitofp i32 %[[LOAD_A]] to float
+// LLVM-NEXT: %[[SCALE_A:.*]] = fmul float %[[A_TO_FLOAT]], f0x38000000
+// LLVM-NEXT: %[[LOAD_F:.*]] = load float, ptr %[[F]], align 4
+// LLVM-NEXT: %[[DIV:.*]] = fdiv float %[[SCALE_A]], %[[LOAD_F]]
+// LLVM-NEXT: %[[SCALE_RES:.*]] = fmul float %[[DIV]], 3.276800e+04
+// LLVM-NEXT: %[[RES_TO_INT:.*]] = fptosi float %[[SCALE_RES]] to i32
+_Accum div2(_Accum a, float f) {
+  return a / f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@umul(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!u32i>
+// CIR-NEXT: %[[B:.*]] = cir.alloca "b" align(4) init : !cir.ptr<!u32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!u32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!u32i>, !u32i
+// CIR-NEXT: %[[LOAD_B:.*]] = cir.load align(4) %[[B]] : !cir.ptr<!u32i>, !u32i
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<16> : !u32i
+// CIR-NEXT: %[[MUL:.*]] = cir.call_llvm_intrinsic "umul.fix" %[[LOAD_A]], 
%[[LOAD_B]], %[[SCALE]] : (!u32i, !u32i, !u32i) -> !u32i
+// CIR-NEXT: cir.store %[[MUL]], %[[RET]] : !u32i, !cir.ptr<!u32i>
+
+// LLVM-LABEL: define {{.*}}i32 @umul(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[B:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_B:.*]] = load i32, ptr %[[B]], align 4
+// LLVM-NEXT: %[[MUL:.*]] = call i32 @llvm.umul.fix.i32(i32 %[[LOAD_A]], i32 
%[[LOAD_B]], i32 16)
+unsigned _Accum umul(unsigned _Accum a, unsigned _Accum b) {
+  return a * b;
+}
+
+// CIR-LABEL: cir.func {{.*}}@cmp(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(1) : !cir.ptr<!cir.bool>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[LT:.*]] = cir.cmp lt %[[LOAD_A]], %[[WIDEN_F]] : !s32i
+// CIR-NEXT: cir.store %[[LT]], %[[RET]] : !cir.bool, !cir.ptr<!cir.bool>
+
+// LLVM-LABEL: define {{.*}}i1 @cmp(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[CMP:.*]] = icmp slt i32 %[[LOAD_A]], %[[EXT_F]]
+bool cmp(_Accum a, _Fract f) {
+  return a < f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@cmp2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(1) : !cir.ptr<!cir.bool>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[GT:.*]] = cir.cmp gt %[[LOAD_A]], %[[WIDEN_F]] : !s32i
+// CIR-NEXT: cir.store %[[GT]], %[[RET]] : !cir.bool, !cir.ptr<!cir.bool>
+
+// LLVM-LABEL: define {{.*}}i1 @cmp2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[CMP:.*]] = icmp sgt i32 %[[LOAD_A]], %[[EXT_F]]
+bool cmp2(_Accum a, _Fract f) {
+  return a > f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@cmp3(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(1) : !cir.ptr<!cir.bool>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[WIDEN_F:.*]] = cir.cast integral %[[LOAD_F]] : !s16i -> !s32i
+// CIR-NEXT: %[[EQ:.*]] = cir.cmp eq %[[LOAD_A]], %[[WIDEN_F]] : !s32i
+// CIR-NEXT: cir.store %[[EQ]], %[[RET]] : !cir.bool, !cir.ptr<!cir.bool>
+
+// LLVM-LABEL: define {{.*}}i1 @cmp3(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[EXT_F:.*]] = sext i16 %[[LOAD_F]] to i32
+// LLVM-NEXT: %[[CMP:.*]] = icmp eq i32 %[[LOAD_A]], %[[EXT_F]]
+bool cmp3(_Accum a, _Fract f) {
+  return a == f;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shl(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[LOAD_A]] : !s32i, 
%[[SHIFT_NUM]] : !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @shl(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = shl i32 %[[LOAD_A]], 2
+_Accum shl(_Accum a) {
+  return a << 2;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shl2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[C:.*]] = cir.alloca "c" align(1) init : !cir.ptr<!u8i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_C:.*]] = cir.load align(1) %[[C]] : !cir.ptr<!u8i>, !u8i
+// CIR-NEXT: %[[WIDEN_C:.*]] = cir.cast integral %[[LOAD_C]] : !u8i -> !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[LOAD_A]] : !s32i, %[[WIDEN_C]] 
: !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @shl2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[C:.*]] = alloca i8, align 1
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_C:.*]] = load i8, ptr %[[C]], align 1
+// LLVM-NEXT: %[[C_EXT:.*]] = zext i8 %[[LOAD_C]] to i32
+// LLVM-NEXT: %[[SHIFT:.*]] = shl i32 %[[LOAD_A]], %[[C_EXT]]
+_Accum shl2(_Accum a, unsigned char c) {
+  return a << c;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shl3(
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[C:.*]] = cir.alloca "c" align(1) init : !cir.ptr<!u8i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[LOAD_C:.*]] = cir.load align(1) %[[C]] : !cir.ptr<!u8i>, !u8i
+// CIR-NEXT: %[[WIDEN_C:.*]] = cir.cast integral %[[LOAD_C]] : !u8i -> !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[LOAD_F]] : !s16i, %[[WIDEN_C]] 
: !s32i) -> !s16i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @shl3(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM-NEXT: %[[C:.*]] = alloca i8, align 1
+// LLVM:      %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[LOAD_C:.*]] = load i8, ptr %[[C]], align 1
+// LLVM-NEXT: %[[EXT_C:.*]] = zext i8 %[[LOAD_C]] to i32
+// LLVM-NEXT: %[[TRUNC_C:.*]] = trunc i32 %[[EXT_C]] to i16
+// LLVM-NEXT: %[[SHIFT:.*]] = shl i16 %[[LOAD_F]], %[[TRUNC_C]]
+_Fract shl3(_Fract f, unsigned char c) {
+  return f << c;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shr(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD_A]] : !s32i, 
%[[SHIFT_NUM]] : !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @shr(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = ashr i32 %[[LOAD_A]], 2
+_Accum shr(_Accum a) {
+  return a >> 2;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shr2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[U:.*]] = cir.alloca "u" align(4) init : !cir.ptr<!u32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_U:.*]] = cir.load align(4) %[[U]] : !cir.ptr<!u32i>, !u32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD_A]] : !s32i, %[[LOAD_U]] 
: !u32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @shr2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[U:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_U:.*]] = load i32, ptr %[[U]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = ashr i32 %[[LOAD_A]], %[[LOAD_U]]
+_Accum shr2(_Accum a, unsigned u) {
+  return a >> u;
+}
+
+// CIR-LABEL: cir.func {{.*}}@shr3(
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[U:.*]] = cir.alloca "u" align(4) init : !cir.ptr<!u32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[LOAD_U:.*]] = cir.load align(4) %[[U]] : !cir.ptr<!u32i>, !u32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD_F]] : !s16i, %[[LOAD_U]] 
: !u32i) -> !s16i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @shr3(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM-NEXT: %[[U:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[LOAD_U:.*]] = load i32, ptr %[[U]], align 4
+// LLVM-NEXT: %[[TRUNC_U:.*]] = trunc i32 %[[LOAD_U]] to i16
+// LLVM-NEXT: %[[SHIFT:.*]] = ashr i16 %[[LOAD_F]], %[[TRUNC_U]]
+_Fract shr3(_Fract f, unsigned u) {
+  return f >> u;
+}
+
+// CIR-LABEL: cir.func {{.*}}@inc(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1]] : !s32i, 
%[[SHIFT_NUM]] : !s32i) -> !s32i
+// CIR-NEXT: %[[SUB:.*]] = cir.sub %[[LOAD_A]], %[[SHIFT]] : !s32i
+// CIR-NEXT: cir.store align(4) %[[SUB:.*]], %[[A]] : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.store %[[SUB]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @inc(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[INC:.*]] = sub i32 %[[LOAD_A]], -32768
+_Accum inc(_Accum a) {
+  return ++a;
+}
+
+// CIR-LABEL: cir.func {{.*}}@inc2(
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR: %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !s16i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !s16i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1:.*]] : !s16i, 
%[[SHIFT_NUM]] : !s16i) -> !s16i
+// CIR-NEXT: %[[SUB:.*]] = cir.sub %[[LOAD_F]], %[[SHIFT]] : !s16i
+// CIR-NEXT: cir.store align(2) %[[SUB]], %[[F]] : !s16i, !cir.ptr<!s16i>
+// CIR-NEXT: cir.store %[[LOAD_F]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @inc2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i16, ptr %[[A]], align 2
+// LLVM-NEXT: %[[INC:.*]] = sub i16 %[[LOAD_A]], -32768
+_Fract inc2(_Fract f) {
+  return f++;
+}
+
+// CIR-LABEL: cir.func {{.*}}@dec(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1]] : !s32i, 
%[[SHIFT_NUM]] : !s32i) -> !s32i
+// CIR-NEXT: %[[ADD:.*]] = cir.add %[[LOAD_A]], %[[SHIFT]] : !s32i
+// CIR-NEXT: cir.store align(4) %[[ADD]], %[[A]] : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.store %[[ADD]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @dec(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[DEC:.*]] = add i32 %[[LOAD_A]], -32768
+_Accum dec(_Accum a) {
+  return --a;
+}
+
+// CIR-LABEL: cir.func {{.*}}@dec2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(2) %[[A]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !s16i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !s16i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1]] : !s16i, 
%[[SHIFT_NUM]] : !s16i) -> !s16i
+// CIR-NEXT: %[[SUB:.*]] = cir.add %[[LOAD_A]], %[[SHIFT]] : !s16i
+// CIR-NEXT: cir.store align(2) %[[SUB]], %[[A]] : !s16i, !cir.ptr<!s16i>
+// CIR-NEXT: cir.store %[[LOAD_A:.*]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @dec2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i16, ptr %[[A]], align 2
+// LLVM-NEXT: %[[DEC:.*]] = add i16 %[[LOAD_A]], -32768
+_Fract dec2(_Fract a) {
+  return a--;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satAdd(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[B:.*]] = cir.alloca "b" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_B:.*]] = cir.load align(4) %[[B]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[ADD:.*]] = cir.call_llvm_intrinsic "sadd.sat" %[[LOAD_A]], 
%[[LOAD_B]] : (!s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[ADD]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satAdd(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[B:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_B:.*]] = load i32, ptr %[[B]], align 4
+// LLVM-NEXT: %[[SUB:.*]] = call i32 @llvm.sadd.sat.i32(i32 %[[LOAD_A]], i32 
%[[LOAD_B]])
+_Sat _Accum satAdd(_Sat _Accum a, _Accum b) {
+  return a + b;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satSub(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[B:.*]] = cir.alloca "b" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_B:.*]] = cir.load align(4) %[[B]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SUB:.*]] = cir.call_llvm_intrinsic "ssub.sat" %[[LOAD_A]], 
%[[LOAD_B]] : (!s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SUB]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satSub(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[B:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_B:.*]] = load i32, ptr %[[B]], align 4
+// LLVM-NEXT: %[[SUB:.*]] = call i32 @llvm.ssub.sat.i32(i32 %[[LOAD_A]], i32 
%[[LOAD_B]])
+_Sat _Accum satSub(_Sat _Accum a, _Sat _Accum b) {
+  return a - b;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satMul(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[B:.*]] = cir.alloca "b" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_B:.*]] = cir.load align(4) %[[B]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[MUL:.*]] = cir.call_llvm_intrinsic "smul.fix.sat" %[[LOAD_A]], 
%[[LOAD_B]], %[[SCALE]] : (!s32i, !s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[MUL]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satMul(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[B:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_B:.*]] = load i32, ptr %[[B]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = call i32 @llvm.smul.fix.sat.i32(i32 %[[LOAD_A]], 
i32 %[[LOAD_B]], i32 15)
+_Sat _Accum satMul(_Sat _Accum a, _Sat _Accum b) {
+  return a * b;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satDiv(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[B:.*]] = cir.alloca "b" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[LOAD_B:.*]] = cir.load align(4) %[[B]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SCALE:.*]] = cir.const #cir.int<15> : !s32i
+// CIR-NEXT: %[[DIV:.*]] = cir.call_llvm_intrinsic "sdiv.fix.sat" %[[LOAD_A]], 
%[[LOAD_B]], %[[SCALE]] : (!s32i, !s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[DIV]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satDiv(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM-NEXT: %[[B:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[LOAD_B:.*]] = load i32, ptr %[[B]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = call i32 @llvm.sdiv.fix.sat.i32(i32 %[[LOAD_A]], 
i32 %[[LOAD_B]], i32 15)
+_Sat _Accum satDiv(_Sat _Accum a, _Sat _Accum b) {
+  return a / b;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satShl(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.call_llvm_intrinsic "sshl.sat" %[[LOAD_A]], 
%[[SHIFT_NUM]] : (!s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satShl(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = call i32 @llvm.sshl.sat.i32(i32 %[[LOAD_A]], i32 
2)
+_Sat _Accum satShl(_Sat _Accum a) {
+  return a << 2;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satShl2(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[C:.*]] = cir.alloca "c" align(1) init : !cir.ptr<!s8i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(2) %[[A]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[LOAD_C:.*]] = cir.load align(1) %[[C]] : !cir.ptr<!s8i>, !s8i
+// CIR-NEXT: %[[WIDEN_C:.*]] = cir.cast integral %[[LOAD_C]] : !s8i -> !s32i
+// CIR-NEXT: %[[UNSIGNED_C:.*]] = cir.cast integral %[[WIDEN_C]] : !s32i -> 
!u32i
+// CIR-NEXT: %[[TRUNC_C:.*]] = cir.cast integral %[[UNSIGNED_C]] : !u32i -> 
!s16i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.call_llvm_intrinsic "sshl.sat" %[[LOAD_A]], 
%[[TRUNC_C]] : (!s16i, !s16i) -> !s16i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @satShl2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM-NEXT: %[[C:.*]] = alloca i8, align 1
+// LLVM:      %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[LOAD_C:.*]] = load i8, ptr %[[C]], align 1
+// LLVM-NEXT: %[[EXT_C:.*]] = sext i8 %[[LOAD_C]] to i32
+// LLVM-NEXT: %[[TRUNC_C:.*]] = trunc i32 %[[EXT_C]] to i16
+// LLVM-NEXT: %[[SHIFT:.*]] = call i16 @llvm.sshl.sat.i16(i16 %[[LOAD_F]], i16 
%[[TRUNC_C]])
+_Sat _Fract satShl2(_Sat _Fract a, char c) {
+  return a << c;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satShr(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD_A]] : !s32i, 
%[[SHIFT_NUM]] : !s32i) -> !s32i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satShr(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[SHIFT:.*]] = ashr i32 %[[LOAD_A]], 2
+_Sat _Accum satShr(_Sat _Accum a) {
+  return a >> 2;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satShr2(
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[I:.*]] = cir.alloca "i" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[LOAD_I:.*]] = cir.load align(4) %[[I]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(right, %[[LOAD_F]] : !s16i, %[[LOAD_I]] 
: !s32i) -> !s16i
+// CIR-NEXT: cir.store %[[SHIFT]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @satShr2(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM-NEXT: %[[I:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[LOAD_I:.*]] = load i32, ptr %[[I]], align 4
+// LLVM-NEXT: %[[TRUNC:.*]] = trunc i32 %[[LOAD_I]] to i16
+// LLVM-NEXT: %[[SHIFT:.*]] = ashr i16 %[[LOAD_F]], %[[TRUNC]]
+
+_Sat _Fract satShr2(_Sat _Fract f, int i) {
+  return f >> i;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satInc(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(4) init : !cir.ptr<!s32i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(4) : !cir.ptr<!s32i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(4) %[[A]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !cir.int<s, 47>
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1]] : !cir.int<s, 47>, 
%[[SHIFT_NUM]] : !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[MAX:.*]] = cir.const #cir.int<2147483647> : !cir.int<s, 47>
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp gt %[[SHIFT]], %[[MAX]] : !cir.int<s, 47>
+// CIR-NEXT: %[[SELECT:.*]] = cir.select if %[[CMP]] then %[[MAX]] else 
%[[SHIFT]] : (!cir.bool, !cir.int<s, 47>, !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[MIN:.*]] = cir.const #cir.int<-2147483648> : !cir.int<s, 47>
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp lt %[[SELECT]], %[[MIN]] : !cir.int<s, 47>
+// CIR-NEXT: %[[SELECT2:.*]] = cir.select if %[[CMP]] then %[[MIN]] else 
%[[SELECT]] : (!cir.bool, !cir.int<s, 47>, !cir.int<s, 47>) -> !cir.int<s, 47>
+// CIR-NEXT: %[[TRUNC:.*]] = cir.cast integral %[[SELECT2]] : !cir.int<s, 47> 
-> !s32i
+// CIR-NEXT: %[[SUB:.*]] = cir.call_llvm_intrinsic "ssub.sat" %[[LOAD_A]], 
%[[TRUNC]] : (!s32i, !s32i) -> !s32i
+// CIR-NEXT: cir.store align(4) %[[SUB]], %[[A]] : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.store %[[SUB]], %[[RET]] : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}}i32 @satInc(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i32, align 4
+// LLVM:      %[[LOAD_A:.*]] = load i32, ptr %[[A]], align 4
+// LLVM-NEXT: %[[ADD:.*]] = call i32 @llvm.ssub.sat.i32(i32 %[[LOAD_A]], i32 
-32768)
+// LLVM-NEXT: store i32 %[[ADD]], ptr %[[A]], align 4
+_Sat _Accum satInc(_Sat _Accum a) {
+  return ++a;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satDec(
+// CIR-NEXT: %[[F:.*]] = cir.alloca "f" align(2) init : !cir.ptr<!s16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!s16i>
+// CIR:      %[[LOAD_F:.*]] = cir.load align(2) %[[F]] : !cir.ptr<!s16i>, !s16i
+// CIR-NEXT: %[[NEG_1:.*]] = cir.const #cir.int<-1> : !cir.int<s, 31>
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<15> : !cir.int<s, 31>
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[NEG_1]] : !cir.int<s, 31>, 
%[[SHIFT_NUM]] : !cir.int<s, 31>) -> !cir.int<s, 31>
+// CIR-NEXT: %[[MAX:.*]] = cir.const #cir.int<32767> : !cir.int<s, 31>
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp gt %[[SHIFT]], %[[MAX]] : !cir.int<s, 31>
+// CIR-NEXT: %[[SELECT:.*]] = cir.select if %[[CMP]] then %[[MAX]] else 
%[[SHIFT]] : (!cir.bool, !cir.int<s, 31>, !cir.int<s, 31>) -> !cir.int<s, 31>
+// CIR-NEXT: %[[MIN:.*]] = cir.const #cir.int<-32768> : !cir.int<s, 31>
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp lt %[[SELECT]], %[[MIN]] : !cir.int<s, 31>
+// CIR-NEXT: %[[SELECT2:.*]] = cir.select if %[[CMP]] then %[[MIN]] else 
%[[SELECT]] : (!cir.bool, !cir.int<s, 31>, !cir.int<s, 31>) -> !cir.int<s, 31>
+// CIR-NEXT: %[[TRUNC:.*]] = cir.cast integral %[[SELECT2]] : !cir.int<s, 31> 
-> !s16i
+// CIR-NEXT: %[[ADD:.*]] = cir.call_llvm_intrinsic "sadd.sat" %[[LOAD_F]], 
%[[TRUNC]] : (!s16i, !s16i) -> !s16i
+// CIR-NEXT: cir.store align(2) %[[ADD]], %[[F]] : !s16i, !cir.ptr<!s16i>
+// CIR-NEXT: cir.store %[[LOAD_F]], %[[RET]] : !s16i, !cir.ptr<!s16i>
+
+// LLVM-LABEL: define {{.*}}i16 @satDec(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[F:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_F:.*]] = load i16, ptr %[[F]], align 2
+// LLVM-NEXT: %[[ADD:.*]] = call i16 @llvm.sadd.sat.i16(i16 %[[LOAD_F]], i16 
-32768)
+// LLVM-NEXT: store i16 %[[ADD]], ptr %[[F]], align 2
+
+_Sat _Fract satDec(_Sat _Fract f) {
+  return f--;
+}
+
+// CIR-LABEL: cir.func {{.*}}@satIncUFract(
+// CIR-NEXT: %[[A:.*]] = cir.alloca "a" align(2) init : !cir.ptr<!u16i>
+// CIR-NEXT: %[[RET:.*]] = cir.alloca "__retval" align(2) : !cir.ptr<!u16i>
+// CIR:      %[[LOAD_A:.*]] = cir.load align(2) %[[A]] : !cir.ptr<!u16i>, !u16i
+// CIR-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i
+// CIR-NEXT: %[[SHIFT_NUM:.*]] = cir.const #cir.int<16> : !s32i
+// CIR-NEXT: %[[SHIFT:.*]] = cir.shift(left, %[[ONE]] : !s32i, %[[SHIFT_NUM]] 
: !s32i) -> !s32i
+// CIR-NEXT: %[[MAX:.*]] = cir.const #cir.int<65535> : !s32i
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp gt %[[SHIFT]], %[[MAX]] : !s32i
+// CIR-NEXT: %[[SELECT:.*]] = cir.select if %[[CMP]] then %[[MAX]] else 
%[[SHIFT]] : (!cir.bool, !s32i, !s32i) -> !s32i
+// CIR-NEXT: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
+// CIR-NEXT: %[[CMP:.*]] = cir.cmp lt %[[SELECT]], %[[ZERO]] : !s32i
+// CIR-NEXT: %[[SELECT2:.*]] = cir.select if %[[CMP]] then %[[ZERO]] else 
%[[SELECT]] : (!cir.bool, !s32i, !s32i) -> !s32i
+// CIR-NEXT: %[[TRUNC:.*]] = cir.cast integral %[[SELECT2]] : !s32i -> !u16i
+// CIR-NEXT: %[[ADD:.*]] = cir.call_llvm_intrinsic "uadd.sat" %[[LOAD_A]], 
%[[TRUNC]] : (!u16i, !u16i) -> !u16i
+// CIR-NEXT: cir.store align(2) %[[ADD]], %[[A]] : !u16i, !cir.ptr<!u16i>
+// CIR-NEXT: cir.store %[[ADD]], %[[RET]] : !u16i, !cir.ptr<!u16i>
+
+// LLVM-LABEL: define {{.*}}i16 @satIncUFract(
+// OGCG-NEXT: entry:
+// LLVM-NEXT: %[[A:.*]] = alloca i16, align 2
+// LLVM:      %[[LOAD_A:.*]] = load i16, ptr %[[A]], align 2
+// LLVM-NEXT: %[[ADD:.*]] = call i16 @llvm.uadd.sat.i16(i16 %[[LOAD_A]], i16 
-1)
+// LLVM-NEXT: store i16 %[[ADD]], ptr %[[A]], align 2
+_Sat unsigned _Fract satIncUFract(_Sat unsigned _Fract a) {
+  return ++a;
+}
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to